Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
112 commits
Select commit Hold shift + click to select a range
be23cf0
List addons in store
feerrenrut May 24, 2022
c17174d
Update data on background thread.
feerrenrut May 24, 2022
f51cd7c
Installation of Add-ons
feerrenrut Jun 8, 2022
a8d2b58
Show more accurate status for items in Addon store.
feerrenrut Jun 10, 2022
17824e4
Serialize installation step for add-on store
feerrenrut Jul 22, 2022
140a2e6
Handle missing addonStore dir
feerrenrut Aug 4, 2022
abb6f41
fix translator comments
feerrenrut Aug 4, 2022
d1677a4
Add note about download chunk size
feerrenrut Aug 8, 2022
e70f86b
Merge remote-tracking branch 'origin/master' into addonStore-base
feerrenrut Dec 9, 2022
296961d
Review actions
feerrenrut Dec 12, 2022
a4346a9
make widths more comfortable
feerrenrut Dec 12, 2022
067a834
fix link conversion on Windows 7
feerrenrut Dec 12, 2022
68df9c5
Add requests to requirements.txt explicitly
feerrenrut Dec 12, 2022
173572d
Merge remote-tracking branch 'origin/master' into addonStore-base
seanbudd Feb 28, 2023
6ecdb0b
Add-on store: support for optional homepage and licenseURL (#14683)
seanbudd Mar 1, 2023
3ec6d46
create support for latest endpoint and dev channel (#14684)
seanbudd Mar 1, 2023
6e14a93
Addon store: Invalidate cache based on NVDA API Version (#14685)
seanbudd Mar 1, 2023
1d1693b
Merge remote-tracking branch 'origin/master' into addonStore-base
seanbudd Apr 3, 2023
9f24148
Addon store: Handle lost selection when filtering (#14777)
seanbudd Apr 3, 2023
d56f5eb
Addon store: Implement updating add-ons (#14778)
seanbudd Apr 3, 2023
666efa5
Merge remote-tracking branch 'origin/master' into addonStore-base
seanbudd Apr 4, 2023
3f4cf4d
Merge remote-tracking branch 'origin/master' into addonStore-base
seanbudd Apr 4, 2023
811f278
Add-on store: bypass the compatibility checks for an add-on (#14797)
seanbudd Apr 18, 2023
ef72ee2
Add-on store: Group by add-on status, install from external source (#…
seanbudd Apr 18, 2023
3ecd094
Merge remote-tracking branch 'origin/master' into addonStore-base
seanbudd Apr 18, 2023
7a8a653
Merge remote-tracking branch 'origin/master' into addonStore-base
seanbudd Apr 19, 2023
2feb0ff
Add-on store: Add setting to view incompatible add-ons available to d…
seanbudd Apr 20, 2023
7b970fa
Merge remote-tracking branch 'origin/master' into addonStore-base
seanbudd Apr 20, 2023
0c3a2d9
Add-on store: fix missing typing
seanbudd Apr 20, 2023
309cc8d
Add-on store: minor addonHandler fixup (#14845)
seanbudd Apr 20, 2023
5689f87
Add-on store: Notify user when download/fetch fails (#14846)
seanbudd Apr 20, 2023
587afd7
Add-on store: check SHA256 when downloading add-on (#14847)
seanbudd Apr 20, 2023
b00fec5
Add-on store: Fixup #14846
seanbudd Apr 21, 2023
31bdfae
Add-on store: add missing pgettext wrappings
seanbudd Apr 24, 2023
a50f52e
Add-on store: Disable add-ons manager (#14853)
seanbudd Apr 24, 2023
140908a
Add-on store: Display add-on incompatible reason (#14869)
seanbudd Apr 27, 2023
345e77e
Add-on store: Use close button instead of OK/Cancel (#14854)
seanbudd Apr 27, 2023
98a5988
Add-on Store: Add filter by channel (#14852)
seanbudd Apr 27, 2023
86017eb
Add-on store: Fixup translator comment
seanbudd Apr 27, 2023
539cd88
Merge remote-tracking branch 'origin/master' into addonStore-base
seanbudd Apr 27, 2023
66a6b34
Add-on store: fix up references to add-ons manager
seanbudd Apr 28, 2023
1c7c312
Add User Guide documentation
seanbudd Apr 28, 2023
6439cbb
Add-on store: filter legacy add-ons (#14882)
seanbudd May 1, 2023
5b70155
Add-on store: make state pickling safer for downgrading (#14868)
seanbudd May 1, 2023
c70809c
Merge remote-tracking branch 'origin/master' into addonStore-base
seanbudd May 1, 2023
8792796
Add-on store: Make add-on version contextual on views (#14891)
seanbudd May 1, 2023
f54fd1f
Add-on store: refactor and code cleanup
seanbudd May 2, 2023
e479e65
Add-on store: mark new code as private
seanbudd May 2, 2023
03567da
Add-on store: Minor fixups
seanbudd May 3, 2023
e7077b0
Merge branch 'addonStore-base' into addDocumentation
seanbudd May 3, 2023
ea847dd
Add-on Store: Update channel filter when changing status filter
seanbudd May 8, 2023
ac7e12f
Add-on store: Add accelerator key for add-ons list
seanbudd May 8, 2023
caf9b23
Add-on store: Fixup sorting - case/locale insensitive
seanbudd May 8, 2023
04c3363
Add-on store: Add accelerator key for other details
seanbudd May 8, 2023
66b8cda
Add-on store: Turn details links into buttons
seanbudd May 8, 2023
c3c3e3b
Add-on store: Show help for installed views only
seanbudd May 8, 2023
90a8933
Add-on store: Re-focus when opening add-on store dialog twice
seanbudd May 8, 2023
ade51ed
Add-on store: progress dialog for installing add-ons
seanbudd May 8, 2023
5eabc23
Add-on store: Hide disable button for removed addons
seanbudd May 8, 2023
bde9a69
Add-on store: Toggle accelerator key for action buttons with visibility
seanbudd May 8, 2023
ec016eb
Add-on store: Prioritise pending remove
seanbudd May 8, 2023
ec6526c
Add-on store: Use notebook tabs for status filters
seanbudd May 9, 2023
e99ca1e
Add-on store: maximize dialog
seanbudd May 9, 2023
438cfa6
Add-on store: Fix-up tab paging
seanbudd May 10, 2023
eb44aab
Add-on store: Include status tab in dialog title
seanbudd May 10, 2023
249e638
Add-on store: Show links as plain text
seanbudd May 10, 2023
d185497
Add-on store: fixup requires restart state
seanbudd May 15, 2023
54c87b6
Merge branch 'addDocumentation' into addonStore-base
seanbudd May 15, 2023
046837b
Add-on store: fix context menu
seanbudd May 16, 2023
7ee93f0
Add-on store: 'pending removal' string fix
seanbudd May 16, 2023
dbbef29
Add-on store: accelerate key for homepage
seanbudd May 16, 2023
975ebc1
Add-on store: encourage users to manually focus status control
seanbudd May 16, 2023
3811d38
Add-on store: add external channel
seanbudd May 16, 2023
c39c8ff
Add-on store: update status when removing add-ons
seanbudd May 16, 2023
19a28c7
Merge remote-tracking branch 'origin/master' into addonStore-base
seanbudd May 16, 2023
5a737a3
Add-on store: reset selection when changing views
seanbudd May 16, 2023
729c29c
Add-on store: fix up install paths
seanbudd May 17, 2023
0b0c6bd
Add-on store: create incompatible tab to replace disabled tab
seanbudd May 18, 2023
64dd10c
Add-on store: improve visual column widths
seanbudd May 18, 2023
4374cca
Add-on store: enable minimize button for resizable settings dialogs
seanbudd May 18, 2023
bbc7257
Add-on store: Improve installed add-on caching
seanbudd May 18, 2023
f22a54c
Add-on store: Filter by disable/enable
seanbudd May 18, 2023
b4223e9
Add-on store: display available add-ons if no installed add-ons
seanbudd May 18, 2023
764abb1
Add-on store: open context menu from enter on selected add-on
seanbudd May 18, 2023
ac0e5d5
Add-on store: add keys for column sorting
seanbudd May 18, 2023
6da7724
Add-on store: Add more accelerator keys
seanbudd May 23, 2023
00c8415
Add-on store: turn action buttons into context menu
seanbudd May 25, 2023
ba6396e
Add-on store: set default channel as stable for updatable add-ons
seanbudd May 25, 2023
50be870
Add-on store: Have 2 version fields: installed & available
seanbudd May 25, 2023
5b36e30
Add-on store: Move incompatible add-ons filter from settings dialog
seanbudd May 26, 2023
f75845d
Add-on store: Improve loading experience for available add-ons
seanbudd May 26, 2023
b1cccd4
Add-on store: fetch and cache using NVDA language
seanbudd May 26, 2023
ed096ef
Add-on store: Add 'All' option for enabled filter
seanbudd May 26, 2023
204ccb6
Add-on store: fix caching
seanbudd May 29, 2023
9e4152b
Add-on store: fix various install issues
seanbudd May 29, 2023
a0d2b2a
Add-on store: improve installed status checks for actions
seanbudd May 30, 2023
7009a95
Add-on store: focus list on tab page change
seanbudd May 30, 2023
9110c92
Add-on store: move search control to it's own line
seanbudd May 30, 2023
69d8f8f
Add-on store: fixup translation comments
seanbudd May 30, 2023
e6eb1f2
Add-on store: improve documentation and context help
seanbudd May 30, 2023
0b8f8e3
Add-on store: don't delete cache during upgrade
seanbudd May 30, 2023
35c8838
Add-on store: documentation fixups
seanbudd May 30, 2023
1aca3f7
Add-on store: filter remove add-ons to installed views
seanbudd May 31, 2023
81d1748
Add-on store: filter remove add-ons to installed views
seanbudd May 31, 2023
643fa00
Add-on store: address review feedback
seanbudd Jun 1, 2023
1849e1a
Add-on store: fix up docs
seanbudd Jun 1, 2023
becccf5
Merge remote-tracking branch 'origin/master' into addonStore-base
seanbudd Jun 1, 2023
62986cd
Merge remote-tracking branch 'origin/master' into addonStore-base
seanbudd Jun 1, 2023
60581ac
Add-on store: Improve disk management for launcher and secure copies …
seanbudd Jun 2, 2023
094085e
Merge remote-tracking branch 'origin/master' into addonStore-base
seanbudd Jun 2, 2023
36a95d9
Add-on store: update user guide
seanbudd Jun 5, 2023
4cb5654
fix spelling
seanbudd Jun 5, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 28 additions & 12 deletions source/addonAPIVersion.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#Copyright (C) 2018 NV Access Limited
#This file is covered by the GNU General Public License.
#See the file COPYING for more details.
from typing import Tuple

import buildVersion
import re
Expand All @@ -12,8 +13,14 @@
how the API has changed as well as the range of API versions supported by this build of NVDA
"""

CURRENT = (buildVersion.version_year, buildVersion.version_major, buildVersion.version_minor)
BACK_COMPAT_TO = (2022, 1, 0)
AddonApiVersionT = Tuple[int, int, int]

CURRENT: AddonApiVersionT = (
buildVersion.version_year,
buildVersion.version_major,
buildVersion.version_minor
)
BACK_COMPAT_TO: AddonApiVersionT = (2022, 1, 0)
"""
As BACK_COMPAT_TO is incremented, the changed / removed parts / or reasoning should be added below.
These only serve to act as a reminder, the changelog should be consulted for a comprehensive listing.
Expand All @@ -25,24 +32,33 @@
(0, 0, 0): API version zero, used to signify addons released prior to API version checks.
"""

#: Compiled regular expression to match an addon API version string.
#: Supports year.major.minor versions (e.g. 2018.1.1).
# Compiled regular expression to match an addon API version string.
# Supports year.major.minor versions (e.g. 2018.1.1).
# Although year and major are mandatory, minor is optional.
#: Resulting match objects expose three groups reflecting release year, release major, and release minor version,
# respectively.
# As minor is optional, the final group in the resulting match object may be None if minor is not provided in the original string. In this case it should be treated as being 0.
#: @type: RegexObject
ADDON_API_VERSION_REGEX = re.compile(r"^(0|\d{4})\.(\d)(?:\.(\d))?$")
# Resulting match objects expose three groups reflecting:
# - release year
# - release major
# - release minor
# As minor is optional, the final group in the resulting match object may be None if minor is not provided
# in the original string. In this case it should be treated as being 0.
# See also: L{tests.unit.test_addonVersionCheck.TestGetAPIVersionTupleFromString}
ADDON_API_VERSION_REGEX: re.Pattern = re.compile(r"^(0|\d{4})\.(\d)(?:\.(\d))?$")


def getAPIVersionTupleFromString(version):
"""Converts a string containing an NVDA version to a tuple of the form (versionYear, versionMajor, versionMinor)"""
def getAPIVersionTupleFromString(version: str) -> AddonApiVersionT:
"""
Converts a string containing an NVDA version to a tuple of the form:
(versionYear, versionMajor, versionMinor)
Raises ValueError when unable to parse version string.
# See also: L{tests.unit.test_addonVersionCheck.TestGetAPIVersionTupleFromString}
"""
match = ADDON_API_VERSION_REGEX.match(version)
if not match:
raise ValueError(version)
return tuple(int(i) if i is not None else 0 for i in match.groups())


def formatForGUI(versionTuple):
def formatForGUI(versionTuple: AddonApiVersionT) -> str:
"""Converts a version tuple to a string for displaying in the GUI
Examples:
- (2018, 1, 1) becomes "2018.1.1"
Expand Down
91 changes: 67 additions & 24 deletions source/addonHandler/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
# Joseph Lee, Babbage B.V., Arnold Loubriat, Łukasz Golonka
# This file is covered by the GNU General Public License.
# See the file COPYING for more details.

import enum
from abc import abstractmethod, ABC
import sys
import os.path
import gettext
Expand Down Expand Up @@ -51,15 +52,32 @@
isCLIParamKnown = extensionPoints.AccumulatingDecider(defaultDecision=False)


class AddonStateCategory(str, enum.Enum):
""" For backwards compatibility, the enums must remain functionally a string.
I.E. the following must be true:
> assert isinstance(AddonStateCategory.PENDING_REMOVE, str)
> assert AddonStateCategory.PENDING_REMOVE == "pendingRemovesSet"
"""
PENDING_REMOVE = "pendingRemovesSet"
PENDING_INSTALL = "pendingInstallsSet"
DISABLED = "disabledAddons"
PENDING_ENABLE = "pendingEnableSet"
PENDING_DISABLE = "pendingDisableSet"


class AddonsState(collections.UserDict):
"""Subclasses `collections.UserDict` to preserve backwards compatibility."""

_DEFAULT_STATE_CONTENT = {
"pendingRemovesSet": set(),
"pendingInstallsSet": set(),
"disabledAddons": set(),
"pendingEnableSet": set(),
"pendingDisableSet": set(),
"""
Subclasses `collections.UserDict` to preserve backwards compatibility.
self: typing.Dict[AddonStateCategory, typing.Set[str]]
AddonStateCategory string enums mapped to a set of the add-on "name/id" currently in that state.
"""

_DEFAULT_STATE_CONTENT: typing.Dict[AddonStateCategory, typing.Set[str]] = {
AddonStateCategory.PENDING_REMOVE: set(),
AddonStateCategory.PENDING_INSTALL: set(),
AddonStateCategory.DISABLED: set(),
AddonStateCategory.PENDING_ENABLE: set(),
AddonStateCategory.PENDING_DISABLE: set(),
}

@property
Expand Down Expand Up @@ -120,14 +138,16 @@ def cleanupRemovedDisabledAddons(self) -> None:
state = AddonsState()


def getRunningAddons():
def getRunningAddons() -> typing.Generator["Addon", None, None]:
""" Returns currently loaded add-ons.
"""
return getAvailableAddons(filterFunc=lambda addon: addon.isRunning)


def getIncompatibleAddons(
currentAPIVersion=addonAPIVersion.CURRENT,
backCompatToAPIVersion=addonAPIVersion.BACK_COMPAT_TO):
backCompatToAPIVersion=addonAPIVersion.BACK_COMPAT_TO
) -> typing.Generator["Addon", None, None]:
""" Returns a generator of the add-ons that are not compatible.
"""
return getAvailableAddons(
Expand Down Expand Up @@ -266,9 +286,16 @@ def getAvailableAddons(
_availableAddons[addon.path] = addon
return (addon for addon in _availableAddons.values() if not filterFunc or filterFunc(addon))

def installAddonBundle(bundle):
"""Extracts an Addon bundle in to a unique subdirectory of the user addons directory, marking the addon as needing install completion on NVDA restart."""
addonPath = os.path.join(globalVars.appArgs.configPath, "addons",bundle.manifest['name']+ADDON_PENDINGINSTALL_SUFFIX)

def installAddonBundle(bundle: "AddonBundle") -> "Addon":
""" Extracts an Addon bundle in to a unique subdirectory of the user addons directory,
marking the addon as needing 'install completion' on NVDA restart.
"""
addonPath = os.path.join(
globalVars.appArgs.configPath,
"addons",
bundle.manifest['name'] + ADDON_PENDINGINSTALL_SUFFIX
)
bundle.extract(addonPath)
addon=Addon(addonPath)
# #2715: The add-on must be added to _availableAddons here so that
Expand All @@ -288,29 +315,45 @@ def installAddonBundle(bundle):
class AddonError(Exception):
""" Represents an exception coming from the addon subsystem. """

class AddonBase(object):

class AddonBase(ABC):
"""The base class for functionality that is available both for add-on bundles and add-ons on the file system.
Subclasses should at least implement L{manifest}.
"""

@property
def name(self):
def name(self) -> str:
"""A unique name, the id of the add-on.
"""
return self.manifest['name']

@property
def version(self):
def version(self) -> str:
"""A display version. Not necessarily semantic
"""
return self.manifest['version']

@property
def minimumNVDAVersion(self):
def minimumNVDAVersion(self) -> typing.Tuple[int, int, int]:
return self.manifest.get('minimumNVDAVersion')

@property
def lastTestedNVDAVersion(self):
def lastTestedNVDAVersion(self) -> typing.Tuple[int, int, int]:
Comment thread
feerrenrut marked this conversation as resolved.
Resolved
return self.manifest.get('lastTestedNVDAVersion')

@property
@abstractmethod
def manifest(self) -> "AddonManifest":
...


class Addon(AddonBase):
""" Represents an Add-on available on the file system."""

@property
def manifest(self) -> "AddonManifest":
return self._manifest

def __init__(self, path):
""" Constructs an L{Addon} from.
@param path: the base directory for the addon data.
Expand All @@ -327,7 +370,7 @@ def __init__(self, path):
log.debug("Using manifest translation from %s", p)
translatedInput = open(p, 'rb')
break
self.manifest = AddonManifest(f, translatedInput)
self._manifest = AddonManifest(f, translatedInput)
if self.manifest.errors is not None:
_report_manifest_errors(self.manifest)
raise AddonError("Manifest file has errors.")
Expand Down Expand Up @@ -648,9 +691,8 @@ def extract(self, addonPath):
z.extract(info, addonPath)

@property
def manifest(self):
def manifest(self) -> "AddonManifest":
""" Gets the manifest for the represented Addon.
@rtype: AddonManifest
"""
return self._manifest

Expand Down Expand Up @@ -740,7 +782,7 @@ def __init__(self, input, translatedInput=None):
"""
super(AddonManifest, self).__init__(input, configspec=self.configspec, encoding='utf-8', default_encoding='utf-8')
self._errors = None
val = Validator({"apiVersion":validate_apiVersionString})
val = Validator({"apiVersion": validate_apiVersionString})
result = self.validate(val, copy=True, preserve_errors=True)
if result != True:
self._errors = result
Expand All @@ -766,7 +808,8 @@ def _validateApiVersionRange(self):
minRequiredVersion = self.get("minimumNVDAVersion")
return minRequiredVersion <= lastTested

def validate_apiVersionString(value):

def validate_apiVersionString(value) -> typing.Tuple[int, int, int]:
Comment thread
feerrenrut marked this conversation as resolved.
Resolved
from configobj.validate import ValidateError
if not value or value == "None":
return (0, 0, 0)
Expand Down
21 changes: 17 additions & 4 deletions source/addonHandler/addonVersionCheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,38 @@
# Copyright (C) 2018 NV Access Limited
# This file is covered by the GNU General Public License.
# See the file COPYING for more details.

from typing_extensions import Protocol # Python 3.8 adds native support
import addonAPIVersion

def hasAddonGotRequiredSupport(addon, currentAPIVersion=addonAPIVersion.CURRENT):

class SupportsVersionCheck(Protocol):
""" Examples implementing this protocol include:
- addonHandler.AddonBundle
- addonStore.models.AddonDetailsModel
"""
minimumNVDAVersion: addonAPIVersion.AddonApiVersionT
lastTestedNVDAVersion: addonAPIVersion.AddonApiVersionT


def hasAddonGotRequiredSupport(
addon: SupportsVersionCheck,
currentAPIVersion=addonAPIVersion.CURRENT
):
"""True if NVDA provides the add-on with an API version high enough to meet the add-on's minimum requirements
"""
minVersion = addon.minimumNVDAVersion
return minVersion <= currentAPIVersion


def isAddonTested(addon, backwardsCompatToVersion=addonAPIVersion.BACK_COMPAT_TO):
def isAddonTested(addon: SupportsVersionCheck, backwardsCompatToVersion=addonAPIVersion.BACK_COMPAT_TO):
"""True if this add-on is tested for the given API version.
By default, the current version of NVDA is evaluated.
"""
return addon.lastTestedNVDAVersion >= backwardsCompatToVersion


def isAddonCompatible(
addon,
addon: SupportsVersionCheck,
currentAPIVersion=addonAPIVersion.CURRENT,
backwardsCompatToVersion=addonAPIVersion.BACK_COMPAT_TO
):
Expand Down
4 changes: 4 additions & 0 deletions source/addonStore/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# A part of NonVisual Desktop Access (NVDA)
# Copyright (C) 2022 NV Access Limited
# This file is covered by the GNU General Public License.
# See the file COPYING for more details.
Loading