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

« back to all changes in this revision

Viewing changes to scripts/python-console

  • Committer: mattgiuca
  • Date: 2008-07-21 05:25:48 UTC
  • Revision ID: svn-v3-trunk0:2b9c9e99-6f39-0410-b283-7f802c844ae2:trunk:920
userdb: Added to table "offering" fields max_students_per_group and
    max_groups_per_student.

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