libervia-backend/sat.tac

399 lines
15 KiB
Python

#!/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/>.
"""
from twisted.application import internet, service
from twisted.internet import glib2reactor, protocol, task
glib2reactor.install()
from twisted.words.protocols.jabber import client, jid, xmlstream, error
from twisted.words.xish import domish
from twisted.internet import reactor
import pdb
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
### logging configuration FIXME: put this elsewhere ###
logging.basicConfig(level=logging.DEBUG,
format='%(message)s')
###
class SAT:
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.connected=False #FIXME: use twisted var instead
self._iq_cb_map = {} #callback called when ns is found on IQ
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("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("getParam", self.memory.getParam)
self.bridge.register("getParams", self.memory.getParams)
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()
#self.connect()
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)
def connect(self):
if (self.connected):
info("already connected !")
return
print "connecting..."
reactor.connectTCP(self.memory.getParamV("Server", "Connection"), 5222, self.factory)
def disconnect(self):
if (not self.connected):
info("not connected !")
return
info("Disconnecting...")
self.factory.stopTrying()
if self.xmlstream:
self.xmlstream.sendFooter()
def getService(self):
print "GetService !"
"""if (self.connected):
info("already connected !")
return"""
info("Getting service...")
self.me = jid.JID(self.memory.getParamV("JabberID", "Connection"))
self.factory = client.XMPPClientFactory(self.me, self.memory.getParamV("Password", "Connection"))
self.factory.clientConnectionLost = self.connectionLost
self.factory.addBootstrap(xmlstream.STREAM_AUTHD_EVENT,self.authd)
self.factory.addBootstrap(xmlstream.INIT_FAILED_EVENT,self.failed)
return internet.TCPClient(self.memory.getParamV("Server", "Connection"), 5222, self.factory)
def run(self):
debug("running app")
reactor.run()
def stop(self):
debug("stopping app")
reactor.stop()
def authd(self,xmlstream):
self.xmlstream=xmlstream
roster=client.IQ(xmlstream,'get')
roster.addElement(('jabber:iq:roster', 'query'))
roster.addCallback(self.rosterCb)
roster.send()
debug("server = %s",self.memory.getParamV("Server", "Connection"))
###FIXME: tmp disco ###
self.memory.registerFeature("http://jabber.org/protocol/disco#info")
self.disco(self.memory.getParamV("Server", "Connection"), self.serverDisco)
#we now send our presence status
self.setPresence(status="Online")
# add a callback for the messages
xmlstream.addObserver('/message', self.gotMessage)
xmlstream.addObserver('/presence', self.presenceCb)
xmlstream.addObserver("/iq[@type='set' or @type='get']", self.iqCb)
print "********** CONNECTED **********"
self.connected=True
self.keep_alife = task.LoopingCall(self.xmlstream.send, " ") #Needed to avoid disconnection (specially with openfire)
self.keep_alife.start(180)
#reactor.callLater(2,self.sendFile,"goffi2@jabber.goffi.int/Psi", "/tmp/fakefile")
def connectionLost(self, connector, unused_reason):
print "********** DISCONNECTED **********"
if self.keep_alife:
self.keep_alife.stop()
self.connected=False
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 setParam(self, name, value, namespace):
"""set wanted paramater and notice observers"""
info ("setting param: %s=%s in namespace %s", name, value, namespace)
self.memory.setParam(name, value, namespace)
self.bridge.paramUpdate(name, value, namespace)
def setRoster(self, to):
"""Add a contact to roster list"""
to_jid=jid.JID(to)
roster=client.IQ(self.xmlstream,'set')
query=roster.addElement(('jabber:iq:roster', 'query'))
item=query.addElement("item")
item.attributes["jid"]=to_jid.userhost()
roster.send()
#TODO: check IQ result
def delRoster(self, to):
"""Remove a contact from roster list"""
to_jid=jid.JID(to)
roster=client.IQ(self.xmlstream,'set')
query=roster.addElement(('jabber:iq:roster', 'query'))
item=query.addElement("item")
item.attributes["jid"]=to_jid.userhost()
item.attributes["subscription"]="remove"
roster.send()
#TODO: check IQ result
def failed(self,xmlstream):
debug("failed: %s", xmlstream.getErrorMessage())
debug("failed: %s", dir(xmlstream))
def isConnected(self):
return self.connected
## jabber methods ##
def disco (self, item, callback, node=None):
"""XEP-0030 Service discovery Feature."""
disco=client.IQ(self.xmlstream,'get')
disco["from"]=self.me.full()
disco["to"]=item
disco.addElement(('http://jabber.org/protocol/disco#info', 'query'))
disco.addCallback(callback)
disco.send()
def setPresence(self, to="", type="", show="", status="", priority=0):
"""Send our presence information"""
presence = domish.Element(('jabber:client', 'presence'))
if not type in ["", "unavailable", "subscribed", "subscribe",
"unsubscribe", "unsubscribed", "prob", "error"]:
error("Type error !")
#TODO: throw an error
return
if to:
presence.attributes["to"]=to
if type:
presence.attributes["type"]=type
for element in ["show", "status", "priority"]:
if locals()[element]:
presence.addElement(element).addContent(unicode(locals()[element]))
self.xmlstream.send(presence)
def addContact(self, to):
"""Add a contact in roster list"""
to_jid=jid.JID(to)
self.setRoster(to_jid.userhost())
self.setPresence(to_jid.userhost(), "subscribe")
def delContact(self, to):
"""Remove contact from roster list"""
to_jid=jid.JID(to)
self.delRoster(to_jid.userhost())
self.bridge.contactDeleted(to)
def gotMessage(self,message):
debug (u"got_message from: %s", message["from"])
for e in message.elements():
if e.name == "body":
self.bridge.newMessage(message["from"], e.children[0])
self.memory.addToHistory(self.me, jid.JID(message["from"]), self.me, "chat", e.children[0])
break
## callbacks ##
def add_IQ_cb(self, ns, cb):
"""Add an IQ callback on namespace ns"""
debug ("Registered callback for namespace %s", ns)
self._iq_cb_map[ns]=cb
def iqCb(self, stanza):
info ("iqCb")
debug ("="*20)
debug ("DEBUG:\n")
debug (stanza.toXml().encode('utf-8'))
debug ("="*20)
#FIXME: temporary ugly code
uri = stanza.firstChildElement().uri
if self._iq_cb_map.has_key(uri):
self._iq_cb_map[uri](stanza)
#TODO: manage errors stanza
def presenceCb(self, elem):
info ("presence update for [%s]", elem.getAttribute("from"))
debug("\n\nXML=\n%s\n\n", elem.toXml())
presence={}
presence["jid"]=elem.getAttribute("from")
presence["type"]=elem.getAttribute("type") or ""
presence["show"]=""
presence["status"]=""
presence["priority"]=0
for item in elem.elements():
if presence.has_key(item.name):
presence[item.name]=item.children[0]
### we check if the status is not about subscription ###
#TODO: check that from jid is one we wants to subscribe (ie: check a recent subscription asking)
if jid.JID(presence["jid"]).userhost()!=self.me.userhost():
if presence["type"]=="subscribed":
debug ("subscription answer")
elif presence["type"]=="unsubscribed":
debug ("unsubscription answer")
elif presence["type"]=="subscribe":
#FIXME: auto answer for subscribe request, must be checked !
debug ("subscription request")
self.setPresence(to=presence["jid"], type="subscribed")
else:
#We keep presence information only if it is not for subscription
self.memory.addPresenceStatus(presence["jid"], presence["type"], presence["show"],
presence["status"], int(presence["priority"]))
#now it's time to notify frontends
self.bridge.presenceUpdate(presence["jid"], presence["type"], presence["show"],
presence["status"], int(presence["priority"]))
def rosterCb(self,roster):
for contact in roster.firstChildElement().elements():
info ("new contact in roster list: %s", contact['jid'])
#and now the groups
groups=[]
for group in contact.elements():
if group.name!="group":
error("Unexpected element !")
break
groups.append(str(group))
self.memory.addContact(contact['jid'], contact.attributes, groups)
self.bridge.newContact(contact['jid'], contact.attributes, groups)
def serverDisco(self, disco):
"""xep-0030 Discovery Protocol."""
for element in disco.firstChildElement().elements():
if element.name=="feature":
debug ("Feature dectetee: %s",element["var"])
self.server_features.append(element["var"])
elif element.name=="identity":
debug ("categorie= %s",element["category"])
debug ("features= %s",self.server_features)
## 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')
sat = SAT()
service = sat.getService()
service.setServiceParent(application)
#app.memory.save() #FIXME: not the best place
#debug("Good Bye")