• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ParadoxGameConverters / Fronter.NET / 24615805603

18 Apr 2026 10:58PM UTC coverage: 25.99% (-2.4%) from 28.393%
24615805603

push

github

web-flow
Bump dependencies (#970)

* Bump dependencies

* Fix coverage not being uploaded

166 of 770 branches covered (21.56%)

Branch coverage included in aggregate %.

740 of 2716 relevant lines covered (27.25%)

5.14 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

40.0
/Fronter.NET/Services/UpdateChecker.cs
1
using Avalonia;
2
using Avalonia.Controls;
3
using Avalonia.Layout;
4
using Avalonia.Media;
5
using Avalonia.Notification;
6
using commonItems;
7
using Fronter.Extensions;
8
using Fronter.Models;
9
using Fronter.Models.Configuration;
10
using Fronter.Views;
11
using log4net;
12
using System;
13
using System.Diagnostics;
14
using System.IO;
15
using System.Net.Http;
16
using System.Text;
17
using System.Text.Json;
18
using System.Threading.Tasks;
19

20
namespace Fronter.Services;
21

22
internal static class UpdateChecker {
23
        private static readonly ILog Logger = LogManager.GetLogger("Update checker");
1✔
24
        private static readonly HttpClient SharedHttpClient = CreateSharedHttpClient();
1✔
25

26
        private static HttpClient CreateSharedHttpClient() => new() {Timeout = TimeSpan.FromMinutes(5)};
1✔
27
        public static async Task<bool> IsUpdateAvailable(string commitIdFilePath, string commitIdUrl, HttpClient? httpClient = null) {
3✔
28
                if (!File.Exists(commitIdFilePath)) {
4✔
29
                        Logger.Debug($"File \"{commitIdFilePath}\" does not exist!");
1✔
30
                        return false;
1✔
31
                }
32

33
                var client = httpClient ?? SharedHttpClient;
2✔
34
                try {
2✔
35
                        var response = await client.GetAsync(commitIdUrl);
2✔
36
                        if (!response.IsSuccessStatusCode) {
3✔
37
                                Logger.Warn($"Failed to get commit id from \"{commitIdUrl}\"; status code: {response.StatusCode}!");
1✔
38
                                return false;
1✔
39
                        }
40

41
                        var latestReleaseCommitId = await response.Content.ReadAsStringAsync();
1✔
42
                        latestReleaseCommitId = latestReleaseCommitId.Trim();
1✔
43

44
                        using var commitIdFileReader = new StreamReader(commitIdFilePath);
1✔
45
                        var localCommitId = (await commitIdFileReader.ReadLineAsync())?.Trim();
1!
46

47
                        return localCommitId is not null && !localCommitId.Equals(latestReleaseCommitId);
1!
48
                } catch (Exception e) {
×
49
                        Logger.Warn($"Failed to get commit id from \"{commitIdUrl}\"; {e}!");
×
50
                        return false;
×
51
                }
52
        }
3✔
53

54
        private static (string, string)? GetOSNameAndArch() {
1✔
55
                if (OperatingSystem.IsWindows()) {
1!
56
                        return ("win", "x64");
×
57
                }
58
                if (OperatingSystem.IsLinux()) {
2!
59
                        return ("linux", "x64");
1✔
60
                }
61
                if (OperatingSystem.IsMacOS()) {
×
62
                        return ("osx", "arm64");
×
63
                }
64
                return null;
×
65
        }
1✔
66

67
        public static async Task<UpdateInfoModel> GetLatestReleaseInfo(string converterName, HttpClient? httpClient = null) {
1✔
68
                var osNameAndArch = GetOSNameAndArch();
1✔
69
                if (osNameAndArch is null) {
1!
70
                        return new UpdateInfoModel();
×
71
                }
72

73
                var osName = osNameAndArch.Value.Item1;
1✔
74
                var architecture = osNameAndArch.Value.Item2;
1✔
75

76
                var info = new UpdateInfoModel();
1✔
77
                var apiUrl = $"https://api.github.com/repos/ParadoxGameConverters/{converterName}/releases/latest";
1✔
78
                var requestMessage = new HttpRequestMessage(HttpMethod.Get, apiUrl);
1✔
79
                requestMessage.Headers.Add("User-Agent", "ParadoxGameConverters");
1✔
80

81
                HttpResponseMessage responseMessage;
82
                var client = httpClient ?? SharedHttpClient;
1!
83
                try {
1✔
84
                        responseMessage = await client.SendAsync(requestMessage);
1✔
85
                } catch (Exception e) {
1✔
86
                        Logger.Warn($"Failed to get release info from \"{apiUrl}\": {e}!");
×
87
                        return info;
×
88
                }
89
                await using var responseStream = await responseMessage.Content.ReadAsStreamAsync();
1✔
90

91
                var releaseInfo = await JsonSerializer.DeserializeAsync<ConverterReleaseInfo>(responseStream);
1✔
92
                if (releaseInfo is null) {
1!
93
                        return info;
×
94
                }
95

96
                info.Description = releaseInfo.Body;
1✔
97
                info.Version = releaseInfo.Name;
1✔
98

99
                DetermineReleaseBuildUrl(releaseInfo, info, osName, architecture);
1✔
100

101
                if (info.AssetUrl is null) {
1!
102
                        Logger.Debug($"Release {info.Version} doesn't have a release build for this platform.");
×
103
                }
×
104

105
                return info;
1✔
106
        }
1✔
107

108
        private static void DetermineReleaseBuildUrl(ConverterReleaseInfo releaseInfo, UpdateInfoModel info, string osName, string architecture) {
1✔
109
                var assets = releaseInfo.Assets;
1✔
110
                foreach (var asset in assets) {
11✔
111
                        string? assetName = asset.Name;
3✔
112

113
                        if (assetName is null) {
3!
114
                                continue;
×
115
                        }
116

117
                        assetName = assetName.ToLower();
3✔
118
                        var extension = CommonFunctions.GetExtension(assetName);
3✔
119
                        if (extension is not "zip" and not "tgz" and not "exe") {
3!
120
                                continue;
×
121
                        }
122

123
                        // For Windows, prefer an installer over an archive.
124
                        if (extension.Equals("exe") && osName.Equals("win")) {
3!
125
                                info.AssetUrl = asset.BrowserDownloadUrl;
×
126
                                break;
×
127
                        }
128

129
                        var assetNameWithoutExtension = CommonFunctions.TrimExtension(assetName);
3✔
130
                        if (!assetNameWithoutExtension.EndsWith($"-{osName}-{architecture}", StringComparison.OrdinalIgnoreCase)) {
5✔
131
                                continue;
2✔
132
                        }
133

134
                        info.AssetUrl = asset.BrowserDownloadUrl;
1✔
135
                        break;
1✔
136
                }
137
        }
1✔
138

139
        public static string GetUpdateMessageBody(string baseBody, UpdateInfoModel updateInfo) {
×
140
                var stringBuilder = new StringBuilder(baseBody);
×
141
                stringBuilder.AppendLine();
×
142

143
                var version = updateInfo.Version;
×
144
                if (version is not null) {
×
145
                        stringBuilder.AppendLine();
×
146
                        stringBuilder.Append("Version: ");
×
147
                        stringBuilder.AppendLine(version);
×
148
                }
×
149

150
                var description = updateInfo.Description;
×
151
                if (description is not null) {
×
152
                        stringBuilder.AppendLine();
×
153
                        stringBuilder.AppendLine(description);
×
154
                }
×
155

156
                return stringBuilder.ToString();
×
157
        }
×
158

159
        private static async Task DownloadFileAsync(string installerUrl, string fileName) {
×
160
                var responseBytes = await SharedHttpClient.GetByteArrayAsync(installerUrl);
×
161
                await File.WriteAllBytesAsync(fileName, responseBytes);
×
162
        }
×
163

164
        public static async Task RunInstallerAndDie(string installerUrl, Config config, INotificationMessageManager notificationManager) {
×
165
                Logger.Debug("Downloading installer...");
×
166
                var downloadingMessage = notificationManager.CreateMessage()
×
167
                        .Accent(Brushes.Gray)
×
168
                        .Background(Brushes.Gray)
×
169
                        .HasMessage("Downloading installer...")
×
170
                        .WithOverlay(new ProgressBar {
×
171
                                VerticalAlignment = VerticalAlignment.Bottom,
×
172
                                HorizontalAlignment = HorizontalAlignment.Stretch,
×
173
                                Height = 3,
×
174
                                BorderThickness = new Thickness(0),
×
175
                                Foreground = Brushes.Green,
×
176
                                Background = Brushes.Gray,
×
177
                                IsIndeterminate = true,
×
178
                                IsHitTestVisible = false
×
179
                        })
×
180
                        .Queue();
×
181

182
                var fileName = Path.GetTempFileName();
×
183
                try {
×
184
                        await DownloadFileAsync(installerUrl, fileName);
×
185
                } catch (Exception ex) {
×
186
                        Logger.Debug($"Failed to download installer: {ex.Message}");
×
187
                        notificationManager
×
188
                                .CreateError()
×
189
                                .HasMessage("Failed to download installer, probably because of network issues. \n" +
×
190
                                            "Try updating the converter manually.")
×
191
                                .SuggestManualUpdate(config)
×
192
                                .Queue();
×
193
                        return;
×
194
                }
195

196
                notificationManager.Dismiss(downloadingMessage);
×
197

198
                Logger.Debug("Running installer...");
×
199
                var proc = new Process();
×
200
                proc.StartInfo.FileName = fileName;
×
201
                try {
×
202
                        proc.Start();
×
203
                } catch (Exception ex) {
×
204
                        Logger.Debug($"Installer process failed to start: {ex.Message}");
×
205
                        notificationManager
×
206
                                .CreateError()
×
207
                                .HasMessage("Failed to start installer, probably because of an antivirus. \n" +
×
208
                                            "Try updating the converter manually.")
×
209
                                .SuggestManualUpdate(config)
×
210
                                .Queue();
×
211
                        return;
×
212
                }
213

214
                // Die. The installer will handle the rest.
215
                MainWindow.Instance.Close();
×
216
        }
×
217

218
        public static void StartUpdaterAndDie(string archiveUrl, string converterBackendDirName) {
×
219
                var updaterDirPath = Path.Combine(".", "Updater");
×
220
                var updaterRunningDirPath = Path.Combine(".", "Updater-running");
×
221

222
                const string manualUpdateHint = "Try updating the converter manually.";
223
                if (Directory.Exists(updaterRunningDirPath) && !SystemUtils.TryDeleteFolder(updaterRunningDirPath)) {
×
224
                        Logger.Warn($"Failed to delete Updater-running folder! {manualUpdateHint}");
×
225
                        return;
×
226
                }
227

228
                if (!SystemUtils.TryCopyFolder(updaterDirPath, updaterRunningDirPath)) {
×
229
                        Logger.Warn($"Failed to create Updater-running folder! {manualUpdateHint}");
×
230
                        return;
×
231
                }
232

233
                string updaterRunningPath = Path.Combine(updaterRunningDirPath, "updater");
×
234
                if (OperatingSystem.IsWindows()) {
×
235
                        updaterRunningPath += ".exe";
×
236
                }
×
237

238
                var proc = new Process();
×
239
                proc.StartInfo.FileName = updaterRunningPath;
×
240
                proc.StartInfo.Arguments = $"{archiveUrl} {converterBackendDirName}";
×
241
                try {
×
242
                        proc.Start();
×
243
                } catch (Exception ex) {
×
244
                        Logger.Debug($"Updater process failed to start: {ex.Message}");
×
245
                        Logger.Error($"Failed to start updater, probably because of an antivirus. {manualUpdateHint}");
×
246
                        return;
×
247
                }
248

249
                // Die. The updater will start Fronter after a successful update.
250
                MainWindow.Instance.Close();
×
251
        }
×
252
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc