gcmd.py 22 KB

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