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

ParadoxGameConverters / Fronter.NET / 15191293754

22 May 2025 03:57PM UTC coverage: 18.155% (-0.08%) from 18.23%
15191293754

push

github

web-flow
Show a notification when dowloading an update installer (#881)

73 of 654 branches covered (11.16%)

Branch coverage included in aggregate %.

0 of 19 new or added lines in 2 files covered. (0.0%)

1 existing line in 1 file now uncovered.

551 of 2783 relevant lines covered (19.8%)

9.2 hits per line

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

9.75
/Fronter.NET/ViewModels/MainWindowViewModel.cs
1
using Avalonia;
2
using Avalonia.Controls;
3
using Avalonia.Controls.ApplicationLifetimes;
4
using Avalonia.Notification;
5
using Avalonia.Threading;
6
using commonItems.Collections;
7
using Fronter.Extensions;
8
using Fronter.LogAppenders;
9
using Fronter.Models;
10
using Fronter.Models.Configuration;
11
using Fronter.Services;
12
using Fronter.Views;
13
using log4net;
14
using log4net.Core;
15
using MsBox.Avalonia;
16
using MsBox.Avalonia.Dto;
17
using MsBox.Avalonia.Enums;
18
using ReactiveUI;
19
using System;
20
using System.Collections.Generic;
21
using System.Collections.ObjectModel;
22
using System.Diagnostics.CodeAnalysis;
23
using System.IO;
24
using System.Linq;
25
using System.Reactive;
26
using System.Threading;
27
using System.Threading.Tasks;
28

29
namespace Fronter.ViewModels;
30

31
[SuppressMessage("ReSharper", "MemberCanBeMadeStatic.Global")]
32
internal sealed class MainWindowViewModel : ViewModelBase {
33
        private static readonly ILog logger = LogManager.GetLogger("Frontend");
×
34
        private readonly TranslationSource loc = TranslationSource.Instance;
2✔
35
        public IEnumerable<MenuItemViewModel> LanguageMenuItems => loc.LoadedLanguages
×
36
                .Select(l => new MenuItemViewModel {
×
37
                        Command = SetLanguageCommand,
×
38
                        CommandParameter = l,
×
39
                        Header = loc.TranslateLanguage(l),
×
40
                        Items = Array.Empty<MenuItemViewModel>(),
×
41
                });
×
42

43
        public INotificationMessageManager NotificationManager { get; } = new NotificationMessageManager();
2✔
44

45
        private IdObjectCollection<string, FrontendTheme> Themes { get; } = [
2✔
46
                new() {Id = "Light", LocKey = "THEME_LIGHT"},
2✔
47
                new() {Id = "Dark", LocKey = "THEME_DARK"},
2✔
48
        ];
2✔
49
        public IEnumerable<MenuItemViewModel> ThemeMenuItems => Themes
×
50
                .Select(theme => new MenuItemViewModel {
×
51
                        Command = SetThemeCommand,
×
52
                        CommandParameter = theme.Id,
×
53
                        Header = loc.Translate(theme.LocKey),
×
54
                        Items = Array.Empty<MenuItemViewModel>(),
×
55
                });
×
56

57
        internal Config Config { get; }
6✔
58

59
        internal PathPickerViewModel PathPicker { get; }
×
60
        internal ModsPickerViewModel ModsPicker { get; }
×
61
        public bool ModsPickerTabVisible => Config.ModAutoGenerationSource is not null;
×
62
        public OptionsViewModel Options { get; }
6✔
63
        public bool OptionsTabVisible => Options.Items.Any();
2✔
64

65
        public Level LogFilterLevel {
66
                get => LogGridAppender.LogFilterLevel;
×
67
                private set => this.RaiseAndSetIfChanged(ref LogGridAppender.LogFilterLevel, value);
×
68
        }
69

70
        public string SaveStatus {
71
                get;
×
72
                set => this.RaiseAndSetIfChanged(ref field, value);
×
73
        } = "CONVERTSTATUSPRE";
2✔
74

75
        public string ConvertStatus {
76
                get;
×
77
                set => this.RaiseAndSetIfChanged(ref field, value);
×
78
        } = "CONVERTSTATUSPRE";
2✔
79

80
        public string CopyStatus {
81
                get;
×
82
                set => this.RaiseAndSetIfChanged(ref field, value);
×
83
        } = "CONVERTSTATUSPRE";
2✔
84

85
        public bool ConvertButtonEnabled {
86
                get;
×
87
                set => this.RaiseAndSetIfChanged(ref field, value);
×
88
        } = true;
2✔
89

90
        public MainWindowViewModel(DataGrid logGrid) {
4✔
91
                Config = new Config();
2✔
92

93
                var appenders = LogManager.GetRepository().GetAppenders();
2✔
94
                var gridAppender = appenders.First(a => a.Name.Equals("grid"));
6✔
95
                if (gridAppender is not LogGridAppender logGridAppender) {
2!
96
                        throw new LogException($"Log appender \"{gridAppender.Name}\" is not a {typeof(LogGridAppender)}");
×
97
                }
98
                LogGridAppender = logGridAppender;
2✔
99
                LogGridAppender.LogGrid = logGrid;
2✔
100

101
                PathPicker = new PathPickerViewModel(Config);
2✔
102
                ModsPicker = new ModsPickerViewModel(Config);
2✔
103
                Options = new OptionsViewModel(Config.Options);
2✔
104

105
                // Create reactive commands.
106
                ToggleLogFilterLevelCommand = ReactiveCommand.Create<string>(ToggleLogFilterLevel);
2✔
107
                SetLanguageCommand = ReactiveCommand.Create<string>(SetLanguage);
2✔
108
                SetThemeCommand = ReactiveCommand.Create<string>(SetTheme);
2✔
109
        }
2✔
110

111
        public ReadOnlyObservableCollection<LogLine> FilteredLogLines => LogGridAppender.FilteredLogLines;
×
112

113
        #region Reactive commands
114

115
        public ReactiveCommand<string, Unit> ToggleLogFilterLevelCommand { get; }
×
116
        public ReactiveCommand<string, Unit> SetLanguageCommand { get; }
×
117
        public ReactiveCommand<string, Unit> SetThemeCommand { get; }
×
118

119
        #endregion
120

121
        public void ToggleLogFilterLevel(string value) {
×
122
                var level = LogManager.GetRepository().LevelMap[value];
×
123
                if (level is null) {
×
124
                        logger.Error($"Unknown log level: {value}");
×
125
                } else {
×
126
                        LogFilterLevel = level;
×
127
                }
×
128
                LogGridAppender.ToggleLogFilterLevel();
×
129
                this.RaisePropertyChanged(nameof(FilteredLogLines));
×
130
                Dispatcher.UIThread.Post(ScrollToLogEnd, DispatcherPriority.Normal);
×
131
        }
×
132

133
        public ushort Progress {
134
                get;
×
135
                set => this.RaiseAndSetIfChanged(ref field, value);
×
136
        } = 0;
2✔
137

138
        public bool IndeterminateProgress {
139
                get;
×
140
                set => this.RaiseAndSetIfChanged(ref field, value);
×
141
        } = false;
2✔
142

143
        private bool VerifyMandatoryPaths() {
×
144
                foreach (var folder in Config.RequiredFolders) {
×
145
                        if (!folder.Mandatory || Directory.Exists(folder.Value)) {
×
146
                                continue;
×
147
                        }
148

149
                        logger.Error($"Mandatory folder {folder.Name} at {folder.Value} not found.");
×
150
                        return false;
×
151
                }
152

153
                foreach (var file in Config.RequiredFiles.Where(file => file.Mandatory && !File.Exists(file.Value))) {
×
154
                        logger.Error($"Mandatory file {file.Name} at {file.Value} not found.");
×
155
                        return false;
×
156
                }
157

158
                return true;
×
159
        }
×
160

161
        private void ClearLogGrid() {
×
162
                LogGridAppender.LogLines.Clear();
×
163
        }
×
164

165
        private void CopyToTargetGameModDirectory() {
×
166
                var modCopier = new ModCopier(Config);
×
167
                bool copySuccess;
168
                var copyThread = new Thread(() => {
×
169
                        IndeterminateProgress = true;
×
170
                        CopyStatus = "CONVERTSTATUSIN";
×
171

×
172
                        copySuccess = modCopier.CopyMod();
×
173
                        CopyStatus = copySuccess ? "CONVERTSTATUSPOSTSUCCESS" : "CONVERTSTATUSPOSTFAIL";
×
174
                        Progress = Config.ProgressOnCopyingComplete;
×
175
                        IndeterminateProgress = false;
×
176

×
177
                        ConvertButtonEnabled = true;
×
178
                });
×
179
                copyThread.Start();
×
180
        }
×
181
        public async Task LaunchConverter() {
×
182
                ConvertButtonEnabled = false;
×
183
                ClearLogGrid();
×
184

185
                Progress = 0;
×
186
                SaveStatus = "CONVERTSTATUSPRE";
×
187
                ConvertStatus = "CONVERTSTATUSPRE";
×
188
                CopyStatus = "CONVERTSTATUSPRE";
×
189

190
                if (!VerifyMandatoryPaths()) {
×
191
                        ConvertButtonEnabled = true;
×
192
                        return;
×
193
                }
194
                Config.ExportConfiguration();
×
195

196
                var converterLauncher = new ConverterLauncher(Config);
×
197
                bool success;
198
                await Task.Run(async () => {
×
199
                        ConvertStatus = "CONVERTSTATUSIN";
×
200

×
201
                        try {
×
202
                                var launchConverterTask = converterLauncher.LaunchConverter();
×
203
                                launchConverterTask.Wait();
×
204
                                success = launchConverterTask.Result;
×
205
                        } catch (TaskCanceledException e) {
×
206
                                logger.Debug($"Converter backend task was cancelled: {e.Message}");
×
207
                                success = false;
×
208
                        } catch (Exception e) {
×
209
                                logger.Error($"Failed to start converter backend: {e.Message}");
×
210
                                var messageText = $"{loc.Translate("FAILED_TO_START_CONVERTER_BACKEND")}: {e.Message}";
×
211
                                if (!ElevatedPrivilegesDetector.IsAdministrator) {
×
212
                                        messageText += "\n\n" + loc.Translate("ELEVATED_PRIVILEGES_REQUIRED");
×
213
                                        if (OperatingSystem.IsWindows()) {
×
214
                                                messageText += "\n\n" + loc.Translate("RUN_AS_ADMIN");
×
215
                                        } else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS() || OperatingSystem.IsFreeBSD()) {
×
216
                                                messageText += "\n\n" + loc.Translate("RUN_WITH_SUDO");
×
217
                                        }
×
218
                                } else {
×
219
                                        messageText += "\n\n" + loc.Translate("FAILED_TO_START_CONVERTER_POSSIBLE_BUG");
×
220
                                }
×
221

×
222
                                Dispatcher.UIThread.Post(() => MessageBoxManager.GetMessageBoxStandard(
×
223
                                        title: loc.Translate("FAILED_TO_START_CONVERTER"),
×
224
                                        text: messageText,
×
225
                                        ButtonEnum.Ok,
×
226
                                        Icon.Error
×
227
                                ).ShowWindowDialogAsync(MainWindow.Instance).Wait());
×
228

×
229
                                success = false;
×
230
                        }
×
231

×
232
                        if (success) {
×
233
                                ConvertStatus = "CONVERTSTATUSPOSTSUCCESS";
×
234

×
235
                                if (Config.CopyToTargetGameModDirectory) {
×
236
                                        CopyToTargetGameModDirectory();
×
237
                                } else {
×
238
                                        ConvertButtonEnabled = true;
×
239
                                }
×
240
                        } else {
×
241
                                ConvertStatus = "CONVERTSTATUSPOSTFAIL";
×
242
                                await Dispatcher.UIThread.InvokeAsync(ShowErrorMessageBox);
×
243
                                ConvertButtonEnabled = true;
×
244
                        }
×
245
                });
×
246
        }
×
247

248
        private async Task ShowErrorMessageBox() {
×
249
                var messageBoxWindow = MessageBoxManager
×
250
                        .GetMessageBoxStandard(new MessageBoxStandardParams {
×
251
                                Icon = Icon.Error,
×
252
                                ContentTitle = loc.Translate("CONVERSION_FAILED"),
×
253
                                ContentMessage = loc.Translate("CONVERSION_FAILED_MESSAGE"),
×
254
                                Markdown = true,
×
255
                                ButtonDefinitions = ButtonEnum.OkCancel,
×
256
                        });
×
257
                var result = await messageBoxWindow.ShowWindowDialogAsync(MainWindow.Instance);
×
258
                if (result == ButtonResult.Ok) {
×
259
                        BrowserLauncher.Open(Config.ConverterReleaseForumThread);
×
260
                }
×
261
        }
×
262

263
        public async Task CheckForUpdates() {
×
264
                if (!Config.UpdateCheckerEnabled) {
×
265
                        return;
×
266
                }
267

268
                bool isUpdateAvailable = await UpdateChecker.IsUpdateAvailable("commit_id.txt", Config.PagesCommitIdUrl);
×
269
                if (!isUpdateAvailable) {
×
270
                        return;
×
271
                }
272

273
                var info = await UpdateChecker.GetLatestReleaseInfo(Config.Name);
×
274
                if (info.AssetUrl is null) {
×
275
                        return;
×
276
                }
277

278
                var updateNowStr = loc.Translate("UPDATE_NOW");
×
279
                var maybeLaterStr = loc.Translate("MAYBE_LATER");
×
280
                var msgBody = UpdateChecker.GetUpdateMessageBody(loc.Translate("NEW_VERSION_BODY"), info);
×
281
                var messageBoxWindow = MessageBoxManager
×
282
                        .GetMessageBoxCustom(new MessageBoxCustomParams {
×
283
                                Icon = Icon.Info,
×
284
                                ContentTitle = loc.Translate("NEW_VERSION_TITLE"),
×
285
                                ContentHeader = loc.Translate("NEW_VERSION_HEADER"),
×
286
                                ContentMessage = msgBody,
×
287
                                Markdown = true,
×
288
                                ButtonDefinitions = [
×
289
                                        new() {Name = updateNowStr, IsDefault = true},
×
290
                                        new() {Name = maybeLaterStr, IsCancel = true},
×
291
                                ],
×
292
                                MaxWidth = 1280,
×
293
                                MaxHeight = 720,
×
294
                        });
×
295

296
                bool performUpdate = false;
×
297
                await Dispatcher.UIThread.InvokeAsync(async () => {
×
NEW
298
                        string result = await messageBoxWindow.ShowWindowDialogAsync(MainWindow.Instance);
×
NEW
299
                        performUpdate = result.Equals(updateNowStr);
×
UNCOV
300
                }, DispatcherPriority.Normal);
×
301

302
                if (!performUpdate) {
×
303
                        logger.Info($"Update to version {info.Version} postponed.");
×
304
                        return;
×
305
                }
306

307
                // If we can use an installer, download it, run it, and exit.
308
                if (info.UseInstaller) {
×
309
                        await UpdateChecker.RunInstallerAndDie(info.AssetUrl, Config, NotificationManager);
×
NEW
310
                } else {
×
311
                        UpdateChecker.StartUpdaterAndDie(info.AssetUrl, Config.ConverterFolder);
×
312
                }
×
313
        }
×
314

315
        public async Task CheckForUpdatesOnStartup() {
×
316
                if (!Config.CheckForUpdatesOnStartup) {
×
317
                        return;
×
318
                }
319
                await CheckForUpdates();
×
320
        }
×
321

322
#pragma warning disable CA1822
323
        public void Exit() {
×
324
#pragma warning restore CA1822
325
                if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) {
×
326
                        desktop.Shutdown(exitCode: 0);
×
327
                }
×
328
        }
×
329

330
#pragma warning disable CA1822
331
        public async Task OpenAboutDialog() {
×
332
#pragma warning restore CA1822
333
                var messageBoxWindow = MessageBoxManager
×
334
                        .GetMessageBoxStandard(new MessageBoxStandardParams {
×
335
                                ContentTitle = TranslationSource.Instance["ABOUT_TITLE"],
×
336
                                Icon = Icon.Info,
×
337
                                ContentHeader = TranslationSource.Instance["ABOUT_HEADER"],
×
338
                                ContentMessage = TranslationSource.Instance["ABOUT_BODY"],
×
339
                                ButtonDefinitions = ButtonEnum.Ok,
×
340
                                SizeToContent = SizeToContent.WidthAndHeight,
×
341
                                MinHeight = 250,
×
342
                                ShowInCenter = true,
×
343
                                WindowStartupLocation = WindowStartupLocation.CenterOwner,
×
344
                        });
×
345
                await messageBoxWindow.ShowWindowDialogAsync(MainWindow.Instance);
×
346
        }
×
347

348
#pragma warning disable CA1822
349
        public void OpenPatreonPage() {
×
350
#pragma warning restore CA1822
351
                BrowserLauncher.Open("https://www.patreon.com/ParadoxGameConverters");
×
352
        }
×
353

354
        public void SetLanguage(string languageKey) {
×
355
                loc.SaveLanguage(languageKey);
×
356
        }
×
357

358
#pragma warning disable CA1822
359
        public void SetTheme(string themeName) {
×
360
#pragma warning restore CA1822
361
                _ = App.SaveTheme(themeName);
×
362
        }
×
363

364
        public string WindowTitle {
365
                get {
×
366
                        var displayName = loc.Translate(Config.DisplayName);
×
367
                        if (string.IsNullOrWhiteSpace(displayName)) {
×
368
                                displayName = "Converter";
×
369
                        }
×
370
                        return $"{displayName} Frontend";
×
371
                }
×
372
        }
373

374
        private LogGridAppender LogGridAppender { get; }
2✔
375

376
        private void ScrollToLogEnd() {
×
377
                LogGridAppender.ScrollToLogEnd();
×
378
        }
×
379
}
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