Compare commits
2 Commits
0845659c84
...
2bd8a2e1b5
| Author | SHA1 | Date | |
|---|---|---|---|
| 2bd8a2e1b5 | |||
| 597dafc461 |
@@ -19,6 +19,7 @@ struct AppState {
|
||||
sync_engine: Mutex<Option<SyncEngine>>,
|
||||
username: Mutex<Option<String>>,
|
||||
watchers: Mutex<Vec<FileWatcher>>,
|
||||
locked_files: Mutex<Vec<i64>>, // file IDs we have locked on server
|
||||
sync_running: Arc<Mutex<bool>>,
|
||||
sync_paths: Mutex<Vec<SyncPath>>,
|
||||
}
|
||||
@@ -256,11 +257,20 @@ async fn open_cloud_file(state: State<'_, AppState>, cloud_path: String) -> Resu
|
||||
}
|
||||
eprintln!("[OpenCloud] Downloaded {} bytes", std::fs::metadata(&real_path).map(|m| m.len()).unwrap_or(0));
|
||||
|
||||
// Remove .cloud placeholder - file stays as real file (like "offline markieren")
|
||||
// Remove .cloud placeholder - file stays as real file
|
||||
// Changes will be synced automatically by the file watcher
|
||||
// User can manually "unmark offline" via right-click to get .cloud back
|
||||
// User can "unmark offline" or "unlock" via right-click
|
||||
std::fs::remove_file(&path).ok();
|
||||
|
||||
// Lock on server (prevents others from editing)
|
||||
match api.lock_file(file_id, "Desktop Sync Client").await {
|
||||
Ok(_) => {
|
||||
eprintln!("[OpenCloud] Locked on server");
|
||||
state.locked_files.lock().unwrap().push(file_id);
|
||||
}
|
||||
Err(e) => eprintln!("[OpenCloud] Lock failed (file may be locked by someone else): {}", e),
|
||||
}
|
||||
|
||||
// Open with default application for this file type
|
||||
eprintln!("[OpenCloud] Opening with default app: {}", real_path.display());
|
||||
open::that(&real_path)
|
||||
@@ -269,6 +279,14 @@ async fn open_cloud_file(state: State<'_, AppState>, cloud_path: String) -> Resu
|
||||
Ok(real_path.to_string_lossy().to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn unlock_file_cmd(state: State<'_, AppState>, file_id: i64) -> Result<String, String> {
|
||||
let api = state.api.lock().unwrap().clone().ok_or("Nicht eingeloggt")?;
|
||||
api.unlock_file(file_id).await?;
|
||||
state.locked_files.lock().unwrap().retain(|&id| id != file_id);
|
||||
Ok("Datei entsperrt".to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn get_file_tree(state: State<'_, AppState>) -> Result<serde_json::Value, String> {
|
||||
let api = state.api.lock().unwrap().clone().ok_or("Nicht eingeloggt")?;
|
||||
@@ -482,22 +500,36 @@ fn start_background_sync(
|
||||
}
|
||||
});
|
||||
|
||||
// Token refresh every 10 minutes
|
||||
// Token refresh (every 10 min) + Heartbeat for locks (every 60s)
|
||||
let app_hb = app.clone();
|
||||
let api_hb = api.clone();
|
||||
std::thread::spawn(move || {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
let mut api_mut = api_hb.clone();
|
||||
let mut tick = 0u32;
|
||||
|
||||
loop {
|
||||
std::thread::sleep(Duration::from_secs(600));
|
||||
std::thread::sleep(Duration::from_secs(10));
|
||||
tick += 10;
|
||||
let state = app_hb.state::<AppState>();
|
||||
|
||||
if let Ok(new_token) = rt.block_on(api_mut.refresh_token()) {
|
||||
if let Some(ref mut api) = *state.api.lock().unwrap() {
|
||||
api.access_token = new_token;
|
||||
// Heartbeat every 60 seconds for locked files
|
||||
if tick % 60 == 0 {
|
||||
let locked = state.locked_files.lock().unwrap().clone();
|
||||
for file_id in &locked {
|
||||
let _ = rt.block_on(api_mut.heartbeat(*file_id));
|
||||
}
|
||||
}
|
||||
|
||||
// Token refresh every 10 minutes
|
||||
if tick >= 600 {
|
||||
tick = 0;
|
||||
if let Ok(new_token) = rt.block_on(api_mut.refresh_token()) {
|
||||
if let Some(ref mut api) = *state.api.lock().unwrap() {
|
||||
api.access_token = new_token;
|
||||
}
|
||||
eprintln!("[Auth] Token refreshed");
|
||||
}
|
||||
eprintln!("[Auth] Token refreshed");
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -617,6 +649,7 @@ pub fn run() {
|
||||
username: Mutex::new(None),
|
||||
watchers: Mutex::new(Vec::new()),
|
||||
sync_running: Arc::new(Mutex::new(false)),
|
||||
locked_files: Mutex::new(Vec::new()),
|
||||
sync_paths: Mutex::new(Vec::new()),
|
||||
})
|
||||
.on_window_event(|window, event| {
|
||||
@@ -726,6 +759,7 @@ pub fn run() {
|
||||
open_cloud_file,
|
||||
get_file_tree,
|
||||
get_status,
|
||||
unlock_file_cmd,
|
||||
browse_sync_folder,
|
||||
mark_offline,
|
||||
unmark_offline,
|
||||
|
||||
@@ -84,6 +84,33 @@ async function doMarkOffline(file) {
|
||||
}
|
||||
}
|
||||
|
||||
async function doUnlockFile(file) {
|
||||
hideContextMenu();
|
||||
// Find file ID from server tree
|
||||
const serverFile = findFileInTree(fileTree.value, file.name);
|
||||
if (!serverFile) {
|
||||
syncLog.value = [`[${ts()}] Fehler: Datei nicht auf Server gefunden`, ...syncLog.value];
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await invoke("unlock_file_cmd", { fileId: serverFile.id });
|
||||
syncLog.value = [`[${ts()}] Entsperrt: ${file.name}`, ...syncLog.value].slice(0, 200);
|
||||
} catch (err) {
|
||||
syncLog.value = [`[${ts()}] Fehler: ${err}`, ...syncLog.value].slice(0, 200);
|
||||
}
|
||||
}
|
||||
|
||||
function findFileInTree(entries, name) {
|
||||
for (const e of entries) {
|
||||
if (e.name === name) return e;
|
||||
if (e.children) {
|
||||
const found = findFileInTree(e.children, name);
|
||||
if (found) return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async function doUnmarkOffline(file) {
|
||||
hideContextMenu();
|
||||
try {
|
||||
@@ -429,6 +456,9 @@ onUnmounted(() => { unlistenStatus?.(); unlistenLog?.(); unlistenError?.(); unli
|
||||
<div v-if="contextMenu.file?.is_offline" class="cm-item" @click="doUnmarkOffline(contextMenu.file)">
|
||||
☁ Nicht mehr offline (Platzhalter)
|
||||
</div>
|
||||
<div v-if="contextMenu.file?.is_offline" class="cm-item" @click="doUnlockFile(contextMenu.file)">
|
||||
🔓 Entsperren (Freigeben fuer andere)
|
||||
</div>
|
||||
<div class="cm-item" @click="hideContextMenu">Abbrechen</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user