Add support for a configuration file

This commit is contained in:
Marc Plano-Lesay 2024-10-30 17:46:14 +11:00
parent a34dbb7fd3
commit b9664b43c8
10 changed files with 505 additions and 63 deletions

View file

@ -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 {},
}

View file

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

View file

@ -1,4 +1,4 @@
use anyhow::Result;
use color_eyre::eyre::Result;
use crate::Client;

View file

@ -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
View 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,
}

View file

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

View file

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