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