diff --git a/Cargo.lock b/Cargo.lock index 7594bdf..5b88abc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,7 @@ name = "tum_mediasite_downloader" version = "0.1.0" dependencies = [ + "clap 2.20.5 (registry+https://github.com/rust-lang/crates.io-index)", "cookie 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "dotenv 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "json 0.11.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -27,6 +28,11 @@ dependencies = [ "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ansi_term" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "antidote" version = "1.0.0" @@ -55,6 +61,21 @@ dependencies = [ "libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "clap" +version = "2.20.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)", + "strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "term_size 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-segmentation 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "vec_map 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "cookie" version = "0.7.0" @@ -444,6 +465,11 @@ dependencies = [ "url 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "strsim" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "tempdir" version = "0.3.5" @@ -452,6 +478,16 @@ dependencies = [ "rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "term_size" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "thread-id" version = "2.0.0" @@ -511,6 +547,16 @@ name = "unicode-normalization" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "unicode-segmentation" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unicode-width" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "url" version = "1.4.0" @@ -534,6 +580,11 @@ name = "utf8-ranges" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "vec_map" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "winapi" version = "0.2.8" @@ -559,10 +610,12 @@ dependencies = [ [metadata] "checksum advapi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e06588080cb19d0acb6739808aafa5f26bfb2ca015b2b6370028b44cf7cb8a9a" "checksum aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ca972c2ea5f742bfce5687b9aef75506a764f61d37f8f649047846a9686ddb66" +"checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6" "checksum antidote 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "34fde25430d87a9388dadbe6e34d7f72a462c8b43ac8d309b42b0a8505d7e2a5" "checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" "checksum bzip2 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e39c71fcff507b547240346a894c5df38e6fd42fb02590a5d5b3f2dae9173ad2" "checksum bzip2-sys 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "98ce3fff84d4e90011f464bbdf48e3428f04270439f703868fd489d2aaedfc30" +"checksum clap 2.20.5 (registry+https://github.com/rust-lang/crates.io-index)" = "7db281b0520e97fbd15cd615dcd8f8bcad0c26f5f7d5effe705f090f39e9a758" "checksum cookie 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac425401c6141c4df1e8b915909d82ab5f135a72e471806cb75ca54c03e0c45d" "checksum core-foundation 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "25bfd746d203017f7d5cbd31ee5d8e17f94b6521c7af77ece6c9e4b2d4b16c67" "checksum core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "065a5d7ffdcbc8fa145d6f0746f3555025b9097a9e9cda59f7467abae670c78d" @@ -611,7 +664,9 @@ dependencies = [ "checksum serde 0.9.10 (registry+https://github.com/rust-lang/crates.io-index)" = "a78def33a828eb05eb7f0167499f19cca368faf27601f6c43bc70316825d9adf" "checksum serde_json 0.9.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6501ac6f8b74f9b1033f7ddf79a08edfa0f58d6f8e3190cb8dc97736afa257a8" "checksum serde_urlencoded 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "aa4cde9c1d41c4852426d097c53b9151c53e314e9c6ec8a7765e083137d45c76" +"checksum strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b4d15c810519a91cf877e7e36e63fe068815c678181439f2f29e2562147c3694" "checksum tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "87974a6f5c1dfb344d733055601650059a3363de2a6104819293baff662132d6" +"checksum term_size 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "07b6c1ac5b3fffd75073276bca1ceed01f67a28537097a2a9539e116e50fb21a" "checksum thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9539db560102d1cef46b8b78ce737ff0bb64e7e18d35b2a5688f7d097d0ff03" "checksum thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "8576dbbfcaef9641452d5cf0df9b0e7eeab7694956dd33bb61515fb8f18cfdd5" "checksum time 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)" = "211b63c112206356ef1ff9b19355f43740fc3f85960c598a93d3a3d3ba7beade" @@ -620,9 +675,12 @@ dependencies = [ "checksum unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "13a5906ca2b98c799f4b1ab4557b76367ebd6ae5ef14930ec841c74aed5f3764" "checksum unicode-bidi 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d3a078ebdd62c0e71a709c3d53d2af693fe09fe93fbff8344aebe289b78f9032" "checksum unicode-normalization 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "e28fa37426fceeb5cf8f41ee273faa7c82c47dc8fba5853402841e665fcd86ff" +"checksum unicode-segmentation 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "18127285758f0e2c6cf325bb3f3d138a12fee27de4f23e146cd6a179f26c2cf3" +"checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f" "checksum url 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f5ba8a749fb4479b043733416c244fa9d1d3af3d7c23804944651c8a448cb87e" "checksum user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ef4711d107b21b410a3a974b1204d9accc8b10dad75d8324b5d755de1617d47" "checksum utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f" +"checksum vec_map 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cac5efe5cb0fa14ec2f84f83c701c562ee63f6dcc680861b21d65c682adfb05f" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" "checksum zip 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "54fa1f34fd0d00e851e31dd84dd89a67e0f279ad898ffaa7176ed5c616c1a457" diff --git a/Cargo.toml b/Cargo.toml index 0ae42b2..26c1ef6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ name = "tum_mediasite_downloader" version = "0.1.0" [dependencies] +clap = "2.20.5" cookie = "0.7.0" dotenv = "0.8.0" json = "0.11.5" diff --git a/src/main.rs b/src/main.rs index 12c3a72..87ce282 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +extern crate clap; extern crate cookie; extern crate dotenv; extern crate json; @@ -7,12 +8,13 @@ extern crate reqwest; extern crate zip; use std::env; -use std::fs::File; -use std::io::{Read, BufReader, Write}; -use std::path::{Path, PathBuf}; use std::collections::HashMap; +use std::fs::File; +use std::io::{self, Read, BufReader, Write}; +use std::path::{Path, PathBuf}; use std::time::{Instant, Duration}; +use clap::{App, Arg}; use json::JsonValue; use reqwest::{Client, Response, RedirectPolicy, RequestBuilder}; use reqwest::header::{Cookie, Charset, SetCookie}; @@ -67,39 +69,68 @@ impl<'a> From<&'a JsonValue> for Presentation { } fn main() { - let catalog_def = DS; + let matches = App::new("TumMediasiteDownloader") + .author("Boris-Chengbiao Zhou ") + .about("Downloads \'catalogs\' from the TUM's Mediasite lecture archive.") + .arg(Arg::with_name("CATALOG_NAME") + .help("name of the catalog e.g. from the URL:\n\ + https://streams.tum.de/Mediasite/Catalog/catalogs/era-2016 -> era-2016\n\ + special cases (WS16/17; login included): DS, EIDI, ERA") + .required(true) + .index(1)) + .arg(Arg::with_name("OUTPUT_DIRECTORY") + .help("where to output the downloaded files") + .required(true) + .index(2)) + .arg(Arg::with_name("username") + .short("u") + .help("username for login; can be omitted if the user from .env should be used") + .requires("password") + .takes_value(true)) + .arg(Arg::with_name("password") + .short("p") + .help("password for login; can be omitted if the user from .env should be used") + .requires("username") + .takes_value(true)) + .get_matches(); - let out_dir = get_output_directory(); + let catalog_def = match matches.value_of("CATALOG_NAME").unwrap() { + "DS" => DS, + "EIDI" => EIDI, + "ERA" => ERA, + n => { + if let Some(username) = matches.value_of("username") { + let password = matches.value_of("password").unwrap(); + (n, Some((username, password))) + } else { + (n, None) + } + } + }; + + let out_dir = Path::new(matches.value_of("OUTPUT_DIRECTORY").unwrap()); + if out_dir.exists() { + assert!(out_dir.is_dir()); + } else { + ::std::fs::create_dir_all(out_dir).expect("Failed to create output directory!"); + } - let default_login = get_default_login(); let (catalog_name, (username, password)) = if let Some((user, pass)) = catalog_def.1 { (catalog_def.0, (user.to_string(), pass.to_string())) } else { - (catalog_def.0, default_login) + (catalog_def.0, get_default_login()) }; println!("Preparing to download catalog \"{}\"!", catalog_name); let auth = get_auth(&username, &password); - // let out_dir = Path::new("/run/media/bobo1239/30E0E835E0E7FECA/downloaded"); - // let out_dir = Path::new("out"); - download_catalog(catalog_name, &out_dir, &auth); -} - -fn get_output_directory() -> PathBuf { - // The first argument is (usually) the executable - let path_str = env::args_os().nth(1).expect("No output directory specified!"); - let path = Path::new(&path_str); - if path.exists() { - assert!(path.is_dir()); - } else { - ::std::fs::create_dir_all(path).expect("Failed to create output directory!"); - } - path.to_owned() + download_catalog(catalog_name, out_dir, &auth); } fn get_default_login() -> (String, String) { - dotenv::dotenv().expect("Missing .env!"); + if dotenv::dotenv().is_err() { + println!("No .env found!"); + } let username = env::var("TUM_USERNAME").expect("Missing TUM_USERNAME environment variable!"); let password = env::var("TUM_PASSWORD").expect("Missing TUM_PASSWORD environment variable!"); @@ -118,6 +149,13 @@ fn get_auth(username: &str, password: &str) -> String { .form(&form_data) }, |res| res.headers().get::().is_some()); + // FIXME: We're somehow only getting "Object moved" instead of the actual response + // => We can't determine if the login was successful + // (we still get a MediasiteAuth cookie that is useless) + // let body = read_response_body(&mut res); + // if body.contains("Unknown username or bad password.") { + // panic!("Unknown username or bad password!"); + // } let set_cookie: &SetCookie = res.headers().get().unwrap(); let cookie = cookie::Cookie::parse(set_cookie.0[0].to_string()) @@ -151,7 +189,9 @@ fn get_catalog_id(name: &str, auth: &str) -> String { let body = read_response_body(&mut res); let prefix = "CatalogId: '"; - let idx = body.find(prefix).expect("Failed to find CatalogId on the catalog page!"); + let idx = body.find(prefix) + .expect("Failed to find CatalogId on the catalog page! Perhaps you got the wrong catalog \ + name or an invalid login?"); let pre_len = prefix.len(); // Assuming all catalog ids follow this pattern! let len = "a6fca0c1-0be4-4e66-83b7-bcdc4eb5e95e".len(); @@ -211,7 +251,7 @@ fn download_presentation(presentation: &Presentation, out_dir: &Path, auth: &str let len = match reader.read(&mut buf) { Ok(0) => break, Ok(len) => len, - Err(ref e) if e.kind() == ::std::io::ErrorKind::Interrupted => continue, + Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue, Err(e) => panic!(e), };