Пример #1
0
        /// <summary>
        /// Marks and dispatches a message as read. Must not be called on a task which holds the handle lock.
        /// </summary>
        /// <param name="message"></param>
        public async Task SetMessageRead(long index, SignalMessage message, SignalConversation conversation)
        {
            Logger.LogTrace("SetMessageRead() locking");
            await SemaphoreSlim.WaitAsync(CancelSource.Token);

            try
            {
                Logger.LogTrace("SetMessageRead() locked");
                conversation = SignalDBContext.UpdateMessageRead(index, conversation);
                OutgoingMessages.SendMessage(SignalServiceSyncMessage.ForRead(new List <ReadMessage>()
                {
                    new ReadMessage(message.Author.ThreadId, message.ComposedTimestamp)
                }));
                await DispatchMessageRead(index + 1, conversation);
            }
            catch (Exception e)
            {
                Logger.LogError("SetMessageRead() failed: {0}\n{1}", e.Message, e.StackTrace);
            }
            finally
            {
                SemaphoreSlim.Release();
            }
            Logger.LogTrace("SetMessageRead() released");
        }
Пример #2
0
        private void UpdateHeader(SignalConversation thread)
        {
            ThreadDisplayName = thread.ThreadDisplayName;
            ThreadUsername    = thread.ThreadId;
            if (thread is SignalContact contact)
            {
                HeaderBackground = contact.Color != null?Utils.GetBrushFromColor((contact.Color)) :
                                       Utils.GetBrushFromColor(Utils.CalculateDefaultColor(contact.ThreadDisplayName));

                if (ThreadUsername != ThreadDisplayName)
                {
                    ThreadUsernameVisibility = Visibility.Visible;
                    SeparatorVisibility      = Visibility.Visible;
                }
                else
                {
                    ThreadUsernameVisibility = Visibility.Collapsed;
                    SeparatorVisibility      = Visibility.Collapsed;
                }
            }
            else
            {
                HeaderBackground         = Utils.Blue;
                ThreadUsernameVisibility = Visibility.Collapsed;
                SeparatorVisibility      = Visibility.Collapsed;
            }
        }
Пример #3
0
        public void AddOrUpdateConversation(SignalConversation conversation, SignalMessage updateMessage)
        {
            SignalConversation uiConversation;

            if (!ConversationsDictionary.ContainsKey(conversation.ThreadId))
            {
                uiConversation = conversation.Clone();
                Conversations.Add(uiConversation);
                ConversationsDictionary.Add(uiConversation.ThreadId, uiConversation);
            }
            else
            {
                uiConversation = ConversationsDictionary[conversation.ThreadId];
                uiConversation.LastActiveTimestamp  = conversation.LastActiveTimestamp;
                uiConversation.CanReceive           = conversation.CanReceive;
                uiConversation.LastMessage          = conversation.LastMessage;
                uiConversation.LastSeenMessage      = conversation.LastSeenMessage;
                uiConversation.LastSeenMessageIndex = conversation.LastSeenMessageIndex;
                uiConversation.MessagesCount        = conversation.MessagesCount;
                uiConversation.ThreadDisplayName    = conversation.ThreadDisplayName;
                uiConversation.UnreadCount          = conversation.UnreadCount;
                if (uiConversation is SignalContact ourContact && conversation is SignalContact newContact)
                {
                    ourContact.Color = newContact.Color;
                }
Пример #4
0
 public static void UpdateExpiresInLocked(SignalConversation thread, uint exp)
 {
     lock (DBLock)
     {
         using (var ctx = new SignalDBContext())
         {
             if (!thread.ThreadId.EndsWith("="))
             {
                 var contact = ctx.Contacts
                               .Where(c => c.ThreadId == thread.ThreadId)
                               .SingleOrDefault();
                 if (contact != null)
                 {
                     contact.ExpiresInSeconds = exp;
                 }
             }
             else
             {
                 var group = ctx.Groups
                             .Where(c => c.ThreadId == thread.ThreadId)
                             .SingleOrDefault();
                 if (group != null)
                 {
                     group.ExpiresInSeconds = exp;
                 }
             }
             ctx.SaveChanges();
         }
     }
 }
 internal async void ContactsList_SelectionChanged(object sender, SelectionChangedEventArgs e)
 {
     if (e.AddedItems.Count == 1 && SelectedThread != e.AddedItems[0])
     {
         try
         {
             Debug.WriteLine("SelectionChanged lock await");
             using (await ActionInProgress.LockAsync())
             {
                 Debug.WriteLine("SelectionChanged lock grabbed");
                 WelcomeVisibility = Visibility.Collapsed;
                 ThreadVisibility  = Visibility.Visible;
                 SelectedThread    = (SignalConversation)e.AddedItems[0];
                 View.Thread.Load(SelectedThread);
                 View.SwitchToStyle(View.GetCurrentViewStyle());
             }
             Debug.WriteLine("SelectionChanged lock released");
         }
         catch (Exception ex)
         {
             Debug.WriteLine(ex.Message);
             Debug.WriteLine(ex.StackTrace);
         }
     }
 }
 /// <summary>
 /// Saves the selected file in StorageApplicationPermissions store and saves conversation in database.
 /// </summary>
 /// <returns>Task that saves conversation in database.</returns>
 public async Task SaveCurrentConversationInDatabase()
 {
     if (SignalConversation != null)
     {
         SignalConversation.Draft = UserInputBar.InputText;
         if (SelectedFile == null)
         {
             if (!string.IsNullOrEmpty(SignalConversation.DraftFileTokens) &&
                 StorageApplicationPermissions.FutureAccessList.ContainsItem(SignalConversation.DraftFileTokens))
             {
                 StorageApplicationPermissions.FutureAccessList.Remove(SignalConversation.DraftFileTokens);
             }
             SignalConversation.DraftFileTokens = null;
         }
         else
         {
             if (string.IsNullOrEmpty(SignalConversation.DraftFileTokens) ||
                 !StorageApplicationPermissions.FutureAccessList.ContainsItem(SignalConversation.DraftFileTokens))
             {
                 SignalConversation.DraftFileTokens = StorageApplicationPermissions.FutureAccessList.Add(SelectedFile);
             }
             else
             {
                 // Just reuse the old key
                 StorageApplicationPermissions.FutureAccessList.AddOrReplace(SignalConversation.DraftFileTokens, SelectedFile);
             }
         }
         // SignalConversation can change while starting Thread.
         SignalConversation conversationToSave = SignalConversation;
         await Task.Run(() =>
         {
             SignalDBContext.InsertOrUpdateConversationLocked(conversationToSave);
         });
     }
 }
Пример #7
0
        internal async Task DispatchAddOrUpdateConversation(SignalConversation conversation, SignalMessage updateMessage)
        {
            List <Task> operations = new List <Task>();

            foreach (var dispatcher in Frames.Keys)
            {
                var taskCompletionSource = new TaskCompletionSource <bool>();
                await dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
                {
                    try
                    {
                        Frames[dispatcher].AddOrUpdateConversation(conversation, updateMessage);
                    }
                    catch (Exception e)
                    {
                        Logger.LogError("DispatchAddOrUpdateConversation() dispatch failed: {0}\n{1}", e.Message, e.StackTrace);
                    }
                    finally
                    {
                        taskCompletionSource.SetResult(false);
                    }
                });

                operations.Add(taskCompletionSource.Task);
            }
            foreach (var t in operations)
            {
                await t;
            }
        }
Пример #8
0
        public static List <SignalMessageContainer> GetMessagesLocked(SignalConversation thread, int startIndex, int count)
        {
            Logger.LogTrace("GetMessagesLocked() skip {0} take {1}", startIndex, count);
            lock (DBLock)
            {
                using (var ctx = new SignalDBContext())
                {
                    var messages = ctx.Messages
                                   .Where(m => m.ThreadId == thread.ThreadId)
                                   .Include(m => m.Content)
                                   .Include(m => m.Author)
                                   .Include(m => m.Attachments)
                                   .OrderBy(m => m.Id)
                                   .Skip(startIndex)
                                   .Take(count);

                    var containers = new List <SignalMessageContainer>(count);
                    foreach (var message in messages)
                    {
                        containers.Add(new SignalMessageContainer(message, startIndex));
                        startIndex++;
                    }
                    return(containers);
                }
            }
        }
Пример #9
0
        public static void DeleteMessage(SignalMessage message)
        {
            lock (DBLock)
            {
                using (var ctx = new SignalDBContext())
                {
                    ctx.Remove(message);
                    SignalConversation conversation = ctx.Contacts
                                                      .Where(c => c.ThreadId == message.ThreadId)
                                                      .Single();
                    conversation.MessagesCount       -= 1;
                    conversation.LastMessage          = null;
                    conversation.LastMessageId        = null;
                    conversation.LastSeenMessage      = null;
                    conversation.LastSeenMessageIndex = ctx.Messages
                                                        .Where(m => m.ThreadId == conversation.ThreadId)
                                                        .Count() - 1;

                    // also delete fts message
                    SignalMessageContent ftsMessage = ctx.Messages_fts.Where(m => m == message.Content)
                                                      .Single();
                    ctx.Remove(ftsMessage);
                    ctx.SaveChanges();
                }
            }
        }
Пример #10
0
        internal async Task DispatchMessageRead(long unreadMarkerIndex, SignalConversation conversation)
        {
            List <Task> operations = new List <Task>();

            foreach (var dispatcher in Frames.Keys)
            {
                var taskCompletionSource = new TaskCompletionSource <bool>();
                await dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
                {
                    try
                    {
                        Frames[dispatcher].HandleMessageRead(unreadMarkerIndex, conversation);
                    }
                    catch (Exception e)
                    {
                        Logger.LogError("DispatchMessageRead() dispatch failed: {0}\n{1}", e.Message, e.StackTrace);
                    }
                    finally
                    {
                        taskCompletionSource.SetResult(false);
                    }
                });

                operations.Add(taskCompletionSource.Task);
            }
            foreach (var waitHandle in operations)
            {
                await waitHandle;
            }
        }
Пример #11
0
        public static List <SignalMessageContainer> GetMessagesLocked(SignalConversation thread, int startIndex, int count)
        {
            Debug.WriteLine($"GetMessagesLocked {thread.ThreadId} Skip({startIndex}) Take({count})");
            lock (DBLock)
            {
                using (var ctx = new SignalDBContext())
                {
                    var messages = ctx.Messages
                                   .Where(m => m.ThreadId == thread.ThreadId)
                                   .Include(m => m.Content)
                                   .Include(m => m.Author)
                                   .Include(m => m.Attachments)
                                   .OrderBy(m => m.Id)
                                   .Skip(startIndex)
                                   .Take(count);

                    var containers = new List <SignalMessageContainer>(count);
                    foreach (var message in messages)
                    {
                        containers.Add(new SignalMessageContainer(message, startIndex));
                        startIndex++;
                    }
                    return(containers);
                }
            }
        }
Пример #12
0
 private void UpdateHeader(SignalConversation thread)
 {
     ThreadDisplayName = thread.ThreadDisplayName;
     ThreadUsername    = thread.ThreadId;
     if (thread is SignalContact)
     {
         SignalContact contact = (SignalContact)thread;
         HeaderBackground = Utils.GetBrushFromColor(contact.Color);
         if (ThreadUsername != ThreadDisplayName)
         {
             ThreadUsernameVisibility = Visibility.Visible;
             SeparatorVisibility      = Visibility.Visible;
         }
         else
         {
             ThreadUsernameVisibility = Visibility.Collapsed;
             SeparatorVisibility      = Visibility.Collapsed;
         }
     }
     else
     {
         HeaderBackground         = Utils.Blue;
         ThreadUsernameVisibility = Visibility.Collapsed;
         SeparatorVisibility      = Visibility.Collapsed;
     }
 }
Пример #13
0
        public void Load(SignalConversation conversation)
        {
            SignalConversation = conversation;
            if (SignalConversation is SignalContact contact)
            {
                Blocked            = contact.Blocked;
                SendMessageVisible = !Blocked;
            }
            else
            {
                // Need to make sure to reset the Blocked and SendMessageVisible values in case
                // a group chat is selected. Group chats can never be blocked.
                Blocked            = false;
                SendMessageVisible = !Blocked;
            }
            LastMarkReadRequest    = -1;
            InputTextBox.IsEnabled = false;
            DisposeCurrentThread();
            UpdateHeader(conversation);

            /*
             * When selecting a small (~650 messages) conversation after a bigger (~1800 messages) one,
             * ScrollToBottom would scroll so far south that the entire screen was white. Seems like the
             * scrollbar is not properly notified that the collection changed. I tried things like throwing
             * CollectionChanged (reset) event, but to no avail. This hack works, though.
             */
            ConversationItemsControl.ItemsSource = new List <object>();
            UpdateLayout();
            Collection = new VirtualizedCollection(conversation, this);
            ConversationItemsControl.ItemsSource = Collection;
            UpdateLayout();
            InputTextBox.IsEnabled = conversation.CanReceive;
            ScrollToUnread();
        }
        public AppendResult HandleMessage(SignalMessage message, SignalConversation conversation)
        {
            var result = Locator.MainPageInstance.HandleMessage(message, conversation);

            CheckNotification(conversation);
            return(result);
        }
 public void UpdateConversationDisplay(SignalConversation thread)
 {
     Model.ThreadDisplayName      = thread.ThreadDisplayName;
     Model.LastActiveTimestamp    = thread.LastActiveTimestamp;
     ConversationDisplayName.Text = thread.ThreadDisplayName;
     UnreadCount = thread.UnreadCount;
     LastMessage = Model.LastMessage?.Content.Content;
 }
Пример #16
0
 public void TrySelectConversation(string conversationId)
 {
     if (conversationId != null && ConversationsDictionary.ContainsKey(conversationId))
     {
         SelectedThread       = null;
         SelectedConversation = ConversationsDictionary[conversationId];
     }
 }
Пример #17
0
 internal void Deselect()
 {
     if (SelectedConversation != null)
     {
         RequestedConversationId = SelectedConversation.ThreadId;
     }
     SelectedConversation = null;
     SelectedThread       = null;
 }
Пример #18
0
 public void SaveAndDispatchSignalConversation(SignalConversation updatedConversation, SignalMessage updateMessage)
 {
     Logger.LogTrace("SaveAndDispatchSignalConversation() locking");
     SemaphoreSlim.Wait(CancelSource.Token);
     SignalDBContext.InsertOrUpdateConversationLocked(updatedConversation);
     DispatchAddOrUpdateConversation(updatedConversation, updateMessage);
     SemaphoreSlim.Release();
     Logger.LogTrace("SaveAndDispatchSignalConversation() released");
 }
 private void CheckNotification(SignalConversation conversation)
 {
     if (ApplicationView.GetForCurrentView().Id == App.MainViewId)
     {
         if (conversation.UnreadCount == 0)
         {
             NotificationsUtils.Withdraw(conversation.ThreadId);
         }
     }
 }
Пример #20
0
 internal void BackButton_Click(object sender, BackRequestedEventArgs e)
 {
     SelectedThread = null;
     View.Thread.DisposeCurrentThread();
     ThreadVisibility  = Visibility.Collapsed;
     WelcomeVisibility = Visibility.Visible;
     View.SwitchToStyle(View.GetCurrentViewStyle());
     Utils.DisableBackButton(BackButton_Click);
     e.Handled = true;
 }
Пример #21
0
        public void UIUpdateThread(SignalConversation thread)
        {
            SignalConversation uiThread = ThreadsDictionary[thread.ThreadId];

            uiThread.CanReceive = thread.CanReceive;
            uiThread.View.UpdateConversationDisplay(thread);
            if (SelectedThread == uiThread)
            {
                View.Thread.Update(thread);
            }
        }
Пример #22
0
 public void ConversationsList_SelectionChanged(object sender, SelectionChangedEventArgs e)
 {
     if (e.AddedItems.Count == 1)
     {
         Logger.LogDebug("ContactsList_SelectionChanged()");
         WelcomeVisibility = Visibility.Collapsed;
         ThreadVisibility  = Visibility.Visible;
         SelectedThread    = SelectedConversation;
         View.Thread.Load(SelectedThread);
         View.SwitchToStyle(View.GetCurrentViewStyle());
     }
 }
Пример #23
0
 public void AddThread(SignalConversation contact)
 {
     // only add a contact to Threads if it isn't already there
     foreach (var thread in Threads)
     {
         if (thread.ThreadId == contact.ThreadId)
         {
             return;
         }
     }
     Threads.Add(contact);
     ThreadsDictionary[contact.ThreadId] = contact;
 }
Пример #24
0
 public VirtualizedCollection(SignalConversation c)
 {
     Conversation = c;
     if (Conversation.LastSeenMessageIndex > 0 && Conversation.LastSeenMessageIndex < Conversation.MessagesCount)
     {
         UnreadMarkerIndex = (int)Conversation.LastSeenMessageIndex;
         UnreadMarker.SetText(Conversation.UnreadCount > 1 ? $"{Conversation.UnreadCount} new messages" : "1 new message");
     }
     else
     {
         UnreadMarkerIndex = -1;
     }
 }
Пример #25
0
        internal void DispatchAddOrUpdateConversation(SignalConversation conversation, SignalMessage updateMessage)
        {
            List <Task> operations = new List <Task>();

            foreach (var dispatcher in Frames.Keys)
            {
                operations.Add(dispatcher.RunTaskAsync(() =>
                {
                    Frames[dispatcher].AddOrUpdateConversation(conversation, updateMessage);
                }));
            }
            Task.WaitAll(operations.ToArray());
        }
Пример #26
0
 public async Task SendMessage(SignalMessage message, SignalConversation conversation)
 {
     await Task.Run(async() =>
     {
         Logger.LogTrace("SendMessage() locking");
         await SemaphoreSlim.WaitAsync(CancelSource.Token);
         Logger.LogDebug("SendMessage saving message " + message.ComposedTimestamp);
         SaveAndDispatchSignalMessage(message, conversation);
         OutgoingQueue.Add(message);
         SemaphoreSlim.Release();
         Logger.LogTrace("SendMessage() released");
     });
 }
Пример #27
0
        internal void DispatchHandleMessage(SignalMessage message, SignalConversation conversation)
        {
            List <Task> operations = new List <Task>();

            foreach (var dispatcher in Frames.Keys)
            {
                operations.Add(dispatcher.RunTaskAsync(() =>
                {
                    Frames[dispatcher].HandleMessage(message, conversation);
                }));
            }
            SignalMessageEvent?.Invoke(this, new SignalMessageEventArgs(message, Events.SignalMessageType.NormalMessage));
            Task.WaitAll(operations.ToArray());
        }
        public void Load(SignalConversation conversation)
        {
            bool conversationThreadIdChanged = SignalConversation?.ThreadId != conversation?.ThreadId;

            SignalConversation = conversation;
            if (SignalConversation is SignalContact contact)
            {
                Blocked            = contact.Blocked;
                SendMessageVisible = !Blocked;
            }
            else
            {
                // Need to make sure to reset the Blocked and SendMessageVisible values in case
                // a group chat is selected. Group chats can never be blocked.
                Blocked            = false;
                SendMessageVisible = !Blocked;
            }
            LastMarkReadRequest = -1;
            SendButtonEnabled   = false;

            /*
             * On app resume this method (Load()) gets called with the same conversation, but new object.
             * Only load draft if it is acutally a different conversation,
             * because on mobile app gets supended during file picking and
             * the new conversation does not have the DraftFileTokens
             */
            if (conversationThreadIdChanged)
            {
                // We don't need to wait for this
                _ = LoadDraft();
            }
            UserInputBar.FocusTextBox();
            DisposeCurrentThread();
            UpdateHeader(conversation);
            SpellCheckEnabled = GlobalSettingsManager.SpellCheckSetting;

            /*
             * When selecting a small (~650 messages) conversation after a bigger (~1800 messages) one,
             * ScrollToBottom would scroll so far south that the entire screen was white. Seems like the
             * scrollbar is not properly notified that the collection changed. I tried things like throwing
             * CollectionChanged (reset) event, but to no avail. This hack works, though.
             */
            ConversationItemsControl.ItemsSource = new List <object>();
            UpdateLayout();
            Collection = new VirtualizedCollection(conversation);
            ConversationItemsControl.ItemsSource = Collection;
            UpdateLayout();
            SendButtonEnabled = conversation.CanReceive;
            ScrollToUnread();
        }
        internal async Task DispatchHandleMessage(SignalMessage message, SignalConversation conversation)
        {
            List <Task <AppendResult> > operations = new List <Task <AppendResult> >();

            foreach (var dispatcher in Frames.Keys)
            {
                TaskCompletionSource <AppendResult> taskCompletionSource = new TaskCompletionSource <AppendResult>();
                await dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
                {
                    AppendResult ar = null;
                    try
                    {
                        ar = (Frames[dispatcher].HandleMessage(message, conversation));
                    }
                    catch (Exception e)
                    {
                        Logger.LogError("DispatchHandleMessage() dispatch failed: {0}\n{1}", e.Message, e.StackTrace);
                    }
                    finally
                    {
                        taskCompletionSource.SetResult(ar);
                    }
                });

                operations.Add(taskCompletionSource.Task);
            }
            SignalMessageEvent?.Invoke(this, new SignalMessageEventArgs(message, Events.SignalPipeMessageType.NormalMessage));
            if (message.Author != null)
            {
                bool wasInstantlyRead = false;
                foreach (var b in operations)
                {
                    AppendResult result = await b;
                    if (result != null && result.WasInstantlyRead)
                    {
                        UpdateMessageExpiration(message, conversation.ExpiresInSeconds);
                        var updatedConversation = SignalDBContext.UpdateMessageRead(message.ComposedTimestamp);
                        await DispatchMessageRead(updatedConversation);

                        wasInstantlyRead = true;
                        break;
                    }
                }
                if (!wasInstantlyRead)
                {
                    await DispatchHandleUnreadMessage(message);
                }
            }
        }
Пример #30
0
 public static void UpdateExpiresInLocked(SignalConversation thread)
 {
     lock (DBLock)
     {
         using (var ctx = new SignalDBContext())
         {
             var dbConversation = GetSignalConversationByThreadId(ctx, thread.ThreadId);
             if (dbConversation != null)
             {
                 dbConversation.ExpiresInSeconds = thread.ExpiresInSeconds;
                 ctx.SaveChanges();
             }
         }
     }
 }