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

« back to all changes in this revision

Viewing changes to services/python-console

  • Committer: mattgiuca
  • Date: 2007-12-20 05:24:36 UTC
  • Revision ID: svn-v3-trunk0:2b9c9e99-6f39-0410-b283-7f802c844ae2:trunk:102
Added: Makefile (temporary, until a real setup comes along).
    This builds bin/trampoline and chmods it properly. Requires sudo access.

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 codeop
7
 
import cPickle
8
 
import cStringIO
9
 
import md5
10
 
import Queue
11
 
import signal
12
 
import socket
13
 
import sys
14
 
import traceback
15
 
from threading import Thread
16
 
 
17
 
import ivle.chat
18
 
import ivle.util
19
 
 
20
 
# This version must be supported by both the local and remote code
21
 
PICKLEVERSION = 0
22
 
 
23
 
class Interrupt(Exception):
24
 
    def __init__(self):
25
 
        Exception.__init__(self, "Interrupted!")
26
 
 
27
 
class ExpiryTimer(object):
28
 
    def __init__(self, idle):
29
 
        self.idle = idle
30
 
        signal.signal(signal.SIGALRM, self.timeout)
31
 
 
32
 
    def ping(self):
33
 
        signal.alarm(self.idle)
34
 
 
35
 
    def start(self, time):
36
 
        signal.alarm(time)
37
 
 
38
 
    def stop(self):
39
 
        self.ping()
40
 
 
41
 
    def timeout(self, signum, frame):
42
 
        sys.exit(1)
43
 
 
44
 
class StdinFromWeb(object):
45
 
    def __init__(self, cmdQ, lineQ):
46
 
        self.cmdQ = cmdQ
47
 
        self.lineQ = lineQ
48
 
 
49
 
    def readline(self):
50
 
        self.cmdQ.put({"input":None})
51
 
        expiry.ping()
52
 
        action, params = self.lineQ.get()
53
 
        if action == 'chat':
54
 
            return params
55
 
        elif action == 'interrupt':
56
 
            raise Interrupt()
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 = ivle.util.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
 
            action, params = 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
 
            action, params = self.lineQ.get()
110
 
            if action == 'interrupt':
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
 
            action, params = 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 action == 'interrupt':
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
 
    # FIXME: Clean up the whole stdin, stdout, stderr mess. We really need to 
131
 
    # be able to deal with the streams individually.
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
 
        self.cc = codeop.CommandCompiler()
155
 
        Thread.__init__(self)
156
 
 
157
 
    def execCmd(self, cmd):
158
 
        try:
159
 
            # We don't expect a return value - 'single' symbol prints it.
160
 
            self.eval(cmd)
161
 
            self.curr_cmd = ''
162
 
            self.webio.flush()
163
 
            return({"okay": None})
164
 
        except:
165
 
            self.curr_cmd = ''
166
 
            self.webio.flush()
167
 
            tb = format_exc_start(start=2)
168
 
            return({"exc": ''.join(tb).decode('utf-8', 'replace')})
169
 
 
170
 
    def run(self):
171
 
        # Set up global space and partial command buffer
172
 
        self.globs = {'__name__': '__main__'}
173
 
        self.curr_cmd = ''
174
 
 
175
 
        # Set up I/O to use web interface
176
 
        sys.stdin = self.webio
177
 
        sys.stdout = self.webio
178
 
        sys.stderr = self.webio
179
 
 
180
 
        # Handlers for each action
181
 
        actions = {
182
 
            'splash': self.handle_splash,
183
 
            'chat': self.handle_chat,
184
 
            'block': self.handle_block,
185
 
            'globals': self.handle_globals,
186
 
            'call': self.handle_call,
187
 
            'execute': self.handle_execute,
188
 
            'setvars': self.handle_setvars,
189
 
            }
190
 
 
191
 
        # Run the processing loop
192
 
        while True:
193
 
            action, params = self.lineQ.get()
194
 
            try:
195
 
                response = actions[action](params)
196
 
            except Exception, e:
197
 
                response = {'error': repr(e)}
198
 
            finally:
199
 
                self.cmdQ.put(response)
200
 
 
201
 
    def handle_splash(self, params):
202
 
        # Initial console splash screen
203
 
        python_version = '.'.join(str(v) for v in sys.version_info[:3])
204
 
        splash_text = ("""IVLE %s Python Console (Python %s)
205
 
Type "help", "copyright", "credits" or "license" for more information.
206
 
""" % (ivle.__version__, python_version))
207
 
        return {'output': splash_text}
208
 
 
209
 
    def handle_chat(self, params):
210
 
        # Set up the partial cmd buffer
211
 
        if self.curr_cmd == '':
212
 
            self.curr_cmd = params
213
 
        else:
214
 
            self.curr_cmd = self.curr_cmd + '\n' + params
215
 
 
216
 
        # Try to execute the buffer
217
 
        try:
218
 
            # A single trailing newline simply indicates that the line is
219
 
            # finished. Two trailing newlines indicate the end of a block.
220
 
            # Unfortunately, codeop.CommandCompiler causes even one to
221
 
            # terminate a block.
222
 
            # Thus we need to remove a trailing newline from the command,
223
 
            # unless there are *two* trailing newlines, or multi-line indented
224
 
            # blocks are impossible. See Google Code issue 105.
225
 
            cmd_text = self.curr_cmd
226
 
            if cmd_text.endswith('\n') and not cmd_text.endswith('\n\n'):
227
 
                cmd_text = cmd_text[:-1]
228
 
            cmd = self.cc(cmd_text, '<web session>')
229
 
            if cmd is None:
230
 
                # The command was incomplete, so send back a None, so the              
231
 
                # client can print a '...'
232
 
                return({"more":None})
233
 
            else:
234
 
                return(self.execCmd(cmd))
235
 
        except:
236
 
            # Clear any partial command
237
 
            self.curr_cmd = ''
238
 
            # Flush the output buffers
239
 
            sys.stderr.flush()
240
 
            sys.stdout.flush()
241
 
            # Return the exception
242
 
            tb = format_exc_start(start=3)
243
 
            return({"exc": ''.join(tb).decode('utf-8', 'replace')})
244
 
 
245
 
    def handle_block(self, params):
246
 
        # throw away any partial command.
247
 
        self.curr_cmd = ''
248
 
 
249
 
        # Try to execute a complete block of code
250
 
        try:
251
 
            cmd = compile(params, "<web session>", 'exec');
252
 
            return(self.execCmd(cmd))
253
 
        except:
254
 
            # Flush the output buffers
255
 
            sys.stderr.flush()
256
 
            sys.stdout.flush()
257
 
            # Return the exception
258
 
            tb = format_exc_start(start=1)
259
 
            return({"exc": ''.join(tb).decode('utf-8', 'replace')})
260
 
 
261
 
    def handle_globals(self, params):
262
 
        # Unpickle the new space (if provided)
263
 
        if isinstance(params, dict):
264
 
            self.globs = {'__name__': '__main__'}
265
 
            for g in params:
266
 
                try:
267
 
                    self.globs[g] = cPickle.loads(params[g])
268
 
                except:
269
 
                    pass
270
 
 
271
 
        # Return the current globals
272
 
        return({'globals': flatten(self.globs)})
273
 
 
274
 
    def handle_call(self, params):
275
 
        call = {}
276
 
        
277
 
        # throw away any partial command.
278
 
        self.curr_cmd = ''
279
 
 
280
 
        if isinstance(params, dict):
281
 
            try:
282
 
                # Expand parameters
283
 
                if isinstance(params['args'], list):
284
 
                    args = map(self.eval, params['args'])
285
 
                else:
286
 
                    args = []
287
 
                if isinstance(params['kwargs'], dict):
288
 
                    kwargs = {}
289
 
                    for kwarg in params['kwargs']:
290
 
                        kwargs[kwarg] = self.eval(
291
 
                            params['kwargs'][kwarg])
292
 
                else:
293
 
                    kwargs = {}
294
 
 
295
 
                # Run the fuction
296
 
                function = self.eval(params['function'])
297
 
                try:
298
 
                    call['result'] = function(*args, **kwargs)
299
 
                except Exception, e:
300
 
                    exception = {}
301
 
                    tb = format_exc_start(start=1)
302
 
                    exception['traceback'] = \
303
 
                        ''.join(tb).decode('utf-8', 'replace')
304
 
                    exception['except'] = cPickle.dumps(e,
305
 
                        PICKLEVERSION)
306
 
                    call['exception'] = exception
307
 
            except Exception, e:
308
 
                tb = format_exc_start(start=1)
309
 
                call = {"exc": ''.join(tb).decode('utf-8', 'replace')}
310
 
            
311
 
            # Flush the output buffers
312
 
            sys.stderr.flush()
313
 
            sys.stdout.flush()
314
 
 
315
 
            # Write out the inspection object
316
 
            return(call)
317
 
        else:
318
 
            return({'response': 'failure'})
319
 
 
320
 
    def handle_execute(self, params):
321
 
        # throw away any partial command.
322
 
        self.curr_cmd = ''
323
 
        
324
 
        # Like block but return a serialization of the state
325
 
        # throw away partial command
326
 
        response = {'okay': None}
327
 
        try:
328
 
            cmd = compile(params, "<web session>", 'exec');
329
 
            # We don't expect a return value - 'single' symbol prints it.
330
 
            self.eval(cmd)
331
 
        except Exception, e:
332
 
            response = {'exception': cPickle.dumps(e, PICKLEVERSION)}
333
 
           
334
 
        # Flush the output
335
 
        sys.stderr.flush()
336
 
        sys.stdout.flush()
337
 
               
338
 
        # Return the inspection object
339
 
        return(response)
340
 
 
341
 
    def handle_setvars(self, params):
342
 
        # Adds some variables to the global dictionary
343
 
        for var in params['set_vars']:
344
 
            try:
345
 
                self.globs[var] = self.eval(params['set_vars'][var])
346
 
            except Exception, e:
347
 
                tb = format_exc_start(start=1)
348
 
                return({"exc": ''.join(tb).decode('utf-8', 'replace')})
349
 
 
350
 
        return({'okay': None})
351
 
 
352
 
    def eval(self, source):
353
 
        """ Evaluates a string in the private global space """
354
 
        return eval(source, self.globs)
355
 
 
356
 
# The global 'magic' is the secret that the client and server share
357
 
# which is used to create and md5 digest to authenticate requests.
358
 
# It is assigned a real value at startup.
359
 
magic = ''
360
 
 
361
 
cmdQ = Queue.Queue()
362
 
lineQ = Queue.Queue()
363
 
interpThread = PythonRunner(cmdQ, lineQ)
364
 
terminate = None
365
 
 
366
 
# Default expiry time of 15 minutes
367
 
expiry = ExpiryTimer(15 * 60)
368
 
 
369
 
def initializer():
370
 
    interpThread.setDaemon(True)
371
 
    interpThread.start()
372
 
    signal.signal(signal.SIGXCPU, sig_handler)
373
 
    expiry.ping()
374
 
 
375
 
def sig_handler(signum, frame):
376
 
    """Handles response from signals"""
377
 
    global terminate
378
 
    if signum == signal.SIGXCPU:
379
 
        terminate = "CPU time limit exceeded"
380
 
 
381
 
def dispatch_msg(msg):
382
 
    global terminate
383
 
    if msg['cmd'] == 'terminate':
384
 
        terminate = "User requested restart"
385
 
    if terminate:
386
 
        raise ivle.chat.Terminate({"terminate":terminate})
387
 
    expiry.ping()
388
 
    lineQ.put((msg['cmd'],msg['text']))
389
 
    response = cmdQ.get()
390
 
    if terminate:
391
 
        raise ivle.chat.Terminate({"terminate":terminate})
392
 
    return response
393
 
 
394
 
def format_exc_start(start=0):
395
 
    etype, value, tb = sys.exc_info()
396
 
    tbbits = traceback.extract_tb(tb)[start:]
397
 
    list = ['Traceback (most recent call last):\n']
398
 
    list = list + traceback.format_list(tbbits)
399
 
    list = list + traceback.format_exception_only(etype, value)
400
 
    return ''.join(list)
401
 
 
402
 
 
403
 
# Takes an object and returns a flattened version suitable for JSON
404
 
def flatten(object):
405
 
    flat = {}
406
 
    for o in object:
407
 
        try:
408
 
            flat[o] = cPickle.dumps(object[o], PICKLEVERSION)
409
 
        except (TypeError, cPickle.PicklingError):
410
 
            try:
411
 
                o_type = type(object[o]).__name__
412
 
                o_name = object[o].__name__
413
 
                fake_o = ivle.util.FakeObject(o_type, o_name)
414
 
                flat[o] = cPickle.dumps(fake_o, PICKLEVERSION)
415
 
            except AttributeError:
416
 
                pass
417
 
    return flat
418
 
 
419
 
if __name__ == "__main__":
420
 
    port = int(sys.argv[1])
421
 
    magic = sys.argv[2]
422
 
 
423
 
    # Make python's search path follow the cwd
424
 
    sys.path[0] = ''
425
 
 
426
 
    ivle.chat.start_server(port, magic, True, dispatch_msg, initializer)