From 3de33ffcfe5680f6d9f0f2d00f0c622d072230ef Mon Sep 17 00:00:00 2001 From: Christian Friedow Date: Sun, 2 Jun 2024 17:10:26 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=9C=20Firefox=20History=20(#152)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This plugin lets you search and open entried from your Firefox history. This resolves #25. --- README.md | 7 ++ client/src/main.rs | 6 ++ client/src/plugin/brave/history.rs | 2 +- client/src/plugin/firefox/bookmarks.rs | 70 +----------------- client/src/plugin/firefox/history.rs | 98 ++++++++++++++++++++++++++ client/src/plugin/firefox/mod.rs | 2 + client/src/plugin/firefox/utils.rs | 66 +++++++++++++++++ client/src/settings.rs | 14 ++++ home-manager-module.nix | 8 +++ 9 files changed, 203 insertions(+), 70 deletions(-) create mode 100644 client/src/plugin/firefox/history.rs create mode 100644 client/src/plugin/firefox/utils.rs diff --git a/README.md b/README.md index 13cadeb..b4bffad 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,8 @@ plugin: plugin: firefox_bookmarks: enable: true + firefox_history: + enable: true ``` ### Git Repositories @@ -259,6 +261,8 @@ You can specify alternative configuration locations through: enable: true firefox_bookmarks: enable: true + firefox_history: + enable: true git_repositories: enable: true commands: @@ -314,6 +318,9 @@ You can specify alternative configuration locations through: firefox_bookmarks = { enable = true; }; + firefox_history = { + enable = true; + }; git_repositories = { enable = true; commands = [ diff --git a/client/src/main.rs b/client/src/main.rs index d1f6058..b480263 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -189,6 +189,12 @@ impl Application for Centerpiece { >()); } + if self.settings.plugin.firefox_history.enable { + subscriptions.push(crate::plugin::utils::spawn::< + crate::plugin::firefox::history::HistoryPlugin, + >()); + } + if self.settings.plugin.git_repositories.enable { subscriptions.push(crate::plugin::utils::spawn::< crate::plugin::git_repositories::GitRepositoriesPlugin, diff --git a/client/src/plugin/brave/history.rs b/client/src/plugin/brave/history.rs index 0ac5dab..af80d58 100644 --- a/client/src/plugin/brave/history.rs +++ b/client/src/plugin/brave/history.rs @@ -15,7 +15,7 @@ impl Plugin for HistoryPlugin { } fn title() -> &'static str { - "󰃃 History" + "󰋚 History" } fn entries(&self) -> Vec { diff --git a/client/src/plugin/firefox/bookmarks.rs b/client/src/plugin/firefox/bookmarks.rs index d896b33..211d924 100644 --- a/client/src/plugin/firefox/bookmarks.rs +++ b/client/src/plugin/firefox/bookmarks.rs @@ -5,73 +5,6 @@ pub struct BookmarksPlugin { entries: Vec, } -#[derive(serde::Deserialize, Debug)] -#[serde(untagged)] -#[allow(dead_code)] -enum Section { - #[serde(rename_all = "PascalCase")] - Profile { - name: String, - is_relative: String, - path: String, - default: Option, - }, - #[serde(rename_all = "PascalCase")] - General { - start_with_last_profile: String, - version: Option, - }, - - #[serde(rename_all = "PascalCase")] - Install { default: String, locked: String }, -} - -fn profile_path() -> anyhow::Result { - let home_directory = std::env::var("HOME")?; - - let profiles_file_path = format!("{home_directory}/.mozilla/firefox/profiles.ini"); - let profiles_file = std::fs::File::open(profiles_file_path)?; - let profiles_file_contents: std::collections::HashMap = - serde_ini::from_read(profiles_file)?; - - let mut default_profile = profiles_file_contents - .values() - .find(|section| match section { - Section::Profile { default, .. } => { - default.clone().unwrap_or(String::from("")) == String::from("1") - } - _ => false, - }); - - if default_profile.is_none() { - default_profile = profiles_file_contents - .values() - .find(|section| match section { - Section::Profile { .. } => true, - _ => false, - }); - } - - if default_profile.is_none() { - return Err(anyhow::anyhow!("Could not find a firefox profile.")); - } - - match default_profile.unwrap() { - Section::Profile { - is_relative, path, .. - } => { - if is_relative.eq(&String::from("1")) { - Ok(format!("{home_directory}/.mozilla/firefox/{path}")) - } else { - Ok(path.clone()) - } - } - _ => { - unreachable!("A non-profile section should be parsed as a profile."); - } - } -} - impl Plugin for BookmarksPlugin { fn id() -> &'static str { "firefox_bookmarks" @@ -99,8 +32,7 @@ impl Plugin for BookmarksPlugin { fn update_entries(&mut self) -> anyhow::Result<()> { self.entries.clear(); - let profile_path = profile_path()?; - + let profile_path = crate::plugin::firefox::utils::profile_path()?; let bookmarks_file_path = format!("{profile_path}/places.sqlite"); let cache_directory = crate::plugin::utils::centerpiece_cache_directory()?; let bookmarks_cache_file_path = format!("{cache_directory}/firefox-bookmarks.sqlite"); diff --git a/client/src/plugin/firefox/history.rs b/client/src/plugin/firefox/history.rs new file mode 100644 index 0000000..6304631 --- /dev/null +++ b/client/src/plugin/firefox/history.rs @@ -0,0 +1,98 @@ +use crate::plugin::utils::Plugin; +use anyhow::Context; + +pub struct HistoryPlugin { + entries: Vec, +} + +impl Plugin for HistoryPlugin { + fn id() -> &'static str { + "firefox_history" + } + + fn priority() -> u32 { + 0 + } + + fn title() -> &'static str { + "󰋚 History" + } + + fn entries(&self) -> Vec { + self.entries.clone() + } + + fn set_entries(&mut self, entries: Vec) { + self.entries = entries; + } + + fn new() -> Self { + Self { entries: vec![] } + } + + fn update_entries(&mut self) -> anyhow::Result<()> { + self.entries.clear(); + let profile_path = crate::plugin::firefox::utils::profile_path()?; + let history_file_path = format!("{profile_path}/places.sqlite"); + let cache_directory = crate::plugin::utils::centerpiece_cache_directory()?; + let history_cache_file_path = format!("{cache_directory}/firefox-history.sqlite"); + + std::fs::copy(history_file_path, &history_cache_file_path) + .context("Error while creating cache directory")?; + + let connection = sqlite::open(history_cache_file_path)?; + let query = " + SELECT title, url + FROM moz_places + GROUP BY title + ORDER BY visit_count DESC"; + + connection.execute(query)?; + + let url_rows = connection + .prepare(query) + .unwrap() + .into_iter() + .map(|row| row.unwrap()); + + self.entries = url_rows + .map(|row| { + let title = row.read::, _>("title"); + let url = row.read::<&str, _>("url"); + + crate::model::Entry { + id: url.to_string(), + title: title.unwrap_or(url).to_string(), + action: String::from("open"), + meta: String::from("History"), + command: None, + } + }) + .collect(); + + Ok(()) + } + + fn activate( + &mut self, + entry: crate::model::Entry, + plugin_channel_out: &mut iced::futures::channel::mpsc::Sender, + ) -> anyhow::Result<()> { + std::process::Command::new("firefox") + .arg(&entry.id) + .spawn() + .context(format!( + "Failed to launch firefox while activating entry with id '{}'.", + entry.id + ))?; + + plugin_channel_out + .try_send(crate::Message::Exit) + .context(format!( + "Failed to send message to exit application while activating entry with id '{}'.", + entry.id + ))?; + + Ok(()) + } +} diff --git a/client/src/plugin/firefox/mod.rs b/client/src/plugin/firefox/mod.rs index bf3e888..c55dac7 100644 --- a/client/src/plugin/firefox/mod.rs +++ b/client/src/plugin/firefox/mod.rs @@ -1 +1,3 @@ pub mod bookmarks; +pub mod history; +pub mod utils; diff --git a/client/src/plugin/firefox/utils.rs b/client/src/plugin/firefox/utils.rs new file mode 100644 index 0000000..ab19014 --- /dev/null +++ b/client/src/plugin/firefox/utils.rs @@ -0,0 +1,66 @@ +#[derive(serde::Deserialize, Debug)] +#[serde(untagged)] +#[allow(dead_code)] +enum Section { + #[serde(rename_all = "PascalCase")] + Profile { + name: String, + is_relative: String, + path: String, + default: Option, + }, + #[serde(rename_all = "PascalCase")] + General { + start_with_last_profile: String, + version: Option, + }, + + #[serde(rename_all = "PascalCase")] + Install { default: String, locked: String }, +} + +pub fn profile_path() -> anyhow::Result { + let home_directory = std::env::var("HOME")?; + + let profiles_file_path = format!("{home_directory}/.mozilla/firefox/profiles.ini"); + let profiles_file = std::fs::File::open(profiles_file_path)?; + let profiles_file_contents: std::collections::HashMap = + serde_ini::from_read(profiles_file)?; + + let mut default_profile = profiles_file_contents + .values() + .find(|section| match section { + Section::Profile { default, .. } => { + default.clone().unwrap_or(String::from("")) == String::from("1") + } + _ => false, + }); + + if default_profile.is_none() { + default_profile = profiles_file_contents + .values() + .find(|section| match section { + Section::Profile { .. } => true, + _ => false, + }); + } + + if default_profile.is_none() { + return Err(anyhow::anyhow!("Could not find a firefox profile.")); + } + + match default_profile.unwrap() { + Section::Profile { + is_relative, path, .. + } => { + if is_relative.eq(&String::from("1")) { + Ok(format!("{home_directory}/.mozilla/firefox/{path}")) + } else { + Ok(path.clone()) + } + } + _ => { + unreachable!("A non-profile section should be parsed as a profile."); + } + } +} diff --git a/client/src/settings.rs b/client/src/settings.rs index 7073170..4b27f1d 100644 --- a/client/src/settings.rs +++ b/client/src/settings.rs @@ -93,6 +93,18 @@ impl Default for FirefoxBookmarksPluginSettings { } } +#[derive(Debug, Deserialize)] +pub struct FirefoxHistoryPluginSettings { + #[serde(default = "default_true")] + pub enable: bool, +} + +impl Default for FirefoxHistoryPluginSettings { + fn default() -> Self { + Self { enable: true } + } +} + #[derive(Debug, Deserialize)] pub struct GitRepositoriesPluginSettings { #[serde(default = "default_true")] @@ -257,6 +269,8 @@ pub struct PluginSettings { #[serde(default)] pub firefox_bookmarks: FirefoxBookmarksPluginSettings, #[serde(default)] + pub firefox_history: FirefoxHistoryPluginSettings, + #[serde(default)] pub git_repositories: GitRepositoriesPluginSettings, #[serde(default)] pub gitmoji: GitmojiPluginSettings, diff --git a/home-manager-module.nix b/home-manager-module.nix index 810b59b..de08f2f 100644 --- a/home-manager-module.nix +++ b/home-manager-module.nix @@ -56,6 +56,14 @@ in { }; }; + firefox_history = { + enable = lib.mkOption { + default = true; + type = lib.types.bool; + description = lib.mdDoc "Enable / disable the plugin."; + }; + }; + git_repositories = { enable = lib.mkOption { default = true;