# -*- coding: utf-8 -*- # # Copyright (C) 2011-2013 Canonical Ltd. # # This program is free software; you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation; version 3. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA # py3 compat try: import cPickle as pickle pickle # pyflakes except ImportError: import pickle import logging import os import json import softwarecenter.paths from softwarecenter.paths import PistonHelpers from gi import version_info as gi_version from gi.repository import GObject, GLib LOG = logging.getLogger(__name__) class SpawnHelper(GObject.GObject): __gsignals__ = { "data-available": (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, (GObject.TYPE_PYOBJECT,), ), "exited": (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, (int,), ), "error": (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, (str,), ), } def __init__(self, format="pickle"): super(SpawnHelper, self).__init__() self._expect_format = format self._stdout = None self._stderr = None self._io_watch = None self._child_watch = None self._cmd = None self.needs_auth = False self.no_relogin = False self.ignore_cache = False self.parent_xid = None def run_generic_piston_helper(self, klass, func, **kwargs): binary = os.path.join( softwarecenter.paths.datadir, PistonHelpers.GENERIC_HELPER) cmd = [binary] cmd += ["--datadir", softwarecenter.paths.datadir] if self.needs_auth: cmd.append("--needs-auth") if self.ignore_cache: cmd.append("--ignore-cache") if self.no_relogin: cmd.append("--no-relogin") if self.parent_xid: cmd.append("--parent-xid") cmd.append(str(self.parent_xid)) cmd += [klass, func] if kwargs: cmd.append(json.dumps(kwargs)) LOG.debug("run_generic_piston_helper()") self.run(cmd) def run(self, cmd): # only useful for debugging if "SOFTWARE_CENTER_DISABLE_SPAWN_HELPER" in os.environ: return self._cmd = cmd (pid, stdin, stdout, stderr) = GLib.spawn_async( cmd, flags=GObject.SPAWN_DO_NOT_REAP_CHILD, standard_output=True, standard_error=True) LOG.debug("running: '%s' as pid: '%s'" % (cmd, pid)) # python-gobject >= 3.7.3 has changed some API in incompatible # ways, so we need to check the version for which one to use. if gi_version < (3, 7, 3): self._child_watch = GLib.child_watch_add( pid, self._helper_finished, (stdout, stderr)) self._io_watch = GLib.io_add_watch( stdout, GObject.IO_IN, self._helper_io_ready, (stdout, )) else: self._child_watch = GLib.child_watch_add( GLib.PRIORITY_DEFAULT, pid, self._helper_finished, data=(stdout, stderr)) self._io_watch = GLib.io_add_watch( stdout, GLib.PRIORITY_DEFAULT, GObject.IO_IN, self._helper_io_ready, (stdout, )) def _helper_finished(self, pid, status, stdio_tuple): stdout, stderr = stdio_tuple LOG.debug("helper_finished: '%s' '%s'" % (pid, status)) # get status code res = os.WEXITSTATUS(status) if res == 0: self.emit("exited", res) else: LOG.warn("exit code %s from helper for '%s'" % (res, self._cmd)) # check stderr err = os.read(stderr, 4 * 1024) self._stderr = err if err: LOG.warn("got error from helper: '%s'" % err) self.emit("error", err) os.close(stderr) if self._io_watch: # remove with a delay timeout delay to ensure that any # pending data is still flushed GLib.timeout_add(100, GLib.source_remove, self._io_watch) if self._child_watch: GLib.source_remove(self._child_watch) def _helper_io_ready(self, source, condition, stdio_tuple): stdout, = stdio_tuple # read the raw data data = "" while True: s = os.read(stdout, 1024) if not s: break data += s os.close(stdout) self._stdout = data if self._expect_format == "pickle": # unpickle it, we should *always* get valid data here, so if # we don't this should raise a error try: data = pickle.loads(data) except: LOG.exception("can not load pickle data: '%s'" % data) elif self._expect_format == "json": try: data = json.loads(data) except: LOG.exception("can not load json: '%s'" % data) elif self._expect_format == "none": pass else: LOG.error("unknown format: '%s'", self._expect_format) LOG.debug("got data for cmd: '%s'='%s'" % (self._cmd, data)) self.emit("data-available", data) return False