CxFreeze/CxfreezeExecDialog.py

changeset 56
c8a47a8536b0
parent 47
986f27beaad4
child 60
8bf1407ebc46
--- 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))

eric ide

mercurial