treewide: make logging format more consistent; make clipboard persistence opt-in

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I9092f93c29fcbe99c90483875f4acd0c6a6a6964
This commit is contained in:
raf 2026-04-03 13:59:09 +03:00
commit da9bf5ea3e
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF
11 changed files with 44 additions and 32 deletions

View file

@ -175,7 +175,7 @@ unsafe fn fork_and_serve(prepared: PreparedCopy) -> PersistenceResult<()> {
pid => { pid => {
// Parent process, store child PID for loop detection // Parent process, store child PID for loop detection
log::debug!("Forked clipboard persistence process (pid: {pid})"); log::debug!("forked clipboard persistence process (pid: {pid})");
SERVING_PID.store(pid, Ordering::SeqCst); SERVING_PID.store(pid, Ordering::SeqCst);
Ok(()) Ok(())
}, },
@ -185,18 +185,18 @@ unsafe fn fork_and_serve(prepared: PreparedCopy) -> PersistenceResult<()> {
/// Child process entry point for serving clipboard data. /// Child process entry point for serving clipboard data.
fn serve_clipboard_child(prepared: PreparedCopy) { fn serve_clipboard_child(prepared: PreparedCopy) {
let pid = std::process::id() as i32; let pid = std::process::id() as i32;
log::debug!("Clipboard persistence child process started (pid: {pid})"); log::debug!("clipboard persistence child process started (pid: {pid})");
// Serve clipboard requests. The PreparedCopy::serve() method blocks and // Serve clipboard requests. The PreparedCopy::serve() method blocks and
// handles all the Wayland protocol interactions internally via // handles all the Wayland protocol interactions internally via
// wl-clipboard-rs // wl-clipboard-rs
match prepared.serve() { match prepared.serve() {
Ok(()) => { Ok(()) => {
log::debug!("Clipboard persistence: serve completed normally"); log::debug!("clipboard persistence: serve completed normally");
}, },
Err(e) => { Err(e) => {
log::error!("Clipboard persistence: serve failed: {e}"); log::error!("clipboard persistence: serve failed: {e}");
exit(1); exit(1);
}, },
} }

View file

@ -32,7 +32,7 @@ impl DecodeCommand for SqliteClipboardDb {
// 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::debug!("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)
{ {

View file

@ -9,7 +9,7 @@ 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> {
let deleted = self.delete_entries(input)?; let deleted = self.delete_entries(input)?;
log::info!("Deleted {deleted} entries"); log::info!("deleted {deleted} entries");
Ok(deleted) Ok(deleted)
} }
} }

View file

@ -55,11 +55,11 @@ impl ImportCommand for SqliteClipboardDb {
imported += 1; imported += 1;
} }
log::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)?;
log::info!("Trimmed clipboard database to max_items = {max_items}"); log::info!("trimmed clipboard database to max_items = {max_items}");
Ok(()) Ok(())
} }

View file

@ -710,7 +710,7 @@ impl SqliteClipboardDb {
.show(); .show();
}, },
Err(e) => { Err(e) => {
log::error!("Failed to copy entry to clipboard: {e}"); log::error!("failed to copy entry to clipboard: {e}");
let _ = Notification::new() let _ = Notification::new()
.summary("Stash") .summary("Stash")
.body(&format!("Failed to copy to clipboard: {e}")) .body(&format!("Failed to copy to clipboard: {e}"))
@ -719,7 +719,7 @@ impl SqliteClipboardDb {
} }
}, },
Err(e) => { Err(e) => {
log::error!("Failed to fetch entry {id}: {e}"); log::error!("failed to fetch entry {id}: {e}");
let _ = Notification::new() let _ = Notification::new()
.summary("Stash") .summary("Stash")
.body(&format!("Failed to fetch entry: {e}")) .body(&format!("Failed to fetch entry: {e}"))

View file

@ -29,7 +29,7 @@ impl StoreCommand for SqliteClipboardDb {
) -> Result<(), crate::db::StashError> { ) -> Result<(), crate::db::StashError> {
if let Some("sensitive" | "clear") = state.as_deref() { if let Some("sensitive" | "clear") = state.as_deref() {
self.delete_last()?; self.delete_last()?;
log::info!("Entry deleted"); log::info!("entry deleted");
} else { } else {
self.store_entry( self.store_entry(
input, input,
@ -41,7 +41,7 @@ impl StoreCommand for SqliteClipboardDb {
None, // no pre-computed hash for CLI store None, // no pre-computed hash for CLI store
None, // no mime types for CLI store None, // no mime types for CLI store
)?; )?;
log::info!("Entry stored"); log::info!("entry stored");
} }
Ok(()) Ok(())
} }

View file

@ -204,6 +204,7 @@ pub trait WatchCommand {
mime_type_preference: &str, mime_type_preference: &str,
min_size: Option<usize>, min_size: Option<usize>,
max_size: usize, max_size: usize,
persist: bool,
); );
} }
@ -217,6 +218,7 @@ impl WatchCommand for SqliteClipboardDb {
mime_type_preference: &str, mime_type_preference: &str,
min_size: Option<usize>, min_size: Option<usize>,
max_size: usize, max_size: usize,
persist: bool,
) { ) {
let async_db = AsyncClipboardDb::new(self.db_path.clone()); let async_db = AsyncClipboardDb::new(self.db_path.clone());
log::info!( log::info!(
@ -224,6 +226,10 @@ impl WatchCommand for SqliteClipboardDb {
{mime_type_preference}" {mime_type_preference}"
); );
if persist {
log::info!("clipboard persistence enabled");
}
// Build expiration queue from existing entries // Build expiration queue from existing entries
let mut exp_queue = ExpirationQueue::new(); let mut exp_queue = ExpirationQueue::new();
@ -234,11 +240,11 @@ impl WatchCommand for SqliteClipboardDb {
exp_queue.push(expires_at, id); exp_queue.push(expires_at, id);
} }
if !exp_queue.is_empty() { if !exp_queue.is_empty() {
log::info!("Loaded {} expirations from database", exp_queue.len()); log::info!("loaded {} expirations from database", exp_queue.len());
} }
}, },
Err(e) => { Err(e) => {
log::warn!("Failed to load expirations: {e}"); log::warn!("failed to load expirations: {e}");
}, },
} }
@ -277,7 +283,7 @@ impl WatchCommand for SqliteClipboardDb {
match async_db.get_content_hash(id).await { match async_db.get_content_hash(id).await {
Ok(hash) => hash, Ok(hash) => hash,
Err(e) => { Err(e) => {
log::warn!("Failed to get content hash for entry {id}: {e}"); log::warn!("failed to get content hash for entry {id}: {e}");
None None
}, },
}; };
@ -285,9 +291,9 @@ impl WatchCommand for SqliteClipboardDb {
if let Some(stored_hash) = expired_hash { if let Some(stored_hash) = expired_hash {
// Mark as expired // Mark as expired
if let Err(e) = async_db.mark_expired(id).await { if let Err(e) = async_db.mark_expired(id).await {
log::warn!("Failed to mark entry {id} as expired: {e}"); log::warn!("failed to mark entry {id} as expired: {e}");
} else { } else {
log::info!("Entry {id} marked as expired"); log::info!("entry {id} marked as expired");
} }
// Check if this expired entry is currently in the clipboard // Check if this expired entry is currently in the clipboard
@ -315,12 +321,12 @@ impl WatchCommand for SqliteClipboardDb {
.is_ok() .is_ok()
{ {
log::info!( log::info!(
"Cleared clipboard containing expired entry {id}" "cleared clipboard containing expired entry {id}"
); );
last_hash = None; // reset tracked hash last_hash = None; // reset tracked hash
} else { } else {
log::warn!( log::warn!(
"Failed to clear clipboard for expired entry {id}" "failed to clear clipboard for expired entry {id}"
); );
} }
} }
@ -337,7 +343,7 @@ impl WatchCommand for SqliteClipboardDb {
Ok((mut reader, _mime_type, _all_mimes)) => { Ok((mut reader, _mime_type, _all_mimes)) => {
buf.clear(); buf.clear();
if let Err(e) = reader.read_to_end(&mut buf) { if let Err(e) = reader.read_to_end(&mut buf) {
log::error!("Failed to read clipboard contents: {e}"); log::error!("failed to read clipboard contents: {e}");
Timer::after(Duration::from_millis(500)).await; Timer::after(Duration::from_millis(500)).await;
continue; continue;
} }
@ -370,13 +376,13 @@ impl WatchCommand for SqliteClipboardDb {
.await .await
{ {
Ok(id) => { Ok(id) => {
log::info!("Stored new clipboard entry (id: {id})"); log::info!("stored new clipboard entry (id: {id})");
last_hash = Some(current_hash); last_hash = Some(current_hash);
// Persist clipboard: fork child to serve data // Persist clipboard: fork child to serve data
// This keeps the clipboard alive when source app closes // This keeps the clipboard alive when source app closes
// Check if we're already serving to avoid duplicate processes // Check if we're already serving to avoid duplicate processes
if get_serving_pid().is_none() { if persist && get_serving_pid().is_none() {
let clipboard_data = ClipboardData::new( let clipboard_data = ClipboardData::new(
buf_for_persist, buf_for_persist,
mime_types_for_persist, mime_types_for_persist,
@ -393,12 +399,12 @@ impl WatchCommand for SqliteClipboardDb {
.await; .await;
if let Err(e) = result { if let Err(e) = result {
log::debug!("Clipboard persistence failed: {e}"); log::debug!("clipboard persistence failed: {e}");
} }
}) })
.detach(); .detach();
} }
} else { } else if persist {
log::trace!( log::trace!(
"Already serving clipboard, skipping persistence fork" "Already serving clipboard, skipping persistence fork"
); );
@ -420,17 +426,17 @@ impl WatchCommand for SqliteClipboardDb {
} }
}, },
Err(crate::db::StashError::ExcludedByApp(_)) => { Err(crate::db::StashError::ExcludedByApp(_)) => {
log::info!("Clipboard entry excluded by app filter"); log::info!("clipboard entry excluded by app filter");
last_hash = Some(current_hash); last_hash = Some(current_hash);
}, },
Err(crate::db::StashError::Store(ref msg)) Err(crate::db::StashError::Store(ref msg))
if msg.contains("Excluded by app filter") => if msg.contains("Excluded by app filter") =>
{ {
log::info!("Clipboard entry excluded by app filter"); log::info!("clipboard entry excluded by app filter");
last_hash = Some(current_hash); last_hash = Some(current_hash);
}, },
Err(e) => { Err(e) => {
log::error!("Failed to store clipboard entry: {e}"); log::error!("failed to store clipboard entry: {e}");
last_hash = Some(current_hash); last_hash = Some(current_hash);
}, },
} }
@ -440,7 +446,7 @@ impl WatchCommand for SqliteClipboardDb {
Err(e) => { Err(e) => {
let error_msg = e.to_string(); let error_msg = e.to_string();
if !error_msg.contains("empty") { if !error_msg.contains("empty") {
log::error!("Failed to get clipboard contents: {e}"); log::error!("failed to get clipboard contents: {e}");
} }
}, },
} }

View file

@ -7,7 +7,7 @@ pub trait WipeCommand {
impl WipeCommand for SqliteClipboardDb { impl WipeCommand for SqliteClipboardDb {
fn wipe(&self) -> Result<(), StashError> { fn wipe(&self) -> Result<(), StashError> {
self.wipe_db()?; self.wipe_db()?;
log::info!("Database wiped"); log::info!("database wiped");
Ok(()) Ok(())
} }
} }

View file

@ -875,7 +875,7 @@ impl ClipboardDb for SqliteClipboardDb {
out out
.write_all(&contents) .write_all(&contents)
.map_err(|e| StashError::DecodeWrite(e.to_string().into()))?; .map_err(|e| StashError::DecodeWrite(e.to_string().into()))?;
log::info!("Decoded entry with id {id}"); log::info!("decoded entry with id {id}");
Ok(()) Ok(())
} }

View file

@ -160,6 +160,10 @@ enum Command {
/// MIME type preference for clipboard reading. /// MIME type preference for clipboard reading.
#[arg(short = 't', long, default_value = "any")] #[arg(short = 't', long, default_value = "any")]
mime_type: String, mime_type: String,
/// Persist clipboard contents after the source application closes.
#[arg(long)]
persist: bool,
}, },
} }
@ -201,7 +205,7 @@ fn confirm(prompt: &str) -> bool {
.with_default(false) .with_default(false)
.prompt() .prompt()
.unwrap_or_else(|e| { .unwrap_or_else(|e| {
log::error!("Confirmation prompt failed: {e}"); log::error!("confirmation prompt failed: {e}");
false false
}) })
} }
@ -477,6 +481,7 @@ fn main() -> eyre::Result<()> {
Some(Command::Watch { Some(Command::Watch {
expire_after, expire_after,
mime_type, mime_type,
persist,
}) => { }) => {
db.watch( db.watch(
cli.max_dedupe_search, cli.max_dedupe_search,
@ -489,6 +494,7 @@ fn main() -> eyre::Result<()> {
&mime_type, &mime_type,
cli.min_size, cli.min_size,
cli.max_size, cli.max_size,
persist,
) )
.await; .await;
}, },

View file

@ -421,7 +421,7 @@ fn handle_regular_paste(
let selected_type = available_types.as_ref().and_then(select_best_mime_type); let selected_type = available_types.as_ref().and_then(select_best_mime_type);
let mime_type = if let Some(ref best) = selected_type { let mime_type = if let Some(ref best) = selected_type {
log::debug!("Auto-selecting MIME type: {best}"); log::debug!("auto-selecting MIME type: {best}");
PasteMimeType::Specific(best) PasteMimeType::Specific(best)
} else { } else {
get_paste_mime_type(args.mime_type.as_deref()) get_paste_mime_type(args.mime_type.as_deref())