ticker-update: use rrule

This commit is contained in:
Astro 2021-05-24 23:25:24 +02:00
parent ba5b4de68d
commit d6370aa55d
4 changed files with 99 additions and 27 deletions

33
Cargo.lock generated
View File

@ -264,6 +264,16 @@ dependencies = [
"winapi 0.3.9", "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]] [[package]]
name = "cloudabi" name = "cloudabi"
version = "0.0.3" version = "0.0.3"
@ -1462,6 +1472,15 @@ dependencies = [
"winapi 0.3.9", "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]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "1.0.1" version = "1.0.1"
@ -1884,6 +1903,19 @@ dependencies = [
"winapi 0.3.9", "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]] [[package]]
name = "rust-argon2" name = "rust-argon2"
version = "0.8.3" version = "0.8.3"
@ -2311,6 +2343,7 @@ dependencies = [
"libticker", "libticker",
"num_cpus", "num_cpus",
"reqwest", "reqwest",
"rrule",
] ]
[[package]] [[package]]

View File

@ -24,11 +24,11 @@ let
in { in {
ticker-update = build { ticker-update = build {
pname = "ticker-update"; pname = "ticker-update";
cargoSha256 = "04hnbwn7h5cq8pmklq7d374v64x7nqnd6brsbhdard2my5k7sd8q"; cargoSha256 = "0limgs2lwx5vgi7x2wwwrk0z83xs4x59k56yy4ifwwz19h09knma";
}; };
ticker-serve = (build { ticker-serve = (build {
pname = "ticker-serve"; pname = "ticker-serve";
cargoSha256 = "1gr7hp0dbf53bamfwdlc2yxixglbspqq4wjqhrv5ls6njq4by33p"; cargoSha256 = "116mhi8z9n5qmjkpzfslq4bjhgsjy1flag6lsp78v30pyghks8xx";
}).overrideAttrs (oa: { }).overrideAttrs (oa: {
postBuild = '' postBuild = ''
${oa.postBuild} ${oa.postBuild}

View File

@ -11,4 +11,5 @@ diesel = { version = "~1", features = ["postgres", "chrono"] }
chrono = "~0.4" chrono = "~0.4"
num_cpus = "~1" num_cpus = "~1"
crossbeam = "~0.7" crossbeam = "~0.7"
rrule = "0.5"
libticker = { path = "../libticker" } libticker = { path = "../libticker" }

View File

@ -1,10 +1,12 @@
use std::str::FromStr;
use std::mem::replace; use std::mem::replace;
use std::io::Read; use std::io::Read;
use std::sync::RwLock; use std::sync::RwLock;
use std::collections::{HashMap, VecDeque}; 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 reqwest::header::{IF_NONE_MATCH, IF_MODIFIED_SINCE, ETAG, LAST_MODIFIED, USER_AGENT};
use diesel::{Connection, pg::PgConnection, prelude::*}; use diesel::{Connection, pg::PgConnection, prelude::*};
use rrule::RRule;
use libticker::{ use libticker::{
config::{Config, CalendarOptions}, config::{Config, CalendarOptions},
@ -23,30 +25,71 @@ fn extract_vevent_objs(results: &mut Vec<Object>, mut obj: Object) {
} }
} }
fn obj_to_event(calendar: String, obj: &Object) -> Option<Event> { const RRULE_LOOKBACK: i64 = 30;
const RRULE_LOOKAHEAD: i64 = 366;
fn obj_to_events(calendar: String, obj: &Object) -> Vec<Event> {
if obj.name != "VEVENT" { 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 dtstart = dtstart.or_hms(0, 0, 0);
let dtend: Option<Timestamp> = obj.get("DTEND"); let dtend: Option<Timestamp> = obj.get("DTEND");
let dtend = dtend.map(|time| time.or_hms(23, 59, 59)); let dtend = dtend.map(|time| time.or_hms(23, 59, 59));
let summary = obj.get("SUMMARY")?; let summary: &str = match obj.get("SUMMARY") {
let id = format!("{}{}{}{}", Some(summary) => summary,
obj.get("UID").unwrap_or(""), None => return vec![],
dtstart, };
obj.get("DTSTAMP").unwrap_or(""), let uid = obj.get("UID").unwrap_or("");
obj.get("RECURRENCE-ID").unwrap_or("")); let dtstamp = obj.get("DTSTAMP").unwrap_or("");
// TODO: DESCRIPTION let location = obj.get("LOCATION");
Some(Event { let url = obj.get("URL");
calendar, id,
dtstart, let generate_event = |dtstart| {
dtend, let id = format!("{}{}{}", uid, dtstart, dtstamp);
summary, Event {
location: obj.get("LOCATION"), calendar: calendar.clone(),
url: obj.get("URL"), 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::<Vec<_>>()
);
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 { pub struct Resources {
@ -105,13 +148,8 @@ impl Resources {
let mut objs = vec![]; let mut objs = vec![];
extract_vevent_objs(&mut objs, obj); extract_vevent_objs(&mut objs, obj);
for obj in objs { 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); 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);
} }
} }
}); });