From 55772f9b596e5cafd328695f0dc7f5741543c187 Mon Sep 17 00:00:00 2001 From: cos Date: Fri, 7 Jun 2024 08:35:58 +0200 Subject: explore: Unsuccessful attempt at using kaldav crate --- src/bin/caldav-crates.rs | 87 ++++++++++++++++++++++++ src/bin/ccc.rs | 170 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 257 insertions(+) create mode 100644 src/bin/caldav-crates.rs create mode 100644 src/bin/ccc.rs (limited to 'src/bin') diff --git a/src/bin/caldav-crates.rs b/src/bin/caldav-crates.rs new file mode 100644 index 0000000..258b248 --- /dev/null +++ b/src/bin/caldav-crates.rs @@ -0,0 +1,87 @@ +use { + anyhow::{ + Result, + + anyhow, + }, + std::env::args, +}; + +struct CrateKaldav { + client: kaldav::Client, +} + +impl CrateKaldav { + fn new(url: &str, username: String, password: Option) -> Self { + println!("kaldav"); + + let mut client = kaldav::Client::new(url); + client.set_auth(Some(kaldav::Authorization { username, password, })); + + Self { + client, + } + } + + fn show_content(&self) -> Result<()> { + // This function does not do what we want, because Principal::home() does not appear to + // work in our environment. + // Reading https://datatracker.ietf.org/doc/html/rfc4791#section-6.2.1 reveals that the + // CALDAV:calendar-home-set property merely SHOULD, rather than MUST, be implemented. + + let principals = self.client.principals()?; + // eprintln!("Principals: {principals:?}"); + + let mut home = None; + println!("Attempting to find home."); + for p in principals { + home = p.home().ok(); + if home.is_some() { + break; + } + } + if home.is_some() { + println!("Found home: {home:?}"); + } else { + return Err(anyhow!("No home found in any Principal. :(")); + } + + // This function is heavily based on: + // https://github.com/sanpii/kaldav/blob/main/examples/list_events.rs + eprintln!("Retrieving list of calendars."); + let calendars = self.client.calendars()?; + + for (name, calendar) in calendars { + println!("Calendar '{}'", name); + + let objects = calendar.events()?; + + if objects.is_empty() { + println!(" no events"); + continue; + } + + for object in objects { + for event in object.events { + println!( + " {} - {}", + event.dtstart, + event.summary.unwrap_or_default() + ); + } + } + } + Ok(()) + } +} + +fn main() -> Result<()> { + let caldav_url = args().nth(1).expect("Missing caldav url argument."); + let username = args().nth(2).expect("Missing username argument."); + let password = args().nth(3).expect("Missing password argument."); + + let kaldav = CrateKaldav::new(&caldav_url, username.clone(), Some(password.clone())); + kaldav.show_content()?; + + Ok(()) +} diff --git a/src/bin/ccc.rs b/src/bin/ccc.rs new file mode 100644 index 0000000..13d24d6 --- /dev/null +++ b/src/bin/ccc.rs @@ -0,0 +1,170 @@ +use { + crossbeam_channel::{ + Receiver, + Sender, + + unbounded, + }, + cursive::{ + Cursive, + CursiveExt, + Printer, + Rect, + View, + XY, + + direction::Direction, + event::{ + Event, + EventResult, + }, + view::{ + CannotFocus, + Nameable, + Selector, + ViewNotFound, + }, + views::{ + Button, + LinearLayout, + TextView, + } + }, + std::{ + time::Duration, + thread::{ + self, + sleep, + }, + } +}; + +type CursiveSender = Sender FnOnce(&'a mut Cursive) + Send + 'static)>>; + +struct DataView { + data: Option, + view: TextView, +} + +impl DataView { + pub fn new() -> Self { + Self { + data: None, + view: TextView::new("uninitialized"), + } + } + + fn set_data(&mut self, data: &str) { + self.data = Some(data.to_owned()); + self.view.set_content(data.to_string()) + } +} + +impl View for DataView { + fn draw(&self, printer: &Printer<'_, '_>) { + self.view.draw(printer) + } + + fn layout(&mut self, xy: XY) { + self.view.layout(xy) + } + + fn needs_relayout(&self) -> bool { + self.view.needs_relayout() + } + + fn required_size(&mut self, constraint: XY) -> XY { + self.view.required_size(constraint) + } + + fn on_event(&mut self, event: Event) -> EventResult { + self.view.on_event(event) + } + + // fn call_on_any(), should not be needed for this view. + + fn focus_view(&mut self, selector: &Selector<'_>) -> Result { + self.view.focus_view(selector) + } + + fn take_focus( &mut self, source: Direction) -> Result { + self.view.take_focus(source) + } + + fn important_area(&self, view_size: XY) -> Rect { + self.view.important_area(view_size) + } + + fn type_name(&self) -> &'static str { + "DataView" + } +} + +fn data_request(cur: &mut Cursive) { + if let Some(sender) = cur.user_data::>() { + let _ = sender.send(true); + } +} + +fn populate_data(s: &mut Cursive, is_data: bool) { + if let Some(mut data_view) = s.find_name::("populate_me") { + if is_data { + data_view.set_data("populated") + } else { + data_view.set_data("initialized") + } + } +} + +fn background_thread(reciever: Receiver, sender: CursiveSender) { + while let Ok(msg) = reciever.recv() { + eprintln!("Received a message. Sleeping 3 seconds before sending response."); + sleep(Duration::from_secs(3)); + let _ = sender.send(Box::new(move |s| populate_data(s, msg))); + } +} + +fn main() { + // In order to maintain the main thread responsive in dealing with the user interface, we wish + // to offload all server communication to a background thread. This code demonstrates how to + // send a message from the ui, process it in the background and send the result back for the + // user interface to update the content of its view. + + // Firstly, we need a channel pair for sending a message from the ui to the worker thread. + let (wrk_sender, wrk_receiver) = unbounded::(); + + // We then construct the actual user interface. Our DataView, a button to trigger populating + // it and a button to quit the app. + let mut app = Cursive::new(); + let mut horizontal = LinearLayout::horizontal(); + let mut vertical = LinearLayout::vertical(); + let data_view = DataView::new().with_name("populate_me"); + let populate_button = Button::new("data", data_request); + let quit_button = Button::new("quit", |s| s.quit()); + vertical.add_child(data_view); + horizontal.add_child(populate_button); + horizontal.add_child(quit_button); + vertical.add_child(horizontal); + app.add_layer(vertical); + + // Once having created the app, we send a request to initialize our view. + let _ = wrk_sender.send(false); + + // We then place the sender in the user_data, to make it available for any ui function. + app.set_user_data(wrk_sender); + + // Getting the callback sender from the user interface, needed for the worker to respond. + let ui_sender = app.cb_sink().clone(); + + // Then we launch the background worker thread, providing it the appropriate channel ends. + let _worker = thread::spawn(move || background_thread(wrk_receiver, ui_sender)); + + // Lastly we launch the user interface. + app.run(); + + // Three seconds after launching, the text changes from "uninitialized" to "initialized" and + // further, three seconds after pressing the button the text becomes "populated". + // + // Essentially, this design should work for the complete program. Yet with some slightly more + // expressive choice of datatypes than merely a bool. +} -- cgit v1.2.3