diff --git a/src/args.rs b/src/args.rs index a21d5c0..f8be709 100644 --- a/src/args.rs +++ b/src/args.rs @@ -27,6 +27,14 @@ pub(crate) struct Opts { #[derive(Serialize, Subcommand)] pub(crate) enum Commands { + /// Assets related commands + #[serde(rename = "assets")] + Assets { + #[command(subcommand)] + #[serde(flatten)] + assets_command: AssetsCommands, + }, + /// People related commands #[serde(rename = "people")] People { @@ -43,6 +51,17 @@ pub(crate) enum Commands { }, } +#[derive(Serialize, Subcommand)] +pub(crate) enum AssetsCommands { + /// List all assets + #[serde(rename = "list")] + List { + /// List only offline assets + #[arg(short, long, action)] + offline: bool, + }, +} + #[derive(Serialize, Subcommand)] pub(crate) enum PeopleCommands { /// Synchronises date of births from a vcard file diff --git a/src/commands/list_assets.rs b/src/commands/list_assets.rs new file mode 100644 index 0000000..c4ce536 --- /dev/null +++ b/src/commands/list_assets.rs @@ -0,0 +1,59 @@ +use crate::{ + utils::assets::{fetch_all_assets, AssetQuery}, + Client, +}; +use color_eyre::eyre::Result; +use tabled::{ + settings::{ + object::{Columns, Object, Rows}, + Format, Style, + }, + Table, Tabled, +}; +#[derive(Tabled)] +struct Asset { + #[tabled(rename = "Path")] + original_file_path: String, + #[tabled(rename = "Is offline")] + is_offline: bool, + #[tabled(rename = "Camera")] + model: String, +} + +pub async fn list_assets(offline: bool, client: &Client) -> Result<()> { + let query = AssetQuery { + is_offline: if offline { Some(true) } else { None }, + with_exif: true, + }; + + let mut assets: Vec<_> = fetch_all_assets(query, client) + .await? + .into_iter() + .map(|asset| Asset { + is_offline: asset.is_offline, + model: asset + .exif_info + .clone() + .and_then(|exif| exif.model) + .unwrap_or(String::from("(Unknown)")), + original_file_path: asset.original_path, + }) + .collect(); + + assets.sort_by_key(|asset| asset.original_file_path.clone()); + + println!( + "{}", + Table::new(assets).with(Style::rounded()).modify( + Columns::single(1).not(Rows::first()), + Format::content(|s| { + match s { + "true" => "Yes".to_string(), + _ => "No".to_string(), + } + }) + ) + ); + + Ok(()) +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 7b2b675..d8684ee 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,3 +1,4 @@ +pub mod list_assets; pub mod missing_date_of_birth; pub mod server_features; pub mod server_version; diff --git a/src/main.rs b/src/main.rs index 617ce42..b592036 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,11 @@ include!(concat!(env!("OUT_DIR"), "/codegen.rs")); use crate::args::Commands; -use args::{PeopleCommands, ServerCommands}; +use args::{AssetsCommands, PeopleCommands, ServerCommands}; use clap::Parser; use color_eyre::eyre::{Result, WrapErr}; use color_eyre::Section; +use commands::list_assets::list_assets; use commands::missing_date_of_birth::missing_date_of_birth; use commands::server_features::server_features; use commands::server_version::server_version; @@ -51,6 +52,9 @@ async fn main() -> Result<()> { validate_client_connection(&client).await?; match &args.command { + Commands::Assets { assets_command } => match assets_command { + AssetsCommands::List { offline } => list_assets(*offline, &client).await, + }, Commands::People { people_command } => match people_command { PeopleCommands::MissingDateOfBirths {} => missing_date_of_birth(&client).await, PeopleCommands::SyncDateOfBirths { vcard: _ } => { diff --git a/src/utils/assets.rs b/src/utils/assets.rs index a1b62eb..f6878c1 100644 --- a/src/utils/assets.rs +++ b/src/utils/assets.rs @@ -2,9 +2,26 @@ use crate::{ types::{AssetResponseDto, MetadataSearchDto}, Client, }; +use chrono::{TimeZone, Utc}; use color_eyre::eyre::Result; +use log::debug; + +#[derive(Debug, Default)] +pub struct AssetQuery { + pub is_offline: Option, + pub with_exif: bool, +} + +pub async fn fetch_all_assets(query: AssetQuery, client: &Client) -> Result> { + debug!("Fetching assets with query {:?}", query); + + // is_offline is ignored, let's fetch trashed assets instead and filter them later + let trashed_after = if query.is_offline == Some(true) { + Some(Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap()) + } else { + None + }; -pub async fn fetch_all_assets(client: &Client) -> Result> { let mut all_assets = Vec::new(); let mut page_number = None; let mut has_next_page = true; @@ -26,7 +43,7 @@ pub async fn fetch_all_assets(client: &Client) -> Result> is_favorite: None, is_motion: None, is_not_in_album: None, - is_offline: None, + is_offline: query.is_offline, is_visible: None, lens_model: None, library_id: None, @@ -43,14 +60,14 @@ pub async fn fetch_all_assets(client: &Client) -> Result> taken_after: None, taken_before: None, thumbnail_path: None, - trashed_after: None, + trashed_after, trashed_before: None, type_: None, updated_after: None, updated_before: None, with_archived: true, with_deleted: None, - with_exif: None, + with_exif: Some(query.with_exif), with_people: None, with_stacked: None, }) @@ -64,5 +81,9 @@ pub async fn fetch_all_assets(client: &Client) -> Result> .map(|page| page.parse::().unwrap()); } + if query.is_offline == Some(true) { + all_assets.retain(|asset| asset.is_offline); + } + Ok(all_assets) }