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

1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
1
# IVLE
2
# Copyright (C) 2007-2008 The University of Melbourne
3
#
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17
18
# Module: Console
19
# Author: Matt Giuca, Tom Conway, David Coles (refactor)
20
# Date: 13/8/2008
21
22
# Mainly refactored out of consoleservice
23
24
import errno
25
import cPickle
1226 by Matt Giuca
ivle.console: Replaced md5 module with hashlib. (md5 is deprecated and
26
import hashlib
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
27
import os
28
import random
29
import socket
30
import StringIO
31
import uuid
32
1773 by David Coles
console: Make sure HOME is set correctly (not always the same as CWD). Also use ivle.interpret.execute_raw rather than duplicating trapoline calling code in ivle.chat
33
from ivle import chat, interpret
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
34
35
class ConsoleError(Exception):
36
    """ The console failed in some way. This is bad. """
1630 by David Coles
ivle.console should capture ivle.chat.ProtocolErrors as a ConsoleError
37
    pass
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
38
39
class ConsoleException(Exception):
40
    """ The code being exectuted on the console returned an exception. 
41
    """
1630 by David Coles
ivle.console should capture ivle.chat.ProtocolErrors as a ConsoleError
42
    pass
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
43
44
class TruncateStringIO(StringIO.StringIO):
45
    """ A class that wraps around StringIO and truncates the buffer when the 
46
    contents are read (except for when using getvalue).
47
    """
48
    def __init__(self, buffer=None):
49
        StringIO.StringIO.__init__(self, buffer)
50
    
51
    def read(self, n=-1):
52
        """ Read at most size bytes from the file (less if the read hits EOF 
53
        before obtaining size bytes).
54
55
        If the size argument is negative or omitted, read all data until EOF is      
56
        reached. The bytes are returned as a string object. An empty string is 
57
        returned when EOF is encountered immediately.
58
59
        Truncates the buffer.
60
        """
61
62
        self.seek(0)
63
        res = StringIO.StringIO.read(self, n)
64
        self.truncate(0)
65
        return res
66
67
    def readline(self, length=None):
68
        """ Read one entire line from the file.
69
 
70
        A trailing newline character is kept in the string (but may be absent 
71
        when a file ends with an incomplete line). If the size argument is   
72
        present and non-negative, it is a maximum byte count (including the      
73
        trailing newline) and an incomplete line may be returned.
74
75
        An empty string is returned only when EOF is encountered immediately.
76
        
77
        Note: Unlike stdio's fgets(), the returned string contains null   
78
        characters ('\0') if they occurred in the input.
79
80
        Removes the line from the buffer.
81
        """
82
83
        self.seek(0)
84
        res = StringIO.StringIO.readline(self, length)
85
        rest = StringIO.StringIO.read(self)
86
        self.truncate(0)
87
        self.write(rest)
88
        return res
89
90
    def readlines(self, sizehint=0):
91
        """ Read until EOF using readline() and return a list containing the        
92
        lines thus read.
93
        
94
        If the optional sizehint argument is present, instead of reading up to 
95
        EOF, whole lines totalling approximately sizehint bytes (or more to      
96
        accommodate a final whole line).
97
98
        Truncates the buffer.
99
        """
100
101
        self.seek(0)
102
        res = StringIO.StringIO.readlines(self, length)
103
        self.truncate(0)
104
        return res
105
106
class Console(object):
107
    """ Provides a nice python interface to the console
108
    """
1773 by David Coles
console: Make sure HOME is set correctly (not always the same as CWD). Also use ivle.interpret.execute_raw rather than duplicating trapoline calling code in ivle.chat
109
    def __init__(self, config, user, jail_path, working_dir):
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
110
        """Starts up a console service for user uid, inside chroot jail 
111
        jail_path with work directory of working_dir
112
        """
113
        super(Console, self).__init__()
114
1275 by William Grant
Kill ivle.console's dependency on ivle.conf.
115
        self.config = config
1773 by David Coles
console: Make sure HOME is set correctly (not always the same as CWD). Also use ivle.interpret.execute_raw rather than duplicating trapoline calling code in ivle.chat
116
        self.user = user
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
117
        self.jail_path = jail_path
118
        self.working_dir = working_dir
119
120
        # Set up the buffers
121
        self.stdin = TruncateStringIO()
122
        self.stdout = TruncateStringIO()
123
        self.stderr = TruncateStringIO()
124
125
        # Fire up the console
126
        self.restart()
127
128
    def restart(self):
129
        # Empty all the buffers
130
        self.stdin.truncate(0)
131
        self.stdout.truncate(0)
132
        self.stderr.truncate(0)
133
134
        # TODO: Check if we are already running a console. If we are shut it 
135
        # down first.
136
137
        # TODO: Figure out the host name the console server is running on.
138
        self.host = socket.gethostname()
139
140
        # Create magic
141
        # TODO
1226 by Matt Giuca
ivle.console: Replaced md5 module with hashlib. (md5 is deprecated and
142
        self.magic = hashlib.md5(uuid.uuid4().bytes).hexdigest()
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
143
144
        # Try to find a free port on the server.
145
        # Just try some random ports in the range [3000,8000)
146
        # until we either succeed, or give up. If you think this
147
        # sounds risky, it isn't:
148
        # For N ports (e.g. 5000) with k (e.g. 100) in use, the
149
        # probability of failing to find a free port in t (e.g. 5) tries
150
        # is (k / N) ** t (e.g. 3.2*10e-9).
151
152
        tries = 0
1773 by David Coles
console: Make sure HOME is set correctly (not always the same as CWD). Also use ivle.interpret.execute_raw rather than duplicating trapoline calling code in ivle.chat
153
        error = None
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
154
        while tries < 5:
155
            self.port = int(random.uniform(3000, 8000))
156
1773 by David Coles
console: Make sure HOME is set correctly (not always the same as CWD). Also use ivle.interpret.execute_raw rather than duplicating trapoline calling code in ivle.chat
157
            python_console = os.path.join(self.config['paths']['share'],
158
                        'services/python-console')
159
            args = [python_console, str(self.port), str(self.magic)]
160
161
            try:
162
                interpret.execute_raw(self.config, self.user, self.jail_path,
163
                        self.working_dir, "/usr/bin/python", args)
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
164
                # success
1773 by David Coles
console: Make sure HOME is set correctly (not always the same as CWD). Also use ivle.interpret.execute_raw rather than duplicating trapoline calling code in ivle.chat
165
                break
166
            except interpret.ExecutionError, e:
167
                tries += 1
168
                error = e.message
169
170
        # If we can't start the console after 5 attemps (can't find a free 
171
        # port during random probing, syntax errors, segfaults) throw an 
172
        # exception.
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
173
        if tries == 5:
1773 by David Coles
console: Make sure HOME is set correctly (not always the same as CWD). Also use ivle.interpret.execute_raw rather than duplicating trapoline calling code in ivle.chat
174
            raise ConsoleError('Unable to start console service: %s'%error)
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
175
176
    def __chat(self, cmd, args):
177
        """ A wrapper around chat.chat to comunicate directly with the 
178
        console.
179
        """
180
        try:
181
            response = chat.chat(self.host, self.port,
182
                {'cmd': cmd, 'text': args}, self.magic)
183
        except socket.error, (enumber, estring):
184
            if enumber == errno.ECONNREFUSED:
185
                # Timeout
186
                raise ConsoleError(
187
                    "Could not establish a connection to the python console")
188
            else:
189
                # Some other error - probably serious
190
                raise socket.error, (enumber, estring)
1801.1.1 by William Grant
Replace cjson with json, or simplejson if json is not available (Python <2.6)
191
        except ValueError:
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
192
            # Couldn't decode the JSON
193
            raise ConsoleError(
194
                "Could not understand the python console response")
1630 by David Coles
ivle.console should capture ivle.chat.ProtocolErrors as a ConsoleError
195
        except chat.ProtocolError, e:
196
            raise ConsoleError(*e.args)
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
197
198
        return response
199
200
    def __handle_chat(self, cmd, args):
201
        """ A wrapper around self.__chat that handles all the messy responses 
202
        of chat for higher level interfaces such as inspect
203
        """
204
        # Do the request
205
        response = self.__chat(cmd, args)
206
207
        # Process I/O requests
208
        while 'output' in response or 'input' in response:
209
            if 'output' in response:
210
                self.stdout.write(response['output'])
211
                response = self.chat()
212
            elif 'input' in response:
213
                response = self.chat(self.stdin.readline())
214
215
        # Process user exceptions
216
        if 'exc' in response:
217
            raise ConsoleException(response['exc'])
218
219
        return response
220
221
    def chat(self, code=''):
222
        """ Executes a partial block of code """
223
        return self.__chat('chat', code)
224
225
    def block(self, code):
226
        """ Executes a block of code and returns the output """
227
        block = self.__handle_chat('block', code)
228
        if 'output' in block:
229
            return block['output']
230
        elif 'okay' in block:
231
            return
232
        else:
233
            raise ConsoleException("Bad response from console: %s"%str(block))
234
235
    def globals(self, globs=None):
236
        """ Returns a dictionary of the console's globals and optionally set 
237
        them to a new value
238
        """
239
        # Pickle the globals
240
        pickled_globs = None
241
        if globs is not None:
242
            pickled_globs = {}
243
            for g in globs:
244
                pickled_globs[g] = cPickle.dumps(globs[g])
245
246
        globals = self.__handle_chat('globals', pickled_globs)
247
248
        # Unpickle the globals
249
        for g in globals['globals']:
1801.1.2 by William Grant
(simple)json always returns a unicode when decoding, while cjson returned a str where possible. This makes cPickle unhappy, so convert back to a str.
250
            globals['globals'][g] = cPickle.loads(str(globals['globals'][g]))
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
251
252
        return globals['globals']
253
        
254
255
    def call(self, function, *args, **kwargs):
256
        """ Calls a function in the python console. Can take in a list of 
257
        repr() args and dictionary of repr() values kwargs. These will be 
258
        evaluated on the server side.
259
        """
260
        call_args = {
261
            'function': function,
262
            'args': args,
263
            'kwargs': kwargs}
264
        call = self.__handle_chat('call', call_args)
265
266
        # Unpickle any exceptions
267
        if 'exception' in call:
268
            call['exception']['except'] = \
1801.1.2 by William Grant
(simple)json always returns a unicode when decoding, while cjson returned a str where possible. This makes cPickle unhappy, so convert back to a str.
269
                cPickle.loads(str(call['exception']['except']))
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
270
271
        return call
272
273
    def execute(self, code=''):
274
        """ Runs a block of code in the python console.
275
        If an exception was thrown then returns an exception object.
276
        """
277
        execute = self.__handle_chat('execute', code)
278
              
279
        # Unpickle any exceptions
280
        if 'exception' in execute:
1801.1.2 by William Grant
(simple)json always returns a unicode when decoding, while cjson returned a str where possible. This makes cPickle unhappy, so convert back to a str.
281
            execute['exception'] = cPickle.loads(str(execute['exception']))
1397.1.3 by William Grant
In the Console wrapper, unpickle the exception into the original dict and return that, not just the exception.
282
        return execute
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
283
284
285
    def set_vars(self, variables):
286
        """ Takes a dictionary of varibles to add to the console's global 
287
        space. These are evaluated in the local space so you can't use this to 
288
        set a varible to a value to be calculated on the console side.
289
        """
290
        vars = {}
291
        for v in variables:
292
            vars[v] = repr(variables[v])
293
294
        set_vars = self.__handle_chat('set_vars', vars)
295
296
        if set_vars.get('response') != 'okay':
297
            raise ConsoleError("Could not set variables")
298
299
    def close(self):
300
        """ Causes the console process to terminate """
301
        return self.__chat('terminate', None)
302
    
303
class ExistingConsole(Console):
304
    """ Provides a nice python interface to an existing console.
305
    Note: You can't restart an existing console since there is no way to infer 
306
    all the starting parameters. Just start a new Console instead.
307
    """
308
    def __init__(self, host, port, magic):
309
        self.host = host
310
        self.port = port
311
        self.magic = magic
312
313
        # Set up the buffers
314
        self.stdin = TruncateStringIO()
315
        self.stdout = TruncateStringIO()
316
        self.stderr = TruncateStringIO()
317
318
    def restart():
319
        raise NotImplementedError('You can not restart an existing console')
320