11using System ;
22using System . Collections . Generic ;
3- using System . Diagnostics ;
43using System . IO ;
54using System . Threading ;
65using System . Threading . Tasks ;
@@ -54,6 +53,10 @@ public partial class App : Application
5453
5554 private bool _handleWindowClosed = true ;
5655
56+ private readonly ISettingsManager < CoderConnectSettings > _settingsManager ;
57+
58+ private readonly IHostApplicationLifetime _appLifetime ;
59+
5760 public App ( )
5861 {
5962 var builder = Host . CreateApplicationBuilder ( ) ;
@@ -116,6 +119,13 @@ public App()
116119 // FileSyncListMainPage is created by FileSyncListWindow.
117120 services . AddTransient < FileSyncListWindow > ( ) ;
118121
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+
119129 // DirectoryPickerWindow views and view models are created by FileSyncListViewModel.
120130
121131 // TrayWindow views and view models
@@ -136,6 +146,8 @@ public App()
136146 _logger = _services . GetRequiredService < ILogger < App > > ( ) ;
137147 _uriHandler = _services . GetRequiredService < IUriHandler > ( ) ;
138148 _userNotifier = _services . GetRequiredService < IUserNotifier > ( ) ;
149+ _settingsManager = _services . GetRequiredService < ISettingsManager < CoderConnectSettings > > ( ) ;
150+ _appLifetime = _services . GetRequiredService < IHostApplicationLifetime > ( ) ;
139151
140152 InitializeComponent ( ) ;
141153 }
@@ -168,57 +180,75 @@ protected override void OnLaunched(LaunchActivatedEventArgs args)
168180 TrayWindow . AppWindow . Hide ( ) ;
169181 } ;
170182
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 > ( ) ;
172193 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
178205 {
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" ) ;
188213
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 )
193229 {
194- if ( t . Exception != null )
230+ try
195231 {
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 ) ;
201233 }
202-
203- credentialManagerCts . Dispose ( ) ;
204- } , CancellationToken . None ) ;
234+ catch ( Exception ex )
235+ {
236+ _logger . LogError ( ex , "Failed to connect on launch" ) ;
237+ }
238+ }
205239
206240 // 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
209245 {
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+ }
222252 }
223253
224254 public void OnActivated ( object ? sender , AppActivationArguments args )
0 commit comments