- core: 2 new convenient methods: getJidNStream and getClient - new param in plugin info: "handler" to know if there is a handler to plug on profiles clients - plugins with handler now use an other class which is returned to profile client with the new method "getHandler" and pluged when connecting
739 lines
28 KiB
Python
739 lines
28 KiB
Python
#!/usr/bin/python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
SAT: a jabber client
|
|
Copyright (C) 2009, 2010 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/>.
|
|
"""
|
|
|
|
CONST = {
|
|
'client_name' : u'SàT (Salut à toi)',
|
|
'client_version' : u'0.0.2D', #Please add 'D' at the end for dev versions
|
|
'local_dir' : '~/.sat'
|
|
}
|
|
|
|
from twisted.application import internet, service
|
|
from twisted.internet import glib2reactor, protocol, task
|
|
glib2reactor.install()
|
|
|
|
from twisted.words.protocols.jabber import jid, xmlstream
|
|
from twisted.words.protocols.jabber import error as jab_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 tools.xml_tools import XMLTools
|
|
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, host_app, profile, user_jid, password, host=None, port=5222):
|
|
client.XMPPClient.__init__(self, user_jid, password, host, port)
|
|
self.factory.clientConnectionLost = self.connectionLost
|
|
self.__connected=False
|
|
self.profile = profile
|
|
self.host_app = host_app
|
|
|
|
def _authd(self, xmlstream):
|
|
print "SatXMPPClient"
|
|
client.XMPPClient._authd(self, xmlstream)
|
|
self.__connected=True
|
|
print "********** CONNECTED **********"
|
|
self.streamInitialized()
|
|
self.host_app.bridge.connected() #we send the signal to the clients
|
|
|
|
def streamInitialized(self):
|
|
"""Called after _authd"""
|
|
debug ("XML stream is initialized")
|
|
self.keep_alife = task.LoopingCall(self.xmlstream.send, " ") #Needed to avoid disconnection (specially with openfire)
|
|
self.keep_alife.start(180)
|
|
|
|
self.disco = SatDiscoProtocol(self)
|
|
self.disco.setHandlerParent(self)
|
|
self.discoHandler = disco.DiscoHandler()
|
|
self.discoHandler.setHandlerParent(self)
|
|
|
|
self.roster.requestRoster()
|
|
|
|
self.presence.available()
|
|
|
|
self.disco.requestInfo(jid.JID(self.host_app.memory.getParamA("Server", "Connection", profile_key=self.profile))).addCallback(self.host_app.serverDisco) #FIXME: use these informations
|
|
|
|
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")
|
|
self.host_app.bridge.disconnected() #we send the signal to the clients
|
|
|
|
|
|
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.parent.jid, jid.JID(message["from"]), self.parent.jid, "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 raw_jid, item in roster.iteritems():
|
|
self.onRosterSet(item)
|
|
|
|
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"""
|
|
xmppim.RosterClientProtocol.removeItem(self, to)
|
|
#TODO: check IQ result
|
|
|
|
def addItem(self, to):
|
|
"""Add a contact to roster list"""
|
|
xmppim.RosterClientProtocol.addItem(self, to)
|
|
#TODO: check IQ result
|
|
|
|
def onRosterSet(self, item):
|
|
"""Called when a new/update roster item is received"""
|
|
#TODO: send a signal to frontends
|
|
item_attr = {'to': str(item.subscriptionTo),
|
|
'from': str(item.subscriptionFrom),
|
|
'ask': str(item.ask)
|
|
}
|
|
if item.name:
|
|
item_attr['name'] = item.name
|
|
info ("new contact in roster list: %s", item.jid.full())
|
|
self.host.memory.addContact(item.jid, item_attr, item.groups)
|
|
self.host.bridge.newContact(item.jid.full(), item_attr, item.groups)
|
|
|
|
def onRosterRemove(self, entity):
|
|
"""Called when a roster removal event is received"""
|
|
#TODO: send a signal to frontends
|
|
print "removing %s from roster list" % entity.full()
|
|
self.host.memory.delContact(entity)
|
|
|
|
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)
|
|
|
|
if statuses.has_key(None): #we only want string keys
|
|
statuses["default"] = statuses[None]
|
|
del statuses[None]
|
|
|
|
self.host.memory.addPresenceStatus(entity, show or "",
|
|
int(priority), statuses)
|
|
|
|
#now it's time to notify frontends
|
|
self.host.bridge.presenceUpdate(entity.full(), show or "",
|
|
int(priority), statuses)
|
|
|
|
def unavailableReceived(self, entity, statuses=None):
|
|
if statuses and statuses.has_key(None): #we only want string keys
|
|
statuses["default"] = statuses[None]
|
|
del statuses[None]
|
|
self.host.memory.addPresenceStatus(entity, "unavailable", 0, statuses)
|
|
|
|
#now it's time to notify frontends
|
|
self.host.bridge.presenceUpdate(entity.full(), "unavailable", 0, statuses)
|
|
|
|
|
|
def available(self, entity=None, show=None, statuses=None, priority=0):
|
|
if statuses and statuses.has_key('default'):
|
|
statuses[None] = statuses['default']
|
|
del statuses['default']
|
|
xmppim.PresenceClientProtocol.available(self, entity, show, statuses, priority)
|
|
|
|
def subscribedReceived(self, entity):
|
|
debug ("subscription approved for [%s]" % entity.userhost())
|
|
self.host.memory.delWaitingSub(entity.userhost())
|
|
self.host.bridge.subscribe('subscribed', entity.userhost())
|
|
|
|
def unsubscribedReceived(self, entity):
|
|
debug ("unsubscription confirmed for [%s]" % entity.userhost())
|
|
self.host.memory.delWaitingSub(entity.userhost())
|
|
self.host.bridge.subscribe('unsubscribed', entity.userhost())
|
|
|
|
def subscribeReceived(self, entity):
|
|
debug ("subscription request for [%s]" % entity.userhost())
|
|
self.host.memory.addWaitingSub('subscribe', entity.userhost())
|
|
self.host.bridge.subscribe('subscribe', entity.userhost())
|
|
|
|
def unsubscribeReceived(self, entity):
|
|
debug ("unsubscription asked for [%s]" % entity.userhost())
|
|
self.host.memory.addWaitingSub('unsubscribe', entity.userhost())
|
|
self.host.bridge.subscribe('unsubscribe', entity.userhost())
|
|
|
|
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
|
|
print "Registration asked for",user_login, user_pass, jabber_host
|
|
|
|
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={"message":"Registration successfull"}
|
|
self.host.bridge.actionResult(answer_type, self.answer_id, answer_data)
|
|
self.xmlstream.sendFooter()
|
|
|
|
def registrationFailure(self, failure):
|
|
info ("Registration failure: %s" % str(failure.value))
|
|
answer_type = "ERROR"
|
|
answer_data = {}
|
|
if failure.value.condition == 'conflict':
|
|
answer_data['reason'] = 'conflict'
|
|
answer_data={"message":"Username already exists, please choose an other one"}
|
|
else:
|
|
answer_data['reason'] = 'unknown'
|
|
answer_data={"message":"Registration failed (%s)" % str(failure.value.condition)}
|
|
self.host.bridge.actionResult(answer_type, self.answer_id, answer_data)
|
|
self.xmlstream.sendFooter()
|
|
|
|
|
|
class SAT(service.Service):
|
|
|
|
def get_next_id(self):
|
|
return sat_next_id()
|
|
|
|
def get_const(self, name):
|
|
"""Return a constant"""
|
|
if not CONST.has_key(name):
|
|
error('Trying to access an undefined constant')
|
|
raise Exception
|
|
return CONST[name]
|
|
|
|
def set_const(self, name, value):
|
|
"""Save a constant"""
|
|
if CONST.has_key(name):
|
|
error('Trying to redefine a constant')
|
|
raise Exception
|
|
CONST[name] = value
|
|
|
|
def __init__(self):
|
|
#TODO: standardize callback system
|
|
|
|
local_dir = os.path.expanduser(self.get_const('local_dir'))
|
|
if not os.path.exists(local_dir):
|
|
os.makedirs(local_dir)
|
|
|
|
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.__general_cb_map = {} #callback called for general reasons (key = name)
|
|
self.__private_data = {} #used for internal callbacks (key = id)
|
|
self.profiles = {}
|
|
self.plugins = {}
|
|
|
|
self.memory=Memory(self)
|
|
self.server_features=[] #XXX: temp dic, need to be transfered into self.memory in the future
|
|
|
|
self.bridge=DBusBridge()
|
|
self.bridge.register("getProfilesList", self.memory.getProfilesList)
|
|
self.bridge.register("createProfile", self.memory.createProfile)
|
|
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("getWaitingSub", self.memory.getWaitingSub)
|
|
self.bridge.register("sendMessage", self.sendMessage)
|
|
self.bridge.register("setParam", self.setParam)
|
|
self.bridge.register("getParamA", self.memory.getParamA)
|
|
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("subscription", self.subscription)
|
|
self.bridge.register("addContact", self.addContact)
|
|
self.bridge.register("delContact", self.delContact)
|
|
self.bridge.register("isConnected", self.isConnected)
|
|
self.bridge.register("launchAction", self.launchAction)
|
|
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)
|
|
if plug_info.has_key('handler') and plug_info['handler'] == 'yes':
|
|
self.plugins[plug_info['import_name']].is_handler = True
|
|
else:
|
|
self.plugins[plug_info['import_name']].is_handler = False
|
|
#TODO: test xmppclient presence and register handler parent
|
|
|
|
def connect(self, profile_key = '@DEFAULT@'):
|
|
"""Connect to jabber server"""
|
|
|
|
|
|
profile = self.memory.getProfileName(profile_key)
|
|
if not profile_key:
|
|
error ('Trying to connect a non-exsitant profile')
|
|
return
|
|
|
|
if (self.isConnected()):
|
|
info("already connected !")
|
|
return
|
|
print "connecting..."
|
|
current = self.profiles[profile] = SatXMPPClient(self, profile,
|
|
jid.JID(self.memory.getParamA("JabberID", "Connection", profile_key = profile_key), profile),
|
|
self.memory.getParamA("Password", "Connection", profile_key = profile_key),
|
|
self.memory.getParamA("Server", "Connection", profile_key = profile_key), 5222)
|
|
|
|
current.messageProt = SatMessageProtocol(self)
|
|
current.messageProt.setHandlerParent(current)
|
|
|
|
current.roster = SatRosterProtocol(self)
|
|
current.roster.setHandlerParent(current)
|
|
|
|
current.presence = SatPresenceProtocol(self)
|
|
current.presence.setHandlerParent(current)
|
|
|
|
current.fallBack = SatFallbackHandler(self)
|
|
current.fallBack.setHandlerParent(current)
|
|
|
|
current.versionHandler = generic.VersionHandler(self.get_const('client_name'),
|
|
self.get_const('client_version'))
|
|
current.versionHandler.setHandlerParent(current)
|
|
|
|
debug ("setting plugins parents")
|
|
|
|
#FIXME: gof
|
|
for plugin in self.plugins.iteritems():
|
|
if plugin[1].is_handler:
|
|
plugin[1].getHandler().setHandlerParent(current)
|
|
|
|
current.startService()
|
|
|
|
def disconnect(self, profile_key='@DEFAULT@'):
|
|
"""disconnect from jabber server"""
|
|
if (not self.isConnected(profile_key)):
|
|
info("not connected !")
|
|
return
|
|
profile = self.memory.getProfileName(profile_key)
|
|
info("Disconnecting...")
|
|
self.profiles[profile].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()
|
|
|
|
## Misc methods ##
|
|
|
|
def getJidNStream(self, profile_key):
|
|
"""Convenient method to get jid and stream from profile key
|
|
@return: tuple (jid, xmlstream) from profile, can be None"""
|
|
profile = self.memory.getProfileName(profile_key)
|
|
if not profile or not self.profiles[profile].isConnected():
|
|
return (None, None)
|
|
return (self.profiles[profile].jid, self.profiles[profile].xmlstream)
|
|
|
|
def getClient(self, profile_key):
|
|
"""Convenient method to get client from profile key
|
|
@return: client or None if it doesn't exist"""
|
|
profile = self.memory.getProfileName(profile_key)
|
|
if not profile:
|
|
return None
|
|
return self.profiles[profile]
|
|
|
|
def registerNewAccount(self, login, password, server, port = 5222, id = None):
|
|
"""Connect to a server and create a new account using in-band registration"""
|
|
|
|
next_id = id or 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
|
|
|
|
def registerNewAccountCB(self, id, data):
|
|
#FIXME: gof: profile not managed here !
|
|
user = jid.parse(self.memory.getParamA("JabberID", "Connection"))[0]
|
|
password = self.memory.getParamA("Password", "Connection")
|
|
server = self.memory.getParamA("Server", "Connection")
|
|
|
|
if not user or not password or not server:
|
|
info ('No user or server given')
|
|
#TODO: a proper error message must be sent to frontend
|
|
self.actionResult(id, "ERROR", {'message':"No user, password or server given, can't register new account."})
|
|
return
|
|
|
|
confirm_id = sat_next_id()
|
|
self.__private_data[confirm_id]=id
|
|
|
|
self.askConfirmation(confirm_id, "YES/NO",
|
|
{"message":"Are you sure to register new account [%s] to server %s ?" % (user, server)},
|
|
self.regisConfirmCB)
|
|
print ("===============+++++++++++ REGISTER NEW ACCOUNT++++++++++++++============")
|
|
print "id=",id
|
|
print "data=",data
|
|
|
|
def regisConfirmCB(self, id, accepted, data):
|
|
#FIXME: gof: profile not managed here !
|
|
print "register Confirmation CB ! (%s)" % str(accepted)
|
|
action_id = self.__private_data[id]
|
|
del self.__private_data[id]
|
|
user = jid.parse(self.memory.getParamA("JabberID", "Connection"))[0]
|
|
password = self.memory.getParamA("Password", "Connection")
|
|
server = self.memory.getParamA("Server", "Connection")
|
|
if accepted:
|
|
self.registerNewAccount(user, password, server, id=action_id)
|
|
else:
|
|
self.actionResult(action_id, "SUPPRESS", {})
|
|
|
|
def submitForm(self, action, target, fields, profile_key='@DEFAULT@'):
|
|
"""submit a form
|
|
@param target: target jid where we are submitting
|
|
@param fields: list of tuples (name, value)
|
|
@return: tuple: (id, deferred)
|
|
"""
|
|
|
|
profile = self.memory.getProfileName(profile_key)
|
|
assert(profile)
|
|
to_jid = jid.JID(target)
|
|
|
|
iq = compat.IQ(self.profiles[profile].xmlstream, 'set')
|
|
iq["to"] = target
|
|
iq["from"] = self.profiles[profile].jid.full()
|
|
query = iq.addElement(('jabber:iq:register', 'query'))
|
|
if action=='SUBMIT':
|
|
form = XMLTools.tupleList2dataForm(fields)
|
|
query.addChild(form.toElement())
|
|
elif action=='CANCEL':
|
|
query.addElement('remove')
|
|
else:
|
|
error ("FIXME FIXME FIXME: Unmanaged action (%s) in submitForm" % action)
|
|
raise NotImplementedError
|
|
|
|
deferred = iq.send(target)
|
|
return (iq['id'], deferred)
|
|
|
|
## Client management ##
|
|
|
|
def setParam(self, name, value, category, profile_key='@DEFAULT@'):
|
|
"""set wanted paramater and notice observers"""
|
|
info ("setting param: %s=%s in category %s", name, value, category)
|
|
self.memory.setParam(name, value, category, profile_key)
|
|
|
|
def isConnected(self, profile_key='@DEFAULT@'):
|
|
"""Return connection status of profile
|
|
@param profile_key: key_word or profile name to determine profile name
|
|
@return True if connected
|
|
"""
|
|
profile = self.memory.getProfileName(profile_key)
|
|
if not profile:
|
|
error ('asking connection status for a non-existant profile')
|
|
raise Exception #TODO: raise a proper exception
|
|
if not self.profiles.has_key(profile):
|
|
return False
|
|
return self.profiles[profile].isConnected()
|
|
|
|
def launchAction(self, type, data):
|
|
"""Launch a specific action asked by client
|
|
@param type: action type (button)
|
|
@param data: needed data to launch the action
|
|
|
|
@return: action id for result, or empty string in case or error
|
|
"""
|
|
if type=="button":
|
|
try:
|
|
cb_name = self.memory.getParamA(data["name"], data["category"], "callback")
|
|
except KeyError:
|
|
error ("Incomplete data")
|
|
return ""
|
|
id = sat_next_id()
|
|
self.callGeneralCB(cb_name, id, data)
|
|
return id
|
|
else:
|
|
error ("Unknown action type")
|
|
return ""
|
|
|
|
|
|
## jabber methods ##
|
|
|
|
def sendMessage(self,to,msg,type='chat', profile_key='@DEFAULT@'):
|
|
#FIXME: check validity of recipient
|
|
profile = self.memory.getProfileName(profile_key)
|
|
assert(profile)
|
|
current_jid = self.profiles[profile].jid
|
|
debug("Sending jabber message to %s...", to)
|
|
message = domish.Element(('jabber:client','message'))
|
|
message["to"] = jid.JID(to).full()
|
|
message["from"] = current_jid.full()
|
|
message["type"] = type
|
|
message.addElement("body", "jabber:client", msg)
|
|
self.profiles[profile].xmlstream.send(message)
|
|
self.memory.addToHistory(current_jid, current_jid, 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="", show="", priority = 0, statuses={}, profile_key='@DEFAULT@'):
|
|
"""Send our presence information"""
|
|
profile = self.memory.getProfileName(profile_key)
|
|
assert(profile)
|
|
to_jid = jid.JID(to) if to else None
|
|
self.profiles[profile].presence.available(to_jid, show, statuses, priority)
|
|
|
|
def subscription(self, type, raw_jid, profile_key='@DEFAULT@'):
|
|
"""Called to manage subscription"""
|
|
profile = self.memory.getProfileName(profile_key)
|
|
assert(profile)
|
|
to_jid = jid.JID(raw_jid)
|
|
debug ('subsciption request [%s] for %s', type, to_jid.full())
|
|
if type=="subscribe":
|
|
self.profiles[profile].presence.subscribe(to_jid)
|
|
elif type=="subscribed":
|
|
self.profiles[profile].subscribed(to_jid)
|
|
contact = self.memory.getContact(to_jid)
|
|
if not contact or not bool(contact['to']): #we automatically subscribe to 'to' presence
|
|
debug('sending automatic "to" subscription request')
|
|
self.subscription('subscribe', to_jid.userhost())
|
|
elif type=="unsubscribe":
|
|
self.profiles[profile].presence.unsubscribe(to_jid)
|
|
elif type=="unsubscribed":
|
|
self.profiles[profile].presence.unsubscribed(to_jid)
|
|
|
|
|
|
def addContact(self, to, profile_key='@DEFAULT@'):
|
|
"""Add a contact in roster list"""
|
|
profile = self.memory.getProfileName(profile_key)
|
|
assert(profile)
|
|
to_jid=jid.JID(to)
|
|
self.profiles[profile].roster.addItem(to_jid)
|
|
self.profiles[profile].presence.subscribe(to_jid)
|
|
|
|
def delContact(self, to, profile_key='@DEFAULT@'):
|
|
"""Remove contact from roster list"""
|
|
profile = self.memory.getProfileName(profile_key)
|
|
assert(profile)
|
|
to_jid=jid.JID(to)
|
|
self.profiles[profile].roster.removeItem(to_jid)
|
|
self.profiles[profile].presence.unsubscribe(to_jid)
|
|
self.profiles[profile].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 actionResult(self, id, type, data):
|
|
"""Send the result of an action
|
|
@param id: same id used with action
|
|
@param type: result type ("PARAM", "SUCCESS", "ERROR", "FORM")
|
|
@param data: dictionary
|
|
"""
|
|
self.bridge.actionResult(type, id, data)
|
|
|
|
def actionResultExt(self, id, type, data):
|
|
"""Send the result of an action, extended version
|
|
@param id: same id used with action
|
|
@param type: result type /!\ only "DICT_DICT" for this method
|
|
@param data: dictionary of dictionaries
|
|
"""
|
|
if type != "DICT_DICT":
|
|
error("type for actionResultExt must be DICT_DICT, fixing it")
|
|
type = "DICT_DICT"
|
|
self.bridge.actionResultExt(type, id, data)
|
|
|
|
|
|
|
|
def askConfirmation(self, id, type, data, cb):
|
|
"""Add a confirmation callback
|
|
@param id: id used to get answer
|
|
@param type: confirmation type ("YES/NO", "FILE_TRANSFERT")
|
|
@param data: data (depend of confirmation type)
|
|
@param cb: callback called with the answer
|
|
"""
|
|
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
|
|
|
|
def registerGeneralCB(self, name, CB):
|
|
"""Register a callback called for general reason"""
|
|
self.__general_cb_map[name] = CB
|
|
|
|
def removeGeneralCB(self, name):
|
|
"""Remove a general callback"""
|
|
if not self.__general_cb_map.has_key(name):
|
|
error ("Trying to remove an unknow general callback")
|
|
else:
|
|
del self.__general_cb_map[name]
|
|
|
|
def callGeneralCB(self, name, *args, **kwargs):
|
|
"""Call general function back"""
|
|
try:
|
|
return self.__general_cb_map[name](*args, **kwargs)
|
|
except KeyError:
|
|
error("Trying to call unknown function")
|
|
return None
|
|
|
|
application = service.Application('SàT')
|
|
service = SAT()
|
|
service.setServiceParent(application)
|