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