pub mod client; pub mod pretty; use crate::{client::Client, pretty::fmt_condition}; use chrono::{NaiveDateTime, Utc}; use clap::{Args, Parser, Subcommand}; use karlcommon::{ interfaces::unix_path, version, ClientboundPacket, Schedule, ServerboundPacket, Task, }; use log::{error, info}; use std::{os::unix::net::UnixStream, path::PathBuf, process::exit}; /// CLI interface for karld #[derive(Parser)] #[clap(about, author, version)] struct Arguments { /// Custom path to the daemon socket #[clap(long)] socket_path: Option, #[clap(subcommand)] action: Action, } #[derive(Subcommand)] pub enum Action { /// Show version of the client and daemon Version, /// List all tasks List, /// Remove a task by id Remove { id: u64 }, /// Update a task by id Update { id: u64, #[clap(flatten)] task: TaskSpec, }, /// Create a task Create { #[clap(flatten)] task: TaskSpec, }, } #[derive(Args)] pub struct TaskSpec { #[clap(short, long)] name: String, #[clap(short, long)] description: Option, #[clap(short, long)] tags: Vec, } fn main() { env_logger::init(); let args = Arguments::parse(); let socket = match UnixStream::connect(args.socket_path.unwrap_or(unix_path())) { Ok(s) => s, Err(e) => { error!("failed to connect to socket: {}", e); exit(1); } }; let mut client = Client::new(socket); info!("connected"); let handshake = client.receiver.recv().unwrap(); match args.action { Action::Version => { if let ClientboundPacket::Handshake { version: daemon_version, } = handshake { println!("{}", version!()); println!("{daemon_version}"); } else { error!("handshake is not the first packet") } } Action::List => { client.send(ServerboundPacket::ListTasks); if let ClientboundPacket::TaskList(tasks) = client.receiver.recv().unwrap() { for t in tasks { println!("- \x1b[4m\x1b[1mTASK {}\x1b[0m", t.id); println!(" \x1b[38;2;100;255;100mName:\x1b[0m {}", t.name); println!( " \x1b[38;2;100;255;100mDescription:\x1b[0m {}", t.description .unwrap_or("\x1b[3m\x1b[2m(no description)\x1b[0m".to_string()) ); if !t.tags.is_empty() { println!(" \x1b[38;2;100;255;100mTags:\x1b[0m {}", t.tags.join(", ")); } print!(" \x1b[38;2;100;255;100mSchedule: \x1b[0m"); match t.schedule { Schedule::Never => println!("\x1b[3m\x1b[2m(never)\x1b[0m"), Schedule::Dynamic { duration, priority, scheduled, .. } => { // TODO dont ignore condition println!("dynamicly scheduled",); println!(" \x1b[38;2;100;255;100mPriority:\x1b[0m {priority}"); println!( " \x1b[38;2;100;255;100mDuration:\x1b[0m {:?}", std::time::Duration::from_secs(duration as u64) ); println!( " \x1b[38;2;100;255;100mScheduled for:\x1b[0m {}", scheduled .map(|r| format!( "{} - {}", NaiveDateTime::from_timestamp(r.start, 0), NaiveDateTime::from_timestamp(r.end, 0) )) .unwrap_or("...".to_string()) ); } Schedule::Static(t) => { println!( "from {} to {}", NaiveDateTime::from_timestamp(t.start, 0), NaiveDateTime::from_timestamp(t.end, 0) ) } Schedule::Condition(o) => { println!("when {}", fmt_condition(&o)); print!(" \x1b[38;2;100;255;100mNext instances: \x1b[0m"); client.send(ServerboundPacket::ListInstances { task: t.id, limit: 5, range: Some(Utc::now().naive_local().timestamp())..None, }); if let ClientboundPacket::InstanceList(instances) = client.receiver.recv().unwrap() { for i in instances { println!( "\x1b[19G{} - {}", i.start .map(|e| format!( "{}", NaiveDateTime::from_timestamp(e, 0) )) .unwrap_or("...".to_string()), i.end .map(|e| format!( "{}", NaiveDateTime::from_timestamp(e, 0) )) .unwrap_or("...".to_string()), ); } } } } println!(); } } } Action::Remove { id } => { client.send(ServerboundPacket::RemoveTask(id)); } Action::Update { id, task } => { let mut t = task.build(); t.id = id; client.send(ServerboundPacket::UpdateTask(t)) } Action::Create { task } => client.send(ServerboundPacket::UpdateTask(task.build())), } // sync client.send(ServerboundPacket::Sync); for p in client.receiver.iter() { if let ClientboundPacket::Sync = p { break; } } } impl TaskSpec { pub fn build(self) -> Task { Task { id: rand::random(), name: self.name, description: self.description, tags: self.tags, schedule: Schedule::Never, } } }