about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorBenedikt Peetz <benedikt.peetz@b-peetz.de>2025-04-04 16:07:34 +0200
committerBenedikt Peetz <benedikt.peetz@b-peetz.de>2025-04-04 16:07:34 +0200
commit2a9d37026a5b2599930c2d7338cdf04bf254812b (patch)
treea818c19634a05f500daaccf42e36205a28a52635
parentfix(pkgs/tskm): Remove typos (diff)
downloadnixos-config-2a9d37026a5b2599930c2d7338cdf04bf254812b.zip
feat(pkgs/tskm): Support listing the open tabs in a project
Diffstat (limited to '')
-rw-r--r--pkgs/by-name/ts/tskm/Cargo.lock27
-rw-r--r--pkgs/by-name/ts/tskm/Cargo.toml3
-rw-r--r--pkgs/by-name/ts/tskm/src/cli.rs7
-rw-r--r--pkgs/by-name/ts/tskm/src/interface/open/handle.rs43
-rw-r--r--pkgs/by-name/ts/tskm/src/interface/open/mod.rs104
5 files changed, 183 insertions, 1 deletions
diff --git a/pkgs/by-name/ts/tskm/Cargo.lock b/pkgs/by-name/ts/tskm/Cargo.lock
index 879cbf57..4a064858 100644
--- a/pkgs/by-name/ts/tskm/Cargo.lock
+++ b/pkgs/by-name/ts/tskm/Cargo.lock
@@ -470,6 +470,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
 
 [[package]]
+name = "lz4_flex"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75761162ae2b0e580d7e7c390558127e5f01b4194debd6221fd8c207fc80e3f5"
+dependencies = [
+ "twox-hash",
+]
+
+[[package]]
 name = "memchr"
 version = "2.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -603,6 +612,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
 
 [[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
 name = "stderrlog"
 version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -701,6 +716,7 @@ dependencies = [
  "clap_complete",
  "dirs",
  "log",
+ "lz4_flex",
  "serde",
  "serde_json",
  "stderrlog",
@@ -709,6 +725,16 @@ dependencies = [
 ]
 
 [[package]]
+name = "twox-hash"
+version = "1.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675"
+dependencies = [
+ "cfg-if",
+ "static_assertions",
+]
+
+[[package]]
 name = "unicode-ident"
 version = "1.0.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -723,6 +749,7 @@ dependencies = [
  "form_urlencoded",
  "idna",
  "percent-encoding",
+ "serde",
 ]
 
 [[package]]
diff --git a/pkgs/by-name/ts/tskm/Cargo.toml b/pkgs/by-name/ts/tskm/Cargo.toml
index 3344e378..d2990b0c 100644
--- a/pkgs/by-name/ts/tskm/Cargo.toml
+++ b/pkgs/by-name/ts/tskm/Cargo.toml
@@ -10,10 +10,11 @@ anyhow = "1.0.97"
 clap = { version = "4.5.34", features = ["derive"] }
 dirs = "6.0.0"
 log = "0.4.27"
+lz4_flex = "0.11.3"
 serde = { version = "1.0.219", features = ["derive"] }
 serde_json = "1.0.140"
 stderrlog = "0.6.0"
-url = { version = "2.5.4" }
+url = { version = "2.5.4", features = ["serde"] }
 walkdir = "2.5.0"
 
 [profile.release]
diff --git a/pkgs/by-name/ts/tskm/src/cli.rs b/pkgs/by-name/ts/tskm/src/cli.rs
index 958033b3..bf0af7fb 100644
--- a/pkgs/by-name/ts/tskm/src/cli.rs
+++ b/pkgs/by-name/ts/tskm/src/cli.rs
@@ -88,6 +88,13 @@ pub enum OpenCommand {
     /// This will use rofi's dmenu mode to select one project from the list of all registered
     /// projects.
     Select,
+
+    /// List all open tabs in the project.
+    ListTabs {
+        /// The project to open.
+        #[arg(value_parser = task::Project::from_project_string)]
+        project: Option<task::Project>,
+    }
 }
 
 #[derive(Subcommand, Debug)]
diff --git a/pkgs/by-name/ts/tskm/src/interface/open/handle.rs b/pkgs/by-name/ts/tskm/src/interface/open/handle.rs
index 5738d232..08bd1de4 100644
--- a/pkgs/by-name/ts/tskm/src/interface/open/handle.rs
+++ b/pkgs/by-name/ts/tskm/src/interface/open/handle.rs
@@ -58,6 +58,49 @@ pub fn handle(command: OpenCommand) -> Result<()> {
 
             open_in_browser(&selected_project).context("Failed to open project")?;
         }
+        OpenCommand::ListTabs { project } => {
+            let project = if let Some(p) = project {
+                p
+            } else if let Some(p) =
+                task::Project::get_current().context("Failed to get currently focused project")?
+            {
+                p
+            } else {
+                bail!("You need to either supply a project or have a project active!");
+            };
+
+            let session_store = project.get_sessionstore().with_context(|| {
+                format!(
+                    "Failed to get session store for project: '{}'",
+                    project.to_project_display()
+                )
+            })?;
+
+            let selected = session_store
+                .windows
+                .iter()
+                .map(|w| w.selected)
+                .collect::<Vec<_>>();
+
+            let tabs = session_store
+                .windows
+                .iter()
+                .flat_map(|window| window.tabs.iter())
+                .map(|tab| tab.entries.get(tab.index - 1).expect("This should be Some"))
+                .collect::<Vec<_>>();
+
+            for (index, entry) in tabs.iter().enumerate() {
+                let index = index + 1;
+                let is_selected = {
+                    if selected.contains(&index) {
+                        "🔻 "
+                    } else {
+                        "   "
+                    }
+                };
+                println!("{}{}", is_selected, entry.url);
+            }
+        }
     }
     Ok(())
 }
diff --git a/pkgs/by-name/ts/tskm/src/interface/open/mod.rs b/pkgs/by-name/ts/tskm/src/interface/open/mod.rs
index a70793bc..2dc75957 100644
--- a/pkgs/by-name/ts/tskm/src/interface/open/mod.rs
+++ b/pkgs/by-name/ts/tskm/src/interface/open/mod.rs
@@ -1,2 +1,106 @@
+use std::{collections::HashMap, fs::File, io};
+
+use anyhow::{Context, Result};
+use lz4_flex::decompress_size_prepended;
+use serde::Deserialize;
+use serde_json::Value;
+use url::Url;
+
+use crate::task::Project;
+
 pub mod handle;
 pub use handle::handle;
+
+impl Project {
+    pub(super) fn get_sessionstore(&self) -> Result<SessionStore> {
+        let path = dirs::home_dir()
+            .expect("Will exist")
+            .join(".mozilla/firefox")
+            .join(self.to_project_display())
+            .join("sessionstore-backups/recovery.jsonlz4");
+        let file = decompress_mozlz4(
+            File::open(&path)
+                .with_context(|| format!("Failed to open path '{}'", path.display()))?,
+        )
+        .with_context(|| format!("Failed to decompress file as mozlzh '{}'", path.display()))?;
+
+        let contents: SessionStore = serde_json::from_str(&file).with_context(|| {
+            format!(
+                "Failed to deserialize file ('{}') as session store.",
+                path.display()
+            )
+        })?;
+        Ok(contents)
+    }
+}
+
+fn decompress_mozlz4<P: io::Read>(mut file: P) -> Result<String> {
+    const MOZLZ4_MAGIC_NUMBER: &[u8] = b"mozLz40\0";
+
+    let mut buf = [0u8; 8];
+    file.read_exact(&mut buf)
+        .context("Failed to read the mozlz40 header.")?;
+
+    assert_eq!(buf, MOZLZ4_MAGIC_NUMBER);
+
+    let mut buf = vec![];
+    file.read_to_end(&mut buf).context("Failed to read file")?;
+
+    let uncompressed = decompress_size_prepended(&buf).context("Failed to decompress file")?;
+
+    Ok(String::from_utf8(uncompressed).expect("This should be valid json and thus utf8"))
+}
+
+#[derive(Deserialize, Debug)]
+pub struct SessionStore {
+    pub windows: Vec<Window>,
+}
+
+#[derive(Deserialize, Debug)]
+pub struct Window {
+    pub tabs: Vec<Tab>,
+    pub selected: usize,
+}
+
+#[derive(Deserialize, Debug)]
+pub struct Tab {
+    pub entries: Vec<TabEntry>,
+    #[serde(rename = "lastAccessed")]
+    pub last_accessed: u64,
+    pub hidden: bool,
+    #[serde(rename = "searchMode")]
+    pub search_mode: Option<Value>,
+    #[serde(rename = "userContextId")]
+    pub user_context_id: u32,
+    pub attributes: TabAttributes,
+    #[serde(rename = "extData")]
+    pub ext_data: Option<HashMap<String, Value>>,
+    pub index: usize,
+    #[serde(rename = "requestedIndex")]
+    pub requested_index: Option<u32>,
+    pub image: Option<Url>,
+}
+
+#[derive(Deserialize, Debug)]
+pub struct TabEntry {
+    pub url: Url,
+    pub title: String,
+    #[serde(rename = "cacheKey")]
+    pub cache_key: u32,
+    #[serde(rename = "ID")]
+    pub id: u32,
+    #[serde(rename = "docshellUUID")]
+    pub docshell_uuid: Value,
+    #[serde(rename = "resultPrincipalURI")]
+    pub result_principal_uri: Option<Url>,
+    #[serde(rename = "hasUserInteraction")]
+    pub has_user_interaction: bool,
+    #[serde(rename = "triggeringPrincipal_base64")]
+    pub triggering_principal_base64: Value,
+    #[serde(rename = "docIdentifier")]
+    pub doc_identifier: u32,
+    pub persist: bool,
+}
+
+#[derive(Deserialize, Debug, Clone, Copy)]
+pub struct TabAttributes {}