From bb0a20e1497b6940ae65db2ea0611acd94609918 Mon Sep 17 00:00:00 2001 From: Sean Budd Date: Thu, 1 Jun 2023 16:29:57 +1000 Subject: [PATCH 1/2] Add-on store: Improve cache management for launcher and secure copies --- source/_addonStore/dataManager.py | 11 +++++++++++ source/addonHandler/__init__.py | 9 +++++++-- source/gui/__init__.py | 1 + 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/source/_addonStore/dataManager.py b/source/_addonStore/dataManager.py index de85a32532b..2c242a089b9 100644 --- a/source/_addonStore/dataManager.py +++ b/source/_addonStore/dataManager.py @@ -22,6 +22,7 @@ import addonAPIVersion from baseObject import AutoPropertyObject +import config from core import callLater import globalVars import languageHandler @@ -54,6 +55,9 @@ def initialize(): global addonDataManager + if config.isAppX: + log.info("Add-ons not supported when running as a Windows Store application") + return log.debug("initializing addonStore data manager") addonDataManager = _DataManager() @@ -64,6 +68,7 @@ class _DataManager: _cachePeriod = timedelta(hours=6) def __init__(self): + self._shouldCacheToDisk = not (globalVars.appArgs.secure or globalVars.appArgs.launcher) cacheDirLocation = os.path.join(globalVars.appArgs.configPath, "addonStore") self._lang = languageHandler.getLanguage() self._preferredChannel = Channel.ALL @@ -104,6 +109,8 @@ def _getLatestAddonsDataForVersion(self, apiVersion: str) -> Optional[bytes]: return response.content def _cacheCompatibleAddons(self, addonData: str, fetchTime: datetime): + if not self._shouldCacheToDisk: + return if not addonData: return cacheData = { @@ -116,6 +123,8 @@ def _cacheCompatibleAddons(self, addonData: str, fetchTime: datetime): json.dump(cacheData, cacheFile, ensure_ascii=False) def _cacheLatestAddons(self, addonData: str, fetchTime: datetime): + if not self._shouldCacheToDisk: + return if not addonData: return cacheData = { @@ -226,6 +235,8 @@ def _deleteCacheInstalledAddon(self, addonId: str): os.remove(addonCachePath) def _cacheInstalledAddon(self, addonData: AddonStoreModel): + if not self._shouldCacheToDisk: + return if not addonData: return addonCachePath = os.path.join(self._installedAddonDataCacheDir, f"{addonData.addonId}.json") diff --git a/source/addonHandler/__init__.py b/source/addonHandler/__init__.py index c136ff74835..1ea8c5195d5 100644 --- a/source/addonHandler/__init__.py +++ b/source/addonHandler/__init__.py @@ -131,6 +131,10 @@ def removeStateFile(self) -> None: def save(self) -> None: """Saves content of the state to a file unless state is empty in which case this would be pointless.""" + if globalVars.appArgs.secure or globalVars.appArgs.launcher: + log.error("NVDA should not write to disk from secure mode or launcher", stack_info=True) + return + if any(self.values()): try: # #9038: Python 3 requires binary format when working with pickles. @@ -216,8 +220,9 @@ def initialize(): # #3090: Are there add-ons that are supposed to not run for this session? disableAddonsIfAny() getAvailableAddons(refresh=True, isFirstLoad=True) - state.cleanupRemovedDisabledAddons() - state.save() + if not (globalVars.appArgs.secure or globalVars.appArgs.launcher): + state.cleanupRemovedDisabledAddons() + state.save() initializeModulePackagePaths() diff --git a/source/gui/__init__.py b/source/gui/__init__.py index b9d62fb44eb..93c1423305e 100644 --- a/source/gui/__init__.py +++ b/source/gui/__init__.py @@ -335,6 +335,7 @@ def onAddonsManagerCommand(self, evt: wx.MenuEvent): blockAction.Context.MODAL_DIALOG_OPEN, blockAction.Context.WINDOWS_LOCKED, blockAction.Context.WINDOWS_STORE_VERSION, + blockAction.Context.RUNNING_LAUNCHER, ) def onAddonStoreCommand(self, evt: wx.MenuEvent): self.prePopup() From 65a48a827b3b2565e7170351d8d69b82c1e69940 Mon Sep 17 00:00:00 2001 From: Sean Budd Date: Fri, 2 Jun 2023 09:58:26 +1000 Subject: [PATCH 2/2] Create NVDA state variable --- source/NVDAState.py | 10 ++++++++++ source/_addonStore/dataManager.py | 8 ++++---- source/addonHandler/__init__.py | 5 +++-- source/config/__init__.py | 9 ++------- source/gui/__init__.py | 11 +++++++---- 5 files changed, 26 insertions(+), 17 deletions(-) diff --git a/source/NVDAState.py b/source/NVDAState.py index 0283d57f6d7..1abaec31760 100644 --- a/source/NVDAState.py +++ b/source/NVDAState.py @@ -46,6 +46,16 @@ def _setExitCode(exitCode: int) -> None: globalVars.exitCode = exitCode +def shouldWriteToDisk() -> bool: + """ + Never save config or state if running securely or if running from the launcher. + When running from the launcher we don't save settings because the user may decide not to + install this version, and these settings may not be compatible with the already + installed version. See #7688 + """ + return not (globalVars.appArgs.secure or globalVars.appArgs.launcher) + + class _TrackNVDAInitialization: """ During NVDA initialization, diff --git a/source/_addonStore/dataManager.py b/source/_addonStore/dataManager.py index 2c242a089b9..edea266e98a 100644 --- a/source/_addonStore/dataManager.py +++ b/source/_addonStore/dataManager.py @@ -27,6 +27,7 @@ import globalVars import languageHandler from logHandler import log +import NVDAState from .models.addon import ( AddonStoreModel, @@ -68,7 +69,6 @@ class _DataManager: _cachePeriod = timedelta(hours=6) def __init__(self): - self._shouldCacheToDisk = not (globalVars.appArgs.secure or globalVars.appArgs.launcher) cacheDirLocation = os.path.join(globalVars.appArgs.configPath, "addonStore") self._lang = languageHandler.getLanguage() self._preferredChannel = Channel.ALL @@ -109,7 +109,7 @@ def _getLatestAddonsDataForVersion(self, apiVersion: str) -> Optional[bytes]: return response.content def _cacheCompatibleAddons(self, addonData: str, fetchTime: datetime): - if not self._shouldCacheToDisk: + if not NVDAState.shouldWriteToDisk(): return if not addonData: return @@ -123,7 +123,7 @@ def _cacheCompatibleAddons(self, addonData: str, fetchTime: datetime): json.dump(cacheData, cacheFile, ensure_ascii=False) def _cacheLatestAddons(self, addonData: str, fetchTime: datetime): - if not self._shouldCacheToDisk: + if not NVDAState.shouldWriteToDisk(): return if not addonData: return @@ -235,7 +235,7 @@ def _deleteCacheInstalledAddon(self, addonId: str): os.remove(addonCachePath) def _cacheInstalledAddon(self, addonData: AddonStoreModel): - if not self._shouldCacheToDisk: + if not NVDAState.shouldWriteToDisk(): return if not addonData: return diff --git a/source/addonHandler/__init__.py b/source/addonHandler/__init__.py index 1ea8c5195d5..19b6cc5d782 100644 --- a/source/addonHandler/__init__.py +++ b/source/addonHandler/__init__.py @@ -38,6 +38,7 @@ import winKernel import addonAPIVersion import importlib +import NVDAState from types import ModuleType from _addonStore.models.status import AddonStateCategory, SupportsAddonState @@ -131,7 +132,7 @@ def removeStateFile(self) -> None: def save(self) -> None: """Saves content of the state to a file unless state is empty in which case this would be pointless.""" - if globalVars.appArgs.secure or globalVars.appArgs.launcher: + if not NVDAState.shouldWriteToDisk(): log.error("NVDA should not write to disk from secure mode or launcher", stack_info=True) return @@ -220,7 +221,7 @@ def initialize(): # #3090: Are there add-ons that are supposed to not run for this session? disableAddonsIfAny() getAvailableAddons(refresh=True, isFirstLoad=True) - if not (globalVars.appArgs.secure or globalVars.appArgs.launcher): + if NVDAState.shouldWriteToDisk(): state.cleanupRemovedDisabledAddons() state.save() initializeModulePackagePaths() diff --git a/source/config/__init__.py b/source/config/__init__.py index 6725e7a7601..a24ce168c9c 100644 --- a/source/config/__init__.py +++ b/source/config/__init__.py @@ -538,11 +538,6 @@ def __init__(self): self._shouldHandleProfileSwitch: bool = True self._pendingHandleProfileSwitch: bool = False self._suspendedTriggers: Optional[List[ProfileTrigger]] = None - # Never save the config if running securely or if running from the launcher. - # When running from the launcher we don't save settings because the user may decide not to - # install this version, and these settings may not be compatible with the already - # installed version. See #7688 - self._shouldWriteProfile: bool = not (globalVars.appArgs.secure or globalVars.appArgs.launcher) self._initBaseConf() #: Maps triggers to profiles. self.triggersToProfiles: Optional[Dict[ProfileTrigger, ConfigObj]] = None @@ -600,7 +595,7 @@ def _loadConfig(self, fn, fileError=False): profile.newlines = "\r\n" profileCopy = deepcopy(profile) try: - writeProfileFunc = self._writeProfileToFile if self._shouldWriteProfile else None + writeProfileFunc = self._writeProfileToFile if NVDAState.shouldWriteToDisk() else None profileUpgrader.upgrade(profile, self.validator, writeProfileFunc) except Exception as e: # Log at level info to ensure that the profile is logged. @@ -704,7 +699,7 @@ def save(self): """ # #7598: give others a chance to either save settings early or terminate tasks. pre_configSave.notify() - if not self._shouldWriteProfile: + if not NVDAState.shouldWriteToDisk(): log.info("Not writing profile, either --secure or --launcher args present") return try: diff --git a/source/gui/__init__.py b/source/gui/__init__.py index 93c1423305e..56b428ddfeb 100644 --- a/source/gui/__init__.py +++ b/source/gui/__init__.py @@ -469,13 +469,16 @@ def __init__(self, frame: MainFrame): self.menu_tools_toggleBrailleViewer.Check(brailleViewer.isBrailleViewerActive()) brailleViewer.postBrailleViewerToolToggledAction.register(frame.onBrailleViewerChangedState) + if not config.isAppX and NVDAState.shouldWriteToDisk(): + # Translators: The label of a menu item to open the Add-on store + item = menu_tools.Append(wx.ID_ANY, _("Add-on &store...")) + self.Bind(wx.EVT_MENU, frame.onAddonStoreCommand, item) + if not globalVars.appArgs.secure and not config.isAppX: # Translators: The label for the menu item to open NVDA Python Console. item = menu_tools.Append(wx.ID_ANY, _("Python console")) self.Bind(wx.EVT_MENU, frame.onPythonConsoleCommand, item) - # Translators: The label of a menu item to open the Add-on store - item = menu_tools.Append(wx.ID_ANY, _("Add-on &store...")) - self.Bind(wx.EVT_MENU, frame.onAddonStoreCommand, item) + if not globalVars.appArgs.secure and not config.isAppX and not NVDAState.isRunningAsSource(): # Translators: The label for the menu item to create a portable copy of NVDA from an installed or another portable version. item = menu_tools.Append(wx.ID_ANY, _("Create portable copy...")) @@ -633,7 +636,7 @@ def _appendConfigManagementSection(self, frame: wx.Frame) -> None: _("Reset all settings to default state") ) self.Bind(wx.EVT_MENU, frame.onRevertToDefaultConfigurationCommand, item) - if not (globalVars.appArgs.secure or globalVars.appArgs.launcher): + if NVDAState.shouldWriteToDisk(): item = self.menu.Append( wx.ID_SAVE, # Translators: The label for the menu item to save current settings.