Merge branch 'scan-libraries' into 'main'
Add a libraries command and scan subcommand See merge request kernald/immich-tools!23
This commit is contained in:
		
						commit
						be4734e5b6
					
				
					 9 changed files with 182 additions and 1 deletions
				
			
		
							
								
								
									
										31
									
								
								src/actions/fetch_all_libraries.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/actions/fetch_all_libraries.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,31 @@
 | 
				
			||||||
 | 
					use color_eyre::eyre::Result;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::{context::Context, models::library::Library};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use super::action::Action;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct FetchAllLibraries {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Action for FetchAllLibraries {
 | 
				
			||||||
 | 
					    type Input = ();
 | 
				
			||||||
 | 
					    type Output = Vec<Library>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn new(_input: Self::Input) -> Self {
 | 
				
			||||||
 | 
					        Self {}
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn describe(&self) -> String {
 | 
				
			||||||
 | 
					        String::from("Fetching all libraries")
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async fn execute(&self, ctx: &Context) -> Result<Self::Output> {
 | 
				
			||||||
 | 
					        Ok(ctx
 | 
				
			||||||
 | 
					            .client
 | 
				
			||||||
 | 
					            .get_all_libraries()
 | 
				
			||||||
 | 
					            .await?
 | 
				
			||||||
 | 
					            .clone()
 | 
				
			||||||
 | 
					            .into_iter()
 | 
				
			||||||
 | 
					            .map(Into::into)
 | 
				
			||||||
 | 
					            .collect())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -2,5 +2,7 @@ pub mod action;
 | 
				
			||||||
pub mod confirm;
 | 
					pub mod confirm;
 | 
				
			||||||
pub mod fetch_album_assets;
 | 
					pub mod fetch_album_assets;
 | 
				
			||||||
pub mod fetch_all_albums;
 | 
					pub mod fetch_all_albums;
 | 
				
			||||||
 | 
					pub mod fetch_all_libraries;
 | 
				
			||||||
pub mod fetch_all_persons;
 | 
					pub mod fetch_all_persons;
 | 
				
			||||||
 | 
					pub mod scan_library;
 | 
				
			||||||
pub mod update_person_date_of_birth;
 | 
					pub mod update_person_date_of_birth;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										27
									
								
								src/actions/scan_library.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/actions/scan_library.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,27 @@
 | 
				
			||||||
 | 
					use color_eyre::eyre::Result;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::{context::Context, models::library::Library};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use super::action::Action;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct ScanLibrary {
 | 
				
			||||||
 | 
					    library: Library,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Action for ScanLibrary {
 | 
				
			||||||
 | 
					    type Input = Library;
 | 
				
			||||||
 | 
					    type Output = ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn new(input: Self::Input) -> Self {
 | 
				
			||||||
 | 
					        Self { library: input }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn describe(&self) -> String {
 | 
				
			||||||
 | 
					        format!("Scanning library {}", self.library.name)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async fn execute(&self, ctx: &Context) -> Result<Self::Output> {
 | 
				
			||||||
 | 
					        ctx.client.scan_library(&self.library.id).await?;
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										15
									
								
								src/args.rs
									
										
									
									
									
								
							
							
						
						
									
										15
									
								
								src/args.rs
									
										
									
									
									
								
							| 
						 | 
					@ -44,6 +44,14 @@ pub(crate) enum Commands {
 | 
				
			||||||
        assets_command: AssetsCommands,
 | 
					        assets_command: AssetsCommands,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Libraries related commands
 | 
				
			||||||
 | 
					    #[serde(rename = "libaries")]
 | 
				
			||||||
 | 
					    Libraries {
 | 
				
			||||||
 | 
					        #[command(subcommand)]
 | 
				
			||||||
 | 
					        #[serde(flatten)]
 | 
				
			||||||
 | 
					        libraries_command: LibrariesCommands,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// People related commands
 | 
					    /// People related commands
 | 
				
			||||||
    #[serde(rename = "people")]
 | 
					    #[serde(rename = "people")]
 | 
				
			||||||
    People {
 | 
					    People {
 | 
				
			||||||
| 
						 | 
					@ -78,6 +86,13 @@ pub(crate) enum AssetsCommands {
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Serialize, Subcommand)]
 | 
				
			||||||
 | 
					pub(crate) enum LibrariesCommands {
 | 
				
			||||||
 | 
					    /// Scan all libraries
 | 
				
			||||||
 | 
					    #[serde(rename = "scan")]
 | 
				
			||||||
 | 
					    Scan {},
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[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
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,7 @@
 | 
				
			||||||
pub mod delete_assets;
 | 
					pub mod delete_assets;
 | 
				
			||||||
pub mod list_assets;
 | 
					pub mod list_assets;
 | 
				
			||||||
pub mod missing_date_of_birth;
 | 
					pub mod missing_date_of_birth;
 | 
				
			||||||
 | 
					pub mod scan_libraries;
 | 
				
			||||||
pub mod server_features;
 | 
					pub mod server_features;
 | 
				
			||||||
pub mod server_version;
 | 
					pub mod server_version;
 | 
				
			||||||
pub mod sync_date_of_birth;
 | 
					pub mod sync_date_of_birth;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										17
									
								
								src/commands/scan_libraries.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/commands/scan_libraries.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,17 @@
 | 
				
			||||||
 | 
					use color_eyre::eyre::Result;
 | 
				
			||||||
 | 
					use log::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::actions::action::Action;
 | 
				
			||||||
 | 
					use crate::actions::scan_library::ScanLibrary;
 | 
				
			||||||
 | 
					use crate::{actions::fetch_all_libraries::FetchAllLibraries, context::Context};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub async fn scan_libraries(ctx: Context) -> Result<()> {
 | 
				
			||||||
 | 
					    let libraries = FetchAllLibraries::new(()).execute(&ctx).await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for library in libraries {
 | 
				
			||||||
 | 
					        debug!("Scanning {}", library.name);
 | 
				
			||||||
 | 
					        ScanLibrary::new(library).execute(&ctx).await?;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok(())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -9,13 +9,14 @@ mod tests {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::args::Commands;
 | 
					use crate::args::Commands;
 | 
				
			||||||
use args::{AssetsCommands, PeopleCommands, ServerCommands};
 | 
					use args::{AssetsCommands, LibrariesCommands, 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::delete_assets::delete_assets;
 | 
					use commands::delete_assets::delete_assets;
 | 
				
			||||||
use commands::list_assets::list_assets;
 | 
					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::scan_libraries::scan_libraries;
 | 
				
			||||||
use commands::server_features::server_features;
 | 
					use commands::server_features::server_features;
 | 
				
			||||||
use commands::server_version::server_version;
 | 
					use commands::server_version::server_version;
 | 
				
			||||||
use commands::sync_date_of_birth::sync_date_of_birth;
 | 
					use commands::sync_date_of_birth::sync_date_of_birth;
 | 
				
			||||||
| 
						 | 
					@ -79,6 +80,9 @@ async fn main() -> Result<()> {
 | 
				
			||||||
            AssetsCommands::Delete { offline } => delete_assets(ctx, *offline).await,
 | 
					            AssetsCommands::Delete { offline } => delete_assets(ctx, *offline).await,
 | 
				
			||||||
            AssetsCommands::List { offline } => list_assets(ctx, *offline).await,
 | 
					            AssetsCommands::List { offline } => list_assets(ctx, *offline).await,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        Commands::Libraries { libraries_command } => match libraries_command {
 | 
				
			||||||
 | 
					            LibrariesCommands::Scan {} => scan_libraries(ctx).await,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        Commands::People { people_command } => match people_command {
 | 
					        Commands::People { people_command } => match people_command {
 | 
				
			||||||
            PeopleCommands::MissingDateOfBirths {} => missing_date_of_birth(ctx).await,
 | 
					            PeopleCommands::MissingDateOfBirths {} => missing_date_of_birth(ctx).await,
 | 
				
			||||||
            PeopleCommands::SyncDateOfBirths { vcard: _ } => {
 | 
					            PeopleCommands::SyncDateOfBirths { vcard: _ } => {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										83
									
								
								src/models/library.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								src/models/library.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,83 @@
 | 
				
			||||||
 | 
					use chrono::{DateTime, Utc};
 | 
				
			||||||
 | 
					use uuid::Uuid;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::types::LibraryResponseDto;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone)]
 | 
				
			||||||
 | 
					pub struct Library {
 | 
				
			||||||
 | 
					    pub id: Uuid,
 | 
				
			||||||
 | 
					    pub name: String,
 | 
				
			||||||
 | 
					    pub asset_count: i64,
 | 
				
			||||||
 | 
					    pub updated_at: DateTime<Utc>,
 | 
				
			||||||
 | 
					    pub refreshed_at: Option<DateTime<Utc>>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl From<LibraryResponseDto> for Library {
 | 
				
			||||||
 | 
					    fn from(value: LibraryResponseDto) -> Self {
 | 
				
			||||||
 | 
					        Self {
 | 
				
			||||||
 | 
					            id: Uuid::parse_str(&value.id).expect("Unable to parse a library's UUID"),
 | 
				
			||||||
 | 
					            name: value.name,
 | 
				
			||||||
 | 
					            asset_count: value.asset_count,
 | 
				
			||||||
 | 
					            updated_at: value.updated_at,
 | 
				
			||||||
 | 
					            refreshed_at: value.refreshed_at,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[cfg(test)]
 | 
				
			||||||
 | 
					mod tests {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    use std::str::FromStr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    use super::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn from_library_response_dto_success() {
 | 
				
			||||||
 | 
					        let dto = LibraryResponseDto {
 | 
				
			||||||
 | 
					            id: String::from("123e4567-e89b-12d3-a456-426655440000"),
 | 
				
			||||||
 | 
					            name: String::from("Christmas photos"),
 | 
				
			||||||
 | 
					            asset_count: 1246,
 | 
				
			||||||
 | 
					            exclusion_patterns: vec![],
 | 
				
			||||||
 | 
					            import_paths: vec![],
 | 
				
			||||||
 | 
					            owner_id: String::from("abc123"),
 | 
				
			||||||
 | 
					            updated_at: DateTime::<Utc>::from_str("2024-11-17T12:55:12Z").unwrap(),
 | 
				
			||||||
 | 
					            created_at: DateTime::<Utc>::from_str("2023-10-17T12:55:12Z").unwrap(),
 | 
				
			||||||
 | 
					            refreshed_at: Some(DateTime::<Utc>::from_str("2024-11-17T12:53:12Z").unwrap()),
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let library = Library::from(dto);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert_eq!(
 | 
				
			||||||
 | 
					            library.id,
 | 
				
			||||||
 | 
					            Uuid::parse_str("123e4567-e89b-12d3-a456-426655440000").unwrap()
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        assert_eq!(library.name, "Christmas photos".to_string());
 | 
				
			||||||
 | 
					        assert_eq!(library.asset_count, 1246);
 | 
				
			||||||
 | 
					        assert_eq!(
 | 
				
			||||||
 | 
					            library.updated_at,
 | 
				
			||||||
 | 
					            DateTime::<Utc>::from_str("2024-11-17T12:55:12Z").unwrap()
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        assert_eq!(
 | 
				
			||||||
 | 
					            library.refreshed_at,
 | 
				
			||||||
 | 
					            Some(DateTime::<Utc>::from_str("2024-11-17T12:53:12Z").unwrap())
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    #[should_panic]
 | 
				
			||||||
 | 
					    fn from_library_response_dto_invalid_uuid_panics() {
 | 
				
			||||||
 | 
					        let dto = LibraryResponseDto {
 | 
				
			||||||
 | 
					            id: String::from("invalid_uuid"),
 | 
				
			||||||
 | 
					            name: String::from("Christmas photos"),
 | 
				
			||||||
 | 
					            asset_count: 1246,
 | 
				
			||||||
 | 
					            exclusion_patterns: vec![],
 | 
				
			||||||
 | 
					            import_paths: vec![],
 | 
				
			||||||
 | 
					            owner_id: String::from("abc123"),
 | 
				
			||||||
 | 
					            updated_at: DateTime::<Utc>::from_str("2024-11-17T12:55:12Z").unwrap(),
 | 
				
			||||||
 | 
					            created_at: DateTime::<Utc>::from_str("2023-10-17T12:55:12Z").unwrap(),
 | 
				
			||||||
 | 
					            refreshed_at: Some(DateTime::<Utc>::from_str("2024-11-17T12:53:12Z").unwrap()),
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let _ = Library::from(dto);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,3 +1,4 @@
 | 
				
			||||||
pub mod album;
 | 
					pub mod album;
 | 
				
			||||||
pub mod asset;
 | 
					pub mod asset;
 | 
				
			||||||
 | 
					pub mod library;
 | 
				
			||||||
pub mod person;
 | 
					pub mod person;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue