You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
266 lines
8.8 KiB
266 lines
8.8 KiB
use std::env;
|
|
use std::error::Error;
|
|
use std::fs::{create_dir_all, File, OpenOptions};
|
|
use std::io::{stdout, BufReader, Write};
|
|
use std::path::Path;
|
|
use std::process::exit;
|
|
|
|
use clap::{App, Arg};
|
|
use crossterm::{cursor, QueueableCommand};
|
|
use curl::easy::{Easy, WriteError};
|
|
use serde_json::from_reader;
|
|
use url::Url;
|
|
|
|
use lazy_static::lazy_static;
|
|
|
|
use crate::config::ConfigManager;
|
|
use crate::state::{State, StateManager};
|
|
|
|
const VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION");
|
|
|
|
lazy_static! {
|
|
static ref COMMANDS: Vec<&'static str> = vec!["download", "favorites", "print", "searches"];
|
|
}
|
|
mod config;
|
|
mod model;
|
|
mod state;
|
|
|
|
fn read_animeboxes_backup<P: AsRef<Path>>(
|
|
path: P,
|
|
) -> Result<model::anime_boxes::Backup, Box<dyn Error>> {
|
|
let file = File::open(path)?;
|
|
let reader = BufReader::new(file);
|
|
let result: model::anime_boxes::Backup = from_reader(reader)?;
|
|
Ok(result)
|
|
}
|
|
|
|
fn write_to_file(data: &[u8], path: &Path) -> std::io::Result<usize> {
|
|
if !path.exists() {
|
|
let mut file = File::create(path)?;
|
|
file.write_all(data)?;
|
|
} else {
|
|
let mut file = OpenOptions::new().append(true).open(path)?;
|
|
file.write_all(data)?;
|
|
}
|
|
Ok(data.len())
|
|
}
|
|
|
|
fn curl_write_to_file_function(data: &[u8], path: &Path) -> Result<usize, WriteError> {
|
|
match write_to_file(data, &path) {
|
|
Err(e) => {
|
|
println!("Error writing to {:#?}: {}", &path, e);
|
|
Err(WriteError::Pause)
|
|
}
|
|
Ok(d) => Ok(d),
|
|
}
|
|
}
|
|
|
|
fn calculate_percentage(numerator: f64, denominator: f64) -> f64 {
|
|
if numerator <= 0.0 {
|
|
0.0
|
|
} else {
|
|
(numerator / denominator) * 100.0
|
|
}
|
|
}
|
|
|
|
fn print_progress(data: &[u8]) -> Result<(), Box<dyn Error>> {
|
|
let mut stdout = stdout();
|
|
stdout.queue(cursor::SavePosition)?;
|
|
stdout.write(data)?;
|
|
stdout.queue(cursor::RestorePosition)?;
|
|
stdout.flush()?;
|
|
Ok(())
|
|
}
|
|
|
|
fn download_command(
|
|
backup: model::anime_boxes::Backup,
|
|
config_manager: config::ConfigManager,
|
|
mut state_manager: state::StateManager,
|
|
) {
|
|
let temp_directory = env::current_dir().unwrap().join(
|
|
config_manager
|
|
.config
|
|
.save_directory
|
|
.as_ref()
|
|
.unwrap_or(&String::from("tmp")),
|
|
);
|
|
match create_dir_all(&temp_directory) {
|
|
Err(e) => println!("Failed to create directory {:#?}: {}", &temp_directory, e),
|
|
Ok(_) => (),
|
|
};
|
|
let favorites: Vec<String> = backup
|
|
.favorites
|
|
.iter()
|
|
.map(|f| String::from(&f.file.url))
|
|
.collect();
|
|
let mut favorites_to_download: Vec<String> = Vec::new();
|
|
for favorite in favorites {
|
|
if !state_manager.state.downloaded.contains(&favorite) {
|
|
favorites_to_download.push(favorite);
|
|
}
|
|
}
|
|
let to_download_count = favorites_to_download.len();
|
|
let mut count = 0;
|
|
let mut favorites_downloaded = Vec::new();
|
|
for favorite in favorites_to_download {
|
|
count += 1;
|
|
let mut easy = Easy::new();
|
|
let favorite_url = Url::parse(&favorite);
|
|
match favorite_url {
|
|
Err(e) => {
|
|
println!("Error downloading {}. Not a valid URL. {}", favorite, e);
|
|
continue;
|
|
}
|
|
Ok(url) => {
|
|
let path_segments = url.path_segments().unwrap();
|
|
let filename = path_segments.last().unwrap();
|
|
let target_path = temp_directory.as_path().join(&filename);
|
|
|
|
let progress_count = format!("{:}/{:}", count, to_download_count);
|
|
let progress_filename = String::from(filename);
|
|
|
|
match easy.url(url.as_str()) {
|
|
Err(e) => println!("Error setting download URL {}: {}", &filename, e),
|
|
Ok(_) => (),
|
|
};
|
|
match easy.progress(true) {
|
|
Err(e) => println!("Error setting progress setting: {}", e),
|
|
Ok(_) => (),
|
|
};
|
|
let mut progress_length: usize = 0;
|
|
match easy.progress_function(
|
|
move |total_bytes_to_download,
|
|
bytes_downloaded,
|
|
_total_bytes_to_upload,
|
|
_bytes_uploaded| {
|
|
let percentage =
|
|
calculate_percentage(bytes_downloaded, total_bytes_to_download);
|
|
let mut progress = format!(
|
|
"{}: {:.2}% {}",
|
|
progress_filename, percentage, &progress_count
|
|
);
|
|
progress_length = if progress.len() > progress_length {
|
|
progress.len()
|
|
} else {
|
|
progress_length
|
|
};
|
|
if progress_length > progress.len() {
|
|
progress = format!(
|
|
"{}{}",
|
|
progress,
|
|
" ".repeat(progress_length - progress.len())
|
|
);
|
|
}
|
|
match print_progress(progress.as_bytes()) {
|
|
Err(e) => println!("Error showing downloading progress: {}", e),
|
|
Ok(_) => (),
|
|
};
|
|
true
|
|
},
|
|
) {
|
|
Err(e) => println!("Error showing downloading progress: {}", e),
|
|
Ok(_) => (),
|
|
};
|
|
match easy
|
|
.write_function(move |data| curl_write_to_file_function(data, &target_path))
|
|
{
|
|
Err(e) => println!("Error downloading {}: {}", &favorite, e),
|
|
Ok(_) => favorites_downloaded.push(String::from(&favorite)),
|
|
}
|
|
easy.perform().unwrap();
|
|
}
|
|
}
|
|
}
|
|
let favorites_downloaded_count = favorites_downloaded.len();
|
|
let mut all_downloaded: Vec<String> = Vec::new();
|
|
for downloaded in &state_manager.state.downloaded {
|
|
all_downloaded.push(String::from(downloaded));
|
|
}
|
|
for downloaded in favorites_downloaded {
|
|
println!("{}", downloaded);
|
|
all_downloaded.push(downloaded);
|
|
}
|
|
let all_downloaded_count = all_downloaded.len();
|
|
let new_state = State {
|
|
downloaded: all_downloaded,
|
|
};
|
|
state_manager.save(new_state);
|
|
println!(
|
|
"Downloaded {:#?}/{:#?} images",
|
|
favorites_downloaded_count, to_download_count
|
|
);
|
|
println!("Total Downloaded: {:#?}", all_downloaded_count);
|
|
}
|
|
|
|
fn favorites_command(backup: model::anime_boxes::Backup) {
|
|
let favorites: Vec<String> = backup
|
|
.favorites
|
|
.iter()
|
|
.map(|f| String::from(&f.file.url))
|
|
.collect();
|
|
for favorite in favorites {
|
|
println!("{}", favorite);
|
|
}
|
|
}
|
|
|
|
fn searches_command(backup: model::anime_boxes::Backup) {
|
|
let mut searches: Vec<String> = backup
|
|
.search_history
|
|
.iter()
|
|
.map(|s| String::from(&s.search_text))
|
|
.collect();
|
|
searches.sort();
|
|
for search in searches {
|
|
println!("{}", search);
|
|
}
|
|
}
|
|
|
|
fn main() {
|
|
let matches = App::new("AnimeBoxes Sync")
|
|
.version(VERSION.unwrap_or("UNKNOWN"))
|
|
.author("Drew Short <warrick@sothr.com>")
|
|
.about("Parses AnimeBoxes backup files")
|
|
.arg(
|
|
Arg::with_name("config")
|
|
.short("c")
|
|
.value_name("FILE")
|
|
.help("Set a custom config file")
|
|
.takes_value(true),
|
|
)
|
|
.arg(
|
|
Arg::with_name("INPUT")
|
|
.help("The AnimeBoxes file to process")
|
|
.required(true)
|
|
.index(1),
|
|
)
|
|
.arg(
|
|
Arg::with_name("COMMAND")
|
|
.help("The command to run on the backup")
|
|
.required(true)
|
|
.index(2)
|
|
.possible_values(&COMMANDS),
|
|
)
|
|
.get_matches();
|
|
|
|
let config = matches.value_of("config").unwrap_or("config.json");
|
|
let config_path = env::current_dir().unwrap().join(config);
|
|
let config_manager = ConfigManager::new(config_path.as_path());
|
|
|
|
let state_path = env::current_dir().unwrap().join("state.json");
|
|
let state_manager = StateManager::new(state_path.as_path());
|
|
|
|
let path = matches.value_of("INPUT").unwrap();
|
|
let result = read_animeboxes_backup(path).unwrap();
|
|
|
|
let command = matches.value_of("COMMAND").unwrap();
|
|
match command {
|
|
"download" => download_command(result, config_manager, state_manager),
|
|
"favorites" => favorites_command(result),
|
|
"print" => println!("{:#?}", result),
|
|
"searches" => searches_command(result),
|
|
_ => {
|
|
println!("{} is unrecognized", command);
|
|
exit(1)
|
|
}
|
|
}
|
|
}
|