Skip to content

Commit 53e0fc8

Browse files
lostindarkCopilot
andcommitted
Add winget update support for winget-installed instances
- Detect winget installation by checking app path for WinGet\Packages - WinGetUpdateManager checks available version via winget show --versions (parses locale-independent version numbers only) - Falls back to GitHub API if winget command fails - Upgrade runs via batch script: waits for app exit, winget upgrade, restart - IUpdateManager.HandlesRestart controls whether caller or script restarts Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent a3b86a6 commit 53e0fc8

5 files changed

Lines changed: 155 additions & 8 deletions

File tree

Rapr/AboutBox.cs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -196,15 +196,16 @@ private async Task PerformUpdateAsync()
196196

197197
await this.updateManager.ApplyUpdateAsync(this.latestVersionInfo, progress);
198198

199-
var newProcess = Process.Start(exePath);
200-
if (newProcess != null)
199+
if (!this.updateManager.HandlesRestart)
201200
{
202-
Application.Exit();
203-
}
204-
else
205-
{
206-
throw new InvalidOperationException("Failed to restart the application. Please restart manually.");
201+
var newProcess = Process.Start(exePath);
202+
if (newProcess == null)
203+
{
204+
throw new InvalidOperationException("Failed to restart the application. Please restart manually.");
205+
}
207206
}
207+
208+
Application.Exit();
208209
}
209210
catch (Exception ex)
210211
{

Rapr/DSEForm.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,19 @@ public partial class DSEForm : Form
3535

3636
private HashSet<DriverStoreEntry> driversWithNewerDate = new HashSet<DriverStoreEntry>();
3737

38-
private static readonly IUpdateManager UpdateManager = new UpdateManager();
38+
private static readonly IUpdateManager UpdateManager = CreateUpdateManager();
39+
40+
private static IUpdateManager CreateUpdateManager()
41+
{
42+
string appDir = DSEFormHelper.GetApplicationFolder();
43+
44+
if (appDir.IndexOf("_Microsoft.Winget.Source_", StringComparison.OrdinalIgnoreCase) >= 0)
45+
{
46+
return new WinGetUpdateManager();
47+
}
48+
49+
return new UpdateManager();
50+
}
3951

4052
private static readonly ICollection<CultureInfo> SupportedLanguage = DSEFormHelper.GetSupportedLanguage();
4153

Rapr/IUpdateManager.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ namespace Rapr
55
{
66
public interface IUpdateManager
77
{
8+
bool HandlesRestart { get; }
9+
810
Task<VersionInfo> GetLatestVersionInfo();
911

1012
Task ApplyUpdateAsync(VersionInfo versionInfo, IProgress<float> progress);

Rapr/UpdateManager.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ public class UpdateManager : IUpdateManager, IDisposable
1818
private readonly HttpClient httpClient = new HttpClient();
1919
private bool disposedValue; // To detect redundant calls
2020

21+
public bool HandlesRestart => false;
22+
2123
public async Task<VersionInfo> GetLatestVersionInfo()
2224
{
2325
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;

Rapr/WinGetUpdateManager.cs

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
using System;
2+
using System.Diagnostics;
3+
using System.IO;
4+
using System.Reflection;
5+
using System.Threading.Tasks;
6+
7+
namespace Rapr
8+
{
9+
public class WinGetUpdateManager : IUpdateManager, IDisposable
10+
{
11+
private const string WinGetPackageId = "lostindark.DriverStoreExplorer";
12+
private readonly UpdateManager updateManager = new UpdateManager();
13+
14+
public bool HandlesRestart => true;
15+
16+
public async Task<VersionInfo> GetLatestVersionInfo()
17+
{
18+
// Use winget search to get the latest available version — version numbers
19+
// in the output are always locale-independent (digits and dots).
20+
string output = await RunCommandAsync(
21+
"winget",
22+
$"search --id {WinGetPackageId} --exact --source winget --disable-interactivity --accept-source-agreements")
23+
.ConfigureAwait(false);
24+
25+
if (!string.IsNullOrEmpty(output))
26+
{
27+
var lines = output.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
28+
bool pastSeparator = false;
29+
30+
foreach (var line in lines)
31+
{
32+
var trimmed = line.Trim();
33+
34+
if (trimmed.StartsWith("-", StringComparison.Ordinal))
35+
{
36+
pastSeparator = true;
37+
continue;
38+
}
39+
40+
if (pastSeparator)
41+
{
42+
// Extract the last whitespace-delimited token — that's the version
43+
var parts = trimmed.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
44+
if (parts.Length > 0 && Version.TryParse(parts[parts.Length - 1], out var version))
45+
{
46+
return new VersionInfo
47+
{
48+
Version = version,
49+
PageUrl = new Uri($"https://github.com/lostindark/DriverStoreExplorer/releases/tag/v{parts[parts.Length - 1]}"),
50+
};
51+
}
52+
}
53+
}
54+
}
55+
56+
// Fall back to GitHub API if winget command fails
57+
return await this.updateManager.GetLatestVersionInfo().ConfigureAwait(false);
58+
}
59+
60+
public Task ApplyUpdateAsync(VersionInfo versionInfo, IProgress<float> progress)
61+
{
62+
int pid = Process.GetCurrentProcess().Id;
63+
string exePath = Assembly.GetExecutingAssembly().Location;
64+
65+
string scriptDir = Path.Combine(Path.GetTempPath(), "DriverStoreExplorer");
66+
if (!Directory.Exists(scriptDir))
67+
{
68+
Directory.CreateDirectory(scriptDir);
69+
}
70+
71+
string scriptPath = Path.Combine(scriptDir, "winget_update.cmd");
72+
string script = $@"@echo off
73+
:wait
74+
tasklist /FI ""PID eq {pid}"" 2>NUL | find /I ""{pid}"" >NUL
75+
if %errorlevel%==0 (timeout /t 1 /nobreak >nul & goto wait)
76+
winget upgrade --id {WinGetPackageId} --silent --disable-interactivity --accept-source-agreements --accept-package-agreements
77+
start """" ""{exePath}""
78+
del ""%~f0""
79+
";
80+
81+
File.WriteAllText(scriptPath, script);
82+
83+
Process.Start(new ProcessStartInfo
84+
{
85+
FileName = "cmd.exe",
86+
Arguments = $"/c \"{scriptPath}\"",
87+
CreateNoWindow = true,
88+
WindowStyle = ProcessWindowStyle.Hidden,
89+
UseShellExecute = false
90+
});
91+
92+
return Task.CompletedTask;
93+
}
94+
95+
public void Dispose()
96+
{
97+
this.updateManager.Dispose();
98+
}
99+
100+
private static Task<string> RunCommandAsync(string fileName, string arguments)
101+
{
102+
return Task.Run(() =>
103+
{
104+
try
105+
{
106+
var psi = new ProcessStartInfo
107+
{
108+
FileName = fileName,
109+
Arguments = arguments,
110+
UseShellExecute = false,
111+
RedirectStandardOutput = true,
112+
RedirectStandardError = true,
113+
CreateNoWindow = true
114+
};
115+
116+
using (var process = Process.Start(psi))
117+
{
118+
string output = process.StandardOutput.ReadToEnd();
119+
process.WaitForExit();
120+
return output;
121+
}
122+
}
123+
catch
124+
{
125+
return null;
126+
}
127+
});
128+
}
129+
}
130+
}

0 commit comments

Comments
 (0)