GRASS Programmer's Manual  6.4.2(2012)
gcmd.py
Go to the documentation of this file.
00001 """!
00002 @package gcmd
00003 
00004 @brief wxGUI command interface
00005 
00006 Classes:
00007  - GError
00008  - GWarning
00009  - GMessage
00010  - GException
00011  - Popen (from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/440554)
00012  - Command
00013  - CommandThread
00014 
00015 Functions:
00016  - RunCommand
00017 
00018 (C) 2007-2008, 2010-2011 by the GRASS Development Team
00019 This program is free software under the GNU General Public
00020 License (>=v2). Read the file COPYING that comes with GRASS
00021 for details.
00022 
00023 @author Jachym Cepicky
00024 @author Martin Landa <landa.martin gmail.com>
00025 """
00026 
00027 import os
00028 import sys
00029 import time
00030 import errno
00031 import signal
00032 import locale
00033 import traceback
00034 import types
00035 
00036 import wx
00037 
00038 try:
00039     import subprocess
00040 except:
00041     compatPath = os.path.join(globalvar.ETCWXDIR, "compat")
00042     sys.path.append(compatPath)
00043     import subprocess
00044 if subprocess.mswindows:
00045     from win32file import ReadFile, WriteFile
00046     from win32pipe import PeekNamedPipe
00047     import msvcrt
00048 else:
00049     import select
00050     import fcntl
00051 from threading import Thread
00052 
00053 import globalvar
00054 grassPath = os.path.join(globalvar.ETCDIR, "python")
00055 sys.path.append(grassPath)
00056 from grass.script import core as grass
00057 
00058 import utils
00059 from debug import Debug as Debug
00060 
00061 class GError:
00062     def __init__(self, message, parent = None, caption = None, showTraceback = True):
00063         if not caption:
00064             caption = _('Error')
00065         style = wx.OK | wx.ICON_ERROR | wx.CENTRE
00066         exc_type, exc_value, exc_traceback = sys.exc_info()
00067         if exc_traceback:
00068             exception = traceback.format_exc()
00069             reason = exception.splitlines()[-1].split(':', 1)[-1].strip()
00070         
00071         if Debug.GetLevel() > 0 and exc_traceback:
00072             sys.stderr.write(exception)
00073         
00074         if showTraceback and exc_traceback:
00075             wx.MessageBox(parent = parent,
00076                           message = message + '\n\n%s: %s\n\n%s' % \
00077                               (_('Reason'),
00078                                reason, exception),
00079                           caption = caption,
00080                           style = style)
00081         else:
00082             wx.MessageBox(parent = parent,
00083                           message = message,
00084                           caption = caption,
00085                           style = style)
00086 
00087 class GWarning:
00088     def __init__(self, message, parent = None):
00089         caption = _('Warning')
00090         style = wx.OK | wx.ICON_WARNING | wx.CENTRE
00091         wx.MessageBox(parent = parent,
00092                       message = message,
00093                       caption = caption,
00094                       style = style)
00095         
00096 class GMessage:
00097     def __init__(self, message, parent = None):
00098         caption = _('Message')
00099         style = wx.OK | wx.ICON_INFORMATION | wx.CENTRE
00100         wx.MessageBox(parent = parent,
00101                       message = message,
00102                       caption = caption,
00103                       style = style)
00104 
00105 class GException(Exception):
00106     def __init__(self, value):
00107         self.value = value
00108 
00109     def __str__(self):
00110         return str(self.value)
00111 
00112 class Popen(subprocess.Popen):
00113     """!Subclass subprocess.Popen"""
00114     def __init__(self, *args, **kwargs):
00115         if subprocess.mswindows:
00116             try:
00117                 kwargs['args'] = map(utils.EncodeString, kwargs['args'])
00118             except KeyError:
00119                 if len(args) > 0:
00120                     targs = list(args)
00121                     targs[0] = map(utils.EncodeString, args[0])
00122                     args = tuple(targs)
00123         
00124         subprocess.Popen.__init__(self, *args, **kwargs)
00125         
00126     def recv(self, maxsize = None):
00127         return self._recv('stdout', maxsize)
00128     
00129     def recv_err(self, maxsize = None):
00130         return self._recv('stderr', maxsize)
00131 
00132     def send_recv(self, input = '', maxsize = None):
00133         return self.send(input), self.recv(maxsize), self.recv_err(maxsize)
00134 
00135     def get_conn_maxsize(self, which, maxsize):
00136         if maxsize is None:
00137             maxsize = 1024
00138         elif maxsize < 1:
00139             maxsize = 1
00140         return getattr(self, which), maxsize
00141     
00142     def _close(self, which):
00143         getattr(self, which).close()
00144         setattr(self, which, None)
00145 
00146     def kill(self):
00147         """!Try to kill running process"""
00148         if subprocess.mswindows:
00149             import win32api
00150             handle = win32api.OpenProcess(1, 0, self.pid)
00151             return (0 != win32api.TerminateProcess(handle, 0))
00152         else:
00153             try:
00154                 os.kill(-self.pid, signal.SIGTERM) # kill whole group
00155             except OSError:
00156                 pass
00157 
00158     if subprocess.mswindows:
00159         def send(self, input):
00160             if not self.stdin:
00161                 return None
00162 
00163             try:
00164                 x = msvcrt.get_osfhandle(self.stdin.fileno())
00165                 (errCode, written) = WriteFile(x, input)
00166             except ValueError:
00167                 return self._close('stdin')
00168             except (subprocess.pywintypes.error, Exception), why:
00169                 if why[0] in (109, errno.ESHUTDOWN):
00170                     return self._close('stdin')
00171                 raise
00172 
00173             return written
00174 
00175         def _recv(self, which, maxsize):
00176             conn, maxsize = self.get_conn_maxsize(which, maxsize)
00177             if conn is None:
00178                 return None
00179             
00180             try:
00181                 x = msvcrt.get_osfhandle(conn.fileno())
00182                 (read, nAvail, nMessage) = PeekNamedPipe(x, 0)
00183                 if maxsize < nAvail:
00184                     nAvail = maxsize
00185                 if nAvail > 0:
00186                     (errCode, read) = ReadFile(x, nAvail, None)
00187             except ValueError:
00188                 return self._close(which)
00189             except (subprocess.pywintypes.error, Exception), why:
00190                 if why[0] in (109, errno.ESHUTDOWN):
00191                     return self._close(which)
00192                 raise
00193             
00194             if self.universal_newlines:
00195                 read = self._translate_newlines(read)
00196             return read
00197 
00198     else:
00199         def send(self, input):
00200             if not self.stdin:
00201                 return None
00202 
00203             if not select.select([], [self.stdin], [], 0)[1]:
00204                 return 0
00205 
00206             try:
00207                 written = os.write(self.stdin.fileno(), input)
00208             except OSError, why:
00209                 if why[0] == errno.EPIPE: #broken pipe
00210                     return self._close('stdin')
00211                 raise
00212 
00213             return written
00214 
00215         def _recv(self, which, maxsize):
00216             conn, maxsize = self.get_conn_maxsize(which, maxsize)
00217             if conn is None:
00218                 return None
00219             
00220             flags = fcntl.fcntl(conn, fcntl.F_GETFL)
00221             if not conn.closed:
00222                 fcntl.fcntl(conn, fcntl.F_SETFL, flags| os.O_NONBLOCK)
00223             
00224             try:
00225                 if not select.select([conn], [], [], 0)[0]:
00226                     return ''
00227                 
00228                 r = conn.read(maxsize)
00229                 
00230                 if not r:
00231                     return self._close(which)
00232     
00233                 if self.universal_newlines:
00234                     r = self._translate_newlines(r)
00235                 return r
00236             finally:
00237                 if not conn.closed:
00238                     fcntl.fcntl(conn, fcntl.F_SETFL, flags)
00239 
00240 message = "Other end disconnected!"
00241 
00242 def recv_some(p, t = .1, e = 1, tr = 5, stderr = 0):
00243     if tr < 1:
00244         tr = 1
00245     x = time.time()+t
00246     y = []
00247     r = ''
00248     pr = p.recv
00249     if stderr:
00250         pr = p.recv_err
00251     while time.time() < x or r:
00252         r = pr()
00253         if r is None:
00254             if e:
00255                 raise Exception(message)
00256             else:
00257                 break
00258         elif r:
00259             y.append(r)
00260         else:
00261             time.sleep(max((x-time.time())/tr, 0))
00262     return ''.join(y)
00263     
00264 def send_all(p, data):
00265     while len(data):
00266         sent = p.send(data)
00267         if sent is None:
00268             raise Exception(message)
00269         data = buffer(data, sent)
00270 
00271 class Command:
00272     """!Run command in separate thread. Used for commands launched
00273     on the background.
00274     
00275     If stdout/err is redirected, write() method is required for the
00276     given classes.
00277 
00278     @code
00279         cmd = Command(cmd=['d.rast', 'elevation.dem'], verbose=3, wait=True)
00280 
00281         if cmd.returncode == None:
00282             print 'RUNNING?'
00283         elif cmd.returncode == 0:
00284             print 'SUCCESS'
00285         else:
00286             print 'FAILURE (%d)' % cmd.returncode
00287     @endcode
00288 
00289     @param cmd     command given as list
00290     @param stdin   standard input stream
00291     @param verbose verbose level [0, 3] (--q, --v)
00292     @param wait    wait for child execution terminated
00293     @param rerr    error handling (when CmdError raised).
00294     True for redirection to stderr, False for GUI dialog,
00295     None for no operation (quiet mode)
00296     @param stdout  redirect standard output or None
00297     @param stderr  redirect standard error output or None
00298     """
00299     def __init__ (self, cmd, stdin = None,
00300                   verbose = None, wait = True, rerr = False,
00301                   stdout = None, stderr = None):
00302         Debug.msg(1, "gcmd.Command(): %s" % ' '.join(cmd))
00303         self.cmd = cmd
00304         self.stderr = stderr
00305         
00306         #
00307         # set verbosity level
00308         #
00309         verbose_orig = None
00310         if ('--q' not in self.cmd and '--quiet' not in self.cmd) and \
00311                 ('--v' not in self.cmd and '--verbose' not in self.cmd):
00312             if verbose is not None:
00313                 if verbose == 0:
00314                     self.cmd.append('--quiet')
00315                 elif verbose == 3:
00316                     self.cmd.append('--verbose')
00317                 else:
00318                     verbose_orig = os.getenv("GRASS_VERBOSE")
00319                     os.environ["GRASS_VERBOSE"] = str(verbose)
00320 
00321         #
00322         # create command thread
00323         #
00324         self.cmdThread = CommandThread(cmd, stdin,
00325                                        stdout, stderr)
00326         self.cmdThread.start()
00327         
00328         if wait:
00329             self.cmdThread.join()
00330             if self.cmdThread.module:
00331                 self.cmdThread.module.wait()
00332                 self.returncode = self.cmdThread.module.returncode
00333             else:
00334                 self.returncode = 1
00335         else:
00336             self.cmdThread.join(0.5)
00337             self.returncode = None
00338 
00339         if self.returncode is not None:
00340             Debug.msg (3, "Command(): cmd='%s', wait=%s, returncode=%d, alive=%s" % \
00341                            (' '.join(cmd), wait, self.returncode, self.cmdThread.isAlive()))
00342             if rerr is not None and self.returncode != 0:
00343                 if rerr is False: # GUI dialog
00344                     raise GException("%s '%s'%s%s%s %s%s" % \
00345                                          (_("Execution failed:"),
00346                                           ' '.join(self.cmd),
00347                                           os.linesep, os.linesep,
00348                                           _("Details:"),
00349                                           os.linesep,
00350                                           _("Error: ") + self.__GetError()))
00351                 elif rerr == sys.stderr: # redirect message to sys
00352                     stderr.write("Execution failed: '%s'" % (' '.join(self.cmd)))
00353                     stderr.write("%sDetails:%s%s" % (os.linesep,
00354                                                      _("Error: ") + self.__GetError(),
00355                                                      os.linesep))
00356             else:
00357                 pass # nop
00358         else:
00359             Debug.msg (3, "Command(): cmd='%s', wait=%s, returncode=?, alive=%s" % \
00360                            (' '.join(cmd), wait, self.cmdThread.isAlive()))
00361 
00362         if verbose_orig:
00363             os.environ["GRASS_VERBOSE"] = verbose_orig
00364         elif "GRASS_VERBOSE" in os.environ:
00365             del os.environ["GRASS_VERBOSE"]
00366             
00367     def __ReadOutput(self, stream):
00368         """!Read stream and return list of lines
00369 
00370         @param stream stream to be read
00371         """
00372         lineList = []
00373 
00374         if stream is None:
00375             return lineList
00376 
00377         while True:
00378             line = stream.readline()
00379             if not line:
00380                 break
00381             line = line.replace('%s' % os.linesep, '').strip()
00382             lineList.append(line)
00383 
00384         return lineList
00385                     
00386     def __ReadErrOutput(self):
00387         """!Read standard error output and return list of lines"""
00388         return self.__ReadOutput(self.cmdThread.module.stderr)
00389 
00390     def __ProcessStdErr(self):
00391         """
00392         Read messages/warnings/errors from stderr
00393 
00394         @return list of (type, message)
00395         """
00396         if self.stderr is None:
00397             lines = self.__ReadErrOutput()
00398         else:
00399             lines = self.cmdThread.error.strip('%s' % os.linesep). \
00400                 split('%s' % os.linesep)
00401         
00402         msg = []
00403 
00404         type    = None
00405         content = ""
00406         for line in lines:
00407             if len(line) == 0:
00408                 continue
00409             if 'GRASS_' in line: # error or warning
00410                 if 'GRASS_INFO_WARNING' in line: # warning
00411                     type = "WARNING"
00412                 elif 'GRASS_INFO_ERROR' in line: # error
00413                     type = "ERROR"
00414                 elif 'GRASS_INFO_END': # end of message
00415                     msg.append((type, content))
00416                     type = None
00417                     content = ""
00418                 
00419                 if type:
00420                     content += line.split(':', 1)[1].strip()
00421             else: # stderr
00422                 msg.append((None, line.strip()))
00423 
00424         return msg
00425 
00426     def __GetError(self):
00427         """!Get error message or ''"""
00428         if not self.cmdThread.module:
00429             return _("Unable to exectute command: '%s'") % ' '.join(self.cmd)
00430 
00431         for type, msg in self.__ProcessStdErr():
00432             if type == 'ERROR':
00433                 enc = locale.getdefaultlocale()[1]
00434                 if enc:
00435                     return unicode(msg, enc)
00436                 else:
00437                     return msg
00438         
00439         return ''
00440     
00441 class CommandThread(Thread):
00442     """!Create separate thread for command. Used for commands launched
00443     on the background."""
00444     def __init__ (self, cmd, stdin = None,
00445                   stdout = sys.stdout, stderr = sys.stderr):
00446         """
00447         @param cmd command (given as list)
00448         @param stdin standard input stream 
00449         @param stdout redirect standard output or None
00450         @param stderr redirect standard error output or None
00451         """
00452         Thread.__init__(self)
00453         
00454         self.cmd    = cmd
00455         self.stdin  = stdin
00456         self.stdout = stdout
00457         self.stderr = stderr
00458         
00459         self.module = None
00460         self.error  = ''
00461         
00462         self._want_abort = False
00463         self.aborted = False
00464         
00465         self.setDaemon(True)
00466         
00467         # set message formatting
00468         self.message_format = os.getenv("GRASS_MESSAGE_FORMAT")
00469         os.environ["GRASS_MESSAGE_FORMAT"] = "gui"
00470         
00471     def __del__(self):
00472         if self.message_format:
00473             os.environ["GRASS_MESSAGE_FORMAT"] = self.message_format
00474         else:
00475             del os.environ["GRASS_MESSAGE_FORMAT"]
00476         
00477     def run(self):
00478         """!Run command"""
00479         if len(self.cmd) == 0:
00480             return
00481 
00482         Debug.msg(1, "gcmd.CommandThread(): %s" % ' '.join(self.cmd))
00483 
00484         self.startTime = time.time()
00485 
00486         # TODO: replace ugly hack bellow
00487         args = self.cmd
00488         if sys.platform == 'win32' and os.path.splitext(self.cmd[0])[1] == '.py':
00489             os.chdir(os.path.join(os.getenv('GISBASE'), 'etc', 'gui', 'scripts'))
00490             args = [sys.executable, self.cmd[0]] + self.cmd[1:]
00491         
00492         try:
00493             self.module = Popen(args,
00494                                 stdin = subprocess.PIPE,
00495                                 stdout = subprocess.PIPE,
00496                                 stderr = subprocess.PIPE,
00497                                 shell = sys.platform == "win32")
00498         except OSError, e:
00499             self.error = str(e)
00500             print >> sys.stderr, e
00501             return 1
00502         
00503         if self.stdin: # read stdin if requested ...
00504             self.module.stdin.write(self.stdin)
00505             self.module.stdin.close()
00506             
00507         # redirect standard outputs...
00508         self._redirect_stream()
00509         
00510     def _redirect_stream(self):
00511         """!Redirect stream"""
00512         if self.stdout:
00513             # make module stdout/stderr non-blocking
00514             out_fileno = self.module.stdout.fileno()
00515             if not subprocess.mswindows:
00516                 flags = fcntl.fcntl(out_fileno, fcntl.F_GETFL)
00517                 fcntl.fcntl(out_fileno, fcntl.F_SETFL, flags| os.O_NONBLOCK)
00518                 
00519         if self.stderr:
00520             # make module stdout/stderr non-blocking
00521             out_fileno = self.module.stderr.fileno()
00522             if not subprocess.mswindows:
00523                 flags = fcntl.fcntl(out_fileno, fcntl.F_GETFL)
00524                 fcntl.fcntl(out_fileno, fcntl.F_SETFL, flags| os.O_NONBLOCK)
00525         
00526         # wait for the process to end, sucking in stuff until it does end
00527         while self.module.poll() is None:
00528             if self._want_abort: # abort running process
00529                 self.module.kill()
00530                 self.aborted = True
00531                 return 
00532             if self.stdout:
00533                 line = recv_some(self.module, e = 0, stderr = 0)
00534                 self.stdout.write(line)
00535             if self.stderr:
00536                 line = recv_some(self.module, e = 0, stderr = 1)
00537                 self.stderr.write(line)
00538                 if len(line) > 0:
00539                     self.error = line
00540 
00541         # get the last output
00542         if self.stdout:
00543             line = recv_some(self.module, e = 0, stderr = 0)
00544             self.stdout.write(line)
00545         if self.stderr:
00546             line = recv_some(self.module, e = 0, stderr = 1)
00547             self.stderr.write(line)
00548             if len(line) > 0:
00549                 self.error = line
00550             
00551     def abort(self):
00552         """!Abort running process, used by main thread to signal an abort"""
00553         self._want_abort = True
00554     
00555 def _formatMsg(text):
00556     """!Format error messages for dialogs
00557     """
00558     message = ''
00559     for line in text.splitlines():
00560         if len(line) == 0:
00561             continue
00562         elif 'GRASS_INFO_MESSAGE' in line:
00563             message += line.split(':', 1)[1].strip() + '\n'
00564         elif 'GRASS_INFO_WARNING' in line:
00565             message += line.split(':', 1)[1].strip() + '\n'
00566         elif 'GRASS_INFO_ERROR' in line:
00567             message += line.split(':', 1)[1].strip() + '\n'
00568         elif 'GRASS_INFO_END' in line:
00569             return message
00570         else:
00571             message += line.strip() + '\n'
00572     
00573     return message
00574 
00575 def RunCommand(prog, flags = "", overwrite = False, quiet = False, verbose = False,
00576                parent = None, read = False, stdin = None, getErrorMsg = False, **kwargs):
00577     """!Run GRASS command
00578 
00579     @param prog program to run
00580     @param flags flags given as a string
00581     @param overwrite, quiet, verbose flags
00582     @param parent parent window for error messages
00583     @param read fetch stdout
00584     @param stdin stdin or None
00585     @param getErrorMsg get error messages on failure
00586     @param kwargs program parameters
00587     
00588     @return returncode (read == False and getErrorMsg == False)
00589     @return returncode, messages (read == False and getErrorMsg == True)
00590     @return stdout (read == True and getErrorMsg == False)
00591     @return returncode, stdout, messages (read == True and getErrorMsg == True)
00592     @return stdout, stderr
00593     """
00594     cmdString = ' '.join(grass.make_command(prog, flags, overwrite,
00595                                             quiet, verbose, **kwargs))
00596     
00597     Debug.msg(1, "gcmd.RunCommand(): %s" % cmdString)
00598     
00599     kwargs['stderr'] = subprocess.PIPE
00600     
00601     if read:
00602         kwargs['stdout'] = subprocess.PIPE
00603     
00604     if stdin:
00605         kwargs['stdin'] = subprocess.PIPE
00606     
00607     ps = grass.start_command(prog, flags, overwrite, quiet, verbose, **kwargs)
00608     
00609     Debug.msg(2, "gcmd.RunCommand(): command started")
00610 
00611     if stdin:
00612         ps.stdin.write(stdin)
00613         ps.stdin.close()
00614         ps.stdin = None
00615     
00616     Debug.msg(3, "gcmd.RunCommand(): decoding string")
00617     stdout, stderr = map(utils.DecodeString, ps.communicate())
00618     
00619     ret = ps.returncode
00620     Debug.msg(1, "gcmd.RunCommand(): get return code %d" % ret)
00621         
00622     Debug.msg(3, "gcmd.RunCommand(): print error")
00623     if ret != 0 and parent:
00624         Debug.msg(2, "gcmd.RunCommand(): error %s" % stderr)
00625         if (stderr == None):
00626             Debug.msg(2, "gcmd.RunCommand(): nothing to print ???")
00627         else:
00628             GError(parent = parent,
00629                    message = stderr)
00630         
00631     Debug.msg(3, "gcmd.RunCommand(): print read error")
00632     if not read:
00633         if not getErrorMsg:
00634             return ret
00635         else:
00636             return ret, _formatMsg(stderr)
00637 
00638     if stdout:
00639         Debug.msg(2, "gcmd.RunCommand(): return stdout\n'%s'" % stdout)
00640     else:
00641         Debug.msg(2, "gcmd.RunCommand(): return stdout = None")
00642     if not getErrorMsg:
00643         return stdout
00644     
00645     Debug.msg(2, "gcmd.RunCommand(): return ret, stdout")
00646     if read and getErrorMsg:
00647         return ret, stdout, _formatMsg(stderr)
00648     
00649     Debug.msg(2, "gcmd.RunCommand(): return result")
00650     return stdout, _formatMsg(stderr)
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines