Skip to content

Commit afe26af

Browse files
committed
Beginnings of disappearing messages
Use at your own risk. Messages get fully deleted once their timer is up. Missing things: Messages get deleted as soon as their received, not read. Expired messages don't get cleaned up by the background thread when it starts up. Expired messages don't get cleaned up when the app starts.
1 parent 229d1d0 commit afe26af

13 files changed

+297
-4
lines changed
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics;
4+
using System.Linq;
5+
using System.Text;
6+
using System.Threading.Tasks;
7+
using Signal_Windows.Models;
8+
using Signal_Windows.Storage;
9+
using Windows.UI.Core;
10+
11+
namespace Signal_Windows.Lib
12+
{
13+
public static class DisappearingMessagesManager
14+
{
15+
private static Dictionary<CoreDispatcher, ISignalFrontend> frames;
16+
17+
static DisappearingMessagesManager()
18+
{
19+
frames = new Dictionary<CoreDispatcher, ISignalFrontend>();
20+
}
21+
22+
public static void AddFrontend(CoreDispatcher coreDispatcher, ISignalFrontend frontend)
23+
{
24+
if (!frames.ContainsKey(coreDispatcher))
25+
{
26+
frames.Add(coreDispatcher, frontend);
27+
}
28+
}
29+
30+
public static void RemoveFrontend(CoreDispatcher coreDispatcher)
31+
{
32+
if (frames.ContainsKey(coreDispatcher))
33+
{
34+
frames.Remove(coreDispatcher);
35+
}
36+
}
37+
38+
public static void AddMessage(SignalMessage message)
39+
{
40+
if (message.ExpiresAt == 0)
41+
{
42+
return;
43+
}
44+
Action deleteTask = async () =>
45+
{
46+
DateTimeOffset expireTime = DateTimeOffset.FromUnixTimeMilliseconds(message.ExpiresAt);
47+
DateTimeOffset receivedTime = DateTimeOffset.FromUnixTimeMilliseconds(message.ReceivedTimestamp);
48+
TimeSpan deleteTimeSpan = expireTime - receivedTime;
49+
if (deleteTimeSpan < TimeSpan.Zero)
50+
{
51+
Debug.WriteLine($"deleteTimeSpan was less than 0: {deleteTimeSpan.ToString()}");
52+
Debug.WriteLine($"Deleting message: {message.Content.Content}");
53+
await DeleteMessage(message);
54+
return;
55+
}
56+
Debug.WriteLine($"Deleting message in {deleteTimeSpan.TotalSeconds} seconds");
57+
await Task.Delay(deleteTimeSpan);
58+
Debug.WriteLine($"Deleting message: {message.Content.Content}");
59+
await DeleteMessage(message);
60+
};
61+
Task.Run(deleteTask);
62+
}
63+
64+
private static async Task DeleteMessage(SignalMessage message)
65+
{
66+
List<Task> operations = new List<Task>();
67+
foreach (var dispatcher in frames.Keys)
68+
{
69+
var taskCompletionSource = new TaskCompletionSource<bool>();
70+
await dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
71+
{
72+
try
73+
{
74+
frames[dispatcher].HandleMessageDelete(message);
75+
}
76+
catch (Exception e)
77+
{
78+
}
79+
finally
80+
{
81+
taskCompletionSource.SetResult(false);
82+
}
83+
});
84+
operations.Add(taskCompletionSource.Task);
85+
}
86+
87+
foreach (var t in operations)
88+
{
89+
await t;
90+
}
91+
92+
foreach (var attachment in message.Attachments)
93+
{
94+
SignalDBContext.DeleteAttachment(attachment);
95+
}
96+
SignalDBContext.DeleteMessage(message);
97+
}
98+
}
99+
}

Signal-Windows.Lib/IncomingMessages.cs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,14 +322,26 @@ private async Task HandleExpirationUpdateMessage(SignalServiceEnvelope envelope,
322322
conversation = await SignalDBContext.GetOrCreateContactLocked(envelope.GetSource(), 0);
323323
}
324324
}
325+
conversation.ExpiresInSeconds = (uint)message.ExpiresInSeconds;
325326
SignalDBContext.UpdateExpiresInLocked(conversation, (uint)message.ExpiresInSeconds);
327+
string finalMessage;
328+
if (message.ExpiresInSeconds == 0)
329+
{
330+
finalMessage = $"{prefix} has turned off disappearing messages.";
331+
}
332+
else
333+
{
334+
finalMessage = $"{prefix} set disappearing message time to {message.ExpiresInSeconds} seconds.";
335+
}
336+
// Update conversations to reflect the new expires in seconds
337+
await SignalLibHandle.Instance.DispatchAddOrUpdateConversation(conversation, null);
326338
SignalMessage sm = new SignalMessage()
327339
{
328340
Direction = type,
329341
Type = SignalMessageType.ExpireUpdate,
330342
Status = status,
331343
Author = author,
332-
Content = new SignalMessageContent() { Content = $"{prefix} set the expiration timer to {message.ExpiresInSeconds} seconds." },
344+
Content = new SignalMessageContent() { Content = finalMessage },
333345
ThreadId = conversation.ThreadId,
334346
DeviceId = (uint)envelope.GetSourceDevice(),
335347
Receipts = 0,
@@ -518,6 +530,15 @@ private async Task HandleGroupUpdateMessage(SignalServiceEnvelope envelope, Sign
518530
composedTimestamp = envelope.GetTimestamp();
519531
}
520532

533+
long messageExpiration;
534+
if (dataMessage.ExpiresInSeconds == 0)
535+
{
536+
messageExpiration = 0;
537+
}
538+
else
539+
{
540+
messageExpiration = timestamp + (long)TimeSpan.FromSeconds(dataMessage.ExpiresInSeconds).TotalMilliseconds;
541+
}
521542
SignalMessage sm = new SignalMessage()
522543
{
523544
Direction = type,
@@ -530,6 +551,7 @@ private async Task HandleGroupUpdateMessage(SignalServiceEnvelope envelope, Sign
530551
Receipts = 0,
531552
ComposedTimestamp = composedTimestamp,
532553
ReceivedTimestamp = timestamp,
554+
ExpiresAt = messageExpiration
533555
};
534556
SignalDBContext.SaveMessageLocked(sm);
535557
dbgroup.MessagesCount += 1;
@@ -615,6 +637,15 @@ private async Task HandleSignalMessage(SignalServiceEnvelope envelope, SignalSer
615637
return;
616638
}
617639

640+
long messageExpiration;
641+
if (dataMessage.ExpiresInSeconds == 0)
642+
{
643+
messageExpiration = 0;
644+
}
645+
else
646+
{
647+
messageExpiration = timestamp + (long)TimeSpan.FromSeconds(dataMessage.ExpiresInSeconds).TotalMilliseconds;
648+
}
618649
List<SignalAttachment> attachments = new List<SignalAttachment>();
619650
SignalMessage message = new SignalMessage()
620651
{
@@ -627,6 +658,7 @@ private async Task HandleSignalMessage(SignalServiceEnvelope envelope, SignalSer
627658
Receipts = 0,
628659
ComposedTimestamp = composedTimestamp,
629660
ReceivedTimestamp = timestamp,
661+
ExpiresAt = messageExpiration,
630662
AttachmentsCount = (uint)attachments.Count,
631663
Attachments = attachments
632664
};
@@ -654,6 +686,7 @@ private async Task HandleSignalMessage(SignalServiceEnvelope envelope, SignalSer
654686
// Make sure to update attachments count
655687
message.AttachmentsCount = (uint)attachments.Count;
656688
}
689+
DisappearingMessagesManager.AddMessage(message);
657690
await SignalLibHandle.Instance.SaveAndDispatchSignalMessage(message, conversation);
658691
}
659692
}

Signal-Windows.Lib/Migrations/SignalDB/SignalDBContextModelSnapshot.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
using Microsoft.EntityFrameworkCore.Metadata;
55
using Microsoft.EntityFrameworkCore.Migrations;
66
using Signal_Windows.Storage;
7-
using Signal_Windows.Models;
87

98
namespace Signal_Windows.Migrations
109
{

Signal-Windows.Lib/Models/SignalMessage.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public class SignalMessage
1919
public uint Receipts { get; set; }
2020
public long ReceivedTimestamp { get; set; }
2121
public long ComposedTimestamp { get; set; }
22-
public uint ExpiresAt { get; set; }
22+
public long ExpiresAt { get; set; }
2323
public uint AttachmentsCount { get; set; }
2424
public List<SignalAttachment> Attachments { get; set; } = new List<SignalAttachment>();
2525
}

Signal-Windows.Lib/OutgoingMessages.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ public async Task HandleOutgoingMessages()
8080
if (!Token.IsCancellationRequested)
8181
{
8282
await MessageSender.SendMessage(Token, new SignalServiceAddress(outgoingSignalMessage.ThreadId), message);
83+
UpdateExpiresAt(outgoingSignalMessage);
84+
DisappearingMessagesManager.AddMessage(outgoingSignalMessage);
8385
outgoingSignalMessage.Status = SignalMessageStatus.Confirmed;
8486
}
8587
}
@@ -102,6 +104,8 @@ public async Task HandleOutgoingMessages()
102104
if (!Token.IsCancellationRequested)
103105
{
104106
await SendMessage(recipients, message);
107+
UpdateExpiresAt(outgoingSignalMessage);
108+
DisappearingMessagesManager.AddMessage(outgoingSignalMessage);
105109
outgoingSignalMessage.Status = SignalMessageStatus.Confirmed;
106110
}
107111
}
@@ -150,5 +154,24 @@ public async Task HandleOutgoingMessages()
150154
}
151155
Logger.LogInformation("HandleOutgoingMessages() finished");
152156
}
157+
158+
/// <summary>
159+
/// Updates a message ExpiresAt to be a timestamp instead of a relative value.
160+
/// </summary>
161+
/// <param name="message">The message to update</param>
162+
private void UpdateExpiresAt(SignalMessage message)
163+
{
164+
// We update here instead of earlier because we only want to start the timer once the message is actually sent.
165+
long messageExpiration;
166+
if (message.ExpiresAt == 0)
167+
{
168+
messageExpiration = 0;
169+
}
170+
else
171+
{
172+
messageExpiration = Util.CurrentTimeMillis() + (long)TimeSpan.FromSeconds(message.ExpiresAt).TotalMilliseconds;
173+
}
174+
message.ExpiresAt = messageExpiration;
175+
}
153176
}
154177
}

Signal-Windows.Lib/Signal-Windows.Lib.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@
106106
<RestoreProjectStyle>PackageReference</RestoreProjectStyle>
107107
</PropertyGroup>
108108
<ItemGroup>
109+
<Compile Include="DisappearingMessagesManager.cs" />
109110
<Compile Include="Events\SignalMessageEventArgs.cs" />
110111
<Compile Include="GlobalSettingsManager.cs" />
111112
<Compile Include="IncomingMessages.cs" />

Signal-Windows.Lib/SignalLibHandle.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ public interface ISignalFrontend
4848
Task HandleAuthFailure();
4949
void HandleAttachmentStatusChanged(SignalAttachment sa);
5050
void HandleBlockedContacts(List<SignalContact> blockedContacts);
51+
void HandleMessageDelete(SignalMessage messsage);
5152
}
5253

5354
public interface ISignalLibHandle
@@ -131,6 +132,7 @@ public bool AddFrontend(CoreDispatcher d, ISignalFrontend w)
131132
Logger.LogInformation("Registering frontend of dispatcher {0}", w.GetHashCode());
132133
Frames.Add(d, w);
133134
w.ReplaceConversationList(GetConversations());
135+
DisappearingMessagesManager.AddFrontend(d, w);
134136
return true;
135137
}
136138
else
@@ -152,6 +154,7 @@ public void RemoveFrontend(CoreDispatcher d)
152154
SemaphoreSlim.Wait(CancelSource.Token);
153155
Logger.LogTrace("RemoveFrontend() locked");
154156
Logger.LogInformation("Unregistering frontend of dispatcher {0}", d.GetHashCode());
157+
DisappearingMessagesManager.RemoveFrontend(d);
155158
Frames.Remove(d);
156159
SemaphoreSlim.Release();
157160
Logger.LogTrace("RemoveFrontend() released");

Signal-Windows.Lib/Storage/DB.cs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -919,6 +919,33 @@ public static SignalMessage IncreaseReceiptCountLocked(SignalServiceEnvelope env
919919
return set_mark? m : null;
920920
}
921921

922+
public static void DeleteMessage(SignalMessage message)
923+
{
924+
lock (DBLock)
925+
{
926+
using (var ctx = new SignalDBContext())
927+
{
928+
ctx.Remove(message);
929+
SignalConversation conversation = ctx.Contacts
930+
.Where(c => c.ThreadId == message.ThreadId)
931+
.Single();
932+
conversation.MessagesCount -= 1;
933+
conversation.LastMessage = null;
934+
conversation.LastMessageId = null;
935+
conversation.LastSeenMessage = null;
936+
conversation.LastSeenMessageIndex = ctx.Messages
937+
.Where(m => m.ThreadId == conversation.ThreadId)
938+
.Count() - 1;
939+
940+
// also delete fts message
941+
SignalMessageContent ftsMessage = ctx.Messages_fts.Where(m => m == message.Content)
942+
.Single();
943+
ctx.Remove(ftsMessage);
944+
ctx.SaveChanges();
945+
}
946+
}
947+
}
948+
922949
#endregion Messages
923950

924951
#region Attachments
@@ -936,6 +963,18 @@ public static SignalAttachment GetAttachmentByGuidNameLocked(string guid)
936963
}
937964
}
938965

966+
public static void DeleteAttachment(SignalAttachment attachment)
967+
{
968+
lock (DBLock)
969+
{
970+
using (var ctx = new SignalDBContext())
971+
{
972+
ctx.Remove(attachment);
973+
ctx.SaveChanges();
974+
}
975+
}
976+
}
977+
939978
internal static void UpdateAttachmentGuid(SignalAttachment attachment)
940979
{
941980
lock (DBLock)

Signal-Windows/App.xaml.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,7 @@ await newView.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
260260
if (success)
261261
{
262262
Views.Add(newViewId, frontend);
263+
DisappearingMessagesManager.AddFrontend(frontend.Dispatcher, frontend);
263264
await switcher.ShowAsStandaloneAsync(newViewId);
264265
Logger.LogInformation("CreateSecondaryWindow() added view {0}", newViewId);
265266
}
@@ -316,6 +317,7 @@ private async Task<bool> CreateMainWindow(string conversationId)
316317
}
317318
var frontend = new SignalWindowsFrontend(Window.Current.Dispatcher, (ViewModelLocator)Resources["Locator"], currView.Id);
318319
Views.Add(currView.Id, frontend);
320+
DisappearingMessagesManager.AddFrontend(frontend.Dispatcher, frontend);
319321
MainViewId = currView.Id;
320322
SetupTopBar();
321323

@@ -359,6 +361,7 @@ private void CurrView_Consolidated(ApplicationView sender, ApplicationViewConsol
359361
var signalWindowsFrontend = Views[sender.Id];
360362
Handle.RemoveFrontend(signalWindowsFrontend.Dispatcher);
361363
Views.Remove(sender.Id);
364+
DisappearingMessagesManager.RemoveFrontend(signalWindowsFrontend.Dispatcher);
362365
if (sender.Id != MainViewId)
363366
{
364367
Window.Current.Close();

Signal-Windows/Controls/Conversation.xaml.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,11 @@ public AppendResult Append(Message sm)
280280
}
281281
return result;
282282
}
283+
284+
public void HandleDeleteMesage(SignalMessage message)
285+
{
286+
Collection.Remove(message);
287+
}
283288

284289
private async void TextBox_KeyDown(object sender, KeyRoutedEventArgs e)
285290
{

0 commit comments

Comments
 (0)