121 lines
3.7 KiB
Rust
121 lines
3.7 KiB
Rust
use std::{fs, path::PathBuf};
|
|
|
|
use anyhow::Result;
|
|
use chrono::{Datelike, NaiveDate};
|
|
use log::*;
|
|
use uuid::Uuid;
|
|
use vcard4::{
|
|
parse_loose,
|
|
property::{DateAndOrTime, DateTimeOrTextProperty},
|
|
};
|
|
|
|
use crate::{
|
|
types::{PersonResponseDto, PersonUpdateDto},
|
|
utils::people::fetch_all_contacts,
|
|
Client,
|
|
};
|
|
|
|
pub async fn sync_date_of_birth(
|
|
client: &Client,
|
|
vcard_file: &PathBuf,
|
|
dry_run: bool,
|
|
) -> Result<()> {
|
|
let server_contacts = fetch_all_contacts(client).await?;
|
|
|
|
let filtered_contacts: Vec<_> = server_contacts
|
|
.iter()
|
|
.filter(|p| !p.name.is_empty())
|
|
.collect();
|
|
|
|
let contacts = fs::read_to_string(vcard_file)?;
|
|
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.birth_date.as_ref().map_or(false, |bdate| bday == *bdate) {
|
|
debug!(
|
|
"{} already has the proper date of birth, skipping",
|
|
formatted_name
|
|
);
|
|
} else if bday.year() > 0 {
|
|
update_person_bday(c, bday, client, dry_run).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) => {
|
|
Ok(
|
|
NaiveDate::from_ymd_opt(date.year(), date.month() as u32, date.day().into())
|
|
.expect(&format!("Unable to parse {}", date)),
|
|
)
|
|
}
|
|
DateAndOrTime::DateTime(datetime) => Ok(NaiveDate::from_ymd_opt(
|
|
datetime.year(),
|
|
datetime.month() as u32,
|
|
datetime.day().into(),
|
|
)
|
|
.expect(&format!("Unable to parse {}", datetime))),
|
|
DateAndOrTime::Time(_time) => todo!(),
|
|
}
|
|
}
|
|
|
|
async fn update_person_bday(
|
|
person: &PersonResponseDto,
|
|
bday: NaiveDate,
|
|
client: &Client,
|
|
dry_run: bool,
|
|
) -> Result<()> {
|
|
let update = PersonUpdateDto {
|
|
feature_face_asset_id: None,
|
|
is_hidden: None,
|
|
name: None,
|
|
birth_date: Some(bday),
|
|
};
|
|
|
|
info!("Updating {}: {:?}", person.name, update);
|
|
|
|
if !dry_run {
|
|
client
|
|
.update_person(&Uuid::parse_str(&person.id)?, &update)
|
|
.await?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|