File: spawn_helper.py

package info (click to toggle)
software-center 5.1.2debian3.1
  • links: PTS
  • area: main
  • in suites: wheezy
  • size: 8,708 kB
  • sloc: python: 28,999; xml: 379; sh: 127; makefile: 28
file content (123 lines) | stat: -rw-r--r-- 4,177 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
#!/usr/bin/python
# -*- coding: utf-8 -*-

# Copyright (C) 2011 Canonical
#
# Authors:
#  Michael Vogt
#
# 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

from gi.repository import GObject

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

    def run(self, cmd):
        self._cmd = cmd
        (pid, stdin, stdout, stderr) = GObject.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))
        self._child_watch = GObject.child_watch_add(
            pid, self._helper_finished, data=(stdout, stderr))
        self._io_watch = GObject.io_add_watch(
            stdout, GObject.IO_IN, self._helper_io_ready, (stdout, ))

    def _helper_finished(self, pid, status, (stdout, stderr)):
        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" % res)
            # 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 flused
            GObject.timeout_add(100, GObject.source_remove, self._io_watch)
        if self._child_watch:
            GObject.source_remove(self._child_watch)

    def _helper_io_ready(self, source, condition, (stdout,)):
        # 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