etisserant@79: #!/usr/bin/env python etisserant@79: # -*- coding: utf-8 -*- etisserant@79: etisserant@79: #This file is part of Beremiz, a Integrated Development Environment for etisserant@79: #programming IEC 61131-3 automates supporting plcopen standard and CanFestival. etisserant@79: # etisserant@79: #Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD etisserant@79: # etisserant@79: #See COPYING file for copyrights details. etisserant@79: # etisserant@79: #This library is free software; you can redistribute it and/or etisserant@79: #modify it under the terms of the GNU General Public etisserant@79: #License as published by the Free Software Foundation; either etisserant@79: #version 2.1 of the License, or (at your option) any later version. etisserant@79: # etisserant@79: #This library is distributed in the hope that it will be useful, etisserant@79: #but WITHOUT ANY WARRANTY; without even the implied warranty of etisserant@79: #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU etisserant@79: #General Public License for more details. etisserant@79: # etisserant@79: #You should have received a copy of the GNU General Public etisserant@79: #License along with this library; if not, write to the Free Software etisserant@79: #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA etisserant@79: etisserant@79: etisserant@79: import time etisserant@110: import wx etisserant@110: import subprocess, ctypes Edouard@704: from threading import Timer, Lock, Thread, Semaphore laurent@788: import os, sys etisserant@235: if os.name == 'posix': etisserant@235: from signal import SIGTERM, SIGKILL etisserant@79: etisserant@110: Edouard@704: class outputThread(Thread): etisserant@110: """ etisserant@110: Thread is used to print the output of a command to the stdout etisserant@110: """ etisserant@110: def __init__(self, Proc, fd, callback=None, endcallback=None): Edouard@704: Thread.__init__(self) etisserant@110: self.killed = False etisserant@110: self.finished = False etisserant@110: self.retval = None etisserant@110: self.Proc = Proc etisserant@110: self.callback = callback etisserant@110: self.endcallback = endcallback etisserant@110: self.fd = fd etisserant@79: etisserant@110: def run(self): etisserant@162: outchunk = None etisserant@149: self.retval = None etisserant@162: while outchunk != '' and not self.killed : etisserant@162: outchunk = self.fd.readline() etisserant@162: if self.callback : self.callback(outchunk) etisserant@149: while self.retval is None and not self.killed : etisserant@149: self.retval = self.Proc.poll() etisserant@110: outchunk = self.fd.readline() etisserant@162: if self.callback : self.callback(outchunk) etisserant@162: while outchunk != '' and not self.killed : etisserant@162: outchunk = self.fd.readline() Edouard@704: if self.callback : self.callback(outchunk) etisserant@110: if self.endcallback: greg@130: try: greg@232: err = self.Proc.wait() greg@130: except: etisserant@149: err = self.retval etisserant@110: self.finished = True laurent@421: self.endcallback(self.Proc.pid, err) laurent@421: etisserant@110: class ProcessLogger: Edouard@704: def __init__(self, logger, Command, finish_callback = None, Edouard@704: no_stdout = False, no_stderr = False, no_gui = True, Edouard@704: timeout = None, outlimit = None, errlimit = None, Edouard@958: endlog = None, keyword = None, kill_it = False, cwd = None): etisserant@110: self.logger = logger greg@424: if not isinstance(Command, list): greg@424: self.Command_str = Command greg@424: self.Command = [] greg@424: for i,word in enumerate(Command.replace("'",'"').split('"')): greg@424: if i % 2 == 0: greg@424: word = word.strip() greg@424: if len(word) > 0: greg@424: self.Command.extend(word.split()) greg@424: else: greg@424: self.Command.append(word) greg@424: else: greg@424: self.Command = Command greg@424: self.Command_str = subprocess.list2cmdline(self.Command) laurent@788: laurent@788: self.Command = map(lambda x: x.encode(sys.getfilesystemencoding()), laurent@788: self.Command) laurent@788: etisserant@110: self.finish_callback = finish_callback etisserant@110: self.no_stdout = no_stdout etisserant@110: self.no_stderr = no_stderr greg@111: self.startupinfo = None etisserant@110: self.errlen = 0 etisserant@110: self.outlen = 0 Edouard@704: self.errlimit = errlimit Edouard@704: self.outlimit = outlimit etisserant@110: self.exitcode = None Edouard@704: self.outdata = [] Edouard@704: self.errdata = [] Edouard@704: self.keyword = keyword Edouard@704: self.kill_it = kill_it Edouard@704: self.finishsem = Semaphore(0) Edouard@704: self.endlock = Lock() greg@111: greg@128: popenargs= { Edouard@958: "cwd":os.getcwd() if cwd is None else cwd, greg@128: "stdin":subprocess.PIPE, greg@128: "stdout":subprocess.PIPE, greg@128: "stderr":subprocess.PIPE} greg@424: greg@111: if no_gui == True and wx.Platform == '__WXMSW__': greg@111: self.startupinfo = subprocess.STARTUPINFO() greg@111: self.startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW greg@128: popenargs["startupinfo"] = self.startupinfo greg@128: elif wx.Platform == '__WXGTK__': Edouard@704: popenargs["shell"] = False greg@111: greg@128: self.Proc = subprocess.Popen( self.Command, **popenargs ) etisserant@112: etisserant@110: self.outt = outputThread( etisserant@110: self.Proc, etisserant@110: self.Proc.stdout, etisserant@110: self.output, greg@232: self.finish) etisserant@110: self.outt.start() etisserant@79: etisserant@112: self.errt = outputThread( etisserant@112: self.Proc, etisserant@112: self.Proc.stderr, etisserant@112: self.errors) greg@232: self.errt.start() etisserant@79: Edouard@723: if timeout: Edouard@723: self.timeout = Timer(timeout,self.endlog) Edouard@723: self.timeout.start() Edouard@723: else: Edouard@723: self.timeout = None Edouard@704: etisserant@110: def output(self,v): Edouard@704: self.outdata.append(v) etisserant@110: self.outlen += 1 etisserant@110: if not self.no_stdout: Edouard@686: self.logger.write(v) Edouard@704: if (self.keyword and v.find(self.keyword)!=-1) or (self.outlimit and self.outlen > self.outlimit): Edouard@704: self.endlog() laurent@421: etisserant@110: def errors(self,v): Edouard@704: self.errdata.append(v) etisserant@110: self.errlen += 1 etisserant@110: if not self.no_stderr: Edouard@686: self.logger.write_warning(v) Edouard@704: if self.errlimit and self.errlen > self.errlimit: Edouard@704: self.endlog() etisserant@79: greg@232: def log_the_end(self,ecode,pid): greg@232: self.logger.write(self.Command_str + "\n") laurent@361: self.logger.write_warning(_("exited with status %s (pid %s)\n")%(str(ecode),str(pid))) greg@232: etisserant@110: def finish(self, pid,ecode): Edouard@723: if self.timeout: self.timeout.cancel() etisserant@110: self.exitcode = ecode etisserant@110: if self.exitcode != 0: Edouard@686: self.log_the_end(ecode,pid) etisserant@110: if self.finish_callback is not None: etisserant@110: self.finish_callback(self,ecode,pid) Edouard@704: self.finishsem.release() etisserant@79: etisserant@235: def kill(self,gently=True): etisserant@110: self.outt.killed = True etisserant@112: self.errt.killed = True etisserant@110: if wx.Platform == '__WXMSW__': etisserant@110: PROCESS_TERMINATE = 1 etisserant@110: handle = ctypes.windll.kernel32.OpenProcess(PROCESS_TERMINATE, False, self.Proc.pid) etisserant@110: ctypes.windll.kernel32.TerminateProcess(handle, -1) etisserant@110: ctypes.windll.kernel32.CloseHandle(handle) etisserant@110: else: etisserant@235: if gently: etisserant@235: sig=SIGTERM etisserant@235: else: etisserant@235: sig=SIGKILL etisserant@154: try: etisserant@235: os.kill(self.Proc.pid, sig) etisserant@154: except: etisserant@154: pass greg@232: self.outt.join() greg@232: self.errt.join() etisserant@79: Edouard@704: def endlog(self): Edouard@704: if self.endlock.acquire(False): Edouard@704: self.finishsem.release() Edouard@704: if not self.outt.finished and self.kill_it: Edouard@704: self.kill() etisserant@89: Edouard@704: Edouard@704: def spin(self): Edouard@704: self.finishsem.acquire() Edouard@704: return [self.exitcode, "".join(self.outdata), "".join(self.errdata)] etisserant@89: