use std::fs::DirEntry; use std::{fs, io}; use std::path::PathBuf; use lazy_static::lazy_static; use regex::Regex; fn home_dir() -> PathBuf { dirs::home_dir().expect("Couldn't find home dir") } fn documents_dir() -> PathBuf { dirs::document_dir().expect("Couldn't find downloads dir") } fn downloads_dir() -> PathBuf { dirs::download_dir().expect("Couldn't find downloads dir") } fn pictures_dir() -> PathBuf { dirs::picture_dir().expect("Couldn't find pictures dir") } fn strip_null_bytes(str: &str) -> String { str.replace('\0', "") } /// e.g. for ~/Music/ABBA/ABBA Gold: /// `create_dir_if_not_exists(vec!["Music", "ABBA", "ABBA Gold"])` fn create_dir_if_not_exists(path: Vec<&str>) -> PathBuf { let mut dir = home_dir(); for item in path { dir.push(item); } std::fs::create_dir_all(&dir).unwrap_or(()); dir } fn move_file(file: PathBuf, mut destination: PathBuf) { let file_name = file.file_name().expect("No file name").to_str().unwrap(); println!("Moving {:?} to {:?}", file_name, destination); destination.push(file_name); match fs_extra::file::move_file(file, destination, &Default::default()) { Ok(_) => println!("OK!"), Err(e) => println!("Couldn't move file: {:?}", e), } } fn yes() -> bool { let mut answer = String::new(); io::stdin().read_line(&mut answer) .expect("Failed to read input"); answer.to_lowercase().starts_with("y") } fn handle_image(file: DirEntry) { move_file(file.path(), pictures_dir()); } fn handle_gif(file: DirEntry) { let mut dir = pictures_dir(); dir.push("Internet"); dir.push("GIFs"); move_file(file.path(), dir); } fn handle_mp3(file: DirEntry) { let meta = mp3_metadata::read_from_file(file.path()); if meta.is_err() { println!("Couldn't read ID3 metadata for file {:?}", file.path()); } if let Some(tag) = meta.unwrap().tag { println!("----------------------"); println!("file: {:?}", file.path()); println!("artist: {}", tag.artist.trim()); println!("album: {}", tag.album.trim()); println!("title: {}", tag.title.trim()); println!("move to: ~/Music/{}/{}/?", tag.artist.trim(), tag.album.trim()); if yes() { move_file( file.path(), create_dir_if_not_exists( vec![ "Music", strip_null_bytes(&tag.artist).trim(), strip_null_bytes(&tag.album).trim() ] ) ); } else { println!("skipping..."); } } } fn handle_pdf(file: DirEntry) { let mut dir = documents_dir(); dir.push("PDFs"); move_file(file.path(), dir); } /// if filename contains something like `(1971)` or `[2003]` or `.1985.` it's a movie fn is_movie(file: &DirEntry) -> bool { lazy_static! { static ref MOVIE: Regex = Regex::new(r"\.\d{4}\.|\(\d{4}\)|\[\d{4}]").unwrap(); } MOVIE.is_match(file.file_name().to_str().unwrap()) } /// if filename contains something like "S03E21" it's a TV programme fn is_tv_episode(file: &DirEntry) -> bool { lazy_static! { static ref TV: Regex = Regex::new(r"[sS]\d{2}\s*[eE][pP]?\d{2}").unwrap(); } TV.is_match(file.file_name().to_str().unwrap()) } fn handle_video(file: DirEntry) { let mut destination = vec!["Videos"]; if is_tv_episode(&file) { destination.push("TV"); } else if is_movie(&file) { // todo match decade? destination.push("Movies"); } println!( "Move to ~/{}/{}?", destination.join("/"), file.file_name().to_str().unwrap() ); if yes() { move_file(file.path(), create_dir_if_not_exists(destination)); } } fn handle_dir(path: PathBuf) { let mut dir = std::fs::read_dir(&path).expect("Couldn't read dir"); if dir.next().is_none() { println!("Deleting empty dir: {:?}", path); fs::remove_dir(path).expect("Couldn't delete dir") } else { for inode in dir { let inode = inode.expect("Couldn't read inode"); // recursively handle directories if inode.metadata().unwrap().is_dir() { handle_dir(inode.path()); } if let Some(extension) = inode.path().extension().unwrap_or("none".as_ref()).to_str() { match extension.to_string().to_lowercase().as_ref() { "gif" => { handle_gif( inode) } "jpg" => { handle_image(inode) } "jpeg" => { handle_image(inode) } "png" => { handle_image(inode) } "mp3" => { handle_mp3( inode) } "pdf" => { handle_pdf( inode) } "avi" => { handle_video(inode) } "m4v" => { handle_video(inode) } "mkv" => { handle_video(inode) } "mp4" => { handle_video(inode) } // todo m4a, flac, etc? // todo pdf, other documents _ => { /*println!("Here's where we would do nothing.");*/ } } } } } } fn main() { handle_dir(downloads_dir()); }