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
|
mod cmdline;
use clap::Parser;
use cmdline::Args;
use rusqlite::{Connection, Result};
use std::io::Write;
use std::{
collections::HashSet,
fmt::Display,
fs,
path::PathBuf,
process::{Command, Stdio},
};
use url;
#[derive(Debug)]
struct FirefoxPlace {
id: u64,
url: String,
}
struct NoProfileFoundError {}
impl Display for NoProfileFoundError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "No suitable profile was found")
}
}
fn match_firefox_profile() -> Result<PathBuf, NoProfileFoundError> {
todo!()
}
fn copy_db(root_path: Option<&PathBuf>) -> Option<PathBuf> {
let mut root = if let Some(p) = root_path {
p.clone()
} else {
if let Ok(found_profile_path) = match_firefox_profile() {
found_profile_path
} else {
panic!("[Profile] Cannot find any profiles!");
}
};
root.push("places.sqlite");
let tmp_path = PathBuf::from("/tmp/firefox-dmenu-places.db");
match fs::exists(&root) {
Ok(existence) => {
if !existence {
panic!("[Filesystem] Database does not exist!")
} else {
let _ = fs::copy(root, &tmp_path);
return Some(tmp_path);
}
}
Err(_) => {
panic!("[Filesystem] Nondescript error.")
}
};
}
fn main() -> Result<()> {
let args = Args::parse();
let path = if let Some(p) = copy_db(Some(&args.profile)) {
p
} else {
panic!("[main] Database copying failed.")
};
let conn = Connection::open(&path).unwrap();
let query = format!(
"SELECT id, url FROM moz_places ORDER BY last_visit_date DESC LIMIT {}",
args.limit
);
let mut stmt = conn.prepare(&query).unwrap();
let urls_iter = stmt.query_map([], |row| {
Ok(FirefoxPlace {
id: row.get(0)?,
url: row.get(1)?,
})
})?;
let mut hosts: HashSet<String> = HashSet::new();
for place in urls_iter {
if let Ok(p) = place {
let url = url::Url::parse(&p.url);
if let Ok(u) = url {
if let Some(s) = u.host_str() {
hosts.insert(s.to_string());
}
}
}
}
for h in &hosts {
println!("{}", h);
}
let tmp_urls_path = PathBuf::from("/tmp/firefox-dmenu-urls.tmp");
let _ = fs::remove_file(&path);
let dmenu_opts = hosts.into_iter().collect::<Vec<String>>().join("\n");
println!(
"cat {} | {}",
tmp_urls_path.to_str().unwrap(),
args.dmenu.to_str().unwrap()
);
let _ = fs::write(&tmp_urls_path, &dmenu_opts);
let source_command = Command::new("cat")
.arg(&tmp_urls_path)
.stdout(Stdio::piped())
.output()
.expect("[cat] Failed to cat source file. Is it readable?");
let mut dmenu_command = Command::new(args.dmenu)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.expect("[dmenu] Failed to launch dmenu. Is it executable?");
let mut dmenu_stdin = dmenu_command
.stdin
.take()
.expect("[dmenu] Failed to take standard input from dmenu.");
std::thread::spawn(move || {
dmenu_stdin
.write_all(&source_command.stdout)
.expect("[dmenu] Failed to write to stdin of dmenu.");
});
let dmenu_out = dmenu_command
.wait_with_output()
.expect("[dmenu] Expected dmenu to successfully execute.");
let dmenu_sel = String::from_utf8_lossy(&dmenu_out.stdout);
println!("Captured selection: {}", dmenu_sel);
println!("{}", dmenu_sel);
if dmenu_sel.to_string() == String::from("") {
println!("dmenu did not produce any output.");
return Ok(());
}
let _ = Command::new(args.browser)
.arg(dmenu_sel.to_string())
.spawn();
Ok(())
}
|