From 0222c7e90491912b86e85e286e4e20073a0baf5c Mon Sep 17 00:00:00 2001 From: zhom <2717306+zhom@users.noreply.github.com> Date: Sat, 21 Mar 2026 04:04:48 +0400 Subject: [PATCH] fix: profile sync metadata merge and delta subscription matching --- src-tauri/src/sync/engine.rs | 54 +++++++++++++++++++++++++----- src-tauri/src/sync/scheduler.rs | 18 +++------- src-tauri/src/sync/subscription.rs | 14 +++++--- 3 files changed, 59 insertions(+), 27 deletions(-) diff --git a/src-tauri/src/sync/engine.rs b/src-tauri/src/sync/engine.rs index ca3da82..123cfec 100644 --- a/src-tauri/src/sync/engine.rs +++ b/src-tauri/src/sync/engine.rs @@ -597,15 +597,35 @@ impl SyncEngine { let _ = self.sync_vpn(vpn_id, Some(app_handle)).await; } - // Update profile last_sync - let mut updated_profile = profile.clone(); - updated_profile.last_sync = Some( - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_secs(), - ); - let _ = profile_manager.save_profile(&updated_profile); + // Download remote metadata and merge changes (name, tags, notes, etc.) + let remote_metadata_key = format!("{}profiles/{}/metadata.json", key_prefix, profile_id); + if let Ok(remote_meta) = self.download_profile_metadata(&remote_metadata_key).await { + let mut updated_profile = profile.clone(); + // Merge fields that can be changed on other devices + updated_profile.name = remote_meta.name; + updated_profile.tags = remote_meta.tags; + updated_profile.note = remote_meta.note; + updated_profile.proxy_id = remote_meta.proxy_id; + updated_profile.vpn_id = remote_meta.vpn_id; + updated_profile.group_id = remote_meta.group_id; + updated_profile.last_sync = Some( + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_secs(), + ); + let _ = profile_manager.save_profile(&updated_profile); + } else { + // Fallback: just update last_sync + let mut updated_profile = profile.clone(); + updated_profile.last_sync = Some( + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_secs(), + ); + let _ = profile_manager.save_profile(&updated_profile); + } let _ = events::emit("profiles-changed", ()); let _ = events::emit( @@ -691,6 +711,22 @@ impl SyncEngine { Ok(()) } + async fn download_profile_metadata(&self, key: &str) -> SyncResult { + let stat = self.client.stat(key).await?; + if !stat.exists { + return Err(SyncError::InvalidData( + "Remote metadata not found".to_string(), + )); + } + + let presign = self.client.presign_download(key).await?; + let data = self.client.download_bytes(&presign.url).await?; + let profile: BrowserProfile = serde_json::from_slice(&data) + .map_err(|e| SyncError::SerializationError(format!("Failed to parse metadata: {e}")))?; + + Ok(profile) + } + async fn upload_profile_metadata( &self, profile_id: &str, diff --git a/src-tauri/src/sync/scheduler.rs b/src-tauri/src/sync/scheduler.rs index 083ec6a..029529a 100644 --- a/src-tauri/src/sync/scheduler.rs +++ b/src-tauri/src/sync/scheduler.rs @@ -153,30 +153,20 @@ impl SyncScheduler { } pub async fn is_profile_running(&self, profile_id: &str) -> bool { - // First check our internal tracking + // Check our internal tracking (authoritative — immediately updated by mark_profile_stopped) let running = self.running_profiles.lock().await; if running.contains(profile_id) { return true; } drop(running); - // Also check the actual profile state from ProfileManager - let profile_manager = ProfileManager::instance(); - if let Ok(profiles) = profile_manager.list_profiles() { - if let Some(profile) = profiles.iter().find(|p| p.id.to_string() == profile_id) { - if profile.process_id.is_some() { - return true; - } - } - } - - // Check if locked by another team member (profile in use remotely) - if crate::team_lock::TEAM_LOCK + // Check if locked by another device (profile in use remotely) + if crate::team_lock::PROFILE_LOCK .is_locked_by_another(profile_id) .await { log::debug!( - "Profile {} is locked by another team member, treating as running", + "Profile {} is locked on another device, treating as running", profile_id ); return true; diff --git a/src-tauri/src/sync/subscription.rs b/src-tauri/src/sync/subscription.rs index b1711bb..360d366 100644 --- a/src-tauri/src/sync/subscription.rs +++ b/src-tauri/src/sync/subscription.rs @@ -233,10 +233,16 @@ impl SyncSubscription { let key = Self::strip_team_prefix(raw_key); let work_item = if key.starts_with("profiles/") { - key - .strip_prefix("profiles/") - .and_then(|s| s.strip_suffix(".tar.gz")) - .map(|s| SyncWorkItem::Profile(s.to_string())) + // Match both bundle uploads (profiles/{id}.tar.gz) and delta sync updates + // (profiles/{id}/manifest.json, profiles/{id}/files/*, profiles/{id}/metadata.json) + let profile_id = key.strip_prefix("profiles/").and_then(|rest| { + // profiles/{id}.tar.gz → id + rest + .strip_suffix(".tar.gz") + // profiles/{id}/manifest.json → id + .or_else(|| rest.split('/').next().filter(|s| !s.is_empty())) + }); + profile_id.map(|s| SyncWorkItem::Profile(s.to_string())) } else if key.starts_with("proxies/") { key .strip_prefix("proxies/")