Reformat code according to new RFC style
This commit is contained in:
74
src/main.rs
74
src/main.rs
@@ -26,8 +26,6 @@ use presentation::Presentation;
|
|||||||
// TODO: Old "Publish To Go" packages get sweeped from the server and you have to request a new one;
|
// TODO: Old "Publish To Go" packages get sweeped from the server and you have to request a new one;
|
||||||
// Implement this
|
// Implement this
|
||||||
// TODO: sometimes you need to access the video listing at least once using moodle; emulate this
|
// TODO: sometimes you need to access the video listing at least once using moodle; emulate this
|
||||||
// TODO: Add aliases with semester postfix so other can contribute aliases; move to separate file
|
|
||||||
// split into files in general?
|
|
||||||
|
|
||||||
const MAX_RETRIES: u8 = 10;
|
const MAX_RETRIES: u8 = 10;
|
||||||
|
|
||||||
@@ -54,26 +52,36 @@ fn main() {
|
|||||||
let matches = App::new("TumMediasiteDownloader")
|
let matches = App::new("TumMediasiteDownloader")
|
||||||
.author("Boris-Chengbiao Zhou <bobo1239@web.de>")
|
.author("Boris-Chengbiao Zhou <bobo1239@web.de>")
|
||||||
.about("Downloads \'catalogs\' from the TUM's Mediasite lecture archive.")
|
.about("Downloads \'catalogs\' from the TUM's Mediasite lecture archive.")
|
||||||
.arg(Arg::with_name("CATALOG_NAME")
|
.arg(
|
||||||
.help("name of the catalog e.g. from the URL:\n\
|
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\
|
https://streams.tum.de/Mediasite/Catalog/catalogs/era-2016 -> era-2016\n\
|
||||||
special cases (WS16/17; login included): DS, EIDI, ERA")
|
special cases (WS16/17; login included): DS, EIDI, ERA, ..."
|
||||||
|
)
|
||||||
.required(true)
|
.required(true)
|
||||||
.index(1))
|
.index(1)
|
||||||
.arg(Arg::with_name("OUTPUT_DIRECTORY")
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("OUTPUT_DIRECTORY")
|
||||||
.help("where to output the downloaded files")
|
.help("where to output the downloaded files")
|
||||||
.required(true)
|
.required(true)
|
||||||
.index(2))
|
.index(2)
|
||||||
.arg(Arg::with_name("username")
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("username")
|
||||||
.short("u")
|
.short("u")
|
||||||
.help("username for login; can be omitted if the user from .env should be used",)
|
.help("username for login; can be omitted if the user from .env should be used")
|
||||||
.requires("password")
|
.requires("password")
|
||||||
.takes_value(true))
|
.takes_value(true)
|
||||||
.arg(Arg::with_name("password")
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("password")
|
||||||
.short("p")
|
.short("p")
|
||||||
.help("password for login; can be omitted if the user from .env should be used",)
|
.help("password for login; can be omitted if the user from .env should be used")
|
||||||
.requires("username")
|
.requires("username")
|
||||||
.takes_value(true))
|
.takes_value(true)
|
||||||
|
)
|
||||||
.get_matches();
|
.get_matches();
|
||||||
|
|
||||||
let catalog_name = matches.value_of("CATALOG_NAME").unwrap();
|
let catalog_name = matches.value_of("CATALOG_NAME").unwrap();
|
||||||
@@ -128,12 +136,14 @@ fn get_auth() {
|
|||||||
form_data.insert("UserName", &*username);
|
form_data.insert("UserName", &*username);
|
||||||
form_data.insert("Password", &*password);
|
form_data.insert("Password", &*password);
|
||||||
|
|
||||||
let res = try_to_get_valid_response(|client| {
|
let res = try_to_get_valid_response(
|
||||||
|
|client| {
|
||||||
client
|
client
|
||||||
.post("https://streams.tum.de/Mediasite/Login")
|
.post("https://streams.tum.de/Mediasite/Login")
|
||||||
.form(&form_data)
|
.form(&form_data)
|
||||||
},
|
},
|
||||||
|res| res.headers().get::<SetCookie>().is_some());
|
|res| res.headers().get::<SetCookie>().is_some(),
|
||||||
|
);
|
||||||
// FIXME: We're somehow only getting "302 Object moved" instead of the actual response
|
// FIXME: We're somehow only getting "302 Object moved" instead of the actual response
|
||||||
// => We can't determine if the login was successful
|
// => We can't determine if the login was successful
|
||||||
// (we still get a MediasiteAuth cookie that is useless)
|
// (we still get a MediasiteAuth cookie that is useless)
|
||||||
@@ -156,14 +166,18 @@ fn download_catalog(catalog_name: &str, out_dir: &Path) {
|
|||||||
let catalog_id = get_catalog_id(catalog_name);
|
let catalog_id = get_catalog_id(catalog_name);
|
||||||
let json = get_json(&catalog_id);
|
let json = get_json(&catalog_id);
|
||||||
let presentations = json_to_presentations(&json);
|
let presentations = json_to_presentations(&json);
|
||||||
println!("Starting to download {} presentations!",
|
println!(
|
||||||
presentations.len());
|
"Starting to download {} presentations!",
|
||||||
|
presentations.len()
|
||||||
|
);
|
||||||
|
|
||||||
for (i, presentation) in presentations.iter().enumerate() {
|
for (i, presentation) in presentations.iter().enumerate() {
|
||||||
println!("\nDownloading {}/{}: {}",
|
println!(
|
||||||
|
"\nDownloading {}/{}: {}",
|
||||||
i + 1,
|
i + 1,
|
||||||
presentations.len(),
|
presentations.len(),
|
||||||
presentation.name());
|
presentation.name()
|
||||||
|
);
|
||||||
for _ in 0..MAX_RETRIES {
|
for _ in 0..MAX_RETRIES {
|
||||||
match presentation.download(out_dir) {
|
match presentation.download(out_dir) {
|
||||||
Ok(()) => break,
|
Ok(()) => break,
|
||||||
@@ -189,8 +203,10 @@ fn get_catalog_id(name: &str) -> String {
|
|||||||
|
|
||||||
let prefix = "CatalogId: '";
|
let prefix = "CatalogId: '";
|
||||||
let idx = body.find(prefix)
|
let idx = body.find(prefix)
|
||||||
.expect("Failed to find CatalogId on the catalog page! Perhaps you got the wrong catalog \
|
.expect(
|
||||||
name or an invalid login? Maybe you need to open the page in a browser once...");
|
"Failed to find CatalogId on the catalog page! Perhaps you got the wrong catalog \
|
||||||
|
name or an invalid login? Maybe you need to open the page in a browser once..."
|
||||||
|
);
|
||||||
let pre_len = prefix.len();
|
let pre_len = prefix.len();
|
||||||
// Assuming all catalog ids follow this pattern!
|
// Assuming all catalog ids follow this pattern!
|
||||||
let len = "a6fca0c1-0be4-4e66-83b7-bcdc4eb5e95e".len();
|
let len = "a6fca0c1-0be4-4e66-83b7-bcdc4eb5e95e".len();
|
||||||
@@ -218,12 +234,14 @@ fn get_json(catalog_id: &str) -> String {
|
|||||||
data.insert("CurrentFolderId", catalog_id);
|
data.insert("CurrentFolderId", catalog_id);
|
||||||
data.insert("ItemsPerPage", "500");
|
data.insert("ItemsPerPage", "500");
|
||||||
|
|
||||||
let mut res = try_to_get_response(|client| {
|
let mut res = try_to_get_response(
|
||||||
|
|client| {
|
||||||
client
|
client
|
||||||
.post("https://streams.tum.de/Mediasite/Catalog/Data/GetPresentationsForFolder")
|
.post("https://streams.tum.de/Mediasite/Catalog/Data/GetPresentationsForFolder")
|
||||||
.header(construct_cookie())
|
.header(construct_cookie())
|
||||||
.json(&data)
|
.json(&data)
|
||||||
});
|
}
|
||||||
|
);
|
||||||
read_response_body(&mut res)
|
read_response_body(&mut res)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,14 +250,16 @@ fn construct_cookie() -> Cookie {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn try_to_get_response<F>(f: F) -> Response
|
fn try_to_get_response<F>(f: F) -> Response
|
||||||
where F: Fn(&Client) -> RequestBuilder
|
where
|
||||||
|
F: Fn(&Client) -> RequestBuilder,
|
||||||
{
|
{
|
||||||
try_to_get_valid_response(f, |_| true)
|
try_to_get_valid_response(f, |_| true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_to_get_valid_response<F1, F2>(f1: F1, f2: F2) -> Response
|
fn try_to_get_valid_response<F1, F2>(f1: F1, f2: F2) -> Response
|
||||||
where F1: Fn(&Client) -> RequestBuilder,
|
where
|
||||||
F2: Fn(&Response) -> bool
|
F1: Fn(&Client) -> RequestBuilder,
|
||||||
|
F2: Fn(&Response) -> bool,
|
||||||
{
|
{
|
||||||
for retries in 0..MAX_RETRIES {
|
for retries in 0..MAX_RETRIES {
|
||||||
if retries > 0 {
|
if retries > 0 {
|
||||||
|
@@ -32,10 +32,11 @@ impl Presentation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn download(&self, out_dir: &Path) -> Result<(), DownloadError> {
|
pub(crate) fn download(&self, out_dir: &Path) -> Result<(), DownloadError> {
|
||||||
fn download_to_file_and_check<F: Fn(&Path) -> bool>(response: &mut Response,
|
fn download_to_file_and_check<F: Fn(&Path) -> bool>(
|
||||||
|
response: &mut Response,
|
||||||
out_file: &Path,
|
out_file: &Path,
|
||||||
check: F)
|
check: F,
|
||||||
-> Result<(), io::Error> {
|
) -> Result<(), io::Error> {
|
||||||
println!("Output file: {}", out_file.display());
|
println!("Output file: {}", out_file.display());
|
||||||
if out_file.exists() {
|
if out_file.exists() {
|
||||||
println!("File is present already. Checking validity...");
|
println!("File is present already. Checking validity...");
|
||||||
@@ -56,14 +57,10 @@ impl Presentation {
|
|||||||
|
|
||||||
match *self {
|
match *self {
|
||||||
Presentation::PublishToGo { ref download_url, .. } => {
|
Presentation::PublishToGo { ref download_url, .. } => {
|
||||||
let mut response = try_to_get_valid_response(|client| {
|
let mut response = try_to_get_valid_response(
|
||||||
client
|
|client| client.get(download_url).header(construct_cookie()),
|
||||||
.get(download_url)
|
|resp| resp.headers().get::<ContentDisposition>().is_some(),
|
||||||
.header(construct_cookie())
|
);
|
||||||
},
|
|
||||||
|resp| {
|
|
||||||
resp.headers().get::<ContentDisposition>().is_some()
|
|
||||||
});
|
|
||||||
|
|
||||||
let filename = {
|
let filename = {
|
||||||
let content_disposition =
|
let content_disposition =
|
||||||
@@ -74,8 +71,10 @@ impl Presentation {
|
|||||||
match *param {
|
match *param {
|
||||||
DispositionParam::Filename(ref charset, _, ref vec) => {
|
DispositionParam::Filename(ref charset, _, ref vec) => {
|
||||||
assert_eq!(&Charset::Ext("UTF-8".to_string()), charset);
|
assert_eq!(&Charset::Ext("UTF-8".to_string()), charset);
|
||||||
name = Some(String::from_utf8(vec.to_vec())
|
name = Some(
|
||||||
.expect("Suggested filename isn't valid UTF-8!",));
|
String::from_utf8(vec.to_vec())
|
||||||
|
.expect("Suggested filename isn't valid UTF-8!")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
_ => continue,
|
_ => continue,
|
||||||
}
|
}
|
||||||
@@ -95,22 +94,26 @@ impl Presentation {
|
|||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
let videos = {
|
let videos = {
|
||||||
let payload = format!("{{\"getPlayerOptionsRequest\":{{\
|
let payload = format!(
|
||||||
|
"{{\"getPlayerOptionsRequest\":{{\
|
||||||
\"ResourceId\":\"{}\",\
|
\"ResourceId\":\"{}\",\
|
||||||
\"QueryString\":\"{}\"\
|
\"QueryString\":\"{}\"\
|
||||||
}}}}",
|
}}}}",
|
||||||
resource_id,
|
resource_id,
|
||||||
query_string);
|
query_string
|
||||||
|
);
|
||||||
|
|
||||||
let url = "https://streams.tum.de/Mediasite/PlayerService/\
|
let url = "https://streams.tum.de/Mediasite/PlayerService/\
|
||||||
PlayerService.svc/json/GetPlayerOptions";
|
PlayerService.svc/json/GetPlayerOptions";
|
||||||
let mut res = try_to_get_response(|client| {
|
let mut res = try_to_get_response(
|
||||||
|
|client| {
|
||||||
client
|
client
|
||||||
.post(url)
|
.post(url)
|
||||||
.header(construct_cookie())
|
.header(construct_cookie())
|
||||||
.header(ContentType::json())
|
.header(ContentType::json())
|
||||||
.body(&*payload)
|
.body(&*payload)
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
let json_text = read_response_body(&mut res);
|
let json_text = read_response_body(&mut res);
|
||||||
if json_text.contains("You are not authorized to view the requested content") {
|
if json_text.contains("You are not authorized to view the requested content") {
|
||||||
@@ -127,13 +130,11 @@ impl Presentation {
|
|||||||
vec
|
vec
|
||||||
};
|
};
|
||||||
for (i, download_url) in videos.iter().enumerate() {
|
for (i, download_url) in videos.iter().enumerate() {
|
||||||
let mut response = try_to_get_valid_response(|client| {
|
let mut response =
|
||||||
client.get(download_url).header(construct_cookie())
|
try_to_get_valid_response(
|
||||||
},
|
|client| client.get(download_url).header(construct_cookie()),
|
||||||
|resp| {
|
|resp| resp.status() == &StatusCode::Ok,
|
||||||
resp.status() ==
|
);
|
||||||
&StatusCode::Ok
|
|
||||||
});
|
|
||||||
// TODO: Support formats besides mp4
|
// TODO: Support formats besides mp4
|
||||||
// extract extension from url or somewehre else...
|
// extract extension from url or somewehre else...
|
||||||
let filename = format!("{} - {}.mp4", fix_filename(self.name()), i + 1);
|
let filename = format!("{} - {}.mp4", fix_filename(self.name()), i + 1);
|
||||||
@@ -211,9 +212,11 @@ fn download_to_file(response: &mut Response, path: &Path) -> Result<(), io::Erro
|
|||||||
|
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
if now - last > Duration::from_millis(update_duration_millis) {
|
if now - last > Duration::from_millis(update_duration_millis) {
|
||||||
print!("{:.*} kB/s \r",
|
print!(
|
||||||
|
"{:.*} kB/s \r",
|
||||||
2,
|
2,
|
||||||
done as f64 * 1000.0 / update_duration_millis as f64 / 1024.0);
|
done as f64 * 1000.0 / update_duration_millis as f64 / 1024.0
|
||||||
|
);
|
||||||
::std::io::stdout()
|
::std::io::stdout()
|
||||||
.flush()
|
.flush()
|
||||||
.expect("Failed flushing the terminal!");
|
.expect("Failed flushing the terminal!");
|
||||||
@@ -261,9 +264,11 @@ fn check_video(path: &Path) -> bool {
|
|||||||
match command {
|
match command {
|
||||||
Ok(output) => output.stderr.is_empty(),
|
Ok(output) => output.stderr.is_empty(),
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
println!("Failed to check {} with ffmpeg. Perhaps ffmpeg isn't in your path. \
|
println!(
|
||||||
|
"Failed to check {} with ffmpeg. Perhaps ffmpeg isn't in your path. \
|
||||||
Skipping check...",
|
Skipping check...",
|
||||||
path.display());
|
path.display()
|
||||||
|
);
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user