diff -r f8f333fffe81 -r c8a47a8536b0 CxFreeze/CxfreezeExecDialog.py --- a/CxFreeze/CxfreezeExecDialog.py Sat Jul 13 15:36:08 2013 +0200 +++ b/CxFreeze/CxfreezeExecDialog.py Sun Aug 11 22:17:02 2013 +0200 @@ -13,9 +13,12 @@ except (NameError): pass +import shutil +import errno +import fnmatch import os.path -from PyQt4.QtCore import pyqtSlot, QProcess, QTimer +from PyQt4.QtCore import pyqtSlot, QProcess, QTimer, QThread, pyqtSignal from PyQt4.QtGui import QDialog, QDialogButtonBox, QAbstractButton from E5Gui import E5MessageBox @@ -45,21 +48,28 @@ self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True) self.process = None + self.copyProcess = None self.cmdname = cmdname - def start(self, args, script): + def start(self, args, parms, ppath, mainscript): """ Public slot to start the packager command. @param args commandline arguments for packager program (list of strings) + @param parms parameters got from the config dialog (dict) + @param ppath project path (string) @param script main script name to be processed by by the packager (string) @return flag indicating the successful start of the process """ self.errorGroup.hide() - + script = os.path.join(ppath, mainscript) dname = os.path.dirname(script) script = os.path.basename(script) + self.ppath = ppath + self.additionalFiles = parms.get('additionalFiles', []) + self.targetDirectory = os.path.join(parms.get('targetDirectory', 'dist')) + self.contents.clear() self.errors.clear() @@ -70,7 +80,7 @@ self.process.readyReadStandardOutput.connect(self.__readStdout) self.process.readyReadStandardError.connect(self.__readStderr) - self.process.finished.connect(self.__finish) + self.process.finished.connect(self.__finishedFreeze) self.setWindowTitle(self.trUtf8('{0} - {1}').format(self.cmdname, script)) self.contents.insertPlainText(' '.join(args) + '\n\n') @@ -98,6 +108,7 @@ if button == self.buttonBox.button(QDialogButtonBox.Close): self.accept() elif button == self.buttonBox.button(QDialogButtonBox.Cancel): + self.additionalFiles = [] # Skip copying additional files self.__finish() def __finish(self): @@ -107,22 +118,52 @@ It is called when the process finished or the user pressed the cancel button. """ - if self.process is not None and \ - self.process.state() != QProcess.NotRunning: + if self.process is not None: + self.process.disconnect(self.__finishedFreeze) self.process.terminate() QTimer.singleShot(2000, self.process.kill) self.process.waitForFinished(3000) + self.process = None + if self.copyProcess is not None: + self.copyProcess.terminate() + self.copyProcess = None + + self.contents.insertPlainText( + self.trUtf8('\n{0} aborted.\n').format(self.cmdname)) + + self.__enableButtons() + + def __finishedFreeze(self): + """ + Private slot called when the process finished. + + It is called when the process finished or + the user pressed the cancel button. + """ + self.process = None + + self.contents.insertPlainText( + self.trUtf8('\n{0} finished.\n').format(self.cmdname)) + + self.copyProcess = copyAdditionalFiles(self) + self.copyProcess.insertPlainText.connect(self.contents.insertPlainText) + self.copyProcess.finished.connect(self.__enableButtons) + self.copyProcess.start() + + def __enableButtons(self): + """ + Private slot called when all processes finished. + + It is called when the process finished or + the user pressed the cancel button. + """ + self.copyProcess = None self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True) self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Close).setDefault(True) - - self.process = None - - self.contents.insertPlainText( - self.trUtf8('\n{0} finished.\n').format(self.cmdname)) self.contents.ensureCursorVisible() - + def __readStdout(self): """ Private slot to handle the readyReadStandardOutput signal. @@ -155,3 +196,110 @@ 'replace') self.errors.insertPlainText(s) self.errors.ensureCursorVisible() + + +class copyAdditionalFiles(QThread): + """ + Thread to copy the distribution dependend files. + + @signal insertPlainText(text) emitted to inform user about the copy progress + """ + insertPlainText = pyqtSignal(str) + + def __init__(self, main): + """ + Constructor, which stores the needed variables. + + @param main self-object of the caller + """ + super(copyAdditionalFiles, self).__init__() + + self.ppath = main.ppath + self.additionalFiles = main.additionalFiles + self.targetDirectory = main.targetDirectory + + def __copytree(self, src, dst): + """ + Copies a file or folder. Wildcards allowed. Existing files are overwitten. + + @param src source file or folder to copy. Wildcards allowed. (str) + @param dst destination (str) + """ + def src2dst(srcname, base, dst): + """ + Combines the relativ path of the source (srcname) with the + destination folder. + + @param srcname actual file or folder to copy + @param base basename of the source folder + @param dst basename of the destination folder + @return destination path + """ + delta = srcname.split(base)[1] + return os.path.join(dst, delta[1:]) + + base, fileOrFolderName = os.path.split(src) + initDone = False + for root, dirs, files in os.walk(base): + copied = False + # remove all none matching directorynames, create all others + for dir in dirs[:]: + pathname = os.path.join(root, dir) + if initDone or fnmatch.fnmatch(pathname, src): + newDir = src2dst(pathname, base, dst) + # avoid infinit loop + if fnmatch.fnmatch(newDir, src): + dirs.remove(dir) + continue + try: + copied = True + os.makedirs(newDir) + except OSError as err: + if err.errno != errno.EEXIST: # it's ok if directory already exists + raise err + else: + dirs.remove(dir) + + for file in files: + fn = os.path.join(root, file) + if initDone or fnmatch.fnmatch(fn, src): + newFile = src2dst(fn, base, dst) + # copy files, give errors to caller + shutil.copy2(fn, newFile) + copied = True + + # check if file was found and copied + if len(files) and not copied: + raise IOError(errno.ENOENT, + self.trUtf8("No such file or directory: '{0}'").format(src)) + + initDone = True + + def run(self): + """ + QThread entry point to copy the selected additional files and folders. + """ + self.insertPlainText.emit('----\n') + os.chdir(self.ppath) + for fn in self.additionalFiles: + self.insertPlainText.emit( + self.trUtf8('\nCopying {0}: ').format(fn)) + + # on linux normpath doesn't replace backslashes to slashes. + fn = fn.replace('\\', '/') + fn = os.path.abspath(os.path.normpath(fn)) + dst = os.path.join(self.ppath, self.targetDirectory) + if fn.startswith(os.path.normpath(self.ppath)): + dirname = fn.split(self.ppath+os.sep)[1] + dst = os.path.join(dst, os.path.dirname(dirname)) + try: + os.makedirs(dst) + except OSError as err: + if err.errno != errno.EEXIST: # it's ok if directory already exists + raise err + + try: + self.__copytree(fn, dst) + self.insertPlainText.emit(self.trUtf8('ok')) + except IOError as err: + self.insertPlainText.emit(self.trUtf8('failed: {0}').format(err.strerror))