GRASS Programmer's Manual
6.4.2(2012)
|
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)