core: main SAT class now moved in its own module core.sat_main
This commit is contained in:
parent
021d926b27
commit
905118a774
2 changed files with 616 additions and 595 deletions
613
src/core/sat_main.py
Normal file
613
src/core/sat_main.py
Normal file
|
|
@ -0,0 +1,613 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
SAT: a jabber client
|
||||
Copyright (C) 2009, 2010, 2011 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.1.1D', #Please add 'D' at the end for dev versions
|
||||
'local_dir' : '~/.sat'
|
||||
}
|
||||
|
||||
from twisted.application import service
|
||||
from twisted.internet import defer
|
||||
|
||||
from twisted.words.protocols.jabber import jid, xmlstream
|
||||
from twisted.words.xish import domish
|
||||
|
||||
from twisted.internet import reactor
|
||||
|
||||
from wokkel import compat
|
||||
|
||||
from sat.bridge.DBus import DBusBridge
|
||||
import logging
|
||||
from logging import debug, info, error
|
||||
|
||||
import sys
|
||||
import os.path
|
||||
|
||||
from sat.core.xmpp import SatXMPPClient, SatMessageProtocol, SatRosterProtocol, SatPresenceProtocol, SatDiscoProtocol, SatFallbackHandler, RegisteringAuthenticator, SatVersionHandler
|
||||
from sat.tools.memory import Memory
|
||||
from sat.tools.xml_tools import tupleList2dataForm
|
||||
from sat.tools.misc import TriggerManager
|
||||
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 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.trigger = TriggerManager() #trigger are user to change SàT behaviour
|
||||
self.profiles = {}
|
||||
self.plugins = {}
|
||||
self.menus = {} #used to know which new menus are wanted by plugins
|
||||
|
||||
self.memory=Memory(self)
|
||||
|
||||
self.bridge=DBusBridge()
|
||||
self.bridge.register("getVersion", lambda: self.get_const('client_version'))
|
||||
self.bridge.register("getProfileName", self.memory.getProfileName)
|
||||
self.bridge.register("getProfilesList", self.memory.getProfilesList)
|
||||
self.bridge.register("createProfile", self.memory.createProfile)
|
||||
self.bridge.register("deleteProfile", self.memory.deleteProfile)
|
||||
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("getParamsUI", self.memory.getParamsUI)
|
||||
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.bridge.register("getMenus", self.getMenus)
|
||||
self.bridge.register("getMenuHelp", self.getMenuHelp)
|
||||
self.bridge.register("callMenu", self.callMenu)
|
||||
|
||||
self._import_plugins()
|
||||
|
||||
|
||||
def _import_plugins(self):
|
||||
"""Import all plugins found in plugins directory"""
|
||||
import sat.plugins
|
||||
plugins_path = os.path.dirname(sat.plugins.__file__)
|
||||
plug_lst = [os.path.splitext(plugin)[0] for plugin in map(os.path.basename,glob (os.path.join(plugins_path,"plugin*.py")))]
|
||||
__plugins_to_import = {} #plugins will still have to import
|
||||
for plug in plug_lst:
|
||||
plugin_path = 'sat.plugins.'+plug
|
||||
__import__(plugin_path)
|
||||
mod = sys.modules[plugin_path]
|
||||
plugin_info = mod.PLUGIN_INFO
|
||||
__plugins_to_import[plugin_info['import_name']] = (plugin_path, mod, plugin_info)
|
||||
while True:
|
||||
self._import_plugins_from_dict(__plugins_to_import)
|
||||
if not __plugins_to_import:
|
||||
break
|
||||
|
||||
def _import_plugins_from_dict(self, plugins_to_import, import_name=None):
|
||||
"""Recursively import and their dependencies in the right order
|
||||
@param plugins_to_import: dict where key=import_name and values= (plugin_path, module, plugin_info)"""
|
||||
if self.plugins.has_key(import_name):
|
||||
debug('Plugin [%s] already imported, passing' % import_name)
|
||||
return
|
||||
if not import_name:
|
||||
import_name,(plugin_path, mod, plugin_info) = plugins_to_import.popitem()
|
||||
else:
|
||||
if not import_name in plugins_to_import:
|
||||
raise ImportError(_('Dependency plugin not found: [%s]') % import_name)
|
||||
plugin_path, mod, plugin_info = plugins_to_import.pop(import_name)
|
||||
dependencies = plugin_info.setdefault("dependencies",[])
|
||||
for dependency in dependencies:
|
||||
if not self.plugins.has_key(dependency):
|
||||
debug('Recursively import dependency of [%s]: [%s]' % (import_name, dependency))
|
||||
self._import_plugins_from_dict(plugins_to_import, dependency)
|
||||
info (_("importing plugin: %s"), plugin_info['name'])
|
||||
self.plugins[import_name] = getattr(mod, plugin_info['main'])(self)
|
||||
if plugin_info.has_key('handler') and plugin_info['handler'] == 'yes':
|
||||
self.plugins[import_name].is_handler = True
|
||||
else:
|
||||
self.plugins[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:
|
||||
error (_('Trying to connect a non-exsitant profile'))
|
||||
return
|
||||
|
||||
if (self.isConnected(profile)):
|
||||
info(_("already connected !"))
|
||||
return
|
||||
current = self.profiles[profile] = SatXMPPClient(self, profile,
|
||||
jid.JID(self.memory.getParamA("JabberID", "Connection", profile_key = profile), profile),
|
||||
self.memory.getParamA("Password", "Connection", profile_key = profile),
|
||||
self.memory.getParamA("Server", "Connection", profile_key = profile), 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 = SatVersionHandler(self.get_const('client_name'),
|
||||
self.get_const('client_version'))
|
||||
current.versionHandler.setHandlerParent(current)
|
||||
|
||||
debug (_("setting plugins parents"))
|
||||
|
||||
for plugin in self.plugins.iteritems():
|
||||
if plugin[1].is_handler:
|
||||
plugin[1].getHandler(profile).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 !")
|
||||
#TODO: manage autoconnect
|
||||
#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, profile):
|
||||
user = jid.parse(self.memory.getParamA("JabberID", "Connection", profile_key=profile))[0]
|
||||
password = self.memory.getParamA("Password", "Connection", profile_key=profile)
|
||||
server = self.memory.getParamA("Server", "Connection", profile_key=profile)
|
||||
|
||||
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,profile)
|
||||
|
||||
self.askConfirmation(confirm_id, "YES/NO",
|
||||
{"message":_("Are you sure to register new account [%(user)s] to server %(server)s ?") % {'user':user, 'server':server, 'profile':profile}},
|
||||
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,profile = self.__private_data[id]
|
||||
del self.__private_data[id]
|
||||
if accepted:
|
||||
user = jid.parse(self.memory.getParamA("JabberID", "Connection", profile_key=profile))[0]
|
||||
password = self.memory.getParamA("Password", "Connection", profile_key=profile)
|
||||
server = self.memory.getParamA("Server", "Connection", profile_key=profile)
|
||||
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 = 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: %(name)s=%(value)s in category %(category)s") % {'name':name, 'value':value, 'category':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'))
|
||||
return
|
||||
if not self.profiles.has_key(profile):
|
||||
return False
|
||||
return self.profiles[profile].isConnected()
|
||||
|
||||
def launchAction(self, type, data, profile_key='@DEFAULT@'):
|
||||
"""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
|
||||
"""
|
||||
profile = self.memory.getProfileName(profile_key)
|
||||
if not profile:
|
||||
error (_('trying to launch action with a non-existant profile'))
|
||||
raise Exception #TODO: raise a proper exception
|
||||
if type=="button":
|
||||
try:
|
||||
cb_name = data['callback_id']
|
||||
except KeyError:
|
||||
error (_("Incomplete data"))
|
||||
return ""
|
||||
id = sat_next_id()
|
||||
self.callGeneralCB(cb_name, id, data, profile = profile)
|
||||
return id
|
||||
else:
|
||||
error (_("Unknown action type"))
|
||||
return ""
|
||||
|
||||
|
||||
## jabber methods ##
|
||||
|
||||
def sendMessage(self, to, msg, subject=None, 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
|
||||
if subject:
|
||||
message.addElement("subject", "jabber:client", subject)
|
||||
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))
|
||||
if type!="groupchat":
|
||||
self.bridge.newMessage(message['from'], unicode(msg), mess_type=type, to_jid=message['to'], profile=profile) #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)
|
||||
#XXX: FIXME: temporary fix to work around openfire 3.7.0 bug (presence is not broadcasted to generating resource)
|
||||
if statuses.has_key(''):
|
||||
statuses['default'] = statuses['']
|
||||
del statuses['']
|
||||
self.bridge.presenceUpdate(self.profiles[profile].jid.full(), show,
|
||||
int(priority), statuses, profile)
|
||||
|
||||
|
||||
def subscription(self, subs_type, raw_jid, profile_key='@DEFAULT@'):
|
||||
"""Called to manage subscription
|
||||
@param subs_type: subsciption type (cf RFC 3921)
|
||||
@param raw_jid: unicode entity's jid
|
||||
@param profile_key: profile"""
|
||||
profile = self.memory.getProfileName(profile_key)
|
||||
assert(profile)
|
||||
to_jid = jid.JID(raw_jid)
|
||||
debug (_('subsciption request [%(subs_type)s] for %(jid)s') % {'subs_type':subs_type, 'jid':to_jid.full()})
|
||||
if subs_type=="subscribe":
|
||||
self.profiles[profile].presence.subscribe(to_jid)
|
||||
elif subs_type=="subscribed":
|
||||
self.profiles[profile].presence.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 subs_type=="unsubscribe":
|
||||
self.profiles[profile].presence.unsubscribe(to_jid)
|
||||
elif subs_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) XXX: disabled (cf http://wokkel.ik.nu/ticket/56))
|
||||
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.bridge.contactDeleted(to, profile)
|
||||
|
||||
|
||||
## callbacks ##
|
||||
|
||||
def serverDisco(self, disco, profile):
|
||||
"""xep-0030 Discovery Protocol."""
|
||||
for feature in disco.features:
|
||||
debug (_("Feature found: %s"),feature)
|
||||
self.memory.addServerFeature(feature, profile)
|
||||
for cat, type in disco.identities:
|
||||
debug (_("Identity found: [%(category)s/%(type)s] %(identity)s") % {'category':cat, 'type':type, 'identity':disco.identities[(cat,type)]})
|
||||
|
||||
def serverDiscoItems(self, disco_result, disco_client, profile, initialized):
|
||||
"""xep-0030 Discovery Protocol.
|
||||
@param disco_result: result of the disco item querry
|
||||
@param disco_client: SatDiscoProtocol instance
|
||||
@param profile: profile of the user
|
||||
@param initialized: deferred which must be chained when everything is done"""
|
||||
def _check_entity_cb(result, entity, profile):
|
||||
for category, type in result.identities:
|
||||
debug (_('Identity added: (%(category)s,%(type)s) ==> %(entity)s [%(profile)s]') % {
|
||||
'category':category, 'type':type, 'entity':entity, 'profile':profile})
|
||||
self.memory.addServerIdentity(category, type, entity, profile)
|
||||
|
||||
defer_list = []
|
||||
for item in disco_result._items:
|
||||
defer_list.append(disco_client.requestInfo(item.entity).addCallback(_check_entity_cb, item.entity, profile))
|
||||
defer.DeferredList(defer_list).chainDeferred(initialized)
|
||||
|
||||
|
||||
## 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", "XMLUI")
|
||||
@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 [%(id)s]: %(success)s") % {'id': id, 'success':_("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 (%s)") % name)
|
||||
return None
|
||||
|
||||
#Menus management
|
||||
|
||||
def importMenu(self, category, name, callback, help_string = "", type = "NORMAL"):
|
||||
"""register a new menu for frontends
|
||||
@param category: category of the menu
|
||||
@param name: menu item entry
|
||||
@param callback: method to be called when menuitem is selected"""
|
||||
if self.menus.has_key((category,name)):
|
||||
error ("Want to register a menu which already existe")
|
||||
return
|
||||
self.menus[(category,name,type)] = {'callback':callback, 'help_string':help_string, 'type':type}
|
||||
|
||||
def getMenus(self):
|
||||
"""Return all menus registered"""
|
||||
return self.menus.keys()
|
||||
|
||||
def getMenuHelp(self, category, name, type="NORMAL"):
|
||||
"""return the help string of the menu"""
|
||||
try:
|
||||
return self.menus[(category,name,type)]['help_string']
|
||||
except KeyError:
|
||||
error (_("Trying to access an unknown menu"))
|
||||
return ""
|
||||
|
||||
def callMenu(self, category, name, type="NORMAL", profile_key='@DEFAULT@'):
|
||||
"""return the id of the action"""
|
||||
profile = self.memory.getProfileName(profile_key)
|
||||
if not profile_key:
|
||||
error (_('Non-exsitant profile'))
|
||||
return ""
|
||||
if self.menus.has_key((category,name,type)):
|
||||
id = self.get_next_id()
|
||||
self.menus[(category,name,type)]['callback'](id, profile)
|
||||
return id
|
||||
else:
|
||||
error (_("Trying to access an unknown menu (%(category)s/%(name)s/%(type)s)")%{'category':category, 'name':name,'type':type})
|
||||
return ""
|
||||
598
src/sat.tac
598
src/sat.tac
|
|
@ -19,606 +19,14 @@ 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.1.1D', #Please add 'D' at the end for dev versions
|
||||
'local_dir' : '~/.sat'
|
||||
}
|
||||
|
||||
import gettext
|
||||
gettext.install('sat', "i18n", unicode=True)
|
||||
|
||||
from twisted.application import internet, service
|
||||
from twisted.internet import glib2reactor, protocol, task, defer
|
||||
from twisted.application import service
|
||||
from twisted.internet import glib2reactor
|
||||
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 sat.core.xmpp import SatXMPPClient, SatMessageProtocol, SatRosterProtocol, SatPresenceProtocol, SatDiscoProtocol, SatFallbackHandler, RegisteringAuthenticator, SatVersionHandler
|
||||
from sat.tools.memory import Memory
|
||||
from sat.tools.xml_tools import tupleList2dataForm
|
||||
from sat.tools.misc import TriggerManager
|
||||
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 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.trigger = TriggerManager() #trigger are user to change SàT behaviour
|
||||
self.profiles = {}
|
||||
self.plugins = {}
|
||||
self.menus = {} #used to know which new menus are wanted by plugins
|
||||
|
||||
self.memory=Memory(self)
|
||||
|
||||
self.bridge=DBusBridge()
|
||||
self.bridge.register("getVersion", lambda: self.get_const('client_version'))
|
||||
self.bridge.register("getProfileName", self.memory.getProfileName)
|
||||
self.bridge.register("getProfilesList", self.memory.getProfilesList)
|
||||
self.bridge.register("createProfile", self.memory.createProfile)
|
||||
self.bridge.register("deleteProfile", self.memory.deleteProfile)
|
||||
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("getParamsUI", self.memory.getParamsUI)
|
||||
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.bridge.register("getMenus", self.getMenus)
|
||||
self.bridge.register("getMenuHelp", self.getMenuHelp)
|
||||
self.bridge.register("callMenu", self.callMenu)
|
||||
|
||||
self._import_plugins()
|
||||
|
||||
|
||||
def _import_plugins(self):
|
||||
"""Import all plugins found in plugins directory"""
|
||||
import sat.plugins
|
||||
plugins_path = os.path.dirname(sat.plugins.__file__)
|
||||
plug_lst = [os.path.splitext(plugin)[0] for plugin in map(os.path.basename,glob (os.path.join(plugins_path,"plugin*.py")))]
|
||||
__plugins_to_import = {} #plugins will still have to import
|
||||
for plug in plug_lst:
|
||||
plugin_path = 'sat.plugins.'+plug
|
||||
__import__(plugin_path)
|
||||
mod = sys.modules[plugin_path]
|
||||
plugin_info = mod.PLUGIN_INFO
|
||||
__plugins_to_import[plugin_info['import_name']] = (plugin_path, mod, plugin_info)
|
||||
while True:
|
||||
self._import_plugins_from_dict(__plugins_to_import)
|
||||
if not __plugins_to_import:
|
||||
break
|
||||
|
||||
def _import_plugins_from_dict(self, plugins_to_import, import_name=None):
|
||||
"""Recursively import and their dependencies in the right order
|
||||
@param plugins_to_import: dict where key=import_name and values= (plugin_path, module, plugin_info)"""
|
||||
if self.plugins.has_key(import_name):
|
||||
debug('Plugin [%s] already imported, passing' % import_name)
|
||||
return
|
||||
if not import_name:
|
||||
import_name,(plugin_path, mod, plugin_info) = plugins_to_import.popitem()
|
||||
else:
|
||||
if not import_name in plugins_to_import:
|
||||
raise ImportError(_('Dependency plugin not found: [%s]') % import_name)
|
||||
plugin_path, mod, plugin_info = plugins_to_import.pop(import_name)
|
||||
dependencies = plugin_info.setdefault("dependencies",[])
|
||||
for dependency in dependencies:
|
||||
if not self.plugins.has_key(dependency):
|
||||
debug('Recursively import dependency of [%s]: [%s]' % (import_name, dependency))
|
||||
self._import_plugins_from_dict(plugins_to_import, dependency)
|
||||
info (_("importing plugin: %s"), plugin_info['name'])
|
||||
self.plugins[import_name] = getattr(mod, plugin_info['main'])(self)
|
||||
if plugin_info.has_key('handler') and plugin_info['handler'] == 'yes':
|
||||
self.plugins[import_name].is_handler = True
|
||||
else:
|
||||
self.plugins[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:
|
||||
error (_('Trying to connect a non-exsitant profile'))
|
||||
return
|
||||
|
||||
if (self.isConnected(profile)):
|
||||
info(_("already connected !"))
|
||||
return
|
||||
current = self.profiles[profile] = SatXMPPClient(self, profile,
|
||||
jid.JID(self.memory.getParamA("JabberID", "Connection", profile_key = profile), profile),
|
||||
self.memory.getParamA("Password", "Connection", profile_key = profile),
|
||||
self.memory.getParamA("Server", "Connection", profile_key = profile), 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 = SatVersionHandler(self.get_const('client_name'),
|
||||
self.get_const('client_version'))
|
||||
current.versionHandler.setHandlerParent(current)
|
||||
|
||||
debug (_("setting plugins parents"))
|
||||
|
||||
for plugin in self.plugins.iteritems():
|
||||
if plugin[1].is_handler:
|
||||
plugin[1].getHandler(profile).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 !")
|
||||
#TODO: manage autoconnect
|
||||
#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, profile):
|
||||
user = jid.parse(self.memory.getParamA("JabberID", "Connection", profile_key=profile))[0]
|
||||
password = self.memory.getParamA("Password", "Connection", profile_key=profile)
|
||||
server = self.memory.getParamA("Server", "Connection", profile_key=profile)
|
||||
|
||||
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,profile)
|
||||
|
||||
self.askConfirmation(confirm_id, "YES/NO",
|
||||
{"message":_("Are you sure to register new account [%(user)s] to server %(server)s ?") % {'user':user, 'server':server, 'profile':profile}},
|
||||
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,profile = self.__private_data[id]
|
||||
del self.__private_data[id]
|
||||
if accepted:
|
||||
user = jid.parse(self.memory.getParamA("JabberID", "Connection", profile_key=profile))[0]
|
||||
password = self.memory.getParamA("Password", "Connection", profile_key=profile)
|
||||
server = self.memory.getParamA("Server", "Connection", profile_key=profile)
|
||||
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 = 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: %(name)s=%(value)s in category %(category)s") % {'name':name, 'value':value, 'category':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'))
|
||||
return
|
||||
if not self.profiles.has_key(profile):
|
||||
return False
|
||||
return self.profiles[profile].isConnected()
|
||||
|
||||
def launchAction(self, type, data, profile_key='@DEFAULT@'):
|
||||
"""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
|
||||
"""
|
||||
profile = self.memory.getProfileName(profile_key)
|
||||
if not profile:
|
||||
error (_('trying to launch action with a non-existant profile'))
|
||||
raise Exception #TODO: raise a proper exception
|
||||
if type=="button":
|
||||
try:
|
||||
cb_name = data['callback_id']
|
||||
except KeyError:
|
||||
error (_("Incomplete data"))
|
||||
return ""
|
||||
id = sat_next_id()
|
||||
self.callGeneralCB(cb_name, id, data, profile = profile)
|
||||
return id
|
||||
else:
|
||||
error (_("Unknown action type"))
|
||||
return ""
|
||||
|
||||
|
||||
## jabber methods ##
|
||||
|
||||
def sendMessage(self, to, msg, subject=None, 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
|
||||
if subject:
|
||||
message.addElement("subject", "jabber:client", subject)
|
||||
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))
|
||||
if type!="groupchat":
|
||||
self.bridge.newMessage(message['from'], unicode(msg), mess_type=type, to_jid=message['to'], profile=profile) #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)
|
||||
#XXX: FIXME: temporary fix to work around openfire 3.7.0 bug (presence is not broadcasted to generating resource)
|
||||
if statuses.has_key(''):
|
||||
statuses['default'] = statuses['']
|
||||
del statuses['']
|
||||
self.bridge.presenceUpdate(self.profiles[profile].jid.full(), show,
|
||||
int(priority), statuses, profile)
|
||||
|
||||
|
||||
def subscription(self, subs_type, raw_jid, profile_key='@DEFAULT@'):
|
||||
"""Called to manage subscription
|
||||
@param subs_type: subsciption type (cf RFC 3921)
|
||||
@param raw_jid: unicode entity's jid
|
||||
@param profile_key: profile"""
|
||||
profile = self.memory.getProfileName(profile_key)
|
||||
assert(profile)
|
||||
to_jid = jid.JID(raw_jid)
|
||||
debug (_('subsciption request [%(subs_type)s] for %(jid)s') % {'subs_type':subs_type, 'jid':to_jid.full()})
|
||||
if subs_type=="subscribe":
|
||||
self.profiles[profile].presence.subscribe(to_jid)
|
||||
elif subs_type=="subscribed":
|
||||
self.profiles[profile].presence.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 subs_type=="unsubscribe":
|
||||
self.profiles[profile].presence.unsubscribe(to_jid)
|
||||
elif subs_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) XXX: disabled (cf http://wokkel.ik.nu/ticket/56))
|
||||
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.bridge.contactDeleted(to, profile)
|
||||
|
||||
|
||||
## callbacks ##
|
||||
|
||||
def serverDisco(self, disco, profile):
|
||||
"""xep-0030 Discovery Protocol."""
|
||||
for feature in disco.features:
|
||||
debug (_("Feature found: %s"),feature)
|
||||
self.memory.addServerFeature(feature, profile)
|
||||
for cat, type in disco.identities:
|
||||
debug (_("Identity found: [%(category)s/%(type)s] %(identity)s") % {'category':cat, 'type':type, 'identity':disco.identities[(cat,type)]})
|
||||
|
||||
def serverDiscoItems(self, disco_result, disco_client, profile, initialized):
|
||||
"""xep-0030 Discovery Protocol.
|
||||
@param disco_result: result of the disco item querry
|
||||
@param disco_client: SatDiscoProtocol instance
|
||||
@param profile: profile of the user
|
||||
@param initialized: deferred which must be chained when everything is done"""
|
||||
def _check_entity_cb(result, entity, profile):
|
||||
for category, type in result.identities:
|
||||
debug (_('Identity added: (%(category)s,%(type)s) ==> %(entity)s [%(profile)s]') % {
|
||||
'category':category, 'type':type, 'entity':entity, 'profile':profile})
|
||||
self.memory.addServerIdentity(category, type, entity, profile)
|
||||
|
||||
defer_list = []
|
||||
for item in disco_result._items:
|
||||
defer_list.append(disco_client.requestInfo(item.entity).addCallback(_check_entity_cb, item.entity, profile))
|
||||
defer.DeferredList(defer_list).chainDeferred(initialized)
|
||||
|
||||
|
||||
## 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", "XMLUI")
|
||||
@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 [%(id)s]: %(success)s") % {'id': id, 'success':_("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 (%s)") % name)
|
||||
return None
|
||||
|
||||
#Menus management
|
||||
|
||||
def importMenu(self, category, name, callback, help_string = "", type = "NORMAL"):
|
||||
"""register a new menu for frontends
|
||||
@param category: category of the menu
|
||||
@param name: menu item entry
|
||||
@param callback: method to be called when menuitem is selected"""
|
||||
if self.menus.has_key((category,name)):
|
||||
error ("Want to register a menu which already existe")
|
||||
return
|
||||
self.menus[(category,name,type)] = {'callback':callback, 'help_string':help_string, 'type':type}
|
||||
|
||||
def getMenus(self):
|
||||
"""Return all menus registered"""
|
||||
return self.menus.keys()
|
||||
|
||||
def getMenuHelp(self, category, name, type="NORMAL"):
|
||||
"""return the help string of the menu"""
|
||||
try:
|
||||
return self.menus[(category,name,type)]['help_string']
|
||||
except KeyError:
|
||||
error (_("Trying to access an unknown menu"))
|
||||
return ""
|
||||
|
||||
def callMenu(self, category, name, type="NORMAL", profile_key='@DEFAULT@'):
|
||||
"""return the id of the action"""
|
||||
profile = self.memory.getProfileName(profile_key)
|
||||
if not profile_key:
|
||||
error (_('Non-exsitant profile'))
|
||||
return ""
|
||||
if self.menus.has_key((category,name,type)):
|
||||
id = self.get_next_id()
|
||||
self.menus[(category,name,type)]['callback'](id, profile)
|
||||
return id
|
||||
else:
|
||||
error (_("Trying to access an unknown menu (%(category)s/%(name)s/%(type)s)")%{'category':category, 'name':name,'type':type})
|
||||
return ""
|
||||
|
||||
|
||||
from sat.core.sat_main import SAT
|
||||
|
||||
application = service.Application('SàT')
|
||||
service = SAT()
|
||||
|
|
|
|||
Loading…
Reference in a new issue