db: allow listing database contents as JSON

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I6a6a6964a756588168f476d984d18f9e8d65bc4e
This commit is contained in:
raf 2025-08-12 22:17:36 +03:00
commit d9b0908ada
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF
3 changed files with 81 additions and 0 deletions

32
Cargo.lock generated
View file

@ -192,6 +192,12 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.9.1" version = "2.9.1"
@ -520,6 +526,12 @@ version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "itoa"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]] [[package]]
name = "jiff" name = "jiff"
version = "0.2.15" version = "0.2.15"
@ -857,6 +869,12 @@ dependencies = [
"windows-sys 0.60.2", "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]] [[package]]
name = "serde" name = "serde"
version = "1.0.219" version = "1.0.219"
@ -877,6 +895,18 @@ dependencies = [
"syn", "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]] [[package]]
name = "shlex" name = "shlex"
version = "1.3.0" version = "1.3.0"
@ -925,6 +955,7 @@ dependencies = [
name = "stash" name = "stash"
version = "0.2.0" version = "0.2.0"
dependencies = [ dependencies = [
"base64",
"clap", "clap",
"clap-verbosity-flag", "clap-verbosity-flag",
"dirs", "dirs",
@ -934,6 +965,7 @@ dependencies = [
"rmp-serde", "rmp-serde",
"rusqlite", "rusqlite",
"serde", "serde",
"serde_json",
"smol", "smol",
"thiserror", "thiserror",
"wl-clipboard-rs", "wl-clipboard-rs",

View file

@ -21,6 +21,8 @@ thiserror = "2.0.14"
wl-clipboard-rs = "0.9.2" wl-clipboard-rs = "0.9.2"
rusqlite = { version = "0.37.0", features = ["bundled"] } rusqlite = { version = "0.37.0", features = ["bundled"] }
smol = "2.0.2" smol = "2.0.2"
serde_json = "1.0.142"
base64 = "0.22.1"
[profile.release] [profile.release]

View file

@ -9,6 +9,10 @@ use rusqlite::{Connection, OptionalExtension, params};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use thiserror::Error; use thiserror::Error;
use base64::Engine;
use base64::engine::general_purpose::STANDARD;
use serde_json::json;
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum StashError { pub enum StashError {
#[error("Input is empty or too large, skipping store.")] #[error("Input is empty or too large, skipping store.")]
@ -103,6 +107,49 @@ impl SqliteClipboardDb {
} }
} }
impl SqliteClipboardDb {
pub fn list_json(&self) -> Result<String, StashError> {
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<u8> = row
.get(1)
.map_err(|e| StashError::ListDecode(e.to_string()))?;
let mime: Option<String> = 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 { impl ClipboardDb for SqliteClipboardDb {
fn store_entry( fn store_entry(
&self, &self,