AspansQulyp/src/config.rs
deni.reyton 45de8f9f51 Qulyp: локальный менеджер паролей на Rust + egui
- Шифрованный файл .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>
2026-09-30 14:32:11 +05:00

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,
}
}