feat(desktop): remember login in sqlite and streamline update flow
This commit is contained in:
76
desktop-client/src-tauri/Cargo.lock
generated
76
desktop-client/src-tauri/Cargo.lock
generated
@@ -8,6 +8,18 @@ version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.4"
|
||||
@@ -681,9 +693,10 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "desktop-client"
|
||||
version = "0.1.4"
|
||||
version = "0.1.5"
|
||||
dependencies = [
|
||||
"reqwest 0.12.28",
|
||||
"rusqlite",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
@@ -917,6 +930,18 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fallible-iterator"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
|
||||
|
||||
[[package]]
|
||||
name = "fallible-streaming-iterator"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.3.0"
|
||||
@@ -1423,6 +1448,15 @@ version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.5"
|
||||
@@ -1438,6 +1472,15 @@ version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
||||
|
||||
[[package]]
|
||||
name = "hashlink"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af"
|
||||
dependencies = [
|
||||
"hashbrown 0.14.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
@@ -1957,6 +2000,17 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libsqlite3-sys"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c10584274047cb335c23d3e61bcef8e323adae7c5c8c760540f73610177fc3f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.11.0"
|
||||
@@ -3204,6 +3258,20 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rusqlite"
|
||||
version = "0.31.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b838eba278d213a8beaf485bd313fd580ca4505a00d5871caeb1457c55322cae"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"fallible-iterator",
|
||||
"fallible-streaming-iterator",
|
||||
"hashlink",
|
||||
"libsqlite3-sys",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.1"
|
||||
@@ -4624,6 +4692,12 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||
|
||||
[[package]]
|
||||
name = "version-compare"
|
||||
version = "0.2.1"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "desktop-client"
|
||||
version = "0.1.4"
|
||||
version = "0.1.5"
|
||||
description = "A Tauri App"
|
||||
authors = ["you"]
|
||||
edition = "2021"
|
||||
@@ -26,3 +26,4 @@ serde_json = "1"
|
||||
reqwest = { version = "0.12", default-features = false, features = ["json", "cookies", "multipart", "stream", "rustls-tls"] }
|
||||
urlencoding = "2.1"
|
||||
walkdir = "2.5"
|
||||
rusqlite = { version = "0.31", features = ["bundled"] }
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use reqwest::Method;
|
||||
use reqwest::StatusCode;
|
||||
use rusqlite::{params, Connection};
|
||||
use serde::Serialize;
|
||||
use serde_json::{Map, Value};
|
||||
use std::env;
|
||||
@@ -8,7 +9,7 @@ use std::io::Write;
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
use std::time::{Duration, Instant, UNIX_EPOCH};
|
||||
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
|
||||
use tauri::Emitter;
|
||||
|
||||
struct ApiState {
|
||||
@@ -34,6 +35,16 @@ struct NativeDownloadProgressPayload {
|
||||
done: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct NativeUploadProgressPayload {
|
||||
task_id: String,
|
||||
uploaded_bytes: u64,
|
||||
total_bytes: u64,
|
||||
progress: f64,
|
||||
done: bool,
|
||||
}
|
||||
|
||||
fn emit_native_download_progress(
|
||||
window: &tauri::WebviewWindow,
|
||||
task_id: &str,
|
||||
@@ -64,6 +75,32 @@ fn emit_native_download_progress(
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_native_upload_progress(
|
||||
window: &tauri::WebviewWindow,
|
||||
task_id: &str,
|
||||
uploaded_bytes: u64,
|
||||
total_bytes: u64,
|
||||
done: bool,
|
||||
) {
|
||||
if task_id.trim().is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let normalized_total = total_bytes.max(1);
|
||||
let progress = (uploaded_bytes as f64 / normalized_total as f64) * 100.0;
|
||||
let payload = NativeUploadProgressPayload {
|
||||
task_id: task_id.to_string(),
|
||||
uploaded_bytes,
|
||||
total_bytes,
|
||||
progress,
|
||||
done,
|
||||
};
|
||||
|
||||
if let Err(err) = window.emit("native-upload-progress", payload) {
|
||||
eprintln!("emit native-upload-progress failed: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_base_url(base_url: &str) -> String {
|
||||
let trimmed = base_url.trim();
|
||||
if trimmed.is_empty() {
|
||||
@@ -163,6 +200,81 @@ fn build_download_resume_temp_path(download_dir: &Path, preferred_name: &str, ur
|
||||
download_dir.join(temp_name)
|
||||
}
|
||||
|
||||
fn resolve_local_state_dir() -> PathBuf {
|
||||
if let Some(appdata) = env::var_os("APPDATA") {
|
||||
return PathBuf::from(appdata).join("wanwan-cloud-desktop");
|
||||
}
|
||||
if let Some(home) = env::var_os("HOME") {
|
||||
return PathBuf::from(home).join(".wanwan-cloud-desktop");
|
||||
}
|
||||
PathBuf::from(".").join(".wanwan-cloud-desktop")
|
||||
}
|
||||
|
||||
fn open_local_state_db() -> Result<Connection, String> {
|
||||
let state_dir = resolve_local_state_dir();
|
||||
fs::create_dir_all(&state_dir).map_err(|err| format!("创建本地状态目录失败: {}", err))?;
|
||||
let db_path = state_dir.join("client_state.db");
|
||||
let conn = Connection::open(db_path).map_err(|err| format!("打开本地状态数据库失败: {}", err))?;
|
||||
conn.execute(
|
||||
"CREATE TABLE IF NOT EXISTS login_state (
|
||||
id INTEGER PRIMARY KEY CHECK (id = 1),
|
||||
base_url TEXT NOT NULL,
|
||||
username TEXT NOT NULL,
|
||||
password TEXT NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
)",
|
||||
[],
|
||||
)
|
||||
.map_err(|err| format!("初始化本地状态表失败: {}", err))?;
|
||||
Ok(conn)
|
||||
}
|
||||
|
||||
fn save_login_state_record(base_url: &str, username: &str, password: &str) -> Result<(), String> {
|
||||
let conn = open_local_state_db()?;
|
||||
let now = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.map(|duration| duration.as_secs() as i64)
|
||||
.unwrap_or_default();
|
||||
conn.execute(
|
||||
"INSERT INTO login_state (id, base_url, username, password, updated_at)
|
||||
VALUES (1, ?1, ?2, ?3, ?4)
|
||||
ON CONFLICT(id) DO UPDATE SET
|
||||
base_url = excluded.base_url,
|
||||
username = excluded.username,
|
||||
password = excluded.password,
|
||||
updated_at = excluded.updated_at",
|
||||
params![base_url, username, password, now],
|
||||
)
|
||||
.map_err(|err| format!("保存登录状态失败: {}", err))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn load_login_state_record() -> Result<Option<(String, String, String)>, String> {
|
||||
let conn = open_local_state_db()?;
|
||||
let mut stmt = conn
|
||||
.prepare("SELECT base_url, username, password FROM login_state WHERE id = 1 LIMIT 1")
|
||||
.map_err(|err| format!("读取登录状态失败: {}", err))?;
|
||||
let row = stmt.query_row([], |record| {
|
||||
Ok((
|
||||
record.get::<_, String>(0)?,
|
||||
record.get::<_, String>(1)?,
|
||||
record.get::<_, String>(2)?,
|
||||
))
|
||||
});
|
||||
match row {
|
||||
Ok(value) => Ok(Some(value)),
|
||||
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
|
||||
Err(err) => Err(format!("读取登录状态失败: {}", err)),
|
||||
}
|
||||
}
|
||||
|
||||
fn clear_login_state_record() -> Result<(), String> {
|
||||
let conn = open_local_state_db()?;
|
||||
conn.execute("DELETE FROM login_state WHERE id = 1", [])
|
||||
.map_err(|err| format!("清除登录状态失败: {}", err))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn parse_response_as_bridge(response: reqwest::Response) -> Result<BridgeResponse, String> {
|
||||
let status = response.status();
|
||||
let text = response
|
||||
@@ -314,6 +426,70 @@ async fn api_get_profile(
|
||||
.await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn api_save_login_state(
|
||||
base_url: String,
|
||||
username: String,
|
||||
password: String,
|
||||
) -> Result<BridgeResponse, String> {
|
||||
let normalized_base = normalize_base_url(&base_url);
|
||||
let normalized_user = username.trim().to_string();
|
||||
if normalized_base.is_empty() {
|
||||
return Err("服务地址不能为空".to_string());
|
||||
}
|
||||
if normalized_user.is_empty() {
|
||||
return Err("用户名不能为空".to_string());
|
||||
}
|
||||
if password.trim().is_empty() {
|
||||
return Err("密码不能为空".to_string());
|
||||
}
|
||||
|
||||
save_login_state_record(&normalized_base, &normalized_user, &password)?;
|
||||
|
||||
let mut data = Map::new();
|
||||
data.insert("success".to_string(), Value::Bool(true));
|
||||
data.insert("message".to_string(), Value::String("登录状态已保存".to_string()));
|
||||
Ok(BridgeResponse {
|
||||
ok: true,
|
||||
status: 200,
|
||||
data: Value::Object(data),
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn api_load_login_state() -> Result<BridgeResponse, String> {
|
||||
let state = load_login_state_record()?;
|
||||
let mut data = Map::new();
|
||||
data.insert("success".to_string(), Value::Bool(true));
|
||||
if let Some((base_url, username, password)) = state {
|
||||
data.insert("hasState".to_string(), Value::Bool(true));
|
||||
data.insert("baseUrl".to_string(), Value::String(base_url));
|
||||
data.insert("username".to_string(), Value::String(username));
|
||||
data.insert("password".to_string(), Value::String(password));
|
||||
} else {
|
||||
data.insert("hasState".to_string(), Value::Bool(false));
|
||||
}
|
||||
|
||||
Ok(BridgeResponse {
|
||||
ok: true,
|
||||
status: 200,
|
||||
data: Value::Object(data),
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn api_clear_login_state() -> Result<BridgeResponse, String> {
|
||||
clear_login_state_record()?;
|
||||
let mut data = Map::new();
|
||||
data.insert("success".to_string(), Value::Bool(true));
|
||||
data.insert("message".to_string(), Value::String("登录状态已清除".to_string()));
|
||||
Ok(BridgeResponse {
|
||||
ok: true,
|
||||
status: 200,
|
||||
data: Value::Object(data),
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn api_list_files(
|
||||
state: tauri::State<'_, ApiState>,
|
||||
@@ -820,6 +996,79 @@ fn api_launch_installer(installer_path: String) -> Result<BridgeResponse, String
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn api_silent_install_and_restart(installer_path: String) -> Result<BridgeResponse, String> {
|
||||
let path_text = installer_path.trim().to_string();
|
||||
if path_text.is_empty() {
|
||||
return Err("安装包路径不能为空".to_string());
|
||||
}
|
||||
|
||||
let installer = PathBuf::from(&path_text);
|
||||
if !installer.exists() {
|
||||
return Err("安装包不存在,请重新下载".to_string());
|
||||
}
|
||||
if !installer.is_file() {
|
||||
return Err("安装包路径无效".to_string());
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
let current_exe = env::current_exe().map_err(|err| format!("获取当前程序路径失败: {}", err))?;
|
||||
let temp_dir = env::temp_dir().join("wanwan-cloud-desktop");
|
||||
fs::create_dir_all(&temp_dir).map_err(|err| format!("创建更新脚本目录失败: {}", err))?;
|
||||
let script_stamp = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.map(|duration| duration.as_millis())
|
||||
.unwrap_or_default();
|
||||
let script_path = temp_dir.join(format!("silent-update-{}.cmd", script_stamp));
|
||||
|
||||
let installer_text = installer.to_string_lossy().replace('"', "\"\"");
|
||||
let app_text = current_exe.to_string_lossy().replace('"', "\"\"");
|
||||
let script_content = format!(
|
||||
"@echo off\r\n\
|
||||
setlocal\r\n\
|
||||
set \"INSTALLER={installer}\"\r\n\
|
||||
set \"APP_EXE={app_exe}\"\r\n\
|
||||
timeout /t 2 /nobreak >nul\r\n\
|
||||
start \"\" /wait \"%INSTALLER%\" /S\r\n\
|
||||
start \"\" \"%APP_EXE%\"\r\n\
|
||||
del \"%~f0\" >nul 2>nul\r\n",
|
||||
installer = installer_text,
|
||||
app_exe = app_text
|
||||
);
|
||||
fs::write(&script_path, script_content).map_err(|err| format!("写入更新脚本失败: {}", err))?;
|
||||
|
||||
let script_arg = script_path.to_string_lossy().to_string();
|
||||
Command::new("cmd")
|
||||
.args(["/C", "start", "", "/min", &script_arg])
|
||||
.spawn()
|
||||
.map_err(|err| format!("启动静默更新流程失败: {}", err))?;
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
#[cfg(target_os = "macos")]
|
||||
let spawn_result = Command::new("open").arg(&installer).spawn();
|
||||
#[cfg(all(not(target_os = "windows"), not(target_os = "macos")))]
|
||||
let spawn_result = Command::new("xdg-open").arg(&installer).spawn();
|
||||
spawn_result.map_err(|err| format!("启动安装程序失败: {}", err))?;
|
||||
}
|
||||
|
||||
let mut data = Map::new();
|
||||
data.insert("success".to_string(), Value::Bool(true));
|
||||
data.insert(
|
||||
"message".to_string(),
|
||||
Value::String("静默安装流程已启动,安装完成后将自动重启".to_string()),
|
||||
);
|
||||
data.insert("installerPath".to_string(), Value::String(path_text));
|
||||
|
||||
Ok(BridgeResponse {
|
||||
ok: true,
|
||||
status: 200,
|
||||
data: Value::Object(data),
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn api_check_client_update(
|
||||
state: tauri::State<'_, ApiState>,
|
||||
@@ -939,10 +1188,12 @@ async fn api_list_local_files(dir_path: String) -> Result<BridgeResponse, String
|
||||
#[tauri::command]
|
||||
async fn api_upload_file_resumable(
|
||||
state: tauri::State<'_, ApiState>,
|
||||
window: tauri::WebviewWindow,
|
||||
base_url: String,
|
||||
file_path: String,
|
||||
target_path: String,
|
||||
chunk_size: Option<u64>,
|
||||
task_id: Option<String>,
|
||||
) -> Result<BridgeResponse, String> {
|
||||
let trimmed_path = file_path.trim().to_string();
|
||||
if trimmed_path.is_empty() {
|
||||
@@ -1030,7 +1281,18 @@ async fn api_upload_file_resumable(
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut uploaded_bytes = uploaded_chunks.iter().fold(0_u64, |sum, chunk_index| {
|
||||
let offset = chunk_index.saturating_mul(server_chunk_size);
|
||||
let remaining = file_size.saturating_sub(offset);
|
||||
let bytes = std::cmp::min(remaining, server_chunk_size);
|
||||
sum.saturating_add(bytes)
|
||||
});
|
||||
if let Some(ref id) = task_id {
|
||||
emit_native_upload_progress(&window, id, uploaded_bytes, file_size, false);
|
||||
}
|
||||
|
||||
let mut source = fs::File::open(&source_path).map_err(|err| format!("打开文件失败: {}", err))?;
|
||||
let mut last_emit = Instant::now();
|
||||
for chunk_index in 0..total_chunks {
|
||||
if uploaded_chunks.contains(&chunk_index) {
|
||||
continue;
|
||||
@@ -1078,26 +1340,44 @@ async fn api_upload_file_resumable(
|
||||
if !chunk_bridge.ok || !chunk_bridge.data.get("success").and_then(Value::as_bool).unwrap_or(false) {
|
||||
return Ok(chunk_bridge);
|
||||
}
|
||||
|
||||
uploaded_bytes = uploaded_bytes.saturating_add(read_size as u64).min(file_size);
|
||||
if let Some(ref id) = task_id {
|
||||
if last_emit.elapsed() >= Duration::from_millis(120) {
|
||||
emit_native_upload_progress(&window, id, uploaded_bytes, file_size, false);
|
||||
last_emit = Instant::now();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut complete_body = Map::new();
|
||||
complete_body.insert("session_id".to_string(), Value::String(session_id));
|
||||
request_json(
|
||||
let complete_resp = request_json(
|
||||
&state.client,
|
||||
Method::POST,
|
||||
join_api_url(&base_url, "/api/upload/resumable/complete"),
|
||||
Some(Value::Object(complete_body)),
|
||||
csrf_token,
|
||||
)
|
||||
.await
|
||||
.await?;
|
||||
|
||||
if complete_resp.ok && complete_resp.data.get("success").and_then(Value::as_bool).unwrap_or(false) {
|
||||
if let Some(ref id) = task_id {
|
||||
emit_native_upload_progress(&window, id, file_size, file_size, true);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(complete_resp)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn api_upload_file(
|
||||
state: tauri::State<'_, ApiState>,
|
||||
window: tauri::WebviewWindow,
|
||||
base_url: String,
|
||||
file_path: String,
|
||||
target_path: String,
|
||||
task_id: Option<String>,
|
||||
) -> Result<BridgeResponse, String> {
|
||||
let trimmed_path = file_path.trim().to_string();
|
||||
if trimmed_path.is_empty() {
|
||||
@@ -1111,6 +1391,9 @@ async fn api_upload_file(
|
||||
if !source_path.is_file() {
|
||||
return Err("仅支持上传文件,不支持文件夹".to_string());
|
||||
}
|
||||
let file_size = fs::metadata(&source_path)
|
||||
.map(|meta| meta.len())
|
||||
.unwrap_or(0);
|
||||
|
||||
let file_name = source_path
|
||||
.file_name()
|
||||
@@ -1129,6 +1412,10 @@ async fn api_upload_file(
|
||||
return Err("API 地址不能为空".to_string());
|
||||
}
|
||||
|
||||
if let Some(ref id) = task_id {
|
||||
emit_native_upload_progress(&window, id, 0, file_size.max(1), false);
|
||||
}
|
||||
|
||||
// 使用流式 multipart 上传,避免大文件整块读入内存导致占用暴涨。
|
||||
let file_part = reqwest::multipart::Part::file(&source_path)
|
||||
.await
|
||||
@@ -1164,6 +1451,12 @@ async fn api_upload_file(
|
||||
Ok(parsed) => parsed,
|
||||
Err(_) => fallback_json(status, &text),
|
||||
};
|
||||
let success = status.is_success() && data.get("success").and_then(Value::as_bool).unwrap_or(false);
|
||||
if success {
|
||||
if let Some(ref id) = task_id {
|
||||
emit_native_upload_progress(&window, id, file_size, file_size.max(1), true);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(BridgeResponse {
|
||||
ok: status.is_success(),
|
||||
@@ -1186,6 +1479,9 @@ pub fn run() {
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
api_login,
|
||||
api_save_login_state,
|
||||
api_load_login_state,
|
||||
api_clear_login_state,
|
||||
api_get_profile,
|
||||
api_list_files,
|
||||
api_logout,
|
||||
@@ -1200,6 +1496,7 @@ pub fn run() {
|
||||
api_create_direct_link,
|
||||
api_native_download,
|
||||
api_launch_installer,
|
||||
api_silent_install_and_restart,
|
||||
api_check_client_update,
|
||||
api_list_local_files,
|
||||
api_upload_file_resumable,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "wanwan-cloud-desktop",
|
||||
"version": "0.1.4",
|
||||
"version": "0.1.5",
|
||||
"identifier": "cn.workyai.wanwancloud.desktop",
|
||||
"build": {
|
||||
"beforeDevCommand": "npm run dev",
|
||||
|
||||
Reference in New Issue
Block a user