@@ -25,12 +25,12 @@ namespace Signal_Windows.Lib
2525 public interface ISignalFrontend
2626 {
2727 void AddOrUpdateConversation ( SignalConversation conversation , SignalMessage updateMessage ) ;
28- Task HandleMessage ( SignalMessage message , SignalConversation conversation ) ;
29- Task HandleIdentitykeyChange ( LinkedList < SignalMessage > messages ) ;
28+ void HandleMessage ( SignalMessage message , SignalConversation conversation ) ;
29+ void HandleIdentitykeyChange ( LinkedList < SignalMessage > messages ) ;
3030 void HandleMessageUpdate ( SignalMessage updatedMessage ) ;
3131 void ReplaceConversationList ( List < SignalConversation > conversations ) ;
3232 void HandleAuthFailure ( ) ;
33- void HandleAttachmentStatusChanged ( SignalAttachment sa , SignalAttachmentStatus newStatus ) ;
33+ void HandleAttachmentStatusChanged ( SignalAttachment sa ) ;
3434 }
3535
3636 public interface ISignalLibHandle
@@ -84,6 +84,7 @@ internal class SignalLibHandle : ISignalLibHandle
8484 private SignalServiceMessageReceiver MessageReceiver ;
8585 public BlockingCollection < SignalMessage > OutgoingQueue = new BlockingCollection < SignalMessage > ( new ConcurrentQueue < SignalMessage > ( ) ) ;
8686 private EventWaitHandle GlobalResetEvent ;
87+ private ISet < DownloadOperation > Downloads = new HashSet < DownloadOperation > ( ) ;
8788
8889 public event EventHandler < SignalMessageEventArgs > SignalMessageEvent ;
8990
@@ -166,6 +167,7 @@ public async Task Acquire(CoreDispatcher d, ISignalFrontend w) //TODO wrap tryca
166167 await Task . Run ( ( ) =>
167168 {
168169 InitNetwork ( ) ;
170+ RecoverDownloads ( ) . Wait ( ) ;
169171 } ) ;
170172 await failTask ; // has to complete before messages are loaded
171173 Running = true ;
@@ -180,6 +182,7 @@ public void BackgroundAcquire()
180182 SignalDBContext . FailAllPendingMessages ( ) ;
181183 Store = LibsignalDBContext . GetSignalStore ( ) ;
182184 InitNetwork ( ) ;
185+ RecoverDownloads ( ) . Wait ( ) ;
183186 Running = true ;
184187 }
185188
@@ -206,6 +209,8 @@ await Task.Run(() =>
206209 }
207210 Task . WaitAll ( tasks . ToArray ( ) ) ;
208211 InitNetwork ( ) ;
212+ Downloads . Clear ( ) ;
213+ RecoverDownloads ( ) . Wait ( ) ;
209214 } ) ;
210215 Running = true ;
211216 Logger . LogTrace ( "Reacquire() releasing" ) ;
@@ -240,15 +245,22 @@ public void BackgroundRelease()
240245
241246 public async Task SendMessage ( SignalMessage message , SignalConversation conversation )
242247 {
243- await Task . Run ( async ( ) =>
248+ await Task . Run ( ( ) =>
244249 {
245250 Logger . LogTrace ( "SendMessage() locking" ) ;
246- await SemaphoreSlim . WaitAsync ( CancelSource . Token ) ;
247- Logger . LogDebug ( "SendMessage saving message " + message . ComposedTimestamp ) ;
248- SaveAndDispatchSignalMessage ( message , conversation ) ;
249- OutgoingQueue . Add ( message ) ;
250- SemaphoreSlim . Release ( ) ;
251- Logger . LogTrace ( "SendMessage() released" ) ;
251+ SemaphoreSlim . Wait ( CancelSource . Token ) ;
252+ Logger . LogTrace ( "SendMessage() locked" ) ;
253+ try
254+ {
255+ Logger . LogDebug ( "SendMessage saving message " + message . ComposedTimestamp ) ;
256+ SaveAndDispatchSignalMessage ( message , conversation ) ;
257+ OutgoingQueue . Add ( message ) ;
258+ }
259+ finally
260+ {
261+ SemaphoreSlim . Release ( ) ;
262+ Logger . LogTrace ( "SendMessage() released" ) ;
263+ }
252264 } ) ;
253265 }
254266
@@ -286,26 +298,6 @@ public void DecryptAttachment(SignalServiceAttachmentPointer pointer, Stream tem
286298 {
287299 MessageReceiver . DecryptAttachment ( pointer , tempStream , downloadStream ) ;
288300 }
289-
290- public async Task HandleDownload ( DownloadOperation download , bool start , SignalAttachment attachment )
291- {
292- if ( start )
293- {
294- await download . StartAsync ( ) ;
295- }
296- else
297- {
298- await download . AttachAsync ( ) ;
299- }
300- IStorageFile tempFile = download . ResultFile ;
301- StorageFile downloadedFile = await DownloadsFolder . CreateFileAsync ( attachment . SentFileName , CreationCollisionOption . GenerateUniqueName ) ;
302- using ( var tempFileStream = ( await tempFile . OpenAsync ( FileAccessMode . ReadWrite ) ) . AsStream ( ) )
303- using ( var downloadedFileStream = ( await downloadedFile . OpenAsync ( FileAccessMode . ReadWrite ) ) . AsStream ( ) )
304- {
305- DecryptAttachment ( attachment . ToAttachmentPointer ( ) , tempFileStream , downloadedFileStream ) ;
306- }
307- await tempFile . DeleteAsync ( ) ;
308- }
309301 #endregion
310302
311303 #region attachment api
@@ -327,21 +319,23 @@ internal void SaveAndDispatchSignalMessage(SignalMessage message, SignalConversa
327319 {
328320 conversation . UnreadCount = 0 ;
329321 conversation . LastSeenMessageIndex = conversation . MessagesCount ;
322+
330323 }
331324 SignalDBContext . SaveMessageLocked ( message ) ;
332325 conversation . LastMessage = message ;
333326 conversation . LastActiveTimestamp = message . ComposedTimestamp ;
327+ StartAttachmentDownloads ( message ) ;
334328 DispatchHandleMessage ( message , conversation ) ;
335329 }
336330
337331 internal void DispatchHandleIdentityKeyChange ( LinkedList < SignalMessage > messages )
338332 {
339- List < Task > operations = new List < Task > ( ) ; ;
333+ List < Task > operations = new List < Task > ( ) ;
340334 foreach ( var dispatcher in Frames . Keys )
341335 {
342- operations . Add ( dispatcher . RunTaskAsync ( async ( ) =>
336+ operations . Add ( dispatcher . RunTaskAsync ( ( ) =>
343337 {
344- await Frames [ dispatcher ] . HandleIdentitykeyChange ( messages ) ;
338+ Frames [ dispatcher ] . HandleIdentitykeyChange ( messages ) ;
345339 } ) ) ;
346340 }
347341 Task . WaitAll ( operations . ToArray ( ) ) ;
@@ -365,9 +359,9 @@ internal void DispatchHandleMessage(SignalMessage message, SignalConversation co
365359 List < Task > operations = new List < Task > ( ) ;
366360 foreach ( var dispatcher in Frames . Keys )
367361 {
368- operations . Add ( dispatcher . RunTaskAsync ( async ( ) =>
362+ operations . Add ( dispatcher . RunTaskAsync ( ( ) =>
369363 {
370- await Frames [ dispatcher ] . HandleMessage ( message , conversation ) ;
364+ Frames [ dispatcher ] . HandleMessage ( message , conversation ) ;
371365 } ) ) ;
372366 }
373367 SignalMessageEvent ? . Invoke ( this , new SignalMessageEventArgs ( message , Events . SignalMessageType . NormalMessage ) ) ;
@@ -470,6 +464,108 @@ private void InitNetwork()
470464 OutgoingMessages = new OutgoingMessages ( CancelSource . Token , MessageSender , this ) ;
471465 OutgoingMessagesTask = Task . Factory . StartNew ( ( ) => OutgoingMessages . HandleOutgoingMessages ( ) , TaskCreationOptions . LongRunning ) ;
472466 }
467+
468+ private void StartAttachmentDownloads ( SignalMessage message )
469+ {
470+ if ( message . Attachments != null )
471+ {
472+ foreach ( var attachment in message . Attachments )
473+ {
474+ if ( Downloads . Count < 100 )
475+ {
476+ SignalServiceAttachmentPointer attachmentPointer = attachment . ToAttachmentPointer ( ) ;
477+ IStorageFolder localFolder = ApplicationData . Current . LocalFolder ;
478+ IStorageFile tmpDownload = Task . Run ( async ( ) =>
479+ {
480+ return await ApplicationData . Current . LocalCacheFolder . CreateFileAsync ( @"Attachments\" + attachment . Id + ".cipher" ) ;
481+ } ) . Result ;
482+ BackgroundDownloader downloader = new BackgroundDownloader ( ) ;
483+ downloader . SetRequestHeader ( "Content-Type" , "application/octet-stream" ) ;
484+ downloader . SuccessToastNotification = LibUtils . CreateToastNotification ( $ "{ attachment . SentFileName } has finished downloading.") ;
485+ downloader . FailureToastNotification = LibUtils . CreateToastNotification ( $ "{ attachment . SentFileName } has failed to download.") ;
486+ // this is the recommended way to call CreateDownload
487+ // see https://docs.microsoft.com/en-us/uwp/api/windows.networking.backgroundtransfer.backgrounddownloader#Methods
488+ DownloadOperation download = downloader . CreateDownload ( new Uri ( RetrieveAttachmentUrl ( attachmentPointer ) ) , tmpDownload ) ;
489+ SignalDBContext . UpdateAttachmentFileName ( attachment ) ;
490+ Downloads . Add ( download ) ;
491+ Task . Run ( async ( ) =>
492+ {
493+ Logger . LogInformation ( "Waiting for download {0}({1})" , attachment . SentFileName , attachment . Id ) ;
494+ await download . StartAsync ( ) ;
495+ await HandleSuccessfullDownload ( attachment , tmpDownload , download ) ;
496+ } ) ;
497+ }
498+ }
499+ }
500+ }
501+
502+ private async Task HandleSuccessfullDownload ( SignalAttachment attachment , IStorageFile tmpDownload , DownloadOperation download )
503+ {
504+ StorageFile plaintextFile = await ApplicationData . Current . LocalCacheFolder . CreateFileAsync ( @"Attachments\" + attachment . Id + ".plain" , CreationCollisionOption . ReplaceExisting ) ;
505+ using ( var tmpFileStream = ( await tmpDownload . OpenAsync ( FileAccessMode . ReadWrite ) ) . AsStream ( ) )
506+ using ( var plaintextFileStream = ( await plaintextFile . OpenAsync ( FileAccessMode . ReadWrite ) ) . AsStream ( ) )
507+ {
508+ Logger . LogInformation ( "Decrypting to {0}\\ {1}" , plaintextFile . Path , plaintextFile . Name ) ;
509+ DecryptAttachment ( attachment . ToAttachmentPointer ( ) , tmpFileStream , plaintextFileStream ) ;
510+ }
511+ Logger . LogInformation ( "Deleting tmpFile {0}" , tmpDownload . Name ) ;
512+ await tmpDownload . DeleteAsync ( ) ;
513+ attachment . Status = SignalAttachmentStatus . Finished ;
514+ DispatchAttachmentStatusChanged ( download , attachment ) ;
515+ }
516+
517+ private async Task RecoverDownloads ( )
518+ {
519+ var downloads = await BackgroundDownloader . GetCurrentDownloadsAsync ( ) ;
520+ foreach ( DownloadOperation download in downloads )
521+ {
522+ try
523+ {
524+ SignalAttachment attachment = SignalDBContext . GetAttachmentByFileNameLocked ( download . Guid . ToString ( ) ) ;
525+ if ( attachment != null )
526+ {
527+ Downloads . Add ( download ) ;
528+ var t = Task . Run ( async ( ) =>
529+ {
530+ Logger . LogInformation ( "Attaching to download {0} ({1})" , attachment . Id , download . Guid ) ;
531+ await download . AttachAsync ( ) ;
532+ await HandleSuccessfullDownload ( attachment , download . ResultFile , download ) ;
533+ } ) ;
534+ }
535+ else
536+ {
537+ Logger . LogInformation ( "Aborting unrecognized download {0}" , download . Guid ) ;
538+ download . AttachAsync ( ) . Cancel ( ) ;
539+ }
540+ }
541+ catch ( Exception e )
542+ {
543+ Logger . LogError ( "TriageDownloads encountered an error: {0}\n {1}" , e . Message , e . StackTrace ) ;
544+ }
545+ }
546+ }
547+
548+ private void DispatchAttachmentStatusChanged ( DownloadOperation op , SignalAttachment attachment )
549+ {
550+ try
551+ {
552+ SemaphoreSlim . Wait ( CancelSource . Token ) ;
553+ Downloads . Remove ( op ) ;
554+ List < Task > operations = new List < Task > ( ) ;
555+ foreach ( var dispatcher in Frames . Keys )
556+ {
557+ operations . Add ( dispatcher . RunTaskAsync ( ( ) =>
558+ {
559+ Frames [ dispatcher ] . HandleAttachmentStatusChanged ( attachment ) ;
560+ } ) ) ;
561+ }
562+ Task . WaitAll ( operations . ToArray ( ) ) ;
563+ }
564+ finally
565+ {
566+ SemaphoreSlim . Release ( ) ;
567+ }
568+ }
473569 #endregion
474570 }
475571}
0 commit comments