midnight/
lib.rs

1use std::io::{BufRead, BufReader};
2use std::process::exit;
3
4use anyhow::Result;
5use duct::cmd;
6use flager::{Flag, Parser, new_flag};
7
8mod model;
9pub use model::Message;
10pub use model::Mua;
11use model::Pipe;
12pub use model::Record;
13
14/// Program entry point
15pub struct Midnight;
16
17impl Midnight {
18    /// Parse CLI flags
19    pub fn flags() -> Result<()> {
20        let parser = Parser::new();
21
22        let help_msg = "Print this help message then quit";
23        let queue_msg = "List items in queue (alias for 'mnq')";
24        let remove_msg = "Remove item from queue via job ID (alias for 'mnrm')";
25        let send_msg = "Send message in queue via message ID (alias for 'mnsend')";
26        let version_msg = "Print the version then quit";
27
28        let help_flag: Flag<bool> = new_flag!("-h", "--help").help(help_msg);
29        let queue_flag: Flag<bool> = new_flag!("-q", "--queue").help(queue_msg);
30        let remove_flag: Flag<String> = new_flag!("-r", "--remove").help(remove_msg);
31        let send_flag: Flag<bool> = new_flag!("-s", "--send").help(send_msg);
32        let version_flag: Flag<bool> = new_flag!("-V", "--version").help(version_msg);
33
34        if parser.parse(&help_flag).unwrap() == true {
35            println!("midnight");
36            println!("\tSend mail later via batch queueing");
37            println!();
38            println!("Usage:");
39            println!("\t<message> | midnight");
40            println!("\t<message> | mn");
41            println!("\tmnq");
42            println!("\tmnrm <jobid>");
43            println!("\t<message_id> | mnsend");
44            println!();
45            println!("Options:");
46            println!("\t-h, --help\t{}", help_msg);
47            println!("\t-q, --queue\t{}", queue_msg);
48            println!("\t-r, --remove\t{}", remove_msg);
49            println!("\t-s, --send\t{}", send_msg);
50            println!("\t-V, --version\t{}", version_msg);
51            exit(0);
52        }
53
54        if parser.parse(&queue_flag).unwrap() == true {
55            Self::list()?;
56            exit(0);
57        }
58
59        if let Some(jobid) = parser.parse(&remove_flag) {
60            if jobid.is_empty() {
61                eprintln!("Usage: midnight -r <jobid>");
62                exit(1);
63            }
64            Self::rm(&jobid)?;
65            exit(0);
66        }
67
68        if parser.parse(&send_flag).unwrap() == true {
69            Self::send()?;
70            exit(0);
71        }
72
73        if parser.parse(&version_flag).unwrap() == true {
74            println!("midnight v{}", env!("CARGO_PKG_VERSION"));
75            exit(0);
76        }
77
78        Ok(())
79    }
80
81    /// Schedule a message to be sent later
82    pub fn enqueue() -> Result<()> {
83        // Get raw message and parse it
84        let raw = Pipe::drain()?;
85        let mut msg = Message::new(raw)?;
86
87        // Figure out what time the user wants to schedule the delivery
88        msg.at = Pipe::user(Some("What time? "))?;
89
90        // Queue message for delivery via at(1)
91        let (jobid, at) = msg.enqueue()?;
92        msg.at = at;
93
94        // Record message details in the queue file
95        let rcd = Record::new(jobid, msg);
96        rcd.write()?;
97
98        Ok(())
99    }
100
101    /// Send a message that was scheduled for delivery
102    pub fn send() -> Result<()> {
103        let id = Pipe::drain()?;
104        let mut rcd = Record::find(id)?;
105        rcd.msg.send()
106    }
107
108    /// Remove a message from the queue that was scheduled, but hasn't been sent yet
109    pub fn rm(jobid: &str) -> Result<()> {
110        cmd!("atrm", jobid).run()?;
111        Self::clean()?;
112        Ok(())
113    }
114
115    /// List scheduled messages in the queue
116    pub fn list() -> Result<()> {
117        Self::clean()?;
118        let rcds = Record::read()?;
119        for (i, rcd) in rcds.iter().enumerate() {
120            println!("{}", rcd);
121            if i < rcds.len() - 1 {
122                println!();
123            }
124        }
125        Ok(())
126    }
127
128    /// Remove stale entries from the queue file
129    pub fn clean() -> Result<()> {
130        // Read in jobs from atq(1)
131        let cmd = cmd!("atq");
132        let reader = cmd.stderr_to_stdout().reader()?; // {
133
134        let mut jobs = vec![];
135        let reader = BufReader::new(reader);
136        for line in reader.lines() {
137            let line = line?;
138            let jobid = String::from(line.split_whitespace().collect::<Vec<&str>>()[0]);
139            jobs.push(jobid);
140        }
141
142        // Read in records from the queue files
143        let rcds = Record::read()?;
144
145        // Find records that still have a job in atq(1)
146        let scheduled = rcds
147            .iter()
148            .filter(|rcd| jobs.contains(&rcd.id))
149            .collect::<Vec<&Record>>();
150
151        // Clear the queue file
152        Record::clear()?;
153
154        // Write records back out that are still in atq(1)
155        for rcd in scheduled {
156            rcd.write()?;
157        }
158
159        Ok(())
160    }
161}