107 lines
3.7 KiB
Rust
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!(),
|
|
}
|
|
}
|