correction + regexp
This commit is contained in:
@ -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(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user