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

ParadoxGameConverters / Fronter.NET / 12979648212

27 Jan 2025 12:16AM UTC coverage: 18.767% (-0.006%) from 18.773%
12979648212

Pull #763

github

web-flow
Merge 3c653fa8a into dcd0095bb
Pull Request #763: Speed up OnFrameworkInitializationCompleted, remove some async void usage

73 of 628 branches covered (11.62%)

Branch coverage included in aggregate %.

2 of 18 new or added lines in 3 files covered. (11.11%)

1 existing line in 1 file now uncovered.

548 of 2681 relevant lines covered (20.44%)

9.56 hits per line

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

9.69
/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 MsBox.Avalonia.Models;
19
using ReactiveUI;
20
using System;
21
using System.Collections.Generic;
22
using System.Collections.ObjectModel;
23
using System.Diagnostics.CodeAnalysis;
24
using System.IO;
25
using System.Linq;
26
using System.Reactive;
27
using System.Threading;
28
using System.Threading.Tasks;
29

30
namespace Fronter.ViewModels;
31

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

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

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

58
        internal Config Config { get; }
6✔
59

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

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

71
        private string saveStatus = "CONVERTSTATUSPRE";
2✔
72
        private string convertStatus = "CONVERTSTATUSPRE";
2✔
73
        private string copyStatus = "CONVERTSTATUSPRE";
2✔
74

75
        public string SaveStatus {
76
                get => saveStatus;
×
77
                set => this.RaiseAndSetIfChanged(ref saveStatus, value);
×
78
        }
79
        public string ConvertStatus {
80
                get => convertStatus;
×
81
                set => this.RaiseAndSetIfChanged(ref convertStatus, value);
×
82
        }
83
        public string CopyStatus {
84
                get => copyStatus;
×
85
                set => this.RaiseAndSetIfChanged(ref copyStatus, value);
×
86
        }
87

88
        private bool convertButtonEnabled = true;
2✔
89
        public bool ConvertButtonEnabled {
90
                get => convertButtonEnabled;
×
91
                set => this.RaiseAndSetIfChanged(ref convertButtonEnabled, value);
×
92
        }
93

94
        public MainWindowViewModel(DataGrid logGrid) {
4✔
95
                Config = new Config();
2✔
96

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

105
                PathPicker = new PathPickerViewModel(Config);
2✔
106
                ModsPicker = new ModsPickerViewModel(Config);
2✔
107
                Options = new OptionsViewModel(Config.Options);
2✔
108

109
                // Create reactive commands.
110
                ToggleLogFilterLevelCommand = ReactiveCommand.Create<string>(ToggleLogFilterLevel);
2✔
111
                SetLanguageCommand = ReactiveCommand.Create<string>(SetLanguage);
2✔
112
                SetThemeCommand = ReactiveCommand.Create<string>(SetTheme);
2✔
113
        }
2✔
114

115
        public ReadOnlyObservableCollection<LogLine> FilteredLogLines => LogGridAppender.FilteredLogLines;
×
116

117
        #region Reactive commands
118

119
        public ReactiveCommand<string, Unit> ToggleLogFilterLevelCommand { get; }
×
120
        public ReactiveCommand<string, Unit> SetLanguageCommand { get; }
×
121
        public ReactiveCommand<string, Unit> SetThemeCommand { get; }
×
122

123
        #endregion
124

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

137
        private ushort progress = 0;
2✔
138
        public ushort Progress {
139
                get => progress;
×
140
                set => this.RaiseAndSetIfChanged(ref progress, value);
×
141
        }
142

143
        private bool indeterminateProgress = false;
2✔
144
        public bool IndeterminateProgress {
145
                get => indeterminateProgress;
×
146
                set => this.RaiseAndSetIfChanged(ref indeterminateProgress, value);
×
147
        }
148

149
        private bool VerifyMandatoryPaths() {
×
150
                foreach (var folder in Config.RequiredFolders) {
×
151
                        if (!folder.Mandatory || Directory.Exists(folder.Value)) {
×
152
                                continue;
×
153
                        }
154

155
                        logger.Error($"Mandatory folder {folder.Name} at {folder.Value} not found.");
×
156
                        return false;
×
157
                }
158

159
                foreach (var file in Config.RequiredFiles.Where(file => file.Mandatory && !File.Exists(file.Value))) {
×
160
                        logger.Error($"Mandatory file {file.Name} at {file.Value} not found.");
×
161
                        return false;
×
162
                }
163

164
                return true;
×
165
        }
×
166

167
        private void ClearLogGrid() {
×
168
                LogGridAppender.LogLines.Clear();
×
169
        }
×
170

171
        private void CopyToTargetGameModDirectory() {
×
172
                var modCopier = new ModCopier(Config);
×
173
                bool copySuccess;
174
                var copyThread = new Thread(() => {
×
175
                        IndeterminateProgress = true;
×
176
                        CopyStatus = "CONVERTSTATUSIN";
×
177

×
178
                        copySuccess = modCopier.CopyMod();
×
179
                        CopyStatus = copySuccess ? "CONVERTSTATUSPOSTSUCCESS" : "CONVERTSTATUSPOSTFAIL";
×
180
                        Progress = Config.ProgressOnCopyingComplete;
×
181
                        IndeterminateProgress = false;
×
182

×
183
                        ConvertButtonEnabled = true;
×
184
                });
×
185
                copyThread.Start();
×
186
        }
×
NEW
187
        public async Task LaunchConverter() {
×
188
                ConvertButtonEnabled = false;
×
189
                ClearLogGrid();
×
190

191
                Progress = 0;
×
192
                SaveStatus = "CONVERTSTATUSPRE";
×
193
                ConvertStatus = "CONVERTSTATUSPRE";
×
194
                CopyStatus = "CONVERTSTATUSPRE";
×
195

196
                if (!VerifyMandatoryPaths()) {
×
197
                        ConvertButtonEnabled = true;
×
198
                        return;
×
199
                }
200
                Config.ExportConfiguration();
×
201

202
                var converterLauncher = new ConverterLauncher(Config);
×
203
                bool success;
NEW
204
                await Task.Run(async () => {
×
205
                        ConvertStatus = "CONVERTSTATUSIN";
×
206

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

×
228
                                Dispatcher.UIThread.Post(() => MessageBoxManager.GetMessageBoxStandard(
×
229
                                        title: loc.Translate("FAILED_TO_START_CONVERTER"),
×
230
                                        text: messageText,
×
231
                                        ButtonEnum.Ok,
×
232
                                        Icon.Error
×
233
                                ).ShowWindowDialogAsync(MainWindow.Instance).Wait());
×
234

×
235
                                success = false;
×
236
                        }
×
237

×
238
                        if (success) {
×
239
                                ConvertStatus = "CONVERTSTATUSPOSTSUCCESS";
×
240

×
241
                                if (Config.CopyToTargetGameModDirectory) {
×
242
                                        CopyToTargetGameModDirectory();
×
243
                                } else {
×
244
                                        ConvertButtonEnabled = true;
×
245
                                }
×
246
                        } else {
×
247
                                ConvertStatus = "CONVERTSTATUSPOSTFAIL";
×
NEW
248
                                await Dispatcher.UIThread.InvokeAsync(ShowErrorMessageBox);
×
249
                                ConvertButtonEnabled = true;
×
250
                        }
×
251
                });
×
252
        }
×
253

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

NEW
269
        public async Task CheckForUpdates() {
×
270
                if (!Config.UpdateCheckerEnabled) {
×
271
                        return;
×
272
                }
273

274
                bool isUpdateAvailable = await UpdateChecker.IsUpdateAvailable("commit_id.txt", Config.PagesCommitIdUrl);
×
275
                if (!isUpdateAvailable) {
×
276
                        return;
×
277
                }
278

279
                var info = await UpdateChecker.GetLatestReleaseInfo(Config.Name);
×
280
                if (info.AssetUrl is null) {
×
281
                        return;
×
282
                }
283

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

302
                bool performUpdate = false;
×
303
                await Dispatcher.UIThread.InvokeAsync(async () => {
×
304
                        string? result = await messageBoxWindow.ShowWindowDialogAsync(MainWindow.Instance);
×
305
                        performUpdate = result?.Equals(updateNowStr) == true;
×
306
                }, DispatcherPriority.Normal);
×
307

308
                if (!performUpdate) {
×
309
                        logger.Info($"Update to version {info.Version} postponed.");
×
310
                        return;
×
311
                }
312

313
                // If we can use an installer, download it, run it, and exit.
314
                if (info.UseInstaller) {
×
315
                        await UpdateChecker.RunInstallerAndDie(info.AssetUrl, Config, NotificationManager);
×
316
                } else{
×
317
                        UpdateChecker.StartUpdaterAndDie(info.AssetUrl, Config.ConverterFolder);
×
318
                }
×
319
        }
×
320

NEW
321
        public async Task CheckForUpdatesOnStartup() {
×
322
                if (!Config.CheckForUpdatesOnStartup) {
×
323
                        return;
×
324
                }
NEW
325
                await CheckForUpdates();
×
326
        }
×
327

328
#pragma warning disable CA1822
329
        public void Exit() {
×
330
#pragma warning restore CA1822
331
                if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) {
×
332
                        desktop.Shutdown(exitCode: 0);
×
333
                }
×
334
        }
×
335

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

354
#pragma warning disable CA1822
355
        public void OpenPatreonPage() {
×
356
#pragma warning restore CA1822
357
                BrowserLauncher.Open("https://www.patreon.com/ParadoxGameConverters");
×
358
        }
×
359

360
        public void SetLanguage(string languageKey) {
×
361
                loc.SaveLanguage(languageKey);
×
362
        }
×
363

364
#pragma warning disable CA1822
365
        public void SetTheme(string themeName) {
×
366
#pragma warning restore CA1822
NEW
367
                _ = App.SaveTheme(themeName);
×
368
        }
×
369

370
        public string WindowTitle {
371
                get {
×
372
                        var displayName = loc.Translate(Config.DisplayName);
×
373
                        if (string.IsNullOrWhiteSpace(displayName)) {
×
374
                                displayName = "Converter";
×
375
                        }
×
376
                        return $"{displayName} Frontend";
×
377
                }
×
378
        }
379

380
        private LogGridAppender LogGridAppender { get; }
2✔
381

382
        private void ScrollToLogEnd() {
×
383
                LogGridAppender.ScrollToLogEnd();
×
384
        }
×
385
}
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