gcmd.py 22 KB

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