diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index cf125be..78b6c3f 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -268,6 +268,7 @@ name = "bilibili-video-downloader" version = "0.1.0" dependencies = [ "anyhow", + "parking_lot", "serde", "serde_json", "specta", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 8afd82a..0b96406 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -28,6 +28,7 @@ tauri-specta = { version = "=2.0.0-rc.20", features = ["derive", "typescript"] } specta-typescript = { version = "=0.0.7" } anyhow = { version = "1.0.98" } +parking_lot = { version = "0.12.4", features = ["send_guard"] } [profile.release] strip = true diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index 1d9ce03..4a6174f 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -1,5 +1,16 @@ +use parking_lot::RwLock; + +use crate::config::Config; + #[tauri::command] #[specta::specta] pub fn greet(name: &str) -> String { format!("Hello, {}! You've been greeted from Rust!", name) } + +#[tauri::command(async)] +#[specta::specta] +#[allow(clippy::needless_pass_by_value)] +pub fn get_config(config: tauri::State>) -> Config { + config.read().clone() +} diff --git a/src-tauri/src/config.rs b/src-tauri/src/config.rs new file mode 100644 index 0000000..cb080d1 --- /dev/null +++ b/src-tauri/src/config.rs @@ -0,0 +1,69 @@ +use std::path::{Path, PathBuf}; + +use serde::{Deserialize, Serialize}; +use specta::Type; +use tauri::{AppHandle, Manager}; + +#[derive(Debug, Clone, Serialize, Deserialize, Type)] +#[serde(rename_all = "camelCase")] +pub struct Config { + pub download_dir: PathBuf, +} + +impl Config { + pub fn new(app: &AppHandle) -> anyhow::Result { + let app_data_dir = app.path().app_data_dir()?; + let config_path = app_data_dir.join("config.json"); + + let config = if config_path.exists() { + let config_string = std::fs::read_to_string(config_path)?; + match serde_json::from_str(&config_string) { + // 如果能够直接解析为Config,则直接返回 + Ok(config) => config, + // 否则,将默认配置与文件中已有的配置合并 + // 以免新版本添加了新的配置项,用户升级到新版本后,所有配置项都被重置 + Err(_) => Config::merge_config(&config_string, &app_data_dir), + } + } else { + Config::default(&app_data_dir) + }; + config.save(app)?; + Ok(config) + } + + pub fn save(&self, app: &AppHandle) -> anyhow::Result<()> { + let app_data_dir = app.path().app_data_dir()?; + let config_path = app_data_dir.join("config.json"); + let config_string = serde_json::to_string_pretty(self)?; + std::fs::write(config_path, config_string)?; + Ok(()) + } + + fn merge_config(config_string: &str, app_data_dir: &Path) -> Config { + let Ok(mut json_value) = serde_json::from_str::(config_string) else { + return Config::default(app_data_dir); + }; + let serde_json::Value::Object(ref mut map) = json_value else { + return Config::default(app_data_dir); + }; + let Ok(default_config_value) = serde_json::to_value(Config::default(app_data_dir)) else { + return Config::default(app_data_dir); + }; + let serde_json::Value::Object(default_map) = default_config_value else { + return Config::default(app_data_dir); + }; + for (key, value) in default_map { + map.entry(key).or_insert(value); + } + let Ok(config) = serde_json::from_value(json_value) else { + return Config::default(app_data_dir); + }; + config + } + + fn default(app_data_dir: &Path) -> Config { + Config { + download_dir: app_data_dir.join("视频下载"), + } + } +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index d63cda1..16df6db 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,10 +1,13 @@ mod commands; +mod config; mod extensions; mod utils; -use tauri::Wry; - -use crate::commands::*; +use anyhow::Context; +use commands::*; +use config::Config; +use parking_lot::RwLock; +use tauri::{Manager, Wry}; fn generate_context() -> tauri::Context { tauri::generate_context!() @@ -13,7 +16,7 @@ fn generate_context() -> tauri::Context { #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { let builder = tauri_specta::Builder::::new() - .commands(tauri_specta::collect_commands![greet]) + .commands(tauri_specta::collect_commands![greet, get_config]) .events(tauri_specta::collect_events![]); #[cfg(debug_assertions)] @@ -32,6 +35,18 @@ pub fn run() { .invoke_handler(builder.invoke_handler()) .setup(move |app| { builder.mount_events(app); + + let app_data_dir = app + .path() + .app_data_dir() + .context("获取app_data_dir目录失败")?; + + std::fs::create_dir_all(&app_data_dir) + .context(format!("创建app_data_dir目录`{app_data_dir:?}`失败"))?; + + let config = RwLock::new(Config::new(app.handle())?); + app.manage(config); + Ok(()) }) .run(generate_context()) diff --git a/src/App.vue b/src/App.vue index 0d67b22..ce7037e 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,6 +1,6 @@ diff --git a/src/bindings.ts b/src/bindings.ts index 40b6c6e..8147007 100644 --- a/src/bindings.ts +++ b/src/bindings.ts @@ -7,6 +7,9 @@ export const commands = { async greet(name: string) : Promise { return await TAURI_INVOKE("greet", { name }); +}, +async getConfig() : Promise { + return await TAURI_INVOKE("get_config"); } } @@ -20,7 +23,7 @@ async greet(name: string) : Promise { /** user-defined types **/ - +export type Config = { downloadDir: string } /** tauri-specta globals **/