Ejemplo n.º 1
0
 internal static void SyncService(Service service)
 {
     if (ServiceManager.IsRunning(service))
     {
         Utils.DebugPrint("Refreshing service contacts...");
         service.RefreshPhoneBookContacts();
     }
     else
     {
         Utils.DebugPrint("Force refreshing service " + service.Information.ServiceName +
                          " isn't possible. Why? It isn't running!");
     }
     Utils.DebugPrint("Finished refreshing service contacts. Calling contacts update and bubble group update.");
     BubbleGroupUpdater.Update(service);
 }
Ejemplo n.º 2
0
        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);
        }
Ejemplo n.º 3
0
 public static Task ForceUpdate(Service service)
 {
     return(Task.Factory.StartNew(() =>
     {
         Utils.DebugPrint("Querying phone contacts...");
         _phoneBookContacts = Platform.GetPhoneBookContacts();
         if (service != null)
         {
             if (ServiceManager.IsRunning(service))
             {
                 Utils.DebugPrint("Refreshing service contacts...");
                 service.RefreshPhoneBookContacts();
             }
             else
             {
                 Utils.DebugPrint("Force refreshing service " + service.Information.ServiceName +
                                  " isn't possible. Why? It isn't running!");
             }
             Utils.DebugPrint("Finished refreshing service contacts. Calling contacts update and bubble group update.");
             BubbleGroupUpdater.Update(service);
         }
     }));
 }
Ejemplo n.º 4
0
        public static Task Start(Service service, bool smartStart = false, int smartStartSeconds = 10)
        {
            return(Task.Factory.StartNew(() =>
            {
                using (var wakeLock = Platform.AquireWakeLock("DisaStart"))
                {
                    if (IsRunning(service))
                    {
                        Utils.DebugPrint(
                            "The service is already running. Preventing possible deadlock. SmartStart? " +
                            smartStart);
                        return;
                    }

                    if (IsStarting(service))
                    {
                        Utils.DebugPrint(
                            "The service is being started. Preventing possible deadlock. SmartStart? " +
                            smartStart);
                        return;
                    }

                    Action epilogue = () => { GetFlags(service).Starting = false; };

                    lock (service)
                    {
                        GetFlags(service).Aborted = false;
                        GetFlags(service).AbortedSpecial = false;
                        GetFlags(service).Starting = true;
                        GetFlags(service).ManualSettingsNeeded = false;

                        Utils.DebugPrint("Loading settings for service " + service.Information.ServiceName);
                        try
                        {
                            var settings = SettingsManager.Load(service);
                            if (settings == null)
                            {
                                Utils.DebugPrint("Failed to load saved settings for "
                                                 + service.Information.ServiceName +
                                                 ". Will try to initialize with no settings...");
                                if (!service.InitializeDefault())
                                {
                                    Utils.DebugPrint(
                                        "Service doesn't allow initializing without settings. Needs manual input.");
                                    GetFlags(service).ManualSettingsNeeded = true;
                                    ServiceEvents.RaiseServiceManualSettingsNeeded(service);
                                }
                                else
                                {
                                    Utils.DebugPrint("Service initialized under no settings.");
                                }
                            }
                            else
                            {
                                Utils.DebugPrint("Loading saved settings! Initializing...");
                                if (service.Initialize(settings))
                                {
                                    Utils.DebugPrint("Successfully initialized service!");
                                }
                                else
                                {
                                    Utils.DebugPrint("Failed to initialize service. Needs manual input.");
                                    GetFlags(service).ManualSettingsNeeded = true;
                                    ServiceEvents.RaiseServiceManualSettingsNeeded(service);
                                }
                            }
                        }
                        catch (Exception ex)
                        {
                            Utils.DebugPrint("Failed: " + ex);
                            epilogue();
                            return;
                        }

                        Utils.DebugPrint("Starting service " + service.Information.ServiceName);

                        try
                        {
                            if (service.Information.UsesInternet &&
                                !Platform.HasInternetConnection())
                            {
                                throw new Exception("No internet connection. Cannot connect service: "
                                                    + service.Information.ServiceName);
                            }

                            StartInternal(service, wakeLock);
                        }
                        catch (ServiceSchedulerException ex)
                        {
                            Utils.DebugPrint("Problem in scheduler: " + ex.Message);
                            epilogue();
                            return;
                        }
                        catch (ServiceSpecialRestartException)
                        {
                            Utils.DebugPrint("Service " + service.Information.ServiceName +
                                             " is asking to be restarted on connect/authenticate. This should be called sparingly, Disa can easily " +
                                             "break under these circumstances. Restarting...");
                            StopInternal(service);
                            epilogue();
                            Start(service, smartStart, smartStartSeconds);
                            return;
                        }
                        catch (ServiceExpiredException)
                        {
                            Utils.DebugPrint("The service " + service.Information.ServiceName +
                                             " has expired. :(");
                            GetFlags(service).Aborted = true;
                            GetFlags(service).Expired = true;
                            ServiceEvents.RaiseServiceExpired(service);
                            StopInternal(service);
                            epilogue();
                            return;
                        }
                        catch (Exception ex)
                        {
                            if (smartStart)
                            {
                                StopInternal(service, false);

                                if (smartStartSeconds > 600)
                                {
                                    Utils.DebugPrint("Service " + service.Information.ServiceName +
                                                     " needs to wait over 10minutes to be restarted." +
                                                     " Killing SmartStart. The service will not be restarted. Reason: " + ex);
                                    epilogue();
                                    return;
                                }

                                Utils.DebugPrint("Service " + service.Information.ServiceName +
                                                 " failed to be started. SmartStart enabled. "
                                                 + "Service being scheduled to be re-started in T-" +
                                                 smartStartSeconds + " seconds! Reason: " + ex);

                                var hasSmartStart = new object();
                                service.HasSmartStart = hasSmartStart;

                                Platform.ScheduleAction(smartStartSeconds,
                                                        new WakeLockBalancer.ActionObject(() =>
                                {
                                    if (IsAborted(service))
                                    {
                                        Utils.DebugPrint(
                                            "Service " +
                                            service.Information
                                            .ServiceName +
                                            " tried to be started, but it deemed killed.");
                                        return;
                                    }

                                    if (service.HasSmartStart !=
                                        hasSmartStart)
                                    {
                                        Utils.DebugPrint(
                                            "This smart start has been invalidated. There " +
                                            "seems to be another one on the block.");
                                        return;
                                    }

                                    Utils.DebugPrint(
                                        "Smart start is firing the service " +
                                        service.Information
                                        .ServiceName +
                                        " up again!");

                                    StopInternal(service);
                                    Start(service, true, smartStartSeconds * 2);
                                }, WakeLockBalancer.ActionObject.ExecuteType.TaskWithWakeLock));

                                epilogue();
                                return;
                            }

                            Utils.DebugPrint("Failed to start service " + service.Information.ServiceName +
                                             " (No SmartStart) : " + ex);
                            StopInternal(service, false);
                            epilogue();
                            return;
                        }

                        BubbleManager.SendSubscribe(service, true);
                        BubbleManager.SendLastPresence(service);

                        service.ReceivingBubblesThread = new Thread(() =>
                        {
                            try
                            {
                                StartReceiveBubbles(service);
                            }
                            catch (ThreadAbortException)
                            {
                                Utils.DebugPrint(
                                    "Abort thread excepton in receiving bubbles on service (outer thread) " +
                                    service.Information.ServiceName);
                            }
                            catch (Exception ex)
                            {
                                Utils.DebugPrint(">>>>>>>>> " + ex.Message + " " + ex.StackTrace);
                            }
                            Utils.DebugPrint("Receiving bubbles for service " +
                                             service.Information.ServiceName + " has come to an end.");
                        });
                        service.ReceivingBubblesThread.Start();

                        GetFlags(service).Starting = false;

                        BubbleManager.SetNotQueuedToFailures(service);

                        Utils.Delay(1000).ContinueWith(x =>
                        {
                            BubbleGroupSync.ResetSyncsIfHasAgent(service);
                            BubbleGroupUpdater.Update(service);
                            BubbleQueueManager.Send(new[] { service.Information.ServiceName });
                            BubbleGroupManager.ProcessUpdateLastOnlineQueue(service);
                        });
                    }
                }
            }));
        }
Ejemplo n.º 5
0
        public static void OnPhoneContactsUpdated()
        {
            Task.Factory.StartNew(() =>
            {
                if (_isUpdating)
                {
                    Utils.DebugPrint("Contacts are already updating... Returning...");
                    return;
                }

                Action action = () =>
                {
                    lock (PhoneBookContactsLock)
                    {
                        using (new PhoneContactsUpdatingDisposable())
                        {
                            if (_phoneBookContacts == null)
                            {
                                Utils.DebugPrint("Phone contacts have not yet been used by the framework/services. Therefore, and update doesn't make sense. Skipping.");
                                return;
                            }

                            LastUpdate = Time.GetNowUnixTimestamp();

                            Utils.DebugPrint("Phone contacts have been updated! Querying phone contacts...");

                            var updatedPhoneContacts = Platform.GetPhoneBookContacts();

                            Utils.DebugPrint("Checking if any numbers/names have been updated...");

                            var updatedPhoneContactsNumbers =
                                (from phoneBookRelation in updatedPhoneContacts
                                 from phoneNumber in phoneBookRelation.PhoneNumbers
                                 select phoneNumber.Number).ToList();

                            var phoneBookContactsNumbers =
                                (from phoneBookRelation in _phoneBookContacts
                                 from phoneNumber in phoneBookRelation.PhoneNumbers
                                 select phoneNumber.Number).ToList();

                            var updatedPhoneContactsFullName =
                                (from phoneBookRelation in updatedPhoneContacts
                                 select phoneBookRelation.FullName).ToList();

                            var phoneContactsFullName =
                                (from phoneBookRelation in _phoneBookContacts
                                 select phoneBookRelation.FullName).ToList();

                            var phoneBooksEqual =
                                updatedPhoneContactsNumbers.Intersect(phoneBookContactsNumbers).Count() ==
                                updatedPhoneContactsNumbers.Union(phoneBookContactsNumbers).Count() &&
                                updatedPhoneContactsFullName.Intersect(phoneContactsFullName).Count() ==
                                updatedPhoneContactsFullName.Union(phoneContactsFullName).Count();

                            if (phoneBooksEqual)
                            {
                                Utils.DebugPrint(
                                    "Phone books are equal. No need to update contacts!");
                                return;
                            }

                            _phoneBookContacts = updatedPhoneContacts;

                            Utils.DebugPrint("Got phone contacts... updating running services...");

                            foreach (
                                var service in
                                ServiceManager.Running)
                            {
                                try
                                {
                                    service.RefreshPhoneBookContacts();
                                    BubbleGroupUpdater.Update(service);
                                }
                                catch (Exception ex)
                                {
                                    Utils.DebugPrint("Service " + service.Information.ServiceName +
                                                     " failed to refresh it contacts. " + ex.Message);
                                }
                            }
                        }
                    }
                };

                if (Time.GetNowUnixTimestamp() - LastUpdate < 10)
                {
                    Utils.DebugPrint(
                        "Contacts were updated less than 10 seconds ago... Waiting 10 seconds...");
                    Platform.ScheduleAction(10000, new WakeLockBalancer.ActionObject(() =>
                    {
                        action();
                    }, WakeLockBalancer.ActionObject.ExecuteType.TaskWithWakeLock));
                }
                else
                {
                    using (Platform.AquireWakeLock("DisaContactsUpdate"))
                    {
                        action();
                    }
                }
            });
        }
Ejemplo n.º 6
0
        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);

                    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);
                    }
                }

                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);
            }
        }
Ejemplo n.º 7
0
        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);
            }
        }
Ejemplo n.º 8
0
        public static void OnPhoneContactsUpdated()
        {
            Task.Factory.StartNew(() =>
            {
                if (_isUpdating)
                {
                    Utils.DebugPrint("Contacts are already updating... Returning...");
                    return;
                }

                Action action = () =>
                {
                    lock (PhoneBookContactsLock)
                    {
                        using (new PhoneContactsUpdatingDisposable())
                        {
                            if (_phoneBookContacts == null)
                            {
                                Utils.DebugPrint("Phone contacts have not yet been used by the framework/services. Therefore, and update doesn't make sense. Skipping.");
                                return;
                            }

                            Utils.DebugPrint("Phone contacts have been updated! Querying phone contacts...");

                            var updatedPhoneContacts = Platform.GetPhoneBookContacts();

                            Utils.DebugPrint("Checking if any numbers/names have been updated...");

                            var updatedPhoneContactsNumbers =
                                (from phoneBookRelation in updatedPhoneContacts
                                 from phoneNumber in phoneBookRelation.PhoneNumbers
                                 select phoneNumber.Number).ToList();

                            var phoneBookContactsNumbers =
                                (from phoneBookRelation in _phoneBookContacts
                                 from phoneNumber in phoneBookRelation.PhoneNumbers
                                 select phoneNumber.Number).ToList();

                            var updatedPhoneContactsFullName =
                                (from phoneBookRelation in updatedPhoneContacts
                                 select phoneBookRelation.FullName).ToList();

                            var phoneContactsFullName =
                                (from phoneBookRelation in _phoneBookContacts
                                 select phoneBookRelation.FullName).ToList();

                            var phoneBooksEqual =
                                updatedPhoneContactsNumbers.Intersect(phoneBookContactsNumbers).Count() ==
                                updatedPhoneContactsNumbers.Union(phoneBookContactsNumbers).Count() &&
                                updatedPhoneContactsFullName.Intersect(phoneContactsFullName).Count() ==
                                updatedPhoneContactsFullName.Union(phoneContactsFullName).Count();

                            if (phoneBooksEqual)
                            {
                                Utils.DebugPrint(
                                    "Phone books are equal. No need to update contacts!");
                                return;
                            }

                            _phoneBookContacts = updatedPhoneContacts;

                            Utils.DebugPrint("Got phone contacts... updating running services...");

                            var notRunningServices = ServiceManager.AllNoUnified.ToList();

                            foreach (
                                var service in
                                ServiceManager.RunningNoUnified)
                            {
                                try
                                {
                                    service.RefreshPhoneBookContacts();
                                    BubbleGroupUpdater.Update(service);
                                    ServiceEvents.RaiseContactsUpdated(service);
                                    notRunningServices.Remove(service);
                                }
                                catch (Exception ex)
                                {
                                    Utils.DebugPrint("Service " + service.Information.ServiceName +
                                                     " failed to refresh it contacts. " + ex.Message);
                                }
                            }

                            if (notRunningServices.Any())
                            {
                                SettingsChangedManager.SetNeedsContactSync(notRunningServices.ToArray(), true);
                            }
                            SaveCachedPhoneBookContacts(updatedPhoneContacts);
                        }
                    }
                };

                using (Platform.AquireWakeLock("DisaContactsUpdate"))
                {
                    action();
                }
            });
        }
Ejemplo n.º 9
0
        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);
            }
        }