|
GRASS Programmer's Manual
6.5.svn(2012)-r51648
|
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)