Add a command to synchronise date of births
This commit is contained in:
parent
5eb1e3bba4
commit
bc09979a62
6 changed files with 392 additions and 5 deletions
|
@ -1 +1,2 @@
|
|||
pub mod server_version;
|
||||
pub mod sync_date_of_birth;
|
||||
|
|
137
src/commands/sync_date_of_birth.rs
Normal file
137
src/commands/sync_date_of_birth.rs
Normal file
|
@ -0,0 +1,137 @@
|
|||
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},
|
||||
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(())
|
||||
}
|
||||
|
||||
async fn fetch_all_contacts(client: &Client) -> Result<Vec<PersonResponseDto>> {
|
||||
let mut all_people = Vec::new();
|
||||
let mut page_number = 1;
|
||||
let mut has_next_page = true;
|
||||
|
||||
while has_next_page {
|
||||
let fetched = client
|
||||
.get_all_people(Some(page_number.into()), None, Some(true))
|
||||
.await?;
|
||||
all_people.extend(fetched.people.clone());
|
||||
has_next_page = fetched.has_next_page.expect("Missing has_next_page");
|
||||
page_number += 1;
|
||||
}
|
||||
|
||||
Ok(all_people)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue