public static UnifiedBubbleGroup CreateUnified(List <BubbleGroup> groups, BubbleGroup primaryGroup) { lock (BubbleGroupDatabase.OperationLock) { var unifiedGroupsToKill = new HashSet <UnifiedBubbleGroup>(); foreach (var group in groups) { if (group.IsUnified) { unifiedGroupsToKill.Add(group.Unified); @group.DeregisterUnified(); } } foreach (var unifiedGroup in unifiedGroupsToKill) { BubbleGroupManager.BubbleGroupsRemove(unifiedGroup); } BubbleGroupIndex.RemoveUnified(unifiedGroupsToKill.Select(x => x.ID).ToArray()); var unified = CreateUnifiedInternal(groups, primaryGroup); BubbleGroupIndex.AddUnified(unified); BubbleGroupManager.BubbleGroupsAdd(unified); BubbleGroupSettingsManager.SetUnreadIndicatorGuid(unified, unified.LastBubbleSafe().ID, false); return(unified); } }
protected internal void OnBubbleUpdated(VisualBubble bubble, BubbleGroup group) { if (Bubbles.Count >= BubblesCapSize) { Bubbles.RemoveAt(0); } var addedToEnd = false; var unreadIndicatorGuid = BubbleGroupSettingsManager.GetUnreadIndicatorGuid(this); for (int i = Bubbles.Count - 1; i >= 0; i--) { var nBubble = Bubbles[i]; var unreadIndicatorIndex = -1; if (unreadIndicatorGuid != null && unreadIndicatorGuid == nBubble.ID) { unreadIndicatorIndex = i; } if (nBubble.Time <= bubble.Time) { // adding it to the end, we can do a simple contract if (i == Bubbles.Count - 1) { addedToEnd = true; Bubbles.Add(bubble); if (bubble.Direction == Bubble.BubbleDirection.Incoming) { BubbleGroupSettingsManager.SetUnread(this, true); } } // inserting, do a full contract else { Bubbles.Insert(i + 1, bubble); } if (i >= unreadIndicatorIndex && bubble.Direction == Bubble.BubbleDirection.Outgoing) { BubbleGroupSettingsManager.SetUnreadIndicatorGuid(this, bubble.ID, false); } break; } // could not find a valid place to insert, then skip insertion. if (i == 0) { return; } } if (SendingGroup != group && addedToEnd) { SendingGroup = group; BubbleGroupEvents.RaiseSendingServiceChange(this); } RaiseBubbleInserted(bubble); }
private static BubbleGroup AddNewInternal(VisualBubble newBubble, bool raiseBubbleInserted) { var group = new BubbleGroup(newBubble, null, false); BubbleGroupSettingsManager.SetUnreadIndicatorGuid(group, group.LastBubbleSafe().ID, true); if (ServiceManager.IsRunning(@group.Service)) { newBubble.Service.NewBubbleGroupCreated(@group).ContinueWith(x => { // force the UI to refetch the photo @group.IsPhotoSetFromService = false; BubbleManager.SendSubscribe(@group, true); BubbleGroupUpdater.Update(@group); }); } BubbleGroupManager.BubbleGroupsAdd(@group); BubbleGroupDatabase.AddBubble(@group, newBubble); if (raiseBubbleInserted) { try { BubbleGroupEvents.RaiseBubbleInserted(newBubble, @group); } catch (Exception ex) { Utils.DebugPrint( "Error in notifying the interface that the bubble group has been updated (" + newBubble.Service.Information.ServiceName + "): " + ex.Message); } } BubbleGroupUpdater.Update(@group); return(@group); }
public static bool LoadFullyIfNeeded(BubbleGroup group, bool sync = false) { if (@group == null) { return(false); } var loadedSomething = false; lock (BubbleGroupDatabase.OperationLock) { var unifiedGroup = @group as UnifiedBubbleGroup; var adjustUnifiedGroupUnreadIndicatorIfExists = false; var associatedGroups = unifiedGroup != null ? unifiedGroup.Groups.ToList() : new[] { @group }.ToList(); var associatedPartiallyLoadedGroups = associatedGroups.Where(x => x.PartiallyLoaded).ToList(); foreach (var partiallyLoadedGroup in associatedPartiallyLoadedGroups) { var partiallyLoadedBubblesToRemove = partiallyLoadedGroup.Bubbles.ToList(); var rollingBack = false; TryAgain: try { loadedSomething = true; partiallyLoadedGroup.PartiallyLoaded = false; foreach (var bubble in BubbleGroupDatabase.FetchBubbles(partiallyLoadedGroup).Reverse()) { partiallyLoadedGroup.Bubbles.Add(bubble); } foreach (var partiallyLoadedBubbleToRemove in partiallyLoadedBubblesToRemove) { partiallyLoadedGroup.Bubbles.Remove(partiallyLoadedBubbleToRemove); } if (partiallyLoadedGroup.Bubbles.Count < 1) { foreach (var partiallyLoadedBubbleToRemove in partiallyLoadedBubblesToRemove) { partiallyLoadedGroup.Bubbles.Add(partiallyLoadedBubbleToRemove); } } } catch (Exception ex) { Utils.DebugPrint("Failed to fully load partially loaded group " + partiallyLoadedGroup.ID + ": " + ex); if (!rollingBack) { Utils.DebugPrint("Attempting to roll back the last transaction...."); BubbleGroupSettingsManager.SetUnreadIndicatorGuid(partiallyLoadedGroup, null, false); adjustUnifiedGroupUnreadIndicatorIfExists = true; rollingBack = true; var lastModifiedIndex = BubbleGroupIndex.GetLastModifiedIndex(partiallyLoadedGroup.ID); if (lastModifiedIndex.HasValue) { try { BubbleGroupDatabase.RollBackTo(partiallyLoadedGroup, lastModifiedIndex.Value); goto TryAgain; } catch (Exception ex2) { Utils.DebugPrint("Failed to rollback: " + ex2); // fall-through. It's unrecoverable! } } else { // fall-through. It's unrecoverable! } } Utils.DebugPrint("Partially loaded group is dead. Killing and restarting (lost data occurred)."); BubbleGroupDatabase.Kill(partiallyLoadedGroup); BubbleGroupSync.RemoveFromSync(partiallyLoadedGroup); BubbleGroupDatabase.AddBubbles(partiallyLoadedGroup, partiallyLoadedBubblesToRemove.ToArray()); } } if (unifiedGroup != null && !unifiedGroup.UnifiedGroupLoaded) { Populate(unifiedGroup); } if (unifiedGroup != null && adjustUnifiedGroupUnreadIndicatorIfExists) { BubbleGroupSettingsManager.SetUnreadIndicatorGuid(unifiedGroup, null, false); } } if (!sync && loadedSomething) { Task.Factory.StartNew(() => { var unifiedGroup = @group as UnifiedBubbleGroup; if (unifiedGroup != null) { BubbleQueueManager.Send(unifiedGroup.Groups.Where(x => ServiceManager.IsRunning(x.Service)) .Select(x => x.Service.Information.ServiceName).ToArray()); } else if (ServiceManager.IsRunning(@group.Service)) { BubbleQueueManager.Send(new[] { @group.Service.Information.ServiceName }); } BubbleQueueManager.SetNotQueuedToFailures(@group); }); } return(loadedSomething); }
internal static BubbleGroup Group(VisualBubble vb, bool resend = false, bool insertAtBottom = false) { lock (BubbleGroupDatabase.OperationLock) { Utils.DebugPrint("Grouping an " + vb.Direction + " bubble on service " + vb.Service.Information.ServiceName); var theGroup = BubbleGroupManager.FindWithAddress(vb.Service, vb.Address); BubbleGroupFactory.LoadFullyIfNeeded(theGroup); var duplicate = false; var newGroup = false; if (theGroup == null) { Utils.DebugPrint(vb.Service.Information.ServiceName + " unable to find suitable group. Creating a new one."); theGroup = new BubbleGroup(vb, null, false); newGroup = true; Utils.DebugPrint("GUID of new group: " + theGroup.ID); BubbleGroupSettingsManager.SetUnreadIndicatorGuid(theGroup, theGroup.LastBubbleSafe().ID, true); vb.Service.NewBubbleGroupCreated(theGroup).ContinueWith(x => { // force the UI to refetch the photo theGroup.IsPhotoSetFromService = false; SendSubscribe(theGroup, true); BubbleGroupUpdater.Update(theGroup); }); BubbleGroupManager.BubbleGroupsAdd(theGroup); } else { if (resend) { if (vb.Status == Bubble.BubbleStatus.Failed) { UpdateStatus(vb, Bubble.BubbleStatus.Waiting, theGroup); } return(theGroup); } var visualBubbleServiceId = vb.Service as IVisualBubbleServiceId; if (visualBubbleServiceId != null && visualBubbleServiceId.DisctinctIncomingVisualBubbleIdServices()) { if (vb.IdService != null) { duplicate = theGroup.Bubbles.FirstOrDefault(x => x.GetType() == vb.GetType() && x.IdService == vb.IdService) != null; } if (!duplicate && vb.IdService2 != null) { duplicate = theGroup.Bubbles.FirstOrDefault(x => x.GetType() == vb.GetType() && x.IdService2 == vb.IdService2) != null; } } if (!duplicate) { Utils.DebugPrint(vb.Service.Information.ServiceName + " found a group. Adding."); if (insertAtBottom) { var lastBubble = theGroup.LastBubbleSafe(); if (lastBubble.Time > vb.Time) { vb.Time = lastBubble.Time; } } theGroup.InsertByTime(vb); } else { Utils.DebugPrint("Yuck. It's a duplicate bubble. No need to readd: " + vb.IdService + ", " + vb.IdService2); } } try { if (theGroup.IsParty && !string.IsNullOrWhiteSpace(vb.ParticipantAddressNickname)) { var participantAddressNicknamesArray = theGroup.ParticipantNicknames; if (participantAddressNicknamesArray == null) { participantAddressNicknamesArray = new DisaParticipantNickname[0]; } var participantAddressNicknames = participantAddressNicknamesArray.ToList(); var changed = false; var adding = true; foreach (var participantAddressNickname in participantAddressNicknames) { if (theGroup.Service.BubbleGroupComparer(participantAddressNickname.Address, vb.ParticipantAddress)) { if (participantAddressNickname.Nickname != vb.ParticipantAddressNickname) { participantAddressNickname.Nickname = vb.ParticipantAddressNickname; changed = true; } adding = false; break; } } if (adding) { participantAddressNicknames.Add(new DisaParticipantNickname { Address = vb.ParticipantAddress, Nickname = vb.ParticipantAddressNickname, }); } if (changed || adding) { theGroup.ParticipantNicknames = participantAddressNicknames.ToArray(); } } } catch (Exception ex) { Utils.DebugPrint("Failed to insert/update participant nickname into cache: " + ex); } if (!duplicate) { Utils.DebugPrint("Inserting bubble into database group!"); try { if (newGroup) { BubbleGroupDatabase.AddBubble(theGroup, vb); } else { BubbleGroupDatabase.InsertBubbleByTime(theGroup, vb); } } catch (Exception ex) { Utils.DebugPrint("Bubble failed to be inserting/added into the group " + theGroup.ID + ": " + ex); } try { BubbleGroupEvents.RaiseBubbleInserted(vb, theGroup); } catch (Exception ex) { Utils.DebugPrint( "Error in notifying the interface that the bubble group has been updated (" + vb.Service.Information.ServiceName + "): " + ex.Message); } } return(theGroup); } }
public void InsertByTime(VisualBubble b) { if (Bubbles.Count >= BubblesCapSize) { Bubbles.RemoveAt(0); } if (!(this is ComposeBubbleGroup)) { var unreadIndicatorGuid = BubbleGroupSettingsManager.GetUnreadIndicatorGuid(this); for (int i = Bubbles.Count - 1; i >= 0; i--) { var nBubble = Bubbles[i]; var unreadIndicatorIndex = -1; if (unreadIndicatorGuid != null && unreadIndicatorGuid == nBubble.ID) { unreadIndicatorIndex = i; } if (nBubble.Time <= b.Time) { // adding it to the end, we can do a simple contract if (i == Bubbles.Count - 1) { Bubbles.Add(b); if (b.Direction == Bubble.BubbleDirection.Incoming) { BubbleGroupSettingsManager.SetUnread(this, true); } } // inserting, do a full contract else { Bubbles.Insert(i + 1, b); } if (i >= unreadIndicatorIndex && b.Direction == Bubble.BubbleDirection.Outgoing) { BubbleGroupSettingsManager.SetUnreadIndicatorGuid(this, b.ID, false); } break; } // could not find a valid place to insert, then skip insertion. if (i == 0) { return; } } } if (Unified == null) { _bubblesInsertedCount++; if (_bubblesInsertedCount % 100 == 0) { if (BubbleGroupSync.SupportsSyncAndIsRunning(this)) { Action doSync = async() => { using (Platform.AquireWakeLock("DisaSync")) { await Utils.Delay(1000); await BubbleGroupSync.Sync(this, true); } }; doSync(); } } } RaiseBubbleInserted(b); RaiseUnifiedBubblesUpdatedIfUnified(b); }
internal static BubbleGroup Group(VisualBubble vb, bool resend = false, bool insertAtBottom = false) { lock (BubbleGroupDatabase.OperationLock) { Utils.DebugPrint("Grouping an " + vb.Direction + " bubble on service " + vb.Service.Information.ServiceName); AddUrlMarkupIfNeeded(vb); var theGroup = BubbleGroupManager.FindWithAddress(vb.Service, vb.Address); BubbleGroupFactory.LoadFullyIfNeeded(theGroup); var duplicate = false; var newGroup = false; if (theGroup == null) { Utils.DebugPrint(vb.Service.Information.ServiceName + " unable to find suitable group. Creating a new one."); theGroup = new BubbleGroup(vb, null, false); newGroup = true; Utils.DebugPrint("GUID of new group: " + theGroup.ID); BubbleGroupSettingsManager.SetUnreadIndicatorGuid(theGroup, theGroup.LastBubbleSafe().ID, true); vb.Service.NewBubbleGroupCreated(theGroup).ContinueWith(x => { // force the UI to refetch the photo theGroup.IsPhotoSetFromService = false; SendSubscribe(theGroup, true); BubbleGroupUpdater.Update(theGroup); }); BubbleGroupManager.BubbleGroupsAdd(theGroup); } else { if (resend) { if (vb.Status == Bubble.BubbleStatus.Failed) { UpdateStatus(vb, Bubble.BubbleStatus.Waiting, theGroup); } return(theGroup); } // Does the Service for this VisualBubble require that the VisualBubble's IdService and IdService2 // be distinct? var visualBubbleServiceId = vb.Service as IVisualBubbleServiceId; if (visualBubbleServiceId != null && visualBubbleServiceId.DisctinctIncomingVisualBubbleIdServices()) { // Ok, we need to be distinct, BUT do the VisualBubble Type's have to be distinct as well? var checkType = true; if (!DisaFrameworkMethods.Missing(vb.Service, DisaFrameworkMethods.IVisualBubbleServiceIdCheckType)) { checkType = visualBubbleServiceId.CheckType(); } // Ok, now does the Service have special additional logic it wants to use for the distinction comparison? // Example: For Telegram, we allow an ImageBubble immediately followed by a TextBubble to have the // same VisualBubble.IdService - as this represents an image with a caption in Telegram. if (DisaFrameworkMethods.Missing(vb.Service, DisaFrameworkMethods.IVisualBubbleServiceIdVisualBubbleIdComparer)) { // Normal distinction checks if (vb.IdService != null) { duplicate = theGroup.Bubbles.FirstOrDefault(x => (!checkType || x.GetType() == vb.GetType()) && x.IdService == vb.IdService) != null; } if (!duplicate && vb.IdService2 != null) { duplicate = theGroup.Bubbles.FirstOrDefault(x => (!checkType || x.GetType() == vb.GetType()) && x.IdService2 == vb.IdService2) != null; } } else { // Special additional Service defined distinction checks if (vb.IdService != null) { var duplicateBubble = theGroup.Bubbles.FirstOrDefault(x => (!checkType || x.GetType() == vb.GetType()) && x.IdService == vb.IdService); duplicate = duplicateBubble == null ? false : visualBubbleServiceId.VisualBubbleIdComparer(left: duplicateBubble, right: vb); } if (!duplicate && vb.IdService2 != null) { var duplicateBubble = theGroup.Bubbles.FirstOrDefault(x => (!checkType || x.GetType() == vb.GetType()) && x.IdService2 == vb.IdService2); duplicate = duplicateBubble == null ? false : visualBubbleServiceId.VisualBubbleIdComparer(duplicateBubble, vb); } } } if (!duplicate) { Utils.DebugPrint(vb.Service.Information.ServiceName + " found a group. Adding."); if (insertAtBottom) { var lastBubble = theGroup.LastBubbleSafe(); if (lastBubble.Time > vb.Time) { vb.Time = lastBubble.Time; } } theGroup.InsertByTime(vb); } else { Utils.DebugPrint("Yuck. It's a duplicate bubble. No need to readd: " + vb.IdService + ", " + vb.IdService2); } } try { if (theGroup.IsParty && !string.IsNullOrWhiteSpace(vb.ParticipantAddressNickname)) { var participantAddressNicknamesArray = theGroup.ParticipantNicknames; if (participantAddressNicknamesArray == null) { participantAddressNicknamesArray = new DisaParticipantNickname[0]; } var participantAddressNicknames = participantAddressNicknamesArray.ToList(); var changed = false; var adding = true; foreach (var participantAddressNickname in participantAddressNicknames) { if (theGroup.Service.BubbleGroupComparer(participantAddressNickname.Address, vb.ParticipantAddress)) { if (participantAddressNickname.Nickname != vb.ParticipantAddressNickname) { participantAddressNickname.Nickname = vb.ParticipantAddressNickname; changed = true; } adding = false; break; } } if (adding) { participantAddressNicknames.Add(new DisaParticipantNickname { Address = vb.ParticipantAddress, Nickname = vb.ParticipantAddressNickname, }); } if (changed || adding) { theGroup.ParticipantNicknames = participantAddressNicknames.ToArray(); } } } catch (Exception ex) { Utils.DebugPrint("Failed to insert/update participant nickname into cache: " + ex); } if (!duplicate) { Utils.DebugPrint("Inserting bubble into database group!"); try { if (newGroup) { BubbleGroupDatabase.AddBubble(theGroup, vb); } else { BubbleGroupDatabase.InsertBubbleByTime(theGroup, vb); } } catch (Exception ex) { Utils.DebugPrint("Bubble failed to be inserting/added into the group " + theGroup.ID + ": " + ex); } try { BubbleGroupEvents.RaiseBubbleInserted(vb, theGroup); } catch (Exception ex) { Utils.DebugPrint( "Error in notifying the interface that the bubble group has been updated (" + vb.Service.Information.ServiceName + "): " + ex.Message); } } return(theGroup); } }
public void InsertByTime(VisualBubble b) { if (Bubbles.Count >= BubblesCapSize) { Bubbles.RemoveAt(0); } if (!(this is ComposeBubbleGroup)) { var unreadIndicatorGuid = BubbleGroupSettingsManager.GetUnreadIndicatorGuid(this); for (int i = Bubbles.Count - 1; i >= 0; i--) { var nBubble = Bubbles[i]; var unreadIndicatorIndex = -1; if (unreadIndicatorGuid != null && unreadIndicatorGuid == nBubble.ID) { unreadIndicatorIndex = i; } // IMPORTANT: Our Time field is specified in seconds, however scenarios are appearing // (e.g., bots) where messages are sent in on the same second but still require // proper ordering. In this case, Services may set a flag specifying a fallback // to the ID assigned by the Service (e.g. Telegram). if ((nBubble.Time == b.Time) && (nBubble.IsServiceIdSequence && b.IsServiceIdSequence)) { if (string.Compare( strA: nBubble.IdService, strB: b.IdService, ignoreCase: false, culture: CultureInfo.InvariantCulture) < 0) { // // Incoming bubble must be placed AFTER current bubble we are evaluating // if (i == Bubbles.Count - 1) { Bubbles.Add(b); if (b.Direction == Bubble.BubbleDirection.Incoming) { BubbleGroupSettingsManager.SetUnread(this, true); } } else { Bubbles.Insert(i + 1, b); if (i >= unreadIndicatorIndex && b.Direction == Bubble.BubbleDirection.Outgoing) { BubbleGroupSettingsManager.SetUnreadIndicatorGuid(this, b.ID, false); } } } else { // // Incoming bubble must be placed BEFORE current bubble we are evaluating // Bubbles.Insert(i, b); if (i >= unreadIndicatorIndex && b.Direction == Bubble.BubbleDirection.Outgoing) { BubbleGroupSettingsManager.SetUnreadIndicatorGuid(this, b.ID, false); } } break; } // OK, simpler scenario, incoming bubble must be placed AFTER current bubble we are evaluating else if (nBubble.Time <= b.Time) { // adding it to the end, we can do a simple contract if (i == Bubbles.Count - 1) { Bubbles.Add(b); if (b.Direction == Bubble.BubbleDirection.Incoming) { BubbleGroupSettingsManager.SetUnread(this, true); } } // inserting, do a full contract else { Bubbles.Insert(i + 1, b); } if (i >= unreadIndicatorIndex && b.Direction == Bubble.BubbleDirection.Outgoing) { BubbleGroupSettingsManager.SetUnreadIndicatorGuid(this, b.ID, false); } break; } // could not find a valid place to insert, then skip insertion. if (i == 0) { return; } } } if (Unified == null) { _bubblesInsertedCount++; if (_bubblesInsertedCount % 100 == 0) { if (BubbleGroupSync.SupportsSyncAndIsRunning(this)) { Action doSync = async() => { using (Platform.AquireWakeLock("DisaSync")) { await Utils.Delay(1000); await BubbleGroupSync.Sync(this, true); } }; doSync(); } } } RaiseBubbleInserted(b); RaiseUnifiedBubblesUpdatedIfUnified(b); }