gcmd.py 22 KB

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