From d6370aa55d8f53897e8380a60dd7efe7fcff031b Mon Sep 17 00:00:00 2001 From: Astro Date: Mon, 24 May 2021 23:25:24 +0200 Subject: [PATCH] ticker-update: use rrule --- Cargo.lock | 33 +++++++++++++++ default.nix | 4 +- ticker-update/Cargo.toml | 1 + ticker-update/src/main.rs | 88 ++++++++++++++++++++++++++++----------- 4 files changed, 99 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2782067..e5a9b8e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -264,6 +264,16 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "chrono-tz" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2554a3155fec064362507487171dcc4edc3df60cb10f3a1fb10ed8094822b120" +dependencies = [ + "chrono", + "parse-zoneinfo", +] + [[package]] name = "cloudabi" version = "0.0.3" @@ -1462,6 +1472,15 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "parse-zoneinfo" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c705f256449c60da65e11ff6626e0c16a0a0b96aaa348de61376b249bc340f41" +dependencies = [ + "regex", +] + [[package]] name = "percent-encoding" version = "1.0.1" @@ -1884,6 +1903,19 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "rrule" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1ccf2e8be78bd6a32bccace1b6ae378bf093e4c4cc7c16a8a8ec3795fcf19c6" +dependencies = [ + "chrono", + "chrono-tz", + "lazy_static", + "regex", + "serde", +] + [[package]] name = "rust-argon2" version = "0.8.3" @@ -2311,6 +2343,7 @@ dependencies = [ "libticker", "num_cpus", "reqwest", + "rrule", ] [[package]] diff --git a/default.nix b/default.nix index 43d0509..8833701 100644 --- a/default.nix +++ b/default.nix @@ -24,11 +24,11 @@ let in { ticker-update = build { pname = "ticker-update"; - cargoSha256 = "04hnbwn7h5cq8pmklq7d374v64x7nqnd6brsbhdard2my5k7sd8q"; + cargoSha256 = "0limgs2lwx5vgi7x2wwwrk0z83xs4x59k56yy4ifwwz19h09knma"; }; ticker-serve = (build { pname = "ticker-serve"; - cargoSha256 = "1gr7hp0dbf53bamfwdlc2yxixglbspqq4wjqhrv5ls6njq4by33p"; + cargoSha256 = "116mhi8z9n5qmjkpzfslq4bjhgsjy1flag6lsp78v30pyghks8xx"; }).overrideAttrs (oa: { postBuild = '' ${oa.postBuild} diff --git a/ticker-update/Cargo.toml b/ticker-update/Cargo.toml index 11bcd3d..b047d79 100644 --- a/ticker-update/Cargo.toml +++ b/ticker-update/Cargo.toml @@ -11,4 +11,5 @@ diesel = { version = "~1", features = ["postgres", "chrono"] } chrono = "~0.4" num_cpus = "~1" crossbeam = "~0.7" +rrule = "0.5" libticker = { path = "../libticker" } diff --git a/ticker-update/src/main.rs b/ticker-update/src/main.rs index b4baecd..00cd3c7 100644 --- a/ticker-update/src/main.rs +++ b/ticker-update/src/main.rs @@ -1,10 +1,12 @@ +use std::str::FromStr; use std::mem::replace; use std::io::Read; use std::sync::RwLock; use std::collections::{HashMap, VecDeque}; -use chrono::offset::Utc; +use chrono::{Duration, offset::Utc}; use reqwest::header::{IF_NONE_MATCH, IF_MODIFIED_SINCE, ETAG, LAST_MODIFIED, USER_AGENT}; use diesel::{Connection, pg::PgConnection, prelude::*}; +use rrule::RRule; use libticker::{ config::{Config, CalendarOptions}, @@ -23,30 +25,71 @@ fn extract_vevent_objs(results: &mut Vec, mut obj: Object) { } } -fn obj_to_event(calendar: String, obj: &Object) -> Option { +const RRULE_LOOKBACK: i64 = 30; +const RRULE_LOOKAHEAD: i64 = 366; + +fn obj_to_events(calendar: String, obj: &Object) -> Vec { if obj.name != "VEVENT" { - return None; + return vec![]; } - let dtstart: Timestamp = obj.get("DTSTART")?; + let (dtstart, dtstart_str): (Timestamp, &str) = match (obj.get("DTSTART"), obj.get("DTSTART")) { + (Some(dtstart), Some(dtstart_str)) => (dtstart, dtstart_str), + _ => return vec![], + }; let dtstart = dtstart.or_hms(0, 0, 0); let dtend: Option = obj.get("DTEND"); let dtend = dtend.map(|time| time.or_hms(23, 59, 59)); - let summary = obj.get("SUMMARY")?; - let id = format!("{}{}{}{}", - obj.get("UID").unwrap_or(""), - dtstart, - obj.get("DTSTAMP").unwrap_or(""), - obj.get("RECURRENCE-ID").unwrap_or("")); - // TODO: DESCRIPTION - Some(Event { - calendar, id, - dtstart, - dtend, - summary, - location: obj.get("LOCATION"), - url: obj.get("URL"), - }) + let summary: &str = match obj.get("SUMMARY") { + Some(summary) => summary, + None => return vec![], + }; + let uid = obj.get("UID").unwrap_or(""); + let dtstamp = obj.get("DTSTAMP").unwrap_or(""); + let location = obj.get("LOCATION"); + let url = obj.get("URL"); + + let generate_event = |dtstart| { + let id = format!("{}{}{}", uid, dtstart, dtstamp); + Event { + calendar: calendar.clone(), + id, + dtstart, + dtend, + summary: summary.to_owned(), + location: location.clone(), + url: url.clone(), + } + }; + + let rrule_str: Option<&str> = obj.get("rrule"); + let rrule = rrule_str.and_then( + |rrule_str| RRule::from_str( + &format!("DTSTART:{}\nRRULE:{}", dtstart_str, rrule_str) + ).map_err(|e| println!("Error parsing RRULE: {}", e)) + .ok() + ); + match rrule { + Some(rrule) => { + let now = Utc::now(); + let start = now - Duration::days(RRULE_LOOKBACK); + let end = now + Duration::days(RRULE_LOOKAHEAD); + println!("rrule {}:\n{:?}", rrule_str.unwrap_or(""), rrule.into_iter() + .skip_while(|d| *d < start) + .take_while(|d| *d <= end) + .map(|dtstart| dtstart.naive_utc()) + .collect::>() + ); + rrule.into_iter() + .skip_while(|d| *d < start) + .take_while(|d| *d <= end) + .map(|dtstart| dtstart.naive_utc()) + .map(generate_event) + .collect() + } + None => + vec![generate_event(dtstart)], + } } pub struct Resources { @@ -105,13 +148,8 @@ impl Resources { let mut objs = vec![]; extract_vevent_objs(&mut objs, obj); for obj in objs { - if let Some(event) = obj_to_event(cal_id.to_owned(), &obj) { + for event in obj_to_events(cal_id.to_owned(), &obj) { events.insert(event.id.clone(), event); - } else { - let dtstart: Option<&str> = obj.get("DTSTART"); - let summary: Option<&str> = obj.get("SUMMARY"); - println!("cannot convert {} {:?} {:?}", - obj.name, dtstart, summary); } } });