~azzar1/unity/add-show-desktop-key

« back to all changes in this revision

Viewing changes to scripts/python-console

  • Committer: wagrant
  • Date: 2008-08-10 11:25:42 UTC
  • Revision ID: svn-v3-trunk0:2b9c9e99-6f39-0410-b283-7f802c844ae2:trunk:1008
groups: Pretty up a bit. Remove buttons that did nothing and won't for
        months. Also actually list the members of one's own groups.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/python
 
2
 
 
3
# usage:
 
4
#   python-console <port> <magic> [<working-dir>]
 
5
 
 
6
import cjson
 
7
import codeop
 
8
import md5
 
9
import os
 
10
import Queue
 
11
import signal
 
12
import socket
 
13
import sys
 
14
import traceback
 
15
from threading import Thread
 
16
 
 
17
import common.chat
 
18
 
 
19
class Interrupt(Exception):
 
20
    def __init__(self):
 
21
        Exception.__init__(self, "Interrupted!")
 
22
 
 
23
class ExpiryTimer(object):
 
24
    def __init__(self, idle):
 
25
        self.idle = idle
 
26
        signal.signal(signal.SIGALRM, self.timeout)
 
27
 
 
28
    def ping(self):
 
29
        signal.alarm(self.idle)
 
30
 
 
31
    def start(self, time):
 
32
        signal.alarm(time)
 
33
 
 
34
    def stop(self):
 
35
        self.ping()
 
36
 
 
37
    def timeout(self, signum, frame):
 
38
        sys.exit(1)
 
39
 
 
40
class StdinFromWeb(object):
 
41
    def __init__(self, cmdQ, lineQ):
 
42
        self.cmdQ = cmdQ
 
43
        self.lineQ = lineQ
 
44
 
 
45
    def readline(self):
 
46
        self.cmdQ.put({"input":None})
 
47
        expiry.ping()
 
48
        ln = self.lineQ.get()
 
49
        if 'chat' in ln:
 
50
            return ln['chat']
 
51
        if 'interrupt' in ln:
 
52
            raise Interrupt()
 
53
 
 
54
class StdoutToWeb(object):
 
55
    def __init__(self, cmdQ, lineQ):
 
56
        self.cmdQ = cmdQ
 
57
        self.lineQ = lineQ
 
58
        self.remainder = ''
 
59
 
 
60
    def _trim_incomplete_final(self, stuff):
 
61
        '''Trim an incomplete UTF-8 character from the end of a string.
 
62
           Returns (trimmed_string, count_of_trimmed_bytes).
 
63
        '''
 
64
        tokill = incomplete_utf8_sequence(stuff)
 
65
        if tokill == 0:
 
66
            return (stuff, tokill)
 
67
        else:
 
68
            return (stuff[:-tokill], tokill)
 
69
 
 
70
    def write(self, stuff):
 
71
        # print will only give a non-file a unicode or str. There's no way
 
72
        # to convince it to encode unicodes, so we have to do it ourselves.
 
73
        # Yay for file special-cases (fileobject.c, PyFile_WriteObject).
 
74
        # If somebody wants to write some other object to here, they do it
 
75
        # at their own peril.
 
76
        if isinstance(stuff, unicode):
 
77
            stuff = stuff.encode('utf-8')
 
78
        self.remainder = self.remainder + stuff
 
79
 
 
80
        # if there's less than 128 bytes, buffer
 
81
        if len(self.remainder) < 128:
 
82
            return
 
83
 
 
84
        # if there's lots, then send it in 1/2K blocks
 
85
        while len(self.remainder) > 512:
 
86
            # We send things as Unicode inside JSON, so we must only send
 
87
            # complete UTF-8 characters.
 
88
            (blk, count) = self._trim_incomplete_final(self.remainder[:512])
 
89
            self.cmdQ.put({"output":blk.decode('utf-8', 'replace')})
 
90
            expiry.ping()
 
91
            ln = self.lineQ.get()
 
92
            self.remainder = self.remainder[512 - count:]
 
93
 
 
94
        # Finally, split the remainder up into lines, and ship all the
 
95
        # completed lines off to the server.
 
96
        lines = self.remainder.split("\n")
 
97
        self.remainder = lines[-1]
 
98
        del lines[-1]
 
99
 
 
100
        if len(lines) > 0:
 
101
            lines.append('')
 
102
            text = "\n".join(lines)
 
103
            self.cmdQ.put({"output":text.decode('utf-8', 'replace')})
 
104
            expiry.ping()
 
105
            ln = self.lineQ.get()
 
106
            if 'interrupt' in ln:
 
107
                raise Interrupt()
 
108
 
 
109
    def flush(self):
 
110
        if len(self.remainder) > 0:
 
111
            (out, count) = self._trim_incomplete_final(self.remainder)
 
112
            self.cmdQ.put({"output":out.decode('utf-8', 'replace')})
 
113
            expiry.ping()
 
114
            ln = self.lineQ.get()
 
115
            # Leave incomplete characters in the buffer.
 
116
            # Yes, this does mean that an incomplete character will be left
 
117
            # off the end, but we discussed this and it was deemed best.
 
118
            self.remainder = self.remainder[len(self.remainder)-count:]
 
119
            if 'interrupt' in ln:
 
120
                raise Interrupt()
 
121
 
 
122
class WebIO(object):
 
123
    """Provides a file like interface to the Web front end of the console.
 
124
    You may print text to the console using write(), flush any buffered output 
 
125
    using flush(), or request text from the console using readline()"""
 
126
    
 
127
    def __init__(self, cmdQ, lineQ):
 
128
        self.cmdQ = cmdQ
 
129
        self.lineQ = lineQ
 
130
        self.stdin = StdinFromWeb(self.cmdQ, self.lineQ)
 
131
        self.stdout = StdoutToWeb(self.cmdQ, self.lineQ)
 
132
 
 
133
    def write(self, stuff):
 
134
        self.stdout.write(stuff)
 
135
 
 
136
    def flush(self):
 
137
        self.stdout.flush()
 
138
 
 
139
    def readline(self):
 
140
        self.stdout.flush()
 
141
        return self.stdin.readline()
 
142
 
 
143
class PythonRunner(Thread):
 
144
    def __init__(self, cmdQ, lineQ):
 
145
        self.cmdQ = cmdQ
 
146
        self.lineQ = lineQ
 
147
        self.webio = WebIO(self.cmdQ, self.lineQ)
 
148
        Thread.__init__(self)
 
149
 
 
150
    def execCmd(self, cmd):
 
151
        try:
 
152
            sys.stdin = self.webio
 
153
            sys.stdout = self.webio
 
154
            sys.stderr = self.webio
 
155
            # We don't expect a return value - 'single' symbol prints it.
 
156
            eval(cmd, self.globs)
 
157
            self.webio.flush()
 
158
            self.cmdQ.put({"okay": None})
 
159
            self.curr_cmd = ''
 
160
        except:
 
161
            tb = format_exc_start(start=1)
 
162
            self.webio.flush()
 
163
            self.cmdQ.put({"exc": ''.join(tb).decode('utf-8', 'replace')})
 
164
            self.curr_cmd = ''
 
165
 
 
166
    def run(self):
 
167
        self.globs = {}
 
168
        self.globs['__builtins__'] = globals()['__builtins__']
 
169
        self.curr_cmd = ''
 
170
 
 
171
        while True:
 
172
            ln = self.lineQ.get()
 
173
            if 'chat' in ln:
 
174
                if self.curr_cmd == '':
 
175
                    self.curr_cmd = ln['chat']
 
176
                else:
 
177
                    self.curr_cmd = self.curr_cmd + '\n' + ln['chat']
 
178
                try:
 
179
                    cmd = codeop.compile_command(self.curr_cmd, '<web session>')
 
180
                    if cmd is None:
 
181
                        # The command was incomplete,
 
182
                        # so send back a None, so the
 
183
                        # client can print a '...'
 
184
                        self.cmdQ.put({"more":None})
 
185
                    else:
 
186
                        self.execCmd(cmd)
 
187
                except:
 
188
                    tb = format_exc_start(start=3)
 
189
                    self.cmdQ.put({"exc": ''.join(tb).decode('utf-8', 'replace')})
 
190
                    self.webio.flush()
 
191
                    self.curr_cmd = ''
 
192
            if 'block' in ln:
 
193
                # throw away a partial command.
 
194
                try:
 
195
                    cmd = compile(ln['block'], "<web session>", 'exec');
 
196
                    self.execCmd(cmd)
 
197
                except:
 
198
                    tb = format_exc_start(start=1)
 
199
                    self.webio.flush()
 
200
                    self.cmdQ.put({"exc": ''.join(tb).decode('utf-8', 'replace')})
 
201
                    self.curr_cmd = ''
 
202
 
 
203
def daemonize():
 
204
    if os.fork():   # launch child and...
 
205
        os._exit(0) # kill off parent
 
206
    os.setsid()
 
207
    if os.fork():   # launch child and...
 
208
        os._exit(0) # kill off parent again.
 
209
    os.umask(077)
 
210
 
 
211
# The global 'magic' is the secret that the client and server share
 
212
# which is used to create and md5 digest to authenticate requests.
 
213
# It is assigned a real value at startup.
 
214
magic = ''
 
215
 
 
216
cmdQ = Queue.Queue()
 
217
lineQ = Queue.Queue()
 
218
interpThread = PythonRunner(cmdQ, lineQ)
 
219
terminate = None
 
220
 
 
221
# Default expiry time of 15 minutes
 
222
expiry = ExpiryTimer(15 * 60)
 
223
 
 
224
def initializer():
 
225
    interpThread.setDaemon(True)
 
226
    interpThread.start()
 
227
    signal.signal(signal.SIGXCPU, sig_handler)
 
228
    expiry.ping()
 
229
 
 
230
def sig_handler(signum, frame):
 
231
    """Handles response from signals"""
 
232
    global terminate
 
233
    if signum == signal.SIGXCPU:
 
234
        terminate = "CPU Time Limit Exceeded"
 
235
 
 
236
def dispatch_msg(msg):
 
237
    global terminate
 
238
    if msg['cmd'] == 'restart':
 
239
        terminate = "User requested console be reset"
 
240
    if terminate:
 
241
        raise common.chat.Terminate({"restart":terminate})
 
242
    expiry.ping()
 
243
    lineQ.put({msg['cmd']:msg['text']})
 
244
    if terminate:
 
245
        raise common.chat.Terminate({"restart":terminate})
 
246
    return cmdQ.get()
 
247
 
 
248
def format_exc_start(start=0):
 
249
    etype, value, tb = sys.exc_info()
 
250
    tbbits = traceback.extract_tb(tb)[start:]
 
251
    list = ['Traceback (most recent call last):\n']
 
252
    list = list + traceback.format_list(tbbits)
 
253
    list = list + traceback.format_exception_only(etype, value)
 
254
    return ''.join(list)
 
255
 
 
256
def incomplete_utf8_sequence(byteseq):
 
257
    """
 
258
    str -> int
 
259
    Given a UTF-8-encoded byte sequence (str), returns the number of bytes at
 
260
    the end of the string which comprise an incomplete UTF-8 character
 
261
    sequence.
 
262
 
 
263
    If the string is empty or ends with a complete character OR INVALID
 
264
    sequence, returns 0.
 
265
    Otherwise, returns 1-3 indicating the number of bytes in the final
 
266
    incomplete (but valid) character sequence.
 
267
 
 
268
    Does not check any bytes before the final sequence for correctness.
 
269
 
 
270
    >>> incomplete_utf8_sequence("")
 
271
    0
 
272
    >>> incomplete_utf8_sequence("xy")
 
273
    0
 
274
    >>> incomplete_utf8_sequence("xy\xc3\xbc")
 
275
    0
 
276
    >>> incomplete_utf8_sequence("\xc3")
 
277
    1
 
278
    >>> incomplete_utf8_sequence("\xbc\xc3")
 
279
    1
 
280
    >>> incomplete_utf8_sequence("xy\xbc\xc3")
 
281
    1
 
282
    >>> incomplete_utf8_sequence("xy\xe0\xa0")
 
283
    2
 
284
    >>> incomplete_utf8_sequence("xy\xf4")
 
285
    1
 
286
    >>> incomplete_utf8_sequence("xy\xf4\x8f")
 
287
    2
 
288
    >>> incomplete_utf8_sequence("xy\xf4\x8f\xa0")
 
289
    3
 
290
    """
 
291
    count = 0
 
292
    expect = None
 
293
    for b in byteseq[::-1]:
 
294
        b = ord(b)
 
295
        count += 1
 
296
        if b & 0x80 == 0x0:
 
297
            # 0xxxxxxx (single-byte character)
 
298
            expect = 1
 
299
            break
 
300
        elif b & 0xc0 == 0x80:
 
301
            # 10xxxxxx (subsequent byte)
 
302
            pass
 
303
        elif b & 0xe0 == 0xc0:
 
304
            # 110xxxxx (start of 2-byte sequence)
 
305
            expect = 2
 
306
            break
 
307
        elif b & 0xf0 == 0xe0:
 
308
            # 1110xxxx (start of 3-byte sequence)
 
309
            expect = 3
 
310
            break
 
311
        elif b & 0xf8 == 0xf0:
 
312
            # 11110xxx (start of 4-byte sequence)
 
313
            expect = 4
 
314
            break
 
315
        else:
 
316
            # Invalid byte
 
317
            return 0
 
318
 
 
319
        if count >= 4:
 
320
            # Seen too many "subsequent bytes", invalid
 
321
            return 0
 
322
 
 
323
    if expect is None:
 
324
        # We never saw a "first byte", invalid
 
325
        return 0
 
326
 
 
327
    # We now know expect and count
 
328
    if count >= expect:
 
329
        # Complete, or we saw an invalid sequence
 
330
        return 0
 
331
    elif count < expect:
 
332
        # Incomplete
 
333
        return count
 
334
 
 
335
if __name__ == "__main__":
 
336
    port = int(sys.argv[1])
 
337
    magic = sys.argv[2]
 
338
    
 
339
    # Sanitise the Enviroment
 
340
    os.environ = {}
 
341
    os.environ['PATH'] = '/usr/local/bin:/usr/bin:/bin'
 
342
 
 
343
    if len(sys.argv) >= 4:
 
344
        # working_dir
 
345
        os.chdir(sys.argv[3])
 
346
        os.environ['HOME'] = sys.argv[3]
 
347
 
 
348
    # Make python's search path follow the cwd
 
349
    sys.path[0] = ''
 
350
 
 
351
    common.chat.start_server(port, magic, True, dispatch_msg, initializer)