Fixed way apps are launched in parralel with single log window... Tested in win32 only.
authoretisserant
Sun, 24 Feb 2008 02:06:42 +0100
changeset 110 a05e8b30c024
parent 109 f27ca37b6e7a
child 111 e2e498333fbc
Fixed way apps are launched in parralel with single log window... Tested in win32 only.
Beremiz.py
plugger.py
runtime/plc_Win32_main.c
wxPopen.py
--- a/Beremiz.py	Fri Feb 22 19:04:01 2008 +0100
+++ b/Beremiz.py	Sun Feb 24 02:06:42 2008 +0100
@@ -36,8 +36,6 @@
 
 from plugger import PluginsRoot
 
-from wxPopen import wxPopen3
-
 SCROLLBAR_UNIT = 10
 WINDOW_COLOUR = wx.Colour(240,240,240)
 TITLE_COLOUR = wx.Colour(200,200,220)
@@ -72,30 +70,14 @@
 
 # Patch wx.lib.imageutils so that gray is supported on alpha images
 import wx.lib.imageutils
+from wx.lib.imageutils import grayOut as old_grayOut
 def grayOut(anImage):
-    """
-    Convert the given image (in place) to a grayed-out
-    version, appropriate for a 'disabled' appearance.
-    """
-    factor = 0.7        # 0 < f < 1.  Higher is grayer.
-    if anImage.HasMask():
-        maskColor = (anImage.GetMaskRed(), anImage.GetMaskGreen(), anImage.GetMaskBlue())
-    else:
-        maskColor = None
-
     if anImage.HasAlpha():
         AlphaData = anImage.GetAlphaData()
     else :
         AlphaData = None
 
-    data = map(ord, list(anImage.GetData()))
-         
-    for i in range(0, len(data), 3):
-        pixel = (data[i], data[i+1], data[i+2])
-        pixel = wx.lib.imageutils.makeGray(pixel, factor, maskColor)
-        for x in range(3):
-            data[i+x] = pixel[x]
-    anImage.SetData(''.join(map(chr, data)))
+    old_grayOut(anImage)
 
     if AlphaData is not None:
         anImage.SetAlphaData(AlphaData)
@@ -136,22 +118,23 @@
                         
 class LogPseudoFile:
     """ Base class for file like objects to facilitate StdOut for the Shell."""
-    def __init__(self, output = None):
+    def __init__(self, output):
         self.red_white = wx.TextAttr("RED", "WHITE")
         self.red_yellow = wx.TextAttr("RED", "YELLOW")
         self.black_white = wx.TextAttr("BLACK", "WHITE")
         self.default_style = None
         self.output = output
 
-    def writelines(self, l):
-        map(self.write, l)
-
     def write(self, s, style = None):
-        if not style : style=self.black_white    
+        if style is None : style=self.black_white
+        self.output.Freeze(); 
         if self.default_style != style: 
             self.output.SetDefaultStyle(style)
             self.default_style = style
-        self.output.AppendText(s) 
+        self.output.AppendText(s)
+        self.output.ScrollLines(s.count('\n')+1)
+        self.output.ShowPosition(self.output.GetLastPosition())
+        self.output.Thaw()
 
     def write_warning(self, s):
         self.write(s,self.red_white)
@@ -165,42 +148,6 @@
     def isatty(self):
         return false
 
-    def LogCommand(self, Command, sz_limit = 100, no_stdout=False):
-        self.errlen = 0
-        self.exitcode = None
-        self.outdata = ""
-        self.errdata = ""
-        
-        def output(v):
-            self.outdata += v
-            if not no_stdout:
-                self.write(v)
-
-        def errors(v):
-            self.errdata += v
-            self.errlen += 1
-            if self.errlen > sz_limit:
-                p.kill()
-            self.write_warning(v)
-
-        def fin(pid,ecode):
-            self.exitcode = ecode
-            if self.exitcode != 0:
-                self.write(Command + "\n")
-                self.write_warning("exited with status %d (pid %d)\n"%(ecode,pid))
-
-        def spin(p):
-            while not p.finished:
-                wx.Yield()
-                time.sleep(0.01)
-
-        input = []
-        p = wxPopen3(Command, input, output, errors, fin, self.output)
-        if p.pid:
-        	spin(p)
-
-        return (self.exitcode, self.outdata, self.errdata)
-
 [ID_BEREMIZ, ID_BEREMIZMAINSPLITTER, 
  ID_BEREMIZPLCCONFIG, ID_BEREMIZLOGCONSOLE, 
 ] = [wx.NewId() for _init_ctrls in range(4)]
@@ -869,9 +816,15 @@
         return AddPluginMenu
     
     def GetButtonCallBackFunction(self, plugin, method):
+        """ Generate the callbackfunc for a given plugin method"""
         def OnButtonClick(event):
+            # Disable button to prevent re-entrant call 
+            event.GetEventObject().Disable()
+            # Call
             getattr(plugin,method)(self.Log)
-            #self.RefreshVariableLists()
+            # Re-enable button 
+            event.GetEventObject().Enable()
+            # Trigger refresh on Idle
             wx.CallAfter(self.RefreshAll)
             event.Skip()
         return OnButtonClick
--- a/plugger.py	Fri Feb 22 19:04:01 2008 +0100
+++ b/plugger.py	Sun Feb 24 02:06:42 2008 +0100
@@ -8,13 +8,13 @@
 import shutil
 from xml.dom import minidom
 import wx
-import subprocess, ctypes, time, shutil
 
 #Quick hack to be able to find Beremiz IEC tools. Should be config params.
 base_folder = os.path.split(sys.path[0])[0]
 sys.path.append(os.path.join(base_folder, "plcopeneditor"))
 
 from xmlclass import GenerateClassesFromXSDstring
+from wxPopen import ProcessLogger
 
 _BaseParamsClass = GenerateClassesFromXSDstring("""<?xml version="1.0" encoding="ISO-8859-1" ?>
         <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
@@ -670,6 +670,9 @@
         
         # copy PluginMethods so that it can be later customized
         self.PluginMethods = [dic.copy() for dic in self.PluginMethods]
+        
+        self.runningPLC = None
+
     
     def HasProjectOpened(self):
         """
@@ -860,7 +863,13 @@
         logger.write("Compiling IEC Program in to C code...\n")
         # Now compile IEC code into many C files
         # files are listed to stdout, and errors to stderr. 
-        status, result, err_result = logger.LogCommand("%s \"%s\" -I \"%s\" \"%s\""%(iec2c_path, self._getIECcodepath(), ieclib_path, buildpath), no_stdout=True)
+        status, result, err_result = ProcessLogger(
+               logger,
+               "%s \"%s\" -I \"%s\" \"%s\""%(
+                         iec2c_path,
+                         self._getIECcodepath(),
+                         ieclib_path, buildpath),
+               no_stdout=True).spin()
         if status:
             # Failed !
             logger.write_error("Error : IEC to C compiler returned %d\n"%status)
@@ -893,12 +902,16 @@
         
         logger.flush()
         logger.write("Start build in %s\n" % buildpath)
+
+        self.EnableMethod("_Clean", True)
+        self.EnableMethod("_showIECcode", True)
         
         # Generate SoftPLC code
         if not self._Generate_SoftPLC(logger):
             logger.write_error("SoftPLC code generation failed !\n")
             return False
 
+
         #logger.write("SoftPLC code generation successfull\n")
 
         logger.write("Generating plugins code ...\n")
@@ -960,7 +973,13 @@
                 obns.append(obn)
                 logger.write("   [CC]  "+bn+" -> "+obn+"\n")
                 objectfilename = os.path.splitext(CFile)[0]+".o"
-                status, result, err_result = logger.LogCommand("\"%s\" -c \"%s\" -o \"%s\" %s %s"%(compiler, CFile, objectfilename, _CFLAGS, CFLAGS))
+
+                status, result, err_result = ProcessLogger(
+                       logger,
+                       "\"%s\" -c \"%s\" -o \"%s\" %s %s"%
+                           (compiler, CFile, objectfilename, _CFLAGS, CFLAGS)
+                       ).spin()
+
                 if status != 0:
                     logger.write_error("Build failed\n")
                     return False
@@ -972,7 +991,14 @@
             exe += ".exe"
         exe_path = os.path.join(buildpath, exe)
         logger.write("   [CC]  " + ' '.join(obns)+" -> " + exe + "\n")
-        status, result, err_result = logger.LogCommand("\"%s\" \"%s\" -o \"%s\" %s"%(linker, '" "'.join(objs), exe_path, ' '.join(LDFLAGS+[_LDFLAGS])))
+        status, result, err_result = ProcessLogger(
+               logger,
+               "\"%s\" \"%s\" -o \"%s\" %s"%
+                   (linker,
+                    '" "'.join(objs),
+                    exe_path,
+                    ' '.join(LDFLAGS+[_LDFLAGS]))
+               ).spin()
         if status != 0:
             logger.write_error("Build failed\n")
             self.EnableMethod("_Run", False)
@@ -1034,24 +1060,42 @@
             shutil.rmtree(os.path.join(self._getBuildPath()))
         else:
             logger.write_error("Build directory already clean\n")
+        self.EnableMethod("_showIECcode", False)
+        self.EnableMethod("_Clean", False)
+        self.EnableMethod("_Run", False)
     
     def _Run(self, logger):
-        logger.write("\n")
-        self.pid_plc = 0
         command_start_plc = os.path.join(self._getBuildPath(),self.GetProjectName() + exe_ext)
         if os.path.isfile(command_start_plc):
-            logger.write("\nStarting PLC\n")
-            self.pid_plc = subprocess.Popen(command_start_plc).pid
+            logger.write("Starting PLC\n")
+            def this_plc_finish_callback(*args):
+                if self.runningPLC is not None:
+                    self.runningPLC = None
+                    self._Stop(logger) 
+            self.runningPLC = ProcessLogger(
+               logger,
+               command_start_plc,
+               finish_callback = this_plc_finish_callback)
+            self.EnableMethod("_Clean", False)
+            self.EnableMethod("_Run", False)
+            self.EnableMethod("_Stop", True)
+            self.EnableMethod("_build", False)
         else:
             logger.write_error("%s doesn't exist\n" %command_start_plc)
 
+    def reset_finished(self):
+        self.EnableMethod("_Clean", True)
+        self.EnableMethod("_Run", True)
+        self.EnableMethod("_Stop", False)
+        self.EnableMethod("_build", True)
+
     def _Stop(self, logger):
-        PROCESS_TERMINATE = 1
-        if self.pid_plc != 0:
+        if self.runningPLC is not None:
             logger.write("Stopping PLC\n")
-            handle = ctypes.windll.kernel32.OpenProcess(PROCESS_TERMINATE, False, self.pid_plc)
-            ctypes.windll.kernel32.TerminateProcess(handle, -1)
-            ctypes.windll.kernel32.CloseHandle(handle)
+            was_runningPLC = self.runningPLC 
+            self.runningPLC = None
+            was_runningPLC.kill()
+            self.reset_finished()
 
     PluginMethods = [
         {"bitmap" : os.path.join("images", "editPLC"),
@@ -1078,10 +1122,10 @@
          "method" : "_Stop"},
         {"bitmap" : os.path.join("images", "ShowIECcode"),
          "name" : "Show IEC code",
+         "enabled" : False,
          "tooltip" : "Show IEC code generated by PLCGenerator",
          "method" : "_showIECcode"},
         {"name" : "Edit raw IEC code",
          "tooltip" : "Edit raw IEC code added to code generated by PLCGenerator",
          "method" : "_editIECrawcode"}
     ]
-
--- a/runtime/plc_Win32_main.c	Fri Feb 22 19:04:01 2008 +0100
+++ b/runtime/plc_Win32_main.c	Sun Feb 24 02:06:42 2008 +0100
@@ -3,10 +3,15 @@
 #include <time.h>
 #include <windows.h>
 
+int localcount = 0;
+
 void timer_notify()
 {
    struct _timeb timebuffer;
-   printf(".");
+   if(++localcount % 50 == 0){
+	   printf("PLC tick : %d\n",localcount);
+	   fflush(stdout);
+	   }
 
    _ftime( &timebuffer );
    __CURRENT_TIME.tv_sec = timebuffer.time;
--- a/wxPopen.py	Fri Feb 22 19:04:01 2008 +0100
+++ b/wxPopen.py	Sun Feb 24 02:06:42 2008 +0100
@@ -22,168 +22,128 @@
 #License along with this library; if not, write to the Free Software
 #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
-#
-# based on wxPopen.py from boa-constructor
-#
 
 import time
-from StringIO import StringIO
+import wx
+import subprocess, ctypes
+import threading
+import os
 
-import wx
+    
+class outputThread(threading.Thread):
+    """
+    Thread is used to print the output of a command to the stdout
+    """
+    def __init__(self, Proc, fd, callback=None, endcallback=None):
+        threading.Thread.__init__(self)
+        self.killed = False
+        self.finished = False
+        self.retval = None
+        self.Proc = Proc
+        self.callback = callback
+        self.endcallback = endcallback
+        self.fd = fd
 
-class ProcessRunnerMix:
+    def run(self):
+        outeof = False
+        self.retval = self.Proc.poll()
+        while not self.retval and not self.killed and not outeof:   
+            outchunk = self.fd.readline()
+            if outchunk == '': outeof = True
+            if self.callback :
+                wx.CallAfter(self.callback,outchunk)
+            self.retval=self.Proc.poll()
+        if self.endcallback:
+            err = self.Proc.wait()
+            self.finished = True
+            wx.CallAfter(self.endcallback, self.Proc.pid, self.retval)
 
-    if wx.VERSION < (2, 6, 0):
-        def Bind(self, event, function, id = None):
-            if id is not None:
-                event(self, id, function)
-            else:
-                event(self, function)
-    
-    def __init__(self, input, handler=None):
-        if handler is None:
-            handler = self
-        self.handler = handler
-        handler.Bind(wx.EVT_IDLE, self.OnIdle)
-        handler.Bind(wx.EVT_END_PROCESS, self.OnProcessEnded)
+class ProcessLogger:
+    def __init__(self, logger, Command, finish_callback=None, no_stdout=False, no_stderr=False):
+        self.logger = logger
+        self.Command = Command
+        self.finish_callback = finish_callback
+        self.no_stdout = no_stdout
+        self.no_stderr = no_stderr
+        self.errlen = 0
+        self.outlen = 0
+        self.exitcode = None
+        self.outdata = ""
+        self.errdata = ""
+        self.finished = False
 
-        input.reverse() # so we can pop
-        self.input = input
-        
-        self.reset()
+        self.Proc = subprocess.Popen(self.Command, 
+                                   cwd = os.getcwd(),
+                                   stdin = subprocess.PIPE, 
+                                   stdout = subprocess.PIPE, 
+                                   stderr = subprocess.STDOUT)
+#                                   stderr = subprocess.PIPE)
 
-    def reset(self):
-        self.process = None
-        self.pid = -1
-        self.output = []
-        self.errors = []
-        self.inputStream = None
-        self.errorStream = None
-        self.outputStream = None
-        self.outputFunc = None
-        self.errorsFunc = None
-        self.finishedFunc = None
-        self.finished = False
-        self.responded = False
+        self.outt = outputThread(
+                      self.Proc,
+                      self.Proc.stdout,
+                      self.output,
+                      self.finish)
 
-    def execute(self, cmd):
-        self.process = wx.Process(self.handler)
-        self.process.Redirect()
+        self.outt.start()
 
-        self.pid = wx.Execute(cmd, wx.EXEC_ASYNC, self.process)
+#        self.errt = outputThread(
+#                      self.Proc,
+#                      self.Proc.stderr,
+#                      self.errors)
+#
+#        self.errt.start()
 
-        self.inputStream = self.process.GetOutputStream()
-        self.errorStream = self.process.GetErrorStream()
-        self.outputStream = self.process.GetInputStream()
+    def output(self,v):
+        self.outdata += v
+        self.outlen += 1
+        if not self.no_stdout:
+            self.logger.write(v)
 
-        #self.OnIdle()
-        wx.WakeUpIdle()
-    
-    def setCallbacks(self, output, errors, finished):
-        self.outputFunc = output
-        self.errorsFunc = errors
-        self.finishedFunc = finished
+    def errors(self,v):
+        self.errdata += v
+        self.errlen += 1
+        if not self.no_stderr:
+            self.logger.write_warning(v)
 
-    def detach(self):
-        if self.process is not None:
-            self.process.CloseOutput()
-            self.process.Detach()
-            self.process = None
+    def finish(self, pid,ecode):
+        self.finished = True
+        self.exitcode = ecode
+        if self.exitcode != 0:
+            self.logger.write(self.Command + "\n")
+            self.logger.write_warning("exited with status %s (pid %s)\n"%(str(ecode),str(pid)))
+        if self.finish_callback is not None:
+            self.finish_callback(self,ecode,pid)
 
     def kill(self):
-        if self.process is not None:
-            self.process.CloseOutput()
-            if wx.Process.Kill(self.pid, wx.SIGTERM) != wx.KILL_OK:
-                wx.Process.Kill(self.pid, wx.SIGKILL)
-            self.process = None
+        self.outt.killed = True
+#        self.errt.killed = True
+        if wx.Platform == '__WXMSW__':
+            PROCESS_TERMINATE = 1
+            handle = ctypes.windll.kernel32.OpenProcess(PROCESS_TERMINATE, False, self.Proc.pid)
+            ctypes.windll.kernel32.TerminateProcess(handle, -1)
+            ctypes.windll.kernel32.CloseHandle(handle)
+        else:
+            os.kill(self.Proc.pid)
 
-    def updateStream(self, stream, data):
-        if stream and stream.CanRead():
-            if not self.responded:
-                self.responded = True
-            text = stream.read()
-            data.append(text)
-            return text
-        else:
-            return None
-
-    def updateInpStream(self, stream, input):
-        if stream and input:
-            line = input.pop()
-            stream.write(line)
-
-    def updateErrStream(self, stream, data):
-        return self.updateStream(stream, data)
-
-    def updateOutStream(self, stream, data):
-        return self.updateStream(stream, data)
-
-    def OnIdle(self, event=None):
-        if self.process is not None:
-            self.updateInpStream(self.inputStream, self.input)
-            e = self.updateErrStream(self.errorStream, self.errors)
-            if e is not None and self.errorsFunc is not None:
-                wx.CallAfter(self.errorsFunc, e)
-            o = self.updateOutStream(self.outputStream, self.output)
-            if o is not None and self.outputFunc is not None:
-                wx.CallAfter(self.outputFunc, o)
-
-            #wx.WakeUpIdle()
-            #time.sleep(0.001)
-
-    def OnProcessEnded(self, event):
-        self.OnIdle()
-        pid,exitcode = event.GetPid(), event.GetExitCode()
-        if self.process:
-            self.process.Destroy()
-            self.process = None
-
-        self.finished = True
-        
-        # XXX doesn't work ???
-        #self.handler.Disconnect(-1, wx.EVT_IDLE)
-        
-        if self.finishedFunc:
-            wx.CallAfter(self.finishedFunc, pid, exitcode)
-
-class ProcessRunner(wx.EvtHandler, ProcessRunnerMix):
-    def __init__(self, input):
-        wx.EvtHandler.__init__(self)
-        ProcessRunnerMix.__init__(self, input)
-
-def wxPopen3(cmd, input, output, errors, finish, handler=None):
-    p = ProcessRunnerMix(input, handler)
-    p.setCallbacks(output, errors, finish)
-    p.execute(cmd)
-    return p
-
-def _test():
-    app = wx.PySimpleApp()
-    f = wx.Frame(None, -1, 'asd')#, style=0)
-    f.Show()
-
-    def output(v):
-        print 'OUTPUT:', v
-    def errors(v):
-        print 'ERRORS:', v
-    def fin():
-        p.Close()
-        f.Close()
-        print 'FINISHED'
-
-
-    def spin(p):
-        while not p.finished:
+    def spin(self, timeout=None, out_limit=None, err_limit=None, keyword = None, kill_it = True):
+        count = 0
+        while not self.finished:
+            if err_limit and self.errlen > err_limit:
+                break
+            if out_limit and self.outlen > out_limit:
+                break
+            if timeout:
+                if count > timeout:
+                    break
+                count += 1
+            if keyword and self.outdata.find(keyword)!=-1:
+                    break
             wx.Yield()
             time.sleep(0.01)
 
-    def evt(self, event):
-        input = []
-        p = wxPopen3('''c:\\python23\\python.exe -c "print '*'*5000"''',
-                 input, output, errors, fin, f)
-        print p.pid
+        if not self.outt.finished and kill_it:
+            self.kill()
 
-    app.MainLoop()
+        return [self.exitcode, self.outdata, self.errdata]
 
-if __name__ == '__main__':
-    _test()