diff --git a/CMakeLists.txt b/CMakeLists.txt index d496b62..8017c41 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.24) -project(KSwitcher VERSION 0.6.10 LANGUAGES CXX RC) +project(KSwitcher VERSION 0.6.11 LANGUAGES CXX RC) if(NOT WIN32) message(FATAL_ERROR "KSwitcher uses native Windows APIs") endif() diff --git a/README.md b/README.md index c9945a4..ffb12bf 100644 --- a/README.md +++ b/README.md @@ -184,7 +184,15 @@ Click the notification or **Review update** to check the latest release and conf Failed notification submissions are logged and retried after five minutes instead of being marked as delivered. Check results and successful submissions are recorded in the log when logging is enabled. Network failures stay in diagnostics; the next scheduled check retries. You can also check manually at any time. -Confirm the offered version to download and install it. The updater uses the existing +Confirm the offered version to download and install it. A progress window shows +download percentage and received/total size, package verification, +installation and restart. After the new process confirms startup, that window shows the installed +version and a Done button; it does not send a separate completion notification. If installation +fails after KSwitcher closes, the same window shows recovery and the error. Background checks +remain silent unless a new version is available. This progress UI is supplied by the updater in +the currently installed build, so it becomes available for subsequent updates after installing it. + +The updater uses the existing `KSwitcher-win-x64.zip` and `SHA256SUMS.txt` assets, checks the checksum, package contents, x64 executable identity and version, then closes and restarts KSwitcher. Downloads and staging happen before the application exits. If replacement or startup fails, it restores the previous files; diff --git a/Release/KSwitcher-win-x64.zip b/Release/KSwitcher-win-x64.zip index 5adb31a..0f343e3 100644 Binary files a/Release/KSwitcher-win-x64.zip and b/Release/KSwitcher-win-x64.zip differ diff --git a/Release/README.md b/Release/README.md index e9433bb..f4c2ec1 100644 --- a/Release/README.md +++ b/Release/README.md @@ -1,24 +1,26 @@ # Latest Windows release -## v0.6.10 — 2026-10-29 +## v0.6.11 — 2026-10-29 ## Changes -- Preserve typing during automatic layout correction: queue key presses and releases until replacement, clipboard restoration and layout switching finish. Preserve modifiers and Backspace; collapse temporary selection before replay when a correction is rejected. -- Add My dictionary fields for English, Ukrainian and Russian in Auto-correction options. Enter correct words separated by commas or new lines; save them with settings without editing dictionary files, sorting entries or assigning ranks. -- Accept unsorted dictionary files and duplicate entries, retaining the lowest rank for duplicates. Include Ukrainian `реліз` in the bundled vocabulary so `релыз` can be corrected when Russian and Ukrainian are enabled. +- Show an update progress window for manual checks and installation. Downloads display percentage and received/total size; verification, unpacking, installation and restart have distinct status messages. +- Keep the progress window running independently while KSwitcher exits and restarts. After the new application confirms startup, show the installed version and a Done button in the same window, without a separate completion notification. +- Show recovery and installation errors in the same window after the application exits. Background update checks remain quiet. + +The progress UI is provided by the currently installed updater. Updating from v0.6.10 to this release still uses the older interface; subsequent updates use the new window. ## Validation -Native regression checks cover personal words, settings persistence, language validation, dictionary ordering and duplicate ranks. Input/UI checks cover personal-word entry and normalization. Native editor integration covers typing with Shift and Backspace during successful and rejected corrections. Release checks also cover screenshot UI, isolated notification registration and updater validation. +214 native regression checks, 466 input/UI checks, screenshot editor UI checks, isolated notification registration, 45 updater regression checks, and progress-window checks for download, installation, recovery and completion states. Network tests verify real download progress and release download/checksum validation. Startup acknowledgement tests prevent reporting success for ordinary startup or a missing updater handshake. **Upgrade:** choose Update in the tray menu. Legacy v0.3.0–v0.4.1 clients first receive the v0.4.2 compatibility bridge, then the current full release. v0.2.3 and earlier require manual installation. -For manual installation, exit KSwitcher and extract the entire ZIP into its installation folder. Keep personal `data/` and customized dictionaries. Existing customized dictionary files may be preserved by the updater; use My dictionary to add missing words without replacing those files. +For manual installation, exit KSwitcher and extract the entire ZIP into its installation folder. Keep personal `data/` and customized dictionaries. Download **KSwitcher-win-x64.zip** with **SHA256SUMS.txt**. The package contains all three executables, a release manifest, offline dictionaries, documentation and license notices. It contains no personal settings, clipboard history, Tesseract installation or language models. Build and package: ```powershell -.\build.ps1 -Stage -Test -Integration +.\build.ps1 -Stage -Test .\package.ps1 -PublishRelease ``` diff --git a/Release/SHA256SUMS.txt b/Release/SHA256SUMS.txt index ade2d1a..a7c0f34 100644 --- a/Release/SHA256SUMS.txt +++ b/Release/SHA256SUMS.txt @@ -1 +1 @@ -36966eb0b2b0af47c245aec6337a16703e0fbdca4d3eda7a8f25fa14a20f347e KSwitcher-win-x64.zip +d12994e3038e3a10f76b6eb4dba4aca0fac4c4d5f3805ba16f132f890a941bcf KSwitcher-win-x64.zip diff --git a/build.ps1 b/build.ps1 index 6bee55c..ed1ff23 100644 --- a/build.ps1 +++ b/build.ps1 @@ -61,6 +61,8 @@ try { & "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -ExecutionPolicy Bypass -File tests/update.ps1 -Executable $executablePath if ($LASTEXITCODE) { throw 'Updater tests failed' } + & "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -ExecutionPolicy Bypass -File tests/update_progress.ps1 + if ($LASTEXITCODE) { throw 'Updater progress UI tests failed' } } if ($Integration) { & $compiler @common tests/integration.cpp build/core.o build/platform.o build/autostart.o build/autocorrect.o build/storage.o build/ocr.o build/richtext.o build/update.o build/screenshot.o build/screenshot_launcher.o build/screenshot_activation.o build/screenshot_notifications.o build/app.o build/configuration.o build/ui.o build/screenshot-res.o @libs -static -s -o build/KSwitcherIntegration.exe diff --git a/resources/app.rc b/resources/app.rc index 296a259..e2191de 100644 --- a/resources/app.rc +++ b/resources/app.rc @@ -9,8 +9,8 @@ IDC_SCREENSHOT_PENCIL CURSOR "icons/lucide/pencil.cur" 1 RT_MANIFEST "app.manifest" 201 RCDATA "update.ps1" 1 VERSIONINFO - FILEVERSION 0,6,10,0 - PRODUCTVERSION 0,6,10,0 + FILEVERSION 0,6,11,0 + PRODUCTVERSION 0,6,11,0 FILEOS VOS_NT_WINDOWS32 FILETYPE VFT_APP BEGIN @@ -20,7 +20,7 @@ BEGIN BEGIN VALUE "FileDescription", "KSwitcher — native keyboard and clipboard utility" VALUE "ProductName", "KSwitcher" - VALUE "FileVersion", "0.6.10" + VALUE "FileVersion", "0.6.11" END END BLOCK "VarFileInfo" diff --git a/resources/update.ps1 b/resources/update.ps1 index cf327fc..cda2c2e 100644 --- a/resources/update.ps1 +++ b/resources/update.ps1 @@ -3,10 +3,192 @@ [string]$CurrentVersion, [string]$ExpectedVersion, [string]$InstallDirectory, [string]$WorkDirectory, [int]$ParentProcessId, [string]$ReadyEvent, + [switch]$ShowProgress, [switch]$Library ) $ErrorActionPreference = 'Stop' Set-StrictMode -Version Latest +$script:progressWindow = $null + +function Start-UpdateProgress { + Add-Type -AssemblyName System.Windows.Forms + Add-Type -AssemblyName System.Drawing + if (!('KSwitcherUpdateProgress' -as [type])) { + Add-Type -ReferencedAssemblies System.Windows.Forms,System.Drawing -TypeDefinition @' +using System; +using System.Drawing; +using System.Threading; +using System.Windows.Forms; +using System.Drawing.Drawing2D; +using System.Runtime.InteropServices; +public sealed class UpdateMeter : Control { + public int Percent = -1; + private int position; + private readonly System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer { Interval = 35 }; + public UpdateMeter() { + DoubleBuffered = true; + timer.Tick += delegate { if (Percent < 0) { position = (position + 5) % Math.Max(1,Width); Invalidate(); } }; + timer.Start(); + } + protected override void OnPaint(PaintEventArgs e) { + using(var track = new SolidBrush(Color.FromArgb(232,234,241))) e.Graphics.FillRectangle(track,0,0,Width,Height); + using(var fill = new SolidBrush(Color.FromArgb(79,70,229))) { + if(Percent < 0) e.Graphics.FillRectangle(fill,position-Width/4,0,Width/4,Height); + else e.Graphics.FillRectangle(fill,0,0,Width*Percent/100,Height); + } + } + protected override void Dispose(bool disposing) { if(disposing) timer.Dispose(); base.Dispose(disposing); } +} +public sealed class UpdateSteps : Control { + public int Current = -1; + public bool Complete; + public UpdateSteps() { DoubleBuffered = true; } + protected override void OnPaint(PaintEventArgs e) { + e.Graphics.SmoothingMode = SmoothingMode.AntiAlias; + string[] names = {"Download","Verify","Install","Restart"}; + for(int i=0;i<4;i++) { + int x = i*(Width-80)/3; + Color color = Complete || i<=Current ? Color.FromArgb(79,70,229) : Color.FromArgb(135,141,158); + using(var pen = new Pen(Color.FromArgb(231,233,240))) + if(i<3) e.Graphics.DrawLine(pen,x+22,8,x+(Width-80)/3-10,8); + using(var brush = new SolidBrush(color)) { + if(Complete || i=0) detail.Text = text.Substring(separator+4)+" downloaded"; + if(title.StartsWith("Downloading")) steps.Current=0; + else if(title.StartsWith("Verifying") || title.StartsWith("Preparing")) steps.Current=1; + else if(title.StartsWith("Installing") || title.StartsWith("Closing")) steps.Current=2; + else if(title.StartsWith("Restarting")) steps.Current=3; + progress.Invalidate(); steps.Invalidate(); + }); + } + public void SavePreview(string path) { + form.Invoke((Action)delegate { + using(var bitmap = new Bitmap(form.Width,form.Height)) { + form.DrawToBitmap(bitmap,new Rectangle(0,0,bitmap.Width,bitmap.Height)); + bitmap.Save(path,System.Drawing.Imaging.ImageFormat.Png); + } + }); + } + public string Status { get { return (string)form.Invoke((Func)delegate { return stage.Text; }); } } + public int Percent { get { return (int)form.Invoke((Func)delegate { return progress.Percent; }); } } + public void Finish(string title, string text, bool success) { + form.Invoke((Action)delegate { + closing = true; + stage.Text = title; detail.Text = text; + progress.Percent = success ? 100 : 0; percentage.Text = success ? "100%" : ""; + steps.Complete = success; + if(!success) { + detail.Visible = false; steps.Visible = false; + var result = new TextBox { Text = text, Location = new Point(28,148), Size = new Size(484,126), + Multiline = true, ReadOnly = true, BorderStyle = BorderStyle.None, + BackColor = Color.White, ScrollBars = ScrollBars.Vertical }; + form.Controls.Add(result); result.BringToFront(); progress.Visible = false; + } + footer.Text = success ? "Your settings and history are preserved." : "Review the details above."; + close.Enabled = true; done.Visible = true; + form.AcceptButton = done; form.CancelButton = done; + progress.Invalidate(); steps.Invalidate(); + }); + } + public void WaitForClose() { thread.Join(); } + public void Dispose() { + if (form != null && !form.IsDisposed) + form.BeginInvoke((Action)delegate { closing = true; form.Close(); }); + thread.Join(3000); + } +} +'@ + } + $script:progressWindow = New-Object KSwitcherUpdateProgress +} +function Show-UpdateProgress([string]$Stage,[string]$Detail,[int]$Percent = -1) { + if ($script:progressWindow) { + try { $script:progressWindow.Report($Stage,$Detail,$Percent) } + catch { Write-Warning 'Could not refresh update progress.' } + } +} +function Complete-UpdateProgress([string]$Title,[string]$Detail,[bool]$Success) { + if ($script:progressWindow) { + try { $script:progressWindow.Finish($Title,$Detail,$Success); return $true } + catch { Write-Warning 'Could not display the update result.' } + } + return $false +} function Get-StableVersion([string]$Tag) { if ($Tag -notmatch '^(?:v|release-v)?(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$') { return $null } @@ -20,7 +202,7 @@ function Assert-ReleaseUrl([string]$Url) { throw 'Release asset URL is outside the KSwitcher repository.' } } -function Receive-File([string]$Url,[string]$Path,[long]$Limit) { +function Receive-File([string]$Url,[string]$Path,[long]$Limit,[string]$ProgressLabel = '') { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 $request = [Net.HttpWebRequest]::Create($Url) $request.AllowAutoRedirect = $false @@ -36,11 +218,20 @@ function Receive-File([string]$Url,[string]$Path,[long]$Limit) { $buffer = New-Object byte[] 65536 $total = 0L $deadline = [DateTime]::UtcNow.AddSeconds(60) + $lastProgress = [DateTime]::MinValue while (($count = $inputStream.Read($buffer,0,$buffer.Length)) -gt 0) { $total += $count if ($total -gt $Limit -or [DateTime]::UtcNow -gt $deadline) { throw 'Download exceeded size or time limit.' } $outputStream.Write($buffer,0,$count) + if ($ProgressLabel -and ([DateTime]::UtcNow - $lastProgress).TotalMilliseconds -ge 100) { + $percent = if ($response.ContentLength -gt 0) { [int][Math]::Min(100,100.0*$total/$response.ContentLength) } else { -1 } + $detail = if ($percent -ge 0) { '{0}% - {1:N1} / {2:N1} MB' -f $percent,($total/1MB),($response.ContentLength/1MB) } else { '{0:N1} MB received' -f ($total/1MB) } + Show-UpdateProgress $ProgressLabel $detail $percent + $lastProgress = [DateTime]::UtcNow + } } + if ($response.ContentLength -ge 0 -and $total -ne $response.ContentLength) { throw 'The download ended before the complete file was received.' } + if ($ProgressLabel) { Show-UpdateProgress $ProgressLabel ('Downloaded {0:N1} MB' -f ($total/1MB)) 100 } } finally { if ($outputStream) { $outputStream.Dispose() } if ($inputStream) { $inputStream.Dispose() } @@ -220,8 +411,9 @@ function Write-Result($Value) { if ($Library) { return } $transaction = $null; $parentProcess = $null; $ready = $null; $started = $null $changed = [Collections.Generic.List[string]]::new() -$parentExited = $false; $success = $false; $preserveBackup = $false; $child = $null +$parentExited = $false; $success = $false; $preserveBackup = $false; $child = $null; $uiFinished = $false try { + if ($ShowProgress -or $Mode -eq 'Install') { Start-UpdateProgress } $current = Get-StableVersion $CurrentVersion if (!$current) { throw 'Invalid installed version.' } if ($Mode -eq 'Install') { @@ -242,25 +434,35 @@ try { $stage = Join-Path $transaction 'stage'; $backup = Join-Path $transaction 'backup' [IO.Directory]::CreateDirectory($stage) | Out-Null $archive = Join-Path $WorkDirectory 'release.zip'; $checksum = Join-Path $WorkDirectory 'SHA256SUMS.txt' - Receive-File $release.zip $archive 50MB + Show-UpdateProgress 'Downloading update' $release.version 0 + Receive-File $release.zip $archive 50MB 'Downloading update' + Show-UpdateProgress 'Verifying download' 'Checking the archive SHA-256 checksum' Receive-File $release.checksum $checksum 64KB Assert-Checksum $archive $checksum + Show-UpdateProgress 'Preparing installation' 'Validating and unpacking the release files' Expand-Release $archive $stage (Get-StableVersion $release.version) $screenshotPath = [IO.Path]::GetFullPath((Join-Path $InstallDirectory 'KSwitcher.Screenshot.exe')) if (Get-Process 'KSwitcher.Screenshot' -ErrorAction SilentlyContinue | Where-Object { $_.Path -eq $screenshotPath }) { throw 'Close screenshot editors before installing an update.' } Write-Result @{state='prepared';version=$release.version} + Show-UpdateProgress 'Closing KSwitcher' 'Waiting for the running application to exit safely' $ready.Set() | Out-Null if (!$parentProcess.WaitForExit(120000)) { throw 'KSwitcher did not exit; the installed files were not changed.' } $parentExited = $true + Show-UpdateProgress 'Installing update' 'Replacing application files; keeping your personal data' Install-Files $stage $InstallDirectory $backup $changed $startedName = 'Local\KSwitcher.Update.Started.'+[guid]::NewGuid().ToString('N') $started = [Threading.EventWaitHandle]::new($false,[Threading.EventResetMode]::ManualReset,$startedName) + Show-UpdateProgress 'Restarting KSwitcher' 'Waiting for the updated application to confirm startup' $child = Start-Process -FilePath (Join-Path $InstallDirectory 'KSwitcher.exe') -ArgumentList "--update-ready-event=$startedName" -WorkingDirectory $InstallDirectory -WindowStyle Hidden -PassThru if (!$started.WaitOne(30000) -or $child.HasExited) { throw 'The updated application did not confirm startup.' } $success = $true + if ($script:progressWindow) { + $uiFinished = Complete-UpdateProgress 'Update completed' "KSwitcher $(Get-StableVersion $release.version) is now running." $true + } } catch { $message = $_.Exception.Message if ($parentExited) { + Show-UpdateProgress 'Restoring previous version' 'The update failed; recovering the previous installation' try { if ($child -and !$child.HasExited) { $child.Kill(); $child.WaitForExit() } Undo-Files $InstallDirectory $backup $changed @@ -273,8 +475,12 @@ try { } Write-Result @{state='error';error=$message} if ($ready -and $ready.WaitOne(0)) { - Add-Type -AssemblyName System.Windows.Forms - [Windows.Forms.MessageBox]::Show($message,'KSwitcher update failed') | Out-Null + if ($script:progressWindow) { + $uiFinished = Complete-UpdateProgress 'Update failed' $message $false + } else { + Add-Type -AssemblyName System.Windows.Forms + [Windows.Forms.MessageBox]::Show($message,'KSwitcher update failed') | Out-Null + } } exit 1 } finally { @@ -291,4 +497,8 @@ try { Remove-Item -LiteralPath $WorkDirectory -Recurse -Force -ErrorAction SilentlyContinue } } + if ($script:progressWindow) { + if ($uiFinished) { $script:progressWindow.WaitForClose() } + $script:progressWindow.Dispose(); $script:progressWindow = $null + } } diff --git a/src/app.cpp b/src/app.cpp index b561c9e..3eceeae 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -1387,7 +1387,7 @@ void App::checkUpdate(bool interactive, bool install, const std::wstring &versio updateWorker = std::thread([this, interactive, install, version] { auto result = std::make_unique(); - try { *result = runUpdate(install, version); } + try { *result = runUpdate(install, version, interactive); } catch (const std::exception &e) { result->state = L"error"; result->error = wide(e.what()); } result->interactive = interactive; result->install = install; @@ -1635,8 +1635,8 @@ int App::execute() SetTimer(window, 2, 10000, nullptr); else SetTimer(window, 5, 5000, nullptr); - if (keyboardHook && mouseHook) - confirmUpdatedStartup(updateStartupEvent); + if (keyboardHook && mouseHook && confirmUpdatedStartup(updateStartupEvent)) + log(std::wstring(L"Update completed; running version ") + AppVersion); if (startCommand) PostMessageW(window, WM_HOTKEY, startCommand, 0); try { registerScreenshotNotifications(); } diff --git a/src/update.cpp b/src/update.cpp index 1a3a3a8..39e485a 100644 --- a/src/update.cpp +++ b/src/update.cpp @@ -26,7 +26,7 @@ std::wstring quoteArgument(const std::wstring &value) return out + L'"'; } } -UpdateResult runUpdate(bool install, const std::wstring &expectedVersion) +UpdateResult runUpdate(bool install, const std::wstring &expectedVersion, bool showProgress) { UpdateResult result; GUID guid{}; @@ -56,6 +56,7 @@ UpdateResult runUpdate(bool install, const std::wstring &expectedVersion) L" -InstallDirectory " + quoteArgument(std::filesystem::path(executable()).parent_path().wstring()) + L" -ParentProcessId " + std::to_wstring(GetCurrentProcessId()) + L" -ReadyEvent " + quoteArgument(readyName); if (install) command += L" -ExpectedVersion " + quoteArgument(expectedVersion); + if (showProgress) command += L" -ShowProgress"; STARTUPINFOW startup{sizeof(startup)}; PROCESS_INFORMATION process{}; if (!CreateProcessW(powershell.c_str(), command.data(), nullptr, nullptr, FALSE, CREATE_NO_WINDOW, @@ -94,10 +95,10 @@ UpdateResult runUpdate(bool install, const std::wstring &expectedVersion) throw; } } -void confirmUpdatedStartup(const std::wstring &eventName) +bool confirmUpdatedStartup(const std::wstring &eventName) { - if (!eventName.starts_with(L"Local\\KSwitcher.Update.Started.")) return; + if (!eventName.starts_with(L"Local\\KSwitcher.Update.Started.")) return false; Handle event{OpenEventW(EVENT_MODIFY_STATE, FALSE, eventName.c_str())}; - if (event.value) SetEvent(event.value); + return event.value && SetEvent(event.value); } } diff --git a/src/update.h b/src/update.h index 72f4d9b..974e36b 100644 --- a/src/update.h +++ b/src/update.h @@ -2,7 +2,7 @@ #include "platform.h" namespace ks { -inline constexpr wchar_t AppVersion[] = L"0.6.10"; +inline constexpr wchar_t AppVersion[] = L"0.6.11"; inline constexpr UINT UpdateMessage = WM_APP + 7; inline constexpr UINT UpdateCheckIntervalMs = 6 * 60 * 60 * 1000; inline constexpr UINT UpdateNotificationRetryMs = 5 * 60 * 1000; @@ -13,6 +13,6 @@ struct UpdateResult std::wstring state, version, error; bool interactive{}, install{}; }; -UpdateResult runUpdate(bool install, const std::wstring &expectedVersion); -void confirmUpdatedStartup(const std::wstring &eventName); +UpdateResult runUpdate(bool install, const std::wstring &expectedVersion, bool showProgress = false); +bool confirmUpdatedStartup(const std::wstring &eventName); } diff --git a/tests/input.cpp b/tests/input.cpp index 4adba77..75c9a9a 100644 --- a/tests/input.cpp +++ b/tests/input.cpp @@ -55,6 +55,14 @@ int main(int argc, char **argv) { // Message-only window: no hooks, synthetic input, clipboard changes or visible editor. app.window=CreateWindowExW(0,L"STATIC",L"Input tests",0,0,0,0,0,HWND_MESSAGE,nullptr,nullptr,nullptr); require(app.window!=nullptr,"message-only test window"); + require(!ks::confirmUpdatedStartup(L""),"normal startup does not report update success"); + require(!ks::confirmUpdatedStartup(L"Local.Other.Event"),"unrelated event cannot report update success"); + auto startedName=L"Local\\KSwitcher.Update.Started.Test."+std::to_wstring(GetCurrentProcessId()); + require(!ks::confirmUpdatedStartup(startedName),"missing updater handshake does not report success"); + auto started=CreateEventW(nullptr,TRUE,FALSE,startedName.c_str()); + require(started&&ks::confirmUpdatedStartup(startedName)&&WaitForSingleObject(started,0)==WAIT_OBJECT_0, + "successful startup signals the updater before reporting completion"); + CloseHandle(started); require(ks::UpdateCheckIntervalMs==21600000,"automatic update interval is six hours"); require(ks::InputRegressionAccess::updateTimer(app),"update timer rearms and avoids overlapping checks"); require(ks::InputRegressionAccess::updateResult(app,L"available",L"99.0.0")==L"99.0.0","background update records announced version"); diff --git a/tests/update_progress.ps1 b/tests/update_progress.ps1 new file mode 100644 index 0000000..e991c5d --- /dev/null +++ b/tests/update_progress.ps1 @@ -0,0 +1,31 @@ +param([string]$PreviewDirectory = (Join-Path $PSScriptRoot '../build/update-preview'), [switch]$Network) +$ErrorActionPreference = 'Stop' +. (Join-Path $PSScriptRoot '../resources/update.ps1') -Library +[IO.Directory]::CreateDirectory($PreviewDirectory) | Out-Null +Start-UpdateProgress +try { + if ($script:progressWindow.Status -ne 'Checking for updates') { throw 'Initial progress stage missing' } + Show-UpdateProgress 'Downloading update' '42% - 1.6 / 3.9 MB' 42 + if ($script:progressWindow.Percent -ne 42) { throw 'Determinate download progress missing' } + $script:progressWindow.SavePreview((Join-Path $PreviewDirectory 'download.png')) + if ($Network) { + Receive-File 'https://gitea.hrynco.com/api/v1/repos/hrynco/hrynco-kswitcher/releases?limit=100' (Join-Path $PreviewDirectory 'releases.json') 2MB 'Downloading update' + if ($script:progressWindow.Percent -ne 100) { throw 'Real download did not finish at 100 percent' } + } + foreach ($stage in @('Verifying download','Preparing installation','Closing KSwitcher','Installing update','Restarting KSwitcher','Restoring previous version')) { + Show-UpdateProgress $stage 'Update stage under test' + if ($script:progressWindow.Status -ne $stage -or $script:progressWindow.Percent -ne -1) { throw "Invalid progress stage: $stage" } + } + $script:progressWindow.SavePreview((Join-Path $PreviewDirectory 'rollback.png')) + $script:progressWindow.Finish('Update completed','KSwitcher 0.6.11 is now running.',$true) + if ($script:progressWindow.Status -ne 'Update completed' -or $script:progressWindow.Percent -ne 100) { throw 'Completion state missing' } + Start-Sleep -Milliseconds 800 # Let the native progress-bar animation finish before capturing it. + $script:progressWindow.SavePreview((Join-Path $PreviewDirectory 'complete.png')) + $script:progressWindow.Finish('Update failed',"The updated application did not confirm startup.`r`nThe previous version has been restored and restarted.",$false) + if ($script:progressWindow.Status -ne 'Update failed' -or $script:progressWindow.Percent -ne 0) { throw 'Failure state missing' } + $script:progressWindow.SavePreview((Join-Path $PreviewDirectory 'failed.png')) + 'Passed updater progress UI checks' +} finally { + $script:progressWindow.Dispose() + $script:progressWindow = $null +}