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