Phase 3 — Clipboard Watcher (Windows): - clipboard.rs: détection d'images via arboard, conversion BGRA→PNG - Gère les doublons consécutifs (même image dans le presse-papier) Phase 6 — Watcher Loop: - Windows: polling clipboard → hash → dédoublonnage → upload - Linux/macOS: polling de dossier configurable (watch_dir) - Ajout champ watch_dir dans WatcherConfig - Client Imago cloné avant await (évite problème Send/Sync) Dashboard: - Badge version v0.1.0 dans le header - Barre de navigation: Galerie / Changelog / Aide - Pages changelog.html et help.html (chargement dynamique) - Endpoint GET /api/version avec infos build - Correction import delete inutilisé dans routes.rs
195 lines
4.8 KiB
Rust
195 lines
4.8 KiB
Rust
//! Gestion de la configuration TOML
|
|
|
|
use anyhow::{Context, Result};
|
|
use serde::{Deserialize, Serialize};
|
|
use std::path::PathBuf;
|
|
|
|
/// Configuration complète d'imagod-web
|
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
|
pub struct Config {
|
|
#[serde(default)]
|
|
pub imago: ImagoConfig,
|
|
#[serde(default)]
|
|
pub watcher: WatcherConfig,
|
|
#[serde(default)]
|
|
pub dedup: DedupConfig,
|
|
#[serde(default)]
|
|
pub ui: UiConfig,
|
|
#[serde(default)]
|
|
pub logging: LoggingConfig,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
|
pub struct ImagoConfig {
|
|
#[serde(default = "default_imago_url")]
|
|
pub url: String,
|
|
#[serde(default)]
|
|
pub api_key: String,
|
|
}
|
|
|
|
fn default_imago_url() -> String {
|
|
"http://imago.dracodev.net:8000".into()
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct WatcherConfig {
|
|
#[serde(default = "default_true")]
|
|
pub capture_images: bool,
|
|
#[serde(default = "default_poll_interval")]
|
|
pub poll_interval_ms: u64,
|
|
/// Dossier à surveiller pour les nouveaux screenshots (Linux/macOS)
|
|
/// Si vide, utilise le clipboard (Windows uniquement)
|
|
#[serde(default)]
|
|
pub watch_dir: Option<String>,
|
|
#[serde(default)]
|
|
pub filters: WatcherFilters,
|
|
}
|
|
|
|
impl Default for WatcherConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
capture_images: true,
|
|
poll_interval_ms: 500,
|
|
watch_dir: None,
|
|
filters: WatcherFilters::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct WatcherFilters {
|
|
#[serde(default)]
|
|
pub only_apps: Vec<String>,
|
|
#[serde(default = "default_exclude_apps")]
|
|
pub exclude_apps: Vec<String>,
|
|
}
|
|
|
|
impl Default for WatcherFilters {
|
|
fn default() -> Self {
|
|
Self {
|
|
only_apps: vec![],
|
|
exclude_apps: vec!["mstsc".into()],
|
|
}
|
|
}
|
|
}
|
|
|
|
fn default_exclude_apps() -> Vec<String> {
|
|
vec!["mstsc".into()]
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct DedupConfig {
|
|
#[serde(default = "default_dedup_method")]
|
|
pub method: String,
|
|
#[serde(default = "default_cache_size")]
|
|
pub cache_size: usize,
|
|
}
|
|
|
|
impl Default for DedupConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
method: default_dedup_method(),
|
|
cache_size: default_cache_size(),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn default_dedup_method() -> String {
|
|
"sha256".into()
|
|
}
|
|
|
|
fn default_cache_size() -> usize {
|
|
10000
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct UiConfig {
|
|
#[serde(default = "default_true")]
|
|
pub show_notifications: bool,
|
|
#[serde(default = "default_true")]
|
|
pub show_tray_icon: bool,
|
|
#[serde(default = "default_notification_timeout")]
|
|
pub notification_timeout_sec: u64,
|
|
}
|
|
|
|
impl Default for UiConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
show_notifications: true,
|
|
show_tray_icon: true,
|
|
notification_timeout_sec: default_notification_timeout(),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn default_notification_timeout() -> u64 {
|
|
3
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct LoggingConfig {
|
|
#[serde(default = "default_log_level")]
|
|
pub level: String,
|
|
}
|
|
|
|
impl Default for LoggingConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
level: default_log_level(),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn default_log_level() -> String {
|
|
"info".into()
|
|
}
|
|
|
|
fn default_true() -> bool {
|
|
true
|
|
}
|
|
|
|
fn default_poll_interval() -> u64 {
|
|
500
|
|
}
|
|
|
|
impl Config {
|
|
/// Charge la configuration depuis `%APPDATA%/imagod-web/imagod.toml`
|
|
pub fn load() -> Result<Self> {
|
|
let path = Self::config_path()?;
|
|
if path.exists() {
|
|
let content = std::fs::read_to_string(&path)
|
|
.with_context(|| format!("Impossible de lire {}", path.display()))?;
|
|
let config: Config = toml::from_str(&content)
|
|
.with_context(|| "Erreur de parsing du fichier de configuration")?;
|
|
Ok(config)
|
|
} else {
|
|
let config = Config::default();
|
|
config.save()?;
|
|
Ok(config)
|
|
}
|
|
}
|
|
|
|
/// Sauvegarde la configuration
|
|
pub fn save(&self) -> Result<()> {
|
|
let path = Self::config_path()?;
|
|
if let Some(parent) = path.parent() {
|
|
std::fs::create_dir_all(parent)?;
|
|
}
|
|
let content = toml::to_string_pretty(self)?;
|
|
std::fs::write(&path, content)?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Chemin du fichier de configuration
|
|
fn config_path() -> Result<PathBuf> {
|
|
let base = if cfg!(target_os = "windows") {
|
|
let appdata = std::env::var("APPDATA").context("Variable APPDATA non définie")?;
|
|
PathBuf::from(appdata)
|
|
} else {
|
|
let home = std::env::var("HOME").context("Variable HOME non définie")?;
|
|
PathBuf::from(home).join(".config")
|
|
};
|
|
Ok(base.join("imagod-web").join("imagod.toml"))
|
|
}
|
|
}
|