use crate::{
common::{Data, IanaType, timezone::TzTimestamp},
icalendar::{ICalendarEntry, ICalendarParameterName, ICalendarValue, ICalendarValueType, Uri},
jscalendar::{
JSCalendarDateTime, JSCalendarId, JSCalendarProperty, JSCalendarValue,
import::{
EntryState, ICalendarConvertedProperty, ICalendarParams, State, params::ExtractParams,
},
uuid5,
},
};
use ahash::AHashMap;
use jmap_tools::{JsonPointer, JsonPointerHandler, Key, Map, Property, Value};
use std::{borrow::Cow, collections::hash_map::Entry};
impl<I: JSCalendarId, B: JSCalendarId> State<I, B> {
pub(super) fn map_named_entry(
&mut self,
entry: &mut EntryState,
extract: &[ICalendarParameterName],
top_property_name: JSCalendarProperty<I>,
values: impl IntoIterator<
Item = (
Key<'static, JSCalendarProperty<I>>,
Value<'static, JSCalendarProperty<I>, JSCalendarValue<I, B>>,
),
>,
) {
let mut values = values.into_iter().peekable();
let (property, value) = match values.peek() {
Some((property, Value::Str(s))) => (property, s.as_ref()),
Some((property, _)) => {
debug_assert!(false, "Cannot generate jsid without a string value");
(property, "unknown")
}
_ => {
panic!("Cannot generate jsid without a value");
}
};
let mut parameters = AHashMap::new();
let js_id = parameters
.extract_params(&mut entry.entry, extract)
.unwrap_or_else(|| uuid5(value));
entry.set_converted_to::<I>(&[
top_property_name.to_cow().as_ref(),
js_id.as_str(),
property.to_string().as_ref(),
]);
let obj = self
.get_mut_object_or_insert(top_property_name)
.insert_or_get_mut(Key::Owned(js_id), Value::new_object())
.as_object_mut()
.unwrap();
for (key, value) in values.chain(parameters) {
if let Some(current_value) = obj.get_mut(&key) {
match (value, current_value) {
(Value::Object(new_obj), Value::Object(existing_obj)) => {
existing_obj.extend(new_obj.into_vec());
}
(value, current_value) => {
*current_value = value;
}
}
} else {
obj.insert_unchecked(key, value);
}
}
}
pub(super) fn add_conversion_props(&mut self, mut entry: EntryState) {
if self.include_ical_components {
if let Some(converted_to) = entry.converted_to.take() {
if entry.map_name || !entry.entry.params.is_empty() {
let mut value_type = None;
match self.ical_converted_properties.entry(converted_to) {
Entry::Occupied(mut conv_prop) => {
entry.jcal_parameters(&mut conv_prop.get_mut().params, &mut value_type);
}
Entry::Vacant(conv_prop) => {
let mut params = ICalendarParams::default();
entry.jcal_parameters(&mut params, &mut value_type);
if let Some(value_type) = value_type {
params.0.insert(
ICalendarParameterName::Value,
vec![Value::Str(value_type.into_string())],
);
}
if !params.0.is_empty() || entry.map_name {
conv_prop.insert(ICalendarConvertedProperty {
name: if entry.map_name {
Some(entry.entry.name)
} else {
None
},
params,
});
}
}
}
}
} else {
self.ical_properties.push(entry.into_jcal());
}
}
}
pub(super) fn into_object(
mut self,
) -> Value<'static, JSCalendarProperty<I>, JSCalendarValue<I, B>> {
let mut ical_obj = Map::from(Vec::new());
if !self.ical_converted_properties.is_empty() {
let mut converted_properties =
Map::from(Vec::with_capacity(self.ical_converted_properties.len()));
for (converted_to, props) in self.ical_converted_properties {
let mut obj = Map::from(Vec::with_capacity(2));
if let Some(params) = props.params.into_jscalendar_value() {
obj.insert(
Key::Property(JSCalendarProperty::Parameters),
Value::Object(params),
);
}
if let Some(name) = props.name {
obj.insert(
Key::Property(JSCalendarProperty::Name),
Value::Str(name.into_string().to_ascii_lowercase().into()),
);
}
converted_properties.insert_unchecked(Key::Owned(converted_to), Value::Object(obj));
}
ical_obj.insert_unchecked(
Key::Property(JSCalendarProperty::ConvertedProperties),
Value::Object(converted_properties),
);
}
if !self.ical_properties.is_empty() {
ical_obj.insert_unchecked(
Key::Property(JSCalendarProperty::Properties),
Value::Array(self.ical_properties),
);
}
if let Some(components) = self.ical_components {
ical_obj.insert_unchecked(Key::Property(JSCalendarProperty::Components), components);
}
if !ical_obj.is_empty() || self.map_component {
ical_obj.insert_unchecked(
Key::Property(JSCalendarProperty::Name),
Value::Str(self.component_type.as_str().to_ascii_lowercase().into()),
);
self.entries.insert(
Key::Property(JSCalendarProperty::ICalendar),
Value::Object(ical_obj),
);
}
if self.has_dates {
if !self.is_recurrence_instance {
self.entries.insert(
Key::Property(JSCalendarProperty::TimeZone),
self.tz_start
.and_then(|tz| tz.name())
.map(Value::Str)
.unwrap_or(Value::Null),
);
}
if self.tz_end.is_some() && self.tz_start.is_some() && self.tz_end != self.tz_start {
self.entries.insert(
Key::Property(JSCalendarProperty::EndTimeZone),
self.tz_end
.and_then(|tz| tz.name())
.map(Value::Str)
.unwrap_or(Value::Null),
);
}
if let Some(recurrence_id) = self.recurrence_id {
self.entries.insert(
Key::Property(JSCalendarProperty::RecurrenceId),
Value::Element(JSCalendarValue::DateTime(JSCalendarDateTime::new(
recurrence_id
.with_timezone(&self.tz_start.unwrap_or_default())
.to_naive_timestamp(),
true,
))),
);
let rid_tz = recurrence_id.timezone().to_resolved();
if rid_tz.is_some() && !self.is_recurrence_instance {
self.entries.insert(
Key::Property(JSCalendarProperty::RecurrenceIdTimeZone),
rid_tz
.and_then(|tz| tz.name())
.map(Value::Str)
.unwrap_or(Value::Null),
);
}
}
}
if let Some(uid) = self.uid {
self.entries.insert(
Key::Property(JSCalendarProperty::Uid),
Value::Str(uid.into()),
);
}
if !self.is_recurrence_instance {
self.entries.insert(
Key::Property(JSCalendarProperty::Type),
Value::Element(JSCalendarValue::Type(
self.component_type.to_jscalendar_type().unwrap(),
)),
);
}
let mut obj = Value::Object(self.entries.into_iter().collect());
if !self.patch_objects.is_empty() {
for (ptr, patch) in self.patch_objects {
obj.patch_jptr(ptr.iter(), patch);
}
}
obj
}
pub(super) fn set_map_component(&mut self) {
self.map_component = true;
}
pub(super) fn set_is_recurrence_instance(&mut self) {
self.is_recurrence_instance = true;
}
#[inline]
pub(super) fn get_mut_object_or_insert(
&mut self,
key: JSCalendarProperty<I>,
) -> &mut Map<'static, JSCalendarProperty<I>, JSCalendarValue<I, B>> {
self.entries
.entry(Key::Property(key))
.or_insert_with(|| Value::Object(Map::from(Vec::new())))
.as_object_mut()
.unwrap()
}
}
impl EntryState {
pub(super) fn new(entry: ICalendarEntry) -> Self {
Self {
entry,
converted_to: None,
map_name: false,
}
}
pub(super) fn set_converted_to<I: JSCalendarId>(&mut self, converted_to: &[&str]) {
self.converted_to = Some(JsonPointer::<JSCalendarProperty<I>>::encode(converted_to));
}
pub(super) fn set_map_name(&mut self) {
self.map_name = true;
}
pub(super) fn into_jcal<I: JSCalendarId, B: JSCalendarId>(
mut self,
) -> Value<'static, JSCalendarProperty<I>, JSCalendarValue<I, B>> {
let mut value_type = None;
let mut params = ICalendarParams::default();
self.jcal_parameters(&mut params, &mut value_type);
let values = if self.entry.values.len() == 1 {
self.entry
.values
.into_iter()
.next()
.unwrap()
.into_jscalendar_value(value_type.as_ref())
} else {
let mut values = Vec::with_capacity(self.entry.values.len());
for value in self.entry.values {
values.push(value.into_jscalendar_value(value_type.as_ref()));
}
Value::Array(values)
};
Value::Array(vec![
Value::Str(self.entry.name.as_str().to_ascii_lowercase().into()),
Value::Object(
params
.into_jscalendar_value()
.unwrap_or(Map::from(Vec::new())),
),
Value::Str(
value_type
.map(|v| v.into_string())
.unwrap_or(Cow::Borrowed("unknown")),
),
values,
])
}
pub(super) fn jcal_parameters<I: JSCalendarId, B: JSCalendarId>(
&mut self,
params: &mut ICalendarParams<I, B>,
value_type: &mut Option<IanaType<ICalendarValueType, String>>,
) {
if self.entry.params.is_empty() {
return;
}
let (default_type, _) = self.entry.name.default_types();
let default_type = default_type.unwrap_ical();
for param in std::mem::take(&mut self.entry.params) {
if matches!(param.name, ICalendarParameterName::Value) {
if let Some(v) = param
.value
.into_value_type()
.filter(|v| !v.is_iana_and(|v| v == &default_type))
{
*value_type = Some(v);
}
} else if let Some(value) = param.value.into_text() {
params
.0
.entry(param.name)
.or_default()
.push(Value::Str(value));
}
}
}
}
impl ICalendarValue {
pub(super) fn uri_to_string(self) -> Self {
match self {
ICalendarValue::Uri(uri) => ICalendarValue::Text(uri.into_unwrapped_string()),
ICalendarValue::Binary(data) => ICalendarValue::Text(
Uri::Data(Data {
content_type: None,
data,
})
.into_unwrapped_string(),
),
other => other,
}
}
}