rework sql
This commit is contained in:
1839
rklipd/Cargo.lock
generated
Normal file
1839
rklipd/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -4,3 +4,12 @@ version = "0.1.0"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
arboard = "3.6.1"
|
||||||
|
image = "0.25.9"
|
||||||
|
clipboard-master = "4.0.0"
|
||||||
|
uuid = {version = "1.22.0", features = ["v4", "serde"]}
|
||||||
|
rusqlite = "0.38.0"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
x11 = []
|
||||||
|
wayland = []
|
||||||
|
|||||||
68
rklipd/src/clipboard.rs
Normal file
68
rklipd/src/clipboard.rs
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
use arboard::{Clipboard, ImageData};
|
||||||
|
use image::{ExtendedColorType, ImageEncoder, codecs::png::PngEncoder};
|
||||||
|
use std::error::Error;
|
||||||
|
use std::time::SystemTime;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::models::{ClipboardData, ClipboardEntry, Image};
|
||||||
|
|
||||||
|
use clipboard_master::{CallbackResult, ClipboardHandler};
|
||||||
|
use std::sync::mpsc::Sender;
|
||||||
|
|
||||||
|
pub struct Handler {
|
||||||
|
pub clipboard_tx: Sender<()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClipboardHandler for Handler {
|
||||||
|
fn on_clipboard_change(&mut self) -> CallbackResult {
|
||||||
|
if let Err(e) = self.clipboard_tx.send(()) {
|
||||||
|
eprintln!("{}", e);
|
||||||
|
}
|
||||||
|
CallbackResult::Next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ImageDataExt {
|
||||||
|
fn to_png(&self) -> Result<Vec<u8>, Box<dyn Error>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ImageDataExt for ImageData<'_> {
|
||||||
|
fn to_png(&self) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||||
|
let mut buffer = Vec::new();
|
||||||
|
let encoder = PngEncoder::new(&mut buffer);
|
||||||
|
encoder.write_image(
|
||||||
|
&self.bytes,
|
||||||
|
self.width as u32,
|
||||||
|
self.height as u32,
|
||||||
|
ExtendedColorType::Rgba8,
|
||||||
|
)?;
|
||||||
|
Ok(buffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClipboardEntry {
|
||||||
|
pub fn new(clipboard: &mut Clipboard) -> Result<ClipboardEntry, Box<dyn Error>> {
|
||||||
|
let clipboard_data_opt: Option<ClipboardData> = match clipboard.get_text() {
|
||||||
|
Ok(text) => Some(ClipboardData::Text(text)),
|
||||||
|
Err(_) => match clipboard.get_image() {
|
||||||
|
Ok(image) => {
|
||||||
|
let id = Uuid::new_v4();
|
||||||
|
Some(ClipboardData::Image(Image {
|
||||||
|
bytes: Some(image.to_png()?),
|
||||||
|
id,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
Err(_) => None,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(clipboard_data) = clipboard_data_opt else {
|
||||||
|
return Err("Clipboard empty".into());
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(ClipboardEntry {
|
||||||
|
content: clipboard_data,
|
||||||
|
timestamp: SystemTime::now(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
114
rklipd/src/database.rs
Normal file
114
rklipd/src/database.rs
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
use crate::models::{ClipboardData, ClipboardEntry, Image};
|
||||||
|
use rusqlite::Connection;
|
||||||
|
use std::error::Error;
|
||||||
|
use std::fs;
|
||||||
|
use std::time::{Duration, UNIX_EPOCH};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
pub struct Database {
|
||||||
|
conn: Connection,
|
||||||
|
dir_path: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Database {
|
||||||
|
pub fn init(dir_path: &str) -> Result<Self, Box<dyn Error>> {
|
||||||
|
if !std::fs::exists(dir_path)? {
|
||||||
|
fs::create_dir(dir_path)?;
|
||||||
|
} else {
|
||||||
|
println!("{:?} dir already exists.", { dir_path });
|
||||||
|
}
|
||||||
|
|
||||||
|
let image_path = format!("{}/images", dir_path);
|
||||||
|
if !std::fs::exists(&image_path)? {
|
||||||
|
fs::create_dir(&image_path)?;
|
||||||
|
} else {
|
||||||
|
println!("{:?} dir already exists.", { image_path });
|
||||||
|
}
|
||||||
|
|
||||||
|
let db_path = format!("{}/clipboard.db", dir_path);
|
||||||
|
let conn = Connection::open(&db_path)?;
|
||||||
|
|
||||||
|
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
|
||||||
|
)",
|
||||||
|
[],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
conn,
|
||||||
|
dir_path: dir_path.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn append(&self, entry: ClipboardEntry) -> Result<(), Box<dyn Error>> {
|
||||||
|
let timestamp_millis = entry.timestamp.duration_since(UNIX_EPOCH)?.as_millis() as i64;
|
||||||
|
|
||||||
|
let (entry_type, content) = match &entry.content {
|
||||||
|
ClipboardData::Text(text) => ("text", text.clone()),
|
||||||
|
ClipboardData::Image(img) => {
|
||||||
|
if let Some(bytes) = &img.bytes {
|
||||||
|
let img_path = format!("{}/images/{}.png", self.dir_path, img.id);
|
||||||
|
fs::write(&img_path, bytes)?;
|
||||||
|
}
|
||||||
|
("image", img.id.to_string())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.conn.execute(
|
||||||
|
"INSERT INTO history (type, content, timestamp) VALUES (?1, ?2, ?3)",
|
||||||
|
(entry_type, content, timestamp_millis),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_history(&self) -> Result<Vec<ClipboardEntry>, Box<dyn Error>> {
|
||||||
|
let mut stmt = self
|
||||||
|
.conn
|
||||||
|
.prepare("SELECT type, content, timestamp FROM history ORDER BY timestamp ASC")?;
|
||||||
|
|
||||||
|
let rows = stmt.query_map([], |row| {
|
||||||
|
let ty: String = row.get(0)?;
|
||||||
|
let content: String = row.get(1)?;
|
||||||
|
let timestamp: i64 = row.get(2)?;
|
||||||
|
Ok((ty, content, timestamp))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let mut entries = Vec::new();
|
||||||
|
|
||||||
|
for row in rows {
|
||||||
|
let (ty, content, timestamp) = row?;
|
||||||
|
|
||||||
|
let timestamp = UNIX_EPOCH + Duration::from_millis(timestamp as u64);
|
||||||
|
let data = if ty == "text" {
|
||||||
|
ClipboardData::Text(content)
|
||||||
|
} else {
|
||||||
|
let id = Uuid::parse_str(&content)?;
|
||||||
|
ClipboardData::Image(Image { id, bytes: None })
|
||||||
|
};
|
||||||
|
|
||||||
|
// let data = if ty == "text" {
|
||||||
|
// ClipboardData::Text(content)
|
||||||
|
// } else {
|
||||||
|
// let id = Uuid::parse_str(&content)?;
|
||||||
|
// let img_path = format!("{}/images/{}.png", self.dir_path, id);
|
||||||
|
// let bytes = fs::read(&img_path).unwrap_or_default();
|
||||||
|
// ClipboardData::Image(Image {
|
||||||
|
// bytes: Some(bytes),
|
||||||
|
// id,
|
||||||
|
// })
|
||||||
|
// };
|
||||||
|
|
||||||
|
entries.push(ClipboardEntry {
|
||||||
|
content: data,
|
||||||
|
timestamp,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(entries)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,3 +1,22 @@
|
|||||||
fn main() {
|
use arboard::Clipboard;
|
||||||
println!("Hello, world!");
|
|
||||||
|
use crate::database::Database;
|
||||||
|
|
||||||
|
mod clipboard;
|
||||||
|
mod database;
|
||||||
|
mod models;
|
||||||
|
mod monitor;
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let clipboard = Clipboard::new()?;
|
||||||
|
|
||||||
|
let dir_path = "clipboard";
|
||||||
|
|
||||||
|
let db = Database::init(dir_path)?;
|
||||||
|
|
||||||
|
println!("{:#?}", db.read_history());
|
||||||
|
|
||||||
|
monitor::start(db, clipboard)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
31
rklipd/src/models.rs
Normal file
31
rklipd/src/models.rs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
use std::time::SystemTime;
|
||||||
|
use std::{fs, io};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ClipboardEntry {
|
||||||
|
pub content: ClipboardData,
|
||||||
|
pub timestamp: SystemTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum ClipboardData {
|
||||||
|
Text(String),
|
||||||
|
Image(Image),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Image {
|
||||||
|
pub bytes: Option<Vec<u8>>,
|
||||||
|
pub id: Uuid,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Image {
|
||||||
|
pub fn load_bytes(&self, dir_path: &str) -> io::Result<Vec<u8>> {
|
||||||
|
if let Some(b) = &self.bytes {
|
||||||
|
return Ok(b.clone());
|
||||||
|
}
|
||||||
|
let img_path = format!("{}/images/{}.png", dir_path, self.id);
|
||||||
|
fs::read(img_path)
|
||||||
|
}
|
||||||
|
}
|
||||||
32
rklipd/src/monitor.rs
Normal file
32
rklipd/src/monitor.rs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
use std::{error::Error, sync::mpsc::channel};
|
||||||
|
|
||||||
|
use arboard::Clipboard;
|
||||||
|
use clipboard_master::Master;
|
||||||
|
|
||||||
|
use crate::clipboard::Handler;
|
||||||
|
use crate::database::Database;
|
||||||
|
use crate::models::ClipboardEntry;
|
||||||
|
|
||||||
|
pub fn start(db: Database, mut clipboard: Clipboard) -> Result<(), Box<dyn Error>> {
|
||||||
|
let (tx, rx) = channel();
|
||||||
|
|
||||||
|
let mut master = Master::new(Handler { clipboard_tx: tx })?;
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
if let Err(e) = master.run() {
|
||||||
|
eprintln!("Clipboard monitor error : {}", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for _ in rx {
|
||||||
|
print!("Clipboard changed!");
|
||||||
|
if let Ok(entry) = ClipboardEntry::new(&mut clipboard) {
|
||||||
|
if let Err(e) = db.append(entry) {
|
||||||
|
eprintln!("SQLite writing error: {}", e);
|
||||||
|
} else {
|
||||||
|
println!("SQLite edited!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user