Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
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
108 changes: 71 additions & 37 deletions source/installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import addonHandler
import easeOfAccess

MOVEFILE_DELAY_UNTIL_REBOOT=4

_wsh=None
def _getWSH():
global _wsh
Expand Down Expand Up @@ -158,9 +160,13 @@ def removeOldProgramFiles(destPath):
for topDir in ('lib','lib64'):
for parent,subdirs,files in os.walk(os.path.join(destPath,topDir),topdown=False):
for d in subdirs:
tryRemoveFile(os.path.join(parent,d),numRetries=1,rebootOK=True)
path=os.path.join(parent,d)
try:
os.rmdir(path)
except OSError as e:
log.debugWarning("Failed to remove directory %s, %s"%(path,e))
for f in files:
tryRemoveFile(os.path.join(parent,f),numRetries=1,rebootOK=True)
tryRemoveFile(os.path.join(parent,f),tempDir=destPath)

# #4235: mpr.dll is a Windows system dll accidentally included with
# earlier versions of NVDA. Its presence causes problems in Windows Vista.
Expand Down Expand Up @@ -323,52 +329,80 @@ def _deleteKeyAndSubkeys(key, subkey):
class RetriableFailure(Exception):
pass

def tryRemoveFile(path,numRetries=6,retryInterval=0.5,rebootOK=False):
dirPath=os.path.dirname(path)
tempPath=tempfile.mktemp(dir=dirPath)
try:
os.rename(path,tempPath)
except (WindowsError,IOError):
raise RetriableFailure("Failed to rename file %s before remove"%path)
for count in xrange(numRetries):
def deleteFileOnReboot(path):
"""
Marks a file for delete on reboot.
This uses Windows' MoveFileEx (either unicode or ascii depending on the given path) and also prepends'\\?\' to allow for long file names.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the type/meaning of the return value?

@return: True if the action was successfull. False otherwise.
@rtype: boolean
"""
MoveFileEx=windll.kernel32.MoveFileExW if isinstance(path,unicode) else windll.kernel32.MoveFileExA
return MoveFileEx("\\\\?\\"+path,None,MOVEFILE_DELAY_UNTIL_REBOOT)!=0

def copyFile(sourceFilePath,destFilePath):
"""
Copies the file at sourcePath to destPath.
This uses Windows' CopyFile and prepends'\\?\' to allow for long file names.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does the return value mean?

@return: True if the action was successfull. False otherwise.
@rtype: boolean
"""
return windll.kernel32.CopyFileW(u'\\\\?\\'+sourceFilePath,u'\\\\?\\'+destFilePath,False)!=0

def tryRemoveFile(path,numTries=6,retryInterval=0.5,tempDir=None):
"""
Tries to remove a file multiple times using veris strategies.
It first tries a simple delete. If that fails, it tries to rename the file to a temp file in the given temp directory, and marks it for delete on next reboot.
If both the delete and or the rename fail, the function waits a small period of time and tries to remove the file again, just in case the file was temporarily locked.
@param path: the path to the file that should be removed
@ type path: string
@param numTries: the number of times the file should try to be removed.
@type numTries: int
@param retryInterval: the number of seconds the function waits before trying to remove the file again.
@type retryInterval: float
@param tempDir: the path to the temporary directory where files marked for delete on reboot should be stored.
This directory should be on the same physical drive as the file being removed.
A value of None (default) uses the same directory as the file being removed.
@type tempDir: string
@raises: RetriableFailure if the file could not be removed at all.
"""
if not tempDir:
tempDir=os.path.dirname(path)
for count in xrange(numTries):
if count>0:
log.debugWarning("Will try to remove file again after delay")
time.sleep(retryInterval)
try:
if os.path.isdir(tempPath):
shutil.rmtree(tempPath)
if os.path.isdir(path):
shutil.rmtree(path)
else:
os.remove(tempPath)
os.remove(path)
return
except OSError:
pass
time.sleep(retryInterval)
if rebootOK:
log.debugWarning("Failed to delete file %s, marking for delete on reboot"%tempPath)
MoveFileEx=windll.kernel32.MoveFileExW if isinstance(tempPath,unicode) else windll.kernel32.MoveFileExA
MoveFileEx("\\\\?\\"+tempPath,None,4)
except OSError as e:
log.debugWarning("Failed to delete file %s, %s, will try renaming"%(path,e))
# The file could not be removed.
# Rename it to a temporary file, and try removing on reboot if allowed
tempPath=tempfile.mktemp(dir=tempDir)
log.debug("Renaming %s to %s"%(path,tempPath))
try:
os.rename(path,tempPath)
except (WindowsError,IOError) as e:
log.debugWarning("Failed to rename file %s before remove on reboot"%path)
continue
log.debug("Marking file for delete on reboot: %s"%tempPath)
if not deleteFileOnReboot(tempPath):
log.warning("Could not mark file for remove on reboot: %s"%tempPath)
return
try:
os.rename(tempPath,path)
except:
log.error("Unable to rename back to %s before retriable failier"%path)
raise RetriableFailure("File %s could not be removed"%path)

def tryCopyFile(sourceFilePath,destFilePath):
if not sourceFilePath.startswith('\\\\'):
sourceFilePath=u"\\\\?\\"+sourceFilePath
if not destFilePath.startswith('\\\\'):
destFilePath=u"\\\\?\\"+destFilePath
if windll.kernel32.CopyFileW(sourceFilePath,destFilePath,False)==0:
if copyFile(sourceFilePath,destFilePath)==0:
errorCode=GetLastError()
log.debugWarning("Unable to copy %s, error %d"%(sourceFilePath,errorCode))
if not os.path.exists(destFilePath):
raise OSError("error %d copying %s to %s"%(errorCode,sourceFilePath,destFilePath))
tempPath=tempfile.mktemp(dir=os.path.dirname(destFilePath))
try:
os.rename(destFilePath,tempPath)
except (WindowsError,OSError):
log.error("Failed to rename %s after failed overwrite"%destFilePath,exc_info=True)
raise RetriableFailure("Failed to rename %s after failed overwrite"%destFilePath)
windll.kernel32.MoveFileExW(tempPath,None,4)
if windll.kernel32.CopyFileW(sourceFilePath,destFilePath,False)==0:
tryRemoveFile(destFilePath)
log.debug("Trying to copy again after remove")
if copyFile(sourceFilePath,destFilePath)==0:
errorCode=GetLastError()
raise OSError("Unable to copy file %s to %s, error %d"%(sourceFilePath,destFilePath,errorCode))

Expand Down
18 changes: 11 additions & 7 deletions source/logHandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import traceback
from types import MethodType, FunctionType
import globalVars
import versionInfo

ERROR_INVALID_WINDOW_HANDLE = 1400
ERROR_TIMEOUT = 1460
Expand All @@ -29,6 +30,7 @@
EVENT_E_ALL_SUBSCRIBERS_FAILED = -2147220991
RPC_E_CALL_REJECTED = -2147418111
RPC_E_DISCONNECTED = -2147417848
LOAD_WITH_ALTERED_SEARCH_PATH=0x8

def getCodePath(f):
"""Using a frame object, gets its module path (relative to the current directory).[className.[funcName]]
Expand Down Expand Up @@ -188,17 +190,19 @@ class RemoteHandler(logging.Handler):

def __init__(self):
#Load nvdaHelperRemote.dll but with an altered search path so it can pick up other dlls in lib
h=ctypes.windll.kernel32.LoadLibraryExW(os.path.abspath(ur"lib\nvdaHelperRemote.dll"),0,0x8)
self._remoteLib=ctypes.WinDLL("nvdaHelperRemote",handle=h) if h else None
path=os.path.abspath(os.path.join(u"lib",versionInfo.version,u"nvdaHelperRemote.dll"))
h=ctypes.windll.kernel32.LoadLibraryExW(path,0,LOAD_WITH_ALTERED_SEARCH_PATH)
if not h:
raise OSError("Could not load %s"%path)
self._remoteLib=ctypes.WinDLL("nvdaHelperRemote",handle=h)
logging.Handler.__init__(self)

def emit(self, record):
msg = self.format(record)
if self._remoteLib:
try:
self._remoteLib.nvdaControllerInternal_logMessage(record.levelno, ctypes.windll.kernel32.GetCurrentProcessId(), msg)
except WindowsError:
pass
try:
self._remoteLib.nvdaControllerInternal_logMessage(record.levelno, ctypes.windll.kernel32.GetCurrentProcessId(), msg)
except WindowsError:
pass

class FileHandler(logging.StreamHandler):

Expand Down
15 changes: 8 additions & 7 deletions source/nvda.pyw
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ import pythonMonkeyPatches
import ctypes
import locale
import gettext

#Localization settings
locale.setlocale(locale.LC_ALL,'')
try:
gettext.translation('nvda',localedir='locale',languages=[locale.getlocale()[0]]).install(True)
except:
gettext.install('nvda',unicode=True)

import time
import argparse
import win32con
Expand Down Expand Up @@ -51,13 +59,6 @@ class NoConsoleOptionParser(argparse.ArgumentParser):

globalVars.startTime=time.time()

#Localization settings
locale.setlocale(locale.LC_ALL,'')
try:
gettext.translation('nvda',localedir='locale',languages=[locale.getlocale()[0]]).install(True)
except:
gettext.install('nvda',unicode=True)

# Check OS version requirements
import winVersion
if not winVersion.isSupportedOS():
Expand Down
6 changes: 6 additions & 0 deletions tests/unit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@
import os
import sys

import locale
import gettext
#Localization settings
locale.setlocale(locale.LC_ALL,'')
gettext.install('nvda',unicode=True)

# The path to the unit tests.
UNIT_DIR = os.path.dirname(os.path.abspath(__file__))
# The path to the top of the repo.
Expand Down