From 194e5112e565b7a75738c5745a3b8828e2bc2f77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20B=C3=B6hme?= Date: Tue, 20 Jun 2023 20:12:02 +0200 Subject: [PATCH] feat: init --- .envrc | 1 + .gitignore | 24 ++++ Cargo.lock | 365 ++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 12 ++ default.nix | 7 + flake.lock | 94 ++++++++++++++ flake.nix | 21 +++ shell.nix | 7 + src/main.rs | 286 ++++++++++++++++++++++++++++++++++++++++ 9 files changed, 817 insertions(+) create mode 100644 .envrc create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 default.nix create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 shell.nix create mode 100644 src/main.rs diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5bbf394 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Created by https://www.toptal.com/developers/gitignore/api/direnv,rust +# Edit at https://www.toptal.com/developers/gitignore?templates=direnv,rust + +### direnv ### +.direnv +.envrc + +### Rust ### +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +# End of https://www.toptal.com/developers/gitignore/api/direnv,rust diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..6a47f51 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,365 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "anstream" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is-terminal", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" + +[[package]] +name = "anstyle-parse" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] +name = "anyhow" +version = "1.0.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + +[[package]] +name = "clap" +version = "4.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80672091db20273a15cf9fdd4e47ed43b5091ec9841bf4c6145c9dfbbcae09ed" +dependencies = [ + "clap_builder", + "clap_derive", + "once_cell", +] + +[[package]] +name = "clap_builder" +version = "4.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1458a1df40e1e2afebb7ab60ce55c1fa8f431146205aa5f4887e0b111c27636" +dependencies = [ + "anstream", + "anstyle", + "bitflags", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8cd2b2a819ad6eec39e8f1d6b53001af1e5469f8c177579cdaeb313115b825f" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "half" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys", +] + +[[package]] +name = "is-terminal" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" +dependencies = [ + "hermit-abi", + "io-lifetimes", + "rustix", + "windows-sys", +] + +[[package]] +name = "libc" +version = "0.2.146" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" + +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "proc-macro2" +version = "1.0.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustix" +version = "0.37.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b96e891d04aa506a6d1f318d2771bcb1c7dfda84e126660ace067c9b474bb2c0" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "serde" +version = "1.0.164" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_cbor" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" +dependencies = [ + "half", + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.164" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "timers" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "serde", + "serde_cbor", +] + +[[package]] +name = "unicode-ident" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..cd9c0c4 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "timers" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0.71" +clap = { version = "4.3.4", features = ["derive"] } +serde = { version = "1.0.164", features = ["derive"] } +serde_cbor = "0.11.2" diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..39bacff --- /dev/null +++ b/default.nix @@ -0,0 +1,7 @@ +(import ( + fetchTarball { + url = "https://github.com/edolstra/flake-compat/archive/99f1c2157fba4bfe6211a321fd0ee43199025dbf.tar.gz"; + sha256 = "0x2jn3vrawwv9xp15674wjz9pixwjyj3j771izayl962zziivbx2"; } +) { + src = ./.; +}).defaultNix diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..dea1ff1 --- /dev/null +++ b/flake.lock @@ -0,0 +1,94 @@ +{ + "nodes": { + "naersk": { + "inputs": { + "nixpkgs": "nixpkgs" + }, + "locked": { + "lastModified": 1686572087, + "narHash": "sha256-jXTut7ZSYqLEgm/nTk7TuVL2ExahTip605bLINklAnQ=", + "owner": "nix-community", + "repo": "naersk", + "rev": "8507af04eb40c5520bd35d9ce6f9d2342cea5ad1", + "type": "github" + }, + "original": { + "owner": "nix-community", + "ref": "master", + "repo": "naersk", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1685677062, + "narHash": "sha256-zoHF7+HNwNwne2XEomphbdc4Y8tdWT16EUxUTXpOKpQ=", + "path": "/nix/store/dnqwkazyg92hzya7400klxlk072g3zsk-source", + "rev": "95be94370d09f97f6af6a1df1eb9649b5260724e", + "type": "path" + }, + "original": { + "id": "nixpkgs", + "type": "indirect" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1687103638, + "narHash": "sha256-dwy/TK6Db5W7ivcgmcxUykhFwodIg0jrRzOFt7H5NUc=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "91430887645a0953568da2f3e9a3a3bb0a0378ac", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "naersk": "naersk", + "nixpkgs": "nixpkgs_2", + "utils": "utils" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1687171271, + "narHash": "sha256-BJlq+ozK2B1sJDQXS3tzJM5a+oVZmi1q0FlBK/Xqv7M=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "abfb11bd1aec8ced1c9bb9adfe68018230f4fb3c", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..6e8a761 --- /dev/null +++ b/flake.nix @@ -0,0 +1,21 @@ +{ + inputs = { + naersk.url = "github:nix-community/naersk/master"; + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, nixpkgs, utils, naersk }: + utils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { inherit system; }; + naersk-lib = pkgs.callPackage naersk { }; + in + { + defaultPackage = naersk-lib.buildPackage ./.; + devShell = with pkgs; mkShell { + buildInputs = [ cargo rustc rustfmt pre-commit rustPackages.clippy rust-analyzer ]; + RUST_SRC_PATH = rustPlatform.rustLibSrc; + }; + }); +} diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..77db547 --- /dev/null +++ b/shell.nix @@ -0,0 +1,7 @@ +(import ( + fetchTarball { + url = "https://github.com/edolstra/flake-compat/archive/99f1c2157fba4bfe6211a321fd0ee43199025dbf.tar.gz"; + sha256 = "0x2jn3vrawwv9xp15674wjz9pixwjyj3j771izayl962zziivbx2"; } +) { + src = ./.; +}).shellNix diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..ed98523 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,286 @@ +use crate::cli::{convert_command, send_command, Cli, Commands}; +use crate::daemon::Daemon; +use anyhow::Result; +use clap::Parser; + +mod timer { + use serde::{Deserialize, Serialize}; + use std::{ + fmt::{Display, Formatter}, + time::{Duration, Instant}, + }; + mod approx_instant { + use std::time::{Duration, Instant}; + + use serde::de::Error; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + pub fn serialize(instant: &Instant, serializer: S) -> Result + where + S: Serializer, + { + let duration = instant.elapsed(); + duration.serialize(serializer) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let duration = Duration::deserialize(deserializer)?; + let now = Instant::now(); + let instant = now + .checked_sub(duration) + .ok_or_else(|| Error::custom("Error deserializing instant!"))?; + Ok(instant) + } + } + + #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] + pub struct Timer { + pub name: String, + #[serde(with = "approx_instant")] + start: Instant, + duration: Duration, + } + + impl Display for Timer { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { + write!( + f, + "{} has {}s remaining.", + self.name, + self.remaining().as_secs() + ) + } + } + + impl Timer { + pub fn new(name: String, duration: Duration) -> Timer { + Timer { + name, + start: Instant::now(), + duration, + } + } + + pub fn is_expired(&self) -> bool { + return Instant::now() - self.start > self.duration; + } + + pub fn remaining(&self) -> Duration { + self.duration - (Instant::now() - self.start) + } + } +} + +mod daemon { + use crate::timer::Timer; + use anyhow::{Context, Ok, Result}; + use serde::{Deserialize, Serialize}; + use std::fmt::{Display, Formatter}; + use std::result::Result::Ok as ResultOk; + use std::{ + io::Write, + os::unix::net::{UnixListener, UnixStream}, + thread::sleep, + time::Duration, + }; + #[derive(Debug, Serialize, Deserialize)] + pub enum Command { + Add(String, Duration), + Remove(String), + List, + } + + #[derive(Debug, Serialize, Deserialize)] + pub enum Answer { + Ok, + Timers(Vec), + Err(String), + } + + impl Display for Answer { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { + match self { + Answer::Ok => write!(f, "Ok"), + Answer::Timers(timers) => { + if timers.is_empty() { + write!(f, "No timers running.") + } else { + let strings: Vec = + timers.iter().map(|timer| timer.to_string()).collect(); + write!(f, "{}", strings.join("\n")) + } + } + Answer::Err(msg) => write!(f, "Error: {}", msg), + } + } + } + + pub struct Daemon { + listener: UnixListener, + timers: Vec, + } + + impl Daemon { + pub fn new(socket_path: String) -> Result { + let path = std::path::Path::new(&socket_path); + if path.exists() { + std::fs::remove_file(path).with_context(|| { + format!("Could not remove previous socket {}!", socket_path) + })?; + } + let listener = UnixListener::bind(&socket_path) + .with_context(|| format!("Cannot bind to socket {}!", socket_path))?; + Ok(Self { + listener, + timers: Vec::new(), + }) + } + + fn has_timer(&mut self, name: &String) -> bool { + self.timers + .iter() + .find(|other| &other.name == name) + .is_some() + } + + fn handle_command(&mut self, command: Command) -> Answer { + println!("Received command {:?}", command); + match command { + Command::List => Answer::Timers(self.timers.to_vec()), + Command::Add(name, duration) => { + if self.has_timer(&name) { + return Answer::Err(format!("Timer with name {} already exists!", name)); + } + let timer = Timer::new(name, duration); + self.timers.push(timer); + Answer::Ok + } + Command::Remove(name) => { + if !self.has_timer(&name) { + return Answer::Err(format!("Timer with name {} does not exist!", name)); + } + self.timers = self + .timers + .to_vec() + .into_iter() + .filter(|other| other.name != name) + .collect(); + Answer::Ok + } + } + } + + fn handle_stream(&mut self, mut stream: &UnixStream) -> Result<()> { + let command = serde_cbor::from_reader(stream).context("Could not read command!")?; + let answer = self.handle_command(command); + serde_cbor::to_writer(stream, &answer).context("Could not write answer!")?; + stream.flush().context("Could not flush stream!")?; + Ok(()) + } + + fn check_timers(&mut self) { + self.timers = self + .timers + .to_vec() + .into_iter() + .filter(|timer| { + let expired = timer.is_expired(); + if expired { + println!("Timer {} is expired!", timer.name); + } + !expired + }) + .collect(); + } + + pub fn run(&mut self) -> Result<()> { + self.listener + .set_nonblocking(true) + .context("Could not set listener to non blocking!")?; + loop { + while let ResultOk((stream, _)) = self.listener.accept() { + if let Err(e) = self.handle_stream(&stream) { + println!("Error while handling stream: {}", e) + } + } + self.check_timers(); + sleep(Duration::from_millis(100)); + } + } + } +} + +mod cli { + use std::net::Shutdown; + use std::os::unix::net::UnixStream; + + use std::time::Duration; + + use clap::{Parser, Subcommand}; + + use anyhow::{Context, Result}; + + use crate::daemon::{Answer, Command}; + + #[derive(Debug, Parser)] + #[command(name = "timers")] + #[command(about = "A advanced timer daemon/cli.", long_about = None)] + pub struct Cli { + #[command(subcommand)] + pub command: Commands, + #[arg(short, long)] + #[clap(default_value = "/tmp/timers.socket")] + pub socket: String, + } + + #[derive(Debug, Subcommand)] + pub enum Commands { + Daemon, + Add { name: String, duration_seconds: u64 }, + List, + Remove { name: String }, + } + + fn get_stream(socket_path: &String) -> Result { + UnixStream::connect(socket_path) + .with_context(|| format!("Could not connect to socket {}!", socket_path)) + } + + pub fn convert_command(command: &Commands) -> Command { + match command { + Commands::Add { + name, + duration_seconds, + } => Command::Add(name.to_string(), Duration::from_secs(*duration_seconds)), + Commands::List => Command::List, + Commands::Remove { name } => Command::Remove(name.to_string()), + _ => panic!("Invalid command!"), + } + } + + pub fn send_command(socket_path: &String, command: Command) -> Result<()> { + let stream = get_stream(socket_path)?; + serde_cbor::to_writer(&stream, &command).with_context(|| "Could not write command!")?; + stream + .shutdown(Shutdown::Write) + .context("Could not shutdown write!")?; + let answer: Answer = + serde_cbor::from_reader(&stream).with_context(|| "Could not read answer!")?; + println!("{}", answer); + Ok(()) + } +} + +fn main() -> Result<()> { + let args = Cli::parse(); + match args.command { + Commands::Daemon => { + Daemon::new(args.socket)?.run()?; + Ok(()) + } + _ => send_command(&args.socket, convert_command(&args.command)), + } +}