-
Notifications
You must be signed in to change notification settings - Fork 808
Expand file tree
/
Copy pathInstallOptionsViewModel.cs
More file actions
504 lines (432 loc) · 24.7 KB
/
InstallOptionsViewModel.cs
File metadata and controls
504 lines (432 loc) · 24.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
using System.Collections.ObjectModel;
using System.Net.Http;
using System.Windows.Input;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using UniGetUI.Core.Language;
using UniGetUI.Core.Logging;
using UniGetUI.Core.SettingsEngine;
using UniGetUI.Core.Tools;
using UniGetUI.PackageEngine.Enums;
using UniGetUI.PackageEngine.Interfaces;
using UniGetUI.PackageEngine.PackageClasses;
using UniGetUI.PackageEngine.Serializable;
namespace UniGetUI.Avalonia.ViewModels;
public partial class InstallOptionsViewModel : ObservableObject
{
// ── Public result ──────────────────────────────────────────────────────────
public bool ShouldProceedWithOperation { get; private set; }
public event EventHandler? CloseRequested;
private readonly IPackage _package;
private readonly InstallOptions _options;
private readonly bool _uiLoaded;
// ── Translated static labels ───────────────────────────────────────────────
public string DialogTitle { get; }
public string ProfileLabel { get; } = CoreTools.Translate("Operation profile:");
public string FollowGlobalLabel { get; } = CoreTools.Translate("Follow the default options when installing, upgrading or uninstalling this package");
public string GeneralInfoLabel { get; } = CoreTools.Translate("The following settings will be applied each time this package is installed, updated or removed.");
public string VersionLabel { get; } = CoreTools.Translate("Version to install:");
public string ArchLabel { get; } = CoreTools.Translate("Architecture to install:");
public string ScopeLabel { get; } = CoreTools.Translate("Installation scope:");
public string LocationLabel { get; } = CoreTools.Translate("Install location:");
public string SelectDirLabel { get; } = CoreTools.Translate("Select");
public string ResetDirLabel { get; } = CoreTools.Translate("Reset");
public string ParamsInstallLabel { get; } = CoreTools.Translate("Custom install arguments:");
public string ParamsUpdateLabel { get; } = CoreTools.Translate("Custom update arguments:");
public string ParamsUninstallLabel { get; } = CoreTools.Translate("Custom uninstall arguments:");
public string PreInstallLabel { get; } = CoreTools.Translate("Pre-install command:");
public string PostInstallLabel { get; } = CoreTools.Translate("Post-install command:");
public string AbortInstallLabel { get; } = CoreTools.Translate("Abort install if pre-install command fails");
public string PreUpdateLabel { get; } = CoreTools.Translate("Pre-update command:");
public string PostUpdateLabel { get; } = CoreTools.Translate("Post-update command:");
public string AbortUpdateLabel { get; } = CoreTools.Translate("Abort update if pre-update command fails");
public string PreUninstallLabel { get; } = CoreTools.Translate("Pre-uninstall command:");
public string PostUninstallLabel { get; } = CoreTools.Translate("Post-uninstall command:");
public string AbortUninstallLabel { get; } = CoreTools.Translate("Abort uninstall if pre-uninstall command fails");
public string CommandPreviewLabel { get; } = CoreTools.Translate("Command-line to run:");
public string SaveLabel { get; } = CoreTools.Translate("Save and close");
public string TabGeneralLabel { get; } = CoreTools.Translate("General");
public string TabLocationLabel { get; } = CoreTools.Translate("Architecture & Location");
public string TabCLILabel { get; } = CoreTools.Translate("Command-line");
public string TabCloseAppsLabel { get; } = CoreTools.Translate("Close apps");
public string TabPrePostLabel { get; } = CoreTools.Translate("Pre/Post install");
// Close-apps tab labels
public string KillProcessesDescriptionLabel { get; } = CoreTools.Translate(
"Select the processes that should be closed before this package is installed, updated or uninstalled.");
public string KillProcessesPlaceholderLabel { get; } = CoreTools.Translate(
"Write here the process names here, separated by commas (,)");
public string ForceKillLabelText { get; } = CoreTools.Translate(
"Try to kill the processes that refuse to close when requested to");
// Checkbox content labels
public string AdminCheckBox_Content { get; } = CoreTools.Translate("Run as admin");
public string InteractiveCheckBox_Content { get; } = CoreTools.Translate("Interactive installation");
public string SkipHashCheckBox_Content { get; } = CoreTools.Translate("Skip hash check");
public string UninstallPrevCheckBox_Content { get; } = CoreTools.Translate("Uninstall previous versions when updated");
public string SkipMinorCheckBox_Content { get; } = CoreTools.Translate("Skip minor updates for this package");
public string AutoUpdateCheckBox_Content { get; } = CoreTools.Translate("Automatically update this package");
// ── Capability flags (for IsEnabled bindings) ─────────────────────────────
public bool CanRunAsAdmin { get; }
public bool CanRunInteractively { get; }
public bool CanSkipHash { get; }
public bool CanUninstallPrev { get; }
public bool HasCustomScopes { get; }
public bool HasCustomLocations { get; }
// ── Package icon ───────────────────────────────────────────────────────────
[ObservableProperty] private Bitmap? _packageIcon;
// ── Follow-global toggle ───────────────────────────────────────────────────
[ObservableProperty] private bool _followGlobal;
[ObservableProperty] private bool _isCustomMode;
[ObservableProperty] private double _optionsOpacity = 1.0;
partial void OnFollowGlobalChanged(bool value)
{
IsCustomMode = !value;
OptionsOpacity = value ? 0.35 : 1.0;
if (_uiLoaded) _ = RefreshCommandPreviewAsync();
}
// ── Profile combo ──────────────────────────────────────────────────────────
public ObservableCollection<string> ProfileOptions { get; } = [];
[ObservableProperty] private string? _selectedProfile;
[ObservableProperty] private string _proceedButtonLabel = "";
partial void OnSelectedProfileChanged(string? value)
{
ProceedButtonLabel = value ?? "";
ApplyProfileEnableState();
if (_uiLoaded) _ = RefreshCommandPreviewAsync();
}
// ── General tab ───────────────────────────────────────────────────────────
[ObservableProperty] private bool _adminChecked;
[ObservableProperty] private bool _interactiveChecked;
[ObservableProperty] private bool _skipHashChecked;
[ObservableProperty] private bool _skipHashEnabled;
[ObservableProperty] private bool _uninstallPrevChecked;
[ObservableProperty] private bool _versionEnabled;
public ObservableCollection<string> VersionOptions { get; } = [];
[ObservableProperty] private string? _selectedVersion;
[ObservableProperty] private bool _skipMinorChecked;
[ObservableProperty] private bool _autoUpdateChecked;
partial void OnAdminCheckedChanged(bool _) => Refresh();
partial void OnInteractiveCheckedChanged(bool _) => Refresh();
partial void OnSkipHashCheckedChanged(bool _) => Refresh();
partial void OnSelectedVersionChanged(string? _) => Refresh();
// ── Architecture / Scope / Location tab ───────────────────────────────────
[ObservableProperty] private bool _archEnabled;
public ObservableCollection<string> ArchOptions { get; } = [];
[ObservableProperty] private string? _selectedArch;
[ObservableProperty] private bool _scopeEnabled;
public ObservableCollection<string> ScopeOptions { get; } = [];
[ObservableProperty] private string? _selectedScope;
[ObservableProperty] private string _locationText = "";
[ObservableProperty] private bool _locationEnabled;
partial void OnSelectedArchChanged(string? _) => Refresh();
partial void OnSelectedScopeChanged(string? _) => Refresh();
// ── CLI params tab ────────────────────────────────────────────────────────
[ObservableProperty] private string _paramsInstall = "";
[ObservableProperty] private string _paramsUpdate = "";
[ObservableProperty] private string _paramsUninstall = "";
partial void OnParamsInstallChanged(string _) => Refresh();
partial void OnParamsUpdateChanged(string _) => Refresh();
partial void OnParamsUninstallChanged(string _) => Refresh();
// ── Pre/Post commands tab ─────────────────────────────────────────────────
[ObservableProperty] private string _preInstallText = "";
[ObservableProperty] private string _postInstallText = "";
[ObservableProperty] private bool _abortInstall;
[ObservableProperty] private string _preUpdateText = "";
[ObservableProperty] private string _postUpdateText = "";
[ObservableProperty] private bool _abortUpdate;
[ObservableProperty] private string _preUninstallText = "";
[ObservableProperty] private string _postUninstallText = "";
[ObservableProperty] private bool _abortUninstall;
// ── Close apps tab ────────────────────────────────────────────────────────
public ObservableCollection<KillProcessEntry> KillProcessEntries { get; } = [];
[ObservableProperty] private string _killProcessNewEntry = "";
[ObservableProperty] private bool _forceKillChecked;
[RelayCommand]
private void AddKillProcess()
{
var name = KillProcessNewEntry.Trim();
if (string.IsNullOrEmpty(name)) return;
if (!KillProcessEntries.Any(e => e.Name.Equals(name, StringComparison.OrdinalIgnoreCase)))
KillProcessEntries.Add(new KillProcessEntry(name, e => KillProcessEntries.Remove(e)));
KillProcessNewEntry = "";
}
// ── Command preview ───────────────────────────────────────────────────────
[ObservableProperty] private string _commandPreview = "";
// ── Constructor ───────────────────────────────────────────────────────────
public InstallOptionsViewModel(IPackage package, OperationType operation, InstallOptions options)
{
_package = package;
_options = options;
var caps = package.Manager.Capabilities;
DialogTitle = CoreTools.Translate("{0} installation options", package.Name);
// Capability flags
CanRunAsAdmin = OperatingSystem.IsWindows() && caps.CanRunAsAdmin;
CanRunInteractively = caps.CanRunInteractively;
CanSkipHash = caps.CanSkipIntegrityChecks;
CanUninstallPrev = caps.CanUninstallPreviousVersionsAfterUpdate;
HasCustomScopes = caps.SupportsCustomScopes;
HasCustomLocations = caps.SupportsCustomLocations;
// Profile
string installLabel = CoreTools.Translate("Install");
string updateLabel = CoreTools.Translate("Update");
string uninstallLabel = CoreTools.Translate("Uninstall");
ProfileOptions.Add(installLabel);
ProfileOptions.Add(updateLabel);
ProfileOptions.Add(uninstallLabel);
SelectedProfile = operation switch
{
OperationType.Update => updateLabel,
OperationType.Uninstall => uninstallLabel,
_ => installLabel,
};
ProceedButtonLabel = SelectedProfile;
// Follow-global
FollowGlobal = !options.OverridesNextLevelOpts;
IsCustomMode = options.OverridesNextLevelOpts;
OptionsOpacity = options.OverridesNextLevelOpts ? 1.0 : 0.35;
// General checkboxes
AdminChecked = options.RunAsAdministrator;
InteractiveChecked = options.InteractiveInstallation;
SkipHashChecked = options.SkipHashCheck;
SkipHashEnabled = caps.CanSkipIntegrityChecks;
UninstallPrevChecked = options.UninstallPreviousVersionsOnUpdate;
SkipMinorChecked = options.SkipMinorUpdates;
AutoUpdateChecked = options.AutoUpdatePackage;
// Version
VersionOptions.Add(CoreTools.Translate("Latest"));
if (caps.SupportsPreRelease)
VersionOptions.Add(CoreTools.Translate("PreRelease"));
SelectedVersion = options.PreRelease
? CoreTools.Translate("PreRelease")
: CoreTools.Translate("Latest");
VersionEnabled = caps.SupportsCustomVersions || caps.SupportsPreRelease;
// Architecture
string defaultLabel = CoreTools.Translate("Default");
ArchOptions.Add(defaultLabel);
SelectedArch = defaultLabel;
if (caps.SupportsCustomArchitectures)
{
foreach (var arch in caps.SupportedCustomArchitectures)
{
ArchOptions.Add(arch);
if (options.Architecture == arch) SelectedArch = arch;
}
}
ArchEnabled = caps.SupportsCustomArchitectures;
// Scope
ScopeOptions.Add(CoreTools.Translate("Default"));
SelectedScope = CoreTools.Translate("Default");
if (caps.SupportsCustomScopes)
{
string localName = CoreTools.Translate(CommonTranslations.ScopeNames[PackageScope.Local]);
string globalName = CoreTools.Translate(CommonTranslations.ScopeNames[PackageScope.Global]);
ScopeOptions.Add(localName);
ScopeOptions.Add(globalName);
if (options.InstallationScope == "Local") SelectedScope = localName;
if (options.InstallationScope == "Global") SelectedScope = globalName;
}
ScopeEnabled = caps.SupportsCustomScopes;
// Location
LocationText = options.CustomInstallLocation;
LocationEnabled = caps.SupportsCustomLocations;
// CLI params
ParamsInstall = string.Join(' ', options.CustomParameters_Install);
ParamsUpdate = string.Join(' ', options.CustomParameters_Update);
ParamsUninstall = string.Join(' ', options.CustomParameters_Uninstall);
// Pre/Post commands
PreInstallText = options.PreInstallCommand;
PostInstallText = options.PostInstallCommand;
AbortInstall = options.AbortOnPreInstallFail;
PreUpdateText = options.PreUpdateCommand;
PostUpdateText = options.PostUpdateCommand;
AbortUpdate = options.AbortOnPreUpdateFail;
PreUninstallText = options.PreUninstallCommand;
PostUninstallText = options.PostUninstallCommand;
AbortUninstall = options.AbortOnPreUninstallFail;
// Close apps
foreach (var proc in options.KillBeforeOperation)
KillProcessEntries.Add(new KillProcessEntry(proc, e => KillProcessEntries.Remove(e)));
ForceKillChecked = Settings.Get(Settings.K.KillProcessesThatRefuseToDie);
// Show fallback immediately, then replace with real icon if available
using var fallback = AssetLoader.Open(_fallbackIconUri);
PackageIcon = new Bitmap(fallback);
_ = LoadIconAsync();
if (caps.SupportsCustomVersions)
_ = LoadVersionsAsync(options.Version);
_uiLoaded = true;
_ = RefreshCommandPreviewAsync();
}
// ── Commands ──────────────────────────────────────────────────────────────
/// <summary>Captures the current UI state into the options object without closing.</summary>
public void ApplyChanges() => ApplyToOptions();
[RelayCommand]
private void Save()
{
ApplyToOptions();
CloseRequested?.Invoke(this, EventArgs.Empty);
}
[RelayCommand]
private void Proceed()
{
ApplyToOptions();
ShouldProceedWithOperation = true;
CloseRequested?.Invoke(this, EventArgs.Empty);
}
[RelayCommand]
private void ResetLocation() => LocationText = "";
// ── Enable/disable based on selected operation profile ────────────────────
private void ApplyProfileEnableState()
{
if (!_uiLoaded) return;
var op = CurrentOp();
var caps = _package.Manager.Capabilities;
SkipHashEnabled = op is not OperationType.Uninstall && caps.CanSkipIntegrityChecks;
ArchEnabled = op is not OperationType.Uninstall && caps.SupportsCustomArchitectures;
VersionEnabled = op is OperationType.Install && (caps.SupportsCustomVersions || caps.SupportsPreRelease);
}
// ── Live command preview ──────────────────────────────────────────────────
private async Task RefreshCommandPreviewAsync()
{
if (!_uiLoaded) return;
var snap = SnapshotOptions();
var op = CurrentOp();
var applied = await InstallOptionsFactory.LoadApplicableAsync(_package, overridePackageOptions: snap);
var args = await Task.Run(() => _package.Manager.OperationHelper.GetParameters(_package, applied, op));
CommandPreview = _package.Manager.Properties.ExecutableFriendlyName + " " + string.Join(' ', args);
}
private void Refresh() { if (_uiLoaded) _ = RefreshCommandPreviewAsync(); }
// ── Package icon ──────────────────────────────────────────────────────────
private static readonly HttpClient _iconHttp = new(CoreTools.GenericHttpClientParameters);
private static readonly Uri _fallbackIconUri =
new("avares://UniGetUI.Avalonia/Assets/package_color.png");
private async Task LoadIconAsync()
{
try
{
var uri = await Task.Run(_package.GetIconUrlIfAny);
if (uri is null) return;
Bitmap bmp;
if (uri.IsFile)
bmp = new Bitmap(uri.LocalPath);
else if (uri.Scheme is "http" or "https")
{
var bytes = await _iconHttp.GetByteArrayAsync(uri);
using var ms = new MemoryStream(bytes);
bmp = new Bitmap(ms);
}
else return;
PackageIcon = bmp;
}
catch (Exception ex) { Logger.Warn($"[InstallOptionsViewModel] Failed to load icon for {_package.Id}: {ex.Message}"); }
}
// ── Async version loader ──────────────────────────────────────────────────
private async Task LoadVersionsAsync(string selectedVersion)
{
VersionEnabled = false;
var versions = await Task.Run(() => _package.Manager.DetailsHelper.GetVersions(_package));
foreach (var ver in versions)
{
VersionOptions.Add(ver);
if (selectedVersion == ver)
SelectedVersion = ver;
}
var op = CurrentOp();
VersionEnabled = op is OperationType.Install &&
(_package.Manager.Capabilities.SupportsCustomVersions ||
_package.Manager.Capabilities.SupportsPreRelease);
}
// ── Snapshot & apply ──────────────────────────────────────────────────────
private OperationType CurrentOp() => SelectedProfile switch
{
var s when s == CoreTools.Translate("Update") => OperationType.Update,
var s when s == CoreTools.Translate("Uninstall") => OperationType.Uninstall,
_ => OperationType.Install,
};
private InstallOptions SnapshotOptions()
{
var o = new InstallOptions();
o.RunAsAdministrator = AdminChecked;
o.InteractiveInstallation = InteractiveChecked;
o.SkipHashCheck = SkipHashChecked;
o.UninstallPreviousVersionsOnUpdate = UninstallPrevChecked;
o.AutoUpdatePackage = AutoUpdateChecked;
o.SkipMinorUpdates = SkipMinorChecked;
o.OverridesNextLevelOpts = !FollowGlobal;
var ver = SelectedVersion ?? "";
o.PreRelease = ver == CoreTools.Translate("PreRelease");
o.Version = (ver != CoreTools.Translate("Latest") && ver != CoreTools.Translate("PreRelease") && ver.Length > 0) ? ver : "";
string defaultLabel = CoreTools.Translate("Default");
o.Architecture = (SelectedArch != defaultLabel && SelectedArch is not null) ? SelectedArch : "";
o.InstallationScope = ScopeToString(SelectedScope);
o.CustomInstallLocation = LocationText;
o.CustomParameters_Install = Split(ParamsInstall);
o.CustomParameters_Update = Split(ParamsUpdate);
o.CustomParameters_Uninstall = Split(ParamsUninstall);
o.PreInstallCommand = PreInstallText;
o.PostInstallCommand = PostInstallText;
o.AbortOnPreInstallFail = AbortInstall;
o.PreUpdateCommand = PreUpdateText;
o.PostUpdateCommand = PostUpdateText;
o.AbortOnPreUpdateFail = AbortUpdate;
o.PreUninstallCommand = PreUninstallText;
o.PostUninstallCommand = PostUninstallText;
o.AbortOnPreUninstallFail = AbortUninstall;
return o;
}
private void ApplyToOptions()
{
var s = SnapshotOptions();
_options.RunAsAdministrator = s.RunAsAdministrator;
_options.InteractiveInstallation = s.InteractiveInstallation;
_options.SkipHashCheck = s.SkipHashCheck;
_options.UninstallPreviousVersionsOnUpdate = s.UninstallPreviousVersionsOnUpdate;
_options.AutoUpdatePackage = s.AutoUpdatePackage;
_options.SkipMinorUpdates = s.SkipMinorUpdates;
_options.OverridesNextLevelOpts = s.OverridesNextLevelOpts;
_options.PreRelease = s.PreRelease;
_options.Version = s.Version;
_options.Architecture = s.Architecture;
_options.InstallationScope = s.InstallationScope;
_options.CustomInstallLocation = s.CustomInstallLocation;
_options.CustomParameters_Install = s.CustomParameters_Install;
_options.CustomParameters_Update = s.CustomParameters_Update;
_options.CustomParameters_Uninstall = s.CustomParameters_Uninstall;
_options.PreInstallCommand = s.PreInstallCommand;
_options.PostInstallCommand = s.PostInstallCommand;
_options.AbortOnPreInstallFail = s.AbortOnPreInstallFail;
_options.PreUpdateCommand = s.PreUpdateCommand;
_options.PostUpdateCommand = s.PostUpdateCommand;
_options.AbortOnPreUpdateFail = s.AbortOnPreUpdateFail;
_options.PreUninstallCommand = s.PreUninstallCommand;
_options.PostUninstallCommand = s.PostUninstallCommand;
_options.AbortOnPreUninstallFail = s.AbortOnPreUninstallFail;
_options.KillBeforeOperation = KillProcessEntries.Select(e => e.Name).ToList();
Settings.Set(Settings.K.KillProcessesThatRefuseToDie, ForceKillChecked);
}
private static string ScopeToString(string? selected)
{
if (selected == CoreTools.Translate(CommonTranslations.ScopeNames[PackageScope.Local])) return "Local";
if (selected == CoreTools.Translate(CommonTranslations.ScopeNames[PackageScope.Global])) return "Global";
return "";
}
private static List<string> Split(string text)
=> text.Split(' ', StringSplitOptions.RemoveEmptyEntries).ToList();
}
/// <summary>A single entry in the "close apps before installing" list.</summary>
public sealed class KillProcessEntry
{
public string Name { get; }
public ICommand RemoveCommand { get; }
public KillProcessEntry(string name, Action<KillProcessEntry> remove)
{
Name = name;
RemoveCommand = new SyncCommand(() => remove(this));
}
private sealed class SyncCommand(Action action) : ICommand
{
public event EventHandler? CanExecuteChanged { add { } remove { } }
public bool CanExecute(object? _) => true;
public void Execute(object? _) => action();
}
}