gcmd.py 20 KB

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