Upstream commit: 474dd0229cb20cf513036619781ed97bd8073c3f Enterprise-only files removed or emptied: 63 Enterprise-only snippets removed: 117 in 50 files Dangling module declarations removed: 5 Cargo edits turning enterprise off: 14 Verification: clean Enterprise feature gates left for rebuilt features: 19 in 18 files Produced by tools/fork/strip.py. The full report is in docs/fork/strip-reports/ on main.
405 lines
13 KiB
Rust
405 lines
13 KiB
Rust
/*
|
|
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
|
*
|
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
|
*/
|
|
|
|
use super::GroupwareCache;
|
|
use crate::{
|
|
DavResourceName, RFC_3986,
|
|
calendar::{
|
|
ArchivedCalendar, ArchivedCalendarEvent, Calendar, CalendarEvent, SCHEDULE_INBOX_ID,
|
|
SCHEDULE_OUTBOX_ID, storage::ItipAutoExpunge,
|
|
},
|
|
contact::{AddressBook, ArchivedAddressBook, ArchivedContactCard, ContactCard},
|
|
encode_path_segment,
|
|
};
|
|
use calcard::common::timezone::Tz;
|
|
use common::{
|
|
DavName, DavPath, DavResource, DavResourceMetadata, DavResources, Server,
|
|
TinyCalendarPreferences, UpdateLock,
|
|
};
|
|
use std::sync::Arc;
|
|
use store::ahash::{AHashMap, AHashSet};
|
|
use trc::AddContext;
|
|
use types::{
|
|
acl::AclGrant,
|
|
collection::{Collection, SyncCollection},
|
|
};
|
|
use utils::map::bitmap::Bitmap;
|
|
|
|
pub(super) async fn build_calcard_resources(
|
|
server: &Server,
|
|
access_account_id: u32,
|
|
account_id: u32,
|
|
sync_collection: SyncCollection,
|
|
container_collection: Collection,
|
|
item_collection: Collection,
|
|
update_lock: Arc<UpdateLock>,
|
|
) -> trc::Result<DavResources> {
|
|
let is_calendar = matches!(sync_collection, SyncCollection::Calendar);
|
|
let owner_account_info = server.account(account_id).await?;
|
|
let access_account_info = if account_id == access_account_id {
|
|
owner_account_info.clone()
|
|
} else {
|
|
server.account(access_account_id).await?
|
|
};
|
|
let mut cache = DavResources {
|
|
base_path: format!(
|
|
"{}/{}/",
|
|
if is_calendar {
|
|
DavResourceName::Cal
|
|
} else {
|
|
DavResourceName::Card
|
|
}
|
|
.base_path(),
|
|
percent_encoding::utf8_percent_encode(owner_account_info.name(), RFC_3986),
|
|
),
|
|
paths: AHashSet::with_capacity(16),
|
|
resources: Vec::with_capacity(16),
|
|
item_change_id: 0,
|
|
container_change_id: 0,
|
|
highest_change_id: 0,
|
|
size: std::mem::size_of::<DavResources>() as u64,
|
|
update_lock,
|
|
};
|
|
|
|
let mut is_first_check = true;
|
|
loop {
|
|
let last_change_id = server
|
|
.core
|
|
.storage
|
|
.data
|
|
.get_last_change_id(account_id, sync_collection.into())
|
|
.await
|
|
.caused_by(trc::location!())?
|
|
.unwrap_or_default();
|
|
cache.item_change_id = last_change_id;
|
|
cache.container_change_id = last_change_id;
|
|
cache.highest_change_id = last_change_id;
|
|
cache.update_lock.set_revision(last_change_id);
|
|
|
|
server
|
|
.archives(
|
|
account_id,
|
|
container_collection,
|
|
&(),
|
|
|document_id, archive| {
|
|
let resource = if is_calendar {
|
|
resource_from_calendar(archive.unarchive::<Calendar>()?, document_id)
|
|
} else {
|
|
resource_from_addressbook(archive.unarchive::<AddressBook>()?, document_id)
|
|
};
|
|
let path = DavPath {
|
|
path: encode_path_segment(resource.container_name().unwrap()).into_owned(),
|
|
parent_id: None,
|
|
hierarchy_seq: 1,
|
|
resource_idx: cache.resources.len(),
|
|
};
|
|
|
|
cache.size += (std::mem::size_of::<DavPath>()
|
|
+ std::mem::size_of::<DavResource>()
|
|
+ (path.path.len()) * 2) as u64;
|
|
cache.paths.insert(path);
|
|
cache.resources.push(resource);
|
|
|
|
Ok(true)
|
|
},
|
|
)
|
|
.await
|
|
.caused_by(trc::location!())?;
|
|
|
|
if cache.paths.is_empty() {
|
|
if is_first_check {
|
|
if is_calendar {
|
|
server
|
|
.create_default_calendar(&access_account_info, &owner_account_info)
|
|
.await?;
|
|
} else {
|
|
server
|
|
.create_default_addressbook(&access_account_info, &owner_account_info)
|
|
.await?;
|
|
}
|
|
is_first_check = false;
|
|
continue;
|
|
} else {
|
|
return Ok(cache);
|
|
}
|
|
}
|
|
|
|
let parent_range = cache.resources.len();
|
|
server
|
|
.archives(account_id, item_collection, &(), |document_id, archive| {
|
|
let resource = if is_calendar {
|
|
resource_from_event(archive.unarchive::<CalendarEvent>()?, document_id)
|
|
} else {
|
|
resource_from_card(archive.unarchive::<ContactCard>()?, document_id)
|
|
};
|
|
let resource_idx = cache.resources.len();
|
|
|
|
for name in resource.child_names().unwrap_or_default().iter() {
|
|
if let Some(parent) =
|
|
cache.resources.get(..parent_range).and_then(|resources| {
|
|
resources.iter().find(|r| r.document_id == name.parent_id)
|
|
})
|
|
{
|
|
let path = DavPath {
|
|
path: format!(
|
|
"{}/{}",
|
|
encode_path_segment(parent.container_name().unwrap()),
|
|
encode_path_segment(&name.name)
|
|
),
|
|
parent_id: Some(name.parent_id),
|
|
hierarchy_seq: 0,
|
|
resource_idx,
|
|
};
|
|
|
|
cache.size += (std::mem::size_of::<DavPath>()
|
|
+ name.name.len()
|
|
+ path.path.len()) as u64;
|
|
cache.paths.insert(path);
|
|
}
|
|
}
|
|
cache.size += std::mem::size_of::<DavResource>() as u64;
|
|
cache.resources.push(resource);
|
|
|
|
Ok(true)
|
|
})
|
|
.await
|
|
.caused_by(trc::location!())?;
|
|
|
|
return Ok(cache);
|
|
}
|
|
}
|
|
|
|
pub(super) async fn build_scheduling_resources(
|
|
server: &Server,
|
|
account_id: u32,
|
|
update_lock: Arc<UpdateLock>,
|
|
) -> trc::Result<DavResources> {
|
|
let last_change_id = server
|
|
.core
|
|
.storage
|
|
.data
|
|
.get_last_change_id(account_id, SyncCollection::CalendarEventNotification.into())
|
|
.await
|
|
.caused_by(trc::location!())?
|
|
.unwrap_or_default();
|
|
|
|
let account_info = server.account(account_id).await?;
|
|
let item_ids = server
|
|
.itip_ids(account_id)
|
|
.await
|
|
.caused_by(trc::location!())?;
|
|
|
|
update_lock.set_revision(last_change_id);
|
|
let mut cache = DavResources {
|
|
base_path: format!(
|
|
"{}/{}/",
|
|
DavResourceName::Scheduling.base_path(),
|
|
percent_encoding::utf8_percent_encode(account_info.name(), RFC_3986),
|
|
),
|
|
paths: AHashSet::with_capacity((2 + item_ids.len()) as usize),
|
|
resources: Vec::with_capacity((2 + item_ids.len()) as usize),
|
|
item_change_id: last_change_id,
|
|
container_change_id: last_change_id,
|
|
highest_change_id: last_change_id,
|
|
size: std::mem::size_of::<DavResources>() as u64,
|
|
update_lock,
|
|
};
|
|
|
|
for (document_id, is_container) in item_ids
|
|
.into_iter()
|
|
.map(|document_id| (document_id, false))
|
|
.chain([(SCHEDULE_INBOX_ID, true), (SCHEDULE_OUTBOX_ID, true)])
|
|
{
|
|
let path = path_from_scheduling(document_id, cache.resources.len(), is_container);
|
|
cache.size += (std::mem::size_of::<DavPath>() + (path.path.len() * 2)) as u64
|
|
+ std::mem::size_of::<DavResource>() as u64;
|
|
cache.paths.insert(path);
|
|
cache
|
|
.resources
|
|
.push(resource_from_scheduling(document_id, is_container));
|
|
}
|
|
|
|
Ok(cache)
|
|
}
|
|
|
|
pub(super) fn build_simple_hierarchy(cache: &mut DavResources) {
|
|
cache.paths = AHashSet::with_capacity(cache.resources.len());
|
|
let name_idx = cache
|
|
.resources
|
|
.iter()
|
|
.filter_map(|resource| {
|
|
resource
|
|
.container_name()
|
|
.map(|name| (resource.document_id, name))
|
|
})
|
|
.collect::<AHashMap<_, _>>();
|
|
|
|
for (resource_idx, resource) in cache.resources.iter().enumerate() {
|
|
match &resource.data {
|
|
DavResourceMetadata::Calendar { name, .. }
|
|
| DavResourceMetadata::AddressBook { name, .. } => {
|
|
let path = DavPath {
|
|
path: encode_path_segment(name).into_owned(),
|
|
parent_id: None,
|
|
hierarchy_seq: 1,
|
|
resource_idx,
|
|
};
|
|
cache.size +=
|
|
(std::mem::size_of::<DavPath>() + name.len() + path.path.len()) as u64;
|
|
cache.paths.insert(path);
|
|
}
|
|
DavResourceMetadata::CalendarEvent { names, .. }
|
|
| DavResourceMetadata::ContactCard { names } => {
|
|
for name in names {
|
|
if let Some(parent_name) = name_idx.get(&name.parent_id) {
|
|
let path = DavPath {
|
|
path: format!(
|
|
"{}/{}",
|
|
encode_path_segment(parent_name),
|
|
encode_path_segment(&name.name)
|
|
),
|
|
parent_id: Some(name.parent_id),
|
|
hierarchy_seq: 0,
|
|
resource_idx,
|
|
};
|
|
cache.size += (std::mem::size_of::<DavPath>()
|
|
+ name.name.len()
|
|
+ path.path.len()) as u64;
|
|
cache.paths.insert(path);
|
|
}
|
|
}
|
|
}
|
|
_ => unreachable!(),
|
|
}
|
|
cache.size += std::mem::size_of::<DavResource>() as u64;
|
|
}
|
|
}
|
|
|
|
pub(super) fn resource_from_calendar(calendar: &ArchivedCalendar, document_id: u32) -> DavResource {
|
|
DavResource {
|
|
document_id,
|
|
data: DavResourceMetadata::Calendar {
|
|
name: calendar.name.to_string(),
|
|
acls: calendar
|
|
.acls
|
|
.iter()
|
|
.map(|acl| AclGrant {
|
|
account_id: acl.account_id.to_native(),
|
|
grants: Bitmap::from(&acl.grants),
|
|
})
|
|
.collect(),
|
|
preferences: calendar
|
|
.preferences
|
|
.iter()
|
|
.map(|pref| TinyCalendarPreferences {
|
|
account_id: pref.account_id.to_native(),
|
|
flags: pref.flags.to_native(),
|
|
tz: pref.time_zone.tz().unwrap_or(Tz::UTC),
|
|
})
|
|
.collect(),
|
|
},
|
|
}
|
|
}
|
|
|
|
pub(super) fn resource_from_event(event: &ArchivedCalendarEvent, document_id: u32) -> DavResource {
|
|
let (start, duration) = event.data.event_range().unwrap_or_default();
|
|
DavResource {
|
|
document_id,
|
|
data: DavResourceMetadata::CalendarEvent {
|
|
names: event
|
|
.names
|
|
.iter()
|
|
.map(|name| DavName {
|
|
name: name.name.to_string(),
|
|
parent_id: name.parent_id.to_native(),
|
|
})
|
|
.collect(),
|
|
start,
|
|
duration,
|
|
},
|
|
}
|
|
}
|
|
|
|
pub(super) fn resource_from_scheduling(document_id: u32, is_container: bool) -> DavResource {
|
|
DavResource {
|
|
document_id,
|
|
data: DavResourceMetadata::CalendarEventNotification {
|
|
names: if !is_container {
|
|
[DavName {
|
|
name: format!("{document_id}.ics"),
|
|
parent_id: SCHEDULE_INBOX_ID,
|
|
}]
|
|
.into_iter()
|
|
.collect()
|
|
} else {
|
|
Default::default()
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
pub(super) fn path_from_scheduling(
|
|
document_id: u32,
|
|
resource_idx: usize,
|
|
is_container: bool,
|
|
) -> DavPath {
|
|
if is_container {
|
|
DavPath {
|
|
path: if document_id == SCHEDULE_INBOX_ID {
|
|
"inbox".to_string()
|
|
} else {
|
|
"outbox".to_string()
|
|
},
|
|
parent_id: None,
|
|
hierarchy_seq: 1,
|
|
resource_idx,
|
|
}
|
|
} else {
|
|
DavPath {
|
|
path: format!("inbox/{document_id}.ics"),
|
|
parent_id: Some(SCHEDULE_INBOX_ID),
|
|
hierarchy_seq: 0,
|
|
resource_idx,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(super) fn resource_from_addressbook(
|
|
book: &ArchivedAddressBook,
|
|
document_id: u32,
|
|
) -> DavResource {
|
|
DavResource {
|
|
document_id,
|
|
data: DavResourceMetadata::AddressBook {
|
|
name: book.name.to_string(),
|
|
acls: book
|
|
.acls
|
|
.iter()
|
|
.map(|acl| AclGrant {
|
|
account_id: acl.account_id.to_native(),
|
|
grants: Bitmap::from(&acl.grants),
|
|
})
|
|
.collect(),
|
|
},
|
|
}
|
|
}
|
|
|
|
pub(super) fn resource_from_card(card: &ArchivedContactCard, document_id: u32) -> DavResource {
|
|
DavResource {
|
|
document_id,
|
|
data: DavResourceMetadata::ContactCard {
|
|
names: card
|
|
.names
|
|
.iter()
|
|
.map(|name| DavName {
|
|
name: name.name.to_string(),
|
|
parent_id: name.parent_id.to_native(),
|
|
})
|
|
.collect(),
|
|
},
|
|
}
|
|
}
|