Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add fully automatic add-on updates #17682

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open

Add fully automatic add-on updates #17682

wants to merge 4 commits into from

Conversation

seanbudd
Copy link
Member

@seanbudd seanbudd commented Feb 7, 2025

Link to issue number:

Closes #3208
Closes #17162

Summary of the issue:

To update add-ons automatically, users must click-through a dialog to confirm they wish to update all add-ons.
Instead, this can be done in the background, and the user just needs to confirm whether to restart NVDA once add-on updates are complete.

Additionally:

  • Users couldn't automatically update to incompatible add-ons. this is useful for alpha/beta testers testing add-on compatibility in breaking releases
  • The update notification was annoying - it queued too late and users should get a ding when it appeared.
  • If the add-on store server failed, users didn't receive displayable error messages when using automatic update notification
  • Since Ability to download and install updates from other channels #10679, the update notification panel will list all available updates for all channels. The update all button would update the add-on for each channel. Instead only the latest add-on version for any channel should be used/listed.

Description of user facing changes

  • Added the ability to automatically update add-ons in the background.
    This can be changed in the "automatic updates" section.
  • Added the ability to update to incompatible add-ons. users can enable this via a new setting in the add-on store panel.
  • The update notification is now queued sooner (1 minute not 3 minutes) and provides a ding before focusing the panel
  • the update notification panel now only lists the most recent version of an add-on, rather than showing all add-ons that are newer across channels
  • Server errors now display a message if the update notification fails to fetch data

Description of development approach

  • Refactored update status tracking so devs can compare add-ons to see which is newer more easily
  • Re-designed message box tracking to make it work for pseudo-modals, e.g. the background update process
  • use a shorter starting offset for daily scheduled jobs

Testing strategy:

  • Installed an old add-on
  • Tested automatic update notifications
  • Tested background automatic updates
  • Tested both of these without a server connection

Known issues with pull request:

None

Code Review Checklist:

  • Documentation:
    • Change log entry
    • User Documentation
    • Developer / Technical Documentation
    • Context sensitive help for GUI changes
  • Testing:
    • Unit tests
    • System (end to end) tests
    • Manual testing
  • UX of all users considered:
    • Speech
    • Braille
    • Low Vision
    • Different web browsers
    • Localization in other languages / culture than English
  • API is compatible with existing add-ons.
  • Security precautions taken.

Summary by CodeRabbit

  • New Features
    • Introduced an "Update Automatically" option for managing add-on updates in the background.
    • Added a setting to enable automatic updates for add-ons marked as incompatible.
    • Improved error handling and feedback during the update process.
  • Documentation
    • Updated the User Guide to detail the new update options and behaviors.

@seanbudd seanbudd requested review from a team as code owners February 7, 2025 08:52
@seanbudd
Copy link
Member Author

seanbudd commented Feb 7, 2025

@coderabbitai review

Copy link
Contributor

coderabbitai bot commented Feb 7, 2025

Walkthrough

The changes modify the update logic for NVDA add-ons across several modules. The _DataManager._addonsPendingUpdate method now accepts an optional error callback and tracks pending updates using a dictionary. The status module has been refactored with a new _canUpdateAddon function to determine update eligibility via type checks and version comparisons. Configuration files have been updated to activate an automatic update option and introduce an allowIncompatibleUpdates setting. GUI components are enhanced with improved error handling, background update processing via threading, and refined message box management. Scheduling logic and tests now incorporate a new start offset, and the user guide documentation has been updated accordingly.

Changes

File(s) Change Summary
source/addonStore/dataManager.py,
source/addonStore/models/status.py
Enhanced update logic: added an optional error parameter in _addonsPendingUpdate, switched update tracking from a list to a dictionary, and refactored update status checks using the new _canUpdateAddon function.
source/config/configFlags.py,
source/config/configSpec.py
Updated configuration: activated the UPDATE member, extended the automaticUpdates option to include "update", and added the allowIncompatibleUpdates setting with updated option values.
source/gui/addonStoreGui/controls/messageDialogs.py Enhanced update dialog functionality: added the static method handleDisplayableError, refactored _checkForUpdatableAddons with a match statement, and introduced the background _updateAddons function utilizing threading.
source/gui/message.py,
source/gui/settingsDialogs.py
Improved UI components: introduced the _countAsMessageBox decorator for centralized management of message-box counts and updated the AddonStorePanel with a new checkbox for incompatible updates and revised labels.
source/utils/schedule.py,
tests/unit/test_util/test_schedule.py
Modified scheduling: introduced the START_MINUTE_OFFSET constant and updated daily job time offsets; tests now use the TODAY_AT_MIDNIGHT constant to verify offset logic.
user_docs/en/userGuide.md Updated documentation: added new automatic update options and a setting for allowing incompatible add-on upgrades.

Sequence Diagram(s)

sequenceDiagram
    participant U as UpdatableAddonsDialog
    participant D as _DataManager
    participant M as Status Module (_canUpdateAddon)
    participant BG as Background Thread
    participant E as Error Handler

    U->>D: Call _addonsPendingUpdate(onDisplayableError)
    D->>M: Check addon update status
    M-->>D: Return update status
    D-->>U: Return dict of pending updates
    U->>U: Evaluate automaticUpdates setting (match statement)
    alt Automatic Update
        U->>BG: Spawn background _updateAddons(addons)
    else Notification
        U->>U: Display update notification (with error handling)
    end
    BG->>E: If error occurs, call handleDisplayableError
Loading

Assessment against linked issues

Objective Addressed Explanation
Automatically update add-ons (#3208)
Immediate update notification with audible alert (#17162) Sound alert is not explicitly implemented; immediate display logic remains unclear.

Possibly related PRs

Suggested labels

conceptApproved, api-breaking-change

Suggested reviewers

  • Qchristensen
  • SaschaCowley
✨ Finishing Touches
  • 📝 Generate Docstrings (Beta)

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (5)
source/addonStore/dataManager.py (1)

355-358: Consider adding a docstring.
A brief docstring would help explain the new “onDisplayableError” parameter and its usage in this method.

source/gui/addonStoreGui/controls/messageDialogs.py (1)

601-624: Background update approach may benefit from event-driven logic.
Currently, the loop uses a short sleep, which can be acceptable in smaller codebases. An event or condition-based wait could improve performance in more significant scenarios. Otherwise, the logic and concurrency approach is valid.

user_docs/en/userGuide.md (3)

3079-3086: Verify options list formatting

The options list should be consistently formatted. Consider using a bullet list instead of a table for better readability.

-|Options |Notify (Default), Update Automatically, Disabled |
-|Default |Notify |
+Options:
+* Notify (Default)
+* Update Automatically 
+* Disabled

3088-3106: Verify behavior table formatting

The behavior table should be consistently formatted with other similar tables in the documentation. Consider adding a header row.

-|Option |Behaviour |
-|---|---|
+| Option | Behavior |
+|---------|-----------|

3110-3117: Verify cross-reference links

The text references "automatic updates" but doesn't include a link to the relevant section. Consider adding a cross-reference link for better navigation.

-Enabling this may be useful for switching over to using add-on breaking releases (the first release of the year).
+Enabling this may be useful for switching over to using add-on breaking releases (the first release of the year). See [Automatic add-on updates](#AutomaticAddonUpdates) for more information.
📜 Review details

Configuration used: .coderabbit.yml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ffd1cf5 and cf5fb94.

📒 Files selected for processing (10)
  • source/addonStore/dataManager.py (3 hunks)
  • source/addonStore/models/status.py (3 hunks)
  • source/config/configFlags.py (2 hunks)
  • source/config/configSpec.py (1 hunks)
  • source/gui/addonStoreGui/controls/messageDialogs.py (5 hunks)
  • source/gui/message.py (3 hunks)
  • source/gui/settingsDialogs.py (3 hunks)
  • source/utils/schedule.py (2 hunks)
  • tests/unit/test_util/test_schedule.py (2 hunks)
  • user_docs/en/userGuide.md (2 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
`**/*`: Focus on code smells, logic errors, edge cases, mis...

**/*: Focus on code smells, logic errors, edge cases, missing test cases, security flaws and serious issues. Avoid commenting on minor issues such as linting, formatting and style issues. This project uses tabs instead of spaces, do not suggest usage of spaces over tabs. Are there any 'red flags' in this code that might warrant closer investigation from a security standpoint? Explain what makes them suspicious. When providing code suggestions, particularly when requested, ensure GitHub's suggestion format is used, i.e.: suggestion <code changes>

  • source/config/configFlags.py
  • tests/unit/test_util/test_schedule.py
  • source/gui/message.py
  • source/config/configSpec.py
  • source/utils/schedule.py
  • source/addonStore/models/status.py
  • source/gui/addonStoreGui/controls/messageDialogs.py
  • source/addonStore/dataManager.py
  • user_docs/en/userGuide.md
  • source/gui/settingsDialogs.py
`**/*.py`: _, pgettext, ngettext, and ngettext are defined ...

**/*.py: _, pgettext, ngettext, and ngettext are defined globally, errors for this being undefined can be ignored.

  • source/config/configFlags.py
  • tests/unit/test_util/test_schedule.py
  • source/gui/message.py
  • source/config/configSpec.py
  • source/utils/schedule.py
  • source/addonStore/models/status.py
  • source/gui/addonStoreGui/controls/messageDialogs.py
  • source/addonStore/dataManager.py
  • source/gui/settingsDialogs.py
`**/*.md`: Focus on grammar, spelling, and punctuation. Als...

**/*.md: Focus on grammar, spelling, and punctuation. Also consider readability and clarity of contents. Ensure that changes follow the style of one sentence per line, suggest changes if this is not met.

  • user_docs/en/userGuide.md
📓 Learnings (1)
source/config/configFlags.py (1)
Learnt from: seanbudd
PR: nvaccess/nvda#16636
File: source/config/configFlags.py:209-209
Timestamp: 2024-11-12T04:38:46.468Z
Learning: The `UPDATE` state in the `AddonsAutomaticUpdate` class is commented out and should be implemented in the future, specifically linked to issue #3208.
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Analyze (python)
🔇 Additional comments (29)
source/utils/schedule.py (2)

76-80: LGTM! Well-documented constant with clear purpose.

The START_MINUTE_OFFSET constant provides a sensible 1-minute delay for the first scheduled job after NVDA starts, allowing time for initialization.


96-100: Verify the offset calculation for edge cases.

The offset calculation looks correct, but let's verify it handles all edge cases properly:

  1. When startTime.minute is close to 60
  2. When multiple jobs are scheduled (scheduledDailyJobCount > 0)
✅ Verification successful

🏁 Script executed:

#!/bin/bash
# Description: Search for any reported issues or bugs related to job scheduling timing
# Look for patterns like "schedule", "timing", "delay", "offset" in issue comments and commit messages

rg -i "schedule|timing|delay|offset" $(git log --pretty=format:%H -n 100 | xargs -I{} echo "{}^!") 

Length of output: 0


I'll gather additional context on the offset calculation by inspecting the relevant file and the definitions of the constants. Let's run a new shell script that prints the surrounding code from source/utils/schedule.py (lines 80–130) and also searches for the definitions of both START_MINUTE_OFFSET and DAILY_JOB_MINUTE_OFFSET.


🏁 Script executed:

#!/bin/bash
echo "------ Displaying lines 80-130 from source/utils/schedule.py ------"
sed -n '80,130p' source/utils/schedule.py

echo "------ Searching for START_MINUTE_OFFSET definitions ------"
rg "START_MINUTE_OFFSET" -n .

echo "------ Searching for DAILY_JOB_MINUTE_OFFSET definitions ------"
rg "DAILY_JOB_MINUTE_OFFSET" -n .

Length of output: 3408


Offset calculation handles edge cases correctly.

After reviewing the _calculateDailyTimeOffset() implementation and the associated constants (START_MINUTE_OFFSET = 1 and DAILY_JOB_MINUTE_OFFSET = 3) along with the rollover logic that adjusts both minutes (using % 60) and hours (by adding the quotient and taking % 24), it’s clear that:

  • Minutes near 60 are correctly rolled over into hours.
  • Increasing scheduledDailyJobCount (i.e. adding multiples of DAILY_JOB_MINUTE_OFFSET) is properly accounted for by the modulo operations.

Additionally, unit tests in tests/unit/test_util/test_schedule.py verify these behaviors, ensuring that expected offsets (e.g., for minute values near 60) are returned correctly.

tests/unit/test_util/test_schedule.py (4)

27-28: LGTM! Good refactoring to reduce duplication.

The TODAY_AT_MIDNIGHT constant effectively centralizes the midnight time reference used across multiple test methods.


121-127: LGTM! Test verifies the initial job delay.

The test correctly verifies that the first job is scheduled with the new START_MINUTE_OFFSET delay.


128-137: LGTM! Test verifies subsequent job scheduling.

The test correctly verifies that the second job is scheduled with both START_MINUTE_OFFSET and DAILY_JOB_MINUTE_OFFSET delays.


138-156: LGTM! Comprehensive edge case testing.

The tests thoroughly verify:

  1. Minute overflow (11:59 → 12:xx)
  2. Day overflow (23:59 → 00:xx)
source/addonStore/dataManager.py (3)

40-40: Import usage looks fine.
No issues found with the newly imported symbols.


360-365: Logic for allowing incompatible updates looks correct.
The condition seamlessly switches between fetching latest vs. compatible add-ons based on the user’s config.


381-388: ⚠️ Potential issue

Duplicate assignment likely indicates a logic error.
When an add-on name already exists in the dictionary, lines 385 and 387 both overwrite it unconditionally. This defeats the purpose of the if-check on line 384. To retain only the newer add-on when '_canUpdateAddon' is true, consider:

 if addon.name in addonsPendingUpdate:
     if _canUpdateAddon(addon, addonsPendingUpdate[addon.name]):
         addonsPendingUpdate[addon.name] = addon
 else:
     addonsPendingUpdate[addon.name] = addon

Likely invalid or redundant comment.

source/addonStore/models/status.py (4)

2-2: No specific issues with updated copyright.


26-26: No issues with expanded import.


246-281: Update check logic looks good overall.
The function correctly distinguishes between store-based and side-loaded add-ons, returning None when version parsing fails.


283-330: Overall update status logic is appropriate.
Correctly interprets the result of '_canUpdateAddon' and provides relevant statuses, including REPLACE_SIDE_LOAD for unknown version comparisons.

source/gui/addonStoreGui/controls/messageDialogs.py (7)

6-7: New imports appear consistent with usage.
Imports for threading, sleep, and additional message handling are aligned with the new background updates functionality.

Also applies to: 35-35, 38-38


372-372: New class-level error callback definition is clear.
This provides a convenient hook for error handling in background operations.


558-560: Static method for error handling is straightforward.
Properly invokes 'displayError' on the main frame.


571-574: Registering/unregistering callbacks is cleanly done.
This ensures consistent handling of displayable errors within a specific scope.


575-578: Early return when no updates are found is sensible.
Prevents unnecessary UI prompts or threads from being started when there’s nothing to update.


579-580: Logging found updates is straightforward.
No issues identified.


581-599: Match statement cleanly handles various update strategies.
Logic for NOTIFY vs. UPDATE vs. fallback error is well-structured.

source/config/configFlags.py (1)

238-248: LGTM! Implementation of automatic updates feature.

The changes correctly implement the UPDATE member in the AddonsAutomaticUpdate class, which was previously commented out. This aligns with the PR objectives to enable automatic add-on updates.

source/config/configSpec.py (1)

339-340: LGTM! Configuration options for automatic updates.

The changes correctly add:

  1. "update" as a valid option for automaticUpdates
  2. allowIncompatibleUpdates setting to control incompatible add-on updates

This aligns with the PR objectives to enable automatic updates and provide control over incompatible add-on updates.

source/gui/message.py (2)

55-74: LGTM! Thread-safe message box counter management.

The new _countAsMessageBox decorator correctly manages the message box counter in a thread-safe way:

  1. Uses _messageBoxCounterLock for thread safety
  2. Ensures counter is decremented in finally block even if an exception occurs
  3. Properly wraps the decorated function using wraps to preserve metadata

77-77: LGTM! Decorator application.

The displayDialogAsModal function is correctly decorated with @_countAsMessageBox() to manage the message box counter.

source/gui/settingsDialogs.py (3)

3247-3247: LGTM! Label updated to reflect automatic updates functionality.

The label change from "Update notifications" to "Automatic updates" better reflects the new automatic add-on update functionality.


3271-3277: LGTM! New checkbox added for incompatible add-on updates.

The implementation:

  • Adds UI control with clear label
  • Properly binds help event
  • Correctly initializes from config value

3354-3354: LGTM! Config value properly saved.

The new incompatible updates setting is correctly saved to the configuration.

user_docs/en/userGuide.md (2)

3074-3076: Added new automatic update option for add-ons

The documentation introduces a new "Update Automatically" option for add-on updates, which will automatically update add-ons in the background and prompt for an NVDA restart when updates are complete.


3110-3117: Added new setting for allowing incompatible add-on updates

The documentation adds a new setting "Allow automatic updates to install incompatible add-ons" which enables automatic updates to add-ons that may not be fully compatible with the current NVDA version. The explanation clearly outlines that this is disabled by default and explains the use case for alpha/beta testers.

@CyrilleB79
Copy link
Collaborator

In #3208, there have been security concerns regarding automatic update. How are they taken in consideration here?

Given add-ons have a very wide access to the user's system, we can imagine such a attack:

  1. The attacker creates a simple add-on targeting a wide range of people, e.g. very popular addition, enhanced support of a mainstream application, etc. They can also take an existing popular free add-on that is not in the store, improve it a bit and submit it to the store.
  2. Waits a few weeks / months
  3. Add a malicious feature in the add-on

This risk already exists in NVDA, and users are warned to install only add-ons they trust. But automatic update makes the risk more important IMO. In comparison, browser extensions, that may also been updated automatically, have not such a wide access to the system.

If NV Access chooses to keep this possibility anyway, I'd suggest:

  • That NVDA does not ask to restart after automatic add-on update so that the workflow is not interrupted
  • That after the add-ons are installed and NVDA restarted, the user be informed of the updates that have been done.

@seanbudd
Copy link
Member Author

seanbudd commented Feb 7, 2025

I don't think it's a serious security risk - users are just as likely to click through the notification or manually install a malicious add-on in these cases.

You raise a good point about the restart notification, but I think a message after installation is equally as invasive as the update notification itself.
Perhaps it's better to just be fully silent?
Instead you just get a ui.message when the updates start and end.
Your add-on updates get installed when you later decide to restart NVDA and you just don't have to think about it.
I might consider removing the server error messages too in favour of failing silently.

@CyrilleB79
Copy link
Collaborator

If we accept the risk of current add-on framework (as today) and their automatic update, the best UX would be to use Windows notifications IMO. People were already used to this in @josephsl's Add-on Updater. Unfortunately, Windows notifications are not available in Windows 8.1 so a fallback solution would be needed for people running this OS.

Also, one interesting point going with automatic add-on update, would be to store somewhere the last install date of an add-on. This way, people experimenting a regression or strange behaviour could look at add-on install dates and check if one matches the period they noticed the unexpected behaviour.

@wmhn1872265132
Copy link
Contributor

For the Add-on of Enabled (incompatible) it will be disabled after update, I think it should be kept enabled, or the settings should be allowed to remain enabled, otherwise it will lose the meaning of allowing automatic update of these Add-ons

@wmhn1872265132
Copy link
Contributor

It is recommended to list the updated Add-ons information in the dialog box that prompts to restart after the Add-ons update is completed, so that users can easily understand which Add-ons have been updated.

@seanbudd seanbudd added the conceptApproved Similar 'triaged' for issues, PR accepted in theory, implementation needs review. label Feb 10, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
conceptApproved Similar 'triaged' for issues, PR accepted in theory, implementation needs review.
Projects
None yet
3 participants