A tool to read animebox backup files and export the data in alternate formats.
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.

211 lines
7.0 KiB

4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
  1. use std::env;
  2. use std::error::Error;
  3. use std::fs::{create_dir_all, File, OpenOptions};
  4. use std::io::{stdout, BufReader, Write};
  5. use std::path::Path;
  6. use std::process::exit;
  7. use clap::{App, Arg};
  8. use curl::easy::{Easy, WriteError};
  9. use serde_json::from_reader;
  10. use url::Url;
  11. use crate::config::{Config, ConfigManager};
  12. use crossterm::{cursor, QueueableCommand};
  13. use lazy_static::lazy_static;
  14. const VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION");
  15. lazy_static! {
  16. static ref COMMANDS: Vec<&'static str> = vec!["download", "favorites", "print", "searches"];
  17. }
  18. mod config;
  19. mod model;
  20. fn read_animeboxes_backup<P: AsRef<Path>>(
  21. path: P,
  22. ) -> Result<model::anime_boxes::Backup, Box<dyn Error>> {
  23. let file = File::open(path)?;
  24. let reader = BufReader::new(file);
  25. let result: model::anime_boxes::Backup = from_reader(reader)?;
  26. Ok(result)
  27. }
  28. fn write_to_file(data: &[u8], path: &Path) -> std::io::Result<()> {
  29. if !path.exists() {
  30. let mut file = File::create(path)?;
  31. file.write_all(data)?;
  32. } else {
  33. let mut file = OpenOptions::new().append(true).open(path)?;
  34. file.write_all(data)?;
  35. }
  36. Ok(())
  37. }
  38. fn download_command(backup: model::anime_boxes::Backup, mut config: config::ConfigManager) {
  39. let temp_directory = env::current_dir().unwrap().join(
  40. config
  41. .config
  42. .save_directory
  43. .as_ref()
  44. .unwrap_or(&String::from("tmp")),
  45. );
  46. create_dir_all(&temp_directory);
  47. let favorites: Vec<String> = backup
  48. .favorites
  49. .iter()
  50. .map(|f| String::from(&f.file.url))
  51. .collect();
  52. let mut favorites_to_download: Vec<String> = Vec::new();
  53. for favorite in favorites {
  54. if !config.config.downloaded.contains(&favorite) {
  55. favorites_to_download.push(favorite);
  56. }
  57. }
  58. let to_download_count = favorites_to_download.len();
  59. let mut count = 0;
  60. let mut favorites_downloaded = Vec::new();
  61. for favorite in favorites_to_download {
  62. count += 1;
  63. let mut easy = Easy::new();
  64. let favorite_url = Url::parse(&favorite);
  65. match favorite_url {
  66. Err(e) => {
  67. println!("Error downloading {}. Not a valid URL", favorite);
  68. continue;
  69. }
  70. Ok(url) => {
  71. let path_segments = url.path_segments().unwrap();
  72. let filename = path_segments.last().unwrap();
  73. let target_path = temp_directory.as_path().join(&filename);
  74. let progress_count = format!("{}/{}", count, to_download_count);
  75. let progress_filename = String::from(filename);
  76. easy.url(url.as_str());
  77. easy.progress(true);
  78. easy.progress_function(
  79. move |total_bytes_to_download,
  80. bytes_downloaded,
  81. total_bytes_to_upload,
  82. bytes_uploaded| {
  83. let percentage = (bytes_downloaded / total_bytes_to_download) * 100.0;
  84. let mut stdout = stdout();
  85. stdout.queue(cursor::SavePosition);
  86. stdout.write(
  87. format!(
  88. " {} {}: {:.2}",
  89. &progress_count, progress_filename, percentage
  90. )
  91. .as_bytes(),
  92. );
  93. stdout.queue(cursor::RestorePosition);
  94. stdout.flush();
  95. true
  96. },
  97. );
  98. match easy.write_function(move |data| match write_to_file(data, &target_path) {
  99. Err(e) => {
  100. println!("Error writing to {:#?}: {}%", target_path, e);
  101. Err(WriteError::Pause)
  102. }
  103. Ok(d) => Ok(data.len()),
  104. }) {
  105. Err(e) => {
  106. println!("Error downloading {}", &favorite);
  107. }
  108. Ok(_) => favorites_downloaded.push(String::from(&favorite)),
  109. }
  110. easy.perform().unwrap();
  111. }
  112. }
  113. }
  114. let favorites_downloaded_count = favorites_downloaded.len();
  115. let mut all_downloaded: Vec<String> = Vec::new();
  116. for downloaded in &config.config.downloaded {
  117. all_downloaded.push(String::from(downloaded));
  118. }
  119. for downloaded in favorites_downloaded {
  120. println!("{}", downloaded);
  121. all_downloaded.push(downloaded);
  122. }
  123. let all_downloaded_count = all_downloaded.len();
  124. let new_config = Config {
  125. save_directory: Some(String::from(config.config.save_directory.as_ref().unwrap())),
  126. downloaded: all_downloaded,
  127. };
  128. config.save(new_config);
  129. println!(
  130. "Downloaded {:#?}/{:#?} images",
  131. favorites_downloaded_count, all_downloaded_count
  132. )
  133. }
  134. fn favorites_command(backup: model::anime_boxes::Backup) {
  135. let favorites: Vec<String> = backup
  136. .favorites
  137. .iter()
  138. .map(|f| String::from(&f.file.url))
  139. .collect();
  140. for favorite in favorites {
  141. println!("{}", favorite);
  142. }
  143. }
  144. fn searches_command(backup: model::anime_boxes::Backup) {
  145. let mut searches: Vec<String> = backup
  146. .search_history
  147. .iter()
  148. .map(|s| String::from(&s.search_text))
  149. .collect();
  150. searches.sort();
  151. for search in searches {
  152. println!("{}", search);
  153. }
  154. }
  155. fn main() {
  156. let matches = App::new("AnimeBoxes Sync")
  157. .version(VERSION.unwrap_or("UNKNOWN"))
  158. .author("Drew Short <warrick@sothr.com>")
  159. .about("Parses AnimeBoxes backup files")
  160. .arg(
  161. Arg::with_name("config")
  162. .short("c")
  163. .value_name("FILE")
  164. .help("Set a custom config file")
  165. .takes_value(true),
  166. )
  167. .arg(
  168. Arg::with_name("INPUT")
  169. .help("The AnimeBoxes file to process")
  170. .required(true)
  171. .index(1),
  172. )
  173. .arg(
  174. Arg::with_name("COMMAND")
  175. .help("The command to run on the backup")
  176. .required(true)
  177. .index(2)
  178. .possible_values(&COMMANDS),
  179. )
  180. .get_matches();
  181. let config = matches.value_of("config").unwrap_or("config.json");
  182. let config_path = env::current_dir().unwrap().join(config);
  183. let mut config_manager = ConfigManager::new(config_path.as_path());
  184. let path = matches.value_of("INPUT").unwrap();
  185. let result = read_animeboxes_backup(path).unwrap();
  186. let command = matches.value_of("COMMAND").unwrap();
  187. match command {
  188. "download" => download_command(result, config_manager),
  189. "favorites" => favorites_command(result),
  190. "print" => println!("{:#?}", result),
  191. "searches" => searches_command(result),
  192. _ => {
  193. println!("{} is unrecognized", command);
  194. exit(1)
  195. }
  196. }
  197. }