diff --git a/Cargo.lock b/Cargo.lock index a52df6b..774e972 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -192,17 +192,6 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi 0.1.19", - "libc", - "winapi", -] - [[package]] name = "autocfg" version = "1.5.0" @@ -712,15 +701,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - [[package]] name = "hermit-abi" version = "0.5.2" @@ -1058,7 +1038,7 @@ checksum = "b5bd19146350fe804f7cb2669c851c03d69da628803dab0d98018142aaa5d829" dependencies = [ "cfg-if", "concurrent-queue", - "hermit-abi 0.5.2", + "hermit-abi", "pin-project-lite", "rustix 1.0.8", "windows-sys 0.60.2", @@ -1342,7 +1322,6 @@ dependencies = [ name = "stash" version = "0.2.4" dependencies = [ - "atty", "base64", "clap", "clap-verbosity-flag", diff --git a/Cargo.toml b/Cargo.toml index e63edd1..ca39681 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,6 @@ serde_json = "1.0.142" base64 = "0.22.1" regex = "1.11.1" ratatui = "0.29.0" -atty = "0.2.14" crossterm = "0.29.0" unicode-segmentation = "1.12.0" unicode-width = "0.2.0" diff --git a/src/commands/decode.rs b/src/commands/decode.rs index e6df237..9dc9116 100644 --- a/src/commands/decode.rs +++ b/src/commands/decode.rs @@ -24,26 +24,33 @@ impl DecodeCommand for SqliteClipboardDb { s } else { let mut buf = String::new(); - if let Err(e) = in_.read_to_string(&mut buf) { - log::error!("Failed to read stdin for decode: {e}"); - } + in_ + .read_to_string(&mut buf) + .map_err(|e| StashError::DecodeRead(e.to_string()))?; buf }; // If input is empty or whitespace, treat as error and trigger fallback if input_str.trim().is_empty() { - log::info!("No input provided to decode; relaying clipboard to stdout"); + log::debug!("No input provided to decode; relaying clipboard to stdout"); if let Ok((mut reader, _mime)) = get_contents(ClipboardType::Regular, Seat::Unspecified, MimeType::Any) { let mut buf = Vec::new(); - if let Err(err) = reader.read_to_end(&mut buf) { - log::error!("Failed to read clipboard for relay: {err}"); - } else { - let _ = out.write_all(&buf); - } + reader.read_to_end(&mut buf).map_err(|e| { + StashError::DecodeRead(format!( + "Failed to read clipboard for relay: {e}" + )) + })?; + out.write_all(&buf).map_err(|e| { + StashError::DecodeWrite(format!( + "Failed to write clipboard relay: {e}" + )) + })?; } else { - log::error!("Failed to get clipboard contents for relay"); + return Err(StashError::DecodeGet( + "Failed to get clipboard contents for relay".to_string(), + )); } return Ok(()); } @@ -54,25 +61,28 @@ impl DecodeCommand for SqliteClipboardDb { &mut out, Some(input_str.clone()), ) { - Ok(()) => { - log::info!("Entry decoded"); - }, + Ok(()) => Ok(()), Err(e) => { - log::error!("Failed to decode entry: {e}"); + // On decode failure, relay clipboard as fallback if let Ok((mut reader, _mime)) = get_contents(ClipboardType::Regular, Seat::Unspecified, MimeType::Any) { let mut buf = Vec::new(); - if let Err(err) = reader.read_to_end(&mut buf) { - log::error!("Failed to read clipboard for relay: {err}"); - } else { - let _ = out.write_all(&buf); - } + reader.read_to_end(&mut buf).map_err(|err| { + StashError::DecodeRead(format!( + "Failed to read clipboard for relay: {err}" + )) + })?; + out.write_all(&buf).map_err(|err| { + StashError::DecodeWrite(format!( + "Failed to write clipboard relay: {err}" + )) + })?; + Ok(()) } else { - log::error!("Failed to get clipboard contents for relay"); + Err(e) } }, } - Ok(()) } } diff --git a/src/commands/delete.rs b/src/commands/delete.rs index e7e2c92..dd84989 100644 --- a/src/commands/delete.rs +++ b/src/commands/delete.rs @@ -8,15 +8,8 @@ pub trait DeleteCommand { impl DeleteCommand for SqliteClipboardDb { fn delete(&self, input: impl Read) -> Result { - match self.delete_entries(input) { - Ok(deleted) => { - log::info!("Deleted {deleted} entries"); - Ok(deleted) - }, - Err(e) => { - log::error!("Failed to delete entries: {e}"); - Err(e) - }, - } + let deleted = self.delete_entries(input)?; + log::info!("Deleted {deleted} entries"); + Ok(deleted) } } diff --git a/src/commands/import.rs b/src/commands/import.rs index 95cb06e..05833d7 100644 --- a/src/commands/import.rs +++ b/src/commands/import.rs @@ -1,7 +1,5 @@ use std::io::{self, BufRead}; -use log::{error, info}; - use crate::db::{ ClipboardDb, Entry, @@ -12,18 +10,6 @@ use crate::db::{ pub trait ImportCommand { /// Import clipboard entries from TSV format. - /// - /// # Arguments - /// - /// * `input` - A readable stream containing TSV lines, each of the form - /// `\t`. - /// * `max_items` - The maximum number of clipboard entries to keep after - /// import. If set to `u64::MAX`, no trimming occurs. - /// - /// # Returns - /// - /// * `Ok(())` if all entries are imported and trimming succeeds. - /// * `Err(StashError)` if any error occurs during import or trimming. fn import_tsv( &self, input: impl io::Read, @@ -39,16 +25,21 @@ impl ImportCommand for SqliteClipboardDb { ) -> Result<(), StashError> { let reader = io::BufReader::new(input); let mut imported = 0; - for line in reader.lines().map_while(Result::ok) { + for (lineno, line) in reader.lines().enumerate() { + let line = line.map_err(|e| { + StashError::Store(format!("Failed to read line {lineno}: {e}")) + })?; let mut parts = line.splitn(2, '\t'); let (Some(id_str), Some(val)) = (parts.next(), parts.next()) else { - error!("Malformed TSV line: {line:?}"); - continue; + return Err(StashError::Store(format!( + "Malformed TSV line {lineno}: {line:?}" + ))); }; let Ok(_id) = id_str.parse::() else { - error!("Failed to parse id from line: {id_str}"); - continue; + return Err(StashError::Store(format!( + "Failed to parse id from line {lineno}: {id_str}" + ))); }; let entry = Entry { @@ -56,22 +47,26 @@ impl ImportCommand for SqliteClipboardDb { mime: detect_mime(val.as_bytes()), }; - match self.conn.execute( - "INSERT INTO clipboard (contents, mime) VALUES (?1, ?2)", - rusqlite::params![entry.contents, entry.mime], - ) { - Ok(_) => { - imported += 1; - info!("Imported entry from TSV"); - }, - Err(e) => error!("Failed to insert entry: {e}"), - } + self + .conn + .execute( + "INSERT INTO clipboard (contents, mime) VALUES (?1, ?2)", + rusqlite::params![entry.contents, entry.mime], + ) + .map_err(|e| { + StashError::Store(format!( + "Failed to insert entry at line {lineno}: {e}" + )) + })?; + imported += 1; } - info!("Imported {imported} records from TSV into SQLite database."); + + log::info!("Imported {imported} records from TSV into SQLite database."); // Trim database to max_items after import self.trim_db(max_items)?; - info!("Trimmed clipboard database to max_items = {max_items}"); + log::info!("Trimmed clipboard database to max_items = {max_items}"); + Ok(()) } } diff --git a/src/commands/list.rs b/src/commands/list.rs index 1464956..75c1ce5 100644 --- a/src/commands/list.rs +++ b/src/commands/list.rs @@ -16,14 +16,11 @@ impl ListCommand for SqliteClipboardDb { out: impl Write, preview_width: u32, ) -> Result<(), StashError> { - self.list_entries(out, preview_width)?; - log::info!("Listed clipboard entries"); - Ok(()) + self.list_entries(out, preview_width).map(|_| ()) } } 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; @@ -272,14 +269,14 @@ impl SqliteClipboardDb { Ok(()) })(); - disable_raw_mode().ok(); - execute!( + // Ignore errors during terminal restore, as we can't recover here. + let _ = disable_raw_mode(); + let _ = execute!( terminal.backend_mut(), LeaveAlternateScreen, DisableMouseCapture - ) - .ok(); - terminal.show_cursor().ok(); + ); + let _ = terminal.show_cursor(); res } diff --git a/src/main.rs b/src/main.rs index 6033b64..0b40450 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,10 @@ use std::{ env, - io::{self}, + io::{self, IsTerminal}, path::PathBuf, process, }; -use atty::Stream; use clap::{CommandFactory, Parser, Subcommand}; use inquire::Confirm; @@ -44,7 +43,7 @@ struct Cli { #[arg(long, default_value_t = 100)] preview_width: u32, - /// Path to the SQLite clipboard database file. + /// Path to the `SQLite` clipboard database file. #[arg(long)] db_path: Option, @@ -187,7 +186,7 @@ fn main() { log::error!("Unsupported format: {other}"); }, None => { - if atty::is(Stream::Stdout) { + if std::io::stdout().is_terminal() { report_error( db.list_tui(cli.preview_width), "Failed to list entries in TUI",