1use std::collections::HashMap;
20
21use crate::caldav;
22use crate::ical;
23use crate::ical::Ical;
24#[cfg(feature = "serde")]
25use serde::{Deserialize, Serialize};
26use ureq::Agent;
27use url::Url;
28
29pub use crate::credentials::Credentials;
30
31pub fn get_calendars(
33 agent: Agent,
34 credentials: &Credentials,
35 base_url: &Url,
36) -> Result<Vec<Calendar>, Error> {
37 let calendar_refs = caldav::get_calendars(agent, credentials, base_url)?;
38 let mut calendars = Vec::new();
39 for calendar_ref in calendar_refs {
40 calendars.push(Calendar {
41 base_url: base_url.clone(),
42 inner: calendar_ref,
43 });
44 }
45 Ok(calendars)
46}
47
48pub fn get_todos(
52 agent: Agent,
53 credentials: &Credentials,
54 calendar: &Calendar,
55) -> Result<(Vec<Event>, Vec<Error>), Error> {
56 let todo_refs = caldav::get_todos(agent, credentials, &calendar.base_url, &calendar.inner)?;
57 let mut todos = Vec::new();
58 let mut errors = Vec::new();
59 for todo_ref in todo_refs {
60 let lines = ical::LineIterator::new(&todo_ref.data);
61 match ical::Ical::parse(&lines) {
62 Ok(ical) => todos.push(Event {
63 url: todo_ref.url.clone(),
64 etag: todo_ref.etag.clone(),
65 ical,
66 }),
67 Err(e) => errors.push(Error::Ical(format!(
68 "Could not parse todo {}: {:?}",
69 todo_ref.data, e
70 ))),
71 }
72 }
73 Ok((todos, errors))
74}
75
76pub fn get_events(
80 agent: Agent,
81 credentials: &Credentials,
82 calendar: &Calendar,
83) -> Result<(Vec<Event>, Vec<Error>), Error> {
84 let event_refs = caldav::get_events(agent, credentials, &calendar.base_url, calendar.url())?;
85 let mut events = Vec::new();
86 let mut errors = Vec::new();
87 for event_ref in event_refs {
88 let lines = ical::LineIterator::new(&event_ref.data);
89 match ical::Ical::parse(&lines) {
90 Ok(ical) => events.push(Event {
91 url: event_ref.url.clone(),
92 etag: event_ref.etag.clone(),
93 ical,
94 }),
95 Err(e) => errors.push(Error::Ical(format!(
96 "Could not parse event {}: {:?}",
97 event_ref.data, e
98 ))),
99 }
100 }
101 Ok((events, errors))
102}
103
104pub fn parse_ical(raw: &str) -> Result<Ical, Error> {
106 let lines = ical::LineIterator::new(raw);
107 match ical::Ical::parse(&lines) {
108 Ok(ical) => Ok(ical),
109 Err(e) => Err(Error::Ical(format!("Could not parse event: {:?}", e))),
110 }
111}
112
113pub fn save_event(
115 agent: Agent,
116 credentials: &Credentials,
117 mut event: Event,
118) -> Result<Event, Error> {
119 for prop in &mut event.ical.properties {
120 if prop.name == "SEQUENCE" {
121 if let Ok(num) = prop.value.parse::<i64>() {
122 prop.value = format!("{}", num + 1);
123 }
124 }
125 }
126
127 let event_ref = caldav::EventRef {
128 data: event.ical.serialize(),
129 etag: None,
130 url: event.url,
131 };
132 let event_ref = caldav::save_event(agent, credentials, event_ref)?;
133 Ok(Event {
134 etag: event_ref.etag,
135 url: event_ref.url,
136 ..event
137 })
138}
139
140pub fn remove_event(agent: Agent, credentials: &Credentials, event: Event) -> Result<(), Error> {
142 let event_ref = caldav::EventRef {
143 data: event.ical.serialize(),
144 etag: event.etag,
145 url: event.url,
146 };
147 caldav::remove_event(agent, credentials, event_ref)?;
148 Ok(())
149}
150
151#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
153#[derive(Debug, Clone)]
154pub struct Calendar {
155 base_url: Url,
156 inner: caldav::CalendarRef,
157}
158
159impl Calendar {
160 pub fn url(&self) -> &Url {
161 &self.inner.url
162 }
163 pub fn name(&self) -> &String {
164 &self.inner.name
165 }
166 pub fn color(&self) -> Option<&String> {
167 self.inner.color.as_ref()
168 }
169}
170
171#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
172#[derive(Debug, Clone, Eq, PartialEq)]
174pub struct Event {
175 etag: Option<String>,
176 url: Url,
177 ical: ical::Ical,
178}
179
180impl Event {
181 pub fn new(etag: Option<String>, url: Url, ical: ical::Ical) -> Self {
183 Self { etag, url, ical }
184 }
185
186 pub fn url(&self) -> &Url {
188 &self.url
189 }
190
191 fn get_property(&self, name: &str, datatype: &str) -> Option<Property> {
192 self.ical.get(datatype).and_then(|ical| {
193 ical.properties.iter().find_map(|p| {
194 if p.name == name {
195 Some(Property::from(p.clone()))
196 } else {
197 None
198 }
199 })
200 })
201 }
202
203 pub fn pop_property(&mut self, name: &str) -> Option<Property> {
205 self.ical.get_mut("VEVENT").and_then(|ical| {
206 let index = ical.properties.iter().enumerate().find_map(|(i, p)| {
207 if p.name == name {
208 Some(i)
209 } else {
210 None
211 }
212 });
213
214 if let Some(index) = index {
215 Some(Property::from(ical.properties.remove(index)))
216 } else {
217 None
218 }
219 })
220 }
221
222 pub fn ical(&self) -> &Ical {
223 &self.ical
224 }
225
226 pub fn add(&mut self, property: Property) {
227 if let Some(ical) = self.ical.get_mut("VEVENT") {
228 ical.properties.push(property.into());
229 }
230 }
231
232 pub fn property(&self, name: &str) -> Option<Property> {
233 self.get_property(name, "VEVENT")
234 }
235
236 pub fn property_todo(&self, name: &str) -> Option<Property> {
238 self.get_property(name, "VTODO")
239 }
240
241 pub fn get(&self, name: &str) -> Option<&String> {
243 self.ical.get("VEVENT").and_then(|ical| {
244 ical.properties
245 .iter()
246 .find_map(|p| if p.name == name { Some(&p.value) } else { None })
247 })
248 }
249
250 pub fn set(&mut self, name: &str, value: &str) {
252 match self
253 .ical
254 .get_mut("VEVENT")
255 .and_then(|e| e.properties.iter_mut().find(|p| p.name == name))
256 {
257 Some(p) => {
258 p.value = value.into();
259 }
260 None => self.add(Property::new(name, value)),
261 }
262 }
263
264 pub fn set_property_attribute(&mut self, name: &str, attr_name: &str, attr_value: &str) {
265 if let Some(p) = self
266 .ical
267 .get_mut("VEVENT")
268 .and_then(|e| e.properties.iter_mut().find(|p| p.name == name))
269 {
270 p.attributes.insert(attr_name.into(), attr_value.into());
271 }
272 }
273
274 fn get_properties(&self, datatype: &str) -> Vec<(&String, &String)> {
276 self.ical
277 .get(datatype)
278 .map(|ical| {
279 ical.properties
280 .iter()
281 .map(|p| (&p.name, &p.value))
282 .collect::<Vec<(&String, &String)>>()
283 })
284 .unwrap_or_else(|| {
285 error!("Could not get properties: No VEVENT section.");
286 Vec::new()
287 })
288 }
289
290 pub fn into_properties(self) -> Vec<Property> {
291 for ical in self.ical.children {
292 if ical.name == "VEVENT" {
293 return ical.properties.into_iter().map(Property::from).collect();
294 }
295 }
296 Vec::new()
297 }
298
299 pub fn properties(&self) -> Vec<(&String, &String)> {
301 self.get_properties("VEVENT")
302 }
303
304 pub fn properties_todo(&self) -> Vec<(&String, &String)> {
306 self.get_properties("VTODO")
307 }
308
309 pub fn etag(&self) -> Option<&String> {
310 self.etag.as_ref()
311 }
312
313 pub fn builder(url: Url) -> EventBuilder {
314 EventBuilder {
315 url,
316 etag: None,
317 properties: vec![],
318 }
319 }
320
321 pub fn set_etag(&mut self, etag: Option<String>) {
322 self.etag = etag
323 }
324}
325
326#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
327#[derive(Debug, Clone, PartialEq, Eq)]
328pub struct Property {
329 name: String,
330 value: String,
331 attributes: HashMap<String, String>,
332}
333
334impl Property {
335 pub fn new(name: &str, value: &str) -> Self {
336 Self {
337 name: name.into(),
338 value: value.into(),
339 attributes: Default::default(),
340 }
341 }
342 pub fn name(&self) -> &String {
343 &self.name
344 }
345
346 pub fn value(&self) -> &String {
347 &self.value
348 }
349
350 pub fn into_value(self) -> String {
351 self.value
352 }
353
354 pub fn attribute(&self, name: &str) -> Option<&String> {
355 self.attributes.get(name)
356 }
357}
358
359impl From<ical::Property> for Property {
360 fn from(p: ical::Property) -> Self {
361 Self {
362 name: p.name,
363 value: p.value,
364 attributes: p.attributes,
365 }
366 }
367}
368
369impl From<Property> for ical::Property {
370 fn from(p: Property) -> Self {
371 Self {
372 name: p.name,
373 value: p.value,
374 attributes: p.attributes,
375 }
376 }
377}
378
379#[derive(Debug)]
381pub enum Error {
382 Ical(String),
383 Caldav(String),
384}
385
386impl std::error::Error for Error {}
387
388impl std::fmt::Display for Error {
389 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
390 match self {
391 Error::Ical(msg) => write!(f, "CalDAV Error: '{}'", msg),
392 Error::Caldav(msg) => write!(f, "ICAL Error: '{}'", msg),
393 }
394 }
395}
396
397impl From<caldav::Error> for Error {
398 fn from(e: caldav::Error) -> Self {
399 Error::Ical(e.message)
400 }
401}
402
403impl From<ical::Error> for Error {
404 fn from(e: ical::Error) -> Self {
405 Error::Caldav(e.message)
406 }
407}
408
409#[derive(Debug)]
410pub struct EventBuilder {
411 url: Url,
412 etag: Option<String>,
413 properties: Vec<ical::Property>,
414}
415
416impl EventBuilder {
417 fn build_event(self, name: String) -> Event {
418 Event {
419 etag: self.etag,
420 url: self.url,
421 ical: ical::Ical {
422 name: "VCALENDAR".into(),
423 properties: vec![],
424 children: vec![ical::Ical {
425 name,
426 properties: self.properties,
427 children: vec![],
428 }],
429 },
430 }
431 }
432
433 pub fn build(self) -> Event {
434 self.build_event("VEVENT".into())
435 }
436
437 pub fn build_todo(self) -> Event {
438 self.build_event("VTODO".into())
439 }
440
441 pub fn etag(mut self, etag: Option<String>) -> Self {
442 self.etag = etag;
443 self
444 }
445
446 pub fn uid(mut self, value: String) -> Self {
447 self.properties.push(ical::Property {
448 name: "UID".to_string(),
449 value,
450 attributes: HashMap::new(),
451 });
452 self
453 }
454
455 pub fn timestamp(mut self, value: String) -> Self {
456 self.properties.push(ical::Property {
457 name: "DTSTAMP".to_string(),
458 value,
459 attributes: HashMap::new(),
460 });
461 self
462 }
463
464 pub fn summary(mut self, value: String) -> Self {
465 self.properties.push(ical::Property {
466 name: "SUMMARY".to_string(),
467 value,
468 attributes: HashMap::new(),
469 });
470 self
471 }
472
473 pub fn priority(mut self, value: String) -> Self {
474 self.properties.push(ical::Property {
475 name: "PRIORITY".to_string(),
476 value,
477 attributes: HashMap::new(),
478 });
479 self
480 }
481
482 pub fn duedate(mut self, value: String) -> Self {
483 self.properties.push(ical::Property {
484 name: "DUE".to_string(),
485 value,
486 attributes: HashMap::new(),
487 });
488 self
489 }
490
491 pub fn status(mut self, value: String) -> Self {
492 self.properties.push(ical::Property {
493 name: "STATUS".to_string(),
494 value,
495 attributes: HashMap::new(),
496 });
497 self
498 }
499
500 pub fn generic(mut self, name: String, value: String) -> Self {
501 self.properties.push(ical::Property {
502 name,
503 value,
504 attributes: HashMap::new(),
505 });
506 self
507 }
508
509 pub fn location(mut self, value: Option<String>) -> Self {
510 if let Some(value) = value {
511 self.properties.push(ical::Property {
512 name: "LOCATION".to_string(),
513 value,
514 attributes: HashMap::new(),
515 });
516 }
517 self
518 }
519
520 pub fn start(mut self, value: String, attributes: Vec<(&str, &str)>) -> Self {
521 let mut attribs = HashMap::new();
522 for (k, v) in attributes {
523 attribs.insert(k.into(), v.into());
524 }
525 self.properties.push(ical::Property {
526 name: "DTSTART".to_string(),
527 value,
528 attributes: attribs,
529 });
530 self
531 }
532
533 pub fn end(mut self, value: String, attributes: Vec<(&str, &str)>) -> Self {
534 let mut attribs = HashMap::new();
535 for (k, v) in attributes {
536 attribs.insert(k.into(), v.into());
537 }
538 self.properties.push(ical::Property {
539 name: "DTEND".to_string(),
540 value,
541 attributes: attribs,
542 });
543 self
544 }
545
546 pub fn description(mut self, value: Option<String>) -> Self {
547 if let Some(value) = value {
548 self.properties.push(ical::Property {
549 name: "DESCRIPTION".to_string(),
550 value,
551 attributes: HashMap::new(),
552 });
553 }
554 self
555 }
556
557 pub fn rrule(mut self, value: Option<String>) -> Self {
558 if let Some(value) = value {
559 self.properties.push(ical::Property {
560 name: "RRULE".to_string(),
561 value,
562 attributes: HashMap::new(),
563 });
564 }
565 self
566 }
567}