Merge branch 'assets-list' into 'main'

Add an assets list command

See merge request kernald/immich-tools!14
This commit is contained in:
Marc Plano-Lesay 2024-11-05 02:42:22 +00:00
commit af5990796e
5 changed files with 109 additions and 5 deletions

View file

@ -27,6 +27,14 @@ pub(crate) struct Opts {
#[derive(Serialize, Subcommand)] #[derive(Serialize, Subcommand)]
pub(crate) enum Commands { pub(crate) enum Commands {
/// Assets related commands
#[serde(rename = "assets")]
Assets {
#[command(subcommand)]
#[serde(flatten)]
assets_command: AssetsCommands,
},
/// People related commands /// People related commands
#[serde(rename = "people")] #[serde(rename = "people")]
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)] #[derive(Serialize, Subcommand)]
pub(crate) enum PeopleCommands { pub(crate) enum PeopleCommands {
/// Synchronises date of births from a vcard file /// Synchronises date of births from a vcard file

View file

@ -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(())
}

View file

@ -1,3 +1,4 @@
pub mod list_assets;
pub mod missing_date_of_birth; pub mod missing_date_of_birth;
pub mod server_features; pub mod server_features;
pub mod server_version; pub mod server_version;

View file

@ -1,10 +1,11 @@
include!(concat!(env!("OUT_DIR"), "/codegen.rs")); include!(concat!(env!("OUT_DIR"), "/codegen.rs"));
use crate::args::Commands; use crate::args::Commands;
use args::{PeopleCommands, ServerCommands}; use args::{AssetsCommands, PeopleCommands, ServerCommands};
use clap::Parser; use clap::Parser;
use color_eyre::eyre::{Result, WrapErr}; use color_eyre::eyre::{Result, WrapErr};
use color_eyre::Section; use color_eyre::Section;
use commands::list_assets::list_assets;
use commands::missing_date_of_birth::missing_date_of_birth; use commands::missing_date_of_birth::missing_date_of_birth;
use commands::server_features::server_features; use commands::server_features::server_features;
use commands::server_version::server_version; use commands::server_version::server_version;
@ -52,6 +53,9 @@ async fn main() -> Result<()> {
validate_client_connection(&client).await?; validate_client_connection(&client).await?;
match &args.command { 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 { Commands::People { people_command } => match people_command {
PeopleCommands::MissingDateOfBirths {} => missing_date_of_birth(&client).await, PeopleCommands::MissingDateOfBirths {} => missing_date_of_birth(&client).await,
PeopleCommands::SyncDateOfBirths { vcard: _ } => { PeopleCommands::SyncDateOfBirths { vcard: _ } => {

View file

@ -2,9 +2,26 @@ use crate::{
types::{AssetResponseDto, MetadataSearchDto}, types::{AssetResponseDto, MetadataSearchDto},
Client, Client,
}; };
use chrono::{TimeZone, Utc};
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use log::debug;
#[derive(Debug, Default)]
pub struct AssetQuery {
pub is_offline: Option<bool>,
pub with_exif: bool,
}
pub async fn fetch_all_assets(query: AssetQuery, client: &Client) -> Result<Vec<AssetResponseDto>> {
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<Vec<AssetResponseDto>> {
let mut all_assets = Vec::new(); let mut all_assets = Vec::new();
let mut page_number = None; let mut page_number = None;
let mut has_next_page = true; let mut has_next_page = true;
@ -26,7 +43,7 @@ pub async fn fetch_all_assets(client: &Client) -> Result<Vec<AssetResponseDto>>
is_favorite: None, is_favorite: None,
is_motion: None, is_motion: None,
is_not_in_album: None, is_not_in_album: None,
is_offline: None, is_offline: query.is_offline,
is_visible: None, is_visible: None,
lens_model: None, lens_model: None,
library_id: None, library_id: None,
@ -43,14 +60,14 @@ pub async fn fetch_all_assets(client: &Client) -> Result<Vec<AssetResponseDto>>
taken_after: None, taken_after: None,
taken_before: None, taken_before: None,
thumbnail_path: None, thumbnail_path: None,
trashed_after: None, trashed_after,
trashed_before: None, trashed_before: None,
type_: None, type_: None,
updated_after: None, updated_after: None,
updated_before: None, updated_before: None,
with_archived: true, with_archived: true,
with_deleted: None, with_deleted: None,
with_exif: None, with_exif: Some(query.with_exif),
with_people: None, with_people: None,
with_stacked: None, with_stacked: None,
}) })
@ -64,5 +81,9 @@ pub async fn fetch_all_assets(client: &Client) -> Result<Vec<AssetResponseDto>>
.map(|page| page.parse::<f64>().unwrap()); .map(|page| page.parse::<f64>().unwrap());
} }
if query.is_offline == Some(true) {
all_assets.retain(|asset| asset.is_offline);
}
Ok(all_assets) Ok(all_assets)
} }