Feat: Implement Storage-based (JSON) Settings & Data-Storage (WIP) (#56)

This commit is contained in:
Youwes09
2026-04-29 00:18:09 -05:00
parent c009bd71fc
commit 170493aa71
23 changed files with 493 additions and 164 deletions
+12 -5
View File
@@ -2,7 +2,8 @@ use std::path::PathBuf;
use tauri::Manager;
fn backup_dir(app: &tauri::AppHandle) -> PathBuf {
app.path().app_data_dir()
app.path()
.app_data_dir()
.unwrap_or_else(|_| PathBuf::from("."))
.join("backups")
}
@@ -20,7 +21,8 @@ pub async fn export_app_data(app: tauri::AppHandle, json: String) -> Result<Stri
let filename = format!("moku-backup-{}.json", unix_now());
let path = app.dialog()
let path = app
.dialog()
.file()
.set_title("Save Moku app data backup")
.set_file_name(&filename)
@@ -37,7 +39,8 @@ pub async fn export_app_data(app: tauri::AppHandle, json: String) -> Result<Stri
pub async fn import_app_data(app: tauri::AppHandle) -> Result<String, String> {
use tauri_plugin_dialog::DialogExt;
let path = app.dialog()
let path = app
.dialog()
.file()
.set_title("Open Moku app data backup")
.blocking_pick_file()
@@ -57,7 +60,11 @@ pub fn auto_backup_app_data(app: tauri::AppHandle, json: String) -> Result<(), S
let mut entries: Vec<_> = std::fs::read_dir(&dir)
.map_err(|e| e.to_string())?
.filter_map(|e| e.ok())
.filter(|e| e.file_name().to_string_lossy().starts_with("auto-moku-backup-"))
.filter(|e| {
e.file_name()
.to_string_lossy()
.starts_with("auto-moku-backup-")
})
.collect();
entries.sort_by_key(|e| e.file_name());
@@ -72,4 +79,4 @@ pub fn auto_backup_app_data(app: tauri::AppHandle, json: String) -> Result<(), S
#[tauri::command]
pub fn get_auto_backup_dir(app: tauri::AppHandle) -> String {
backup_dir(&app).to_string_lossy().into_owned()
}
}
+1 -1
View File
@@ -2,4 +2,4 @@ pub mod backup;
pub mod server;
pub mod storage;
pub mod system;
pub mod updater;
pub mod updater;
+28 -11
View File
@@ -1,6 +1,6 @@
use tauri::Manager;
use crate::server::{self, resolve::suwayomi_data_dir, SpawnError};
use crate::ServerState;
use tauri::Manager;
#[tauri::command]
pub fn spawn_server(binary: String, app: tauri::AppHandle) -> Result<(), SpawnError> {
@@ -14,16 +14,24 @@ pub fn spawn_server(binary: String, app: tauri::AppHandle) -> Result<(), SpawnEr
let data_dir = suwayomi_data_dir();
let log_path = data_dir.join("moku-spawn.log");
let _ = std::fs::create_dir_all(&data_dir);
let mut log = std::fs::OpenOptions::new().create(true).append(true).open(&log_path).ok();
let mut log = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(&log_path)
.ok();
server::do_log(&mut log, &format!("[spawn_server] binary={:?} data_dir={:?}", binary, data_dir));
server::do_log(
&mut log,
&format!("[spawn_server] binary={:?} data_dir={:?}", binary, data_dir),
);
server::conf::seed_server_conf(&data_dir);
let mut invocation = server::resolve::resolve_server_binary(&binary, &app, &mut log).map_err(|e| {
server::do_log(&mut log, &format!("[spawn_server] resolve failed: {:?}", e));
e
})?;
let mut invocation =
server::resolve::resolve_server_binary(&binary, &app, &mut log).map_err(|e| {
server::do_log(&mut log, &format!("[spawn_server] resolve failed: {:?}", e));
e
})?;
if invocation.bin.ends_with("java") || invocation.bin.ends_with("java.exe") {
let rootdir_flag = format!(
@@ -33,12 +41,21 @@ pub fn spawn_server(binary: String, app: tauri::AppHandle) -> Result<(), SpawnEr
invocation.args.insert(0, rootdir_flag);
}
let working_dir = invocation.working_dir.unwrap_or_else(|| std::env::current_dir().unwrap_or_default());
let working_dir = invocation
.working_dir
.unwrap_or_else(|| std::env::current_dir().unwrap_or_default());
server::do_log(&mut log, &format!("[spawn_server] bin={:?} args={:?} cwd={:?}", invocation.bin, invocation.args, working_dir));
server::do_log(
&mut log,
&format!(
"[spawn_server] bin={:?} args={:?} cwd={:?}",
invocation.bin, invocation.args, working_dir
),
);
use tauri_plugin_shell::ShellExt;
let cmd = app.shell()
let cmd = app
.shell()
.command(&invocation.bin)
.env("JAVA_TOOL_OPTIONS", "-Djava.awt.headless=true")
.args(&invocation.args)
@@ -60,4 +77,4 @@ pub fn spawn_server(binary: String, app: tauri::AppHandle) -> Result<(), SpawnEr
pub fn kill_server(app: tauri::AppHandle) -> Result<(), String> {
server::kill_tachidesk(&app);
Ok(())
}
}
+25 -12
View File
@@ -1,5 +1,5 @@
use std::path::PathBuf;
use serde::Serialize;
use std::path::PathBuf;
use sysinfo::Disks;
use tauri::Emitter;
use walkdir::WalkDir;
@@ -10,8 +10,8 @@ use crate::server::resolve::suwayomi_data_dir;
pub struct StorageInfo {
pub manga_bytes: u64,
pub total_bytes: u64,
pub free_bytes: u64,
pub path: String,
pub free_bytes: u64,
pub path: String,
}
fn resolve_downloads_path(downloads_path: &str) -> PathBuf {
@@ -53,8 +53,8 @@ pub fn get_storage_info(downloads_path: String) -> Result<StorageInfo, String> {
Ok(StorageInfo {
manga_bytes,
total_bytes: disk.total_space(),
free_bytes: disk.available_space(),
path: path.to_string_lossy().into_owned(),
free_bytes: disk.available_space(),
path: path.to_string_lossy().into_owned(),
})
}
@@ -74,7 +74,11 @@ pub fn create_directory(path: String) -> Result<(), String> {
}
#[tauri::command]
pub async fn migrate_downloads(app: tauri::AppHandle, src: String, dst: String) -> Result<(), String> {
pub async fn migrate_downloads(
app: tauri::AppHandle,
src: String,
dst: String,
) -> Result<(), String> {
use std::fs;
let src_path = PathBuf::from(src.trim());
@@ -90,12 +94,18 @@ pub async fn migrate_downloads(app: tauri::AppHandle, src: String, dst: String)
.filter(|e| e.file_type().is_file())
.count() as u64;
let _ = app.emit("migrate_progress", serde_json::json!({ "done": 0u64, "total": total, "current": "" }));
let _ = app.emit(
"migrate_progress",
serde_json::json!({ "done": 0u64, "total": total, "current": "" }),
);
let mut done: u64 = 0;
for entry in WalkDir::new(&src_path).into_iter().filter_map(|e| e.ok()) {
let rel = entry.path().strip_prefix(&src_path).map_err(|e| e.to_string())?;
let rel = entry
.path()
.strip_prefix(&src_path)
.map_err(|e| e.to_string())?;
let target = dst_path.join(rel);
if entry.file_type().is_dir() {
@@ -106,12 +116,15 @@ pub async fn migrate_downloads(app: tauri::AppHandle, src: String, dst: String)
}
fs::copy(entry.path(), &target).map_err(|e| e.to_string())?;
done += 1;
let _ = app.emit("migrate_progress", serde_json::json!({
"done": done, "total": total, "current": rel.to_string_lossy()
}));
let _ = app.emit(
"migrate_progress",
serde_json::json!({
"done": done, "total": total, "current": rel.to_string_lossy()
}),
);
}
}
fs::remove_dir_all(&src_path).map_err(|e| e.to_string())?;
Ok(())
}
}
+2 -2
View File
@@ -1,6 +1,6 @@
use tauri::Manager;
#[cfg(target_os = "windows")]
use crate::server::resolve::strip_unc;
use tauri::Manager;
#[tauri::command]
pub fn get_platform_ui_scale(window: tauri::Window) -> f64 {
@@ -49,4 +49,4 @@ pub async fn pick_downloads_folder(app: tauri::AppHandle) -> Option<String> {
.set_title("Choose Downloads Folder")
.blocking_pick_folder()
.map(|p| p.to_string())
}
}
+54 -30
View File
@@ -2,18 +2,18 @@ use serde::{Deserialize, Serialize};
#[derive(Serialize, Clone)]
pub struct ReleaseInfo {
pub tag_name: String,
pub name: String,
pub body: String,
pub tag_name: String,
pub name: String,
pub body: String,
pub published_at: String,
pub html_url: String,
pub html_url: String,
}
#[derive(Clone, Serialize)]
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
struct UpdateProgress {
downloaded: u64,
total: Option<u64>,
total: Option<u64>,
}
#[tauri::command]
@@ -22,11 +22,11 @@ pub async fn list_releases() -> Result<Vec<ReleaseInfo>, String> {
#[derive(Deserialize)]
struct GhRelease {
tag_name: String,
name: Option<String>,
body: Option<String>,
tag_name: String,
name: Option<String>,
body: Option<String>,
published_at: Option<String>,
html_url: String,
html_url: String,
}
let client = reqwest::Client::builder()
@@ -44,17 +44,20 @@ pub async fn list_releases() -> Result<Vec<ReleaseInfo>, String> {
return Err(format!("GitHub API returned {}", resp.status()));
}
let releases: Vec<GhRelease> = serde_json::from_str(
&resp.text().await.map_err(|e| e.to_string())?
).map_err(|e| e.to_string())?;
let releases: Vec<GhRelease> =
serde_json::from_str(&resp.text().await.map_err(|e| e.to_string())?)
.map_err(|e| e.to_string())?;
Ok(releases.into_iter().map(|r| ReleaseInfo {
tag_name: r.tag_name.clone(),
name: r.name.unwrap_or_else(|| r.tag_name.clone()),
body: r.body.unwrap_or_default(),
published_at: r.published_at.unwrap_or_default(),
html_url: r.html_url,
}).collect())
Ok(releases
.into_iter()
.map(|r| ReleaseInfo {
tag_name: r.tag_name.clone(),
name: r.name.unwrap_or_else(|| r.tag_name.clone()),
body: r.body.unwrap_or_default(),
published_at: r.published_at.unwrap_or_default(),
html_url: r.html_url,
})
.collect())
}
#[tauri::command]
@@ -70,9 +73,15 @@ pub async fn download_and_install_update(app: tauri::AppHandle, tag: String) ->
use tauri_plugin_http::reqwest;
#[derive(Deserialize)]
struct Asset { name: String, browser_download_url: String, size: u64 }
struct Asset {
name: String,
browser_download_url: String,
size: u64,
}
#[derive(Deserialize)]
struct Release { assets: Vec<Asset> }
struct Release {
assets: Vec<Asset>,
}
let client = reqwest::Client::builder()
.user_agent("Moku")
@@ -80,26 +89,41 @@ pub async fn download_and_install_update(app: tauri::AppHandle, tag: String) ->
.map_err(|e| e.to_string())?;
let resp = client
.get(format!("https://api.github.com/repos/moku-project/Moku/releases/tags/{}", tag))
.get(format!(
"https://api.github.com/repos/moku-project/Moku/releases/tags/{}",
tag
))
.send()
.await
.map_err(|e| e.to_string())?;
if !resp.status().is_success() {
return Err(format!("GitHub API returned {} for tag {}", resp.status(), tag));
return Err(format!(
"GitHub API returned {} for tag {}",
resp.status(),
tag
));
}
let release: Release = serde_json::from_str(
&resp.text().await.map_err(|e| e.to_string())?
).map_err(|e| e.to_string())?;
let release: Release = serde_json::from_str(&resp.text().await.map_err(|e| e.to_string())?)
.map_err(|e| e.to_string())?;
let asset = release.assets
let asset = release
.assets
.into_iter()
.find(|a| a.name.ends_with("_x64-setup.exe"))
.ok_or_else(|| format!("No x64-setup.exe asset found in release {}", tag))?;
let total = if asset.size > 0 { Some(asset.size) } else { None };
let mut resp = client.get(&asset.browser_download_url).send().await.map_err(|e| e.to_string())?;
let total = if asset.size > 0 {
Some(asset.size)
} else {
None
};
let mut resp = client
.get(&asset.browser_download_url)
.send()
.await
.map_err(|e| e.to_string())?;
let tmp_path = std::env::temp_dir().join(&asset.name);
let mut file = std::fs::File::create(&tmp_path).map_err(|e| e.to_string())?;
@@ -123,4 +147,4 @@ pub async fn download_and_install_update(app: tauri::AppHandle, tag: String) ->
Ok(())
}
}
}