1 from twisted
.web
import resource
, server
, static
2 from twisted
.names
import client
3 from twisted
.internet
import reactor
, error
4 from authgateengine
import login_optional
, getSessionData
5 import simplejson
, md5
, sys
, os
, time
, config
, weakref
, traceback
6 import qwebirc
.ircclient
as ircclient
7 from adminengine
import AdminEngineAction
8 from qwebirc
.util
import HitCounter
13 return md5
.md5(os
.urandom(16)).hexdigest()
15 class BufferOverflowException(Exception):
18 class AJAXException(Exception):
21 class IDGenerationException(Exception):
27 def decorator(*args
, **kwargs
):
29 x
= fn(*args
, **kwargs
)
31 return server
.NOT_DONE_YET
33 except AJAXException
, e
:
36 return simplejson
.dumps(x
)
39 def cleanupSession(id):
46 def __init__(self
, id):
48 self
.subscriptions
= []
53 self
.cleanupschedule
= None
55 def subscribe(self
, channel
, notifier
):
56 timeout_entry
= reactor
.callLater(config
.HTTP_AJAX_REQUEST_TIMEOUT
, self
.timeout
, channel
)
57 def cancel_timeout(result
):
58 if channel
in self
.subscriptions
:
59 self
.subscriptions
.remove(channel
)
61 timeout_entry
.cancel()
62 except error
.AlreadyCalled
:
64 notifier
.addCallbacks(cancel_timeout
, cancel_timeout
)
66 if len(self
.subscriptions
) >= config
.MAXSUBSCRIPTIONS
:
67 self
.subscriptions
.pop(0).close()
69 self
.subscriptions
.append(channel
)
72 def timeout(self
, channel
):
76 channel
.write(simplejson
.dumps([]))
77 if channel
in self
.subscriptions
:
78 self
.subscriptions
.remove(channel
)
80 def flush(self
, scheduled
=False):
84 if not self
.buffer or not self
.subscriptions
:
91 self
.schedule
= reactor
.callLater(self
.throttle
- t
, self
.flush
, True)
94 # process the rest of the packet
97 self
.schedule
= reactor
.callLater(0, self
.flush
, True)
100 self
.throttle
= t
+ config
.UPDATE_FREQ
102 encdata
= simplejson
.dumps(self
.buffer)
106 for x
in self
.subscriptions
:
110 self
.subscriptions
= newsubs
111 if self
.closed
and not self
.subscriptions
:
112 cleanupSession(self
.id)
114 def event(self
, data
):
115 bufferlen
= sum(map(len, self
.buffer))
116 if bufferlen
+ len(data
) > config
.MAXBUFLEN
:
118 self
.client
.error("Buffer overflow")
121 self
.buffer.append(data
)
124 def push(self
, data
):
126 self
.client
.write(data
)
128 def disconnect(self
):
129 # keep the session hanging around for a few seconds so the
130 # client has a chance to see what the issue was
133 reactor
.callLater(5, cleanupSession
, self
.id)
136 def __init__(self
, request
):
137 self
.request
= request
139 class SingleUseChannel(Channel
):
140 def write(self
, data
):
141 self
.request
.write(data
)
142 self
.request
.finish()
146 self
.request
.finish()
148 class MultipleUseChannel(Channel
):
149 def write(self
, data
):
150 self
.request
.write(data
)
153 class AJAXEngine(resource
.Resource
):
156 def __init__(self
, prefix
):
158 self
.__connect
_hit
= HitCounter()
159 self
.__total
_hit
= HitCounter()
162 def render_POST(self
, request
):
163 path
= request
.path
[len(self
.prefix
):]
165 handler
= self
.COMMANDS
.get(path
[1:])
166 if handler
is not None:
167 return handler(self
, request
)
168 raise AJAXException("404")
170 # def render_GET(self, request):
171 # return self.render_POST(request)
173 def newConnection(self
, request
):
174 ticket
= login_optional(request
)
176 _
, ip
, port
= request
.transport
.getPeer()
178 nick
= request
.args
.get("nick")
180 raise AJAXException("Nickname not supplied")
181 nick
= ircclient
.irc_decode(nick
[0])
183 ident
, realname
= "webchat", config
.REALNAME
186 id = get_session_id()
187 if not Sessions
.get(id):
190 raise IDGenerationException()
192 session
= IRCSession(id)
194 qticket
= getSessionData(request
).get("qticket")
198 perform
= ["PRIVMSG %s :TICKETAUTH %s" % (config
.QBOT
, qticket
)]
201 client
= ircclient
.createIRC(session
, nick
=nick
, ident
=ident
, ip
=ip
, realname
=realname
, perform
=perform
)
202 session
.client
= client
204 Sessions
[id] = session
208 def getSession(self
, request
):
209 sessionid
= request
.args
.get("s")
210 if sessionid
is None:
211 raise AJAXException("Bad session ID")
213 session
= Sessions
.get(sessionid
[0])
215 raise AJAXException("Bad session ID")
218 def subscribe(self
, request
):
219 request
.channel
._savedTimeOut
= None # HACK
220 self
.getSession(request
).subscribe(SingleUseChannel(request
), request
.notifyFinish())
223 def push(self
, request
):
224 command
= request
.args
.get("c")
226 raise AJAXException("No command specified")
229 decoded
= ircclient
.irc_decode(command
[0])
231 session
= self
.getSession(request
)
233 if len(decoded
) > config
.MAXLINELEN
:
235 raise AJAXException("Line too long")
238 session
.push(decoded
)
239 except AttributeError: # occurs when we haven't noticed an error
241 raise AJAXException("Connection closed by server.")
242 except Exception, e
: # catch all
244 traceback
.print_exc(file=sys
.stderr
)
245 raise AJAXException("Unknown error.")
249 def closeById(self
, k
):
253 s
.client
.client
.error("Closed by admin interface")
256 def adminEngine(self
):
258 "Sessions": [(str(v
.client
.client
), AdminEngineAction("close", self
.closeById
, k
)) for k
, v
in Sessions
.iteritems() if not v
.closed
],
259 "Connections": [(self
.__connect
_hit
,)],
260 "Total hits": [(self
.__total
_hit
,)],
263 COMMANDS
= dict(p
=push
, n
=newConnection
, s
=subscribe
)