diff --git a/Cargo.lock b/Cargo.lock index 39be59b..0428cb5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -192,6 +192,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "bitflags" version = "2.9.1" @@ -520,6 +526,12 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + [[package]] name = "jiff" version = "0.2.15" @@ -857,6 +869,12 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + [[package]] name = "serde" version = "1.0.219" @@ -877,6 +895,18 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_json" +version = "1.0.142" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + [[package]] name = "shlex" version = "1.3.0" @@ -925,6 +955,7 @@ dependencies = [ name = "stash" version = "0.2.0" dependencies = [ + "base64", "clap", "clap-verbosity-flag", "dirs", @@ -934,6 +965,7 @@ dependencies = [ "rmp-serde", "rusqlite", "serde", + "serde_json", "smol", "thiserror", "wl-clipboard-rs", diff --git a/Cargo.toml b/Cargo.toml index 4529591..dc76816 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,8 @@ thiserror = "2.0.14" wl-clipboard-rs = "0.9.2" rusqlite = { version = "0.37.0", features = ["bundled"] } smol = "2.0.2" +serde_json = "1.0.142" +base64 = "0.22.1" [profile.release] diff --git a/src/db/mod.rs b/src/db/mod.rs index 97d0ad0..c675c98 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -9,6 +9,10 @@ use rusqlite::{Connection, OptionalExtension, params}; use serde::{Deserialize, Serialize}; use thiserror::Error; +use base64::Engine; +use base64::engine::general_purpose::STANDARD; +use serde_json::json; + #[derive(Error, Debug)] pub enum StashError { #[error("Input is empty or too large, skipping store.")] @@ -103,6 +107,49 @@ impl SqliteClipboardDb { } } +impl SqliteClipboardDb { + pub fn list_json(&self) -> Result { + let mut stmt = self + .conn + .prepare("SELECT id, contents, mime FROM clipboard ORDER BY id DESC") + .map_err(|e| StashError::ListDecode(e.to_string()))?; + let mut rows = stmt + .query([]) + .map_err(|e| StashError::ListDecode(e.to_string()))?; + + let mut entries = Vec::new(); + + while let Some(row) = rows + .next() + .map_err(|e| StashError::ListDecode(e.to_string()))? + { + let id: u64 = row + .get(0) + .map_err(|e| StashError::ListDecode(e.to_string()))?; + let contents: Vec = row + .get(1) + .map_err(|e| StashError::ListDecode(e.to_string()))?; + let mime: Option = row + .get(2) + .map_err(|e| StashError::ListDecode(e.to_string()))?; + let contents_str = match mime.as_deref() { + Some(m) if m.starts_with("text/") || m == "application/json" => { + String::from_utf8_lossy(&contents).to_string() + } + _ => STANDARD.encode(&contents), + }; + entries.push(json!({ + "id": id, + "contents": contents_str, + "mime": mime, + })); + } + + Ok(serde_json::to_string_pretty(&entries) + .map_err(|e| StashError::ListDecode(e.to_string()))?) + } +} + impl ClipboardDb for SqliteClipboardDb { fn store_entry( &self,