{"record":{"id":"98a85a0935ebecbc","repo":"memstechtips/Winhance","slug":"no-update-has-been-downloaded","errorCode":null,"errorMessage":"No update has been downloaded.","messagePattern":"No update has been downloaded\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Winhance.Infrastructure/Features/Common/Services/VersionService.cs","lineNumber":204,"sourceCode":"        downloadRequest.Headers.TryAddWithoutValidation(\"User-Agent\", _userAgent);\r\n        using var response = await _httpClient.SendAsync(downloadRequest, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);\r\n        response.EnsureSuccessStatusCode();\r\n\r\n        // Use explicit block so streams are disposed before returning\r\n        {\r\n            await using var contentStream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);\r\n            await using var fileStream = new FileStream(tempPath, FileMode.Create, FileAccess.Write, FileShare.None, 81920, true);\r\n            await contentStream.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false);\r\n        }\r\n\r\n        _downloadedInstallerPath = tempPath;\r\n        _logService.Log(LogLevel.Info, $\"Update downloaded to {tempPath}\");\r\n    }\r\n\r\n    public void LaunchInstallerAndRestart()\r\n    {\r\n        if (string.IsNullOrEmpty(_downloadedInstallerPath))\r\n            throw new InvalidOperationException(\"No update has been downloaded.\");\r\n\r\n        string appDir = AppContext.BaseDirectory;\r\n        bool isPortable = _fileSystemService.FileExists(_fileSystemService.CombinePath(appDir, \"portable.marker\"));\r\n        string appExePath = _fileSystemService.CombinePath(appDir, \"Winhance.exe\");\r\n\r\n        string installerArgs = BuildInstallerArgs(appDir, isPortable);\r\n\r\n        _logService.Log(LogLevel.Info, $\"Launching installer (portable: {isPortable}), app will restart after install...\");\r\n\r\n        // Use cmd /c to: run installer (wait for it to finish) then relaunch the app.\r\n        // The caller should exit the application immediately after this call.\r\n        var cmdArgs = $\"/c start /wait \\\"\\\" \\\"{_downloadedInstallerPath}\\\" {installerArgs} && start \\\"\\\" \\\"{appExePath}\\\"\";\r\n        Process.Start(new ProcessStartInfo\r\n        {\r\n            FileName = \"cmd.exe\",\r\n            Arguments = cmdArgs,\r\n            UseShellExecute = false,\r\n            CreateNoWindow = true\r","sourceCodeStart":186,"sourceCodeEnd":222,"githubUrl":"https://github.com/memstechtips/Winhance/blob/f23d554eb2d6400b1827bcc46b91294ffa53fbc9/src/Winhance.Infrastructure/Features/Common/Services/VersionService.cs#L186-L222","documentation":"InvalidOperationException thrown by VersionService.LaunchInstallerAndRestart when the private _downloadedInstallerPath field is null/empty — i.e. the caller invoked the launcher without first successfully downloading the update installer. The method cannot launch what was not downloaded, so it refuses rather than launching nothing or a stale path.","triggerScenarios":"LaunchInstallerAndRestart() is called before DownloadUpdateAsync (or CheckForUpdateAsync + download) completed successfully. The download was skipped because CheckForUpdateAsync reported no update, the download was cancelled, or a previous download's temp file was cleaned up between calls.","commonSituations":"A UI flow wires the 'restart & update' button directly to LaunchInstallerAndRestart without gating it on a completed download. A download failed silently (network) and _downloadedInstallerPath stayed null. The app was restarted (instance field reset) between download and launch.","solutions":["Gate the launch button on a successful download: only enable it after DownloadUpdateAsync sets the installer path.","Call CheckForUpdateAsync first; if no update exists, do not offer the launch action.","Persist the downloaded installer path (and verify the file still exists) across app restarts so a relaunch can resume.","Surface the exception to the UI as 'No update downloaded yet — check for updates first.'"],"exampleFix":"// before\npublic void LaunchInstallerAndRestart()\n{\n    if (string.IsNullOrEmpty(_downloadedInstallerPath))\n        throw new InvalidOperationException(\"No update has been downloaded.\");\n\n// after: explicit precondition with a recovery hint for the caller\npublic void LaunchInstallerAndRestart()\n{\n    if (string.IsNullOrEmpty(_downloadedInstallerPath) || !_fileSystemService.FileExists(_downloadedInstallerPath))\n        throw new InvalidOperationException(\"No update has been downloaded. Call CheckForUpdateAsync/DownloadUpdateAsync first.\");","handlingStrategy":"validation","validationCode":"// Gate the launch on a completed, file-present download.\nbool IsUpdateReadyToLaunch() =>\n    !string.IsNullOrEmpty(_downloadedInstallerPath) && _fileSystemService.FileExists(_downloadedInstallerPath);","typeGuard":null,"tryCatchPattern":"catch (InvalidOperationException ex) when (ex.Message.Contains(\"No update has been downloaded\"))\n{\n    // Tell the user to check for updates first; the launch action should be disabled until then.\n}","preventionTips":["Only enable the 'restart & update' button after DownloadUpdateAsync succeeds.","Verify the downloaded installer file still exists before launching (it may have been cleaned).","Persist the installer path across app restarts so a relaunch can resume."],"tags":["state-validation","update","version","installer","contract"],"backgroundTag":null,"analyzedSha":"f23d554eb2d6400b1827bcc46b91294ffa53fbc9","analyzedAt":"2026-08-13T17:55:20.922Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}