Skip to content

Add updater download and verification (#444, #445)#733

Open
fernandotonon wants to merge 1 commit into
masterfrom
feat/updater-download-verify-444-445
Open

Add updater download and verification (#444, #445)#733
fernandotonon wants to merge 1 commit into
masterfrom
feat/updater-download-verify-444-445

Conversation

@fernandotonon

@fernandotonon fernandotonon commented Jun 17, 2026

Copy link
Copy Markdown
Owner

Summary

  • Implements resumable artifact download with progress, cancel, and retry (Range resume + .part staging under AppData).
  • Adds ArtifactResolver (platform-specific release asset pick) and UpdateVerifier (SHA-256 manifest + minisign signature).
  • Wires UpdaterController::downloadAndInstall(), auto-download when enabled, and dialog UI through ReadyToInstall (install step remains Update: macOS installer — replace .app bundle atomically #446–448).

Test plan

  • UnitTests --gtest_filter="ArtifactResolver*:UpdateVerifier*:MinisignVerify*:UpdaterController*" passes locally on Linux
  • CI unit-tests-linux green
  • CI build-windows / build-macos green (Windows verify backend still stubbed — fail-closed at signature step)
  • Manual: Help → Check for Updates → Download & install on a portable build when a signed release asset exists

Made with Cursor

Summary by CodeRabbit

  • New Features

    • Download and install updates now fully functional with the "Download & install" button working as expected.
    • Added comprehensive update verification using SHA-256 checksums and digital signatures before installation.
    • New "Ready to install" screen displays after update is successfully downloaded and verified.
    • Auto-download capability now enabled when configured.
  • Chores

    • Updated build configuration and documentation for improved cross-platform update support.

Wire downloadAndInstall through ArtifactResolver, a resumable worker-thread
downloader with retry/cancel, and SHA-256 + minisign verification before
ReadyToInstall. Enables the dialog download button and auto-download path.

Co-authored-by: Cursor <cursoragent@cursor.com>
@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Implements the full auto-updater download-verify-install pipeline: platform-aware artifact resolution via ArtifactResolver, a resumable HTTP downloader with retry logic in UpdaterWorker, SHA-256 and minisign verification in UpdateVerifier, UpdaterController state machine wiring through DownloadingVerifyingReadyToInstall, libsodium CMake platform gating extended to macOS, and a QML "Ready to install" dialog screen.

Changes

Auto-updater download, verify, and install pipeline

Layer / File(s) Summary
Libsodium CMake platform gating
cmake/Libsodium.cmake
Adds a WIN32 early-return stub with QTMESH_MINISIGN_VERIFY=0, restricts system pkg-config lookup to UNIX AND NOT APPLE, and lets macOS and other non-WIN32 platforms fall through to building a static library from source with QTMESH_MINISIGN_VERIFY=1.
MinisignVerify macro gating
src/updater/MinisignVerify.cpp, src/updater/MinisignVerify.h, src/updater/MinisignVerify_test.cpp
Replaces Q_OS_LINUX guards with QTMESH_MINISIGN_VERIFY throughout; disabled-feature branch now returns Result::Unsupported with an updated message; unsupported test renamed to UnsupportedWhenVerifyBackendDisabled.
ArtifactResolver contract and implementation
src/updater/ArtifactResolver.h, src/updater/ArtifactResolver.cpp, src/updater/ArtifactResolver_test.cpp
New ResolvedArtifact struct and resolveForCurrentPlatform() that picks a platform-regex-matched primary artifact, requires a .minisig sidecar, and optionally captures SHA256SUMS; tests cover Windows/macOS/Linux selection and missing-sidecar failure.
UpdateVerifier contract and implementation
src/updater/UpdateVerifier.h, src/updater/UpdateVerifier.cpp, src/updater/UpdateVerifier_test.cpp
New Outcome struct and four functions for SHA-256 file hashing, hex comparison, manifest-based verification, and a full artifact pipeline (verifyDownloadedArtifact) that conditionally runs SHA-256 then minisign under QTMESH_MINISIGN_VERIFY; gtest coverage for all three public paths.
UpdaterWorker download and verify slots
src/updater/UpdaterWorker.h, src/updater/UpdaterWorker.cpp
Adds DownloadRequest/Outcome, VerifyRequest/Outcome, ActiveJob enum, downloadUpdate with HTTP Range resume/retry, startArtifactDownloadAttempt, onDownloadReplyFinished (part-file management + sidecar downloads), downloadUrlBlocking, and verifyDownload; renames check-completion handler.
UpdaterController state machine wiring
src/updater/UpdaterController.h, src/updater/UpdaterController.cpp
Connects worker download/verify signals with throttled progress; implements beginDownloadIfNeeded, startDownloadJob (artifact resolution + staging), handleDownloadFinished, handleVerifyFinished; refines cancel() to restore UpdateAvailable or Idle; caches release assets.
QML UpdaterDialog UI
qml/UpdaterDialog.qml
Enables the "Download & install" button to call UpdaterController.downloadAndInstall(); adds a ReadyToInstall screen with a "downloaded and verified" message and a Close button.
Build registration and design doc
src/CMakeLists.txt, docs/AUTO_UPDATER_DESIGN.md
Registers new ArtifactResolver and UpdateVerifier source/header files; updates design doc to reflect QTMESH_MINISIGN_VERIFY conditions, deferred Windows MinGW behavior, and the #444–445 in-progress status.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UpdaterDialog as UpdaterDialog (QML)
    participant Controller as UpdaterController
    participant Worker as UpdaterWorker
    participant Network as QNetworkAccessManager
    participant Verifier as UpdateVerifier

    rect rgba(100, 149, 237, 0.5)
        Note over Controller,Worker: Update detected
        Controller->>Controller: beginDownloadIfNeeded(false)
        Controller->>Worker: downloadUpdate(DownloadRequest)
    end

    rect rgba(144, 238, 144, 0.5)
        Note over Worker,Network: Resumable artifact download
        Worker->>Network: GET artifact (Range header)
        Network-->>Worker: progress / data chunks
        Worker-->>Controller: downloadProgress(received, total)
        Controller-->>UpdaterDialog: progressChanged
        Worker->>Network: downloadUrlBlocking(signature)
        Worker->>Network: downloadUrlBlocking(SHA256SUMS)
        Worker-->>Controller: downloadFinished(DownloadOutcome)
    end

    rect rgba(255, 200, 100, 0.5)
        Note over Controller,Verifier: Verification stage
        Controller->>Worker: verifyDownload(VerifyRequest)
        Worker->>Verifier: verifyDownloadedArtifact(artifact, sig, manifest, name)
        Verifier-->>Worker: Outcome
        Worker-->>Controller: verifyFinished(VerifyOutcome)
        Controller->>Controller: state = ReadyToInstall
        Controller-->>UpdaterDialog: stateChanged
    end

    User->>UpdaterDialog: sees "Ready to install" screen
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related issues

Possibly related PRs

  • fernandotonon/QtMeshEditor#722: Directly related — both modify cmake/Libsodium.cmake's qtmesh_sodium target and QTMESH_MINISIGN_VERIFY gating, and wire MinisignVerify to that build flag.
  • fernandotonon/QtMeshEditor#731: Directly related — touches the same qml/UpdaterDialog.qml and UpdaterController download/install flow that this PR builds upon.

Poem

🐰 Hop, hop, the bunny downloads with care,
A .minisig sidecar tucked in its lair.
SHA-256 checked, the hash rings true,
ReadyToInstall glows a verified hue.
On macOS and Linux, libsodium's built,
No unverified bits, and no reason for guilt! 🔐

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.46% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main changes: adding updater download and verification functionality, with explicit issue references (#444, #445).
Description check ✅ Passed The description covers all key aspects: a clear summary of the three main components (resumable download, artifact resolution, verification), technical details of the implementation, and a comprehensive test plan with local verification status.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/updater-download-verify-444-445

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 143050f5ab

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


const qint64 resumeOffset = reply->property("resumeOffset").toLongLong();
const bool append = resumeOffset > 0;
const QByteArray payload = reply->readAll();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Stream artifact data into the partial file

Because the artifact body is read only after finished and the .part file is written only after a no-error reply, bytes received before a dropped connection are discarded; the retry Range header therefore resumes from the old on-disk size, normally 0, instead of the point of failure. Large zip/dmg/tarball assets are also buffered entirely in memory. This breaks the resumable download path for the network failures it is meant to recover; stream readyRead chunks to the .part file during the transfer.

Useful? React with 👍 / 👎.

QByteArray("bytes=" + QByteArray::number(existingSize) + '-'));
}

QNetworkReply* reply = m_network->get(httpRequest);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Track sidecar replies during cancellation

downloadUrlBlocking keeps the sidecar reply in a local variable while m_activeReply is null, so pressing Escape/cancel during the .minisig or SHA256SUMS fetch calls cancelActiveRequest() but has no reply to abort. The nested event loop continues, and if the sidecar downloads finish the controller can proceed to verify/ReadyToInstall after the user cancelled or closed the dialog; store this reply or check cancellation after the blocking call so cancellation takes effect.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🧹 Nitpick comments (1)
src/updater/UpdaterWorker.cpp (1)

110-344: ⚡ Quick win

Add breadcrumbs for download/verify I/O operations in this worker path.

This new flow introduces significant user-visible I/O stages (artifact download, sidecar download, verification) without breadcrumb instrumentation in this file. Add SentryReporter::addBreadcrumb() at start/retry/success/failure points using file.import for downloads and a consistent category for verify stage events.

As per coding guidelines, “Add Sentry breadcrumbs for all user-facing actions and significant operations using SentryReporter::addBreadcrumb() with categories: 'ui.action' for toolbar/menu clicks, 'ai.tool_call' for MCP tools, 'file.import'/'file.export' for I/O.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/updater/UpdaterWorker.cpp` around lines 110 - 344, Add Sentry breadcrumbs
to instrument the download and verification workflow in UpdaterWorker. Insert
SentryReporter::addBreadcrumb() calls with category "file.import" at the
following points: at the start of downloadUpdate() to mark the beginning of the
artifact download, at the beginning of startArtifactDownloadAttempt() to track
each retry attempt with the attempt number, in onDownloadReplyFinished() to
record successful artifact download completion and again before downloading each
sidecar file (signature and sha256Sums), and in verifyDownload() at the start
and upon completion to track the verification stage. Use descriptive messages
that include relevant context like attempt numbers and file names to aid
debugging.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmake/Libsodium.cmake`:
- Around line 16-30: The source-build fallback path in this CMake file uses a
make invocation with the -j flag that lacks a numeric argument, which is
incompatible with BSD make (the default on macOS). Locate the make command
invocation (referenced as being around line 67) in the source-build fallback
section and modify it to specify a concrete number of parallel jobs or use a
CMake-compatible approach that works across both GNU make and BSD make. Ensure
the parallel build flag is portable by providing an explicit job count or using
a CMake function that handles this portability concern.

In `@docs/AUTO_UPDATER_DESIGN.md`:
- Around line 45-47: The documentation contains contradictory statements about
platform support for MinisignVerify. Line 45 states the feature is "Linux-only"
while line 46 indicates that MinisignVerify with QTMESH_MINISIGN_VERIFY=1
supports both Linux and macOS via libsodium. Resolve this contradiction by
choosing one consistent statement across both lines. Either revise line 45 to
accurately reflect that Linux and macOS are supported, or revise line 46 to
clarify the actual platform scope. Ensure the final wording clearly states which
platforms are currently supported under QTMESH_MINISIGN_VERIFY.

In `@src/updater/ArtifactResolver.cpp`:
- Around line 79-80: The error message string at lines 79-80 in the
ArtifactResolver.cpp file currently mentions only AppImage and .tar.gz as
accepted portable Linux package formats, but the resolver pattern actually
accepts .tar.xz as well. Update the error message string to include .tar.xz in
the list of supported extensions alongside AppImage and .tar.gz to ensure users
are provided accurate information about what package formats are accepted.
- Around line 58-60: The install-flavor validation logic in ArtifactResolver.cpp
has an inverted condition that allows package-managed installs to bypass the
error check. The current condition at the if statement rejects only when the
flavor is NOT package-manager-managed, NOT portable, and NOT unknown, which
means package-managed flavors pass through without triggering the error. Since
the error message states "In-app download is only supported for portable
installs", you need to invert the logic by removing the
InstallFlavor::isPackageManagerManaged() check and keeping only the checks that
ensure the flavor is either Portable or Unknown; if it's any other flavor type
(especially package-managed), the error should be triggered.

In `@src/updater/MinisignVerify.h`:
- Around line 14-15: The documentation comment in MinisignVerify.h incorrectly
states that only Linux links libsodium when QTMESH_MINISIGN_VERIFY is set, but
this PR enables the backend on macOS as well. Update the comment at lines 14-15
to reflect that both Linux and macOS support the minisign verify backend with
libsodium, either by explicitly listing both platforms or by using
platform-agnostic language such as "supported platforms" instead of specifically
mentioning only Linux.

In `@src/updater/UpdaterController.cpp`:
- Around line 435-492: Both handleDownloadFinished and handleVerifyFinished
methods need to validate that the outcome is still relevant for the current
state before processing it. Add a check at the beginning of each method to
ensure the current state matches the expected state for that operation. In
handleDownloadFinished, verify the state is in an active download state before
processing the outcome. In handleVerifyFinished, verify the state is in the
Verifying state before processing the outcome. If the state has changed (e.g.,
due to cancellation), return early without updating the state or error message.
This prevents stale signals from resurrecting canceled operations.
- Around line 367-369: In the downloadAndInstall() method, change the breadcrumb
category passed to SentryReporter::addBreadcrumb() from "updater.download.start"
to "ui.action" on line 367, since this action is triggered by a user button
click and should follow the coding guideline of using the "ui.action" category
for toolbar and button-triggered user-facing actions.
- Around line 403-408: The QDir().mkpath(stagingRoot) call does not check its
return value, which is a boolean indicating success or failure. Capture the
return value from mkpath() and add an explicit check: if it returns false, log
an appropriate error message and return early from the function or throw an
exception to prevent the workflow from continuing with a non-existent staging
directory. This will ensure failures are caught immediately with proper error
context rather than failing later in the update process.

In `@src/updater/UpdaterWorker.cpp`:
- Around line 177-203: The downloadUrlBlocking() method creates a local
QNetworkReply pointer but never assigns it to the m_activeReply member variable,
preventing cancelActiveRequest() from actually canceling the blocking download
since it returns early when m_activeReply is null. To fix this, assign the reply
returned from m_network->get(httpRequest) to m_activeReply immediately after
creation, then clear it back to nullptr after the event loop completes (after
loop.exec()). Additionally, ensure the propagation of the cancelled state
through finishDownloadWithError() calls is consistent by passing the appropriate
cancellation flag to maintain proper controller state handling.
- Around line 256-278: The code does not handle the case where a server ignores
a Range header request during resume operations and responds with 200 OK (full
body) instead of 206 Partial Content, causing data corruption when the full
payload is appended to an existing partial file. Reorder the logic so the 416
status check (for Range Not Satisfiable) executes before the generic httpOk
error check. Additionally, add a new condition that checks if httpStatus equals
200 while append is true (indicating the server ignored the Range header), and
in that case, remove the partial file at m_downloadRequest.artifactPartPath and
restart the download attempt from the beginning using
startArtifactDownloadAttempt. This prevents blindly appending full responses to
partial files.

---

Nitpick comments:
In `@src/updater/UpdaterWorker.cpp`:
- Around line 110-344: Add Sentry breadcrumbs to instrument the download and
verification workflow in UpdaterWorker. Insert SentryReporter::addBreadcrumb()
calls with category "file.import" at the following points: at the start of
downloadUpdate() to mark the beginning of the artifact download, at the
beginning of startArtifactDownloadAttempt() to track each retry attempt with the
attempt number, in onDownloadReplyFinished() to record successful artifact
download completion and again before downloading each sidecar file (signature
and sha256Sums), and in verifyDownload() at the start and upon completion to
track the verification stage. Use descriptive messages that include relevant
context like attempt numbers and file names to aid debugging.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 09aa5e2f-aebf-4610-a552-90f2e88b5b27

📥 Commits

Reviewing files that changed from the base of the PR and between 1f1ab9c and 143050f.

📒 Files selected for processing (17)
  • cmake/Libsodium.cmake
  • docs/AUTO_UPDATER_DESIGN.md
  • qml/UpdaterDialog.qml
  • src/CMakeLists.txt
  • src/updater/ArtifactResolver.cpp
  • src/updater/ArtifactResolver.h
  • src/updater/ArtifactResolver_test.cpp
  • src/updater/MinisignVerify.cpp
  • src/updater/MinisignVerify.h
  • src/updater/MinisignVerify_test.cpp
  • src/updater/UpdateVerifier.cpp
  • src/updater/UpdateVerifier.h
  • src/updater/UpdateVerifier_test.cpp
  • src/updater/UpdaterController.cpp
  • src/updater/UpdaterController.h
  • src/updater/UpdaterWorker.cpp
  • src/updater/UpdaterWorker.h

Comment thread cmake/Libsodium.cmake
Comment on lines +16 to 30
if(UNIX AND NOT APPLE)
find_package(PkgConfig QUIET)
if(PkgConfig_FOUND)
pkg_check_modules(QTMESH_LIBSODIUM libsodium)
endif()
if(QTMESH_LIBSODIUM_FOUND)
message(STATUS "libsodium: using system package (pkg-config)")
add_library(qtmesh_sodium INTERFACE)
target_include_directories(qtmesh_sodium SYSTEM INTERFACE ${QTMESH_LIBSODIUM_INCLUDE_DIRS})
target_link_libraries(qtmesh_sodium INTERFACE ${QTMESH_LIBSODIUM_LIBRARIES})
target_compile_options(qtmesh_sodium INTERFACE ${QTMESH_LIBSODIUM_CFLAGS_OTHER})
target_compile_definitions(qtmesh_sodium INTERFACE QTMESH_MINISIGN_VERIFY=1)
return()
endif()
endif()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Does BSD make (the default /usr/bin/make on macOS) accept -j without a numeric argument?

💡 Result:

Yes, BSD make (the default make on macOS) accepts the -j flag without a numeric argument, but its behavior differs significantly from GNU make. In GNU make, -j without an argument allows an unlimited number of concurrent jobs [1][2][3]. In contrast, BSD make (as implemented in macOS, FreeBSD, NetBSD, and OpenBSD) requires an argument for -j [4][5][6][7]. If you provide -j without a following number, it is generally treated as an invalid usage or can cause the command to fail, as it expects a numeric value to specify the maximum number of jobs [4][5][7]. Consequently, users on macOS must specify a number (e.g., make -j8) or use a command substitution to dynamically provide a number (e.g., make -j$(sysctl -n hw.ncpu)) [8][9]. Attempting to run it without an argument may result in errors or unexpected behavior depending on the specific shell environment and version [10][9].

Citations:


🏁 Script executed:

cat -n cmake/Libsodium.cmake | head -100

Repository: fernandotonon/QtMeshEditor

Length of output: 4166


Use a portable parallel-build invocation in the source-build path.

Line 16 routes macOS into the source-build fallback (since the condition excludes Apple), and line 67 runs make -j. This flag form is not portable: BSD make (the default on macOS) requires a numeric argument and will fail when invoked without one, blocking builds on macOS.

Proposed fix
 include(FetchContent)
+include(ProcessorCount)
+
+ProcessorCount(QTMESH_LIBSODIUM_JOBS)
+if(NOT QTMESH_LIBSODIUM_JOBS OR QTMESH_LIBSODIUM_JOBS LESS 1)
+    set(QTMESH_LIBSODIUM_JOBS 1)
+endif()
@@
     execute_process(
-        COMMAND make -j
+        COMMAND make -j${QTMESH_LIBSODIUM_JOBS}
         WORKING_DIRECTORY "${qtmesh_libsodium_src_SOURCE_DIR}"
         RESULT_VARIABLE qtmesh_sodium_build_result
     )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmake/Libsodium.cmake` around lines 16 - 30, The source-build fallback path
in this CMake file uses a make invocation with the -j flag that lacks a numeric
argument, which is incompatible with BSD make (the default on macOS). Locate the
make command invocation (referenced as being around line 67) in the source-build
fallback section and modify it to specify a concrete number of parallel jobs or
use a CMake-compatible approach that works across both GNU make and BSD make.
Ensure the parallel build flag is portable by providing an explicit job count or
using a CMake function that handles this portability concern.

Comment on lines 45 to 47
- Linux-only in this spike; Windows/macOS return `Unsupported` until #445 wires cross-platform static libsodium.
- **#445:** `MinisignVerify` builds wherever `QTMESH_MINISIGN_VERIFY=1` (Linux + macOS via libsodium). Windows MinGW verify is deferred — downloads fail closed at the signature step until a follow-up wires libsodium there.
- Tests: `MinisignVerify_test` verifies good/tampered/bad-key against `tests/fixtures/updater/release-3.5.3-readme.md` (content from GitHub tag `3.5.3`).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Resolve contradictory platform-support wording in PoC status.

Line 45 says “Linux-only,” while Line 46 says QTMESH_MINISIGN_VERIFY=1 supports Linux + macOS. Please keep one consistent statement.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/AUTO_UPDATER_DESIGN.md` around lines 45 - 47, The documentation contains
contradictory statements about platform support for MinisignVerify. Line 45
states the feature is "Linux-only" while line 46 indicates that MinisignVerify
with QTMESH_MINISIGN_VERIFY=1 supports both Linux and macOS via libsodium.
Resolve this contradiction by choosing one consistent statement across both
lines. Either revise line 45 to accurately reflect that Linux and macOS are
supported, or revise line 46 to clarify the actual platform scope. Ensure the
final wording clearly states which platforms are currently supported under
QTMESH_MINISIGN_VERIFY.

Comment on lines +58 to +60
if (!InstallFlavor::isPackageManagerManaged(flavor) && flavor != InstallFlavor::Flavor::Portable
&& flavor != InstallFlavor::Flavor::Unknown) {
result.errorMessage = QStringLiteral("In-app download is only supported for portable installs");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Invert the install-flavor gate; package-managed installs are currently allowed through.

The current predicate rejects only non-package-managed/non-portable flavors, which means package-managed flavors pass validation and can reach download resolution. That contradicts the “portable-only” behavior and can trigger incorrect updater flows.

Suggested fix
-    if (!InstallFlavor::isPackageManagerManaged(flavor) && flavor != InstallFlavor::Flavor::Portable
-        && flavor != InstallFlavor::Flavor::Unknown) {
+    if (InstallFlavor::isPackageManagerManaged(flavor)
+        || (flavor != InstallFlavor::Flavor::Portable
+            && flavor != InstallFlavor::Flavor::Unknown)) {
         result.errorMessage = QStringLiteral("In-app download is only supported for portable installs");
         return result;
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/updater/ArtifactResolver.cpp` around lines 58 - 60, The install-flavor
validation logic in ArtifactResolver.cpp has an inverted condition that allows
package-managed installs to bypass the error check. The current condition at the
if statement rejects only when the flavor is NOT package-manager-managed, NOT
portable, and NOT unknown, which means package-managed flavors pass through
without triggering the error. Since the error message states "In-app download is
only supported for portable installs", you need to invert the logic by removing
the InstallFlavor::isPackageManagerManaged() check and keeping only the checks
that ensure the flavor is either Portable or Unknown; if it's any other flavor
type (especially package-managed), the error should be triggered.

Comment on lines +79 to +80
QStringLiteral("No portable Linux update package found in this release "
"(expected AppImage or .tar.gz; .deb installs use apt)");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Keep Linux guidance text aligned with accepted extensions.

The resolver pattern accepts .tar.xz, but the error string mentions only AppImage and .tar.gz. This can mislead users when a valid .tar.xz artifact is expected.

Suggested fix
-            QStringLiteral("No portable Linux update package found in this release "
-                           "(expected AppImage or .tar.gz; .deb installs use apt)");
+            QStringLiteral("No portable Linux update package found in this release "
+                           "(expected AppImage, .tar.gz, or .tar.xz; .deb installs use apt)");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/updater/ArtifactResolver.cpp` around lines 79 - 80, The error message
string at lines 79-80 in the ArtifactResolver.cpp file currently mentions only
AppImage and .tar.gz as accepted portable Linux package formats, but the
resolver pattern actually accepts .tar.xz as well. Update the error message
string to include .tar.xz in the list of supported extensions alongside AppImage
and .tar.gz to ensure users are provided accurate information about what package
formats are accepted.

Comment on lines +14 to +15
* Linux builds link libsodium for verify when @c QTMESH_MINISIGN_VERIFY is set.
* Builds without the backend return @ref Result::Unsupported.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update the platform note to include macOS backend support.

The comment says only Linux links libsodium when QTMESH_MINISIGN_VERIFY is set, but this PR’s platform gating enables that backend on macOS too. Please make the doc platform-agnostic or list both platforms.

Proposed fix
- * Linux builds link libsodium for verify when `@c` QTMESH_MINISIGN_VERIFY is set.
+ * Builds with `@c` QTMESH_MINISIGN_VERIFY set link libsodium for verify
+ * (currently Linux and macOS).
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
* Linux builds link libsodium for verify when @c QTMESH_MINISIGN_VERIFY is set.
* Builds without the backend return @ref Result::Unsupported.
* Builds with `@c` QTMESH_MINISIGN_VERIFY set link libsodium for verify
* (currently Linux and macOS).
* Builds without the backend return `@ref` Result::Unsupported.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/updater/MinisignVerify.h` around lines 14 - 15, The documentation comment
in MinisignVerify.h incorrectly states that only Linux links libsodium when
QTMESH_MINISIGN_VERIFY is set, but this PR enables the backend on macOS as well.
Update the comment at lines 14-15 to reflect that both Linux and macOS support
the minisign verify backend with libsodium, either by explicitly listing both
platforms or by using platform-agnostic language such as "supported platforms"
instead of specifically mentioning only Linux.

Comment on lines +367 to +369
SentryReporter::addBreadcrumb(QStringLiteral("updater.download.start"),
QStringLiteral("version=%1").arg(m_latestVersion));
beginDownloadIfNeeded(true);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use ui.action breadcrumb category for the button-triggered download action.

downloadAndInstall() is triggered by a user button click, but Line 367 logs under updater.download.start instead of ui.action.

Suggested fix
 void UpdaterController::downloadAndInstall()
 {
+    SentryReporter::addBreadcrumb(QStringLiteral("ui.action"),
+                                  QStringLiteral("Updater dialog: Download & install"));
     SentryReporter::addBreadcrumb(QStringLiteral("updater.download.start"),
                                   QStringLiteral("version=%1").arg(m_latestVersion));
     beginDownloadIfNeeded(true);
 }

As per coding guidelines: "Add Sentry breadcrumbs for all user-facing actions and significant operations using SentryReporter::addBreadcrumb() with categories: 'ui.action' for toolbar/menu clicks, 'ai.tool_call' for MCP tools, 'file.import'/'file.export' for I/O".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/updater/UpdaterController.cpp` around lines 367 - 369, In the
downloadAndInstall() method, change the breadcrumb category passed to
SentryReporter::addBreadcrumb() from "updater.download.start" to "ui.action" on
line 367, since this action is triggered by a user button click and should
follow the coding guideline of using the "ui.action" category for toolbar and
button-triggered user-facing actions.

Source: Coding guidelines

Comment on lines +403 to +408
const QString stagingRoot =
QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)
+ QStringLiteral("/updater/staging/")
+ m_latestVersion;
QDir().mkpath(stagingRoot);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Handle staging directory creation failure explicitly.

QDir().mkpath(stagingRoot) on Line 407 is unchecked. If it fails, the workflow continues and fails later with a less precise error path.

Suggested fix
-    QDir().mkpath(stagingRoot);
+    if (!QDir().mkpath(stagingRoot)) {
+        setError(tr("Could not create updater staging directory: %1").arg(stagingRoot));
+        setState(State::Error);
+        SentryReporter::addBreadcrumb(QStringLiteral("updater.download.error"),
+                                      QStringLiteral("mkpath failed: %1").arg(stagingRoot),
+                                      QStringLiteral("error"));
+        logDialogStateBreadcrumb();
+        return;
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const QString stagingRoot =
QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)
+ QStringLiteral("/updater/staging/")
+ m_latestVersion;
QDir().mkpath(stagingRoot);
const QString stagingRoot =
QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)
QStringLiteral("/updater/staging/")
m_latestVersion;
if (!QDir().mkpath(stagingRoot)) {
setError(tr("Could not create updater staging directory: %1").arg(stagingRoot));
setState(State::Error);
SentryReporter::addBreadcrumb(QStringLiteral("updater.download.error"),
QStringLiteral("mkpath failed: %1").arg(stagingRoot),
QStringLiteral("error"));
logDialogStateBreadcrumb();
return;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/updater/UpdaterController.cpp` around lines 403 - 408, The
QDir().mkpath(stagingRoot) call does not check its return value, which is a
boolean indicating success or failure. Capture the return value from mkpath()
and add an explicit check: if it returns false, log an appropriate error message
and return early from the function or throw an exception to prevent the workflow
from continuing with a non-existent staging directory. This will ensure failures
are caught immediately with proper error context rather than failing later in
the update process.

Comment on lines +435 to +492
void UpdaterController::handleDownloadFinished(const DownloadOutcome& outcome)
{
if (outcome.cancelled) {
SentryReporter::addBreadcrumb(QStringLiteral("updater.download.cancel"),
outcome.fileName);
setState(State::UpdateAvailable);
setProgressPercent(0);
logDialogStateBreadcrumb();
return;
}

if (!outcome.ok) {
SentryReporter::addBreadcrumb(QStringLiteral("updater.download.error"),
outcome.errorMessage,
QStringLiteral("error"));
setError(outcome.errorMessage);
setState(State::Error);
logDialogStateBreadcrumb();
return;
}

SentryReporter::addBreadcrumb(QStringLiteral("updater.download.complete"),
outcome.fileName);
setProgressPercent(100);
setState(State::Verifying);
logDialogStateBreadcrumb();

VerifyRequest verifyRequest;
verifyRequest.artifactPath = outcome.artifactPath;
verifyRequest.signaturePath = outcome.signaturePath;
verifyRequest.sha256SumsPath = outcome.sha256SumsPath;
verifyRequest.fileName = outcome.fileName;

QMetaObject::invokeMethod(m_worker,
"verifyDownload",
Qt::QueuedConnection,
Q_ARG(VerifyRequest, verifyRequest));
}

void UpdaterController::handleVerifyFinished(const VerifyOutcome& outcome)
{
if (!outcome.ok) {
SentryReporter::addBreadcrumb(
QStringLiteral("updater.verify.failure"),
QStringLiteral("%1: %2").arg(outcome.failedStage, outcome.errorMessage),
QStringLiteral("error"));
setError(outcome.errorMessage.isEmpty()
? tr("Download verification failed.")
: outcome.errorMessage);
setState(State::Error);
logDialogStateBreadcrumb();
return;
}

SentryReporter::addBreadcrumb(QStringLiteral("updater.verify.success"), m_latestVersion);
setState(State::ReadyToInstall);
logDialogStateBreadcrumb();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Ignore stale worker outcomes after cancellation/state change.

After cancel() (Line 510) moves state away from active jobs, late downloadFinished/verifyFinished signals can still transition to Verifying, ReadyToInstall, or Error. This causes canceled flows to “resurrect.”

Suggested fix
 void UpdaterController::handleDownloadFinished(const DownloadOutcome& outcome)
 {
+    if (m_state != State::Downloading) {
+        return;
+    }
+
     if (outcome.cancelled) {
         SentryReporter::addBreadcrumb(QStringLiteral("updater.download.cancel"),
                                     outcome.fileName);
@@
 void UpdaterController::handleVerifyFinished(const VerifyOutcome& outcome)
 {
+    if (m_state != State::Verifying) {
+        return;
+    }
+
     if (!outcome.ok) {

Also applies to: 504-519

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/updater/UpdaterController.cpp` around lines 435 - 492, Both
handleDownloadFinished and handleVerifyFinished methods need to validate that
the outcome is still relevant for the current state before processing it. Add a
check at the beginning of each method to ensure the current state matches the
expected state for that operation. In handleDownloadFinished, verify the state
is in an active download state before processing the outcome. In
handleVerifyFinished, verify the state is in the Verifying state before
processing the outcome. If the state has changed (e.g., due to cancellation),
return early without updating the state or error message. This prevents stale
signals from resurrecting canceled operations.

Comment on lines +177 to +203
bool UpdaterWorker::downloadUrlBlocking(const QString& url,
const QString& destPath,
bool resume,
QString* errorMessage)
{
qint64 existingSize = 0;
if (resume && QFile::exists(destPath)) {
existingSize = QFileInfo(destPath).size();
}

QNetworkRequest httpRequest = makeRequest(QUrl(url));
if (existingSize > 0) {
httpRequest.setRawHeader("Range",
QByteArray("bytes=" + QByteArray::number(existingSize) + '-'));
}

QNetworkReply* reply = m_network->get(httpRequest);
QEventLoop loop;
connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
loop.exec();

const bool wasCancelled = reply->error() == QNetworkReply::OperationCanceledError;
const bool ok = reply->error() == QNetworkReply::NoError;
const QByteArray payload = reply->readAll();
const QString errorString = reply->errorString();
reply->deleteLater();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find the UpdaterWorker files
fd -t f "UpdaterWorker" --extension cpp --extension h

Repository: fernandotonon/QtMeshEditor

Length of output: 128


🏁 Script executed:

# Read the specific lines mentioned in the review comment
cat -n src/updater/UpdaterWorker.cpp | sed -n '170,230p'

Repository: fernandotonon/QtMeshEditor

Length of output: 2419


🏁 Script executed:

# Check what's at lines 304-318 (also applies to)
cat -n src/updater/UpdaterWorker.cpp | sed -n '300,325p'

Repository: fernandotonon/QtMeshEditor

Length of output: 1120


🏁 Script executed:

# Search for cancelActiveRequest implementation
rg -n "cancelActiveRequest" src/updater/UpdaterWorker.cpp -A 10

Repository: fernandotonon/QtMeshEditor

Length of output: 1202


🏁 Script executed:

# Search for m_activeReply usage
rg -n "m_activeReply" src/updater/UpdaterWorker.cpp

Repository: fernandotonon/QtMeshEditor

Length of output: 1144


🏁 Script executed:

# Search for finishDownloadWithError
rg -n "finishDownloadWithError" src/updater/UpdaterWorker.cpp

Repository: fernandotonon/QtMeshEditor

Length of output: 688


Cancellation does not reliably interrupt blocking sidecar downloads.

downloadUrlBlocking() uses a local reply and event loop but never assigns that reply to m_activeReply. During sidecar fetches, cancelActiveRequest() becomes a no-op because it checks if (!m_activeReply) and returns early, so cancellation cannot abort the blocking network request.

Suggested fix
 bool UpdaterWorker::downloadUrlBlocking(const QString& url,
                                         const QString& destPath,
                                         bool resume,
                                         QString* errorMessage)
 {
     qint64 existingSize = 0;
     if (resume && QFile::exists(destPath)) {
         existingSize = QFileInfo(destPath).size();
     }

     QNetworkRequest httpRequest = makeRequest(QUrl(url));
     if (existingSize > 0) {
         httpRequest.setRawHeader("Range",
                                  QByteArray("bytes=" + QByteArray::number(existingSize) + '-'));
     }

     QNetworkReply* reply = m_network->get(httpRequest);
+    m_activeReply = reply;
     QEventLoop loop;
     connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
     loop.exec();
+    if (m_activeReply == reply) {
+        m_activeReply = nullptr;
+    }

     const bool wasCancelled = reply->error() == QNetworkReply::OperationCanceledError;

Also propagate the cancelled state through finishDownloadWithError(sidecarError, true) at lines 308 and 317 so that cancellation is properly tracked during sidecar download failures, keeping controller state handling consistent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/updater/UpdaterWorker.cpp` around lines 177 - 203, The
downloadUrlBlocking() method creates a local QNetworkReply pointer but never
assigns it to the m_activeReply member variable, preventing
cancelActiveRequest() from actually canceling the blocking download since it
returns early when m_activeReply is null. To fix this, assign the reply returned
from m_network->get(httpRequest) to m_activeReply immediately after creation,
then clear it back to nullptr after the event loop completes (after
loop.exec()). Additionally, ensure the propagation of the cancelled state
through finishDownloadWithError() calls is consistent by passing the appropriate
cancellation flag to maintain proper controller state handling.

Comment on lines +256 to +278
const qint64 resumeOffset = reply->property("resumeOffset").toLongLong();
const bool append = resumeOffset > 0;
const QByteArray payload = reply->readAll();
const bool httpOk = reply->error() == QNetworkReply::NoError;
const int httpStatus =
reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
reply->deleteLater();

if (!httpOk) {
if (m_downloadAttempt + 1 < kMaxDownloadAttempts) {
startArtifactDownloadAttempt(m_downloadAttempt + 1);
return;
}
finishDownloadWithError(QStringLiteral("Download failed after %1 attempts")
.arg(kMaxDownloadAttempts));
return;
}

if (httpStatus == 416 && append) {
QFile::remove(m_downloadRequest.artifactPartPath);
startArtifactDownloadAttempt(0);
return;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name "UpdaterWorker.cpp" -type f

Repository: fernandotonon/QtMeshEditor

Length of output: 102


🏁 Script executed:

cat -n src/updater/UpdaterWorker.cpp | sed -n '250,300p'

Repository: fernandotonon/QtMeshEditor

Length of output: 2303


🏁 Script executed:

cat -n src/updater/UpdaterWorker.cpp | grep -A 30 "startArtifactDownloadAttempt"

Repository: fernandotonon/QtMeshEditor

Length of output: 3209


🏁 Script executed:

sed -n '1,256p' src/updater/UpdaterWorker.cpp | tail -100

Repository: fernandotonon/QtMeshEditor

Length of output: 3162


🏁 Script executed:

rg -i "206|416|range|partial.*content" src/updater/ --type cpp

Repository: fernandotonon/QtMeshEditor

Length of output: 283


🏁 Script executed:

rg "QNetworkReply" src/updater/UpdaterWorker.cpp | head -20

Repository: fernandotonon/QtMeshEditor

Length of output: 1043


🏁 Script executed:

cat -n src/updater/UpdaterWorker.cpp | sed -n '200,230p'

Repository: fernandotonon/QtMeshEditor

Length of output: 1090


🏁 Script executed:

rg "HttpStatusCodeAttribute|httpStatus" src/updater/UpdaterWorker.cpp -B 2 -A 5

Repository: fernandotonon/QtMeshEditor

Length of output: 631


🏁 Script executed:

cat -n src/updater/UpdaterWorker.cpp | sed -n '218,230p'

Repository: fernandotonon/QtMeshEditor

Length of output: 554


Check for server responses that ignore Range header and prevent data corruption.

When resuming from a partial .part file with a Range header, if the server responds with 200 OK (ignoring the Range request) instead of 206 Partial Content, the payload contains the full body. The current code blindly appends this full content to the existing .part file, duplicating all bytes and corrupting the download. Additionally, the 416 recovery check should be processed before generic error handling to ensure it's never bypassed. Reorder the 416 check before the error handler and add explicit detection for 200 OK responses when resuming to reset the download:

Suggested fix
-    const qint64 resumeOffset = reply->property("resumeOffset").toLongLong();
-    const bool append = resumeOffset > 0;
+    const qint64 resumeOffset = reply->property("resumeOffset").toLongLong();
+    bool append = resumeOffset > 0;
@@
-    if (!httpOk) {
+    if (httpStatus == 416 && append) {
+        QFile::remove(m_downloadRequest.artifactPartPath);
+        startArtifactDownloadAttempt(0);
+        return;
+    }
+
+    if (!httpOk) {
         if (m_downloadAttempt + 1 < kMaxDownloadAttempts) {
             startArtifactDownloadAttempt(m_downloadAttempt + 1);
             return;
         }
         finishDownloadWithError(QStringLiteral("Download failed after %1 attempts")
                                     .arg(kMaxDownloadAttempts));
         return;
     }
 
-    if (httpStatus == 416 && append) {
-        QFile::remove(m_downloadRequest.artifactPartPath);
-        startArtifactDownloadAttempt(0);
-        return;
+    if (append && httpStatus == 200) {
+        // Server ignored Range; restart from scratch to avoid duplicate payload.
+        QFile::remove(m_downloadRequest.artifactPartPath);
+        append = false;
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const qint64 resumeOffset = reply->property("resumeOffset").toLongLong();
const bool append = resumeOffset > 0;
const QByteArray payload = reply->readAll();
const bool httpOk = reply->error() == QNetworkReply::NoError;
const int httpStatus =
reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
reply->deleteLater();
if (!httpOk) {
if (m_downloadAttempt + 1 < kMaxDownloadAttempts) {
startArtifactDownloadAttempt(m_downloadAttempt + 1);
return;
}
finishDownloadWithError(QStringLiteral("Download failed after %1 attempts")
.arg(kMaxDownloadAttempts));
return;
}
if (httpStatus == 416 && append) {
QFile::remove(m_downloadRequest.artifactPartPath);
startArtifactDownloadAttempt(0);
return;
}
const qint64 resumeOffset = reply->property("resumeOffset").toLongLong();
bool append = resumeOffset > 0;
const QByteArray payload = reply->readAll();
const bool httpOk = reply->error() == QNetworkReply::NoError;
const int httpStatus =
reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
reply->deleteLater();
if (httpStatus == 416 && append) {
QFile::remove(m_downloadRequest.artifactPartPath);
startArtifactDownloadAttempt(0);
return;
}
if (!httpOk) {
if (m_downloadAttempt + 1 < kMaxDownloadAttempts) {
startArtifactDownloadAttempt(m_downloadAttempt + 1);
return;
}
finishDownloadWithError(QStringLiteral("Download failed after %1 attempts")
.arg(kMaxDownloadAttempts));
return;
}
if (append && httpStatus == 200) {
// Server ignored Range; restart from scratch to avoid duplicate payload.
QFile::remove(m_downloadRequest.artifactPartPath);
append = false;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/updater/UpdaterWorker.cpp` around lines 256 - 278, The code does not
handle the case where a server ignores a Range header request during resume
operations and responds with 200 OK (full body) instead of 206 Partial Content,
causing data corruption when the full payload is appended to an existing partial
file. Reorder the logic so the 416 status check (for Range Not Satisfiable)
executes before the generic httpOk error check. Additionally, add a new
condition that checks if httpStatus equals 200 while append is true (indicating
the server ignored the Range header), and in that case, remove the partial file
at m_downloadRequest.artifactPartPath and restart the download attempt from the
beginning using startArtifactDownloadAttempt. This prevents blindly appending
full responses to partial files.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant