From a5f9e6552104f9b49a4eca61c814f62170e83c0e Mon Sep 17 00:00:00 2001 From: Pylogmon Date: Wed, 30 Aug 2023 23:17:10 +0800 Subject: [PATCH] feat: Add WebDav Backup --- src-tauri/Cargo.lock | 85 +++++++++- src-tauri/Cargo.toml | 3 +- src-tauri/src/cmd.rs | 79 --------- src-tauri/src/lang_detect.rs | 78 +++++++++ src-tauri/src/main.rs | 10 +- src-tauri/src/webdav.rs | 158 ++++++++++++++++++ src-tauri/tauri.conf.json | 6 + src/i18n/locales/en_US.json | 13 ++ src/i18n/locales/zh_CN.json | 13 ++ src/services/translate/deepl/Config.jsx | 1 - .../Config/components/SideBar/index.jsx | 13 ++ .../Config/pages/Backup/WebDavModal/index.jsx | 157 +++++++++++++++++ src/window/Config/pages/Backup/index.jsx | 151 +++++++++++++++++ src/window/Config/routes/index.jsx | 5 + 14 files changed, 684 insertions(+), 88 deletions(-) create mode 100644 src-tauri/src/lang_detect.rs create mode 100644 src-tauri/src/webdav.rs create mode 100644 src/window/Config/pages/Backup/WebDavModal/index.jsx create mode 100644 src/window/Config/pages/Backup/index.jsx diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index c337822ecf..363b2d8d23 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -518,8 +518,11 @@ checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" dependencies = [ "android-tzdata", "iana-time-zone", + "js-sys", "num-traits", "serde", + "time 0.1.45", + "wasm-bindgen", "winapi", ] @@ -911,6 +914,19 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "digest_auth" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3054f4e81d395e50822796c5e99ca522e6ba7be98947d6d4b0e5e61640bdb894" +dependencies = [ + "digest", + "hex", + "md-5", + "rand 0.8.5", + "sha2", +] + [[package]] name = "dirs" version = "4.0.0" @@ -2551,7 +2567,7 @@ dependencies = [ "dirs-next", "objc-foundation", "objc_id", - "time", + "time 0.3.23", ] [[package]] @@ -2598,6 +2614,15 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" +[[package]] +name = "md-5" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6365506850d44bff6e2fbcb5176cf63650e48bd45ef2fe2665ae1570e0f4b9ca" +dependencies = [ + "digest", +] + [[package]] name = "memchr" version = "2.5.0" @@ -3337,7 +3362,7 @@ dependencies = [ "line-wrap", "quick-xml 0.29.0", "serde", - "time", + "time 0.3.23", ] [[package]] @@ -3383,6 +3408,7 @@ dependencies = [ "log", "mouse_position", "once_cell", + "reqwest_dav", "screenshots", "selection", "serde", @@ -3728,6 +3754,24 @@ dependencies = [ "winreg 0.10.1", ] +[[package]] +name = "reqwest_dav" +version = "0.1.3" +source = "git+https://github.com/pot-app/reqwest_dav.git#241363249e0fb2a12912ec04400fc55c93b4a079" +dependencies = [ + "async-trait", + "chrono", + "digest_auth", + "http", + "reqwest", + "serde", + "serde-xml-rs", + "serde_derive", + "serde_json", + "tokio", + "url", +] + [[package]] name = "rfd" version = "0.10.0" @@ -3944,6 +3988,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "serde-xml-rs" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb3aa78ecda1ebc9ec9847d5d3aba7d618823446a049ba2491940506da6e2782" +dependencies = [ + "log", + "serde", + "thiserror", + "xml-rs", +] + [[package]] name = "serde_derive" version = "1.0.174" @@ -4011,7 +4067,7 @@ dependencies = [ "serde", "serde_json", "serde_with_macros", - "time", + "time 0.3.23", ] [[package]] @@ -4457,7 +4513,7 @@ dependencies = [ "tauri-utils", "tempfile", "thiserror", - "time", + "time 0.3.23", "tokio", "url", "uuid", @@ -4505,7 +4561,7 @@ dependencies = [ "sha2", "tauri-utils", "thiserror", - "time", + "time 0.3.23", "uuid", "walkdir", ] @@ -4563,7 +4619,7 @@ dependencies = [ "serde_json", "serde_repr", "tauri", - "time", + "time 0.3.23", ] [[package]] @@ -4753,6 +4809,17 @@ dependencies = [ "weezl", ] +[[package]] +name = "time" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + [[package]] name = "time" version = "0.3.23" @@ -5171,6 +5238,12 @@ version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index fe1432c4f1..496cf904a9 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -14,7 +14,7 @@ tauri-build = { version = "1.4", features = [] } [dependencies] -tauri = { version = "1.4", features = [ "protocol-asset", "shell-all", "clipboard-all", "os-all", "http-all", "http-multipart", "updater", "notification-all", "global-shortcut-all", "window-all", "path-all", "system-tray"] } +tauri = { version = "1.4", features = [ "fs-read-file", "fs-write-file", "protocol-asset", "shell-all", "clipboard-all", "os-all", "http-all", "http-multipart", "updater", "notification-all", "global-shortcut-all", "window-all", "path-all", "system-tray"] } tauri-plugin-single-instance = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" } tauri-plugin-autostart = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" } tauri-plugin-fs-watch = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" } @@ -35,6 +35,7 @@ arboard = "3.2.0" libloader = "0.1.4" libloading = "0.8.0" lingua = { version = "1.5.0", default-features = false,features = ["chinese", "japanese", "english", "korean", "french", "spanish", "german", "russian", "italian", "portuguese", "turkish", "arabic", "vietnamese", "thai", "indonesian", "malay", "hindi", "mongolian",] } +reqwest_dav = {git="https://github.com/pot-app/reqwest_dav.git"} [target.'cfg(target_os = "macos")'.dependencies] window-shadows = "0.2" diff --git a/src-tauri/src/cmd.rs b/src-tauri/src/cmd.rs index 5f1e61277c..4af84f0182 100644 --- a/src-tauri/src/cmd.rs +++ b/src-tauri/src/cmd.rs @@ -141,82 +141,3 @@ pub fn unset_proxy() -> Result { std::env::remove_var("all_proxy"); Ok(true) } - -pub fn init_lang_detect() { - use lingua::{Language, LanguageDetectorBuilder}; - let languages = vec![ - Language::Chinese, - Language::Japanese, - Language::English, - Language::Korean, - Language::French, - Language::Spanish, - Language::German, - Language::Russian, - Language::Italian, - Language::Portuguese, - Language::Turkish, - Language::Arabic, - Language::Vietnamese, - Language::Thai, - Language::Indonesian, - Language::Malay, - Language::Hindi, - Language::Mongolian, - ]; - let detector = LanguageDetectorBuilder::from_languages(&languages) - .with_preloaded_language_models() - .build(); - let _ = detector.detect_language_of("Hello Language"); -} -#[tauri::command] -pub fn lang_detect(text: &str) -> Result<&str, ()> { - use lingua::{Language, LanguageDetectorBuilder}; - let languages = vec![ - Language::Chinese, - Language::Japanese, - Language::English, - Language::Korean, - Language::French, - Language::Spanish, - Language::German, - Language::Russian, - Language::Italian, - Language::Portuguese, - Language::Turkish, - Language::Arabic, - Language::Vietnamese, - Language::Thai, - Language::Indonesian, - Language::Malay, - Language::Hindi, - Language::Mongolian, - ]; - let detector = LanguageDetectorBuilder::from_languages(&languages) - .with_preloaded_language_models() - .build(); - if let Some(lang) = detector.detect_language_of(text) { - match lang { - Language::Chinese => Ok("zh_cn"), - Language::Japanese => Ok("ja"), - Language::English => Ok("en"), - Language::Korean => Ok("ko"), - Language::French => Ok("fr"), - Language::Spanish => Ok("es"), - Language::German => Ok("de"), - Language::Russian => Ok("ru"), - Language::Italian => Ok("it"), - Language::Portuguese => Ok("pt_pt"), - Language::Turkish => Ok("tr"), - Language::Arabic => Ok("ar"), - Language::Vietnamese => Ok("vi"), - Language::Thai => Ok("th"), - Language::Indonesian => Ok("id"), - Language::Malay => Ok("ms"), - Language::Hindi => Ok("hi"), - Language::Mongolian => Ok("mn_cy"), - } - } else { - return Ok(""); - } -} diff --git a/src-tauri/src/lang_detect.rs b/src-tauri/src/lang_detect.rs new file mode 100644 index 0000000000..aee1eae72e --- /dev/null +++ b/src-tauri/src/lang_detect.rs @@ -0,0 +1,78 @@ +pub fn init_lang_detect() { + use lingua::{Language, LanguageDetectorBuilder}; + let languages = vec![ + Language::Chinese, + Language::Japanese, + Language::English, + Language::Korean, + Language::French, + Language::Spanish, + Language::German, + Language::Russian, + Language::Italian, + Language::Portuguese, + Language::Turkish, + Language::Arabic, + Language::Vietnamese, + Language::Thai, + Language::Indonesian, + Language::Malay, + Language::Hindi, + Language::Mongolian, + ]; + let detector = LanguageDetectorBuilder::from_languages(&languages) + .with_preloaded_language_models() + .build(); + let _ = detector.detect_language_of("Hello Language"); +} +#[tauri::command] +pub fn lang_detect(text: &str) -> Result<&str, ()> { + use lingua::{Language, LanguageDetectorBuilder}; + let languages = vec![ + Language::Chinese, + Language::Japanese, + Language::English, + Language::Korean, + Language::French, + Language::Spanish, + Language::German, + Language::Russian, + Language::Italian, + Language::Portuguese, + Language::Turkish, + Language::Arabic, + Language::Vietnamese, + Language::Thai, + Language::Indonesian, + Language::Malay, + Language::Hindi, + Language::Mongolian, + ]; + let detector = LanguageDetectorBuilder::from_languages(&languages) + .with_preloaded_language_models() + .build(); + if let Some(lang) = detector.detect_language_of(text) { + match lang { + Language::Chinese => Ok("zh_cn"), + Language::Japanese => Ok("ja"), + Language::English => Ok("en"), + Language::Korean => Ok("ko"), + Language::French => Ok("fr"), + Language::Spanish => Ok("es"), + Language::German => Ok("de"), + Language::Russian => Ok("ru"), + Language::Italian => Ok("it"), + Language::Portuguese => Ok("pt_pt"), + Language::Turkish => Ok("tr"), + Language::Arabic => Ok("ar"), + Language::Vietnamese => Ok("vi"), + Language::Thai => Ok("th"), + Language::Indonesian => Ok("id"), + Language::Malay => Ok("ms"), + Language::Hindi => Ok("hi"), + Language::Mongolian => Ok("mn_cy"), + } + } else { + return Ok(""); + } +} diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index b349735d45..d1c18ee537 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -4,16 +4,19 @@ mod cmd; mod config; mod hotkey; +mod lang_detect; mod screenshot; mod server; mod system_ocr; mod tray; mod updater; +mod webdav; mod window; use cmd::*; use config::*; use hotkey::*; +use lang_detect::*; use log::info; use once_cell::sync::OnceCell; use screenshot::screenshot; @@ -25,6 +28,7 @@ use tauri::Manager; use tauri_plugin_log::LogTarget; use tray::*; use updater::check_update; +use webdav::*; use window::config_window; use window::updater_window; @@ -111,7 +115,11 @@ fn main() { update_tray, updater_window, screenshot, - lang_detect + lang_detect, + backup_list, + get_backup, + put_backup, + delete_backup ]) .on_system_tray_event(tray_event_handler) .build(tauri::generate_context!()) diff --git a/src-tauri/src/webdav.rs b/src-tauri/src/webdav.rs new file mode 100644 index 0000000000..1e9c0f1451 --- /dev/null +++ b/src-tauri/src/webdav.rs @@ -0,0 +1,158 @@ +use log::error; +use reqwest_dav::{Auth, ClientBuilder, Depth}; + +#[tauri::command(async)] +pub async fn backup_list( + url: String, + username: String, + password: String, +) -> Result { + // build a client + let client = match ClientBuilder::new() + .set_host(url) + .set_auth(Auth::Basic(username, password)) + .build() + { + Ok(v) => v, + Err(e) => { + error!("Build WebDav Client Error: {}", e); + return Err(format!("Build WebDav Client Error: {}", e)); + } + }; + match client.mkcol("/pot-app").await { + Ok(()) => {} + Err(e) => { + error!("WebDav Mkcol Error: {}", e); + return Err(format!("WebDav Mkcol Error: {}", e)); + } + }; + let res = match client.list("pot-app", Depth::Number(1)).await { + Ok(v) => v, + Err(e) => { + error!("WebDav List Error: {}", e); + return Err(format!("WebDav List Error: {}", e)); + } + }; + match serde_json::to_string(&res) { + Ok(v) => Ok(v), + Err(e) => { + error!("WebDav List Json Error: {}", e); + Err(format!("WebDav List Json Error: {}", e)) + } + } +} + +#[tauri::command(async)] +pub async fn get_backup( + url: String, + username: String, + password: String, + name: String, +) -> Result { + // build a client + let client = match ClientBuilder::new() + .set_host(url) + .set_auth(Auth::Basic(username, password)) + .build() + { + Ok(v) => v, + Err(e) => { + error!("Build WebDav Client Error: {}", e); + return Err(format!("Build WebDav Client Error: {}", e)); + } + }; + match client.mkcol("/pot-app").await { + Ok(()) => {} + Err(e) => { + error!("WebDav Mkcol Error: {}", e); + return Err(format!("WebDav Mkcol Error: {}", e)); + } + }; + let res = match client.get(&format!("pot-app/{name}")).await { + Ok(v) => v, + Err(e) => { + error!("WebDav Get Error: {}", e); + return Err(format!("WebDav Get Error: {}", e)); + } + }; + match res.text().await { + Ok(v) => Ok(v), + Err(e) => { + error!("WebDav Get Text Error: {}", e); + Err(format!("WebDav Get Text Error: {}", e)) + } + } +} + +#[tauri::command(async)] +pub async fn put_backup( + url: String, + username: String, + password: String, + name: String, + body: String, +) -> Result<(), String> { + // build a client + let client = match ClientBuilder::new() + .set_host(url) + .set_auth(Auth::Basic(username, password)) + .build() + { + Ok(v) => v, + Err(e) => { + error!("Build WebDav Client Error: {}", e); + return Err(format!("Build WebDav Client Error: {}", e)); + } + }; + match client.mkcol("/pot-app").await { + Ok(()) => {} + Err(e) => { + error!("WebDav Mkcol Error: {}", e); + return Err(format!("WebDav Mkcol Error: {}", e)); + } + }; + + match client.put(&format!("pot-app/{name}"), body).await { + Ok(()) => return Ok(()), + Err(e) => { + error!("WebDav Put Error: {}", e); + return Err(format!("WebDav Put Error: {}", e)); + } + } +} + +#[tauri::command(async)] +pub async fn delete_backup( + url: String, + username: String, + password: String, + name: String, +) -> Result<(), String> { + // build a client + let client = match ClientBuilder::new() + .set_host(url) + .set_auth(Auth::Basic(username, password)) + .build() + { + Ok(v) => v, + Err(e) => { + error!("Build WebDav Client Error: {}", e); + return Err(format!("Build WebDav Client Error: {}", e)); + } + }; + match client.mkcol("/pot-app").await { + Ok(()) => {} + Err(e) => { + error!("WebDav Mkcol Error: {}", e); + return Err(format!("WebDav Mkcol Error: {}", e)); + } + }; + + match client.delete(&format!("pot-app/{name}")).await { + Ok(()) => return Ok(()), + Err(e) => { + error!("WebDav Delete Error: {}", e); + return Err(format!("WebDav Delete Error: {}", e)); + } + } +} diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 93360cdd47..7f21195171 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -43,6 +43,12 @@ "protocol": { "asset": true, "assetScope": ["$CACHE/**"] + }, + "fs": { + "all": false, + "scope": ["$APPCONFIG/**"], + "writeFile": true, + "readFile": true } }, "bundle": { diff --git a/src/i18n/locales/en_US.json b/src/i18n/locales/en_US.json index f7450d1977..a92f145c8f 100644 --- a/src/i18n/locales/en_US.json +++ b/src/i18n/locales/en_US.json @@ -123,6 +123,19 @@ "label": "History", "title": "History" }, + "backup": { + "label": "Backup", + "title": "Backup Setting", + "type": "Service Type", + "webdav": "WebDav", + "url": "WebDav Url", + "username": "UserName", + "password": "Password", + "backup": "Backup", + "show": "View Backup", + "load_success": "Load Backup Success", + "backup_success": "Backup Success" + }, "about": { "label": "About", "title": "About Application", diff --git a/src/i18n/locales/zh_CN.json b/src/i18n/locales/zh_CN.json index 5751ec7ff2..8246b2a8bf 100644 --- a/src/i18n/locales/zh_CN.json +++ b/src/i18n/locales/zh_CN.json @@ -122,6 +122,19 @@ "label": "历史记录", "title": "历史记录" }, + "backup": { + "label": "备份设置", + "title": "备份设置", + "type": "服务类型", + "webdav": "WebDav", + "url": "WebDav地址", + "username": "用户名", + "password": "密码", + "backup": "备份", + "show": "查看备份", + "load_success": "载入备份成功", + "backup_success": "备份成功" + }, "about": { "label": "关于应用", "title": "关于应用", diff --git a/src/services/translate/deepl/Config.jsx b/src/services/translate/deepl/Config.jsx index af695cf670..331fce579f 100644 --- a/src/services/translate/deepl/Config.jsx +++ b/src/services/translate/deepl/Config.jsx @@ -112,7 +112,6 @@ export function Config(props) { onClose(); }, (e) => { - console.log(e); setIsLoading(false); toast.error(t('config.service.test_failed') + e.toString(), { style: toastStyle }); } diff --git a/src/window/Config/components/SideBar/index.jsx b/src/window/Config/components/SideBar/index.jsx index a3f7a682d4..c92c6a178d 100644 --- a/src/window/Config/components/SideBar/index.jsx +++ b/src/window/Config/components/SideBar/index.jsx @@ -6,6 +6,7 @@ import { useTranslation } from 'react-i18next'; import { PiTextboxFill } from 'react-icons/pi'; import { MdKeyboardAlt } from 'react-icons/md'; import { MdExtension } from 'react-icons/md'; +import { AiFillCloud } from 'react-icons/ai'; // import { FaHistory } from 'react-icons/fa'; import { Button } from '@nextui-org/react'; import React from 'react'; @@ -93,6 +94,18 @@ export default function SideBar() { >
{t('config.history.label')}
*/} + + + + ); + })} + + )} + + + )} + + + ); +} diff --git a/src/window/Config/pages/Backup/index.jsx b/src/window/Config/pages/Backup/index.jsx new file mode 100644 index 0000000000..37a353d67a --- /dev/null +++ b/src/window/Config/pages/Backup/index.jsx @@ -0,0 +1,151 @@ +import { readTextFile, BaseDirectory } from '@tauri-apps/api/fs'; +import { DropdownTrigger } from '@nextui-org/react'; +import { useDisclosure } from '@nextui-org/react'; +import toast, { Toaster } from 'react-hot-toast'; +import { DropdownMenu } from '@nextui-org/react'; +import { DropdownItem } from '@nextui-org/react'; +import { useTranslation } from 'react-i18next'; +import { CardBody } from '@nextui-org/react'; +import { Dropdown } from '@nextui-org/react'; +import { Button } from '@nextui-org/react'; +import { Input } from '@nextui-org/react'; +import { Card } from '@nextui-org/react'; +import { invoke } from '@tauri-apps/api'; +import React, { useState } from 'react'; + +import { useConfig, useToastStyle } from '../../../../hooks'; +import WebDavModal from './WebDavModal'; + +export default function Backup() { + const [backupType, setBackupType] = useConfig('backup_type', 'webdav'); + const [davUserName, setDavUserName] = useConfig('webdav_username', ''); + const [davPassword, setDavPassword] = useConfig('webdav_password', ''); + const [davUrl, setDavUrl] = useConfig('webdav_url', ''); + const { + isOpen: isWebDavListOpen, + onOpen: onWebDavListOpen, + onOpenChange: onWebDavListOpenChange, + } = useDisclosure(); + const [uploading, setUploading] = useState(false); + const toastStyle = useToastStyle(); + const { t } = useTranslation(); + + return ( + + + +
+

{t('config.backup.type')}

+ {backupType !== null && ( + + + + + { + setBackupType(key); + }} + > + {t('config.backup.webdav')} + + + )} +
+
+
+

{t('config.backup.url')}

+ {davUrl !== null && ( + { + setDavUrl(v); + }} + className='max-w-[300px]' + /> + )} +
+
+

{t('config.backup.username')}

+ {davUserName !== null && ( + { + setDavUserName(v); + }} + className='max-w-[300px]' + /> + )} +
+
+

{t('config.backup.password')}

+ {davPassword !== null && ( + { + setDavPassword(v); + }} + className='max-w-[300px]' + /> + )} +
+
+ + +
+
+
+ +
+ ); +} diff --git a/src/window/Config/routes/index.jsx b/src/window/Config/routes/index.jsx index a78cebfa84..f36a68dac7 100644 --- a/src/window/Config/routes/index.jsx +++ b/src/window/Config/routes/index.jsx @@ -5,6 +5,7 @@ import Recognize from '../pages/Recognize'; import General from '../pages/General'; import Service from '../pages/Service'; import Hotkey from '../pages/Hotkey'; +import Backup from '../pages/Backup'; import About from '../pages/About'; const routes = [ @@ -32,6 +33,10 @@ const routes = [ path: '/history', element:
, }, + { + path: '/backup', + element: , + }, { path: '/about', element: ,