use std::str::FromStr;
use crate::{
common::{
IanaParse, LinkRelation, PartialDateTime,
parser::Boolean,
timezone::{Tz, TzTimestamp},
},
icalendar::*,
jscalendar::{
JSCalendarDateTime, JSCalendarId, JSCalendarProperty, JSCalendarValue,
export::ConvertedComponent,
},
jscontact::export::params::ParamValue,
};
use chrono::{DateTime, NaiveDateTime, TimeZone};
use jmap_tools::{Key, Value};
impl ICalendarEntry {
pub(super) fn import_converted<I: JSCalendarId, B: JSCalendarId>(
mut self,
path: &[JSCalendarProperty<I>],
conversions: &mut Option<ConvertedComponent<'_, I, B>>,
) -> Self {
let Some(conversions) = conversions
.as_mut()
.filter(|c| c.converted_props_count < c.converted_props.len())
else {
return self;
};
let value = self.values.first().and_then(|v| v.as_text());
let jsid = if matches!(self.name, ICalendarProperty::RelatedTo) {
value
} else {
self.jsid()
};
let mut matched_once = false;
'outer: for (keys, value) in conversions.converted_props.iter_mut() {
if matches!(value, Value::Null) {
continue;
}
for (pos, item) in path.iter().enumerate() {
if !keys
.iter()
.any(|k| matches!(k, Key::Property(p) if p == item))
{
if pos == 0 && matched_once {
break 'outer;
} else {
continue 'outer;
}
} else {
matched_once = true;
}
}
if jsid
.map(Key::Borrowed)
.is_none_or(|prop_id| keys.iter().any(|k| k == &prop_id))
{
self.import_converted_properties(std::mem::take(value));
conversions.converted_props_count += 1;
break;
}
}
if self
.parameter(&ICalendarParameterName::Value)
.is_some_and(|v| {
matches!(
v,
ICalendarParameterValue::Value(ICalendarValueType::Binary)
)
})
&& let Some(ICalendarValue::Uri(Uri::Data(data))) = self.values.first_mut()
{
let bin = ICalendarValue::Binary(std::mem::take(&mut data.data));
if let Some(content_type) = data.content_type.take()
&& !self.has_parameter(&ICalendarParameterName::Fmttype)
{
self.params.push(ICalendarParameter::fmttype(content_type));
}
self.values[0] = bin;
}
self
}
pub(super) fn import_converted_properties<I: JSCalendarId, B: JSCalendarId>(
&mut self,
props: Value<'_, JSCalendarProperty<I>, JSCalendarValue<I, B>>,
) {
for (key, value) in props.into_expanded_object() {
match key {
Key::Property(JSCalendarProperty::Name) => {
if let Some(name) = value.into_string() {
self.name = ICalendarProperty::parse(name.as_bytes())
.unwrap_or(ICalendarProperty::Other(name.to_ascii_uppercase()));
}
}
Key::Property(JSCalendarProperty::Parameters) => {
self.import_jcal_params(value);
}
_ => {}
}
}
}
pub(super) fn import_jcal_params<I: JSCalendarId, B: JSCalendarId>(
&mut self,
params: Value<'_, JSCalendarProperty<I>, JSCalendarValue<I, B>>,
) {
for (key, value) in params.into_expanded_object() {
let mut values = match value {
Value::Array(values) => values.into_iter().filter_map(ParamValue::try_from_value),
value => vec![value]
.into_iter()
.filter_map(ParamValue::try_from_value),
}
.peekable();
if values.peek().is_none() {
continue;
}
let key = key.to_string();
let Some(param) = ICalendarParameterName::try_parse(key.as_bytes()) else {
let key = key.into_owned();
for value in values {
self.params.push(ICalendarParameter {
name: ICalendarParameterName::Other(key.to_ascii_uppercase()),
value: value.into_string().into_owned().into(),
});
}
continue;
};
for value in values {
let value = match ¶m {
ICalendarParameterName::Value => {
let value = value.into_string();
ICalendarValueType::parse(value.as_bytes())
.map(ICalendarParameterValue::Value)
.unwrap_or_else(|| ICalendarParameterValue::Text(value.into_owned()))
}
ICalendarParameterName::Size | ICalendarParameterName::Order => {
match value.into_number() {
Ok(n) => ICalendarParameterValue::Integer(n.unsigned_abs()),
Err(value) => {
ICalendarParameterValue::Text(value.into_string().into_owned())
}
}
}
ICalendarParameterName::Rsvp | ICalendarParameterName::Derived => {
let value = value.into_string();
Boolean::parse(value.as_bytes())
.map(|v| ICalendarParameterValue::Bool(v.0))
.unwrap_or_else(|| ICalendarParameterValue::Text(value.into_owned()))
}
ICalendarParameterName::Altrep
| ICalendarParameterName::DelegatedFrom
| ICalendarParameterName::DelegatedTo
| ICalendarParameterName::Dir
| ICalendarParameterName::Member
| ICalendarParameterName::SentBy
| ICalendarParameterName::Schema => {
ICalendarParameterValue::Uri(Uri::parse(value.into_string().into_owned()))
}
ICalendarParameterName::Range => {
let value = value.into_string();
if value.eq_ignore_ascii_case("THISANDFUTURE") {
ICalendarParameterValue::Bool(true)
} else {
ICalendarParameterValue::Text(value.into_owned())
}
}
ICalendarParameterName::Gap => {
let value = value.into_string();
ICalendarDuration::parse(value.as_bytes())
.map(ICalendarParameterValue::Duration)
.unwrap_or_else(|| ICalendarParameterValue::Text(value.into_owned()))
}
ICalendarParameterName::Cutype => {
let value = value.into_string();
ICalendarUserTypes::parse(value.as_bytes())
.map(ICalendarParameterValue::Cutype)
.unwrap_or_else(|| ICalendarParameterValue::Text(value.into_owned()))
}
ICalendarParameterName::Fbtype => {
let value = value.into_string();
ICalendarFreeBusyType::parse(value.as_bytes())
.map(ICalendarParameterValue::Fbtype)
.unwrap_or_else(|| ICalendarParameterValue::Text(value.into_owned()))
}
ICalendarParameterName::Partstat => {
let value = value.into_string();
ICalendarParticipationStatus::parse(value.as_bytes())
.map(ICalendarParameterValue::Partstat)
.unwrap_or_else(|| ICalendarParameterValue::Text(value.into_owned()))
}
ICalendarParameterName::Related => {
let value = value.into_string();
ICalendarRelated::parse(value.as_bytes())
.map(ICalendarParameterValue::Related)
.unwrap_or_else(|| ICalendarParameterValue::Text(value.into_owned()))
}
ICalendarParameterName::Reltype => {
let value = value.into_string();
ICalendarRelationshipType::parse(value.as_bytes())
.map(ICalendarParameterValue::Reltype)
.unwrap_or_else(|| ICalendarParameterValue::Text(value.into_owned()))
}
ICalendarParameterName::Role => {
let value = value.into_string();
ICalendarParticipationRole::parse(value.as_bytes())
.map(ICalendarParameterValue::Role)
.unwrap_or_else(|| ICalendarParameterValue::Text(value.into_owned()))
}
ICalendarParameterName::ScheduleAgent => {
let value = value.into_string();
ICalendarScheduleAgentValue::parse(value.as_bytes())
.map(ICalendarParameterValue::ScheduleAgent)
.unwrap_or_else(|| ICalendarParameterValue::Text(value.into_owned()))
}
ICalendarParameterName::ScheduleForceSend => {
let value = value.into_string();
ICalendarScheduleForceSendValue::parse(value.as_bytes())
.map(ICalendarParameterValue::ScheduleForceSend)
.unwrap_or_else(|| ICalendarParameterValue::Text(value.into_owned()))
}
ICalendarParameterName::Display => {
let value = value.into_string();
ICalendarDisplayType::parse(value.as_bytes())
.map(ICalendarParameterValue::Display)
.unwrap_or_else(|| ICalendarParameterValue::Text(value.into_owned()))
}
ICalendarParameterName::Feature => {
let value = value.into_string();
ICalendarFeatureType::parse(value.as_bytes())
.map(ICalendarParameterValue::Feature)
.unwrap_or_else(|| ICalendarParameterValue::Text(value.into_owned()))
}
ICalendarParameterName::Linkrel => {
let value = value.into_string();
LinkRelation::parse(value.as_bytes())
.map(ICalendarParameterValue::Linkrel)
.unwrap_or_else(|| ICalendarParameterValue::Text(value.into_owned()))
}
_ => ICalendarParameterValue::Text(value.into_string().into_owned()),
};
self.params.push(ICalendarParameter {
name: param.clone(),
value,
});
}
}
}
}
impl ICalendarEntry {
pub(super) fn with_date_time(mut self, dt: DateTime<Tz>) -> Self {
debug_assert!(self.values.is_empty());
let tz_id = self.tz_id();
let has_tz_id = tz_id.is_some();
self.insert_date(
dt,
has_tz_id,
!has_tz_id,
tz_id.and_then(|id| Tz::from_str(id).ok()),
);
self
}
pub(super) fn with_date_times(mut self, dts: Vec<DateTime<Tz>>) -> Self {
debug_assert!(self.values.is_empty());
let tz_id = self.tz_id();
let has_tz_id = tz_id.is_some();
let entry_tz = tz_id
.and_then(|id| Tz::from_str(id).ok())
.or_else(|| dts.first().map(|dt| dt.timezone()));
for (pos, dt) in dts.into_iter().enumerate() {
self.insert_date(dt, has_tz_id, pos == 0, entry_tz);
}
self
}
fn insert_date(
&mut self,
mut dt: DateTime<Tz>,
has_tz_id: bool,
add_tz_id: bool,
entry_tz: Option<Tz>,
) {
if let Some(tz) = entry_tz
&& tz != dt.timezone()
{
dt = dt.with_timezone(&tz);
}
let tz = dt.timezone();
if has_tz_id {
self.values
.push(PartialDateTime::from_naive_timestamp(dt.to_naive_timestamp()).into());
} else if tz.is_utc() {
self.values
.push(PartialDateTime::from_utc_timestamp(dt.timestamp()).into());
} else {
if add_tz_id && let Some(tz_name) = tz.name() {
self.params
.push(ICalendarParameter::tzid(tz_name.into_owned()));
}
self.values
.push(PartialDateTime::from_naive_timestamp(dt.to_naive_timestamp()).into());
}
}
}
impl JSCalendarDateTime {
pub fn to_utc_date_time(&self) -> Option<DateTime<Tz>> {
DateTime::from_timestamp(self.timestamp, 0)
.and_then(|local| Tz::UTC.from_local_datetime(&local.naive_utc()).single())
}
pub fn to_naive_date_time(&self) -> Option<NaiveDateTime> {
DateTime::from_timestamp(self.timestamp, 0).map(|local| local.naive_utc())
}
}
impl From<JSCalendarDateTime> for PartialDateTime {
fn from(dt: JSCalendarDateTime) -> Self {
if !dt.is_local {
Self::from_utc_timestamp(dt.timestamp)
} else {
Self::from_naive_timestamp(dt.timestamp)
}
}
}