1
1
using System ;
2
2
using System . Collections . Generic ;
3
- using System . Diagnostics ;
4
3
using System . IO ;
5
4
using System . Threading ;
6
5
using System . Threading . Tasks ;
@@ -54,6 +53,10 @@ public partial class App : Application
54
53
55
54
private bool _handleWindowClosed = true ;
56
55
56
+ private readonly ISettingsManager < CoderConnectSettings > _settingsManager ;
57
+
58
+ private readonly IHostApplicationLifetime _appLifetime ;
59
+
57
60
public App ( )
58
61
{
59
62
var builder = Host . CreateApplicationBuilder ( ) ;
@@ -116,6 +119,13 @@ public App()
116
119
// FileSyncListMainPage is created by FileSyncListWindow.
117
120
services . AddTransient < FileSyncListWindow > ( ) ;
118
121
122
+ services . AddSingleton < ISettingsManager < CoderConnectSettings > , SettingsManager < CoderConnectSettings > > ( ) ;
123
+ services . AddSingleton < IStartupManager , StartupManager > ( ) ;
124
+ // SettingsWindow views and view models
125
+ services . AddTransient < SettingsViewModel > ( ) ;
126
+ // SettingsMainPage is created by SettingsWindow.
127
+ services . AddTransient < SettingsWindow > ( ) ;
128
+
119
129
// DirectoryPickerWindow views and view models are created by FileSyncListViewModel.
120
130
121
131
// TrayWindow views and view models
@@ -136,6 +146,8 @@ public App()
136
146
_logger = _services . GetRequiredService < ILogger < App > > ( ) ;
137
147
_uriHandler = _services . GetRequiredService < IUriHandler > ( ) ;
138
148
_userNotifier = _services . GetRequiredService < IUserNotifier > ( ) ;
149
+ _settingsManager = _services . GetRequiredService < ISettingsManager < CoderConnectSettings > > ( ) ;
150
+ _appLifetime = _services . GetRequiredService < IHostApplicationLifetime > ( ) ;
139
151
140
152
InitializeComponent ( ) ;
141
153
}
@@ -168,57 +180,75 @@ protected override void OnLaunched(LaunchActivatedEventArgs args)
168
180
TrayWindow . AppWindow . Hide ( ) ;
169
181
} ;
170
182
171
- // Start connecting to the manager in the background.
183
+ _ = InitializeServicesAsync ( _appLifetime . ApplicationStopping ) ;
184
+ }
185
+
186
+ /// <summary>
187
+ /// Loads stored VPN credentials, reconnects the RPC controller,
188
+ /// and (optionally) starts the VPN tunnel on application launch.
189
+ /// </summary>
190
+ private async Task InitializeServicesAsync ( CancellationToken cancellationToken = default )
191
+ {
192
+ var credentialManager = _services . GetRequiredService < ICredentialManager > ( ) ;
172
193
var rpcController = _services . GetRequiredService < IRpcController > ( ) ;
173
- if ( rpcController . GetState ( ) . RpcLifecycle == RpcLifecycle . Disconnected )
174
- // Passing in a CT with no cancellation is desired here, because
175
- // the named pipe open will block until the pipe comes up.
176
- _logger . LogDebug ( "reconnecting with VPN service" ) ;
177
- _ = rpcController . Reconnect ( CancellationToken . None ) . ContinueWith ( t =>
194
+
195
+ using var credsCts = CancellationTokenSource . CreateLinkedTokenSource ( cancellationToken ) ;
196
+ credsCts . CancelAfter ( TimeSpan . FromSeconds ( 15 ) ) ;
197
+
198
+ var loadCredsTask = credentialManager . LoadCredentials ( credsCts . Token ) ;
199
+ var reconnectTask = rpcController . Reconnect ( cancellationToken ) ;
200
+ var settingsTask = _settingsManager . Read ( cancellationToken ) ;
201
+
202
+ var dependenciesLoaded = true ;
203
+
204
+ try
178
205
{
179
- if ( t . Exception != null )
180
- {
181
- _logger . LogError ( t . Exception , "failed to connect to VPN service" ) ;
182
- #if DEBUG
183
- Debug . WriteLine ( t . Exception ) ;
184
- Debugger . Break ( ) ;
185
- #endif
186
- }
187
- } ) ;
206
+ await Task . WhenAll ( loadCredsTask , reconnectTask , settingsTask ) ;
207
+ }
208
+ catch ( Exception )
209
+ {
210
+ if ( loadCredsTask . IsFaulted )
211
+ _logger . LogError ( loadCredsTask . Exception ! . GetBaseException ( ) ,
212
+ "Failed to load credentials" ) ;
188
213
189
- // Load the credentials in the background.
190
- var credentialManagerCts = new CancellationTokenSource ( TimeSpan . FromSeconds ( 15 ) ) ;
191
- var credentialManager = _services . GetRequiredService < ICredentialManager > ( ) ;
192
- _ = credentialManager . LoadCredentials ( credentialManagerCts . Token ) . ContinueWith ( t =>
214
+ if ( reconnectTask . IsFaulted )
215
+ _logger . LogError ( reconnectTask . Exception ! . GetBaseException ( ) ,
216
+ "Failed to connect to VPN service" ) ;
217
+
218
+ if ( settingsTask . IsFaulted )
219
+ _logger . LogError ( settingsTask . Exception ! . GetBaseException ( ) ,
220
+ "Failed to fetch Coder Connect settings" ) ;
221
+
222
+ // Don't attempt to connect if we failed to load credentials or reconnect.
223
+ // This will prevent the app from trying to connect to the VPN service.
224
+ dependenciesLoaded = false ;
225
+ }
226
+
227
+ var attemptCoderConnection = settingsTask . Result ? . ConnectOnLaunch ?? false ;
228
+ if ( dependenciesLoaded && attemptCoderConnection )
193
229
{
194
- if ( t . Exception != null )
230
+ try
195
231
{
196
- _logger . LogError ( t . Exception , "failed to load credentials" ) ;
197
- #if DEBUG
198
- Debug . WriteLine ( t . Exception ) ;
199
- Debugger . Break ( ) ;
200
- #endif
232
+ await rpcController . StartVpn ( cancellationToken ) ;
201
233
}
202
-
203
- credentialManagerCts . Dispose ( ) ;
204
- } , CancellationToken . None ) ;
234
+ catch ( Exception ex )
235
+ {
236
+ _logger . LogError ( ex , "Failed to connect on launch" ) ;
237
+ }
238
+ }
205
239
206
240
// Initialize file sync.
207
- // We're adding a 5s delay here to avoid race conditions when loading the mutagen binary.
208
- _ = Task . Delay ( 5000 ) . ContinueWith ( ( _ ) =>
241
+ using var syncSessionCts = CancellationTokenSource . CreateLinkedTokenSource ( cancellationToken ) ;
242
+ syncSessionCts . CancelAfter ( TimeSpan . FromSeconds ( 10 ) ) ;
243
+ var syncSessionController = _services . GetRequiredService < ISyncSessionController > ( ) ;
244
+ try
209
245
{
210
- var syncSessionCts = new CancellationTokenSource ( TimeSpan . FromSeconds ( 10 ) ) ;
211
- var syncSessionController = _services . GetRequiredService < ISyncSessionController > ( ) ;
212
- syncSessionController . RefreshState ( syncSessionCts . Token ) . ContinueWith (
213
- t =>
214
- {
215
- if ( t . IsCanceled || t . Exception != null )
216
- {
217
- _logger . LogError ( t . Exception , "failed to refresh sync state (canceled = {canceled})" , t . IsCanceled ) ;
218
- }
219
- syncSessionCts . Dispose ( ) ;
220
- } , CancellationToken . None ) ;
221
- } ) ;
246
+ await syncSessionController . RefreshState ( syncSessionCts . Token ) ;
247
+ }
248
+ catch ( Exception ex )
249
+ {
250
+ _logger . LogError ( $ "Failed to refresh sync session state { ex . Message } ", ex ) ;
251
+ }
222
252
}
223
253
224
254
public void OnActivated ( object ? sender , AppActivationArguments args )
0 commit comments