Skip to content

Commit e46e380

Browse files
committed
Add About section and manual GitHub update check
1 parent b9d9cfa commit e46e380

10 files changed

Lines changed: 210 additions & 9 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
# Escludi tutti gli .exe
22
*.exe
33

4+
.claude/
5+
Installer/Output/
6+
47
# Escludi la sorgente CPUSetSetter upstream
58
/CPUSetSetter-main/
69

App.xaml.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ protected override void OnStartup(StartupEventArgs e)
4040
if (!createdNew)
4141
{
4242
System.Windows.MessageBox.Show(
43-
"ThreadPilot è già in esecuzione.",
44-
"Istanza già aperta",
43+
"ThreadPilot is already running.",
44+
"Instance already open",
4545
MessageBoxButton.OK,
4646
MessageBoxImage.Information);
4747

Installer/Installer.iss

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
#define MyAppPublisher "ThreadPilot"
66
#define MyAppURL "https://github.com/"
77
#define MyAppExeName "ThreadPilot.exe"
8-
#define MyAppVersion "1.0.0"
8+
#define MyAppVersion "0.1.0-beta"
99
; Point this to the folder containing the published binaries (e.g. dotnet publish -c Release -r win-x64)
10-
#define MyAppBuildDir "..\\artifacts\\build"
10+
#define MyAppBuildDir "..\\publish"
1111

1212
[Setup]
1313
AppId={{A2A4C8B5-4A9A-4B1B-93F4-5F8B1C7E8C2A}

MainWindow.xaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@
124124
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
125125
<!-- Application Icon -->
126126
<Image x:Name="LoadingIcon"
127+
Source="pack://application:,,,/ico.ico"
127128
Width="64"
128129
Height="64"
129130
Margin="0,0,0,20"

Services/GitHubUpdateChecker.cs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using System;
2+
using System.Net.Http;
3+
using System.Net.Http.Headers;
4+
using System.Text.Json;
5+
using System.Threading.Tasks;
6+
7+
namespace ThreadPilot.Services
8+
{
9+
public static class GitHubUpdateChecker
10+
{
11+
private record LatestRelease(string tag_name, bool prerelease, bool draft, string html_url);
12+
13+
public static async Task<(Version? latest, string? releaseUrl)> GetLatestVersionAsync(string owner, string repo)
14+
{
15+
if (string.IsNullOrWhiteSpace(owner)) throw new ArgumentException("Owner is required", nameof(owner));
16+
if (string.IsNullOrWhiteSpace(repo)) throw new ArgumentException("Repository is required", nameof(repo));
17+
18+
var url = $"https://api.github.com/repos/{owner}/{repo}/releases/latest";
19+
20+
using var http = new HttpClient();
21+
http.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("ThreadPilot", "1.0"));
22+
http.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/vnd.github+json"));
23+
24+
var json = await http.GetStringAsync(url);
25+
var release = JsonSerializer.Deserialize<LatestRelease>(json, new JsonSerializerOptions
26+
{
27+
PropertyNameCaseInsensitive = true
28+
});
29+
30+
if (release is null || release.draft || release.prerelease || string.IsNullOrWhiteSpace(release.tag_name))
31+
{
32+
return (null, null);
33+
}
34+
35+
var tag = release.tag_name.Trim();
36+
if (tag.StartsWith("v", StringComparison.OrdinalIgnoreCase))
37+
{
38+
tag = tag[1..];
39+
}
40+
41+
var sanitized = tag.Split('-', '+')[0];
42+
43+
return Version.TryParse(sanitized, out var version)
44+
? (version, release.html_url)
45+
: (null, release.html_url);
46+
}
47+
}
48+
}

Services/SystemTrayService.cs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -456,12 +456,31 @@ public void Dispose()
456456
return File.Exists(bundledIcon) ? bundledIcon : null;
457457
}
458458

459+
private Icon? TryLoadEmbeddedIcon()
460+
{
461+
try
462+
{
463+
var uri = new Uri("pack://application:,,,/ico.ico", UriKind.Absolute);
464+
var streamInfo = System.Windows.Application.GetResourceStream(uri);
465+
if (streamInfo != null)
466+
{
467+
return new Icon(streamInfo.Stream);
468+
}
469+
}
470+
catch (Exception ex)
471+
{
472+
_logger.LogWarning(ex, "Failed to load embedded icon");
473+
}
474+
return null;
475+
}
476+
459477
private void TryLoadTrayIcon(TrayIconState? stateOverride = null)
460478
{
461479
if (_notifyIcon == null) return;
462480

463481
try
464482
{
483+
// Try custom or external bundled icon first
465484
var iconPath = ResolveTrayIconPath();
466485
if (iconPath != null)
467486
{
@@ -470,7 +489,16 @@ private void TryLoadTrayIcon(TrayIconState? stateOverride = null)
470489
return;
471490
}
472491

473-
// Fallback to system icons if no custom/bundled icon is available
492+
// Try embedded resource icon (for single-file publish)
493+
var embeddedIcon = TryLoadEmbeddedIcon();
494+
if (embeddedIcon != null)
495+
{
496+
_notifyIcon.Icon = embeddedIcon;
497+
_logger.LogDebug("Tray icon set from embedded resource");
498+
return;
499+
}
500+
501+
// Fallback to system icons if no custom/bundled/embedded icon is available
474502
var state = stateOverride ?? _currentIconState;
475503
_notifyIcon.Icon = state switch
476504
{

ThreadPilot.csproj

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616
<TrimMode>link</TrimMode>
1717
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
1818
<NoWarn>CS1998;CS0067;CS0414;WFAC010;IL3000;MVVMTK0034</NoWarn>
19+
<Version>0.1.0-beta</Version>
20+
<AssemblyVersion>0.1.0.0</AssemblyVersion>
21+
<FileVersion>0.1.0.0</FileVersion>
22+
<InformationalVersion>0.1.0-beta</InformationalVersion>
1923
</PropertyGroup>
2024

2125
<ItemGroup>
@@ -30,9 +34,7 @@
3034
</ItemGroup>
3135

3236
<ItemGroup>
33-
<None Include="ico.ico">
34-
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
35-
</None>
37+
<Resource Include="ico.ico" />
3638
</ItemGroup>
3739

3840
<!-- Ensure XAML for ProcessPowerPlanAssociationView generates InitializeComponent -->

ViewModels/SettingsViewModel.cs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
using System;
22
using System.Collections.ObjectModel;
3+
using System.Diagnostics;
34
using System.Threading.Tasks;
45
using System.Windows.Input;
6+
using System.Windows;
7+
using System.Reflection;
58
using CommunityToolkit.Mvvm.ComponentModel;
69
using CommunityToolkit.Mvvm.Input;
710
using Microsoft.Extensions.Logging;
@@ -37,12 +40,15 @@ public partial class SettingsViewModel : BaseViewModel
3740
[ObservableProperty]
3841
private ObservableCollection<PowerPlanModel> availablePowerPlans = new();
3942

43+
public string ApplicationVersion { get; }
44+
4045
public ICommand SaveSettingsCommand { get; }
4146
public ICommand ResetToDefaultsCommand { get; }
4247
public ICommand ExportSettingsCommand { get; }
4348
public ICommand ImportSettingsCommand { get; }
4449
public ICommand TestNotificationCommand { get; }
4550
public ICommand RefreshPowerPlansCommand { get; }
51+
public ICommand CheckUpdatesCommand { get; }
4652

4753
public SettingsViewModel(
4854
ILogger<SettingsViewModel> logger,
@@ -60,6 +66,12 @@ public SettingsViewModel(
6066
_powerPlanService = powerPlanService ?? throw new ArgumentNullException(nameof(powerPlanService));
6167
_processMonitorManagerService = processMonitorManagerService ?? throw new ArgumentNullException(nameof(processMonitorManagerService));
6268

69+
ApplicationVersion = typeof(App).Assembly
70+
.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?
71+
.InformationalVersion
72+
?? typeof(App).Assembly.GetName().Version?.ToString()
73+
?? "0.0.0";
74+
6375
// Initialize with current settings
6476
settings = (ApplicationSettingsModel)_settingsService.Settings.Clone();
6577

@@ -70,6 +82,7 @@ public SettingsViewModel(
7082
ImportSettingsCommand = new AsyncRelayCommand(ImportSettingsAsync);
7183
TestNotificationCommand = new AsyncRelayCommand(TestNotificationAsync);
7284
RefreshPowerPlansCommand = new AsyncRelayCommand(RefreshPowerPlansAsync);
85+
CheckUpdatesCommand = new AsyncRelayCommand(CheckUpdatesAsync);
7386

7487
// Subscribe to property changes to track unsaved changes
7588
Settings.PropertyChanged += OnSettingsPropertyChanged;
@@ -332,5 +345,88 @@ private async Task RefreshPowerPlansAsync()
332345
Logger.LogError(ex, "Failed to refresh power plans");
333346
}
334347
}
348+
349+
private async Task CheckUpdatesAsync()
350+
{
351+
try
352+
{
353+
IsLoading = true;
354+
StatusMessage = "Checking for updates...";
355+
356+
var currentVersion = ParseVersion(ApplicationVersion);
357+
var (latest, releaseUrl) = await GitHubUpdateChecker.GetLatestVersionAsync("PrimeBuild-pc", "ThreadPilot");
358+
359+
if (latest is null)
360+
{
361+
StatusMessage = "Unable to determine the latest version.";
362+
await _notificationService.ShowErrorNotificationAsync(
363+
"Update Check",
364+
"Unable to retrieve latest release information.");
365+
return;
366+
}
367+
368+
if (latest > currentVersion)
369+
{
370+
StatusMessage = $"New version available: {latest}";
371+
372+
var result = System.Windows.MessageBox.Show(
373+
$"Update available\nInstalled version: {ApplicationVersion}\nNew version: {latest}\n\nDo you want to open the download page?",
374+
"Update available",
375+
MessageBoxButton.YesNo,
376+
MessageBoxImage.Information);
377+
378+
if (result == MessageBoxResult.Yes)
379+
{
380+
var url = releaseUrl ?? "https://github.com/PrimeBuild-pc/ThreadPilot/releases/latest";
381+
Process.Start(new ProcessStartInfo
382+
{
383+
FileName = url,
384+
UseShellExecute = true
385+
});
386+
}
387+
}
388+
else
389+
{
390+
StatusMessage = $"Application is up to date. Installed version: {ApplicationVersion}";
391+
await _notificationService.ShowSuccessNotificationAsync(
392+
"Application up to date",
393+
$"Installed version: {ApplicationVersion}");
394+
}
395+
}
396+
catch (Exception ex)
397+
{
398+
StatusMessage = $"Error while checking updates: {ex.Message}";
399+
Logger.LogError(ex, "Error checking for updates");
400+
401+
await _notificationService.ShowErrorNotificationAsync(
402+
"Update check error",
403+
"Unable to verify updates",
404+
ex);
405+
}
406+
finally
407+
{
408+
IsLoading = false;
409+
}
410+
}
411+
412+
private static Version ParseVersion(string versionString)
413+
{
414+
if (string.IsNullOrWhiteSpace(versionString))
415+
{
416+
return new Version(0, 0, 0);
417+
}
418+
419+
var sanitized = versionString.Trim();
420+
if (sanitized.StartsWith("v", StringComparison.OrdinalIgnoreCase))
421+
{
422+
sanitized = sanitized[1..];
423+
}
424+
425+
sanitized = sanitized.Split('-', '+')[0];
426+
427+
return Version.TryParse(sanitized, out var parsed)
428+
? parsed
429+
: new Version(0, 0, 0);
430+
}
335431
}
336432
}

Views/SettingsView.xaml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,29 @@
301301
</StackPanel>
302302
</Border>
303303

304+
<!-- About Section -->
305+
<TextBlock Text="About" Style="{StaticResource SectionHeaderStyle}"/>
306+
<Border Background="#F8F9FA" CornerRadius="5" Padding="15" Margin="0,5,0,15">
307+
<StackPanel>
308+
<TextBlock Text="ThreadPilot" FontWeight="SemiBold" FontSize="14"/>
309+
<TextBlock Text="Version:" FontWeight="SemiBold" Margin="0,8,0,0"/>
310+
<TextBlock Text="{Binding ApplicationVersion}" Margin="0,0,0,10"/>
311+
312+
<TextBlock Text="Copyright © 2025 PrimeBuild" Margin="0,0,0,4"/>
313+
<TextBlock Text="License: MIT (see LICENSE)" Style="{StaticResource DescriptionStyle}"/>
314+
315+
<Separator Margin="0,12,0,12"/>
316+
317+
<Button Content="Check for updates"
318+
Command="{Binding CheckUpdatesCommand}"
319+
Padding="15,6"
320+
HorizontalAlignment="Left"/>
321+
<TextBlock Text="Checks on demand using the official GitHub Releases."
322+
Style="{StaticResource DescriptionStyle}"
323+
Margin="0,6,0,0"/>
324+
</StackPanel>
325+
</Border>
326+
304327
</StackPanel>
305328
</ScrollViewer>
306329

Views/SettingsWindow.xaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
MinHeight="600" MinWidth="800"
1111
WindowStartupLocation="CenterOwner"
1212
ShowInTaskbar="False"
13-
Icon="/Resources/icon.ico">
13+
Icon="pack://application:,,,/ico.ico">
1414

1515
<Window.Resources>
1616
<Style TargetType="Window">

0 commit comments

Comments
 (0)