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 ;
@@ -44,6 +43,10 @@ public partial class App : Application
44
43
private readonly ILogger < App > _logger ;
45
44
private readonly IUriHandler _uriHandler ;
46
45
46
+ private readonly ISettingsManager < CoderConnectSettings > _settingsManager ;
47
+
48
+ private readonly IHostApplicationLifetime _appLifetime ;
49
+
47
50
public App ( )
48
51
{
49
52
var builder = Host . CreateApplicationBuilder ( ) ;
@@ -90,6 +93,13 @@ public App()
90
93
// FileSyncListMainPage is created by FileSyncListWindow.
91
94
services . AddTransient < FileSyncListWindow > ( ) ;
92
95
96
+ services . AddSingleton < ISettingsManager < CoderConnectSettings > , SettingsManager < CoderConnectSettings > > ( ) ;
97
+ services . AddSingleton < IStartupManager , StartupManager > ( ) ;
98
+ // SettingsWindow views and view models
99
+ services . AddTransient < SettingsViewModel > ( ) ;
100
+ // SettingsMainPage is created by SettingsWindow.
101
+ services . AddTransient < SettingsWindow > ( ) ;
102
+
93
103
// DirectoryPickerWindow views and view models are created by FileSyncListViewModel.
94
104
95
105
// TrayWindow views and view models
@@ -107,8 +117,10 @@ public App()
107
117
services . AddTransient < TrayWindow > ( ) ;
108
118
109
119
_services = services . BuildServiceProvider ( ) ;
110
- _logger = ( ILogger < App > ) _services . GetService ( typeof ( ILogger < App > ) ) ! ;
111
- _uriHandler = ( IUriHandler ) _services . GetService ( typeof ( IUriHandler ) ) ! ;
120
+ _logger = _services . GetRequiredService < ILogger < App > > ( ) ;
121
+ _uriHandler = _services . GetRequiredService < IUriHandler > ( ) ;
122
+ _settingsManager = _services . GetRequiredService < ISettingsManager < CoderConnectSettings > > ( ) ;
123
+ _appLifetime = _services . GetRequiredService < IHostApplicationLifetime > ( ) ;
112
124
113
125
InitializeComponent ( ) ;
114
126
}
@@ -129,58 +141,8 @@ public async Task ExitApplication()
129
141
protected override void OnLaunched ( LaunchActivatedEventArgs args )
130
142
{
131
143
_logger . LogInformation ( "new instance launched" ) ;
132
- // Start connecting to the manager in the background.
133
- var rpcController = _services . GetRequiredService < IRpcController > ( ) ;
134
- if ( rpcController . GetState ( ) . RpcLifecycle == RpcLifecycle . Disconnected )
135
- // Passing in a CT with no cancellation is desired here, because
136
- // the named pipe open will block until the pipe comes up.
137
- _logger . LogDebug ( "reconnecting with VPN service" ) ;
138
- _ = rpcController . Reconnect ( CancellationToken . None ) . ContinueWith ( t =>
139
- {
140
- if ( t . Exception != null )
141
- {
142
- _logger . LogError ( t . Exception , "failed to connect to VPN service" ) ;
143
- #if DEBUG
144
- Debug . WriteLine ( t . Exception ) ;
145
- Debugger . Break ( ) ;
146
- #endif
147
- }
148
- } ) ;
149
-
150
- // Load the credentials in the background.
151
- var credentialManagerCts = new CancellationTokenSource ( TimeSpan . FromSeconds ( 15 ) ) ;
152
- var credentialManager = _services . GetRequiredService < ICredentialManager > ( ) ;
153
- _ = credentialManager . LoadCredentials ( credentialManagerCts . Token ) . ContinueWith ( t =>
154
- {
155
- if ( t . Exception != null )
156
- {
157
- _logger . LogError ( t . Exception , "failed to load credentials" ) ;
158
- #if DEBUG
159
- Debug . WriteLine ( t . Exception ) ;
160
- Debugger . Break ( ) ;
161
- #endif
162
- }
163
144
164
- credentialManagerCts . Dispose ( ) ;
165
- } , CancellationToken . None ) ;
166
-
167
- // Initialize file sync.
168
- // We're adding a 5s delay here to avoid race conditions when loading the mutagen binary.
169
-
170
- _ = Task . Delay ( 5000 ) . ContinueWith ( ( _ ) =>
171
- {
172
- var syncSessionCts = new CancellationTokenSource ( TimeSpan . FromSeconds ( 10 ) ) ;
173
- var syncSessionController = _services . GetRequiredService < ISyncSessionController > ( ) ;
174
- syncSessionController . RefreshState ( syncSessionCts . Token ) . ContinueWith (
175
- t =>
176
- {
177
- if ( t . IsCanceled || t . Exception != null )
178
- {
179
- _logger . LogError ( t . Exception , "failed to refresh sync state (canceled = {canceled})" , t . IsCanceled ) ;
180
- }
181
- syncSessionCts . Dispose ( ) ;
182
- } , CancellationToken . None ) ;
183
- } ) ;
145
+ _ = InitializeServicesAsync ( _appLifetime . ApplicationStopping ) ;
184
146
185
147
// Prevent the TrayWindow from closing, just hide it.
186
148
var trayWindow = _services . GetRequiredService < TrayWindow > ( ) ;
@@ -192,6 +154,74 @@ protected override void OnLaunched(LaunchActivatedEventArgs args)
192
154
} ;
193
155
}
194
156
157
+ /// <summary>
158
+ /// Loads stored VPN credentials, reconnects the RPC controller,
159
+ /// and (optionally) starts the VPN tunnel on application launch.
160
+ /// </summary>
161
+ private async Task InitializeServicesAsync ( CancellationToken cancellationToken = default )
162
+ {
163
+ var credentialManager = _services . GetRequiredService < ICredentialManager > ( ) ;
164
+ var rpcController = _services . GetRequiredService < IRpcController > ( ) ;
165
+
166
+ using var credsCts = CancellationTokenSource . CreateLinkedTokenSource ( cancellationToken ) ;
167
+ credsCts . CancelAfter ( TimeSpan . FromSeconds ( 15 ) ) ;
168
+
169
+ var loadCredsTask = credentialManager . LoadCredentials ( credsCts . Token ) ;
170
+ var reconnectTask = rpcController . Reconnect ( cancellationToken ) ;
171
+ var settingsTask = _settingsManager . Read ( cancellationToken ) ;
172
+
173
+ var dependenciesLoaded = true ;
174
+
175
+ try
176
+ {
177
+ await Task . WhenAll ( loadCredsTask , reconnectTask , settingsTask ) ;
178
+ }
179
+ catch ( Exception )
180
+ {
181
+ if ( loadCredsTask . IsFaulted )
182
+ _logger . LogError ( loadCredsTask . Exception ! . GetBaseException ( ) ,
183
+ "Failed to load credentials" ) ;
184
+
185
+ if ( reconnectTask . IsFaulted )
186
+ _logger . LogError ( reconnectTask . Exception ! . GetBaseException ( ) ,
187
+ "Failed to connect to VPN service" ) ;
188
+
189
+ if ( settingsTask . IsFaulted )
190
+ _logger . LogError ( settingsTask . Exception ! . GetBaseException ( ) ,
191
+ "Failed to fetch Coder Connect settings" ) ;
192
+
193
+ // Don't attempt to connect if we failed to load credentials or reconnect.
194
+ // This will prevent the app from trying to connect to the VPN service.
195
+ dependenciesLoaded = false ;
196
+ }
197
+
198
+ var attemptCoderConnection = settingsTask . Result ? . ConnectOnLaunch ?? false ;
199
+ if ( dependenciesLoaded && attemptCoderConnection )
200
+ {
201
+ try
202
+ {
203
+ await rpcController . StartVpn ( cancellationToken ) ;
204
+ }
205
+ catch ( Exception ex )
206
+ {
207
+ _logger . LogError ( ex , "Failed to connect on launch" ) ;
208
+ }
209
+ }
210
+
211
+ // Initialize file sync.
212
+ using var syncSessionCts = CancellationTokenSource . CreateLinkedTokenSource ( cancellationToken ) ;
213
+ syncSessionCts . CancelAfter ( TimeSpan . FromSeconds ( 10 ) ) ;
214
+ var syncSessionController = _services . GetRequiredService < ISyncSessionController > ( ) ;
215
+ try
216
+ {
217
+ await syncSessionController . RefreshState ( syncSessionCts . Token ) ;
218
+ }
219
+ catch ( Exception ex )
220
+ {
221
+ _logger . LogError ( $ "Failed to refresh sync session state { ex . Message } ", ex ) ;
222
+ }
223
+ }
224
+
195
225
public void OnActivated ( object ? sender , AppActivationArguments args )
196
226
{
197
227
switch ( args . Kind )
0 commit comments