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

« back to all changes in this revision

Viewing changes to scripts/python-console

Restore the media files (just help.css) to the new help system.

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)