Last active
January 28, 2020 12:04
-
-
Save estshorter/b20e93d785404947bfc746163c0bed49 to your computer and use it in GitHub Desktop.
Rust script for downloading and applying MusicBee Patches from https://getmusicbee.com/patches/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use std::fs::{self, File}; | |
use std::io::{self, stdout, BufWriter, ErrorKind, Write}; | |
use std::path::PathBuf; | |
use std::process::Command; | |
use std::sync::mpsc; | |
use std::thread; | |
use std::time::Duration; | |
use std::time::SystemTime; | |
use chrono::{DateTime, Local, TimeZone, Utc}; | |
use crossterm::event::{read, Event, KeyEvent}; | |
use filetime::FileTime; | |
use reqwest; | |
use scraper::{Html, Selector}; | |
use zip::read::ZipArchive; | |
fn main() { | |
let last_access_filename = r#"C:\tmp\mb_last_download_datetime.txt"#; | |
let (need_update, timestamp_str) = check_update("MusicBee33_Patched.zip", last_access_filename); | |
if !need_update { | |
println!("No need to download."); | |
timeout(3); | |
return; | |
} | |
println!("Downloading the zip file."); | |
let filepath = r#"C:\tmp\MusicBee_Patched.zip"#; | |
download_file( | |
filepath, | |
"https://getmusicbee.com/patches/MusicBee33_Patched.zip", | |
); | |
Command::new("taskkill") | |
.args(&["/im", "MusicBee.exe"]) | |
.output() | |
.expect("failed to kill MB"); | |
match extract_updated_files(filepath, r#"C:\Program Files (x86)\MusicBee\"#) { | |
0 => println!("All files are up-to-date."), | |
cnt => println!("Update/added {} file(s).", cnt), | |
} | |
write_download_date(×tamp_str, r#"C:\tmp\mb_last_download_datetime.txt"#); | |
timeout(7); | |
Command::new(r#"C:\Program Files (x86)\MusicBee\MusicBee.exe"#) | |
.spawn() | |
.expect("failed to launch MB"); | |
} | |
fn timeout(wait_second: usize) { | |
let (tx, rx) = mpsc::channel(); | |
let tx1 = mpsc::Sender::clone(&tx); | |
thread::spawn(move || { | |
for idx in (0..wait_second).rev() { | |
tx1.send(Some(format!("{}秒後に終了します", idx + 1))) | |
.unwrap(); | |
thread::sleep(Duration::from_secs(1)); | |
} | |
tx1.send(Some(String::from("0秒後に終了します"))).unwrap(); | |
tx1.send(None).unwrap(); | |
}); | |
thread::spawn(move || loop { | |
if let Event::Key(KeyEvent { .. }) = read().unwrap() { | |
tx.send(None).unwrap(); | |
break; | |
} | |
}); | |
for received in rx { | |
match received { | |
Some(msg) => { | |
print!("{}\r", msg); | |
stdout().flush().unwrap(); | |
} | |
None => break, | |
} | |
} | |
} | |
fn write_download_date(timestamp: &str, filepath: &str) { | |
let f = File::create(filepath).expect("Unable to create a timestamp file"); | |
let mut buffer = BufWriter::new(f); | |
buffer | |
.write_all(timestamp.as_bytes()) | |
.expect("Unable to write timestamp"); | |
} | |
fn extract_updated_files(src: &str, dst: &str) -> u32 { | |
let mut extract_cnt = 0; | |
let file = File::open(src).expect("Couldn't open file"); | |
let mut zip = ZipArchive::new(file).unwrap(); | |
for i in 0..zip.len() { | |
if extract_updated_file(i, &mut zip, &dst) == true { | |
extract_cnt += 1; | |
} | |
} | |
extract_cnt | |
} | |
fn extract_updated_file(idx: usize, zip: &mut ZipArchive<fs::File>, dst: &str) -> bool { | |
let mut file_in_zip = zip.by_index(idx).unwrap(); | |
let mut path = PathBuf::new(); | |
path.push(dst); | |
path.push(file_in_zip.name().replace("/", "\\")); | |
// dir | |
if file_in_zip.is_dir() { | |
if !path.exists() { | |
fs::create_dir_all(path).unwrap(); | |
} | |
return false; | |
} | |
// file | |
let modified_exist: DateTime<Local> = if path.exists() { | |
let metadata = fs::metadata(&path).unwrap(); | |
let systime = metadata.modified().unwrap(); | |
systime.into() | |
} else { | |
SystemTime::UNIX_EPOCH.into() | |
}; | |
let lm = file_in_zip.last_modified(); | |
let modified_zip = Utc | |
.ymd(lm.year() as i32, lm.month() as u32, lm.day() as u32) | |
.and_hms(lm.hour() as u32, lm.minute() as u32, lm.second() as u32); | |
let modified_zip: DateTime<Local> = DateTime::from(modified_zip); | |
if modified_zip <= modified_exist { | |
return false; | |
} | |
let mut file = File::create(&path).expect("failed to create file"); | |
io::copy(&mut file_in_zip, &mut file).expect("failed to copy content"); | |
let mtime = FileTime::from_unix_time( | |
modified_zip.timestamp(), | |
modified_zip.timestamp_subsec_nanos(), | |
); | |
filetime::set_file_handle_times(&file, Some(mtime), Some(mtime)).unwrap(); | |
let modified_zip_str = modified_zip.format("%Y/%m/%d %H:%M:%S %Z").to_string(); | |
let modified_exist_str = modified_exist.format("%Y/%m/%d %H:%M:%S %Z").to_string(); | |
println!("{} -> {}", modified_exist_str, modified_zip_str); | |
return true; | |
} | |
fn download_file(filepath: &str, url: &str) { | |
let mut resp = reqwest::blocking::get(url).expect("zip download request failed"); | |
let mut out = File::create(filepath).expect("failed to create a zip file"); | |
io::copy(&mut resp, &mut out).expect("failed to copy zip content"); | |
} | |
fn get_last_access_date(filename: &str) -> String { | |
fs::read_to_string(filename).unwrap_or_else(|error| { | |
if error.kind() == ErrorKind::NotFound { | |
String::from("2020-01-01 00:00") | |
} else { | |
panic!("Problem opening the file: {:?}", filename); | |
} | |
}) | |
} | |
fn check_update(target_filename: &str, last_access_filename: &str) -> (bool, String) { | |
let site_timestamp_str = get_timestamp_from_site(target_filename); | |
let site_timestamp: DateTime<Local> = Local | |
.datetime_from_str(&site_timestamp_str, "%Y-%m-%d %H:%M") | |
.unwrap(); | |
let last_access_date = get_last_access_date(last_access_filename); | |
let last_access_date: DateTime<Local> = Local | |
.datetime_from_str(&last_access_date, "%Y-%m-%d %H:%M") | |
.unwrap(); | |
if site_timestamp > last_access_date { | |
return (true, site_timestamp_str); | |
} | |
return (false, site_timestamp_str); | |
} | |
fn get_timestamp_from_site(target_filename: &str) -> String { | |
let client = reqwest::blocking::Client::builder() | |
.user_agent("Mozilla/5.0") | |
.build() | |
.expect("failed to build client"); | |
let html = client | |
.get("https://getmusicbee.com/patches/") | |
.send() | |
.unwrap() | |
.text() | |
.expect("failed to get web page"); | |
let doc = Html::parse_fragment(&html); | |
let select_str = format!("a[href=\"{}\"]", target_filename); | |
let selector = Selector::parse(&select_str).unwrap(); | |
doc.select(&selector) | |
.next() | |
.unwrap() | |
.parent() | |
.unwrap() | |
.next_sibling() | |
.unwrap() | |
.first_child() | |
.unwrap() | |
.value() | |
.as_text() | |
.unwrap() | |
.to_string() | |
.trim() | |
.to_string() | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment