1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
use crate::config::Config;
use crate::consts::{errors, help, interact};
use crate::utils::GuessablePassword;
use std::collections::HashSet;
use std::env;
use std::path::PathBuf;
use std::process;
use std::sync::mpsc;
use std::sync::mpsc::{Receiver, Sender};
use std::thread;
use std::time::{Duration, Instant};
use sysinfo::{Pid, ProcessExt, System, SystemExt, Uid, UserExt};
pub mod config;
pub mod consts;
pub mod utils;

/// The two types of communications are end or a config, This is an enum that
/// reflects that reality
pub enum Coms {
    Message(crate::config::Config, Option<Instant>),
    End,
}

fn help() {
    println!("{}", help::HELP);
}

fn anotator(filter_users: bool) {
    let s = System::new_all();
    println!("{}", interact::ANNOTATOR);
    let proc_self = s
        .process(Pid::from(process::id() as i32))
        .expect(errors::PROC);
    if filter_users {
        println!(
            "We are selecting the user with uid {} form these\n{:?}",
            proc_self.uid,
            s.users()
                .iter()
                .map(|user| (user.name(), user.uid()))
                .collect::<Vec<(&str, Uid)>>()
        );
    }
    let mut a = HashSet::new();
    s.processes()
        .iter()
        .filter(|(_, proc)| {
            if filter_users {
                proc.uid == proc_self.uid
            } else {
                true
            }
        })
        .for_each(|(_, proc)| {
            a.insert(proc.name());
        });
    println!("{}", interact::START);
    utils::get_item::<String>();
    println!("{}", interact::CAND);
    System::new_all()
        .processes()
        .iter()
        .filter(|(_, proc)| {
            if filter_users {
                proc.uid == proc_self.uid
            } else {
                true
            }
        })
        .for_each(|(_, proc)| {
            if !a.contains(proc.name()) {
                println!("\t- {}", proc.name());
            }
        });
}

fn interpret_args() -> (Config, bool, PathBuf) {
    let mut args = env::args();
    args.next(); // ignore
    let mut interactive = true;
    let (mut config, mut path) = Config::get_or_create(None);
    while let Some(arg) = args.next() {
        match arg.as_str() {
            "-f" | "f" | "--file" => {
                (config, path) = Config::get_or_create(args.next());
            }
            "-s" | "s" | "--silent" => {
                interactive = false;
            }
            "--help" | "-h" | "h" => {
                help();
                interactive = false;
                config = Config::default();
            }
            "-a" | "a" | "--annotator" => {
                anotator(true);
                interactive = false;
                config = Config::default();
            }
            "-A" | "A" => {
                anotator(false);
                interactive = false;
                config = Config::default();
            }
            x => {
                println!("{} {}", x, errors::ARG);
                interactive = false;
                config = Config::default();
            }
        }
    }
    (config, interactive, path)
}

/// # The interaction loop
///
/// It spawns another thread that is on the lookout for user input synchronously.
/// Meanwhile this loop is checking if any of the threads (killer and
/// synchronous input) have sent anything back.
///
/// When the killing loop send a unit type it means it has finished, and
/// whenever this thread is free it should quit.
///
/// When the synchronous interaction returns a letter, it is processed and this
/// thread asumes control over the interactions synchronously until it is
/// finished. When it is finished, this thread spawns the next synchronous one
///
/// When in silent mode, it doesn't spawn the thread and it just waits for the
/// killing thread to be done, synchronously,
pub fn interact(tx: Sender<Coms>, rx: Receiver<()>) {
    let (mut config, interactive, path) = interpret_args();
    let mut init_time = Instant::now();
    tx.send(Coms::Message(config.clone(), Some(init_time)))
        .expect(errors::COM);
    if interactive {
        config.print_curr_state();
        println!("{}", interact::INSTRUCTIONS);
        utils::bar();
        let (interuptor, interruptee) = mpsc::channel();
        let int = interuptor.clone();
        thread::spawn(move || {
            utils::async_string(int);
        });
        //I don't really care what happens to it, only if it produces a value
        while rx.try_recv().is_err() {
            thread::sleep(Duration::from_millis(500));
            if let Ok(string) = interruptee.try_recv() {
                match string.trim() {
                    "e" => {
                        if config.check_pass(None) {
                            config = config.edit();
                            tx.send(Coms::Message(config.clone(), None))
                                .expect(errors::COM);
                        }
                    }
                    "p" => {
                        if config.check_pass(None) {
                            let config_rem = config.remain(init_time);
                            tx.send(Coms::Message(
                                Config::new(1, u16::MAX, "".to_string(), HashSet::new()),
                                None,
                            ))
                            .expect(errors::COM);
                            println!("{}", interact::PAUSE);
                            utils::get_item::<String>();
                            init_time = Instant::now();
                            tx.send(Coms::Message(config_rem, Some(init_time)))
                                .expect(errors::COM);
                            println!("{}", interact::CONT);
                        }
                    }
                    "q" => {
                        if config.check_pass(None) {
                            println!(
                                "It will take up to {} seconds, but wont kill any processes you might have running",
                                config.get_kill_time_as_seconfs()
                            );
                            tx.send(Coms::Message(Config::default(), None))
                                .expect(errors::COM);
                        }
                    }
                    "r" => {
                        println!("{} minutes remaining",
                                config.remain(init_time).get_work_time_as_min()
                        );
                    }
                    "a" => {
                        println!("{}", interact::ADD);
                        if let Some(time) = utils::get_item() {
                            tx.send(Coms::Message(config.add_time(time), None))
                                .expect(errors::COM);
                        }
                    }
                    _ => {
                        println!("{}", errors::INTER);
                    }
                };
                // handle the petition, then spawn another
                let int = interuptor.clone();
                thread::spawn(move || {
                    utils::async_string(int);
                });
                utils::bar();
            };
        }
        println!("{}", interact::FINNISH);
    } else {
        rx.recv().expect(errors::COM);
    }
    tx.send(Coms::End).expect(errors::COM);
    config.write_config(&path);
}

/// This function is the one that sends the kill signals to the processes
/// It waits for a Config to be sent to it to start blocking.
///
/// It runs on a while that
/// 1. checks if you have run out of time
/// 1. Sees if config has changed
/// 1. gets all running processes
/// 1. kills those in the processes list of the running configuration
/// 1. sleeps
pub fn killer(tx: Sender<()>, rx: Receiver<Coms>) {
    let (mut config, mut init_time);
    if let Coms::Message(conf, Some(time)) = rx.recv().expect(errors::COM) {
        (config, init_time) = (conf, time);
    } else {
        panic!("{}", errors::COM)
    }
    while Instant::now().duration_since(init_time) < config.get_work_time() {
        if let Ok(Coms::Message(conf, time)) = rx.try_recv() {
            config = conf;
            if let Some(now) = time {
                init_time = now;
            }
        }
        let s = System::new_all();
        s.processes()
            .iter()
            .filter(|(_, process)| config.contains(process.name()))
            .for_each(|(_, process)| {
                process.kill();
            });
        thread::sleep(config.get_kill_time());
    }
    tx.send(()).expect(errors::COM);

    loop {
        if let Coms::End = rx.recv().expect(errors::COM) {
            break;
        }
    }
}