feat: Lokaler Datei-Browser mit Offline-Markierung + Kontextmenue
Datei-Browser im Client: - Zeigt lokalen Sync-Ordner mit allen Dateien an - Ordner navigierbar mit Breadcrumb - Status pro Datei: ☁ Cloud (Platzhalter) / 📄 Offline (echte Datei) - Badges: blaues "Cloud" oder gruenes "Offline" - Cloud-Dateien zeigen Originalgroesse aus .cloud-Metadaten - Aktualisiert sich automatisch nach jedem Sync Rechtsklick-Kontextmenue: - .cloud Datei: "Oeffnen (herunterladen)" + "Offline verfuegbar machen" - Echte Datei: "Nicht mehr offline (Platzhalter)" - Doppelklick auf Ordner = navigieren - Doppelklick auf .cloud = herunterladen + oeffnen Rust-Backend: - browse_sync_folder: Listet lokale Dateien mit Status auf (is_cloud, is_offline, cloud_size aus JSON-Metadaten) - Sortierung: Ordner zuerst, dann alphabetisch Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -216,6 +216,85 @@ async fn unlock_file_cmd(state: State<'_, AppState>, file_id: i64) -> Result<Str
|
||||
Ok("Datei entsperrt".to_string())
|
||||
}
|
||||
|
||||
// --- Local File Browser ---
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
struct LocalFileEntry {
|
||||
name: String,
|
||||
path: String,
|
||||
is_folder: bool,
|
||||
is_cloud: bool, // .cloud placeholder
|
||||
is_offline: bool, // real file (offline available)
|
||||
size: i64,
|
||||
cloud_size: Option<i64>, // original size from .cloud metadata
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn browse_sync_folder(state: State<'_, AppState>, sub_path: Option<String>) -> Result<Vec<LocalFileEntry>, String> {
|
||||
let paths = state.sync_paths.lock().unwrap();
|
||||
if paths.is_empty() {
|
||||
return Err("Keine Sync-Pfade konfiguriert".to_string());
|
||||
}
|
||||
|
||||
// If sub_path given, use it directly; otherwise use first sync path
|
||||
let base_dir = if let Some(ref sp) = sub_path {
|
||||
PathBuf::from(sp)
|
||||
} else {
|
||||
PathBuf::from(&paths[0].local_dir)
|
||||
};
|
||||
|
||||
if !base_dir.exists() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
let mut entries = Vec::new();
|
||||
let dir = std::fs::read_dir(&base_dir).map_err(|e| e.to_string())?;
|
||||
|
||||
for entry in dir.flatten() {
|
||||
let name = entry.file_name().to_string_lossy().to_string();
|
||||
let path = entry.path();
|
||||
|
||||
// Skip hidden files
|
||||
if name.starts_with('.') || name.starts_with('~') { continue; }
|
||||
|
||||
let is_folder = path.is_dir();
|
||||
let is_cloud = name.ends_with(".cloud");
|
||||
let size = std::fs::metadata(&path).map(|m| m.len() as i64).unwrap_or(0);
|
||||
|
||||
// For .cloud files, read the original size from JSON
|
||||
let mut cloud_size = None;
|
||||
let mut display_name = name.clone();
|
||||
if is_cloud {
|
||||
display_name = name.trim_end_matches(".cloud").to_string();
|
||||
if let Ok(content) = std::fs::read_to_string(&path) {
|
||||
if let Ok(json) = serde_json::from_str::<serde_json::Value>(&content) {
|
||||
cloud_size = json.get("size").and_then(|v| v.as_i64());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A real (non-.cloud) file = offline available
|
||||
let is_offline = !is_cloud && !is_folder;
|
||||
|
||||
entries.push(LocalFileEntry {
|
||||
name: display_name,
|
||||
path: path.to_string_lossy().to_string(),
|
||||
is_folder,
|
||||
is_cloud,
|
||||
is_offline,
|
||||
size,
|
||||
cloud_size,
|
||||
});
|
||||
}
|
||||
|
||||
// Sort: folders first, then by name
|
||||
entries.sort_by(|a, b| {
|
||||
b.is_folder.cmp(&a.is_folder).then(a.name.to_lowercase().cmp(&b.name.to_lowercase()))
|
||||
});
|
||||
|
||||
Ok(entries)
|
||||
}
|
||||
|
||||
// --- Offline-Markierung ---
|
||||
|
||||
#[tauri::command]
|
||||
@@ -477,6 +556,7 @@ pub fn run() {
|
||||
get_status,
|
||||
lock_file_cmd,
|
||||
unlock_file_cmd,
|
||||
browse_sync_folder,
|
||||
mark_offline,
|
||||
unmark_offline,
|
||||
])
|
||||
|
||||
Reference in New Issue
Block a user