opti
This commit is contained in:
@ -20,3 +20,6 @@ x11rb = "0.13.2"
|
||||
[features]
|
||||
x11 = []
|
||||
wayland = []
|
||||
|
||||
[profile.release]
|
||||
debug = true
|
||||
|
||||
BIN
rklipd/profile.json.gz
Normal file
BIN
rklipd/profile.json.gz
Normal file
Binary file not shown.
@ -24,27 +24,31 @@ impl Config {
|
||||
while i < args.len() {
|
||||
match args[i].as_str() {
|
||||
"--max-entries" => {
|
||||
if let Some(v) = args.get(i + 1).and_then(|s| s.parse().ok()) {
|
||||
cfg.max_entries = v;
|
||||
i += 1;
|
||||
} else {
|
||||
eprintln!("--max-entries requiert une valeur entière positive");
|
||||
i += 1;
|
||||
match args.get(i).and_then(|s| s.parse::<usize>().ok()) {
|
||||
Some(0) => eprintln!("--max-entries doit être > 0"),
|
||||
Some(v) => cfg.max_entries = v,
|
||||
None => eprintln!("--max-entries requiert une valeur entière positive"),
|
||||
}
|
||||
}
|
||||
"--max-entry-size-kb" => {
|
||||
if let Some(v) = args.get(i + 1).and_then(|s| s.parse().ok()) {
|
||||
cfg.max_entry_size_kb = v;
|
||||
i += 1;
|
||||
} else {
|
||||
eprintln!("--max-entry-size-kb requiert une valeur entière positive");
|
||||
i += 1;
|
||||
match args.get(i).and_then(|s| s.parse::<usize>().ok()) {
|
||||
Some(0) => eprintln!("--max-entry-size-kb doit être > 0"),
|
||||
Some(v) => cfg.max_entry_size_kb = v,
|
||||
None => {
|
||||
eprintln!("--max-entry-size-kb requiert une valeur entière positive")
|
||||
}
|
||||
}
|
||||
}
|
||||
"--expiry-days" => {
|
||||
if let Some(v) = args.get(i + 1).and_then(|s| s.parse::<u64>().ok()) {
|
||||
cfg.expiry_days = Some(v);
|
||||
i += 1;
|
||||
} else {
|
||||
eprintln!("--expiry-days requiert une valeur entière positive");
|
||||
i += 1;
|
||||
match args.get(i).and_then(|s| s.parse::<u64>().ok()) {
|
||||
Some(0) => eprintln!(
|
||||
"--expiry-days doit être > 0 (0 supprimerait tout immédiatement)"
|
||||
),
|
||||
Some(v) => cfg.expiry_days = Some(v),
|
||||
None => eprintln!("--expiry-days requiert une valeur entière positive"),
|
||||
}
|
||||
}
|
||||
"--help" | "-h" => {
|
||||
|
||||
@ -101,9 +101,7 @@ impl Database {
|
||||
ExtendedColorType::Rgb8,
|
||||
)?;
|
||||
}
|
||||
None => {
|
||||
return Ok(());
|
||||
}
|
||||
None => return Ok(()),
|
||||
}
|
||||
("image", format!("{}.jpg", img.id))
|
||||
}
|
||||
@ -115,7 +113,6 @@ impl Database {
|
||||
)?;
|
||||
|
||||
self.trim_to_max()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -124,8 +121,10 @@ impl Database {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let tx = self.conn.unchecked_transaction()?;
|
||||
|
||||
let image_files: Vec<String> = {
|
||||
let mut stmt = self.conn.prepare(
|
||||
let mut stmt = tx.prepare(
|
||||
"SELECT content FROM history
|
||||
WHERE type = 'image'
|
||||
AND id NOT IN (
|
||||
@ -137,13 +136,15 @@ impl Database {
|
||||
.collect()
|
||||
};
|
||||
|
||||
self.conn.execute(
|
||||
tx.execute(
|
||||
"DELETE FROM history WHERE id NOT IN (
|
||||
SELECT id FROM history ORDER BY timestamp DESC LIMIT ?1
|
||||
)",
|
||||
[self.max_entries as i64],
|
||||
)?;
|
||||
|
||||
tx.commit()?;
|
||||
|
||||
for filename in image_files {
|
||||
let path = Path::new(&self.dir_path).join("images").join(&filename);
|
||||
if path.exists() {
|
||||
@ -213,18 +214,19 @@ impl Database {
|
||||
let cutoff_ms = SystemTime::now().duration_since(UNIX_EPOCH)?.as_millis() as i64
|
||||
- (days as i64 * 86_400_000);
|
||||
|
||||
let tx = self.conn.unchecked_transaction()?;
|
||||
|
||||
let image_files: Vec<String> = {
|
||||
let mut stmt = self
|
||||
.conn
|
||||
.prepare("SELECT content FROM history WHERE type = 'image' AND timestamp < ?1")?;
|
||||
let mut stmt =
|
||||
tx.prepare("SELECT content FROM history WHERE type = 'image' AND timestamp < ?1")?;
|
||||
stmt.query_map([cutoff_ms], |row| row.get(0))?
|
||||
.filter_map(|r| r.ok())
|
||||
.collect()
|
||||
};
|
||||
|
||||
let count = self
|
||||
.conn
|
||||
.execute("DELETE FROM history WHERE timestamp < ?1", [cutoff_ms])?;
|
||||
let count = tx.execute("DELETE FROM history WHERE timestamp < ?1", [cutoff_ms])?;
|
||||
|
||||
tx.commit()?;
|
||||
|
||||
for filename in image_files {
|
||||
let path = Path::new(&self.dir_path).join("images").join(&filename);
|
||||
|
||||
@ -48,13 +48,17 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let db_for_expiry = Arc::clone(&db);
|
||||
std::thread::spawn(move || {
|
||||
loop {
|
||||
std::thread::sleep(Duration::from_secs(3600));
|
||||
let lock = db_for_expiry.lock().unwrap();
|
||||
match lock.delete_entries_older_than(days) {
|
||||
Ok(0) => {}
|
||||
Ok(n) => println!("Expiration : {n} entrée(s) supprimée(s) (> {days} jours)"),
|
||||
Err(e) => eprintln!("Erreur expiration : {e}"),
|
||||
{
|
||||
let lock = db_for_expiry.lock().unwrap();
|
||||
match lock.delete_entries_older_than(days) {
|
||||
Ok(0) => {}
|
||||
Ok(n) => {
|
||||
println!("Expiration : {n} entrée(s) supprimée(s) (> {days} jours)")
|
||||
}
|
||||
Err(e) => eprintln!("Erreur expiration : {e}"),
|
||||
}
|
||||
}
|
||||
std::thread::sleep(Duration::from_secs(3600));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,28 +1,42 @@
|
||||
use crate::database::Database;
|
||||
use crate::models::ClipboardEntry;
|
||||
use arboard::Clipboard;
|
||||
use std::error::Error;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::{Arc, Mutex, mpsc};
|
||||
|
||||
pub fn start(db: Arc<Mutex<Database>>, clipboard: Clipboard) -> Result<(), Box<dyn Error>> {
|
||||
let (tx, rx) = mpsc::channel::<ClipboardEntry>();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
for entry in rx {
|
||||
let lock = db.lock().unwrap();
|
||||
if let Err(e) = lock.append(entry) {
|
||||
eprintln!("SQLite write error: {e}");
|
||||
} else {
|
||||
println!("SQLite updated!");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
#[cfg(all(feature = "wayland", not(feature = "x11")))]
|
||||
{
|
||||
crate::ws::wayland::start(db, clipboard)
|
||||
crate::ws::wayland::start(tx, clipboard)
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "x11", not(feature = "wayland")))]
|
||||
{
|
||||
crate::ws::x11::start(db, clipboard)
|
||||
crate::ws::x11::start(tx, clipboard)
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "x11", feature = "wayland"))]
|
||||
{
|
||||
let _ = (db, clipboard);
|
||||
let _ = (tx, clipboard);
|
||||
Err("Les features 'x11' et 'wayland' sont mutuellement exclusives".into())
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature = "x11", feature = "wayland")))]
|
||||
{
|
||||
let _ = (db, clipboard);
|
||||
let _ = (tx, clipboard);
|
||||
Err("Aucune feature de système de fenêtrage activée (--features x11 ou wayland)".into())
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,15 +1,22 @@
|
||||
use crate::database::Database;
|
||||
use crate::models::{ClipboardData, ClipboardEntry, Image};
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::error::Error;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::sync::mpsc;
|
||||
use std::time::SystemTime;
|
||||
use uuid::Uuid;
|
||||
use wayland_clipboard_listener::{WlClipboardPasteStream, WlListenType};
|
||||
|
||||
const MAX_IMAGE_PIXELS: usize = 3840 * 2160;
|
||||
|
||||
fn hash_bytes(data: &[u8]) -> u64 {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
data.hash(&mut hasher);
|
||||
hasher.finish()
|
||||
}
|
||||
|
||||
pub fn start(
|
||||
db: Arc<Mutex<Database>>,
|
||||
tx: mpsc::Sender<ClipboardEntry>,
|
||||
_clipboard: arboard::Clipboard,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let mut stream = WlClipboardPasteStream::init(WlListenType::ListenOnCopy)
|
||||
@ -17,9 +24,11 @@ pub fn start(
|
||||
|
||||
println!("Écoute du presse-papier Wayland...");
|
||||
|
||||
let mut last_text: Option<String> = None;
|
||||
let mut last_image_hash: Option<u64> = None;
|
||||
|
||||
for msg in stream.paste_stream().flatten() {
|
||||
let context = &msg.context;
|
||||
let data: &[u8] = context.context.as_slice();
|
||||
let data: &[u8] = msg.context.context.as_slice();
|
||||
|
||||
if data.is_empty() {
|
||||
continue;
|
||||
@ -27,14 +36,22 @@ pub fn start(
|
||||
|
||||
let entry = if let Ok(text) = String::from_utf8(data.to_vec()) {
|
||||
let text = text.trim_end_matches('\n').to_string();
|
||||
if text.is_empty() {
|
||||
if text.is_empty() || Some(&text) == last_text.as_ref() {
|
||||
continue;
|
||||
}
|
||||
last_text = Some(text.clone());
|
||||
last_image_hash = None;
|
||||
println!("Clipboard update (texte)");
|
||||
ClipboardEntry {
|
||||
content: ClipboardData::Text(text),
|
||||
timestamp: SystemTime::now(),
|
||||
}
|
||||
} else {
|
||||
let hash = hash_bytes(data);
|
||||
if Some(hash) == last_image_hash {
|
||||
continue;
|
||||
}
|
||||
|
||||
match image::load_from_memory(data) {
|
||||
Ok(img) => {
|
||||
let (width, height) = (img.width(), img.height());
|
||||
@ -48,9 +65,15 @@ pub fn start(
|
||||
3840,
|
||||
2160
|
||||
);
|
||||
last_image_hash = Some(hash);
|
||||
last_text = None;
|
||||
continue;
|
||||
}
|
||||
|
||||
last_image_hash = Some(hash);
|
||||
last_text = None;
|
||||
println!("Clipboard update (image)");
|
||||
|
||||
let rgba = img.into_rgba8();
|
||||
ClipboardEntry {
|
||||
content: ClipboardData::Image(Image {
|
||||
@ -69,15 +92,10 @@ pub fn start(
|
||||
}
|
||||
};
|
||||
|
||||
println!("Clipboard update détecté");
|
||||
|
||||
let db_clone = Arc::clone(&db);
|
||||
std::thread::spawn(move || {
|
||||
let db_lock = db_clone.lock().unwrap();
|
||||
if let Err(e) = db_lock.append(entry) {
|
||||
eprintln!("SQLite error : {e}");
|
||||
}
|
||||
});
|
||||
if tx.send(entry).is_err() {
|
||||
eprintln!("Wayland : writer thread disparu, arrêt");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
use crate::database::Database;
|
||||
use crate::models::{ClipboardData, ClipboardEntry, Image};
|
||||
use arboard::Clipboard;
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::error::Error;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
use std::time::{Duration, SystemTime};
|
||||
use uuid::Uuid;
|
||||
@ -22,7 +21,10 @@ fn hash_bytes(data: &[u8]) -> u64 {
|
||||
hasher.finish()
|
||||
}
|
||||
|
||||
pub fn start(db: Arc<Mutex<Database>>, mut clipboard: Clipboard) -> Result<(), Box<dyn Error>> {
|
||||
pub fn start(
|
||||
tx: mpsc::Sender<ClipboardEntry>,
|
||||
mut clipboard: Clipboard,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let (conn, screen_num) =
|
||||
RustConnection::connect(None).map_err(|e| format!("Connexion X11 impossible : {e}"))?;
|
||||
|
||||
@ -49,15 +51,14 @@ pub fn start(db: Arc<Mutex<Database>>, mut clipboard: Clipboard) -> Result<(), B
|
||||
.reply()?;
|
||||
|
||||
let clipboard_atom = conn.intern_atom(false, b"CLIPBOARD")?.reply()?.atom;
|
||||
|
||||
conn.xfixes_select_selection_input(
|
||||
win,
|
||||
clipboard_atom,
|
||||
SelectionEventMask::SET_SELECTION_OWNER,
|
||||
)?
|
||||
.check()?;
|
||||
|
||||
conn.flush()?;
|
||||
|
||||
println!("Clipboard monitor démarré (X11 XFIXES — zéro polling)");
|
||||
|
||||
let mut last_text: Option<String> = None;
|
||||
@ -68,14 +69,14 @@ pub fn start(db: Arc<Mutex<Database>>, mut clipboard: Clipboard) -> Result<(), B
|
||||
|
||||
if let Event::XfixesSelectionNotify(_) = event {
|
||||
thread::sleep(Duration::from_millis(50));
|
||||
handle_clipboard_event(&mut clipboard, &db, &mut last_text, &mut last_image_hash);
|
||||
handle_clipboard_event(&mut clipboard, &tx, &mut last_text, &mut last_image_hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_clipboard_event(
|
||||
clipboard: &mut Clipboard,
|
||||
db: &Arc<Mutex<Database>>,
|
||||
tx: &mpsc::Sender<ClipboardEntry>,
|
||||
last_text: &mut Option<String>,
|
||||
last_image_hash: &mut Option<u64>,
|
||||
) {
|
||||
@ -89,13 +90,15 @@ fn handle_clipboard_event(
|
||||
*last_image_hash = None;
|
||||
println!("Clipboard update (texte)");
|
||||
|
||||
spawn_db_write(
|
||||
Arc::clone(db),
|
||||
ClipboardEntry {
|
||||
if tx
|
||||
.send(ClipboardEntry {
|
||||
content: ClipboardData::Text(text),
|
||||
timestamp: SystemTime::now(),
|
||||
},
|
||||
);
|
||||
})
|
||||
.is_err()
|
||||
{
|
||||
eprintln!("X11 : writer thread disparu");
|
||||
}
|
||||
}
|
||||
|
||||
Err(_) => {
|
||||
@ -124,29 +127,20 @@ fn handle_clipboard_event(
|
||||
*last_text = None;
|
||||
println!("Clipboard update (image)");
|
||||
|
||||
spawn_db_write(
|
||||
Arc::clone(db),
|
||||
ClipboardEntry {
|
||||
content: ClipboardData::Image(crate::models::Image {
|
||||
if tx
|
||||
.send(ClipboardEntry {
|
||||
content: ClipboardData::Image(Image {
|
||||
raw_pixels: Some(img_data.bytes.into_owned()),
|
||||
width: img_data.width as u32,
|
||||
height: img_data.height as u32,
|
||||
id: Uuid::new_v4(),
|
||||
}),
|
||||
timestamp: SystemTime::now(),
|
||||
},
|
||||
);
|
||||
})
|
||||
.is_err()
|
||||
{
|
||||
eprintln!("X11 : writer thread disparu");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_db_write(db: Arc<Mutex<Database>>, entry: ClipboardEntry) {
|
||||
thread::spawn(move || {
|
||||
let lock = db.lock().unwrap();
|
||||
if let Err(e) = lock.append(entry) {
|
||||
eprintln!("SQLite write error: {e}");
|
||||
} else {
|
||||
println!("SQLite updated!");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user