Skip to content

Commit

Permalink
Add nicer updater view
Browse files Browse the repository at this point in the history
  • Loading branch information
haefele committed Oct 16, 2023
1 parent 01ce807 commit bd2962c
Show file tree
Hide file tree
Showing 15 changed files with 215 additions and 36 deletions.
1 change: 1 addition & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<PropertyGroup Label="Default-Properties">
<DebugType>embedded</DebugType>
<UseArtifactsOutput>true</UseArtifactsOutput>
<LangVersion>preview</LangVersion>
</PropertyGroup>

<PropertyGroup Label="Code-Style">
Expand Down
1 change: 1 addition & 0 deletions src/ChatPrisma/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ private IHostBuilder CreateHostBuilder(string[] args) => Microsoft.Extensions.Ho
o.GitHubRepository = "ChatPrisma";
o.GitHubReleaseAssetName = "App.zip";
})
.Bind(context.Configuration.GetSection("Updater"))
.ValidateDataAnnotations()
.ValidateOnStart();

Expand Down
25 changes: 11 additions & 14 deletions src/ChatPrisma/HostedServices/UpdaterHostedService.cs
Original file line number Diff line number Diff line change
@@ -1,32 +1,29 @@
using System.Windows;
using ChatPrisma.Options;
using ChatPrisma.Services.Dialogs;
using ChatPrisma.Services.ViewModels;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using Onova;

namespace ChatPrisma.HostedServices;

public class UpdaterHostedService(IUpdateManager updateManager, Application app, IHostEnvironment hostEnvironment) : BackgroundService
public class UpdaterHostedService(IUpdateManager updateManager, IViewModelFactory viewModelFactory, IDialogService dialogService, IOptionsMonitor<UpdaterOptions> updaterOptions) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
if (hostEnvironment.IsProduction() is false)
return;

while (stoppingToken.IsCancellationRequested is false)
{
var result = await updateManager.CheckForUpdatesAsync(stoppingToken);
if (result is { CanUpdate: true, LastVersion: not null })
if (updaterOptions.CurrentValue.CheckForUpdatesInBackground)
{
var updateResult = MessageBox.Show($"Update available {result.LastVersion.ToString(3)}!, Wanna update now?", "Title", MessageBoxButton.YesNo);
if (updateResult == MessageBoxResult.Yes)
var result = await updateManager.CheckForUpdatesAsync(stoppingToken);
if (result is { CanUpdate: true, LastVersion: not null })
{
await updateManager.PrepareUpdateAsync(result.LastVersion, cancellationToken: stoppingToken);
updateManager.LaunchUpdater(result.LastVersion);

app.Shutdown();
var viewModel = viewModelFactory.CreateUpdateViewModel(result);
await dialogService.ShowDialog(viewModel);
}
}

await Task.Delay(TimeSpan.FromMinutes(10), stoppingToken);
await Task.Delay(TimeSpan.FromMinutes(updaterOptions.CurrentValue.MinutesBetweenUpdateChecks), stoppingToken);
}
}
}
3 changes: 3 additions & 0 deletions src/ChatPrisma/Options/UpdaterOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,7 @@ public class UpdaterOptions
public string GitHubRepository { get; set; } = default!;
[Required]
public string GitHubReleaseAssetName { get; set; } = default!;

public bool CheckForUpdatesInBackground { get; set; } = true;
public int MinutesBetweenUpdateChecks { get; set; } = 10;
}
8 changes: 4 additions & 4 deletions src/ChatPrisma/Services/AutoStart/RegistryAutoStartService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@

namespace ChatPrisma.Services.AutoStart;

public class RegistryAutoStartService(IOptions<ApplicationOptions> applicationOptions) : IAutoStartService
public class RegistryAutoStartService(IOptionsMonitor<ApplicationOptions> applicationOptions) : IAutoStartService
{
public async Task<bool> IsInAutoStart()
{
await Task.CompletedTask;

var key = Registry.CurrentUser.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", writable: false);
var result = key?.GetValue(applicationOptions.Value.ApplicationName) is not null;
var result = key?.GetValue(applicationOptions.CurrentValue.ApplicationName) is not null;

// If auto-start is enabled, ensure that we have the correct application path in the registry
// We do that by just enabling auto-start again
Expand All @@ -32,11 +32,11 @@ public async Task SetAutoStart(bool enabled)

if (enabled)
{
key.SetValue(applicationOptions.Value.ApplicationName, $"\"{Environment.ProcessPath}\"");
key.SetValue(applicationOptions.CurrentValue.ApplicationName, $"\"{Environment.ProcessPath}\"");
}
else
{
key.DeleteValue(applicationOptions.Value.ApplicationName, throwOnMissingValue: false);
key.DeleteValue(applicationOptions.CurrentValue.ApplicationName, throwOnMissingValue: false);
}
}
}
10 changes: 5 additions & 5 deletions src/ChatPrisma/Services/ChatBot/OpenAIChatBotService.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
using System.Runtime.CompilerServices;
using System.Runtime.CompilerServices;
using Azure.AI.OpenAI;
using ChatPrisma.Options;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace ChatPrisma.Services.ChatBot;

public class OpenAIChatBotService(IOptions<OpenAIOptions> openAiConfig, ILogger<OpenAIChatBotService> logger) : IChatBotService
public class OpenAIChatBotService(IOptionsMonitor<OpenAIOptions> openAiConfig, ILogger<OpenAIChatBotService> logger) : IChatBotService
{
private readonly OpenAIClient _client = new(openAiConfig.Value.ApiKey ?? throw new PrismaException("OpenAI API Key is missing"));
private readonly OpenAIClient _client = new(openAiConfig.CurrentValue.ApiKey ?? throw new PrismaException("OpenAI API Key is missing"));

public async IAsyncEnumerable<string> GetResponse(List<PrismaChatMessage> messages, [EnumeratorCancellation] CancellationToken token = default)
{
Expand All @@ -18,9 +18,9 @@ public async IAsyncEnumerable<string> GetResponse(List<PrismaChatMessage> messag
chatCompletionsOptions.Messages.Add(this.ConvertChatMessage(message));
}

logger.LogInformation("Calling ChatGPT model {Model}", openAiConfig.Value.Model);
logger.LogInformation("Calling ChatGPT model {Model}", openAiConfig.CurrentValue.Model);

var response = await this._client.GetChatCompletionsStreamingAsync(openAiConfig.Value.Model, chatCompletionsOptions, token);
var response = await this._client.GetChatCompletionsStreamingAsync(openAiConfig.CurrentValue.Model, chatCompletionsOptions, token);

using var completions = response.Value;

Expand Down
4 changes: 2 additions & 2 deletions src/ChatPrisma/Services/Dialogs/DialogService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

namespace ChatPrisma.Services.Dialogs;

public class DialogService(IServiceProvider serviceProvider, IOptions<ApplicationOptions> applicationOptions) : IDialogService
public class DialogService(IServiceProvider serviceProvider, IOptionsMonitor<ApplicationOptions> applicationOptions) : IDialogService
{
public async Task<bool?> ShowDialog(object viewModel)
{
Expand All @@ -29,7 +29,7 @@ public class DialogService(IServiceProvider serviceProvider, IOptions<Applicatio
{
Path = new PropertyPath(Attached.WindowTitleProperty),
Source = view,
FallbackValue = applicationOptions.Value.ApplicationName,
FallbackValue = applicationOptions.CurrentValue.ApplicationName,
});

if (viewModel is ICloseWindow closeWindow)
Expand Down
7 changes: 5 additions & 2 deletions src/ChatPrisma/Services/ViewModels/IViewModelFactory.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using ChatPrisma.Views.Settings;
using ChatPrisma.Views.Settings;
using ChatPrisma.Views.About;
using ChatPrisma.Views.TextEnhancement;
using ChatPrisma.Views.Update;
using Onova.Models;

namespace ChatPrisma.Services.ViewModels;

Expand All @@ -9,4 +11,5 @@ public interface IViewModelFactory
AboutViewModel CreateAboutViewModel();
SettingsViewModel CreateSettingsViewModel();
TextEnhancementViewModel CreateTextEnhancementViewModel(string text);
}
UpdateViewModel CreateUpdateViewModel(CheckForUpdatesResult? updatesResult = null);
}
10 changes: 8 additions & 2 deletions src/ChatPrisma/Services/ViewModels/ViewModelFactory.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using ChatPrisma.Views.About;
using ChatPrisma.Views.About;
using ChatPrisma.Views.Settings;
using ChatPrisma.Views.TextEnhancement;
using ChatPrisma.Views.Update;
using Microsoft.Extensions.DependencyInjection;
using Onova.Models;

namespace ChatPrisma.Services.ViewModels;

Expand All @@ -12,4 +14,8 @@ public class ViewModelFactory(IServiceProvider serviceProvider) : IViewModelFact
public SettingsViewModel CreateSettingsViewModel() => ActivatorUtilities.CreateInstance<SettingsViewModel>(serviceProvider);

public TextEnhancementViewModel CreateTextEnhancementViewModel(string text) => ActivatorUtilities.CreateInstance<TextEnhancementViewModel>(serviceProvider, text);
}

public UpdateViewModel CreateUpdateViewModel(CheckForUpdatesResult? updatesResult = null) => updatesResult is null
? ActivatorUtilities.CreateInstance<UpdateViewModel>(serviceProvider)
: ActivatorUtilities.CreateInstance<UpdateViewModel>(serviceProvider, updatesResult);
}
13 changes: 7 additions & 6 deletions src/ChatPrisma/Views/About/AboutViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,22 @@

namespace ChatPrisma.Views.About;

public partial class AboutViewModel(IOptions<ApplicationOptions> options) : ObservableObject
public partial class AboutViewModel(IOptionsMonitor<ApplicationOptions> options) : ObservableObject
{
[ObservableProperty]
private string _applicationName = options.Value.ApplicationName;
private string _applicationName = options.CurrentValue.ApplicationName;

[ObservableProperty]
private string _applicationVersion = options.Value.ApplicationVersion;
private string _applicationVersion = options.CurrentValue.ApplicationVersion;

[ObservableProperty]
private string _commitId = options.Value.CommitId;
private string _commitId = options.CurrentValue.CommitId;

[ObservableProperty]
private string _contactName = options.Value.ContactName;
private string _contactName = options.CurrentValue.ContactName;

[ObservableProperty]
private string _contactEmailAddress = options.Value.ContactEmailAddress;
private string _contactEmailAddress = options.CurrentValue.ContactEmailAddress;

[ObservableProperty]
private ObservableCollection<ThirdPartyLibrary> _thirdPartyLibraries = new()
Expand All @@ -36,6 +36,7 @@ public partial class AboutViewModel(IOptions<ApplicationOptions> options) : Obse
new ThirdPartyLibrary("DevExpress.Mvvm", new("https://github.com/DevExpress/DevExpress.Mvvm.Free"), "MIT", new Uri("https://github.com/DevExpress/DevExpress.Mvvm.Free/blob/main/LICENSE")),
new ThirdPartyLibrary("Nerdbank.GitVersioning", new("https://github.com/dotnet/Nerdbank.GitVersioning"), "MIT", new Uri("https://github.com/dotnet/Nerdbank.GitVersioning/blob/main/LICENSE")),
new ThirdPartyLibrary("SingleInstanceCore", new("https://github.com/soheilkd/SingleInstanceCore"), "MIT", new Uri("https://github.com/soheilkd/SingleInstanceCore/blob/master/LICENSE")),
new ThirdPartyLibrary("Onova", new("https://github.com/Tyrrrz/Onova"), "MIT", new Uri("https://github.com/Tyrrrz/Onova/blob/master/License.txt")),
};
}

Expand Down
42 changes: 42 additions & 0 deletions src/ChatPrisma/Views/Update/UpdateView.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<UserControl x:Class="ChatPrisma.Views.Update.UpdateView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ChatPrisma.Views.Update"
xmlns:dxmvvm="clr-namespace:DevExpress.Mvvm.UI;assembly=DevExpress.Mvvm.UI"

xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"

d:DesignWidth="800"
d:DesignHeight="450"
d:DataContext="{d:DesignInstance local:UpdateViewModel}">
<UserControl.Resources>
<dxmvvm:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
<dxmvvm:BooleanToVisibilityConverter x:Key="InverseBooleanToVisibilityConverter" Inverse="True" />
</UserControl.Resources>
<Grid>
<GroupBox Header="Update verfügbar!"
Visibility="{Binding UpdateAvailable, Converter={StaticResource BooleanToVisibilityConverter}}">
<StackPanel>
<TextBlock>
<Run Text="Ein Update für "/><Run Text="{Binding ApplicationName}" /><Run Text=" steht zur verfügung!" /><LineBreak />
<LineBreak />
<Bold><Run Text="Ihre Version: " /></Bold><Run Text="{Binding CurrentVersion}" /><LineBreak />
<Bold><Run Text="Neue Version: " /></Bold><Run Text="{Binding UpdateVersion}" /><LineBreak />
</TextBlock>
<Button Content="Herunterladen und installieren"
Command="{Binding DownloadAndInstallUpdateCommand}" />
</StackPanel>
</GroupBox>

<GroupBox Header="Kein Update verfügbar!"
Visibility="{Binding UpdateAvailable, Converter={StaticResource InverseBooleanToVisibilityConverter}}">
<TextBlock>
<Run Text="Sie verwenden im Moment die aktuellste Version von "/><Run Text="{Binding ApplicationName}" /><Run Text="!" /><LineBreak />
<LineBreak />
<Bold><Run Text="Ihre Version: " /></Bold><Run Text="{Binding CurrentVersion}" />
</TextBlock>
</GroupBox>
</Grid>
</UserControl>
28 changes: 28 additions & 0 deletions src/ChatPrisma/Views/Update/UpdateView.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace ChatPrisma.Views.Update
{
/// <summary>
/// Interaction logic for UpdateView.xaml
/// </summary>
public partial class UpdateView : UserControl
{
public UpdateView()
{
InitializeComponent();
}
}
}
78 changes: 78 additions & 0 deletions src/ChatPrisma/Views/Update/UpdateViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using ChatPrisma.Options;
using ChatPrisma.Services.Dialogs;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.Extensions.Options;
using Onova;
using Onova.Models;

namespace ChatPrisma.Views.Update
{
public partial class UpdateViewModel : ObservableObject, IInitialize
{
private readonly Application _app;
private readonly IUpdateManager _updateManager;
private CheckForUpdatesResult? _updatesResult;

public UpdateViewModel(IOptionsMonitor<ApplicationOptions> applicationOptions, Application app, IUpdateManager updateManager)
{
this._app = app;
this._updateManager = updateManager;
this.CurrentVersion = applicationOptions.CurrentValue.ApplicationVersion;
this.ApplicationName = applicationOptions.CurrentValue.ApplicationName;
}
public UpdateViewModel(CheckForUpdatesResult? updatesResult, IOptionsMonitor<ApplicationOptions> applicationOptions, Application app, IUpdateManager updateManager)
: this(applicationOptions, app, updateManager)
{
this.UseUpdateResult(updatesResult);
}

public async Task InitializeAsync()
{
if (this._updatesResult is null)
{
var updateResult = await this._updateManager.CheckForUpdatesAsync();
this.UseUpdateResult(updateResult);
}
}

[ObservableProperty]
private string _currentVersion;

[ObservableProperty]
private string _applicationName;

[ObservableProperty]
private bool _updateAvailable = false;

[ObservableProperty]
private string? _updateVersion;


[RelayCommand]
private async Task DownloadAndInstallUpdate(CancellationToken cancellationToken)
{
if (this._updatesResult?.LastVersion is null)
return;

await this._updateManager.PrepareUpdateAsync(this._updatesResult.LastVersion, cancellationToken: cancellationToken);
cancellationToken.ThrowIfCancellationRequested();

this._updateManager.LaunchUpdater(this._updatesResult.LastVersion!);
this._app.Shutdown();
}

private void UseUpdateResult(CheckForUpdatesResult? updatesResult)
{
this._updatesResult = updatesResult;
this.UpdateAvailable = updatesResult?.CanUpdate ?? false;
this.UpdateVersion = updatesResult?.LastVersion?.ToString(3);
}
}
}
Loading

0 comments on commit bd2962c

Please sign in to comment.