gcmd.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619
  1. """
  2. @package gcmd
  3. @brief GRASS command interface
  4. Classes:
  5. - GException
  6. - GStdError
  7. - CmdError
  8. - SettingsError
  9. - DigitError
  10. - DBMError
  11. - NvizError
  12. - Popen (from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/440554)
  13. - Command
  14. - CommandThread
  15. Functions:
  16. - RunCommand
  17. (C) 2007-2008 by the GRASS Development Team
  18. This program is free software under the GNU General Public
  19. License (>=v2). Read the file COPYING that comes with GRASS
  20. for details.
  21. @author Jachym Cepicky
  22. Martin Landa <landa.martin gmail.com>
  23. @date 2007-2008
  24. """
  25. import os
  26. import sys
  27. import time
  28. import errno
  29. import signal
  30. import wx
  31. try:
  32. import subprocess
  33. except:
  34. compatPath = os.path.join(globalvar.ETCWXDIR, "compat")
  35. sys.path.append(compatPath)
  36. import subprocess
  37. if subprocess.mswindows:
  38. from win32file import ReadFile, WriteFile
  39. from win32pipe import PeekNamedPipe
  40. import msvcrt
  41. else:
  42. import select
  43. import fcntl
  44. from threading import Thread
  45. import globalvar
  46. grassPath = os.path.join(globalvar.ETCDIR, "python")
  47. sys.path.append(grassPath)
  48. import grass
  49. import utils
  50. from debug import Debug as Debug
  51. class GException(Exception):
  52. """Generic exception"""
  53. def __init__(self, message, title=_("Error"), parent=None):
  54. self.message = message
  55. self.parent = parent
  56. self.title = title
  57. def Show(self):
  58. dlg = wx.MessageDialog(parent=self.parent,
  59. caption=self.title,
  60. message=self.message,
  61. style=wx.ICON_ERROR | wx.CENTRE)
  62. dlg.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass_error.ico'), wx.BITMAP_TYPE_ICO))
  63. if self.parent:
  64. dlg.CentreOnParent()
  65. else:
  66. dlg.CentreOnScreen()
  67. dlg.ShowModal()
  68. def __str__(self):
  69. self.Show()
  70. return ''
  71. class GStdError(GException):
  72. """Generic exception"""
  73. def __init__(self, message, title=_("Error"), parent=None):
  74. GException.__init__(self, message, title=title, parent=parent)
  75. class CmdError(GException):
  76. """Exception used for GRASS commands.
  77. See Command class (command exits with EXIT_FAILURE,
  78. G_fatal_error() is called)."""
  79. def __init__(self, cmd, message, parent=None):
  80. GException.__init__(self, message,
  81. title=_("Error in command execution '%s'" % cmd),
  82. parent=parent)
  83. class SettingsError(GException):
  84. """Exception used for GRASS settings, see
  85. gui_modules/preferences.py."""
  86. def __init__(self, message, parent=None):
  87. GException.__init__(self, message,
  88. title=_("Preferences error"),
  89. parent=parent)
  90. class DigitError(GException):
  91. """Exception raised during digitization session"""
  92. def __init__(self, message, parent=None):
  93. GException.__init__(self, message,
  94. title=_("Vector digitizer error"),
  95. parent=parent)
  96. class DBMError(GException):
  97. """Attribute Table Manager exception class"""
  98. def __init__(self, message, parent=None):
  99. GException.__init__(self, message,
  100. title=_("Attribute table manager error"),
  101. parent=parent)
  102. class NvizError(GException):
  103. """Nviz exception class"""
  104. def __init__(self, message, parent=None):
  105. GException.__init__(self, message,
  106. title=_("Nviz error"),
  107. parent=parent)
  108. class Popen(subprocess.Popen):
  109. """Subclass subprocess.Popen"""
  110. def recv(self, maxsize=None):
  111. return self._recv('stdout', maxsize)
  112. def recv_err(self, maxsize=None):
  113. return self._recv('stderr', maxsize)
  114. def send_recv(self, input='', maxsize=None):
  115. return self.send(input), self.recv(maxsize), self.recv_err(maxsize)
  116. def get_conn_maxsize(self, which, maxsize):
  117. if maxsize is None:
  118. maxsize = 1024
  119. elif maxsize < 1:
  120. maxsize = 1
  121. return getattr(self, which), maxsize
  122. def _close(self, which):
  123. getattr(self, which).close()
  124. setattr(self, which, None)
  125. def kill(self):
  126. """Try to kill running process"""
  127. if subprocess.mswindows:
  128. import win32api
  129. handle = win32api.OpenProcess(1, 0, self.pid)
  130. return (0 != win32api.TerminateProcess(handle, 0))
  131. else:
  132. try:
  133. os.kill(-self.pid, signal.SIGTERM) # kill whole group
  134. except OSError:
  135. pass
  136. if subprocess.mswindows:
  137. def send(self, input):
  138. if not self.stdin:
  139. return None
  140. try:
  141. x = msvcrt.get_osfhandle(self.stdin.fileno())
  142. (errCode, written) = WriteFile(x, input)
  143. except ValueError:
  144. return self._close('stdin')
  145. except (subprocess.pywintypes.error, Exception), why:
  146. if why[0] in (109, errno.ESHUTDOWN):
  147. return self._close('stdin')
  148. raise
  149. return written
  150. def _recv(self, which, maxsize):
  151. conn, maxsize = self.get_conn_maxsize(which, maxsize)
  152. if conn is None:
  153. return None
  154. try:
  155. x = msvcrt.get_osfhandle(conn.fileno())
  156. (read, nAvail, nMessage) = PeekNamedPipe(x, 0)
  157. if maxsize < nAvail:
  158. nAvail = maxsize
  159. if nAvail > 0:
  160. (errCode, read) = ReadFile(x, nAvail, None)
  161. except ValueError:
  162. return self._close(which)
  163. except (subprocess.pywintypes.error, Exception), why:
  164. if why[0] in (109, errno.ESHUTDOWN):
  165. return self._close(which)
  166. raise
  167. if self.universal_newlines:
  168. read = self._translate_newlines(read)
  169. return read
  170. else:
  171. def send(self, input):
  172. if not self.stdin:
  173. return None
  174. if not select.select([], [self.stdin], [], 0)[1]:
  175. return 0
  176. try:
  177. written = os.write(self.stdin.fileno(), input)
  178. except OSError, why:
  179. if why[0] == errno.EPIPE: #broken pipe
  180. return self._close('stdin')
  181. raise
  182. return written
  183. def _recv(self, which, maxsize):
  184. conn, maxsize = self.get_conn_maxsize(which, maxsize)
  185. if conn is None:
  186. return None
  187. flags = fcntl.fcntl(conn, fcntl.F_GETFL)
  188. if not conn.closed:
  189. fcntl.fcntl(conn, fcntl.F_SETFL, flags| os.O_NONBLOCK)
  190. try:
  191. if not select.select([conn], [], [], 0)[0]:
  192. return ''
  193. r = conn.read(maxsize)
  194. if not r:
  195. return self._close(which)
  196. if self.universal_newlines:
  197. r = self._translate_newlines(r)
  198. return r
  199. finally:
  200. if not conn.closed:
  201. fcntl.fcntl(conn, fcntl.F_SETFL, flags)
  202. message = "Other end disconnected!"
  203. def recv_some(p, t=.1, e=1, tr=5, stderr=0):
  204. if tr < 1:
  205. tr = 1
  206. x = time.time()+t
  207. y = []
  208. r = ''
  209. pr = p.recv
  210. if stderr:
  211. pr = p.recv_err
  212. while time.time() < x or r:
  213. r = pr()
  214. if r is None:
  215. if e:
  216. raise Exception(message)
  217. else:
  218. break
  219. elif r:
  220. y.append(r)
  221. else:
  222. time.sleep(max((x-time.time())/tr, 0))
  223. return ''.join(y)
  224. def send_all(p, data):
  225. while len(data):
  226. sent = p.send(data)
  227. if sent is None:
  228. raise Exception(message)
  229. data = buffer(data, sent)
  230. class Command:
  231. """Run command in separate thread. Used for commands launched
  232. on the background.
  233. If stdout/err is redirected, write() method is required for the
  234. given classes.
  235. @code
  236. cmd = Command(cmd=['d.rast', 'elevation.dem'], verbose=3, wait=True)
  237. if cmd.returncode == None:
  238. print 'RUNNING?'
  239. elif cmd.returncode == 0:
  240. print 'SUCCESS'
  241. else:
  242. print 'FAILURE (%d)' % cmd.returncode
  243. @endcode
  244. """
  245. def __init__ (self, cmd, stdin=None,
  246. verbose=None, wait=True, rerr=False,
  247. stdout=None, stderr=None):
  248. """
  249. @param cmd command given as list
  250. @param stdin standard input stream
  251. @param verbose verbose level [0, 3] (--q, --v)
  252. @param wait wait for child execution terminated
  253. @param rerr error handling (when CmdError raised).
  254. True for redirection to stderr, False for GUI dialog,
  255. None for no operation (quiet mode)
  256. @param stdout redirect standard output or None
  257. @param stderr redirect standard error output or None
  258. """
  259. self.cmd = cmd
  260. self.stderr = stderr
  261. # hack around platform-specific extension for binaries
  262. if self.cmd[0] in globalvar.grassCmd['script']:
  263. self.cmd[0] = self.cmd[0] + globalvar.EXT_SCT
  264. else:
  265. self.cmd[0] = self.cmd[0] + globalvar.EXT_BIN
  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 CmdError(cmd=self.cmd,
  302. message="%s '%s'%s%s%s %s%s" %
  303. (_("Execution failed:"),
  304. ' '.join(self.cmd),
  305. os.linesep, os.linesep,
  306. _("Details:"),
  307. os.linesep,
  308. _("Error: ") + self.GetError()))
  309. elif rerr == sys.stderr: # redirect message to sys
  310. stderr.write("Execution failed: '%s'" % (' '.join(self.cmd)))
  311. stderr.write("%sDetails:%s%s" % (os.linesep,
  312. _("Error: ") + self.GetError(),
  313. os.linesep))
  314. else:
  315. pass # nop
  316. else:
  317. Debug.msg (3, "Command(): cmd='%s', wait=%s, returncode=?, alive=%s" % \
  318. (' '.join(cmd), wait, self.cmdThread.isAlive()))
  319. if verbose_orig:
  320. os.environ["GRASS_VERBOSE"] = verbose_orig
  321. elif os.environ.has_key("GRASS_VERBOSE"):
  322. del os.environ["GRASS_VERBOSE"]
  323. def __ReadOutput(self, stream):
  324. """Read stream and return list of lines
  325. @param stream stream to be read
  326. """
  327. lineList = []
  328. if stream is None:
  329. return lineList
  330. while True:
  331. line = stream.readline()
  332. if not line:
  333. break
  334. line = line.replace('%s' % os.linesep, '').strip()
  335. lineList.append(line)
  336. return lineList
  337. def ReadStdOutput(self):
  338. """Read standard output and return list of lines"""
  339. if self.cmdThread.stdout:
  340. stream = self.cmdThread.stdout # use redirected stream instead
  341. stream.seek(0)
  342. else:
  343. stream = self.cmdThread.module.stdout
  344. return self.__ReadOutput(stream)
  345. def ReadErrOutput(self):
  346. """Read standard error output and return list of lines"""
  347. return self.__ReadOutput(self.cmdThread.module.stderr)
  348. def __ProcessStdErr(self):
  349. """
  350. Read messages/warnings/errors from stderr
  351. @return list of (type, message)
  352. """
  353. if self.stderr is None:
  354. lines = self.ReadErrOutput()
  355. else:
  356. lines = self.cmdThread.error.strip('%s' % os.linesep). \
  357. split('%s' % os.linesep)
  358. msg = []
  359. type = None
  360. content = ""
  361. for line in lines:
  362. if len(line) == 0:
  363. continue
  364. if 'GRASS_' in line: # error or warning
  365. if 'GRASS_INFO_WARNING' in line: # warning
  366. type = "WARNING"
  367. elif 'GRASS_INFO_ERROR' in line: # error
  368. type = "ERROR"
  369. elif 'GRASS_INFO_END': # end of message
  370. msg.append((type, content))
  371. type = None
  372. content = ""
  373. if type:
  374. content += line.split(':', 1)[1].strip()
  375. else: # stderr
  376. msg.append((None, line.strip()))
  377. return msg
  378. def GetError(self):
  379. """Get error message or ''"""
  380. if not self.cmdThread.module:
  381. return _("Unable to exectute command: '%s'") % ' '.join(self.cmd)
  382. for type, msg in self.__ProcessStdErr():
  383. if type == 'ERROR':
  384. return unicode(msg, "utf-8")
  385. return ''
  386. def PrintModuleOutput(self, error=True, warning=False, message=False):
  387. """Print module errors, warnings, messages to output
  388. @param error print errors
  389. @param warning print warnings
  390. @param message print messages
  391. @return string
  392. """
  393. msgString = ""
  394. for type, msg in self.__ProcessStdErr():
  395. if type:
  396. if (type == 'ERROR' and error) or \
  397. (type == 'WARNING' and warning) or \
  398. (type == 'MESSAGE' and message):
  399. msgString += " " + type + ": " + msg + "%s" % os.linesep
  400. else:
  401. msgString += " " + msg + "%s" % os.linesep
  402. return msgString
  403. class CommandThread(Thread):
  404. """Create separate thread for command. Used for commands launched
  405. on the background."""
  406. def __init__ (self, cmd, stdin=None,
  407. stdout=sys.stdout, stderr=sys.stderr):
  408. """
  409. @param cmd command (given as list)
  410. @param stdin standard input stream
  411. @param stdout redirect standard output or None
  412. @param stderr redirect standard error output or None
  413. """
  414. Thread.__init__(self)
  415. self.cmd = cmd
  416. self.stdin = stdin
  417. self.stdout = stdout
  418. self.stderr = stderr
  419. self.module = None
  420. self.error = ''
  421. self._want_abort = False
  422. self.aborted = False
  423. self.setDaemon(True)
  424. # set message formatting
  425. self.message_format = os.getenv("GRASS_MESSAGE_FORMAT")
  426. os.environ["GRASS_MESSAGE_FORMAT"] = "gui"
  427. def __del__(self):
  428. if self.message_format:
  429. os.environ["GRASS_MESSAGE_FORMAT"] = self.message_format
  430. else:
  431. del os.environ["GRASS_MESSAGE_FORMAT"]
  432. def run(self):
  433. """Run command"""
  434. if len(self.cmd) == 0:
  435. return
  436. self.startTime = time.time()
  437. try:
  438. self.module = Popen(self.cmd,
  439. stdin=subprocess.PIPE,
  440. stdout=subprocess.PIPE,
  441. stderr=subprocess.PIPE)
  442. except OSError, e:
  443. self.error = str(e)
  444. return 1
  445. if self.stdin: # read stdin if requested ...
  446. self.module.stdin.write(self.stdin)
  447. self.module.stdin.close()
  448. # redirect standard outputs...
  449. if self.stdout or self.stderr:
  450. self.__redirect_stream()
  451. def __redirect_stream(self):
  452. """Redirect stream"""
  453. if self.stdout:
  454. # make module stdout/stderr non-blocking
  455. out_fileno = self.module.stdout.fileno()
  456. if not subprocess.mswindows:
  457. flags = fcntl.fcntl(out_fileno, fcntl.F_GETFL)
  458. fcntl.fcntl(out_fileno, fcntl.F_SETFL, flags| os.O_NONBLOCK)
  459. if self.stderr:
  460. # make module stdout/stderr non-blocking
  461. out_fileno = self.module.stderr.fileno()
  462. if not subprocess.mswindows:
  463. flags = fcntl.fcntl(out_fileno, fcntl.F_GETFL)
  464. fcntl.fcntl(out_fileno, fcntl.F_SETFL, flags| os.O_NONBLOCK)
  465. # wait for the process to end, sucking in stuff until it does end
  466. while self.module.poll() is None:
  467. if self._want_abort: # abort running process
  468. self.module.kill()
  469. self.aborted = True
  470. return
  471. if self.stdout:
  472. line = recv_some(self.module, e=0, stderr=0)
  473. self.stdout.write(line)
  474. if self.stderr:
  475. line = recv_some(self.module, e=0, stderr=1)
  476. self.stderr.write(line)
  477. if len(line) > 0:
  478. self.error = line
  479. # get the last output
  480. if self.stdout:
  481. line = recv_some(self.module, e=0, stderr=0)
  482. self.stdout.write(line)
  483. if self.stderr:
  484. line = recv_some(self.module, e=0, stderr=1)
  485. self.stderr.write(line)
  486. if len(line) > 0:
  487. self.error = line
  488. def abort(self):
  489. """Abort running process, used by main thread to signal an abort"""
  490. self._want_abort = True
  491. def RunCommand(prog, flags = "", overwrite = False, quiet = False, verbose = False,
  492. parent = None, read = False, stdin = None, **kwargs):
  493. """Run GRASS command"""
  494. kwargs['stderr'] = subprocess.PIPE
  495. if read:
  496. kwargs['stdout'] = subprocess.PIPE
  497. if stdin:
  498. kwargs['stdin'] = stdin
  499. ps = grass.start_command(prog, flags, overwrite, quiet, verbose, **kwargs)
  500. ret = ps.wait()
  501. stdout, stderr = ps.communicate()
  502. if ret != 0 and parent:
  503. e = CmdError(cmd = prog,
  504. message = stderr,
  505. parent = parent)
  506. e.Show()
  507. if not read:
  508. return ret
  509. return stdout