treewide: format with rustfmt

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I6a6a69642c2865f41a4b141ddf39a198a3fc2e09
This commit is contained in:
raf 2025-08-20 09:40:20 +03:00
commit 6a5cd9b95d
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF
10 changed files with 1191 additions and 1132 deletions

View file

@ -1,10 +1,9 @@
use crate::db::{ClipboardDb, SqliteClipboardDb};
use std::io::{Read, Write};
use crate::db::StashError;
use wl_clipboard_rs::paste::{ClipboardType, MimeType, Seat, get_contents};
use crate::db::{ClipboardDb, SqliteClipboardDb, StashError};
pub trait DecodeCommand {
fn decode(
&self,
@ -50,10 +49,14 @@ impl DecodeCommand for SqliteClipboardDb {
}
// Try decode as usual
match self.decode_entry(input_str.as_bytes(), &mut out, Some(input_str.clone())) {
match self.decode_entry(
input_str.as_bytes(),
&mut out,
Some(input_str.clone()),
) {
Ok(()) => {
log::info!("Entry decoded");
}
},
Err(e) => {
log::error!("Failed to decode entry: {e}");
if let Ok((mut reader, _mime)) =
@ -68,7 +71,7 @@ impl DecodeCommand for SqliteClipboardDb {
} else {
log::error!("Failed to get clipboard contents for relay");
}
}
},
}
Ok(())
}

View file

@ -1,7 +1,7 @@
use crate::db::{ClipboardDb, SqliteClipboardDb, StashError};
use std::io::Read;
use crate::db::{ClipboardDb, SqliteClipboardDb, StashError};
pub trait DeleteCommand {
fn delete(&self, input: impl Read) -> Result<usize, StashError>;
}
@ -12,11 +12,11 @@ impl DeleteCommand for SqliteClipboardDb {
Ok(deleted) => {
log::info!("Deleted {deleted} entries");
Ok(deleted)
}
},
Err(e) => {
log::error!("Failed to delete entries: {e}");
Err(e)
}
},
}
}
}

View file

@ -1,15 +1,21 @@
use std::io::Write;
use crate::db::{ClipboardDb, SqliteClipboardDb, StashError};
use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr;
use crate::db::{ClipboardDb, SqliteClipboardDb, StashError};
pub trait ListCommand {
fn list(&self, out: impl Write, preview_width: u32) -> Result<(), StashError>;
fn list(&self, out: impl Write, preview_width: u32)
-> Result<(), StashError>;
}
impl ListCommand for SqliteClipboardDb {
fn list(&self, out: impl Write, preview_width: u32) -> Result<(), StashError> {
fn list(
&self,
out: impl Write,
preview_width: u32,
) -> Result<(), StashError> {
self.list_entries(out, preview_width)?;
log::info!("Listed clipboard entries");
Ok(())
@ -20,11 +26,16 @@ impl SqliteClipboardDb {
/// Public TUI listing function for use in main.rs
#[allow(clippy::too_many_lines)]
pub fn list_tui(&self, preview_width: u32) -> Result<(), StashError> {
use std::io::stdout;
use crossterm::{
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
execute,
terminal::{
EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode,
EnterAlternateScreen,
LeaveAlternateScreen,
disable_raw_mode,
enable_raw_mode,
},
};
use ratatui::{
@ -34,7 +45,6 @@ impl SqliteClipboardDb {
text::{Line, Span},
widgets::{Block, Borders, List, ListItem, ListState},
};
use std::io::stdout;
// Query entries from DB
let mut stmt = self
@ -61,7 +71,8 @@ impl SqliteClipboardDb {
let mime: Option<String> = row
.get(2)
.map_err(|e| StashError::ListDecode(e.to_string()))?;
let preview = crate::db::preview_entry(&contents, mime.as_deref(), preview_width);
let preview =
crate::db::preview_entry(&contents, mime.as_deref(), preview_width);
let mime_str = mime.as_deref().unwrap_or("").to_string();
let id_str = id.to_string();
max_id_width = max_id_width.max(id_str.width());
@ -74,8 +85,8 @@ impl SqliteClipboardDb {
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)
.map_err(|e| StashError::ListDecode(e.to_string()))?;
let backend = CrosstermBackend::new(stdout);
let mut terminal =
Terminal::new(backend).map_err(|e| StashError::ListDecode(e.to_string()))?;
let mut terminal = Terminal::new(backend)
.map_err(|e| StashError::ListDecode(e.to_string()))?;
let mut state = ListState::default();
if !entries.is_empty() {
@ -165,7 +176,8 @@ impl SqliteClipboardDb {
mwidth += g_width;
}
// Compose the row as highlight + id + space + preview + space + mimetype
// Compose the row as highlight + id + space + preview + space +
// mimetype
let mut spans = Vec::new();
let (id, preview, mime) = entry;
if Some(i) == selected {
@ -234,11 +246,11 @@ impl SqliteClipboardDb {
} else {
i + 1
}
}
},
None => 0,
};
state.select(Some(i));
}
},
KeyCode::Up | KeyCode::Char('k') => {
let i = match state.selected() {
Some(i) => {
@ -247,12 +259,12 @@ impl SqliteClipboardDb {
} else {
i - 1
}
}
},
None => 0,
};
state.select(Some(i));
}
_ => {}
},
_ => {},
}
}
}

View file

@ -1,6 +1,4 @@
use crate::db::{ClipboardDb, SqliteClipboardDb};
use crate::db::StashError;
use crate::db::{ClipboardDb, SqliteClipboardDb, StashError};
pub trait QueryCommand {
fn query_delete(&self, query: &str) -> Result<usize, StashError>;

View file

@ -1,7 +1,7 @@
use crate::db::{ClipboardDb, SqliteClipboardDb};
use std::io::Read;
use crate::db::{ClipboardDb, SqliteClipboardDb};
pub trait StoreCommand {
fn store(
&self,

View file

@ -1,9 +1,10 @@
use crate::db::{ClipboardDb, Entry, SqliteClipboardDb};
use std::{io::Read, time::Duration};
use smol::Timer;
use std::io::Read;
use std::time::Duration;
use wl_clipboard_rs::paste::{ClipboardType, Seat, get_contents};
use crate::db::{ClipboardDb, Entry, SqliteClipboardDb};
pub trait WatchCommand {
fn watch(&self, max_dedupe_search: u64, max_items: u64);
}
@ -64,13 +65,13 @@ impl WatchCommand for SqliteClipboardDb {
// Drop clipboard contents after storing
last_contents = None;
}
}
},
Err(e) => {
let error_msg = e.to_string();
if !error_msg.contains("empty") {
log::error!("Failed to get clipboard contents: {e}");
}
}
},
}
Timer::after(Duration::from_millis(500)).await;
}

View file

@ -1,6 +1,4 @@
use crate::db::{ClipboardDb, SqliteClipboardDb};
use crate::db::StashError;
use crate::db::{ClipboardDb, SqliteClipboardDb, StashError};
pub trait WipeCommand {
fn wipe(&self) -> Result<(), StashError>;

View file

@ -1,20 +1,19 @@
use std::env;
use std::fmt;
use std::fs;
use std::io::{BufRead, BufReader, Read, Write};
use std::str;
use std::{
env,
fmt,
fs,
io::{BufRead, BufReader, Read, Write},
str,
};
use base64::{Engine, engine::general_purpose::STANDARD};
use imagesize::{ImageSize, ImageType};
use log::{error, info, warn};
use regex::Regex;
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;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum StashError {
@ -67,7 +66,11 @@ pub trait ClipboardDb {
fn trim_db(&self, max: u64) -> Result<(), StashError>;
fn delete_last(&self) -> Result<(), StashError>;
fn wipe_db(&self) -> Result<(), StashError>;
fn list_entries(&self, out: impl Write, preview_width: u32) -> Result<usize, StashError>;
fn list_entries(
&self,
out: impl Write,
preview_width: u32,
) -> Result<usize, StashError>;
fn decode_entry(
&self,
in_: impl Read,
@ -98,7 +101,8 @@ pub struct SqliteClipboardDb {
impl SqliteClipboardDb {
pub fn new(conn: Connection) -> Result<Self, StashError> {
conn.execute_batch(
conn
.execute_batch(
"CREATE TABLE IF NOT EXISTS clipboard (
id INTEGER PRIMARY KEY AUTOINCREMENT,
contents BLOB NOT NULL,
@ -138,7 +142,7 @@ impl SqliteClipboardDb {
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!({
@ -148,7 +152,8 @@ impl SqliteClipboardDb {
}));
}
serde_json::to_string_pretty(&entries).map_err(|e| StashError::ListDecode(e.to_string()))
serde_json::to_string_pretty(&entries)
.map_err(|e| StashError::ListDecode(e.to_string()))
}
}
@ -160,7 +165,10 @@ impl ClipboardDb for SqliteClipboardDb {
max_items: u64,
) -> Result<u64, StashError> {
let mut buf = Vec::new();
if input.read_to_end(&mut buf).is_err() || buf.is_empty() || buf.len() > 5 * 1_000_000 {
if input.read_to_end(&mut buf).is_err()
|| buf.is_empty()
|| buf.len() > 5 * 1_000_000
{
return Err(StashError::EmptyOrTooLarge);
}
if buf.iter().all(u8::is_ascii_whitespace) {
@ -175,7 +183,7 @@ impl ClipboardDb for SqliteClipboardDb {
} else {
None
}
}
},
other => other,
};
@ -186,14 +194,17 @@ impl ClipboardDb for SqliteClipboardDb {
if let Ok(s) = std::str::from_utf8(&buf) {
if re.is_match(s) {
warn!("Clipboard entry matches sensitive regex, skipping store.");
return Err(StashError::Store("Filtered by sensitive regex".to_string()));
return Err(StashError::Store(
"Filtered by sensitive regex".to_string(),
));
}
}
}
self.deduplicate(&buf, max_dedupe_search)?;
self.conn
self
.conn
.execute(
"INSERT INTO clipboard (contents, mime) VALUES (?1, ?2)",
params![buf, mime],
@ -224,7 +235,8 @@ impl ClipboardDb for SqliteClipboardDb {
.get(1)
.map_err(|e| StashError::DeduplicationDecode(e.to_string()))?;
if contents == buf {
self.conn
self
.conn
.execute("DELETE FROM clipboard WHERE id = ?1", params![id])
.map_err(|e| StashError::DeduplicationRemove(e.to_string()))?;
deduped += 1;
@ -240,10 +252,14 @@ impl ClipboardDb for SqliteClipboardDb {
.map_err(|e| StashError::Trim(e.to_string()))?;
if count > max {
let to_delete = count - max;
self.conn.execute(
"DELETE FROM clipboard WHERE id IN (SELECT id FROM clipboard ORDER BY id ASC LIMIT ?1)",
self
.conn
.execute(
"DELETE FROM clipboard WHERE id IN (SELECT id FROM clipboard ORDER \
BY id ASC LIMIT ?1)",
params![i64::try_from(to_delete).unwrap_or(i64::MAX)],
).map_err(|e| StashError::Trim(e.to_string()))?;
)
.map_err(|e| StashError::Trim(e.to_string()))?;
}
Ok(())
}
@ -259,7 +275,8 @@ impl ClipboardDb for SqliteClipboardDb {
.optional()
.map_err(|e| StashError::DeleteLast(e.to_string()))?;
if let Some(id) = id {
self.conn
self
.conn
.execute("DELETE FROM clipboard WHERE id = ?1", params![id])
.map_err(|e| StashError::DeleteLast(e.to_string()))?;
Ok(())
@ -269,13 +286,18 @@ impl ClipboardDb for SqliteClipboardDb {
}
fn wipe_db(&self) -> Result<(), StashError> {
self.conn
self
.conn
.execute("DELETE FROM clipboard", [])
.map_err(|e| StashError::Wipe(e.to_string()))?;
Ok(())
}
fn list_entries(&self, mut out: impl Write, preview_width: u32) -> Result<usize, StashError> {
fn list_entries(
&self,
mut out: impl Write,
preview_width: u32,
) -> Result<usize, StashError> {
let mut stmt = self
.conn
.prepare("SELECT id, contents, mime FROM clipboard ORDER BY id DESC")
@ -315,11 +337,13 @@ impl ClipboardDb for SqliteClipboardDb {
input
} else {
let mut buf = String::new();
in_.read_to_string(&mut buf)
in_
.read_to_string(&mut buf)
.map_err(|e| StashError::DecodeRead(e.to_string()))?;
buf
};
let id = extract_id(&s).map_err(|e| StashError::DecodeExtractId(e.to_string()))?;
let id =
extract_id(&s).map_err(|e| StashError::DecodeExtractId(e.to_string()))?;
let (contents, _mime): (Vec<u8>, Option<String>) = self
.conn
.query_row(
@ -328,7 +352,8 @@ impl ClipboardDb for SqliteClipboardDb {
|row| Ok((row.get(0)?, row.get(1)?)),
)
.map_err(|e| StashError::DecodeGet(e.to_string()))?;
out.write_all(&contents)
out
.write_all(&contents)
.map_err(|e| StashError::DecodeWrite(e.to_string()))?;
info!("Decoded entry with id {id}");
Ok(())
@ -354,7 +379,8 @@ impl ClipboardDb for SqliteClipboardDb {
.get(1)
.map_err(|e| StashError::QueryDelete(e.to_string()))?;
if contents.windows(query.len()).any(|w| w == query.as_bytes()) {
self.conn
self
.conn
.execute("DELETE FROM clipboard WHERE id = ?1", params![id])
.map_err(|e| StashError::QueryDelete(e.to_string()))?;
deleted += 1;
@ -368,7 +394,8 @@ impl ClipboardDb for SqliteClipboardDb {
let mut deleted = 0;
for line in reader.lines().map_while(Result::ok) {
if let Ok(id) = extract_id(&line) {
self.conn
self
.conn
.execute("DELETE FROM clipboard WHERE id = ?1", params![id])
.map_err(|e| StashError::DeleteEntry(id, e.to_string()))?;
deleted += 1;
@ -476,7 +503,7 @@ pub fn preview_entry(data: &[u8], mime: Option<&str>, width: u32) -> String {
Err(e) => {
error!("Failed to decode UTF-8 clipboard data: {e}");
""
}
},
};
let s = s.trim().replace(|c: char| c.is_whitespace(), " ");
return truncate(&s, width as usize, "");

View file

@ -1,7 +1,9 @@
use crate::db::{Entry, SqliteClipboardDb, detect_mime};
use log::{error, info};
use std::io::{self, BufRead};
use log::{error, info};
use crate::db::{Entry, SqliteClipboardDb, detect_mime};
pub trait ImportCommand {
fn import_tsv(&self, input: impl io::Read);
}
@ -34,7 +36,7 @@ impl ImportCommand for SqliteClipboardDb {
Ok(_) => {
imported += 1;
info!("Imported entry from TSV");
}
},
Err(e) => error!("Failed to insert entry: {e}"),
}
}

View file

@ -6,7 +6,6 @@ use std::{
};
use atty::Stream;
use clap::{CommandFactory, Parser, Subcommand};
use inquire::Confirm;
@ -14,14 +13,18 @@ mod commands;
mod db;
mod import;
use crate::commands::decode::DecodeCommand;
use crate::commands::delete::DeleteCommand;
use crate::commands::list::ListCommand;
use crate::commands::query::QueryCommand;
use crate::commands::store::StoreCommand;
use crate::commands::watch::WatchCommand;
use crate::commands::wipe::WipeCommand;
use crate::import::ImportCommand;
use crate::{
commands::{
decode::DecodeCommand,
delete::DeleteCommand,
list::ListCommand,
query::QueryCommand,
store::StoreCommand,
watch::WatchCommand,
wipe::WipeCommand,
},
import::ImportCommand,
};
#[derive(Parser)]
#[command(name = "stash")]
@ -65,8 +68,9 @@ enum Command {
/// Decode and output clipboard entry by id
Decode { input: Option<String> },
/// Delete clipboard entry by id (if numeric), or entries matching a query (if not).
/// Numeric arguments are treated as ids. Use --type to specify explicitly.
/// Delete clipboard entry by id (if numeric), or entries matching a query (if
/// not). Numeric arguments are treated as ids. Use --type to specify
/// explicitly.
Delete {
/// Id or query string
arg: Option<String>,
@ -102,13 +106,16 @@ enum Command {
Watch,
}
fn report_error<T>(result: Result<T, impl std::fmt::Display>, context: &str) -> Option<T> {
fn report_error<T>(
result: Result<T, impl std::fmt::Display>,
context: &str,
) -> Option<T> {
match result {
Ok(val) => Some(val),
Err(e) => {
log::error!("{context}: {e}");
None
}
},
}
}
@ -144,7 +151,7 @@ fn main() {
Err(e) => {
log::error!("Failed to initialize SQLite database: {e}");
process::exit(1);
}
},
};
match cli.command {
@ -154,25 +161,28 @@ fn main() {
db.store(io::stdin(), cli.max_dedupe_search, cli.max_items, state),
"Failed to store entry",
);
}
Some(Command::List { format }) => match format.as_deref() {
},
Some(Command::List { format }) => {
match format.as_deref() {
Some("tsv") => {
report_error(
db.list(io::stdout(), cli.preview_width),
"Failed to list entries",
);
}
Some("json") => match db.list_json() {
},
Some("json") => {
match db.list_json() {
Ok(json) => {
println!("{json}");
}
},
Err(e) => {
log::error!("Failed to list entries as JSON: {e}");
},
}
},
Some(other) => {
log::error!("Unsupported format: {other}");
}
},
None => {
if atty::is(Stream::Stdout) {
report_error(
@ -185,6 +195,7 @@ fn main() {
"Failed to list entries",
);
}
},
}
},
Some(Command::Decode { input }) => {
@ -192,7 +203,7 @@ fn main() {
db.decode(io::stdin(), io::stdout(), input),
"Failed to decode entry",
);
}
},
Some(Command::Delete { arg, r#type, ask }) => {
let mut should_proceed = true;
if ask {
@ -218,10 +229,13 @@ fn main() {
} else {
log::error!("Argument is not a valid id");
}
}
},
(Some(s), Some("query")) => {
report_error(db.query_delete(&s), "Failed to delete entry by query");
}
report_error(
db.query_delete(&s),
"Failed to delete entry by query",
);
},
(Some(s), None) => {
if let Ok(id) = s.parse::<u64>() {
use std::io::Cursor;
@ -235,24 +249,25 @@ fn main() {
"Failed to delete entry by query",
);
}
}
},
(None, _) => {
report_error(
db.delete(io::stdin()),
"Failed to delete entry from stdin",
);
}
},
(_, Some(_)) => {
log::error!("Unknown type for --type. Use \"id\" or \"query\".");
},
}
}
}
}
},
Some(Command::Wipe { ask }) => {
let mut should_proceed = true;
if ask {
should_proceed =
Confirm::new("Are you sure you want to wipe all clipboard history?")
should_proceed = Confirm::new(
"Are you sure you want to wipe all clipboard history?",
)
.with_default(false)
.prompt()
.unwrap_or(false);
@ -263,12 +278,15 @@ fn main() {
if should_proceed {
report_error(db.wipe(), "Failed to wipe database");
}
}
},
Some(Command::Import { r#type, ask }) => {
let mut should_proceed = true;
if ask {
should_proceed = Confirm::new("Are you sure you want to import clipboard data? This may overwrite existing entries.")
should_proceed = Confirm::new(
"Are you sure you want to import clipboard data? This may \
overwrite existing entries.",
)
.with_default(false)
.prompt()
.unwrap_or(false);
@ -281,22 +299,22 @@ fn main() {
match format {
"tsv" => {
db.import_tsv(io::stdin());
}
},
_ => {
log::error!("Unsupported import format: {format}");
},
}
}
}
}
},
Some(Command::Watch) => {
db.watch(cli.max_dedupe_search, cli.max_items);
}
},
None => {
if let Err(e) = Cli::command().print_help() {
log::error!("Failed to print help: {e}");
}
println!();
}
},
}
});
}