gcmd.py 21 KB

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