feat: split into modules
parent
a5cac154f4
commit
853e735fcd
|
@ -199,18 +199,18 @@ checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.60"
|
version = "1.0.66"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406"
|
checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.28"
|
version = "1.0.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488"
|
checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
@ -267,15 +267,35 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.18"
|
version = "2.0.27"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e"
|
checksum = "b60f673f44a8255b9c8c657daf66a596d435f2da81a555b06dc644d080ba45e0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "1.0.44"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "1.0.44"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "timers"
|
name = "timers"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -284,6 +304,7 @@ dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_cbor",
|
"serde_cbor",
|
||||||
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -10,3 +10,4 @@ anyhow = "1.0.71"
|
||||||
clap = { version = "4.3.4", features = ["derive"] }
|
clap = { version = "4.3.4", features = ["derive"] }
|
||||||
serde = { version = "1.0.164", features = ["derive"] }
|
serde = { version = "1.0.164", features = ["derive"] }
|
||||||
serde_cbor = "0.11.2"
|
serde_cbor = "0.11.2"
|
||||||
|
thiserror = "1.0.44"
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
use crate::daemon::{Answer, Command as OtherCommand, AnswerErr};
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use clap::{Parser, Subcommand};
|
||||||
|
use std::net::Shutdown;
|
||||||
|
use std::os::unix::net::UnixStream;
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
#[command(name = "timers")]
|
||||||
|
#[command(about = "A advanced timer daemon/cli.", long_about = None)]
|
||||||
|
#[command(arg_required_else_help = true)]
|
||||||
|
pub struct Cli {
|
||||||
|
#[command(subcommand)]
|
||||||
|
pub command: Command,
|
||||||
|
#[arg(short, long)]
|
||||||
|
#[clap(default_value = "/tmp/timers.socket")]
|
||||||
|
pub socket: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Subcommand)]
|
||||||
|
pub enum Command {
|
||||||
|
Daemon,
|
||||||
|
Add { name: String, duration_seconds: u64 },
|
||||||
|
List,
|
||||||
|
Remove { name: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_stream(socket_path: &String) -> Result<UnixStream> {
|
||||||
|
UnixStream::connect(socket_path)
|
||||||
|
.context(format!("Could not connect to socket {}!", socket_path))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_command(socket_path: &String, command: OtherCommand) -> Result<()> {
|
||||||
|
let stream = get_stream(socket_path)?;
|
||||||
|
serde_cbor::to_writer(&stream, &command).context("Could not write command!")?;
|
||||||
|
stream
|
||||||
|
.shutdown(Shutdown::Write)
|
||||||
|
.context("Could not shutdown write!")?;
|
||||||
|
let answer: Result<Answer, AnswerErr> = serde_cbor::from_reader(&stream).context("Could not read answer!")?;
|
||||||
|
match answer {
|
||||||
|
Ok(answer) => println!("{}", answer),
|
||||||
|
Err(err) => println!("Error: {}", err),
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -0,0 +1,139 @@
|
||||||
|
pub use crate::timer::Timer;
|
||||||
|
use anyhow::Context;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
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<Timer>),
|
||||||
|
}
|
||||||
|
|
||||||
|
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<String> =
|
||||||
|
timers.iter().map(|timer| timer.to_string()).collect();
|
||||||
|
write!(f, "{}", strings.join("\n"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error, Serialize, Deserialize)]
|
||||||
|
pub enum AnswerErr {
|
||||||
|
#[error("Timer with name '{}' already exists", .0)]
|
||||||
|
TimerAlreadyExist(String),
|
||||||
|
#[error("No timer with the name '{}' exists", .0)]
|
||||||
|
NoSuchTimer(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub struct Daemon {
|
||||||
|
listener: UnixListener,
|
||||||
|
timers: Vec<Timer>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Daemon {
|
||||||
|
pub fn new(socket_path: String) -> anyhow::Result<Self> {
|
||||||
|
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)
|
||||||
|
.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().any(|other| &other.name == name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_command(&mut self, command: Command) -> Result<Answer, AnswerErr> {
|
||||||
|
println!("Received command {:?}", command);
|
||||||
|
match command {
|
||||||
|
Command::List => Ok(Answer::Timers(self.timers.to_vec())),
|
||||||
|
Command::Add(name, duration) => {
|
||||||
|
if self.has_timer(&name) {
|
||||||
|
return Err(AnswerErr::TimerAlreadyExist(name));
|
||||||
|
}
|
||||||
|
let timer = Timer::new(name, duration);
|
||||||
|
self.timers.push(timer);
|
||||||
|
Ok(Answer::Ok)
|
||||||
|
}
|
||||||
|
Command::Remove(name) => {
|
||||||
|
if !self.has_timer(&name) {
|
||||||
|
return Err(AnswerErr::NoSuchTimer(name));
|
||||||
|
}
|
||||||
|
self.timers = self
|
||||||
|
.timers
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.filter(|other| other.name != name)
|
||||||
|
.collect();
|
||||||
|
Ok(Answer::Ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_stream(&mut self, mut stream: &UnixStream) -> anyhow::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
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.filter(|timer| {
|
||||||
|
let expired = timer.is_expired();
|
||||||
|
if expired {
|
||||||
|
println!("Timer {} is expired!", timer.name);
|
||||||
|
}
|
||||||
|
!expired
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(&mut self) -> anyhow::Result<()> {
|
||||||
|
self.listener
|
||||||
|
.set_nonblocking(true)
|
||||||
|
.context("Could not set listener to non blocking!")?;
|
||||||
|
loop {
|
||||||
|
while let Ok((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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
298
src/main.rs
298
src/main.rs
|
@ -1,286 +1,24 @@
|
||||||
use crate::cli::{convert_command, send_command, Cli, Commands};
|
pub mod cli;
|
||||||
use crate::daemon::Daemon;
|
pub mod daemon;
|
||||||
|
pub mod timer;
|
||||||
|
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use crate::cli::{send_command, Cli, Command as CliCommand};
|
||||||
|
use crate::daemon::{Command as DaemonCommand, Daemon};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::Parser;
|
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<S>(instant: &Instant, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: Serializer,
|
|
||||||
{
|
|
||||||
let duration = instant.elapsed();
|
|
||||||
duration.serialize(serializer)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<Instant, D::Error>
|
|
||||||
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<Timer>),
|
|
||||||
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<String> =
|
|
||||||
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<Timer>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Daemon {
|
|
||||||
pub fn new(socket_path: String) -> Result<Self> {
|
|
||||||
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> {
|
|
||||||
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<()> {
|
fn main() -> Result<()> {
|
||||||
let args = Cli::parse();
|
let args = Cli::parse();
|
||||||
match args.command {
|
let daemon_command = match args.command {
|
||||||
Commands::Daemon => {
|
CliCommand::Daemon => return Daemon::new(args.socket)?.run(),
|
||||||
Daemon::new(args.socket)?.run()?;
|
CliCommand::Add {
|
||||||
Ok(())
|
name,
|
||||||
}
|
duration_seconds,
|
||||||
_ => send_command(&args.socket, convert_command(&args.command)),
|
} => DaemonCommand::Add(name, Duration::from_secs(duration_seconds)),
|
||||||
}
|
CliCommand::List => DaemonCommand::List,
|
||||||
|
CliCommand::Remove { name } => DaemonCommand::Remove(name),
|
||||||
|
};
|
||||||
|
send_command(&args.socket, daemon_command)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
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<S>(instant: &Instant, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
let duration = instant.elapsed();
|
||||||
|
duration.serialize(serializer)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize<'de, D>(deserializer: D) -> Result<Instant, D::Error>
|
||||||
|
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 {
|
||||||
|
Instant::now() - self.start > self.duration
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remaining(&self) -> Duration {
|
||||||
|
self.duration - (Instant::now() - self.start)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue