diff --git a/Cargo.lock b/Cargo.lock index d588b14..e7d1951 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -32,12 +32,12 @@ checksum = "7825f6833612eb2414095684fcf6c635becf3ce97fe48cf6421321e93bfbd53c" [[package]] name = "atty" -version = "0.2.14" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +checksum = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" dependencies = [ - "hermit-abi", "libc", + "termion", "winapi 0.3.8", ] @@ -217,7 +217,7 @@ dependencies = [ "autocfg 0.1.7", "cfg-if", "crossbeam-utils 0.7.0", - "lazy_static", + "lazy_static 1.4.0", "memoffset", "scopeguard", ] @@ -238,7 +238,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6" dependencies = [ "cfg-if", - "lazy_static", + "lazy_static 1.4.0", ] [[package]] @@ -249,7 +249,7 @@ checksum = "ce446db02cdc3165b94ae73111e570793400d0794e46125cc4056c81cbb039f4" dependencies = [ "autocfg 0.1.7", "cfg-if", - "lazy_static", + "lazy_static 1.4.0", ] [[package]] @@ -598,6 +598,12 @@ dependencies = [ "winapi-build", ] +[[package]] +name = "lazy_static" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" + [[package]] name = "lazy_static" version = "1.4.0" @@ -781,6 +787,12 @@ dependencies = [ "libc", ] +[[package]] +name = "numtoa" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" + [[package]] name = "parking_lot" version = "0.9.0" @@ -825,7 +837,10 @@ version = "0.1.0" dependencies = [ "google-photoslibrary1", "google_api_auth", + "log 0.4.8", "regex", + "serde_json", + "stderrlog", "structopt", "yup-oauth2", ] @@ -873,7 +888,7 @@ checksum = "3bbaa49075179162b49acac1c6aa45fb4dafb5f13cf6794276d77bc7fd95757b" dependencies = [ "error-chain", "idna 0.2.0", - "lazy_static", + "lazy_static 1.4.0", "regex", "url 2.1.1", ] @@ -1008,6 +1023,15 @@ version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" +[[package]] +name = "redox_termios" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" +dependencies = [ + "redox_syscall", +] + [[package]] name = "regex" version = "1.3.4" @@ -1017,7 +1041,7 @@ dependencies = [ "aho-corasick", "memchr", "regex-syntax", - "thread_local", + "thread_local 1.0.1", ] [[package]] @@ -1069,7 +1093,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "113f53b644c5442e20ff3a299be3d6c61ba143737af5bd2ab298e248a7575b2d" dependencies = [ "cc", - "lazy_static", + "lazy_static 1.4.0", "libc", "spin", "untrusted", @@ -1240,6 +1264,19 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "stderrlog" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e5ee9b90a5452c570a0b0ac1c99ae9498db7e56e33d74366de7f2a7add7f25" +dependencies = [ + "atty", + "chrono", + "log 0.4.8", + "termcolor", + "thread_local 0.3.4", +] + [[package]] name = "string" version = "0.2.1" @@ -1262,7 +1299,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1bcbed7d48956fcbb5d80c6b95aedb553513de0a1b451ea92679d999c010e98" dependencies = [ "clap", - "lazy_static", + "lazy_static 1.4.0", "structopt-derive", ] @@ -1313,6 +1350,27 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "termcolor" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "termion" +version = "1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c22cec9d8978d906be5ac94bceb5a010d885c626c4c8855721a4dbd20e3ac905" +dependencies = [ + "libc", + "numtoa", + "redox_syscall", + "redox_termios", +] + [[package]] name = "textnonce" version = "0.6.5" @@ -1336,13 +1394,23 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "thread_local" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1697c4b57aeeb7a536b647165a2825faddffb1d3bad386d507709bd51a90bb14" +dependencies = [ + "lazy_static 0.2.11", + "unreachable", +] + [[package]] name = "thread_local" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" dependencies = [ - "lazy_static", + "lazy_static 1.4.0", ] [[package]] @@ -1452,7 +1520,7 @@ checksum = "6732fe6b53c8d11178dcb77ac6d9682af27fc6d4cb87789449152e5377377146" dependencies = [ "crossbeam-utils 0.6.6", "futures", - "lazy_static", + "lazy_static 1.4.0", "log 0.4.8", "mio", "num_cpus", @@ -1511,7 +1579,7 @@ dependencies = [ "crossbeam-queue", "crossbeam-utils 0.6.6", "futures", - "lazy_static", + "lazy_static 1.4.0", "log 0.4.8", "num_cpus", "slab", @@ -1623,6 +1691,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" +[[package]] +name = "unreachable" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" +dependencies = [ + "void", +] + [[package]] name = "untrusted" version = "0.7.0" @@ -1678,6 +1755,12 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + [[package]] name = "want" version = "0.2.0" @@ -1706,7 +1789,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11cdb95816290b525b32587d76419facd99662a07e59d3cdb560488a819d9a45" dependencies = [ "bumpalo", - "lazy_static", + "lazy_static 1.4.0", "log 0.4.8", "proc-macro2", "quote", @@ -1828,6 +1911,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ccfbf554c6ad11084fb7517daca16cfdcaccbdadba4fc336f032a8b12c2ad80" +dependencies = [ + "winapi 0.3.8", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index f4c29a4..5958848 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,3 +13,6 @@ google_api_auth = { git = "https://github.com/google-apis-rs/generator", feature google-photoslibrary1 = { path = "../google-api-photoslibrary" } structopt = "0.3.9" regex = "1.3.4" +log = "0.4.8" +stderrlog = "0.4.3" +serde_json = "1.0.46" diff --git a/src/main.rs b/src/main.rs index a1a9b7a..282169b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,13 @@ use std::error::Error; +use std::fs; use std::path::PathBuf; use google_api_auth; use google_photoslibrary1; +use google_photoslibrary1::schemas::Album; use google_photoslibrary1::schemas::SearchMediaItemsRequest; +use log::debug; +use log::info; use regex::Regex; use structopt::StructOpt; use yup_oauth2::{Authenticator, InstalledFlow}; @@ -17,6 +21,13 @@ enum Command { SearchMediaItems { album_id: String, }, + Sync { + /// Optional album title to filter. Default will mirror all albums. + #[structopt(short, long)] + title_filter: Option, + /// Directory to store sync. + output: PathBuf, + }, } #[derive(Debug, StructOpt)] @@ -27,7 +38,7 @@ enum Command { struct Opt { /// Activate debug mode #[structopt(short, parse(from_occurrences))] - verbose: u32, + verbose: usize, /// Path to json file containing Google client ID and secrets for out of band auth flow. #[structopt(long)] @@ -58,7 +69,7 @@ fn new_client( .build() .unwrap(); - let scopes = vec!["https://www.googleapis.com/auth/photoslibrary.readonly".to_string()]; + let scopes = vec![google_photoslibrary1::scopes::PHOTOSLIBRARY_READONLY]; let auth = google_api_auth::yup_oauth2::from_authenticator(auth, scopes); @@ -101,26 +112,30 @@ fn search_media_items( } } -fn list_albums( +fn sync_albums( client: google_photoslibrary1::Client, title_filter: Option, + output_dir: PathBuf, ) -> Result<(), Box> { - for album in client - .shared_albums() - .list() - .iter_shared_albums_with_all_fields() - { - let a = album?; - match (&title_filter, &a.title) { - // Print everything when no filter or title. - (None, None) => {} - // skip when filter given but the media item doesn't have a title (it can't match) - (_, None) => continue, - // skip when the media item doesn't match the filter - (Some(title_filter), Some(title)) if !title_filter.is_match(&title) => continue, - // handle everything else - _ => {} + let albums = list_albums(client, title_filter)?; + for a in &albums { + let album_dir = output_dir.join(a.id.as_ref().expect("missing album id")); + if !album_dir.exists() { + info!("making album directory {}", album_dir.to_string_lossy()); + fs::create_dir_all(album_dir)?; } + } + // Serialize it to a JSON string. + let j = serde_json::to_string(&albums)?; + + let path = output_dir.join("albums.json"); + info!("saving {}", path.to_string_lossy()); + fs::write(path, j)?; + Ok(()) +} + +fn print_albums(albums: Vec) { + for a in albums { println!( "album: {} {} ({} items)", a.id.unwrap_or("NO ID".to_string()), @@ -128,15 +143,50 @@ fn list_albums( a.media_items_count.unwrap_or(0) ); } - Ok(()) +} + +fn list_albums( + client: google_photoslibrary1::Client, + title_filter: Option, +) -> Result, Box> { + Ok(client + .shared_albums() + .list() + .iter_shared_albums_with_all_fields() + .filter_map(|a| a.ok()) + .filter(|a| { + match (&title_filter, &a.title) { + // keep everything when no filter or title. + (None, None) => true, + // skip when filter given but the media item doesn't have a title (it can't match) + (_, None) => false, + // skip when the media item doesn't match the filter + (Some(title_filter), Some(title)) if !title_filter.is_match(&title) => false, + // keep everything else + _ => true, + } + }) + .collect()) } fn main() -> Result<(), Box> { let opt = Opt::from_args(); - println!("opt: {:?}", opt); + stderrlog::new() + .module(module_path!()) + .verbosity(opt.verbose) + .init() + .unwrap(); + debug!("opt: {:?}", opt); let client = new_client(&opt.credentials, &opt.token_cache)?; match opt.cmd { - Command::ListAlbums { title_filter } => list_albums(client, title_filter), + Command::ListAlbums { title_filter } => { + print_albums(list_albums(client, title_filter)?); + Ok(()) + } Command::SearchMediaItems { album_id } => search_media_items(client, album_id), + Command::Sync { + title_filter, + output, + } => sync_albums(client, title_filter, output), } }