From 3238f8517097745032e19b3e26f57f0465a00b28 Mon Sep 17 00:00:00 2001 From: metamuffin Date: Fri, 10 Jun 2022 10:47:16 +0200 Subject: move to workspace --- karld/src/condition.rs | 364 +++++++++++++++++++++++++++++++++++++++++++++++++ karld/src/interface.rs | 69 ++++++++++ karld/src/main.rs | 64 +++++++++ karld/src/protocol.rs | 34 +++++ 4 files changed, 531 insertions(+) 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 (limited to 'karld/src') 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, +} -- cgit v1.2.3-70-g09d2