immich-tools/src/commands/sync_date_of_birth.rs

107 lines
3.7 KiB
Rust

use std::{fs, path::PathBuf};
use chrono::{Datelike, NaiveDate};
use color_eyre::eyre::{Context as _, ContextCompat, Result};
use log::*;
use vcard4::{
parse_loose,
property::{DateAndOrTime, DateTimeOrTextProperty},
};
use crate::{
actions::{
action::Action,
fetch_all_persons::FetchAllPersons,
update_person_date_of_birth::{UpdatePersonDateOfBirth, UpdatePersonDateOfBirthArgs},
},
context::Context,
};
pub async fn sync_date_of_birth(ctx: Context, vcard_file: &PathBuf) -> Result<()> {
let fetch_action = FetchAllPersons::new(());
let server_contacts = fetch_action.execute(&ctx).await?;
let filtered_contacts: Vec<_> = server_contacts
.iter()
.filter(|p| !p.name.is_empty())
.collect();
let contacts = fs::read_to_string(vcard_file).wrap_err_with(|| {
format!(
"Unable to read vcard file at {}",
vcard_file.to_string_lossy()
)
})?;
let cards = parse_loose(contacts)?;
let cards_with_bday: Vec<_> = cards.iter().filter(|c| c.bday.is_some()).collect();
info!(
"{} vcards with date of births, {} persons with names fetched from Immich",
cards_with_bday.len(),
filtered_contacts.len()
);
let mut updated_dobs = 0;
for card in cards_with_bday {
let name = card.formatted_name.first().map(|text| text.value.clone());
match name {
Some(formatted_name) => {
let contact = filtered_contacts.iter().find(|c| c.name == formatted_name);
match contact {
Some(c) => match card.bday.as_ref().unwrap() {
DateTimeOrTextProperty::DateTime(dt) => {
let bday = vcard_date_to_naive_date(dt.value.first().unwrap().clone())?;
if c.date_of_birth
.as_ref()
.map_or(false, |bdate| bday == *bdate)
{
debug!(
"{} already has the proper date of birth, skipping",
formatted_name
);
} else if bday.year() > 0 {
UpdatePersonDateOfBirth::new(UpdatePersonDateOfBirthArgs {
person: (*c).clone(),
date_of_birth: bday,
})
.execute(&ctx)
.await?;
updated_dobs += 1;
} else {
debug!(
"{} has an incomplete date of birth, skipping",
formatted_name
);
}
}
DateTimeOrTextProperty::Text(_) => todo!(),
},
None => debug!("No Immich match for {}", formatted_name),
}
}
None => todo!(),
}
}
info!("Updated {} persons", updated_dobs);
Ok(())
}
fn vcard_date_to_naive_date(src: DateAndOrTime) -> Result<NaiveDate> {
match src {
DateAndOrTime::Date(date) => {
NaiveDate::from_ymd_opt(date.year(), date.month() as u32, date.day().into())
.wrap_err("Unable to parse a date")
}
DateAndOrTime::DateTime(datetime) => NaiveDate::from_ymd_opt(
datetime.year(),
datetime.month() as u32,
datetime.day().into(),
)
.wrap_err("Unable to parse a datetime"),
DateAndOrTime::Time(_time) => todo!(),
}
}