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,10 +567,17 @@ 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 :
580+ # TODO: turn into tryCopyFile?
573581 os .rename (path ,tempPath )
574582 except (WindowsError ,IOError ):
575583 raise RetriableFailure ("Failed to rename file %s before remove" % path )
@@ -582,6 +590,7 @@ def tryRemoveFile(path,numRetries=6,retryInterval=0.5,rebootOK=False):
582590 return
583591 except OSError :
584592 pass
593+ log .debugWarning (f"Failed to delete file { tempPath } , attempt { count } /{ numRetries } " )
585594 time .sleep (retryInterval )
586595 if rebootOK :
587596 log .debugWarning ("Failed to delete file %s, marking for delete on reboot" % tempPath )
@@ -592,12 +601,14 @@ def tryRemoveFile(path,numRetries=6,retryInterval=0.5,rebootOK=False):
592601 # #9847: Move file to None to delete it.
593602 winKernel .moveFileEx (pathQualifier + tempPath ,None ,winKernel .MOVEFILE_DELAY_UNTIL_REBOOT )
594603 except WindowsError :
595- log .debugWarning ("Failed to delete file %s, marking for delete on reboot" % tempPath , exc_info = True )
596- return
604+ log .debugWarning (f"Failed to mark file { tempPath } for delete on reboot" , exc_info = True )
605+ else :
606+ return
597607 try :
608+ # TODO: turn into tryCopyFile?
598609 os .rename (tempPath ,path )
599610 except :
600- log .error ("Unable to rename back to %s before retriable failier" % path )
611+ log .error (f "Unable to rename back to { path } before retriable failure" )
601612 raise RetriableFailure ("File %s could not be removed" % path )
602613
603614def tryCopyFile (sourceFilePath ,destFilePath ):
@@ -621,9 +632,27 @@ def tryCopyFile(sourceFilePath,destFilePath):
621632 errorCode = ctypes .GetLastError ()
622633 raise OSError ("Unable to copy file %s to %s, error %d" % (sourceFilePath ,destFilePath ,errorCode ))
623634
635+ _nvdaExes = {
636+ "nvda.exe" ,
637+ "nvda_noUIAccess.exe" ,
638+ "nvda_uiAccess.exe" ,
639+ "nvda_dmp.exe" ,
640+ "nvda_slave.exe"
641+ }
642+
643+
644+ def _isNVDAAlive (installDir : str ) -> bool :
645+ fullPathNvdaExes = {os .path .join (installDir , fn ) for fn in _nvdaExes }
646+ for process in psutil .process_iter ():
647+ if process .exe () in fullPathNvdaExes :
648+ return True
649+ return False
650+
624651
625652def install (shouldCreateDesktopShortcut : bool = True , shouldRunAtLogon : bool = True ):
626653 prevInstallPath = getInstallPath (noDefault = True )
654+ if _isNVDAAlive (prevInstallPath ):
655+ raise RetriableFailure ("Cannot install NVDA while it is still running" )
627656 unregisterInstallation (keepDesktopShortcut = shouldCreateDesktopShortcut )
628657 installDir = defaultInstallPath
629658 startMenuFolder = defaultStartMenuFolder
@@ -633,7 +662,7 @@ def install(shouldCreateDesktopShortcut: bool = True, shouldRunAtLogon: bool = T
633662 # so we shouldn't proceed.
634663 # 2. The appropriate executable for nvda.exe will be determined by
635664 # 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" ) :
665+ for f in _nvdaExes :
637666 f = os .path .join (installDir ,f )
638667 if os .path .isfile (f ):
639668 tryRemoveFile (f )
0 commit comments