]> jfr.im git - irc/quakenet/qwebirc.git/blame - qwebirc/ajaxengine.py
Add connection dialog.
[irc/quakenet/qwebirc.git] / qwebirc / ajaxengine.py
CommitLineData
9e769c12
CP
1from twisted.web import resource, server, static
2from twisted.names import client
3from twisted.internet import reactor
8dc46dfa
CP
4import traceback
5import simplejson, md5, sys, os, ircclient, time, config, weakref
9e769c12
CP
6
7Sessions = {}
8
9def get_session_id():
8dc46dfa
CP
10 return md5.md5(os.urandom(16)).hexdigest()[:10]
11
12class BufferOverflowException(Exception):
13 pass
14
9e769c12
CP
15def jsondump(fn):
16 def decorator(*args, **kwargs):
17 x = fn(*args, **kwargs)
18 if isinstance(x, list):
19 return simplejson.dumps(x)
20 return x
21 return decorator
22
8dc46dfa
CP
23def cleanupSession(id):
24 try:
25 del Sessions[id]
26 except KeyError:
27 pass
28
9e769c12
CP
29class IRCSession:
30 def __init__(self, id):
31 self.id = id
32 self.subscriptions = []
33 self.buffer = []
34 self.throttle = 0
35 self.schedule = None
8dc46dfa
CP
36 self.closed = False
37 self.cleanupschedule = None
38
9e769c12
CP
39 def subscribe(self, channel):
40 self.subscriptions.append(channel)
41 self.flush()
42
43 def flush(self, scheduled=False):
44 if scheduled:
45 self.schedule = None
46
47 if not self.buffer or not self.subscriptions:
48 return
49
50 t = time.time()
51
52 if t < self.throttle:
53 if not self.schedule:
54 self.schedule = reactor.callLater(self.throttle - t, self.flush, True)
55 return
56 else:
57 # process the rest of the packet
58 if not scheduled:
59 if not self.schedule:
60 self.schedule = reactor.callLater(0, self.flush, True)
61 return
62
63 self.throttle = t + config.UPDATE_FREQ
64
65 encdata = simplejson.dumps(self.buffer)
66 self.buffer = []
67
68 newsubs = []
69 for x in self.subscriptions:
70 if x.write(encdata):
71 newsubs.append(x)
72
73 self.subscriptions = newsubs
8dc46dfa
CP
74 if self.closed and not self.subscriptions:
75 cleanupSession(self.id)
76
9e769c12 77 def event(self, data):
8dc46dfa
CP
78 bufferlen = sum(map(len, self.buffer))
79 if bufferlen + len(data) > config.MAXBUFLEN:
80 self.buffer = []
81 self.client.error("Buffer overflow")
82 return
83
9e769c12
CP
84 self.buffer.append(data)
85 self.flush()
86
87 def push(self, data):
8dc46dfa
CP
88 if not self.closed:
89 self.client.write(data)
90
91 def disconnect(self):
92 # keep the session hanging around for a few seconds so the
93 # client has a chance to see what the issue was
94 self.closed = True
95
96 reactor.callLater(5, cleanupSession, self.id)
97
9e769c12
CP
98class Channel:
99 def __init__(self, request):
100 self.request = request
101
102class SingleUseChannel(Channel):
103 def write(self, data):
104 self.request.write(data)
105 self.request.finish()
106 return False
107
108class MultipleUseChannel(Channel):
109 def write(self, data):
110 self.request.write(data)
111 return True
112
113class AJAXEngine(resource.Resource):
114 isLeaf = True
115
116 def __init__(self, prefix):
117 self.prefix = prefix
118
119 @jsondump
57ea572e 120 def render_POST(self, request):
9e769c12
CP
121 path = request.path[len(self.prefix):]
122 if path == "/n":
123 ip = request.transport.getPeer()
124 ip = ip[1]
125
126 nick, ident = request.args.get("nick"), "webchat"
127 if not nick:
128 return [False, "Nickname not supplied"]
129
130 nick = nick[0]
131
132 id = get_session_id()
133
134 session = IRCSession(id)
135
136 client = ircclient.createIRC(session, nick=nick, ident=ident, ip=ip, realname=nick)
137 session.client = client
138
139 Sessions[id] = session
140
57ea572e
CP
141 return [True, id]
142 return [False, "404"]
143
144 @jsondump
145 def render_GET(self, request):
146 path = request.path[len(self.prefix):]
9e769c12
CP
147 if path.startswith("/s/"):
148 sessionid = path[3:]
149 session = Sessions.get(sessionid)
150
151 if not session:
152 return [False, "Bad session ID"]
153
154 session.subscribe(SingleUseChannel(request))
155 return server.NOT_DONE_YET
156 if path.startswith("/p/"):
157 command = request.args.get("c")
158 if not command:
159 return [False, "No command specified"]
160
161 command = command[0]
162
163 sessionid = path[3:]
164 session = Sessions.get(sessionid)
165 if not session:
166 return [False, "Bad session ID"]
167
168 try:
169 decoded = command.decode("utf-8")
170 except UnicodeDecodeError:
171 decoded = command.decode("iso-8859-1", "ignore")
8dc46dfa
CP
172
173 try:
174 session.push(decoded)
175 except AttributeError: # occurs when we haven't noticed an error
176 session.disconnect()
177 return [False, "Connection closed by server."]
178 except Exception, e: # catch all
179 session.disconnect()
180 traceback.print_exc(file=sys.stderr)
181 return [False, "Unknown error."]
182
9e769c12
CP
183 return [True]
184
185 return [False, "404"]