/// <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"); }
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; } }
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; }
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); }); } }
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; } }
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); } } }
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(); } } }
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; } }
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); } } }
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; } }
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; }
public void TrySelectConversation(string conversationId) { if (conversationId != null && ConversationsDictionary.ContainsKey(conversationId)) { SelectedThread = null; SelectedConversation = ConversationsDictionary[conversationId]; } }
internal void Deselect() { if (SelectedConversation != null) { RequestedConversationId = SelectedConversation.ThreadId; } SelectedConversation = null; SelectedThread = null; }
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); } } }
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; }
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); } }
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()); } }
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; }
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; } }
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()); }
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"); }); }
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); } } }
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(); } } } }