feat(desktop): add sort/filter, update center, and local sync workspace
This commit is contained in:
@@ -6,7 +6,7 @@ use std::env;
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Duration;
|
||||
use std::time::{Duration, UNIX_EPOCH};
|
||||
|
||||
struct ApiState {
|
||||
client: reqwest::Client,
|
||||
@@ -595,6 +595,122 @@ async fn api_native_download(
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn api_check_client_update(
|
||||
state: tauri::State<'_, ApiState>,
|
||||
base_url: String,
|
||||
current_version: String,
|
||||
platform: Option<String>,
|
||||
channel: Option<String>,
|
||||
) -> Result<BridgeResponse, String> {
|
||||
let normalized_platform = platform
|
||||
.unwrap_or_else(|| "windows-x64".to_string())
|
||||
.trim()
|
||||
.to_string();
|
||||
let normalized_channel = channel
|
||||
.unwrap_or_else(|| "stable".to_string())
|
||||
.trim()
|
||||
.to_string();
|
||||
|
||||
let api_url = format!(
|
||||
"{}?currentVersion={}&platform={}&channel={}",
|
||||
join_api_url(&base_url, "/api/client/desktop-update"),
|
||||
urlencoding::encode(current_version.trim()),
|
||||
urlencoding::encode(&normalized_platform),
|
||||
urlencoding::encode(&normalized_channel)
|
||||
);
|
||||
request_json(&state.client, Method::GET, api_url, None, None).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn api_list_local_files(dir_path: String) -> Result<BridgeResponse, String> {
|
||||
let trimmed = dir_path.trim().to_string();
|
||||
if trimmed.is_empty() {
|
||||
return Err("本地目录不能为空".to_string());
|
||||
}
|
||||
|
||||
let root = PathBuf::from(&trimmed);
|
||||
if !root.exists() {
|
||||
return Err("本地目录不存在".to_string());
|
||||
}
|
||||
if !root.is_dir() {
|
||||
return Err("请选择有效的目录路径".to_string());
|
||||
}
|
||||
|
||||
let mut items: Vec<Value> = Vec::new();
|
||||
for entry in walkdir::WalkDir::new(&root)
|
||||
.follow_links(false)
|
||||
.into_iter()
|
||||
.filter_map(Result::ok)
|
||||
{
|
||||
if !entry.file_type().is_file() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let full_path = entry.path();
|
||||
let relative = full_path.strip_prefix(&root).unwrap_or(full_path);
|
||||
let relative_path = relative.to_string_lossy().replace('\\', "/");
|
||||
if relative_path.trim().is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let metadata = match entry.metadata() {
|
||||
Ok(meta) => meta,
|
||||
Err(_) => continue,
|
||||
};
|
||||
let modified_ms_u128 = metadata
|
||||
.modified()
|
||||
.ok()
|
||||
.and_then(|value| value.duration_since(UNIX_EPOCH).ok())
|
||||
.map(|duration| duration.as_millis())
|
||||
.unwrap_or(0);
|
||||
let modified_ms = std::cmp::min(modified_ms_u128, u128::from(u64::MAX)) as u64;
|
||||
|
||||
let mut row = Map::new();
|
||||
row.insert(
|
||||
"path".to_string(),
|
||||
Value::String(full_path.to_string_lossy().to_string()),
|
||||
);
|
||||
row.insert("relativePath".to_string(), Value::String(relative_path));
|
||||
row.insert(
|
||||
"size".to_string(),
|
||||
Value::Number(serde_json::Number::from(metadata.len())),
|
||||
);
|
||||
row.insert(
|
||||
"modifiedMs".to_string(),
|
||||
Value::Number(serde_json::Number::from(modified_ms)),
|
||||
);
|
||||
items.push(Value::Object(row));
|
||||
}
|
||||
|
||||
items.sort_by(|a, b| {
|
||||
let av = a
|
||||
.get("relativePath")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or_default();
|
||||
let bv = b
|
||||
.get("relativePath")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or_default();
|
||||
av.cmp(bv)
|
||||
});
|
||||
|
||||
let mut data = Map::new();
|
||||
data.insert("success".to_string(), Value::Bool(true));
|
||||
data.insert("rootPath".to_string(), Value::String(trimmed));
|
||||
data.insert(
|
||||
"count".to_string(),
|
||||
Value::Number(serde_json::Number::from(items.len() as u64)),
|
||||
);
|
||||
data.insert("items".to_string(), Value::Array(items));
|
||||
|
||||
Ok(BridgeResponse {
|
||||
ok: true,
|
||||
status: 200,
|
||||
data: Value::Object(data),
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn api_upload_file(
|
||||
state: tauri::State<'_, ApiState>,
|
||||
@@ -685,6 +801,7 @@ pub fn run() {
|
||||
|
||||
tauri::Builder::default()
|
||||
.manage(ApiState { client })
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
api_login,
|
||||
@@ -701,6 +818,8 @@ pub fn run() {
|
||||
api_delete_share,
|
||||
api_create_direct_link,
|
||||
api_native_download,
|
||||
api_check_client_update,
|
||||
api_list_local_files,
|
||||
api_upload_file
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
|
||||
Reference in New Issue
Block a user