core, quick frontend, plugin xep-0054, bridge: use of memory's entities data for vcard:

- bridge: new bridge method getEntityData and signal entityDataUpdated
	- core: entityDataUpdated signal sent on new string data
	- quick frontend: fixed avatars/vcard infos, fixed _replace in quick_contact_list
	- plugin xep-0054: dropped updatedValue signal, use entities data instead
This commit is contained in:
Goffi 2012-09-26 01:23:56 +02:00
commit 0f6de78a3b
8 changed files with 76 additions and 80 deletions

View file

@ -99,6 +99,9 @@ class DBusBridgeFrontend(BridgeFrontend):
def getContactsFromGroup(self, group, profile_key="@DEFAULT@"):
return self.db_core_iface.getContactsFromGroup(group, profile_key)
def getEntityData(self, jid, keys, profile):
return self.db_core_iface.getEntityData(jid, keys, profile)
def getHistory(self, from_jid, to_jid, limit, between=True, callback=None, errback=None):
return self.db_core_iface.getHistory(from_jid, to_jid, limit, between, reply_handler=callback, error_handler=lambda err:errback(err._dbus_error_name[len(const_ERROR_PREFIX)+1:]))

View file

@ -52,7 +52,7 @@ class QuickApp():
self.bridge.register("subscribe", self.subscribe)
self.bridge.register("paramUpdate", self.paramUpdate)
self.bridge.register("contactDeleted", self.contactDeleted)
self.bridge.register("updatedValue", self.updatedValue)
self.bridge.register("entityDataUpdated", self.entityDataUpdated)
self.bridge.register("askConfirmation", self.askConfirmation)
self.bridge.register("actionResult", self.actionResult)
self.bridge.register("actionResultExt", self.actionResult)
@ -165,6 +165,10 @@ class QuickApp():
priority = presences[contact][res][1]
statuses = presences[contact][res][2]
self.presenceUpdate(jabber_id, show, priority, statuses, profile)
data = self.bridge.getEntityData(contact, ['avatar','nick'], profile)
for key in ('avatar', 'nick'):
if key in data:
self.entityDataUpdated(contact, key, data[key], profile)
#The waiting subscription requests
waitingSub = self.bridge.getWaitingSub(profile)
@ -476,21 +480,19 @@ class QuickApp():
except KeyError:
pass
def updatedValue(self, name, data, profile):
#FIXME: to be removed
def entityDataUpdated(self, jid_str, key, value, profile):
if not self.check_profile(profile):
return
if name == "card_nick":
target = JID(data['jid'])
if target in self.contact_list:
#self.CM.update(target, 'nick', unicode(data['nick']))
self.contact_list._replace(target)
elif name == "card_avatar":
target = JID(data['jid'])
if target in self.contact_list:
filename = self.bridge.getAvatarFile(data['avatar'])
#self.CM.update(target, 'avatar', filename)
self.contact_list._replace(target)
jid = JID(jid_str)
if key == "nick":
if jid in self.contact_list:
self.contact_list.setCache(jid, 'nick', value)
self.contact_list._replace(jid)
elif key == "avatar":
if jid in self.contact_list:
filename = self.bridge.getAvatarFile(value)
self.contact_list.setCache(jid, 'avatar', filename)
self.contact_list._replace(jid)
def askConfirmation(self, type, id, data):
raise NotImplementedError

View file

@ -54,7 +54,7 @@ class QuickContactList():
raise NotImplementedError
def _replace(self, jid, groups=None, attributes=None):
if 'name' in attributes:
if attributes and 'name' in attributes:
self.setCache(jid, 'name', attributes['name'])
self.replace(jid, groups, attributes)
@ -65,7 +65,7 @@ class QuickContactList():
def remove(self, jid):
"""remove a contact from the list"""
raise NotImplementedError
def add(self, jid, param_groups=None):
"""add a contact to the list"""
raise NotImplementedError

View file

@ -139,6 +139,11 @@ class DbusObject(dbus.service.Object):
def disconnected(self, profile):
pass
@dbus.service.signal(const_INT_PREFIX+const_CORE_SUFFIX,
signature='ssss')
def entityDataUpdated(self, jid, name, value, profile):
pass
@dbus.service.signal(const_INT_PREFIX+const_CORE_SUFFIX,
signature='ssss')
def newAlert(self, message, title, alert_type, profile):
@ -169,11 +174,6 @@ class DbusObject(dbus.service.Object):
def subscribe(self, sub_type, entity_jid, profile):
pass
@dbus.service.signal(const_INT_PREFIX+const_CORE_SUFFIX,
signature='sa{ss}s')
def updatedValue(self, name, value, profile):
pass
### methods ###
@ -261,6 +261,12 @@ class DbusObject(dbus.service.Object):
def getContactsFromGroup(self, group, profile_key="@DEFAULT@"):
return self._callback("getContactsFromGroup", unicode(group), unicode(profile_key))
@dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
in_signature='sass', out_signature='a{ss}',
async_callbacks=None)
def getEntityData(self, jid, keys, profile):
return self._callback("getEntityData", unicode(jid), keys, unicode(profile))
@dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
in_signature='ssib', out_signature='a(dsss)',
async_callbacks=('callback', 'errback'))
@ -518,6 +524,9 @@ class DBusBridge(Bridge):
def disconnected(self, profile):
self.dbus_bridge.disconnected(profile)
def entityDataUpdated(self, jid, name, value, profile):
self.dbus_bridge.entityDataUpdated(jid, name, value, profile)
def newAlert(self, message, title, alert_type, profile):
self.dbus_bridge.newAlert(message, title, alert_type, profile)
@ -536,9 +545,6 @@ class DBusBridge(Bridge):
def subscribe(self, sub_type, entity_jid, profile):
self.dbus_bridge.subscribe(sub_type, entity_jid, profile)
def updatedValue(self, name, value, profile):
self.dbus_bridge.updatedValue(name, value, profile)
def register(self, name, callback):
debug("registering DBus bridge method [%s]", name)

View file

@ -136,14 +136,15 @@ doc_param_0=answer_type: Same as for [actionResult] but with the following addit
doc_param_1=id: Id of the action
doc_param_2=data: answer_type specific data
[updatedValue]
[entityDataUpdated]
type=signal
category=core
sig_in=sa{ss}s
doc=A value has been updated
doc_param_0=name: Name of the updated value
doc_param_1=value: New value
doc_param_2=%(doc_profile)s
sig_in=ssss
doc=An entity's data has been updated
doc_param_0=jid: entity's bare jid
doc_param_1=name: Name of the updated value
doc_param_2=value: New value
doc_param_3=%(doc_profile)s
;methods
@ -171,6 +172,18 @@ sig_in=
sig_out=as
doc=Get all profiles
[getEntityData]
type=method
category=core
sig_in=sass
sig_out=a{ss}
doc=Get data for an entity
doc_param_0=jid: entity's bare jid
doc_param_1=keys: list of keys to get
doc_param_2=%(doc_profile)s
doc_return=dictionary of asked key,
if key doesn't exist, the resulting dictionary will neither have the key
[createProfile]
deprecated=
type=method

View file

@ -118,6 +118,7 @@ class SAT(service.Service):
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("getEntityData", lambda _jid, keys, profile: self.memory.getEntityData(jid.JID(_jid),keys, profile))
self.bridge.register("createProfile", self.memory.createProfile)
self.bridge.register("asyncCreateProfile", self.memory.asyncCreateProfile)
self.bridge.register("deleteProfile", self.memory.deleteProfile)
@ -279,7 +280,7 @@ class SAT(service.Service):
if not client:
raise ProfileUnknownError(_('Asking contacts for a non-existant profile'))
ret = []
for item in client.roster.getItems(): #we get all item for client's roster
for item in client.roster.getItems(): #we get all items for client's roster
#and convert them to expected format
attr = client.roster.getAttributes(item)
ret.append([item.jid.userhost(), attr, item.groups])

View file

@ -720,6 +720,8 @@ class Memory:
raise exceptions.ProfileNotInCacheError
entity_data = self.entitiesCache[profile].setdefault(entity_jid.userhost(),{})
entity_data[key] = value
if isinstance(value,basestring):
self.host.bridge.entityDataUpdated(entity_jid.userhost(), key, value, profile)
def getEntityData(self, entity_jid, keys_list, profile_key):
"""Get a list of cached values for entity
@ -727,7 +729,7 @@ class Memory:
@param keys_list: list of keys to get, empty list for everything
@param profile_key: %(doc_profile_key)s
@return: dict withs values for each key in keys_list.
if there is no value of a given key, resultint dict will
if there is no value of a given key, resulting dict will
have nothing with that key nether
@raise: exceptions.UnknownEntityError if entity is not in cache
exceptions.ProfileNotInCacheError if profile is not in cache

View file

@ -20,11 +20,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
from logging import debug, info, error
from twisted.words.xish import domish
from twisted.internet import protocol, defer, threads, reactor
from twisted.internet import threads
from twisted.internet.defer import inlineCallbacks, returnValue
from twisted.words.protocols.jabber import client, jid, xmlstream
from twisted.words.protocols.jabber import error as jab_error
from twisted.words.protocols.jabber import jid
from twisted.words.protocols.jabber.xmlstream import IQ
import os.path
@ -34,8 +32,7 @@ from wokkel import disco, iwokkel
from base64 import b64decode
from hashlib import sha1
from time import sleep
from sat.memory.persistent import PersistentBinaryDict
from sat.core import exceptions
try:
from twisted.words.protocols.xmlstream import XMPPHandler
@ -64,6 +61,9 @@ PLUGIN_INFO = {
}
class XEP_0054():
#TODO: - check that nickname is ok
# - refactor the code/better use of Wokkel
# - get missing values
def __init__(self, host):
info(_("Plugin XEP_0054 initialization"))
@ -73,7 +73,6 @@ class XEP_0054():
os.makedirs(self.avatar_path)
host.bridge.addMethod("getCard", ".plugin", in_sign='ss', out_sign='s', method=self.getCard)
host.bridge.addMethod("getAvatarFile", ".plugin", in_sign='s', out_sign='s', method=self.getAvatarFile)
host.bridge.addMethod("getCardCache", ".plugin", in_sign='ss', out_sign='a{ss}', method=self.getCardCache)
def getHandler(self, profile):
return XEP_0054_handler(self)
@ -81,35 +80,29 @@ class XEP_0054():
def update_cache(self, jid, name, value, profile):
"""update cache value
- save value in memory in case of change
- send updatedValue signal if the value is new or updated
@param jid: jid of the owner of the vcard
@param name: name of the item which changed
@param value: new value of the item
@param profile: profile which received the update
"""
client = self.host.getClient(profile)
if not jid.userhost() in client._vcard_cache:
client._vcard_cache[jid.userhost()] = {}
cache = client._vcard_cache[jid.userhost()]
old_value = cache[name] if name in cache else None
if not old_value or value != old_value:
cache[name] = value
client._vcard_cache.force(jid.userhost()) #we force saving of data to storage
self.host.bridge.updatedValue('card_'+name, {'jid':jid.userhost(), name:value}, profile)
try:
cached = self.host.memory.getEntityData(jid, [name], profile)
except exceptions.UnknownEntityError:
cached = {}
if not name in cached or cached[name] != value:
self.host.memory.updateEntityData(jid, name, value, profile)
def get_cache(self, jid, name, profile):
"""return cached value for jid
@param jid: target contact
@param name: name of the value ('nick' or 'avatar')
@param profile: %(doc_profile)s
@return: wanted value or None"""
client = self.host.getClient(profile)
try:
return client._vcard_cache[jid.userhost()][name]
except KeyError:
data = self.host.memory.getEntityData(jid, [name], profile)
except exceptions.UnknownEntityError:
return None
return data.get(name)
def save_photo(self, photo_xml):
"""Parse a <PHOTO> elem and save the picture"""
@ -134,7 +127,6 @@ class XEP_0054():
"""Convert a VCard to a dict, and save binaries"""
debug (_("parsing vcard"))
dictionary = {}
d = defer.Deferred()
for elem in vcard.elements():
if elem.name == 'FN':
@ -189,7 +181,7 @@ class XEP_0054():
reg_request=IQ(xmlstream,'get')
reg_request["from"]=current_jid.full()
reg_request["to"] = to_jid.userhost()
query=reg_request.addElement('vCard', NS_VCARD)
reg_request.addElement('vCard', NS_VCARD)
reg_request.send(to_jid.userhost()).addCallbacks(self.vcard_ok, self.vcard_err, [profile])
return reg_request["id"]
@ -204,27 +196,6 @@ class XEP_0054():
return ""
return filename
def getCardCache(self, target, profile_key):
"""Request for cached values of profile
return the cached nickname and avatar if exists, else get VCard
@param target: target's jid
@param profile_key: %(doc_profile_key)s
"""
profile = self.host.memory.getProfileName(profile_key)
if not profile:
error(_("Profile not found"))
return {}
to_jid = jid.JID(target)
result = {}
nick = self.get_cache(to_jid, 'nick', profile)
if nick:
result['nick'] = nick
avatar = self.get_cache(to_jid, 'avatar', profile)
if avatar:
result['avatar'] = avatar
return result
class XEP_0054_handler(XMPPHandler):
implements(iwokkel.IDisco)
@ -234,8 +205,6 @@ class XEP_0054_handler(XMPPHandler):
self.host = plugin_parent.host
def connectionInitialized(self):
self.parent._vcard_cache = PersistentBinaryDict(NS_VCARD, self.parent.profile)
self.parent._vcard_cache.load()
self.xmlstream.addObserver(VCARD_UPDATE, self.update)
def getDiscoInfo(self, requestor, target, nodeIdentifier=''):