· 4 years ago · Jan 02, 2021, 08:46 AM
1extern crate argparse;
2extern crate rand;
3extern crate rusqlite;
4
5use rusqlite::*;
6
7use argparse::{ArgumentParser, Store};
8
9use rand::Rng;
10
11use std::convert::TryInto;
12use std::process::Command;
13use std::fs;
14use std::io;
15use std::time::{SystemTime,UNIX_EPOCH};
16
17/// `SAVE_DIR` is the path of where that data directory is located. It
18/// is used by different functions in Megatron looking for the db and
19/// for Chromium profile directories.
20// static SAVE_DIR: &'static str = "megabase/"; // for testing
21static SAVE_DIR: &'static str = "/home/stares/.config/megatron/"; // for real use
22
23fn russia() {
24 let dom = rand::thread_rng().gen_range(1,3);
25 let url = format!("--app={}",
26 match dom {
27 1 => "https://www.youtube.com/watch?v=QptZ8tYZAkE",
28 2 => "https://www.youtube.com/watch?v=xt97SZWis1I",
29 _ => ""
30 });
31 Command::new("chromium")
32 .arg(url)
33 .status()
34 .expect("Failed to execute command");
35}
36
37// ============================================================================
38// Database stuff
39// ============================================================================
40/// `open_db` will be called by all functions that minipulate the
41/// db. For project going with functional design; not using class
42/// to hold db connection.
43fn open_db() -> Connection {
44 // create a dir for data stuff
45 fs::create_dir_all(SAVE_DIR).expect("megabase dir failed");
46
47 // MAYBE IMPLEMENT alternate db save location.
48 // create a connection to sqlite
49 let conn = Connection::open(format!("{}{}",SAVE_DIR,"cats.db")).expect("db conn fail");
50 // rust setup if new commands
51 conn.execute_batch(
52 "create table if not exists short_names (
53 id INTEGER PRIMARY KEY,
54 short_name TEXT NOT NULL UNIQUE
55 );
56 create table if not exists descriptions (
57 id INTEGER NOT NULL,
58 d TEXT NOT NULL
59 );
60 create table if not exists last_used (
61 used_id INTEGER PRIMARY KEY, /* A key to track last used entries*/
62 sho_id INTEGER NOT NULL, /* The key for the rest of the db*/
63 t BLOB NOT NULL
64 );
65 create table if not exists metadata (
66 id INTEGER NOT NULL,
67 creation_date BLOB NOT NULL,
68 use_type TEXT
69 );"
70 ).expect("db conn fail");
71 // return connection
72 return conn;
73}
74
75/// `describe_db` takes the short name, looks it up in
76/// the data base, and returns the value associated with that short
77/// name in the db.
78fn describe_db(short:String) -> String {
79 let mut d:String = String::new();
80 {
81 let db = open_db();
82 /*
83 SELECT d from
84 (SELECT short_names.short_name, descriptions.d from short_names left join descriptions on descriptions.id = short_names.id)
85 WHERE short_name = 'totot'
86 */
87 let mut receiver = db.prepare(
88 "SELECT d from
89 (SELECT short_names.short_name, descriptions.d from short_names left join descriptions on descriptions.id = short_names.id)
90 WHERE short_name = :short;"
91 ).expect("");
92 let mut rows = receiver.query_named(named_params!{ ":short": short }).expect("");
93 while let Some(row) = rows.next().expect("") {
94 d=row.get(0).expect("");
95 }
96 }
97 return d;
98}
99
100/// `new_db` inserts a new profile's info into the DB. It takes all
101/// fields that are user provided and places them into the db.
102fn new_db(short:String,use_type:String,description:String) {
103 // creation date
104 let t:i64 = match SystemTime::now().duration_since(UNIX_EPOCH) {
105 Ok(n) => n.as_secs().try_into().unwrap(),
106 Err(_) => panic!("SystemTime before UNIX EPOCH!"),
107 };
108
109 {
110 let db = open_db();
111 match db.execute("insert into short_names (short_name) values (?1)",params![short]) {
112 Ok(insert) => insert,
113 Err(error) => {
114 panic!("That short_name already exists. Short_names must be unique.\n{}",error)
115 }
116 };
117
118 let mut id:i64 = 0;
119 {
120 let mut receiver = db.prepare("SELECT * FROM short_names WHERE short_name = :short;").expect("");
121 let mut rows = receiver.query_named(named_params!{ ":short": short }).expect("");
122 while let Some(row) = rows.next().expect("") {
123 id=row.get(0).expect("");
124 }
125 }
126 if id == 0 { panic!("new_db id did not return correctly. id is {:?}",id) }
127 // insert into metadata (id,creation_date,use_type) values (1,1571342882,'personal')
128 db.execute("insert into metadata (id,creation_date,use_type) values (?1,?2,?3)",params![id,t,use_type]).expect("db insert fail");
129 // insert into description (id,use_desc) values (1,'This is the description. It is very descrptive')
130 db.execute("insert into descriptions (id,d) values (?1,?2)",params![id,description]).expect("db insert fail");
131 }
132}
133
134// ============================================================================
135// CLI commands
136// ============================================================================
137/// `ls` lists all profiles that have been created. If there are not
138/// profiles in the db, then nothing will be returned.
139fn ls() -> Result<()> {
140 let mut short_vec:Vec<String> = Vec::new();
141 let mut cdate_vec:Vec<i64> = Vec::new();
142 let mut use_vec:Vec<String> = Vec::new();
143
144 // Quarrying Database
145 {
146 let db = open_db();
147 {
148 let mut receiver = db.prepare("SELECT short_names.short_name, metadata.creation_date, metadata.use_type FROM metadata left join short_names on metadata.id = short_names.id;")?;
149 let mut rows = receiver.query(NO_PARAMS).expect("");
150 while let Some(row) = rows.next().expect("") {
151 short_vec.push(
152 row.get(0)?
153 );
154 cdate_vec.push(
155 row.get(1)?
156 );
157 use_vec.push(
158 row.get(2)?
159 );
160 }
161 }
162 }
163
164 // Formatting Output
165 let cdate_str:Vec<String> = cdate_vec.iter().map(|s| s.to_string()).collect();
166 let all_len = short_vec.len();
167
168 let all_col = vec![&short_vec, &cdate_str, &use_vec];
169
170 let mut white_buf:Vec<usize> = Vec::new();
171 for x in &all_col {
172 let mut zeroth:usize = 0;
173 for y in x.iter() {
174 if y.len() > zeroth { zeroth = y.len(); }
175 }
176 white_buf.push(zeroth);
177 }
178
179 for x in 0..all_len {
180 println!("| {0:1$} | {2:3$} | {4:5$} |", short_vec[x], white_buf[0], cdate_str[x], white_buf[1], use_vec[x], white_buf[2]);
181 }
182
183 Ok(())
184}
185
186/// `new` is a macro to gather user fields in the db from the user. It
187/// asks for the type of the profile, a description of the profile,
188/// and the short name for the profile. `new` may be called with the
189/// short name as a parameter or failing it being provided, the short
190/// name will be asked for.
191macro_rules! new {
192 // NOT IMPLEMENTED "Expire Date" and "User Agent"
193 ($short:expr) => {
194 // take short Type for user
195 println!("\n[Type]");
196 let mut out_type = String::new();
197 io::stdin().read_line(&mut out_type).expect("Failed to read line");
198 out_type = out_type.trim().to_string();
199 if out_type == "" { out_type = "NULL".to_string();}
200
201 // take short Desc for user
202 println!("\n[Description]");
203 let mut out_desc = String::new();
204 io::stdin().read_line(&mut out_desc).expect("Failed to read line");
205 out_desc = out_desc.trim().to_string();
206 if out_desc == "" { out_desc = "NULL".to_string();}
207
208 new_db($short, out_type, out_desc);
209 };
210 () => {
211 // take short name for user
212 println!("\n[Short Name]");
213 let mut name = String::new();
214 io::stdin().read_line(&mut name).expect("Failed to read line");
215 new!(name.trim_end().to_string());
216 };
217}
218
219/// `desc` calls for the database right short name and then prints out
220/// that profile's description.
221fn desc(short:String) {
222 // print out describe for short given
223 let out=describe_db(short);
224 // TESTING this is not a sufficient test of out being empty.
225 // IMPLEMENT what if user did not give short?
226 if out!="NULL" {
227 println!("[Description]");
228 println!("{}",out);
229 } else {
230 println!("Description is not available.");
231 }
232}
233
234// ============================================================================
235// Meat and Potatoes
236// ============================================================================
237/// `default` is called by the `run` CLI command. It is expected to be
238/// the most common usage of Megatron. It updates the db with the last
239/// launch of the profile and then will call `browser_activate`. If
240/// there is no profile in the db, then the macro `new` will be
241/// called. After `new` completes, that newly created profile will be
242/// launched.
243fn default(short:String) {
244 // print out describe for short given
245 let mut flag = false;
246 // look up short in db
247 let mut id:i64 = 0;
248 {
249 let db = open_db();
250 let mut receiver = db.prepare("SELECT id FROM short_names WHERE short_name = :short;").expect("");
251 let mut rows = receiver.query_named(named_params!{ ":short": short }).expect("");
252 while let Some(row) = rows.next().expect("") {
253 id=row.get(0).expect("");
254 }
255 // Update last used
256 if id != 0 {
257 let t:isize = match SystemTime::now().duration_since(UNIX_EPOCH) {
258 Ok(n) => n.as_secs().try_into().unwrap(),
259 Err(_) => panic!("SystemTime before UNIX EPOCH!"),
260 };
261 db.execute("insert into last_used (sho_id,t) values (?1,?2)",params![id,t]).expect("db insert fail");
262 flag = true;
263 }
264 }
265 // if there is no short in db
266 if flag == true {
267 // launch browser
268 browser_activate(short);
269 } else {
270 println!("I don't know {}", short);
271 println!("would you like to creat it? (y or n)");
272 // offer to create it
273 let mut input = String::new();
274 io::stdin().read_line(&mut input).expect("Failed to read line");
275 if "y" == input.trim() {
276 new!(short.clone());
277 default(short);
278 }
279 }
280}
281
282/// `browser_activate` is the fn that opens the web browser. This is
283/// in it's own fuction in-case the command is to be changed, only
284/// this fn needs to be modified.
285fn browser_activate(short:String) {
286 let data_dir = format!("{}{}",SAVE_DIR,short);
287 fs::create_dir_all(&data_dir).expect("megabase dir failed");
288 let err_code = Command::new("chromium")
289 .arg(format!("--user-data-dir={}",&data_dir))
290 .arg("--password-store=basic")
291 .status()
292 .expect("fail");
293
294 if err_code.success() {
295 println!("goodbye")
296 } else {
297 println!("Something when horribly wrong.")
298 }
299}
300
301// ============================================================================
302// main
303// ============================================================================
304/// `main` manages taking CLI arguments and passing them to the
305/// appropriate functions. Collects string argument `short` from user.
306///
307/// ## Usage
308///
309/// All invocations begin with the program name: `megatron`. Following
310/// the program is the command and, if the command requires it, the
311/// short name. Short names is the title that profiles are given and
312/// labelled by.
313///
314/// | Command | Action | Short name |
315/// |:-------------------|:--------------------------|:------------|
316/// | run | Launch a profile | yes |
317/// | new | Create a new profile | yes (or) no |
318/// | desc (or) describe | Print profile description | yes |
319/// | ls (or) list | List all profiles | no |
320///
321/// All other usages yield: "That is not a valid command."
322fn main() {
323 let mut command = String::new();
324 let mut short_name = String::new();
325 {
326 let mut ap = ArgumentParser::new();
327 ap.set_description("My purpose: I launch chromium.");
328 ap.refer(&mut command)
329 .add_argument("command", Store, "Command to run");
330 ap.refer(&mut short_name)
331 .add_argument("short name", Store, "short name of web profile");
332 ap.parse_args_or_exit();
333 }
334
335 match command.as_ref() {
336 "new" => {if short_name != "" {new!(short_name);} else {new!();}},
337 "desc" | "describe" => desc(short_name),
338 "ls" | "list" => ls().expect(""),
339 "secreteRussian" => russia(),
340 "run" => default(short_name),
341 _ => println!("That is not a valid command."),
342 }
343}
344