Implement ServeAndSync.

This commit is contained in:
Bill Thiede 2020-03-10 18:38:45 -07:00
parent 89037b6b24
commit a96fe1da9d
2 changed files with 100 additions and 27 deletions

View File

@ -2,12 +2,14 @@ use std::collections::HashMap;
use std::error::Error; use std::error::Error;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::path::PathBuf; use std::path::PathBuf;
use std::thread;
use std::time;
use google_api_auth; use google_api_auth;
use google_photoslibrary1 as photos; use google_photoslibrary1 as photos;
use hexihasher; use hexihasher;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use log::{debug, info}; use log::{debug, error, info};
use photos::schemas::{Album, MediaItem, SearchMediaItemsRequest}; use photos::schemas::{Album, MediaItem, SearchMediaItemsRequest};
use regex::Regex; use regex::Regex;
use structopt::StructOpt; use structopt::StructOpt;
@ -16,6 +18,31 @@ use yup_oauth2::{Authenticator, InstalledFlow};
use photosync::library::Library; use photosync::library::Library;
use photosync::web; use photosync::web;
fn parse_duration(src: &str) -> Result<time::Duration, std::num::ParseIntError> {
let secs = str::parse::<u64>(src)?;
Ok(time::Duration::from_secs(secs))
}
#[derive(Debug, StructOpt)]
struct Sync {
#[structopt(flatten)]
auth: Auth,
/// Optional album title to filter. Default will mirror all albums.
#[structopt(short, long)]
title_filter: Option<Regex>,
/// Directory to store sync.
root: PathBuf,
}
#[derive(Debug, StructOpt)]
struct Serve {
/// Directory of data fetched by `sync`.
root: PathBuf,
/// HTTP address to listen for web requests.
#[structopt(long = "addr", default_value = "0.0.0.0:0")]
addr: SocketAddr,
}
#[derive(Debug, StructOpt)] #[derive(Debug, StructOpt)]
enum Command { enum Command {
/// List albums for the user of the given credentials. Optionally title filter. /// List albums for the user of the given credentials. Optionally title filter.
@ -31,16 +58,19 @@ enum Command {
}, },
Sync { Sync {
#[structopt(flatten)] #[structopt(flatten)]
auth: Auth, sync: Sync,
/// Optional album title to filter. Default will mirror all albums.
#[structopt(short, long)]
title_filter: Option<Regex>,
/// Directory to store sync.
output: PathBuf,
}, },
Serve { Serve {
/// Directory of data fetched by `sync`. #[structopt(flatten)]
root: PathBuf, serve: Serve,
},
ServeAndSync {
/// Sync albums at given interval.
#[structopt(parse(try_from_str = parse_duration))]
interval: time::Duration,
#[structopt(flatten)]
sync: Sync,
/// HTTP address to listen for web requests. /// HTTP address to listen for web requests.
#[structopt(long = "addr", default_value = "0.0.0.0:0")] #[structopt(long = "addr", default_value = "0.0.0.0:0")]
addr: SocketAddr, addr: SocketAddr,
@ -192,10 +222,9 @@ lazy_static! {
fn sync_albums( fn sync_albums(
client: &photos::Client, client: &photos::Client,
title_filter: Option<Regex>, title_filter: &Option<Regex>,
output_dir: PathBuf, lib: &Library,
) -> Result<(), Box<dyn Error>> { ) -> Result<(), Box<dyn Error>> {
let lib = Library::new(output_dir)?;
let albums = list_albums(client, title_filter)?; let albums = list_albums(client, title_filter)?;
info!("albums {:?}", albums); info!("albums {:?}", albums);
lib.create_album_index(&albums)?; lib.create_album_index(&albums)?;
@ -236,12 +265,18 @@ fn print_albums(albums: Vec<Album>) {
fn list_albums( fn list_albums(
client: &photos::Client, client: &photos::Client,
title_filter: Option<Regex>, title_filter: &Option<Regex>,
) -> Result<Vec<Album>, Box<dyn Error>> { ) -> Result<Vec<Album>, Box<dyn Error>> {
Ok(client Ok(client
.shared_albums() .albums()
.list() .list()
.iter_shared_albums_with_all_fields() .iter_albums_with_all_fields()
.chain(
client
.shared_albums()
.list()
.iter_shared_albums_with_all_fields(),
)
.filter_map(|a| a.ok()) .filter_map(|a| a.ok())
.filter(|a| { .filter(|a| {
match (&title_filter, &a.title) { match (&title_filter, &a.title) {
@ -258,8 +293,23 @@ fn list_albums(
.collect()) .collect())
} }
pub fn serve(addr: SocketAddr, root: PathBuf) -> Result<(), Box<dyn Error>> { fn background_sync(
web::run(addr, root) client: photos::Client,
interval: time::Duration,
title_filter: Option<Regex>,
lib: Library,
) -> Result<(), Box<dyn Error>> {
thread::spawn(move || loop {
if let Err(err) = sync_albums(&client, &title_filter, &lib) {
error!("Error syncing: {}", err);
}
thread::sleep(interval);
});
Ok(())
}
pub fn serve(addr: SocketAddr, lib: Library) -> Result<(), Box<dyn Error>> {
web::run(addr, lib)
} }
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
@ -273,7 +323,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
match opt.cmd { match opt.cmd {
Command::ListAlbums { auth, title_filter } => { Command::ListAlbums { auth, title_filter } => {
let client = new_client(&auth.credentials, &auth.token_cache)?; let client = new_client(&auth.credentials, &auth.token_cache)?;
print_albums(list_albums(&client, title_filter)?); print_albums(list_albums(&client, &title_filter)?);
Ok(()) Ok(())
} }
Command::SearchMediaItems { auth, album_id } => { Command::SearchMediaItems { auth, album_id } => {
@ -282,14 +332,39 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
Ok(()) Ok(())
} }
Command::Sync { Command::Sync {
auth, sync:
title_filter, Sync {
output, auth,
title_filter,
root,
},
} => { } => {
let client = new_client(&auth.credentials, &auth.token_cache)?; let client = new_client(&auth.credentials, &auth.token_cache)?;
sync_albums(&client, title_filter, output)?; let lib = Library::new(root)?;
sync_albums(&client, &title_filter, &lib)?;
Ok(())
}
Command::Serve {
serve: Serve { addr, root },
} => {
let lib = Library::new(root)?;
serve(addr, lib)
}
Command::ServeAndSync {
interval,
sync:
Sync {
auth,
title_filter,
root,
},
addr,
} => {
let client = new_client(&auth.credentials, &auth.token_cache)?;
let lib = Library::new(root)?;
background_sync(client, interval, title_filter, lib.clone())?;
serve(addr, lib)?;
Ok(()) Ok(())
} }
Command::Serve { addr, root } => serve(addr, root),
} }
} }

View File

@ -1,6 +1,5 @@
use std::error::Error; use std::error::Error;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::path::PathBuf;
use log::warn; use log::warn;
use prometheus::Encoder; use prometheus::Encoder;
@ -98,11 +97,10 @@ fn image(
} }
#[derive(RustEmbed)] #[derive(RustEmbed)]
#[folder = "react-debug/build/"] #[folder = "react-slideshow/build/"]
struct Asset; struct Asset;
pub fn run(addr: SocketAddr, root: PathBuf) -> Result<(), Box<dyn Error>> { pub fn run(addr: SocketAddr, lib: Library) -> Result<(), Box<dyn Error>> {
let lib = Library::new(root)?;
let lib = warp::any().map(move || lib.clone()); let lib = warp::any().map(move || lib.clone());
let index = warp::get2().and(warp::path::full()).and_then(index); let index = warp::get2().and(warp::path::full()).and_then(index);