use std::path::Path;
use cosmic_pim_accounts::Account;
use cosmic_pim_caldav::{CaldavClient, Flavor, VdirStore};
use cosmic_pim_core::model::{CalendarMeta, DEFAULT_CALENDAR_COLOR, Rgb};
use cosmic_pim_core::store::vdir;
use crate::error::Result;
#[derive(Debug, Clone)]
pub struct Provisioned {
pub href: String,
pub display_name: String,
pub collection_id: String,
pub created: bool,
pub read_only: bool,
pub contested: Option<String>,
pub flavor: Flavor,
}
pub fn provision_account(
client: &mut CaldavClient,
account: &Account,
root: &Path,
) -> Result<Vec<Provisioned>> {
client.discover()?;
let calendars = client.list_calendars()?;
let mut existing = vdir::collections(root);
let mut out = Vec::with_capacity(calendars.len());
for calendar in calendars {
let display_name = calendar
.display_name
.clone()
.map(|n| n.trim().to_owned())
.filter(|n| !n.is_empty())
.unwrap_or_else(|| name_from_href(&calendar.href, client.flavor()));
let read_only = calendar.can_edit == Some(false);
let bound = account
.collections
.get(&calendar.href)
.and_then(|id| existing.iter().find(|c| &c.id == id).cloned());
if let Some(meta) = &bound
&& cosmic_pim_caldav::is_local_only(&meta.path)
{
tracing::debug!(
collection = meta.id,
"bound collection is marked local-only; leaving it alone"
);
continue;
}
let (meta, created) = match bound {
Some(meta) => (meta, false),
None => {
let color = calendar
.color
.as_deref()
.and_then(Rgb::parse)
.unwrap_or(DEFAULT_CALENDAR_COLOR);
let meta = vdir::create_collection(root, &display_name, color)?;
existing.push(meta.clone());
(meta, true)
}
};
let mut store = open_store(client.flavor(), meta.clone())?;
store.set_remote(&calendar.href, read_only)?;
let contested = store.foreign_sync_marker();
if let Some(marker) = &contested {
tracing::warn!(
collection = meta.id,
marker,
"another sync engine already owns this collection; not syncing it"
);
}
out.push(Provisioned {
href: calendar.href,
display_name,
collection_id: meta.id,
created,
read_only,
contested,
flavor: client.flavor(),
});
}
Ok(out)
}
pub(crate) fn open_store(flavor: Flavor, meta: CalendarMeta) -> Result<VdirStore> {
match flavor {
Flavor::CalDav => Ok(VdirStore::open(meta)?),
Flavor::CardDav => Ok(VdirStore::open_carddav(meta)?),
}
}
fn name_from_href(href: &str, flavor: Flavor) -> String {
let fallback = match flavor {
Flavor::CalDav => "Calendar",
Flavor::CardDav => "Contacts",
};
href.rsplit('/')
.find(|segment| !segment.is_empty())
.filter(|segment| !segment.is_empty())
.unwrap_or(fallback)
.to_owned()
}
pub fn open_collection(root: &Path, collection_id: &str) -> Option<CalendarMeta> {
vdir::collections(root)
.into_iter()
.find(|c| c.id == collection_id)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_name_is_derived_from_the_last_href_segment() {
assert_eq!(
name_from_href("/dav/calendars/user/work/", Flavor::CalDav),
"work"
);
assert_eq!(
name_from_href("/dav/calendars/user/work", Flavor::CalDav),
"work"
);
}
#[test]
fn an_unusable_href_still_yields_a_name() {
assert_eq!(name_from_href("/", Flavor::CalDav), "Calendar");
assert_eq!(name_from_href("", Flavor::CalDav), "Calendar");
}
#[test]
fn an_unnamed_address_book_is_not_called_calendar() {
assert_eq!(name_from_href("/", Flavor::CardDav), "Contacts");
}
}