mirror of
https://github.com/RedWizardsLab/EchoHub.git
synced 2026-09-07 15:46:02 +02:00
Merge pull request #12 from Stone-Red-Code/dev
feat: installer & auto update system
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 6.6 KiB |
Binary file not shown.
@@ -0,0 +1,52 @@
|
|||||||
|
; Script generated by the Inno Setup Script Wizard.
|
||||||
|
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
|
||||||
|
|
||||||
|
#expr Exec('cmd.exe', '/C dotnet build -o "' + SourcePath + '\publish" -c Release ' + SourcePath + '..\src\EchoHub.Client\')
|
||||||
|
|
||||||
|
#define MyAppName "EchoHub"
|
||||||
|
#define MyAppVersion GetStringFileInfo("/publish/EchoHub.Client.exe","ProductVersion")
|
||||||
|
#define MyAppPublisher "Hue"
|
||||||
|
#define MyAppExeName "EchoHub.Client.exe"
|
||||||
|
|
||||||
|
[Setup]
|
||||||
|
; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
|
||||||
|
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
|
||||||
|
AppId=c95b1292-3022-4c62-a131-4d46ace370f5
|
||||||
|
AppName={#MyAppName}
|
||||||
|
AppVersion={#MyAppVersion}
|
||||||
|
;AppVerName={#MyAppName} {#MyAppVersion}
|
||||||
|
AppPublisher={#MyAppPublisher}
|
||||||
|
DefaultDirName={autopf}\{#MyAppName}
|
||||||
|
DisableProgramGroupPage=yes
|
||||||
|
SetupIconFile=../assets/hue_icon.ico
|
||||||
|
; The [Icons] "quicklaunchicon" entry uses {userappdata} but its [Tasks] entry has a proper IsAdminInstallMode Check.
|
||||||
|
UsedUserAreasWarning=no
|
||||||
|
; Remove the following line to run in administrative install mode (install for all users.)
|
||||||
|
PrivilegesRequired=lowest
|
||||||
|
PrivilegesRequiredOverridesAllowed=dialog
|
||||||
|
OutputBaseFilename={#MyAppName}-Installer
|
||||||
|
OutputDir=.
|
||||||
|
Compression=lzma
|
||||||
|
SolidCompression=yes
|
||||||
|
WizardStyle=modern
|
||||||
|
|
||||||
|
[Languages]
|
||||||
|
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||||
|
Name: "german"; MessagesFile: "compiler:Languages\German.isl"
|
||||||
|
|
||||||
|
[Tasks]
|
||||||
|
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
||||||
|
Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked; OnlyBelowVersion: 6.1; Check: not IsAdminInstallMode
|
||||||
|
|
||||||
|
[Files]
|
||||||
|
Source: "publish\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs
|
||||||
|
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
||||||
|
|
||||||
|
[Icons]
|
||||||
|
Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
|
||||||
|
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
|
||||||
|
Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: quicklaunchicon
|
||||||
|
|
||||||
|
[Run]
|
||||||
|
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
|
||||||
|
|
||||||
@@ -23,6 +23,7 @@ public sealed class AppOrchestrator : IDisposable
|
|||||||
private readonly CommandHandler _commandHandler;
|
private readonly CommandHandler _commandHandler;
|
||||||
private readonly NotificationSoundService _notificationSound;
|
private readonly NotificationSoundService _notificationSound;
|
||||||
private readonly AudioPlaybackService _audioPlayback = new();
|
private readonly AudioPlaybackService _audioPlayback = new();
|
||||||
|
private readonly UpdateChecker _updateService;
|
||||||
|
|
||||||
private EchoHubConnection? _connection;
|
private EchoHubConnection? _connection;
|
||||||
private ApiClient? _apiClient;
|
private ApiClient? _apiClient;
|
||||||
@@ -45,10 +46,13 @@ public sealed class AppOrchestrator : IDisposable
|
|||||||
_mainWindow = new MainWindow(app);
|
_mainWindow = new MainWindow(app);
|
||||||
_commandHandler = new CommandHandler();
|
_commandHandler = new CommandHandler();
|
||||||
_notificationSound = new NotificationSoundService(config.Notifications);
|
_notificationSound = new NotificationSoundService(config.Notifications);
|
||||||
|
_updateService = new UpdateChecker(app);
|
||||||
|
|
||||||
WireMainWindowEvents();
|
WireMainWindowEvents();
|
||||||
WireCommandHandlerEvents();
|
WireCommandHandlerEvents();
|
||||||
|
|
||||||
|
_updateService.Start();
|
||||||
|
|
||||||
_mainWindow.UpdateStatusBar("Disconnected");
|
_mainWindow.UpdateStatusBar("Disconnected");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,6 +60,7 @@ public sealed class AppOrchestrator : IDisposable
|
|||||||
{
|
{
|
||||||
_connection?.DisposeAsync().AsTask().GetAwaiter().GetResult();
|
_connection?.DisposeAsync().AsTask().GetAwaiter().GetResult();
|
||||||
_apiClient?.Dispose();
|
_apiClient?.Dispose();
|
||||||
|
_updateService.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Convenience Helpers ────────────────────────────────────────────────
|
// ── Convenience Helpers ────────────────────────────────────────────────
|
||||||
@@ -456,18 +461,6 @@ public sealed class AppOrchestrator : IDisposable
|
|||||||
|
|
||||||
FetchAndUpdateOnlineUsers();
|
FetchAndUpdateOnlineUsers();
|
||||||
SaveServerToConfig(result);
|
SaveServerToConfig(result);
|
||||||
|
|
||||||
// Check for newer version in the background
|
|
||||||
_ = Task.Run(async () =>
|
|
||||||
{
|
|
||||||
var newVersion = await UpdateChecker.CheckForUpdateAsync();
|
|
||||||
if (newVersion is not null)
|
|
||||||
{
|
|
||||||
InvokeUI(() => _mainWindow.AddSystemMessage(
|
|
||||||
HubConstants.DefaultChannel,
|
|
||||||
$"A new version of EchoHub is available: v{newVersion} (current: v{MainWindow.AppVersion}). Visit https://github.com/HueByte/EchoHub/releases"));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, "Connection failed", "Connect");
|
}, "Connection failed", "Connect");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="AlwaysUpToDate" Version="2.0.1" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="10.0.3" />
|
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="10.0.3" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.3" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.3" />
|
||||||
<PackageReference Include="NetCoreAudio" Version="2.0.1" />
|
<PackageReference Include="NetCoreAudio" Version="2.0.1" />
|
||||||
@@ -19,6 +20,7 @@
|
|||||||
<Content Include="appsettings.json" Condition="Exists('appsettings.json')">
|
<Content Include="appsettings.json" Condition="Exists('appsettings.json')">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
|
<Content Include="hue_icon.ico" />
|
||||||
<Content Include="Assets\**">
|
<Content Include="Assets\**">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
@@ -32,6 +34,8 @@
|
|||||||
<TargetFramework>net10.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
|
<PackageIcon></PackageIcon>
|
||||||
|
<ApplicationIcon>hue_icon.ico</ApplicationIcon>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,47 +1,106 @@
|
|||||||
using System.Net.Http.Json;
|
using AlwaysUpToDate;
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
|
using EchoHub.Client.UI;
|
||||||
|
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
|
using Terminal.Gui.App;
|
||||||
|
|
||||||
namespace EchoHub.Client.Services;
|
namespace EchoHub.Client.Services;
|
||||||
|
|
||||||
public static class UpdateChecker
|
public sealed class UpdateChecker : IDisposable
|
||||||
{
|
{
|
||||||
private static readonly Uri ReleaseUrl =
|
private const string ManifestUrl = "https://echohub.voidcube.cloud/api/app/version";
|
||||||
new("https://api.github.com/repos/HueByte/EchoHub/releases/latest");
|
|
||||||
|
|
||||||
/// <summary>
|
private readonly Updater _updater;
|
||||||
/// Checks GitHub for a newer release. Returns the new version string if one exists, or null.
|
private readonly IApplication _app;
|
||||||
/// Never throws — all errors are silently swallowed.
|
private UpdateProgressDialog? _progressDialog;
|
||||||
/// </summary>
|
|
||||||
public static async Task<string?> CheckForUpdateAsync()
|
public static string CurrentVersion => typeof(UpdateChecker).Assembly.GetName().Version?.ToString(3) ?? "0.0.0";
|
||||||
|
|
||||||
|
public UpdateChecker(IApplication app)
|
||||||
{
|
{
|
||||||
try
|
_app = app;
|
||||||
{
|
_updater = new Updater(TimeSpan.FromHours(1), ManifestUrl, false);
|
||||||
using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(5) };
|
|
||||||
http.DefaultRequestHeaders.UserAgent.ParseAdd("EchoHub-Client");
|
|
||||||
|
|
||||||
var release = await http.GetFromJsonAsync<GitHubRelease>(ReleaseUrl);
|
_updater.UpdateAvailable += OnUpdateAvailable;
|
||||||
if (release?.TagName is null)
|
_updater.ProgressChanged += OnProgressChanged;
|
||||||
return null;
|
_updater.UpdateStarted += OnUpdateStarted;
|
||||||
|
_updater.NoUpdateAvailable += OnNoUpdateAvailable;
|
||||||
var tag = release.TagName.TrimStart('v', 'V');
|
_updater.OnException += OnException;
|
||||||
if (!Version.TryParse(tag, out var latest))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
var currentStr = typeof(UpdateChecker).Assembly.GetName().Version?.ToString(3);
|
|
||||||
if (currentStr is null || !Version.TryParse(currentStr, out var current))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return latest > current ? tag : null;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class GitHubRelease
|
public void Start()
|
||||||
{
|
{
|
||||||
[JsonPropertyName("tag_name")]
|
#if RELEASE
|
||||||
public string? TagName { get; set; }
|
_updater.Start();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnUpdateAvailable(string version, string changelogUrl)
|
||||||
|
{
|
||||||
|
Log.Information("Update available: v{Version}", version);
|
||||||
|
|
||||||
|
var confirmed = false;
|
||||||
|
_app.Invoke(() =>
|
||||||
|
{
|
||||||
|
confirmed = UpdateConfirmDialog.Show(_app, CurrentVersion, version);
|
||||||
|
|
||||||
|
|
||||||
|
if (confirmed)
|
||||||
|
{
|
||||||
|
_progressDialog = new UpdateProgressDialog(_app, version);
|
||||||
|
|
||||||
|
// Start the update; progress is reported via OnProgressChanged
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await _updater.UpdateAsync();
|
||||||
|
});
|
||||||
|
|
||||||
|
_progressDialog?.Show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnProgressChanged(UpdateStep step, long itemsProcessed, long? totalItems, double? progressPercentage)
|
||||||
|
{
|
||||||
|
var fraction = progressPercentage.HasValue ? (float)(progressPercentage.Value / 100.0) : 0f;
|
||||||
|
var statusText = $"{step}: {itemsProcessed}/{totalItems ?? 0} ({progressPercentage ?? 0:F0}%)";
|
||||||
|
|
||||||
|
if (!progressPercentage.HasValue)
|
||||||
|
{
|
||||||
|
statusText = $"{step}...";
|
||||||
|
}
|
||||||
|
|
||||||
|
_progressDialog?.UpdateProgress(fraction, statusText);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnUpdateStarted(string version)
|
||||||
|
{
|
||||||
|
Log.Information("Update started: v{Version}", version);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnNoUpdateAvailable()
|
||||||
|
{
|
||||||
|
Log.Debug("No update available");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnException(Exception exception)
|
||||||
|
{
|
||||||
|
Log.Error(exception, "Update check failed");
|
||||||
|
_app.Invoke(() =>
|
||||||
|
{
|
||||||
|
_progressDialog?.Close();
|
||||||
|
_progressDialog = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_updater.UpdateAvailable -= OnUpdateAvailable;
|
||||||
|
_updater.ProgressChanged -= OnProgressChanged;
|
||||||
|
_updater.UpdateStarted -= OnUpdateStarted;
|
||||||
|
_updater.NoUpdateAvailable -= OnNoUpdateAvailable;
|
||||||
|
_updater.OnException -= OnException;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
using Terminal.Gui.App;
|
||||||
|
using Terminal.Gui.Views;
|
||||||
|
using Terminal.Gui.ViewBase;
|
||||||
|
|
||||||
|
namespace EchoHub.Client.UI;
|
||||||
|
|
||||||
|
public sealed class UpdateConfirmDialog
|
||||||
|
{
|
||||||
|
public static bool Show(IApplication app, string currentVersion, string newVersion)
|
||||||
|
{
|
||||||
|
var confirmed = false;
|
||||||
|
|
||||||
|
var dialog = new Dialog { Title = "Update Available", Width = 50, Height = 10 };
|
||||||
|
|
||||||
|
var messageLabel = new Label
|
||||||
|
{
|
||||||
|
Text = $"A new version of EchoHub is available.\n\n Current: {currentVersion}\n Latest: {newVersion}",
|
||||||
|
X = 1,
|
||||||
|
Y = 1,
|
||||||
|
Width = Dim.Fill(2),
|
||||||
|
Height = 4
|
||||||
|
};
|
||||||
|
|
||||||
|
var updateButton = new Button
|
||||||
|
{
|
||||||
|
Text = "Update",
|
||||||
|
IsDefault = true,
|
||||||
|
X = Pos.Center() - 10,
|
||||||
|
Y = 6
|
||||||
|
};
|
||||||
|
|
||||||
|
var cancelButton = new Button
|
||||||
|
{
|
||||||
|
Text = "Cancel",
|
||||||
|
X = Pos.Center() + 5,
|
||||||
|
Y = 6
|
||||||
|
};
|
||||||
|
|
||||||
|
updateButton.Accepting += (s, e) =>
|
||||||
|
{
|
||||||
|
confirmed = true;
|
||||||
|
e.Handled = true;
|
||||||
|
app.RequestStop();
|
||||||
|
};
|
||||||
|
|
||||||
|
cancelButton.Accepting += (s, e) =>
|
||||||
|
{
|
||||||
|
confirmed = false;
|
||||||
|
e.Handled = true;
|
||||||
|
app.RequestStop();
|
||||||
|
};
|
||||||
|
|
||||||
|
dialog.Add(messageLabel, updateButton, cancelButton);
|
||||||
|
app.Run(dialog);
|
||||||
|
|
||||||
|
return confirmed;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
using Terminal.Gui.App;
|
||||||
|
using Terminal.Gui.Views;
|
||||||
|
using Terminal.Gui.ViewBase;
|
||||||
|
|
||||||
|
namespace EchoHub.Client.UI;
|
||||||
|
|
||||||
|
public sealed class UpdateProgressDialog
|
||||||
|
{
|
||||||
|
private readonly Dialog _dialog;
|
||||||
|
private readonly ProgressBar _progressBar;
|
||||||
|
private readonly Label _infoLabel;
|
||||||
|
private readonly IApplication _app;
|
||||||
|
|
||||||
|
public UpdateProgressDialog(IApplication app, string newVersion)
|
||||||
|
{
|
||||||
|
_app = app;
|
||||||
|
|
||||||
|
_dialog = new Dialog { Title = $"Updating to {newVersion}", Width = 50, Height = 10 };
|
||||||
|
|
||||||
|
_infoLabel = new Label
|
||||||
|
{
|
||||||
|
Text = "Preparing update...",
|
||||||
|
X = 1,
|
||||||
|
Y = 1,
|
||||||
|
Width = Dim.Fill(2)
|
||||||
|
};
|
||||||
|
|
||||||
|
_progressBar = new ProgressBar
|
||||||
|
{
|
||||||
|
X = 1,
|
||||||
|
Y = 3,
|
||||||
|
Width = Dim.Fill(2),
|
||||||
|
Fraction = 0f
|
||||||
|
};
|
||||||
|
|
||||||
|
var cancelButton = new Button
|
||||||
|
{
|
||||||
|
Text = "Cancel",
|
||||||
|
X = Pos.Center(),
|
||||||
|
Y = 6
|
||||||
|
};
|
||||||
|
|
||||||
|
_dialog.Add(_infoLabel, _progressBar);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateProgress(float fraction, string statusText)
|
||||||
|
{
|
||||||
|
_progressBar.Fraction = fraction;
|
||||||
|
_infoLabel.Text = statusText;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Show()
|
||||||
|
{
|
||||||
|
_app.Run(_dialog);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Close()
|
||||||
|
{
|
||||||
|
_app.RequestStop();
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
@@ -1,5 +1,9 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="hue_icon.ico" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\EchoHub.Core\EchoHub.Core.csproj" />
|
<ProjectReference Include="..\EchoHub.Core\EchoHub.Core.csproj" />
|
||||||
<ProjectReference Include="..\EchoHub.Server.Irc\EchoHub.Server.Irc.csproj" />
|
<ProjectReference Include="..\EchoHub.Server.Irc\EchoHub.Server.Irc.csproj" />
|
||||||
@@ -22,6 +26,8 @@
|
|||||||
<TargetFramework>net10.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<PackageIcon></PackageIcon>
|
||||||
|
<ApplicationIcon>hue_icon.ico</ApplicationIcon>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
Reference in New Issue
Block a user