libervia-backend/sat.tac

381 lines
14 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
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):
print "connecting..."
def getService(self):
print "GetService !"
"""if (self.connected):
info("already connected !")
return"""
info("Getting client...")
self.me = jid.JID(self.memory.getParamV("JabberID", "Connection"))
self.factory = client.XMPPClientFactory(self.me, self.memory.getParamV("Password", "Connection"))
self.factory.addBootstrap(xmlstream.STREAM_AUTHD_EVENT,self.authd)
self.factory.addBootstrap(xmlstream.INIT_FAILED_EVENT,self.failed)
self.connectionStatus="online" #TODO: check if connection is OK
self.connected=True #TODO: use startedConnecting and clientConnectionLost of XMPPClientFactory
return internet.TCPClient(self.memory.getParamV("Server", "Connection"), 5222, self.factory)
def disconnect(self):
if (not self.connected):
info("not connected !")
return
info("Disconnecting...")
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)
#reactor.callLater(2,self.sendFile,"goffi2@jabber.goffi.int/Psi", "/tmp/fakefile")
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")