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

« back to all changes in this revision

Viewing changes to scripts/python-console

  • Committer: wagrant
  • Date: 2008-08-19 12:49:58 UTC
  • Revision ID: svn-v3-trunk0:2b9c9e99-6f39-0410-b283-7f802c844ae2:trunk:1031
Example worksheets: Fix the element names in worksheet 1 to be actually
                    correct. Also fix the first exercise so it too
                    functions properly.

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 cPickle
 
9
import cStringIO
 
10
import md5
 
11
import os
 
12
import Queue
 
13
import signal
 
14
import socket
 
15
import sys
 
16
import traceback
 
17
from threading import Thread
 
18
 
 
19
import common.chat
 
20
import common.util
 
21
 
 
22
# This version must be supported by both the local and remote code
 
23
PICKLEVERSION = 0
 
24
 
 
25
class Interrupt(Exception):
 
26
    def __init__(self):
 
27
        Exception.__init__(self, "Interrupted!")
 
28
 
 
29
class ExpiryTimer(object):
 
30
    def __init__(self, idle):
 
31
        self.idle = idle
 
32
        signal.signal(signal.SIGALRM, self.timeout)
 
33
 
 
34
    def ping(self):
 
35
        signal.alarm(self.idle)
 
36
 
 
37
    def start(self, time):
 
38
        signal.alarm(time)
 
39
 
 
40
    def stop(self):
 
41
        self.ping()
 
42
 
 
43
    def timeout(self, signum, frame):
 
44
        sys.exit(1)
 
45
 
 
46
class StdinFromWeb(object):
 
47
    def __init__(self, cmdQ, lineQ):
 
48
        self.cmdQ = cmdQ
 
49
        self.lineQ = lineQ
 
50
 
 
51
    def readline(self):
 
52
        self.cmdQ.put({"input":None})
 
53
        expiry.ping()
 
54
        ln = self.lineQ.get()
 
55
        if 'chat' in ln:
 
56
            return ln['chat']
 
57
        if 'interrupt' in ln:
 
58
            raise Interrupt()
 
59
 
 
60
class StdoutToWeb(object):
 
61
    def __init__(self, cmdQ, lineQ):
 
62
        self.cmdQ = cmdQ
 
63
        self.lineQ = lineQ
 
64
        self.remainder = ''
 
65
 
 
66
    def _trim_incomplete_final(self, stuff):
 
67
        '''Trim an incomplete UTF-8 character from the end of a string.
 
68
           Returns (trimmed_string, count_of_trimmed_bytes).
 
69
        '''
 
70
        tokill = incomplete_utf8_sequence(stuff)
 
71
        if tokill == 0:
 
72
            return (stuff, tokill)
 
73
        else:
 
74
            return (stuff[:-tokill], tokill)
 
75
 
 
76
    def write(self, stuff):
 
77
        # print will only give a non-file a unicode or str. There's no way
 
78
        # to convince it to encode unicodes, so we have to do it ourselves.
 
79
        # Yay for file special-cases (fileobject.c, PyFile_WriteObject).
 
80
        # If somebody wants to write some other object to here, they do it
 
81
        # at their own peril.
 
82
        if isinstance(stuff, unicode):
 
83
            stuff = stuff.encode('utf-8')
 
84
        self.remainder = self.remainder + stuff
 
85
 
 
86
        # if there's less than 128 bytes, buffer
 
87
        if len(self.remainder) < 128:
 
88
            return
 
89
 
 
90
        # if there's lots, then send it in 1/2K blocks
 
91
        while len(self.remainder) > 512:
 
92
            # We send things as Unicode inside JSON, so we must only send
 
93
            # complete UTF-8 characters.
 
94
            (blk, count) = self._trim_incomplete_final(self.remainder[:512])
 
95
            self.cmdQ.put({"output":blk.decode('utf-8', 'replace')})
 
96
            expiry.ping()
 
97
            ln = self.lineQ.get()
 
98
            self.remainder = self.remainder[512 - count:]
 
99
 
 
100
        # Finally, split the remainder up into lines, and ship all the
 
101
        # completed lines off to the server.
 
102
        lines = self.remainder.split("\n")
 
103
        self.remainder = lines[-1]
 
104
        del lines[-1]
 
105
 
 
106
        if len(lines) > 0:
 
107
            lines.append('')
 
108
            text = "\n".join(lines)
 
109
            self.cmdQ.put({"output":text.decode('utf-8', 'replace')})
 
110
            expiry.ping()
 
111
            ln = self.lineQ.get()
 
112
            if 'interrupt' in ln:
 
113
                raise Interrupt()
 
114
 
 
115
    def flush(self):
 
116
        if len(self.remainder) > 0:
 
117
            (out, count) = self._trim_incomplete_final(self.remainder)
 
118
            self.cmdQ.put({"output":out.decode('utf-8', 'replace')})
 
119
            expiry.ping()
 
120
            ln = self.lineQ.get()
 
121
            # Leave incomplete characters in the buffer.
 
122
            # Yes, this does mean that an incomplete character will be left
 
123
            # off the end, but we discussed this and it was deemed best.
 
124
            self.remainder = self.remainder[len(self.remainder)-count:]
 
125
            if 'interrupt' in ln:
 
126
                raise Interrupt()
 
127
 
 
128
class WebIO(object):
 
129
    """Provides a file like interface to the Web front end of the console.
 
130
    You may print text to the console using write(), flush any buffered output 
 
131
    using flush(), or request text from the console using readline()"""
 
132
    
 
133
    def __init__(self, cmdQ, lineQ):
 
134
        self.cmdQ = cmdQ
 
135
        self.lineQ = lineQ
 
136
        self.stdin = StdinFromWeb(self.cmdQ, self.lineQ)
 
137
        self.stdout = StdoutToWeb(self.cmdQ, self.lineQ)
 
138
 
 
139
    def write(self, stuff):
 
140
        self.stdout.write(stuff)
 
141
 
 
142
    def flush(self):
 
143
        self.stdout.flush()
 
144
 
 
145
    def readline(self):
 
146
        self.stdout.flush()
 
147
        return self.stdin.readline()
 
148
 
 
149
class PythonRunner(Thread):
 
150
    def __init__(self, cmdQ, lineQ):
 
151
        self.cmdQ = cmdQ
 
152
        self.lineQ = lineQ
 
153
        self.webio = WebIO(self.cmdQ, self.lineQ)
 
154
        Thread.__init__(self)
 
155
 
 
156
    def execCmd(self, cmd):
 
157
        try:
 
158
            sys.stdin = self.webio
 
159
            sys.stdout = self.webio
 
160
            sys.stderr = self.webio
 
161
            # We don't expect a return value - 'single' symbol prints it.
 
162
            self.eval(cmd)
 
163
            self.webio.flush()
 
164
            self.cmdQ.put({"okay": None})
 
165
            self.curr_cmd = ''
 
166
        except:
 
167
            tb = format_exc_start(start=1)
 
168
            self.webio.flush()
 
169
            self.cmdQ.put({"exc": ''.join(tb).decode('utf-8', 'replace')})
 
170
            self.curr_cmd = ''
 
171
 
 
172
    def run(self):
 
173
        self.globs = {}
 
174
        self.globs['__builtins__'] = globals()['__builtins__']
 
175
        self.curr_cmd = ''
 
176
 
 
177
        while True:
 
178
            ln = self.lineQ.get()
 
179
            if 'chat' in ln:
 
180
                if self.curr_cmd == '':
 
181
                    self.curr_cmd = ln['chat']
 
182
                else:
 
183
                    self.curr_cmd = self.curr_cmd + '\n' + ln['chat']
 
184
                try:
 
185
                    cmd = codeop.compile_command(self.curr_cmd, '<web session>')
 
186
                    if cmd is None:
 
187
                        # The command was incomplete,
 
188
                        # so send back a None, so the
 
189
                        # client can print a '...'
 
190
                        self.cmdQ.put({"more":None})
 
191
                    else:
 
192
                        self.execCmd(cmd)
 
193
                except:
 
194
                    tb = format_exc_start(start=3)
 
195
                    self.cmdQ.put({"exc": ''.join(tb).decode('utf-8', 'replace')})
 
196
                    self.webio.flush()
 
197
                    self.curr_cmd = ''
 
198
            elif 'block' in ln:
 
199
                # throw away a partial command.
 
200
                try:
 
201
                    cmd = compile(ln['block'], "<web session>", 'exec');
 
202
                    self.execCmd(cmd)
 
203
                except:
 
204
                    tb = format_exc_start(start=1)
 
205
                    self.webio.flush()
 
206
                    self.cmdQ.put({"exc": ''.join(tb).decode('utf-8', 'replace')})
 
207
                    self.curr_cmd = ''
 
208
            elif 'flush' in ln:
 
209
                # reset the globals
 
210
                self.globs = {}
 
211
                self.globs['__builtins__'] = globals()['__builtins__']
 
212
                self.cmdQ.put({'response': 'okay'})
 
213
                # Unpickle the new space (if provided)
 
214
                if isinstance(ln['flush'],dict):
 
215
                    for g in ln['flush']:
 
216
                        try:
 
217
                            self.globs[g] = cPickle.loads(ln['flush'][g])
 
218
                        except:
 
219
                            pass
 
220
            elif 'call' in ln:
 
221
                call = {}
 
222
                stdout = cStringIO.StringIO()
 
223
                stderr = cStringIO.StringIO()
 
224
                sys.stdout = stdout
 
225
                sys.stderr = stderr
 
226
 
 
227
                if isinstance(ln['call'], dict):
 
228
                    params = ln['call']
 
229
                    try:
 
230
                        # Expand parameters
 
231
                        if isinstance(params['args'], list):
 
232
                            args = map(self.eval, params['args'])
 
233
                        else:
 
234
                            args = []
 
235
                        if isinstance(params['kwargs'], dict):
 
236
                            kwargs = {}
 
237
                            for kwarg in params['kwargs']:
 
238
                                kwargs[kwarg] = self.eval(
 
239
                                    params['kwargs'][kwarg])
 
240
                        else:
 
241
                            kwargs = {}
 
242
 
 
243
                        # Run the fuction
 
244
                        function = self.eval(params['function'])
 
245
                        try:
 
246
                            call['result'] = function(*args, **kwargs)
 
247
                        except Exception, e:
 
248
                            exception = {}
 
249
                            tb = format_exc_start(start=1)
 
250
                            exception['traceback'] = \
 
251
                                ''.join(tb).decode('utf-8', 'replace')
 
252
                            exception['except'] = cPickle.dumps(e,
 
253
                                PICKLEVERSION)
 
254
                            call['exception'] = exception
 
255
                    except Exception, e:
 
256
                        tb = format_exc_start(start=1)
 
257
                        self.cmdQ.put(
 
258
                            {"exc": ''.join(tb).decode('utf-8', 'replace')})
 
259
                    
 
260
                    # Write out the inspection object
 
261
                    call['stdout'] = stdout.getvalue()
 
262
                    call['stderr'] = stderr.getvalue()
 
263
                    self.cmdQ.put(call)
 
264
                else:
 
265
                    self.cmdQ.put({'response': 'failure'})
 
266
                stdout.close()
 
267
                stderr.close()
 
268
                self.curr_cmd = ''
 
269
            elif 'inspect' in ln:
 
270
                # Like block but return a serialization of the state
 
271
                # throw away partial command
 
272
                inspection = {}
 
273
                stdout = cStringIO.StringIO()
 
274
                stderr = cStringIO.StringIO()
 
275
                try:
 
276
                    cmd = compile(ln['inspect'], "<web session>", 'exec');
 
277
                    sys.stdin = None
 
278
                    sys.stdout = stdout
 
279
                    sys.stderr = stderr
 
280
                    # We don't expect a return value - 'single' symbol prints 
 
281
                    # it.
 
282
                    self.eval(cmd)
 
283
                except Exception, e:
 
284
                    exception = {}
 
285
                    tb = format_exc_start(start=1)
 
286
                    exception['traceback'] = \
 
287
                        ''.join(tb).decode('utf-8', 'replace')
 
288
                    exception['except'] = cPickle.dumps(e, PICKLEVERSION)
 
289
                    inspection['exception'] = exception                
 
290
                
 
291
                # Write out the inspection object
 
292
                inspection['stdout'] = stdout.getvalue()
 
293
                inspection['stderr'] = stderr.getvalue()
 
294
                inspection['globals'] = flatten(self.globs)
 
295
                self.cmdQ.put(inspection)
 
296
                stdout.close()
 
297
                stderr.close()
 
298
                self.curr_cmd = ''
 
299
            elif 'set_vars' in ln:
 
300
                # Adds some variables to the global dictionary
 
301
                for var in ln['set_vars']:
 
302
                    try:
 
303
                        self.globs[var] = self.eval(ln['set_vars'][var])
 
304
                    except Exception, e:
 
305
                        tb = format_exc_start(start=1)
 
306
                        self.cmdQ.put(
 
307
                            {"exc": ''.join(tb).decode('utf-8', 'replace')})
 
308
 
 
309
                self.cmdQ.put({'response': 'okay'})
 
310
            else:
 
311
                raise Exception, "Invalid Command"
 
312
 
 
313
    def eval(self, source):
 
314
        """ Evaluates a string in the private global space """
 
315
        return eval(source, self.globs)
 
316
 
 
317
def daemonize():
 
318
    if os.fork():   # launch child and...
 
319
        os._exit(0) # kill off parent
 
320
    os.setsid()
 
321
    if os.fork():   # launch child and...
 
322
        os._exit(0) # kill off parent again.
 
323
    os.umask(077)
 
324
 
 
325
# The global 'magic' is the secret that the client and server share
 
326
# which is used to create and md5 digest to authenticate requests.
 
327
# It is assigned a real value at startup.
 
328
magic = ''
 
329
 
 
330
cmdQ = Queue.Queue()
 
331
lineQ = Queue.Queue()
 
332
interpThread = PythonRunner(cmdQ, lineQ)
 
333
terminate = None
 
334
 
 
335
# Default expiry time of 15 minutes
 
336
expiry = ExpiryTimer(15 * 60)
 
337
 
 
338
def initializer():
 
339
    interpThread.setDaemon(True)
 
340
    interpThread.start()
 
341
    signal.signal(signal.SIGXCPU, sig_handler)
 
342
    expiry.ping()
 
343
 
 
344
def sig_handler(signum, frame):
 
345
    """Handles response from signals"""
 
346
    global terminate
 
347
    if signum == signal.SIGXCPU:
 
348
        terminate = "CPU Time Limit Exceeded"
 
349
 
 
350
def dispatch_msg(msg):
 
351
    global terminate
 
352
    if msg['cmd'] == 'restart':
 
353
        terminate = "User requested console be reset"
 
354
    if terminate:
 
355
        raise common.chat.Terminate({"restart":terminate})
 
356
    expiry.ping()
 
357
    lineQ.put({msg['cmd']:msg['text']})
 
358
    if terminate:
 
359
        raise common.chat.Terminate({"restart":terminate})
 
360
    return cmdQ.get()
 
361
 
 
362
def format_exc_start(start=0):
 
363
    etype, value, tb = sys.exc_info()
 
364
    tbbits = traceback.extract_tb(tb)[start:]
 
365
    list = ['Traceback (most recent call last):\n']
 
366
    list = list + traceback.format_list(tbbits)
 
367
    list = list + traceback.format_exception_only(etype, value)
 
368
    return ''.join(list)
 
369
 
 
370
def incomplete_utf8_sequence(byteseq):
 
371
    """
 
372
    str -> int
 
373
    Given a UTF-8-encoded byte sequence (str), returns the number of bytes at
 
374
    the end of the string which comprise an incomplete UTF-8 character
 
375
    sequence.
 
376
 
 
377
    If the string is empty or ends with a complete character OR INVALID
 
378
    sequence, returns 0.
 
379
    Otherwise, returns 1-3 indicating the number of bytes in the final
 
380
    incomplete (but valid) character sequence.
 
381
 
 
382
    Does not check any bytes before the final sequence for correctness.
 
383
 
 
384
    >>> incomplete_utf8_sequence("")
 
385
    0
 
386
    >>> incomplete_utf8_sequence("xy")
 
387
    0
 
388
    >>> incomplete_utf8_sequence("xy\xc3\xbc")
 
389
    0
 
390
    >>> incomplete_utf8_sequence("\xc3")
 
391
    1
 
392
    >>> incomplete_utf8_sequence("\xbc\xc3")
 
393
    1
 
394
    >>> incomplete_utf8_sequence("xy\xbc\xc3")
 
395
    1
 
396
    >>> incomplete_utf8_sequence("xy\xe0\xa0")
 
397
    2
 
398
    >>> incomplete_utf8_sequence("xy\xf4")
 
399
    1
 
400
    >>> incomplete_utf8_sequence("xy\xf4\x8f")
 
401
    2
 
402
    >>> incomplete_utf8_sequence("xy\xf4\x8f\xa0")
 
403
    3
 
404
    """
 
405
    count = 0
 
406
    expect = None
 
407
    for b in byteseq[::-1]:
 
408
        b = ord(b)
 
409
        count += 1
 
410
        if b & 0x80 == 0x0:
 
411
            # 0xxxxxxx (single-byte character)
 
412
            expect = 1
 
413
            break
 
414
        elif b & 0xc0 == 0x80:
 
415
            # 10xxxxxx (subsequent byte)
 
416
            pass
 
417
        elif b & 0xe0 == 0xc0:
 
418
            # 110xxxxx (start of 2-byte sequence)
 
419
            expect = 2
 
420
            break
 
421
        elif b & 0xf0 == 0xe0:
 
422
            # 1110xxxx (start of 3-byte sequence)
 
423
            expect = 3
 
424
            break
 
425
        elif b & 0xf8 == 0xf0:
 
426
            # 11110xxx (start of 4-byte sequence)
 
427
            expect = 4
 
428
            break
 
429
        else:
 
430
            # Invalid byte
 
431
            return 0
 
432
 
 
433
        if count >= 4:
 
434
            # Seen too many "subsequent bytes", invalid
 
435
            return 0
 
436
 
 
437
    if expect is None:
 
438
        # We never saw a "first byte", invalid
 
439
        return 0
 
440
 
 
441
    # We now know expect and count
 
442
    if count >= expect:
 
443
        # Complete, or we saw an invalid sequence
 
444
        return 0
 
445
    elif count < expect:
 
446
        # Incomplete
 
447
        return count
 
448
 
 
449
# Takes an object and returns a flattened version suitable for JSON
 
450
def flatten(object):
 
451
    flat = {}
 
452
    for o in object:
 
453
        try:
 
454
            flat[o] = cPickle.dumps(object[o], PICKLEVERSION)
 
455
        except TypeError:
 
456
            try:
 
457
                o_type = type(object[o]).__name__
 
458
                o_name = object[o].__name__
 
459
                fake_o = common.util.FakeObject(o_type, o_name)
 
460
                flat[o] = cPickle.dumps(fake_o, PICKLEVERSION)
 
461
            except AttributeError:
 
462
                pass
 
463
    return flat
 
464
 
 
465
if __name__ == "__main__":
 
466
    port = int(sys.argv[1])
 
467
    magic = sys.argv[2]
 
468
    
 
469
    # Sanitise the Enviroment
 
470
    os.environ = {}
 
471
    os.environ['PATH'] = '/usr/local/bin:/usr/bin:/bin'
 
472
 
 
473
    if len(sys.argv) >= 4:
 
474
        # working_dir
 
475
        os.chdir(sys.argv[3])
 
476
        os.environ['HOME'] = sys.argv[3]
 
477
 
 
478
    # Make python's search path follow the cwd
 
479
    sys.path[0] = ''
 
480
 
 
481
    common.chat.start_server(port, magic, True, dispatch_msg, initializer)