mirror of
https://github.com/VirtualHotBar/NetMount.git
synced 2026-06-20 02:57:24 +08:00
Merge branch 'pr/20' into dev-tauri2.0
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -25,3 +25,6 @@ dist-ssr
|
||||
|
||||
# bin
|
||||
src-tauri/res/bin
|
||||
|
||||
# TODO: .
|
||||
scripts
|
||||
|
||||
685
src-tauri/Cargo.lock
generated
685
src-tauri/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,16 +1,14 @@
|
||||
[package]
|
||||
name = "app"
|
||||
description = "NetMount"
|
||||
version = "1.0.6"
|
||||
version = "1.1.0"
|
||||
authors = ["VirtualHotBar"]
|
||||
license = ""
|
||||
license = "AGPL-3.0"
|
||||
repository = ""
|
||||
default-run = "app"
|
||||
edition = "2021"
|
||||
rust-version = "1.60"
|
||||
|
||||
windows_subsystem = "windows"
|
||||
|
||||
[package.metadata.windows]
|
||||
manifest = "app.manifest"
|
||||
|
||||
@@ -40,37 +38,21 @@ tauri = { version = "2.0.0-beta", features = [
|
||||
] }
|
||||
anyhow = "1"
|
||||
anyhow-tauri = "1"
|
||||
phf = { version = "0", features = ["macros", "serde"] }
|
||||
phf = { version = "0.11", features = ["macros", "serde"] }
|
||||
rand = "0.8"
|
||||
directories = "5.0.1"
|
||||
|
||||
reqwest = { version = "0.11", features = ["json", "stream"] }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
futures-util = "0.3"
|
||||
|
||||
sysinfo = "0.30.10"
|
||||
once_cell = "1.19.0"
|
||||
|
||||
lazy_static = "1.4.0"
|
||||
fslock = "0.2.1"
|
||||
|
||||
# ipc-channel = "0.18.0"
|
||||
rfd = "0.14.1"
|
||||
tauri-plugin-shell = "2.0.0-beta.7"
|
||||
tauri-plugin-os = "2.0.0-beta.6"
|
||||
tauri-plugin-fs = "2.0.0-beta.9"
|
||||
tauri-plugin-process = "2.0.0-beta.6"
|
||||
|
||||
raw-window-handle = "0.6"
|
||||
|
||||
# tauri-plugin-autostart = "2.0.0-beta.7"
|
||||
tauri-plugin-single-instance = "2.0.0-beta.9"
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winreg = "0.10.1"
|
||||
winapi = "0.3"
|
||||
widestring = "1.1"
|
||||
window-shadows = "0.2.2"
|
||||
|
||||
[target."cfg(not(any(target_os = \"android\", target_os = \"ios\")))".dependencies]
|
||||
tauri-plugin-single-instance = "2.0.0-beta.9"
|
||||
|
||||
[features]
|
||||
# this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled.
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use std::env::current_dir;
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use std::process::exit;
|
||||
@@ -15,11 +14,14 @@ struct ResBinUrls {
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
check_res_bin();
|
||||
compile_locale(&[
|
||||
("en", Path::new("locales/en.json")),
|
||||
("zh-cn", Path::new("locales/zh-cn.json")),
|
||||
("zh-hant", Path::new("locales/zh-hant.json")),
|
||||
])?;
|
||||
compile_locale(
|
||||
&[
|
||||
("en", Path::new("locales/en.json")),
|
||||
("zh-cn", Path::new("locales/zh-cn.json")),
|
||||
("zh-hant", Path::new("locales/zh-hant.json")),
|
||||
],
|
||||
"zh-cn",
|
||||
)?;
|
||||
tauri_build::try_build(Attributes::default())?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -29,9 +31,9 @@ fn escape(str: &str) -> String {
|
||||
format!("r{bound}\"{str}\"{bound}")
|
||||
}
|
||||
|
||||
fn compile_locale(locales: &[(&str, &Path)]) -> anyhow::Result<()> {
|
||||
fn compile_locale(locales: &[(&str, &Path)], default: &str) -> anyhow::Result<()> {
|
||||
let mut file =
|
||||
File::create(Path::new(&env::var("OUT_DIR").unwrap()).join(format!("codegen.rs")))?;
|
||||
File::create(Path::new(&env::var("OUT_DIR").unwrap()).join(format!("language.rs")))?;
|
||||
|
||||
write!(&mut file, "type Pack=phf::Map<&'static str,&'static str>;")?;
|
||||
let get_name = |name: &str| format!("LANG_{}", name.replace("-", "_").to_uppercase());
|
||||
@@ -48,11 +50,12 @@ fn compile_locale(locales: &[(&str, &Path)]) -> anyhow::Result<()> {
|
||||
}
|
||||
write!(
|
||||
&mut file,
|
||||
"fn get_lang(name:&'static str)->Pack{{match name{{{}}}}}",
|
||||
"fn get_lang(name:&str)->&'static Pack{{match name{{{}_=>&{}}}}}",
|
||||
locales
|
||||
.iter()
|
||||
.map(|(name, _)| format!("{}=>{}", escape(name), get_name(name)))
|
||||
.join(",")
|
||||
.map(|(name, _)| format!("{}=>&{},", escape(name), get_name(name)))
|
||||
.join(""),
|
||||
get_name(default)
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
"identifier": "migrated",
|
||||
"description": "permissions that were migrated from v1",
|
||||
"local": true,
|
||||
"windows": [
|
||||
"main"
|
||||
],
|
||||
"windows": ["main"],
|
||||
"permissions": [
|
||||
"path:default",
|
||||
"event:default",
|
||||
@@ -24,9 +22,7 @@
|
||||
"fs:allow-exists",
|
||||
{
|
||||
"identifier": "fs:scope",
|
||||
"allow": [
|
||||
"res/*"
|
||||
]
|
||||
"allow": ["res/*"]
|
||||
},
|
||||
"window:allow-create",
|
||||
"window:allow-center",
|
||||
@@ -61,13 +57,14 @@
|
||||
"window:allow-set-ignore-cursor-events",
|
||||
"window:allow-start-dragging",
|
||||
"webview:allow-print",
|
||||
"shell:allow-kill",
|
||||
{
|
||||
"identifier": "shell:allow-execute",
|
||||
"allow": [
|
||||
{
|
||||
"args": true,
|
||||
"cmd": "res/bin/aria2c",
|
||||
"name": "ria2c",
|
||||
"name": "aria2c",
|
||||
"sidecar": false
|
||||
},
|
||||
{
|
||||
@@ -142,7 +139,8 @@
|
||||
"sidecar": false
|
||||
}
|
||||
]
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"identifier": "shell:allow-open",
|
||||
"allow": [
|
||||
{
|
||||
@@ -198,4 +196,4 @@
|
||||
"process:default",
|
||||
"os:default"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"migrated":{"identifier":"migrated","description":"permissions that were migrated from v1","local":true,"windows":["main"],"permissions":["path:default","event:default","window:default","app:default","resources:default","menu:default","tray:default","fs:allow-read-file","fs:allow-write-file","fs:allow-read-dir","fs:allow-copy-file","fs:allow-mkdir","fs:allow-remove","fs:allow-remove","fs:allow-rename","fs:allow-exists",{"identifier":"fs:scope","allow":["res/*"]},"window:allow-create","window:allow-center","window:allow-request-user-attention","window:allow-set-resizable","window:allow-set-maximizable","window:allow-set-minimizable","window:allow-set-closable","window:allow-set-title","window:allow-maximize","window:allow-unmaximize","window:allow-minimize","window:allow-unminimize","window:allow-show","window:allow-hide","window:allow-close","window:allow-set-decorations","window:allow-set-always-on-top","window:allow-set-content-protected","window:allow-set-size","window:allow-set-min-size","window:allow-set-max-size","window:allow-set-position","window:allow-set-fullscreen","window:allow-set-focus","window:allow-set-icon","window:allow-set-skip-taskbar","window:allow-set-cursor-grab","window:allow-set-cursor-visible","window:allow-set-cursor-icon","window:allow-set-cursor-position","window:allow-set-ignore-cursor-events","window:allow-start-dragging","webview:allow-print",{"identifier":"shell:allow-execute","allow":[{"args":true,"cmd":"res/bin/aria2c","name":"ria2c","sidecar":false},{"args":true,"cmd":"res/bin/rclone","name":"rclone","sidecar":false},{"args":true,"cmd":"msiexec","name":"msiexec","sidecar":false},{"args":true,"cmd":"curl","name":"curl","sidecar":false},{"args":true,"cmd":"explorer","name":"explorer","sidecar":false},{"args":true,"cmd":"res/bin/alist/alist","name":"alist","sidecar":false}]},{"identifier":"shell:allow-spawn","allow":[{"args":true,"cmd":"res/bin/aria2c","name":"ria2c","sidecar":false},{"args":true,"cmd":"res/bin/rclone","name":"rclone","sidecar":false},{"args":true,"cmd":"msiexec","name":"msiexec","sidecar":false},{"args":true,"cmd":"curl","name":"curl","sidecar":false},{"args":true,"cmd":"explorer","name":"explorer","sidecar":false},{"args":true,"cmd":"res/bin/alist/alist","name":"alist","sidecar":false}]},{"identifier":"shell:allow-open","allow":[{"args":true,"cmd":"res/bin/aria2c","name":"ria2c","sidecar":false},{"args":true,"cmd":"res/bin/rclone","name":"rclone","sidecar":false},{"args":true,"cmd":"msiexec","name":"msiexec","sidecar":false},{"args":true,"cmd":"curl","name":"curl","sidecar":false},{"args":true,"cmd":"explorer","name":"explorer","sidecar":false},{"args":true,"cmd":"res/bin/alist/alist","name":"alist","sidecar":false}]},"os:allow-platform","os:allow-version","os:allow-os-type","os:allow-family","os:allow-arch","os:allow-exe-extension","os:allow-locale","os:allow-hostname","process:allow-restart","process:allow-exit","fs:default","shell:default","process:default","os:default"]}}
|
||||
{"migrated":{"identifier":"migrated","description":"permissions that were migrated from v1","local":true,"windows":["main"],"permissions":["path:default","event:default","window:default","app:default","resources:default","menu:default","tray:default","fs:allow-read-file","fs:allow-write-file","fs:allow-read-dir","fs:allow-copy-file","fs:allow-mkdir","fs:allow-remove","fs:allow-remove","fs:allow-rename","fs:allow-exists",{"identifier":"fs:scope","allow":["res/*"]},"window:allow-create","window:allow-center","window:allow-request-user-attention","window:allow-set-resizable","window:allow-set-maximizable","window:allow-set-minimizable","window:allow-set-closable","window:allow-set-title","window:allow-maximize","window:allow-unmaximize","window:allow-minimize","window:allow-unminimize","window:allow-show","window:allow-hide","window:allow-close","window:allow-set-decorations","window:allow-set-always-on-top","window:allow-set-content-protected","window:allow-set-size","window:allow-set-min-size","window:allow-set-max-size","window:allow-set-position","window:allow-set-fullscreen","window:allow-set-focus","window:allow-set-icon","window:allow-set-skip-taskbar","window:allow-set-cursor-grab","window:allow-set-cursor-visible","window:allow-set-cursor-icon","window:allow-set-cursor-position","window:allow-set-ignore-cursor-events","window:allow-start-dragging","webview:allow-print","shell:allow-kill",{"identifier":"shell:allow-execute","allow":[{"args":true,"cmd":"res/bin/aria2c","name":"aria2c","sidecar":false},{"args":true,"cmd":"res/bin/rclone","name":"rclone","sidecar":false},{"args":true,"cmd":"msiexec","name":"msiexec","sidecar":false},{"args":true,"cmd":"curl","name":"curl","sidecar":false},{"args":true,"cmd":"explorer","name":"explorer","sidecar":false},{"args":true,"cmd":"res/bin/alist/alist","name":"alist","sidecar":false}]},{"identifier":"shell:allow-spawn","allow":[{"args":true,"cmd":"res/bin/aria2c","name":"ria2c","sidecar":false},{"args":true,"cmd":"res/bin/rclone","name":"rclone","sidecar":false},{"args":true,"cmd":"msiexec","name":"msiexec","sidecar":false},{"args":true,"cmd":"curl","name":"curl","sidecar":false},{"args":true,"cmd":"explorer","name":"explorer","sidecar":false},{"args":true,"cmd":"res/bin/alist/alist","name":"alist","sidecar":false}]},{"identifier":"shell:allow-open","allow":[{"args":true,"cmd":"res/bin/aria2c","name":"ria2c","sidecar":false},{"args":true,"cmd":"res/bin/rclone","name":"rclone","sidecar":false},{"args":true,"cmd":"msiexec","name":"msiexec","sidecar":false},{"args":true,"cmd":"curl","name":"curl","sidecar":false},{"args":true,"cmd":"explorer","name":"explorer","sidecar":false},{"args":true,"cmd":"res/bin/alist/alist","name":"alist","sidecar":false}]},"os:allow-platform","os:allow-version","os:allow-os-type","os:allow-family","os:allow-arch","os:allow-exe-extension","os:allow-locale","os:allow-hostname","process:allow-restart","process:allow-exit","fs:default","shell:default","process:default","os:default"]}}
|
||||
@@ -1,34 +1,27 @@
|
||||
use std::sync::RwLock;
|
||||
|
||||
use rand::distributions::DistString as _;
|
||||
|
||||
use crate::State;
|
||||
|
||||
fn random_str(len: usize) -> String {
|
||||
rand::distributions::Alphanumeric.sample_string(&mut rand::thread_rng(), len)
|
||||
}
|
||||
|
||||
pub struct ConfigState(pub RwLock<Config>);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Config(pub serde_json::Value);
|
||||
|
||||
impl Config {
|
||||
pub fn get(&self, key: String) -> Option<serde_json::Value> {
|
||||
let parts = key.split(".");
|
||||
Some(parts.fold(self.0.clone(), |value, part| value.get(part).unwrap().clone()))
|
||||
}
|
||||
}
|
||||
impl State for Config {}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self(serde_json::json!({
|
||||
"mount": { "lists": [] },
|
||||
"task": [],
|
||||
"api": { "url": "https://api.hotpe.top/API/NetMount" },
|
||||
"settings": { "themeMode": "auto", "startHide": false },
|
||||
"framework": {
|
||||
"rclone": { "user": random_str(32), "password": random_str(128) },
|
||||
"alist": { "user": "admin", "password": random_str(16) }
|
||||
}
|
||||
"mount": { "lists": [] },
|
||||
"task": [],
|
||||
"api": { "url": "https://api.hotpe.top/API/NetMount" },
|
||||
"settings": { "themeMode": "auto", "startHide": false },
|
||||
"framework": {
|
||||
"rclone": { "user": random_str(32), "password": random_str(128) },
|
||||
"alist": { "user": "admin", "password": random_str(16) }
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
29
src-tauri/src/fs.rs
Normal file
29
src-tauri/src/fs.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use tauri::Manager as _;
|
||||
|
||||
use crate::Runtime;
|
||||
|
||||
fn resolve_path(app: &tauri::AppHandle<Runtime>, path: &str) -> PathBuf {
|
||||
if path.starts_with("~") {
|
||||
app.path().home_dir().unwrap().join(&path[1..]) // 跳过波浪线
|
||||
} else {
|
||||
Path::new(path).to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn fs_exist_dir(app: tauri::AppHandle<Runtime>, path: &str) -> anyhow_tauri::TAResult<bool> {
|
||||
let path = resolve_path(&app, path);
|
||||
let exists = std::fs::metadata(path)
|
||||
.map_err(anyhow::Error::from)?
|
||||
.is_dir();
|
||||
Ok(exists)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn fs_make_dir(app: tauri::AppHandle<Runtime>, path: &str) -> anyhow_tauri::TAResult<()> {
|
||||
let path = resolve_path(&app, path);
|
||||
std::fs::create_dir_all(path).map_err(anyhow::Error::from)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,30 +1,18 @@
|
||||
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
use config::Config;
|
||||
use config::ConfigState;
|
||||
use locale::Locale;
|
||||
use locale::LocaleState;
|
||||
use serde_json::{to_string_pretty, Value};
|
||||
use std::fs::File;
|
||||
use std::ops::Deref;
|
||||
use std::sync::RwLock;
|
||||
use tray::Tray;
|
||||
use tray::TrayState;
|
||||
//use tauri::AppHandle;
|
||||
use std::env;
|
||||
//use std::error::Error;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::{env, fs::File, ops::Deref, path::Path, sync::RwLock};
|
||||
|
||||
//use std::io::Read;
|
||||
//use std::path::Path;
|
||||
use tauri::Manager as _;
|
||||
use config::Config;
|
||||
use fs::{fs_exist_dir, fs_make_dir};
|
||||
use locale::Locale;
|
||||
use tray::Tray;
|
||||
|
||||
mod autostart;
|
||||
mod config;
|
||||
pub mod locale;
|
||||
mod localized;
|
||||
mod fs;
|
||||
mod locale;
|
||||
mod tray;
|
||||
mod utils;
|
||||
|
||||
@@ -34,7 +22,6 @@ use crate::utils::download_with_progress;
|
||||
// use crate::utils::ensure_single_instance;
|
||||
#[cfg(target_os = "windows")]
|
||||
use crate::utils::find_first_available_drive_letter;
|
||||
use crate::utils::get_home_dir;
|
||||
#[cfg(target_os = "windows")]
|
||||
use crate::utils::is_winfsp_installed;
|
||||
#[cfg(target_os = "windows")]
|
||||
@@ -42,59 +29,61 @@ use crate::utils::set_window_shadow;
|
||||
|
||||
pub(crate) type Runtime = tauri::Wry;
|
||||
|
||||
pub trait State: Send + Sync + 'static {}
|
||||
pub struct StateWrapper<T: State>(RwLock<T>);
|
||||
|
||||
pub trait AppExt {
|
||||
fn main_window(&self) -> tauri::WebviewWindow<Runtime>;
|
||||
fn app_locale(&self) -> &Locale;
|
||||
fn app_config(&self) -> &Config;
|
||||
fn app_main_window(&self) -> tauri::WebviewWindow<Runtime>;
|
||||
fn with_app_state<T: State, R>(&self, closure: impl FnOnce(&T) -> R) -> R;
|
||||
fn set_app_state<T: State>(&self, state: T);
|
||||
fn update_app_config(&self) -> anyhow::Result<()>;
|
||||
fn write_app_config(&self, config: Config) -> anyhow::Result<()>;
|
||||
fn app_data_dir(&self) -> PathBuf;
|
||||
fn app_config_file(&self) -> PathBuf;
|
||||
fn quit(&self);
|
||||
fn app_quit(&self);
|
||||
fn app_restart(&self);
|
||||
}
|
||||
|
||||
impl<M: tauri::Manager<Runtime>> AppExt for M {
|
||||
fn main_window(&self) -> tauri::WebviewWindow {
|
||||
fn app_main_window(&self) -> tauri::WebviewWindow {
|
||||
self.get_webview_window("main").unwrap()
|
||||
}
|
||||
|
||||
fn app_locale(&self) -> Locale {
|
||||
self.state::<LocaleState>()
|
||||
.deref()
|
||||
.0
|
||||
.read()
|
||||
.unwrap()
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
fn with_app_state<T: State, R>(&self, closure: impl FnOnce(&T) -> R) -> R {
|
||||
let wrapper = self.state::<StateWrapper<T>>();
|
||||
let state = wrapper.deref().0.read().unwrap();
|
||||
closure(state.deref())
|
||||
}
|
||||
|
||||
fn app_config(&self) -> &Config {
|
||||
self.state::<ConfigState>()
|
||||
.deref()
|
||||
.0
|
||||
.read()
|
||||
.unwrap()
|
||||
.deref()
|
||||
fn set_app_state<T: State>(&self, state: T) {
|
||||
if let Some(wrapper) = self.try_state::<StateWrapper<T>>() {
|
||||
*wrapper.deref().0.write().unwrap() = state;
|
||||
} else {
|
||||
self.manage(StateWrapper(RwLock::new(state)));
|
||||
}
|
||||
}
|
||||
|
||||
fn update_app_config(&self) -> anyhow::Result<()> {
|
||||
let config = self.app_config();
|
||||
|
||||
let current_locale = tauri_plugin_os::locale().unwrap_or_else(|| "C".into());
|
||||
*self.state::<LocaleState>().deref().0.write().unwrap() = Some(Locale::new(
|
||||
config.0["settings"]
|
||||
.get("language")
|
||||
.map(|item| item.as_str().unwrap())
|
||||
.unwrap_or_else(|| ¤t_locale),
|
||||
));
|
||||
*self.state::<TrayState>().deref().0.write().unwrap() = Some(Tray::new(self.app_handle())?);
|
||||
Ok(())
|
||||
self.with_app_state::<Config, _>(|config| {
|
||||
let current_locale = tauri_plugin_os::locale().unwrap_or_else(|| "C".into());
|
||||
self.set_app_state(Locale::new(
|
||||
config.0["settings"]
|
||||
.get("language")
|
||||
.map(|item| item.as_str().unwrap())
|
||||
.unwrap_or_else(|| ¤t_locale),
|
||||
));
|
||||
self.set_app_state(Tray::new(self.app_handle())?);
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn write_app_config(&self, config: Config) -> anyhow::Result<()> {
|
||||
*self.state::<ConfigState>().deref().0.write().unwrap() = config;
|
||||
let mut file = File::create(self.app_config_file())?;
|
||||
serde_json::to_writer_pretty(file, &self.app_config().0)?;
|
||||
self.set_app_state(config);
|
||||
let file = File::create(self.app_config_file())?;
|
||||
serde_json::to_writer_pretty(
|
||||
file,
|
||||
&self.with_app_state::<Config, _>(|config| config.0.clone()),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -106,9 +95,13 @@ impl<M: tauri::Manager<Runtime>> AppExt for M {
|
||||
self.app_data_dir().join("config.json")
|
||||
}
|
||||
|
||||
fn quit(&self) {
|
||||
fn app_quit(&self) {
|
||||
self.app_handle().exit(0)
|
||||
}
|
||||
|
||||
fn app_restart(&self) {
|
||||
self.app_handle().restart()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait WindowExt {
|
||||
@@ -116,7 +109,7 @@ pub trait WindowExt {
|
||||
fn toggle_visibility(&self, preferred_show: Option<bool>) -> anyhow::Result<()>;
|
||||
}
|
||||
|
||||
impl WindowExt for tauri::WebviewWindow {
|
||||
impl WindowExt for tauri::WebviewWindow<Runtime> {
|
||||
fn toggle_devtools(&self, preferred_open: Option<bool>) {
|
||||
let open = preferred_open.unwrap_or_else(|| !self.is_devtools_open());
|
||||
if open {
|
||||
@@ -138,17 +131,8 @@ impl WindowExt for tauri::WebviewWindow {
|
||||
}
|
||||
}
|
||||
|
||||
const USER_DATA_PATH: &str = ".netmount";
|
||||
const CONFIG_FILE: &str = "config.json";
|
||||
|
||||
pub fn init() -> anyhow::Result<()> {
|
||||
let home_dir = get_home_dir();
|
||||
|
||||
if home_dir.join(USER_DATA_PATH).exists() {
|
||||
fs::create_dir_all(home_dir.join(USER_DATA_PATH)).unwrap()
|
||||
}
|
||||
|
||||
//设置运行目录
|
||||
// 设置运行目录
|
||||
let exe_dir = env::current_exe()
|
||||
.expect("无法获取当前可执行文件路径")
|
||||
.parent()
|
||||
@@ -188,48 +172,38 @@ pub fn init() -> anyhow::Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
//run_command("ls").expect("运行ls命令失败");
|
||||
//run_command("dir").expect("运行ls命令失败");
|
||||
|
||||
// 根据不同的操作系统配置Tauri Builder
|
||||
let builder = tauri::Builder::default()
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_os::init())
|
||||
.plugin(tauri_plugin_process::init())
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.plugin(tauri_plugin_fs::init())
|
||||
.plugin(tauri_plugin_single_instance::init(|app, _, _| {
|
||||
app.main_window().toggle_visibility(Some(true));
|
||||
app.app_main_window().toggle_visibility(Some(true)).ok();
|
||||
}))
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
// TODO: alternatives?
|
||||
// set_localized,
|
||||
read_config_file,
|
||||
write_config_file,
|
||||
toggle_devtools,
|
||||
get_config,
|
||||
update_config,
|
||||
get_language_pack,
|
||||
download_file,
|
||||
get_autostart_state,
|
||||
set_autostart_state,
|
||||
get_winfsp_install_state,
|
||||
get_available_drive_letter,
|
||||
// TODO: alternatives?
|
||||
// set_devtools_state,
|
||||
get_available_ports,
|
||||
fs_exist_dir,
|
||||
fs_make_dir,
|
||||
restart_self,
|
||||
get_available_ports
|
||||
restart_self
|
||||
])
|
||||
.setup(|app| {
|
||||
app.manage(ConfigState(RwLock::new(
|
||||
if let Some(file) = File::open(app.app_config_file()).ok() {
|
||||
Config(serde_json::from_reader(file)?)
|
||||
} else {
|
||||
Config::default()
|
||||
},
|
||||
)));
|
||||
app.manage(LocaleState(RwLock::new(None)));
|
||||
app.manage(TrayState(RwLock::new(None)));
|
||||
if let Some(file) = File::open(app.app_config_file()).ok() {
|
||||
app.set_app_state(Config(serde_json::from_reader(file)?))
|
||||
} else {
|
||||
app.write_app_config(Config::default())?
|
||||
};
|
||||
app.update_app_config()?;
|
||||
#[cfg(debug_assertions)]
|
||||
app.main_window().toggle_devtools(Some(true));
|
||||
app.app_main_window().toggle_devtools(Some(true));
|
||||
Ok(())
|
||||
})
|
||||
.run(tauri::generate_context!())?;
|
||||
@@ -237,92 +211,40 @@ pub fn init() -> anyhow::Result<()> {
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn toggle_devtools(window: tauri::WebviewWindow, preferred_open: Option<bool>) {
|
||||
fn toggle_devtools(window: tauri::WebviewWindow<Runtime>, preferred_open: Option<bool>) {
|
||||
window.toggle_devtools(preferred_open)
|
||||
}
|
||||
|
||||
use std::io::ErrorKind;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[tauri::command]
|
||||
fn fs_exist_dir(path: &str) -> bool {
|
||||
let home_dir = get_home_dir();
|
||||
// 替换路径中的波浪线 (~) 为用home目录
|
||||
let mut resolved_path = PathBuf::new();
|
||||
if path.starts_with("~") {
|
||||
resolved_path.push(home_dir);
|
||||
resolved_path.push(&path[1..]); // 跳过波浪线
|
||||
} else {
|
||||
resolved_path.push(path);
|
||||
}
|
||||
is_directory(path)
|
||||
fn get_language_pack(app: tauri::AppHandle<Runtime>) -> serde_json::Value {
|
||||
serde_json::Value::Object(serde_json::value::Map::from_iter(
|
||||
app.with_app_state::<Locale, _>(|locale| locale.0)
|
||||
.entries()
|
||||
.map(|(&key, &value)| (key.into(), serde_json::Value::String(value.into()))),
|
||||
))
|
||||
.into()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn fs_make_dir(path: &str) -> bool {
|
||||
let home_dir = get_home_dir();
|
||||
// 替换路径中的波浪线 (~) 为用home目录
|
||||
let mut resolved_path = PathBuf::new();
|
||||
if path.starts_with("~") {
|
||||
resolved_path.push(home_dir);
|
||||
resolved_path.push(&path[1..]); // 跳过波浪线
|
||||
} else {
|
||||
resolved_path.push(path);
|
||||
}
|
||||
|
||||
// 创建所有必要的父目录
|
||||
if let Some(parent) = resolved_path.parent() {
|
||||
if let Err(e) = fs::create_dir_all(parent) {
|
||||
match e.kind() {
|
||||
ErrorKind::NotFound => (),
|
||||
_ => {
|
||||
eprintln!("Error preparing directory structure: {}", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 尝试创建目标目录
|
||||
match fs::create_dir(&resolved_path) {
|
||||
Ok(_) => true,
|
||||
Err(e) => {
|
||||
eprintln!("Error creating directory: {}", e);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_directory(path: &str) -> bool {
|
||||
match fs::metadata(path) {
|
||||
Ok(metadata) => metadata.is_dir(),
|
||||
Err(_) => false,
|
||||
}
|
||||
fn get_config(app: tauri::AppHandle<Runtime>) -> serde_json::Value {
|
||||
app.with_app_state::<Config, _>(|config| config.0.clone())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn restart_self() {
|
||||
utils::restart_self()
|
||||
}
|
||||
|
||||
use std::error::Error;
|
||||
use std::process::Command;
|
||||
fn run_command(cmd: &str) -> Result<(), Box<dyn Error>> {
|
||||
let cmd_str = if cfg!(target_os = "windows") {
|
||||
format!("{}", cmd.replace("/", "\\"))
|
||||
} else {
|
||||
format!("{}", cmd)
|
||||
};
|
||||
|
||||
let child = if cfg!(target_os = "windows") {
|
||||
Command::new("cmd").arg("/c").arg(cmd_str).spawn()?
|
||||
} else {
|
||||
Command::new("sh").arg("-c").arg(cmd_str).spawn()?
|
||||
};
|
||||
child.wait_with_output()?;
|
||||
fn update_config(
|
||||
app: tauri::AppHandle<Runtime>,
|
||||
data: serde_json::Value,
|
||||
) -> anyhow_tauri::TAResult<()> {
|
||||
app.write_app_config(Config(data))?;
|
||||
app.update_app_config()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn restart_self(app: tauri::AppHandle<Runtime>) {
|
||||
app.restart()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn get_winfsp_install_state() -> Result<bool, usize> {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
@@ -375,56 +297,6 @@ fn get_available_drive_letter() -> Result<String, String> {
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn get_config(app: tauri::AppHandle) -> Config {
|
||||
app.app_config().clone()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn update_config(app: tauri::AppHandle, data: Value) -> anyhow_tauri::TAResult<()> {
|
||||
app.write_app_config(Config(data));
|
||||
app.update_app_config()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn read_config_file(path: Option<&str>) -> Result<Value, String> {
|
||||
let path = path.unwrap_or(CONFIG_FILE);
|
||||
let home_dir = get_home_dir();
|
||||
let content_result = fs::read_to_string(if path == CONFIG_FILE {
|
||||
home_dir.join(USER_DATA_PATH).join(path)
|
||||
} else {
|
||||
PathBuf::from(path)
|
||||
});
|
||||
match content_result {
|
||||
Ok(content) => match serde_json::from_str(&content) {
|
||||
Ok(config) => Ok(config),
|
||||
Err(json_error) => Err(format!("Failed to parse JSON from file: {}", json_error)),
|
||||
},
|
||||
Err(io_error) => Err(format!("Failed to read file: {}", io_error)),
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn write_config_file(config_data: Value, path: Option<&str>) -> Result<(), String> {
|
||||
let path = path.unwrap_or(CONFIG_FILE);
|
||||
let home_dir = get_home_dir();
|
||||
let pretty_config = to_string_pretty(&config_data)
|
||||
.map_err(|json_error| format!("Failed to serialize JSON: {}", json_error))?;
|
||||
|
||||
fs::write(
|
||||
if path == CONFIG_FILE {
|
||||
home_dir.join(USER_DATA_PATH).join(path)
|
||||
} else {
|
||||
PathBuf::from(path)
|
||||
},
|
||||
pretty_config,
|
||||
)
|
||||
.map_err(|io_error| format!("Failed to write file: {}", io_error))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn get_available_ports(count: usize) -> Vec<u16> {
|
||||
return utils::get_available_ports(count);
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
use std::sync::RwLock;
|
||||
use crate::State;
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/codegen.rs"));
|
||||
include!(concat!(env!("OUT_DIR"), "/language.rs"));
|
||||
|
||||
pub struct LocaleState(pub RwLock<Option<Locale>>);
|
||||
|
||||
pub struct Locale(pub Pack);
|
||||
pub struct Locale(pub &'static Pack);
|
||||
|
||||
impl Locale {
|
||||
pub fn new(name: &str) -> Self {
|
||||
@@ -15,3 +13,5 @@ impl Locale {
|
||||
self.0.get(id).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl State for Locale {}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
// use lazy_static::lazy_static;
|
||||
// use serde_json::{Map, Value};
|
||||
// use std::sync::{Mutex, RwLock};
|
||||
// use tauri::Manager;
|
||||
|
||||
// use crate::tray::tray;
|
||||
|
||||
// lazy_static! {
|
||||
// pub static ref LOCALIZATION: RwLock<Map<String, Value>> = RwLock::new(Map::new());
|
||||
// }
|
||||
|
||||
// pub fn get_localized_text(key: &str) -> String {
|
||||
// let lock = LANGUAGE_PACK.lock().unwrap();
|
||||
// match lock.get(key) {
|
||||
// Some(value) => value.as_str().unwrap_or(key).to_owned(),
|
||||
// None => key.to_owned(),
|
||||
// }
|
||||
// }
|
||||
|
||||
// #[tauri::command]
|
||||
// pub fn set_localized(app: tauri::AppHandle, localized_data: Value) -> Result<(), String> {
|
||||
// let map=localized_data.as_object().map_err(||"Provided localized data is not a JSON object.")?;
|
||||
// let language_pack_map: Map<String, Value> = map.clone().into();
|
||||
// let mut pack = ;
|
||||
// *LANGUAGE_PACK.lock().unwrap() = language_pack_map;
|
||||
// app.manage(tray(app))
|
||||
// }
|
||||
|
||||
// Ok(())
|
||||
// }
|
||||
@@ -1,5 +1,5 @@
|
||||
use app::init;
|
||||
|
||||
fn main(){
|
||||
init()
|
||||
fn main() {
|
||||
init().unwrap();
|
||||
}
|
||||
|
||||
@@ -1,39 +1,56 @@
|
||||
use std::sync::RwLock;
|
||||
use crate::{AppExt, Locale, Runtime, State, WindowExt};
|
||||
|
||||
use crate::{AppExt, Runtime};
|
||||
|
||||
pub struct TrayState(pub RwLock<Option<Tray>>);
|
||||
|
||||
pub struct Tray(tauri::tray::TrayIcon<Runtime>);
|
||||
pub struct Tray(pub tauri::tray::TrayIcon<Runtime>);
|
||||
|
||||
impl Tray {
|
||||
pub fn new(app: &tauri::AppHandle<Runtime>) -> anyhow::Result<Self> {
|
||||
let build_item = |id: &str, text: &str| {
|
||||
tauri::menu::MenuItemBuilder::with_id(id, text)
|
||||
.build(app)
|
||||
.unwrap()
|
||||
};
|
||||
let menu = tauri::menu::MenuBuilder::new(app)
|
||||
.items(&[
|
||||
&build_item("show", app.app_locale().get("show&hide")),
|
||||
&build_item("quit", app.app_locale().get("quit")),
|
||||
])
|
||||
.build()
|
||||
.unwrap();
|
||||
let tray = tauri::tray::TrayIconBuilder::new()
|
||||
.menu(&menu)
|
||||
.on_tray_icon_event(|app, event| {
|
||||
// window.show().unwrap();
|
||||
// window.set_focus().unwrap()
|
||||
})
|
||||
.on_menu_event(|app, event| {
|
||||
match event.id.as_ref() {
|
||||
"quit" => app.exit(0),
|
||||
// "hide&show" => hide_or_show(),
|
||||
app.with_app_state::<Locale, _>(|locale| {
|
||||
let build_item = |id: &str, text: &str| {
|
||||
tauri::menu::MenuItemBuilder::with_id(id, text)
|
||||
.build(app)
|
||||
.unwrap()
|
||||
};
|
||||
let menu = tauri::menu::MenuBuilder::new(app)
|
||||
.items(&[
|
||||
&build_item("show", locale.get("tray_show")),
|
||||
&build_item("quit", locale.get("quit")),
|
||||
])
|
||||
.build()
|
||||
.unwrap();
|
||||
let tray = tauri::tray::TrayIconBuilder::new()
|
||||
.menu(&menu)
|
||||
.on_tray_icon_event(|icon, event| match event {
|
||||
tauri::tray::TrayIconEvent::Click {
|
||||
id: _,
|
||||
position: _,
|
||||
rect: _,
|
||||
button,
|
||||
button_state,
|
||||
} => {
|
||||
if button == tauri::tray::MouseButton::Left
|
||||
&& button_state == tauri::tray::MouseButtonState::Up
|
||||
{
|
||||
icon.app_handle()
|
||||
.app_main_window()
|
||||
.toggle_visibility(None)
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
})
|
||||
.build(app)?;
|
||||
Ok(Self(tray))
|
||||
})
|
||||
.on_menu_event(|app, event| match event.id.as_ref() {
|
||||
"show" => {
|
||||
app.app_main_window().toggle_visibility(Some(true)).ok();
|
||||
}
|
||||
"quit" => {
|
||||
app.app_quit();
|
||||
}
|
||||
_ => {}
|
||||
})
|
||||
.build(app)?;
|
||||
Ok(Self(tray))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl State for Tray {}
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
#[cfg(target_os = "windows")]
|
||||
use tauri::{Manager, Runtime};
|
||||
#[cfg(target_os = "windows")]
|
||||
use window_shadows::set_shadow;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
use std::error::Error;
|
||||
#[cfg(target_os = "windows")]
|
||||
use std::fs;
|
||||
use std::io::{self, Write};
|
||||
//use tauri::AppHandle;
|
||||
|
||||
|
||||
|
||||
pub fn get_available_ports(count: usize) -> Vec<u16> {
|
||||
use std::net::TcpListener;
|
||||
@@ -23,28 +18,6 @@ pub fn get_available_ports(count: usize) -> Vec<u16> {
|
||||
ports
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn set_window_shadow<R: Runtime>(app: &tauri::App<R>) {
|
||||
{
|
||||
use raw_window_handle::HasRawWindowHandle;
|
||||
fn set_shadow<W: HasRawWindowHandle>(_window: &W, enabled: bool) -> Result<(), Box<dyn Error>> {
|
||||
set_shadow(_window, true).expect("Unsupported platform!");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
let window_map = app.get_webview_window("main").unwrap().webview_windows();
|
||||
|
||||
// 假设webview_windows返回了一个包含窗口名(String)和窗口实例(WebviewWindow)的映射
|
||||
// 我们需要获取映射中的值(即WebviewWindow实例)
|
||||
if let Some(webview_window) = window_map.values().next() {
|
||||
|
||||
} else {
|
||||
eprintln!("No webview window found.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[tauri::command]
|
||||
pub fn find_first_available_drive_letter() -> Result<Option<String>, io::Error> {
|
||||
@@ -141,123 +114,3 @@ pub fn is_winfsp_installed() -> Result<bool, Box<dyn Error>> {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_home_dir() -> std::path::PathBuf {
|
||||
use directories::UserDirs;
|
||||
let user_dirs = UserDirs::new().expect("Failed to get user dirs");
|
||||
user_dirs.home_dir().to_path_buf()
|
||||
}
|
||||
|
||||
pub fn restart_self() {
|
||||
// 重启自身
|
||||
use std::ffi::OsString;
|
||||
use std::process::Command;
|
||||
|
||||
let args: Vec<String> = std::env::args().skip(1).collect();
|
||||
|
||||
let os_args: Vec<OsString> = args.into_iter().map(OsString::from).collect();
|
||||
|
||||
Command::new(std::env::args().next().unwrap())
|
||||
.args(os_args)
|
||||
.spawn()
|
||||
.expect("Failed to restart the process");
|
||||
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
// pub fn ensure_single_instance(user_data_path: &str) {
|
||||
// fn message_dialog() {
|
||||
// use rfd::MessageDialog;
|
||||
|
||||
// MessageDialog::new()
|
||||
// .set_title("Warning")
|
||||
// .set_description("Process already exists!")
|
||||
// .show();
|
||||
// }
|
||||
|
||||
// let home_dir = get_home_dir();
|
||||
|
||||
// let _ = &home_dir.join(user_data_path);
|
||||
|
||||
// /* //文件锁
|
||||
// use fslock::LockFile;
|
||||
// let pid_path = home_dir.join(user_data_path).join("NetMount.lock");
|
||||
// // 打开pid文件,没有则自动创建
|
||||
// let mut pid_lock = LockFile::open(&pid_path.clone().into_os_string()).unwrap();
|
||||
// // 非阻塞的锁文件
|
||||
// if !pid_lock.try_lock_with_pid().unwrap() {
|
||||
// message_dialog();
|
||||
// panic!("An instance of this application is already running, exiting now.");
|
||||
// // 如果文件已经被锁,则退出进程
|
||||
// } */
|
||||
// // 进程名
|
||||
// use std::sync::Mutex;
|
||||
|
||||
// use once_cell::sync::Lazy;
|
||||
// use std::collections::HashSet;
|
||||
// use sysinfo::{Pid, System};
|
||||
// let current_pid = sysinfo::get_current_pid().expect("Failed to get current PID");
|
||||
// let current_proc_name = std::env::args().next().unwrap_or_default();
|
||||
// let mut system = System::new_all();
|
||||
// system.refresh_all();
|
||||
|
||||
// static EXISTING_PIDS: Lazy<Mutex<HashSet<Pid>>> = Lazy::new(|| Mutex::new(HashSet::new()));
|
||||
|
||||
// {
|
||||
// let mut existing_pids = EXISTING_PIDS.lock().expect("Failed to lock PID set");
|
||||
|
||||
// for (pid, proc_) in system.processes() {
|
||||
// if proc_.name() == current_proc_name && *pid != current_pid {
|
||||
// existing_pids.insert(*pid);
|
||||
// }
|
||||
// }
|
||||
|
||||
// if !existing_pids.is_empty() {
|
||||
// message_dialog();
|
||||
// panic!(
|
||||
// "An instance of this application is already running (PIDs: {:?}), exiting now.",
|
||||
// *existing_pids
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
// #[cfg(target_os = "windows")]
|
||||
// {
|
||||
// extern crate winapi;
|
||||
// extern crate widestring;
|
||||
// use widestring::U16CString;
|
||||
// //use winapi::shared::ntdef::NULL;
|
||||
// use winapi::shared::winerror::ERROR_ALREADY_EXISTS;
|
||||
// use winapi::um::errhandlingapi::GetLastError;
|
||||
// use winapi::um::synchapi::CreateMutexW;
|
||||
// // 定义互斥体名称
|
||||
// let mutex_name = U16CString::from_str("NetMount").expect("Failed to create U16CString");
|
||||
|
||||
// // 创建互斥体
|
||||
// unsafe {
|
||||
// let handle = CreateMutexW(
|
||||
// std::ptr::null_mut(),
|
||||
// winapi::shared::minwindef::FALSE,
|
||||
// mutex_name.as_ptr(),
|
||||
// );
|
||||
|
||||
// // 检查互斥体是否已经创建
|
||||
// if !handle.is_null() && GetLastError() == ERROR_ALREADY_EXISTS {
|
||||
// // 有效句柄,但是互斥体已存在
|
||||
// message_dialog();
|
||||
// panic!("Another instance of the application is already running.");
|
||||
// } else if !handle.is_null() {
|
||||
// // 互斥体创建成功,且无先前存在的实例
|
||||
// println!("Application instance is running.");
|
||||
|
||||
// // 在这里执行应用程序逻辑
|
||||
// // ...
|
||||
|
||||
// // 在程序结束前,应该关闭互斥体句柄(此行代码并未在示例中展示)
|
||||
// } else {
|
||||
// // 创建互斥体失败,可能要进行错误处理
|
||||
// //panic!("Failed to create mutex.");
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -1,33 +1,35 @@
|
||||
//本地化
|
||||
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { t } from "i18next";
|
||||
import i18n from "../../services/i18n";
|
||||
import { roConfig } from "../../services/config";
|
||||
import { hooks } from "../../services/hook";
|
||||
|
||||
async function setLocalized(lang: string) {
|
||||
lang = lang.toLowerCase()
|
||||
|
||||
hooks.setLocaleStr(getLangCode(lang))
|
||||
|
||||
i18n.changeLanguage(lang)
|
||||
await invoke('set_localized', {
|
||||
localizedData: {
|
||||
quit: t("quit"),
|
||||
show: t("tray_show"),
|
||||
hide: t("tray_hide")
|
||||
}
|
||||
})
|
||||
lang = lang.toLowerCase();
|
||||
hooks.setLocaleStr(getLangCode(lang));
|
||||
const pack: Record<string, string> = await invoke("get_language_pack");
|
||||
i18n.addResourceBundle(lang, "translation", pack);
|
||||
i18n.changeLanguage(lang);
|
||||
// TODO: remove comment
|
||||
// await invoke('set_localized', {
|
||||
// localizedData: {
|
||||
// quit: t("quit"),
|
||||
// show: t("tray_show"),
|
||||
// hide: t("tray_hide")
|
||||
// }
|
||||
// })
|
||||
}
|
||||
|
||||
function getLangCode(lang: string): string {
|
||||
for (const value of roConfig.options.setting.language.select) {
|
||||
if (lang === value.value) {
|
||||
return value.langCode
|
||||
}
|
||||
for (const value of roConfig.options.setting.language.select) {
|
||||
if (lang === value.value) {
|
||||
return value.langCode;
|
||||
}
|
||||
return roConfig.options.setting.language.select[roConfig.options.setting.language.defIndex].langCode
|
||||
}
|
||||
return roConfig.options.setting.language.select[
|
||||
roConfig.options.setting.language.defIndex
|
||||
].langCode;
|
||||
}
|
||||
|
||||
export { setLocalized, getLangCode }
|
||||
export { setLocalized, getLangCode };
|
||||
|
||||
@@ -1,123 +1,140 @@
|
||||
import { invoke } from "@tauri-apps/api/core"
|
||||
import { nmConfig, saveNmConfig } from "../../../services/config"
|
||||
import { hooks } from "../../../services/hook"
|
||||
import { rcloneInfo } from "../../../services/rclone"
|
||||
import { MountListItem } from "../../../type/config"
|
||||
import { ParametersType } from "../../../type/defaults"
|
||||
import { rclone_api_post } from "../../../utils/rclone/request"
|
||||
import { fs_exist_dir, fs_make_dir } from "../../../utils/utils"
|
||||
import { convertStoragePath, formatPathRclone } from "../storage"
|
||||
import { MountOptions, VfsOptions } from "../../../type/rclone/storage/mount/parameters"
|
||||
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { nmConfig, saveNmConfig } from "../../../services/config";
|
||||
import { hooks } from "../../../services/hook";
|
||||
import { rcloneInfo } from "../../../services/rclone";
|
||||
import { MountListItem } from "../../../type/config";
|
||||
import { ParametersType } from "../../../type/defaults";
|
||||
import { rclone_api_post } from "../../../utils/rclone/request";
|
||||
import { fs_exist_dir, fs_make_dir } from "../../../utils/utils";
|
||||
import { convertStoragePath, formatPathRclone } from "../storage";
|
||||
import {
|
||||
MountOptions,
|
||||
VfsOptions,
|
||||
} from "../../../type/rclone/storage/mount/parameters";
|
||||
|
||||
//列举存储
|
||||
async function reupMount(noRefreshUI?: boolean) {
|
||||
const mountPoints: any[] =
|
||||
(await rclone_api_post("/mount/listmounts"))?.mountPoints || [];
|
||||
|
||||
const mountPoints: any[] = (await rclone_api_post(
|
||||
'/mount/listmounts',
|
||||
)).mountPoints || []
|
||||
rcloneInfo.mountList = [];
|
||||
|
||||
rcloneInfo.mountList = [];
|
||||
|
||||
mountPoints.forEach((tiem: any) => {
|
||||
const name = tiem.Fs
|
||||
rcloneInfo.mountList.push({
|
||||
storageName: name, //name.substring(0, name.length - 1)
|
||||
mountPath: tiem.MountPoint,
|
||||
mountedTime: new Date(tiem.MountedOn),
|
||||
})
|
||||
mountPoints.forEach((tiem: any) => {
|
||||
const name = tiem.Fs;
|
||||
rcloneInfo.mountList.push({
|
||||
storageName: name, //name.substring(0, name.length - 1)
|
||||
mountPath: tiem.MountPoint,
|
||||
mountedTime: new Date(tiem.MountedOn),
|
||||
});
|
||||
!noRefreshUI && hooks.upMount()
|
||||
});
|
||||
!noRefreshUI && hooks.upMount();
|
||||
}
|
||||
|
||||
function getMountStorage(mountPath: string): MountListItem | undefined {
|
||||
return nmConfig.mount.lists.find((item) => item.mountPath === mountPath)
|
||||
return nmConfig.mount.lists.find((item) => item.mountPath === mountPath);
|
||||
}
|
||||
|
||||
function isMounted(mountPath: string): boolean {
|
||||
return rcloneInfo.mountList.findIndex((item) => item.mountPath === mountPath) !== -1
|
||||
return (
|
||||
rcloneInfo.mountList.findIndex((item) => item.mountPath === mountPath) !==
|
||||
-1
|
||||
);
|
||||
}
|
||||
|
||||
async function addMountStorage(storageName: string, mountPath: string, parameters: { vfsOpt: VfsOptions, mountOpt: MountOptions }, autoMount?: boolean) {
|
||||
if (getMountStorage(mountPath)) {
|
||||
return false
|
||||
}
|
||||
async function addMountStorage(
|
||||
storageName: string,
|
||||
mountPath: string,
|
||||
parameters: { vfsOpt: VfsOptions; mountOpt: MountOptions },
|
||||
autoMount?: boolean,
|
||||
) {
|
||||
if (getMountStorage(mountPath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const mountInfo: MountListItem = {
|
||||
storageName: storageName,
|
||||
mountPath: mountPath,
|
||||
parameters: parameters,
|
||||
autoMount: (autoMount || false),
|
||||
}
|
||||
nmConfig.mount.lists.push(mountInfo)
|
||||
const mountInfo: MountListItem = {
|
||||
storageName: storageName,
|
||||
mountPath: mountPath,
|
||||
parameters: parameters,
|
||||
autoMount: autoMount || false,
|
||||
};
|
||||
nmConfig.mount.lists.push(mountInfo);
|
||||
|
||||
await saveNmConfig()
|
||||
await reupMount()
|
||||
await saveNmConfig();
|
||||
await reupMount();
|
||||
}
|
||||
|
||||
async function delMountStorage(mountPath: string) {
|
||||
if (isMounted(mountPath)) {
|
||||
await unmountStorage(mountPath)
|
||||
if (isMounted(mountPath)) {
|
||||
await unmountStorage(mountPath);
|
||||
}
|
||||
|
||||
nmConfig.mount.lists.forEach((item, index) => {
|
||||
if (item.mountPath === mountPath) {
|
||||
nmConfig.mount.lists.splice(index, 1);
|
||||
}
|
||||
});
|
||||
|
||||
nmConfig.mount.lists.forEach((item, index) => {
|
||||
if (item.mountPath === mountPath) {
|
||||
nmConfig.mount.lists.splice(index, 1)
|
||||
}
|
||||
})
|
||||
|
||||
await saveNmConfig()
|
||||
await reupMount()
|
||||
await saveNmConfig();
|
||||
await reupMount();
|
||||
}
|
||||
|
||||
async function editMountStorage(mountInfo: MountListItem) {
|
||||
|
||||
//await reupMount()
|
||||
//觉得这里是不必要的,就注释了
|
||||
/*rcloneInfo.mountList.forEach((item) => {
|
||||
//await reupMount()
|
||||
//觉得这里是不必要的,就注释了
|
||||
/*rcloneInfo.mountList.forEach((item) => {
|
||||
if (item.mountPath === mountInfo.mountPath) {
|
||||
return false
|
||||
}
|
||||
}) */
|
||||
|
||||
|
||||
for (let i = 0; i < nmConfig.mount.lists.length; i++) {
|
||||
if (nmConfig.mount.lists[i].mountPath === mountInfo.mountPath) {
|
||||
nmConfig.mount.lists[i] = mountInfo
|
||||
break
|
||||
}
|
||||
for (let i = 0; i < nmConfig.mount.lists.length; i++) {
|
||||
if (nmConfig.mount.lists[i].mountPath === mountInfo.mountPath) {
|
||||
nmConfig.mount.lists[i] = mountInfo;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
await saveNmConfig()
|
||||
await saveNmConfig();
|
||||
}
|
||||
|
||||
async function mountStorage(mountInfo: MountListItem) {
|
||||
if (!rcloneInfo.version.os.toLowerCase().includes('windows') && !await fs_exist_dir(mountInfo.mountPath)) {
|
||||
await fs_make_dir(mountInfo.mountPath)
|
||||
}
|
||||
if (
|
||||
!rcloneInfo.version.os.toLowerCase().includes("windows") &&
|
||||
!(await fs_exist_dir(mountInfo.mountPath))
|
||||
) {
|
||||
await fs_make_dir(mountInfo.mountPath);
|
||||
}
|
||||
|
||||
const back = await rclone_api_post('/mount/mount', {
|
||||
fs: convertStoragePath(mountInfo.storageName) || mountInfo.storageName,
|
||||
mountPoint: mountInfo.mountPath,
|
||||
...(mountInfo.parameters)
|
||||
})
|
||||
const back = await rclone_api_post("/mount/mount", {
|
||||
fs: convertStoragePath(mountInfo.storageName) || mountInfo.storageName,
|
||||
mountPoint: mountInfo.mountPath,
|
||||
...mountInfo.parameters,
|
||||
});
|
||||
|
||||
await reupMount()
|
||||
return back
|
||||
await reupMount();
|
||||
return back;
|
||||
}
|
||||
|
||||
async function unmountStorage(mountPath: string) {
|
||||
await rclone_api_post('/mount/unmount', {
|
||||
mountPoint: mountPath,
|
||||
})
|
||||
await rclone_api_post("/mount/unmount", {
|
||||
mountPoint: mountPath,
|
||||
});
|
||||
|
||||
await reupMount()
|
||||
await reupMount();
|
||||
}
|
||||
|
||||
async function getAvailableDriveLetter(): Promise<string> {
|
||||
return await invoke('get_available_drive_letter')//Z:
|
||||
return await invoke("get_available_drive_letter"); //Z:
|
||||
}
|
||||
|
||||
|
||||
export { reupMount, mountStorage, unmountStorage, addMountStorage, delMountStorage, editMountStorage, getMountStorage, isMounted, getAvailableDriveLetter }
|
||||
export {
|
||||
reupMount,
|
||||
mountStorage,
|
||||
unmountStorage,
|
||||
addMountStorage,
|
||||
delMountStorage,
|
||||
editMountStorage,
|
||||
getMountStorage,
|
||||
isMounted,
|
||||
getAvailableDriveLetter,
|
||||
};
|
||||
|
||||
@@ -124,10 +124,10 @@ function Mount_page() {
|
||||
const mounted = isMounted(item.mountPath)
|
||||
return {
|
||||
...item,
|
||||
mountPath_: <div style={{ display: 'flex', alignItems:'center' }}><Typography.Ellipsis className='singe-line' showTooltip>{item.mountPath}</Typography.Ellipsis>{rcloneInfo.endpoint.isLocal&&osInfo.osType==='windows' &&mounted&&
|
||||
<Button title={t('show_path_in_explorer')} onClick={async () => {
|
||||
await showPathInExplorer(item.mountPath,true)
|
||||
}} type='text' icon={<IconEye />}></Button>}</div>,
|
||||
mountPath_: <div style={{ display: 'flex', alignItems: 'center' }}><Typography.Ellipsis className='singe-line' showTooltip>{item.mountPath}</Typography.Ellipsis>{rcloneInfo.endpoint.isLocal && osInfo.osType === 'windows' && mounted &&
|
||||
<Button title={t('show_path_in_explorer')} onClick={async () => {
|
||||
await showPathInExplorer(item.mountPath, true)
|
||||
}} type='text' icon={<IconEye />}></Button>}</div>,
|
||||
mounted: mounted ? t('mounted') : t('unmounted'),
|
||||
actions: <Space>
|
||||
{
|
||||
|
||||
@@ -83,15 +83,15 @@ const setNmConfig = (config: NMConfig) => {
|
||||
}
|
||||
|
||||
const readNmConfig = async () => {
|
||||
await invoke('read_config_file').then(configData => {
|
||||
await invoke('get_config').then(configData => {
|
||||
setNmConfig({ ...nmConfig, ...(configData as NMConfig) })
|
||||
}).catch(err => {
|
||||
console.log(err);
|
||||
})
|
||||
}
|
||||
const saveNmConfig = async () => {
|
||||
await invoke('write_config_file', {
|
||||
configData: nmConfig
|
||||
await invoke('update_config', {
|
||||
data: nmConfig
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,36 +1,12 @@
|
||||
// i18n.js 文件
|
||||
import i18n from 'i18next';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
import i18n from "i18next";
|
||||
import { initReactI18next } from "react-i18next";
|
||||
|
||||
// 引入语言文件
|
||||
import cn from '../controller/language/pack/zh-cn.json';
|
||||
import en from '../controller/language/pack/en.json';
|
||||
import ct from '../controller/language/pack/zh-hant.json';
|
||||
|
||||
|
||||
// 初始化资源文件,即各种语言的json文件
|
||||
const resources = {
|
||||
cn: {
|
||||
translation: cn
|
||||
i18n.use(initReactI18next).init({
|
||||
resources: {},
|
||||
keySeparator: false,
|
||||
interpolation: {
|
||||
escapeValue: false,
|
||||
},
|
||||
ct: {
|
||||
translation: ct
|
||||
},
|
||||
en: {
|
||||
translation: en
|
||||
}
|
||||
};
|
||||
|
||||
i18n
|
||||
// 连接react-i18next与i18next的插件配置
|
||||
.use(initReactI18next)
|
||||
.init({
|
||||
resources,
|
||||
/*lng: "cn", // 初始语言 */
|
||||
keySeparator: false, // 是否允许keys使用点分隔符
|
||||
interpolation: {
|
||||
escapeValue: false // 转义字符
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
export default i18n;
|
||||
|
||||
@@ -20,10 +20,11 @@ async function setAlistPass(pass:string){
|
||||
}
|
||||
|
||||
async function modifyAlistConfig(rewriteData:any=alistInfo.alistConfig){
|
||||
const path = alistDataDir()+'config.json'
|
||||
const oldAlistConfig =await invoke('read_config_file',{path}) as object
|
||||
const newAlistConfig = {...oldAlistConfig, ...rewriteData}
|
||||
await invoke('write_config_file',{configData:newAlistConfig,path:path})
|
||||
// TODO: remove this
|
||||
// const path = alistDataDir()+'config.json'
|
||||
// const oldAlistConfig =await invoke('read_config_file',{path}) as object
|
||||
// const newAlistConfig = {...oldAlistConfig, ...rewriteData}
|
||||
// await invoke('write_config_file',{configData:newAlistConfig,path:path})
|
||||
}
|
||||
|
||||
async function addAlistInRclone(){
|
||||
|
||||
@@ -9,7 +9,7 @@ import { getAlistToken, modifyAlistConfig, setAlistPass } from "./alist";
|
||||
import { alist_api_ping } from "./request";
|
||||
|
||||
const alistDataDir = () => {
|
||||
return formatPath(roConfig.env.path.homeDir + '/.netmount/alist/',osInfo.osType==="windows")
|
||||
return formatPath(roConfig.env.path.homeDir + '/.netmount/alist/', osInfo.osType === "windows")
|
||||
}
|
||||
|
||||
const addParams = (): string[] => {
|
||||
|
||||
@@ -142,8 +142,8 @@ export function compareVersions(v1: string, v2: string) {
|
||||
}
|
||||
|
||||
export async function set_devtools_state(state: boolean) {
|
||||
await invoke('set_devtools_state', {
|
||||
state: state
|
||||
await invoke('toggle_devtools', {
|
||||
preferred_open: state
|
||||
})
|
||||
}
|
||||
|
||||
@@ -154,9 +154,14 @@ export async function fs_exist_dir(path: string) {
|
||||
}
|
||||
|
||||
export async function fs_make_dir(path: string) {
|
||||
return await invoke('fs_make_dir', {
|
||||
path: path
|
||||
}) as boolean
|
||||
try {
|
||||
await invoke('fs_make_dir', {
|
||||
path: path
|
||||
})
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function formatPath(path: string, isWindows: boolean = false) {
|
||||
|
||||
Reference in New Issue
Block a user