- Шифрованный файл .qulyp: Argon2id + XChaCha20-Poly1305, атомарная запись и .bak - Записи с TOTP, папками, избранным и историей паролей; генератор паролей - Синхронизация через облачную папку с послезаписным слиянием и ручное слияние файлов - Автоблокировка, автоочистка буфера обмена, тёмная/светлая тема - Сборка под Windows и Linux, CI для Gitea Actions - Заметки проекта в notes/ с лимитами строк (tests/notes.rs) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
89 lines
2.7 KiB
Rust
89 lines
2.7 KiB
Rust
//! Настройки приложения (не секретные): недавние файлы, тема, таймеры.
|
|
//! Windows: %APPDATA%\Qulyp\config.json, Linux: ~/.config/qulyp/config.json.
|
|
|
|
use std::path::PathBuf;
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::generator::GenOptions;
|
|
|
|
#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Default)]
|
|
pub enum ThemeMode {
|
|
#[default]
|
|
System,
|
|
Dark,
|
|
Light,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Clone)]
|
|
#[serde(default)]
|
|
pub struct Config {
|
|
pub recent: Vec<PathBuf>,
|
|
pub theme: ThemeMode,
|
|
/// Автоблокировка при бездействии, минут (0 — никогда).
|
|
pub auto_lock_min: u32,
|
|
/// Очистка буфера обмена после копирования, секунд (0 — не очищать).
|
|
pub clipboard_sec: u32,
|
|
pub lock_on_minimize: bool,
|
|
pub generator: GenOptions,
|
|
}
|
|
|
|
impl Default for Config {
|
|
fn default() -> Self {
|
|
Self {
|
|
recent: Vec::new(),
|
|
theme: ThemeMode::System,
|
|
auto_lock_min: 10,
|
|
clipboard_sec: 20,
|
|
lock_on_minimize: false,
|
|
generator: GenOptions::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn path() -> Option<PathBuf> {
|
|
// Переопределение — для тестов и «портативного» режима.
|
|
if let Some(p) = std::env::var_os("QULYP_CONFIG") {
|
|
return Some(PathBuf::from(p));
|
|
}
|
|
let dir = if cfg!(windows) { "Qulyp" } else { "qulyp" };
|
|
Some(dirs::config_dir()?.join(dir).join("config.json"))
|
|
}
|
|
|
|
impl Config {
|
|
pub fn load() -> Self {
|
|
path().and_then(|p| std::fs::read(p).ok()).and_then(|b| serde_json::from_slice(&b).ok()).unwrap_or_default()
|
|
}
|
|
|
|
pub fn save(&self) {
|
|
let Some(p) = path() else { return };
|
|
if let Some(dir) = p.parent() {
|
|
let _ = std::fs::create_dir_all(dir);
|
|
}
|
|
if let Ok(json) = serde_json::to_vec_pretty(self) {
|
|
let _ = std::fs::write(p, json);
|
|
}
|
|
}
|
|
|
|
pub fn push_recent(&mut self, file: PathBuf) {
|
|
let file = file.canonicalize().map(strip_unc).unwrap_or(file);
|
|
self.recent.retain(|p| p != &file);
|
|
self.recent.insert(0, file);
|
|
self.recent.truncate(8);
|
|
self.save();
|
|
}
|
|
|
|
pub fn default_vault_dir() -> PathBuf {
|
|
dirs::document_dir().or_else(dirs::home_dir).unwrap_or_else(|| PathBuf::from("."))
|
|
}
|
|
}
|
|
|
|
/// `canonicalize` на Windows возвращает `\\?\C:\...` — убираем префикс для читаемости.
|
|
fn strip_unc(p: PathBuf) -> PathBuf {
|
|
let s = p.to_string_lossy();
|
|
match s.strip_prefix(r"\\?\") {
|
|
Some(rest) if !rest.starts_with("UNC") => PathBuf::from(rest),
|
|
_ => p,
|
|
}
|
|
}
|