This commit is contained in:
2026-05-20 19:31:16 +02:00
parent 20f33f5694
commit 8b07e305f0
17 changed files with 1794 additions and 573 deletions

View File

@ -1,25 +1,55 @@
use crate::crypto::Crypto;
use crate::database::Database;
use crate::models::ClipboardData;
use crate::models::{ClipboardData, ClipboardEntry};
use serde::{Deserialize, Serialize};
use std::fs;
use std::io::{Read, Write};
use std::os::unix::net::UnixListener;
use std::path::Path;
use std::sync::{Arc, Mutex};
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct HistoryItem {
pub content: String,
pub timestamp: i64,
}
#[derive(Serialize, Deserialize, Debug)]
pub enum IpcRequest {
GetHistory { limit: usize },
SetClipboard { content: String },
DeleteEntry { content: String },
GetHistory {
limit: usize,
},
SetClipboard {
content: String,
},
DeleteEntry {
content: String,
},
UpdateEntry {
old_content: String,
new_content: String,
},
AddEntry {
content: String,
},
ClearHistory,
}
#[derive(Serialize, Deserialize, Debug)]
pub enum IpcResponse {
History(Vec<String>),
History(Vec<HistoryItem>),
Ok,
Error(String),
}
pub fn start_server(db: Arc<Mutex<Database>>, socket_path: &Path) {
fn reply(stream: &mut std::os::unix::net::UnixStream, resp: IpcResponse) {
if let Ok(json) = serde_json::to_string(&resp) {
let _ = stream.write_all(json.as_bytes());
}
}
pub fn start_server(db: Arc<Mutex<Database>>, crypto: Arc<Crypto>, socket_path: &Path) {
if socket_path.exists() {
let _ = fs::remove_file(socket_path);
}
@ -27,99 +57,157 @@ pub fn start_server(db: Arc<Mutex<Database>>, socket_path: &Path) {
let listener = match UnixListener::bind(socket_path) {
Ok(l) => l,
Err(e) => {
eprintln!("Error while creating socket {}", e);
eprintln!("Erreur socket IPC : {e}");
return;
}
};
println!("ipc server listening {:?}", socket_path);
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
if let Err(e) = fs::set_permissions(socket_path, fs::Permissions::from_mode(0o600)) {
eprintln!("Impossible de restreindre les permissions du socket : {e}");
}
}
println!("IPC server en écoute sur {:?}", socket_path);
for stream in listener.incoming() {
match stream {
Ok(mut stream) => {
let db_clone = Arc::clone(&db);
let crypto_clone = Arc::clone(&crypto);
std::thread::spawn(move || {
let mut buffer = String::new();
let mut buf = String::new();
if stream.read_to_string(&mut buf).is_err() {
return;
}
if stream.read_to_string(&mut buffer).is_ok() {
if let Ok(request) = serde_json::from_str::<IpcRequest>(&buffer) {
match request {
IpcRequest::GetHistory { limit } => {
let db_lock = db_clone.lock().unwrap();
let req = match serde_json::from_str::<IpcRequest>(&buf) {
Ok(r) => r,
Err(e) => {
eprintln!("IPC parse error : {e}");
return;
}
};
// TODO Implem read_history(limit)
let history = db_lock.read_history().unwrap_or_default();
let items: Vec<String> = history
.into_iter()
.rev()
.take(limit)
.map(|entry| match entry.content {
ClipboardData::Text(t) => t,
ClipboardData::Image(img) => format!("{}.jpg", img.id),
})
.collect();
let response = IpcResponse::History(items);
let response_json = serde_json::to_string(&response).unwrap();
let _ = stream.write_all(response_json.as_bytes());
}
IpcRequest::SetClipboard { content } => {
if let Ok(mut clipboard) = arboard::Clipboard::new() {
if content.ends_with(".jpg") || content.ends_with(".png") {
if let Some(proj_dirs) = directories::ProjectDirs::from(
"com", "zefad", "rklipd",
) {
let img_path = proj_dirs
.data_dir()
.join("images")
.join(&content);
if let Ok(img) = image::open(&img_path) {
let rgba = img.into_rgba8();
let img_data = arboard::ImageData {
width: rgba.width() as usize,
height: rgba.height() as usize,
bytes: std::borrow::Cow::Borrowed(
rgba.as_raw(),
),
};
let _ = clipboard.set_image(img_data);
}
}
} else {
let _ = clipboard.set_text(content);
}
match req {
IpcRequest::GetHistory { limit } => {
let lock = db_clone.lock().unwrap();
let history = lock.read_history(limit).unwrap_or_default();
let items: Vec<HistoryItem> = history
.into_iter()
.map(|e| {
let content = match e.content {
ClipboardData::Text(t) => t,
ClipboardData::Image(img) => format!("{}.jpg", img.id),
};
let ts = e
.timestamp
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_millis()
as i64;
HistoryItem {
content,
timestamp: ts,
}
}
})
.collect();
reply(&mut stream, IpcResponse::History(items));
}
IpcRequest::DeleteEntry { content } => {
{
let db_lock = db_clone.lock().unwrap();
let _ = db_lock.delete_entry_by_content(&content);
}
IpcRequest::SetClipboard { content } => {
let actual =
if content.starts_with("enc:") || content.starts_with("enc2:") {
crypto_clone.decrypt(&content).unwrap_or(content)
} else {
content
};
if content.ends_with(".jpg") || content.ends_with(".png") {
if let Some(proj_dirs) =
match arboard::Clipboard::new() {
Ok(mut cb) => {
if actual.ends_with(".jpg") || actual.ends_with(".png") {
if let Some(dirs) =
directories::ProjectDirs::from("com", "zefad", "rklipd")
{
let img_path =
proj_dirs.data_dir().join("images").join(&content);
if img_path.exists() {
if let Err(e) = std::fs::remove_file(&img_path) {
eprintln!("Error while deleting image: {}", e);
} else {
println!("Image deleted : {}", content);
}
let path = dirs.data_dir().join("images").join(&actual);
if let Ok(img) = image::open(&path) {
let rgba = img.into_rgba8();
let (w, h) =
(rgba.width() as usize, rgba.height() as usize);
let _ = cb.set_image(arboard::ImageData {
width: w,
height: h,
bytes: std::borrow::Cow::Owned(rgba.into_raw()),
});
}
}
} else {
let _ = cb.set_text(actual);
}
reply(&mut stream, IpcResponse::Ok);
}
Err(e) => reply(&mut stream, IpcResponse::Error(e.to_string())),
}
}
IpcRequest::DeleteEntry { content } => {
{
let lock = db_clone.lock().unwrap();
let _ = lock.delete_entry_by_content(&content);
}
if !content.starts_with("enc:")
&& !content.starts_with("enc2:")
&& (content.ends_with(".jpg") || content.ends_with(".png"))
{
if let Some(dirs) =
directories::ProjectDirs::from("com", "zefad", "rklipd")
{
let p = dirs.data_dir().join("images").join(&content);
if p.exists() {
let _ = fs::remove_file(p);
}
}
}
reply(&mut stream, IpcResponse::Ok);
}
IpcRequest::UpdateEntry {
old_content,
new_content,
} => {
let lock = db_clone.lock().unwrap();
match lock.update_entry_content(&old_content, &new_content) {
Ok(_) => reply(&mut stream, IpcResponse::Ok),
Err(e) => reply(&mut stream, IpcResponse::Error(e.to_string())),
}
}
IpcRequest::AddEntry { content } => {
let entry = ClipboardEntry {
content: ClipboardData::Text(content),
timestamp: SystemTime::now(),
};
let lock = db_clone.lock().unwrap();
match lock.append(entry) {
Ok(_) => reply(&mut stream, IpcResponse::Ok),
Err(e) => reply(&mut stream, IpcResponse::Error(e.to_string())),
}
}
IpcRequest::ClearHistory => {
let lock = db_clone.lock().unwrap();
match lock.clear_history() {
Ok(_) => reply(&mut stream, IpcResponse::Ok),
Err(e) => reply(&mut stream, IpcResponse::Error(e.to_string())),
}
}
}
});
}
Err(e) => eprintln!("Erreur de connexion IPC: {}", e),
Err(e) => eprintln!("Erreur connexion IPC : {e}"),
}
}
}