gcmd.py 20 KB

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