GRASS Programmer's Manual  6.4.2(2012)
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
gcmd.py
Go to the documentation of this file.
1 """!
2 @package gcmd
3 
4 @brief wxGUI command interface
5 
6 Classes:
7  - GError
8  - GWarning
9  - GMessage
10  - GException
11  - Popen (from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/440554)
12  - Command
13  - CommandThread
14 
15 Functions:
16  - RunCommand
17 
18 (C) 2007-2008, 2010-2011 by the GRASS Development Team
19 This program is free software under the GNU General Public
20 License (>=v2). Read the file COPYING that comes with GRASS
21 for details.
22 
23 @author Jachym Cepicky
24 @author Martin Landa <landa.martin gmail.com>
25 """
26 
27 import os
28 import sys
29 import time
30 import errno
31 import signal
32 import locale
33 import traceback
34 import types
35 
36 import wx
37 
38 try:
39  import subprocess
40 except:
41  compatPath = os.path.join(globalvar.ETCWXDIR, "compat")
42  sys.path.append(compatPath)
43  import subprocess
44 if subprocess.mswindows:
45  from win32file import ReadFile, WriteFile
46  from win32pipe import PeekNamedPipe
47  import msvcrt
48 else:
49  import select
50  import fcntl
51 from threading import Thread
52 
53 import globalvar
54 grassPath = os.path.join(globalvar.ETCDIR, "python")
55 sys.path.append(grassPath)
56 from grass.script import core as grass
57 
58 import utils
59 from debug import Debug as Debug
60 
61 class GError:
62  def __init__(self, message, parent = None, caption = None, showTraceback = True):
63  if not caption:
64  caption = _('Error')
65  style = wx.OK | wx.ICON_ERROR | wx.CENTRE
66  exc_type, exc_value, exc_traceback = sys.exc_info()
67  if exc_traceback:
68  exception = traceback.format_exc()
69  reason = exception.splitlines()[-1].split(':', 1)[-1].strip()
70 
71  if Debug.GetLevel() > 0 and exc_traceback:
72  sys.stderr.write(exception)
73 
74  if showTraceback and exc_traceback:
75  wx.MessageBox(parent = parent,
76  message = message + '\n\n%s: %s\n\n%s' % \
77  (_('Reason'),
78  reason, exception),
79  caption = caption,
80  style = style)
81  else:
82  wx.MessageBox(parent = parent,
83  message = message,
84  caption = caption,
85  style = style)
86 
87 class GWarning:
88  def __init__(self, message, parent = None):
89  caption = _('Warning')
90  style = wx.OK | wx.ICON_WARNING | wx.CENTRE
91  wx.MessageBox(parent = parent,
92  message = message,
93  caption = caption,
94  style = style)
95 
96 class GMessage:
97  def __init__(self, message, parent = None):
98  caption = _('Message')
99  style = wx.OK | wx.ICON_INFORMATION | wx.CENTRE
100  wx.MessageBox(parent = parent,
101  message = message,
102  caption = caption,
103  style = style)
104 
105 class GException(Exception):
106  def __init__(self, value):
107  self.value = value
108 
109  def __str__(self):
110  return str(self.value)
111 
112 class Popen(subprocess.Popen):
113  """!Subclass subprocess.Popen"""
114  def __init__(self, *args, **kwargs):
115  if subprocess.mswindows:
116  try:
117  kwargs['args'] = map(utils.EncodeString, kwargs['args'])
118  except KeyError:
119  if len(args) > 0:
120  targs = list(args)
121  targs[0] = map(utils.EncodeString, args[0])
122  args = tuple(targs)
123 
124  subprocess.Popen.__init__(self, *args, **kwargs)
125 
126  def recv(self, maxsize = None):
127  return self._recv('stdout', maxsize)
128 
129  def recv_err(self, maxsize = None):
130  return self._recv('stderr', maxsize)
131 
132  def send_recv(self, input = '', maxsize = None):
133  return self.send(input), self.recv(maxsize), self.recv_err(maxsize)
134 
135  def get_conn_maxsize(self, which, maxsize):
136  if maxsize is None:
137  maxsize = 1024
138  elif maxsize < 1:
139  maxsize = 1
140  return getattr(self, which), maxsize
141 
142  def _close(self, which):
143  getattr(self, which).close()
144  setattr(self, which, None)
145 
146  def kill(self):
147  """!Try to kill running process"""
148  if subprocess.mswindows:
149  import win32api
150  handle = win32api.OpenProcess(1, 0, self.pid)
151  return (0 != win32api.TerminateProcess(handle, 0))
152  else:
153  try:
154  os.kill(-self.pid, signal.SIGTERM) # kill whole group
155  except OSError:
156  pass
157 
158  if subprocess.mswindows:
159  def send(self, input):
160  if not self.stdin:
161  return None
162 
163  try:
164  x = msvcrt.get_osfhandle(self.stdin.fileno())
165  (errCode, written) = WriteFile(x, input)
166  except ValueError:
167  return self._close('stdin')
168  except (subprocess.pywintypes.error, Exception), why:
169  if why[0] in (109, errno.ESHUTDOWN):
170  return self._close('stdin')
171  raise
172 
173  return written
174 
175  def _recv(self, which, maxsize):
176  conn, maxsize = self.get_conn_maxsize(which, maxsize)
177  if conn is None:
178  return None
179 
180  try:
181  x = msvcrt.get_osfhandle(conn.fileno())
182  (read, nAvail, nMessage) = PeekNamedPipe(x, 0)
183  if maxsize < nAvail:
184  nAvail = maxsize
185  if nAvail > 0:
186  (errCode, read) = ReadFile(x, nAvail, None)
187  except ValueError:
188  return self._close(which)
189  except (subprocess.pywintypes.error, Exception), why:
190  if why[0] in (109, errno.ESHUTDOWN):
191  return self._close(which)
192  raise
193 
194  if self.universal_newlines:
195  read = self._translate_newlines(read)
196  return read
197 
198  else:
199  def send(self, input):
200  if not self.stdin:
201  return None
202 
203  if not select.select([], [self.stdin], [], 0)[1]:
204  return 0
205 
206  try:
207  written = os.write(self.stdin.fileno(), input)
208  except OSError, why:
209  if why[0] == errno.EPIPE: #broken pipe
210  return self._close('stdin')
211  raise
212 
213  return written
214 
215  def _recv(self, which, maxsize):
216  conn, maxsize = self.get_conn_maxsize(which, maxsize)
217  if conn is None:
218  return None
219 
220  flags = fcntl.fcntl(conn, fcntl.F_GETFL)
221  if not conn.closed:
222  fcntl.fcntl(conn, fcntl.F_SETFL, flags| os.O_NONBLOCK)
223 
224  try:
225  if not select.select([conn], [], [], 0)[0]:
226  return ''
227 
228  r = conn.read(maxsize)
229 
230  if not r:
231  return self._close(which)
232 
233  if self.universal_newlines:
234  r = self._translate_newlines(r)
235  return r
236  finally:
237  if not conn.closed:
238  fcntl.fcntl(conn, fcntl.F_SETFL, flags)
239 
240 message = "Other end disconnected!"
241 
242 def recv_some(p, t = .1, e = 1, tr = 5, stderr = 0):
243  if tr < 1:
244  tr = 1
245  x = time.time()+t
246  y = []
247  r = ''
248  pr = p.recv
249  if stderr:
250  pr = p.recv_err
251  while time.time() < x or r:
252  r = pr()
253  if r is None:
254  if e:
255  raise Exception(message)
256  else:
257  break
258  elif r:
259  y.append(r)
260  else:
261  time.sleep(max((x-time.time())/tr, 0))
262  return ''.join(y)
263 
264 def send_all(p, data):
265  while len(data):
266  sent = p.send(data)
267  if sent is None:
268  raise Exception(message)
269  data = buffer(data, sent)
270 
271 class Command:
272  """!Run command in separate thread. Used for commands launched
273  on the background.
274 
275  If stdout/err is redirected, write() method is required for the
276  given classes.
277 
278  @code
279  cmd = Command(cmd=['d.rast', 'elevation.dem'], verbose=3, wait=True)
280 
281  if cmd.returncode == None:
282  print 'RUNNING?'
283  elif cmd.returncode == 0:
284  print 'SUCCESS'
285  else:
286  print 'FAILURE (%d)' % cmd.returncode
287  @endcode
288 
289  @param cmd command given as list
290  @param stdin standard input stream
291  @param verbose verbose level [0, 3] (--q, --v)
292  @param wait wait for child execution terminated
293  @param rerr error handling (when CmdError raised).
294  True for redirection to stderr, False for GUI dialog,
295  None for no operation (quiet mode)
296  @param stdout redirect standard output or None
297  @param stderr redirect standard error output or None
298  """
299  def __init__ (self, cmd, stdin = None,
300  verbose = None, wait = True, rerr = False,
301  stdout = None, stderr = None):
302  Debug.msg(1, "gcmd.Command(): %s" % ' '.join(cmd))
303  self.cmd = cmd
304  self.stderr = stderr
305 
306  #
307  # set verbosity level
308  #
309  verbose_orig = None
310  if ('--q' not in self.cmd and '--quiet' not in self.cmd) and \
311  ('--v' not in self.cmd and '--verbose' not in self.cmd):
312  if verbose is not None:
313  if verbose == 0:
314  self.cmd.append('--quiet')
315  elif verbose == 3:
316  self.cmd.append('--verbose')
317  else:
318  verbose_orig = os.getenv("GRASS_VERBOSE")
319  os.environ["GRASS_VERBOSE"] = str(verbose)
320 
321  #
322  # create command thread
323  #
324  self.cmdThread = CommandThread(cmd, stdin,
325  stdout, stderr)
326  self.cmdThread.start()
327 
328  if wait:
329  self.cmdThread.join()
330  if self.cmdThread.module:
331  self.cmdThread.module.wait()
332  self.returncode = self.cmdThread.module.returncode
333  else:
334  self.returncode = 1
335  else:
336  self.cmdThread.join(0.5)
337  self.returncode = None
338 
339  if self.returncode is not None:
340  Debug.msg (3, "Command(): cmd='%s', wait=%s, returncode=%d, alive=%s" % \
341  (' '.join(cmd), wait, self.returncode, self.cmdThread.isAlive()))
342  if rerr is not None and self.returncode != 0:
343  if rerr is False: # GUI dialog
344  raise GException("%s '%s'%s%s%s %s%s" % \
345  (_("Execution failed:"),
346  ' '.join(self.cmd),
347  os.linesep, os.linesep,
348  _("Details:"),
349  os.linesep,
350  _("Error: ") + self.__GetError()))
351  elif rerr == sys.stderr: # redirect message to sys
352  stderr.write("Execution failed: '%s'" % (' '.join(self.cmd)))
353  stderr.write("%sDetails:%s%s" % (os.linesep,
354  _("Error: ") + self.__GetError(),
355  os.linesep))
356  else:
357  pass # nop
358  else:
359  Debug.msg (3, "Command(): cmd='%s', wait=%s, returncode=?, alive=%s" % \
360  (' '.join(cmd), wait, self.cmdThread.isAlive()))
361 
362  if verbose_orig:
363  os.environ["GRASS_VERBOSE"] = verbose_orig
364  elif "GRASS_VERBOSE" in os.environ:
365  del os.environ["GRASS_VERBOSE"]
366 
367  def __ReadOutput(self, stream):
368  """!Read stream and return list of lines
369 
370  @param stream stream to be read
371  """
372  lineList = []
373 
374  if stream is None:
375  return lineList
376 
377  while True:
378  line = stream.readline()
379  if not line:
380  break
381  line = line.replace('%s' % os.linesep, '').strip()
382  lineList.append(line)
383 
384  return lineList
385 
386  def __ReadErrOutput(self):
387  """!Read standard error output and return list of lines"""
388  return self.__ReadOutput(self.cmdThread.module.stderr)
389 
390  def __ProcessStdErr(self):
391  """
392  Read messages/warnings/errors from stderr
393 
394  @return list of (type, message)
395  """
396  if self.stderr is None:
397  lines = self.__ReadErrOutput()
398  else:
399  lines = self.cmdThread.error.strip('%s' % os.linesep). \
400  split('%s' % os.linesep)
401 
402  msg = []
403 
404  type = None
405  content = ""
406  for line in lines:
407  if len(line) == 0:
408  continue
409  if 'GRASS_' in line: # error or warning
410  if 'GRASS_INFO_WARNING' in line: # warning
411  type = "WARNING"
412  elif 'GRASS_INFO_ERROR' in line: # error
413  type = "ERROR"
414  elif 'GRASS_INFO_END': # end of message
415  msg.append((type, content))
416  type = None
417  content = ""
418 
419  if type:
420  content += line.split(':', 1)[1].strip()
421  else: # stderr
422  msg.append((None, line.strip()))
423 
424  return msg
425 
426  def __GetError(self):
427  """!Get error message or ''"""
428  if not self.cmdThread.module:
429  return _("Unable to exectute command: '%s'") % ' '.join(self.cmd)
430 
431  for type, msg in self.__ProcessStdErr():
432  if type == 'ERROR':
433  enc = locale.getdefaultlocale()[1]
434  if enc:
435  return unicode(msg, enc)
436  else:
437  return msg
438 
439  return ''
440 
441 class CommandThread(Thread):
442  """!Create separate thread for command. Used for commands launched
443  on the background."""
444  def __init__ (self, cmd, stdin = None,
445  stdout = sys.stdout, stderr = sys.stderr):
446  """
447  @param cmd command (given as list)
448  @param stdin standard input stream
449  @param stdout redirect standard output or None
450  @param stderr redirect standard error output or None
451  """
452  Thread.__init__(self)
453 
454  self.cmd = cmd
455  self.stdin = stdin
456  self.stdout = stdout
457  self.stderr = stderr
458 
459  self.module = None
460  self.error = ''
461 
462  self._want_abort = False
463  self.aborted = False
464 
465  self.setDaemon(True)
466 
467  # set message formatting
468  self.message_format = os.getenv("GRASS_MESSAGE_FORMAT")
469  os.environ["GRASS_MESSAGE_FORMAT"] = "gui"
470 
471  def __del__(self):
472  if self.message_format:
473  os.environ["GRASS_MESSAGE_FORMAT"] = self.message_format
474  else:
475  del os.environ["GRASS_MESSAGE_FORMAT"]
476 
477  def run(self):
478  """!Run command"""
479  if len(self.cmd) == 0:
480  return
481 
482  Debug.msg(1, "gcmd.CommandThread(): %s" % ' '.join(self.cmd))
483 
484  self.startTime = time.time()
485 
486  # TODO: replace ugly hack bellow
487  args = self.cmd
488  if sys.platform == 'win32' and os.path.splitext(self.cmd[0])[1] == '.py':
489  os.chdir(os.path.join(os.getenv('GISBASE'), 'etc', 'gui', 'scripts'))
490  args = [sys.executable, self.cmd[0]] + self.cmd[1:]
491 
492  try:
493  self.module = Popen(args,
494  stdin = subprocess.PIPE,
495  stdout = subprocess.PIPE,
496  stderr = subprocess.PIPE,
497  shell = sys.platform == "win32")
498  except OSError, e:
499  self.error = str(e)
500  print >> sys.stderr, e
501  return 1
502 
503  if self.stdin: # read stdin if requested ...
504  self.module.stdin.write(self.stdin)
505  self.module.stdin.close()
506 
507  # redirect standard outputs...
508  self._redirect_stream()
509 
510  def _redirect_stream(self):
511  """!Redirect stream"""
512  if self.stdout:
513  # make module stdout/stderr non-blocking
514  out_fileno = self.module.stdout.fileno()
515  if not subprocess.mswindows:
516  flags = fcntl.fcntl(out_fileno, fcntl.F_GETFL)
517  fcntl.fcntl(out_fileno, fcntl.F_SETFL, flags| os.O_NONBLOCK)
518 
519  if self.stderr:
520  # make module stdout/stderr non-blocking
521  out_fileno = self.module.stderr.fileno()
522  if not subprocess.mswindows:
523  flags = fcntl.fcntl(out_fileno, fcntl.F_GETFL)
524  fcntl.fcntl(out_fileno, fcntl.F_SETFL, flags| os.O_NONBLOCK)
525 
526  # wait for the process to end, sucking in stuff until it does end
527  while self.module.poll() is None:
528  if self._want_abort: # abort running process
529  self.module.kill()
530  self.aborted = True
531  return
532  if self.stdout:
533  line = recv_some(self.module, e = 0, stderr = 0)
534  self.stdout.write(line)
535  if self.stderr:
536  line = recv_some(self.module, e = 0, stderr = 1)
537  self.stderr.write(line)
538  if len(line) > 0:
539  self.error = line
540 
541  # get the last output
542  if self.stdout:
543  line = recv_some(self.module, e = 0, stderr = 0)
544  self.stdout.write(line)
545  if self.stderr:
546  line = recv_some(self.module, e = 0, stderr = 1)
547  self.stderr.write(line)
548  if len(line) > 0:
549  self.error = line
550 
551  def abort(self):
552  """!Abort running process, used by main thread to signal an abort"""
553  self._want_abort = True
554 
555 def _formatMsg(text):
556  """!Format error messages for dialogs
557  """
558  message = ''
559  for line in text.splitlines():
560  if len(line) == 0:
561  continue
562  elif 'GRASS_INFO_MESSAGE' in line:
563  message += line.split(':', 1)[1].strip() + '\n'
564  elif 'GRASS_INFO_WARNING' in line:
565  message += line.split(':', 1)[1].strip() + '\n'
566  elif 'GRASS_INFO_ERROR' in line:
567  message += line.split(':', 1)[1].strip() + '\n'
568  elif 'GRASS_INFO_END' in line:
569  return message
570  else:
571  message += line.strip() + '\n'
572 
573  return message
574 
575 def RunCommand(prog, flags = "", overwrite = False, quiet = False, verbose = False,
576  parent = None, read = False, stdin = None, getErrorMsg = False, **kwargs):
577  """!Run GRASS command
578 
579  @param prog program to run
580  @param flags flags given as a string
581  @param overwrite, quiet, verbose flags
582  @param parent parent window for error messages
583  @param read fetch stdout
584  @param stdin stdin or None
585  @param getErrorMsg get error messages on failure
586  @param kwargs program parameters
587 
588  @return returncode (read == False and getErrorMsg == False)
589  @return returncode, messages (read == False and getErrorMsg == True)
590  @return stdout (read == True and getErrorMsg == False)
591  @return returncode, stdout, messages (read == True and getErrorMsg == True)
592  @return stdout, stderr
593  """
594  cmdString = ' '.join(grass.make_command(prog, flags, overwrite,
595  quiet, verbose, **kwargs))
596 
597  Debug.msg(1, "gcmd.RunCommand(): %s" % cmdString)
598 
599  kwargs['stderr'] = subprocess.PIPE
600 
601  if read:
602  kwargs['stdout'] = subprocess.PIPE
603 
604  if stdin:
605  kwargs['stdin'] = subprocess.PIPE
606 
607  ps = grass.start_command(prog, flags, overwrite, quiet, verbose, **kwargs)
608 
609  Debug.msg(2, "gcmd.RunCommand(): command started")
610 
611  if stdin:
612  ps.stdin.write(stdin)
613  ps.stdin.close()
614  ps.stdin = None
615 
616  Debug.msg(3, "gcmd.RunCommand(): decoding string")
617  stdout, stderr = map(utils.DecodeString, ps.communicate())
618 
619  ret = ps.returncode
620  Debug.msg(1, "gcmd.RunCommand(): get return code %d" % ret)
621 
622  Debug.msg(3, "gcmd.RunCommand(): print error")
623  if ret != 0 and parent:
624  Debug.msg(2, "gcmd.RunCommand(): error %s" % stderr)
625  if (stderr == None):
626  Debug.msg(2, "gcmd.RunCommand(): nothing to print ???")
627  else:
628  GError(parent = parent,
629  message = stderr)
630 
631  Debug.msg(3, "gcmd.RunCommand(): print read error")
632  if not read:
633  if not getErrorMsg:
634  return ret
635  else:
636  return ret, _formatMsg(stderr)
637 
638  if stdout:
639  Debug.msg(2, "gcmd.RunCommand(): return stdout\n'%s'" % stdout)
640  else:
641  Debug.msg(2, "gcmd.RunCommand(): return stdout = None")
642  if not getErrorMsg:
643  return stdout
644 
645  Debug.msg(2, "gcmd.RunCommand(): return ret, stdout")
646  if read and getErrorMsg:
647  return ret, stdout, _formatMsg(stderr)
648 
649  Debug.msg(2, "gcmd.RunCommand(): return result")
650  return stdout, _formatMsg(stderr)