feat(client/windows): cfapi-Sync lebendig machen (Loop + Watcher + UI)
Jetzt tatsaechlich funktionsfaehig, nicht mehr nur Dummy: - Register-Fallback: erst CF_REGISTER_FLAG_NONE, bei "bereits registriert" automatisch mit UPDATE erneut versuchen. Klappt damit bei Erstaktivierung und bei Client-Neustart. - Hintergrund-Loop (cloud_files::sync_loop) pollt alle 30s /api/sync/changes, legt neue Placeholder an und ersetzt geaenderte. - Eigener Callback-Watcher (cloud_files::watcher::CallbackWatcher) hoert auf den Mount-Ordner und sendet lokale Aenderungen (Create/Modify) an den Loop, der sie via POST /api/files/upload hochlaedt. - Helper create_placeholder_at() vom Windows-Modul exportiert, damit der Loop neue Server-Dateien als Placeholder anlegen kann. - AppState erhaelt cloud_files_loop + cloud_files_watcher Felder; beim Disable wird der Loop sauber gestoppt und der Watcher gedroppt. Frontend (App.vue): - Neue Sektion "Cloud-Files (OneDrive-Style)" nur sichtbar wenn die Plattform es unterstuetzt (cloud_files_supported). - Ordner-Picker + Aktivieren/Deaktivieren-Button. - Fehlermeldungen + Sync-Log-Eintraege. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -31,6 +31,54 @@ const newPathLocal = ref("");
|
||||
const newPathServerFolder = ref("");
|
||||
const newPathServerId = ref(null);
|
||||
const newPathMode = ref("virtual");
|
||||
|
||||
// Cloud-Files (Windows cfapi / Linux FUSE)
|
||||
const cloudFilesSupported = ref(false);
|
||||
const cloudFilesActive = ref(false);
|
||||
const cloudFilesBusy = ref(false);
|
||||
const cloudFilesMountPoint = ref("");
|
||||
const cloudFilesError = ref("");
|
||||
|
||||
async function checkCloudFilesSupport() {
|
||||
try { cloudFilesSupported.value = await invoke("cloud_files_supported"); }
|
||||
catch { cloudFilesSupported.value = false; }
|
||||
}
|
||||
|
||||
async function browseCfMount() {
|
||||
try {
|
||||
const selected = await dialogOpen({ directory: true, multiple: false,
|
||||
title: "Cloud-Files-Ordner waehlen" });
|
||||
if (selected) cloudFilesMountPoint.value = selected;
|
||||
} catch { /* cancelled */ }
|
||||
}
|
||||
|
||||
async function enableCloudFiles() {
|
||||
cloudFilesError.value = "";
|
||||
cloudFilesBusy.value = true;
|
||||
try {
|
||||
await invoke("cloud_files_enable", { mountPoint: cloudFilesMountPoint.value });
|
||||
cloudFilesActive.value = true;
|
||||
syncLog.value = [`[${ts()}] Cloud-Files aktiviert: ${cloudFilesMountPoint.value}`, ...syncLog.value].slice(0, 200);
|
||||
} catch (err) {
|
||||
cloudFilesError.value = String(err);
|
||||
} finally {
|
||||
cloudFilesBusy.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function disableCloudFiles() {
|
||||
cloudFilesError.value = "";
|
||||
cloudFilesBusy.value = true;
|
||||
try {
|
||||
await invoke("cloud_files_disable", { mountPoint: cloudFilesMountPoint.value });
|
||||
cloudFilesActive.value = false;
|
||||
syncLog.value = [`[${ts()}] Cloud-Files deaktiviert`, ...syncLog.value].slice(0, 200);
|
||||
} catch (err) {
|
||||
cloudFilesError.value = String(err);
|
||||
} finally {
|
||||
cloudFilesBusy.value = false;
|
||||
}
|
||||
}
|
||||
const serverFolders = ref([]);
|
||||
|
||||
// Local file browser
|
||||
@@ -289,6 +337,7 @@ function formatSize(b) {
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
checkCloudFilesSupport();
|
||||
// Try auto-login with saved credentials
|
||||
try {
|
||||
const saved = await invoke("load_saved_config");
|
||||
@@ -387,6 +436,24 @@ onUnmounted(() => { unlistenStatus?.(); unlistenLog?.(); unlistenError?.(); unli
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<!-- Cloud-Files (Windows Cloud Files API, OneDrive-artig) -->
|
||||
<div v-if="cloudFilesSupported" class="section">
|
||||
<div class="section-header">
|
||||
<h3>Cloud-Files (OneDrive-Style)</h3>
|
||||
<span v-if="cloudFilesActive" class="status-badge syncing">☁ aktiv</span>
|
||||
</div>
|
||||
<p class="hint">Dateien erscheinen als Platzhalter im Explorer mit Wolken-Icon und werden erst bei Zugriff geladen. Rechtsklick im Explorer → "Immer offline halten" oder "Speicher freigeben".</p>
|
||||
<div class="cf-row">
|
||||
<input v-model="cloudFilesMountPoint" placeholder="Ordner waehlen..." />
|
||||
<button class="btn-secondary" @click="browseCfMount">Durchsuchen</button>
|
||||
<button v-if="!cloudFilesActive" class="btn-primary" :disabled="!cloudFilesMountPoint || cloudFilesBusy" @click="enableCloudFiles">
|
||||
{{ cloudFilesBusy ? "Aktiviere..." : "Aktivieren" }}
|
||||
</button>
|
||||
<button v-else class="btn-secondary" :disabled="cloudFilesBusy" @click="disableCloudFiles">Deaktivieren</button>
|
||||
</div>
|
||||
<div v-if="cloudFilesError" class="error" style="margin-top:0.5rem">{{ cloudFilesError }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Sync Paths -->
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
@@ -604,6 +671,8 @@ body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;f
|
||||
.sp-actions{display:flex;align-items:center;gap:.375rem;flex-shrink:0}
|
||||
.sp-mode{font-size:.75rem;padding:.2rem .4rem;border-radius:4px;cursor:pointer;background:#f0f0f0}
|
||||
.sp-mode.Full{background:#e3f2fd;color:#1565c0}.sp-mode.Virtual{background:#f3e5f5;color:#7b1fa2}
|
||||
.cf-row{display:flex;gap:.5rem;align-items:center;flex-wrap:wrap}
|
||||
.cf-row input{flex:1;min-width:300px}
|
||||
.file-tree{max-height:250px;overflow-y:auto}
|
||||
.tree-item{display:flex;align-items:center;gap:.5rem;padding:.3rem 0;border-bottom:1px solid #f5f5f5;font-size:.85rem}
|
||||
.tree-item.indent{padding-left:1.5rem}.tree-icon{flex-shrink:0}.tree-name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||
|
||||
Reference in New Issue
Block a user