Mercurial > libervia-backend
view sat.tac @ 18:6928e3cb73a8
refactoring: using xml params part II
author | Goffi <goffi@goffi.org> |
---|---|
date | Fri, 06 Nov 2009 23:31:00 +0100 |
parents | 74a39f40eb6d |
children | bb72c29f3432 |
line wrap: on
line source
#!/usr/bin/python # -*- coding: utf-8 -*- """ SAT: a jabber client Copyright (C) 2009 Jérôme Poisson (goffi@goffi.org) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. """ client_name = u'SàT (Salut à toi)' client_version = '0.0.1D' #Please add 'D' at the end for dev versions from twisted.application import internet, service from twisted.internet import glib2reactor, protocol, task glib2reactor.install() from twisted.words.protocols.jabber import jid, xmlstream, error from twisted.words.xish import domish from twisted.internet import reactor import pdb from wokkel import client, disco, xmppim, generic, compat from sat_bridge.DBus import DBusBridge import logging from logging import debug, info, error import signal, sys import os.path from tools.memory import Memory from glob import glob try: from twisted.words.protocols.xmlstream import XMPPHandler except ImportError: from wokkel.subprotocols import XMPPHandler ### logging configuration FIXME: put this elsewhere ### logging.basicConfig(level=logging.DEBUG, format='%(message)s') ### sat_id = 0 def sat_next_id(): global sat_id sat_id+=1 return "sat_id_"+str(sat_id) class SatXMPPClient(client.XMPPClient): def __init__(self, jid, password, host=None, port=5222): client.XMPPClient.__init__(self, jid, password, host, port) self.factory.clientConnectionLost = self.connectionLost self.__connected=False def _authd(self, xmlstream): print "SatXMPPClient" client.XMPPClient._authd(self, xmlstream) self.__connected=True print "********** CONNECTED **********" self.streamInitialized() def streamInitialized(self): """Called after _authd""" self.keep_alife = task.LoopingCall(self.xmlstream.send, " ") #Needed to avoid disconnection (specially with openfire) self.keep_alife.start(180) def isConnected(self): return self.__connected def connectionLost(self, connector, unused_reason): self.__connected=False print "********** DISCONNECTED **********" try: self.keep_alife.stop() except AttributeError: debug("No keep_alife") class SatMessageProtocol(xmppim.MessageProtocol): def __init__(self, host): xmppim.MessageProtocol.__init__(self) self.host = host def onMessage(self, message): debug (u"got_message from: %s", message["from"]) for e in message.elements(): if e.name == "body": self.host.bridge.newMessage(message["from"], e.children[0]) self.host.memory.addToHistory(self.host.me, jid.JID(message["from"]), self.host.me, "chat", e.children[0]) break class SatRosterProtocol(xmppim.RosterClientProtocol): def __init__(self, host): xmppim.RosterClientProtocol.__init__(self) self.host = host def rosterCb(self, roster): for jid, item in roster.iteritems(): info ("new contact in roster list: %s", jid) #FIXME: fill attributes self.host.memory.addContact(jid, {}, item.groups) self.host.bridge.newContact(jid, {}, item.groups) def requestRoster(self): """ ask the server for Roster list """ debug("requestRoster") self.getRoster().addCallback(self.rosterCb) def removeItem(self, to): """Remove a contact from roster list""" to_jid=jid.JID(to) xmppim.RosterClientProtocol.removeItem(self, to_jid) #TODO: check IQ result def addItem(self, to): """Add a contact to roster list""" to_jid=jid.JID(to) xmppim.RosterClientProtocol.addItem(self, to_jid) #TODO: check IQ result class SatPresenceProtocol(xmppim.PresenceClientProtocol): def __init__(self, host): xmppim.PresenceClientProtocol.__init__(self) self.host = host def availableReceived(self, entity, show=None, statuses=None, priority=0): info ("presence update for [%s]", entity) ### we check if the status is not about subscription ### #FIXME: type is not needed anymore #TODO: management of differents statuses (differents languages) status = statuses.values()[0] if len(statuses) else "" self.host.memory.addPresenceStatus(entity.full(), "", show or "", status or "", int(priority)) #now it's time to notify frontends self.host.bridge.presenceUpdate(entity.full(), "", show or "", status or "", int(priority)) def unavailableReceived(self, entity, statuses=None): #TODO: management of differents statuses (differents languages) status = statuses.values()[0] if len(statuses) else "" self.host.memory.addPresenceStatus(entity.full(), "unavailable", "", status or "", 0) #now it's time to notify frontends self.host.bridge.presenceUpdate(entity.full(), "unavailable", "", status or "", 0) def subscribedReceived(self, entity): debug ("subscription approved for [%s]" % entity) def unsubscribedReceived(self, entity): debug ("unsubscription confirmed for [%s]" % entity) def subscribeReceived(self, entity): #FIXME: auto answer for subscribe request, must be checked ! debug ("subscription request for [%s]" % entity) self.subscribed(entity) def unsubscribeReceived(self, entity): debug ("unsubscription asked for [%s]" % entity) class SatDiscoProtocol(disco.DiscoClientProtocol): def __init__(self, host): disco.DiscoClientProtocol.__init__(self) class SatFallbackHandler(generic.FallbackHandler): def __init__(self, host): generic.FallbackHandler.__init__(self) def iqFallback(self, iq): #pdb.set_trace() print "iqFallback: xml = [%s], handled=%s" % (iq.toXml(), "True" if iq.handled else "False") generic.FallbackHandler.iqFallback(self, iq) class RegisteringAuthenticator(xmlstream.ConnectAuthenticator): def __init__(self, host, jabber_host, user_login, user_pass, answer_id): xmlstream.ConnectAuthenticator.__init__(self, jabber_host) self.host = host self.jabber_host = jabber_host self.user_login = user_login self.user_pass = user_pass self.answer_id = answer_id def connectionMade(self): print "connectionMade" self.xmlstream.namespace = "jabber:client" self.xmlstream.sendHeader() iq = compat.IQ(self.xmlstream, 'set') iq["to"] = self.jabber_host query = iq.addElement(('jabber:iq:register', 'query')) _user = query.addElement('username') _user.addContent(self.user_login) _pass = query.addElement('password') _pass.addContent(self.user_pass) reg = iq.send(self.jabber_host).addCallbacks(self.registrationAnswer, self.registrationFailure) def registrationAnswer(self, answer): debug ("registration answer: %s" % answer.toXml()) answer_type = "success" answer_data={"human":"Registration successfull"} self.host.bridge.sendAnswer(answer_type, self.answer_id, answer_data) self.xmlstream.sendFooter() def registrationFailure(self, error): info ("Registration failure: %s" %str(error)) answer_type = "error" answer_data={"human":"Registration failed"} if error.value.condition == 'conflict': answer_data['reason'] = 'conflict' else: answer_data['reason'] = 'unknown' self.host.bridge.sendAnswer(answer_type, self.answer_id, answer_data) self.xmlstream.sendFooter() class SAT(service.Service): def __init__(self): #self.reactor=reactor self.memory=Memory() self.server_features=[] #XXX: temp dic, need to be transfered into self.memory in the future self._waiting_conf = {} #callback called when a confirmation is received self._progress_cb_map = {} #callback called when a progress is requested (key = progress id) self.plugins = {} self.bridge=DBusBridge() self.bridge.register("registerNewAccount", self.registerNewAccount) self.bridge.register("connect", self.connect) self.bridge.register("disconnect", self.disconnect) self.bridge.register("getContacts", self.memory.getContacts) self.bridge.register("getPresenceStatus", self.memory.getPresenceStatus) self.bridge.register("sendMessage", self.sendMessage) self.bridge.register("setParam", self.setParam) self.bridge.register("getParamV", self.memory.getParamV) self.bridge.register("getParams", self.memory.getParams) self.bridge.register("getParamsForCategory", self.memory.getParamsForCategory) self.bridge.register("getParamsCategories", self.memory.getParamsCategories) self.bridge.register("getHistory", self.memory.getHistory) self.bridge.register("setPresence", self.setPresence) self.bridge.register("addContact", self.addContact) self.bridge.register("delContact", self.delContact) self.bridge.register("isConnected", self.isConnected) self.bridge.register("confirmationAnswer", self.confirmationAnswer) self.bridge.register("getProgress", self.getProgress) self._import_plugins() def _import_plugins(self): """Import all plugins found in plugins directory""" #TODO: manage dependencies plug_lst = [os.path.splitext(plugin)[0] for plugin in map(os.path.basename,glob ("plugins/plugin*.py"))] for plug in plug_lst: plug_path = 'plugins.'+plug __import__(plug_path) mod = sys.modules[plug_path] plug_info = mod.PLUGIN_INFO info ("importing plugin: %s", plug_info['name']) self.plugins[plug_info['import_name']] = getattr(mod, plug_info['main'])(self) #TODO: test xmppclient presence and register handler parent def connect(self): """Connect to jabber server""" if (self.isConnected()): info("already connected !") return print "connecting..." self.me = jid.JID(self.memory.getParamV("JabberID", "Connection")) self.xmppclient = SatXMPPClient(self.me, self.memory.getParamV("Password", "Connection"), self.memory.getParamV("Server", "Connection"), 5222) self.xmppclient.streamInitialized = self.streamInitialized self.messageProt = SatMessageProtocol(self) self.messageProt.setHandlerParent(self.xmppclient) self.roster = SatRosterProtocol(self) self.roster.setHandlerParent(self.xmppclient) self.presence = SatPresenceProtocol(self) self.presence.setHandlerParent(self.xmppclient) self.fallBack = SatFallbackHandler(self) self.fallBack.setHandlerParent(self.xmppclient) self.versionHandler = generic.VersionHandler(unicode(client_name), client_version) self.versionHandler.setHandlerParent(self.xmppclient) debug ("setting plugins parents") for plugin in self.plugins.iteritems(): if isinstance(plugin[1], XMPPHandler): plugin[1].setHandlerParent(self.xmppclient) self.xmppclient.startService() def disconnect(self): """disconnect from jabber server""" if (not self.isConnected()): info("not connected !") return info("Disconnecting...") self.xmppclient.stopService() def startService(self): info("Salut à toi ô mon frère !") self.connect() def stopService(self): self.memory.save() info("Salut aussi à Rantanplan") def run(self): debug("running app") reactor.run() def stop(self): debug("stopping app") reactor.stop() def streamInitialized(self): """Called when xmlstream is OK""" SatXMPPClient.streamInitialized(self.xmppclient) debug ("XML stream is initialized") self.xmlstream = self.xmppclient.xmlstream self.me = self.xmppclient.jid #in case of the ressource has changed self.disco = SatDiscoProtocol(self) self.disco.setHandlerParent(self.xmppclient) self.discoHandler = disco.DiscoHandler() self.discoHandler.setHandlerParent(self.xmppclient) self.roster.requestRoster() self.presence.available() self.disco.requestInfo(jid.JID(self.memory.getParamV("Server", "Connection"))).addCallback(self.serverDisco) ## Misc methods ## def registerNewAccount(self, login, password, server, port = 5222): """Connect to a server and create a new account using in-band registration""" next_id = sat_next_id() #the id is used to send server's answer serverRegistrer = xmlstream.XmlStreamFactory(RegisteringAuthenticator(self, server, login, password, next_id)) connector = reactor.connectTCP(server, port, serverRegistrer) serverRegistrer.clientConnectionLost = lambda conn, reason: connector.disconnect() return next_id ## Client management ## def setParam(self, name, value, category): """set wanted paramater and notice observers""" info ("setting param: %s=%s in category %s", name, value, category) self.memory.setParam(name, value, category) self.bridge.paramUpdate(name, value, category) def failed(self,xmlstream): debug("failed: %s", xmlstream.getErrorMessage()) debug("failed: %s", dir(xmlstream)) def isConnected(self): try: if self.xmppclient.isConnected(): return True except AttributeError: #xmppclient not available pass return False ## jabber methods ## def sendMessage(self,to,msg,type='chat'): #FIXME: check validity of recipient debug("Sending jabber message to %s...", to) message = domish.Element(('jabber:client','message')) message["to"] = jid.JID(to).full() message["from"] = self.me.full() message["type"] = type message.addElement("body", "jabber:client", msg) self.xmlstream.send(message) self.memory.addToHistory(self.me, self.me, jid.JID(to), message["type"], unicode(msg)) self.bridge.newMessage(message['from'], unicode(msg), to=message['to']) #We send back the message, so all clients are aware of it def setPresence(self, to="", type="", show="", status="", priority=0): """Send our presence information""" if not type in ["", "unavailable", "subscribed", "subscribe", "unsubscribe", "unsubscribed", "prob", "error"]: error("Type error !") #TODO: throw an error return to_jid=jid.JID(to) #TODO: refactor subscription bridge API if type=="": self.presence.available(to_jid, show, status, priority) elif type=="subscribe": self.presence.subscribe(to_jid) elif type=="subscribed": self.presence.subscribed(to_jid) elif type=="unsubscribe": self.presence.unsubscribe(to_jid) elif type=="unsubscribed": self.presence.unsubscribed(to_jid) def addContact(self, to): """Add a contact in roster list""" to_jid=jid.JID(to) self.roster.addItem(to_jid.userhost()) self.setPresence(to_jid.userhost(), "subscribe") def delContact(self, to): """Remove contact from roster list""" to_jid=jid.JID(to) self.roster.removeItem(to_jid.userhost()) self.bridge.contactDeleted(to) ## callbacks ## def serverDisco(self, disco): """xep-0030 Discovery Protocol.""" for feature in disco.features: debug ("Feature found: %s",feature) self.server_features.append(feature) for cat, type in disco.identities: debug ("Identity found: [%s/%s] %s" % (cat, type, disco.identities[(cat,type)])) ## Generic HMI ## def askConfirmation(self, id, type, data, cb): """Add a confirmation callback""" if self._waiting_conf.has_key(id): error ("Attempt to register two callbacks for the same confirmation") else: self._waiting_conf[id] = cb self.bridge.askConfirmation(type, id, data) def confirmationAnswer(self, id, accepted, data): """Called by frontends to answer confirmation requests""" debug ("Received confirmation answer for id [%s]: %s", id, "accepted" if accepted else "refused") if not self._waiting_conf.has_key(id): error ("Received an unknown confirmation") else: cb = self._waiting_conf[id] del self._waiting_conf[id] cb(id, accepted, data) def registerProgressCB(self, id, CB): """Register a callback called when progress is requested for id""" self._progress_cb_map[id] = CB def removeProgressCB(self, id): """Remove a progress callback""" if not self._progress_cb_map.has_key(id): error ("Trying to remove an unknow progress callback") else: del self._progress_cb_map[id] def getProgress(self, id): """Return a dict with progress information data['position'] : current possition data['size'] : end_position """ data = {} try: self._progress_cb_map[id](data) except KeyError: pass #debug("Requested progress for unknown id") return data application = service.Application('SàT') service = SAT() service.setServiceParent(application)