feat: add webdav backup
This commit is contained in:
@@ -55,7 +55,7 @@ tauri-plugin-deep-link = "2.0.1"
|
||||
tauri-plugin-devtools = "2.0.0-rc"
|
||||
url = "2.5.2"
|
||||
zip = "2.2.0"
|
||||
reqwest_dav = "=0.1.14"
|
||||
reqwest_dav = "0.1.14"
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
runas = "=1.2.0"
|
||||
deelevate = "0.2.0"
|
||||
|
||||
@@ -185,7 +185,7 @@ pub async fn change_clash_core(clash_core: Option<String>) -> CmdResult {
|
||||
|
||||
/// restart the sidecar
|
||||
#[tauri::command]
|
||||
pub async fn restart_sidecar() -> CmdResult {
|
||||
pub async fn restart_core() -> CmdResult {
|
||||
wrap_err!(CoreManager::global().restart_core().await)
|
||||
}
|
||||
|
||||
@@ -396,15 +396,28 @@ pub async fn save_webdav_config(url: String, username: String, password: String)
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn create_webdav_backup() -> CmdResult<()> {
|
||||
feat::create_backup_and_upload_webdav()
|
||||
.await
|
||||
.map_err(|err| err.to_string())?;
|
||||
Ok(())
|
||||
wrap_err!(feat::create_backup_and_upload_webdav().await)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn list_webdav_backup() -> CmdResult<Vec<ListFile>> {
|
||||
feat::list_wevdav_backup().await.map_err(|e| e.to_string())
|
||||
wrap_err!(feat::list_wevdav_backup().await)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn delete_webdav_backup(filename: String) -> CmdResult<()> {
|
||||
wrap_err!(feat::delete_webdav_backup(filename).await)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn restore_webdav_backup(filename: String) -> CmdResult<()> {
|
||||
wrap_err!(feat::restore_webdav_backup(filename).await)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn restart_app() -> CmdResult<()> {
|
||||
feat::restart_app();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub mod service {
|
||||
|
||||
@@ -40,14 +40,27 @@ impl WebDavClient {
|
||||
let url = verge.webdav_url.unwrap_or_default();
|
||||
let username = verge.webdav_username.unwrap_or_default();
|
||||
let password = verge.webdav_password.unwrap_or_default();
|
||||
|
||||
let url = url.trim_end_matches('/');
|
||||
let client = reqwest_dav::ClientBuilder::new()
|
||||
.set_agent(
|
||||
reqwest::Client::builder()
|
||||
.danger_accept_invalid_certs(true)
|
||||
.build()
|
||||
.unwrap(),
|
||||
)
|
||||
.set_host(url.to_owned())
|
||||
.set_auth(reqwest_dav::Auth::Basic(
|
||||
username.to_owned(),
|
||||
password.to_owned(),
|
||||
))
|
||||
.build()?;
|
||||
if let Err(_) = client
|
||||
.list(dirs::BACKUP_DIR, reqwest_dav::Depth::Number(0))
|
||||
.await
|
||||
{
|
||||
client.mkcol(dirs::BACKUP_DIR).await?;
|
||||
}
|
||||
|
||||
*self.client.lock() = Some(client.clone());
|
||||
}
|
||||
Ok(self.client.lock().clone().unwrap())
|
||||
@@ -61,10 +74,6 @@ impl WebDavClient {
|
||||
|
||||
pub async fn upload(&self, file_path: PathBuf, file_name: String) -> Result<(), Error> {
|
||||
let client = self.get_client().await?;
|
||||
if client.get(dirs::BACKUP_DIR).await.is_err() {
|
||||
client.mkcol(dirs::BACKUP_DIR).await?;
|
||||
}
|
||||
|
||||
let webdav_path: String = format!("{}/{}", dirs::BACKUP_DIR, file_name);
|
||||
client
|
||||
.put(webdav_path.as_ref(), fs::read(file_path)?)
|
||||
@@ -72,7 +81,16 @@ impl WebDavClient {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn list_files(&self) -> Result<Vec<ListFile>, Error> {
|
||||
pub async fn download(&self, filename: String, storage_path: PathBuf) -> Result<(), Error> {
|
||||
let client = self.get_client().await?;
|
||||
let path = format!("{}/{}", dirs::BACKUP_DIR, filename);
|
||||
let response = client.get(&path.as_str()).await?;
|
||||
let content = response.bytes().await?;
|
||||
fs::write(&storage_path, &content)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn list(&self) -> Result<Vec<ListFile>, Error> {
|
||||
let client = self.get_client().await?;
|
||||
let files = client
|
||||
.list(dirs::BACKUP_DIR, reqwest_dav::Depth::Number(1))
|
||||
@@ -85,6 +103,13 @@ impl WebDavClient {
|
||||
}
|
||||
Ok(final_files)
|
||||
}
|
||||
|
||||
pub async fn delete(&self, file_name: String) -> Result<(), Error> {
|
||||
let client = self.get_client().await?;
|
||||
let path = format!("{}/{}", dirs::BACKUP_DIR, file_name);
|
||||
client.delete(&path).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_backup() -> Result<(String, PathBuf), Error> {
|
||||
@@ -109,8 +134,17 @@ pub fn create_backup() -> Result<(String, PathBuf), Error> {
|
||||
}
|
||||
zip.start_file(dirs::CLASH_CONFIG, options)?;
|
||||
zip.write_all(fs::read(dirs::clash_path()?)?.as_slice())?;
|
||||
|
||||
let mut verge_config: serde_json::Value =
|
||||
serde_yaml::from_str(&fs::read_to_string(dirs::verge_path()?)?)?;
|
||||
if let Some(obj) = verge_config.as_object_mut() {
|
||||
obj.remove("webdav_username");
|
||||
obj.remove("webdav_password");
|
||||
obj.remove("webdav_url");
|
||||
}
|
||||
zip.start_file(dirs::VERGE_CONFIG, options)?;
|
||||
zip.write_all(fs::read(dirs::verge_path()?)?.as_slice())?;
|
||||
zip.write_all(serde_yaml::to_string(&verge_config)?.as_bytes())?;
|
||||
|
||||
zip.start_file(dirs::PROFILE_YAML, options)?;
|
||||
zip.write_all(fs::read(dirs::profiles_path()?)?.as_slice())?;
|
||||
zip.finish()?;
|
||||
|
||||
@@ -87,6 +87,7 @@ impl CoreManager {
|
||||
service::stop_core_by_service().await?;
|
||||
}
|
||||
*running = false;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
use crate::{
|
||||
cmds,
|
||||
config::Config,
|
||||
core::CoreManager,
|
||||
feat, log_err, t,
|
||||
feat, t,
|
||||
utils::{
|
||||
dirs,
|
||||
resolve::{self, VERSION},
|
||||
},
|
||||
};
|
||||
use anyhow::Result;
|
||||
use tauri::AppHandle;
|
||||
use tauri::{
|
||||
menu::CheckMenuItem,
|
||||
tray::{MouseButton, MouseButtonState, TrayIconEvent, TrayIconId},
|
||||
@@ -17,7 +17,6 @@ use tauri::{
|
||||
menu::{MenuEvent, MenuItem, PredefinedMenuItem, Submenu},
|
||||
Wry,
|
||||
};
|
||||
use tauri::{AppHandle, Manager};
|
||||
|
||||
use super::handle;
|
||||
pub struct Tray {}
|
||||
@@ -408,7 +407,7 @@ fn create_tray_menu(
|
||||
Ok(menu)
|
||||
}
|
||||
|
||||
fn on_menu_event(app_handle: &AppHandle, event: MenuEvent) {
|
||||
fn on_menu_event(_: &AppHandle, event: MenuEvent) {
|
||||
match event.id.as_ref() {
|
||||
mode @ ("rule_mode" | "global_mode" | "direct_mode") => {
|
||||
let mode = &mode[0..mode.len() - 5];
|
||||
@@ -423,15 +422,7 @@ fn on_menu_event(app_handle: &AppHandle, event: MenuEvent) {
|
||||
"open_core_dir" => crate::log_err!(cmds::open_core_dir()),
|
||||
"open_logs_dir" => crate::log_err!(cmds::open_logs_dir()),
|
||||
"restart_clash" => feat::restart_clash_core(),
|
||||
"restart_app" => {
|
||||
tauri::async_runtime::block_on(async move {
|
||||
log_err!(CoreManager::global().stop_core().await);
|
||||
});
|
||||
resolve::resolve_reset();
|
||||
//睡1秒再重启
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
tauri::process::restart(&app_handle.env());
|
||||
}
|
||||
"restart_app" => feat::restart_app(),
|
||||
"quit" => {
|
||||
println!("quit");
|
||||
feat::quit(Some(0));
|
||||
|
||||
@@ -7,10 +7,13 @@
|
||||
use crate::config::*;
|
||||
use crate::core::*;
|
||||
use crate::log_err;
|
||||
use crate::utils::dirs::app_home_dir;
|
||||
use crate::utils::resolve;
|
||||
use anyhow::{bail, Result};
|
||||
use reqwest_dav::list_cmd::ListFile;
|
||||
use serde_yaml::{Mapping, Value};
|
||||
use std::fs;
|
||||
use tauri::Manager;
|
||||
use tauri_plugin_clipboard_manager::ClipboardExt;
|
||||
|
||||
// 打开面板
|
||||
@@ -40,6 +43,18 @@ pub fn restart_clash_core() {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn restart_app() {
|
||||
tauri::async_runtime::spawn_blocking(|| {
|
||||
tauri::async_runtime::block_on(async {
|
||||
log_err!(CoreManager::global().stop_core().await);
|
||||
});
|
||||
resolve::resolve_reset();
|
||||
let app_handle = handle::Handle::global().app_handle().unwrap();
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
tauri::process::restart(&app_handle.env());
|
||||
});
|
||||
}
|
||||
|
||||
// 切换模式 rule/global/direct/script mode
|
||||
pub fn change_clash_mode(mode: String) {
|
||||
let mut mapping = Mapping::new();
|
||||
@@ -425,11 +440,37 @@ pub async fn create_backup_and_upload_webdav() -> Result<()> {
|
||||
}
|
||||
|
||||
pub async fn list_wevdav_backup() -> Result<Vec<ListFile>> {
|
||||
backup::WebDavClient::global().list().await.map_err(|err| {
|
||||
log::error!(target: "app", "Failed to list WebDAV backup files: {:#?}", err);
|
||||
err
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn delete_webdav_backup(filename: String) -> Result<()> {
|
||||
backup::WebDavClient::global()
|
||||
.list_files()
|
||||
.delete(filename)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
log::error!(target: "app", "Failed to list WebDAV backup files: {:#?}", err);
|
||||
log::error!(target: "app", "Failed to delete WebDAV backup file: {:#?}", err);
|
||||
err
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn restore_webdav_backup(filename: String) -> Result<()> {
|
||||
let backup_storage_path = app_home_dir().unwrap().join(&filename);
|
||||
backup::WebDavClient::global()
|
||||
.download(filename, backup_storage_path.clone())
|
||||
.await
|
||||
.map_err(|err| {
|
||||
log::error!(target: "app", "Failed to download WebDAV backup file: {:#?}", err);
|
||||
err
|
||||
})?;
|
||||
|
||||
// extract zip file
|
||||
let mut zip = zip::ZipArchive::new(fs::File::open(backup_storage_path.clone())?)?;
|
||||
zip.extract(app_home_dir()?)?;
|
||||
|
||||
// 最后删除临时文件
|
||||
fs::remove_file(backup_storage_path)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -84,7 +84,8 @@ pub fn run() {
|
||||
cmds::get_portable_flag,
|
||||
cmds::get_network_interfaces,
|
||||
// cmds::kill_sidecar,
|
||||
cmds::restart_sidecar,
|
||||
cmds::restart_core,
|
||||
cmds::restart_app,
|
||||
// clash
|
||||
cmds::get_clash_info,
|
||||
cmds::get_clash_logs,
|
||||
@@ -128,6 +129,8 @@ pub fn run() {
|
||||
cmds::create_webdav_backup,
|
||||
cmds::save_webdav_config,
|
||||
cmds::list_webdav_backup,
|
||||
cmds::delete_webdav_backup,
|
||||
cmds::restore_webdav_backup,
|
||||
]);
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
|
||||
Reference in New Issue
Block a user