comparison src/sat.tac @ 330:608a4a2ba94e

Core: created a new core module where xmpp classes are put
author Goffi <goffi@goffi.org>
date Mon, 23 May 2011 21:18:58 +0200
parents b069055320b1
children 0a8eb0461f31
comparison
equal deleted inserted replaced
329:be9f682c53a5 330:608a4a2ba94e
46 from logging import debug, info, error 46 from logging import debug, info, error
47 47
48 import signal, sys 48 import signal, sys
49 import os.path 49 import os.path
50 50
51 from sat.core.xmpp import SatXMPPClient, SatMessageProtocol, SatRosterProtocol, SatPresenceProtocol, SatDiscoProtocol, SatFallbackHandler, RegisteringAuthenticator, SatVersionHandler
51 from sat.tools.memory import Memory 52 from sat.tools.memory import Memory
52 from sat.tools.xml_tools import tupleList2dataForm 53 from sat.tools.xml_tools import tupleList2dataForm
53 from sat.tools.misc import TriggerManager 54 from sat.tools.misc import TriggerManager
54 from glob import glob 55 from glob import glob
55 56
69 70
70 def sat_next_id(): 71 def sat_next_id():
71 global sat_id 72 global sat_id
72 sat_id+=1 73 sat_id+=1
73 return "sat_id_"+str(sat_id) 74 return "sat_id_"+str(sat_id)
74
75 class SatXMPPClient(client.XMPPClient):
76
77 def __init__(self, host_app, profile, user_jid, password, host=None, port=5222):
78 client.XMPPClient.__init__(self, user_jid, password, host, port)
79 self.factory.clientConnectionLost = self.connectionLost
80 self.__connected=False
81 self.profile = profile
82 self.host_app = host_app
83 self.client_initialized = defer.Deferred()
84
85 def _authd(self, xmlstream):
86 if not self.host_app.trigger.point("XML Initialized", xmlstream, self.profile):
87 return
88 client.XMPPClient._authd(self, xmlstream)
89 self.__connected=True
90 info (_("********** [%s] CONNECTED **********") % self.profile)
91 self.streamInitialized()
92 self.host_app.bridge.connected(self.profile) #we send the signal to the clients
93
94
95 def streamInitialized(self):
96 """Called after _authd"""
97 debug (_("XML stream is initialized"))
98 self.keep_alife = task.LoopingCall(self.xmlstream.send, " ") #Needed to avoid disconnection (specially with openfire)
99 self.keep_alife.start(180)
100
101 self.disco = SatDiscoProtocol(self)
102 self.disco.setHandlerParent(self)
103 self.discoHandler = disco.DiscoHandler()
104 self.discoHandler.setHandlerParent(self)
105
106 if not self.host_app.trigger.point("Disco Handled", self.profile):
107 return
108
109 self.roster.requestRoster()
110
111 self.presence.available()
112
113 self.disco.requestInfo(jid.JID(self.host_app.memory.getParamA("Server", "Connection", profile_key=self.profile))).addCallback(self.host_app.serverDisco, self.profile) #FIXME: use these informations
114 self.disco.requestItems(jid.JID(self.host_app.memory.getParamA("Server", "Connection", profile_key=self.profile))).addCallback(self.host_app.serverDiscoItems, self.disco, self.profile, self.client_initialized)
115
116 def initializationFailed(self, reason):
117 print ("initializationFailed: %s" % reason)
118 self.host_app.bridge.connectionError("AUTH_ERROR", self.profile)
119 try:
120 client.XMPPClient.initializationFailed(self, reason)
121 except:
122 #we already send an error signal, no need to raise an exception
123 pass
124
125 def isConnected(self):
126 return self.__connected
127
128 def connectionLost(self, connector, unused_reason):
129 self.__connected=False
130 info (_("********** [%s] DISCONNECTED **********") % self.profile)
131 try:
132 self.keep_alife.stop()
133 except AttributeError:
134 debug (_("No keep_alife"))
135 self.host_app.bridge.disconnected(self.profile) #we send the signal to the clients
136
137
138 class SatMessageProtocol(xmppim.MessageProtocol):
139
140 def __init__(self, host):
141 xmppim.MessageProtocol.__init__(self)
142 self.host = host
143
144 def onMessage(self, message):
145 debug (_(u"got message from: %s"), message["from"])
146 if not self.host.trigger.point("MessageReceived",message, profile=self.parent.profile):
147 return
148 for e in message.elements():
149 if e.name == "body":
150 mess_type = message['type'] if message.hasAttribute('type') else 'normal'
151 self.host.bridge.newMessage(message["from"], e.children[0], mess_type, message['to'], profile=self.parent.profile)
152 self.host.memory.addToHistory(self.parent.jid, jid.JID(message["from"]), self.parent.jid, "chat", e.children[0])
153 break
154
155 class SatRosterProtocol(xmppim.RosterClientProtocol):
156
157 def __init__(self, host):
158 xmppim.RosterClientProtocol.__init__(self)
159 self.host = host
160 self._groups=set()
161
162 def rosterCb(self, roster):
163 for raw_jid, item in roster.iteritems():
164 self.onRosterSet(item)
165
166 def requestRoster(self):
167 """ ask the server for Roster list """
168 debug("requestRoster")
169 self.getRoster().addCallback(self.rosterCb)
170
171 def removeItem(self, to):
172 """Remove a contact from roster list"""
173 xmppim.RosterClientProtocol.removeItem(self, to)
174 #TODO: check IQ result
175
176 #XXX: disabled (cf http://wokkel.ik.nu/ticket/56))
177 #def addItem(self, to):
178 #"""Add a contact to roster list"""
179 #xmppim.RosterClientProtocol.addItem(self, to)
180 #TODO: check IQ result"""
181
182 def onRosterSet(self, item):
183 """Called when a new/update roster item is received"""
184 #TODO: send a signal to frontends
185 item_attr = {'to': str(item.subscriptionTo),
186 'from': str(item.subscriptionFrom),
187 'ask': str(item.ask)
188 }
189 if item.name:
190 item_attr['name'] = item.name
191 info (_("new contact in roster list: %s"), item.jid.full())
192 self.host.memory.addContact(item.jid, item_attr, item.groups, self.parent.profile)
193 self.host.bridge.newContact(item.jid.full(), item_attr, item.groups, self.parent.profile)
194 self._groups.update(item.groups)
195
196 def onRosterRemove(self, entity):
197 """Called when a roster removal event is received"""
198 #TODO: send a signal to frontends
199 print _("removing %s from roster list") % entity.full()
200 self.host.memory.delContact(entity, self.parent.profile)
201
202 def getGroups(self):
203 """Return a set of groups"""
204 return self._groups
205
206 class SatPresenceProtocol(xmppim.PresenceClientProtocol):
207
208 def __init__(self, host):
209 xmppim.PresenceClientProtocol.__init__(self)
210 self.host = host
211
212 def availableReceived(self, entity, show=None, statuses=None, priority=0):
213 debug (_("presence update for [%(entity)s] (available, show=%(show)s statuses=%(statuses)s priority=%(priority)d)") % {'entity':entity, 'show':show, 'statuses':statuses, 'priority':priority})
214
215 if statuses.has_key(None): #we only want string keys
216 statuses["default"] = statuses[None]
217 del statuses[None]
218
219 self.host.memory.addPresenceStatus(entity, show or "",
220 int(priority), statuses, self.parent.profile)
221
222 #now it's time to notify frontends
223 self.host.bridge.presenceUpdate(entity.full(), show or "",
224 int(priority), statuses, self.parent.profile)
225
226 def unavailableReceived(self, entity, statuses=None):
227 debug (_("presence update for [%(entity)s] (unavailable, statuses=%(statuses)s)") % {'entity':entity, 'statuses':statuses})
228 if statuses and statuses.has_key(None): #we only want string keys
229 statuses["default"] = statuses[None]
230 del statuses[None]
231 self.host.memory.addPresenceStatus(entity, "unavailable", 0, statuses, self.parent.profile)
232
233 #now it's time to notify frontends
234 self.host.bridge.presenceUpdate(entity.full(), "unavailable", 0, statuses, self.parent.profile)
235
236
237 def available(self, entity=None, show=None, statuses=None, priority=0):
238 if statuses and statuses.has_key('default'):
239 statuses[None] = statuses['default']
240 del statuses['default']
241 xmppim.PresenceClientProtocol.available(self, entity, show, statuses, priority)
242
243 def subscribedReceived(self, entity):
244 debug (_("subscription approved for [%s]") % entity.userhost())
245 self.host.memory.delWaitingSub(entity.userhost(), self.parent.profile)
246 self.host.bridge.subscribe('subscribed', entity.userhost(), self.parent.profile)
247
248 def unsubscribedReceived(self, entity):
249 debug (_("unsubscription confirmed for [%s]") % entity.userhost())
250 self.host.memory.delWaitingSub(entity.userhost(), self.parent.profile)
251 self.host.bridge.subscribe('unsubscribed', entity.userhost(), self.parent.profile)
252
253 def subscribeReceived(self, entity):
254 debug (_("subscription request for [%s]") % entity.userhost())
255 self.host.memory.addWaitingSub('subscribe', entity.userhost(), self.parent.profile)
256 self.host.bridge.subscribe('subscribe', entity.userhost(), self.parent.profile)
257
258 def unsubscribeReceived(self, entity):
259 debug (_("unsubscription asked for [%s]") % entity.userhost())
260 self.host.memory.addWaitingSub('unsubscribe', entity.userhost(), self.parent.profile)
261 self.host.bridge.subscribe('unsubscribe', entity.userhost(), self.parent.profile)
262
263 class SatDiscoProtocol(disco.DiscoClientProtocol):
264 def __init__(self, host):
265 disco.DiscoClientProtocol.__init__(self)
266
267 class SatFallbackHandler(generic.FallbackHandler):
268 def __init__(self, host):
269 generic.FallbackHandler.__init__(self)
270
271 def iqFallback(self, iq):
272 if iq.handled == True:
273 return
274 debug (u"iqFallback: xml = [%s]" % (iq.toXml()))
275 generic.FallbackHandler.iqFallback(self, iq)
276
277 class RegisteringAuthenticator(xmlstream.ConnectAuthenticator):
278
279 def __init__(self, host, jabber_host, user_login, user_pass, answer_id):
280 xmlstream.ConnectAuthenticator.__init__(self, jabber_host)
281 self.host = host
282 self.jabber_host = jabber_host
283 self.user_login = user_login
284 self.user_pass = user_pass
285 self.answer_id = answer_id
286 print _("Registration asked for"),user_login, user_pass, jabber_host
287
288 def connectionMade(self):
289 print "connectionMade"
290
291 self.xmlstream.namespace = "jabber:client"
292 self.xmlstream.sendHeader()
293
294 iq = compat.IQ(self.xmlstream, 'set')
295 iq["to"] = self.jabber_host
296 query = iq.addElement(('jabber:iq:register', 'query'))
297 _user = query.addElement('username')
298 _user.addContent(self.user_login)
299 _pass = query.addElement('password')
300 _pass.addContent(self.user_pass)
301 reg = iq.send(self.jabber_host).addCallbacks(self.registrationAnswer, self.registrationFailure)
302
303 def registrationAnswer(self, answer):
304 debug (_("registration answer: %s") % answer.toXml())
305 answer_type = "SUCCESS"
306 answer_data={"message":_("Registration successfull")}
307 self.host.bridge.actionResult(answer_type, self.answer_id, answer_data)
308 self.xmlstream.sendFooter()
309
310 def registrationFailure(self, failure):
311 info (_("Registration failure: %s") % str(failure.value))
312 answer_type = "ERROR"
313 answer_data = {}
314 if failure.value.condition == 'conflict':
315 answer_data['reason'] = 'conflict'
316 answer_data={"message":_("Username already exists, please choose an other one")}
317 else:
318 answer_data['reason'] = 'unknown'
319 answer_data={"message":_("Registration failed (%s)") % str(failure.value.condition)}
320 self.host.bridge.actionResult(answer_type, self.answer_id, answer_data)
321 self.xmlstream.sendFooter()
322
323 class SatVersionHandler(generic.VersionHandler):
324
325 def getDiscoInfo(self, requestor, target, node):
326 #XXX: We need to work around wokkel's behavious (namespace not added if there is a
327 # node) as it cause issues with XEP-0115 & PEP (XEP-0163): there is a node when server
328 # ask for disco info, and not when we generate the key, so the hash is used with different
329 # disco features, and when the server (seen on ejabberd) generate its own hash for security check
330 # it reject our features (resulting in e.g. no notification on PEP)
331 return generic.VersionHandler.getDiscoInfo(self, requestor, target, None)
332 75
333 class SAT(service.Service): 76 class SAT(service.Service):
334 77
335 def get_next_id(self): 78 def get_next_id(self):
336 return sat_next_id() 79 return sat_next_id()