commands: more consistent error propagation

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I6a6a69647a0eb8de028e4251465fbb94f0a14cef
This commit is contained in:
raf 2025-08-20 11:14:51 +03:00
commit a819e2948d
4 changed files with 66 additions and 71 deletions

View file

@ -24,26 +24,33 @@ impl DecodeCommand for SqliteClipboardDb {
s s
} else { } else {
let mut buf = String::new(); let mut buf = String::new();
if let Err(e) = in_.read_to_string(&mut buf) { in_
log::error!("Failed to read stdin for decode: {e}"); .read_to_string(&mut buf)
} .map_err(|e| StashError::DecodeRead(e.to_string()))?;
buf buf
}; };
// If input is empty or whitespace, treat as error and trigger fallback // If input is empty or whitespace, treat as error and trigger fallback
if input_str.trim().is_empty() { 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)) = if let Ok((mut reader, _mime)) =
get_contents(ClipboardType::Regular, Seat::Unspecified, MimeType::Any) get_contents(ClipboardType::Regular, Seat::Unspecified, MimeType::Any)
{ {
let mut buf = Vec::new(); let mut buf = Vec::new();
if let Err(err) = reader.read_to_end(&mut buf) { reader.read_to_end(&mut buf).map_err(|e| {
log::error!("Failed to read clipboard for relay: {err}"); StashError::DecodeRead(format!(
} else { "Failed to read clipboard for relay: {e}"
let _ = out.write_all(&buf); ))
} })?;
out.write_all(&buf).map_err(|e| {
StashError::DecodeWrite(format!(
"Failed to write clipboard relay: {e}"
))
})?;
} else { } 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(()); return Ok(());
} }
@ -54,25 +61,28 @@ impl DecodeCommand for SqliteClipboardDb {
&mut out, &mut out,
Some(input_str.clone()), Some(input_str.clone()),
) { ) {
Ok(()) => { Ok(()) => Ok(()),
log::info!("Entry decoded");
},
Err(e) => { Err(e) => {
log::error!("Failed to decode entry: {e}"); // On decode failure, relay clipboard as fallback
if let Ok((mut reader, _mime)) = if let Ok((mut reader, _mime)) =
get_contents(ClipboardType::Regular, Seat::Unspecified, MimeType::Any) get_contents(ClipboardType::Regular, Seat::Unspecified, MimeType::Any)
{ {
let mut buf = Vec::new(); let mut buf = Vec::new();
if let Err(err) = reader.read_to_end(&mut buf) { reader.read_to_end(&mut buf).map_err(|err| {
log::error!("Failed to read clipboard for relay: {err}"); StashError::DecodeRead(format!(
} else { "Failed to read clipboard for relay: {err}"
let _ = out.write_all(&buf); ))
} })?;
out.write_all(&buf).map_err(|err| {
StashError::DecodeWrite(format!(
"Failed to write clipboard relay: {err}"
))
})?;
Ok(())
} else { } else {
log::error!("Failed to get clipboard contents for relay"); Err(e)
} }
}, },
} }
Ok(())
} }
} }

View file

@ -8,15 +8,8 @@ pub trait DeleteCommand {
impl DeleteCommand for SqliteClipboardDb { impl DeleteCommand for SqliteClipboardDb {
fn delete(&self, input: impl Read) -> Result<usize, StashError> { fn delete(&self, input: impl Read) -> Result<usize, StashError> {
match self.delete_entries(input) { let deleted = self.delete_entries(input)?;
Ok(deleted) => { log::info!("Deleted {deleted} entries");
log::info!("Deleted {deleted} entries"); Ok(deleted)
Ok(deleted)
},
Err(e) => {
log::error!("Failed to delete entries: {e}");
Err(e)
},
}
} }
} }

View file

@ -1,7 +1,5 @@
use std::io::{self, BufRead}; use std::io::{self, BufRead};
use log::{error, info};
use crate::db::{ use crate::db::{
ClipboardDb, ClipboardDb,
Entry, Entry,
@ -12,18 +10,6 @@ use crate::db::{
pub trait ImportCommand { pub trait ImportCommand {
/// Import clipboard entries from TSV format. /// Import clipboard entries from TSV format.
///
/// # Arguments
///
/// * `input` - A readable stream containing TSV lines, each of the form
/// `<id>\t<contents>`.
/// * `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( fn import_tsv(
&self, &self,
input: impl io::Read, input: impl io::Read,
@ -39,16 +25,21 @@ impl ImportCommand for SqliteClipboardDb {
) -> Result<(), StashError> { ) -> Result<(), StashError> {
let reader = io::BufReader::new(input); let reader = io::BufReader::new(input);
let mut imported = 0; 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 mut parts = line.splitn(2, '\t');
let (Some(id_str), Some(val)) = (parts.next(), parts.next()) else { let (Some(id_str), Some(val)) = (parts.next(), parts.next()) else {
error!("Malformed TSV line: {line:?}"); return Err(StashError::Store(format!(
continue; "Malformed TSV line {lineno}: {line:?}"
)));
}; };
let Ok(_id) = id_str.parse::<u64>() else { let Ok(_id) = id_str.parse::<u64>() else {
error!("Failed to parse id from line: {id_str}"); return Err(StashError::Store(format!(
continue; "Failed to parse id from line {lineno}: {id_str}"
)));
}; };
let entry = Entry { let entry = Entry {
@ -56,22 +47,26 @@ impl ImportCommand for SqliteClipboardDb {
mime: detect_mime(val.as_bytes()), mime: detect_mime(val.as_bytes()),
}; };
match self.conn.execute( self
"INSERT INTO clipboard (contents, mime) VALUES (?1, ?2)", .conn
rusqlite::params![entry.contents, entry.mime], .execute(
) { "INSERT INTO clipboard (contents, mime) VALUES (?1, ?2)",
Ok(_) => { rusqlite::params![entry.contents, entry.mime],
imported += 1; )
info!("Imported entry from TSV"); .map_err(|e| {
}, StashError::Store(format!(
Err(e) => error!("Failed to insert entry: {e}"), "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 // Trim database to max_items after import
self.trim_db(max_items)?; 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(()) Ok(())
} }
} }

View file

@ -16,14 +16,11 @@ impl ListCommand for SqliteClipboardDb {
out: impl Write, out: impl Write,
preview_width: u32, preview_width: u32,
) -> Result<(), StashError> { ) -> Result<(), StashError> {
self.list_entries(out, preview_width)?; self.list_entries(out, preview_width).map(|_| ())
log::info!("Listed clipboard entries");
Ok(())
} }
} }
impl SqliteClipboardDb { impl SqliteClipboardDb {
/// Public TUI listing function for use in main.rs
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
pub fn list_tui(&self, preview_width: u32) -> Result<(), StashError> { pub fn list_tui(&self, preview_width: u32) -> Result<(), StashError> {
use std::io::stdout; use std::io::stdout;
@ -272,14 +269,14 @@ impl SqliteClipboardDb {
Ok(()) Ok(())
})(); })();
disable_raw_mode().ok(); // Ignore errors during terminal restore, as we can't recover here.
execute!( let _ = disable_raw_mode();
let _ = execute!(
terminal.backend_mut(), terminal.backend_mut(),
LeaveAlternateScreen, LeaveAlternateScreen,
DisableMouseCapture DisableMouseCapture
) );
.ok(); let _ = terminal.show_cursor();
terminal.show_cursor().ok();
res res
} }