def test_addonCompat_addonRequiresNewFeature(self): """Test an addon that has just been developed, requiring an API feature introduced in the current release.""" addon = mockAddon(minAPIVersion=latestVersionTuple, lastTestedAPIVersion=latestVersionTuple) nvda_current, nvda_backwardsCompatTo = latestVersionTuple, previousVersionTuple self.assertTrue(addonVersionCheck.hasAddonGotRequiredSupport(addon, nvda_current)) self.assertTrue(addonVersionCheck.isAddonTested(addon, nvda_backwardsCompatTo)) self.assertTrue(addonVersionCheck.isAddonCompatible(addon, nvda_current, nvda_backwardsCompatTo))
def refreshAddonsList(self,activeIndex=0): self.addonsList.DeleteAllItems() self.curAddons=[] shouldEnableIncompatAddonsButton = False for addon in addonHandler.getAvailableAddons(): self.addonsList.Append(( addon.manifest['summary'], self.getAddonStatus(addon), addon.manifest['version'], addon.manifest['author'] )) self.curAddons.append(addon) shouldEnableIncompatAddonsButton = ( shouldEnableIncompatAddonsButton or ( addonVersionCheck.hasAddonGotRequiredSupport(addon, version=addonVersionCheck.CURRENT_NVDA_VERSION) and not addonVersionCheck.isAddonTested(addon, version=addonVersionCheck.CURRENT_NVDA_VERSION) ) ) self.incompatAddonsButton.Enable(shouldEnableIncompatAddonsButton) # select the given active addon or the first addon if not given curAddonsLen=len(self.curAddons) if curAddonsLen>0: if activeIndex==-1: activeIndex=curAddonsLen-1 elif activeIndex<0 or activeIndex>=curAddonsLen: activeIndex=0 self.addonsList.Select(activeIndex,on=1) self.addonsList.SetItemState(activeIndex,wx.LIST_STATE_FOCUSED,wx.LIST_STATE_FOCUSED) else: self.aboutButton.Disable() self.helpButton.Disable() self.removeButton.Disable()
def test_addonWithUpToDateTesting(self): uptoDate = mockAddon() self.assertTrue( addonVersionCheck.hasAddonGotRequiredSupport( uptoDate, CurrentNVDAVersionTuple)) self.assertTrue( addonVersionCheck.isAddonTested(uptoDate, CurrentNVDAVersionTuple))
def test_addonCompat_testedAgainstLastBackwardsCompatVersion(self): """Test an addon has been maintained and tested against the backwardsCompatTo version.""" addon = mockAddon(minAPIVersion=oldVersionTuple, lastTestedAPIVersion=previousVersionTuple) nvda_current, nvda_backwardsCompatTo = latestVersionTuple, previousVersionTuple self.assertTrue(addonVersionCheck.hasAddonGotRequiredSupport(addon, nvda_current)) self.assertTrue(addonVersionCheck.isAddonTested(addon, nvda_backwardsCompatTo)) self.assertTrue(addonVersionCheck.isAddonCompatible(addon, nvda_current, nvda_backwardsCompatTo))
def refreshAddonsList(self, activeIndex=0): self.addonsList.DeleteAllItems() self.curAddons = [] shouldEnableIncompatAddonsButton = False for addon in addonHandler.getAvailableAddons(): self.addonsList.Append( (addon.manifest['summary'], self.getAddonStatus(addon), addon.manifest['version'], addon.manifest['author'])) self.curAddons.append(addon) shouldEnableIncompatAddonsButton = ( shouldEnableIncompatAddonsButton or (addonVersionCheck.hasAddonGotRequiredSupport( addon, version=addonVersionCheck.CURRENT_NVDA_VERSION) and not addonVersionCheck.isAddonTested( addon, version=addonVersionCheck.CURRENT_NVDA_VERSION))) self.incompatAddonsButton.Enable(shouldEnableIncompatAddonsButton) # select the given active addon or the first addon if not given curAddonsLen = len(self.curAddons) if curAddonsLen > 0: if activeIndex == -1: activeIndex = curAddonsLen - 1 elif activeIndex < 0 or activeIndex >= curAddonsLen: activeIndex = 0 self.addonsList.Select(activeIndex, on=1) self.addonsList.SetItemState(activeIndex, wx.LIST_STATE_FOCUSED, wx.LIST_STATE_FOCUSED) else: self.aboutButton.Disable() self.helpButton.Disable() self.removeButton.Disable()
def test_addonMissingLastTestedField(self): addonNoLastTested = mockAddon(lastTestedNVDAVersion=None) self.assertTrue( addonVersionCheck.hasAddonGotRequiredSupport( addonNoLastTested, CurrentNVDAVersionTuple)) self.assertFalse( addonVersionCheck.isAddonTested(addonNoLastTested, CurrentNVDAVersionTuple))
def test_addonUntestedWithSupport(self): addonNotTested = mockAddon(lastTestedNVDAVersion=LastNVDAVersionString) self.assertTrue( addonVersionCheck.hasAddonGotRequiredSupport( addonNotTested, CurrentNVDAVersionTuple)) self.assertFalse( addonVersionCheck.isAddonTested(addonNotTested, CurrentNVDAVersionTuple))
def test_addonMissingNVDASupportField(self): addonNoMinNVDAVer = mockAddon(minNVDAVersion=None) self.assertTrue( addonVersionCheck.hasAddonGotRequiredSupport( addonNoMinNVDAVer, CurrentNVDAVersionTuple)) self.assertTrue( addonVersionCheck.isAddonTested(addonNoMinNVDAVer, CurrentNVDAVersionTuple))
def test_addonCompat_attemptingToUseAddonRequiringNewAPIFeaturesWithOldNVDA(self): """Test that is considered incompatible if a user tries to install a new addon with an old version of NVDA""" # addon requires API features in the future release addon = mockAddon(minAPIVersion=nextVersionTuple, lastTestedAPIVersion=nextVersionTuple) nvda_current, nvda_backwardsCompatTo = latestVersionTuple, previousVersionTuple self.assertFalse(addonVersionCheck.hasAddonGotRequiredSupport(addon, latestVersionTuple)) self.assertTrue(addonVersionCheck.isAddonTested(addon, latestVersionTuple)) self.assertFalse(addonVersionCheck.isAddonCompatible(addon, nvda_current, nvda_backwardsCompatTo))
def test_addonWithoutNVDASupportTested(self): addonNvdaDoesntSupport = mockAddon( minNVDAVersion=NextNVDAVersionString) self.assertFalse( addonVersionCheck.hasAddonGotRequiredSupport( addonNvdaDoesntSupport, CurrentNVDAVersionTuple)) self.assertFalse( addonVersionCheck.isAddonTested(addonNvdaDoesntSupport, CurrentNVDAVersionTuple))
def test_addonCompat_lastTestedAgainstNowNoLongerSupportedAPIVersion(self): """Test an addon is considered incompatible if the backwards compatible to version is moved forward for an addon that has not been updated.""" addon = mockAddon(minAPIVersion=oldVersionTuple, lastTestedAPIVersion=previousVersionTuple) # NVDA backwards compatible to has been moved forward one version: nvda_current, nvda_backwardsCompatTo = latestVersionTuple, latestVersionTuple self.assertTrue(addonVersionCheck.hasAddonGotRequiredSupport(addon, nvda_current)) self.assertFalse(addonVersionCheck.isAddonTested(addon, nvda_backwardsCompatTo)) self.assertFalse(addonVersionCheck.isAddonCompatible(addon, nvda_current, nvda_backwardsCompatTo))
def _getIncompatReason(self, addon): if not addonVersionCheck.hasAddonGotRequiredSupport( addon, currentAPIVersion=self._APIVersion ): # Translators: The reason an add-on is not compatible. A more recent version of NVDA is # required for the add-on to work. The placeholder will be replaced with Year.Major.Minor (EG 2019.1). return _("An updated version of NVDA is required. NVDA version {} or later." ).format(addonAPIVersion.formatForGUI(addon.minimumNVDAVersion)) elif not addonVersionCheck.isAddonTested( addon, backwardsCompatToVersion=self._APIBackwardsCompatToVersion ): # Translators: The reason an add-on is not compatible. The addon relies on older, removed features of NVDA, # an updated add-on is required. The placeholder will be replaced with Year.Major.Minor (EG 2019.1). return _("An updated version of this add-on is required. The minimum supported API version is now {}" ).format(addonAPIVersion.formatForGUI(self._APIBackwardsCompatToVersion))
def _downloadSuccess(self): self._stopped() try: try: bundle = addonHandler.AddonBundle(self.destPath) except: log.error(f"Error opening addon bundle from {self.destPath}", exc_info=True) gui.messageBox( # Translators: The message displayed when an error occurs # when trying to update an add-on package due to package problems. _("Cannot update {name} - missing file or invalid file format" ).format(name=self.addonName), translate("Error"), wx.OK | wx.ICON_ERROR) self.continueUpdatingAddons() return # Check compatibility with NVDA and/or Windows release. # NVDA itself will check add-on compatibility range. # As such, the below fragment was borrowed from NVDA Core (credit: NV Access). from addonHandler import addonVersionCheck from gui import addonGui # Check compatibility with NVDA and/or Windows release. if not addonVersionCheck.hasAddonGotRequiredSupport(bundle): addonGui._showAddonRequiresNVDAUpdateDialog( gui.mainFrame, bundle) self.continueUpdatingAddons() return elif not addonVersionCheck.isAddonTested(bundle): addonGui._showAddonTooOldDialog(gui.mainFrame, bundle) self.continueUpdatingAddons() return # Some add-ons require a specific Windows release or later. # Prepare for winVersion.getWinVer function. import winVersion minimumWindowsVersion = bundle.manifest.get( "minimumWindowsVersion", None) if hasattr(winVersion, "getWinVer"): if minimumWindowsVersion is None: minimumWindowsVersion = winVersion.getWinVer() else: minimumWindowsVersion = winVersion.WinVersion.fromVersionText( minimumWindowsVersion) winVersionUnsupported = winVersion.getWinVer( ) < minimumWindowsVersion else: if minimumWindowsVersion is None: minimumWindowsVersion = winVersion.winVersion[:3] else: minimumWindowsVersion = [ int(data) for data in minimumWindowsVersion.split(".") ] minimumWinMajor, minimumWinMinor, minimumWinBuild = minimumWindowsVersion winMajor, winMinor, winBuild = winVersion.winVersion[:3] winVersionUnsupported = (winMajor, winMinor, winBuild) < ( minimumWinMajor, minimumWinMinor, minimumWinBuild) if winVersionUnsupported: gui.messageBox( # Translators: The message displayed when the add-on requires a newer version of Windows. _("{name} add-on is not compatible with this version of Windows." ).format(name=self.addonName), translate("Error"), wx.OK | wx.ICON_ERROR) self.continueUpdatingAddons() return bundleName = bundle.manifest['name'] isDisabled = False # Optimization (future): it is better to remove would-be add-ons all at once # instead of doing it each time a bundle is opened. for addon in addonHandler.getAvailableAddons(): # Check for disabled state first. if bundleName == addon.manifest['name']: if addon.isDisabled: isDisabled = True if not addon.isPendingRemove: addon.requestRemove() break progressDialog = gui.IndeterminateProgressDialog( gui.mainFrame, # Translators: The title of the dialog presented while an Addon is being updated. _("Updating {name}").format(name=self.addonName), # Translators: The message displayed while an addon is being updated. _("Please wait while the add-on is being updated.")) try: gui.ExecAndPump(addonHandler.installAddonBundle, bundle) except: log.error( f"Error installing addon bundle from {self.destPath}", exc_info=True) progressDialog.done() progressDialog.Hide() progressDialog.Destroy() gui.messageBox( # Translators: The message displayed when an error occurs when installing an add-on package. _("Failed to update {name} add-on").format( name=self.addonName), translate("Error"), wx.OK | wx.ICON_ERROR) self.continueUpdatingAddons() return else: progressDialog.done() progressDialog.Hide() progressDialog.Destroy() _updatedAddons.append(bundleName) if isDisabled: for addon in addonHandler.getAvailableAddons(): if bundleName == addon.manifest[ 'name'] and addon.isPendingInstall: addon.enable(False) break finally: try: os.remove(self.destPath) except OSError: pass self.continueUpdatingAddons()
def _downloadSuccess(self): self._stopped() try: try: bundle = addonHandler.AddonBundle(self.destPath) except: log.error(f"Error opening addon bundle from {self.destPath}", exc_info=True) gui.messageBox( # Translators: The message displayed when an error occurs # when trying to update an add-on package due to package problems. _("Cannot update {name} - missing file or invalid file format" ).format(name=self.addonName), translate("Error"), wx.OK | wx.ICON_ERROR) self.continueUpdatingAddons() return # NVDA itself will check add-on compatibility range. # As such, the below fragment was borrowed from NVDA Core (credit: NV Access). from addonHandler import addonVersionCheck from gui import addonGui if not addonVersionCheck.hasAddonGotRequiredSupport(bundle): addonGui._showAddonRequiresNVDAUpdateDialog( gui.mainFrame, bundle) self.continueUpdatingAddons() return elif not addonVersionCheck.isAddonTested(bundle): addonGui._showAddonTooOldDialog(gui.mainFrame, bundle) self.continueUpdatingAddons() return bundleName = bundle.manifest['name'] isDisabled = False # Optimization (future): it is better to remove would-be add-ons all at once # instead of doing it each time a bundle is opened. for addon in addonHandler.getAvailableAddons(): # Check for disabled state first. if bundleName == addon.manifest['name']: if addon.isDisabled: isDisabled = True if not addon.isPendingRemove: addon.requestRemove() break progressDialog = gui.IndeterminateProgressDialog( gui.mainFrame, # Translators: The title of the dialog presented while an Addon is being updated. _("Updating {name}").format(name=self.addonName), # Translators: The message displayed while an addon is being updated. _("Please wait while the add-on is being updated.")) try: gui.ExecAndPump(addonHandler.installAddonBundle, bundle) except: log.error( f"Error installing addon bundle from {self.destPath}", exc_info=True) progressDialog.done() progressDialog.Hide() progressDialog.Destroy() gui.messageBox( # Translators: The message displayed when an error occurs when installing an add-on package. _("Failed to update {name} add-on").format( name=self.addonName), translate("Error"), wx.OK | wx.ICON_ERROR) self.continueUpdatingAddons() return else: progressDialog.done() progressDialog.Hide() progressDialog.Destroy() _updatedAddons.append(bundleName) if isDisabled: for addon in addonHandler.getAvailableAddons(): if bundleName == addon.manifest[ 'name'] and addon.isPendingInstall: addon.enable(False) break finally: try: os.remove(self.destPath) except OSError: pass self.continueUpdatingAddons()
def test_addonWithUpToDateTesting(self): uptoDate = mockAddon() self.assertTrue(addonVersionCheck.hasAddonGotRequiredSupport(uptoDate, CurrentNVDAVersionTuple)) self.assertTrue(addonVersionCheck.isAddonTested(uptoDate, CurrentNVDAVersionTuple))
def test_addonUntestedWithSupport(self): addonNotTested = mockAddon(lastTestedNVDAVersion=LastNVDAVersionString) self.assertTrue(addonVersionCheck.hasAddonGotRequiredSupport(addonNotTested, CurrentNVDAVersionTuple)) self.assertFalse(addonVersionCheck.isAddonTested(addonNotTested, CurrentNVDAVersionTuple))
def installAddon(parentWindow, addonPath): """ Installs the addon at path. Any error messages / warnings are presented to the user via a GUI message box. If attempting to install an addon that is pending removal, it will no longer be pending removal. :return True on success or False on failure. """ try: bundle = addonHandler.AddonBundle(addonPath) except: log.error("Error opening addon bundle from %s" % addonPath, exc_info=True) gui.messageBox( # Translators: The message displayed when an error occurs when opening an add-on package for adding. _("Failed to open add-on package file at %s - missing file or invalid file format" ) % addonPath, # Translators: The title of a dialog presented when an error occurs. _("Error"), wx.OK | wx.ICON_ERROR) return False # Exit early, can't install an invalid bundle if not addonVersionCheck.hasAddonGotRequiredSupport(bundle): _showAddonRequiresNVDAUpdateDialog(parentWindow, bundle) return False # Exit early, addon does not have required support elif not addonVersionCheck.isAddonTested(bundle): _showAddonTooOldDialog(parentWindow, bundle) return False # Exit early, addon is not up to date with the latest API version. elif wx.YES != _showConfirmAddonInstallDialog(parentWindow, bundle): return False # Exit early, User changed their mind about installation. prevAddon = None for addon in addonHandler.getAvailableAddons(): if not addon.isPendingRemove and bundle.name == addon.manifest['name']: prevAddon = addon break if prevAddon: summary = bundle.manifest["summary"] curVersion = prevAddon.manifest["version"] newVersion = bundle.manifest["version"] # Translators: A title for the dialog asking if the user wishes to update a previously installed # add-on with this one. messageBoxTitle = _("Add-on Installation") # Translators: A message asking if the user wishes to update an add-on with the same version # currently installed according to the version number. overwriteExistingAddonInstallationMessage = _( "You are about to install version {newVersion} of {summary}, which appears to be already installed. " "Would you still like to update?").format(summary=summary, newVersion=newVersion) # Translators: A message asking if the user wishes to update a previously installed add-on with this one. updateAddonInstallationMessage = _( "A version of this add-on is already installed. " "Would you like to update {summary} version {curVersion} to version {newVersion}?" ).format(summary=summary, curVersion=curVersion, newVersion=newVersion) if gui.messageBox( overwriteExistingAddonInstallationMessage if curVersion == newVersion else updateAddonInstallationMessage, messageBoxTitle, wx.YES | wx.NO | wx.ICON_WARNING) != wx.YES: return False prevAddon.requestRemove() from contextlib import contextmanager @contextmanager def doneAndDestroy(window): try: yield window except: # pass on any exceptions raise finally: # but ensure that done and Destroy are called. window.done() window.Destroy() # use a progress dialog so users know that something is happening. progressDialog = gui.IndeterminateProgressDialog( parentWindow, # Translators: The title of the dialog presented while an Addon is being installed. _("Installing Add-on"), # Translators: The message displayed while an addon is being installed. _("Please wait while the add-on is being installed.")) try: # Use context manager to ensure that `done` and `Destroy` are called on the progress dialog afterwards with doneAndDestroy(progressDialog): gui.ExecAndPump(addonHandler.installAddonBundle, bundle) return True except: log.error("Error installing addon bundle from %s" % addonPath, exc_info=True) gui.messageBox( # Translators: The message displayed when an error occurs when installing an add-on package. _("Failed to install add-on from %s") % addonPath, # Translators: The title of a dialog presented when an error occurs. _("Error"), wx.OK | wx.ICON_ERROR) return False
def test_addonWithoutNVDASupportUntested(self): addonNvdaDoesntSupport = mockAddon(minNVDAVersion=NextNVDAVersionString, lastTestedNVDAVersion=LastNVDAVersionString) self.assertFalse(addonVersionCheck.hasAddonGotRequiredSupport(addonNvdaDoesntSupport, CurrentNVDAVersionTuple)) self.assertFalse(addonVersionCheck.isAddonTested(addonNvdaDoesntSupport, CurrentNVDAVersionTuple))
def test_addonMissingNVDASupportField(self): addonNoMinNVDAVer = mockAddon(minNVDAVersion=None) self.assertTrue(addonVersionCheck.hasAddonGotRequiredSupport(addonNoMinNVDAVer, CurrentNVDAVersionTuple)) self.assertTrue(addonVersionCheck.isAddonTested(addonNoMinNVDAVer, CurrentNVDAVersionTuple))
def test_addonMissingLastTestedField(self): addonNoLastTested = mockAddon(lastTestedNVDAVersion=None) self.assertTrue(addonVersionCheck.hasAddonGotRequiredSupport(addonNoLastTested, CurrentNVDAVersionTuple)) self.assertFalse(addonVersionCheck.isAddonTested(addonNoLastTested, CurrentNVDAVersionTuple))
def installAddon(self, addonPath, closeAfter=False): try: try: bundle = addonHandler.AddonBundle(addonPath) except: log.error("Error opening addon bundle from %s" % addonPath, exc_info=True) gui.messageBox( # Translators: The message displayed when an error occurs when opening an add-on package for adding. _("Failed to open add-on package file at %s - missing file or invalid file format") % addonPath, # Translators: The title of a dialog presented when an error occurs. _("Error"), wx.OK | wx.ICON_ERROR ) return if not addonVersionCheck.hasAddonGotRequiredSupport(bundle): self._showAddonRequiresNVDAUpdateDialog(bundle) return if not addonVersionCheck.isAddonTested(bundle): if wx.YES != self._showAddonUntestedDialog(bundle): return AddonCompatibilityState.setAddonCompatibility( addon=bundle, compatibilityStateValue=compatValues.MANUALLY_SET_COMPATIBLE ) elif wx.YES != self._showConfirmAddonInstallDialog(bundle): return prevAddon=None for addon in self.curAddons: if not addon.isPendingRemove and bundle.name==addon.manifest['name']: prevAddon=addon break if prevAddon: summary=bundle.manifest["summary"] curVersion=prevAddon.manifest["version"] newVersion=bundle.manifest["version"] if gui.messageBox( # Translators: A message asking if the user wishes to update an add-on with the same version currently installed according to the version number. _("You are about to install version {newVersion} of {summary}, which appears to be already installed. Would you still like to update?").format( summary=summary, newVersion=newVersion ) if curVersion==newVersion else # Translators: A message asking if the user wishes to update a previously installed add-on with this one. _("A version of this add-on is already installed. Would you like to update {summary} version {curVersion} to version {newVersion}?").format( summary=summary, curVersion=curVersion, newVersion=newVersion ), # Translators: A title for the dialog asking if the user wishes to update a previously installed add-on with this one. _("Add-on Installation"), wx.YES|wx.NO|wx.ICON_WARNING)!=wx.YES: return prevAddon.requestRemove() progressDialog = gui.IndeterminateProgressDialog(gui.mainFrame, # Translators: The title of the dialog presented while an Addon is being installed. _("Installing Add-on"), # Translators: The message displayed while an addon is being installed. _("Please wait while the add-on is being installed.")) try: gui.ExecAndPump(addonHandler.installAddonBundle,bundle) except: log.error("Error installing addon bundle from %s"%addonPath,exc_info=True) self.refreshAddonsList() progressDialog.done() del progressDialog # Translators: The message displayed when an error occurs when installing an add-on package. gui.messageBox(_("Failed to install add-on from %s")%addonPath, # Translators: The title of a dialog presented when an error occurs. _("Error"), wx.OK | wx.ICON_ERROR) return else: self.refreshAddonsList(activeIndex=-1) progressDialog.done() del progressDialog finally: if closeAfter: # #4460: If we do this immediately, wx seems to drop the WM_QUIT sent if the user chooses to restart. # This seems to have something to do with the wx.ProgressDialog. # The CallLater seems to work around this. wx.CallLater(1, self.Close)
def installAddon(parentWindow, addonPath): """ Installs the addon at path. Any error messages / warnings are presented to the user via a GUI message box. If attempting to install an addon that is pending removal, it will no longer be pending removal. :return True on success or False on failure. """ try: bundle = addonHandler.AddonBundle(addonPath) except: log.error("Error opening addon bundle from %s" % addonPath, exc_info=True) gui.messageBox( # Translators: The message displayed when an error occurs when opening an add-on package for adding. _("Failed to open add-on package file at %s - missing file or invalid file format") % addonPath, # Translators: The title of a dialog presented when an error occurs. _("Error"), wx.OK | wx.ICON_ERROR ) return False # Exit early, can't install an invalid bundle if not addonVersionCheck.hasAddonGotRequiredSupport(bundle): _showAddonRequiresNVDAUpdateDialog(parentWindow, bundle) return False # Exit early, addon does not have required support elif not addonVersionCheck.isAddonTested(bundle): _showAddonTooOldDialog(parentWindow, bundle) return False # Exit early, addon is not up to date with the latest API version. elif wx.YES != _showConfirmAddonInstallDialog(parentWindow, bundle): return False # Exit early, User changed their mind about installation. prevAddon = None for addon in addonHandler.getAvailableAddons(): if not addon.isPendingRemove and bundle.name==addon.manifest['name']: prevAddon=addon break if prevAddon: summary=bundle.manifest["summary"] curVersion=prevAddon.manifest["version"] newVersion=bundle.manifest["version"] # Translators: A title for the dialog asking if the user wishes to update a previously installed # add-on with this one. messageBoxTitle = _("Add-on Installation") # Translators: A message asking if the user wishes to update an add-on with the same version # currently installed according to the version number. overwriteExistingAddonInstallationMessage = _( "You are about to install version {newVersion} of {summary}, which appears to be already installed. " "Would you still like to update?" ).format(summary=summary, newVersion=newVersion) # Translators: A message asking if the user wishes to update a previously installed add-on with this one. updateAddonInstallationMessage = _( "A version of this add-on is already installed. " "Would you like to update {summary} version {curVersion} to version {newVersion}?" ).format(summary=summary, curVersion=curVersion, newVersion=newVersion) if gui.messageBox( overwriteExistingAddonInstallationMessage if curVersion == newVersion else updateAddonInstallationMessage, messageBoxTitle, wx.YES|wx.NO|wx.ICON_WARNING ) != wx.YES: return False prevAddon.requestRemove() from contextlib import contextmanager @contextmanager def doneAndDestroy(window): try: yield window except: # pass on any exceptions raise finally: # but ensure that done and Destroy are called. window.done() window.Destroy() # use a progress dialog so users know that something is happening. progressDialog = gui.IndeterminateProgressDialog( parentWindow, # Translators: The title of the dialog presented while an Addon is being installed. _("Installing Add-on"), # Translators: The message displayed while an addon is being installed. _("Please wait while the add-on is being installed.") ) try: # Use context manager to ensure that `done` and `Destroy` are called on the progress dialog afterwards with doneAndDestroy(progressDialog): gui.ExecAndPump(addonHandler.installAddonBundle, bundle) return True except: log.error("Error installing addon bundle from %s" % addonPath, exc_info=True) gui.messageBox( # Translators: The message displayed when an error occurs when installing an add-on package. _("Failed to install add-on from %s") % addonPath, # Translators: The title of a dialog presented when an error occurs. _("Error"), wx.OK | wx.ICON_ERROR ) return False
def installAddon(self, addonPath, closeAfter=False): try: try: bundle = addonHandler.AddonBundle(addonPath) except: log.error("Error opening addon bundle from %s" % addonPath, exc_info=True) gui.messageBox( # Translators: The message displayed when an error occurs when opening an add-on package for adding. _("Failed to open add-on package file at %s - missing file or invalid file format" ) % addonPath, # Translators: The title of a dialog presented when an error occurs. _("Error"), wx.OK | wx.ICON_ERROR) return if not addonVersionCheck.hasAddonGotRequiredSupport(bundle): self._showAddonRequiresNVDAUpdateDialog(bundle) return if not addonVersionCheck.isAddonTested(bundle): if wx.YES != self._showAddonUntestedDialog(bundle): return AddonCompatibilityState.setAddonCompatibility( addon=bundle, compatibilityStateValue=compatValues. MANUALLY_SET_COMPATIBLE) elif wx.YES != self._showConfirmAddonInstallDialog(bundle): return prevAddon = None for addon in self.curAddons: if not addon.isPendingRemove and bundle.name == addon.manifest[ 'name']: prevAddon = addon break if prevAddon: summary = bundle.manifest["summary"] curVersion = prevAddon.manifest["version"] newVersion = bundle.manifest["version"] if gui.messageBox( # Translators: A message asking if the user wishes to update an add-on with the same version currently installed according to the version number. _("You are about to install version {newVersion} of {summary}, which appears to be already installed. Would you still like to update?" ).format(summary=summary, newVersion=newVersion) if curVersion == newVersion else # Translators: A message asking if the user wishes to update a previously installed add-on with this one. _("A version of this add-on is already installed. Would you like to update {summary} version {curVersion} to version {newVersion}?" ).format(summary=summary, curVersion=curVersion, newVersion=newVersion), # Translators: A title for the dialog asking if the user wishes to update a previously installed add-on with this one. _("Add-on Installation"), wx.YES | wx.NO | wx.ICON_WARNING) != wx.YES: return prevAddon.requestRemove() progressDialog = gui.IndeterminateProgressDialog( gui.mainFrame, # Translators: The title of the dialog presented while an Addon is being installed. _("Installing Add-on"), # Translators: The message displayed while an addon is being installed. _("Please wait while the add-on is being installed.")) try: gui.ExecAndPump(addonHandler.installAddonBundle, bundle) except: log.error("Error installing addon bundle from %s" % addonPath, exc_info=True) self.refreshAddonsList() progressDialog.done() del progressDialog # Translators: The message displayed when an error occurs when installing an add-on package. gui.messageBox( _("Failed to install add-on from %s") % addonPath, # Translators: The title of a dialog presented when an error occurs. _("Error"), wx.OK | wx.ICON_ERROR) return else: self.refreshAddonsList(activeIndex=-1) progressDialog.done() del progressDialog finally: if closeAfter: # #4460: If we do this immediately, wx seems to drop the WM_QUIT sent if the user chooses to restart. # This seems to have something to do with the wx.ProgressDialog. # The CallLater seems to work around this. wx.CallLater(1, self.Close)