backend, frontends: callbacks refactoring:

- launchAction is now async, and return a dictionary for its result
    - no more action_id, actionResult* are deprecated
    - callback system is about to be unified
This commit is contained in:
Goffi 2013-12-24 15:19:08 +01:00
commit 069a250b04
6 changed files with 151 additions and 78 deletions

View file

@ -378,10 +378,10 @@ class DbusObject(dbus.service.Object):
return self._callback("isConnected", unicode(profile_key))
@dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
in_signature='sa{ss}s', out_signature='s',
async_callbacks=None)
def launchAction(self, action_type, data, profile_key="@DEFAULT@"):
return self._callback("launchAction", unicode(action_type), data, unicode(profile_key))
in_signature='sa{ss}s', out_signature='a{ss}',
async_callbacks=('callback', 'errback'))
def launchAction(self, callback_id, data, profile_key="@DEFAULT@", callback=None, errback=None):
return self._callback("launchAction", unicode(callback_id), data, unicode(profile_key), callback=callback, errback=errback)
@dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
in_signature='ssssi', out_signature='s',

View file

@ -104,6 +104,7 @@ doc_param_0=entity_jid: JID of the contact removed from roster
doc_param_1=%(doc_profile)s
[askConfirmation]
deprecated=
type=signal
category=core
sig_in=ssa{ss}s
@ -116,6 +117,7 @@ doc_param_2=data: conf_type dependent data
doc_param_3=%(doc_profile)s
[actionResult]
deprecated=
type=signal
category=core
sig_in=ssa{ss}s
@ -130,6 +132,7 @@ doc_param_2=data: answer_type specific data
doc_param_3=%(doc_profile)s
[actionResultExt]
deprecated=
type=signal
category=core
sig_in=ssa{sa{ss}}s
@ -533,18 +536,21 @@ doc_param_0=entity_jid: JID to remove from roster
doc_param_1=%(doc_profile_key)s
[launchAction]
async=
type=method
category=core
sig_in=sa{ss}s
sig_out=s
sig_out=a{ss}
param_2_default="@DEFAULT@"
doc=Launch a specific action
doc_param_0=action_type: type of the action which can be:
- button: A button is pushed
doc_param_1=data: action_type dependant data
doc=Launch a registred action
doc_param_0=callback_id: id of the registred callback
doc_param_1=data: optional data
doc_param_2=%(doc_profile_key)s
doc_return=dict where key can be:
- xmlui: a XMLUI need to be displayed
[confirmationAnswer]
deprecated=
type=method
category=core
sig_in=sba{ss}s

View file

@ -42,6 +42,7 @@ from sat.memory.memory import Memory
from sat.tools.xml_tools import tupleList2dataForm
from sat.tools.misc import TriggerManager
from glob import glob
from uuid import uuid4
try:
from twisted.words.protocols.xmlstream import XMPPHandler
@ -103,10 +104,8 @@ class SAT(service.Service):
CONST[name] = value
def __init__(self):
#TODO: standardize callback system
self.__general_cb_map = {} # callback called for general reasons (key = name)
self.__private_data = {} # used for internal callbacks (key = id)
self._cb_map = {} # map from callback_id to callbacks
self.__private_data = {} # used for internal callbacks (key = id) FIXME: to be removed
self.profiles = {}
self.plugins = {}
self.menus = {} # dynamic menus. key: (type, category, name), value: menu data (dictionnary)
@ -157,7 +156,7 @@ class SAT(service.Service):
self.bridge.register("updateContact", self.updateContact)
self.bridge.register("delContact", self.delContact)
self.bridge.register("isConnected", self.isConnected)
self.bridge.register("launchAction", self.launchAction)
self.bridge.register("launchAction", self.launchCallback)
self.bridge.register("confirmationAnswer", self.confirmationAnswer)
self.bridge.register("getProgress", self.getProgress)
self.bridge.register("getMenus", self.getMenus)
@ -388,7 +387,8 @@ class SAT(service.Service):
return next_id
def registerNewAccountCB(self, id, data, profile):
def registerNewAccountCB(self, data, profile):
# FIXME: to be removed/redone elsewhere
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)
@ -407,8 +407,6 @@ class SAT(service.Service):
{"message": _("Are you sure to register new account [%(user)s] to server %(server)s ?") % {'user': user, 'server': server, 'profile': profile}},
self.regisConfirmCB, profile)
print "===============+++++++++++ REGISTER NEW ACCOUNT++++++++++++++============"
print "id=", id
print "data=", data
def regisConfirmCB(self, id, accepted, data, profile):
print _("register Confirmation CB ! (%s)") % str(accepted)
@ -428,6 +426,7 @@ class SAT(service.Service):
@param fields: list of tuples (name, value)
@return: tuple: (id, deferred)
"""
# FIXME: to be removed
profile = self.memory.getProfileName(profile_key)
assert(profile)
@ -469,29 +468,6 @@ class SAT(service.Service):
return False
return self.profiles[profile].isConnected()
def launchAction(self, type, data, profile_key):
"""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 ##
@ -808,6 +784,7 @@ class SAT(service.Service):
@param data: data (depend of confirmation conf_type)
@param cb: callback called with the answer
"""
# FIXME: use XMLUI and *callback methods for dialog
client = self.getClient(profile)
if not client:
raise exceptions.ProfileUnknownError(_("Asking confirmation a non-existant profile"))
@ -863,24 +840,53 @@ class SAT(service.Service):
#debug("Requested progress for unknown progress_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 name not in self.__general_cb_map:
error(_("Trying to remove an unknow general callback"))
def registerCallback(self, callback, *args, **kwargs):
""" Register a callback.
Use with_data=True in kwargs if the callback use the optional data dict
use force_id=id to avoid generated id. Can lead to name conflict, avoid if possible
@param callback: any callable
@return: id of the registered callback
"""
callback_id = kwargs.pop('force_id', None)
if callback_id is None:
callback_id = str(uuid4())
else:
del self.__general_cb_map[name]
if callback_id in self._cb_map:
raise exceptions.ConflictError(_(u"id already registered"))
self._cb_map[callback_id] = (callback, args, kwargs)
return callback_id
def removeCallback(self, callback_id):
""" Remove a previously registered callback
@param callback_id: id returned by [registerCallback] """
del self._cb_map[callback_id]
def launchCallback(self, callback_id, data=None, profile_key="@NONE@"):
"""Launch a specific callback
@param callback_id: id of the action (callback) to launch
@param data: optional data
@profile_key: %(doc_profile_key)s
@return: a deferred which fire a dict where key can be:
- xmlui: a XMLUI need to be displayed
"""
profile = self.memory.getProfileName(profile_key)
if not profile:
raise exceptions.ProfileUnknownError(_('trying to launch action with a non-existant profile'))
def callGeneralCB(self, name, *args, **kwargs):
"""Call general function back"""
try:
return self.__general_cb_map[name](*args, **kwargs)
callback, args, kwargs = self._cb_map[callback_id]
except KeyError:
error(_("Trying to call unknown function (%s)") % name)
return None
raise exceptions.DataError("Unknown callback id")
if kwargs.get("with_data", False):
if data is None:
raise exceptions.DataError("Required data for this callback is missing")
args,kwargs=list(args)[:],kwargs.copy() # we don't want to modify the original (kw)args
args.insert(0, data)
kwargs["profile"] = profile
del kwargs["with_data"]
return defer.maybeDeferred(callback, *args, **kwargs)
#Menus management
@ -925,6 +931,7 @@ class SAT(service.Service):
@param profile_key: %(doc_profile_key)s
@return: XMLUI or empty string if it's a one shot menu
"""
# TODO: menus should use launchCallback
profile = self.memory.getProfileName(profile_key)
if not profile:
raise exceptions.ProfileUnknownError

View file

@ -212,7 +212,7 @@ class Params(object):
self.default_profile = None
self.params = {}
self.params_gen = {}
host.registerGeneralCB("registerNewAccount", host.registerNewAccountCB)
host.registerCallback(host.registerNewAccountCB, force_id="registerNewAccount")
def createProfile(self, profile):
"""Create a new profile

View file

@ -26,8 +26,6 @@ from twisted.words.protocols.jabber.xmlstream import IQ
import random
from wokkel import data_form
from sat.tools.xml_tools import dataForm2XML
from sat_frontends.tools.games import TarotCard
from time import time

View file

@ -219,7 +219,7 @@ class Field(object):
class XMLUI(object):
"""This class is used to create a user interface (form/window/parameters/etc) using SàT XML"""
def __init__(self, panel_type, layout="vertical", title=None):
def __init__(self, panel_type, layout="vertical", title=None, submit_id=None):
"""Init SàT XML Panel
@param panel_type: one of
- window (new window)
@ -232,6 +232,7 @@ class XMLUI(object):
(usually one for a label, the next for the element)
- tabs: elemens are in categories with tabs (notebook)
@param title: title or default if None
@param submit_id: callback id to call for panel_type we can submit (form, param)
"""
if not panel_type in ['window', 'form', 'param']:
error(_("Unknown panel type [%s]") % panel_type)
@ -244,6 +245,8 @@ class XMLUI(object):
top_element.setAttribute("type", panel_type)
if title:
top_element.setAttribute("title", title)
if submit_id:
top_element.setAttribute("submit", submit_id)
self.parentTabsLayout = None # used only we have 'tabs' layout
self.currentCategory = None # used only we have 'tabs' layout
self.currentLayout = None
@ -369,7 +372,7 @@ class XMLUI(object):
@fields_back: list of names of field to give back when pushing the button
"""
elem = self._createElem('button', name, self.currentLayout)
elem.setAttribute('callback_id', callback_id)
elem.setAttribute('callback', callback_id)
elem.setAttribute('value', value)
for field in fields_back:
fback_el = self.doc.createElement('field_back')