comparison src/plugins/plugin_xep_0054.py @ 504:65ecbb473cbb

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
author Goffi <goffi@goffi.org>
date Wed, 26 Sep 2012 01:23:56 +0200
parents ee95ff721b68
children 2c4016921403
comparison
equal deleted inserted replaced
503:10119c2a9d33 504:65ecbb473cbb
18 You should have received a copy of the GNU Affero General Public License 18 You should have received a copy of the GNU Affero General Public License
19 along with this program. If not, see <http://www.gnu.org/licenses/>. 19 along with this program. If not, see <http://www.gnu.org/licenses/>.
20 """ 20 """
21 21
22 from logging import debug, info, error 22 from logging import debug, info, error
23 from twisted.words.xish import domish 23 from twisted.internet import threads
24 from twisted.internet import protocol, defer, threads, reactor
25 from twisted.internet.defer import inlineCallbacks, returnValue 24 from twisted.internet.defer import inlineCallbacks, returnValue
26 from twisted.words.protocols.jabber import client, jid, xmlstream 25 from twisted.words.protocols.jabber import jid
27 from twisted.words.protocols.jabber import error as jab_error
28 from twisted.words.protocols.jabber.xmlstream import IQ 26 from twisted.words.protocols.jabber.xmlstream import IQ
29 import os.path 27 import os.path
30 28
31 from zope.interface import implements 29 from zope.interface import implements
32 30
33 from wokkel import disco, iwokkel 31 from wokkel import disco, iwokkel
34 32
35 from base64 import b64decode 33 from base64 import b64decode
36 from hashlib import sha1 34 from hashlib import sha1
37 from time import sleep 35 from sat.core import exceptions
38 from sat.memory.persistent import PersistentBinaryDict
39 36
40 try: 37 try:
41 from twisted.words.protocols.xmlstream import XMPPHandler 38 from twisted.words.protocols.xmlstream import XMPPHandler
42 except ImportError: 39 except ImportError:
43 from wokkel.subprotocols import XMPPHandler 40 from wokkel.subprotocols import XMPPHandler
62 "handler": "yes", 59 "handler": "yes",
63 "description": _("""Implementation of vcard-temp""") 60 "description": _("""Implementation of vcard-temp""")
64 } 61 }
65 62
66 class XEP_0054(): 63 class XEP_0054():
64 #TODO: - check that nickname is ok
65 # - refactor the code/better use of Wokkel
66 # - get missing values
67 67
68 def __init__(self, host): 68 def __init__(self, host):
69 info(_("Plugin XEP_0054 initialization")) 69 info(_("Plugin XEP_0054 initialization"))
70 self.host = host 70 self.host = host
71 self.avatar_path = os.path.join(self.host.memory.getConfig('', 'local_dir'), AVATAR_PATH) 71 self.avatar_path = os.path.join(self.host.memory.getConfig('', 'local_dir'), AVATAR_PATH)
72 if not os.path.exists(self.avatar_path): 72 if not os.path.exists(self.avatar_path):
73 os.makedirs(self.avatar_path) 73 os.makedirs(self.avatar_path)
74 host.bridge.addMethod("getCard", ".plugin", in_sign='ss', out_sign='s', method=self.getCard) 74 host.bridge.addMethod("getCard", ".plugin", in_sign='ss', out_sign='s', method=self.getCard)
75 host.bridge.addMethod("getAvatarFile", ".plugin", in_sign='s', out_sign='s', method=self.getAvatarFile) 75 host.bridge.addMethod("getAvatarFile", ".plugin", in_sign='s', out_sign='s', method=self.getAvatarFile)
76 host.bridge.addMethod("getCardCache", ".plugin", in_sign='ss', out_sign='a{ss}', method=self.getCardCache)
77 76
78 def getHandler(self, profile): 77 def getHandler(self, profile):
79 return XEP_0054_handler(self) 78 return XEP_0054_handler(self)
80 79
81 def update_cache(self, jid, name, value, profile): 80 def update_cache(self, jid, name, value, profile):
82 """update cache value 81 """update cache value
83 - save value in memory in case of change 82 - save value in memory in case of change
84 - send updatedValue signal if the value is new or updated
85 @param jid: jid of the owner of the vcard 83 @param jid: jid of the owner of the vcard
86 @param name: name of the item which changed 84 @param name: name of the item which changed
87 @param value: new value of the item 85 @param value: new value of the item
88 @param profile: profile which received the update 86 @param profile: profile which received the update
89 """ 87 """
90 client = self.host.getClient(profile) 88 try:
91 if not jid.userhost() in client._vcard_cache: 89 cached = self.host.memory.getEntityData(jid, [name], profile)
92 client._vcard_cache[jid.userhost()] = {} 90 except exceptions.UnknownEntityError:
93 91 cached = {}
94 cache = client._vcard_cache[jid.userhost()] 92 if not name in cached or cached[name] != value:
95 old_value = cache[name] if name in cache else None 93 self.host.memory.updateEntityData(jid, name, value, profile)
96 if not old_value or value != old_value: 94
97 cache[name] = value
98 client._vcard_cache.force(jid.userhost()) #we force saving of data to storage
99 self.host.bridge.updatedValue('card_'+name, {'jid':jid.userhost(), name:value}, profile)
100
101 def get_cache(self, jid, name, profile): 95 def get_cache(self, jid, name, profile):
102 """return cached value for jid 96 """return cached value for jid
103 @param jid: target contact 97 @param jid: target contact
104 @param name: name of the value ('nick' or 'avatar') 98 @param name: name of the value ('nick' or 'avatar')
105 @param profile: %(doc_profile)s 99 @param profile: %(doc_profile)s
106 @return: wanted value or None""" 100 @return: wanted value or None"""
107 client = self.host.getClient(profile)
108 try: 101 try:
109 return client._vcard_cache[jid.userhost()][name] 102 data = self.host.memory.getEntityData(jid, [name], profile)
110 except KeyError: 103 except exceptions.UnknownEntityError:
111 return None 104 return None
112 105 return data.get(name)
113 106
114 def save_photo(self, photo_xml): 107 def save_photo(self, photo_xml):
115 """Parse a <PHOTO> elem and save the picture""" 108 """Parse a <PHOTO> elem and save the picture"""
116 for elem in photo_xml.elements(): 109 for elem in photo_xml.elements():
117 if elem.name == 'TYPE': 110 if elem.name == 'TYPE':
132 @inlineCallbacks 125 @inlineCallbacks
133 def vCard2Dict(self, vcard, target, profile): 126 def vCard2Dict(self, vcard, target, profile):
134 """Convert a VCard to a dict, and save binaries""" 127 """Convert a VCard to a dict, and save binaries"""
135 debug (_("parsing vcard")) 128 debug (_("parsing vcard"))
136 dictionary = {} 129 dictionary = {}
137 d = defer.Deferred()
138 130
139 for elem in vcard.elements(): 131 for elem in vcard.elements():
140 if elem.name == 'FN': 132 if elem.name == 'FN':
141 dictionary['fullname'] = unicode(elem) 133 dictionary['fullname'] = unicode(elem)
142 elif elem.name == 'NICKNAME': 134 elif elem.name == 'NICKNAME':
187 to_jid = jid.JID(target) 179 to_jid = jid.JID(target)
188 debug(_("Asking for %s's VCard") % to_jid.userhost()) 180 debug(_("Asking for %s's VCard") % to_jid.userhost())
189 reg_request=IQ(xmlstream,'get') 181 reg_request=IQ(xmlstream,'get')
190 reg_request["from"]=current_jid.full() 182 reg_request["from"]=current_jid.full()
191 reg_request["to"] = to_jid.userhost() 183 reg_request["to"] = to_jid.userhost()
192 query=reg_request.addElement('vCard', NS_VCARD) 184 reg_request.addElement('vCard', NS_VCARD)
193 reg_request.send(to_jid.userhost()).addCallbacks(self.vcard_ok, self.vcard_err, [profile]) 185 reg_request.send(to_jid.userhost()).addCallbacks(self.vcard_ok, self.vcard_err, [profile])
194 return reg_request["id"] 186 return reg_request["id"]
195 187
196 def getAvatarFile(self, hash): 188 def getAvatarFile(self, hash):
197 """Give the full path of avatar from hash 189 """Give the full path of avatar from hash
202 if not os.path.exists(filename): 194 if not os.path.exists(filename):
203 error (_("Asking for an uncached avatar [%s]") % hash) 195 error (_("Asking for an uncached avatar [%s]") % hash)
204 return "" 196 return ""
205 return filename 197 return filename
206 198
207 def getCardCache(self, target, profile_key):
208 """Request for cached values of profile
209 return the cached nickname and avatar if exists, else get VCard
210 @param target: target's jid
211 @param profile_key: %(doc_profile_key)s
212 """
213 profile = self.host.memory.getProfileName(profile_key)
214 if not profile:
215 error(_("Profile not found"))
216 return {}
217 to_jid = jid.JID(target)
218 result = {}
219 nick = self.get_cache(to_jid, 'nick', profile)
220 if nick:
221 result['nick'] = nick
222 avatar = self.get_cache(to_jid, 'avatar', profile)
223 if avatar:
224 result['avatar'] = avatar
225 return result
226
227
228 199
229 class XEP_0054_handler(XMPPHandler): 200 class XEP_0054_handler(XMPPHandler):
230 implements(iwokkel.IDisco) 201 implements(iwokkel.IDisco)
231 202
232 def __init__(self, plugin_parent): 203 def __init__(self, plugin_parent):
233 self.plugin_parent = plugin_parent 204 self.plugin_parent = plugin_parent
234 self.host = plugin_parent.host 205 self.host = plugin_parent.host
235 206
236 def connectionInitialized(self): 207 def connectionInitialized(self):
237 self.parent._vcard_cache = PersistentBinaryDict(NS_VCARD, self.parent.profile)
238 self.parent._vcard_cache.load()
239 self.xmlstream.addObserver(VCARD_UPDATE, self.update) 208 self.xmlstream.addObserver(VCARD_UPDATE, self.update)
240 209
241 def getDiscoInfo(self, requestor, target, nodeIdentifier=''): 210 def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
242 return [disco.DiscoFeature(NS_VCARD)] 211 return [disco.DiscoFeature(NS_VCARD)]
243 212