Skip to content

Commit

Permalink
When installing NVDA or creating a portable copy, detect when a file …
Browse files Browse the repository at this point in the history
…canot be removed or overwritten and allow the user to retry or cancel the over all operation. Also stop accidentily copying userConfig and or systemConfig when creating a portable copy or installing. (This may significantly speed up portable creation if the system config happened to be large).
  • Loading branch information
michaelDCurran committed May 9, 2012
1 parent c5e86bc commit ac735c6
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 57 deletions.
18 changes: 17 additions & 1 deletion source/gui/installerGui.py
Expand Up @@ -7,6 +7,7 @@
import os
import ctypes
import shellapi
import winUser
import wx
import config
import globalVars
Expand All @@ -28,6 +29,7 @@ def doInstall(createDesktopShortcut,startOnLogon,copyPortableConfig,isUpdate,sil
else _("Please wait while NVDA is being installed"))
try:
res=config.execElevated(config.SLAVE_FILENAME,["install",str(int(createDesktopShortcut)),str(int(startOnLogon))],wait=True,handleAlreadyElevated=True)
if res==2: raise installer.RetriableFailier
if copyPortableConfig:
installedUserConfigPath=config.getInstalledUserConfigPath()
if installedUserConfigPath:
Expand All @@ -37,6 +39,13 @@ def doInstall(createDesktopShortcut,startOnLogon,copyPortableConfig,isUpdate,sil
log.error("Failed to execute installer",exc_info=True)
progressDialog.done()
del progressDialog
if isinstance(res,installer.RetriableFailier):
# Translators: a message dialog asking to retry or cancel when NVDA install fails
message=_("The installation is unable to remove or overwrite a file. Another copy of NVDA may be running on another logged-on user account. Please make sure all installed copies of NVDA are shut down and try the installation again.")
# Translators: the title of a retry cancel dialog when NVDA installation fails
title=_("File in Use")
if winUser.MessageBox(None,message,title,winUser.MB_RETRYCANCEL)==winUser.IDRETRY:
return doInstall(createDesktopShortcut,startOnLogon,copyPortableConfig,isUpdate,silent)
if res!=0:
log.error("Installation failed: %s"%res)
# Translators: The message displayed when an error occurs during installation of NVDA.
Expand Down Expand Up @@ -203,10 +212,17 @@ def doCreatePortable(portableDirectory,createAutorun=False,copyUserConfig=False)
# Translators: The message displayed while a portable copy of NVDA is bieng created.
_("Please wait while a portable copy of NVDA is created."))
try:
installer.CreatePortableCopy(portableDirectory,copyUserConfig=copyUserConfig,createAutorun=createAutorun)
gui.ExecAndPump(installer.createPortableCopy,portableDirectory,copyUserConfig,createAutorun)
except Exception as e:
log.error("Failed to create portable copy",exc_info=True)
d.done()
if isinstance(e,installer.RetriableFailier):
# Translators: a message dialog asking to retry or cancel when NVDA portable copy creation fails
message=_("NVDA is unable to remove or overwrite a file.")
# Translators: the title of a retry cancel dialog when NVDA portable copy creation fails
title=_("File in Use")
if winUser.MessageBox(None,message,title,winUser.MB_RETRYCANCEL)==winUser.IDRETRY:
return doCreatePortable(portableDirectory,createAutorun,copyUserConfig)
# Translators: The message displayed when an error occurs while creating a portable copy of NVDA.
# %s will be replaced with the specific error message.
gui.messageBox(_("Failed to create portable copy: %s")%e,
Expand Down
105 changes: 52 additions & 53 deletions source/installer.py
Expand Up @@ -4,7 +4,6 @@
#See the file COPYING for more details.
#Copyright (C) 2011-2012 NV Access Limited

from ctypes import *
from ctypes import *
from ctypes.wintypes import *
import _winreg
Expand Down Expand Up @@ -38,7 +37,6 @@ def createShortcut(path,targetPath=None,arguments=None,iconLocation=None,working
if prependSpecialFolder:
specialPath=wsh.SpecialFolders(prependSpecialFolder)
path=os.path.join(specialPath,path)
log.info("Creating shortcut at %s"%path)
if not os.path.isdir(os.path.dirname(path)):
os.makedirs(os.path.dirname(path))
short=wsh.CreateShortcut(path)
Expand Down Expand Up @@ -98,9 +96,7 @@ def copyProgramFiles(destPath):
for curSourceDir,subDirs,files in os.walk(sourcePath):
if detectUserConfig:
detectUserConfig=False
if os.path.split(curSourceDir)[1].lower()=="userconfig":
del subDirs[:]
continue
subDirs[:]=[x for x in subDirs if os.path.basename(x).lower() not in ('userconfig','systemconfig')]
curDestDir=os.path.join(destPath,os.path.relpath(curSourceDir,sourcePath))
if not os.path.isdir(curDestDir):
os.makedirs(curDestDir)
Expand All @@ -113,7 +109,10 @@ def copyProgramFiles(destPath):
if windll.kernel32.CopyFileW(u"\\\\?\\"+sourceFilePath,u"\\\\?\\"+destFilePath,False)==0:
log.debugWarning("Unable to copy %s, trying rename and delete on reboot"%sourceFilePath)
tempPath=tempfile.mktemp(dir=os.path.dirname(destFilePath))
os.rename(destFilePath,tempPath)
try:
os.rename(destFilePath,tempPath)
except (WindowsError,OSError):
raise RetriableFailier("Failed to rename %s after failed remove"%destFilePath)
if windll.kernel32.MoveFileExW(u"\\\\?\\"+tempPath,None,4)==0:
raise OSError("Unable to mark file %s for delete on reboot"%tempPath)
if windll.kernel32.CopyFileW(u"\\\\?\\"+sourceFilePath,u"\\\\?\\"+destFilePath,False)==0:
Expand All @@ -131,7 +130,10 @@ def copyUserConfig(destPath):
if windll.kernel32.CopyFileW(u"\\\\?\\"+sourceFilePath,u"\\\\?\\"+destFilePath,False)==0:
log.debugWarning("Unable to copy %s, trying rename and delete on reboot"%sourceFilePath)
tempPath=tempfile.mktemp(dir=os.path.dirname(destFilePath))
os.rename(destFilePath,tempPath)
try:
os.rename(destFilePath,tempPath)
except (WindowsError,OSError):
raise RetriableFailier("Failed to rename %s after failed remove"%destFilePath)
if windll.kernel32.MoveFileExW(u"\\\\?\\"+tempPath,None,4)==0:
raise OSError("Unable to mark file %s for delete on reboot"%tempPath)
if windll.kernel32.CopyFileW(u"\\\\?\\"+sourceFilePath,u"\\\\?\\"+destFilePath,False)==0:
Expand Down Expand Up @@ -209,18 +211,35 @@ def unregisterInstallation(forUpdate=False):
except WindowsError:
pass

class RetriableFailier(Exception):
pass

MB_RETRYCANCEL=0x5
MB_SYSTEMMODAL=0x1000
IDRETRY=4
IDCANCEL=3
def tryRemoveFile(path,numRetries=6,retryInterval=0.5):
for count in xrange(numRetries):
try:
os.remove(path)
return
except OSError:
pass
time.sleep(retryInterval)
raise RetriableFailier("File %s could not be removed"%path)

def install(shouldCreateDesktopShortcut=True,shouldRunAtLogon=True):
prevInstallPath=getInstallPath(noDefault=True)
unregisterInstallation()
if prevInstallPath:
removeOldLoggedFiles(prevInstallPath)
installDir=defaultInstallPath
startMenuFolder=defaultStartMenuFolder
#Remove all the main executables always
for f in ("nvda.exe","nvda_noUIAccess.exe","nvda_UIAccess.exe"):
f=os.path.join(installDir,f)
if os.path.isfile(f):
os.remove(f)
tryRemoveFile(f)
if prevInstallPath:
removeOldLoggedFiles(prevInstallPath)
copyProgramFiles(installDir)
for f in ("nvda_UIAccess.exe","nvda_noUIAccess.exe"):
f=os.path.join(installDir,f)
Expand All @@ -239,9 +258,9 @@ def removeOldLoggedFiles(installPath):
with open(datPath,"r") as datFile:
datFile.readline()
lines=datFile.readlines()
lines.append(os.path.join(installPath,'uninstall.dat'))
lines.append(os.path.join(installPath,'uninstall.exe'))
lines.sort(reverse=True)
lines.append(os.path.join(installPath,'uninstall.dat'))
for line in lines:
filePath=line.rstrip('\n')
try:
Expand All @@ -252,7 +271,10 @@ def removeOldLoggedFiles(installPath):
except WindowsError:
log.debugWarning("Failed to remove %s, removing on reboot"%filePath)
tempPath=tempfile.mktemp(dir=installPath)
os.rename(filePath,tempPath)
try:
os.rename(filePath,tempPath)
except (WindowsError,IOError):
raise RetriableFailier("Failed to rename file %s after failed remove"%filePath)
if windll.kernel32.MoveFileExA("\\\\?\\"+tempPath,None,4)==0:
raise OSError("Unable to mark file %s for delete on reboot"%tempPath)

Expand All @@ -262,44 +284,21 @@ def removeOldLoggedFiles(installPath):
icon={icon}
"""

class CreatePortableCopy(threading.Thread):

def __init__(self,destPath,copyUserConfig=True,createAutorun=False):
super(CreatePortableCopy,self).__init__()
self.destPath=destPath
self.copyUserConfig=copyUserConfig
self.createAutorun=createAutorun
self.threadExc=None
self.start()
time.sleep(0.1)
threadHandle=c_int()
threadHandle.value=windll.kernel32.OpenThread(0x100000,False,self.ident)
msg=MSG()
while windll.user32.MsgWaitForMultipleObjects(1,byref(threadHandle),False,-1,255)==1:
while windll.user32.PeekMessageW(byref(msg),None,0,0,1):
windll.user32.TranslateMessage(byref(msg))
windll.user32.DispatchMessageW(byref(msg))
if self.threadExc:
raise self.threadExc

def run(self,*args,**kwargs):
try:
destPath=os.path.abspath(self.destPath)
#Remove all the main executables always
for f in ("nvda.exe","nvda_noUIAccess.exe","nvda_UIAccess.exe"):
f=os.path.join(destPath,f)
if os.path.isfile(f):
os.remove(f)
copyProgramFiles(destPath)
if windll.kernel32.CopyFileW(u"\\\\?\\"+os.path.join(destPath,"nvda_noUIAccess.exe"),u"\\\\?\\"+os.path.join(destPath,"nvda.exe"),False)==0:
raise OSError("Error copying %s to nvda.exe"%f)
if self.copyUserConfig:
copyUserConfig(os.path.join(destPath,'userConfig'))
if self.createAutorun:
drive,relDestPath=os.path.splitdrive(destPath)
autorunPath=os.path.join(drive,"autorun.inf")
autorunString=autorunTemplate.format(exe=os.path.join(relDestPath,'nvda.exe'),name=versionInfo.name,version=versionInfo.version,icon=os.path.join(relDestPath,'images/nvda.ico'))
with open(autorunPath,"wt") as autorunFile:
autorunFile.write(autorunString)
except Exception as e:
self.threadExc=e
def createPortableCopy(destPath,shouldCopyUserConfig=True,shouldCreateAutorun=False):
destPath=os.path.abspath(destPath)
#Remove all the main executables always
for f in ("nvda.exe","nvda_noUIAccess.exe","nvda_UIAccess.exe"):
f=os.path.join(destPath,f)
if os.path.isfile(f):
tryRemoveFile(f)
copyProgramFiles(destPath)
if windll.kernel32.CopyFileW(u"\\\\?\\"+os.path.join(destPath,"nvda_noUIAccess.exe"),u"\\\\?\\"+os.path.join(destPath,"nvda.exe"),False)==0:
raise OSError("Error copying %s to nvda.exe"%f)
if shouldCopyUserConfig:
copyUserConfig(os.path.join(destPath,'userConfig'))
if shouldCreateAutorun:
drive,relDestPath=os.path.splitdrive(destPath)
autorunPath=os.path.join(drive,"autorun.inf")
autorunString=autorunTemplate.format(exe=os.path.join(relDestPath,'nvda.exe'),name=versionInfo.name,version=versionInfo.version,icon=os.path.join(relDestPath,'images/nvda.ico'))
with open(autorunPath,"wt") as autorunFile:
autorunFile.write(autorunString)
5 changes: 4 additions & 1 deletion source/logHandler.py
Expand Up @@ -184,7 +184,10 @@ def __init__(self):
def emit(self, record):
msg = self.format(record)
if self._remoteLib:
self._remoteLib.nvdaControllerInternal_logMessage(record.levelno, ctypes.windll.kernel32.GetCurrentProcessId(), msg)
try:
self._remoteLib.nvdaControllerInternal_logMessage(record.levelno, ctypes.windll.kernel32.GetCurrentProcessId(), msg)
except WindowsError:
pass

class FileHandler(logging.StreamHandler):

Expand Down
8 changes: 6 additions & 2 deletions source/nvda_slave.pyw
Expand Up @@ -4,6 +4,7 @@ Performs miscellaneous tasks which need to be performed in a separate process.

import sys
import os
import logHandler
if hasattr(sys, "frozen"):
# Error messages (which are only for debugging) should not cause the py2exe log message box to appear.
sys.stderr = sys.stdout
Expand All @@ -21,7 +22,11 @@ def main():
nvda_service.nvdaLauncher()
elif action=="install":
import installer
installer.install(bool(int(args[0])),bool(int(args[1])))
try:
installer.install(bool(int(args[0])),bool(int(args[1])))
except installer.RetriableFailier:
logHandler.log.error("Installation failed, try again",exc_info=True)
sys.exit(2)
elif action=="unregisterInstall":
import installer
installer.unregisterInstallation()
Expand Down Expand Up @@ -69,7 +74,6 @@ def main():
sys.exit(1)

if __name__ == "__main__":
import logHandler
logHandler.initialize(True)
logHandler.log.setLevel(0)
import languageHandler
Expand Down
5 changes: 5 additions & 0 deletions source/winUser.py
Expand Up @@ -474,6 +474,11 @@ def FindWindow(className, windowName):
raise WinError()
return res

MB_RETRYCANCEL=5
MB_SYSTEMMODAL=0x1000
IDRETRY=4
IDCANCEL=3

def MessageBox(hwnd, text, caption, type):
res = user32.MessageBoxW(hwnd, text, caption, type)
if res == 0:
Expand Down

0 comments on commit ac735c6

Please sign in to comment.