aboutsummaryrefslogtreecommitdiff
path: root/src/main.rs
blob: 3a349128b40a5f2928befc1395a39445a3f73fd4 (plain)
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(())
}