gcmd.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655
  1. """!
  2. @package gcmd
  3. @brief wxGUI 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, 2010 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. @author Martin Landa <landa.martin gmail.com>
  23. """
  24. import os
  25. import sys
  26. import time
  27. import errno
  28. import signal
  29. import locale
  30. import traceback
  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 GMessage:
  53. def __init__(self, parent, message, msgType = 'error'):
  54. if msgType == 'error':
  55. caption = _('Error')
  56. style = wx.OK | wx.ICON_ERROR | wx.CENTRE
  57. elif msgType == 'info':
  58. caption = _('Message')
  59. style = wx.OK | wx.ICON_INFORMATION | wx.CENTRE
  60. elif msgType == 'warning':
  61. caption = _('Warning')
  62. style = wx.OK | wx.ICON_WARNING | wx.CENTRE
  63. if msgType != 'error':
  64. wx.MessageBox(parent = parent,
  65. message = message,
  66. caption = caption,
  67. style = style)
  68. else:
  69. exc_type, exc_value, exc_traceback = sys.exc_info()
  70. if exc_traceback:
  71. exception = traceback.format_exc()
  72. reason = exception.splitlines()[-2].split(':', 1)[-1].strip()
  73. if Debug.get_level() > 0 and exc_traceback:
  74. sys.stderr.write(exception)
  75. if exc_traceback:
  76. wx.MessageBox(parent = parent,
  77. message = message + '\n\n%s: %s\n\n%s' % \
  78. (_('Reason'),
  79. reason, exception),
  80. caption = caption,
  81. style = style)
  82. else:
  83. wx.MessageBox(parent = parent,
  84. message = message,
  85. caption = caption,
  86. style = style)
  87. class GError(Exception):
  88. def __init__(self, value):
  89. self.value = value
  90. def __str__(self):
  91. return str(self.value)
  92. class GException(Exception):
  93. """!Generic exception"""
  94. def __init__(self, message, title = _("Error"), parent = None):
  95. self.msg = message
  96. self.parent = parent
  97. self.title = title
  98. def Show(self):
  99. GMessage(parent = self.parent,
  100. message = self.msg,
  101. msgType = 'error')
  102. def GetMessage(self):
  103. return self.msg
  104. def __str__(self):
  105. self.Show()
  106. return ''
  107. class GStdError(GException):
  108. """!Generic exception"""
  109. def __init__(self, message, title = _("Error"), parent = None):
  110. GException.__init__(self, message, title=title, parent=parent)
  111. class CmdError(GException):
  112. """!Exception used for GRASS commands.
  113. See Command class (command exits with EXIT_FAILURE,
  114. G_fatal_error() is called)."""
  115. def __init__(self, cmd, message, parent=None):
  116. GException.__init__(self, message,
  117. title=_("Error in command execution '%s'" % cmd),
  118. parent=parent)
  119. class SettingsError(GException):
  120. """!Exception used for GRASS settings, see
  121. gui_modules/preferences.py."""
  122. def __init__(self, message, parent=None):
  123. GException.__init__(self, message,
  124. title=_("Preferences error"),
  125. parent=parent)
  126. class DigitError(GException):
  127. """!Exception raised during digitization session"""
  128. def __init__(self, message, parent=None):
  129. GException.__init__(self, message,
  130. title=_("Vector digitizer error"),
  131. parent=parent)
  132. class DBMError(GException):
  133. """!Attribute Table Manager exception class"""
  134. def __init__(self, message, parent=None):
  135. GException.__init__(self, message,
  136. title=_("Attribute table manager error"),
  137. parent=parent)
  138. class NvizError(GException):
  139. """!Nviz exception class"""
  140. def __init__(self, message, parent=None):
  141. GException.__init__(self, message,
  142. title=_("Nviz error"),
  143. parent=parent)
  144. class Popen(subprocess.Popen):
  145. """!Subclass subprocess.Popen"""
  146. def __init__(self, *args, **kwargs):
  147. if subprocess.mswindows:
  148. try:
  149. kwargs['args'] = map(utils.EncodeString, kwargs['args'])
  150. except KeyError:
  151. if len(args) > 0:
  152. targs = list(args)
  153. targs[0] = map(utils.EncodeString, args[0])
  154. args = tuple(targs)
  155. subprocess.Popen.__init__(self, *args, **kwargs)
  156. def recv(self, maxsize=None):
  157. return self._recv('stdout', maxsize)
  158. def recv_err(self, maxsize=None):
  159. return self._recv('stderr', maxsize)
  160. def send_recv(self, input='', maxsize=None):
  161. return self.send(input), self.recv(maxsize), self.recv_err(maxsize)
  162. def get_conn_maxsize(self, which, maxsize):
  163. if maxsize is None:
  164. maxsize = 1024
  165. elif maxsize < 1:
  166. maxsize = 1
  167. return getattr(self, which), maxsize
  168. def _close(self, which):
  169. getattr(self, which).close()
  170. setattr(self, which, None)
  171. def kill(self):
  172. """!Try to kill running process"""
  173. if subprocess.mswindows:
  174. import win32api
  175. handle = win32api.OpenProcess(1, 0, self.pid)
  176. return (0 != win32api.TerminateProcess(handle, 0))
  177. else:
  178. try:
  179. os.kill(-self.pid, signal.SIGTERM) # kill whole group
  180. except OSError:
  181. pass
  182. if subprocess.mswindows:
  183. def send(self, input):
  184. if not self.stdin:
  185. return None
  186. try:
  187. x = msvcrt.get_osfhandle(self.stdin.fileno())
  188. (errCode, written) = WriteFile(x, input)
  189. except ValueError:
  190. return self._close('stdin')
  191. except (subprocess.pywintypes.error, Exception), why:
  192. if why[0] in (109, errno.ESHUTDOWN):
  193. return self._close('stdin')
  194. raise
  195. return written
  196. def _recv(self, which, maxsize):
  197. conn, maxsize = self.get_conn_maxsize(which, maxsize)
  198. if conn is None:
  199. return None
  200. try:
  201. x = msvcrt.get_osfhandle(conn.fileno())
  202. (read, nAvail, nMessage) = PeekNamedPipe(x, 0)
  203. if maxsize < nAvail:
  204. nAvail = maxsize
  205. if nAvail > 0:
  206. (errCode, read) = ReadFile(x, nAvail, None)
  207. except ValueError:
  208. return self._close(which)
  209. except (subprocess.pywintypes.error, Exception), why:
  210. if why[0] in (109, errno.ESHUTDOWN):
  211. return self._close(which)
  212. raise
  213. if self.universal_newlines:
  214. read = self._translate_newlines(read)
  215. return read
  216. else:
  217. def send(self, input):
  218. if not self.stdin:
  219. return None
  220. if not select.select([], [self.stdin], [], 0)[1]:
  221. return 0
  222. try:
  223. written = os.write(self.stdin.fileno(), input)
  224. except OSError, why:
  225. if why[0] == errno.EPIPE: #broken pipe
  226. return self._close('stdin')
  227. raise
  228. return written
  229. def _recv(self, which, maxsize):
  230. conn, maxsize = self.get_conn_maxsize(which, maxsize)
  231. if conn is None:
  232. return None
  233. flags = fcntl.fcntl(conn, fcntl.F_GETFL)
  234. if not conn.closed:
  235. fcntl.fcntl(conn, fcntl.F_SETFL, flags| os.O_NONBLOCK)
  236. try:
  237. if not select.select([conn], [], [], 0)[0]:
  238. return ''
  239. r = conn.read(maxsize)
  240. if not r:
  241. return self._close(which)
  242. if self.universal_newlines:
  243. r = self._translate_newlines(r)
  244. return r
  245. finally:
  246. if not conn.closed:
  247. fcntl.fcntl(conn, fcntl.F_SETFL, flags)
  248. message = "Other end disconnected!"
  249. def recv_some(p, t=.1, e=1, tr=5, stderr=0):
  250. if tr < 1:
  251. tr = 1
  252. x = time.time()+t
  253. y = []
  254. r = ''
  255. pr = p.recv
  256. if stderr:
  257. pr = p.recv_err
  258. while time.time() < x or r:
  259. r = pr()
  260. if r is None:
  261. if e:
  262. raise Exception(message)
  263. else:
  264. break
  265. elif r:
  266. y.append(r)
  267. else:
  268. time.sleep(max((x-time.time())/tr, 0))
  269. return ''.join(y)
  270. def send_all(p, data):
  271. while len(data):
  272. sent = p.send(data)
  273. if sent is None:
  274. raise Exception(message)
  275. data = buffer(data, sent)
  276. class Command:
  277. """!Run command in separate thread. Used for commands launched
  278. on the background.
  279. If stdout/err is redirected, write() method is required for the
  280. given classes.
  281. @code
  282. cmd = Command(cmd=['d.rast', 'elevation.dem'], verbose=3, wait=True)
  283. if cmd.returncode == None:
  284. print 'RUNNING?'
  285. elif cmd.returncode == 0:
  286. print 'SUCCESS'
  287. else:
  288. print 'FAILURE (%d)' % cmd.returncode
  289. @endcode
  290. """
  291. def __init__ (self, cmd, stdin=None,
  292. verbose=None, wait=True, rerr=False,
  293. stdout=None, stderr=None):
  294. """
  295. @param cmd command given as list
  296. @param stdin standard input stream
  297. @param verbose verbose level [0, 3] (--q, --v)
  298. @param wait wait for child execution terminated
  299. @param rerr error handling (when CmdError raised).
  300. True for redirection to stderr, False for GUI dialog,
  301. None for no operation (quiet mode)
  302. @param stdout redirect standard output or None
  303. @param stderr redirect standard error output or None
  304. """
  305. Debug.msg(1, "gcmd.Command(): %s" % ' '.join(cmd))
  306. self.cmd = cmd
  307. self.stderr = stderr
  308. #
  309. # set verbosity level
  310. #
  311. verbose_orig = None
  312. if ('--q' not in self.cmd and '--quiet' not in self.cmd) and \
  313. ('--v' not in self.cmd and '--verbose' not in self.cmd):
  314. if verbose is not None:
  315. if verbose == 0:
  316. self.cmd.append('--quiet')
  317. elif verbose == 3:
  318. self.cmd.append('--verbose')
  319. else:
  320. verbose_orig = os.getenv("GRASS_VERBOSE")
  321. os.environ["GRASS_VERBOSE"] = str(verbose)
  322. #
  323. # create command thread
  324. #
  325. self.cmdThread = CommandThread(cmd, stdin,
  326. stdout, stderr)
  327. self.cmdThread.start()
  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. if self.returncode is not None:
  339. Debug.msg (3, "Command(): cmd='%s', wait=%s, returncode=%d, alive=%s" % \
  340. (' '.join(cmd), wait, self.returncode, self.cmdThread.isAlive()))
  341. if rerr is not None and self.returncode != 0:
  342. if rerr is False: # GUI dialog
  343. raise CmdError(cmd=self.cmd,
  344. message="%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. if verbose_orig:
  362. os.environ["GRASS_VERBOSE"] = verbose_orig
  363. elif os.environ.has_key("GRASS_VERBOSE"):
  364. del os.environ["GRASS_VERBOSE"]
  365. def __ReadOutput(self, stream):
  366. """!Read stream and return list of lines
  367. @param stream stream to be read
  368. """
  369. lineList = []
  370. if stream is None:
  371. return lineList
  372. while True:
  373. line = stream.readline()
  374. if not line:
  375. break
  376. line = line.replace('%s' % os.linesep, '').strip()
  377. lineList.append(line)
  378. return lineList
  379. def __ReadErrOutput(self):
  380. """!Read standard error output and return list of lines"""
  381. return self.__ReadOutput(self.cmdThread.module.stderr)
  382. def __ProcessStdErr(self):
  383. """
  384. Read messages/warnings/errors from stderr
  385. @return list of (type, message)
  386. """
  387. if self.stderr is None:
  388. lines = self.__ReadErrOutput()
  389. else:
  390. lines = self.cmdThread.error.strip('%s' % os.linesep). \
  391. split('%s' % os.linesep)
  392. msg = []
  393. type = None
  394. content = ""
  395. for line in lines:
  396. if len(line) == 0:
  397. continue
  398. if 'GRASS_' in line: # error or warning
  399. if 'GRASS_INFO_WARNING' in line: # warning
  400. type = "WARNING"
  401. elif 'GRASS_INFO_ERROR' in line: # error
  402. type = "ERROR"
  403. elif 'GRASS_INFO_END': # end of message
  404. msg.append((type, content))
  405. type = None
  406. content = ""
  407. if type:
  408. content += line.split(':', 1)[1].strip()
  409. else: # stderr
  410. msg.append((None, line.strip()))
  411. return msg
  412. def __GetError(self):
  413. """!Get error message or ''"""
  414. if not self.cmdThread.module:
  415. return _("Unable to exectute command: '%s'") % ' '.join(self.cmd)
  416. for type, msg in self.__ProcessStdErr():
  417. if type == 'ERROR':
  418. enc = locale.getdefaultlocale()[1]
  419. if enc:
  420. return unicode(msg, enc)
  421. else:
  422. return msg
  423. return ''
  424. class CommandThread(Thread):
  425. """!Create separate thread for command. Used for commands launched
  426. on the background."""
  427. def __init__ (self, cmd, stdin=None,
  428. stdout=sys.stdout, stderr=sys.stderr):
  429. """
  430. @param cmd command (given as list)
  431. @param stdin standard input stream
  432. @param stdout redirect standard output or None
  433. @param stderr redirect standard error output or None
  434. """
  435. Thread.__init__(self)
  436. self.cmd = cmd
  437. self.stdin = stdin
  438. self.stdout = stdout
  439. self.stderr = stderr
  440. self.module = None
  441. self.error = ''
  442. self._want_abort = False
  443. self.aborted = False
  444. self.setDaemon(True)
  445. # set message formatting
  446. self.message_format = os.getenv("GRASS_MESSAGE_FORMAT")
  447. os.environ["GRASS_MESSAGE_FORMAT"] = "gui"
  448. def __del__(self):
  449. if self.message_format:
  450. os.environ["GRASS_MESSAGE_FORMAT"] = self.message_format
  451. else:
  452. del os.environ["GRASS_MESSAGE_FORMAT"]
  453. def run(self):
  454. """!Run command"""
  455. if len(self.cmd) == 0:
  456. return
  457. self.startTime = time.time()
  458. try:
  459. self.module = Popen(self.cmd,
  460. stdin=subprocess.PIPE,
  461. stdout=subprocess.PIPE,
  462. stderr=subprocess.PIPE,
  463. shell=sys.platform=="win32")
  464. except OSError, e:
  465. self.error = str(e)
  466. return 1
  467. if self.stdin: # read stdin if requested ...
  468. self.module.stdin.write(self.stdin)
  469. self.module.stdin.close()
  470. # redirect standard outputs...
  471. if self.stdout or self.stderr:
  472. self.__redirect_stream()
  473. def __redirect_stream(self):
  474. """!Redirect stream"""
  475. if self.stdout:
  476. # make module stdout/stderr non-blocking
  477. out_fileno = self.module.stdout.fileno()
  478. if not subprocess.mswindows:
  479. flags = fcntl.fcntl(out_fileno, fcntl.F_GETFL)
  480. fcntl.fcntl(out_fileno, fcntl.F_SETFL, flags| os.O_NONBLOCK)
  481. if self.stderr:
  482. # make module stdout/stderr non-blocking
  483. out_fileno = self.module.stderr.fileno()
  484. if not subprocess.mswindows:
  485. flags = fcntl.fcntl(out_fileno, fcntl.F_GETFL)
  486. fcntl.fcntl(out_fileno, fcntl.F_SETFL, flags| os.O_NONBLOCK)
  487. # wait for the process to end, sucking in stuff until it does end
  488. while self.module.poll() is None:
  489. if self._want_abort: # abort running process
  490. self.module.kill()
  491. self.aborted = True
  492. return
  493. if self.stdout:
  494. line = recv_some(self.module, e=0, stderr=0)
  495. self.stdout.write(line)
  496. if self.stderr:
  497. line = recv_some(self.module, e=0, stderr=1)
  498. self.stderr.write(line)
  499. if len(line) > 0:
  500. self.error = line
  501. # get the last output
  502. if self.stdout:
  503. line = recv_some(self.module, e=0, stderr=0)
  504. self.stdout.write(line)
  505. if self.stderr:
  506. line = recv_some(self.module, e=0, stderr=1)
  507. self.stderr.write(line)
  508. if len(line) > 0:
  509. self.error = line
  510. def abort(self):
  511. """!Abort running process, used by main thread to signal an abort"""
  512. self._want_abort = True
  513. def RunCommand(prog, flags = "", overwrite = False, quiet = False, verbose = False,
  514. parent = None, read = False, stdin = None, getErrorMsg = False, **kwargs):
  515. """!Run GRASS command"""
  516. Debug.msg(1, "gcmd.RunCommand(): %s" % ' '.join(grass.make_command(prog, flags, overwrite,
  517. quiet, verbose, **kwargs)))
  518. kwargs['stderr'] = subprocess.PIPE
  519. if read:
  520. kwargs['stdout'] = subprocess.PIPE
  521. if stdin:
  522. kwargs['stdin'] = subprocess.PIPE
  523. ps = grass.start_command(prog, flags, overwrite, quiet, verbose, **kwargs)
  524. if stdin:
  525. ps.stdin.write(stdin)
  526. ps.stdin.close()
  527. ps.stdin = None
  528. stdout, stderr = ps.communicate()
  529. ret = ps.returncode
  530. if ret != 0 and parent:
  531. e = CmdError(cmd = prog,
  532. message = stderr,
  533. parent = parent)
  534. e.Show()
  535. if not read:
  536. if not getErrorMsg:
  537. return ret
  538. else:
  539. return ret, stderr
  540. if not getErrorMsg:
  541. return stdout
  542. if read and getErrorMsg:
  543. return ret, stdout, stderr
  544. return stdout, stderr