gcmd.py 18 KB

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