comparison src/memory/memory.py @ 432:31e8c48b5f5d

core: - memory refactoring (moved memory.py and sqlite.py from tools to memory) - private values are now stored in storage (currently sqlite database) and splitted in normal/binary storage. Normal storage is for string key/values, binary storage is for string key and any python data for value - PersistentDict and PersistentBinaryDict are sugar classes to manage the data in storage thought a python dictionary like class.
author Goffi <goffi@goffi.org>
date Sun, 20 Nov 2011 15:34:37 +0100
parents src/tools/memory.py@a4a9efadabfc
children 7099ea9c1b12
comparison
equal deleted inserted replaced
431:482b9bcf0ca4 432:31e8c48b5f5d
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3
4 """
5 SAT: a jabber client
6 Copyright (C) 2009, 2010, 2011 Jérôme Poisson (goffi@goffi.org)
7
8 This program is free software: you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation, either version 3 of the License, or
11 (at your option) any later version.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program. If not, see <http://www.gnu.org/licenses/>.
20 """
21
22 from __future__ import with_statement
23
24 import os.path
25 import time
26 import cPickle as pickle
27 from ConfigParser import SafeConfigParser, NoOptionError, NoSectionError
28 from xml.dom import minidom
29 from logging import debug, info, warning, error
30 from twisted.internet import defer
31 from twisted.words.protocols.jabber import jid
32 from sat.tools.xml_tools import paramsXml2xmlUI
33 from sat.core.default_config import default_config
34 from sat.memory.sqlite import SqliteStorage
35 from sat.memory.persistent import PersistentDict
36
37 SAVEFILE_PARAM_XML="/param" #xml parameters template
38 SAVEFILE_PRIVATE="/private" #file used to store misc values (mainly for plugins)
39 SAVEFILE_DATABASE="/sat.db"
40
41 class ProfileNotInCacheError(Exception):
42 pass
43
44 class ConnectedProfileError(Exception):
45 pass
46
47 class Params():
48 """This class manage parameters with xml"""
49 ### TODO: add desciption in params
50
51 #TODO: move Watched in a plugin
52 default_xml = u"""
53 <params>
54 <general>
55 </general>
56 <individual>
57 <category name="Connection" label="%(category_connection)s">
58 <param name="JabberID" value="name@example.org/SàT" type="string" />
59 <param name="Password" value="" type="password" />
60 <param name="Server" value="example.org" type="string" />
61 <param name="NewAccount" value="%(label_NewAccount)s" type="button" callback_id="registerNewAccount"/>
62 <param name="autoconnect" label="%(label_autoconnect)s" value="true" type="bool" />
63 <param name="autodisconnect" label="%(label_autodisconnect)s" value="false" type="bool" />
64 </category>
65 <category name="Misc" label="%(category_misc)s">
66 <param name="Watched" value="test@Jabber.goffi.int" type="string" />
67 </category>
68 </individual>
69 </params>
70 """ % {'category_connection': _("Connection"),
71 'label_NewAccount': _("Register new account"),
72 'label_autoconnect': _('Connect on frontend startup'),
73 'label_autodisconnect': _('Disconnect on frontend closure'),
74 'category_misc': _("Misc")
75 }
76
77 def load_default_params(self):
78 self.dom = minidom.parseString(Param.default_xml.encode('utf-8'))
79
80 def load_xml(self, file):
81 """Load parameters template from file"""
82 self.dom = minidom.parse(file)
83
84 def loadGenParams(self):
85 """Load general parameters data from storage
86 @return: deferred triggered once params are loaded"""
87 return self.storage.loadGenParams(self.params_gen)
88
89 def loadIndParams(self, profile, cache=None):
90 """Load individual parameters
91 set self.params cache or a temporary cache
92 @param profile: profile to load (*must exist*)
93 @param cache: if not None, will be used to store the value, as a short time cache
94 @return: deferred triggered once params are loaded"""
95 if cache == None:
96 self.params[profile] = {}
97 return self.storage.loadIndParams(self.params[profile] if cache==None else cache, profile)
98
99 def purgeProfile(self, profile):
100 """Remove cache data of a profile
101 @param profile: %(doc_profile)s"""
102 try:
103 del self.params[profile]
104 except KeyError:
105 error(_("Trying to purge cache of a profile not in memory: [%s]") % profile)
106
107 def save_xml(self, file):
108 """Save parameters template to xml file"""
109 with open(file, 'wb') as xml_file:
110 xml_file.write(self.dom.toxml('utf-8'))
111
112 def __init__(self, host, storage):
113 debug("Parameters init")
114 self.host = host
115 self.storage = storage
116 self.default_profile = None
117 self.params = {}
118 self.params_gen = {}
119 host.set_const('savefile_param_xml', SAVEFILE_PARAM_XML)
120 host.registerGeneralCB("registerNewAccount", host.registerNewAccountCB)
121
122 def createProfile(self, profile):
123 """Create a new profile
124 @param profile: profile of the profile"""
125 #FIXME: must be asynchronous and call the callback once the profile actually exists
126 if self.storage.hasProfile(profile):
127 info (_('The profile profile already exists'))
128 return True
129 if not self.host.trigger.point("ProfileCreation", profile):
130 return False
131 self.storage.createProfile(profile)
132 return False
133
134 def asyncCreateProfile(self, profile):
135 """Create a new profile
136 @param profile: name of the profile
137 @param callback: called when the profile actually exists in database and memory
138 @param errback: called with a string constant as parameter:
139 - CONFLICT: the profile already exists
140 - CANCELED: profile creation canceled
141 """
142 if self.storage.hasProfile(profile):
143 info (_('The profile name already exists'))
144 return defer.fail("CONFLICT")
145 if not self.host.trigger.point("ProfileCreation", profile):
146 return defer.fail("CANCEL")
147 return self.storage.createProfile(profile)
148
149
150 def deleteProfile(self, profile):
151 """Delete an existing profile
152 @param profile: name of the profile"""
153 #TODO: async equivalent, like for createProfile
154 if not self.storage.hasProfile(profile):
155 error(_('Trying to delete an unknown profile'))
156 return True
157 if self.host.isConnected(profile):
158 error(_("Trying to delete a connected profile"))
159 raise ConnectedProfileError
160 self.storage.deleteProfile(profile)
161 return False
162
163 def getProfileName(self, profile_key):
164 """return profile according to profile_key
165 @param profile_key: profile name or key which can be
166 @ALL@ for all profiles
167 @DEFAULT@ for default profile
168 @return: requested profile name or None if it doesn't exist"""
169 if profile_key=='@DEFAULT@':
170 if not self.params:
171 return ""
172 default = self.host.memory.getPrivate('Profile_default')
173 if not default or not default in self.params:
174 info(_('No default profile, returning first one')) #TODO: manage real default profile
175 default = self.storage.getProfilesList()[0]
176 self.host.memory.setPrivate('Profile_default', default)
177 return default #FIXME: temporary, must use real default value, and fallback to first one if it doesn't exists
178 if not self.storage.hasProfile(profile_key):
179 info (_('Trying to access an unknown profile'))
180 return ""
181 return profile_key
182
183 def __get_unique_node(self, parent, tag, name):
184 """return node with given tag
185 @param parent: parent of nodes to check (e.g. documentElement)
186 @param tag: tag to check (e.g. "category")
187 @param name: name to check (e.g. "JID")
188 @return: node if it exist or None
189 """
190 for node in parent.childNodes:
191 if node.nodeName == tag and node.getAttribute("name") == name:
192 #the node already exists
193 return node
194 #the node is new
195 return None
196
197 def importParams(self, xml):
198 """import xml in parameters, do nothing if the param already exist
199 @param xml: parameters in xml form"""
200 src_dom = minidom.parseString(xml.encode('utf-8'))
201
202 def import_node(tgt_parent, src_parent):
203 for child in src_parent.childNodes:
204 if child.nodeName == '#text':
205 continue
206 node = self.__get_unique_node(tgt_parent, child.nodeName, child.getAttribute("name"))
207 if not node: #The node is new
208 tgt_parent.appendChild(child)
209 else:
210 import_node(node, child)
211
212 import_node(self.dom.documentElement, src_dom.documentElement)
213
214 def __default_ok(self, value, name, category):
215 #FIXME: gof: will not work with individual parameters
216 self.setParam(name, value, category) #FIXME: better to set param xml value ???
217
218 def __default_ko(self, failure, name, category):
219 error (_("Can't determine default value for [%(category)s/%(name)s]: %(reason)s") % {'category':category, 'name':name, 'reason':str(failure.value)})
220
221 def setDefault(self, name, category, callback, errback=None):
222 """Set default value of parameter
223 'default_cb' attibute of parameter must be set to 'yes'
224 @param name: name of the parameter
225 @param category: category of the parameter
226 @param callback: must return a string with the value (use deferred if needed)
227 @param errback: must manage the error with args failure, name, category
228 """
229 #TODO: send signal param update if value changed
230 node = self.__getParamNode(name, category, '@ALL@')
231 if not node:
232 error(_("Requested param [%(name)s] in category [%(category)s] doesn't exist !") % {'name':name, 'category':category})
233 return
234 if node[1].getAttribute('default_cb') == 'yes':
235 del node[1].attributes['default_cb']
236 d = defer.maybeDeferred(callback)
237 d.addCallback(self.__default_ok, name, category)
238 d.addErrback(errback or self.__default_ko, name, category)
239
240 def getParamA(self, name, category, attr="value", profile_key="@DEFAULT@"):
241 """Helper method to get a specific attribute
242 @param name: name of the parameter
243 @param category: category of the parameter
244 @param attr: name of the attribute (default: "value")
245 @param profile: owner of the param (@ALL@ for everyone)
246
247 @return: attribute"""
248 node = self.__getParamNode(name, category)
249 if not node:
250 error(_("Requested param [%(name)s] in category [%(category)s] doesn't exist !") % {'name':name, 'category':category})
251 return ""
252
253 if node[0] == 'general':
254 value = self.__getParam(None, category, name, 'general')
255 return value if value!=None else node[1].getAttribute(attr)
256
257 assert(node[0] == 'individual')
258
259 profile = self.getProfileName(profile_key)
260 if not profile:
261 error(_('Requesting a param for an non-existant profile'))
262 return ""
263
264 if profile not in self.params:
265 error(_('Requesting synchronous param for not connected profile'))
266 return ""
267
268 if attr == "value":
269 value = self.__getParam(profile, category, name)
270 return value if value!=None else node[1].getAttribute(attr)
271 else:
272 return node[1].getAttribute(attr)
273
274 def asyncGetParamA(self, name, category, attr="value", profile_key="@DEFAULT@"):
275 """Helper method to get a specific attribute
276 @param name: name of the parameter
277 @param category: category of the parameter
278 @param attr: name of the attribute (default: "value")
279 @param profile: owner of the param (@ALL@ for everyone)"""
280 node = self.__getParamNode(name, category)
281 if not node:
282 error(_("Requested param [%(name)s] in category [%(category)s] doesn't exist !") % {'name':name, 'category':category})
283 return None
284
285 if node[0] == 'general':
286 value = self.__getParam(None, category, name, 'general')
287 return defer.succeed(value if value!=None else node[1].getAttribute(attr))
288
289 assert(node[0] == 'individual')
290
291 profile = self.getProfileName(profile_key)
292 if not profile:
293 error(_('Requesting a param for a non-existant profile'))
294 return defer.fail()
295
296 if attr != "value":
297 return defer.succeed(node[1].getAttribute(attr))
298 default = node[1].getAttribute(attr)
299 try:
300 value = self.__getParam(profile, category, name)
301 return defer.succeed(value if value!=None else default)
302 except ProfileNotInCacheError:
303 #We have to ask data to the storage manager
304 d = self.storage.getIndParam(category, name, profile)
305 return d.addCallback(lambda value: value if value!=None else default)
306
307 def __getParam(self, profile, category, name, type='individual', cache=None):
308 """Return the param, or None if it doesn't exist
309 @param profile: the profile name (not profile key, i.e. name and not something like @DEFAULT@)
310 @param category: param category
311 @param name: param name
312 @param type: "general" or "individual"
313 @param cache: temporary cache, to use when profile is not logged
314 @return: param value or None if it doesn't exist
315 """
316 if type == 'general':
317 if self.params_gen.has_key((category, name)):
318 return self.params_gen[(category, name)]
319 return None #This general param has the default value
320 assert (type == 'individual')
321 if self.params.has_key(profile):
322 cache = self.params[profile] # if profile is in main cache, we use it,
323 # ignoring the temporary cache
324 elif cache == None: #else we use the temporary cache if it exists, or raise an exception
325 raise ProfileNotInCacheError
326 if not cache.has_key((category, name)):
327 return None
328 return cache[(category, name)]
329
330 def __constructProfileXml(self, profile):
331 """Construct xml for asked profile, filling values when needed
332 /!\ as noticed in doc, don't forget to unlink the minidom.Document
333 @param profile: profile name (not key !)
334 @return: a deferred that fire a minidom.Document of the profile xml (cf warning above)
335 """
336 def constructProfile(ignore,profile_cache):
337 prof_xml = minidom.parseString('<params/>')
338 cache = {}
339
340 for type_node in self.dom.documentElement.childNodes:
341 if type_node.nodeName == 'general' or type_node.nodeName == 'individual': #we use all params, general and individual
342 for cat_node in type_node.childNodes:
343 if cat_node.nodeName == 'category':
344 category = cat_node.getAttribute('name')
345 if not cache.has_key(category):
346 cache[category] = dest_cat = cat_node.cloneNode(True) #we make a copy for the new xml
347 new_node = True
348 else:
349 dest_cat = cache[category]
350 new_node = False #It's not a new node, we will merge information
351 params = cat_node.getElementsByTagName("param")
352 dest_params = {}
353 for node in dest_cat.childNodes:
354 if node.nodeName != "param":
355 continue
356 dest_params[node.getAttribute('name')] = node
357
358 for param_node in params:
359 name = param_node.getAttribute('name')
360
361 if name not in dest_params:
362 dest_params[name] = param_node.cloneNode(True)
363 dest_cat.appendChild(dest_params[name])
364
365 profile_value = self.__getParam(profile, category, name, type_node.nodeName, cache=profile_cache)
366 if profile_value!=None: #there is a value for this profile, we must change the default
367 dest_params[name].setAttribute('value', profile_value)
368 if new_node:
369 prof_xml.documentElement.appendChild(dest_cat)
370 return prof_xml
371
372
373 if self.params.has_key(profile):
374 d = defer.succeed(None)
375 profile_cache = self.params[profile]
376 else:
377 #profile is not in cache, we load values in a short time cache
378 profile_cache = {}
379 d = self.loadIndParams(profile, profile_cache)
380
381 return d.addCallback(constructProfile, profile_cache)
382
383 def getParamsUI(self, profile_key):
384 """Return a SàT XMLUI for parameters, with given profile"""
385 profile = self.getProfileName(profile_key)
386 if not profile:
387 error(_("Asking params for inexistant profile"))
388 return ""
389 d = self.getParams(profile)
390 return d.addCallback(lambda param_xml:paramsXml2xmlUI(param_xml))
391
392 def getParams(self, profile_key):
393 """Construct xml for asked profile
394 Take params xml as skeleton"""
395 profile = self.getProfileName(profile_key)
396 if not profile:
397 error(_("Asking params for inexistant profile"))
398 return ""
399
400 def returnXML(prof_xml):
401 return_xml = prof_xml.toxml()
402 prof_xml.unlink()
403 return return_xml
404
405 return self.__constructProfileXml(profile).addCallback(returnXML)
406
407 def getParamsForCategory(self, category, profile_key):
408 """Return node's xml for selected category"""
409 #TODO: manage category of general type (without existant profile)
410 profile = self.getProfileName(profile_key)
411 if not profile:
412 error(_("Asking params for inexistant profile"))
413 return ""
414
415 def returnCategoryXml(prof_xml):
416 for node in prof_xml.getElementsByTagName("category"):
417 if node.nodeName == "category" and node.getAttribute("name") == category:
418 result = node.toxml()
419 prof_xml.unlink()
420 return result
421
422 prof_xml.unlink()
423 return "<category />"
424
425 d = self.__constructProfileXml(profile)
426 return d.addCallback(returnCategoryXml)
427
428 def __getParamNode(self, name, category, type="@ALL@"): #FIXME: is type useful ?
429 """Return a node from the param_xml
430 @param name: name of the node
431 @param category: category of the node
432 @type: keyword for search:
433 @ALL@ search everywhere
434 @GENERAL@ only search in general type
435 @INDIVIDUAL@ only search in individual type
436 @return: a tuple with the node type and the the node, or None if not found"""
437
438 for type_node in self.dom.documentElement.childNodes:
439 if ( ((type == "@ALL@" or type == "@GENERAL@") and type_node.nodeName == 'general')
440 or ( (type == "@ALL@" or type == "@INDIVIDUAL@") and type_node.nodeName == 'individual') ):
441 for node in type_node.getElementsByTagName('category'):
442 if node.getAttribute("name") == category:
443 params = node.getElementsByTagName("param")
444 for param in params:
445 if param.getAttribute("name") == name:
446 return (type_node.nodeName, param)
447 return None
448
449 def getParamsCategories(self):
450 """return the categories availables"""
451 categories=[]
452 for cat in self.dom.getElementsByTagName("category"):
453 name = cat.getAttribute("name")
454 if name not in categories:
455 categories.append(cat.getAttribute("name"))
456 return categories
457
458 def setParam(self, name, value, category, profile_key='@NONE@'):
459 """Set a parameter, return None if the parameter is not in param xml"""
460 #TODO: use different behaviour depending of the data type (e.g. password encrypted)
461 if profile_key!="@NONE@":
462 profile = self.getProfileName(profile_key)
463 if not profile:
464 error(_('Trying to set parameter for an unknown profile'))
465 return #TODO: throw an error
466
467 node = self.__getParamNode(name, category, '@ALL@')
468 if not node:
469 error(_('Requesting an unknown parameter (%(category)s/%(name)s)') % {'category':category, 'name':name})
470 return
471
472 if node[0] == 'general':
473 self.params_gen[(category, name)] = value
474 self.storage.setGenParam(category, name, value)
475 for profile in self.storage.getProfilesList():
476 if self.host.isConnected(profile):
477 self.host.bridge.paramUpdate(name, value, category, profile)
478 return
479
480 assert (node[0] == 'individual')
481 assert (profile_key != "@NONE@")
482
483 type = node[1].getAttribute("type")
484 if type=="button":
485 print "clique",node.toxml()
486 else:
487 if self.host.isConnected(profile): #key can not exists if profile is not connected
488 self.params[profile][(category, name)] = value
489 self.host.bridge.paramUpdate(name, value, category, profile)
490 self.storage.setIndParam(category, name, value, profile)
491
492 class Memory:
493 """This class manage all persistent informations"""
494
495 def __init__(self, host):
496 info (_("Memory manager init"))
497 self.initialized = defer.Deferred()
498 self.host = host
499 self.contacts={}
500 self.presenceStatus={}
501 self.lastResource={} #tmp, will be refactored with bdd integration
502 self.subscriptions={}
503 self.private={} #used to store private value
504 self.server_features={} #used to store discovery's informations
505 self.server_identities={}
506 self.config = self.parseMainConf()
507 host.set_const('savefile_private', SAVEFILE_PRIVATE)
508 host.set_const('savefile_database', SAVEFILE_DATABASE)
509 database_file = os.path.expanduser(self.getConfig('','local_dir')+
510 self.host.get_const('savefile_database'))
511 self.storage = SqliteStorage(database_file)
512 PersistentDict.storage = self.storage
513 self.params=Params(host, self.storage)
514 self.loadFiles()
515 d = self.storage.initialized.addCallback(self.load)
516 d.chainDeferred(self.initialized)
517
518 def parseMainConf(self):
519 """look for main .ini configuration file, and parse it"""
520 _config = SafeConfigParser(defaults=default_config)
521 try:
522 _config.read(map(os.path.expanduser, ['/etc/sat.conf', '~/sat.conf', '~/.sat.conf', 'sat.conf', '.sat.conf']))
523 except:
524 error (_("Can't read main config !"))
525
526 return _config
527
528 def getConfig(self, section, name):
529 """Get the main configuration option
530 @param section: section of the config file (None or '' for DEFAULT)
531 @param name: name of the option
532 """
533 if not section:
534 section='DEFAULT'
535 try:
536 _value = self.config.get(section, name)
537 except NoOptionError, NoSectionError:
538 _value = ''
539
540 return os.path.expanduser(_value) if name.endswith('_path') or name.endswith('_dir') else _value
541
542
543 def loadFiles(self):
544 """Load parameters and all memory things from file/db"""
545 param_file_xml = os.path.expanduser(self.getConfig('','local_dir')+
546 self.host.get_const('savefile_param_xml'))
547 private_file = os.path.expanduser(self.getConfig('','local_dir')+
548 self.host.get_const('savefile_private'))
549
550 #parameters template
551 if os.path.exists(param_file_xml):
552 try:
553 self.params.load_xml(param_file_xml)
554 debug(_("params template loaded"))
555 except:
556 error (_("Can't load params template !"))
557 self.params.load_default_params()
558 else:
559 info (_("No params template, using default template"))
560 self.params.load_default_params()
561
562 #private
563 if os.path.exists(private_file):
564 try:
565 with open(private_file, 'r') as private_pickle:
566 self.private=pickle.load(private_pickle)
567 debug(_("private values loaded"))
568 except:
569 error (_("Can't load private values !"))
570
571 def load(self, ignore):
572 """Load parameters and all memory things from db"""
573 #parameters data
574 return self.params.loadGenParams()
575
576 def loadIndividualParams(self, profile):
577 """Load individual parameters for a profile
578 @param profile: %(doc_profile)s"""
579 return self.params.loadIndParams(profile)
580
581 def purgeProfile(self, profile):
582 """Delete cache of data of profile
583 @param profile: %(doc_profile)s"""
584 self.params.purgeProfile(profile)
585
586 def save(self):
587 """Save parameters and all memory things to file/db"""
588 #TODO: need to encrypt files (at least passwords !) and set permissions
589 param_file_xml = os.path.expanduser(self.getConfig('','local_dir')+
590 self.host.get_const('savefile_param_xml'))
591 private_file = os.path.expanduser(self.getConfig('','local_dir')+
592 self.host.get_const('savefile_private'))
593
594 self.params.save_xml(param_file_xml)
595 debug(_("params saved"))
596 with open(private_file, 'w') as private_pickle:
597 pickle.dump(self.private, private_pickle)
598 debug(_("private values saved"))
599
600 def getProfilesList(self):
601 return self.storage.getProfilesList()
602
603
604 def getProfileName(self, profile_key):
605 """Return name of profile from keyword
606 @param profile_key: can be the profile name or a keywork (like @DEFAULT@)
607 @return: profile name or None if it doesn't exist"""
608 return self.params.getProfileName(profile_key)
609
610 def createProfile(self, name):
611 """Create a new profile
612 @param name: Profile name
613 """
614 return self.params.createProfile(name)
615
616 def asyncCreateProfile(self, name):
617 """Create a new profile
618 @param name: Profile name
619 """
620 return self.params.asyncCreateProfile(name)
621
622 def deleteProfile(self, name):
623 """Delete an existing profile
624 @param name: Name of the profile"""
625 return self.params.deleteProfile(name)
626
627 def addToHistory(self, from_jid, to_jid, message, timestamp=None, profile="@NONE@"):
628 assert(profile!="@NONE@")
629 return self.storage.addToHistory(from_jid, to_jid, message, timestamp, profile)
630
631 def getHistory(self, from_jid, to_jid, limit=0, between=True):
632 return self.storage.getHistory(jid.JID(from_jid), jid.JID(to_jid), limit, between)
633
634 def setPrivate(self, key, value):
635 """Save a misc private value (mainly useful for plugins)"""
636 self.private[key] = value
637
638 def getPrivate(self, key):
639 """return a private value
640 @param key: name of wanted value
641 @return: value or None if value don't exist"""
642 if self.private.has_key(key):
643 return self.private[key]
644 return None
645
646 def addServerFeature(self, feature, profile):
647 """Add a feature discovered from server
648 @param feature: string of the feature
649 @param profile: which profile is using this server ?"""
650 if not self.server_features.has_key(profile):
651 self.server_features[profile] = []
652 self.server_features[profile].append(feature)
653
654 def addServerIdentity(self, category, type, entity, profile):
655 """Add an identity discovered from server
656 @param feature: string of the feature
657 @param profile: which profile is using this server ?"""
658 if not self.server_identities.has_key(profile):
659 self.server_identities[profile] = {}
660 if not self.server_identities[profile].has_key((category, type)):
661 self.server_identities[profile][(category, type)]=set()
662 self.server_identities[profile][(category, type)].add(entity)
663
664 def getServerServiceEntities(self, category, type, profile):
665 """Return all available entities for a service"""
666 if self.server_identities.has_key(profile):
667 return self.server_identities[profile].get((category, type), set())
668 else:
669 return None
670
671 def getServerServiceEntity(self, category, type, profile):
672 """Helper method to get first available entity for a service"""
673 entities = self.getServerServiceEntities(category, type, profile)
674 if entities == None:
675 warning(_("Entities not available, maybe they haven't been asked to server yet ?"))
676 return None
677 else:
678 return list(entities)[0] if entities else None
679
680 def hasServerFeature(self, feature, profile_key):
681 """Tell if the server of the profile has the required feature"""
682 profile = self.getProfileName(profile_key)
683 if not profile:
684 error (_('Trying find server feature for a non-existant profile'))
685 return
686 assert(self.server_features.has_key(profile))
687 return feature in self.server_features[profile]
688
689
690 def addContact(self, contact_jid, attributes, groups, profile_key):
691 debug("Memory addContact: %s",contact_jid.userhost())
692 profile = self.getProfileName(profile_key)
693 if not profile:
694 error (_('Trying to add a contact to a non-existant profile'))
695 return
696 assert(isinstance(attributes,dict))
697 assert(isinstance(groups,set))
698 if not self.contacts.has_key(profile):
699 self.contacts[profile] = {}
700 self.contacts[profile][contact_jid.userhost()]=[attributes, groups]
701
702 def delContact(self, contact_jid, profile_key):
703 debug("Memory delContact: %s",contact_jid.userhost())
704 profile = self.getProfileName(profile_key)
705 if not profile:
706 error (_('Trying to delete a contact for a non-existant profile'))
707 return
708 if self.contacts.has_key(profile) and self.contacts[profile].has_key(contact_jid.userhost()):
709 del self.contacts[profile][contact_jid.userhost()]
710
711 def getContact(self, contact_jid, profile_key):
712 profile = self.getProfileName(profile_key)
713 if not profile:
714 error(_('Asking a contact for a non-existant profile'))
715 return None
716 if self.contacts.has_key(profile) and self.contacts[profile].has_key(contact_jid.userhost()):
717 return self.contacts[profile][contact_jid.userhost()]
718
719 def getContacts(self, profile_key):
720 """Return list of contacts for given profile
721 @param profile_key: profile key
722 @return list of [contact, attr, groups]"""
723 profile = self.getProfileName(profile_key)
724 if not profile:
725 error(_('Asking contacts for a non-existant profile'))
726 return []
727 ret=[]
728 if self.contacts.has_key(profile):
729 for contact in self.contacts[profile]:
730 attr, groups = self.contacts[profile][contact]
731 ret.append([contact, attr, groups ])
732 return ret
733
734 def getLastResource(self, contact, profile_key):
735 """Return the last resource used by a contact
736 @param contact: contact jid (unicode)
737 @param profile_key: %(doc_profile_key)s"""
738 profile = self.getProfileName(profile_key)
739 if not profile:
740 error(_('Asking contacts for a non-existant profile'))
741 return ""
742 try:
743 return self.lastResource[profile][jid.JID(contact).userhost()]
744 except:
745 return ""
746
747 def addPresenceStatus(self, contact_jid, show, priority, statuses, profile_key):
748 profile = self.getProfileName(profile_key)
749 if not profile:
750 error(_('Trying to add presence status to a non-existant profile'))
751 return
752 if not self.presenceStatus.has_key(profile):
753 self.presenceStatus[profile] = {}
754 if not self.lastResource.has_key(profile):
755 self.lastResource[profile] = {}
756 if not self.presenceStatus[profile].has_key(contact_jid.userhost()):
757 self.presenceStatus[profile][contact_jid.userhost()] = {}
758 resource = jid.parse(contact_jid.full())[2] or ''
759 if resource:
760 self.lastResource[profile][contact_jid.userhost()] = resource
761
762 self.presenceStatus[profile][contact_jid.userhost()][resource] = (show, priority, statuses)
763
764 def addWaitingSub(self, type, contact_jid, profile_key):
765 """Called when a subcription request is received"""
766 profile = self.getProfileName(profile_key)
767 assert(profile)
768 if not self.subscriptions.has_key(profile):
769 self.subscriptions[profile] = {}
770 self.subscriptions[profile][contact_jid] = type
771
772 def delWaitingSub(self, contact_jid, profile_key):
773 """Called when a subcription request is finished"""
774 profile = self.getProfileName(profile_key)
775 assert(profile)
776 if self.subscriptions.has_key(profile) and self.subscriptions[profile].has_key(contact_jid):
777 del self.subscriptions[profile][contact_jid]
778
779 def getWaitingSub(self, profile_key):
780 """Called to get a list of currently waiting subscription requests"""
781 profile = self.getProfileName(profile_key)
782 if not profile:
783 error(_('Asking waiting subscriptions for a non-existant profile'))
784 return {}
785 if not self.subscriptions.has_key(profile):
786 return {}
787
788 return self.subscriptions[profile]
789
790 def getPresenceStatus(self, profile_key):
791 profile = self.getProfileName(profile_key)
792 if not profile:
793 error(_('Asking contacts for a non-existant profile'))
794 return {}
795 if not self.presenceStatus.has_key(profile):
796 self.presenceStatus[profile] = {}
797 debug ("Memory getPresenceStatus (%s)", self.presenceStatus[profile])
798 return self.presenceStatus[profile]
799
800 def getParamA(self, name, category, attr="value", profile_key='@DEFAULT@'):
801 return self.params.getParamA(name, category, attr, profile_key)
802
803 def asyncGetParamA(self, name, category, attr="value", profile_key='@DEFAULT@'):
804 return self.params.asyncGetParamA(name, category, attr, profile_key)
805
806 def getParamsUI(self, profile_key):
807 return self.params.getParamsUI(profile_key)
808
809 def getParams(self, profile_key):
810 return self.params.getParams(profile_key)
811
812 def getParamsForCategory(self, category, profile_key):
813 return self.params.getParamsForCategory(category, profile_key)
814
815 def getParamsCategories(self):
816 return self.params.getParamsCategories()
817
818 def setParam(self, name, value, category, profile_key):
819 return self.params.setParam(name, value, category, profile_key)
820
821 def importParams(self, xml):
822 return self.params.importParams(xml)
823
824 def setDefault(self, name, category, callback, errback=None):
825 return self.params.setDefault(name, category, callback, errback)