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 |
|
8 |
import md5 |
|
9 |
import os |
|
10 |
import Queue |
|
11 |
import signal |
|
12 |
import socket |
|
13 |
import sys |
|
14 |
from threading import Thread |
|
526
by drtomc
python-console: trivial bugfix - missing import. |
15 |
from functools import partial |
418
by mattgiuca
Renamed trunk/console to trunk/scripts. We are now able to put more scripts in |
16 |
|
432
by drtomc
usrmgt: more work on this. Still some work to go. |
17 |
import common.chat |
18 |
||
598
by drtomc
console: send output back to the browser progressively. |
19 |
class Interrupt(Exception): |
628
by drtomc
console: Add output based interrupt. This allows users to interrupt long |
20 |
def __init__(self): |
21 |
Exception.__init__(self, "Interrupted!") |
|
598
by drtomc
console: send output back to the browser progressively. |
22 |
|
522
by drtomc
Add quite a lot of stuff to get usrmgt happening. |
23 |
class ExpiryTimer(object): |
24 |
def __init__(self, idle): |
|
25 |
self.idle = idle |
|
564
by drtomc
python-console: Fix the timeout code. |
26 |
signal.signal(signal.SIGALRM, partial(self.timeout)) |
522
by drtomc
Add quite a lot of stuff to get usrmgt happening. |
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 |
||
418
by mattgiuca
Renamed trunk/console to trunk/scripts. We are now able to put more scripts in |
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}) |
|
522
by drtomc
Add quite a lot of stuff to get usrmgt happening. |
47 |
expiry.ping() |
418
by mattgiuca
Renamed trunk/console to trunk/scripts. We are now able to put more scripts in |
48 |
ln = self.lineQ.get() |
49 |
if 'chat' in ln: |
|
50 |
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. |
51 |
if 'interrupt' in ln: |
52 |
raise Interrupt() |
|
418
by mattgiuca
Renamed trunk/console to trunk/scripts. We are now able to put more scripts in |
53 |
|
598
by drtomc
console: send output back to the browser progressively. |
54 |
class StdoutToWeb(object): |
55 |
def __init__(self, cmdQ, lineQ): |
|
56 |
self.cmdQ = cmdQ |
|
57 |
self.lineQ = lineQ |
|
58 |
self.remainder = '' |
|
59 |
||
60 |
def write(self, stuff): |
|
660
by drtomc
console: buffering now tries to buffer enough, but not too much. |
61 |
self.remainder = self.remainder + stuff |
62 |
||
63 |
# if there's less than 128 bytes, buffer
|
|
64 |
if len(self.remainder) < 128: |
|
641
by drtomc
console: slightly more aggressive output buffering - wait till we've at least |
65 |
return
|
66 |
||
660
by drtomc
console: buffering now tries to buffer enough, but not too much. |
67 |
# if there's lots, then send it in 1/2K blocks
|
68 |
while len(self.remainder) > 512: |
|
69 |
blk = self.remainder[0:512] |
|
70 |
self.cmdQ.put({"output":blk}) |
|
71 |
expiry.ping() |
|
72 |
ln = self.lineQ.get() |
|
73 |
self.remainder = self.remainder[512:] |
|
74 |
||
75 |
# Finally, split the remainder up into lines, and ship all the
|
|
76 |
# completed lines off to the server.
|
|
77 |
lines = self.remainder.split("\n") |
|
598
by drtomc
console: send output back to the browser progressively. |
78 |
self.remainder = lines[-1] |
79 |
del lines[-1] |
|
80 |
||
81 |
if len(lines) > 0: |
|
599
by drtomc
console: improve end of line handling. |
82 |
lines.append('') |
83 |
text = "\n".join(lines) |
|
84 |
self.cmdQ.put({"output":text}) |
|
598
by drtomc
console: send output back to the browser progressively. |
85 |
expiry.ping() |
86 |
ln = self.lineQ.get() |
|
87 |
if 'interrupt' in ln: |
|
88 |
raise Interrupt() |
|
89 |
||
90 |
def flush(self): |
|
599
by drtomc
console: improve end of line handling. |
91 |
if len(self.remainder) > 0: |
92 |
self.cmdQ.put({"output":self.remainder}) |
|
93 |
expiry.ping() |
|
94 |
ln = self.lineQ.get() |
|
95 |
self.remainder = '' |
|
96 |
if 'interrupt' in ln: |
|
97 |
raise Interrupt() |
|
598
by drtomc
console: send output back to the browser progressively. |
98 |
|
750
by dcoles
Console: Flush current output before requesting input from Web |
99 |
class WebIO(object): |
100 |
"""Provides a file like interface to the Web front end of the console.
|
|
101 |
You may print text to the console using write(), flush any buffered output
|
|
102 |
using flush(), or request text from the console using readline()"""
|
|
103 |
||
104 |
def __init__(self, cmdQ, lineQ): |
|
105 |
self.cmdQ = cmdQ |
|
106 |
self.lineQ = lineQ |
|
107 |
self.stdin = StdinFromWeb(self.cmdQ, self.lineQ) |
|
108 |
self.stdout = StdoutToWeb(self.cmdQ, self.lineQ) |
|
109 |
||
110 |
def write(self, stuff): |
|
111 |
self.stdout.write(stuff) |
|
112 |
||
113 |
def flush(self): |
|
114 |
self.stdout.flush() |
|
115 |
||
116 |
def readline(self): |
|
117 |
self.stdout.flush() |
|
118 |
return self.stdin.readline() |
|
119 |
||
418
by mattgiuca
Renamed trunk/console to trunk/scripts. We are now able to put more scripts in |
120 |
class PythonRunner(Thread): |
121 |
def __init__(self, cmdQ, lineQ): |
|
122 |
self.cmdQ = cmdQ |
|
123 |
self.lineQ = lineQ |
|
750
by dcoles
Console: Flush current output before requesting input from Web |
124 |
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 |
125 |
Thread.__init__(self) |
126 |
||
127 |
def execCmd(self, cmd): |
|
128 |
try: |
|
750
by dcoles
Console: Flush current output before requesting input from Web |
129 |
sys.stdin = self.webio |
130 |
sys.stdout = self.webio |
|
131 |
sys.stderr = self.webio |
|
691
by mattgiuca
python-console: Removed "locls" dictionary when calling exec. |
132 |
res = eval(cmd, self.globs) |
750
by dcoles
Console: Flush current output before requesting input from Web |
133 |
self.webio.flush() |
599
by drtomc
console: improve end of line handling. |
134 |
self.cmdQ.put({"okay":res}) |
418
by mattgiuca
Renamed trunk/console to trunk/scripts. We are now able to put more scripts in |
135 |
self.curr_cmd = '' |
136 |
except Exception, exc: |
|
750
by dcoles
Console: Flush current output before requesting input from Web |
137 |
self.webio.flush() |
738
by dcoles
Console: Prints the Exception type as well as the message when code that |
138 |
exc_classname = exc.__class__.__name__ |
741
by dcoles
Console: Use __str__ of exception rather than message. Some exceptions don't |
139 |
self.cmdQ.put({"exc": exc_classname + ": " + str(exc)}) |
418
by mattgiuca
Renamed trunk/console to trunk/scripts. We are now able to put more scripts in |
140 |
self.curr_cmd = '' |
141 |
||
142 |
def run(self): |
|
598
by drtomc
console: send output back to the browser progressively. |
143 |
self.globs = {} |
144 |
self.globs['__builtins__'] = globals()['__builtins__'] |
|
145 |
self.curr_cmd = '' |
|
418
by mattgiuca
Renamed trunk/console to trunk/scripts. We are now able to put more scripts in |
146 |
compiler = codeop.CommandCompiler() |
147 |
||
148 |
while True: |
|
149 |
ln = self.lineQ.get() |
|
150 |
if 'chat' in ln: |
|
151 |
if self.curr_cmd == '': |
|
152 |
self.curr_cmd = ln['chat'] |
|
153 |
else: |
|
154 |
self.curr_cmd = self.curr_cmd + '\n' + ln['chat'] |
|
155 |
try: |
|
156 |
cmd = compiler(self.curr_cmd) |
|
157 |
if cmd is None: |
|
158 |
# The command was incomplete,
|
|
159 |
# so send back a None, so the
|
|
160 |
# client can print a '...'
|
|
161 |
self.cmdQ.put({"more":None}) |
|
162 |
else: |
|
163 |
self.execCmd(cmd) |
|
164 |
except Exception, exc: |
|
750
by dcoles
Console: Flush current output before requesting input from Web |
165 |
self.webio.flush() |
599
by drtomc
console: improve end of line handling. |
166 |
self.cmdQ.put({"exc":str(exc)}) |
418
by mattgiuca
Renamed trunk/console to trunk/scripts. We are now able to put more scripts in |
167 |
self.curr_cmd = '' |
168 |
if 'block' in ln: |
|
169 |
# throw away a partial command.
|
|
170 |
try: |
|
171 |
cmd = compile(ln['block'], "<web session>", 'exec'); |
|
172 |
self.execCmd(cmd) |
|
173 |
except Exception, exc: |
|
750
by dcoles
Console: Flush current output before requesting input from Web |
174 |
self.webio.flush() |
599
by drtomc
console: improve end of line handling. |
175 |
self.cmdQ.put({"exc":str(exc)}) |
418
by mattgiuca
Renamed trunk/console to trunk/scripts. We are now able to put more scripts in |
176 |
self.curr_cmd = '' |
177 |
||
178 |
def daemonize(): |
|
179 |
if os.fork(): # launch child and... |
|
180 |
os._exit(0) # kill off parent |
|
181 |
os.setsid() |
|
182 |
if os.fork(): # launch child and... |
|
183 |
os._exit(0) # kill off parent again. |
|
184 |
os.umask(077) |
|
185 |
||
186 |
# The global 'magic' is the secret that the client and server share
|
|
187 |
# which is used to create and md5 digest to authenticate requests.
|
|
188 |
# It is assigned a real value at startup.
|
|
189 |
magic = '' |
|
190 |
||
191 |
cmdQ = Queue.Queue() |
|
192 |
lineQ = Queue.Queue() |
|
193 |
interpThread = PythonRunner(cmdQ, lineQ) |
|
194 |
||
522
by drtomc
Add quite a lot of stuff to get usrmgt happening. |
195 |
# Default expiry time of 15 minutes
|
196 |
expiry = ExpiryTimer(15 * 60) |
|
197 |
||
432
by drtomc
usrmgt: more work on this. Still some work to go. |
198 |
def initializer(): |
199 |
interpThread.setDaemon(True) |
|
200 |
interpThread.start() |
|
522
by drtomc
Add quite a lot of stuff to get usrmgt happening. |
201 |
expiry.ping() |
432
by drtomc
usrmgt: more work on this. Still some work to go. |
202 |
|
203 |
def dispatch_msg(msg): |
|
522
by drtomc
Add quite a lot of stuff to get usrmgt happening. |
204 |
expiry.ping() |
432
by drtomc
usrmgt: more work on this. Still some work to go. |
205 |
lineQ.put({msg['cmd']:msg['text']}) |
206 |
return cmdQ.get() |
|
207 |
||
418
by mattgiuca
Renamed trunk/console to trunk/scripts. We are now able to put more scripts in |
208 |
if __name__ == "__main__": |
209 |
port = int(sys.argv[1]) |
|
210 |
magic = sys.argv[2] |
|
603
by mattgiuca
Console now starts up in the user's home directory. |
211 |
if len(sys.argv) >= 4: |
212 |
# working_dir
|
|
213 |
os.chdir(sys.argv[3]) |
|
749
by dcoles
Console: Override the sys.path on the console process so it's search path follows the cwd of the console (like how the commandline console works in interactive mode). This means you can now import modules in the $HOME directory. Ideally this should be the cwd of the browser (or just $HOME if it's a stand alone console), but at present there is no facility to set the cwd of the console when initilized. (So, we'll just stick with standalone mode for the moment) |
214 |
# Make python's search path follow the cwd
|
215 |
sys.path[0] = '' |
|
662
by drtomc
console: set the environment variable HOME so matplotlib works in the console. |
216 |
os.environ['HOME'] = sys.argv[3] |
418
by mattgiuca
Renamed trunk/console to trunk/scripts. We are now able to put more scripts in |
217 |
|
432
by drtomc
usrmgt: more work on this. Still some work to go. |
218 |
common.chat.start_server(port, magic, True, dispatch_msg, initializer) |