diff --git a/Signal-Windows.Lib/IncomingMessages.cs b/Signal-Windows.Lib/IncomingMessages.cs index d50c915..88377b6 100644 --- a/Signal-Windows.Lib/IncomingMessages.cs +++ b/Signal-Windows.Lib/IncomingMessages.cs @@ -431,13 +431,18 @@ private void HandleSignalMessage(SignalServiceEnvelope envelope, SignalServiceCo Message = message, Status = (uint)SignalAttachmentStatus.Default, SentFileName = pointer.FileName, - ContentType = "", + ContentType = receivedAttachment.getContentType(), Key = pointer.Key, Relay = pointer.Relay, - StorageId = pointer.Id + StorageId = pointer.Id, + Size = (long)pointer.Size, + Digest = pointer.Digest }; attachments.Add(sa); } + + // Make sure to update attachments count + message.AttachmentsCount = (uint)attachments.Count; } SignalLibHandle.Instance.SaveAndDispatchSignalMessage(message, conversation); } diff --git a/Signal-Windows.Lib/LibUtils.cs b/Signal-Windows.Lib/LibUtils.cs index 25ac809..28e14e5 100644 --- a/Signal-Windows.Lib/LibUtils.cs +++ b/Signal-Windows.Lib/LibUtils.cs @@ -1,11 +1,15 @@ using libsignalservice.push; +using Microsoft.Toolkit.Uwp.Notifications; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; +using Windows.Networking.BackgroundTransfer; +using Windows.Storage; using Windows.UI.Core; +using Windows.UI.Notifications; namespace Signal_Windows.Lib { @@ -77,5 +81,27 @@ internal static void Unlock() { GlobalSemaphore.Release(); } + + public static ToastNotification CreateToastNotification(string text) + { + ToastContent toastContent = new ToastContent() + { + Visual = new ToastVisual() + { + BindingGeneric = new ToastBindingGeneric() + { + Children = + { + new AdaptiveText() + { + Text = text, + HintWrap = true + } + } + } + } + }; + return new ToastNotification(toastContent.GetXml()); + } } } diff --git a/Signal-Windows.Lib/Migrations/SignalDB/20180203062229_m4.Designer.cs b/Signal-Windows.Lib/Migrations/SignalDB/20180203062229_m4.Designer.cs new file mode 100644 index 0000000..4aa924b --- /dev/null +++ b/Signal-Windows.Lib/Migrations/SignalDB/20180203062229_m4.Designer.cs @@ -0,0 +1,256 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Signal_Windows.Storage; +using Signal_Windows.Models; + +namespace Signal_Windows.Migrations +{ + [DbContext(typeof(SignalDBContext))] + [Migration("20180203062229_m4")] + partial class m4 + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { + modelBuilder + .HasAnnotation("ProductVersion", "1.1.4"); + + modelBuilder.Entity("Signal_Windows.Models.GroupMembership", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ContactId"); + + b.Property("GroupId"); + + b.HasKey("Id"); + + b.HasIndex("ContactId"); + + b.HasIndex("GroupId"); + + b.ToTable("GroupMemberships"); + }); + + modelBuilder.Entity("Signal_Windows.Models.SignalAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ContentType"); + + b.Property("Digest"); + + b.Property("FileName"); + + b.Property("Key"); + + b.Property("MessageId"); + + b.Property("Relay"); + + b.Property("SentFileName"); + + b.Property("Size"); + + b.Property("Status"); + + b.Property("StorageId"); + + b.HasKey("Id"); + + b.HasIndex("MessageId"); + + b.ToTable("Attachments"); + }); + + modelBuilder.Entity("Signal_Windows.Models.SignalConversation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AvatarFile"); + + b.Property("CanReceive"); + + b.Property("Discriminator") + .IsRequired(); + + b.Property("Draft"); + + b.Property("ExpiresInSeconds"); + + b.Property("LastActiveTimestamp"); + + b.Property("LastMessageId"); + + b.Property("LastSeenMessageId"); + + b.Property("LastSeenMessageIndex"); + + b.Property("MessagesCount"); + + b.Property("ThreadDisplayName"); + + b.Property("ThreadId"); + + b.Property("UnreadCount"); + + b.HasKey("Id"); + + b.HasIndex("LastMessageId"); + + b.HasIndex("LastSeenMessageId"); + + b.HasIndex("ThreadId"); + + b.ToTable("SignalConversation"); + + b.HasDiscriminator("Discriminator").HasValue("SignalConversation"); + }); + + modelBuilder.Entity("Signal_Windows.Models.SignalEarlyReceipt", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DeviceId"); + + b.Property("Timestamp"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("DeviceId"); + + b.HasIndex("Timestamp"); + + b.HasIndex("Username"); + + b.ToTable("EarlyReceipts"); + }); + + modelBuilder.Entity("Signal_Windows.Models.SignalMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AttachmentsCount"); + + b.Property("AuthorId"); + + b.Property("ComposedTimestamp"); + + b.Property("Contentrowid"); + + b.Property("DeviceId"); + + b.Property("Direction"); + + b.Property("ExpiresAt"); + + b.Property("Read"); + + b.Property("Receipts"); + + b.Property("ReceivedTimestamp"); + + b.Property("Status"); + + b.Property("ThreadId"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.HasIndex("AuthorId"); + + b.HasIndex("Contentrowid"); + + b.HasIndex("ThreadId"); + + b.ToTable("Messages"); + }); + + modelBuilder.Entity("Signal_Windows.Models.SignalMessageContent", b => + { + b.Property("rowid") + .ValueGeneratedOnAdd(); + + b.Property("Content"); + + b.HasKey("rowid"); + + b.ToTable("Messages_fts"); + }); + + modelBuilder.Entity("Signal_Windows.Models.SignalContact", b => + { + b.HasBaseType("Signal_Windows.Models.SignalConversation"); + + b.Property("Color"); + + b.ToTable("SignalContact"); + + b.HasDiscriminator().HasValue("SignalContact"); + }); + + modelBuilder.Entity("Signal_Windows.Models.SignalGroup", b => + { + b.HasBaseType("Signal_Windows.Models.SignalConversation"); + + + b.ToTable("SignalGroup"); + + b.HasDiscriminator().HasValue("SignalGroup"); + }); + + modelBuilder.Entity("Signal_Windows.Models.GroupMembership", b => + { + b.HasOne("Signal_Windows.Models.SignalContact", "Contact") + .WithMany("GroupMemberships") + .HasForeignKey("ContactId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Signal_Windows.Models.SignalGroup", "Group") + .WithMany("GroupMemberships") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Signal_Windows.Models.SignalAttachment", b => + { + b.HasOne("Signal_Windows.Models.SignalMessage", "Message") + .WithMany("Attachments") + .HasForeignKey("MessageId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Signal_Windows.Models.SignalConversation", b => + { + b.HasOne("Signal_Windows.Models.SignalMessage", "LastMessage") + .WithMany() + .HasForeignKey("LastMessageId"); + + b.HasOne("Signal_Windows.Models.SignalMessage", "LastSeenMessage") + .WithMany() + .HasForeignKey("LastSeenMessageId"); + }); + + modelBuilder.Entity("Signal_Windows.Models.SignalMessage", b => + { + b.HasOne("Signal_Windows.Models.SignalContact", "Author") + .WithMany() + .HasForeignKey("AuthorId"); + + b.HasOne("Signal_Windows.Models.SignalMessageContent", "Content") + .WithMany() + .HasForeignKey("Contentrowid"); + }); + } + } +} diff --git a/Signal-Windows.Lib/Migrations/SignalDB/20180203062229_m4.cs b/Signal-Windows.Lib/Migrations/SignalDB/20180203062229_m4.cs new file mode 100644 index 0000000..aee4bde --- /dev/null +++ b/Signal-Windows.Lib/Migrations/SignalDB/20180203062229_m4.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Signal_Windows.Migrations +{ + public partial class m4 : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Digest", + table: "Attachments", + nullable: true); + + migrationBuilder.AddColumn( + name: "Size", + table: "Attachments", + nullable: false, + defaultValue: 0L); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Digest", + table: "Attachments"); + + migrationBuilder.DropColumn( + name: "Size", + table: "Attachments"); + } + } +} diff --git a/Signal-Windows.Lib/Migrations/SignalDB/SignalDBContextModelSnapshot.cs b/Signal-Windows.Lib/Migrations/SignalDB/SignalDBContextModelSnapshot.cs index d1fbedc..77d2243 100644 --- a/Signal-Windows.Lib/Migrations/SignalDB/SignalDBContextModelSnapshot.cs +++ b/Signal-Windows.Lib/Migrations/SignalDB/SignalDBContextModelSnapshot.cs @@ -14,7 +14,7 @@ partial class SignalDBContextModelSnapshot : ModelSnapshot protected override void BuildModel(ModelBuilder modelBuilder) { modelBuilder - .HasAnnotation("ProductVersion", "1.1.2"); + .HasAnnotation("ProductVersion", "1.1.4"); modelBuilder.Entity("Signal_Windows.Models.GroupMembership", b => { @@ -41,6 +41,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("ContentType"); + b.Property("Digest"); + b.Property("FileName"); b.Property("Key"); @@ -51,6 +53,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("SentFileName"); + b.Property("Size"); + b.Property("Status"); b.Property("StorageId"); diff --git a/Signal-Windows.Lib/Models/SignalAttachment.cs b/Signal-Windows.Lib/Models/SignalAttachment.cs index dabb3cc..15f8b16 100644 --- a/Signal-Windows.Lib/Models/SignalAttachment.cs +++ b/Signal-Windows.Lib/Models/SignalAttachment.cs @@ -1,4 +1,6 @@ using System.ComponentModel.DataAnnotations.Schema; +using libsignalservice.messages; +using libsignalservice.util; using Windows.UI.Xaml.Controls; namespace Signal_Windows.Models @@ -15,9 +17,24 @@ public class SignalAttachment public byte[] Key { get; set; } public string Relay { get; set; } public ulong StorageId { get; set; } + public byte[] Digest { get; set; } + public long Size { get; set; } [NotMapped] public Image AttachmentImage { get; set; } + + public SignalServiceAttachmentPointer ToAttachmentPointer() + { + return new SignalServiceAttachmentPointer(StorageId, + ContentType, + Key, + Relay, + (uint)Util.toIntExact(Size), + null, + Digest, + FileName, + false); + } } public enum SignalAttachmentStatus diff --git a/Signal-Windows.Lib/Signal-Windows.Lib.csproj b/Signal-Windows.Lib/Signal-Windows.Lib.csproj index 4af094b..9651a68 100644 --- a/Signal-Windows.Lib/Signal-Windows.Lib.csproj +++ b/Signal-Windows.Lib/Signal-Windows.Lib.csproj @@ -126,6 +126,10 @@ 20170901124533_m3.cs + + + 20180203062229_m4.cs + @@ -150,9 +154,6 @@ - - 2.5.15.1 - 1.1.4 @@ -168,11 +169,19 @@ 6.0.1 + + 2.1.1 + 4.4.0 - + + + {3f0cf02e-f260-48dc-a4ce-f8d72d6a079d} + libsignal-service-dotnet + + 14.0 diff --git a/Signal-Windows.Lib/SignalLibHandle.cs b/Signal-Windows.Lib/SignalLibHandle.cs index cc695d8..0e037fe 100644 --- a/Signal-Windows.Lib/SignalLibHandle.cs +++ b/Signal-Windows.Lib/SignalLibHandle.cs @@ -15,6 +15,10 @@ using libsignal; using System.Diagnostics; using Signal_Windows.Lib.Events; +using libsignalservice.messages; +using System.IO; +using Windows.Networking.BackgroundTransfer; +using Windows.Storage; namespace Signal_Windows.Lib { @@ -26,12 +30,46 @@ public interface ISignalFrontend void HandleMessageUpdate(SignalMessage updatedMessage); void ReplaceConversationList(List conversations); void HandleAuthFailure(); + void HandleAttachmentStatusChanged(SignalAttachment sa); } - public class SignalLibHandle + public interface ISignalLibHandle { - public static SignalLibHandle Instance; - public SignalStore Store; + //Frontend API + SignalStore Store { get; set; } + Task SendMessage(SignalMessage message, SignalConversation conversation); + void ResendMessage(SignalMessage message); + List GetMessages(SignalConversation thread, int startIndex, int count); + void SaveAndDispatchSignalConversation(SignalConversation updatedConversation, SignalMessage updateMessage); + void PurgeAccountData(); + Task Acquire(CoreDispatcher d, ISignalFrontend w); + Task Reacquire(); + void Release(); + void AddFrontend(CoreDispatcher d, ISignalFrontend w); + void RemoveFrontend(CoreDispatcher d); + + // Background API + event EventHandler SignalMessageEvent; + void BackgroundAcquire(); + void BackgroundRelease(); + + // Attachment API + void StartAttachmentDownload(SignalAttachment sa); + //void AbortAttachmentDownload(SignalAttachment sa); TODO + } + + public static class SignalHelper + { + public static ISignalLibHandle CreateSignalLibHandle(bool headless) + { + return new SignalLibHandle(headless); + } + } + + internal class SignalLibHandle : ISignalLibHandle + { + internal static SignalLibHandle Instance; + public SignalStore Store { get; set; } private readonly ILogger Logger = LibsignalLogging.CreateLogger(); public SemaphoreSlim SemaphoreSlim = new SemaphoreSlim(1, 1); private bool Headless; @@ -44,6 +82,7 @@ public class SignalLibHandle private SignalServiceMessageSender MessageSender; private SignalServiceMessageReceiver MessageReceiver; public BlockingCollection OutgoingQueue = new BlockingCollection(new ConcurrentQueue()); + private ISet Downloads = new HashSet(); public event EventHandler SignalMessageEvent; @@ -124,6 +163,7 @@ public async Task Acquire(CoreDispatcher d, ISignalFrontend w) //TODO wrap tryca await Task.Run(() => { InitNetwork(); + RecoverDownloads().Wait(); }); await failTask; // has to complete before messages are loaded Running = true; @@ -138,6 +178,7 @@ public void BackgroundAcquire() SignalDBContext.FailAllPendingMessages(); Store = LibsignalDBContext.GetSignalStore(); InitNetwork(); + RecoverDownloads().Wait(); Running = true; } @@ -162,6 +203,8 @@ await Task.Run(() => } Task.WaitAll(tasks.ToArray()); InitNetwork(); + Downloads.Clear(); + RecoverDownloads().Wait(); }); Running = true; Logger.LogTrace("Reacquire() releasing"); @@ -194,18 +237,30 @@ public void BackgroundRelease() public async Task SendMessage(SignalMessage message, SignalConversation conversation) { - await Task.Run(async () => + await Task.Run(() => { 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"); + SemaphoreSlim.Wait(CancelSource.Token); + Logger.LogTrace("SendMessage() locked"); + try + { + Logger.LogDebug("SendMessage saving message " + message.ComposedTimestamp); + SaveAndDispatchSignalMessage(message, conversation); + OutgoingQueue.Add(message); + } + finally + { + SemaphoreSlim.Release(); + Logger.LogTrace("SendMessage() released"); + } }); } + public void ResendMessage(SignalMessage message) + { + OutgoingQueue.Add(message); + } + public List GetMessages(SignalConversation thread, int startIndex, int count) { return SignalDBContext.GetMessagesLocked(thread, startIndex, count); @@ -220,9 +275,31 @@ public void SaveAndDispatchSignalConversation(SignalConversation updatedConversa SemaphoreSlim.Release(); Logger.LogTrace("SaveAndDispatchSignalConversation() released"); } + + public void RetrieveAttachment(SignalServiceAttachmentPointer pointer, Stream downloadStream, Stream tempStream) + { + MessageReceiver.retrieveAttachment(pointer, downloadStream, tempStream, 0); + } + + public string RetrieveAttachmentUrl(SignalServiceAttachmentPointer pointer) + { + return MessageReceiver.RetrieveAttachmentDownloadUrl(pointer); + } + + public void DecryptAttachment(SignalServiceAttachmentPointer pointer, Stream tempStream, Stream downloadStream) + { + MessageReceiver.DecryptAttachment(pointer, tempStream, downloadStream); + } #endregion - #region backend api + #region attachment api + public void StartAttachmentDownload(SignalAttachment sa) + { + //TODO lock, check if already downloading, start a new download if not exists + } + #endregion + + #region internal api internal void SaveAndDispatchSignalMessage(SignalMessage message, SignalConversation conversation) { conversation.MessagesCount += 1; @@ -234,16 +311,18 @@ internal void SaveAndDispatchSignalMessage(SignalMessage message, SignalConversa { conversation.UnreadCount = 0; conversation.LastSeenMessageIndex = conversation.MessagesCount; + } SignalDBContext.SaveMessageLocked(message); conversation.LastMessage = message; conversation.LastActiveTimestamp = message.ComposedTimestamp; + StartAttachmentDownloads(message); DispatchHandleMessage(message, conversation); } internal void DispatchHandleIdentityKeyChange(LinkedList messages) { - List operations = new List(); ; + List operations = new List(); foreach (var dispatcher in Frames.Keys) { operations.Add(dispatcher.RunTaskAsync(() => @@ -371,6 +450,110 @@ private void InitNetwork() IncomingMessagesTask = Task.Factory.StartNew(() => new IncomingMessages(CancelSource.Token, Pipe, this).HandleIncomingMessages(), TaskCreationOptions.LongRunning); OutgoingMessagesTask = Task.Factory.StartNew(() => new OutgoingMessages(CancelSource.Token, MessageSender, this).HandleOutgoingMessages(), TaskCreationOptions.LongRunning); } + + private void StartAttachmentDownloads(SignalMessage message) + { + if (message.Attachments != null) + { + foreach (var attachment in message.Attachments) + { + if (Downloads.Count < 100) + { + SignalServiceAttachmentPointer attachmentPointer = attachment.ToAttachmentPointer(); + IStorageFolder localFolder = ApplicationData.Current.LocalFolder; + IStorageFile tmpDownload = Task.Run(async () => + { + return await ApplicationData.Current.LocalCacheFolder.CreateFileAsync(@"Attachments\" + attachment.Id + ".cipher"); + }).Result; + BackgroundDownloader downloader = new BackgroundDownloader(); + downloader.SetRequestHeader("Content-Type", "application/octet-stream"); + downloader.SuccessToastNotification = LibUtils.CreateToastNotification($"{attachment.SentFileName} has finished downloading."); + downloader.FailureToastNotification = LibUtils.CreateToastNotification($"{attachment.SentFileName} has failed to download."); + // this is the recommended way to call CreateDownload + // see https://docs.microsoft.com/en-us/uwp/api/windows.networking.backgroundtransfer.backgrounddownloader#Methods + DownloadOperation download = downloader.CreateDownload(new Uri(RetrieveAttachmentUrl(attachmentPointer)), tmpDownload); + attachment.FileName = "" + download.Guid; + SignalDBContext.UpdateAttachmentFileName(attachment); + Downloads.Add(download); + Task.Run(async () => + { + Logger.LogInformation("Waiting for download {0}({1})", attachment.SentFileName, attachment.Id); + await download.StartAsync(); + await HandleSuccessfullDownload(attachment, tmpDownload, download); + }); + } + } + } + } + + private async Task HandleSuccessfullDownload(SignalAttachment attachment, IStorageFile tmpDownload, DownloadOperation download) + { + StorageFile plaintextFile = await ApplicationData.Current.LocalCacheFolder.CreateFileAsync(@"Attachments\" + attachment.Id + ".plain", CreationCollisionOption.ReplaceExisting); + using (var tmpFileStream = (await tmpDownload.OpenAsync(FileAccessMode.ReadWrite)).AsStream()) + using (var plaintextFileStream = (await plaintextFile.OpenAsync(FileAccessMode.ReadWrite)).AsStream()) + { + Logger.LogInformation("Decrypting to {0}\\{1}", plaintextFile.Path, plaintextFile.Name); + DecryptAttachment(attachment.ToAttachmentPointer(), tmpFileStream, plaintextFileStream); + } + Logger.LogInformation("Deleting tmpFile {0}", tmpDownload.Name); + await tmpDownload.DeleteAsync(); + attachment.Status = SignalAttachmentStatus.Finished; + SignalDBContext.UpdateAttachmentStatus(attachment); + DispatchAttachmentStatusChanged(download, attachment); + } + + private async Task RecoverDownloads() + { + var downloads = await BackgroundDownloader.GetCurrentDownloadsAsync(); + foreach (DownloadOperation download in downloads) + { + try + { + SignalAttachment attachment = SignalDBContext.GetAttachmentByFileNameLocked(download.Guid.ToString()); + if (attachment != null) + { + Downloads.Add(download); + var t = Task.Run(async () => + { + Logger.LogInformation("Attaching to download {0} ({1})", attachment.Id, download.Guid); + await download.AttachAsync(); + await HandleSuccessfullDownload(attachment, download.ResultFile, download); + }); + } + else + { + Logger.LogInformation("Aborting unrecognized download {0}", download.Guid); + download.AttachAsync().Cancel(); + } + } + catch(Exception e) + { + Logger.LogError("TriageDownloads encountered an error: {0}\n{1}", e.Message, e.StackTrace); + } + } + } + + private void DispatchAttachmentStatusChanged(DownloadOperation op, SignalAttachment attachment) + { + try + { + SemaphoreSlim.Wait(CancelSource.Token); + Downloads.Remove(op); + List operations = new List(); + foreach (var dispatcher in Frames.Keys) + { + operations.Add(dispatcher.RunTaskAsync(() => + { + Frames[dispatcher].HandleAttachmentStatusChanged(attachment); + })); + } + Task.WaitAll(operations.ToArray()); + } + finally + { + SemaphoreSlim.Release(); + } + } #endregion } } diff --git a/Signal-Windows.Lib/Storage/DB.cs b/Signal-Windows.Lib/Storage/DB.cs index fdd63ba..393e459 100644 --- a/Signal-Windows.Lib/Storage/DB.cs +++ b/Signal-Windows.Lib/Storage/DB.cs @@ -907,13 +907,44 @@ public static SignalMessage IncreaseReceiptCountLocked(SignalServiceEnvelope env #region Attachments - public static void UpdateAttachmentLocked(SignalAttachment sa) + public static SignalAttachment GetAttachmentByFileNameLocked(string fileName) { lock (DBLock) { using (var ctx = new SignalDBContext()) { - ctx.Attachments.Update(sa); + return ctx.Attachments + .Where(a => a.FileName == fileName) + .FirstOrDefault(); + } + } + } + + internal static void UpdateAttachmentFileName(SignalAttachment attachment) + { + lock (DBLock) + { + using (var ctx = new SignalDBContext()) + { + var savedAttachment = ctx.Attachments + .Where(a => a.Id == attachment.Id) + .First(); + savedAttachment.FileName = attachment.FileName; + ctx.SaveChanges(); + } + } + } + + internal static void UpdateAttachmentStatus(SignalAttachment attachment) + { + lock (DBLock) + { + using (var ctx = new SignalDBContext()) + { + var savedAttachment = ctx.Attachments + .Where(a => a.Id == attachment.Id) + .First(); + savedAttachment.Status = attachment.Status; ctx.SaveChanges(); } } diff --git a/Signal-Windows.RC/Signal-Windows.RC.csproj b/Signal-Windows.RC/Signal-Windows.RC.csproj index f2d33a5..3c01062 100644 --- a/Signal-Windows.RC/Signal-Windows.RC.csproj +++ b/Signal-Windows.RC/Signal-Windows.RC.csproj @@ -119,6 +119,10 @@ + + {3f0cf02e-f260-48dc-a4ce-f8d72d6a079d} + libsignal-service-dotnet + {1934fd82-a5ea-4b71-b915-a1826593cb6e} Signal-Windows.Lib diff --git a/Signal-Windows.RC/SignalBackgroundTask.cs b/Signal-Windows.RC/SignalBackgroundTask.cs index f7e3c4f..86edbc8 100644 --- a/Signal-Windows.RC/SignalBackgroundTask.cs +++ b/Signal-Windows.RC/SignalBackgroundTask.cs @@ -29,7 +29,7 @@ public sealed class SignalBackgroundTask : IBackgroundTask private Semaphore semaphore; private DateTime taskStartTime; private DateTime taskEndTime; - private SignalLibHandle handle; + private ISignalLibHandle handle; private ToastNotifier toastNotifier; public async void Run(IBackgroundTaskInstance taskInstance) @@ -48,7 +48,7 @@ public async void Run(IBackgroundTaskInstance taskInstance) deferral.Complete(); return; } - handle = new SignalLibHandle(true); + handle = SignalHelper.CreateSignalLibHandle(true); handle.SignalMessageEvent += Handle_SignalMessageEvent; handle.BackgroundAcquire(); await CheckTimer(); diff --git a/Signal-Windows.sln b/Signal-Windows.sln index 3864689..da7a327 100644 --- a/Signal-Windows.sln +++ b/Signal-Windows.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.27130.2020 +VisualStudioVersion = 15.0.27130.2026 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Signal-Windows", "Signal-Windows\Signal-Windows.csproj", "{41736A64-5B66-44AF-879A-501192A46920}" ProjectSection(ProjectDependencies) = postProject @@ -11,6 +11,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Signal-Windows.Lib", "Signa EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Signal-Windows.RC", "Signal-Windows.RC\Signal-Windows.RC.csproj", "{F8AF07FF-395F-4BF7-84F7-D16EDE7B00DF}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "libsignal-service-dotnet", "..\libsignal-service-dotnet\libsignal-service-dotnet\libsignal-service-dotnet.csproj", "{3F0CF02E-F260-48DC-A4CE-F8D72D6A079D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -77,6 +79,22 @@ Global {F8AF07FF-395F-4BF7-84F7-D16EDE7B00DF}.Release|x64.Build.0 = Release|x64 {F8AF07FF-395F-4BF7-84F7-D16EDE7B00DF}.Release|x86.ActiveCfg = Release|x86 {F8AF07FF-395F-4BF7-84F7-D16EDE7B00DF}.Release|x86.Build.0 = Release|x86 + {3F0CF02E-F260-48DC-A4CE-F8D72D6A079D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3F0CF02E-F260-48DC-A4CE-F8D72D6A079D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3F0CF02E-F260-48DC-A4CE-F8D72D6A079D}.Debug|ARM.ActiveCfg = Debug|Any CPU + {3F0CF02E-F260-48DC-A4CE-F8D72D6A079D}.Debug|ARM.Build.0 = Debug|Any CPU + {3F0CF02E-F260-48DC-A4CE-F8D72D6A079D}.Debug|x64.ActiveCfg = Debug|Any CPU + {3F0CF02E-F260-48DC-A4CE-F8D72D6A079D}.Debug|x64.Build.0 = Debug|Any CPU + {3F0CF02E-F260-48DC-A4CE-F8D72D6A079D}.Debug|x86.ActiveCfg = Debug|Any CPU + {3F0CF02E-F260-48DC-A4CE-F8D72D6A079D}.Debug|x86.Build.0 = Debug|Any CPU + {3F0CF02E-F260-48DC-A4CE-F8D72D6A079D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3F0CF02E-F260-48DC-A4CE-F8D72D6A079D}.Release|Any CPU.Build.0 = Release|Any CPU + {3F0CF02E-F260-48DC-A4CE-F8D72D6A079D}.Release|ARM.ActiveCfg = Release|Any CPU + {3F0CF02E-F260-48DC-A4CE-F8D72D6A079D}.Release|ARM.Build.0 = Release|Any CPU + {3F0CF02E-F260-48DC-A4CE-F8D72D6A079D}.Release|x64.ActiveCfg = Release|Any CPU + {3F0CF02E-F260-48DC-A4CE-F8D72D6A079D}.Release|x64.Build.0 = Release|Any CPU + {3F0CF02E-F260-48DC-A4CE-F8D72D6A079D}.Release|x86.ActiveCfg = Release|Any CPU + {3F0CF02E-F260-48DC-A4CE-F8D72D6A079D}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Signal-Windows/App.xaml.cs b/Signal-Windows/App.xaml.cs index cfd99b5..c52b053 100644 --- a/Signal-Windows/App.xaml.cs +++ b/Signal-Windows/App.xaml.cs @@ -22,6 +22,7 @@ using System.Collections.Generic; using System.Collections.Concurrent; using Windows.ApplicationModel.Background; +using Windows.Networking.BackgroundTransfer; namespace Signal_Windows { @@ -39,7 +40,7 @@ sealed partial class App : Application public static bool MainPageActive = false; public static string USER_AGENT = "Signal-Windows"; public static uint PREKEY_BATCH_SIZE = 100; - public static SignalLibHandle Handle = new SignalLibHandle(false); + public static ISignalLibHandle Handle = SignalHelper.CreateSignalLibHandle(false); private Dictionary Views = new Dictionary(); public static int MainViewId; private IBackgroundTaskRegistration backgroundTaskRegistration; diff --git a/Signal-Windows/Controls/Attachment.xaml b/Signal-Windows/Controls/Attachment.xaml new file mode 100644 index 0000000..c90af03 --- /dev/null +++ b/Signal-Windows/Controls/Attachment.xaml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Signal-Windows/Controls/Attachment.xaml.cs b/Signal-Windows/Controls/Attachment.xaml.cs new file mode 100644 index 0000000..fcd9399 --- /dev/null +++ b/Signal-Windows/Controls/Attachment.xaml.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices.WindowsRuntime; +using Signal_Windows.Models; +using Windows.Foundation; +using Windows.Foundation.Collections; +using Windows.Storage; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Data; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Navigation; + +// The User Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234236 + +namespace Signal_Windows.Controls +{ + public sealed partial class Attachment : UserControl, INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + + private string fileName; + public string FileName + { + get { return fileName; } + set { fileName = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(FileName))); } + } + + private string fileSize; + public string FileSize + { + get { return fileSize; } + set { fileSize = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(FileSize))); } + } + + private Uri imagePath; + public Uri ImagePath + { + get { return imagePath; } + set { imagePath = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ImagePath))); } + } + + public SignalAttachment Model + { + get { return DataContext as SignalAttachment; } + } + + public Attachment() + { + this.InitializeComponent(); + DataContextChanged += Attachment_DataContextChanged; + } + + private void Attachment_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args) + { + if (Model != null) + { + FileName = Model.FileName; + FileSize = Utils.BytesToString(Model.Size); + if (Model.Status == SignalAttachmentStatus.Finished) + { + if (IMAGE_TYPES.Contains(Model.ContentType)) + { + var path = ApplicationData.Current.LocalCacheFolder.Path + @"\Attachments\" + Model.Id + ".plain"; + ImagePath = new Uri(path); + } + } + } + } + + private static HashSet IMAGE_TYPES = new HashSet() + { + "image/jpeg", + "image/png", + "image/gif" + }; + } +} diff --git a/Signal-Windows/Controls/Message.xaml b/Signal-Windows/Controls/Message.xaml index 8dc2810..4f84c5e 100644 --- a/Signal-Windows/Controls/Message.xaml +++ b/Signal-Windows/Controls/Message.xaml @@ -16,8 +16,9 @@ + - + diff --git a/Signal-Windows/Controls/Message.xaml.cs b/Signal-Windows/Controls/Message.xaml.cs index 263bd76..c555e06 100644 --- a/Signal-Windows/Controls/Message.xaml.cs +++ b/Signal-Windows/Controls/Message.xaml.cs @@ -13,8 +13,10 @@ namespace Signal_Windows.Controls { - public sealed partial class Message : UserControl + public sealed partial class Message : UserControl, INotifyPropertyChanged { + public event PropertyChangedEventHandler PropertyChanged; + public SignalMessageContainer Model { get @@ -27,6 +29,20 @@ public SignalMessageContainer Model } } + private bool hasAttachment; + public bool HasAttachment + { + get { return hasAttachment; } + set { hasAttachment = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(HasAttachment))); } + } + + private SignalAttachment attachment; + public SignalAttachment Attachment + { + get { return attachment; } + set { attachment = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Attachment))); } + } + public Message() { this.InitializeComponent(); @@ -93,6 +109,13 @@ private void UpdateUI() FooterPanel.HorizontalAlignment = HorizontalAlignment.Left; } FancyTimestampBlock.Text = Utils.GetTimestamp(Model.Message.ComposedTimestamp); + + HasAttachment = false; + if (Model.Message.Attachments?.Count > 0) + { + HasAttachment = true; + Attachment = Model.Message.Attachments[0]; + } } } @@ -103,7 +126,7 @@ private void MessageBox_DataContextChanged(FrameworkElement sender, DataContextC private void ResendTextBlock_Tapped(object sender, Windows.UI.Xaml.Input.TappedRoutedEventArgs e) { - App.Handle.OutgoingQueue.Add(Model.Message); + App.Handle.ResendMessage(Model.Message); } internal bool HandleUpdate(SignalMessage updatedMessage) diff --git a/Signal-Windows/Signal-Windows.csproj b/Signal-Windows/Signal-Windows.csproj index a5b676c..35e534a 100644 --- a/Signal-Windows/Signal-Windows.csproj +++ b/Signal-Windows/Signal-Windows.csproj @@ -145,6 +145,9 @@ AddContactListElement.xaml + + Attachment.xaml + IdentityKeyChangeMessage.xaml @@ -226,6 +229,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile @@ -291,7 +298,7 @@ 2.1.1 - 2.0.0 + 2.1.1 5.3.0 @@ -310,6 +317,10 @@ + + {3f0cf02e-f260-48dc-a4ce-f8d72d6a079d} + libsignal-service-dotnet + {1934fd82-a5ea-4b71-b915-a1826593cb6e} Signal-Windows.Lib diff --git a/Signal-Windows/SignalWindowsFrontend.cs b/Signal-Windows/SignalWindowsFrontend.cs index acee33e..e5e9add 100644 --- a/Signal-Windows/SignalWindowsFrontend.cs +++ b/Signal-Windows/SignalWindowsFrontend.cs @@ -48,5 +48,10 @@ public void HandleAuthFailure() { // TODO } + + public void HandleAttachmentStatusChanged(SignalAttachment sa) + { + Locator.MainPageInstance.HandleAttachmentStatusChanged(sa); + } } } diff --git a/Signal-Windows/Utils.cs b/Signal-Windows/Utils.cs index b26f196..be7674e 100644 --- a/Signal-Windows/Utils.cs +++ b/Signal-Windows/Utils.cs @@ -252,6 +252,17 @@ public static bool ContainsCaseInsensitive(this string str, string value) return CultureInfo.InvariantCulture.CompareInfo.IndexOf(str, value, CompareOptions.IgnoreCase) >= 0; } + public static String BytesToString(long byteCount) //https://stackoverflow.com/a/4975942/1569755 + { + string[] suf = { "B", "KB", "MB", "GB", "TB", "PB", "EB" }; //Longs run out around EB + if (byteCount == 0) + return "0" + suf[0]; + long bytes = Math.Abs(byteCount); + int place = Convert.ToInt32(Math.Floor(Math.Log(bytes, 1024))); + double num = Math.Round(bytes / Math.Pow(1024, place), 1); + return (Math.Sign(byteCount) * num).ToString() + suf[place]; + } + public static string GetCountryCode(string ISO3166) //https://stackoverflow.com/questions/34837436/uwp-get-country-phone-number-prefix { var dictionary = new Dictionary(); diff --git a/Signal-Windows/ViewModels/FinishRegistrationPageViewModel.cs b/Signal-Windows/ViewModels/FinishRegistrationPageViewModel.cs index 318e6fe..8597a6b 100644 --- a/Signal-Windows/ViewModels/FinishRegistrationPageViewModel.cs +++ b/Signal-Windows/ViewModels/FinishRegistrationPageViewModel.cs @@ -46,7 +46,7 @@ await Task.Run(() => Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => { App.Store = store; - SignalLibHandle.Instance.Store = store; + App.Handle.Store = store; }).AsTask().Wait(); /* create prekeys */ diff --git a/Signal-Windows/ViewModels/LinkPageViewModel.cs b/Signal-Windows/ViewModels/LinkPageViewModel.cs index 381aacb..047bf9a 100644 --- a/Signal-Windows/ViewModels/LinkPageViewModel.cs +++ b/Signal-Windows/ViewModels/LinkPageViewModel.cs @@ -114,7 +114,7 @@ public async Task BeginLinking() { UIEnabled = false; App.Store = store; - SignalLibHandle.Instance.Store = store; + App.Handle.Store = store; }).AsTask().Wait(); /* create prekeys */ @@ -125,7 +125,7 @@ public async Task BeginLinking() Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => { App.Store = store; - SignalLibHandle.Instance.Store = store; + App.Handle.Store = store; View.Finish(true); }).AsTask().Wait(); }); diff --git a/Signal-Windows/ViewModels/MainPageViewModel.cs b/Signal-Windows/ViewModels/MainPageViewModel.cs index 02df0ec..cc60cf3 100644 --- a/Signal-Windows/ViewModels/MainPageViewModel.cs +++ b/Signal-Windows/ViewModels/MainPageViewModel.cs @@ -1,5 +1,6 @@ using GalaSoft.MvvmLight; using libsignalservice; +using libsignalservice.messages; using libsignalservice.push.exceptions; using libsignalservice.util; using Microsoft.Extensions.Logging; @@ -13,9 +14,12 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; +using System.IO; using System.Threading; using System.Threading.Tasks; using Windows.ApplicationModel.Core; +using Windows.Networking.BackgroundTransfer; +using Windows.Storage; using Windows.System; using Windows.UI.Core; using Windows.UI.Notifications; @@ -102,8 +106,7 @@ private async Task SendMessage(string messageText) Read = true, Type = SignalMessageType.Normal }; - await SignalLibHandle.Instance.SendMessage(message, SelectedThread); - Debug.WriteLine("keydown lock released"); + await App.Handle.SendMessage(message, SelectedThread); } return true; } @@ -281,6 +284,11 @@ public void ReplaceConversationList(List conversations) SelectConversation(RequestedConversationId); } } + + public void HandleAttachmentStatusChanged(SignalAttachment sa) + { + Logger.LogInformation("MPVM received attachment status update! {0}", sa.Status); + } #endregion } } \ No newline at end of file