123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672 |
- """
- @package gcmd
- @brief GRASS command interface
- Classes:
- * GException
- * DigitError
- * Popen (from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/440554)
- * Command
- * CommandThread
- (C) 2007-2008 by the GRASS Development Team
- This program is free software under the GNU General Public
- License (>=v2). Read the file COPYING that comes with GRASS
- for details.
- @author Jachym Cepicky
- Martin Landa <landa.martin gmail.com>
- @date 2007-2008
- """
- import os
- import sys
- import time
- import errno
- import signal
- import wx
- try:
- import subprocess
- except:
- compatPath = os.path.join(globalvar.ETCWXDIR, "compat")
- sys.path.append(compatPath)
- import subprocess
- if subprocess.mswindows:
- from win32file import ReadFile, WriteFile
- from win32pipe import PeekNamedPipe
- import msvcrt
- else:
- import select
- import fcntl
- from threading import Thread
- # import wxgui_utils # log window
- import globalvar
- import utils
- from debug import Debug as Debug
- class GException(Exception):
- """Generic exception"""
- def __init__(self, message, title=_("Error"), parent=None):
- self.message = message
- self.parent = parent
- self.title = title
-
- def __str__(self):
- wx.MessageBox(parent=self.parent,
- caption=self.title,
- message=self.message,
- style=wx.ICON_ERROR | wx.CENTRE)
- # return 'GException: %s' % self.message
- return ''
- class GStdError(GException):
- """Generic exception"""
- def __init__(self, message, title=_("Error"), parent=None):
- GException.__init__(self, message, title=title, parent=parent)
- class CmdError(GException):
- """Exception used for GRASS commands.
- See Command class (command exits with EXIT_FAILURE,
- G_fatal_error() is called)."""
- def __init__(self, cmd, message, parent=None):
- self.cmd = cmd
- GException.__init__(self, message,
- title=_("Error in command execution %s" % self.cmd[0]),
- parent=parent)
- class SettingsError(GException):
- """Exception used for GRASS settings, see
- gui_modules/preferences.py."""
- def __init__(self, message, parent=None):
- GException.__init__(self, message,
- title=_("Preferences error"),
- parent=parent)
- class DigitError(GException):
- """Exception raised during digitization session"""
- def __init__(self, message, parent=None):
- GException.__init__(self, message,
- title=_("Error in digitization tool"),
- parent=parent)
- class DBMError(GException):
- """Attribute Table Manager exception class"""
- def __init__(self, message, parent=None):
- GException.__init__(self, message,
- title=_("Error in Attribute Table Manager"),
- parent=parent)
- class NvizError(GException):
- """Nviz exception class"""
- def __init__(self, message, parent=None):
- GException.__init__(self, message,
- title=_("Nviz error"),
- parent=parent)
- class Popen(subprocess.Popen):
- """Subclass subprocess.Popen"""
- def recv(self, maxsize=None):
- return self._recv('stdout', maxsize)
-
- def recv_err(self, maxsize=None):
- return self._recv('stderr', maxsize)
- def send_recv(self, input='', maxsize=None):
- return self.send(input), self.recv(maxsize), self.recv_err(maxsize)
- def get_conn_maxsize(self, which, maxsize):
- if maxsize is None:
- maxsize = 1024
- elif maxsize < 1:
- maxsize = 1
- return getattr(self, which), maxsize
-
- def _close(self, which):
- getattr(self, which).close()
- setattr(self, which, None)
- def kill(self):
- """Try to kill running process"""
- if subprocess.mswindows:
- import win32api
- handle = win32api.OpenProcess(1, 0, self.pid)
- return (0 != win32api.TerminateProcess(handle, 0))
- else:
- try:
- os.kill(-self.pid, signal.SIGTERM) # kill whole group
- except OSError:
- pass
- if subprocess.mswindows:
- def send(self, input):
- if not self.stdin:
- return None
- try:
- x = msvcrt.get_osfhandle(self.stdin.fileno())
- (errCode, written) = WriteFile(x, input)
- except ValueError:
- return self._close('stdin')
- except (subprocess.pywintypes.error, Exception), why:
- if why[0] in (109, errno.ESHUTDOWN):
- return self._close('stdin')
- raise
- return written
- def _recv(self, which, maxsize):
- conn, maxsize = self.get_conn_maxsize(which, maxsize)
- if conn is None:
- return None
-
- try:
- x = msvcrt.get_osfhandle(conn.fileno())
- (read, nAvail, nMessage) = PeekNamedPipe(x, 0)
- if maxsize < nAvail:
- nAvail = maxsize
- if nAvail > 0:
- (errCode, read) = ReadFile(x, nAvail, None)
- except ValueError:
- return self._close(which)
- except (subprocess.pywintypes.error, Exception), why:
- if why[0] in (109, errno.ESHUTDOWN):
- return self._close(which)
- raise
-
- if self.universal_newlines:
- read = self._translate_newlines(read)
- return read
- else:
- def send(self, input):
- if not self.stdin:
- return None
- if not select.select([], [self.stdin], [], 0)[1]:
- return 0
- try:
- written = os.write(self.stdin.fileno(), input)
- except OSError, why:
- if why[0] == errno.EPIPE: #broken pipe
- return self._close('stdin')
- raise
- return written
- def _recv(self, which, maxsize):
- conn, maxsize = self.get_conn_maxsize(which, maxsize)
- if conn is None:
- return None
-
- flags = fcntl.fcntl(conn, fcntl.F_GETFL)
- if not conn.closed:
- fcntl.fcntl(conn, fcntl.F_SETFL, flags| os.O_NONBLOCK)
-
- try:
- if not select.select([conn], [], [], 0)[0]:
- return ''
-
- r = conn.read(maxsize)
- if not r:
- return self._close(which)
-
- if self.universal_newlines:
- r = self._translate_newlines(r)
- return r
- finally:
- if not conn.closed:
- fcntl.fcntl(conn, fcntl.F_SETFL, flags)
- message = "Other end disconnected!"
- def recv_some(p, t=.1, e=1, tr=5, stderr=0):
- if tr < 1:
- tr = 1
- x = time.time()+t
- y = []
- r = ''
- pr = p.recv
- if stderr:
- pr = p.recv_err
- while time.time() < x or r:
- r = pr()
- if r is None:
- if e:
- raise Exception(message)
- else:
- break
- elif r:
- y.append(r)
- else:
- time.sleep(max((x-time.time())/tr, 0))
- return ''.join(y)
-
- def send_all(p, data):
- while len(data):
- sent = p.send(data)
- if sent is None:
- raise Exception(message)
- data = buffer(data, sent)
- # Define notification event for thread completion
- EVT_RESULT_ID = wx.NewId()
- def EVT_RESULT(win, func):
- """Define Result Event"""
- win.Connect(-1, -1, EVT_RESULT_ID, func)
- class ResultEvent(wx.PyEvent):
- """Simple event to carry arbitrary result data"""
- def __init__(self, data):
- wx.PyEvent.__init__(self)
- self.SetEventType(EVT_RESULT_ID)
- self.cmdThread = data
- class Command:
- """
- Run GRASS command in separate thread
- If stdout/err is redirected, write() method is required for the
- given classes.
- @code
- cmd = Command(cmd=['d.rast', 'elevation.dem'], verbose=3, wait=True)
- if cmd.returncode == None:
- print 'RUNNING?'
- elif cmd.returncode == 0:
- print 'SUCCESS'
- else:
- print 'FAILURE (%d)' % cmd.returncode
- @endcode
- @param cmd command given as list
- @param stdin standard input stream
- @param verbose verbose level [0, 3] (--q, --v)
- @param wait wait for child execution terminated
- @param rerr error handling (when CmdError raised).
- True for redirection to stderr, False for GUI dialog,
- None for no operation (quiet mode)
- @param stdout redirect standard output or None
- @param stderr redirect standard error output or None
- """
- def __init__ (self, cmd, stdin=None,
- verbose=None, wait=True, rerr=False,
- stdout=None, stderr=sys.stderr):
- self.cmd = cmd
- # hack around platform-specific extension for binaries
- if self.cmd[0] in globalvar.grassCmd['script']:
- self.cmd[0] = self.cmd[0] + globalvar.EXT_SCT
- else:
- self.cmd[0] = self.cmd[0] + globalvar.EXT_BIN
- self.stderr = stderr
- #
- # set verbosity level
- #
- verbose_orig = None
- if ('--q' not in self.cmd and '--quiet' not in self.cmd) and \
- ('--v' not in self.cmd and '--verbose' not in self.cmd):
- if verbose is not None:
- if verbose == 0:
- self.cmd.append('--quiet')
- elif verbose == 3:
- self.cmd.append('--verbose')
- else:
- verbose_orig = os.getenv("GRASS_VERBOSE")
- os.environ["GRASS_VERBOSE"] = str(verbose)
- #
- # set message formatting
- #
- message_format = os.getenv("GRASS_MESSAGE_FORMAT")
- os.environ["GRASS_MESSAGE_FORMAT"] = "gui"
- #
- # create command thread
- #
- self.cmdThread = CommandThread(cmd, stdin,
- stdout, stderr)
-
- #
- # start thread
- #
- self.cmdThread.start()
-
- if wait:
- self.cmdThread.join()
- if self.cmdThread.module:
- self.cmdThread.module.wait()
- self.returncode = self.cmdThread.module.returncode
- else:
- self.returncode = 1
- else:
- self.cmdThread.join(0.5)
- self.returncode = None
- if self.returncode is not None:
- Debug.msg (3, "Command(): cmd='%s', wait=%s, returncode=%d, alive=%s" % \
- (' '.join(cmd), wait, self.returncode, self.cmdThread.isAlive()))
- if rerr is not None and self.returncode != 0:
- if rerr is False: # GUI dialog
- raise CmdError(cmd=self.cmd,
- message="%s '%s'%s%s%s %s%s" %
- (_("Execution failed:"),
- ' '.join(self.cmd),
- os.linesep, os.linesep,
- _("Details:"),
- os.linesep,
- self.PrintModuleOutput()))
- elif rerr == sys.stderr: # redirect message to sys
- stderr.write("Execution failed: '%s'" % (' '.join(self.cmd)))
- stderr.write("%sDetails:%s%s" % (os.linesep,
- self.PrintModuleOutput(),
- os.linesep))
- else:
- pass # nop
- else:
- Debug.msg (3, "Command(): cmd='%s', wait=%s, returncode=?, alive=%s" % \
- (' '.join(cmd), wait, self.cmdThread.isAlive()))
- if message_format:
- os.environ["GRASS_MESSAGE_FORMAT"] = message_format
- else:
- os.unsetenv("GRASS_MESSAGE_FORMAT")
- if verbose_orig:
- os.environ["GRASS_VERBOSE"] = verbose_orig
- else:
- os.unsetenv("GRASS_VERBOSE")
- def __ReadOutput(self, stream):
- """Read stream and return list of lines
- @param stream stream to be read
- """
- lineList = []
- if stream is None:
- return lineList
- while True:
- line = stream.readline()
- if not line:
- break
- line = line.replace('%s' % os.linesep, '').strip()
- lineList.append(line)
- return lineList
-
- def ReadStdOutput(self):
- """Read standard output and return list of lines"""
- if self.cmdThread.stdout:
- stream = self.cmdThread.stdout # use redirected stream instead
- stream.seek(0)
- else:
- stream = self.cmdThread.module.stdout
- return self.__ReadOutput(stream)
-
- def ReadErrOutput(self):
- """Read standard error output and return list of lines"""
- return self.__ReadOutput(self.cmdThread.module.stderr)
- def __ProcessStdErr(self):
- """
- Read messages/warnings/errors from stderr
- @return list of (type, message)
- """
- if self.stderr is None:
- lines = self.ReadErrOutput()
- else:
- lines = self.cmdThread.rerr.strip('%s' % os.linesep). \
- split('%s' % os.linesep)
-
- msg = []
- type = None
- content = ""
- for line in lines:
- if len(line) == 0:
- continue
- if 'GRASS_' in line: # error or warning
- if 'GRASS_INFO_WARNING' in line: # warning
- type = "WARNING"
- elif 'GRASS_INFO_ERROR' in line: # error
- type = "ERROR"
- elif 'GRASS_INFO_END': # end of message
- msg.append((type, content))
- type = None
- content = ""
-
- if type:
- content += line.split(':')[1].strip()
- else: # stderr
- msg.append((None, line.strip()))
- return msg
- def PrintModuleOutput(self, error=True, warning=False, message=False):
- """Print module errors, warnings, messages to output
- @param error print errors
- @param warning print warnings
- @param message print messages
- @return string
- """
- msgString = ""
- for type, msg in self.__ProcessStdErr():
- if type:
- if (type == 'ERROR' and error) or \
- (type == 'WARNING' and warning) or \
- (type == 'MESSAGE' and message):
- msgString += " " + type + ": " + msg + "%s" % os.linesep
- else:
- msgString += " " + msg + "%s" % os.linesep
- return msgString
- class CommandThread(Thread):
- """Run command in separate thread
- @param cmd GRASS command (given as list)
- @param stdin standard input stream
- @param stdout redirect standard output or None
- @param stderr redirect standard error output or None
- """
- def __init__ (self, cmd, stdin=None,
- stdout=None, stderr=sys.stderr):
- Thread.__init__(self)
-
- self.cmd = cmd
- self.stdin = stdin
- self.stdout = stdout
- self.stderr = stderr
- self.module = None
- self.rerr = ''
- self._want_abort = False
- self.aborted = False
- self.startTime = None
- self.setDaemon(True)
-
- def run(self):
- """Run command"""
- if len(self.cmd) == 0:
- return
-
- self.startTime = time.time()
- # TODO: wx.Exectute/wx.Process (?)
- try:
- self.module = Popen(self.cmd,
- stdin=subprocess.PIPE,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
- except OSError, e:
- self.rerr = str(e)
- return
- # raise CmdError(self.cmd[0], str(e))
- if self.stdin: # read stdin if requested ...
- self.module.stdin.write(self.stdin)
- self.module.stdin.close()
- # redirect standard outputs...
- if self.stdout or self.stderr:
- self.__redirect_stream()
- def __read_all(self, fd):
- out = ""
- while True:
- try:
- bytes = fd.read(4096)
- except IOError, e:
- if e[0] != errno.EAGAIN:
- raise
- break
- if not bytes:
- break
- out += bytes
- return out
- def __redirect_stream(self):
- """Redirect stream"""
- if self.stdout:
- # make module stdout/stderr non-blocking
- out_fileno = self.module.stdout.fileno()
- if not subprocess.mswindows:
- flags = fcntl.fcntl(out_fileno, fcntl.F_GETFL)
- fcntl.fcntl(out_fileno, fcntl.F_SETFL, flags| os.O_NONBLOCK)
-
- if self.stderr:
- # make module stdout/stderr non-blocking
- out_fileno = self.module.stderr.fileno()
- if not subprocess.mswindows:
- flags = fcntl.fcntl(out_fileno, fcntl.F_GETFL)
- fcntl.fcntl(out_fileno, fcntl.F_SETFL, flags| os.O_NONBLOCK)
-
- # wait for the process to end, sucking in stuff until it does end
- while self.module.poll() is None:
- time.sleep(.1)
- if self._want_abort: # abort running process
- self.module.kill()
- self.aborted = True
- if hasattr(self.stderr, "gmstc"):
- # -> GMConsole
- wx.PostEvent(self.stderr.gmstc.parent, ResultEvent(self))
- return
- if self.stdout:
- # line = self.__read_all(self.module.stdout)
- line = recv_some(self.module, e=0, stderr=0)
- self.stdout.write(line)
- if self.stderr:
- # line = self.__read_all(self.module.stderr)
- line = recv_some(self.module, e=0, stderr=1)
- self.stderr.write(line)
- self.rerr = line
- # get the last output
- if self.stdout:
- # line = self.__read_all(self.module.stdout)
- line = recv_some(self.module, e=0, stderr=0)
- self.stdout.write(line)
- if self.stderr:
- # line = self.__read_all(self.module.stderr)
- line = recv_some(self.module, e=0, stderr=1)
- self.stderr.write(line)
- if len(line) > 0:
- self.rerr = line
- if hasattr(self.stderr, "gmstc"):
- # -> GMConsole
- wx.PostEvent(self.stderr.gmstc.parent, ResultEvent(self))
- def abort(self):
- """Abort running process, used by main thread to signal an abort"""
- self._want_abort = True
- # testing ...
- if __name__ == "__main__":
- SEP = "-----------------------------------------------------------------------------"
- print SEP
- # d.rast verbosely, wait for process termination
- print "Running d.rast..."
- cmd = Command(cmd=["d.rast", "elevation.dem"], verbose=3, wait=True, rerr=True)
- if cmd.returncode == None:
- print "RUNNING"
- elif cmd.returncode == 0:
- print "SUCCESS"
- else:
- print "FAILURE (%d)" % cmd.returncode
- print SEP
- # v.net.path silently, wait for process termination
- print "Running v.net.path for 0 593527.6875 4925297.0625 602083.875 4917545.8125..."
- cmd = Command(cmd=["v.net.path", "in=roads@PERMANENT", "out=tmp", "dmax=100000", "--o"],
- stdin="0 593527.6875 4925297.0625 602083.875 4917545.8125",
- verbose=0,
- wait=True, rerr=None)
- if cmd.returncode == None:
- print "RUNNING"
- elif cmd.returncode == 0:
- print "SUCCESS"
- else:
- print "FAILURE (%d)" % cmd.returncode
- print SEP
- # d.vect silently, do not wait for process termination
- # returncode will be None
- print "Running d.vect tmp..."
- cmd = Command(["d.vect", "tmp"], verbose=2, wait=False, rerr=None)
- if cmd.returncode == None:
- print "RUNNING"
- elif cmd.returncode == 0:
- print "SUCCESS"
- else:
- print "FAILURE (%d)" % cmd.returncode
- cmd = Command(["g.region", "-p"])
- for line in cmd.ReadStdOutput():
- print line
- cmd = Command(["g.region", "-p"], stderr=None)
- for line in cmd.ReadStdOutput():
- print line
- for line in cmd.ReadErrOutput():
- print line
-
|