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,75 +1,78 @@
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,
in_: impl Read,
out: impl Write,
input: Option<String>,
) -> Result<(), StashError>;
fn decode(
&self,
in_: impl Read,
out: impl Write,
input: Option<String>,
) -> Result<(), StashError>;
}
impl DecodeCommand for SqliteClipboardDb {
fn decode(
&self,
mut in_: impl Read,
mut out: impl Write,
input: Option<String>,
) -> Result<(), StashError> {
let input_str = if let Some(s) = input {
s
fn decode(
&self,
mut in_: impl Read,
mut out: impl Write,
input: Option<String>,
) -> Result<(), StashError> {
let input_str = if let Some(s) = input {
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}");
}
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");
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 mut buf = String::new();
if let Err(e) = in_.read_to_string(&mut buf) {
log::error!("Failed to read stdin for decode: {e}");
}
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");
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);
}
} else {
log::error!("Failed to get clipboard contents for relay");
}
return Ok(());
let _ = out.write_all(&buf);
}
// Try decode as usual
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)) =
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);
}
} else {
log::error!("Failed to get clipboard contents for relay");
}
}
}
Ok(())
} else {
log::error!("Failed to get clipboard contents for relay");
}
return Ok(());
}
// Try decode as usual
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)) =
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);
}
} else {
log::error!("Failed to get clipboard contents for relay");
}
},
}
Ok(())
}
}

View file

@ -1,22 +1,22 @@
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>;
fn delete(&self, input: impl Read) -> Result<usize, StashError>;
}
impl DeleteCommand for SqliteClipboardDb {
fn delete(&self, input: impl Read) -> Result<usize, StashError> {
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)
}
}
fn delete(&self, input: impl Read) -> Result<usize, StashError> {
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)
},
}
}
}

View file

@ -1,274 +1,286 @@
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> {
self.list_entries(out, preview_width)?;
log::info!("Listed clipboard entries");
Ok(())
}
fn list(
&self,
out: impl Write,
preview_width: u32,
) -> Result<(), StashError> {
self.list_entries(out, preview_width)?;
log::info!("Listed clipboard entries");
Ok(())
}
}
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 crossterm::{
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
execute,
terminal::{
EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode,
},
};
use ratatui::{
Terminal,
backend::CrosstermBackend,
style::{Color, Modifier, Style},
text::{Line, Span},
widgets::{Block, Borders, List, ListItem, ListState},
};
use std::io::stdout;
/// 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;
// Query entries from DB
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()))?;
use crossterm::{
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
execute,
terminal::{
EnterAlternateScreen,
LeaveAlternateScreen,
disable_raw_mode,
enable_raw_mode,
},
};
use ratatui::{
Terminal,
backend::CrosstermBackend,
style::{Color, Modifier, Style},
text::{Line, Span},
widgets::{Block, Borders, List, ListItem, ListState},
};
let mut entries: Vec<(u64, String, String)> = Vec::new();
let mut max_id_width = 2;
let mut max_mime_width = 8;
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 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());
max_mime_width = max_mime_width.max(mime_str.width());
entries.push((id, preview, mime_str));
}
// Query entries from DB
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()))?;
enable_raw_mode().map_err(|e| StashError::ListDecode(e.to_string()))?;
let mut stdout = stdout();
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 state = ListState::default();
if !entries.is_empty() {
state.select(Some(0));
}
let res = (|| -> Result<(), StashError> {
loop {
terminal
.draw(|f| {
let area = f.area();
let block = Block::default()
.title("Clipboard Entries (j/k/↑/↓ to move, q/ESC to quit)")
.borders(Borders::ALL);
let border_width = 2;
let highlight_symbol = ">";
let highlight_width = 1;
let content_width = area.width as usize - border_width;
// Minimum widths for columns
let min_id_width = 2;
let min_mime_width = 6;
let min_preview_width = 4;
let spaces = 3; // [id][ ][preview][ ][mime]
// Dynamically allocate widths
let mut id_col = max_id_width.max(min_id_width);
let mut mime_col = max_mime_width.max(min_mime_width);
let mut preview_col = content_width
.saturating_sub(highlight_width)
.saturating_sub(id_col)
.saturating_sub(mime_col)
.saturating_sub(spaces);
// If not enough space, shrink columns
if preview_col < min_preview_width {
let needed = min_preview_width - preview_col;
if mime_col > min_mime_width {
let reduce = mime_col - min_mime_width;
let take = reduce.min(needed);
mime_col -= take;
preview_col += take;
}
}
if preview_col < min_preview_width {
let needed = min_preview_width - preview_col;
if id_col > min_id_width {
let reduce = id_col - min_id_width;
let take = reduce.min(needed);
id_col -= take;
preview_col += take;
}
}
if preview_col < min_preview_width {
preview_col = min_preview_width;
}
let selected = state.selected();
let list_items: Vec<ListItem> = entries
.iter()
.enumerate()
.map(|(i, entry)| {
// Truncate preview by grapheme clusters and display width
let mut preview = String::new();
let mut width = 0;
for g in entry.1.graphemes(true) {
let g_width = UnicodeWidthStr::width(g);
if width + g_width > preview_col {
preview.push('…');
break;
}
preview.push_str(g);
width += g_width;
}
// Truncate and pad mimetype
let mut mime = String::new();
let mut mwidth = 0;
for g in entry.2.graphemes(true) {
let g_width = UnicodeWidthStr::width(g);
if mwidth + g_width > mime_col {
mime.push('…');
break;
}
mime.push_str(g);
mwidth += g_width;
}
// Compose the row as highlight + id + space + preview + space + mimetype
let mut spans = Vec::new();
let (id, preview, mime) = entry;
if Some(i) == selected {
spans.push(Span::styled(
highlight_symbol,
Style::default()
.fg(Color::Yellow)
.add_modifier(Modifier::BOLD),
));
spans.push(Span::styled(
format!("{id:>id_col$}"),
Style::default()
.fg(Color::Yellow)
.add_modifier(Modifier::BOLD),
));
spans.push(Span::raw(" "));
spans.push(Span::styled(
format!("{preview:<preview_col$}"),
Style::default()
.fg(Color::Yellow)
.add_modifier(Modifier::BOLD),
));
spans.push(Span::raw(" "));
spans.push(Span::styled(
format!("{mime:>mime_col$}"),
Style::default().fg(Color::Green),
));
} else {
spans.push(Span::raw(" "));
spans.push(Span::raw(format!("{id:>id_col$}")));
spans.push(Span::raw(" "));
spans.push(Span::raw(format!("{preview:<preview_col$}")));
spans.push(Span::raw(" "));
spans.push(Span::raw(format!("{mime:>mime_col$}")));
}
ListItem::new(Line::from(spans))
})
.collect();
let list = List::new(list_items)
.block(block)
.highlight_style(
Style::default()
.fg(Color::Yellow)
.add_modifier(Modifier::BOLD),
)
.highlight_symbol(""); // handled manually
f.render_stateful_widget(list, area, &mut state);
})
.map_err(|e| StashError::ListDecode(e.to_string()))?;
if event::poll(std::time::Duration::from_millis(250))
.map_err(|e| StashError::ListDecode(e.to_string()))?
{
if let Event::Key(key) =
event::read().map_err(|e| StashError::ListDecode(e.to_string()))?
{
match key.code {
KeyCode::Char('q') | KeyCode::Esc => break,
KeyCode::Down | KeyCode::Char('j') => {
let i = match state.selected() {
Some(i) => {
if i >= entries.len() - 1 {
0
} else {
i + 1
}
}
None => 0,
};
state.select(Some(i));
}
KeyCode::Up | KeyCode::Char('k') => {
let i = match state.selected() {
Some(i) => {
if i == 0 {
entries.len() - 1
} else {
i - 1
}
}
None => 0,
};
state.select(Some(i));
}
_ => {}
}
}
}
}
Ok(())
})();
disable_raw_mode().ok();
execute!(
terminal.backend_mut(),
LeaveAlternateScreen,
DisableMouseCapture
)
.ok();
terminal.show_cursor().ok();
res
let mut entries: Vec<(u64, String, String)> = Vec::new();
let mut max_id_width = 2;
let mut max_mime_width = 8;
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 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());
max_mime_width = max_mime_width.max(mime_str.width());
entries.push((id, preview, mime_str));
}
enable_raw_mode().map_err(|e| StashError::ListDecode(e.to_string()))?;
let mut stdout = stdout();
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 state = ListState::default();
if !entries.is_empty() {
state.select(Some(0));
}
let res = (|| -> Result<(), StashError> {
loop {
terminal
.draw(|f| {
let area = f.area();
let block = Block::default()
.title("Clipboard Entries (j/k/↑/↓ to move, q/ESC to quit)")
.borders(Borders::ALL);
let border_width = 2;
let highlight_symbol = ">";
let highlight_width = 1;
let content_width = area.width as usize - border_width;
// Minimum widths for columns
let min_id_width = 2;
let min_mime_width = 6;
let min_preview_width = 4;
let spaces = 3; // [id][ ][preview][ ][mime]
// Dynamically allocate widths
let mut id_col = max_id_width.max(min_id_width);
let mut mime_col = max_mime_width.max(min_mime_width);
let mut preview_col = content_width
.saturating_sub(highlight_width)
.saturating_sub(id_col)
.saturating_sub(mime_col)
.saturating_sub(spaces);
// If not enough space, shrink columns
if preview_col < min_preview_width {
let needed = min_preview_width - preview_col;
if mime_col > min_mime_width {
let reduce = mime_col - min_mime_width;
let take = reduce.min(needed);
mime_col -= take;
preview_col += take;
}
}
if preview_col < min_preview_width {
let needed = min_preview_width - preview_col;
if id_col > min_id_width {
let reduce = id_col - min_id_width;
let take = reduce.min(needed);
id_col -= take;
preview_col += take;
}
}
if preview_col < min_preview_width {
preview_col = min_preview_width;
}
let selected = state.selected();
let list_items: Vec<ListItem> = entries
.iter()
.enumerate()
.map(|(i, entry)| {
// Truncate preview by grapheme clusters and display width
let mut preview = String::new();
let mut width = 0;
for g in entry.1.graphemes(true) {
let g_width = UnicodeWidthStr::width(g);
if width + g_width > preview_col {
preview.push('…');
break;
}
preview.push_str(g);
width += g_width;
}
// Truncate and pad mimetype
let mut mime = String::new();
let mut mwidth = 0;
for g in entry.2.graphemes(true) {
let g_width = UnicodeWidthStr::width(g);
if mwidth + g_width > mime_col {
mime.push('…');
break;
}
mime.push_str(g);
mwidth += g_width;
}
// Compose the row as highlight + id + space + preview + space +
// mimetype
let mut spans = Vec::new();
let (id, preview, mime) = entry;
if Some(i) == selected {
spans.push(Span::styled(
highlight_symbol,
Style::default()
.fg(Color::Yellow)
.add_modifier(Modifier::BOLD),
));
spans.push(Span::styled(
format!("{id:>id_col$}"),
Style::default()
.fg(Color::Yellow)
.add_modifier(Modifier::BOLD),
));
spans.push(Span::raw(" "));
spans.push(Span::styled(
format!("{preview:<preview_col$}"),
Style::default()
.fg(Color::Yellow)
.add_modifier(Modifier::BOLD),
));
spans.push(Span::raw(" "));
spans.push(Span::styled(
format!("{mime:>mime_col$}"),
Style::default().fg(Color::Green),
));
} else {
spans.push(Span::raw(" "));
spans.push(Span::raw(format!("{id:>id_col$}")));
spans.push(Span::raw(" "));
spans.push(Span::raw(format!("{preview:<preview_col$}")));
spans.push(Span::raw(" "));
spans.push(Span::raw(format!("{mime:>mime_col$}")));
}
ListItem::new(Line::from(spans))
})
.collect();
let list = List::new(list_items)
.block(block)
.highlight_style(
Style::default()
.fg(Color::Yellow)
.add_modifier(Modifier::BOLD),
)
.highlight_symbol(""); // handled manually
f.render_stateful_widget(list, area, &mut state);
})
.map_err(|e| StashError::ListDecode(e.to_string()))?;
if event::poll(std::time::Duration::from_millis(250))
.map_err(|e| StashError::ListDecode(e.to_string()))?
{
if let Event::Key(key) =
event::read().map_err(|e| StashError::ListDecode(e.to_string()))?
{
match key.code {
KeyCode::Char('q') | KeyCode::Esc => break,
KeyCode::Down | KeyCode::Char('j') => {
let i = match state.selected() {
Some(i) => {
if i >= entries.len() - 1 {
0
} else {
i + 1
}
},
None => 0,
};
state.select(Some(i));
},
KeyCode::Up | KeyCode::Char('k') => {
let i = match state.selected() {
Some(i) => {
if i == 0 {
entries.len() - 1
} else {
i - 1
}
},
None => 0,
};
state.select(Some(i));
},
_ => {},
}
}
}
}
Ok(())
})();
disable_raw_mode().ok();
execute!(
terminal.backend_mut(),
LeaveAlternateScreen,
DisableMouseCapture
)
.ok();
terminal.show_cursor().ok();
res
}
}

View file

@ -1,13 +1,11 @@
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>;
fn query_delete(&self, query: &str) -> Result<usize, StashError>;
}
impl QueryCommand for SqliteClipboardDb {
fn query_delete(&self, query: &str) -> Result<usize, StashError> {
<SqliteClipboardDb as ClipboardDb>::delete_query(self, query)
}
fn query_delete(&self, query: &str) -> Result<usize, StashError> {
<SqliteClipboardDb as ClipboardDb>::delete_query(self, query)
}
}

View file

@ -1,32 +1,32 @@
use crate::db::{ClipboardDb, SqliteClipboardDb};
use std::io::Read;
use crate::db::{ClipboardDb, SqliteClipboardDb};
pub trait StoreCommand {
fn store(
&self,
input: impl Read,
max_dedupe_search: u64,
max_items: u64,
state: Option<String>,
) -> Result<(), crate::db::StashError>;
fn store(
&self,
input: impl Read,
max_dedupe_search: u64,
max_items: u64,
state: Option<String>,
) -> Result<(), crate::db::StashError>;
}
impl StoreCommand for SqliteClipboardDb {
fn store(
&self,
input: impl Read,
max_dedupe_search: u64,
max_items: u64,
state: Option<String>,
) -> Result<(), crate::db::StashError> {
if let Some("sensitive" | "clear") = state.as_deref() {
self.delete_last()?;
log::info!("Entry deleted");
} else {
self.store_entry(input, max_dedupe_search, max_items)?;
log::info!("Entry stored");
}
Ok(())
fn store(
&self,
input: impl Read,
max_dedupe_search: u64,
max_items: u64,
state: Option<String>,
) -> Result<(), crate::db::StashError> {
if let Some("sensitive" | "clear") = state.as_deref() {
self.delete_last()?;
log::info!("Entry deleted");
} else {
self.store_entry(input, max_dedupe_search, max_items)?;
log::info!("Entry stored");
}
Ok(())
}
}

View file

@ -1,79 +1,80 @@
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);
fn watch(&self, max_dedupe_search: u64, max_items: u64);
}
impl WatchCommand for SqliteClipboardDb {
fn watch(&self, max_dedupe_search: u64, max_items: u64) {
smol::block_on(async {
log::info!("Starting clipboard watch daemon");
fn watch(&self, max_dedupe_search: u64, max_items: u64) {
smol::block_on(async {
log::info!("Starting clipboard watch daemon");
// Preallocate buffer for clipboard contents
let mut last_contents: Option<Vec<u8>> = None;
let mut buf = Vec::with_capacity(4096); // reasonable default, hopefully
// Preallocate buffer for clipboard contents
let mut last_contents: Option<Vec<u8>> = None;
let mut buf = Vec::with_capacity(4096); // reasonable default, hopefully
// Initialize with current clipboard to avoid duplicating on startup
if let Ok((mut reader, _)) = get_contents(
ClipboardType::Regular,
Seat::Unspecified,
wl_clipboard_rs::paste::MimeType::Any,
) {
buf.clear();
if reader.read_to_end(&mut buf).is_ok() && !buf.is_empty() {
last_contents = Some(buf.clone());
}
// Initialize with current clipboard to avoid duplicating on startup
if let Ok((mut reader, _)) = get_contents(
ClipboardType::Regular,
Seat::Unspecified,
wl_clipboard_rs::paste::MimeType::Any,
) {
buf.clear();
if reader.read_to_end(&mut buf).is_ok() && !buf.is_empty() {
last_contents = Some(buf.clone());
}
}
loop {
match get_contents(
ClipboardType::Regular,
Seat::Unspecified,
wl_clipboard_rs::paste::MimeType::Any,
) {
Ok((mut reader, mime_type)) => {
buf.clear();
if let Err(e) = reader.read_to_end(&mut buf) {
log::error!("Failed to read clipboard contents: {e}");
Timer::after(Duration::from_millis(500)).await;
continue;
}
loop {
match get_contents(
ClipboardType::Regular,
Seat::Unspecified,
wl_clipboard_rs::paste::MimeType::Any,
) {
Ok((mut reader, mime_type)) => {
buf.clear();
if let Err(e) = reader.read_to_end(&mut buf) {
log::error!("Failed to read clipboard contents: {e}");
Timer::after(Duration::from_millis(500)).await;
continue;
}
// Only store if changed and not empty
if !buf.is_empty() && (last_contents.as_ref() != Some(&buf)) {
last_contents = Some(std::mem::take(&mut buf));
let mime = Some(mime_type.to_string());
let entry = Entry {
contents: last_contents.as_ref().unwrap().clone(),
mime,
};
let id = self.next_sequence();
match self.store_entry(
&entry.contents[..],
max_dedupe_search,
max_items,
) {
Ok(_) => log::info!("Stored new clipboard entry (id: {id})"),
Err(e) => log::error!("Failed to store clipboard entry: {e}"),
}
// Only store if changed and not empty
if !buf.is_empty() && (last_contents.as_ref() != Some(&buf)) {
last_contents = Some(std::mem::take(&mut buf));
let mime = Some(mime_type.to_string());
let entry = Entry {
contents: last_contents.as_ref().unwrap().clone(),
mime,
};
let id = self.next_sequence();
match self.store_entry(
&entry.contents[..],
max_dedupe_search,
max_items,
) {
Ok(_) => log::info!("Stored new clipboard entry (id: {id})"),
Err(e) => log::error!("Failed to store clipboard entry: {e}"),
}
// 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;
// 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,15 +1,13 @@
use crate::db::{ClipboardDb, SqliteClipboardDb};
use crate::db::StashError;
use crate::db::{ClipboardDb, SqliteClipboardDb, StashError};
pub trait WipeCommand {
fn wipe(&self) -> Result<(), StashError>;
fn wipe(&self) -> Result<(), StashError>;
}
impl WipeCommand for SqliteClipboardDb {
fn wipe(&self) -> Result<(), StashError> {
self.wipe_db()?;
log::info!("Database wiped");
Ok(())
}
fn wipe(&self) -> Result<(), StashError> {
self.wipe_db()?;
log::info!("Database wiped");
Ok(())
}
}