Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 11c1f28

Browse files
committedMay 1, 2025·
Merge branch 'main' into dean/remote-dir-picker
2 parents 9ae1dad + b803aa1 commit 11c1f28

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1033
-749
lines changed
 

‎App/App.csproj

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,13 @@
6262
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
6363
</PackageReference>
6464
<PackageReference Include="H.NotifyIcon.WinUI" Version="2.2.0" />
65-
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.1" />
66-
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.1" />
67-
<PackageReference Include="Microsoft.Extensions.Options" Version="9.0.1" />
65+
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.4" />
66+
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.4" />
67+
<PackageReference Include="Microsoft.Extensions.Options" Version="9.0.4" />
6868
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.6.250108002" />
69+
<PackageReference Include="Serilog.Extensions.Hosting" Version="9.0.0" />
70+
<PackageReference Include="Serilog.Settings.Configuration" Version="9.0.0" />
71+
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
6972
<PackageReference Include="WinUIEx" Version="2.5.1" />
7073
</ItemGroup>
7174

‎App/App.xaml.cs

Lines changed: 97 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Diagnostics;
4+
using System.IO;
35
using System.Threading;
46
using System.Threading.Tasks;
7+
using Windows.ApplicationModel.Activation;
58
using Coder.Desktop.App.Models;
69
using Coder.Desktop.App.Services;
710
using Coder.Desktop.App.ViewModels;
@@ -12,8 +15,12 @@
1215
using Microsoft.Extensions.Configuration;
1316
using Microsoft.Extensions.DependencyInjection;
1417
using Microsoft.Extensions.Hosting;
18+
using Microsoft.Extensions.Logging;
1519
using Microsoft.UI.Xaml;
1620
using Microsoft.Win32;
21+
using Microsoft.Windows.AppLifecycle;
22+
using Serilog;
23+
using LaunchActivatedEventArgs = Microsoft.UI.Xaml.LaunchActivatedEventArgs;
1724

1825
namespace Coder.Desktop.App;
1926

@@ -22,22 +29,39 @@ public partial class App : Application
2229
private readonly IServiceProvider _services;
2330

2431
private bool _handleWindowClosed = true;
32+
private const string MutagenControllerConfigSection = "MutagenController";
2533

2634
#if !DEBUG
27-
private const string MutagenControllerConfigSection = "AppMutagenController";
35+
private const string ConfigSubKey = @"SOFTWARE\Coder Desktop\App";
36+
private const string logFilename = "app.log";
2837
#else
29-
private const string MutagenControllerConfigSection = "DebugAppMutagenController";
38+
private const string ConfigSubKey = @"SOFTWARE\Coder Desktop\DebugApp";
39+
private const string logFilename = "debug-app.log";
3040
#endif
3141

42+
private readonly ILogger<App> _logger;
43+
3244
public App()
3345
{
3446
var builder = Host.CreateApplicationBuilder();
47+
var configBuilder = builder.Configuration as IConfigurationBuilder;
3548

36-
(builder.Configuration as IConfigurationBuilder).Add(
37-
new RegistryConfigurationSource(Registry.LocalMachine, @"SOFTWARE\Coder Desktop"));
49+
// Add config in increasing order of precedence: first builtin defaults, then HKLM, finally HKCU
50+
// so that the user's settings in the registry take precedence.
51+
AddDefaultConfig(configBuilder);
52+
configBuilder.Add(
53+
new RegistryConfigurationSource(Registry.LocalMachine, ConfigSubKey));
54+
configBuilder.Add(
55+
new RegistryConfigurationSource(Registry.CurrentUser, ConfigSubKey));
3856

3957
var services = builder.Services;
4058

59+
// Logging
60+
builder.Services.AddSerilog((_, loggerConfig) =>
61+
{
62+
loggerConfig.ReadFrom.Configuration(builder.Configuration);
63+
});
64+
4165
services.AddSingleton<IAgentApiClientFactory, AgentApiClientFactory>();
4266

4367
services.AddSingleton<ICredentialManager, CredentialManager>();
@@ -71,12 +95,14 @@ public App()
7195
services.AddTransient<TrayWindow>();
7296

7397
_services = services.BuildServiceProvider();
98+
_logger = (ILogger<App>)_services.GetService(typeof(ILogger<App>))!;
7499

75100
InitializeComponent();
76101
}
77102

78103
public async Task ExitApplication()
79104
{
105+
_logger.LogDebug("exiting app");
80106
_handleWindowClosed = false;
81107
Exit();
82108
var syncController = _services.GetRequiredService<ISyncSessionController>();
@@ -89,36 +115,39 @@ public async Task ExitApplication()
89115

90116
protected override void OnLaunched(LaunchActivatedEventArgs args)
91117
{
118+
_logger.LogInformation("new instance launched");
92119
// Start connecting to the manager in the background.
93120
var rpcController = _services.GetRequiredService<IRpcController>();
94121
if (rpcController.GetState().RpcLifecycle == RpcLifecycle.Disconnected)
95122
// Passing in a CT with no cancellation is desired here, because
96123
// the named pipe open will block until the pipe comes up.
97-
// TODO: log
98-
_ = rpcController.Reconnect(CancellationToken.None).ContinueWith(t =>
124+
_logger.LogDebug("reconnecting with VPN service");
125+
_ = rpcController.Reconnect(CancellationToken.None).ContinueWith(t =>
126+
{
127+
if (t.Exception != null)
99128
{
129+
_logger.LogError(t.Exception, "failed to connect to VPN service");
100130
#if DEBUG
101-
if (t.Exception != null)
102-
{
103-
Debug.WriteLine(t.Exception);
104-
Debugger.Break();
105-
}
131+
Debug.WriteLine(t.Exception);
132+
Debugger.Break();
106133
#endif
107-
});
134+
}
135+
});
108136

109137
// Load the credentials in the background.
110138
var credentialManagerCts = new CancellationTokenSource(TimeSpan.FromSeconds(15));
111139
var credentialManager = _services.GetRequiredService<ICredentialManager>();
112140
_ = credentialManager.LoadCredentials(credentialManagerCts.Token).ContinueWith(t =>
113141
{
114-
// TODO: log
115-
#if DEBUG
116142
if (t.Exception != null)
117143
{
144+
_logger.LogError(t.Exception, "failed to load credentials");
145+
#if DEBUG
118146
Debug.WriteLine(t.Exception);
119147
Debugger.Break();
120-
}
121148
#endif
149+
}
150+
122151
credentialManagerCts.Dispose();
123152
}, CancellationToken.None);
124153

@@ -127,10 +156,14 @@ protected override void OnLaunched(LaunchActivatedEventArgs args)
127156
var syncSessionController = _services.GetRequiredService<ISyncSessionController>();
128157
_ = syncSessionController.RefreshState(syncSessionCts.Token).ContinueWith(t =>
129158
{
130-
// TODO: log
159+
if (t.IsCanceled || t.Exception != null)
160+
{
161+
_logger.LogError(t.Exception, "failed to refresh sync state (canceled = {canceled})", t.IsCanceled);
131162
#if DEBUG
132-
if (t.IsCanceled || t.Exception != null) Debugger.Break();
163+
Debugger.Break();
133164
#endif
165+
}
166+
134167
syncSessionCts.Dispose();
135168
}, CancellationToken.None);
136169

@@ -143,4 +176,51 @@ protected override void OnLaunched(LaunchActivatedEventArgs args)
143176
trayWindow.AppWindow.Hide();
144177
};
145178
}
179+
180+
public void OnActivated(object? sender, AppActivationArguments args)
181+
{
182+
switch (args.Kind)
183+
{
184+
case ExtendedActivationKind.Protocol:
185+
var protoArgs = args.Data as IProtocolActivatedEventArgs;
186+
if (protoArgs == null)
187+
{
188+
_logger.LogWarning("URI activation with null data");
189+
return;
190+
}
191+
192+
HandleURIActivation(protoArgs.Uri);
193+
break;
194+
195+
default:
196+
_logger.LogWarning("activation for {kind}, which is unhandled", args.Kind);
197+
break;
198+
}
199+
}
200+
201+
public void HandleURIActivation(Uri uri)
202+
{
203+
// don't log the query string as that's where we include some sensitive information like passwords
204+
_logger.LogInformation("handling URI activation for {path}", uri.AbsolutePath);
205+
}
206+
207+
private static void AddDefaultConfig(IConfigurationBuilder builder)
208+
{
209+
var logPath = Path.Combine(
210+
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
211+
"CoderDesktop",
212+
logFilename);
213+
builder.AddInMemoryCollection(new Dictionary<string, string?>
214+
{
215+
[MutagenControllerConfigSection + ":MutagenExecutablePath"] = @"C:\mutagen.exe",
216+
["Serilog:Using:0"] = "Serilog.Sinks.File",
217+
["Serilog:MinimumLevel"] = "Information",
218+
["Serilog:Enrich:0"] = "FromLogContext",
219+
["Serilog:WriteTo:0:Name"] = "File",
220+
["Serilog:WriteTo:0:Args:path"] = logPath,
221+
["Serilog:WriteTo:0:Args:outputTemplate"] =
222+
"{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {SourceContext} - {Message:lj}{NewLine}{Exception}",
223+
["Serilog:WriteTo:0:Args:rollingInterval"] = "Day",
224+
});
225+
}
146226
}

0 commit comments

Comments
 (0)
Please sign in to comment.