feat: improve sync workspace and updater stability in 0.1.27
This commit is contained in:
@@ -153,7 +153,7 @@ const syncState = reactive({
|
||||
nextRunAt: "",
|
||||
});
|
||||
const updateState = reactive({
|
||||
currentVersion: "0.1.26",
|
||||
currentVersion: "0.1.27",
|
||||
latestVersion: "",
|
||||
available: false,
|
||||
mandatory: false,
|
||||
@@ -725,6 +725,30 @@ function normalizeRelativePath(rawPath: string) {
|
||||
return String(rawPath || "").replace(/\\/g, "/").replace(/^\/+/, "");
|
||||
}
|
||||
|
||||
function sanitizeSyncFolderSegment(raw: string, fallback = "默认同步盘") {
|
||||
const normalized = String(raw || "")
|
||||
.trim()
|
||||
.replace(/[\/\\:*?"<>|]/g, "_")
|
||||
.replace(/\.\./g, "_")
|
||||
.replace(/\s+/g, " ");
|
||||
const compact = normalized.replace(/^_+|_+$/g, "").trim();
|
||||
if (!compact) return fallback;
|
||||
return compact.slice(0, 60);
|
||||
}
|
||||
|
||||
function deriveDefaultSyncRemoteBasePath() {
|
||||
const localDirName = sanitizeSyncFolderSegment(extractFileNameFromPath(syncState.localDir || ""), "");
|
||||
const owner = sanitizeSyncFolderSegment(String(user.value?.username || user.value?.id || "desktop"), "desktop");
|
||||
const folderName = localDirName || `同步盘_${owner}`;
|
||||
return normalizePath(`/同步盘/${folderName}`);
|
||||
}
|
||||
|
||||
function applyDefaultSyncRemoteBasePath(force = false) {
|
||||
const normalizedCurrent = normalizePath(syncState.remoteBasePath || "/");
|
||||
if (!force && normalizedCurrent !== "/") return;
|
||||
syncState.remoteBasePath = deriveDefaultSyncRemoteBasePath();
|
||||
}
|
||||
|
||||
function getSyncConfigStorageKey() {
|
||||
const userId = String(user.value?.id || "guest");
|
||||
return `wanwan_desktop_sync_config_v2_${userId}`;
|
||||
@@ -748,7 +772,10 @@ function safeParseObject(raw: string | null) {
|
||||
function loadSyncConfig() {
|
||||
const key = getSyncConfigStorageKey();
|
||||
const raw = localStorage.getItem(key);
|
||||
if (!raw) return;
|
||||
if (!raw) {
|
||||
applyDefaultSyncRemoteBasePath(true);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const parsed = JSON.parse(raw);
|
||||
if (!parsed || typeof parsed !== "object") return;
|
||||
@@ -760,6 +787,7 @@ function loadSyncConfig() {
|
||||
} catch {
|
||||
// ignore invalid cache
|
||||
}
|
||||
applyDefaultSyncRemoteBasePath(false);
|
||||
}
|
||||
|
||||
function saveSyncConfig() {
|
||||
@@ -1052,8 +1080,12 @@ async function installLatestUpdate(): Promise<boolean> {
|
||||
const launchResponse = await invokeBridge("api_silent_install_and_restart", {
|
||||
installerPath: savePath,
|
||||
});
|
||||
const logFilePath = String(launchResponse.data?.logFilePath || "").trim();
|
||||
if (launchResponse.ok && launchResponse.data?.success) {
|
||||
showToast("静默安装已启动,完成后会自动重启客户端", "success");
|
||||
const launchTip = logFilePath
|
||||
? `静默安装已启动(日志:${logFilePath})`
|
||||
: "静默安装已启动,完成后会自动重启客户端";
|
||||
showToast(launchTip, "success");
|
||||
setTimeout(() => {
|
||||
const win = getCurrentWindow();
|
||||
void (async () => {
|
||||
@@ -1070,6 +1102,9 @@ async function installLatestUpdate(): Promise<boolean> {
|
||||
}, 400);
|
||||
return true;
|
||||
}
|
||||
if (logFilePath) {
|
||||
console.warn("silent updater log path:", logFilePath);
|
||||
}
|
||||
|
||||
try {
|
||||
await openPath(savePath);
|
||||
@@ -1077,7 +1112,7 @@ async function installLatestUpdate(): Promise<boolean> {
|
||||
console.error("open installer fallback failed", error);
|
||||
}
|
||||
}
|
||||
showToast("更新包已下载,请手动运行安装程序", "info");
|
||||
showToast("更新包已下载,静默安装未启动,请手动运行安装程序", "info");
|
||||
setTimeout(() => {
|
||||
resetUpdateRuntime();
|
||||
}, 1200);
|
||||
@@ -1197,7 +1232,11 @@ async function chooseSyncDirectory() {
|
||||
title: "选择本地同步文件夹",
|
||||
});
|
||||
if (typeof result === "string" && result.trim()) {
|
||||
const hadCustomRemoteBase = normalizePath(syncState.remoteBasePath || "/") !== "/";
|
||||
syncState.localDir = result.trim();
|
||||
if (!hadCustomRemoteBase) {
|
||||
applyDefaultSyncRemoteBasePath(true);
|
||||
}
|
||||
showToast("同步目录已更新", "success");
|
||||
}
|
||||
} catch {
|
||||
@@ -1302,6 +1341,36 @@ async function clearSyncSnapshot() {
|
||||
showToast("同步索引已清理,下次会全量扫描", "success");
|
||||
}
|
||||
|
||||
async function ensureRemoteFolderPath(targetPath: string) {
|
||||
const normalized = normalizePath(targetPath || "/");
|
||||
if (normalized === "/") {
|
||||
return { ok: true, path: "/" };
|
||||
}
|
||||
|
||||
const segments = normalized.split("/").filter(Boolean);
|
||||
let current = "/";
|
||||
for (const segment of segments) {
|
||||
const response = await invokeBridge("api_mkdir", {
|
||||
baseUrl: appConfig.baseUrl,
|
||||
path: current,
|
||||
folderName: segment,
|
||||
});
|
||||
if (!(response.ok && response.data?.success)) {
|
||||
const message = String(response.data?.message || "");
|
||||
if (!message.includes("已存在")) {
|
||||
return {
|
||||
ok: false,
|
||||
path: normalizePath(`${current}/${segment}`),
|
||||
message: message || "创建远程同步目录失败",
|
||||
};
|
||||
}
|
||||
}
|
||||
current = normalizePath(`${current}/${segment}`);
|
||||
}
|
||||
|
||||
return { ok: true, path: normalized };
|
||||
}
|
||||
|
||||
async function runSyncOnce(trigger: "manual" | "auto" = "manual") {
|
||||
if (!authenticated.value) return;
|
||||
const localDir = syncState.localDir.trim();
|
||||
@@ -1355,6 +1424,21 @@ async function runSyncOnce(trigger: "manual" | "auto" = "manual") {
|
||||
return;
|
||||
}
|
||||
|
||||
applyDefaultSyncRemoteBasePath(false);
|
||||
const remoteBase = normalizePath(syncState.remoteBasePath || "/");
|
||||
const ensuredRemotePaths = new Set<string>();
|
||||
const baseEnsureResult = await ensureRemoteFolderPath(remoteBase);
|
||||
if (!baseEnsureResult.ok) {
|
||||
syncState.syncing = false;
|
||||
syncState.lastRunAt = new Date().toISOString();
|
||||
syncState.lastSummary = String(baseEnsureResult.message || "创建远程同步目录失败");
|
||||
if (trigger === "manual") {
|
||||
showToast(syncState.lastSummary, "error");
|
||||
}
|
||||
return;
|
||||
}
|
||||
ensuredRemotePaths.add(remoteBase);
|
||||
|
||||
const successPaths = new Set<string>();
|
||||
for (let index = 0; index < changedItems.length; index += 1) {
|
||||
await waitForTransferQueue();
|
||||
@@ -1362,7 +1446,6 @@ async function runSyncOnce(trigger: "manual" | "auto" = "manual") {
|
||||
const relPath = normalizeRelativePath(item.relativePath);
|
||||
const fileName = extractFileNameFromPath(relPath) || `同步文件${index + 1}`;
|
||||
const parent = getRelativeParentPath(relPath);
|
||||
const remoteBase = normalizePath(syncState.remoteBasePath || "/");
|
||||
const targetPath = parent ? normalizePath(`${remoteBase}/${parent}`) : remoteBase;
|
||||
const taskId = `S-${Date.now()}-${index}`;
|
||||
|
||||
@@ -1380,6 +1463,21 @@ async function runSyncOnce(trigger: "manual" | "auto" = "manual") {
|
||||
});
|
||||
updateTransferTask(taskId, { status: "uploading", speed: "上传中", progress: 10 });
|
||||
|
||||
if (!ensuredRemotePaths.has(targetPath)) {
|
||||
const ensureResult = await ensureRemoteFolderPath(targetPath);
|
||||
if (!ensureResult.ok) {
|
||||
syncState.failedCount += 1;
|
||||
updateTransferTask(taskId, {
|
||||
speed: "-",
|
||||
progress: 0,
|
||||
status: "failed",
|
||||
note: String(ensureResult.message || "创建远程同步目录失败"),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
ensuredRemotePaths.add(targetPath);
|
||||
}
|
||||
|
||||
const resp = await uploadFileWithResume(item.path, targetPath, taskId);
|
||||
|
||||
if (resp.ok && resp.data?.success) {
|
||||
|
||||
Reference in New Issue
Block a user