feat(client): Windows Cloud-Files-API als File-Provider (OneDrive-Style)

Neuer Modus neben dem bestehenden Full-Sync: Dateien erscheinen im
Explorer als Platzhalter mit Wolken-Icon und werden erst bei Zugriff
vom Mini-Cloud-Server gestreamt.

Windows (MVP):
- CfRegisterSyncRoot + CfConnectSyncRoot
- CfCreatePlaceholders fuer jede Datei aus /api/sync/tree
- FETCH_DATA-Callback mit Range-basiertem HTTPS-Download + CfExecute
- CfSetPinState fuer manuelles "Immer offline halten"

Linux (Skelett):
- FUSE-Provider hinter Feature-Flag linux_fuse (libfuse3-dev)
- Stub-Funktionen - Implementierung folgt

macOS:
- Platzhalter, erfordert Apple-Signatur - spaeter

Tauri-Commands: cloud_files_supported/enable/disable/pin/unpin.
Cargo.toml: target-spezifische windows-rs Dependency.
Doku: clients/desktop/CLOUD_FILES.md

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Stefan Hacker
2026-04-14 16:19:22 +02:00
parent 2610e3b183
commit de1039fc7d
6 changed files with 776 additions and 0 deletions
@@ -0,0 +1,117 @@
//! Native File-Provider-Integration (Platzhalter-Dateien wie bei OneDrive).
//!
//! Auf Windows realisiert ueber die Cloud Files API (cfapi.dll), auf Linux
//! ueber FUSE (optional, hinter `linux_fuse`-Feature). macOS folgt spaeter
//! ueber NSFileProviderExtension (braucht Apple-Signatur).
//!
//! Der bestehende `sync::engine` bleibt unberuehrt und bietet weiterhin
//! den klassischen "kopiere-alles-lokal"-Modus. Der Cloud-Files-Modus
//! ist sozusagen "files-on-demand": Datei wird erst bei Zugriff geladen.
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
/// Ein Eintrag aus dem Mini-Cloud-Syncbaum, so wie er vom Server kommt.
/// Wird von beiden Plattformen genutzt, um Platzhalter / FUSE-Inodes zu
/// erzeugen.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RemoteEntry {
pub id: i64,
pub name: String,
pub parent_id: Option<i64>,
pub is_folder: bool,
pub size: i64,
/// UTC-ISO8601
pub modified_at: String,
/// SHA-256 falls vom Server ausgeliefert, sonst None.
pub checksum: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SyncState {
/// Datei existiert nur als Platzhalter (online-only).
Cloud,
/// Datei ist vollstaendig lokal vorhanden und aktuell.
InSync,
/// Lokal geaendert, Upload ausstehend.
PendingUpload,
/// Auf dem Server gesperrt (durch anderen Nutzer).
LockedByOther,
/// Durch diesen Client gesperrt.
LockedLocal,
}
#[cfg(windows)]
pub mod windows;
#[cfg(all(target_os = "linux", feature = "linux_fuse"))]
pub mod linux;
/// Registriere den Sync-Root beim Betriebssystem. Ruft je nach Plattform
/// cfapi/CfRegisterSyncRoot bzw. mountet ein FUSE-Dateisystem.
#[allow(unused_variables)]
pub fn register_sync_root(
mount_point: &PathBuf,
provider_name: &str,
account_id: &str,
) -> Result<(), String> {
#[cfg(windows)]
return windows::register_sync_root(mount_point, provider_name, account_id);
#[cfg(all(target_os = "linux", feature = "linux_fuse"))]
return linux::mount(mount_point);
#[cfg(not(any(windows, all(target_os = "linux", feature = "linux_fuse"))))]
Err("File-Provider-Integration fuer diese Plattform noch nicht verfuegbar".into())
}
#[allow(unused_variables)]
pub fn unregister_sync_root(mount_point: &PathBuf) -> Result<(), String> {
#[cfg(windows)]
return windows::unregister_sync_root(mount_point);
#[cfg(all(target_os = "linux", feature = "linux_fuse"))]
return linux::unmount(mount_point);
#[cfg(not(any(windows, all(target_os = "linux", feature = "linux_fuse"))))]
Err("File-Provider-Integration fuer diese Plattform noch nicht verfuegbar".into())
}
/// Erzeuge fuer alle Remote-Eintraege Platzhalter (cloud-only Dateien).
/// Ordner werden als echte Verzeichnisse angelegt, Dateien als
/// Platzhalter mit gespeicherten Metadaten (Groesse, Mtime, ID).
#[allow(unused_variables)]
pub fn populate_placeholders(
mount_point: &PathBuf,
entries: &[RemoteEntry],
) -> Result<(), String> {
#[cfg(windows)]
return windows::populate_placeholders(mount_point, entries);
#[cfg(all(target_os = "linux", feature = "linux_fuse"))]
return linux::populate(mount_point, entries);
#[cfg(not(any(windows, all(target_os = "linux", feature = "linux_fuse"))))]
Err("File-Provider-Integration fuer diese Plattform noch nicht verfuegbar".into())
}
/// Ist File-Provider-Integration auf dieser Plattform grundsaetzlich verfuegbar?
pub fn is_supported() -> bool {
cfg!(windows) || cfg!(all(target_os = "linux", feature = "linux_fuse"))
}
/// Markiere eine lokal bereits vorhandene Datei als "immer offline halten".
#[allow(unused_variables)]
pub fn pin_file(path: &PathBuf) -> Result<(), String> {
#[cfg(windows)]
return windows::set_pin_state(path, true);
#[cfg(not(windows))]
Err("Nur auf Windows unterstuetzt".into())
}
#[allow(unused_variables)]
pub fn unpin_file(path: &PathBuf) -> Result<(), String> {
#[cfg(windows)]
return windows::set_pin_state(path, false);
#[cfg(not(windows))]
Err("Nur auf Windows unterstuetzt".into())
}