Compare commits

...

2 Commits

Author SHA1 Message Date
Stefan Hacker b3da50e6ce fix: Server->Client Sync + File Locking repariert
Server->Client Sync:
- Server sendet Timestamps ohne Timezone (2026-04-11T12:49:24.735436)
- parse_from_rfc3339 braucht Timezone -> schlug still fehl
- Client dachte IMMER er sei neuer -> Upload statt Download
- Fix: parse_server_time() akzeptiert beides (mit/ohne Timezone)
- Probiert RFC3339, dann NaiveDateTime mit Microseconds, dann ohne

File Locking:
- open_cloud_file nutzte API-Clone vom SyncEngine (evtl. alter Token)
- Jetzt direkt state.api (immer aktueller Token nach Refresh)
- Lock wird zuverlaessig gesetzt beim Oeffnen von .cloud Dateien

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 02:05:10 +02:00
Stefan Hacker a445256d86 fix: Alle Rust-Warnings bereinigt
- unused variables: Underscore-Prefix (_real_path, _had_changes, _file_id)
- dead_code: #[allow(dead_code)] fuer zukuenftige Methoden
  (open_cloud_file, close_cloud_file, get_changes, LockResponse, SyncChangesResponse)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 02:02:53 +02:00
3 changed files with 32 additions and 13 deletions
+6 -8
View File
@@ -19,7 +19,7 @@ use std::collections::HashMap;
/// Tracks a file opened from a .cloud placeholder
#[derive(Clone, Debug)]
struct OpenedFile {
file_id: i64,
_file_id: i64,
real_path: PathBuf,
cloud_name: String, // original .cloud filename
}
@@ -230,10 +230,8 @@ async fn run_sync_now(state: State<'_, AppState>) -> Result<Vec<String>, String>
#[tauri::command]
async fn open_cloud_file(state: State<'_, AppState>, cloud_path: String) -> Result<String, String> {
let engine = {
let guard = state.sync_engine.lock().unwrap();
guard.as_ref().ok_or("Sync nicht gestartet")?.api.clone()
};
let engine = state.api.lock().unwrap().clone()
.ok_or("Nicht eingeloggt")?;
let path = PathBuf::from(&cloud_path);
let content = std::fs::read_to_string(&path).map_err(|e| e.to_string())?;
@@ -255,7 +253,7 @@ async fn open_cloud_file(state: State<'_, AppState>, cloud_path: String) -> Resu
// Track opened file for auto-close detection
state.opened_files.lock().unwrap().insert(file_id, OpenedFile {
file_id,
_file_id: file_id,
real_path: real_path.clone(),
cloud_name: path.file_name().unwrap().to_string_lossy().to_string(),
});
@@ -592,7 +590,7 @@ fn start_background_sync(
let state = app_w.state::<AppState>();
let watchers = state.watchers.lock().unwrap();
let mut had_changes = false;
let mut _had_changes = false;
for watcher in watchers.iter() {
while let Ok(change) = watcher.receiver.try_recv() {
@@ -608,7 +606,7 @@ fn start_background_sync(
ChangeKind::Deleted => format!("Geloescht: {}", name),
};
let _ = app_w.emit("file-change", msg);
had_changes = true;
_had_changes = true;
last_change = std::time::Instant::now();
pending = true;
}
@@ -41,12 +41,14 @@ pub struct SyncTreeResponse {
}
#[derive(Debug, Serialize, Deserialize)]
#[allow(dead_code)]
pub struct SyncChangesResponse {
pub changes: Vec<FileEntry>,
pub server_time: String,
}
#[derive(Debug, Serialize, Deserialize)]
#[allow(dead_code)]
pub struct LockResponse {
pub locked: Option<bool>,
pub locked_by: Option<String>,
@@ -127,6 +129,7 @@ impl MiniCloudApi {
Ok(data.tree)
}
#[allow(dead_code)]
pub async fn get_changes(&self, since: &str) -> Result<SyncChangesResponse, String> {
let url = format!("{}/api/sync/changes?since={}", self.server_url, since);
let resp = self.client.get(&url)
+23 -5
View File
@@ -109,8 +109,7 @@ impl SyncEngine {
let local_modified = std::fs::metadata(&local_path)
.and_then(|m| m.modified()).ok();
let server_modified = entry.updated_at.as_deref()
.and_then(|s| chrono::DateTime::parse_from_rfc3339(s).ok())
.map(|dt| std::time::SystemTime::from(dt));
.and_then(parse_server_time);
let server_is_newer = match (local_modified, server_modified) {
(Some(lt), Some(st)) => st > lt,
@@ -344,8 +343,7 @@ impl SyncEngine {
let local_modified = std::fs::metadata(&path)
.and_then(|m| m.modified()).ok();
let server_modified = se.updated_at.as_deref()
.and_then(|s| chrono::DateTime::parse_from_rfc3339(s).ok())
.map(|dt| std::time::SystemTime::from(dt));
.and_then(parse_server_time);
let server_is_newer = match (local_modified, server_modified) {
(Some(lt), Some(st)) => st > lt,
@@ -376,13 +374,14 @@ impl SyncEngine {
}
/// Open a .cloud placeholder file: download the real file, rename, return path
#[allow(dead_code)]
pub async fn open_cloud_file(&self, cloud_path: &Path) -> Result<PathBuf, String> {
let content = std::fs::read_to_string(cloud_path)
.map_err(|e| format!("Platzhalter lesen: {}", e))?;
let placeholder: CloudPlaceholder = serde_json::from_str(&content)
.map_err(|e| format!("Platzhalter ungueltig: {}", e))?;
let real_path = cloud_path.with_extension("");
let _real_path = cloud_path.with_extension("");
// Remove .cloud extension to get real filename
let real_path = cloud_path.parent().unwrap().join(&placeholder.name);
@@ -399,6 +398,7 @@ impl SyncEngine {
}
/// Close a previously opened file: sync back, recreate .cloud, unlock
#[allow(dead_code)]
pub async fn close_cloud_file(&self, real_path: &Path, file_id: i64) -> Result<(), String> {
// Upload changes
// We need the parent_id - for now upload to the same location
@@ -446,6 +446,24 @@ fn find_subtree(tree: &[FileEntry], folder_id: i64) -> Option<Vec<FileEntry>> {
None
}
/// Parse a server timestamp (may or may not have timezone)
fn parse_server_time(s: &str) -> Option<std::time::SystemTime> {
// Try with timezone first (RFC3339)
if let Ok(dt) = chrono::DateTime::parse_from_rfc3339(s) {
return Some(std::time::SystemTime::from(dt));
}
// Try without timezone (naive, assume UTC)
if let Ok(dt) = chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S%.f") {
let utc = dt.and_utc();
return Some(std::time::SystemTime::from(utc));
}
if let Ok(dt) = chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S") {
let utc = dt.and_utc();
return Some(std::time::SystemTime::from(utc));
}
None
}
pub fn compute_file_hash(path: &Path) -> String {
let data = match std::fs::read(path) {
Ok(d) => d,