123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649 |
- """!
- @package gcmd
- @brief wxGUI command interface
- Classes:
- - GError
- - GWarning
- - GMessage
- - GException
- - Popen (from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/440554)
- - Command
- - CommandThread
- Functions:
- - RunCommand
- (C) 2007-2008, 2010-2011 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
- @author Martin Landa <landa.martin gmail.com>
- """
- import os
- import sys
- import time
- import errno
- import signal
- import locale
- import traceback
- import types
- 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 globalvar
- grassPath = os.path.join(globalvar.ETCDIR, "python")
- sys.path.append(grassPath)
- from grass.script import core as grass
- import utils
- from debug import Debug as Debug
- class GError:
- def __init__(self, message, parent = None, caption = None, showTraceback = True):
- if not caption:
- caption = _('Error')
- style = wx.OK | wx.ICON_ERROR | wx.CENTRE
- exc_type, exc_value, exc_traceback = sys.exc_info()
- if exc_traceback:
- exception = traceback.format_exc()
- reason = exception.splitlines()[-1].split(':', 1)[-1].strip()
-
- if Debug.GetLevel() > 0 and exc_traceback:
- sys.stderr.write(exception)
-
- if showTraceback and exc_traceback:
- wx.MessageBox(parent = parent,
- message = message + '\n\n%s: %s\n\n%s' % \
- (_('Reason'),
- reason, exception),
- caption = caption,
- style = style)
- else:
- wx.MessageBox(parent = parent,
- message = message,
- caption = caption,
- style = style)
- class GWarning:
- def __init__(self, message, parent = None):
- caption = _('Warning')
- style = wx.OK | wx.ICON_WARNING | wx.CENTRE
- wx.MessageBox(parent = parent,
- message = message,
- caption = caption,
- style = style)
-
- class GMessage:
- def __init__(self, message, parent = None):
- caption = _('Message')
- style = wx.OK | wx.ICON_INFORMATION | wx.CENTRE
- wx.MessageBox(parent = parent,
- message = message,
- caption = caption,
- style = style)
- class GException(Exception):
- def __init__(self, value = ''):
- self.value = value
- def __str__(self):
- return str(self.value)
- class Popen(subprocess.Popen):
- """!Subclass subprocess.Popen"""
- def __init__(self, *args, **kwargs):
- if subprocess.mswindows:
- try:
- kwargs['args'] = map(utils.EncodeString, kwargs['args'])
- except KeyError:
- if len(args) > 0:
- targs = list(args)
- targs[0] = map(utils.EncodeString, args[0])
- args = tuple(targs)
-
- subprocess.Popen.__init__(self, *args, **kwargs)
-
- 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)
- class Command:
- """!Run command in separate thread. Used for commands launched
- on the background.
- 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
- """
- def __init__ (self, cmd, stdin = None,
- verbose = None, wait = True, rerr = False,
- stdout = None, stderr = None):
- """
- @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 GException 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
- """
- Debug.msg(1, "gcmd.Command(): %s" % ' '.join(cmd))
- self.cmd = cmd
- 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)
- #
- # create command thread
- #
- self.cmdThread = CommandThread(cmd, stdin,
- stdout, stderr)
- 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 GException("%s '%s'%s%s%s %s%s" % \
- (_("Execution failed:"),
- ' '.join(self.cmd),
- os.linesep, os.linesep,
- _("Details:"),
- os.linesep,
- _("Error: ") + self.__GetError()))
- elif rerr == sys.stderr: # redirect message to sys
- stderr.write("Execution failed: '%s'" % (' '.join(self.cmd)))
- stderr.write("%sDetails:%s%s" % (os.linesep,
- _("Error: ") + self.__GetError(),
- os.linesep))
- else:
- pass # nop
- else:
- Debug.msg (3, "Command(): cmd='%s', wait=%s, returncode=?, alive=%s" % \
- (' '.join(cmd), wait, self.cmdThread.isAlive()))
- if verbose_orig:
- os.environ["GRASS_VERBOSE"] = verbose_orig
- elif "GRASS_VERBOSE" in os.environ:
- del os.environ["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 __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.error.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)[1].strip()
- else: # stderr
- msg.append((None, line.strip()))
- return msg
- def __GetError(self):
- """!Get error message or ''"""
- if not self.cmdThread.module:
- return _("Unable to exectute command: '%s'") % ' '.join(self.cmd)
- for type, msg in self.__ProcessStdErr():
- if type == 'ERROR':
- enc = locale.getdefaultlocale()[1]
- if enc:
- return unicode(msg, enc)
- else:
- return msg
-
- return ''
-
- class CommandThread(Thread):
- """!Create separate thread for command. Used for commands launched
- on the background."""
- def __init__ (self, cmd, stdin = None,
- stdout = sys.stdout, stderr = sys.stderr):
- """
- @param cmd command (given as list)
- @param stdin standard input stream
- @param stdout redirect standard output or None
- @param stderr redirect standard error output or None
- """
- Thread.__init__(self)
-
- self.cmd = cmd
- self.stdin = stdin
- self.stdout = stdout
- self.stderr = stderr
-
- self.module = None
- self.error = ''
-
- self._want_abort = False
- self.aborted = False
-
- self.setDaemon(True)
-
- # set message formatting
- self.message_format = os.getenv("GRASS_MESSAGE_FORMAT")
- os.environ["GRASS_MESSAGE_FORMAT"] = "gui"
-
- def __del__(self):
- if self.message_format:
- os.environ["GRASS_MESSAGE_FORMAT"] = self.message_format
- else:
- del os.environ["GRASS_MESSAGE_FORMAT"]
-
- def run(self):
- """!Run command"""
- if len(self.cmd) == 0:
- return
- Debug.msg(1, "gcmd.CommandThread(): %s" % ' '.join(self.cmd))
- self.startTime = time.time()
- try:
- self.module = Popen(self.cmd,
- stdin = subprocess.PIPE,
- stdout = subprocess.PIPE,
- stderr = subprocess.PIPE,
- shell = sys.platform == "win32")
- except OSError, e:
- self.error = str(e)
- return 1
-
- if self.stdin: # read stdin if requested ...
- self.module.stdin.write(self.stdin)
- self.module.stdin.close()
-
- # redirect standard outputs...
- self._redirect_stream()
-
- 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:
- if self._want_abort: # abort running process
- self.module.kill()
- self.aborted = True
- return
- if self.stdout:
- line = recv_some(self.module, e = 0, stderr = 0)
- self.stdout.write(line)
- if self.stderr:
- line = recv_some(self.module, e = 0, stderr = 1)
- self.stderr.write(line)
- if len(line) > 0:
- self.error = line
- # get the last output
- if self.stdout:
- line = recv_some(self.module, e = 0, stderr = 0)
- self.stdout.write(line)
- if self.stderr:
- line = recv_some(self.module, e = 0, stderr = 1)
- self.stderr.write(line)
- if len(line) > 0:
- self.error = line
-
- def abort(self):
- """!Abort running process, used by main thread to signal an abort"""
- self._want_abort = True
-
- def _formatMsg(text):
- """!Format error messages for dialogs
- """
- message = ''
- for line in text.splitlines():
- if len(line) == 0:
- continue
- elif 'GRASS_INFO_MESSAGE' in line:
- message += line.split(':', 1)[1].strip() + '\n'
- elif 'GRASS_INFO_WARNING' in line:
- message += line.split(':', 1)[1].strip() + '\n'
- elif 'GRASS_INFO_ERROR' in line:
- message += line.split(':', 1)[1].strip() + '\n'
- elif 'GRASS_INFO_END' in line:
- return message
- else:
- message += line.strip() + '\n'
-
- return message
- def RunCommand(prog, flags = "", overwrite = False, quiet = False, verbose = False,
- parent = None, read = False, parse = None, stdin = None, getErrorMsg = False, **kwargs):
- """!Run GRASS command
- @param prog program to run
- @param flags flags given as a string
- @param overwrite, quiet, verbose flags
- @param parent parent window for error messages
- @param read fetch stdout
- @param parse fn to parse stdout (e.g. grass.parse_key_val) or None
- @param stdin stdin or None
- @param getErrorMsg get error messages on failure
- @param kwargs program parameters
-
- @return returncode (read == False and getErrorMsg == False)
- @return returncode, messages (read == False and getErrorMsg == True)
- @return stdout (read == True and getErrorMsg == False)
- @return returncode, stdout, messages (read == True and getErrorMsg == True)
- @return stdout, stderr
- """
- cmdString = ' '.join(grass.make_command(prog, flags, overwrite,
- quiet, verbose, **kwargs))
-
- Debug.msg(1, "gcmd.RunCommand(): %s" % cmdString)
-
- kwargs['stderr'] = subprocess.PIPE
-
- if read:
- kwargs['stdout'] = subprocess.PIPE
-
- if stdin:
- kwargs['stdin'] = subprocess.PIPE
-
- ps = grass.start_command(prog, flags, overwrite, quiet, verbose, **kwargs)
-
- Debug.msg(2, "gcmd.RunCommand(): command started")
- if stdin:
- ps.stdin.write(stdin)
- ps.stdin.close()
- ps.stdin = None
-
- Debug.msg(3, "gcmd.RunCommand(): decoding string")
- stdout, stderr = map(utils.DecodeString, ps.communicate())
-
- ret = ps.returncode
- Debug.msg(1, "gcmd.RunCommand(): get return code %d" % ret)
-
- Debug.msg(3, "gcmd.RunCommand(): print error")
- if ret != 0 and parent:
- Debug.msg(2, "gcmd.RunCommand(): error %s" % stderr)
- if (stderr == None):
- Debug.msg(2, "gcmd.RunCommand(): nothing to print ???")
- else:
- GError(parent = parent,
- message = stderr)
-
- Debug.msg(3, "gcmd.RunCommand(): print read error")
- if not read:
- if not getErrorMsg:
- return ret
- else:
- return ret, _formatMsg(stderr)
- if stdout:
- Debug.msg(2, "gcmd.RunCommand(): return stdout\n'%s'" % stdout)
- else:
- Debug.msg(2, "gcmd.RunCommand(): return stdout = None")
-
- if parse:
- stdout = parse(stdout)
-
- if not getErrorMsg:
- return stdout
-
- Debug.msg(2, "gcmd.RunCommand(): return ret, stdout")
- if read and getErrorMsg:
- return ret, stdout, _formatMsg(stderr)
-
- Debug.msg(2, "gcmd.RunCommand(): return result")
- return stdout, _formatMsg(stderr)
|