720 lines
27 KiB
Python
720 lines
27 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, bridge, 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.bridge = bridge
|
|
self.profile = profile
|
|
|
|
def _authd(self, xmlstream):
|
|
print "SatXMPPClient"
|
|
client.XMPPClient._authd(self, xmlstream)
|
|
self.__connected=True
|
|
print "********** CONNECTED **********"
|
|
self.streamInitialized()
|
|
self.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.memory.getParamA("Server", "Connection"))).addCallback(self.host.serverDisco) #gof: FIXME
|
|
|
|
|
|
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.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() #gof:
|
|
|
|
|
|
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, 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.bridge, profile,
|
|
jid.JID(self.memory.getParamA("JabberID", "Connection"), profile),
|
|
self.memory.getParamA("Password", "Connection"),
|
|
self.memory.getParamA("Server", "Connection"), 5222)
|
|
|
|
#current.client.streamInitialized = self.streamInitialized #gof:
|
|
|
|
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 isinstance(plugin[1], XMPPHandler):
|
|
plugin[1].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 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):
|
|
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):
|
|
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)
|