Page MenuHomePhabricator

No OneTemporary

diff --git a/sipsimple/xcap/__init__.py b/sipsimple/xcap/__init__.py
index 1d12bbc0..f650e056 100644
--- a/sipsimple/xcap/__init__.py
+++ b/sipsimple/xcap/__init__.py
@@ -1,3323 +1,3333 @@
# Copyright (C) 2010 AG Projects. See LICENSE for details.
#
"""High-level management of XCAP documents based on OMA specifications."""
__all__ = ['Contact', 'Service', 'CatchAllCondition', 'DomainCondition', 'DomainExcepton', 'UserException', 'Policy', 'Class', 'OccurenceID',
'DeviceID', 'ServiceURI', 'ServiceURIScheme', 'PresencePolicy', 'DialoginfoPolicy', 'Icon', 'OfflineStatus', 'XCAPManager']
import base64
import cPickle
import os
import random
import re
import string
from collections import deque
from copy import deepcopy
from datetime import datetime
from itertools import chain, count
from time import time
from urllib2 import URLError
from application.notification import IObserver, NotificationCenter
from application.python.util import Null, Singleton
from application.system import host, unlink
from eventlet import api, coros
from eventlet.green.httplib import BadStatusLine
from xcaplib.green import XCAPClient
from xcaplib.error import HTTPError
from zope.interface import implements
from sipsimple.account import ContactURI
from sipsimple.configuration.settings import SIPSimpleSettings
from sipsimple.core import ContactHeader, FromHeader, RouteHeader, ToHeader, SIPCoreError, SIPURI, Subscription
from sipsimple.lookup import DNSLookup, DNSLookupError
from sipsimple.payloads import ParserError
from sipsimple.payloads import dialogrules, extensions, omapolicy, policy as common_policy, prescontent, presdm, presrules, resourcelists, rlsservices, rpid, xcapcaps, xcapdiff
from sipsimple.util import All, Any, Command, TimestampedNotificationData, limit, makedirs, run_in_green_thread, run_in_twisted_thread
from sipsimple.xcap.uri import XCAPURI
class XCAPError(Exception): pass
class FetchRequiredError(XCAPError): pass
class SubscriptionError(Exception):
def __init__(self, error, timeout):
self.error = error
self.timeout = timeout
class SIPSubscriptionDidFail(Exception):
def __init__(self, data):
self.__dict__.update(data)
class Document(object):
name = None
payload_type = None
application = None
global_tree = None
filename = None
cached = True
def __init__(self):
self.client = None
self.cache_directory = None
self.content = None
self.etag = None
self.fetch_time = datetime.fromtimestamp(0)
self.dirty = False
self.supported = True
@property
def relative_uri(self):
uri = self.uri[len(self.client.root):]
if uri.startswith('/'):
uri = uri[1:]
return uri
@property
def uri(self):
return self.client.get_url(self.application, None, globaltree=self.global_tree, filename=self.filename)
def load_from_cache(self, cache_directory):
if not self.cached:
return
self.cache_directory = cache_directory
cache_filename = os.path.join(cache_directory, '%s.xml' % self.name)
try:
cache_file = open(cache_filename, 'rb')
self.etag = cache_file.readline().strip() or None
self.content = self.payload_type.parse(cache_file)
except (IOError, OSError, ParserError):
self.etag = None
self.content = None
def initialize(self, client, server_caps):
self.client = client
self.supported = self.application in server_caps.auids
def reset(self):
self.content = None
self.etag = None
def fetch(self):
try:
document = self.client.get(self.application, etagnot=self.etag, globaltree=self.global_tree, headers={'Accept': self.payload_type.content_type}, filename=self.filename)
self.content = self.payload_type.parse(document)
self.etag = document.etag
except BadStatusLine, e:
raise XCAPError("failed to fetch %s document: %s" % (self.name, e))
except URLError, e:
raise XCAPError("failed to fetch %s document: %s" % (self.name, e))
except HTTPError, e:
if e.status == 404: # Not Found
self.reset()
self.fetch_time = datetime.utcnow()
if self.cached:
unlink(os.path.join(self.cache_directory, '%s.xml' % self.name))
elif e.status != 304: # Other than Not Modified:
raise XCAPError("failed to fetch %s document: %s" % (self.name, e))
except ParserError, e:
raise XCAPError("failed to parse %s document: %s" % (self.name, e))
else:
self.fetch_time = datetime.utcnow()
if self.cached:
try:
makedirs(self.cache_directory)
file = open(os.path.join(self.cache_directory, '%s.xml' % self.name), 'wb')
file.write('%s\n' % (self.etag or ''))
file.write(document)
file.close()
except (IOError, OSError):
pass
def update(self):
if not self.dirty:
return
data = self.content.toxml() if self.content is not None else None
try:
kw = dict(etag=self.etag) if self.etag is not None else dict(etagnot='*')
if data is not None:
response = self.client.put(self.application, data, globaltree=self.global_tree, filename=self.filename, headers={'Content-Type': self.payload_type.content_type}, **kw)
else:
response = self.client.delete(self.application, data, globaltree=self.global_tree, filename=self.filename, **kw)
except BadStatusLine, e:
raise XCAPError("failed to update %s document: %s" % (self.name, e))
except URLError, e:
raise XCAPError("failed to update %s document: %s" % (self.name, e))
except HTTPError, e:
if e.status == 412: # Precondition Failed
raise FetchRequiredError("document %s was modified externally" % self.name)
else:
raise XCAPError("failed to update %s document: %s" % (self.name, e))
else:
self.etag = response.etag if data is not None else None
self.dirty = False
if self.cached:
try:
if data is not None:
makedirs(self.cache_directory)
file = open(os.path.join(self.cache_directory, '%s.xml' % self.name), 'wb')
file.write('%s\n' % self.etag)
file.write(data)
file.close()
else:
unlink(os.path.join(self.cache_directory, '%s.xml' % self.name))
except (IOError, OSError):
pass
class DialogRulesDocument(Document):
name = 'dialog-rules'
payload_type = dialogrules.DialogRules
application = 'org.openxcap.dialog-rules'
global_tree = False
filename = 'index'
class PresRulesDocument(Document):
name = 'pres-rules'
payload_type = presrules.PresRules
application = 'pres-rules'
global_tree = False
filename = 'index'
def initialize(self, client, server_caps):
self.application = 'org.openmobilealliance.pres-rules' if 'org.openmobilealliance.pres-rules' in server_caps.auids else 'pres-rules'
super(PresRulesDocument, self).initialize(client, server_caps)
class ResourceListsDocument(Document):
name = 'resource-lists'
payload_type = resourcelists.ResourceLists
application = 'resource-lists'
global_tree = False
filename = 'index'
class RLSServicesDocument(Document):
name = 'rls-services'
payload_type = rlsservices.RLSServices
application = 'rls-services'
global_tree = False
filename = 'index'
class XCAPCapsDocument(Document):
name = 'xcap-caps'
payload_type = xcapcaps.XCAPCapabilities
application = 'xcap-caps'
global_tree = True
filename = 'index'
cached = False
def initialize(self, client):
self.client = client
class StatusIconDocument(Document):
name = 'status-icon'
payload_type = prescontent.PresenceContent
application = 'org.openmobilealliance.pres-content'
global_tree = False
filename = 'oma_status-icon/index'
def __init__(self):
super(StatusIconDocument, self).__init__()
self.alternative_location = None
def fetch(self):
try:
document = self.client.get(self.application, etagnot=self.etag, globaltree=self.global_tree, headers={'Accept': self.payload_type.content_type}, filename=self.filename)
self.content = self.payload_type.parse(document)
self.etag = document.etag
except URLError, e:
raise XCAPError("failed to fetch %s document: %s" % (self.name, e))
except HTTPError, e:
if e.status == 404: # Not Found
self.reset()
self.fetch_time = datetime.utcnow()
if self.cached:
unlink(os.path.join(self.cache_directory, '%s.xml' % self.name))
elif e.status == 304: # Not Modified:
self.alternative_location = e.headers.get('X-AGP-Alternative-Location', None)
else:
raise XCAPError("failed to fetch %s document: %s" % (self.name, e))
except ParserError, e:
raise XCAPError("failed to parse %s document: %s" % (self.name, e))
else:
self.fetch_time = datetime.utcnow()
self.alternative_location = e.headers.get('X-AGP-Alternative-Location', None)
if self.cached:
try:
makedirs(self.cache_directory)
file = open(os.path.join(self.cache_directory, '%s.xml' % self.name), 'wb')
file.write('%s\n' % (self.etag or ''))
file.write(document)
file.close()
except (IOError, OSError):
pass
def update(self):
if not self.dirty:
return
data = self.content.toxml() if self.content is not None else None
try:
kw = dict(etag=self.etag) if self.etag is not None else dict(etagnot='*')
if data is not None:
response = self.client.put(self.application, data, globaltree=self.global_tree, filename=self.filename, headers={'Content-Type': self.payload_type.content_type}, **kw)
else:
response = self.client.delete(self.application, data, globaltree=self.global_tree, filename=self.filename, **kw)
except BadStatusLine, e:
raise XCAPError("failed to update %s document: %s" % (self.name, e))
except URLError, e:
raise XCAPError("failed to update %s document: %s" % (self.name, e))
except HTTPError, e:
if e.status == 412: # Precondition Failed
raise FetchRequiredError("document %s was modified externally" % self.name)
else:
raise XCAPError("failed to update %s document: %s" % (self.name, e))
else:
self.alternative_location = response.headers.get('X-AGP-Alternative-Location', None)
self.etag = response.etag if data is not None else None
self.dirty = False
if self.cached:
try:
if data is not None:
makedirs(self.cache_directory)
file = open(os.path.join(self.cache_directory, '%s.xml' % self.name), 'wb')
file.write('%s\n' % self.etag)
file.write(data)
file.close()
else:
unlink(os.path.join(self.cache_directory, '%s.xml' % self.name))
except (IOError, OSError):
pass
def reset(self):
super(StatusIconDocument, self).reset()
self.alternative_location = None
class PIDFManipulationDocument(Document):
name = 'pidf-manipulation'
payload_type = presdm.PIDF
application = 'pidf-manipulation'
global_tree = False
filename = 'index'
class Contact(object):
def __init__(self, name, uri, group, **attributes):
self.name = name
self.uri = uri
self.group = group
self.attributes = attributes
self.presence_policies = None
self.dialoginfo_policies = None
self.subscribe_to_presence = True
self.subscribe_to_dialoginfo = True
def __eq__(self, other):
return (self.name == other.name and self.uri == other.uri and self.group == other.group and self.attributes == other.attributes and
self.presence_policies == other.presence_policies and self.dialoginfo_policies == other.dialoginfo_policies and
self.subscribe_to_presence == other.subscribe_to_presence and self.subscribe_to_dialoginfo == other.subscribe_to_dialoginfo)
def __hash__(self):
return hash(self.uri)
class Service(object):
def __init__(self, uri, packages, entries=None):
self.uri = uri
self.packages = packages
self.entries = entries or []
class CatchAllCondition(object):
def __init__(self, exceptions=None):
self.exceptions = exceptions or []
def __eq__(self, other):
return self.exceptions == other.exceptions
class CatchNoneCondition(object):
def __eq__(self, other):
return self.__class__.__name__ == other.__class__.__name__
class DomainCondition(object):
def __init__(self, domain, exceptions=None):
self.domain = domain
self.exceptions = exceptions or []
def __eq__(self, other):
return self.domain == other.domain and self.exceptions == other.exceptions
class DomainException(object):
def __init__(self, domain):
self.domain = domain
def __eq__(self, other):
return self.domain == other.domain
class UserException(object):
def __init__(self, uri):
self.uri = uri
def __eq__(self, other):
return self.uri == other.uri
class Policy(object):
def __init__(self, id, action, name=None, validity=None, sphere=None, multi_identity_conditions=None):
self.id = id
self.action = action
self.name = name
self.validity = validity
self.sphere = sphere
self.multi_identity_conditions = multi_identity_conditions
def check_validity(self, timestamp, sphere=Any):
if sphere is not Any and sphere != self.sphere:
return False
if self.validity is None:
return True
for from_timestamp, until_timestamp in self.validity:
if from_timestamp <= timestamp <= until_timestamp:
return True
else:
return False
def __eq__(self, other):
return (self.id == other.id and self.action == other.action and self.name == other.name and self.validity == other.validity and
self.sphere == other.sphere and self.multi_identity_conditions == other.multi_identity_conditions)
def __hash__(self):
return hash(self.id)
# elements to represent provide_(devices|persons|services) components for pres-rules
class Class(unicode): pass
class OccurenceID(unicode): pass
class DeviceID(unicode): pass
class ServiceURI(unicode): pass
class ServiceURIScheme(unicode): pass
class PresencePolicy(Policy):
def __init__(self, id, action, name=None, validity=None, sphere=None, multi_identity_conditions=None):
super(PresencePolicy, self).__init__(id, action, name, validity, sphere, multi_identity_conditions)
self.provide_devices = All
self.provide_persons = All
self.provide_services = All
self.provide_activities = None
self.provide_class = None
self.provide_device_id = None
self.provide_mood = None
self.provide_place_is = None
self.provide_place_type = None
self.provide_privacy = None
self.provide_relationship = None
self.provide_status_icon = None
self.provide_sphere = None
self.provide_time_offset = None
self.provide_user_input = None
self.provide_unknown_attributes = None
self.provide_all_attributes = True
def __eq__(self, other):
return (super(PresencePolicy, self).__eq__(other) and self.provide_devices == other.provide_devices and
self.provide_persons == other.provide_persons and self.provide_services == other.provide_services and
self.provide_activities == other.provide_activities and self.provide_class == other.provide_class and
self.provide_device_id == other.provide_device_id and self.provide_mood == other.provide_mood and
self.provide_place_is == other.provide_place_is and self.provide_place_type == other.provide_place_type and
self.provide_privacy == other.provide_privacy and self.provide_relationship == other.provide_relationship and
self.provide_status_icon == other.provide_status_icon and self.provide_sphere == other.provide_sphere and
self.provide_time_offset == other.provide_time_offset and self.provide_user_input == other.provide_user_input and
self.provide_unknown_attributes == other.provide_unknown_attributes and self.provide_all_attributes == other.provide_all_attributes)
class DialoginfoPolicy(Policy):
pass
class Icon(object):
def __init__(self, data, mime_type, description=None, location=None):
self.data = data
self.mime_type = mime_type
self.description = description
self.location = location
class OfflineStatus(object):
def __init__(self, activity=None, note=None):
self.activity = activity
self.note = note
class Operation(object):
name = None
documents = []
params = []
def __init__(self, **params):
self.__dict__.update(dict.fromkeys(self.params))
self.__dict__.update(params)
self.applied = False
self.timestamp = datetime.utcnow()
class NormalizeOperation(Operation):
name = 'normalize'
documents = ['dialog-rules', 'pres-rules', 'resource-lists', 'rls-services']
params = []
class AddGroupOperation(Operation):
name = 'add_group'
documents = ['resource-lists']
params = ['group']
class RenameGroupOperation(Operation):
name = 'rename_group'
documents = ['resource-lists']
params = ['old_name', 'new_name']
class RemoveGroupOperation(Operation):
name = 'remove_group'
documents = ['resource-lists']
params = ['group']
class AddContactOperation(Operation):
name = 'add_contact'
documents = ['dialog-rules', 'pres-rules', 'resource-lists', 'rls-services']
params = ['contact']
class UpdateContactOperation(Operation):
name = 'update_contact'
documents = ['dialog-rules', 'pres-rules', 'resource-lists', 'rls-services']
params = ['contact', 'attributes']
class RemoveContactOperation(Operation):
name = 'remove_contact'
documents = ['dialog-rules', 'pres-rules', 'resource-lists', 'rls-services']
params = ['contact']
class AddPresencePolicyOperation(Operation):
name = 'add_presence_policy'
documents = ['pres-rules']
params = ['policy']
class UpdatePresencePolicyOperation(Operation):
name = 'update_presence_policy'
documents = ['pres-rules']
params = ['policy', 'attributes']
class RemovePresencePolicyOperation(Operation):
name = 'remove_presence_policy'
documents = ['pres-rules']
params = ['policy']
class AddDialoginfoPolicyOperation(Operation):
name = 'add_dialoginfo_policy'
documents = ['pres-rules']
params = ['policy']
class UpdateDialoginfoPolicyOperation(Operation):
name = 'update_dialoginfo_policy'
documents = ['pres-rules']
params = ['policy', 'attributes']
class RemoveDialoginfoPolicyOperation(Operation):
name = 'remove_dialoginfo_policy'
documents = ['pres-rules']
params = ['policy']
class SetStatusIconOperation(Operation):
name = 'set_status_icon'
documents = ['status-icon']
params = ['icon']
class SetOfflineStatusOperation(Operation):
name = 'set_offline_status'
documents = ['pidf-manipulation']
params = ['status']
class XCAPManager(object):
__metaclass__ = Singleton
implements(IObserver)
def __init__(self, account):
self.account = account
self.cache_directory = None
self.client = None
self.journal = []
self.last_fetch_time = datetime.fromtimestamp(0)
self.not_executed_fetch = None
self.oma_compliant = False
self.state = 'stopped'
self.subscribe_interval = 0
self.subscription = None
self.subscription_timer = None
self.timer = None
self.transaction_level = 0
+ self._command_greenlet = None
self._command_channel = coros.queue()
self._data_channel = coros.queue()
+ self._current_command = None
self.server_caps = XCAPCapsDocument()
self.dialog_rules = DialogRulesDocument()
self.pidf_manipulation = PIDFManipulationDocument()
self.pres_rules = PresRulesDocument()
self.resource_lists = ResourceListsDocument()
self.rls_services = RLSServicesDocument()
self.status_icon = StatusIconDocument()
def _get_state(self):
return self.__dict__['state']
def _set_state(self, value):
old_value = self.__dict__.get('state', Null)
self.__dict__['state'] = value
if old_value != value and old_value is not Null:
notification_center = NotificationCenter()
notification_center.post_notification('XCAPManagerDidChangeState', sender=self, data=TimestampedNotificationData(prev_state=old_value, state=value))
state = property(_get_state, _set_state)
del _get_state, _set_state
@property
def cached_documents(self):
return [document for document in self.dialog_rules, self.pidf_manipulation, self.pres_rules, self.resource_lists, self.rls_services, self.status_icon, self.server_caps if document.cached]
@property
def documents(self):
return [self.dialog_rules, self.pidf_manipulation, self.pres_rules, self.resource_lists, self.rls_services, self.status_icon]
@property
def document_names(self):
return [document.name for document in self.documents]
@property
def journal_filename(self):
return os.path.join(self.cache_directory, self.account.id, 'journal')
@property
def contactlist_supported(self):
return self.resource_lists.supported and self.rls_services.supported
@property
def presence_policies_supported(self):
return self.pres_rules.supported
@property
def dialoginfo_policies_supported(self):
return self.dialog_rules.supported
@property
def status_icon_supported(self):
return self.status_icon.supported
@property
def offline_status_supported(self):
return self.pidf_manipulation.supported
@property
def namespaces(self):
return dict((document.application, document.payload_type._xml_namespace) for document in self.documents)
@property
def xcap_root(self):
return self.client.root if self.client else None
def load(self, cache_directory):
"""
Initializes the XCAP manager, by loading any saved data from disk. Needs
to be called before any other method and in a green thread.
"""
if not cache_directory:
raise RuntimeError("A cache directory for XCAP documents must me specified")
self.cache_directory = cache_directory
cache_directory = os.path.join(cache_directory, self.account.id)
for document in self.cached_documents:
document.load_from_cache(cache_directory)
try:
self.journal = cPickle.load(open(self.journal_filename, 'rb'))
except (IOError, OSError, cPickle.UnpicklingError):
self.journal = []
else:
for operation in self.journal:
operation.applied = False
self._run()
def start(self):
"""
Starts the XCAP manager. This method needs to be called in a green
thread.
"""
if self.state not in ('stopping', 'stopped'):
return
notification_center = NotificationCenter()
notification_center.post_notification('XCAPManagerWillStart', sender=self, data=TimestampedNotificationData())
notification_center.add_observer(self, sender=self.account)
notification_center.add_observer(self, sender=SIPSimpleSettings())
notification_center.add_observer(self, name='SystemIPAddressDidChange')
self._command_channel.send(Command('initialize'))
notification_center.post_notification('XCAPManagerDidStart', sender=self, data=TimestampedNotificationData())
def stop(self):
"""
Stops the XCAP manager. This method blocks until all the operations are
stopped and needs to be called in a green thread.
"""
if self.state in ('stopping', 'stopped'):
return
notification_center = NotificationCenter()
notification_center.post_notification('XCAPManagerWillEnd', sender=self, data=TimestampedNotificationData())
self.state = 'stopping'
notification_center.remove_observer(self, sender=self.account)
notification_center.remove_observer(self, sender=SIPSimpleSettings())
notification_center.remove_observer(self, name='SystemIPAddressDidChange')
if self.timer is not None and self.timer.active():
self.timer.cancel()
if self.subscription_timer is not None and self.subscription_timer.active():
self.subscription_timer.cancel()
+ if self._current_command is not None and self._current_command.name in ('subscribe'):
+ api.kill(self._command_greenlet, api.GreenletExit())
+ self._run()
command = Command('unsubscribe')
self._command_channel.send(command)
command.wait()
if self.state == 'stopping':
self.client = None
self.state = 'stopped'
self._save_journal()
notification_center.post_notification('XCAPManagerDidEnd', sender=self, data=TimestampedNotificationData())
@run_in_twisted_thread
def start_transaction(self):
self.transaction_level += 1
@run_in_twisted_thread
def commit_transaction(self):
if self.transaction_level == 0:
return
self.transaction_level -= 1
if self.transaction_level == 0 and self.journal:
self._command_channel.send(Command('update'))
@run_in_twisted_thread
def add_group(self, group):
operation = AddGroupOperation(group=group)
self.journal.append(operation)
if self.transaction_level == 0:
self._command_channel.send(Command('update'))
@run_in_twisted_thread
def rename_group(self, old_name, new_name):
operation = RenameGroupOperation(old_name=old_name, new_name=new_name)
self.journal.append(operation)
if self.transaction_level == 0:
self._command_channel.send(Command('update'))
@run_in_twisted_thread
def remove_group(self, group):
operation = RemoveGroupOperation(group=group)
self.journal.append(operation)
if self.transaction_level == 0:
self._command_channel.send(Command('update'))
@run_in_twisted_thread
def add_contact(self, contact):
operation = AddContactOperation(contact=contact)
self.journal.append(operation)
if self.transaction_level == 0:
self._command_channel.send(Command('update'))
@run_in_twisted_thread
def update_contact(self, contact, **attributes):
operation = UpdateContactOperation(contact=contact, attributes=attributes)
self.journal.append(operation)
if self.transaction_level == 0:
self._command_channel.send(Command('update'))
@run_in_twisted_thread
def remove_contact(self, contact):
operation = RemoveContactOperation(contact=contact)
self.journal.append(operation)
if self.transaction_level == 0:
self._command_channel.send(Command('update'))
@run_in_twisted_thread
def add_presence_policy(self, policy):
operation = AddPresencePolicyOperation(policy=policy)
self.journal.append(operation)
if self.transaction_level == 0:
self._command_channel.send(Command('update'))
@run_in_twisted_thread
def update_presence_policy(self, policy, **attributes):
operation = UpdatePresencePolicyOperation(policy=policy, attributes=attributes)
self.journal.append(operation)
if self.transaction_level == 0:
self._command_channel.send(Command('update'))
@run_in_twisted_thread
def remove_presence_policy(self, policy):
operation = RemovePresencePolicyOperation(policy=policy)
self.journal.append(operation)
if self.transaction_level == 0:
self._command_channel.send(Command('update'))
@run_in_twisted_thread
def add_dialoginfo_policy(self, policy):
operation = AddDialoginfoPolicyOperation(policy=policy)
self.journal.append(operation)
if self.transaction_level == 0:
self._command_channel.send(Command('update'))
@run_in_twisted_thread
def update_dialoginfo_policy(self, policy, **attributes):
operation = UpdateDialoginfoPolicyOperation(policy=policy, attributes=attributes)
self.journal.append(operation)
if self.transaction_level == 0:
self._command_channel.send(Command('update'))
@run_in_twisted_thread
def remove_dialoginfo_policy(self, policy):
operation = RemoveDialoginfoPolicyOperation(policy=policy)
self.journal.append(operation)
if self.transaction_level == 0:
self._command_channel.send(Command('update'))
@run_in_twisted_thread
def set_status_icon(self, icon):
operation = SetStatusIconOperation(icon=icon)
self.journal.append(operation)
if self.transaction_level == 0:
self._command_channel.send(Command('update'))
@run_in_twisted_thread
def set_offline_status(self, status):
operation = SetOfflineStatusOperation(status=status)
self.journal.append(operation)
if self.transaction_level == 0:
self._command_channel.send(Command('update'))
@run_in_green_thread
def _run(self):
+ self._command_greenlet = api.getcurrent()
while True:
- command = self._command_channel.wait()
+ self._current_command = command = self._command_channel.wait()
handler = getattr(self, '_CH_%s' % command.name)
handler(command)
+ self._current_command = None
# command handlers
#
def _CH_initialize(self, command):
self.state = 'initializing'
if self.timer is not None and self.timer.active():
self.timer.cancel()
self.timer = None
if self.account.xcap.xcap_root:
self.client = XCAPClient(self.account.xcap.xcap_root, self.account.id, password=self.account.auth.password, auth=None)
else:
try:
lookup = DNSLookup()
uri = random.choice(lookup.lookup_xcap_server(self.account.uri).wait())
except DNSLookupError:
self.timer = self._schedule_command(60, Command('initialize', command.event))
return
else:
self.client = XCAPClient(uri, self.account.id, password=self.account.auth.password, auth=None)
self.server_caps.initialize(self.client)
try:
self.server_caps.fetch()
except XCAPError:
self.timer = self._schedule_command(60, Command('initialize', command.event))
return
else:
if self.server_caps.content is None:
# XCAP server must always return some content for xcap-caps
self.timer = self._schedule_command(60, Command('initialize', command.event))
return
self.oma_compliant = 'org.openmobilealliance.pres-rules' in self.server_caps.content.auids
for document in self.documents:
document.initialize(self.client, self.server_caps.content)
notification_center = NotificationCenter()
notification_center.post_notification('XCAPManagerDidDiscoverServerCapabilities', sender=self,
data=TimestampedNotificationData(contactlist_supported = self.contactlist_supported,
presence_policies_supported = self.presence_policies_supported,
dialoginfo_policies_supported = self.dialoginfo_policies_supported,
status_icon_supported = self.status_icon_supported,
offline_status_supported = self.offline_status_supported))
self.subscribe_interval = limit(self.account.sip.subscribe_interval, min=60)
self.state = 'fetching'
self._command_channel.send(Command('fetch', documents=self.document_names))
self._command_channel.send(Command('subscribe', command.event))
def _CH_reload(self, command):
if self.state in ('stopping', 'stopped'):
return
if 'id' in command.modified:
for document in self.documents:
document.cache_directory = os.path.join(self.cache_directory, self.account.id)
document.reset()
if 'sip.subscribe_interval' in command.modified:
self.subscribe_interval = limit(self.account.sip.subscribe_interval, min=60)
if set(['id', 'auth.password', 'xcap.xcap_root']).intersection(command.modified):
self.state = 'initializing'
self._command_channel.send(Command('initialize', command.event))
elif self.subscription is None and self.account.xcap.use_xcap_diff:
self._command_channel.send(Command('subscribe', command.event))
elif self.subscription is not None and not self.account.xcap.use_xcap_diff:
self._command_channel.send(Command('subscribe', command.event))
elif set(['sip.subscribe_interval', 'sip.transport_list']).intersection(command.modified):
self._command_channel.send(Command('subscribe', command.event))
else:
command.signal()
def _CH_fetch(self, command):
if self.state not in ('insync', 'fetching'):
self.not_executed_fetch = command
return
if self.not_executed_fetch is not None:
self.not_executed_fetch = None
if self.last_fetch_time > command.timestamp:
self.state = 'insync'
return
self.state = 'fetching'
if self.timer is not None and self.timer.active():
command.documents = list(set(command.documents) | set(self.timer.command.documents))
self.timer.cancel()
self.timer = None
try:
for document in (doc for doc in self.documents if doc.name in command.documents and doc.supported):
document.fetch()
except XCAPError:
self.timer = self._schedule_command(60, Command('fetch', command.event, documents=command.documents))
return
if self.last_fetch_time > datetime.fromtimestamp(0) and all(doc.fetch_time < command.timestamp for doc in self.documents):
self.last_fetch_time = datetime.utcnow()
self.state = 'insync'
return
else:
self.last_fetch_time = datetime.utcnow()
self.state = 'updating'
self.journal.insert(0, NormalizeOperation())
self._command_channel.send(Command('update', command.event))
def _CH_update(self, command):
if self.state not in ('insync', 'updating'):
return
if self.transaction_level != 0:
return
self.state = 'updating'
if self.timer is not None and self.timer.active():
self.timer.cancel()
self.timer = None
journal = self.journal[:]
for operation in (operation for operation in journal if not operation.applied):
handler = getattr(self, '_OH_%s' % operation.name)
try:
handler(operation)
except Exception:
# Error while applying operation, needs to be logged -Luci
import traceback
traceback.print_exc()
continue
api.sleep(0) # Operations are quite CPU intensive
try:
for document in (doc for doc in self.documents if doc.dirty and doc.supported):
document.update()
except FetchRequiredError:
for document in (doc for doc in self.documents if doc.dirty and doc.supported):
document.reset()
for operation in journal:
operation.applied = False
self.state = 'fetching'
self._command_channel.send(Command('fetch', documents=self.document_names)) # Try to fetch them all just in case
except XCAPError:
self.timer = self._schedule_command(60, Command('update'))
else:
self.state = 'insync'
del self.journal[:len(journal)]
self._load_data()
command.signal()
if self.not_executed_fetch is not None:
self._command_channel.send(self.not_executed_fetch)
self.not_executed_fetch = None
self._save_journal()
def _CH_subscribe(self, command):
if self.state in ('stopping', 'stopped'):
return
notification_center = NotificationCenter()
settings = SIPSimpleSettings()
if self.subscription_timer is not None and self.subscription_timer.active():
self.subscription_timer.cancel()
self.subscription_timer = None
if self.subscription is not None:
notification_center.remove_observer(self, sender=self.subscription)
self.subscription.end()
self.subscription = None
try:
if not self.account.xcap.use_xcap_diff:
raise SubscriptionError(error='xcap-diff is disabled', timeout=120)
if self.account.sip.outbound_proxy is not None:
uri = SIPURI(host=self.account.sip.outbound_proxy.host,
port=self.account.sip.outbound_proxy.port,
parameters={'transport': self.account.sip.outbound_proxy.transport})
else:
uri = SIPURI(host=self.account.id.domain)
lookup = DNSLookup()
try:
routes = lookup.lookup_sip_proxy(uri, settings.sip.transport_list).wait()
except DNSLookupError, e:
timeout = random.uniform(15, 30)
raise SubscriptionError(error='DNS lookup failed: %s' % e, timeout=timeout)
rlist = resourcelists.List()
for document in (doc for doc in self.documents if doc.supported):
rlist.append(resourcelists.Entry(document.relative_uri))
body = resourcelists.ResourceLists([rlist]).toxml()
content_type = resourcelists.ResourceLists.content_type
timeout = time() + 30
for route in routes:
remaining_time = timeout - time()
if remaining_time > 0:
subscription = Subscription(FromHeader(self.account.uri, self.account.display_name),
ToHeader(self.account.uri, self.account.display_name),
ContactHeader(ContactURI('%s@%s' % (self.account.contact, host.outgoing_ip_for(route.address)))[route.transport]),
'xcap-diff',
RouteHeader(route.get_uri()),
credentials=self.account.credentials,
refresh=self.subscribe_interval)
notification_center.add_observer(self, sender=subscription)
try:
subscription.subscribe(body=body, content_type=content_type, timeout=limit(remaining_time, min=1, max=5))
while True:
notification = self._data_channel.wait()
if notification.sender is subscription and notification.name == 'SIPSubscriptionDidStart':
break
except SIPCoreError:
continue
except SIPSubscriptionDidFail, e:
notification_center.remove_observer(self, sender=subscription)
if e.code == 407:
# Authentication failed, so retry the subscription in some time
timeout = random.uniform(60, 120)
raise SubscriptionError(error='authentication failed', timeout=timeout)
elif e.code == 423:
# Internal too brief
self.subscribe_interval *= 2
raise SubscriptionError(error='interval too brief', timeout=5)
elif e.code == 408 or 500 <= e.code <= 599:
# Try the next route
continue
else:
# For any other code, we retry in some time
timeout = random.uniform(60, 120)
raise SubscriptionError(error=e.reason, timeout=timeout)
else:
self.subscription = subscription
command.signal()
break
else:
# No more routes to try, reschedule the subscription
timeout = random.uniform(60, 120)
raise SubscriptionError(error='no more routes to try', timeout=timeout)
except SubscriptionError, e:
# There was an error, so do a fetch instead
self._command_channel.send(Command('fetch', documents=self.document_names))
self.subscription_timer = self._schedule_command(e.timeout, Command('subscribe', command.event))
def _CH_unsubscribe(self, command):
if self.subscription is not None:
subscription = self.subscription
self.subscription = None
- subscription.end()
- while True:
- notification = self._data_channel.wait()
- if notification.sender is subscription and notification.name == 'SIPSubscriptionDidEnd':
- break
+ subscription.end(timeout=2)
+ try:
+ while True:
+ notification = self._data_channel.wait()
+ if notification.sender is subscription and notification.name == 'SIPSubscriptionDidEnd':
+ break
+ except SIPSubscriptionDidFail:
+ pass
command.signal()
# operation handlers
#
def _OH_normalize(self, operation):
# Normalize resource-lists document :
# * create one if it doesn't exist
# * use OMA suggested names for lists (create them if inexistent)
# * if no OMA suggested lists existed, then assume all lists contain buddies and add them to the oma_buddylist
if self.resource_lists.supported:
if self.resource_lists.content is None:
self.resource_lists.content = resourcelists.ResourceLists()
self.resource_lists.dirty = True
resource_lists = self.resource_lists.content
try:
oma_buddylist = (child for child in resource_lists if isinstance(child, resourcelists.List) and child.name=='oma_buddylist').next()
except StopIteration:
oma_buddylist = None
try:
oma_grantedcontacts = (child for child in resource_lists if isinstance(child, resourcelists.List) and child.name=='oma_grantedcontacts').next()
except StopIteration:
oma_grantedcontacts = None
try:
oma_blockedcontacts = (child for child in resource_lists if isinstance(child, resourcelists.List) and child.name=='oma_blockedcontacts').next()
except StopIteration:
oma_blockedcontacts = None
try:
oma_allcontacts = (child for child in resource_lists if isinstance(child, resourcelists.List) and child.name=='oma_allcontacts').next()
except StopIteration:
oma_allcontacts = None
buddy_lists = []
if oma_buddylist is None:
oma_buddylist = resourcelists.List(name='oma_buddylist')
# If no other oma defined list exists, assume all the current lists in the resource list document contain buddies
if (oma_grantedcontacts, oma_blockedcontacts, oma_allcontacts) == (None, None, None):
for rlist in (child for child in resource_lists if isinstance(child, resourcelists.List)):
path = self.resource_lists.uri + '/~~' + resource_lists.get_xpath(rlist)
oma_buddylist.append(resourcelists.External(path))
resource_lists.append(oma_buddylist)
self.resource_lists.dirty = True
else:
for child in oma_buddylist:
if isinstance(child, resourcelists.List):
buddy_lists.append(child)
elif isinstance(child, resourcelists.External):
try:
buddy_lists.extend(self._follow_rl_external(resource_lists, child))
except ValueError:
continue
if oma_grantedcontacts is None:
oma_grantedcontacts = resourcelists.List(name='oma_grantedcontacts')
resource_lists.append(oma_grantedcontacts)
self.resource_lists.dirty = True
else:
# Make sure there are no references to oma_buddylist (like OMA suggests)
for child in oma_grantedcontacts:
if isinstance(child, resourcelists.External):
try:
if oma_buddylist in self._follow_rl_external(resource_lists, child):
oma_grantedcontacts.remove(child)
oma_grantedcontacts.extend(resourcelists.External(self.resource_lists.uri+'/~~'+resource_lists.get_xpath(blist)) for blist in buddy_lists)
self.resource_lists.dirty = True
break
except ValueError:
continue
if oma_blockedcontacts is None:
oma_blockedcontacts = resourcelists.List(name='oma_blockedcontacts')
resource_lists.append(oma_blockedcontacts)
self.resource_lists.dirty = True
if oma_allcontacts is None:
oma_allcontacts = resourcelists.List(name='oma_allcontacts')
for rlist in (oma_buddylist, oma_grantedcontacts, oma_blockedcontacts):
path = self.resource_lists.uri + '/~~' + resource_lists.get_xpath(rlist)
oma_allcontacts.append(resourcelists.External(path))
resource_lists.append(oma_allcontacts)
self.resource_lists.dirty = True
# Remove any external references which don't point to anything
notexpanded = deque([resource_lists])
visited = set(notexpanded)
remove_elements = []
while notexpanded:
rlist = notexpanded.popleft()
for child in rlist:
if isinstance(child, resourcelists.List) and child not in visited:
visited.add(child)
notexpanded.append(child)
elif isinstance(child, resourcelists.External):
try:
ref_lists = self._follow_rl_external(resource_lists, child)
except ValueError:
continue
if not ref_lists:
remove_elements.append((child, rlist))
for element, parent in remove_elements:
parent.remove(element)
# Normalize rls-services document:
# * create one if it doesn't exist
# * if it is empty, start by assuming we want to subscribe to all the buddies
if self.rls_services.supported:
if self.rls_services.content is None:
self.rls_services.content = rlsservices.RLSServices()
self.rls_services.dirty = True
rls_services = self.rls_services.content
if len(rls_services) == 0 and self.resource_lists.supported and buddy_lists:
rlist = resourcelists.List()
service = rlsservices.Service('sip:buddies@%s' % self.account.id.domain, list=rlist, packages=rlsservices.Packages(['presence', 'dialog']))
for blist in buddy_lists:
path = self.resource_lists.uri + '/~~' + resource_lists.get_xpath(blist)
rlist.append(resourcelists.External(path))
rls_services.append(service)
self.rls_services.dirty = True
# Normalize pres-rules document:
# * create one if it doesn't exist
# * if OMA support is enabled on the presence server, create some OMA suggested rules
# * also normalize the document so that it is OMA compliant
# * get rid of any wp_prs_(allow_)one_* rules because they can't be supported
if self.pres_rules.supported:
if self.pres_rules.content is None:
self.pres_rules.content = presrules.PresRules()
self.pres_rules.dirty = True
if self.oma_compliant and self.resource_lists.supported:
pres_rules = self.pres_rules.content
resource_lists = self.resource_lists.content
try:
path = self.resource_lists.uri + '/~~/resource-lists/list[@name="oma_grantedcontacts"]'
wp_prs_grantedcontacts = (child for child in pres_rules if isinstance(child, common_policy.Rule) and child.id=='wp_prs_grantedcontacts').next()
except StopIteration:
wp_prs_grantedcontacts = common_policy.Rule('wp_prs_grantedcontacts', conditions=[omapolicy.ExternalList([path])], actions=[presrules.SubHandling('allow')])
#wp_prs_grantedcontacts.display_name = 'Allow' # Rule display-name extension
pres_rules.append(wp_prs_grantedcontacts)
self.pres_rules.dirty = True
else:
if wp_prs_grantedcontacts.conditions != common_policy.Conditions([omapolicy.ExternalList([path])]):
wp_prs_grantedcontacts.conditions = [omapolicy.ExternalList([path])]
self.pres_rules.dirty = True
if wp_prs_grantedcontacts.actions != common_policy.Actions([presrules.SubHandling('allow')]):
wp_prs_grantedcontacts.actions = [presrules.SubHandling('allow')]
self.pres_rules.dirty = True
try:
path = self.resource_lists.uri + '/~~/resource-lists/list[@name="oma_blockedcontacts"]'
wp_prs_blockedcontacts = (child for child in pres_rules if isinstance(child, common_policy.Rule) and child.id=='wp_prs_blockedcontacts').next()
except StopIteration:
wp_prs_blockedcontacts = common_policy.Rule('wp_prs_blockedcontacts', conditions=[omapolicy.ExternalList([path])], actions=[presrules.SubHandling('polite-block')])
#wp_prs_blockedcontacts.display_name = 'Block' # Rule display-name extension
pres_rules.append(wp_prs_blockedcontacts)
self.pres_rules.dirty = True
else:
if wp_prs_blockedcontacts.conditions != common_policy.Conditions([omapolicy.ExternalList([path])]):
wp_prs_blockedcontacts.conditions = [omapolicy.ExternalList([path])]
self.pres_rules.dirty = True
if wp_prs_blockedcontacts.actions not in [common_policy.Actions([presrules.SubHandling('block')]), common_policy.Actions([presrules.SubHandling('polite-block')])]:
wp_prs_blockedcontacts.actions = [presrules.SubHandling('polite-block')]
self.pres_rules.dirty = True
if wp_prs_blockedcontacts.transformations:
wp_prs_blockedcontacts.transformations = None
self.pres_rules.dirty = True
wp_prs_unlisted = [child for child in pres_rules if isinstance(child, common_policy.Rule) and child.id in ('wp_prs_unlisted', 'wp_prs_allow_unlisted')]
if len(wp_prs_unlisted) == 0:
wp_prs_unlisted = common_policy.Rule('wp_prs_unlisted', conditions=[omapolicy.OtherIdentity()], actions=[presrules.SubHandling('confirm')])
pres_rules.append(wp_prs_unlisted)
self.pres_rules.dirty = True
else:
for rule in wp_prs_unlisted[1:]:
pres_rules.remove(rule)
wp_prs_unlisted = wp_prs_unlisted[0]
if wp_prs_unlisted.conditions != common_policy.Conditions([omapolicy.OtherIdentity()]):
wp_prs_unlisted.conditions = [omapolicy.OtherIdentity()]
self.pres_rules.dirty = True
if wp_prs_unlisted.id == 'wp_prs_unlisted' and wp_prs_unlisted.actions not in [common_policy.Actions([presrules.SubHandling('confirm')]), common_policy.Actions([presrules.SubHandling('block')]), common_policy.Actions([presrules.SubHandling('polite-block')])]:
wp_prs_unlisted.actions = [presrules.SubHandling('confirm')]
self.pres_rules.dirty = True
elif wp_prs_unlisted.id == 'wp_prs_allow_unlisted' and wp_prs_unlisted.actions != common_policy.Actions([presrules.SubHandling('allow')]):
wp_prs_unlisted.actions = [presrules.SubHandling('allow')]
self.pres_rules.dirty = True
if wp_prs_unlisted.id == 'wp_prs_unlisted' and wp_prs_unlisted.transformations:
wp_prs_unlisted.transformations = None
self.pres_rules.dirty = True
try:
wp_prs_block_anonymous = (child for child in pres_rules if isinstance(child, common_policy.Rule) and child.id=='wp_prs_block_anonymous').next()
except StopIteration:
wp_prs_block_anonymous = common_policy.Rule('wp_prs_block_anonymous', conditions=[omapolicy.AnonymousRequest()], actions=[presrules.SubHandling('block')])
pres_rules.append(wp_prs_block_anonymous)
self.pres_rules.dirty = True
else:
if wp_prs_block_anonymous.conditions != common_policy.Conditions([omapolicy.AnonymousRequest()]):
wp_prs_block_anonymous.conditions = [omapolicy.AnonymousRequest()]
self.pres_rules.dirty = True
if wp_prs_block_anonymous.actions not in [common_policy.Actions([presrules.SubHandling('block')]), common_policy.Actions([presrules.SubHandling('polite-block')])]:
wp_prs_block_anonymous.actions = [presrules.SubHandling('block')]
self.pres_rules.dirty = True
if wp_prs_block_anonymous.transformations:
wp_prs_block_anonymous.transformations = None
self.pres_rules.dirty = True
try:
identity = 'sip:'+self.account.id
wp_prs_allow_own = (child for child in pres_rules if isinstance(child, common_policy.Rule) and child.id=='wp_prs_allow_own').next()
except StopIteration:
wp_prs_allow_own = common_policy.Rule('wp_prs_allow_own', conditions=[common_policy.Identity([common_policy.IdentityOne(identity)])], actions=[presrules.SubHandling('allow')])
pres_rules.append(wp_prs_allow_own)
self.pres_rules.dirty = True
else:
if wp_prs_allow_own.conditions != common_policy.Conditions([common_policy.Identity([common_policy.IdentityOne(identity)])]):
wp_prs_allow_own.conditions = [common_policy.Identity([common_policy.IdentityOne(identity)])]
self.pres_rules.dirty = True
if wp_prs_allow_own.actions != common_policy.Actions([presrules.SubHandling('allow')]):
wp_prs_allow_own.actions = [presrules.SubHandling('allow')]
self.pres_rules.dirty = True
# We cannot work with wp_prs_allow_one_* rules because they don't allow adding new identities to the rule
for rule in (child for child in pres_rules if isinstance(child, common_policy.Rule) and child.id.startswith('wp_prs_allow_one_')):
# Create a new list just for this identity
rlist = resourcelists.List(name=self.unique_name('presrules_group', (list.name for list in resource_lists), skip_preferred_name=True).next())
resource_lists.append(rlist)
# Change the name to wp_prs_allow_onelist_<group name>
rule.id = self.unique_name('wp_prs_allow_onelist_'+rlist.name, (rule.id for rule in pres_rules)).next()
# Change the condition
for condition in (rule.conditions or []):
if isinstance(condition, common_policy.Identity):
for identity in (identity_condition for identity_condition in condition if isinstance(identity_condition, common_policy.IdentityOne)):
rlist.append(resourcelists.Entry(uri=identity.id))
elif isinstance(condition, omapolicy.ExternalList):
# This shouldn't happen but accept it anyway
for uri in condition:
rlist.append(resourcelists.External(uri))
path = self.resource_lists.uri + '/~~' + resource_lists.get_xpath(rlist)
rule.conditions = [omapolicy.ExternalList([path])]
# Make sure the action is allow
if rule.actions != common_policy.Actions([presrules.SubHandling('allow')]):
rule.actions = [presrules.SubHandling('allow')]
self.resource_lists.dirty = True
self.pres_rules.dirty = True
for rule in (child for child in pres_rules if isinstance(child, common_policy.Rule) and child.id.startswith('wp_prs_allow_onelist_')):
if rule.actions != common_policy.Actions([presrules.SubHandling('allow')]):
rule.actions = [presrules.SubHandling('allow')]
self.pres_rules.dirty = True
# We cannot work with wp_prs_one_* rules because they don't allow adding new identities to the rule
for rule in (child for child in pres_rules if isinstance(child, common_policy.Rule) and child.id.startswith('wp_prs_one_')):
# Create a new list just for this identity
rlist = resourcelists.List(name=self.unique_name('presrules_group', (list.name for list in resource_lists), skip_preferred_name=True).next())
resource_lists.append(rlist)
# Change the name to wp_prs_onelist_<group name>
rule.id = self.unique_name('wp_prs_onelist_'+rlist.name, (rule.id for rule in pres_rules)).next()
# Change the condition
for condition in (rule.conditions or []):
if isinstance(condition, common_policy.Identity):
for identity in (identity_condition for identity_condition in condition if isinstance(identity_condition, common_policy.IdentityOne)):
rlist.append(resourcelists.Entry(uri=identity.id))
elif isinstance(condition, omapolicy.ExternalList):
# This shouldn't happen but accept it anyway
for uri in condition:
rlist.append(resourcelists.External(uri))
path = self.resource_lists.uri + '/~~' + resource_lists.get_xpath(rlist)
rule.conditions = [omapolicy.ExternalList([path])]
# Make sure the action is one of confirm, block or polite-block
if rule.actions not in [common_policy.Actions([presrules.SubHandling('confirm')]), common_policy.Actions([presrules.SubHandling('block')]), common_policy.Actions([presrules.SubHandling('polite-block')])]:
rule.actions = [presrules.SubHandling('confirm')]
# Make sure there are no transformations
if rule.transformations:
rule.transformations = None
self.resource_lists.dirty = True
self.pres_rules.dirty = True
for rule in (child for child in pres_rules if isinstance(child, common_policy.Rule) and child.id.startswith('wp_prs_onelist_')):
if rule.actions not in [common_policy.Actions([presrules.SubHandling('confirm')]), common_policy.Actions([presrules.SubHandling('block')]), common_policy.Actions([presrules.SubHandling('polite-block')])]:
rule.actions = [presrules.SubHandling('confirm')]
self.pres_rules.dirty = True
if rule.transformations:
rule.transformations = None
self.pres_rules.dirty = True
# Normalize dialog-rules document:
# * create one if it doesn't exist
# * create a rule of each of the four possible actions except 'confirm'
if self.dialog_rules.supported:
if self.dialog_rules.content is None:
self.dialog_rules.content = dialogrules.DialogRules()
self.dialog_rules.dirty = True
dialog_rules = self.dialog_rules.content
for action in ('allow', 'block', 'polite-block'):
try:
(child for child in dialog_rules if isinstance(child, common_policy.Rule) and any(isinstance(a, dialogrules.SubHandling) and a.value==action for a in (child.actions or []))).next()
except StopIteration:
name = self.unique_name('dialog_%s' % action, (rule.id for rule in dialog_rules)).next()
rule = common_policy.Rule(name, conditions=[], actions=[dialogrules.SubHandling(action)])
dialog_rules.append(rule)
self.dialog_rules.dirty = True
def _OH_add_group(self, operation):
if not self.resource_lists.supported:
return
resource_lists = self.resource_lists.content
try:
oma_buddylist = (child for child in resource_lists if isinstance(child, resourcelists.List) and child.name=='oma_buddylist').next()
except StopIteration:
# This should never happen as the document is normalized
return
# See if there already is a group with the specified name: a group reachable from within oma_buddylist
notexpanded = deque([oma_buddylist])
visited = set(notexpanded)
while notexpanded:
rlist = notexpanded.popleft()
for child in rlist:
if isinstance(child, resourcelists.List) and child not in visited:
if child.display_name is not None and child.display_name.value == operation.group:
return
visited.add(child)
notexpanded.append(child)
elif isinstance(child, resourcelists.External):
try:
ref_lists = set(l for l in self._follow_rl_external(resource_lists, child) if l not in visited)
except ValueError:
ref_lists = set()
if child.display_name is not None and child.display_name.value == operation.group and ref_lists:
# Only assume the group exists if there is at least one list obtained by dereferencing this external element
return
if child.display_name is None and any(l.display_name is not None and l.display_name.value == operation.group for l in ref_lists):
# The lists obtained by dereferencing this external element inherit its display name if it exists, otherwise they use their own display name
return
visited.update(ref_lists)
notexpanded.extend(ref_lists)
# A list with the specified name was not found, create it
name = self.unique_name('group', (list.name for list in resource_lists), skip_preferred_name=True).next()
rlist = resourcelists.List(name=name, display_name=operation.group)
resource_lists.append(rlist)
path = self.resource_lists.uri + '/~~' + resource_lists.get_xpath(rlist)
oma_buddylist.append(resourcelists.External(path))
self.resource_lists.dirty = True
def _OH_rename_group(self, operation):
if not self.resource_lists.supported:
return
resource_lists = self.resource_lists.content
try:
oma_buddylist = (child for child in resource_lists if isinstance(child, resourcelists.List) and child.name=='oma_buddylist').next()
except StopIteration:
# This should never happen as the document is normalized
return
notexpanded = deque([oma_buddylist])
visited = set(notexpanded)
while notexpanded:
rlist = notexpanded.popleft()
for child in rlist:
if isinstance(child, resourcelists.List) and child not in visited:
if child.display_name is not None and child.display_name.value == operation.old_name:
child.display_name = operation.new_name
self.resource_lists.dirty = True
visited.add(child)
notexpanded.append(child)
elif isinstance(child, resourcelists.External):
try:
ref_lists = self._follow_rl_external(resource_lists, child)
except ValueError:
ref_lists = []
if child.display_name is not None and child.display_name.value == operation.old_name and ref_lists:
child.display_name = operation.new_name
ref_lists = set(l for l in ref_lists if l not in visited)
if child.display_name is None:
for ref_list in ref_lists:
if ref_list.display_name is not None and ref_list.display_name.value == operation.old_name:
ref_list.display_name = operation.new_name
self.resource_lists.dirty = True
visited.update(ref_lists)
notexpanded.extend(ref_lists)
def _OH_remove_group(self, operation):
if not self.resource_lists.supported:
return
resource_lists = self.resource_lists.content
try:
oma_buddylist = (child for child in resource_lists if isinstance(child, resourcelists.List) and child.name=='oma_buddylist').next()
except StopIteration:
# This should never happen as the document is normalized
return
remove_elements = [] # (element, parent)
move_elements = [] # (element, new_parent)
notexpanded = deque([(oma_buddylist, False, None)])
visited = set([oma_buddylist])
while notexpanded:
rlist, list_removed, new_parent = notexpanded.popleft()
for child in rlist:
if isinstance(child, resourcelists.List) and child not in visited:
if child.display_name is not None and child.display_name.value == operation.group:
# Needs to be removed
if not list_removed:
# Only remove the child if the parent (rlist) will not be removed
remove_elements.append((child, rlist))
notexpanded.append((child, True, new_parent if list_removed else rlist))
else:
if list_removed and child.display_name is not None:
# If the parent is removed, the child needs to be moved
move_elements.append((child, new_parent))
# Will not be removed
notexpanded.append((child, list_removed and child.display_name is None, new_parent))
visited.add(child)
elif isinstance(child, resourcelists.External):
try:
all_references = self._follow_rl_external(resource_lists, child)
except ValueError:
all_references = []
ref_lists = set(l for l in all_references if l not in visited)
if child.display_name is not None and child.display_name.value == operation.group and all_references:
# The whole reference needs to be removed
if not list_removed:
# Only remove the child if the parent (rlist) will not be removed
remove_elements.append((child, rlist))
# The referenced lists may also need to be removed from their parents as they can also have a display name
for l in ref_lists:
if l.display_name is not None and l.display_name.value == operation.group:
parent = resource_lists.find_parent(l)
if parent is not None:
remove_elements.append((l, parent))
notexpanded.append((l, True, parent))
else:
notexpanded.append((l, False, None))
else:
notexpanded.append((l, False, None))
visited.update(ref_lists)
elif child.display_name is None and all_references:
# If the display name is None, the list's display name applies
for l in ref_lists:
if l.display_name is not None and l.display_name.value == operation.group:
# Also remove from all_references so that we can check whether to remove the external reference or not
all_references.remove(l)
parent = resource_lists.find_parent(l)
if parent is not None:
remove_elements.append((l, parent))
notexpanded.append((l, True, parent))
else:
notexpanded.append((l, False, None))
else:
notexpanded.append((l, False, None))
if not all_references and not list_removed:
# If the external reference would not point to any other *thing*, remove it
remove_elements.append((child, rlist))
elif list_removed:
# If the parent is removed, the child needs to be moved
move_elements.append((child, new_parent))
visited.update(ref_lists)
else:
if list_removed and all_references:
# If the parent is removed, the child needs to be moved
move_elements.append((child, new_parent))
notexpanded.extend((l, False, None) for l in ref_lists)
# Do not update visited with ref_lists since these lists might also have a display name and be referenced some other way
for element, parent in move_elements:
parent.append(element)
for element, parent in remove_elements:
parent.remove(element)
if move_elements or remove_elements:
self.resource_lists.dirty = True
# Remove any external references which no longer point to anything
notexpanded = deque([resource_lists])
visited = set(notexpanded)
del remove_elements[:]
while notexpanded:
rlist = notexpanded.popleft()
for child in rlist:
if isinstance(child, resourcelists.List) and child not in visited:
visited.add(child)
notexpanded.append(child)
elif isinstance(child, resourcelists.External):
try:
ref_lists = self._follow_rl_external(resource_lists, child)
except ValueError:
continue
if not ref_lists:
remove_elements.append((child, rlist))
for element, parent in remove_elements:
parent.remove(element)
def _OH_add_contact(self, operation):
if not self.resource_lists.supported:
return
resource_lists = self.resource_lists.content
try:
oma_buddylist = (child for child in resource_lists if isinstance(child, resourcelists.List) and child.name=='oma_buddylist').next()
except StopIteration:
# This should never happen as the document is normalized
return
if operation.contact.presence_policies is None:
operation.contact.presence_policies = []
elif self.oma_compliant:
# Filter out policies we mustn't add contacts to
operation.contact.presence_policies = [policy for policy in operation.contact.presence_policies if policy.id not in ('wp_prs_unlisted', 'wp_prs_allow_unlisted', 'wp_prs_block_anonymous', 'wp_prs_allow_own')]
if operation.contact.dialoginfo_policies is None:
operation.contact.dialoginfo_policies = []
# First see if this contact (uniquely identified by uri) is already a buddy; if it is, don't do anything
notexpanded = deque([oma_buddylist])
visited = set(notexpanded)
while notexpanded:
rlist = notexpanded.popleft()
for child in rlist:
if isinstance(child, resourcelists.List) and child not in visited:
visited.add(child)
notexpanded.append(child)
elif isinstance(child, resourcelists.External):
try:
ref_lists = set(l for l in self._follow_rl_external(resource_lists, child) if l not in visited)
except ValueError:
ref_lists = set()
visited.update(ref_lists)
notexpanded.extend(ref_lists)
elif isinstance(child, resourcelists.EntryRef):
try:
entries = self._follow_rl_entry_ref(resource_lists, child)
except ValueError:
continue
if any(entry.uri == operation.contact.uri for entry in entries):
return
elif isinstance(child, resourcelists.Entry):
if child.uri == operation.contact.uri:
return
# Then find all the lists where we can add this new contact
if operation.contact.group is not None:
# Only add contacts to lists directly referenced from oma_buddylist by an external element.
candidate_lists = []
for child in oma_buddylist:
if isinstance(child, resourcelists.External):
try:
ref_lists = set(l for l in self._follow_rl_external(resource_lists, child))
except ValueError:
ref_lists = set()
if child.display_name is not None:
if child.display_name.value == operation.contact.group:
candidate_lists.extend(ref_lists)
else:
continue
else:
candidate_lists.extend(l for l in ref_lists if l.display_name is not None and l.display_name.value == operation.contact.group)
else:
# Add contacts which are not buddies to top-level lists which are not referenced by oma_buddylist
candidate_lists = [l for l in resource_lists if l not in visited]
# Add a fallback candidate which should never, ever be referenced from anywhere
fallback_candidate = resourcelists.List(name=self.unique_name('group', (l.name for l in resource_lists), skip_preferred_name=True).next(), display_name=operation.contact.group)
candidate_lists.append(fallback_candidate)
# We may need the XPath to this list
resource_lists.append(fallback_candidate)
presence_lists = set() # Lists to which, if added, the rls services document need not be modified for presence subscription
dialoginfo_lists = set() # Lists to which, if added, the rls services document need not be modified for dialoginfo subscription
presrules_lists = set() # Lists to which, if added, the presrules document need not be modified
not_wanted_lists = set() # Lists we don't want to add the contact to because they are referenced from other places
remove_from_lists = set() # Lists we need to remove the contact from
to_remove = [] # (child, parent)
# Filter candidates based on subscribe_to_* flags
if self.rls_services.supported:
# While filtering candidates, also remove any reference to the uri from unwanted services
rls_services = self.rls_services.content
for service in rls_services:
packages = set(package.value for package in (service.packages or []))
if isinstance(service.list, rlsservices.RLSList):
expanded_list = [service.list]
elif isinstance(service.list, rlsservices.ResourceList):
try:
expanded_list = self._follow_rls_resource_list(resource_lists, service.list)
except ValueError:
expanded_list = []
else:
expanded_list = []
if any(package not in ('presence', 'dialog') for package in packages):
# We do not want lists referenced by this service because we do not know what we'd add the contact to.
# However, if the contact is already in this list, allow it to stay here
not_wanted_lists.update(expanded_list)
if not operation.contact.subscribe_to_presence and 'presence' in packages:
not_wanted_lists.update(expanded_list)
remove_from_lists.update(expanded_list)
elif not operation.contact.subscribe_to_dialoginfo and 'dialog' in packages:
not_wanted_lists.update(expanded_list)
remove_from_lists.update(expanded_list)
elif set(['presence', 'dialog']).issuperset(packages):
if 'presence' in packages:
presence_lists.update(expanded_list)
if 'dialog' in packages:
dialoginfo_lists.update(expanded_list)
else:
# Any list will do
presence_lists.update(candidate_lists)
dialoginfo_lists.update(candidate_lists)
# Filter out condidates based on presence policy
if self.pres_rules.supported:
pres_rules = self.pres_rules.content
# While filtering candidates based on presence policy also remove any reference to the uri
remaining_policies = [policy.id for policy in operation.contact.presence_policies]
policy_lists = {}
for rule in pres_rules:
# Action is used to ignore rules whose actions we don't understand
try:
action = (unicode(action.value) for action in (rule.actions or []) if isinstance(action, presrules.SubHandling)).next()
except StopIteration:
action = None
if not operation.contact.presence_policies or rule.id not in (policy.id for policy in operation.contact.presence_policies) or action is None:
# We do not want to add the contact to lists referenced by this rule (we also want to remove the contact from them if we understand the rule)
for condition in (rule.conditions or []):
if isinstance(condition, omapolicy.ExternalList):
try:
ref_lists = self._follow_policy_external_list(resource_lists, condition)
not_wanted_lists.update(ref_lists)
if action is not None:
remove_from_lists.update(ref_lists)
except ValueError:
continue
elif isinstance(condition, common_policy.Identity) and action is not None:
for identity in condition:
if isinstance(identity, common_policy.IdentityOne) and identity.id == operation.contact.uri:
to_remove.append((identity, condition))
self.pres_rules.dirty = True
elif isinstance(identity, common_policy.IdentityMany) and identity.matches(operation.contact.uri):
identity.append(common_policy.IdentityExcept(operation.contact.uri))
self.pres_rules.dirty = True
elif operation.contact.presence_policies and action is not None:
# This is one of the rules we want to add the contact to
remaining_policies.remove(rule.id)
policy_lists[rule.id] = set()
for condition in (rule.conditions or []):
if isinstance(condition, omapolicy.ExternalList):
try:
ref_lists = self._follow_policy_external_list(resource_lists, condition)
policy_lists[rule.id].update(ref_lists)
except ValueError:
continue
elif isinstance(condition, common_policy.Identity):
if condition.matches(operation.contact.uri):
policy_lists[rule.id].update(candidate_lists) # Any list will do
if remaining_policies:
# Some policies do not exist, they need to be added; therefore, no list will do
pass
elif operation.contact.presence_policies:
lists = policy_lists.popitem()[1]
while policy_lists:
lists = lists.intersection(policy_lists.popitem()[1])
presrules_lists.update(lists)
else:
presrules_lists.update(candidate_lists) # Any list will do
else:
presrules_lists.update(candidate_lists) # Any list will do
notexpanded = deque(presence_lists|dialoginfo_lists|presrules_lists|not_wanted_lists|remove_from_lists)
visited = set(notexpanded)
add_to_presence_list = operation.contact.subscribe_to_presence
add_to_dialoginfo_list = operation.contact.subscribe_to_dialoginfo
while notexpanded:
rlist = notexpanded.popleft()
if rlist in not_wanted_lists and rlist in candidate_lists:
candidate_lists.remove(rlist)
# Some children will need to be revisited so that their descendents are added to appropriate sets
for child in rlist:
if isinstance(child, resourcelists.List):
revisit = False
if rlist in presence_lists:
revisit = (child not in presence_lists) or revisit
presence_lists.add(child)
if rlist in dialoginfo_lists:
revisit = (child not in dialoginfo_lists) or revisit
dialoginfo_lists.add(child)
if rlist in presrules_lists:
revisit = (child not in presrules_lists) or revisit
presrules_lists.add(child)
if rlist in not_wanted_lists:
revisit = (child not in not_wanted_lists) or revisit
not_wanted_lists.add(child)
if rlist in remove_from_lists:
revisit = (child not in remove_from_lists) or revisit
remove_from_lists.add(child)
if child not in visited or revisit:
visited.add(child)
notexpanded.append(child)
elif isinstance(child, resourcelists.External):
try:
ref_lists = self._follow_rl_external(resource_lists, child)
except ValueError:
ref_lists = []
revisit = set()
if rlist in presence_lists:
revisit.update(l for l in ref_lists if l not in presence_lists)
presence_lists.update(ref_lists)
if rlist in dialoginfo_lists:
revisit.update(l for l in ref_lists if l not in dialoginfo_lists)
dialoginfo_lists.update(ref_lists)
if rlist in presrules_lists:
revisit.update(l for l in ref_lists if l not in presrules_lists)
presrules_lists.update(ref_lists)
if rlist in not_wanted_lists:
revisit.update(l for l in ref_lists if l not in not_wanted_lists)
not_wanted_lists.update(ref_lists)
if rlist in remove_from_lists:
revisit.update(l for l in ref_lists if l not in remove_from_lists)
remove_from_lists.update(ref_lists)
visited.update(l for l in ref_lists if l not in visited)
notexpanded.extend(l for l in ref_lists if l not in visited or l in revisit)
elif isinstance(child, resourcelists.EntryRef):
try:
entries = self._follow_rl_entry_ref(resource_lists, child)
except ValueError:
continue
if any(entry.uri == operation.contact.uri for entry in entries):
if rlist in remove_from_lists:
to_remove.append((child, rlist))
self.resource_lists.dirty = True
self.rls_services.dirty = True
if operation.contact.subscribe_to_presence and rlist in presence_lists:
add_to_presence_list = False
if operation.contact.subscribe_to_dialoginfo and rlist in dialoginfo_lists:
add_to_dialoginfo_list = False
elif isinstance(child, resourcelists.Entry):
if child.uri == operation.contact.uri:
if rlist in remove_from_lists:
to_remove.append((child, rlist))
self.resource_lists.dirty = True
self.rls_services.dirty = True
if operation.contact.subscribe_to_presence and rlist in presence_lists:
add_to_presence_list = False
if operation.contact.subscribe_to_dialoginfo and rlist in dialoginfo_lists:
add_to_dialoginfo_list = False
# Update the dialoginfo rules
if self.dialog_rules.supported:
dialog_rules = self.dialog_rules.content
# Create any non-existing rules
if operation.contact.dialoginfo_policies:
for policy in (p for p in operation.contact.dialoginfo_policies if p.id not in dialog_rules):
op = AddDialoginfoPolicyOperation(policy=policy)
handler = getattr(self, '_OH_%s' % op.name)
handler(op)
# Remove any reference to the uri and add it to the correct rule
for rule in dialog_rules:
if not operation.contact.dialoginfo_policies or rule.id not in (policy.id for policy in operation.contact.dialoginfo_policies):
for condition in (rule.conditions or []):
if isinstance(condition, common_policy.Identity):
for identity in condition:
if isinstance(identity, common_policy.IdentityOne) and identity.id == operation.contact.uri:
to_remove.append((identity, condition))
self.dialog_rules.dirty = True
elif isinstance(identity, common_policy.IdentityMany) and identity.matches(operation.contact.uri):
identity.append(common_policy.IdentityExcept(operation.contact.uri))
self.dialog_rules.dirty = True
elif operation.contact.dialoginfo_policies:
# This is one of the rules we want to add the contact to
if rule.conditions is None:
rule.conditions = common_policy.Conditions()
for condition in rule.conditions:
if isinstance(condition, common_policy.Identity):
if not condition.matches(operation.contact.uri):
# First see if there is an exception added for this uri
for identity_condition in condition:
if isinstance(identity_condition, common_policy.IdentityMany):
try:
except_condition = (child for child in identity_condition if isinstance(child, common_policy.IdentityExcept) and child.id==operation.contact.uri).next()
except StopIteration:
continue
else:
identity_condition.remove(except_condition)
self.dialog_rules.dirty = True
break
else:
# Otherwise just add a identity one
condition.append(common_policy.IdentityOne(operation.contact.uri))
self.dialog_rules.dirty = True
break
else:
# No identity condition found, add it
rule.conditions.append(common_policy.Identity([common_policy.IdentityOne(operation.contact.uri)]))
self.dialog_rules.dirty = True
# Remove the elements we wanted to remove
for child, parent in to_remove:
parent.remove(child)
# Identity elements can't be empty
if self.pres_rules.supported:
pres_rules = self.pres_rules.content
for rule in pres_rules:
try:
action = (unicode(action.value) for action in (rule.actions or []) if isinstance(action, presrules.SubHandling)).next()
except StopIteration:
continue # Ignore rules whose actions we don't understand
for condition in (condition for condition in (rule.conditions[:] or []) if isinstance(condition, common_policy.Identity)):
if len(condition) == 0:
rule.conditions.remove(condition)
rule.conditions.append(extensions.FalseCondition())
if self.dialog_rules.supported:
dialog_rules = self.dialog_rules.content
for rule in dialog_rules:
try:
action = (unicode(action.value) for action in (rule.actions or []) if isinstance(action, dialogrules.SubHandling)).next()
except StopIteration:
continue # Ignore rules whose actions we don't understand
for condition in (condition for condition in (rule.conditions[:] or []) if isinstance(condition, common_policy.Identity)):
if len(condition) == 0:
rule.conditions.remove(condition)
rule.conditions.append(extensions.FalseCondition())
# Preferred candidates are candidates where if added, the pres-rules and rls-services document need not be modified.
preferred_candidate_lists = presrules_lists.intersection(candidate_lists)
if operation.contact.subscribe_to_presence:
preferred_candidate_lists.intersection_update(presence_lists)
if operation.contact.subscribe_to_dialoginfo:
preferred_candidate_lists.intersection_update(dialoginfo_lists)
if preferred_candidate_lists:
rlist = preferred_candidate_lists.pop()
else:
rlist = candidate_lists[0]
if self.pres_rules.supported and operation.contact.presence_policies and rlist not in presrules_lists:
pres_rules = self.pres_rules.content
for policy in operation.contact.presence_policies:
try:
rule = pres_rules[policy.id]
except KeyError:
op = AddPresencePolicyOperation(policy=policy)
handler = getattr(self, '_OH_%s' % op.name)
handler(op)
if policy.id not in pres_rules:
continue
rule = pres_rules[policy.id]
if rule.conditions is None:
rule.conditions = []
else:
try:
action = (unicode(action.value) for action in (rule.actions or []) if isinstance(action, presrules.SubHandling)).next()
except StopIteration:
continue # We don't understand this rule, ignore the request to add to this policy
# If we have an identity condition, use that one
identity_conditions = [condition for condition in rule.conditions if isinstance(condition, common_policy.Identity)]
if identity_conditions:
# First see if there is an exception added for this uri
for subcondition in chain(*identity_conditions):
if isinstance(subcondition, common_policy.IdentityMany):
try:
except_condition = (child for child in subcondition if isinstance(child, common_policy.IdentityExcept) and child.id==operation.contact.uri).next()
except StopIteration:
continue
else:
subcondition.remove(except_condition)
break
else:
# Otherwise just add a identity one
identity_conditions[0].append(common_policy.IdentityOne(operation.contact.uri))
elif self.oma_compliant:
external_lists = [condition for condition in rule.conditions if isinstance(condition, omapolicy.ExternalList)]
if external_lists or len(rlist) > 0 or rule.id in ('wp_prs_grantedcontacts', 'wp_prs_blockedcontacts'):
# We cannot modify the references of wp_prs_grantedcontacts and wp_prs_blockedcontacts
for external_list in external_lists:
try:
container_list = (l for l in self._follow_policy_external_list(resource_lists, external_list) if l not in not_wanted_lists).next()
except (ValueError, StopIteration):
continue
else:
break
else:
if rule.id in ('wp_prs_grantedcontacts', 'wp_prs_blockedcontacts'):
# The user wants to add this contact to one of these rules, but the lists referenced by them are not
# good because they are referenced from other places; create a new rule, similar to this one
container_list = resourcelists.List(name=self.unique_name('presrules_group', (list.name for list in resource_lists), skip_preferred_name=True).next())
resource_lists.append(container_list)
new_policy = deepcopy(policy)
new_policy.id = self.unique_name(('wp_prs_allow_onelist_' if new_policy.action=='allow' else 'wp_prs_onelist_')+container_list.name, (rule.id for rule in pres_rules)).next()
op = AddPresencePolicyOperation(policy=new_policy)
handler = getattr(self, '_OH_%s' % op.name)
handler(op)
rule = pres_rules[new_policy.id]
path = self.resource_lists.uri + '/~~/resource-lists/list[@name="%s"]' % container_list.name
rule.conditions.append(omapolicy.ExternalList([path]))
else:
for external_list in external_lists:
rule.conditions.remove(external_list)
container_list = resourcelists.List(name=self.unique_name('presrules_group', (list.name for list in resource_lists), skip_preferred_name=True).next())
container_list.extend(resourcelists.External(uri) for uri in chain(*external_lists))
resource_lists.append(container_list)
path = self.resource_lists.uri + '/~~/resource-lists/list[@name="%s"]' % container_list.name
rule.conditions.append(omapolicy.ExternalList([path]))
if len(rlist) == 0:
path = self.resource_lists.uri + '/~~' + resource_lists.get_xpath(rlist)
container_list.append(resourcelists.External(path))
else:
container_list.append(resourcelists.Entry(operation.contact.uri))
else:
path = self.resource_lists.uri + '/~~' + resource_lists.get_xpath(rlist)
rule.conditions.append(omapolicy.ExternalList([path]))
else:
rule.conditions.append(common_policy.Identity([common_policy.IdentityOne(operation.contact.uri)]))
self.pres_rules.dirty = True
if self.rls_services.supported and (add_to_presence_list or add_to_dialoginfo_list):
if operation.contact.subscribe_to_presence and not operation.contact.subscribe_to_dialoginfo:
good_lists = presence_lists - dialoginfo_lists
elif not operation.contact.subscribe_to_presence and operation.contact.subscribe_to_dialoginfo:
good_lists = dialoginfo_lists - presence_lists
else:
good_lists = presence_lists & dialoginfo_lists
if rlist not in good_lists:
rls_services = self.rls_services.content
try:
# First, find those lists which are according to what we want (good_lists) and are not unwanted
container_lists = [(l for l in good_lists if l not in not_wanted_lists).next()]
except StopIteration:
# Then find separate lists which are not unwanted
container_lists = []
if add_to_presence_list:
try:
container_lists.append((l for l in presence_lists if l not in not_wanted_lists).next())
except StopIteration:
# Too bad, we have to create a new service
service_list = resourcelists.List(name=self.unique_name('subscribe_group', (list.name for list in resource_lists), skip_preferred_name=True).next())
resource_lists.append(service_list)
path = self.resource_lists.uri + '/~~/resource-lists/list[@name="%s"]' % service_list.name
# Find a new uri
uri_re = re.compile(r'sip:(buddies_.*)@%s' % self.account.id.domain)
taken_names = [m.group(1) for m in (uri_re.match(service.uri) for service in rls_services) if m]
uri = 'sip:%s@%s' % (self.unique_name('buddies', taken_names, skip_preferred_name=True).next(), self.account.id.domain)
# We'll also make it a dialog service just so that it can also be used as a container for dialog
service = rlsservices.Service(uri=uri, list=rlsservices.ResourceList(path), packages=['presence', 'dialog'] if add_to_dialoginfo_list and rlist not in dialoginfo_lists else ['presence'])
rls_services.append(service)
container_lists.append(service_list)
add_to_dialoginfo_list = False
if add_to_dialoginfo_list:
try:
container_lists.append((l for l in dialoginfo_lists if l not in not_wanted_lists).next())
except StopIteration:
# Too bad, we have to create a new service
service_list = resourcelists.List(name=self.unique_name('subscribe_group', (list.name for list in resource_lists), skip_preferred_name=True).next())
resource_lists.append(service_list)
path = self.resource_lists.uri + '/~~/resource-lists/list[@name="%s"]' % service_list.name
# Find a new uri
uri_re = re.compile(r'sip:(buddies_.*)@%s' % self.account.id.domain)
taken_names = [m.group(1) for m in (uri_re.match(service.uri) for service in rls_services) if m]
uri = 'sip:%s@%s' % (self.unique_name('buddies', taken_names, skip_preferred_name=True).next(), self.account.id.domain)
# We'll also make it a presence service just so that it can also be used as a container for presence
service = rlsservices.Service(uri=uri, list=rlsservices.ResourceList(path), packages=['presence', 'dialog'] if operation.contact.subscribe_to_presence and rlist not in presence_lists else ['dialog'])
rls_services.append(service)
container_lists = [service_list] # Don't use the previously determined presence service
for container_list in container_lists:
if container_list is rlist:
continue
if len(rlist) == 0:
path = self.resource_lists.uri + '/~~' + resource_lists.get_xpath(rlist)
container_list.append(resourcelists.External(path))
else:
container_list.append(resourcelists.Entry(operation.contact.uri))
self.rls_services.dirty = True
if rlist is not fallback_candidate:
resource_lists.remove(fallback_candidate)
elif operation.contact.group is not None:
path = self.resource_lists.uri + '/~~' + resource_lists.get_xpath(rlist)
oma_buddylist.append(resourcelists.External(path))
# After all this trouble, we've found a list that can take our new contact: add it to the list!
entry = resourcelists.Entry(uri=operation.contact.uri, display_name=operation.contact.name)
if operation.contact.attributes:
entry.attributes = extensions.EntryAttributes((key, value) for key, value in operation.contact.attributes.iteritems() if value is not None)
rlist.append(entry)
self.resource_lists.dirty = True
def _OH_update_contact(self, operation):
if not self.resource_lists.supported:
return
resource_lists = self.resource_lists.content
try:
oma_buddylist = (child for child in resource_lists if isinstance(child, resourcelists.List) and child.name=='oma_buddylist').next()
except StopIteration:
# This should never happen as the document is normalized
return
if not set(['uri', 'group', 'presence_policies', 'dialoginfo_policies', 'subscribe_to_presence', 'subscribe_to_dialoginfo']).intersection(operation.attributes):
# If none of these attributes are specified, then we only need to look at the resource-lists document
# and since we prefer keeping the name and additional attributes in the buddies entry, start with oma_buddylist
# and do a DFS.
notexpanded = deque([oma_buddylist]+[l for l in resource_lists if l is not oma_buddylist])
visited = set(notexpanded)
entries = []
while notexpanded:
rlist = notexpanded.popleft()
for child in rlist:
if isinstance(child, resourcelists.List) and child not in visited:
visited.add(child)
notexpanded.appendleft(child)
elif isinstance(child, resourcelists.External):
try:
ref_lists = set(l for l in self._follow_rl_external(resource_lists, child) if l not in visited)
except ValueError:
ref_lists = set()
visited.update(ref_lists)
notexpanded.extendleft(ref_lists)
elif isinstance(child, resourcelists.EntryRef):
try:
ref_entries = self._follow_rl_entry_ref(resource_lists, child)
except ValueError:
continue
entries.extend(entry for entry in ref_entries if entry.uri==operation.contact.uri)
elif isinstance(child, resourcelists.Entry) and child.uri == operation.contact.uri:
entries.append(child)
if entries:
entry = entries[0]
else:
# This contact might be referenced only from a rls-services, pres-rules or dialog-rules document; create a list
# for any additional information we want to store about the contact
rlist = resourcelists.List(name=self.unique_name('group', (list.name for list in resource_lists), skip_preferred_name=True).next())
resource_lists.append(rlist)
entry = resourcelists.Entry(uri=operation.contact.uri, display_name=operation.contact.name)
rlist.append(entry)
if 'name' in operation.attributes:
entry.display_name = operation.attributes.pop('name')
if operation.attributes and entry.attributes is None:
entry.attributes = extensions.EntryAttributes()
for key, value in operation.attributes.iteritems():
if value is None:
entry.attributes.pop(key, None)
else:
entry.attributes[key] = value
self.resource_lists.dirty = True
else:
# We may need to move the contact to a different place; the logic would be too complicated, so first remove the contact and then add it.
# We retrieve any missing information from what currently exists
contact = Contact(operation.attributes.pop('name', Null), operation.attributes.pop('uri', operation.contact.uri), operation.attributes.pop('group', Null))
contact.presence_policies = operation.attributes.pop('presence_policies', Null)
contact.dialoginfo_policies = operation.attributes.pop('dialoginfo_policies', Null)
contact.subscribe_to_presence = operation.attributes.pop('subscribe_to_presence', Null)
contact.subscribe_to_dialoginfo = operation.attributes.pop('subscribe_to_dialoginfo', Null)
contact.attributes = dict(operation.attributes)
if contact.presence_policies is None:
contact.presence_policies = set()
elif contact.presence_policies is not Null and self.oma_compliant:
# Filter out policies we mustn't add contacts to
contact.presence_policies = set(policy for policy in contact.presence_policies if policy.id not in ('wp_prs_unlisted', 'wp_prs_allow_unlisted', 'wp_prs_block_anonymous', 'wp_prs_allow_own'))
if contact.dialoginfo_policies is None:
contact.dialoginfo_policies = set()
elif contact.dialoginfo_policies is not Null:
contact.dialoginfo_policies = set(contact.dialoginfo_policies)
presence_lists = set()
dialoginfo_lists = set()
list_policies = {} # Maps a resourcelists.List to a list of PresencePolicy objects
buddy_lists = set([oma_buddylist])
if self.rls_services.supported and Null in (contact.subscribe_to_presence, contact.subscribe_to_dialoginfo):
# Only bother reading the rls services document if we don't change at least one of the subsccribe_to_* flags
rls_services = self.rls_services.content
for service in rls_services:
packages = set(package.value for package in (service.packages or []))
if isinstance(service.list, rlsservices.RLSList):
expanded_list = [service.list]
elif isinstance(service.list, rlsservices.ResourceList):
try:
expanded_list = self._follow_rls_resource_list(resource_lists, service.list)
except ValueError:
expanded_list = []
else:
expanded_list = []
if 'presence' in packages:
presence_lists.update(expanded_list)
if 'dialog' in packages:
dialoginfo_lists.update(expanded_list)
if self.pres_rules.supported and contact.presence_policies is Null:
pres_rules = self.pres_rules.content
contact.presence_policies = set()
for rule in pres_rules:
try:
action = (unicode(action.value) for action in (rule.actions or []) if isinstance(action, presrules.SubHandling)).next()
except StopIteration:
continue # Ignore rules whose actions we don't understand
policy = PresencePolicy(rule.id, action)
for condition in (rule.conditions or []):
if isinstance(condition, omapolicy.ExternalList):
try:
ref_lists = self._follow_policy_external_list(resource_lists, condition)
except ValueError:
continue
else:
for ref_list in ref_lists:
list_policies.setdefault(ref_list, set()).add(policy)
elif isinstance(condition, common_policy.Identity):
if condition.matches(operation.contact.uri):
contact.presence_policies.add(policy)
break
if self.dialog_rules.supported and contact.dialoginfo_policies is Null:
dialog_rules = self.dialog_rules.content
contact.dialoginfo_policies = set()
for rule in dialog_rules:
try:
action = (unicode(action.value) for action in (rule.actions or []) if isinstance(action, dialogrules.SubHandling)).next()
except StopIteration:
continue # Ignore rules whose actions we don't understand
policy = DialoginfoPolicy(rule.id, action)
for condition in (rule.conditions or []):
if isinstance(condition, common_policy.Identity) and condition.matches(operation.contact.uri):
contact.dialoginfo_policies.add(policy)
break
notexpanded = deque((l, l.display_name.value if l.display_name else None) for l in resource_lists if isinstance(l, resourcelists.List))
visited = set(notexpanded)
while notexpanded:
rlist, list_name = notexpanded.popleft()
# Some children will need to be revisited so that their descendents are added to appropriate sets
for child in rlist:
if isinstance(child, resourcelists.List):
revisit = False
if rlist in presence_lists:
revisit = (child not in presence_lists) or revisit
presence_lists.add(child)
if rlist in dialoginfo_lists:
revisit = (child not in dialoginfo_lists) or revisit
dialoginfo_lists.add(child)
if rlist in list_policies:
revisit = (child not in list_policies or not list_policies[child].issuperset(list_policies[rlist])) or revisit
list_policies.setdefault(child, set()).update(list_policies[rlist])
if rlist in buddy_lists:
revisit = (child not in buddy_lists) or revisit
buddy_lists.add(child)
if child not in visited or revisit:
visited.add(child)
notexpanded.append((child, child.display_name.value if child.display_name else list_name))
elif isinstance(child, resourcelists.External):
try:
ref_lists = self._follow_rl_external(resource_lists, child)
except ValueError:
ref_lists = []
revisit = set()
if rlist in presence_lists:
revisit.update(l for l in ref_lists if l not in presence_lists)
presence_lists.update(ref_lists)
if rlist in dialoginfo_lists:
revisit.update(l for l in ref_lists if l not in dialoginfo_lists)
dialoginfo_lists.update(ref_lists)
if rlist in list_policies:
revisit.update(l for l in ref_lists if l not in list_policies or not list_policies[l].issuperset(list_policies[rlist]))
for l in ref_lists:
list_policies.setdefault(l, set()).update(list_policies[rlist])
if rlist in buddy_lists:
revisit.update(l for l in ref_lists if l not in buddy_lists)
buddy_lists.update(ref_lists)
visited.update(l for l in ref_lists if l not in visited)
if child.display_name:
notexpanded.extend((l, child.display_name.value) for l in ref_lists if l not in visited or l in revisit)
else:
notexpanded.extend((l, l.display_name.value if l.display_name else list_name) for l in ref_lists if l not in visited or l in revisit)
elif isinstance(child, resourcelists.EntryRef):
try:
entries = self._follow_rl_entry_ref(resource_lists, child)
except ValueError:
continue
if any(entry.uri==operation.contact.uri for entry in entries):
if rlist in list_policies:
contact.presence_policies.update(list_policies[rlist])
if contact.name is Null and rlist in buddy_lists and child.display_name:
contact.name = child.display_name.value
for entry in (e for e in entries if e.uri==operation.contact.uri):
if contact.group is Null and rlist in buddy_lists and list_name is not None:
contact.group = list_name
if contact.name is Null and rlist in buddy_lists and entry.display_name:
contact.name = entry.display_name.value
if contact.subscribe_to_presence is Null and rlist in presence_lists:
contact.subscribe_to_presence = True
if contact.subscribe_to_dialoginfo is Null and rlist in dialoginfo_lists:
contact.subscribe_to_dialoginfo = True
if entry.attributes and rlist in buddy_lists:
for key in (key for key in entry.attributes if key not in contact.attributes):
contact.attributes[key] = entry.attributes[key]
elif isinstance(child, resourcelists.Entry):
if child.uri == operation.contact.uri:
if rlist in list_policies:
contact.presence_policies.update(list_policies[rlist])
if contact.group is Null and rlist in buddy_lists and list_name is not None:
contact.group = list_name
if contact.name is Null and rlist in buddy_lists and child.display_name:
contact.name = child.display_name.value
if contact.subscribe_to_presence is Null and rlist in presence_lists:
contact.subscribe_to_presence = True
if contact.subscribe_to_dialoginfo is Null and rlist in dialoginfo_lists:
contact.subscribe_to_dialoginfo = True
if child.attributes and rlist in buddy_lists:
for key in (key for key in child.attributes if key not in contact.attributes):
contact.attributes[key] = child.attributes[key]
if contact.name is Null:
contact.name = operation.contact.uri
if contact.group is Null:
# We still don't know where to add this contact, just assume we don't want it as a buddy
contact.group = operation.contact.group
contact.presence_policies = list(contact.presence_policies) if contact.presence_policies is not Null else operation.contact.presence_policies
contact.dialoginfo_policies = list(contact.dialoginfo_policies) if contact.dialoginfo_policies is not Null else operation.contact.dialoginfo_policies
if contact.subscribe_to_presence is Null:
contact.subscribe_to_presence = operation.contact.subscribe_to_presence
if contact.subscribe_to_dialoginfo is Null:
contact.subscribe_to_dialoginfo = operation.contact.subscribe_to_dialoginfo
contact.attributes = dict((key, value) for key, value in contact.attributes.iteritems() if value is not None)
# Now we can delete the contact and add it again
ops = [RemoveContactOperation(contact=operation.contact), AddContactOperation(contact=contact)]
for op in ops:
handler = getattr(self, '_OH_%s' % op.name)
handler(op)
def _OH_remove_contact(self, operation):
if not self.resource_lists.supported:
return
resource_lists = self.resource_lists.content
try:
oma_buddylist = (child for child in resource_lists if isinstance(child, resourcelists.List) and child.name=='oma_buddylist').next()
except StopIteration:
# This should never happen as the document is normalized
return
lists = set([oma_buddylist])
to_remove = []
# Get all rls lists
if self.rls_services.supported:
rls_services = self.rls_services.content
for service in rls_services:
packages = set(package.value for package in (service.packages or []))
if isinstance(service.list, rlsservices.RLSList):
expanded_list = [service.list]
elif isinstance(service.list, rlsservices.ResourceList):
try:
expanded_list = self._follow_rls_resource_list(resource_lists, service.list)
except ValueError:
expanded_list = []
else:
expanded_list = []
if 'presence' in packages or 'dialog' in packages:
lists.update(expanded_list)
# Get all pres-rules lists and update the pres-rules document so that it doesn't contain references to this contact
if self.pres_rules.supported:
pres_rules = self.pres_rules.content
for rule in pres_rules:
try:
action = (unicode(action.value) for action in (rule.actions or []) if isinstance(action, presrules.SubHandling)).next()
except StopIteration:
continue # Ignore rules whose actions we don't understand
for condition in (rule.conditions or []):
if isinstance(condition, omapolicy.ExternalList):
try:
ref_lists = self._follow_policy_external_list(resource_lists, condition)
except ValueError:
continue
else:
lists.update(ref_lists)
elif isinstance(condition, common_policy.Identity):
for identity in condition:
if isinstance(identity, common_policy.IdentityOne) and identity.id == operation.contact.uri:
to_remove.append((identity, condition))
self.pres_rules.dirty = True
elif isinstance(identity, common_policy.IdentityMany) and identity.matches(operation.contact.uri):
identity.append(common_policy.IdentityExcept(operation.contact.uri))
self.pres_rules.dirty = True
# Update the dialoginfo rules
if self.dialog_rules.supported:
dialog_rules = self.dialog_rules.content
# Remove any reference to the uri and add it to the correct rule
for rule in dialog_rules:
try:
action = (unicode(action.value) for action in (rule.actions or []) if isinstance(action, dialogrules.SubHandling)).next()
except StopIteration:
continue # Ignore rules whose actions we don't understand
for condition in (rule.conditions or []):
if isinstance(condition, common_policy.Identity):
for identity in condition:
if isinstance(identity, common_policy.IdentityOne) and identity.id == operation.contact.uri:
to_remove.append((identity, condition))
self.dialog_rules.dirty = True
elif isinstance(identity, common_policy.IdentityMany) and identity.matches(operation.contact.uri):
identity.append(common_policy.IdentityExcept(operation.contact.uri))
self.dialog_rules.dirty = True
notexpanded = deque(lists)
visited = set(notexpanded)
while notexpanded:
rlist = notexpanded.popleft()
for child in rlist:
if isinstance(child, resourcelists.List) and child not in visited:
visited.add(child)
notexpanded.append(child)
elif isinstance(child, resourcelists.External):
try:
ref_lists = set(l for l in self._follow_rl_external(resource_lists, child) if l not in visited)
except ValueError:
ref_lists = set()
visited.update(ref_lists)
notexpanded.extend(ref_lists)
elif isinstance(child, resourcelists.EntryRef):
try:
entries = self._follow_rl_entry_ref(resource_lists, child)
except ValueError:
continue
if any(entry.uri == operation.contact.uri for entry in entries):
to_remove.append((child, rlist))
self.resource_lists.dirty = True
self.rls_services.dirty = True
elif isinstance(child, resourcelists.Entry):
if child.uri == operation.contact.uri:
to_remove.append((child, rlist))
self.resource_lists.dirty = True
self.rls_services.dirty = True
for child, parent in to_remove:
parent.remove(child)
# Identity elements can't be empty
if self.pres_rules.supported:
pres_rules = self.pres_rules.content
for rule in pres_rules:
try:
action = (unicode(action.value) for action in (rule.actions or []) if isinstance(action, presrules.SubHandling)).next()
except StopIteration:
continue # Ignore rules whose actions we don't understand
for condition in (condition for condition in (rule.conditions[:] or []) if isinstance(condition, common_policy.Identity)):
if len(condition) == 0:
rule.conditions.remove(condition)
rule.conditions.append(extensions.FalseCondition())
if self.dialog_rules.supported:
dialog_rules = self.dialog_rules.content
for rule in dialog_rules:
try:
action = (unicode(action.value) for action in (rule.actions or []) if isinstance(action, dialogrules.SubHandling)).next()
except StopIteration:
continue # Ignore rules whose actions we don't understand
for condition in (condition for condition in (rule.conditions[:] or []) if isinstance(condition, common_policy.Identity)):
if len(condition) == 0:
rule.conditions.remove(condition)
rule.conditions.append(extensions.FalseCondition())
def _OH_add_presence_policy(self, operation):
if not self.pres_rules.supported or operation.policy.id in ('wp_prs_unlisted', 'wp_prs_allow_unlisted', 'wp_prs_grantedcontacts', 'wp_prs_blockedcontacts', 'wp_prs_block_anonymous', 'wp_prs_allow_own'):
return
if operation.policy.id is not None and (operation.policy.id.startswith('wp_prs_allow_one_') or operation.policy.id.startswith('wp_prs_one_')):
return
if operation.policy.action is None:
return
pres_rules = self.pres_rules.content
if operation.policy.id is None and self.oma_compliant and not operation.policy.multi_identity_conditions:
operation.policy.id = self.unique_name('wp_prs_allow_onelist_presrules_group' if operation.policy.action=='allow' else 'wp_prs_onelist_presrules_group', (rule.id for rule in pres_rules), skip_preferred_name=True).next()
elif operation.policy.id is None:
operation.policy.id = self.unique_name('rule', (rule.id for rule in pres_rules), skip_preferred_name=True).next()
elif operation.policy.id in pres_rules:
return
elif (operation.policy.id.startswith('wp_prs_allow_onelist_') or operation.policy.id.startswith('wp_prs_onelist_')) and operation.policy.multi_identity_conditions:
operation.policy.id = self.unique_name('rule', (rule.id for rule in pres_rules), skip_preferred_name=True).next()
rule = common_policy.Rule(operation.policy.id, conditions=[], actions=[], transformations=[])
#rule.display_name = operation.policy.name # Rule display-name extension
rule.actions.append(presrules.SubHandling(operation.policy.action))
if operation.policy.sphere:
rule.conditions.append(common_policy.Sphere(operation.policy.sphere))
if operation.policy.validity:
rule.conditions.append(common_policy.Validity(operation.policy.validity))
if operation.policy.action == 'allow':
if operation.policy.provide_devices is All:
rule.transformations.append(presrules.ProvideDevices(all=True))
elif operation.policy.provide_devices:
provide_devices = presrules.ProvideDevices()
for component in provide_devices:
if isinstance(component, Class):
provide_devices.append(presrules.Class(component))
elif isinstance(component, OccurenceID):
provide_devices.append(presrules.OccurenceID(component))
elif isinstance(component, DeviceID):
provide_devices.append(presrules.DeviceID(component))
rule.transformations.append(provide_devices)
if operation.policy.provide_persons is All:
rule.transformations.append(presrules.ProvidePersons(all=True))
elif operation.policy.provide_persons:
provide_persons = presrules.ProvidePersons()
for component in provide_persons:
if isinstance(component, Class):
provide_persons.append(presrules.Class(component))
elif isinstance(component, OccurenceID):
provide_persons.append(presrules.OccurenceID(component))
rule.transformations.append(provide_persons)
if operation.policy.provide_services is All:
rule.transformations.append(presrules.ProvideServices(all=True))
else:
provide_services = presrules.ProvideServices()
for component in provide_services:
if isinstance(component, Class):
provide_services.append(presrules.Class(component))
elif isinstance(component, OccurenceID):
provide_services.append(presrules.OccurenceID(component))
elif isinstance(component, ServiceURI):
provide_services.append(presrules.ServiceURI(component))
elif isinstance(component, ServiceURIScheme):
provide_services.append(presrules.ServiceURIScheme(component))
rule.transformations.append(provide_services)
if operation.policy.provide_all_attributes:
rule.transformations.append(presrules.ProvideAllAttributes())
else:
attribute_class = {'provide_activities': presrules.ProvideActivities,
'provide_class': presrules.ProvideClass,
'provide_device_id': presrules.ProvideDeviceID,
'provide_mood': presrules.ProvideMood,
'provide_place_is': presrules.ProvidePlaceIs,
'provide_place_type': presrules.ProvidePlaceType,
'provide_privacy': presrules.ProvidePrivacy,
'provide_relationship': presrules.ProvideRelationship,
'provide_status_icon': presrules.ProvideStatusIcon,
'provide_sphere': presrules.ProvideSphere,
'provide_time_offset': presrules.ProvideTimeOffset,
'provide_user_input': presrules.ProvideUserInput,
'provide_unknown_attributes': presrules.ProvideUnknownAttribute}
for attribute, cls in attribute_class.iteritems():
value = getattr(operation.policy, attribute)
if value is not None:
rule.transformations.append(cls(value))
if self.oma_compliant and self.resource_lists.supported and (rule.id.startswith('wp_prs_allow_onelist_') or rule.id.startswith('wp_prs_onelist_')):
resource_lists = self.resource_lists.content
preferred_name = rule.id[len('wp_prs_allow_onelist_'):] if rule.id.startswith('wp_prs_allow_onelist') else rule.id[len('wp_prs_onelist_'):]
rlist = resourcelists.List(name=self.unique_name(preferred_name, (list.name for list in resource_lists)).next())
resource_lists.append(rlist)
path = self.resource_lists.uri + '/~~' + resource_lists.get_xpath(rlist)
rule.conditions.append(omapolicy.ExternalList([path]))
self.resource_lists.dirty = True
else:
identity_condition = common_policy.Identity()
for multi_identity_condition in (operation.policy.multi_identity_conditions or []):
if isinstance(multi_identity_condition, CatchAllCondition):
condition = common_policy.IdentityMany()
identity_condition.append(condition)
for exception in multi_identity_condition.exceptions:
if isinstance(exception, DomainException):
condition.append(common_policy.IdentityExcept(domain=exception.domain))
elif isinstance(exception, UserException):
condition.append(common_policy.IdentityExcept(id=exception.uri))
elif isinstance(multi_identity_condition, DomainCondition):
condition = common_policy.IdentityMany(domain=multi_identity_condition.domain)
identity_condition.append(condition)
for exception in multi_identity_condition.exceptions:
if isinstance(exception, UserException):
condition.append(common_policy.IdentityExcept(id=exception.uri))
if len(identity_condition) == 0:
rule.conditions.append(extensions.FalseCondition())
else:
rule.conditions.append(identity_condition)
pres_rules.append(rule)
self.pres_rules.dirty = True
def _OH_update_presence_policy(self, operation):
if not self.pres_rules.supported or operation.policy.id in ('wp_prs_unlisted', 'wp_prs_allow_unlisted', 'wp_prs_grantedcontacts', 'wp_prs_blockedcontacts', 'wp_prs_block_anonymous', 'wp_prs_allow_own'):
return
pres_rules = self.pres_rules.content
if operation.policy.id is None or operation.policy.id not in pres_rules:
if 'action' not in operation.attributes:
# We cannot assume what this policy's action was
return
policy = PresencePolicy(operation.policy.id, operation.attributes.pop('action'))
if 'name' in operation.attributes:
policy.name = operation.attributes.pop('name')
if 'sphere' in operation.attributes:
policy.sphere = operation.attributes.pop('sphere')
if 'validity' in operation.attributes:
policy.validity = operation.attributes.pop('validity')
if 'multi_identity_conditions' in operation.attributes:
policy.multi_identity_conditions = operation.attributes.pop('multi_identity_conditions')
if 'provide_devices' in operation.attributes:
policy.provide_devices = operation.attributes.pop('provide_devices')
if 'provide_persons' in operation.attributes:
policy.provide_persons = operation.attributes.pop('provide_persons')
if 'provide_services' in operation.attributes:
policy.provide_services = operation.attributes.pop('provide_services')
if operation.attributes.get('provide_all_attributes', False):
policy.provide_all_attributes = operation.attributes.pop('provide_all_attributes')
else:
policy.provide_all_attributes = operation.attributes.pop('provide_all_attributes', False)
for key, value in operation.attributes.iteritems():
setattr(policy, key, value)
op = AddPresencePolicyOperation(policy=policy)
handler = getattr(self, '_OH_%s' % op.name)
handler(op)
return
rule = pres_rules[operation.policy.id]
if operation.attributes.get('multi_identity_conditions', None) and (rule.id.startswith('wp_prs_allow_onelist_') or \
rule.id.startswith('wp_prs_onelist') or any(isinstance(condition, omapolicy.ExternalList) for condition in (rule.conditions or []))):
# We canot add multi identity conditions to this rule, so create a new one using whatever data from the old one
try:
action = (unicode(action.value) for action in (rule.actions or []) if isinstance(action, presrules.SubHandling)).next()
except StopIteration:
action = None
policy = PresencePolicy(None, operation.attributes.pop('action', action))
policy.name = operation.attributes.pop('name', None) # rule.display_name.value if rule.display_name else None # Rule display-name extension
try:
sphere = (condition.value for condition in (rule.conditions or []) if isinstance(condition, common_policy.Sphere)).next()
except StopIteration:
sphere = None
policy.sphere = operation.attributes.pop('sphere', sphere)
try:
validity = list((condition for condition in (rule.conditions or []) if isinstance(condition, common_policy.Validity)).next())
except StopIteration:
validity = None
policy.validity = operation.attributes.pop('validity', validity)
transformation_attribute = {presrules.ProvideDeviceID: 'provide_device_id',
presrules.ProvidePlaceType: 'provide_place_type',
presrules.ProvidePrivacy: 'provide_privacy',
presrules.ProvideRelationship: 'provide_relationship',
presrules.ProvideUnknownAttribute: 'provide_unknown_attributes',
presrules.ProvidePlaceIs: 'provide_place_is',
presrules.ProvideClass: 'provide_class',
presrules.ProvideUserInput: 'provide_user_input',
presrules.ProvideTimeOffset: 'provide_time_offset',
presrules.ProvideStatusIcon: 'provide_status_icon',
presrules.ProvideMood: 'provide_mood',
presrules.ProvideActivities: 'provide_activities',
presrules.ProvideSphere: 'provide_sphere'}
policy.provide_all_attributes = rule.transformations is None
for transformation in (rule.transformations or []):
if transformation.__class__ in transformation_attribute:
setattr(policy, transformation_attribute[transformation.__class__], transformation.value)
elif isinstance(transformation, presrules.ProvideAllAttributes):
policy.provide_all_attributes = True
elif isinstance(transformation, presrules.ProvideDevices):
if transformation.all:
policy.provide_devices = All
else:
policy.provide_devices = []
for component in transformation:
if isinstance(component, presrules.Class):
policy.provide_devices.append(Class(component.value))
elif isinstance(component, presrules.OccurenceID):
policy.provide_devices.append(OccurenceID(component.value))
elif isinstance(component, presrules.DeviceID):
policy.provide_devices.append(DeviceID(component.value))
elif isinstance(transformation, presrules.ProvidePersons):
if transformation.all:
policy.provide_persons = All
else:
policy.provide_persons = []
for component in transformation:
if isinstance(component, presrules.Class):
policy.provide_persons.append(Class(component.value))
elif isinstance(component, presrules.OccurenceID):
policy.provide_persons.append(OccurenceID(component.value))
elif isinstance(transformation, presrules.ProvideServices):
if transformation.all:
policy.provide_services = All
else:
policy.provide_services = []
for component in transformation:
if isinstance(component, presrules.Class):
policy.provide_services.append(Class(component.value))
elif isinstance(component, presrules.OccurenceID):
policy.provide_services.append(OccurenceID(component.value))
elif isinstance(component, presrules.ServiceURI):
policy.provide_services.append(ServiceURI(component.value))
elif isinstance(component, presrules.ServiceURIScheme):
policy.provide_services.append(ServiceURIScheme(component.value))
policy.provide_devices = operation.attributes.pop('provide_devices', policy.provide_devices)
policy.provide_persons = operation.attributes.pop('provide_persons', policy.provide_persons)
policy.provide_services = operation.attributes.pop('provide_persons', policy.provide_persons)
if operation.attributes.get('provide_all_attributes', policy.provide_all_attributes):
policy.provide_all_attributes = operation.attributes.pop('provide_all_attributes', True)
else:
policy.provide_all_attributes = operation.attributes.pop('provide_all_attributes', False)
for key, value in operation.attributes.iteritems():
setattr(policy, key, value)
op = AddPresencePolicyOperation(policy=policy)
handler = getattr(self, '_OH_%s' % op.name)
handler(op)
return
if rule.conditions is None:
rule.conditions = []
if rule.transformations is None:
rule.transformations = []
if 'name' in operation.attributes:
# rule.display_name = operation.attributes.pop('name') # Rule display-name extension
operation.attributes.pop('name')
if 'action' in operation.attributes:
rule.actions = [presrules.SubHandling(operation.attributes.pop('action'))]
if 'sphere' in operation.attributes:
sphere = operation.attributes.pop('sphere')
try:
condition = (condition for condition in rule.conditions if isinstance(condition, common_policy.Sphere)).next()
except StopIteration:
if sphere is not None:
rule.conditions.append(common_policy.Sphere(sphere))
else:
if sphere is not None:
condition.value = sphere
else:
rule.conditions.remove(condition)
if 'validity' in operation.attributes:
validity = operation.attributes.pop('validity')
try:
condition = (condition for condition in rule.conditions if isinstance(condition, common_policy.Validity)).next()
except StopIteration:
if validity is not None:
rule.conditions.append(common_policy.Validity(validity))
else:
if validity is not None:
condition[:] = validity
else:
rule.conditions.remove(condition)
try:
action = (unicode(action.value) for action in (rule.actions or []) if isinstance(action, presrules.SubHandling)).next()
except StopIteration:
action = None
if action == 'allow':
attribute_class = {'provide_devices': presrules.ProvideDevices,
'provide_persons': presrules.ProvidePersons,
'provide_services': presrules.ProvideServices}
for attribute, cls in attribute_class.iteritems():
value = operation.attributes.pop('provide_devices', Null)
if value is Null:
continue
try:
transformation = (transformation for transformation in rule.transformations if isinstance(transformation, cls)).next()
except StopIteration:
if value is All:
rule.transformations.append(cls(all=True))
elif value is not None:
rule.transformations.append(cls(value))
else:
if value is All:
transformation.all = True
elif value is not None:
del transformation[:]
for component in value:
if isinstance(component, Class):
transformation.append(presrules.Class(component))
elif isinstance(component, OccurenceID):
transformation.append(presrules.OccurenceID(component))
elif isinstance(component, DeviceID) and attribute == 'provide_devices':
transformation.append(presrules.DeviceID(component))
elif isinstance(component, ServiceURI) and attribute == 'provide_services':
transformation.append(presrules.ServiceURI(component))
elif isinstance(component, ServiceURIScheme) and attribute == 'provide_services':
transformation.append(presrules.ServiceURIScheme(component))
else:
rule.transformations.remove(transformation)
provide_all_attributes = operation.attributes.pop('provide_all_attributes', Null)
if provide_all_attributes is True:
rule.transformations.clear()
rule.transformations.append(presrules.ProvideAllAttributes())
elif not (provide_all_attributes is Null and any(isinstance(transformation, presrules.ProvideAllAttributes) for transformation in rule.transformations)):
if provide_all_attributes is False:
for transformation in [t for t in rule.transformations if isinstance(t, presrules.ProvideAllAttributes)]:
rule.transformations.remove(transformation)
attribute_class = {'provide_activities': presrules.ProvideActivities,
'provide_class': presrules.ProvideClass,
'provide_device_id': presrules.ProvideDeviceID,
'provide_mood': presrules.ProvideMood,
'provide_place_is': presrules.ProvidePlaceIs,
'provide_place_type': presrules.ProvidePlaceType,
'provide_privacy': presrules.ProvidePrivacy,
'provide_relationship': presrules.ProvideRelationship,
'provide_status_icon': presrules.ProvideStatusIcon,
'provide_sphere': presrules.ProvideSphere,
'provide_time_offset': presrules.ProvideTimeOffset,
'provide_user_input': presrules.ProvideUserInput,
'provide_unknown_attributes': presrules.ProvideUnknownAttribute}
for attribute, cls in attribute_class.iteritems():
value = operation.attributes.pop(attribute, Null)
if value is Null:
continue
try:
transformation = (transformation for transformation in rule.transformations if isinstance(transformation, cls)).next()
except StopIteration:
if value is not None:
rule.transformations.append(cls(value))
else:
if value is not None:
transformation.value = value
else:
rule.transformations.remove(transformation)
elif self.oma_compliant:
# No transformations are allowed if action is not 'allow'
rule.transformations = []
if not any(isinstance(condition, (common_policy.Identity, omapolicy.ExternalList)) for condition in rule.conditions):
if self.oma_compliant and self.resource_lists.supported and (rule.id.startswith('wp_prs_allow_onelist_') or rule.id.startswith('wp_prs_onelist_')):
resource_lists = self.resource_lists.content
preferred_name = rule.id[len('wp_prs_allow_onelist_'):] if rule.id.startswith('wp_prs_allow_onelist') else rule.id[len('wp_prs_onelist_'):]
rlist = resourcelists.List(name=self.unique_name(preferred_name, (list.name for list in resource_lists)).next())
resource_lists.append(rlist)
path = self.resource_lists.uri + '/~~' + resource_lists.get_xpath(rlist)
rule.conditions.append(omapolicy.ExternalList([path]))
self.resource_lists.dirty = True
try:
identity_condition = (condition for condition in rule.conditions if isinstance(condition, common_policy.Identity)).next()
except StopIteration:
identity_condition = common_policy.Identity()
if 'multi_identity_conditions' in operation.attributes:
for multi_identity_condition in [id_condition for id_condition in identity_condition if isinstance(id_condition, common_policy.IdentityMany)]:
identity_condition.remove(multi_identity_condition)
for multi_identity_condition in (operation.attributes.pop('multi_identity_conditions') or []):
if isinstance(multi_identity_condition, CatchAllCondition):
condition = common_policy.IdentityMany()
identity_condition.append(condition)
for exception in multi_identity_condition.exceptions:
if isinstance(exception, DomainException):
condition.append(common_policy.IdentityExcept(domain=exception.domain))
elif isinstance(exception, UserException):
condition.append(common_policy.IdentityExcept(id=exception.uri))
elif isinstance(multi_identity_condition, DomainCondition):
condition = common_policy.IdentityMany(domain=multi_identity_condition.domain)
identity_condition.append(condition)
for exception in multi_identity_condition.exceptions:
if isinstance(exception, UserException):
condition.append(common_policy.IdentityExcept(id=exception.uri))
# Identity condition can't be empty
if not identity_condition:
rule.conditions.append(extensions.FalseCondition())
else:
rule.conditions.append(identity_condition)
self.pres_rules.dirty = True
def _OH_remove_presence_policy(self, operation):
if not self.pres_rules.supported or operation.policy.id in (None, 'wp_prs_unlisted', 'wp_prs_allow_unlisted', 'wp_prs_grantedcontacts', 'wp_prs_blockedcontacts', 'wp_prs_block_anonymous', 'wp_prs_allow_own'):
return
pres_rules = self.pres_rules.content
try:
del pres_rules[operation.policy.id]
except KeyError:
return
self.pres_rules.dirty = True
def _OH_add_dialoginfo_policy(self, operation):
if not self.dialog_rules.supported:
return
dialog_rules = self.dialog_rules.content
if operation.policy.id is None:
operation.policy.id = self.unique_name('rule', (rule.id for rule in dialog_rules), skip_preferred_name=True).next()
elif operation.policy.id in dialog_rules:
return
rule = common_policy.Rule(operation.policy.id, conditions=[], actions=[])
#rule.display_name = operation.policy.name # Rule display-name extension
rule.actions.append(dialogrules.SubHandling(operation.policy.action))
if operation.policy.sphere:
rule.conditions.append(common_policy.Sphere(operation.policy.sphere))
if operation.policy.validity:
rule.conditions.append(common_policy.Validity(operation.policy.validity))
identity_condition = common_policy.Identity()
for multi_identity_condition in (operation.policy.multi_identity_conditions or []):
if isinstance(multi_identity_condition, CatchAllCondition):
condition = common_policy.IdentityMany()
identity_condition.append(condition)
for exception in multi_identity_condition.exceptions:
if isinstance(exception, DomainException):
condition.append(common_policy.IdentityExcept(domain=exception.domain))
elif isinstance(exception, UserException):
condition.append(common_policy.IdentityExcept(id=exception.uri))
elif isinstance(multi_identity_condition, DomainCondition):
condition = common_policy.IdentityMany(domain=multi_identity_condition.domain)
identity_condition.append(condition)
for exception in multi_identity_condition.exceptions:
if isinstance(exception, UserException):
condition.append(common_policy.IdentityExcept(id=exception.uri))
if len(identity_condition) == 0:
rule.conditions.append(extensions.FalseCondition())
else:
rule.conditions.append(identity_condition)
dialog_rules.append(rule)
self.dialog_rules.dirty = True
def _OH_update_dialoginfo_policy(self, operation):
if not self.dialog_rules.supported:
return
dialog_rules = self.dialog_rules.content
if operation.policy.id is None or operation.policy.id not in dialog_rules:
if 'action' not in operation.attributes:
# We cannot assume what this policy's action was
return
policy = DialoginfoPolicy(operation.policy.id, operation.attributes.pop('action'))
if 'name' in operation.attributes:
policy.name = operation.attributes.pop('name')
if 'sphere' in operation.attributes:
policy.sphere = operation.attributes.pop('sphere')
if 'validity' in operation.attributes:
policy.validity = operation.attributes.pop('validity')
op = AddDialoginfoPolicyOperation(policy=policy)
handler = getattr(self, '_OH_%s' % op.name)
handler(op)
return
rule = dialog_rules[operation.policy.id]
if rule.conditions is None:
rule.conditions = []
if 'name' in operation.attributes:
#rule.display_name = operation.attributes.pop('name') # Rule display-name extension
operation.attributes.pop('name')
if 'action' in operation.attributes:
rule.actions = [dialogrules.SubHandling(operation.attributes.pop('action'))]
if 'sphere' in operation.attributes:
sphere = operation.attributes.pop('sphere')
try:
condition = (condition for condition in rule.conditions if isinstance(condition, common_policy.Sphere)).next()
except StopIteration:
if sphere is not None:
rule.conditions.append(common_policy.Sphere(sphere))
else:
if sphere is not None:
condition.value = sphere
else:
rule.conditions.remove(condition)
if 'validity' in operation.attributes:
validity = operation.attributes.pop('validity')
try:
condition = (condition for condition in rule.conditions if isinstance(condition, common_policy.Validity)).next()
except StopIteration:
if validity is not None:
rule.conditions.append(common_policy.Validity(validity))
else:
if validity is not None:
condition[:] = validity
else:
rule.conditions.remove(condition)
try:
identity_condition = (condition for condition in rule.conditions if isinstance(condition, common_policy.Identity)).next()
except StopIteration:
identity_condition = common_policy.Identity()
if 'multi_identity_conditions' in operation.attributes:
for multi_identity_condition in [id_condition for id_condition in identity_condition if isinstance(id_condition, common_policy.IdentityMany)]:
identity_condition.remove(multi_identity_condition)
for multi_identity_condition in (operation.attributes.pop('multi_identity_conditions') or []):
if isinstance(multi_identity_condition, CatchAllCondition):
condition = common_policy.IdentityMany()
identity_condition.append(condition)
for exception in multi_identity_condition.exceptions:
if isinstance(exception, DomainException):
condition.append(common_policy.IdentityExcept(domain=exception.domain))
elif isinstance(exception, UserException):
condition.append(common_policy.IdentityExcept(id=exception.uri))
elif isinstance(multi_identity_condition, DomainCondition):
condition = common_policy.IdentityMany(domain=multi_identity_condition.domain)
identity_condition.append(condition)
for exception in multi_identity_condition.exceptions:
if isinstance(exception, UserException):
condition.append(common_policy.IdentityExcept(id=exception.uri))
# Identity condition can't be empty
if len(identity_condition) == 0:
rule.conditions.append(extensions.FalseCondition())
else:
rule.conditions.append(identity_condition)
self.dialog_rules.dirty = True
def _OH_remove_dialoginfo_policy(self, operation):
if not self.dialog_rules.supported or operation.policy.id is None:
return
dialog_rules = self.dialog_rules.content
try:
del dialog_rules[operation.policy.id]
except KeyError:
return
self.dialog_rules.dirty = True
def _OH_set_status_icon(self, operation):
if not self.status_icon.supported:
return
if operation.icon is None or not operation.icon.data:
self.status_icon.content = None
else:
data = base64.encodestring(operation.icon.data)
mime_type = operation.icon.mime_type if operation.icon.mime_type in ('image/gif', 'image/jpeg', 'image/png') else 'image/jpeg'
self.status_icon.content = prescontent.PresenceContent(data=data, mime_type=mime_type, encoding='base64', description=operation.icon.description)
self.status_icon.dirty = True
def _OH_set_offline_status(self, operation):
if not self.pidf_manipulation.supported:
return
if operation.status is None:
self.pidf_manipulation.content = None
else:
self.pidf_manipulation.content = presdm.PIDF('sip:'+self.account.id)
person = presdm.Person('offline_status')
person.timestamp = presdm.PersonTimestamp()
if operation.status.note:
person.notes.add(presdm.PersonNote(operation.status.note))
if operation.status.activity:
person.activities = rpid.Activities()
person.activities.append(operation.status.activity)
self.pidf_manipulation.content.append(person)
self.pidf_manipulation.dirty = True
# notification handling
#
@run_in_twisted_thread
def handle_notification(self, notification):
handler = getattr(self, '_NH_%s' % notification.name, Null)
handler(notification)
def _NH_CFGSettingsObjectDidChange(self, notification):
if set(['xcap.xcap_root', 'xcap.use_xcap_diff', 'id', 'auth.username', 'auth.password', 'sip.subscribe_interval', 'sip.transport_list']).intersection(notification.data.modified):
self._command_channel.send(Command('reload', modified=notification.data.modified))
def _NH_SIPSubscriptionDidStart(self, notification):
self._data_channel.send(notification)
def _NH_SIPSubscriptionDidEnd(self, notification):
self._data_channel.send(notification)
def _NH_SIPSubscriptionDidFail(self, notification):
if notification.sender is not self.subscription:
self._data_channel.send_exception(SIPSubscriptionDidFail(notification.data.__dict__))
else:
self._command_channel.send(Command('subscribe'))
def _NH_SIPSubscriptionGotNotify(self, notification):
if notification.data.headers.get('Content-Type', Null)[0] == xcapdiff.XCAPDiff.content_type:
try:
xcap_diff = xcapdiff.XCAPDiff.parse(notification.data.body)
except ParserError:
self._command_channel.send(Command('fetch', documents=self.document_names))
else:
applications = set(child.selector.auid for child in xcap_diff if isinstance(child, xcapdiff.Document))
documents = [document.name for document in self.documents if document.application in applications]
self._command_channel.send(Command('fetch', documents=documents))
def _NH_SystemIPAddressDidChange(self, notification):
if self.subscription is not None:
self._command_channel.send(Command('subscribe'))
def _load_data(self):
if not self.resource_lists.supported:
return
resource_lists = self.resource_lists.content
try:
oma_buddylist = (child for child in resource_lists if isinstance(child, resourcelists.List) and child.name=='oma_buddylist').next()
except StopIteration:
# This should never happen as the document is normalized
return
list_presence_policies = {} # Maps a resourcelists.List to a list of PresencePolicy objects
presence_policies = {} # Maps a PresencePolicy to a common_policy.Identity condition if the policy contains one, otherwise to None
dialoginfo_policies = {} # Maps a DialoginfoPolicy to a common_policy.Identity condition if the policy contains one, otherwise to None
presence_lists = set() # resourcelists.List objects which are referenced from an rls service with package presence
dialoginfo_lists = set() # resourcelists.List objects which are referenced from an rls service with package dialog
list_services = {} # Maps a resourcelists.List to the set of services which reference the list
buddy_lists = set([oma_buddylist]) # resourcelists.List objects which are referenced from oma_buddylist
contacts = {} # Maps a URI to a Contact object
services = []
groups = set() # Group names
if self.rls_services.supported:
rls_services = self.rls_services.content
for service_element in rls_services:
packages = set(package.value for package in (service_element.packages or []))
service = Service(service_element.uri, list(packages))
service.entries = set()
services.append(service)
if isinstance(service_element.list, rlsservices.RLSList):
expanded_list = [service_element.list]
elif isinstance(service_element.list, rlsservices.ResourceList):
try:
expanded_list = self._follow_rls_resource_list(resource_lists, service_element.list)
except ValueError:
expanded_list = []
else:
expanded_list = []
if 'presence' in packages:
presence_lists.update(expanded_list)
if 'dialog' in packages:
dialoginfo_lists.update(expanded_list)
for rlist in expanded_list:
list_services.setdefault(rlist, set()).add(service)
if self.pres_rules.supported:
pres_rules = self.pres_rules.content
for rule in pres_rules:
try:
action = (unicode(action.value) for action in (rule.actions or []) if isinstance(action, presrules.SubHandling)).next()
except StopIteration:
continue # Ignore rules whose actions we don't understand
policy = PresencePolicy(rule.id, action)
#policy.name = rule.display_name.value if rule.display_name else None # Rule display-name extension
presence_policies[policy] = None
policy.multi_identity_conditions = []
for condition in (rule.conditions or []):
if isinstance(condition, omapolicy.ExternalList):
try:
ref_lists = self._follow_policy_external_list(resource_lists, condition)
except ValueError:
continue
else:
for ref_list in ref_lists:
list_presence_policies.setdefault(ref_list, set()).add(policy)
elif isinstance(condition, common_policy.Identity):
presence_policies[policy] = condition
for identity_many_condition in (identity_condition for identity_condition in condition if isinstance(identity_condition, common_policy.IdentityMany)):
if identity_many_condition.domain:
multi_condition = DomainCondition(identity_many_condition.domain)
else:
multi_condition = CatchAllCondition()
policy.multi_identity_conditions.append(multi_condition)
for exception_condition in (sub_condition for sub_condition in identity_many_condition if isinstance(sub_condition, common_policy.IdentityExcept)):
if exception_condition.domain:
multi_condition.exceptions.append(DomainException(exception_condition.domain))
elif exception_condition.id:
multi_condition.exceptions.append(UserException(exception_condition.id))
elif isinstance(condition, common_policy.Sphere):
policy.sphere = condition.value
elif isinstance(condition, common_policy.Validity):
policy.validity = list(condition)
transformation_attribute = {presrules.ProvideDeviceID: 'provide_device_id',
presrules.ProvidePlaceType: 'provide_place_type',
presrules.ProvidePrivacy: 'provide_privacy',
presrules.ProvideRelationship: 'provide_relationship',
presrules.ProvideUnknownAttribute: 'provide_unknown_attributes',
presrules.ProvidePlaceIs: 'provide_place_is',
presrules.ProvideClass: 'provide_class',
presrules.ProvideUserInput: 'provide_user_input',
presrules.ProvideTimeOffset: 'provide_time_offset',
presrules.ProvideStatusIcon: 'provide_status_icon',
presrules.ProvideMood: 'provide_mood',
presrules.ProvideActivities: 'provide_activities',
presrules.ProvideSphere: 'provide_sphere'}
policy.provide_all_attributes = rule.transformations is None
for transformation in (rule.transformations or []):
if transformation.__class__ in transformation_attribute:
setattr(policy, transformation_attribute[transformation.__class__], transformation.value)
elif isinstance(transformation, presrules.ProvideAllAttributes):
policy.provide_all_attributes = True
elif isinstance(transformation, presrules.ProvideDevices):
if transformation.all:
policy.provide_devices = All
else:
policy.provide_devices = []
for component in transformation:
if isinstance(component, presrules.Class):
policy.provide_devices.append(Class(component.value))
elif isinstance(component, presrules.OccurenceID):
policy.provide_devices.append(OccurenceID(component.value))
elif isinstance(component, presrules.DeviceID):
policy.provide_devices.append(DeviceID(component.value))
elif isinstance(transformation, presrules.ProvidePersons):
if transformation.all:
policy.provide_persons = All
else:
policy.provide_persons = []
for component in transformation:
if isinstance(component, presrules.Class):
policy.provide_persons.append(Class(component.value))
elif isinstance(component, presrules.OccurenceID):
policy.provide_persons.append(OccurenceID(component.value))
elif isinstance(transformation, presrules.ProvideServices):
if transformation.all:
policy.provide_services = All
else:
policy.provide_services = []
for component in transformation:
if isinstance(component, presrules.Class):
policy.provide_services.append(Class(component.value))
elif isinstance(component, presrules.OccurenceID):
policy.provide_services.append(OccurenceID(component.value))
elif isinstance(component, presrules.ServiceURI):
policy.provide_services.append(ServiceURI(component.value))
elif isinstance(component, presrules.ServiceURIScheme):
policy.provide_services.append(ServiceURIScheme(component.value))
if self.dialog_rules.supported:
dialog_rules = self.dialog_rules.content
for rule in dialog_rules:
try:
action = (unicode(action.value) for action in (rule.actions or []) if isinstance(action, dialogrules.SubHandling)).next()
except StopIteration:
continue # Ignore rules whose actions we don't understand
policy = DialoginfoPolicy(rule.id, action)
#policy.name = rule.display_name.value if rule.display_name else None # Rule display-name extension
dialoginfo_policies[policy] = None
policy.multi_identity_conditions = []
for condition in (rule.conditions or []):
if isinstance(condition, common_policy.Identity):
dialoginfo_policies[policy] = condition
for identity_many_condition in (identity_condition for identity_condition in condition if isinstance(identity_condition, common_policy.IdentityMany)):
if identity_many_condition.domain:
multi_condition = DomainCondition(identity_many_condition.domain)
else:
multi_condition = CatchAllCondition()
policy.multi_identity_conditions.append(multi_condition)
for exception_condition in (sub_condition for sub_condition in identity_many_condition if isinstance(sub_condition, common_policy.IdentityExcept)):
if exception_condition.domain:
multi_condition.exceptions.append(DomainException(exception_condition.domain))
elif exception_condition.id:
multi_condition.exceptions.append(UserException(exception_condition.id))
elif isinstance(condition, common_policy.Sphere):
policy.sphere = condition.value
elif isinstance(condition, common_policy.Validity):
policy.validity = list(condition)
notexpanded = deque((l, l.display_name.value if l.display_name else None) for l in presence_lists|dialoginfo_lists|set(list_presence_policies)|buddy_lists)
visited = set(notexpanded)
while notexpanded:
rlist, list_name = notexpanded.popleft()
if rlist in buddy_lists and list_name is not None:
groups.add(list_name)
# Some children will need to be revisited so that their descendents are added to appropriate sets
for child in rlist:
if isinstance(child, resourcelists.List):
revisit = False
if rlist in presence_lists:
revisit = (child not in presence_lists) or revisit
presence_lists.add(child)
if rlist in dialoginfo_lists:
revisit = (child not in dialoginfo_lists) or revisit
dialoginfo_lists.add(child)
if rlist in list_presence_policies:
revisit = (child not in list_presence_policies) or not list_presence_policies[child].issuperset(list_presence_policies[rlist]) or revisit
list_presence_policies.setdefault(child, set()).update(list_presence_policies[rlist])
if rlist in list_services:
revisit = (child not in list_services) or not list_services[child].issuperset(list_services[rlist]) or revisit
list_services.setdefault(child, set()).update(list_services[rlist])
if rlist in buddy_lists:
revisit = (child not in buddy_lists) or revisit
buddy_lists.add(child)
if child not in visited or revisit:
visited.add(child)
notexpanded.append((child, child.display_name.value if child.display_name else list_name))
elif isinstance(child, resourcelists.External):
try:
ref_lists = self._follow_rl_external(resource_lists, child)
except ValueError:
ref_lists = []
revisit = set()
if rlist in presence_lists:
revisit.update(l for l in ref_lists if l not in presence_lists)
presence_lists.update(ref_lists)
if rlist in dialoginfo_lists:
revisit.update(l for l in ref_lists if l not in dialoginfo_lists)
dialoginfo_lists.update(ref_lists)
if rlist in list_presence_policies:
revisit.update(l for l in ref_lists if l not in list_presence_policies or not list_presence_policies[l].issuperset(list_presence_policies[rlist]))
for l in ref_lists:
list_presence_policies.setdefault(l, set()).update(list_presence_policies[rlist])
if rlist in list_services:
revisit.update(l for l in ref_lists if l not in list_services or not list_services[l].issuperset(list_services[rlist]))
for l in ref_lists:
list_services.setdefault(l, set()).update(list_services[rlist])
if rlist in buddy_lists:
revisit.update(l for l in ref_lists if l not in buddy_lists)
buddy_lists.update(ref_lists)
visited.update(l for l in ref_lists if l not in visited)
if child.display_name:
notexpanded.extend((l, child.display_name.value) for l in ref_lists if l not in visited or l in revisit)
else:
notexpanded.extend((l, l.display_name.value if l.display_name else list_name) for l in ref_lists if l not in visited or l in revisit)
elif isinstance(child, resourcelists.EntryRef):
try:
entries = self._follow_rl_entry_ref(resource_lists, child)
except ValueError:
continue
for service in list_services.get(rlist, ()):
service.entries.update(entry.uri for entry in entries)
for entry in entries:
try:
contact = contacts[entry.uri]
except KeyError:
contact = contacts[entry.uri] = Contact(None, entry.uri, None)
contact.presence_policies = set(policy for policy, identity in presence_policies.iteritems() if identity is not None and identity.matches(contact.uri))
contact.dialoginfo_policies = set(policy for policy, identity in dialoginfo_policies.iteritems() if identity is not None and identity.matches(contact.uri))
contact.subscribe_to_presence = False
contact.subscribe_to_dialoginfo = False
if contact.group is None and rlist in buddy_lists and list_name is not None:
contact.group = list_name
if contact.name is None and rlist in buddy_lists and child.display_name:
contact.name = child.display_name.value
elif contact.name is None and rlist in buddy_lists and entry.display_name:
contact.name = entry.display_name.value
if rlist in list_presence_policies:
contact.presence_policies.update(list_presence_policies[rlist])
if not contact.subscribe_to_presence and rlist in presence_lists:
contact.subscribe_to_presence = True
if not contact.subscribe_to_dialoginfo and rlist in dialoginfo_lists:
contact.subscribe_to_dialoginfo = True
if entry.attributes and rlist in buddy_lists:
for key in (key for key in entry.attributes if key not in contact.attributes):
contact.attributes[key] = entry.attributes[key]
elif isinstance(child, resourcelists.Entry):
for service in list_services.get(rlist, ()):
service.entries.add(child.uri)
try:
contact = contacts[child.uri]
except KeyError:
contact = contacts[child.uri] = Contact(None, child.uri, None)
contact.presence_policies = set(policy for policy, identity in presence_policies.iteritems() if identity is not None and identity.matches(contact.uri))
contact.dialoginfo_policies = set(policy for policy, identity in dialoginfo_policies.iteritems() if identity is not None and identity.matches(contact.uri))
contact.subscribe_to_presence = False
contact.subscribe_to_dialoginfo = False
if contact.group is None and rlist in buddy_lists and list_name is not None:
contact.group = list_name
if contact.name is None and rlist in buddy_lists and child.display_name:
contact.name = child.display_name.value
if rlist in list_presence_policies:
contact.presence_policies.update(list_presence_policies[rlist])
if not contact.subscribe_to_presence and rlist in presence_lists:
contact.subscribe_to_presence = True
if not contact.subscribe_to_dialoginfo and rlist in dialoginfo_lists:
contact.subscribe_to_dialoginfo = True
if child.attributes and rlist in buddy_lists:
for key in (key for key in child.attributes if key not in contact.attributes):
contact.attributes[key] = child.attributes[key]
if self.status_icon.supported and self.status_icon.content:
status_icon = self.status_icon.content
try:
data = base64.decodestring(status_icon.data)
except Exception:
icon = None
else:
mime_type = status_icon.mime_type.value if status_icon.mime_type else None
description = status_icon.description.value if status_icon.description else None
location = self.status_icon.alternative_location or self.status_icon.uri
icon = Icon(data, mime_type, description, location)
else:
icon = None
if self.pidf_manipulation.supported and self.pidf_manipulation.content:
pidf_manipulation = self.pidf_manipulation.content
persons = [child for child in pidf_manipulation if isinstance(child, presdm.Person)]
try:
note = unicode(chain(*(person.notes for person in persons if person.notes)).next())
except StopIteration:
note = None
try:
activity = chain(*(person.activities for person in persons if person.activities)).next()
except StopIteration:
activity = None
offline_status = OfflineStatus(activity, note)
else:
offline_status = None
contacts = contacts.values()
groups = list(groups)
presence_policies = list(presence_policies)
dialoginfo_policies = list(dialoginfo_policies)
for contact in contacts:
contact.presence_policies = list(contact.presence_policies)
contact.dialoginfo_policies = list(contact.dialoginfo_policies)
for service in services:
service.entries = list(service.entries)
notification_center = NotificationCenter()
notification_center.post_notification('XCAPManagerDidReloadData', sender=self,
data=TimestampedNotificationData(contacts=contacts, groups=groups, services=services, presence_policies=presence_policies,
dialoginfo_policies=dialoginfo_policies, status_icon=icon, offline_status=offline_status))
def _follow_policy_external_list(self, resource_lists, external_list):
result = []
for relative_uri in (uri[len(self.xcap_root):] for uri in external_list if uri.startswith(self.xcap_root)):
try:
uri = XCAPURI(self.xcap_root, relative_uri, self.namespaces)
# Only follow references to default resource-lists document which belongs to ourselves
if uri.application_id == self.resource_lists.application and uri.document_selector.document_path == self.resource_lists.filename and \
uri.user is not None and (uri.user.username, uri.user.domain) == (self.account.id.username, self.account.id.domain):
result.extend(l for l in resource_lists.xpath(uri.node_selector.normalized, uri.node_selector.nsmap) if isinstance(l, resourcelists.List))
except ValueError:
pass
return result
def _follow_rl_external(self, resource_lists, external):
if external.anchor.startswith(self.xcap_root):
uri = XCAPURI(self.xcap_root, external.anchor[len(self.xcap_root):], self.namespaces)
# Only follow references to default resource-lists document which belongs to ourselves
if uri.application_id == self.resource_lists.application and uri.document_selector.document_path == self.resource_lists.filename and \
uri.user is not None and (uri.user.username, uri.user.domain) == (self.account.id.username, self.account.id.domain):
return [l for l in resource_lists.xpath(uri.node_selector.normalized, uri.node_selector.nsmap) if isinstance(l, resourcelists.List)]
raise ValueError("XCAP URI does not point to default resource-lists document")
def _follow_rl_entry_ref(self, resource_lists, entry_ref):
uri = XCAPURI(self.xcap_root, entry_ref.ref, self.namespaces)
# Only follow references to default resource-lists document which belongs to ourselves
if uri.application_id == self.resource_lists.application and uri.document_selector.document_path == self.resource_lists.filename and \
uri.user is not None and (uri.user.username, uri.user.domain) == (self.account.id.username, self.account.id.domain):
return [e for e in resource_lists.xpath(uri.node_selector.normalized, uri.node_selector.nsmap) if isinstance(e, resourcelists.Entry)]
def _follow_rls_resource_list(self, resource_lists, resource_list):
if resource_list.value.startswith(self.xcap_root):
uri = XCAPURI(self.xcap_root, resource_list.value[len(self.xcap_root):], self.namespaces)
# Only follow references to default resource-lists document which belongs to ourselves
if uri.application_id == self.resource_lists.application and uri.document_selector.document_path == self.resource_lists.filename and \
uri.user is not None and (uri.user.username, uri.user.domain) == (self.account.id.username, self.account.id.domain):
return [l for l in resource_lists.xpath(uri.node_selector.normalized, uri.node_selector.nsmap) if isinstance(l, resourcelists.List)]
raise ValueError("XCAP URI does not point to default resource-lists document")
def _schedule_command(self, timeout, command):
from twisted.internet import reactor
timer = reactor.callLater(timeout, self._command_channel.send, command)
timer.command = command
return timer
def _save_journal(self):
try:
makedirs(os.path.dirname(self.journal_filename))
cPickle.dump(self.journal, open(self.journal_filename, 'wb'))
except (IOError, OSError):
pass
@staticmethod
def unique_name(preferred_name, disallowed_names, skip_preferred_name=False):
disallowed_names = set(disallowed_names)
if not skip_preferred_name and preferred_name not in disallowed_names:
disallowed_names.add(preferred_name)
yield preferred_name
for i in xrange(100):
name = '%s_%03d' % (preferred_name, i)
if name not in disallowed_names:
disallowed_names.add(name)
yield name
while True:
characters = len(string.ascii_letters+string.digits)
for limit in count(4):
for i in xrange(characters**(limit-2)):
name = preferred_name + '_' + ''.join(random.choice(string.ascii_letters+string.digits) for x in xrange(limit))
if name not in disallowed_names:
disallowed_names.add(name)
yield name

File Metadata

Mime Type
text/x-diff
Expires
Sat, Dec 28, 3:47 AM (1 d, 1 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3454205
Default Alt Text
(194 KB)

Event Timeline