fix: profile sync metadata merge and delta subscription matching

This commit is contained in:
zhom
2026-03-21 04:04:48 +04:00
parent 786acc4356
commit 0222c7e904
3 changed files with 59 additions and 27 deletions
+45 -9
View File
@@ -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<BrowserProfile> {
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,
+4 -14
View File
@@ -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;
+10 -4
View File
@@ -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/")