1use crate::db::DB;
2use anyhow::{anyhow, Result};
3use serde::{Deserialize, Serialize};
4use std::collections::BTreeMap;
5
6pub type Schedule = (chrono::NaiveTime, chrono::NaiveTime);
7pub type Notifications = Vec<fancy_duration::FancyDuration<chrono::Duration>>;
8
9#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
10pub enum RecordType {
11 At,
12 Schedule,
13 AllDay,
14}
15
16#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
17pub struct PresentedSchedule {
18 start: chrono::NaiveTime,
19 stop: chrono::NaiveTime,
20}
21
22impl std::fmt::Display for PresentedSchedule {
23 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24 f.write_str(&format!(
25 "{} - {}",
26 self.start.format("%H:%M"),
27 self.stop.format("%H:%M")
28 ))
29 }
30}
31
32impl From<PresentedSchedule> for Schedule {
33 fn from(ps: PresentedSchedule) -> Self {
34 (ps.start, ps.stop)
35 }
36}
37
38impl From<Schedule> for PresentedSchedule {
39 fn from(s: Schedule) -> Self {
40 Self {
41 start: s.0,
42 stop: s.1,
43 }
44 }
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
48pub struct Fields(BTreeMap<String, Vec<String>>);
49
50impl std::ops::Deref for Fields {
51 type Target = BTreeMap<String, Vec<String>>;
52
53 fn deref(&self) -> &Self::Target {
54 &self.0
55 }
56}
57
58impl std::fmt::Display for Fields {
59 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60 if self.is_empty() {
61 return Ok(());
62 }
63
64 let mut fields = String::new();
65 for (key, value) in &self.0 {
66 if value.len() > 1 {
67 fields += &format!("[{}: {}], ", key, value.len());
68 } else if !value.is_empty() {
69 fields += &format!("[{}: {}], ", key, value[0]);
70 }
71 }
72
73 fields.remove(fields.len() - 1);
75 fields.remove(fields.len() - 1);
76 f.write_str(&fields)
77 }
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
81pub struct PresentedRecord {
82 pub date: chrono::NaiveDate,
83 #[serde(rename = "type")]
84 pub typ: RecordType,
85 #[serde(skip_serializing_if = "Option::is_none")]
86 pub at: Option<chrono::NaiveTime>,
87 #[serde(skip_serializing_if = "Option::is_none")]
88 pub scheduled: Option<PresentedSchedule>,
89 pub detail: String,
90 pub fields: Fields,
91 #[serde(skip_serializing_if = "Option::is_none")]
92 pub notifications: Option<Notifications>,
93 pub completed: bool,
94}
95
96impl From<Record> for PresentedRecord {
97 fn from(value: Record) -> Self {
98 Self {
99 date: value.date,
100 typ: value.typ,
101 at: value.at,
102 scheduled: value.scheduled.map(|x| x.into()),
103 detail: value.detail,
104 fields: value.fields,
105 notifications: value.notifications,
106 completed: value.completed,
107 }
108 }
109}
110
111impl PresentedRecord {
112 pub fn to_record(
113 self,
114 primary_key: u64,
115 recurrence_key: Option<u64>,
116 internal_key: Option<String>,
117 internal_recurrence_key: Option<String>,
118 ) -> Record {
119 Record {
120 primary_key,
121 recurrence_key,
122 internal_key,
123 internal_recurrence_key,
124 date: self.date,
125 typ: self.typ,
126 at: self.at,
127 scheduled: self.scheduled.map(|x| x.into()),
128 detail: self.detail,
129 fields: self.fields,
130 notifications: self.notifications,
131 completed: self.completed,
132 }
133 }
134}
135
136#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
137pub struct PresentedRecurringRecord {
138 pub record: PresentedRecord,
139 pub recurrence: fancy_duration::FancyDuration<chrono::Duration>,
140}
141
142impl From<RecurringRecord> for PresentedRecurringRecord {
143 fn from(value: RecurringRecord) -> Self {
144 Self {
145 record: value.record.into(),
146 recurrence: value.recurrence,
147 }
148 }
149}
150impl PresentedRecurringRecord {
151 pub fn to_record(
152 self,
153 primary_key: u64,
154 recurrence_key: u64,
155 internal_key: Option<String>,
156 internal_recurrence_key: Option<String>,
157 ) -> RecurringRecord {
158 RecurringRecord {
159 internal_key: internal_key.clone(),
160 recurrence_key,
161 record: self.record.to_record(
162 primary_key,
163 Some(recurrence_key),
164 internal_key,
165 internal_recurrence_key,
166 ),
167 recurrence: self.recurrence,
168 }
169 }
170}
171
172#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
173pub struct RecurringRecord {
174 record: Record,
175 recurrence: fancy_duration::FancyDuration<chrono::Duration>,
176 recurrence_key: u64,
177 internal_key: Option<String>,
178}
179
180#[derive(Clone, Debug)]
181enum RuleFrequency {
182 Daily,
183 Monthly,
184 Weekly,
185 Yearly,
186}
187
188impl ToString for RuleFrequency {
189 fn to_string(&self) -> String {
190 match self {
191 RuleFrequency::Daily => "daily",
192 RuleFrequency::Monthly => "monthly",
193 RuleFrequency::Yearly => "yearly",
194 RuleFrequency::Weekly => "weekly",
195 }
196 .to_uppercase()
197 }
198}
199
200impl std::str::FromStr for RuleFrequency {
201 type Err = anyhow::Error;
202
203 fn from_str(s: &str) -> Result<Self, Self::Err> {
204 match s.to_lowercase().as_str() {
205 "daily" => Ok(RuleFrequency::Daily),
206 "yearly" => Ok(RuleFrequency::Yearly),
207 "monthly" => Ok(RuleFrequency::Monthly),
208 "weekly" => Ok(RuleFrequency::Weekly),
209 _ => Err(anyhow!("Invalid frequency {}", s)),
210 }
211 }
212}
213
214impl RecurringRecord {
215 pub fn new(
216 record: Record,
217 recurrence: fancy_duration::FancyDuration<chrono::Duration>,
218 ) -> Self {
219 Self {
220 record,
221 recurrence,
222 recurrence_key: 0,
223 internal_key: None,
224 }
225 }
226
227 pub fn from_rrule(record: Record, rrule: String) -> Result<Self> {
228 let parts = rrule.split(':').collect::<Vec<&str>>();
229
230 if parts[0] == "RRULE" {
231 let tokens = parts[1]
232 .split(';')
233 .map(|s| s.split('=').collect::<Vec<&str>>());
234 let mut freq: Option<RuleFrequency> = None;
235 let mut interval: Option<i64> = None;
236
237 for pair in tokens {
238 match pair[0] {
239 "FREQ" => {
240 freq = Some(pair[1].parse()?);
241 }
242 "INTERVAL" => {
243 interval = Some(pair[1].parse()?);
244 }
245 _ => {}
246 }
247
248 if freq.is_some() && interval.is_some() {
249 break;
250 }
251 }
252
253 if let Some(freq) = freq {
254 if let Some(interval) = interval {
255 return Ok(Self::new(
256 record,
257 fancy_duration::FancyDuration::new(match freq {
258 RuleFrequency::Daily => chrono::TimeDelta::try_days(interval).unwrap_or_default(),
259 RuleFrequency::Yearly => chrono::TimeDelta::try_weeks(interval).unwrap_or_default() * 52,
260 RuleFrequency::Weekly => chrono::TimeDelta::try_weeks(interval).unwrap_or_default(),
261 RuleFrequency::Monthly => chrono::TimeDelta::try_days(interval).unwrap_or_default() * 30,
262 }),
263 ));
264 }
265 }
266 }
267
268 Err(anyhow!("Recurring data cannot be parsed"))
269 }
270
271 pub fn to_rrule(&self) -> String {
272 let recur = self.recurrence.duration();
273
274 let freq = if recur < chrono::TimeDelta::try_days(30).unwrap_or_default() {
275 ("DAILY", recur.num_days())
276 } else if recur < chrono::TimeDelta::try_weeks(52).unwrap_or_default() {
277 ("MONTHLY", recur.num_days() / 30)
278 } else {
279 ("YEARLY", recur.num_weeks() * 52)
280 };
281
282 format!("RRULE:FREQ={};INTERVAL={}", freq.0, freq.1)
283 }
284
285 pub fn record(&mut self) -> &mut Record {
286 &mut self.record
287 }
288
289 pub fn recurrence(&self) -> fancy_duration::FancyDuration<chrono::Duration> {
290 self.recurrence.clone()
291 }
292
293 pub fn recurrence_key(&self) -> u64 {
294 self.recurrence_key
295 }
296
297 pub fn set_record(&mut self, record: Record) {
298 self.record = record;
299 }
300
301 pub fn set_recurrence_key(&mut self, key: u64) {
302 self.recurrence_key = key;
303 self.record().set_recurrence_key(Some(key));
304 }
305
306 pub fn internal_key(&self) -> Option<String> {
307 self.internal_key.clone()
308 }
309
310 pub fn set_internal_key(&mut self, key: Option<String>) {
311 self.internal_key = key.clone();
312 self.record().set_internal_recurrence_key(key);
313 }
314
315 pub fn record_from(&self, primary_key: u64, from: chrono::NaiveDateTime) -> Record {
316 let mut record = self.record.clone();
317 record.set_primary_key(primary_key);
318 record.set_recurrence_key(Some(self.recurrence_key));
319 record.set_internal_recurrence_key(self.internal_key.clone());
320 record.set_date(from.date());
321 match record.record_type() {
322 RecordType::At => {
323 record.set_at(Some(from.time()));
324 }
325 RecordType::AllDay => {}
326 RecordType::Schedule => {
327 let schedule = record.scheduled().unwrap();
328 let duration = schedule.1 - schedule.0;
329 record.set_scheduled(Some((from.time(), from.time() + duration)));
330 }
331 };
332 record
333 }
334}
335
336#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
337pub struct Record {
338 primary_key: u64,
339 recurrence_key: Option<u64>,
340 internal_key: Option<String>,
341 internal_recurrence_key: Option<String>,
342 date: chrono::NaiveDate,
343 typ: RecordType,
344 at: Option<chrono::NaiveTime>,
345 scheduled: Option<Schedule>,
346 detail: String,
347 fields: Fields,
348 notifications: Option<Notifications>,
349 completed: bool,
350}
351
352impl Default for Record {
353 fn default() -> Self {
354 let now = chrono::Local::now();
355 Self {
356 primary_key: 0,
357 recurrence_key: None,
358 internal_key: None,
359 internal_recurrence_key: None,
360 date: now.date_naive(),
361 typ: RecordType::AllDay,
362 at: None,
363 scheduled: None,
364 detail: String::new(),
365 fields: Fields::default(),
366 notifications: None,
367 completed: false,
368 }
369 }
370}
371
372impl Record {
373 pub fn primary_key(&self) -> u64 {
374 self.primary_key
375 }
376
377 pub fn recurrence_key(&self) -> Option<u64> {
378 self.recurrence_key
379 }
380
381 pub fn internal_recurrence_key(&self) -> Option<String> {
382 self.internal_recurrence_key.clone()
383 }
384
385 pub fn internal_key(&self) -> Option<String> {
386 self.internal_key.clone()
387 }
388
389 pub fn set_internal_key(&mut self, key: Option<String>) {
390 self.internal_key = key
391 }
392
393 pub fn record_type(&self) -> RecordType {
394 self.typ.clone()
395 }
396
397 pub fn datetime(&self) -> chrono::DateTime<chrono::Local> {
398 let time = match self.record_type() {
399 RecordType::At => self.at.unwrap(),
400 RecordType::AllDay => chrono::NaiveTime::from_hms_opt(0, 0, 0).unwrap(),
401 RecordType::Schedule => self.scheduled.unwrap().0,
402 };
403
404 chrono::NaiveDateTime::new(self.date, time)
405 .and_local_timezone(chrono::Local::now().timezone())
406 .unwrap()
407 }
408
409 pub fn completed(&self) -> bool {
410 self.completed
411 }
412
413 pub fn date(&self) -> chrono::NaiveDate {
414 self.date
415 }
416
417 pub fn at(&self) -> Option<chrono::NaiveTime> {
418 self.at
419 }
420
421 pub fn scheduled(&self) -> Option<Schedule> {
422 self.scheduled
423 }
424
425 pub fn all_day(&self) -> bool {
426 matches!(self.typ, RecordType::AllDay)
427 }
428
429 pub fn detail(&self) -> String {
430 self.detail.clone()
431 }
432
433 pub fn fields(&self) -> Fields {
434 self.fields.clone()
435 }
436
437 pub fn set_fields(&mut self, fields: Fields) {
438 self.fields = fields
439 }
440
441 pub fn notifications(&self) -> Option<Notifications> {
442 self.notifications.clone()
443 }
444
445 pub fn build() -> Self {
446 Self::default()
447 }
448
449 pub async fn record(&self, mut db: crate::db::memory::MemoryDB) -> Result<()> {
450 db.record(self.clone()).await
451 }
452
453 pub fn set_internal_recurrence_key(&mut self, internal_recurrence_key: Option<String>) {
454 self.internal_recurrence_key = internal_recurrence_key
455 }
456
457 pub fn set_primary_key(&mut self, primary_key: u64) -> &mut Self {
458 self.primary_key = primary_key;
459 self
460 }
461
462 pub fn set_recurrence_key(&mut self, key: Option<u64>) -> &mut Self {
463 self.recurrence_key = key;
464 self
465 }
466
467 pub fn set_record_type(&mut self, typ: RecordType) -> &mut Self {
468 self.typ = typ;
469 self
470 }
471
472 pub fn set_all_day(&mut self) -> &mut Self {
473 self.at = None;
474 self.scheduled = None;
475 self.typ = RecordType::AllDay;
476 self
477 }
478
479 pub fn set_completed(&mut self, completed: bool) -> &mut Self {
480 self.completed = completed;
481 self
482 }
483
484 pub fn set_date(&mut self, date: chrono::NaiveDate) -> &mut Self {
485 self.date = date;
486 self
487 }
488
489 pub fn set_at(&mut self, at: Option<chrono::NaiveTime>) -> &mut Self {
490 self.at = at;
491 self.scheduled = None;
492 self.typ = RecordType::At;
493 self
494 }
495
496 pub fn set_scheduled(&mut self, schedule: Option<Schedule>) -> &mut Self {
497 self.scheduled = schedule;
498 self.at = None;
499 self.typ = RecordType::Schedule;
500 self
501 }
502
503 pub fn set_detail(&mut self, detail: String) -> &mut Self {
504 self.detail = detail;
505 self
506 }
507
508 pub fn add_field(&mut self, field: String, content: String) -> &mut Self {
509 let mut v = self.fields.0.get(&field).unwrap_or(&Vec::new()).to_owned();
510 v.push(content);
511 self.fields.0.insert(field, v);
512 self
513 }
514
515 pub fn get_field(&self, field: String) -> Option<Vec<String>> {
516 self.fields.0.get(&field).cloned()
517 }
518
519 pub fn add_notification(&mut self, notification: chrono::Duration) -> &mut Self {
520 let notification = fancy_duration::FancyDuration::new(notification);
521 if let Some(notifications) = &mut self.notifications {
522 notifications.push(notification)
523 } else {
524 self.notifications = Some(vec![notification])
525 }
526
527 self
528 }
529
530 pub fn set_notifications(&mut self, notifications: Option<Notifications>) {
531 self.notifications = notifications
532 }
533}
534
535pub fn sort_records(a: &Record, b: &Record) -> std::cmp::Ordering {
536 let cmp = a.date().cmp(&b.date());
537 if cmp == std::cmp::Ordering::Equal {
538 match a.record_type() {
539 RecordType::At => {
540 if let Some(a_at) = a.at() {
541 if let Some(b_at) = b.at() {
542 a_at.cmp(&b_at)
543 } else if let Some(b_schedule) = b.scheduled() {
544 a_at.cmp(&b_schedule.0)
545 } else {
546 std::cmp::Ordering::Equal
547 }
548 } else {
549 std::cmp::Ordering::Equal
550 }
551 }
552 RecordType::AllDay => {
553 if b.record_type() == RecordType::AllDay {
554 a.primary_key().cmp(&b.primary_key())
555 } else {
556 std::cmp::Ordering::Less
557 }
558 }
559 RecordType::Schedule => {
560 if let Some(a_schedule) = a.scheduled() {
561 if let Some(b_schedule) = b.scheduled() {
562 a_schedule.0.cmp(&b_schedule.0)
563 } else if let Some(b_at) = b.at() {
564 a_schedule.0.cmp(&b_at)
565 } else {
566 std::cmp::Ordering::Equal
567 }
568 } else {
569 std::cmp::Ordering::Equal
570 }
571 }
572 }
573 } else {
574 cmp
575 }
576}