Add support for a configuration file
This commit is contained in:
		
							parent
							
								
									a34dbb7fd3
								
							
						
					
					
						commit
						b9664b43c8
					
				
					 10 changed files with 505 additions and 63 deletions
				
			
		
							
								
								
									
										29
									
								
								src/args.rs
									
										
									
									
									
								
							
							
						
						
									
										29
									
								
								src/args.rs
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -1,51 +1,66 @@
 | 
			
		|||
use std::path::PathBuf;
 | 
			
		||||
 | 
			
		||||
use clap::{command, Parser, Subcommand};
 | 
			
		||||
use serde::Serialize;
 | 
			
		||||
 | 
			
		||||
#[derive(Parser)]
 | 
			
		||||
#[derive(Parser, Serialize)]
 | 
			
		||||
#[command(version, about, long_about = None)]
 | 
			
		||||
pub(crate) struct Opts {
 | 
			
		||||
    /// The Immich API URL - it should probably end in "/api"
 | 
			
		||||
    #[arg(short, long)]
 | 
			
		||||
    pub server_url: String,
 | 
			
		||||
    #[serde(skip_serializing_if = "::std::option::Option::is_none")]
 | 
			
		||||
    pub server_url: Option<String>,
 | 
			
		||||
 | 
			
		||||
    /// The Immich API key
 | 
			
		||||
    #[arg(short, long)]
 | 
			
		||||
    pub api_key: String,
 | 
			
		||||
    #[serde(skip_serializing_if = "::std::option::Option::is_none")]
 | 
			
		||||
    pub api_key: Option<String>,
 | 
			
		||||
 | 
			
		||||
    /// If enabled, actions that would have been performed are only logged
 | 
			
		||||
    #[arg(short, long)]
 | 
			
		||||
    pub dry_run: bool,
 | 
			
		||||
 | 
			
		||||
    #[command(subcommand)]
 | 
			
		||||
    #[serde(flatten)]
 | 
			
		||||
    pub command: Commands,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Subcommand)]
 | 
			
		||||
#[derive(Serialize, Subcommand)]
 | 
			
		||||
pub(crate) enum Commands {
 | 
			
		||||
    /// People related commands
 | 
			
		||||
    #[serde(rename = "people")]
 | 
			
		||||
    People {
 | 
			
		||||
        #[command(subcommand)]
 | 
			
		||||
        #[serde(flatten)]
 | 
			
		||||
        people_command: PeopleCommands,
 | 
			
		||||
    },
 | 
			
		||||
    /// Server related commands
 | 
			
		||||
    #[serde(rename = "server")]
 | 
			
		||||
    Server {
 | 
			
		||||
        #[command(subcommand)]
 | 
			
		||||
        #[serde(flatten)]
 | 
			
		||||
        server_command: ServerCommands,
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Subcommand)]
 | 
			
		||||
#[derive(Serialize, Subcommand)]
 | 
			
		||||
pub(crate) enum PeopleCommands {
 | 
			
		||||
    /// Synchronises date of births from a vcard file
 | 
			
		||||
    #[serde(rename = "sync_date_of_births")]
 | 
			
		||||
    SyncDateOfBirths {
 | 
			
		||||
        #[arg(short, long)]
 | 
			
		||||
        vcard_file: PathBuf,
 | 
			
		||||
        #[serde(skip_serializing_if = "::std::option::Option::is_none")]
 | 
			
		||||
        vcard: Option<PathBuf>,
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    /// Lists the people without date of birth
 | 
			
		||||
    #[serde(rename = "missing_date_of_births")]
 | 
			
		||||
    MissingDateOfBirths {},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Subcommand)]
 | 
			
		||||
#[derive(Serialize, Subcommand)]
 | 
			
		||||
pub(crate) enum ServerCommands {
 | 
			
		||||
    /// Fetches the version of the server
 | 
			
		||||
    #[serde(rename = "version")]
 | 
			
		||||
    Version {},
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
use crate::{utils::people::fetch_all_contacts, Client};
 | 
			
		||||
use anyhow::Result;
 | 
			
		||||
use color_eyre::eyre::Result;
 | 
			
		||||
use log::*;
 | 
			
		||||
 | 
			
		||||
pub async fn missing_date_of_birth(client: &Client) -> Result<()> {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,4 @@
 | 
			
		|||
use anyhow::Result;
 | 
			
		||||
use color_eyre::eyre::Result;
 | 
			
		||||
 | 
			
		||||
use crate::Client;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,7 @@
 | 
			
		|||
use std::{fs, path::PathBuf};
 | 
			
		||||
 | 
			
		||||
use anyhow::Result;
 | 
			
		||||
use chrono::{Datelike, NaiveDate};
 | 
			
		||||
use color_eyre::eyre::{ContextCompat, Result};
 | 
			
		||||
use log::*;
 | 
			
		||||
use uuid::Uuid;
 | 
			
		||||
use vcard4::{
 | 
			
		||||
| 
						 | 
				
			
			@ -81,17 +81,15 @@ pub async fn sync_date_of_birth(
 | 
			
		|||
fn vcard_date_to_naive_date(src: DateAndOrTime) -> Result<NaiveDate> {
 | 
			
		||||
    match src {
 | 
			
		||||
        DateAndOrTime::Date(date) => {
 | 
			
		||||
            Ok(
 | 
			
		||||
                NaiveDate::from_ymd_opt(date.year(), date.month() as u32, date.day().into())
 | 
			
		||||
                    .expect(&format!("Unable to parse {}", date)),
 | 
			
		||||
            )
 | 
			
		||||
            NaiveDate::from_ymd_opt(date.year(), date.month() as u32, date.day().into())
 | 
			
		||||
                .wrap_err("Unable to parse a date")
 | 
			
		||||
        }
 | 
			
		||||
        DateAndOrTime::DateTime(datetime) => Ok(NaiveDate::from_ymd_opt(
 | 
			
		||||
        DateAndOrTime::DateTime(datetime) => NaiveDate::from_ymd_opt(
 | 
			
		||||
            datetime.year(),
 | 
			
		||||
            datetime.month() as u32,
 | 
			
		||||
            datetime.day().into(),
 | 
			
		||||
        )
 | 
			
		||||
        .expect(&format!("Unable to parse {}", datetime))),
 | 
			
		||||
        .wrap_err("Unable to parse a datetime"),
 | 
			
		||||
        DateAndOrTime::Time(_time) => todo!(),
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										21
									
								
								src/config.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/config.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,21 @@
 | 
			
		|||
use std::path::PathBuf;
 | 
			
		||||
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
 | 
			
		||||
#[derive(Deserialize, Serialize)]
 | 
			
		||||
pub(crate) struct Config {
 | 
			
		||||
    pub(crate) server_url: String,
 | 
			
		||||
    pub(crate) api_key: String,
 | 
			
		||||
 | 
			
		||||
    pub(crate) people: People,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Deserialize, Serialize)]
 | 
			
		||||
pub(crate) struct People {
 | 
			
		||||
    pub(crate) sync_date_of_births: SyncDateOfBirths,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Deserialize, Serialize)]
 | 
			
		||||
pub(crate) struct SyncDateOfBirths {
 | 
			
		||||
    pub(crate) vcard: PathBuf,
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										47
									
								
								src/main.rs
									
										
									
									
									
								
							
							
						
						
									
										47
									
								
								src/main.rs
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -1,41 +1,66 @@
 | 
			
		|||
include!(concat!(env!("OUT_DIR"), "/codegen.rs"));
 | 
			
		||||
 | 
			
		||||
use crate::args::Commands;
 | 
			
		||||
use anyhow::Result;
 | 
			
		||||
use args::{PeopleCommands, ServerCommands};
 | 
			
		||||
use clap::Parser;
 | 
			
		||||
use color_eyre::eyre::Result;
 | 
			
		||||
use commands::missing_date_of_birth::missing_date_of_birth;
 | 
			
		||||
use commands::server_version::server_version;
 | 
			
		||||
use commands::sync_date_of_birth::sync_date_of_birth;
 | 
			
		||||
use config::Config;
 | 
			
		||||
use directories::ProjectDirs;
 | 
			
		||||
use figment::providers::{Env, Format, Serialized, Toml};
 | 
			
		||||
use figment::Figment;
 | 
			
		||||
use figment_file_provider_adapter::FileAdapter;
 | 
			
		||||
use log::*;
 | 
			
		||||
use reqwest::header;
 | 
			
		||||
 | 
			
		||||
mod args;
 | 
			
		||||
mod commands;
 | 
			
		||||
mod config;
 | 
			
		||||
mod utils;
 | 
			
		||||
 | 
			
		||||
#[tokio::main]
 | 
			
		||||
async fn main() {
 | 
			
		||||
async fn main() -> Result<()> {
 | 
			
		||||
    color_eyre::install()?;
 | 
			
		||||
    pretty_env_logger::init();
 | 
			
		||||
 | 
			
		||||
    let mut conf_extractor = Figment::new();
 | 
			
		||||
    if let Some(project_dirs) = ProjectDirs::from("fr", "enoent", "Immich Tools") {
 | 
			
		||||
        let config_file_path = project_dirs.config_dir().join("config.toml");
 | 
			
		||||
        if config_file_path.exists() {
 | 
			
		||||
            debug!("Reading configuration from {:?}", config_file_path);
 | 
			
		||||
            conf_extractor =
 | 
			
		||||
                conf_extractor.merge(FileAdapter::wrap(Toml::file_exact(config_file_path)));
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        warn!("Unable to determine configuration file path");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let args = args::Opts::parse();
 | 
			
		||||
 | 
			
		||||
    let client = get_client(&args.server_url, &args.api_key).unwrap();
 | 
			
		||||
    let conf: Config = conf_extractor
 | 
			
		||||
        .merge(FileAdapter::wrap(Env::prefixed("IMMICH_TOOLS_")))
 | 
			
		||||
        .merge(Serialized::defaults(&args))
 | 
			
		||||
        .extract()?;
 | 
			
		||||
 | 
			
		||||
    let res = match &args.command {
 | 
			
		||||
    let client = get_client(&conf.server_url, &conf.api_key)?;
 | 
			
		||||
 | 
			
		||||
    match &args.command {
 | 
			
		||||
        Commands::People { people_command } => match people_command {
 | 
			
		||||
            PeopleCommands::MissingDateOfBirths {} => missing_date_of_birth(&client).await,
 | 
			
		||||
            PeopleCommands::SyncDateOfBirths { vcard_file } => {
 | 
			
		||||
                sync_date_of_birth(&client, vcard_file, args.dry_run).await
 | 
			
		||||
            PeopleCommands::SyncDateOfBirths { vcard: _ } => {
 | 
			
		||||
                sync_date_of_birth(
 | 
			
		||||
                    &client,
 | 
			
		||||
                    &conf.people.sync_date_of_births.vcard,
 | 
			
		||||
                    args.dry_run,
 | 
			
		||||
                )
 | 
			
		||||
                .await
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        Commands::Server { server_command } => match server_command {
 | 
			
		||||
            ServerCommands::Version {} => server_version(&client).await,
 | 
			
		||||
        },
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    match res {
 | 
			
		||||
        Ok(_) => {}
 | 
			
		||||
        Err(e) => println!("Error: {:?}", e),
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
use crate::{types::PersonResponseDto, Client};
 | 
			
		||||
use anyhow::Result;
 | 
			
		||||
use color_eyre::eyre::Result;
 | 
			
		||||
 | 
			
		||||
pub async fn fetch_all_contacts(client: &Client) -> Result<Vec<PersonResponseDto>> {
 | 
			
		||||
    let mut all_people = Vec::new();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue