From 3238f8517097745032e19b3e26f57f0465a00b28 Mon Sep 17 00:00:00 2001 From: metamuffin Date: Fri, 10 Jun 2022 10:47:16 +0200 Subject: move to workspace --- Cargo.lock | 31 +++-- Cargo.toml | 15 +- karlc/Cargo.toml | 8 ++ karlc/src/main.rs | 3 + karld/Cargo.lock | 314 ++++++++++++++++++++++++++++++++++++++++++ karld/Cargo.toml | 14 ++ karld/protocol.d.ts | 75 ++++++++++ karld/src/condition.rs | 364 ++++++++++++++++++++++++++++++++++++++++++++++++ karld/src/interface.rs | 69 ++++++++++ karld/src/main.rs | 64 +++++++++ karld/src/protocol.rs | 34 +++++ protocol.d.ts | 63 --------- src/condition.rs | 365 ------------------------------------------------- src/interface.rs | 68 --------- src/main.rs | 44 ------ src/protocol.rs | 32 ----- 16 files changed, 965 insertions(+), 598 deletions(-) create mode 100644 karlc/Cargo.toml create mode 100644 karlc/src/main.rs create mode 100644 karld/Cargo.lock create mode 100644 karld/Cargo.toml create mode 100644 karld/protocol.d.ts create mode 100644 karld/src/condition.rs create mode 100644 karld/src/interface.rs create mode 100644 karld/src/main.rs create mode 100644 karld/src/protocol.rs delete mode 100644 protocol.d.ts delete mode 100644 src/condition.rs delete mode 100644 src/interface.rs delete mode 100644 src/main.rs delete mode 100644 src/protocol.rs diff --git a/Cargo.lock b/Cargo.lock index bcb7a9f..7cdf259 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -34,19 +34,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" -[[package]] -name = "calender-thing" -version = "0.1.1" -dependencies = [ - "anyhow", - "chrono", - "crossbeam-channel", - "env_logger", - "log", - "serde", - "serde_json", -] - [[package]] name = "cfg-if" version = "1.0.0" @@ -120,6 +107,24 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" +[[package]] +name = "karlc" +version = "0.1.0" + +[[package]] +name = "karld" +version = "0.1.1" +dependencies = [ + "anyhow", + "chrono", + "crossbeam-channel", + "env_logger", + "lazy_static", + "log", + "serde", + "serde_json", +] + [[package]] name = "lazy_static" version = "1.4.0" diff --git a/Cargo.toml b/Cargo.toml index 2008cc8..f392c53 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,13 +1,2 @@ -[package] -name = "calender-thing" -version = "0.1.1" -edition = "2021" - -[dependencies] -serde = { version = "1.0.137", features = ["derive"] } -anyhow = "1.0.57" -log = "0.4.17" -env_logger = "0.9.0" -crossbeam-channel = "0.5.4" -serde_json = "1.0.81" -chrono = "0.4.19" +[workspace] +members = ["karld","karlc"] \ No newline at end of file diff --git a/karlc/Cargo.toml b/karlc/Cargo.toml new file mode 100644 index 0000000..7e43bed --- /dev/null +++ b/karlc/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "karlc" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/karlc/src/main.rs b/karlc/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/karlc/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/karld/Cargo.lock b/karld/Cargo.lock new file mode 100644 index 0000000..2b4b0b9 --- /dev/null +++ b/karld/Cargo.lock @@ -0,0 +1,314 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "time", + "winapi", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aaa7bd5fb665c6864b5f963dd9097905c54125909c7aa94c9e18507cdbe6c53" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" +dependencies = [ + "cfg-if", + "lazy_static", +] + +[[package]] +name = "env_logger" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "itoa" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" + +[[package]] +name = "karld" +version = "0.1.1" +dependencies = [ + "anyhow", + "chrono", + "crossbeam-channel", + "env_logger", + "lazy_static", + "log", + "serde", + "serde_json", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "proc-macro2" +version = "1.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" + +[[package]] +name = "ryu" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" + +[[package]] +name = "serde" +version = "1.0.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "syn" +version = "1.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi", + "winapi", +] + +[[package]] +name = "unicode-ident" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/karld/Cargo.toml b/karld/Cargo.toml new file mode 100644 index 0000000..4c2221d --- /dev/null +++ b/karld/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "karld" +version = "0.1.1" +edition = "2021" + +[dependencies] +serde = { version = "1.0.137", features = ["derive"] } +anyhow = "1.0.57" +log = "0.4.17" +env_logger = "0.9.0" +crossbeam-channel = "0.5.4" +serde_json = "1.0.81" +chrono = "0.4.19" +lazy_static = "1.4.0" diff --git a/karld/protocol.d.ts b/karld/protocol.d.ts new file mode 100644 index 0000000..a592146 --- /dev/null +++ b/karld/protocol.d.ts @@ -0,0 +1,75 @@ + +// { type: "handshake", version: "10"} +// { type: "handshake", data: {version: "10"}} +//! { "handshake": {version: "10"}} + +export type ServerboundPacket = Download | UpdateTask | RemoveTask +export type ClientboundPacket = Handshake | DownloadResponse + +interface Handshake { + type: "handshake" + data: { version: string } +} + +interface Download { + type: "download", + data: null +} +interface DownloadResponse { + type: "download_response", + data: { tasks: Task[] } +} + +interface UpdateTask { + type: "update_task", + data: Task +} +interface RemoveTask { + type: "remove_task", + data: Task +} + +interface Task { + id: number + name: string, + description: string, + + tags: string[], + priority: number, + + completed?: number, + scheduled?: number, + + occurence?: Condition, + deadline?: Condition, +} + +export type Condition = { from?: Condition } + | { or?: Condition[] } + | { and?: Condition[] } + | { equal?: { prop: Thing, value: number, mod?: number } } + | { range?: { prop: Thing, min: number, max: number, mod?: number } } + +type Thing = "year" + | "monthofyear" + | "weekofmonth" + | "dayofyear" + | "dayofmonth" + | "dayofweek" + | "hour" + | "minute" + | "second" + | "unix" + +/* + examples: + + 11:00 - 12:00 every first monday of the month + + and: [ + { range: { prop: "hour", min: 11, max: 12 } }, + { equal: { prop: "dayofweek", value: 0 } }, + { equal: { prop: "weekofmonth", value: 0 } } + ] + +*/ diff --git a/karld/src/condition.rs b/karld/src/condition.rs new file mode 100644 index 0000000..830e8ae --- /dev/null +++ b/karld/src/condition.rs @@ -0,0 +1,364 @@ +use chrono::{Datelike, Duration, NaiveDate, NaiveDateTime, NaiveTime, Timelike}; +use serde::{Deserialize, Serialize}; +use std::cmp::{max, min}; +use Direction::*; +use Edge::*; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum Condition { + From(Box), + + Or(Vec), + And(Vec), + Invert(Box), + + Equal { + prop: Property, + value: i64, + modulus: Option, + }, + Range { + prop: Property, + min: i64, + max: i64, + modulus: Option, + }, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum Property { + Year, + Monthofyear, + Weekofmonth, + Dayofyear, + Dayofmonth, + Dayofweek, + Hour, + Minute, + Second, + Unix, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Range(T, T); +impl Range { + pub fn includes(&self, a: T) -> bool { + a > self.0 && a < self.1 + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Edge { + Start, + End, +} +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Direction { + Forward, + Backward, +} + +impl Condition { + pub fn find( + &self, + edge: Edge, + dir: Direction, + mut from: NaiveDateTime, + ) -> Option { + match self { + Condition::And(cs) => loop { + // TODO improve efficiency for backward search + let last_start = cs + .iter() + .map(|c| c.find(Start, dir, from)) + .reduce(|a, b| Some(max(a?, b?)))?; + let first_end = cs + .iter() + .map(|c| c.find(End, dir, from)) + .reduce(|a, b| Some(min(a?, b?)))?; + match last_start { + Some(start) => match first_end { + Some(end) => { + if end > start { + break Some(match edge { + Start => start, + End => end, + }); + } else { + from = start - Duration::seconds(10); // TODO proper fix + } + } + None => break Some(start), + }, + None => break None, + } + }, + Condition::Or(cs) => { + cs.iter() + .filter_map(|c| c.find(edge, dir, from)) + .reduce(match dir { + Forward => min, + Backward => max, + }) + } + Condition::From(_c) => todo!(), + Condition::Invert(c) => c.find(edge.invert(), dir, from), + Condition::Equal { + prop, + value, + modulus: _, + } => { + let value = *value; + let off: i64 = match edge { + Start => 0, + End => 1, + }; + let dir_off = match dir { + Forward => 0, + Backward => -1, + }; + match prop { + Property::Year => { + let geq = match dir { + Forward => |a, b| a >= b, + Backward => |a, b| a < b, + }; + + if geq(from.year(), (value + off) as i32) { + None + } else { + Some(NaiveDateTime::new( + NaiveDate::from_ymd((value + off) as i32, 1, 1), + NaiveTime::from_hms(0, 0, 0), + )) + } + } + Property::Monthofyear => { + let rollover = (value + off) / 12; + let value = (value + off) % 12; + // month still coming for this year + if from.month0() < value as u32 { + Some(NaiveDateTime::new( + NaiveDate::from_ymd( + from.year() + (rollover + dir_off) as i32, + value as u32 + 1, + 1, + ), + NaiveTime::from_hms(0, 0, 0), + )) + } else { + Some(NaiveDateTime::new( + NaiveDate::from_ymd( + from.year() + (rollover + dir_off + 1) as i32, + value as u32 + 1, + 1, + ), + NaiveTime::from_hms(0, 0, 0), + )) + } + } + Property::Weekofmonth => todo!(), + Property::Dayofyear => { + todo!() + } + Property::Dayofmonth => { + // let mut target = NaiveDateTime::new( + // NaiveDate::from_ymd(from.year(), from.month(), value as u32), + // NaiveTime::from_hms(0, 0, 0), + // ); + // if edge == End { + // target += Duration::days(1) + // } + // fn increment_month(d: NaiveDateTime) -> NaiveDateTime { + // NaiveDateTime::new( + // NaiveDate::from_ymd( + // d.year() + (d.month() as i32 / 12), + // (d.month() + 1) % 12, + // d.day(), + // ), + // NaiveTime::from_hms(d.hour(), d.minute(), d.second()), + // ) + // } + // let dir_off = match dir { + // Forward => |d| d, + // Backward => increment_month, + // }; + // if target > from { + // Some(dir_off(target)) + // } else { + // Some(increment_month(dir_off(target))) + // } + todo!() + } + Property::Dayofweek => todo!(), + Property::Hour => { + let mut target = NaiveDateTime::new( + NaiveDate::from_ymd(from.year(), from.month(), from.day()), + NaiveTime::from_hms(value as u32, 0, 0), + ); + if edge == End { + target += Duration::hours(1) + } + let dir_off = match dir { + Forward => Duration::zero(), + Backward => Duration::days(-1), + }; + if target > from { + Some(target + dir_off) + } else { + Some(target + dir_off + Duration::days(1)) + } + } + Property::Minute => { + let mut target = NaiveDateTime::new( + NaiveDate::from_ymd(from.year(), from.month(), from.day()), + NaiveTime::from_hms(from.hour(), value as u32, 0), + ); + if edge == End { + target += Duration::minutes(1) + } + let dir_off = match dir { + Forward => Duration::zero(), + Backward => Duration::hours(-1), + }; + if target > from { + Some(target + dir_off) + } else { + Some(target + dir_off + Duration::hours(1)) + } + } + Property::Second => { + let mut target = NaiveDateTime::new( + NaiveDate::from_ymd(from.year(), from.month(), from.day()), + NaiveTime::from_hms(from.hour(), from.minute(), value as u32), + ); + if edge == End { + target += Duration::seconds(1) + } + let dir_off = match dir { + Forward => Duration::zero(), + Backward => Duration::minutes(-1), + }; + if target > from { + Some(target + dir_off) + } else { + Some(target + dir_off + Duration::minutes(1)) + } + } + Property::Unix => { + let geq = match dir { + Forward => |a, b| a >= b, + Backward => |a, b| a < b, + }; + if geq(from.timestamp(), (value + off) as i64) { + None + } else { + Some(NaiveDateTime::from_timestamp(value, 0)) + } + } + } + } + Condition::Range { + prop: _, + min: _, + max: _, + modulus: _, + } => todo!(), + } + } +} + +impl Edge { + pub fn invert(self) -> Self { + match self { + Edge::Start => Edge::End, + Edge::End => Edge::Start, + } + } +} + +#[cfg(test)] +mod test { + use super::{Condition, Direction, Edge, Property}; + use chrono::{NaiveDateTime, Utc}; + use std::str::FromStr; + use Direction::*; + use Edge::*; + + #[test] + fn blub() { + let cond = Condition::And(vec![ + Condition::Equal { + modulus: None, + prop: Property::Monthofyear, + value: 1, + }, + Condition::Equal { + modulus: None, + prop: Property::Hour, + value: 12, + }, + ]); + // let cond = Condition::Equal { + // modulus: None, + // prop: Property::Hour, + // value: 12, + // }; + let dt = Utc::now().naive_utc(); + println!("START FORWARD => {:?}", cond.find(Start, Forward, dt)); + println!("END FORWARD => {:?}", cond.find(End, Forward, dt)); + println!("START BACKWARD => {:?}", cond.find(Start, Backward, dt)); + println!("END BACKWARD => {:?}", cond.find(End, Backward, dt)); + } + + #[test] + fn year_equal() { + let cond = Condition::Equal { + modulus: None, + prop: Property::Year, + value: 2023, + }; + + let dt = NaiveDateTime::from_str("2022-06-07T13:37:48").unwrap(); + assert_eq!( + cond.find(Edge::Start, Direction::Forward, dt).unwrap(), + NaiveDateTime::from_str("2023-01-01T00:00:00").unwrap(), + ); + assert_eq!( + cond.find(Edge::End, Direction::Forward, dt).unwrap(), + NaiveDateTime::from_str("2024-01-01T00:00:00").unwrap(), + ); + assert_eq!(cond.find(Edge::End, Direction::Backward, dt), None); + assert_eq!(cond.find(Edge::Start, Direction::Backward, dt), None); + } + + #[test] + fn month_equal() { + let cond = Condition::Equal { + modulus: None, + prop: Property::Monthofyear, + value: 3, + }; + + let dt = NaiveDateTime::from_str("2022-06-07T13:37:48").unwrap(); + + assert_eq!( + cond.find(Edge::Start, Direction::Forward, dt), + Some(NaiveDateTime::from_str("2023-04-01T00:00:00").unwrap()) + ); + assert_eq!( + cond.find(Edge::End, Direction::Forward, dt), + Some(NaiveDateTime::from_str("2023-05-01T00:00:00").unwrap()) + ); + assert_eq!( + cond.find(Edge::Start, Direction::Backward, dt), + Some(NaiveDateTime::from_str("2022-04-01T00:00:00").unwrap()) + ); + assert_eq!( + cond.find(Edge::End, Direction::Backward, dt), + Some(NaiveDateTime::from_str("2022-05-01T00:00:00").unwrap()) + ); + } +} diff --git a/karld/src/interface.rs b/karld/src/interface.rs new file mode 100644 index 0000000..e3d2ba3 --- /dev/null +++ b/karld/src/interface.rs @@ -0,0 +1,69 @@ +use super::protocol::{ClientboundPacket, ServerboundPacket}; +use crate::handle_packet; +use log::{debug, error, info, warn}; +use std::io; +use std::io::{BufRead, BufReader, ErrorKind, Write}; +use std::os::unix::net::{UnixListener, UnixStream}; +use std::thread; + +pub fn network_loop() { + let listener = UnixListener::bind("/run/user/1000/calendar").unwrap(); + info!("listening."); + let mut id_counter = 0; + + loop { + let (stream, addr) = listener.accept().unwrap(); + let id = id_counter; + id_counter += 1; + thread::spawn(move || { + info!("client connected: {:?}", addr); + if let Err(err) = handle_connection(id, stream) { + warn!("client dropped: {:?} ({})", addr, err); + } else { + info!("client dropped: {:?}", addr); + } + }); + } +} + +fn handle_connection(id: u32, mut stream: UnixStream) -> io::Result<()> { + let mut reader = BufReader::new(stream.try_clone()?); + let (responder, responses) = crossbeam_channel::unbounded(); + responder + .send(ClientboundPacket::Handshake { + version: env!("CARGO_PKG_VERSION").to_string(), + }) + .unwrap(); + thread::spawn(move || { + for m in responses { + debug!("{id} -> {m:?}"); + match stream + .write_fmt(format_args!("{}\n", serde_json::to_string(&m).unwrap())) + .map_err(|e| e.kind()) + { + Ok(_) => (), + Err(ErrorKind::BrokenPipe) => break, + Err(e) => error!("network error: {:?}", e), + } + } + }); + { + let mut buf = String::new(); + loop { + if reader.read_line(&mut buf)? == 0 { + break Ok(()); + }; + match serde_json::from_str::(buf.as_str()) { + Ok(packet) => { + debug!("{id} <- {packet:?}"); + handle_packet(id, packet, responder.clone()); + } + Err(err) => responder + .send(ClientboundPacket::Error(format!("{}", &err))) + .map_err(|_| io::Error::from(ErrorKind::InvalidInput))?, + } + + buf.clear(); + } + } +} diff --git a/karld/src/main.rs b/karld/src/main.rs new file mode 100644 index 0000000..ae49ad3 --- /dev/null +++ b/karld/src/main.rs @@ -0,0 +1,64 @@ +pub mod condition; +pub mod interface; +pub mod protocol; + +use std::{collections::HashMap, sync::RwLock}; + +use crate::{ + condition::{Condition, Property}, + protocol::Task, +}; +use crossbeam_channel::Sender; +use interface::network_loop; +use protocol::{ClientboundPacket, ServerboundPacket}; + +fn main() { + env_logger::init(); + TASKS.write().unwrap().insert( + 0, + Task { + id: 0, + name: "blub".to_string(), + description: "blob".to_string(), + tags: vec![], + priority: 69.0, + completed: None, + scheduled: None, + occurence: Some(Condition::And(vec![ + Condition::Equal { + modulus: None, + prop: Property::Monthofyear, + value: 1, + }, + Condition::Equal { + modulus: None, + prop: Property::Hour, + value: 12, + }, + ])), + deadline: None, + }, + ); + network_loop(); +} + +lazy_static::lazy_static! { + static ref TASKS: RwLock> = RwLock::new(HashMap::new()); +} + +pub fn handle_packet(client: u32, packet: ServerboundPacket, responder: Sender) { + println!("{:?}, {:?}, {:?}", client, packet, responder); + match packet { + ServerboundPacket::Download => { + let _ = responder.send(ClientboundPacket::DownloadResponse( + TASKS.read().unwrap().values().map(|e| e.clone()).collect(), + )); + } + ServerboundPacket::UpdateTask(t) => { + TASKS.write().unwrap().insert(t.id, t); + } + ServerboundPacket::RemoveTask(i) => { + TASKS.write().unwrap().remove(&i); + } + } +} diff --git a/karld/src/protocol.rs b/karld/src/protocol.rs new file mode 100644 index 0000000..40ab0b2 --- /dev/null +++ b/karld/src/protocol.rs @@ -0,0 +1,34 @@ +use crate::condition::Condition; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "type", content = "data", rename_all = "snake_case")] +pub enum ClientboundPacket { + Handshake { version: String }, + Error(String), + DownloadResponse(Vec), +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "type", content = "data", rename_all = "snake_case")] +pub enum ServerboundPacket { + Download, + UpdateTask(Task), + RemoveTask(u64), +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Task { + pub id: u64, + pub name: String, + pub description: String, + + pub tags: Vec, + pub priority: f64, + + pub completed: Option, + pub scheduled: Option, + + pub occurence: Option, + pub deadline: Option, +} diff --git a/protocol.d.ts b/protocol.d.ts deleted file mode 100644 index 79949b8..0000000 --- a/protocol.d.ts +++ /dev/null @@ -1,63 +0,0 @@ - -// { type: "handshake", version: "10"} -// { type: "handshake", data: {version: "10"}} -//! { "handshake": {version: "10"}} - -export type ServerboundPacket = Download -export type ClientboundPacket = Handshake | DownloadResponse - -interface Handshake { - type: "handshake" - data: { version: string } -} - -interface Download { - type: "download", - data: null -} -interface DownloadResponse { - type: "download_response", - data: { tasks: Task[] } -} - -interface Task { - name: string, - description: string, - - tags: string[], - priority: number, - - completed?: number, - scheduled?: number, - - occurence?: Condition, - deadline?: Condition, -} - -/* - 11:00 - 12:00 every first monday of the month - - and: [ - range: {prop: "hour", min: 11, max: 12}, - equal: {prop: "dayofweek", value: 0}, - equal: {prop: "weekofmonth", value: 0}, - ] -*/ - -// should only have one property -export type Condition = { from?: Condition } - | { or?: Condition[] } - | { and?: Condition[] } - | { equal?: { prop: Thing, value: number, mod?: number } } - | { range?: { prop: Thing, min: number, max: number, mod?: number } } - -type Thing = "year" - | "monthofyear" - | "weekofmonth" - | "dayofyear" - | "dayofmonth" - | "dayofweek" - | "hour" - | "minute" - | "second" - | "unix" \ No newline at end of file diff --git a/src/condition.rs b/src/condition.rs deleted file mode 100644 index bc1b3ec..0000000 --- a/src/condition.rs +++ /dev/null @@ -1,365 +0,0 @@ -use std::cmp::{max, min}; - -use chrono::{Datelike, Duration, NaiveDate, NaiveDateTime, NaiveTime, Timelike}; -use serde::{Deserialize, Serialize}; -use Direction::*; -use Edge::*; - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum Condition { - From(Box), - - Or(Vec), - And(Vec), - Invert(Box), - - Equal { - prop: Property, - value: i64, - modulus: Option, - }, - Range { - prop: Property, - min: i64, - max: i64, - modulus: Option, - }, -} - -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum Property { - Year, - Monthofyear, - Weekofmonth, - Dayofyear, - Dayofmonth, - Dayofweek, - Hour, - Minute, - Second, - Unix, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Range(T, T); -impl Range { - pub fn includes(&self, a: T) -> bool { - a > self.0 && a < self.1 - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Edge { - Start, - End, -} -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Direction { - Forward, - Backward, -} - -impl Condition { - pub fn find( - &self, - edge: Edge, - dir: Direction, - mut from: NaiveDateTime, - ) -> Option { - match self { - Condition::And(cs) => loop { - // TODO improve efficiency for backward search - let last_start = cs - .iter() - .map(|c| c.find(Start, dir, from)) - .reduce(|a, b| Some(max(a?, b?)))?; - let first_end = cs - .iter() - .map(|c| c.find(End, dir, from)) - .reduce(|a, b| Some(min(a?, b?)))?; - match last_start { - Some(start) => match first_end { - Some(end) => { - if end > start { - break Some(match edge { - Start => start, - End => end, - }); - } else { - from = start - Duration::seconds(10); // TODO proper fix - } - } - None => break Some(start), - }, - None => break None, - } - }, - Condition::Or(cs) => { - cs.iter() - .filter_map(|c| c.find(edge, dir, from)) - .reduce(match dir { - Forward => min, - Backward => max, - }) - } - Condition::From(_c) => todo!(), - Condition::Invert(c) => c.find(edge.invert(), dir, from), - Condition::Equal { - prop, - value, - modulus: _, - } => { - let value = *value; - let off: i64 = match edge { - Start => 0, - End => 1, - }; - let dir_off = match dir { - Forward => 0, - Backward => -1, - }; - match prop { - Property::Year => { - let geq = match dir { - Forward => |a, b| a >= b, - Backward => |a, b| a < b, - }; - - if geq(from.year(), (value + off) as i32) { - None - } else { - Some(NaiveDateTime::new( - NaiveDate::from_ymd((value + off) as i32, 1, 1), - NaiveTime::from_hms(0, 0, 0), - )) - } - } - Property::Monthofyear => { - let rollover = (value + off) / 12; - let value = (value + off) % 12; - // month still coming for this year - if from.month0() < value as u32 { - Some(NaiveDateTime::new( - NaiveDate::from_ymd( - from.year() + (rollover + dir_off) as i32, - value as u32 + 1, - 1, - ), - NaiveTime::from_hms(0, 0, 0), - )) - } else { - Some(NaiveDateTime::new( - NaiveDate::from_ymd( - from.year() + (rollover + dir_off + 1) as i32, - value as u32 + 1, - 1, - ), - NaiveTime::from_hms(0, 0, 0), - )) - } - } - Property::Weekofmonth => todo!(), - Property::Dayofyear => { - todo!() - } - Property::Dayofmonth => { - // let mut target = NaiveDateTime::new( - // NaiveDate::from_ymd(from.year(), from.month(), value as u32), - // NaiveTime::from_hms(0, 0, 0), - // ); - // if edge == End { - // target += Duration::days(1) - // } - // fn increment_month(d: NaiveDateTime) -> NaiveDateTime { - // NaiveDateTime::new( - // NaiveDate::from_ymd( - // d.year() + (d.month() as i32 / 12), - // (d.month() + 1) % 12, - // d.day(), - // ), - // NaiveTime::from_hms(d.hour(), d.minute(), d.second()), - // ) - // } - // let dir_off = match dir { - // Forward => |d| d, - // Backward => increment_month, - // }; - // if target > from { - // Some(dir_off(target)) - // } else { - // Some(increment_month(dir_off(target))) - // } - todo!() - } - Property::Dayofweek => todo!(), - Property::Hour => { - let mut target = NaiveDateTime::new( - NaiveDate::from_ymd(from.year(), from.month(), from.day()), - NaiveTime::from_hms(value as u32, 0, 0), - ); - if edge == End { - target += Duration::hours(1) - } - let dir_off = match dir { - Forward => Duration::zero(), - Backward => Duration::days(-1), - }; - if target > from { - Some(target + dir_off) - } else { - Some(target + dir_off + Duration::days(1)) - } - } - Property::Minute => { - let mut target = NaiveDateTime::new( - NaiveDate::from_ymd(from.year(), from.month(), from.day()), - NaiveTime::from_hms(from.hour(), value as u32, 0), - ); - if edge == End { - target += Duration::minutes(1) - } - let dir_off = match dir { - Forward => Duration::zero(), - Backward => Duration::hours(-1), - }; - if target > from { - Some(target + dir_off) - } else { - Some(target + dir_off + Duration::hours(1)) - } - } - Property::Second => { - let mut target = NaiveDateTime::new( - NaiveDate::from_ymd(from.year(), from.month(), from.day()), - NaiveTime::from_hms(from.hour(), from.minute(), value as u32), - ); - if edge == End { - target += Duration::seconds(1) - } - let dir_off = match dir { - Forward => Duration::zero(), - Backward => Duration::minutes(-1), - }; - if target > from { - Some(target + dir_off) - } else { - Some(target + dir_off + Duration::minutes(1)) - } - } - Property::Unix => { - let geq = match dir { - Forward => |a, b| a >= b, - Backward => |a, b| a < b, - }; - if geq(from.timestamp(), (value + off) as i64) { - None - } else { - Some(NaiveDateTime::from_timestamp(value, 0)) - } - } - } - } - Condition::Range { - prop: _, - min: _, - max: _, - modulus: _, - } => todo!(), - } - } -} - -impl Edge { - pub fn invert(self) -> Self { - match self { - Edge::Start => Edge::End, - Edge::End => Edge::Start, - } - } -} - -#[cfg(test)] -mod test { - use super::{Condition, Direction, Edge, Property}; - use chrono::{NaiveDateTime, Utc}; - use std::str::FromStr; - use Direction::*; - use Edge::*; - - #[test] - fn blub() { - let cond = Condition::And(vec![ - Condition::Equal { - modulus: None, - prop: Property::Monthofyear, - value: 1, - }, - Condition::Equal { - modulus: None, - prop: Property::Hour, - value: 12, - }, - ]); - // let cond = Condition::Equal { - // modulus: None, - // prop: Property::Hour, - // value: 12, - // }; - let dt = Utc::now().naive_utc(); - println!("START FORWARD => {:?}", cond.find(Start, Forward, dt)); - println!("END FORWARD => {:?}", cond.find(End, Forward, dt)); - println!("START BACKWARD => {:?}", cond.find(Start, Backward, dt)); - println!("END BACKWARD => {:?}", cond.find(End, Backward, dt)); - } - - #[test] - fn year_equal() { - let cond = Condition::Equal { - modulus: None, - prop: Property::Year, - value: 2023, - }; - - let dt = NaiveDateTime::from_str("2022-06-07T13:37:48").unwrap(); - assert_eq!( - cond.find(Edge::Start, Direction::Forward, dt).unwrap(), - NaiveDateTime::from_str("2023-01-01T00:00:00").unwrap(), - ); - assert_eq!( - cond.find(Edge::End, Direction::Forward, dt).unwrap(), - NaiveDateTime::from_str("2024-01-01T00:00:00").unwrap(), - ); - assert_eq!(cond.find(Edge::End, Direction::Backward, dt), None); - assert_eq!(cond.find(Edge::Start, Direction::Backward, dt), None); - } - - #[test] - fn month_equal() { - let cond = Condition::Equal { - modulus: None, - prop: Property::Monthofyear, - value: 3, - }; - - let dt = NaiveDateTime::from_str("2022-06-07T13:37:48").unwrap(); - - assert_eq!( - cond.find(Edge::Start, Direction::Forward, dt), - Some(NaiveDateTime::from_str("2023-04-01T00:00:00").unwrap()) - ); - assert_eq!( - cond.find(Edge::End, Direction::Forward, dt), - Some(NaiveDateTime::from_str("2023-05-01T00:00:00").unwrap()) - ); - assert_eq!( - cond.find(Edge::Start, Direction::Backward, dt), - Some(NaiveDateTime::from_str("2022-04-01T00:00:00").unwrap()) - ); - assert_eq!( - cond.find(Edge::End, Direction::Backward, dt), - Some(NaiveDateTime::from_str("2022-05-01T00:00:00").unwrap()) - ); - } -} diff --git a/src/interface.rs b/src/interface.rs deleted file mode 100644 index fcdb470..0000000 --- a/src/interface.rs +++ /dev/null @@ -1,68 +0,0 @@ -use super::protocol::{ClientboundPacket, ServerboundPacket}; -use crate::handle_packet; -use log::{debug, error, info, warn}; -use std::io; -use std::io::{BufRead, BufReader, ErrorKind, Write}; -use std::os::unix::net::{UnixListener, UnixStream}; -use std::thread; - -pub fn network_loop() { - let listener = UnixListener::bind("/run/user/1000/calendar").unwrap(); - let mut id_counter = 0; - - loop { - let (stream, addr) = listener.accept().unwrap(); - let id = id_counter; - id_counter += 1; - thread::spawn(move || { - info!("client connected: {:?}", addr); - if let Err(err) = handle_connection(id, stream.try_clone().unwrap()) { - warn!("client dropped: {:?} ({})", addr, err); - } else { - info!("client dropped: {:?}", addr); - } - }); - } -} - -fn handle_connection(id: u32, mut stream: UnixStream) -> io::Result<()> { - let mut reader = BufReader::new(stream.try_clone()?); - let (responder, responses) = crossbeam_channel::unbounded(); - responder - .send(ClientboundPacket::Handshake { - version: env!("CARGO_PKG_VERSION").to_string(), - }) - .unwrap(); - thread::spawn(move || { - for m in responses { - debug!("{id} -> {m:?}"); - match stream - .write_fmt(format_args!("{}\n", serde_json::to_string(&m).unwrap())) - .map_err(|e| e.kind()) - { - Ok(_) => (), - Err(ErrorKind::BrokenPipe) => break, - Err(e) => error!("network error: {:?}", e), - } - } - }); - { - let mut buf = String::new(); - loop { - if reader.read_line(&mut buf)? == 0 { - break Ok(()); - }; - match serde_json::from_str::(buf.as_str()) { - Ok(packet) => { - debug!("{id} <- {packet:?}"); - handle_packet(id, packet, responder.clone()); - } - Err(err) => responder - .send(ClientboundPacket::Error(format!("{}", &err))) - .map_err(|_| io::Error::from(ErrorKind::InvalidInput))?, - } - - buf.clear(); - } - } -} diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 9a84286..0000000 --- a/src/main.rs +++ /dev/null @@ -1,44 +0,0 @@ -pub mod condition; -pub mod interface; -pub mod protocol; - -use crate::{ - condition::{Condition, Property}, - protocol::Task, -}; -use crossbeam_channel::Sender; -use interface::network_loop; -use protocol::{ClientboundPacket, ServerboundPacket}; - -fn main() { - network_loop(); -} - -pub fn handle_packet(client: u32, packet: ServerboundPacket, responder: Sender) { - println!("{:?}, {:?}, {:?}", client, packet, responder); - match packet { - ServerboundPacket::Download => { - let _ = responder.send(ClientboundPacket::DownloadResponse(vec![Task { - name: "blub".to_string(), - description: "blob".to_string(), - tags: vec![], - priority: 69.0, - completed: None, - scheduled: None, - occurence: Some(Condition::And(vec![ - Condition::Equal { - modulus: None, - prop: Property::Monthofyear, - value: 1, - }, - Condition::Equal { - modulus: None, - prop: Property::Hour, - value: 12, - }, - ])), - deadline: None, - }])); - } - } -} diff --git a/src/protocol.rs b/src/protocol.rs deleted file mode 100644 index af89263..0000000 --- a/src/protocol.rs +++ /dev/null @@ -1,32 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::condition::Condition; - -#[derive(Debug, Serialize, Deserialize)] -#[serde(tag = "type", content = "data", rename_all = "snake_case")] -pub enum ClientboundPacket { - Handshake { version: String }, - Error(String), - DownloadResponse(Vec), -} - -#[derive(Debug, Serialize, Deserialize)] -#[serde(tag = "type", content = "data", rename_all = "snake_case")] -pub enum ServerboundPacket { - Download, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct Task { - pub name: String, - pub description: String, - - pub tags: Vec, - pub priority: f64, - - pub completed: Option, - pub scheduled: Option, - - pub occurence: Option, - pub deadline: Option, -} -- cgit v1.2.3-70-g09d2