Skip to content

Commit 3609675

Browse files
committed
Merge branch 'disappearing-messages'
2 parents e54ac3c + 0926511 commit 3609675

18 files changed

+567
-14
lines changed
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
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 libsignalservice.util;
8+
using Signal_Windows.Models;
9+
using Signal_Windows.Storage;
10+
using Windows.UI.Core;
11+
12+
namespace Signal_Windows.Lib
13+
{
14+
public static class DisappearingMessagesManager
15+
{
16+
private static Dictionary<CoreDispatcher, ISignalFrontend> frames;
17+
18+
static DisappearingMessagesManager()
19+
{
20+
frames = new Dictionary<CoreDispatcher, ISignalFrontend>();
21+
}
22+
23+
public static void AddFrontend(CoreDispatcher coreDispatcher, ISignalFrontend frontend)
24+
{
25+
if (!frames.ContainsKey(coreDispatcher))
26+
{
27+
frames.Add(coreDispatcher, frontend);
28+
}
29+
}
30+
31+
public static void RemoveFrontend(CoreDispatcher coreDispatcher)
32+
{
33+
if (frames.ContainsKey(coreDispatcher))
34+
{
35+
frames.Remove(coreDispatcher);
36+
}
37+
}
38+
39+
/// <summary>
40+
/// Queues a message for deletion.
41+
/// </summary>
42+
/// <param name="message">The message to queue for deletion</param>
43+
/// <remarks>If the message expire time is 0 then the message will not be deleted.</remarks>
44+
public static void QueueForDeletion(SignalMessage message)
45+
{
46+
if (message.ExpiresAt <= 0)
47+
{
48+
return;
49+
}
50+
Action deleteTask = async () =>
51+
{
52+
DateTimeOffset expireTime = DateTimeOffset.FromUnixTimeMilliseconds(message.ExpiresAt);
53+
DateTimeOffset receivedTime = DateTimeOffset.FromUnixTimeMilliseconds(message.ReceivedTimestamp);
54+
TimeSpan deleteTimeSpan = expireTime - receivedTime;
55+
if (deleteTimeSpan < TimeSpan.Zero)
56+
{
57+
await DeleteMessage(message);
58+
return;
59+
}
60+
await Task.Delay(deleteTimeSpan);
61+
await DeleteMessage(message);
62+
};
63+
Task.Run(deleteTask);
64+
}
65+
66+
/// <summary>
67+
/// Deletes expired messages from the database.
68+
/// </summary>
69+
public static void DeleteExpiredMessages()
70+
{
71+
long currentTimeMillis = Util.CurrentTimeMillis();
72+
List<SignalMessage> expiredMessages = SignalDBContext.GetExpiredMessages(currentTimeMillis);
73+
foreach (var expiredMessage in expiredMessages)
74+
{
75+
DeleteFromDb(expiredMessage);
76+
}
77+
}
78+
79+
private static async Task DeleteMessage(SignalMessage message)
80+
{
81+
List<Task> operations = new List<Task>();
82+
foreach (var dispatcher in frames.Keys)
83+
{
84+
var taskCompletionSource = new TaskCompletionSource<bool>();
85+
await dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
86+
{
87+
try
88+
{
89+
frames[dispatcher].HandleMessageDelete(message);
90+
}
91+
catch (Exception e)
92+
{
93+
}
94+
finally
95+
{
96+
taskCompletionSource.SetResult(false);
97+
}
98+
});
99+
operations.Add(taskCompletionSource.Task);
100+
}
101+
102+
foreach (var t in operations)
103+
{
104+
await t;
105+
}
106+
107+
DeleteFromDb(message);
108+
}
109+
110+
private static void DeleteFromDb(SignalMessage message)
111+
{
112+
foreach (var attachment in message.Attachments)
113+
{
114+
SignalDBContext.DeleteAttachment(attachment);
115+
}
116+
SignalDBContext.DeleteMessage(message);
117+
}
118+
}
119+
}

Signal-Windows.Lib/IncomingMessages.cs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,16 @@ public async Task OnMessage(SignalServiceMessagePipeMessage message)
104104
private async Task HandleMessage(SignalServiceEnvelope envelope)
105105
{
106106
var cipher = new SignalServiceCipher(new SignalServiceAddress(SignalLibHandle.Instance.Store.Username), new Store(), LibUtils.GetCertificateValidator());
107+
// TODO: Starting to get messages of an unknown type which causes Decrypt to return null, so handle the null case for now.
107108
var content = cipher.Decrypt(envelope);
108109
long timestamp = Util.CurrentTimeMillis();
109110

111+
if (content == null)
112+
{
113+
//TODO callmessages
114+
Logger.LogWarning("HandleMessage() received unrecognized message");
115+
return;
116+
}
110117
if (content.Message != null)
111118
{
112119
SignalServiceDataMessage message = content.Message;
@@ -326,15 +333,26 @@ private async Task HandleExpirationUpdateMessage(SignalServiceEnvelope envelope,
326333
conversation = await SignalDBContext.GetOrCreateContactLocked(content.Sender, 0);
327334
}
328335
}
329-
conversation.ExpiresInSeconds = (uint) message.ExpiresInSeconds;
336+
conversation.ExpiresInSeconds = (uint)message.ExpiresInSeconds;
330337
SignalDBContext.UpdateExpiresInLocked(conversation);
338+
string finalMessage;
339+
if (message.ExpiresInSeconds == 0)
340+
{
341+
finalMessage = $"{prefix} has turned off disappearing messages.";
342+
}
343+
else
344+
{
345+
finalMessage = $"{prefix} set disappearing message time to {message.ExpiresInSeconds} seconds.";
346+
}
347+
// Update conversations to reflect the new expires in seconds
348+
await SignalLibHandle.Instance.DispatchAddOrUpdateConversation(conversation, null);
331349
SignalMessage sm = new SignalMessage()
332350
{
333351
Direction = type,
334352
Type = SignalMessageType.ExpireUpdate,
335353
Status = status,
336354
Author = author,
337-
Content = new SignalMessageContent() { Content = $"{prefix} set the expiration timer to {message.ExpiresInSeconds} seconds." },
355+
Content = new SignalMessageContent() { Content = finalMessage },
338356
ThreadId = conversation.ThreadId,
339357
DeviceId = (uint)envelope.GetSourceDevice(),
340358
Receipts = 0,
@@ -534,7 +552,7 @@ private async Task HandleGroupUpdateMessage(SignalServiceEnvelope envelope, Sign
534552
DeviceId = (uint)envelope.GetSourceDevice(),
535553
Receipts = 0,
536554
ComposedTimestamp = composedTimestamp,
537-
ReceivedTimestamp = timestamp,
555+
ReceivedTimestamp = timestamp
538556
};
539557
SignalDBContext.SaveMessageLocked(sm);
540558
dbgroup.MessagesCount += 1;

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: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,9 @@ public async Task Send(SignalServiceMessageSender messageSender, CancellationTok
108108
Attachments = outgoingAttachmentsList
109109
};
110110

111+
UpdateExpiresAt(OutgoingSignalMessage);
112+
DisappearingMessagesManager.QueueForDeletion(OutgoingSignalMessage);
113+
111114
if (!OutgoingSignalMessage.ThreadId.EndsWith("="))
112115
{
113116
if (!token.IsCancellationRequested)
@@ -144,6 +147,25 @@ public async Task Send(SignalServiceMessageSender messageSender, CancellationTok
144147
}
145148
}
146149
}
150+
151+
/// <summary>
152+
/// Updates a message ExpiresAt to be a timestamp instead of a relative value.
153+
/// </summary>
154+
/// <param name="message">The message to update</param>
155+
private void UpdateExpiresAt(SignalMessage message)
156+
{
157+
// We update here instead of earlier because we only want to start the timer once the message is actually sent.
158+
long messageExpiration;
159+
if (message.ExpiresAt == 0)
160+
{
161+
messageExpiration = 0;
162+
}
163+
else
164+
{
165+
messageExpiration = Util.CurrentTimeMillis() + (long)TimeSpan.FromSeconds(message.ExpiresAt).TotalMilliseconds;
166+
}
167+
message.ExpiresAt = messageExpiration;
168+
}
147169
}
148170

149171
class OutgoingMessages
@@ -193,6 +215,11 @@ public async Task HandleOutgoingMessages()
193215
}
194216
foreach (UntrustedIdentityException e in identityExceptions)
195217
{
218+
// TODO: Not sure what to do with this.
219+
//await SendMessage(recipients, message);
220+
//UpdateExpiresAt(outgoingSignalMessage);
221+
//DisappearingMessagesManager.QueueForDeletion(outgoingSignalMessage);
222+
//outgoingSignalMessage.Status = SignalMessageStatus.Confirmed;
196223
await Handle.HandleOutgoingKeyChangeLocked(e.E164number, Base64.EncodeBytes(e.IdentityKey.serialize()));
197224
}
198225
}

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: 28 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
@@ -129,6 +130,7 @@ public bool AddFrontend(CoreDispatcher d, ISignalFrontend w)
129130
Logger.LogInformation("Registering frontend of dispatcher {0}", w.GetHashCode());
130131
Frames.Add(d, w);
131132
w.ReplaceConversationList(GetConversations());
133+
DisappearingMessagesManager.AddFrontend(d, w);
132134
return true;
133135
}
134136
else
@@ -152,6 +154,7 @@ public async Task RemoveFrontend(CoreDispatcher d)
152154
{
153155
Logger.LogTrace("RemoveFrontend() locked");
154156
Logger.LogInformation("Unregistering frontend of dispatcher {0}", d.GetHashCode());
157+
DisappearingMessagesManager.RemoveFrontend(d);
155158
Frames.Remove(d);
156159
}
157160
catch (Exception e)
@@ -420,6 +423,7 @@ public async Task SetMessageRead(SignalMessage message)
420423
{
421424
Logger.LogTrace("SetMessageRead() locked");
422425
var updatedConversation = SignalDBContext.UpdateMessageRead(message.ComposedTimestamp);
426+
UpdateMessageExpiration(message, updatedConversation.ExpiresInSeconds);
423427
OutgoingQueue.Add(new SignalServiceSyncMessageSendable(SignalServiceSyncMessage.ForRead(new List<ReadMessage>() {
424428
new ReadMessage(message.Author.ThreadId, message.ComposedTimestamp)
425429
})));
@@ -720,6 +724,7 @@ await dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
720724
AppendResult result = await b;
721725
if (result != null && result.WasInstantlyRead)
722726
{
727+
UpdateMessageExpiration(message, conversation.ExpiresInSeconds);
723728
var updatedConversation = SignalDBContext.UpdateMessageRead(message.ComposedTimestamp);
724729
await DispatchMessageRead(updatedConversation);
725730
wasInstantlyRead = true;
@@ -791,6 +796,29 @@ await dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
791796
}
792797
}
793798

799+
internal void UpdateMessageExpiration(SignalMessage message, uint conversationExpireTimeSeconds)
800+
{
801+
if (message.Type == Signal_Windows.Models.SignalMessageType.Normal && message.ExpiresAt == 0)
802+
{
803+
long messageExpiration;
804+
if (conversationExpireTimeSeconds == 0)
805+
{
806+
messageExpiration = 0;
807+
}
808+
else
809+
{
810+
messageExpiration = Util.CurrentTimeMillis() + (long)TimeSpan.FromSeconds(conversationExpireTimeSeconds).TotalMilliseconds;
811+
}
812+
813+
if (messageExpiration > 0)
814+
{
815+
message.ExpiresAt = messageExpiration;
816+
SignalDBContext.UpdateMessageExpiresAt(message);
817+
DisappearingMessagesManager.QueueForDeletion(message);
818+
}
819+
}
820+
}
821+
794822
internal void DispatchPipeEmptyMessage()
795823
{
796824
SignalMessageEvent?.Invoke(this, new SignalMessageEventArgs(null, Events.SignalPipeMessageType.PipeEmptyMessage));

0 commit comments

Comments
 (0)