Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Check Improvements #1447

Merged
23 changes: 23 additions & 0 deletions LenovoLegionToolkit.Lib/Enums.cs
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,29 @@ public enum TouchpadLockState
On
}

public enum UpdateCheckFrequency
{
[Display(ResourceType = typeof(Resource), Name = "UpdateCheckFrequency_PerHour")]
PerHour,
[Display(ResourceType = typeof(Resource), Name = "UpdateCheckFrequency_PerThreeHours")]
PerThreeHours,
[Display(ResourceType = typeof(Resource), Name = "UpdateCheckFrequency_PerTwelveHours")]
PerTwelveHours,
[Display(ResourceType = typeof(Resource), Name = "UpdateCheckFrequency_PerDay")]
PerDay,
[Display(ResourceType = typeof(Resource), Name = "UpdateCheckFrequency_PerWeek")]
PerWeek,
[Display(ResourceType = typeof(Resource), Name = "UpdateCheckFrequency_PerMonth")]
PerMonth
}

public enum UpdateCheckStatus
{
Success,
RateLimitReached,
Error
}

public enum WhiteKeyboardBacklightState
{
[Display(ResourceType = typeof(Resource), Name = "WhiteKeyboardBacklightState_Off")]
Expand Down
1 change: 1 addition & 0 deletions LenovoLegionToolkit.Lib/IoCModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ protected override void Load(ContainerBuilder builder)
builder.Register<RGBKeyboardSettings>();
builder.Register<SpectrumKeyboardSettings>();
builder.Register<SunriseSunsetSettings>();
builder.Register<UpdateCheckSettings>();

builder.Register<AlwaysOnUSBFeature>();
builder.Register<BatteryFeature>();
Expand Down
54 changes: 54 additions & 0 deletions LenovoLegionToolkit.Lib/Resources/Resource.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 22 additions & 4 deletions LenovoLegionToolkit.Lib/Resources/Resource.resx
Original file line number Diff line number Diff line change
Expand Up @@ -467,15 +467,33 @@
<value>Unmute</value>
</data>
<data name="PowerModeMappingMode_Disabled" xml:space="preserve">
<value>Disabled</value>
<value>Disabled</value>
</data>
<data name="PowerModeMappingMode_WindowsPowerMode" xml:space="preserve">
<value>Windows Power Mode</value>
<value>Windows Power Mode</value>
</data>
<data name="PowerModeMappingMode_WindowsPowerPlan" xml:space="preserve">
<value>Windows Power Plan</value>
<value>Windows Power Plan</value>
</data>
<data name="RGBKeyboardBacklightPreset_Four" xml:space="preserve">
<value>Preset 4</value>
<value>Preset 4</value>
</data>
<data name="UpdateCheckFrequency_PerDay" xml:space="preserve">
<value>Once per day</value>
</data>
<data name="UpdateCheckFrequency_PerHour" xml:space="preserve">
<value>Once per hour</value>
</data>
<data name="UpdateCheckFrequency_PerMonth" xml:space="preserve">
<value>Once per month</value>
</data>
<data name="UpdateCheckFrequency_PerThreeHours" xml:space="preserve">
<value>Once per 3 hours</value>
</data>
<data name="UpdateCheckFrequency_PerTwelveHours" xml:space="preserve">
<value>Once per 12 hours</value>
</data>
<data name="UpdateCheckFrequency_PerWeek" xml:space="preserve">
<value>Once per week</value>
</data>
</root>
23 changes: 23 additions & 0 deletions LenovoLegionToolkit.Lib/Settings/UpdateCheckSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace LenovoLegionToolkit.Lib.Settings;

public class UpdateCheckSettings()
: AbstractSettings<UpdateCheckSettings.UpdateCheckSettingsStore>("update_check.json")
{
public class UpdateCheckSettingsStore
{
public DateTime? LastUpdateCheckDateTime { get; set; }
public UpdateCheckFrequency UpdateCheckFrequency { get; set; }
}

protected override UpdateCheckSettingsStore Default => new()
{
LastUpdateCheckDateTime = null,
UpdateCheckFrequency = UpdateCheckFrequency.PerThreeHours
};
}
50 changes: 43 additions & 7 deletions LenovoLegionToolkit.Lib/Utils/UpdateChecker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,35 @@
using System.Threading;
using System.Threading.Tasks;
using LenovoLegionToolkit.Lib.Extensions;
using LenovoLegionToolkit.Lib.Settings;
using NeoSmart.AsyncLock;
using Octokit;
using Octokit.Internal;

namespace LenovoLegionToolkit.Lib.Utils;

public class UpdateChecker(HttpClientFactory httpClientFactory)
public class UpdateChecker
{
private readonly TimeSpan _minimumTimeSpanForRefresh = new(hours: 3, minutes: 0, seconds: 0);
private readonly HttpClientFactory _httpClientFactory;
private readonly UpdateCheckSettings _updateCheckSettings = IoCContainer.Resolve<UpdateCheckSettings>();
private readonly AsyncLock _updateSemaphore = new();

private DateTime _lastUpdate = DateTime.MinValue;
private DateTime _lastUpdate;
private TimeSpan _minimumTimeSpanForRefresh;
private Update[] _updates = [];

public bool Disable { get; set; }
public UpdateCheckStatus Status { get; set; }

public async Task<Version?> CheckAsync()
public UpdateChecker(HttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;

UpdateMiniumTimeSpanForRefresh();
_lastUpdate = _updateCheckSettings.Store.LastUpdateCheckDateTime ?? DateTime.MinValue;
}

public async Task<Version?> CheckAsync(bool forceCheck)
{
using (await _updateSemaphore.LockAsync().ConfigureAwait(false))
{
Expand All @@ -37,13 +49,13 @@ public class UpdateChecker(HttpClientFactory httpClientFactory)
var timeSpanSinceLastUpdate = DateTime.UtcNow - _lastUpdate;
var shouldCheck = timeSpanSinceLastUpdate > _minimumTimeSpanForRefresh;

if (!shouldCheck)
if (!forceCheck && !shouldCheck)
return _updates.Length != 0 ? _updates.First().Version : null;

if (Log.Instance.IsTraceEnabled)
Log.Instance.Trace($"Checking...");

var adapter = new HttpClientAdapter(httpClientFactory.CreateHandler);
var adapter = new HttpClientAdapter(_httpClientFactory.CreateHandler);
var productInformation = new ProductHeaderValue("LenovoLegionToolkit-UpdateChecker");
var connection = new Connection(productInformation, adapter);
var githubClient = new GitHubClient(connection);
Expand All @@ -65,18 +77,31 @@ public class UpdateChecker(HttpClientFactory httpClientFactory)
Log.Instance.Trace($"Checked [updates.Length={updates.Length}]");

_updates = updates;
Status = UpdateCheckStatus.Success;

return _updates.Length != 0 ? _updates.First().Version : null;
}
catch (RateLimitExceededException ex)
{
if (Log.Instance.IsTraceEnabled)
Log.Instance.Trace($"Reached API Rate Limitation.", ex);

Status = UpdateCheckStatus.RateLimitReached;
return null;
}
catch (Exception ex)
{
if (Log.Instance.IsTraceEnabled)
Log.Instance.Trace($"Error checking for updates.", ex);

Status = UpdateCheckStatus.Error;
return null;
}
finally
{
_lastUpdate = DateTime.UtcNow;
_updateCheckSettings.Store.LastUpdateCheckDateTime = _lastUpdate;
_updateCheckSettings.SynchronizeStore();
}
}
}
Expand All @@ -101,10 +126,21 @@ public async Task<string> DownloadLatestUpdateAsync(IProgress<float>? progress =
throw new InvalidOperationException("Setup file URL could not be found");

await using var fileStream = File.OpenWrite(tempPath);
using var httpClient = httpClientFactory.Create();
using var httpClient = _httpClientFactory.Create();
await httpClient.DownloadAsync(latestUpdate.Url, fileStream, progress, cancellationToken).ConfigureAwait(false);

return tempPath;
}
}

public void UpdateMiniumTimeSpanForRefresh() => _minimumTimeSpanForRefresh = _updateCheckSettings.Store.UpdateCheckFrequency switch
{
UpdateCheckFrequency.PerHour => new(hours: 1, minutes: 0, seconds: 0),
UpdateCheckFrequency.PerThreeHours => new(hours: 3, minutes: 0, seconds: 0),
UpdateCheckFrequency.PerTwelveHours => new(hours: 2, minutes: 0, seconds: 0),
UpdateCheckFrequency.PerDay => new(days: 1, hours: 0, minutes: 0, seconds: 0),
UpdateCheckFrequency.PerWeek => new(days: 7, hours: 0, minutes: 0, seconds: 0),
UpdateCheckFrequency.PerMonth => new(days: 30, hours: 0, minutes: 0, seconds: 0),
_ => throw new ArgumentException(nameof(_updateCheckSettings.Store.UpdateCheckFrequency))
};
}
36 changes: 36 additions & 0 deletions LenovoLegionToolkit.WPF/Pages/SettingsPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,42 @@
</custom:CardAction.Content>
</custom:CardAction>

<TextBlock x:Name="_updateTextBlock"
Margin="0,16,0,24"
AutomationProperties.Name="{Binding RelativeSource={RelativeSource Mode=Self}, Path=Text}"
Focusable="True"
FontSize="24"
FontWeight="Medium"
Text="{x:Static resources:Resource.SettingsPage_Update_Title}" />

<custom:CardControl x:Name="_checkUpdatesCard" Margin="0,0,0,8">
<custom:CardControl.Header>
<controls:CardHeaderControl Title="{x:Static resources:Resource.SettingsPage_CheckUpdates_Title}" Subtitle="{x:Static resources:Resource.SettingsPage_CheckUpdates_Message}" />
</custom:CardControl.Header>
<wpfui:Button
x:Name="_checkUpdatesButton"
Margin="0,0,0,8"
MinWidth="100"
AutomationProperties.Name="{x:Static resources:Resource.SettingsPage_CheckUpdates_Title}"
Click="CheckUpdates_Click"
Content="{x:Static resources:Resource.SettingsPage_CheckUpdatesButton_Content}"
Visibility="Hidden" />
</custom:CardControl>

<custom:CardControl x:Name="_updateCheckFrequencyCard" Margin="0,0,0,8">
<custom:CardControl.Header>
<controls:CardHeaderControl Title="{x:Static resources:Resource.SettingsPage_UpdateCheckFrequency_Title}" Subtitle="{x:Static resources:Resource.SettingsPage_UpdateCheckFrequency_Message}" />
</custom:CardControl.Header>
<ComboBox
x:Name="_updateCheckFrequencyComboBox"
MinWidth="160"
Margin="0,0,0,8"
AutomationProperties.Name="{x:Static resources:Resource.SettingsPage_UpdateCheckFrequency_Title}"
MaxDropDownHeight="Auto"
SelectionChanged="UpdateCheckFrequencyComboBox_SelectionChanged"
Visibility="Hidden" />
</custom:CardControl>

<TextBlock
Margin="0,16,0,24"
AutomationProperties.Name="{Binding RelativeSource={RelativeSource Mode=Self}, Path=Text}"
Expand Down
41 changes: 41 additions & 0 deletions LenovoLegionToolkit.WPF/Pages/SettingsPage.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
using LenovoLegionToolkit.WPF.Extensions;
using LenovoLegionToolkit.WPF.Resources;
using LenovoLegionToolkit.WPF.Utils;
using LenovoLegionToolkit.WPF.Windows;
using LenovoLegionToolkit.WPF.Windows.Settings;

namespace LenovoLegionToolkit.WPF.Pages;
Expand All @@ -36,6 +37,8 @@ public partial class SettingsPage
private readonly ThemeManager _themeManager = IoCContainer.Resolve<ThemeManager>();
private readonly HWiNFOIntegration _hwinfoIntegration = IoCContainer.Resolve<HWiNFOIntegration>();
private readonly IpcServer _ipcServer = IoCContainer.Resolve<IpcServer>();
private readonly UpdateChecker _updateChecker = IoCContainer.Resolve<UpdateChecker>();
private readonly UpdateCheckSettings _updateCheckSettings = IoCContainer.Resolve<UpdateCheckSettings>();

private bool _isRefreshing;

Expand Down Expand Up @@ -119,6 +122,19 @@ private async Task RefreshAsync()

_bootLogoCard.Visibility = await BootLogo.IsSupportedAsync() ? Visibility.Visible : Visibility.Collapsed;

if (_updateChecker.Disable)
{
_updateTextBlock.Visibility = Visibility.Collapsed;
_checkUpdatesCard.Visibility = Visibility.Collapsed;
_updateCheckFrequencyCard.Visibility = Visibility.Collapsed;
}
else
{
_checkUpdatesButton.Visibility = Visibility.Visible;
_updateCheckFrequencyComboBox.Visibility = Visibility.Visible;
_updateCheckFrequencyComboBox.SetItems(Enum.GetValues<UpdateCheckFrequency>(), _updateCheckSettings.Store.UpdateCheckFrequency, t => t.GetDisplayName());
}

try
{
var mi = await Compatibility.GetMachineInformationAsync();
Expand Down Expand Up @@ -555,6 +571,31 @@ private void BootLogo_Click(object sender, RoutedEventArgs e)
window.ShowDialog();
}

private void CheckUpdates_Click(object sender, RoutedEventArgs e)
{
if (_isRefreshing)
return;

if (App.Current.MainWindow is not MainWindow mainWindow)
return;

mainWindow.CheckForUpdates(true);
SnackbarHelper.Show(Resource.SettingsPage_CheckUpdates_Started_Title, Resource.SettingsPage_CheckUpdates_Started_Message);
}

private void UpdateCheckFrequencyComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (_isRefreshing)
return;

if (!_updateCheckFrequencyComboBox.TryGetSelectedItem(out UpdateCheckFrequency frequency))
return;

_updateCheckSettings.Store.UpdateCheckFrequency = frequency;
_updateCheckSettings.SynchronizeStore();
_updateChecker.UpdateMiniumTimeSpanForRefresh();
}

private async void GodModeFnQSwitchableToggle_Click(object sender, RoutedEventArgs e)
{
if (_isRefreshing)
Expand Down
Loading
Loading