public static Task Sync(BubbleGroup group, bool force = false) { return(Task.Factory.StartNew(() => { Utils.DebugPrint("Syncing convo " + group.ID); lock (SyncLock) { Utils.DebugPrint("Syncing convo " + group.ID + " (after lock)"); var somethingSynced = true; var groupsToSync = new List <BubbleGroup>(); var unifiedGroup = group as UnifiedBubbleGroup; if (unifiedGroup != null) { groupsToSync.AddRange(unifiedGroup.Groups); } else { groupsToSync.Add(group); } foreach (var groupToSync in groupsToSync) { if (!groupToSync.NeedsSync && !force) { continue; } var groupToSyncAgent = groupToSync.Service as Agent; if (groupToSyncAgent == null) { continue; } if (!ServiceManager.IsRunning(groupToSync.Service)) { continue; } try { ReSync: var syncTime = Time.GetNowUnixTimestamp(); var syncTask = groupToSyncAgent.Sync(groupToSync, Database.GetActionId(groupToSync)); syncTask.Wait(); var syncResult = syncTask.Result; if (!syncResult.EmptyResult && !syncResult.NullActionId && !syncResult.JustRefresh) { lock (BubbleGroupDatabase.OperationLock) { if (syncResult.ResultType == Result.Type.Purge) { if (BubbleGroupManager.LastBubbleSentTimestamps.ContainsKey(groupToSync.ID)) { lock (BubbleGroupManager.LastBubbleSentTimestamps) { var lastBubbleSentTime = BubbleGroupManager.LastBubbleSentTimestamps[groupToSync.ID]; if (lastBubbleSentTime >= syncTime) { Utils.DebugPrint("Sync has detected that a bubble was sent during the time the sync was fetched. Redoing sync..."); goto ReSync; } } } Utils.DebugPrint("Sync is purging the database for bubble group " + groupToSync.ID + " on service " + groupToSync.Service.Information.ServiceName); BubbleGroupDatabase.Kill(groupToSync); } if (!syncResult.EmptyInserts) { Utils.DebugPrint("Sync is inserting " + syncResult.Inserts.Length + " bubbles into " + groupToSync.ID + " on service " + groupToSync.Service.Information.ServiceName); if (syncResult.ResultType == Result.Type.Purge) { syncResult.Inserts.TimSort((x, y) => x.Time.CompareTo(y.Time)); BubbleGroupDatabase.AddBubbles(groupToSync, syncResult.Inserts); // If a purge is issued, then we need to carry over all sending bubbles to the new BubbleGroup being created. // Additionally, we need to remove any sending bubbles that are already on the insert list to avoid duplicates. var sendingBubbles = BubbleManager.FetchAllSending(groupToSync).ToList(); var sendingBubblesFiltered = new List <VisualBubble>(); foreach (var sendingBubble in sendingBubbles) { var hasInsertBubble = syncResult.Inserts.FirstOrDefault(x => x.ID == sendingBubble.ID) != null; if (!hasInsertBubble) { sendingBubblesFiltered.Add(sendingBubble); } } if (sendingBubblesFiltered.Any()) { BubbleGroupDatabase.InsertBubblesByTime(groupToSync, sendingBubblesFiltered.ToArray(), SearchDepth); } } else { BubbleGroupDatabase.InsertBubblesByTime(groupToSync, syncResult.Inserts, SearchDepth); } } if (!syncResult.EmptyUpdates) { Utils.DebugPrint("Sync is updating " + syncResult.Updates.Length + " bubbles into " + groupToSync.ID + " on service " + groupToSync.Service.Information.ServiceName); BubbleManager.Update(groupToSync, syncResult.Updates, SearchDepth); } } } else { Utils.DebugPrint("Sync for bubble group " + groupToSync.ID + " on service " + groupToSync.Service.Information.ServiceName + " returned an empty result (" + syncResult.ResultType.ToString() + ")."); somethingSynced = syncResult.JustRefresh; } if (!syncResult.NullActionId) { Database.SetActionId(groupToSync, syncResult.NewActionId); } groupToSync.NeedsSync = false; } catch (Exception ex) { Utils.DebugPrint("Failed to sync bubble group " + groupToSync.ID + " on service " + groupToSync.Service.Information.ServiceName + ": " + ex); somethingSynced = false; } } if (somethingSynced) { lock (BubbleGroupDatabase.OperationLock) { // Here we need to replace all the sending or downloading bubbles with the 'real' ones. // If this is never done, then on the UI, a 'sending' will never be set to 'sent', and any download // progress will never be updated. Why? Different memory objects. var sendingBubbles = BubbleManager.FetchAllSendingAndDownloading(group).ToList(); BubbleGroupFactory.UnloadFullLoad(group); BubbleGroupFactory.LoadFullyIfNeeded(group, true); if (sendingBubbles.Any()) { BubbleManager.Replace(group, sendingBubbles); } group.RaiseBubblesSynced(); } } } })); }