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

418 by mattgiuca
Renamed trunk/console to trunk/scripts. We are now able to put more scripts in
1
#!/usr/bin/python
2
3
# usage:
603 by mattgiuca
Console now starts up in the user's home directory.
4
#   python-console <port> <magic> [<working-dir>]
418 by mattgiuca
Renamed trunk/console to trunk/scripts. We are now able to put more scripts in
5
6
import cjson
7
import codeop
1016 by dcoles
Console: Added a 'inspect' mode to the console to allow a bit of code to be run
8
import cPickle
9
import cStringIO
418 by mattgiuca
Renamed trunk/console to trunk/scripts. We are now able to put more scripts in
10
import md5
11
import os
12
import Queue
13
import signal
14
import socket
15
import sys
846 by wagrant
python-console: Print proper tracebacks on exceptions. They actually
16
import traceback
418 by mattgiuca
Renamed trunk/console to trunk/scripts. We are now able to put more scripts in
17
from threading import Thread
18
432 by drtomc
usrmgt: more work on this. Still some work to go.
19
import common.chat
20
1016 by dcoles
Console: Added a 'inspect' mode to the console to allow a bit of code to be run
21
# This version must be supported by both the local and remote code
22
PICKLEVERSION = 2
23
598 by drtomc
console: send output back to the browser progressively.
24
class Interrupt(Exception):
628 by drtomc
console: Add output based interrupt. This allows users to interrupt long
25
    def __init__(self):
26
        Exception.__init__(self, "Interrupted!")
598 by drtomc
console: send output back to the browser progressively.
27
522 by drtomc
Add quite a lot of stuff to get usrmgt happening.
28
class ExpiryTimer(object):
29
    def __init__(self, idle):
30
        self.idle = idle
977 by dcoles
Console: Work on making console more responsive to signals (rather than just
31
        signal.signal(signal.SIGALRM, self.timeout)
522 by drtomc
Add quite a lot of stuff to get usrmgt happening.
32
33
    def ping(self):
34
        signal.alarm(self.idle)
35
36
    def start(self, time):
37
        signal.alarm(time)
38
39
    def stop(self):
40
        self.ping()
41
42
    def timeout(self, signum, frame):
43
        sys.exit(1)
977 by dcoles
Console: Work on making console more responsive to signals (rather than just
44
418 by mattgiuca
Renamed trunk/console to trunk/scripts. We are now able to put more scripts in
45
class StdinFromWeb(object):
46
    def __init__(self, cmdQ, lineQ):
47
        self.cmdQ = cmdQ
48
        self.lineQ = lineQ
49
50
    def readline(self):
51
        self.cmdQ.put({"input":None})
522 by drtomc
Add quite a lot of stuff to get usrmgt happening.
52
        expiry.ping()
418 by mattgiuca
Renamed trunk/console to trunk/scripts. We are now able to put more scripts in
53
        ln = self.lineQ.get()
54
        if 'chat' in ln:
55
            return ln['chat']
648 by drtomc
console: fix a trivial bug which caused it to loop if you got a syntax error in your first command.
56
        if 'interrupt' in ln:
57
            raise Interrupt()
418 by mattgiuca
Renamed trunk/console to trunk/scripts. We are now able to put more scripts in
58
598 by drtomc
console: send output back to the browser progressively.
59
class StdoutToWeb(object):
60
    def __init__(self, cmdQ, lineQ):
61
        self.cmdQ = cmdQ
62
        self.lineQ = lineQ
63
        self.remainder = ''
64
874 by wagrant
python-console: We are now a shiny UTF-8 console. Don't send anything
65
    def _trim_incomplete_final(self, stuff):
66
        '''Trim an incomplete UTF-8 character from the end of a string.
67
           Returns (trimmed_string, count_of_trimmed_bytes).
68
        '''
69
        tokill = incomplete_utf8_sequence(stuff)
70
        if tokill == 0:
71
            return (stuff, tokill)
72
        else:
73
            return (stuff[:-tokill], tokill)
74
598 by drtomc
console: send output back to the browser progressively.
75
    def write(self, stuff):
874 by wagrant
python-console: We are now a shiny UTF-8 console. Don't send anything
76
        # print will only give a non-file a unicode or str. There's no way
77
        # to convince it to encode unicodes, so we have to do it ourselves.
78
        # Yay for file special-cases (fileobject.c, PyFile_WriteObject).
79
        # If somebody wants to write some other object to here, they do it
80
        # at their own peril.
81
        if isinstance(stuff, unicode):
82
            stuff = stuff.encode('utf-8')
660 by drtomc
console: buffering now tries to buffer enough, but not too much.
83
        self.remainder = self.remainder + stuff
84
85
        # if there's less than 128 bytes, buffer
86
        if len(self.remainder) < 128:
641 by drtomc
console: slightly more aggressive output buffering - wait till we've at least
87
            return
88
660 by drtomc
console: buffering now tries to buffer enough, but not too much.
89
        # if there's lots, then send it in 1/2K blocks
90
        while len(self.remainder) > 512:
874 by wagrant
python-console: We are now a shiny UTF-8 console. Don't send anything
91
            # We send things as Unicode inside JSON, so we must only send
92
            # complete UTF-8 characters.
93
            (blk, count) = self._trim_incomplete_final(self.remainder[:512])
94
            self.cmdQ.put({"output":blk.decode('utf-8', 'replace')})
660 by drtomc
console: buffering now tries to buffer enough, but not too much.
95
            expiry.ping()
96
            ln = self.lineQ.get()
874 by wagrant
python-console: We are now a shiny UTF-8 console. Don't send anything
97
            self.remainder = self.remainder[512 - count:]
660 by drtomc
console: buffering now tries to buffer enough, but not too much.
98
99
        # Finally, split the remainder up into lines, and ship all the
100
        # completed lines off to the server.
101
        lines = self.remainder.split("\n")
598 by drtomc
console: send output back to the browser progressively.
102
        self.remainder = lines[-1]
103
        del lines[-1]
104
105
        if len(lines) > 0:
599 by drtomc
console: improve end of line handling.
106
            lines.append('')
107
            text = "\n".join(lines)
874 by wagrant
python-console: We are now a shiny UTF-8 console. Don't send anything
108
            self.cmdQ.put({"output":text.decode('utf-8', 'replace')})
598 by drtomc
console: send output back to the browser progressively.
109
            expiry.ping()
110
            ln = self.lineQ.get()
111
            if 'interrupt' in ln:
112
                raise Interrupt()
113
114
    def flush(self):
599 by drtomc
console: improve end of line handling.
115
        if len(self.remainder) > 0:
874 by wagrant
python-console: We are now a shiny UTF-8 console. Don't send anything
116
            (out, count) = self._trim_incomplete_final(self.remainder)
117
            self.cmdQ.put({"output":out.decode('utf-8', 'replace')})
599 by drtomc
console: improve end of line handling.
118
            expiry.ping()
119
            ln = self.lineQ.get()
874 by wagrant
python-console: We are now a shiny UTF-8 console. Don't send anything
120
            # Leave incomplete characters in the buffer.
121
            # Yes, this does mean that an incomplete character will be left
122
            # off the end, but we discussed this and it was deemed best.
123
            self.remainder = self.remainder[len(self.remainder)-count:]
599 by drtomc
console: improve end of line handling.
124
            if 'interrupt' in ln:
125
                raise Interrupt()
598 by drtomc
console: send output back to the browser progressively.
126
750 by dcoles
Console: Flush current output before requesting input from Web
127
class WebIO(object):
128
    """Provides a file like interface to the Web front end of the console.
129
    You may print text to the console using write(), flush any buffered output 
130
    using flush(), or request text from the console using readline()"""
131
    
132
    def __init__(self, cmdQ, lineQ):
133
        self.cmdQ = cmdQ
134
        self.lineQ = lineQ
135
        self.stdin = StdinFromWeb(self.cmdQ, self.lineQ)
136
        self.stdout = StdoutToWeb(self.cmdQ, self.lineQ)
137
138
    def write(self, stuff):
139
        self.stdout.write(stuff)
140
141
    def flush(self):
142
        self.stdout.flush()
143
144
    def readline(self):
145
        self.stdout.flush()
146
        return self.stdin.readline()
147
418 by mattgiuca
Renamed trunk/console to trunk/scripts. We are now able to put more scripts in
148
class PythonRunner(Thread):
149
    def __init__(self, cmdQ, lineQ):
150
        self.cmdQ = cmdQ
151
        self.lineQ = lineQ
750 by dcoles
Console: Flush current output before requesting input from Web
152
        self.webio = WebIO(self.cmdQ, self.lineQ)
418 by mattgiuca
Renamed trunk/console to trunk/scripts. We are now able to put more scripts in
153
        Thread.__init__(self)
154
155
    def execCmd(self, cmd):
156
        try:
750 by dcoles
Console: Flush current output before requesting input from Web
157
            sys.stdin = self.webio
158
            sys.stdout = self.webio
159
            sys.stderr = self.webio
869 by wagrant
python-console: Don't assume that our command will give a return value,
160
            # We don't expect a return value - 'single' symbol prints it.
161
            eval(cmd, self.globs)
750 by dcoles
Console: Flush current output before requesting input from Web
162
            self.webio.flush()
869 by wagrant
python-console: Don't assume that our command will give a return value,
163
            self.cmdQ.put({"okay": None})
418 by mattgiuca
Renamed trunk/console to trunk/scripts. We are now able to put more scripts in
164
            self.curr_cmd = ''
846 by wagrant
python-console: Print proper tracebacks on exceptions. They actually
165
        except:
166
            tb = format_exc_start(start=1)
750 by dcoles
Console: Flush current output before requesting input from Web
167
            self.webio.flush()
874 by wagrant
python-console: We are now a shiny UTF-8 console. Don't send anything
168
            self.cmdQ.put({"exc": ''.join(tb).decode('utf-8', 'replace')})
418 by mattgiuca
Renamed trunk/console to trunk/scripts. We are now able to put more scripts in
169
            self.curr_cmd = ''
170
171
    def run(self):
598 by drtomc
console: send output back to the browser progressively.
172
        self.globs = {}
173
        self.globs['__builtins__'] = globals()['__builtins__']
174
        self.curr_cmd = ''
418 by mattgiuca
Renamed trunk/console to trunk/scripts. We are now able to put more scripts in
175
176
        while True:
177
            ln = self.lineQ.get()
178
            if 'chat' in ln:
179
                if self.curr_cmd == '':
180
                    self.curr_cmd = ln['chat']
181
                else:
182
                    self.curr_cmd = self.curr_cmd + '\n' + ln['chat']
183
                try:
869 by wagrant
python-console: Don't assume that our command will give a return value,
184
                    cmd = codeop.compile_command(self.curr_cmd, '<web session>')
418 by mattgiuca
Renamed trunk/console to trunk/scripts. We are now able to put more scripts in
185
                    if cmd is None:
186
                        # The command was incomplete,
187
                        # so send back a None, so the
188
                        # client can print a '...'
189
                        self.cmdQ.put({"more":None})
190
                    else:
191
                        self.execCmd(cmd)
846 by wagrant
python-console: Print proper tracebacks on exceptions. They actually
192
                except:
193
                    tb = format_exc_start(start=3)
874 by wagrant
python-console: We are now a shiny UTF-8 console. Don't send anything
194
                    self.cmdQ.put({"exc": ''.join(tb).decode('utf-8', 'replace')})
750 by dcoles
Console: Flush current output before requesting input from Web
195
                    self.webio.flush()
418 by mattgiuca
Renamed trunk/console to trunk/scripts. We are now able to put more scripts in
196
                    self.curr_cmd = ''
197
            if 'block' in ln:
198
                # throw away a partial command.
199
                try:
200
                    cmd = compile(ln['block'], "<web session>", 'exec');
201
                    self.execCmd(cmd)
846 by wagrant
python-console: Print proper tracebacks on exceptions. They actually
202
                except:
867 by wagrant
python-console: Fix traceback trimming when in block mode (worksheets).
203
                    tb = format_exc_start(start=1)
750 by dcoles
Console: Flush current output before requesting input from Web
204
                    self.webio.flush()
874 by wagrant
python-console: We are now a shiny UTF-8 console. Don't send anything
205
                    self.cmdQ.put({"exc": ''.join(tb).decode('utf-8', 'replace')})
418 by mattgiuca
Renamed trunk/console to trunk/scripts. We are now able to put more scripts in
206
                    self.curr_cmd = ''
1016 by dcoles
Console: Added a 'inspect' mode to the console to allow a bit of code to be run
207
            if 'inspect' in ln:
208
                # Like block but return a serialization of the state
209
                # throw away a partial command and all state.
210
                inspection = {}
211
                globs = {}
212
                globs['__builtins__'] = globals()['__builtins__']
213
                output_buffer = cStringIO.StringIO()
214
                try:
215
                    cmd = compile(ln['inspect'], "<web session>", 'exec');
216
                    #self.execCmd(cmd)
217
                    # Begin changes
218
                    sys.stdin = None
219
                    sys.stdout = output_buffer
220
                    sys.stderr = output_buffer
221
                    # We don't expect a return value - 'single' symbol prints 
222
                    # it.
223
                    eval(cmd, globs)
224
                except Exception, e:
225
                    exception = {}
226
                    tb = format_exc_start(start=1)
227
                    exception['traceback'] = \
228
                        ''.join(tb).decode('utf-8', 'replace')
229
                    exception['except'] = cPickle.dumps(e, PICKLEVERSION)
230
                    inspection['exception'] = exception                
231
                
232
                # Write out the inspection object
233
                inspection['output'] = output_buffer.getvalue()
234
                inspection['globals'] = flatten(globs)
235
                self.cmdQ.put({"inspection": inspection})
236
                output_buffer.close()
237
                self.curr_cmd = ''
238
418 by mattgiuca
Renamed trunk/console to trunk/scripts. We are now able to put more scripts in
239
240
def daemonize():
241
    if os.fork():   # launch child and...
242
        os._exit(0) # kill off parent
243
    os.setsid()
244
    if os.fork():   # launch child and...
245
        os._exit(0) # kill off parent again.
246
    os.umask(077)
247
248
# The global 'magic' is the secret that the client and server share
249
# which is used to create and md5 digest to authenticate requests.
250
# It is assigned a real value at startup.
251
magic = ''
252
253
cmdQ = Queue.Queue()
254
lineQ = Queue.Queue()
255
interpThread = PythonRunner(cmdQ, lineQ)
977 by dcoles
Console: Work on making console more responsive to signals (rather than just
256
terminate = None
418 by mattgiuca
Renamed trunk/console to trunk/scripts. We are now able to put more scripts in
257
522 by drtomc
Add quite a lot of stuff to get usrmgt happening.
258
# Default expiry time of 15 minutes
259
expiry = ExpiryTimer(15 * 60)
260
432 by drtomc
usrmgt: more work on this. Still some work to go.
261
def initializer():
262
    interpThread.setDaemon(True)
263
    interpThread.start()
977 by dcoles
Console: Work on making console more responsive to signals (rather than just
264
    signal.signal(signal.SIGXCPU, sig_handler)
522 by drtomc
Add quite a lot of stuff to get usrmgt happening.
265
    expiry.ping()
432 by drtomc
usrmgt: more work on this. Still some work to go.
266
977 by dcoles
Console: Work on making console more responsive to signals (rather than just
267
def sig_handler(signum, frame):
268
    """Handles response from signals"""
269
    global terminate
270
    if signum == signal.SIGXCPU:
271
        terminate = "CPU Time Limit Exceeded"
272
432 by drtomc
usrmgt: more work on this. Still some work to go.
273
def dispatch_msg(msg):
991 by dcoles
Console: Some improvements to the python console code - most notably the
274
    global terminate
275
    if msg['cmd'] == 'restart':
276
        terminate = "User requested console be reset"
277
    if terminate:
278
        raise common.chat.Terminate({"restart":terminate})
522 by drtomc
Add quite a lot of stuff to get usrmgt happening.
279
    expiry.ping()
432 by drtomc
usrmgt: more work on this. Still some work to go.
280
    lineQ.put({msg['cmd']:msg['text']})
977 by dcoles
Console: Work on making console more responsive to signals (rather than just
281
    if terminate:
282
        raise common.chat.Terminate({"restart":terminate})
432 by drtomc
usrmgt: more work on this. Still some work to go.
283
    return cmdQ.get()
284
846 by wagrant
python-console: Print proper tracebacks on exceptions. They actually
285
def format_exc_start(start=0):
286
    etype, value, tb = sys.exc_info()
287
    tbbits = traceback.extract_tb(tb)[start:]
288
    list = ['Traceback (most recent call last):\n']
289
    list = list + traceback.format_list(tbbits)
290
    list = list + traceback.format_exception_only(etype, value)
291
    return ''.join(list)
292
871 by mattgiuca
scripts/python-console: Added function incomplete_utf8_sequence, for use with
293
def incomplete_utf8_sequence(byteseq):
294
    """
295
    str -> int
296
    Given a UTF-8-encoded byte sequence (str), returns the number of bytes at
297
    the end of the string which comprise an incomplete UTF-8 character
298
    sequence.
299
300
    If the string is empty or ends with a complete character OR INVALID
301
    sequence, returns 0.
302
    Otherwise, returns 1-3 indicating the number of bytes in the final
303
    incomplete (but valid) character sequence.
304
305
    Does not check any bytes before the final sequence for correctness.
306
307
    >>> incomplete_utf8_sequence("")
308
    0
309
    >>> incomplete_utf8_sequence("xy")
310
    0
311
    >>> incomplete_utf8_sequence("xy\xc3\xbc")
312
    0
313
    >>> incomplete_utf8_sequence("\xc3")
314
    1
315
    >>> incomplete_utf8_sequence("\xbc\xc3")
316
    1
317
    >>> incomplete_utf8_sequence("xy\xbc\xc3")
318
    1
319
    >>> incomplete_utf8_sequence("xy\xe0\xa0")
320
    2
321
    >>> incomplete_utf8_sequence("xy\xf4")
322
    1
323
    >>> incomplete_utf8_sequence("xy\xf4\x8f")
324
    2
325
    >>> incomplete_utf8_sequence("xy\xf4\x8f\xa0")
326
    3
327
    """
328
    count = 0
329
    expect = None
330
    for b in byteseq[::-1]:
331
        b = ord(b)
332
        count += 1
333
        if b & 0x80 == 0x0:
334
            # 0xxxxxxx (single-byte character)
335
            expect = 1
336
            break
337
        elif b & 0xc0 == 0x80:
338
            # 10xxxxxx (subsequent byte)
339
            pass
340
        elif b & 0xe0 == 0xc0:
341
            # 110xxxxx (start of 2-byte sequence)
342
            expect = 2
343
            break
344
        elif b & 0xf0 == 0xe0:
345
            # 1110xxxx (start of 3-byte sequence)
346
            expect = 3
347
            break
348
        elif b & 0xf8 == 0xf0:
349
            # 11110xxx (start of 4-byte sequence)
350
            expect = 4
351
            break
352
        else:
353
            # Invalid byte
354
            return 0
355
356
        if count >= 4:
357
            # Seen too many "subsequent bytes", invalid
358
            return 0
359
360
    if expect is None:
361
        # We never saw a "first byte", invalid
362
        return 0
363
364
    # We now know expect and count
365
    if count >= expect:
366
        # Complete, or we saw an invalid sequence
367
        return 0
368
    elif count < expect:
369
        # Incomplete
370
        return count
371
1016 by dcoles
Console: Added a 'inspect' mode to the console to allow a bit of code to be run
372
# Takes an object and returns a flattened version suitable for JSON
373
def flatten(object):
374
    flat = {}
375
    for o in object:
376
        try:
377
            flat[o] = cPickle.dumps(object[o], PICKLEVERSION)
378
        except:
379
            pass
380
    return flat
381
418 by mattgiuca
Renamed trunk/console to trunk/scripts. We are now able to put more scripts in
382
if __name__ == "__main__":
383
    port = int(sys.argv[1])
384
    magic = sys.argv[2]
974 by dcoles
Console: Sanitise Environment varibles. We now only give PATH and HOME (and
385
    
386
    # Sanitise the Enviroment
387
    os.environ = {}
388
    os.environ['PATH'] = '/usr/local/bin:/usr/bin:/bin'
389
603 by mattgiuca
Console now starts up in the user's home directory.
390
    if len(sys.argv) >= 4:
391
        # working_dir
392
        os.chdir(sys.argv[3])
662 by drtomc
console: set the environment variable HOME so matplotlib works in the console.
393
        os.environ['HOME'] = sys.argv[3]
418 by mattgiuca
Renamed trunk/console to trunk/scripts. We are now able to put more scripts in
394
974 by dcoles
Console: Sanitise Environment varibles. We now only give PATH and HOME (and
395
    # Make python's search path follow the cwd
396
    sys.path[0] = ''
397
432 by drtomc
usrmgt: more work on this. Still some work to go.
398
    common.chat.start_server(port, magic, True, dispatch_msg, initializer)