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