gcmd.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613
  1. """
  2. @package gcmd
  3. @brief GRASS command interface
  4. Classes:
  5. - GException
  6. - GStdError
  7. - CmdError
  8. - SettingsError
  9. - DigitError
  10. - DBMError
  11. - NvizError
  12. - Popen (from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/440554)
  13. - Command
  14. - CommandThread
  15. Functions:
  16. - RunCommand
  17. (C) 2007-2008 by the GRASS Development Team
  18. This program is free software under the GNU General Public
  19. License (>=v2). Read the file COPYING that comes with GRASS
  20. for details.
  21. @author Jachym Cepicky
  22. Martin Landa <landa.martin gmail.com>
  23. @date 2007-2008
  24. """
  25. import os
  26. import sys
  27. import time
  28. import errno
  29. import signal
  30. import locale
  31. import wx
  32. try:
  33. import subprocess
  34. except:
  35. compatPath = os.path.join(globalvar.ETCWXDIR, "compat")
  36. sys.path.append(compatPath)
  37. import subprocess
  38. if subprocess.mswindows:
  39. from win32file import ReadFile, WriteFile
  40. from win32pipe import PeekNamedPipe
  41. import msvcrt
  42. else:
  43. import select
  44. import fcntl
  45. from threading import Thread
  46. import globalvar
  47. grassPath = os.path.join(globalvar.ETCDIR, "python")
  48. sys.path.append(grassPath)
  49. from grass.script import core as grass
  50. import utils
  51. from debug import Debug as Debug
  52. class GException(Exception):
  53. """!Generic exception"""
  54. def __init__(self, message, title=_("Error"), parent=None):
  55. self.msg = message
  56. self.parent = parent
  57. self.title = title
  58. def Show(self):
  59. dlg = wx.MessageDialog(parent=self.parent,
  60. caption=self.title,
  61. message=self.msg,
  62. style=wx.ICON_ERROR | wx.CENTRE)
  63. dlg.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass_error.ico'), wx.BITMAP_TYPE_ICO))
  64. if self.parent:
  65. dlg.CentreOnParent()
  66. else:
  67. dlg.CentreOnScreen()
  68. dlg.ShowModal()
  69. def __str__(self):
  70. self.Show()
  71. return ''
  72. class GStdError(GException):
  73. """!Generic exception"""
  74. def __init__(self, message, title=_("Error"), parent=None):
  75. GException.__init__(self, message, title=title, parent=parent)
  76. class CmdError(GException):
  77. """!Exception used for GRASS commands.
  78. See Command class (command exits with EXIT_FAILURE,
  79. G_fatal_error() is called)."""
  80. def __init__(self, cmd, message, parent=None):
  81. GException.__init__(self, message,
  82. title=_("Error in command execution '%s'" % cmd),
  83. parent=parent)
  84. class SettingsError(GException):
  85. """!Exception used for GRASS settings, see
  86. gui_modules/preferences.py."""
  87. def __init__(self, message, parent=None):
  88. GException.__init__(self, message,
  89. title=_("Preferences error"),
  90. parent=parent)
  91. class DigitError(GException):
  92. """!Exception raised during digitization session"""
  93. def __init__(self, message, parent=None):
  94. GException.__init__(self, message,
  95. title=_("Vector digitizer error"),
  96. parent=parent)
  97. class DBMError(GException):
  98. """!Attribute Table Manager exception class"""
  99. def __init__(self, message, parent=None):
  100. GException.__init__(self, message,
  101. title=_("Attribute table manager error"),
  102. parent=parent)
  103. class NvizError(GException):
  104. """!Nviz exception class"""
  105. def __init__(self, message, parent=None):
  106. GException.__init__(self, message,
  107. title=_("Nviz error"),
  108. parent=parent)
  109. class Popen(subprocess.Popen):
  110. """!Subclass subprocess.Popen"""
  111. def __init__(self, *args, **kwargs):
  112. if subprocess.mswindows:
  113. try:
  114. kwargs['args'] = map(utils.EncodeString, kwargs['args'])
  115. except KeyError:
  116. if len(args) > 0:
  117. targs = list(args)
  118. targs[0] = map(utils.EncodeString, args[0])
  119. args = tuple(targs)
  120. subprocess.Popen.__init__(self, *args, **kwargs)
  121. def recv(self, maxsize=None):
  122. return self._recv('stdout', maxsize)
  123. def recv_err(self, maxsize=None):
  124. return self._recv('stderr', maxsize)
  125. def send_recv(self, input='', maxsize=None):
  126. return self.send(input), self.recv(maxsize), self.recv_err(maxsize)
  127. def get_conn_maxsize(self, which, maxsize):
  128. if maxsize is None:
  129. maxsize = 1024
  130. elif maxsize < 1:
  131. maxsize = 1
  132. return getattr(self, which), maxsize
  133. def _close(self, which):
  134. getattr(self, which).close()
  135. setattr(self, which, None)
  136. def kill(self):
  137. """!Try to kill running process"""
  138. if subprocess.mswindows:
  139. import win32api
  140. handle = win32api.OpenProcess(1, 0, self.pid)
  141. return (0 != win32api.TerminateProcess(handle, 0))
  142. else:
  143. try:
  144. os.kill(-self.pid, signal.SIGTERM) # kill whole group
  145. except OSError:
  146. pass
  147. if subprocess.mswindows:
  148. def send(self, input):
  149. if not self.stdin:
  150. return None
  151. try:
  152. x = msvcrt.get_osfhandle(self.stdin.fileno())
  153. (errCode, written) = WriteFile(x, input)
  154. except ValueError:
  155. return self._close('stdin')
  156. except (subprocess.pywintypes.error, Exception), why:
  157. if why[0] in (109, errno.ESHUTDOWN):
  158. return self._close('stdin')
  159. raise
  160. return written
  161. def _recv(self, which, maxsize):
  162. conn, maxsize = self.get_conn_maxsize(which, maxsize)
  163. if conn is None:
  164. return None
  165. try:
  166. x = msvcrt.get_osfhandle(conn.fileno())
  167. (read, nAvail, nMessage) = PeekNamedPipe(x, 0)
  168. if maxsize < nAvail:
  169. nAvail = maxsize
  170. if nAvail > 0:
  171. (errCode, read) = ReadFile(x, nAvail, None)
  172. except ValueError:
  173. return self._close(which)
  174. except (subprocess.pywintypes.error, Exception), why:
  175. if why[0] in (109, errno.ESHUTDOWN):
  176. return self._close(which)
  177. raise
  178. if self.universal_newlines:
  179. read = self._translate_newlines(read)
  180. return read
  181. else:
  182. def send(self, input):
  183. if not self.stdin:
  184. return None
  185. if not select.select([], [self.stdin], [], 0)[1]:
  186. return 0
  187. try:
  188. written = os.write(self.stdin.fileno(), input)
  189. except OSError, why:
  190. if why[0] == errno.EPIPE: #broken pipe
  191. return self._close('stdin')
  192. raise
  193. return written
  194. def _recv(self, which, maxsize):
  195. conn, maxsize = self.get_conn_maxsize(which, maxsize)
  196. if conn is None:
  197. return None
  198. flags = fcntl.fcntl(conn, fcntl.F_GETFL)
  199. if not conn.closed:
  200. fcntl.fcntl(conn, fcntl.F_SETFL, flags| os.O_NONBLOCK)
  201. try:
  202. if not select.select([conn], [], [], 0)[0]:
  203. return ''
  204. r = conn.read(maxsize)
  205. if not r:
  206. return self._close(which)
  207. if self.universal_newlines:
  208. r = self._translate_newlines(r)
  209. return r
  210. finally:
  211. if not conn.closed:
  212. fcntl.fcntl(conn, fcntl.F_SETFL, flags)
  213. message = "Other end disconnected!"
  214. def recv_some(p, t=.1, e=1, tr=5, stderr=0):
  215. if tr < 1:
  216. tr = 1
  217. x = time.time()+t
  218. y = []
  219. r = ''
  220. pr = p.recv
  221. if stderr:
  222. pr = p.recv_err
  223. while time.time() < x or r:
  224. r = pr()
  225. if r is None:
  226. if e:
  227. raise Exception(message)
  228. else:
  229. break
  230. elif r:
  231. y.append(r)
  232. else:
  233. time.sleep(max((x-time.time())/tr, 0))
  234. return ''.join(y)
  235. def send_all(p, data):
  236. while len(data):
  237. sent = p.send(data)
  238. if sent is None:
  239. raise Exception(message)
  240. data = buffer(data, sent)
  241. class Command:
  242. """!Run command in separate thread. Used for commands launched
  243. on the background.
  244. If stdout/err is redirected, write() method is required for the
  245. given classes.
  246. @code
  247. cmd = Command(cmd=['d.rast', 'elevation.dem'], verbose=3, wait=True)
  248. if cmd.returncode == None:
  249. print 'RUNNING?'
  250. elif cmd.returncode == 0:
  251. print 'SUCCESS'
  252. else:
  253. print 'FAILURE (%d)' % cmd.returncode
  254. @endcode
  255. """
  256. def __init__ (self, cmd, stdin=None,
  257. verbose=None, wait=True, rerr=False,
  258. stdout=None, stderr=None):
  259. """
  260. @param cmd command given as list
  261. @param stdin standard input stream
  262. @param verbose verbose level [0, 3] (--q, --v)
  263. @param wait wait for child execution terminated
  264. @param rerr error handling (when CmdError raised).
  265. True for redirection to stderr, False for GUI dialog,
  266. None for no operation (quiet mode)
  267. @param stdout redirect standard output or None
  268. @param stderr redirect standard error output or None
  269. """
  270. self.cmd = cmd
  271. self.stderr = stderr
  272. #
  273. # set verbosity level
  274. #
  275. verbose_orig = None
  276. if ('--q' not in self.cmd and '--quiet' not in self.cmd) and \
  277. ('--v' not in self.cmd and '--verbose' not in self.cmd):
  278. if verbose is not None:
  279. if verbose == 0:
  280. self.cmd.append('--quiet')
  281. elif verbose == 3:
  282. self.cmd.append('--verbose')
  283. else:
  284. verbose_orig = os.getenv("GRASS_VERBOSE")
  285. os.environ["GRASS_VERBOSE"] = str(verbose)
  286. #
  287. # create command thread
  288. #
  289. self.cmdThread = CommandThread(cmd, stdin,
  290. stdout, stderr)
  291. self.cmdThread.start()
  292. if wait:
  293. self.cmdThread.join()
  294. if self.cmdThread.module:
  295. self.cmdThread.module.wait()
  296. self.returncode = self.cmdThread.module.returncode
  297. else:
  298. self.returncode = 1
  299. else:
  300. self.cmdThread.join(0.5)
  301. self.returncode = None
  302. if self.returncode is not None:
  303. Debug.msg (3, "Command(): cmd='%s', wait=%s, returncode=%d, alive=%s" % \
  304. (' '.join(cmd), wait, self.returncode, self.cmdThread.isAlive()))
  305. if rerr is not None and self.returncode != 0:
  306. if rerr is False: # GUI dialog
  307. raise CmdError(cmd=self.cmd,
  308. message="%s '%s'%s%s%s %s%s" %
  309. (_("Execution failed:"),
  310. ' '.join(self.cmd),
  311. os.linesep, os.linesep,
  312. _("Details:"),
  313. os.linesep,
  314. _("Error: ") + self.__GetError()))
  315. elif rerr == sys.stderr: # redirect message to sys
  316. stderr.write("Execution failed: '%s'" % (' '.join(self.cmd)))
  317. stderr.write("%sDetails:%s%s" % (os.linesep,
  318. _("Error: ") + self.__GetError(),
  319. os.linesep))
  320. else:
  321. pass # nop
  322. else:
  323. Debug.msg (3, "Command(): cmd='%s', wait=%s, returncode=?, alive=%s" % \
  324. (' '.join(cmd), wait, self.cmdThread.isAlive()))
  325. if verbose_orig:
  326. os.environ["GRASS_VERBOSE"] = verbose_orig
  327. elif os.environ.has_key("GRASS_VERBOSE"):
  328. del os.environ["GRASS_VERBOSE"]
  329. def __ReadOutput(self, stream):
  330. """!Read stream and return list of lines
  331. @param stream stream to be read
  332. """
  333. lineList = []
  334. if stream is None:
  335. return lineList
  336. while True:
  337. line = stream.readline()
  338. if not line:
  339. break
  340. line = line.replace('%s' % os.linesep, '').strip()
  341. lineList.append(line)
  342. return lineList
  343. def __ReadErrOutput(self):
  344. """!Read standard error output and return list of lines"""
  345. return self.__ReadOutput(self.cmdThread.module.stderr)
  346. def __ProcessStdErr(self):
  347. """
  348. Read messages/warnings/errors from stderr
  349. @return list of (type, message)
  350. """
  351. if self.stderr is None:
  352. lines = self.__ReadErrOutput()
  353. else:
  354. lines = self.cmdThread.error.strip('%s' % os.linesep). \
  355. split('%s' % os.linesep)
  356. msg = []
  357. type = None
  358. content = ""
  359. for line in lines:
  360. if len(line) == 0:
  361. continue
  362. if 'GRASS_' in line: # error or warning
  363. if 'GRASS_INFO_WARNING' in line: # warning
  364. type = "WARNING"
  365. elif 'GRASS_INFO_ERROR' in line: # error
  366. type = "ERROR"
  367. elif 'GRASS_INFO_END': # end of message
  368. msg.append((type, content))
  369. type = None
  370. content = ""
  371. if type:
  372. content += line.split(':', 1)[1].strip()
  373. else: # stderr
  374. msg.append((None, line.strip()))
  375. return msg
  376. def __GetError(self):
  377. """!Get error message or ''"""
  378. if not self.cmdThread.module:
  379. return _("Unable to exectute command: '%s'") % ' '.join(self.cmd)
  380. for type, msg in self.__ProcessStdErr():
  381. if type == 'ERROR':
  382. enc = locale.getdefaultlocale()[1]
  383. if enc:
  384. return unicode(msg, enc)
  385. else:
  386. return msg
  387. return ''
  388. class CommandThread(Thread):
  389. """!Create separate thread for command. Used for commands launched
  390. on the background."""
  391. def __init__ (self, cmd, stdin=None,
  392. stdout=sys.stdout, stderr=sys.stderr):
  393. """
  394. @param cmd command (given as list)
  395. @param stdin standard input stream
  396. @param stdout redirect standard output or None
  397. @param stderr redirect standard error output or None
  398. """
  399. Thread.__init__(self)
  400. self.cmd = cmd
  401. self.stdin = stdin
  402. self.stdout = stdout
  403. self.stderr = stderr
  404. self.module = None
  405. self.error = ''
  406. self._want_abort = False
  407. self.aborted = False
  408. self.setDaemon(True)
  409. # set message formatting
  410. self.message_format = os.getenv("GRASS_MESSAGE_FORMAT")
  411. os.environ["GRASS_MESSAGE_FORMAT"] = "gui"
  412. def __del__(self):
  413. if self.message_format:
  414. os.environ["GRASS_MESSAGE_FORMAT"] = self.message_format
  415. else:
  416. del os.environ["GRASS_MESSAGE_FORMAT"]
  417. def run(self):
  418. """!Run command"""
  419. if len(self.cmd) == 0:
  420. return
  421. self.startTime = time.time()
  422. try:
  423. self.module = Popen(self.cmd,
  424. stdin=subprocess.PIPE,
  425. stdout=subprocess.PIPE,
  426. stderr=subprocess.PIPE,
  427. shell=sys.platform=="win32")
  428. except OSError, e:
  429. self.error = str(e)
  430. return 1
  431. if self.stdin: # read stdin if requested ...
  432. self.module.stdin.write(self.stdin)
  433. self.module.stdin.close()
  434. # redirect standard outputs...
  435. if self.stdout or self.stderr:
  436. self.__redirect_stream()
  437. def __redirect_stream(self):
  438. """!Redirect stream"""
  439. if self.stdout:
  440. # make module stdout/stderr non-blocking
  441. out_fileno = self.module.stdout.fileno()
  442. if not subprocess.mswindows:
  443. flags = fcntl.fcntl(out_fileno, fcntl.F_GETFL)
  444. fcntl.fcntl(out_fileno, fcntl.F_SETFL, flags| os.O_NONBLOCK)
  445. if self.stderr:
  446. # make module stdout/stderr non-blocking
  447. out_fileno = self.module.stderr.fileno()
  448. if not subprocess.mswindows:
  449. flags = fcntl.fcntl(out_fileno, fcntl.F_GETFL)
  450. fcntl.fcntl(out_fileno, fcntl.F_SETFL, flags| os.O_NONBLOCK)
  451. # wait for the process to end, sucking in stuff until it does end
  452. while self.module.poll() is None:
  453. if self._want_abort: # abort running process
  454. self.module.kill()
  455. self.aborted = True
  456. return
  457. if self.stdout:
  458. line = recv_some(self.module, e=0, stderr=0)
  459. self.stdout.write(line)
  460. if self.stderr:
  461. line = recv_some(self.module, e=0, stderr=1)
  462. self.stderr.write(line)
  463. if len(line) > 0:
  464. self.error = line
  465. # get the last output
  466. if self.stdout:
  467. line = recv_some(self.module, e=0, stderr=0)
  468. self.stdout.write(line)
  469. if self.stderr:
  470. line = recv_some(self.module, e=0, stderr=1)
  471. self.stderr.write(line)
  472. if len(line) > 0:
  473. self.error = line
  474. def abort(self):
  475. """!Abort running process, used by main thread to signal an abort"""
  476. self._want_abort = True
  477. def RunCommand(prog, flags = "", overwrite = False, quiet = False, verbose = False,
  478. parent = None, read = False, stdin = None, getErrorMsg = False, **kwargs):
  479. """!Run GRASS command"""
  480. kwargs['stderr'] = subprocess.PIPE
  481. if read:
  482. kwargs['stdout'] = subprocess.PIPE
  483. if stdin:
  484. kwargs['stdin'] = subprocess.PIPE
  485. ps = grass.start_command(prog, flags, overwrite, quiet, verbose, **kwargs)
  486. if stdin:
  487. ps.stdin.write(stdin)
  488. ps.stdin.close()
  489. ps.stdin = None
  490. stdout, stderr = ps.communicate()
  491. ret = ps.returncode
  492. if ret != 0 and parent:
  493. e = CmdError(cmd = prog,
  494. message = stderr,
  495. parent = parent)
  496. e.Show()
  497. if not read:
  498. if not getErrorMsg:
  499. return ret
  500. else:
  501. return ret, stderr
  502. if not getErrorMsg:
  503. return stdout
  504. if read and getErrorMsg:
  505. return ret, stdout, stderr
  506. return stdout, stderr