@@ -43,10 +43,10 @@ public interface ISignalLibHandle
4343 List < SignalMessageContainer > GetMessages ( SignalConversation thread , int startIndex , int count ) ;
4444 void SaveAndDispatchSignalConversation ( SignalConversation updatedConversation , SignalMessage updateMessage ) ;
4545 void PurgeAccountData ( ) ;
46- Task Acquire ( CoreDispatcher d , ISignalFrontend w ) ;
46+ Task < bool > Acquire ( CoreDispatcher d , ISignalFrontend w ) ;
4747 Task Reacquire ( ) ;
4848 void Release ( ) ;
49- void AddFrontend ( CoreDispatcher d , ISignalFrontend w ) ;
49+ bool AddFrontend ( CoreDispatcher d , ISignalFrontend w ) ;
5050 void RemoveFrontend ( CoreDispatcher d ) ;
5151
5252 // Background API
@@ -75,8 +75,11 @@ internal class SignalLibHandle : ISignalLibHandle
7575 public SemaphoreSlim SemaphoreSlim = new SemaphoreSlim ( 1 , 1 ) ;
7676 private bool Headless ;
7777 private bool Running = false ;
78+ private bool LikelyHasValidStore = false ;
7879 private CancellationTokenSource CancelSource = new CancellationTokenSource ( ) ;
7980 private Dictionary < CoreDispatcher , ISignalFrontend > Frames = new Dictionary < CoreDispatcher , ISignalFrontend > ( ) ;
81+ private CoreDispatcher MainWindowDispatcher ;
82+ private ISignalFrontend MainWindow ;
8083 private Task IncomingMessagesTask ;
8184 private Task OutgoingMessagesTask ;
8285 internal OutgoingMessages OutgoingMessages ;
@@ -96,23 +99,31 @@ public SignalLibHandle(bool headless)
9699 Instance = this ;
97100 }
98101
99- public void AddFrontend ( CoreDispatcher d , ISignalFrontend w )
102+ public bool AddFrontend ( CoreDispatcher d , ISignalFrontend w )
100103 {
101104 Logger . LogTrace ( "AddFrontend() locking" ) ;
102105 SemaphoreSlim . Wait ( CancelSource . Token ) ;
103- Logger . LogTrace ( "AddFrontend() locked" ) ;
104- if ( Running )
106+ try
105107 {
106- Logger . LogInformation ( "Registering frontend of dispatcher {0}" , w . GetHashCode ( ) ) ;
107- Frames . Add ( d , w ) ;
108- w . ReplaceConversationList ( GetConversations ( ) ) ;
108+ Logger . LogTrace ( "AddFrontend() locked" ) ;
109+ if ( Running && LikelyHasValidStore )
110+ {
111+ Logger . LogInformation ( "Registering frontend of dispatcher {0}" , w . GetHashCode ( ) ) ;
112+ Frames . Add ( d , w ) ;
113+ w . ReplaceConversationList ( GetConversations ( ) ) ;
114+ return true ;
115+ }
116+ else
117+ {
118+ Logger . LogInformation ( "Ignoring AddFrontend call" ) ;
119+ return false ;
120+ }
109121 }
110- else
122+ finally
111123 {
112- Logger . LogInformation ( "Ignoring AddFrontend call, release in progress" ) ;
124+ SemaphoreSlim . Release ( ) ;
125+ Logger . LogTrace ( "AddFrontend() released" ) ;
113126 }
114- SemaphoreSlim . Release ( ) ;
115- Logger . LogTrace ( "AddFrontend() released" ) ;
116127 }
117128
118129 public void RemoveFrontend ( CoreDispatcher d )
@@ -136,44 +147,60 @@ public void PurgeAccountData()
136147 Logger . LogTrace ( "PurgeAccountData() released" ) ;
137148 }
138149
139- public async Task Acquire ( CoreDispatcher d , ISignalFrontend w ) //TODO wrap trycatch dispatch auth failure
150+ public async Task < bool > Acquire ( CoreDispatcher d , ISignalFrontend w ) //TODO wrap trycatch dispatch auth failure
140151 {
141152 Logger . LogTrace ( "Acquire() locking" ) ;
142153 CancelSource = new CancellationTokenSource ( ) ;
143154 SemaphoreSlim . Wait ( CancelSource . Token ) ;
144- GlobalResetEvent = LibUtils . OpenResetEventSet ( ) ;
145- LibUtils . Lock ( ) ;
146- GlobalResetEvent . Reset ( ) ;
147- var getConversationsTask = Task . Run ( ( ) =>
148- {
149- return GetConversations ( ) ; // we want to display the conversations asap!
150- } ) ;
151- Logger . LogDebug ( "Acquire() locked (global and local)" ) ;
152- Instance = this ;
153- Frames . Add ( d , w ) ;
154- w . ReplaceConversationList ( await getConversationsTask ) ;
155- var failTask = Task . Run ( ( ) =>
156- {
157- SignalDBContext . FailAllPendingMessages ( ) ; // TODO GetMessages needs to be protected by semaphoreslim as we fail defered
158- } ) ;
159- Store = await Task . Run ( ( ) =>
155+ try
160156 {
161- return LibsignalDBContext . GetSignalStore ( ) ;
162- } ) ;
163- if ( Store == null )
157+ GlobalResetEvent = LibUtils . OpenResetEventSet ( ) ;
158+ LibUtils . Lock ( ) ;
159+ GlobalResetEvent . Reset ( ) ;
160+ MainWindowDispatcher = d ;
161+ MainWindow = w ;
162+ Logger . LogDebug ( "Acquire() locked (global and local)" ) ;
163+ var getConversationsTask = Task . Run ( ( ) =>
164+ {
165+ return GetConversations ( ) ; // we want to display the conversations asap!
166+ } ) ;
167+ Instance = this ;
168+ Frames . Add ( d , w ) ;
169+ w . ReplaceConversationList ( await getConversationsTask ) ;
170+ var failTask = Task . Run ( ( ) =>
171+ {
172+ SignalDBContext . FailAllPendingMessages ( ) ; // TODO GetMessages needs to be protected by semaphoreslim as we fail defered
173+ } ) ;
174+ Store = await Task . Run ( ( ) =>
175+ {
176+ return LibsignalDBContext . GetSignalStore ( ) ;
177+ } ) ;
178+ if ( Store == null )
179+ {
180+ return false ;
181+ }
182+ else
183+ {
184+ LikelyHasValidStore = true ;
185+ }
186+ var initNetwork = Task . Run ( ( ) =>
187+ {
188+ InitNetwork ( ) ;
189+ } ) ;
190+ var recoverDownloadsTask = Task . Run ( ( ) =>
191+ {
192+ RecoverDownloads ( ) . Wait ( ) ;
193+ } ) ;
194+ await failTask ; // has to complete before messages are loaded
195+ await recoverDownloadsTask ;
196+ Running = true ;
197+ return true ;
198+ }
199+ finally
164200 {
165201 SemaphoreSlim . Release ( ) ;
166- throw new Exception ( "Signal Store has not been setup yet. ") ;
202+ Logger . LogTrace ( "Acquire() released ") ;
167203 }
168- await Task . Run ( ( ) =>
169- {
170- InitNetwork ( ) ;
171- RecoverDownloads ( ) . Wait ( ) ;
172- } ) ;
173- await failTask ; // has to complete before messages are loaded
174- Running = true ;
175- Logger . LogTrace ( "Acquire() releasing" ) ;
176- SemaphoreSlim . Release ( ) ;
177204 }
178205
179206 public void BackgroundAcquire ( )
@@ -192,46 +219,65 @@ public async Task Reacquire()
192219 Logger . LogTrace ( "Reacquire() locking" ) ;
193220 CancelSource = new CancellationTokenSource ( ) ;
194221 SemaphoreSlim . Wait ( CancelSource . Token ) ;
195- GlobalResetEvent = LibUtils . OpenResetEventSet ( ) ;
196- LibUtils . Lock ( ) ;
197- GlobalResetEvent . Reset ( ) ;
198- LibsignalDBContext . ClearSessionCache ( ) ;
199- Instance = this ;
200- await Task . Run ( ( ) =>
222+ try
201223 {
202- List < Task > tasks = new List < Task > ( ) ;
203- foreach ( var f in Frames )
224+ GlobalResetEvent = LibUtils . OpenResetEventSet ( ) ;
225+ LibUtils . Lock ( ) ;
226+ GlobalResetEvent . Reset ( ) ;
227+ LibsignalDBContext . ClearSessionCache ( ) ;
228+ Instance = this ;
229+ await Task . Run ( ( ) =>
204230 {
205- var conversations = GetConversations ( ) ;
206- tasks . Add ( f . Key . RunTaskAsync ( ( ) =>
231+ List < Task > tasks = new List < Task > ( ) ;
232+ foreach ( var f in Frames )
207233 {
208- f . Value . ReplaceConversationList ( conversations ) ;
209- } ) ) ;
234+ var conversations = GetConversations ( ) ;
235+ tasks . Add ( f . Key . RunTaskAsync ( ( ) =>
236+ {
237+ f . Value . ReplaceConversationList ( conversations ) ;
238+ } ) ) ;
239+ }
240+ Task . WaitAll ( tasks . ToArray ( ) ) ;
241+ RecoverDownloads ( ) . Wait ( ) ;
242+ } ) ;
243+ if ( LikelyHasValidStore )
244+ {
245+ var initNetworkTask = Task . Run ( ( ) =>
246+ {
247+ InitNetwork ( ) ;
248+ } ) ;
210249 }
211- Task . WaitAll ( tasks . ToArray ( ) ) ;
212- InitNetwork ( ) ;
213- RecoverDownloads ( ) . Wait ( ) ;
214- } ) ;
215- Running = true ;
216- Logger . LogTrace ( "Reacquire() releasing " ) ;
217- SemaphoreSlim . Release ( ) ;
250+ Running = true ;
251+ }
252+ finally
253+ {
254+ SemaphoreSlim . Release ( ) ;
255+ Logger . LogTrace ( "Reacquire() released " ) ;
256+ }
218257 }
219258
220259 public void Release ( )
221260 {
222261 //TODO invalidate view information
223262 Logger . LogTrace ( "Release() locking" ) ;
224- SemaphoreSlim . Wait ( CancelSource . Token ) ;
225- Running = false ;
226- CancelSource . Cancel ( ) ;
227- IncomingMessagesTask ? . Wait ( ) ;
228- OutgoingMessagesTask ? . Wait ( ) ;
229- Instance = null ;
230- Logger . LogTrace ( "Release() releasing global)" ) ;
231- LibUtils . Unlock ( ) ;
232- Logger . LogTrace ( "Release() releasing local)" ) ;
233- SemaphoreSlim . Release ( ) ;
234- Logger . LogTrace ( "Release() released" ) ;
263+ if ( Running )
264+ {
265+ SemaphoreSlim . Wait ( CancelSource . Token ) ;
266+ Running = false ;
267+ CancelSource . Cancel ( ) ;
268+ IncomingMessagesTask ? . Wait ( ) ;
269+ OutgoingMessagesTask ? . Wait ( ) ;
270+ Instance = null ;
271+ Logger . LogTrace ( "Release() releasing global)" ) ;
272+ LibUtils . Unlock ( ) ;
273+ Logger . LogTrace ( "Release() releasing local)" ) ;
274+ SemaphoreSlim . Release ( ) ;
275+ Logger . LogTrace ( "Release() released" ) ;
276+ }
277+ else
278+ {
279+ Logger . LogTrace ( "SignalLibHandle was already closed" ) ;
280+ }
235281 }
236282
237283 public void BackgroundRelease ( )
@@ -317,6 +363,26 @@ public void StartAttachmentDownload(SignalAttachment sa)
317363 #endregion
318364
319365 #region internal api
366+ internal void DispatchHandleAuthFailure ( )
367+ {
368+ List < Task > operations = new List < Task > ( ) ;
369+ foreach ( var dispatcher in Frames . Keys )
370+ {
371+ operations . Add ( dispatcher . RunTaskAsync ( ( ) =>
372+ {
373+ try
374+ {
375+ Frames [ dispatcher ] . HandleAuthFailure ( ) ;
376+ }
377+ catch ( Exception e )
378+ {
379+ Logger . LogError ( "DispatchHandleAuthFailure failed: {0}\n {1}" , e . Message , e . StackTrace ) ;
380+ }
381+ } ) ) ;
382+ }
383+ Task . WaitAll ( operations . ToArray ( ) ) ;
384+ }
385+
320386 internal void SaveAndDispatchSignalMessage ( SignalMessage message , SignalConversation conversation )
321387 {
322388 conversation . MessagesCount += 1 ;
@@ -464,14 +530,48 @@ private List<SignalConversation> GetConversations()
464530 return conversations ;
465531 }
466532
533+ /// <summary>
534+ /// Initializes the websocket connection handling. Must not not be called on a UI thread. Must not be called on a task which holds the handle lock.
535+ /// </summary>
467536 private void InitNetwork ( )
468537 {
469- MessageReceiver = new SignalServiceMessageReceiver ( CancelSource . Token , LibUtils . ServiceUrls , new StaticCredentialsProvider ( Store . Username , Store . Password , Store . SignalingKey , ( int ) Store . DeviceId ) , LibUtils . USER_AGENT ) ;
470- Pipe = MessageReceiver . createMessagePipe ( ) ;
471- MessageSender = new SignalServiceMessageSender ( CancelSource . Token , LibUtils . ServiceUrls , Store . Username , Store . Password , ( int ) Store . DeviceId , new Store ( ) , Pipe , null , LibUtils . USER_AGENT ) ;
472- IncomingMessagesTask = Task . Factory . StartNew ( ( ) => new IncomingMessages ( CancelSource . Token , Pipe , this ) . HandleIncomingMessages ( ) , TaskCreationOptions . LongRunning ) ;
473- OutgoingMessages = new OutgoingMessages ( CancelSource . Token , MessageSender , this ) ;
474- OutgoingMessagesTask = Task . Factory . StartNew ( ( ) => OutgoingMessages . HandleOutgoingMessages ( ) , TaskCreationOptions . LongRunning ) ;
538+ try
539+ {
540+ MessageReceiver = new SignalServiceMessageReceiver ( CancelSource . Token , LibUtils . ServiceUrls , new StaticCredentialsProvider ( Store . Username , Store . Password , Store . SignalingKey , ( int ) Store . DeviceId ) , LibUtils . USER_AGENT ) ;
541+ Pipe = MessageReceiver . createMessagePipe ( ) ;
542+ MessageSender = new SignalServiceMessageSender ( CancelSource . Token , LibUtils . ServiceUrls , Store . Username , Store . Password , ( int ) Store . DeviceId , new Store ( ) , Pipe , null , LibUtils . USER_AGENT ) ;
543+ IncomingMessagesTask = Task . Factory . StartNew ( ( ) => new IncomingMessages ( CancelSource . Token , Pipe , this ) . HandleIncomingMessages ( ) , TaskCreationOptions . LongRunning ) ;
544+ OutgoingMessages = new OutgoingMessages ( CancelSource . Token , MessageSender , this ) ;
545+ OutgoingMessagesTask = Task . Factory . StartNew ( ( ) => OutgoingMessages . HandleOutgoingMessages ( ) , TaskCreationOptions . LongRunning ) ;
546+ }
547+ catch ( Exception e )
548+ {
549+ Logger . LogError ( "InitNetwork failed: {0}\n {1}" , e . Message , e . StackTrace ) ;
550+ HandleAuthFailure ( ) ;
551+ }
552+ }
553+
554+ /// <summary>
555+ /// Dispatches the auth failure to all frontends and resets the frontend dict. Must not be called on a UI thread. Must not be called on a task which holds the handle lock.
556+ /// </summary>
557+ private void HandleAuthFailure ( )
558+ {
559+ Logger . LogTrace ( "HandleAuthFailure() locking" ) ;
560+ SemaphoreSlim . Wait ( CancelSource . Token ) ;
561+ try
562+ {
563+ LikelyHasValidStore = false ;
564+ Running = false ;
565+ CancelSource . Cancel ( ) ;
566+ DispatchHandleAuthFailure ( ) ;
567+ Frames . Clear ( ) ;
568+ Frames . Add ( MainWindowDispatcher , MainWindow ) ;
569+ }
570+ finally
571+ {
572+ SemaphoreSlim . Release ( ) ;
573+ Logger . LogTrace ( "HandleAuthFailure() released" ) ;
574+ }
475575 }
476576
477577 private void TryScheduleAttachmentDownload ( SignalAttachment attachment )
0 commit comments