22# A part of NonVisual Desktop Access (NVDA)
33# This file is covered by the GNU General Public License.
44# See the file COPYING for more details.
5- # Copyright (C) 2011-2023 NV Access Limited, Joseph Lee, Babbage B.V., Łukasz Golonka
5+ # Copyright (C) 2011-2024 NV Access Limited, Joseph Lee, Babbage B.V., Łukasz Golonka
66
77import ctypes
88import winreg
99import time
1010import os
11+ import psutil
1112import tempfile
1213import shutil
1314import itertools
2223import COMRegistrationFixes
2324import winKernel
2425from typing import (
25- Any ,
2626 Dict ,
2727 Union ,
2828)
@@ -205,6 +205,7 @@ def removeOldProgramFiles(destPath):
205205 for fn in files :
206206 fn = os .path .join (root , fn )
207207 # No need to use tryRemoveFile here because these files should never be locked.
208+ # TODO: should we use tryRemoveFile anyway here?
208209 if os .path .isdir (fn ):
209210 shutil .rmtree (fn )
210211 else :
@@ -566,7 +567,13 @@ def _deleteKeyAndSubkeys(key, subkey):
566567class RetriableFailure (Exception ):
567568 pass
568569
569- def tryRemoveFile (path ,numRetries = 6 ,retryInterval = 0.5 ,rebootOK = False ):
570+
571+ def tryRemoveFile (
572+ path : str ,
573+ numRetries : int = 6 ,
574+ retryInterval : float = 0.5 ,
575+ rebootOK : bool = False
576+ ):
570577 dirPath = os .path .dirname (path )
571578 tempPath = tempfile .mktemp (dir = dirPath )
572579 try :
@@ -581,7 +588,7 @@ def tryRemoveFile(path,numRetries=6,retryInterval=0.5,rebootOK=False):
581588 os .remove (tempPath )
582589 return
583590 except OSError :
584- pass
591+ log . debugWarning ( f"Failed to delete file { tempPath } , attempt { count } / { numRetries } " , exc_info = True )
585592 time .sleep (retryInterval )
586593 if rebootOK :
587594 log .debugWarning ("Failed to delete file %s, marking for delete on reboot" % tempPath )
@@ -592,12 +599,13 @@ def tryRemoveFile(path,numRetries=6,retryInterval=0.5,rebootOK=False):
592599 # #9847: Move file to None to delete it.
593600 winKernel .moveFileEx (pathQualifier + tempPath ,None ,winKernel .MOVEFILE_DELAY_UNTIL_REBOOT )
594601 except WindowsError :
595- log .debugWarning ("Failed to delete file %s, marking for delete on reboot" % tempPath , exc_info = True )
596- return
602+ log .debugWarning (f"Failed to mark file { tempPath } for delete on reboot" , exc_info = True )
603+ else :
604+ return
597605 try :
598606 os .rename (tempPath ,path )
599- except :
600- log .error ( "Unable to rename back to %s before retriable failier" % path )
607+ except Exception :
608+ log .exception ( f "Unable to rename back to { path } before retriable failure" )
601609 raise RetriableFailure ("File %s could not be removed" % path )
602610
603611def tryCopyFile (sourceFilePath ,destFilePath ):
@@ -621,9 +629,27 @@ def tryCopyFile(sourceFilePath,destFilePath):
621629 errorCode = ctypes .GetLastError ()
622630 raise OSError ("Unable to copy file %s to %s, error %d" % (sourceFilePath ,destFilePath ,errorCode ))
623631
632+ _nvdaExes = {
633+ "nvda.exe" ,
634+ "nvda_noUIAccess.exe" ,
635+ "nvda_uiAccess.exe" ,
636+ "nvda_dmp.exe" ,
637+ "nvda_slave.exe"
638+ }
639+
640+
641+ def _isNVDAAlive (installDir : str ) -> bool :
642+ fullPathNvdaExes = {os .path .join (installDir , fn ) for fn in _nvdaExes }
643+ for process in psutil .process_iter ():
644+ if process .exe () in fullPathNvdaExes :
645+ return True
646+ return False
647+
624648
625649def install (shouldCreateDesktopShortcut : bool = True , shouldRunAtLogon : bool = True ):
626650 prevInstallPath = getInstallPath (noDefault = True )
651+ if _isNVDAAlive (prevInstallPath ):
652+ raise RetriableFailure ("Cannot install NVDA while it is still running" )
627653 unregisterInstallation (keepDesktopShortcut = shouldCreateDesktopShortcut )
628654 installDir = defaultInstallPath
629655 startMenuFolder = defaultStartMenuFolder
@@ -633,7 +659,10 @@ def install(shouldCreateDesktopShortcut: bool = True, shouldRunAtLogon: bool = T
633659 # so we shouldn't proceed.
634660 # 2. The appropriate executable for nvda.exe will be determined by
635661 # which executables exist after copying program files.
636- for f in ("nvda.exe" ,"nvda_noUIAccess.exe" ,"nvda_UIAccess.exe" ,"nvda_service.exe" ,"nvda_slave.exe" ):
662+ # Some exes are no longer used, but we remove them anyway from legacy copies.
663+ # nvda_service.exe was removed in 2017.4 (#7625).
664+ # nvda_eoaProxy.exe was removed in 2024.1 (#15544).
665+ for f in _nvdaExes .union ({"nvda_service.exe" , "nvda_eoaProxy.exe" }):
637666 f = os .path .join (installDir ,f )
638667 if os .path .isfile (f ):
639668 tryRemoveFile (f )
0 commit comments