correction + regexp

This commit is contained in:
2026-05-20 23:26:01 +02:00
parent 8b07e305f0
commit d173db3342
11 changed files with 425 additions and 73 deletions

View File

@ -1,3 +1,4 @@
use crate::config::Config;
use crate::models::{ClipboardData, ClipboardEntry, Image};
use image::codecs::jpeg::JpegEncoder;
use image::{ExtendedColorType, ImageEncoder};
@ -5,46 +6,55 @@ use rusqlite::Connection;
use std::error::Error;
use std::fs;
use std::path::Path;
use std::time::{Duration, UNIX_EPOCH};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use uuid::Uuid;
pub struct Database {
conn: Connection,
dir_path: String,
max_entries: usize,
max_entry_size_bytes: usize,
}
impl Database {
pub fn init(dir_path: &str) -> Result<Self, Box<dyn Error>> {
pub fn init(dir_path: &str, config: &Config) -> Result<Self, Box<dyn Error>> {
let base_path = Path::new(dir_path);
fs::create_dir_all(base_path.join("images"))?;
let conn = Connection::open(base_path.join("clipboard.db"))?;
conn.execute_batch(
"CREATE TABLE IF NOT EXISTS schema_version (version INTEGER NOT NULL);
INSERT OR IGNORE INTO schema_version (version) SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM schema_version);",
)?;
"CREATE TABLE IF NOT EXISTS schema_version (version INTEGER NOT NULL);
INSERT OR IGNORE INTO schema_version (version)
SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM schema_version);",
)?;
conn.execute(
"CREATE TABLE IF NOT EXISTS history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
type TEXT NOT NULL,
content TEXT NOT NULL,
timestamp INTEGER NOT NULL
)",
id INTEGER PRIMARY KEY AUTOINCREMENT,
type TEXT NOT NULL,
content TEXT NOT NULL,
timestamp INTEGER NOT NULL
)",
[],
)?;
conn.execute_batch(
"CREATE UNIQUE INDEX IF NOT EXISTS idx_history_content ON history(content);
CREATE INDEX IF NOT EXISTS idx_history_timestamp ON history(timestamp DESC);",
)?;
conn.execute_batch(
"DELETE FROM history WHERE id NOT IN (
SELECT MAX(id) FROM history GROUP BY content
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_history_content ON history(content);",
SELECT MAX(id) FROM history GROUP BY content
);",
)?;
Ok(Self {
conn,
dir_path: dir_path.to_string(),
max_entries: config.max_entries,
max_entry_size_bytes: config.max_entry_size_kb * 1024,
})
}
@ -56,10 +66,16 @@ impl Database {
if t.trim().is_empty() {
return Ok(());
}
if t.len() > self.max_entry_size_bytes {
return Ok(());
}
("text", t.clone())
}
ClipboardData::Image(img) => {
if let Some(px) = &img.raw_pixels {
if px.len() > self.max_entry_size_bytes * 4 {
return Ok(());
}
let path = img.file_path(&self.dir_path);
let file = fs::File::create(&path)?;
let rgb: Vec<u8> = px
@ -81,6 +97,45 @@ impl Database {
"INSERT OR REPLACE INTO history (type, content, timestamp) VALUES (?1, ?2, ?3)",
(kind, &content, ts),
)?;
self.trim_to_max()?;
Ok(())
}
fn trim_to_max(&self) -> Result<(), Box<dyn Error>> {
if self.max_entries == 0 {
return Ok(());
}
let mut stmt = self.conn.prepare(
"SELECT content FROM history
WHERE type = 'image'
AND id NOT IN (
SELECT id FROM history ORDER BY timestamp DESC LIMIT ?1
)",
)?;
let to_delete: Vec<String> = stmt
.query_map([self.max_entries as i64], |row| row.get(0))?
.filter_map(|r| r.ok())
.collect();
self.conn.execute(
"DELETE FROM history WHERE id NOT IN (
SELECT id FROM history ORDER BY timestamp DESC LIMIT ?1
)",
[self.max_entries as i64],
)?;
for filename in to_delete {
let path = Path::new(&self.dir_path).join("images").join(&filename);
if path.exists() {
if let Err(e) = fs::remove_file(&path) {
eprintln!("Impossible de supprimer l'image {filename} : {e}");
}
}
}
Ok(())
}
@ -137,14 +192,40 @@ impl Database {
Ok(())
}
pub fn delete_entries_older_than(&self, days: u64) -> Result<usize, Box<dyn Error>> {
let cutoff_ms = SystemTime::now().duration_since(UNIX_EPOCH)?.as_millis() as i64
- (days as i64 * 86_400_000);
let mut stmt = self
.conn
.prepare("SELECT content FROM history WHERE type = 'image' AND timestamp < ?1")?;
let image_files: Vec<String> = 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])?;
for filename in image_files {
let path = Path::new(&self.dir_path).join("images").join(&filename);
if path.exists() {
if let Err(e) = fs::remove_file(&path) {
eprintln!("Impossible de supprimer l'image expirée {filename} : {e}");
}
}
}
Ok(count)
}
pub fn clear_history(&self) -> Result<(), Box<dyn Error>> {
let images_dir = Path::new(&self.dir_path).join("images");
if images_dir.exists() {
fs::remove_dir_all(&images_dir)?;
}
fs::create_dir_all(&images_dir)?;
self.conn.execute("DELETE FROM history", [])?;
Ok(())
}