Skip to content

Commit fda3a13

Browse files
authored
Allow automatic updates to be limited to selected packages (#5327)
1 parent 20f3296 commit fda3a13

28 files changed

Lines changed: 1376 additions & 28 deletions

src/Languages/lang_en.json

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1003,5 +1003,30 @@
10031003
"Please wait while {0} is being installed. This may take several minutes.": "Please wait while {0} is being installed. This may take several minutes.",
10041004
"The installer finished, but {0} could not be found on your system.": "The installer finished, but {0} could not be found on your system.",
10051005
"{0} could not be installed. The installer exited with code {1}.": "{0} could not be installed. The installer exited with code {1}.",
1006-
"You can try again, or install it manually with the command below.": "You can try again, or install it manually with the command below."
1006+
"You can try again, or install it manually with the command below.": "You can try again, or install it manually with the command below.",
1007+
"All available updates": "All available updates",
1008+
"Only packages marked for automatic updates": "Only packages marked for automatic updates",
1009+
"No packages are marked yet, so nothing will be installed": "No packages are marked yet, so nothing will be installed",
1010+
"{0} package(s) are marked": "{0} package(s) are marked",
1011+
"Update only the packages marked for automatic updates, respecting the battery and metered connection restrictions": "Update only the packages marked for automatic updates, respecting the battery and metered connection restrictions",
1012+
"Manage the list": "Manage the list",
1013+
"Automatically update selected packages": "Automatically update selected packages",
1014+
"Manage automatic updates": "Manage automatic updates",
1015+
"Update this package automatically": "Update this package automatically",
1016+
"{0} package(s) marked for automatic updates": "{0} package(s) marked for automatic updates",
1017+
"Turn on \"Install available updates\" in the scheduled maintenance settings for this to take effect.": "Turn on \"Install available updates\" in the scheduled maintenance settings for this to take effect.",
1018+
"Every upgradable package is already installed automatically, so this changes nothing until the scheduled task is limited to marked packages.": "Every upgradable package is already installed automatically, so this changes nothing until the scheduled task is limited to marked packages.",
1019+
"They will be updated when the scheduled maintenance task runs.": "They will be updated when the scheduled maintenance task runs.",
1020+
"This package will be updated when the scheduled maintenance task runs.": "This package will be updated when the scheduled maintenance task runs.",
1021+
"Turn on \"Install available updates\" in the scheduled maintenance settings for this list to take effect.": "Turn on \"Install available updates\" in the scheduled maintenance settings for this list to take effect.",
1022+
"Every upgradable package is currently installed automatically, so this list is not being used.": "Every upgradable package is currently installed automatically, so this list is not being used.",
1023+
"Not installed": "Not installed",
1024+
"No packages are updated automatically": "No packages are updated automatically",
1025+
"Tick the packages that should be updated on their own when the scheduled \"Install available updates\" task runs.": "Tick the packages that should be updated on their own when the scheduled \"Install available updates\" task runs.",
1026+
"Search for a package": "Search for a package",
1027+
"Only marked": "Only marked",
1028+
"No packages match the search": "No packages match the search",
1029+
"{0} of {1} packages are updated automatically": "{0} of {1} packages are updated automatically",
1030+
"This package has its updates ignored, so it will never be updated automatically": "This package has its updates ignored, so it will never be updated automatically",
1031+
"This package is no longer installed": "This package is no longer installed"
10071032
}

src/UniGetUI.Avalonia/Assets/Styles/Styles.Common.axaml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -689,6 +689,27 @@
689689
<Setter Property="Foreground" Value="{DynamicResource SystemControlForegroundBaseHighBrush}"/>
690690
</Style>
691691

692+
<!-- Filter chip toggle: accent fill when active, so the on/off state reads at a glance -->
693+
<Style Selector="ToggleButton.accent-toggle:checked /template/ ContentPresenter">
694+
<Setter Property="Background" Value="{DynamicResource AccentFillColorDefaultBrush}"/>
695+
<Setter Property="BorderBrush" Value="{DynamicResource AccentFillColorDefaultBrush}"/>
696+
<Setter Property="Foreground" Value="{DynamicResource TextOnAccentFillColorPrimaryBrush}"/>
697+
<Setter Property="FontWeight" Value="SemiBold"/>
698+
</Style>
699+
<Style Selector="ToggleButton.accent-toggle:checked:pointerover /template/ ContentPresenter">
700+
<Setter Property="Background" Value="{DynamicResource AccentFillColorSecondaryBrush}"/>
701+
<Setter Property="BorderBrush" Value="{DynamicResource AccentFillColorSecondaryBrush}"/>
702+
<Setter Property="Foreground" Value="{DynamicResource TextOnAccentFillColorPrimaryBrush}"/>
703+
</Style>
704+
<Style Selector="ToggleButton.accent-toggle:checked:pressed /template/ ContentPresenter">
705+
<Setter Property="Background" Value="{DynamicResource AccentFillColorTertiaryBrush}"/>
706+
<Setter Property="BorderBrush" Value="{DynamicResource AccentFillColorTertiaryBrush}"/>
707+
<Setter Property="Foreground" Value="{DynamicResource TextOnAccentFillColorSecondaryBrush}"/>
708+
</Style>
709+
<Style Selector="ToggleButton.accent-toggle:not(:checked) /template/ ContentPresenter">
710+
<Setter Property="Foreground" Value="{DynamicResource TextFillColorSecondaryBrush}"/>
711+
</Style>
712+
692713
<!-- Filters toolbar toggle: card-like fill (matches SettingsCard) when checked -->
693714
<Style Selector="ToggleButton.filter-toggle">
694715
<Style.Resources>

src/UniGetUI.Avalonia/Infrastructure/AvaloniaBootstrapper.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using UniGetUI.Interface.Telemetry;
1313
using UniGetUI.PackageEngine;
1414
using UniGetUI.PackageEngine.Classes.Manager.Classes;
15+
using UniGetUI.PackageEngine.Classes.Packages.Classes;
1516
using UniGetUI.PackageEngine.Enums;
1617
using UniGetUI.PackageEngine.Interfaces;
1718
using UniGetUI.PackageEngine.Operations;
@@ -187,6 +188,8 @@ private static async Task InitializePackageEngineAsync()
187188
{
188189
// LoadLoaders is called synchronously in App.axaml.cs before MainWindow creation
189190
await Task.Run(PEInterface.LoadManagers);
191+
192+
await Task.Run(() => AutoUpdatesMigration.RunOnce(PEInterface.Managers));
190193
}
191194

192195
private static async Task InitializeIpcApiAsync()

src/UniGetUI.Avalonia/Models/PackageCollections.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using Avalonia.Media.Imaging;
88
using Avalonia.Threading;
99
using UniGetUI.Avalonia.ViewModels.Pages;
10+
using UniGetUI.Avalonia.Views.Controls;
1011
using UniGetUI.Core.Logging;
1112
using UniGetUI.Core.SettingsEngine;
1213
using UniGetUI.Core.Tools;
@@ -22,7 +23,7 @@ namespace UniGetUI.PackageEngine.PackageClasses;
2223
/// <summary>
2324
/// Avalonia-compatible package wrapper (replaces the WinUI PackageWrapper that uses Microsoft.UI.Xaml).
2425
/// </summary>
25-
public sealed class PackageWrapper : INotifyPropertyChanged, IDisposable
26+
public sealed class PackageWrapper : INotifyPropertyChanged, IDisposable, IPackageIconHost
2627
{
2728
private static readonly HttpClient _iconHttpClient = new(CoreTools.GenericHttpClientParameters)
2829
{
@@ -236,6 +237,14 @@ public void EnsureIconLoaded()
236237
_ = EnsureIconLoadedAsync();
237238
}
238239

240+
public static Task<Bitmap?> LoadSharedIconAsync(IPackage package)
241+
{
242+
if (Settings.Get(Settings.K.DisableIconsOnPackageLists))
243+
return Task.FromResult<Bitmap?>(null);
244+
245+
return GetSharedIconLoad(package.GetHash(), package);
246+
}
247+
239248
/// <summary>Loads this row's icon at most once and returns the shared load operation.</summary>
240249
public Task EnsureIconLoadedAsync()
241250
{

src/UniGetUI.Avalonia/ViewModels/DialogPages/InstallOptionsViewModel.cs

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
using UniGetUI.Core.SettingsEngine;
1212
using UniGetUI.Core.SettingsEngine.SecureSettings;
1313
using UniGetUI.Core.Tools;
14+
using UniGetUI.Core.Tools.Scheduling;
15+
using UniGetUI.PackageEngine.Classes.Packages.Classes;
1416
using UniGetUI.PackageEngine.Enums;
1517
using UniGetUI.PackageEngine.Interfaces;
1618
using UniGetUI.PackageEngine.PackageClasses;
@@ -92,6 +94,7 @@ public partial class InstallOptionsViewModel : ObservableObject
9294
public string SkipMinorLevelSuffix_Content { get; } = CoreTools.Translate("version numbers");
9395
public string AutoUpdateCheckBox_Content { get; } = CoreTools.Translate("Automatically update this package");
9496
public string IgnoreUpdatesCheckBox_Content { get; } = CoreTools.Translate("Ignore future updates for this package");
97+
public string AutoUpdateHint_Content { get; } = BuildAutoUpdateHint();
9598

9699
// ── Security-gated sections (CLI args & pre/post commands), mirrors WinUI ──
97100
public bool CliEnabled { get; }
@@ -161,8 +164,15 @@ partial void OnSelectedProfileChanged(string? value)
161164
// Index 0 => keep first 1 number significant (level 2), 1 => first 2 (level 3), 2 => first 3 (level 4).
162165
public ObservableCollection<string> SkipMinorLevelOptions { get; } = ["1", "2", "3"];
163166
[ObservableProperty] private int _skipMinorLevelIndex;
164-
[ObservableProperty] private bool _autoUpdateChecked;
165-
[ObservableProperty] private bool _ignoreUpdatesChecked;
167+
[ObservableProperty]
168+
[NotifyPropertyChangedFor(nameof(IsAutoUpdateHintVisible))]
169+
private bool _autoUpdateChecked;
170+
171+
[ObservableProperty]
172+
[NotifyPropertyChangedFor(nameof(IsAutoUpdateHintVisible))]
173+
private bool _ignoreUpdatesChecked;
174+
175+
public bool IsAutoUpdateHintVisible => AutoUpdateChecked && !IgnoreUpdatesChecked;
166176

167177
partial void OnAdminCheckedChanged(bool value) => Refresh();
168178
partial void OnInteractiveCheckedChanged(bool value) => Refresh();
@@ -277,7 +287,7 @@ public InstallOptionsViewModel(IPackage package, OperationType operation, Instal
277287
UninstallPrevChecked = options.UninstallPreviousVersionsOnUpdate;
278288
SkipMinorChecked = options.SkipMinorUpdates;
279289
SkipMinorLevelIndex = options.SkipMinorUpdatesLevel - 2; // level is validated to 2..4 by the getter
280-
AutoUpdateChecked = options.AutoUpdatePackage;
290+
AutoUpdateChecked = AutoUpdatesDatabase.IsAutoUpdated(package);
281291

282292
// Version
283293
VersionOptions.Add(CoreTools.Translate("Latest"));
@@ -439,6 +449,32 @@ private async Task ApplyIgnoredUpdatesAsync()
439449
catch (Exception ex) { Logger.Warn($"[InstallOptionsViewModel] Failed to apply ignored-updates state: {ex.Message}"); }
440450
}
441451

452+
private static string BuildAutoUpdateHint()
453+
{
454+
var schedule = MaintenanceScheduleStore.Get(MaintenanceTaskKind.InstallUpdates);
455+
456+
if (!schedule.Enabled)
457+
return CoreTools.Translate("Turn on \"Install available updates\" in the scheduled maintenance settings for this to take effect.");
458+
459+
if (schedule.InstallTargets is ScheduleInstallTargets.AllPackages)
460+
return CoreTools.Translate("Every upgradable package is already installed automatically, so this changes nothing until the scheduled task is limited to marked packages.");
461+
462+
return CoreTools.Translate("This package will be updated when the scheduled maintenance task runs.");
463+
}
464+
465+
private void ApplyAutoUpdate()
466+
{
467+
try
468+
{
469+
string id = AutoUpdatesDatabase.GetIdForPackage(_package);
470+
if (AutoUpdateChecked)
471+
AutoUpdatesDatabase.Add(id);
472+
else if (AutoUpdatesDatabase.IsAutoUpdated(id))
473+
AutoUpdatesDatabase.Remove(id);
474+
}
475+
catch (Exception ex) { Logger.Warn($"[InstallOptionsViewModel] Failed to apply automatic-update state: {ex.Message}"); }
476+
}
477+
442478
// ── Enable/disable based on selected operation profile ────────────────────
443479
private void ApplyProfileEnableState()
444480
{
@@ -538,7 +574,6 @@ private InstallOptions SnapshotOptions()
538574
o.InteractiveInstallation = InteractiveChecked;
539575
o.SkipHashCheck = SkipHashChecked;
540576
o.UninstallPreviousVersionsOnUpdate = UninstallPrevChecked;
541-
o.AutoUpdatePackage = AutoUpdateChecked;
542577
o.SkipMinorUpdates = SkipMinorChecked;
543578
o.SkipMinorUpdatesLevel = SkipMinorLevelIndex + 2;
544579
o.OverridesNextLevelOpts = !FollowGlobal;
@@ -575,7 +610,6 @@ private void ApplyToOptions()
575610
_options.InteractiveInstallation = s.InteractiveInstallation;
576611
_options.SkipHashCheck = s.SkipHashCheck;
577612
_options.UninstallPreviousVersionsOnUpdate = s.UninstallPreviousVersionsOnUpdate;
578-
_options.AutoUpdatePackage = s.AutoUpdatePackage;
579613
_options.SkipMinorUpdates = s.SkipMinorUpdates;
580614
_options.SkipMinorUpdatesLevel = s.SkipMinorUpdatesLevel;
581615
_options.OverridesNextLevelOpts = s.OverridesNextLevelOpts;
@@ -599,6 +633,7 @@ private void ApplyToOptions()
599633
_options.KillBeforeOperation = KillProcessEntries.Select(e => e.Name).ToList();
600634
Settings.Set(Settings.K.KillProcessesThatRefuseToDie, ForceKillChecked);
601635
_ = ApplyIgnoredUpdatesAsync();
636+
ApplyAutoUpdate();
602637
}
603638

604639
private static string ScopeToString(string? selected)

0 commit comments

Comments
 (0)