Page MenuHomePhabricator

No OneTemporary

diff --git a/sipsimple/account/__init__.py b/sipsimple/account/__init__.py
index ad88de63..133759ef 100644
--- a/sipsimple/account/__init__.py
+++ b/sipsimple/account/__init__.py
@@ -1,887 +1,888 @@
"""
Implements a SIP Account management system that allows the definition of
multiple SIP accounts and their properties.
"""
__all__ = ['Account', 'BonjourAccount', 'AccountManager']
from itertools import chain
from threading import Lock
from application.notification import IObserver, NotificationCenter, NotificationData
from application.python import Null
from application.python.descriptor import classproperty
from application.python.types import Singleton
from application.system import host as Host
from eventlib import coros, proc
from gnutls.crypto import X509Certificate, X509PrivateKey
from gnutls.errors import GNUTLSError
from gnutls.interfaces.twisted import X509Credentials
from zope.interface import implementer
from sipsimple.account.bonjour import BonjourServices, _bonjour
from sipsimple.account.publication import PresencePublisher, DialogPublisher
from sipsimple.account.registration import Registrar
from sipsimple.account.subscription import MWISubscriber, PresenceWinfoSubscriber, DialogWinfoSubscriber, PresenceSubscriber, SelfPresenceSubscriber, DialogSubscriber
from sipsimple.account.xcap import XCAPManager
from sipsimple.core import Credentials, SIPURI, ContactURIFactory
from sipsimple.configuration import ConfigurationManager, Setting, SettingsGroup, SettingsObject, SettingsObjectID
from sipsimple.configuration.datatypes import AudioCodecList, MSRPConnectionModel, MSRPRelayAddress, MSRPTransport, NonNegativeInteger, Path, SIPAddress, SIPProxyAddress, SRTPKeyNegotiation, STUNServerAddressList, VideoCodecList, XCAPRoot
from sipsimple.configuration.settings import SIPSimpleSettings
from sipsimple.payloads import ParserError
from sipsimple.payloads.messagesummary import MessageSummary
from sipsimple.payloads.pidf import PIDFDocument
from sipsimple.payloads.rlsnotify import RLSNotify
from sipsimple.payloads.watcherinfo import WatcherInfoDocument
from sipsimple.threading import call_in_thread
from sipsimple.threading.green import call_in_green_thread, run_in_green_thread
from sipsimple.util import user_info, execute_once
class AuthSettings(SettingsGroup):
username = Setting(type=str, default=None, nillable=True)
password = Setting(type=str, default='')
class SIPSettings(SettingsGroup):
always_use_my_proxy = Setting(type=bool, default=False)
outbound_proxy = Setting(type=SIPProxyAddress, default=None, nillable=True)
register = Setting(type=bool, default=True)
register_interval = Setting(type=NonNegativeInteger, default=3600)
subscribe_interval = Setting(type=NonNegativeInteger, default=3600)
publish_interval = Setting(type=NonNegativeInteger, default=3600)
class SRTPEncryptionSettings(SettingsGroup):
enabled = Setting(type=bool, default=True)
key_negotiation = Setting(type=SRTPKeyNegotiation, default='opportunistic')
class RTPSettings(SettingsGroup):
audio_codec_list = Setting(type=AudioCodecList, default=None, nillable=True)
video_codec_list = Setting(type=VideoCodecList, default=None, nillable=True)
encryption = SRTPEncryptionSettings
class NATTraversalSettings(SettingsGroup):
use_ice = Setting(type=bool, default=False)
stun_server_list = Setting(type=STUNServerAddressList, default=None, nillable=True)
msrp_relay = Setting(type=MSRPRelayAddress, default=None, nillable=True)
use_msrp_relay_for_outbound = Setting(type=bool, default=False)
class MessageSummarySettings(SettingsGroup):
enabled = Setting(type=bool, default=False)
voicemail_uri = Setting(type=SIPAddress, default=None, nillable=True)
class XCAPSettings(SettingsGroup):
enabled = Setting(type=bool, default=False)
discovered = Setting(type=bool, default=False)
xcap_root = Setting(type=XCAPRoot, default=None, nillable=True)
+ xcap_diff = Setting(type=bool, default=True)
class PresenceSettings(SettingsGroup):
enabled = Setting(type=bool, default=False)
class TLSSettings(SettingsGroup):
ca_list = Setting(type=Path, default=None, nillable=True)
certificate = Setting(type=Path, default=None, nillable=True)
verify_server = Setting(type=bool, default=False)
class MSRPSettings(SettingsGroup):
transport = Setting(type=MSRPTransport, default='tls')
connection_model = Setting(type=MSRPConnectionModel, default='relay')
@implementer(IObserver)
class Account(SettingsObject):
"""
Object representing a SIP account. Contains configuration settings and
attributes for accessing SIP related objects.
When the account is active, it will register, publish its presence and
subscribe to watcher-info events depending on its settings.
If the object is un-pickled and its enabled flag was set, it will
automatically activate.
When the save method is called, depending on the value of the enabled flag,
the account will activate/deactivate.
Notifications sent by instances of Account:
* CFGSettingsObjectWasCreated
* CFGSettingsObjectWasActivated
* CFGSettingsObjectWasDeleted
* CFGSettingsObjectDidChange
* SIPAccountWillActivate
* SIPAccountDidActivate
* SIPAccountWillDeactivate
* SIPAccountDidDeactivate
"""
__group__ = 'Accounts'
__id__ = SettingsObjectID(type=SIPAddress)
id = __id__
enabled = Setting(type=bool, default=False)
display_name = Setting(type=str, default=None, nillable=True)
auth = AuthSettings
sip = SIPSettings
rtp = RTPSettings
nat_traversal = NATTraversalSettings
message_summary = MessageSummarySettings
msrp = MSRPSettings
presence = PresenceSettings
xcap = XCAPSettings
tls = TLSSettings
def __new__(cls, id):
#with AccountManager.load.lock:
# if not AccountManager.load.called:
# raise RuntimeError("cannot instantiate %s before calling AccountManager.load" % cls.__name__)
return SettingsObject.__new__(cls, id)
def __init__(self, id):
self.contact = ContactURIFactory()
self.xcap_manager = XCAPManager(self)
self._started = False
self._deleted = False
self._active = False
self._activation_lock = coros.Semaphore(1)
self._registrar = Registrar(self)
self._mwi_subscriber = MWISubscriber(self)
self._pwi_subscriber = PresenceWinfoSubscriber(self)
self._dwi_subscriber = DialogWinfoSubscriber(self)
self._presence_subscriber = PresenceSubscriber(self)
self._self_presence_subscriber = SelfPresenceSubscriber(self)
self._dialog_subscriber = DialogSubscriber(self)
self._presence_publisher = PresencePublisher(self)
self._dialog_publisher = DialogPublisher(self)
self._mwi_voicemail_uri = None
self._pwi_version = None
self._dwi_version = None
self._presence_version = None
self._dialog_version = None
self.trusted_cas = []
self.ca_list = None
def start(self):
if self._started or self._deleted:
return
self._started = True
notification_center = NotificationCenter()
notification_center.add_observer(self, name='CFGSettingsObjectDidChange', sender=self)
notification_center.add_observer(self, name='CFGSettingsObjectDidChange', sender=SIPSimpleSettings())
notification_center.add_observer(self, name='XCAPManagerDidDiscoverServerCapabilities', sender=self.xcap_manager)
notification_center.add_observer(self, sender=self._mwi_subscriber)
notification_center.add_observer(self, sender=self._pwi_subscriber)
notification_center.add_observer(self, sender=self._dwi_subscriber)
notification_center.add_observer(self, sender=self._presence_subscriber)
notification_center.add_observer(self, sender=self._self_presence_subscriber)
notification_center.add_observer(self, sender=self._dialog_subscriber)
self.xcap_manager.init()
if self.enabled:
self._activate()
def stop(self):
if not self._started:
return
self._started = False
self._deactivate()
notification_center = NotificationCenter()
notification_center.remove_observer(self, name='CFGSettingsObjectDidChange', sender=self)
notification_center.remove_observer(self, name='CFGSettingsObjectDidChange', sender=SIPSimpleSettings())
notification_center.remove_observer(self, name='XCAPManagerDidDiscoverServerCapabilities', sender=self.xcap_manager)
notification_center.remove_observer(self, sender=self._mwi_subscriber)
notification_center.remove_observer(self, sender=self._pwi_subscriber)
notification_center.remove_observer(self, sender=self._dwi_subscriber)
notification_center.remove_observer(self, sender=self._presence_subscriber)
notification_center.remove_observer(self, sender=self._self_presence_subscriber)
notification_center.remove_observer(self, sender=self._dialog_subscriber)
def delete(self):
if self._deleted:
return
self._deleted = True
self.stop()
self._registrar = None
self._mwi_subscriber = None
self._pwi_subscriber = None
self._dwi_subscriber = None
self._presence_subscriber = None
self._self_presence_subscriber = None
self._dialog_subscriber = None
self._presence_publisher = None
self._dialog_publisher = None
self.xcap_manager = None
SettingsObject.delete(self)
@run_in_green_thread
def reregister(self):
if self._started:
self._registrar.reregister()
@run_in_green_thread
def resubscribe(self):
if self._started:
self._mwi_subscriber.resubscribe()
self._pwi_subscriber.resubscribe()
self._dwi_subscriber.resubscribe()
self._presence_subscriber.resubscribe()
self._self_presence_subscriber.resubscribe()
self._dialog_subscriber.resubscribe()
@property
def credentials(self):
username = self.auth.username or self.id.username
username = username.encode() if username else None
password = self.auth.password.encode() if self.auth.password else None
return Credentials(username, password)
@property
def registered(self):
try:
return self._registrar.registered
except AttributeError:
return False
@property
def mwi_active(self):
try:
return self._mwi_subscriber.subscribed
except AttributeError:
return False
@property
def tls_credentials(self):
# This property can be optimized to cache the credentials it loads from disk,
# however this is not a time consuming operation (~ 3000 req/sec). -Luci
settings = SIPSimpleSettings()
tls_certificate = self.tls.certificate or settings.tls.certificate
certificate = None
private_key = None
if tls_certificate is not None:
try:
certificate_data = open(tls_certificate.normalized).read()
certificate = X509Certificate(certificate_data)
private_key = X509PrivateKey(certificate_data)
except (FileNotFoundError, GNUTLSError, UnicodeDecodeError):
pass
trusted_cas = []
ca_list = self.tls.ca_list or settings.tls.ca_list
if ca_list is not None:
if len(self.trusted_cas) > 0:
trusted_cas = self.trusted_cas
else:
crt = None
start = False
try:
ca_text = open(ca_list.normalized).read()
except (FileNotFoundError, GNUTLSError, UnicodeDecodeError):
ca_text = ''
for line in ca_text.split("\n"):
if "BEGIN CERT" in line:
start = True
crt = line + "\n"
elif "END CERT" in line:
crt = crt + line + "\n"
end = True
start = False
try:
trusted_cas.append(X509Certificate(crt))
except (GNUTLSError, ValueError) as e:
continue
elif start:
crt = crt + line + "\n"
self.trusted_cas = trusted_cas
self.ca_list = ca_list
credentials = X509Credentials(certificate, private_key, trusted_cas)
credentials.verify_peer = self.tls.verify_server or settings.tls.certificate
return credentials
@property
def uri(self):
return SIPURI(user=self.id.username, host=self.id.domain)
@property
def voicemail_uri(self):
return self._mwi_voicemail_uri or self.message_summary.voicemail_uri
@property
def presence_state(self):
try:
return self._presence_publisher.state
except AttributeError:
return None
@presence_state.setter
def presence_state(self, state):
try:
self._presence_publisher.state = state
except AttributeError:
pass
@property
def dialog_state(self):
try:
return self._dialog_publisher.state
except AttributeError:
return None
@dialog_state.setter
def dialog_state(self, state):
try:
self._dialog_publisher.state = state
except AttributeError:
pass
def handle_notification(self, notification):
handler = getattr(self, '_NH_%s' % notification.name, Null)
handler(notification)
@run_in_green_thread
def _NH_CFGSettingsObjectDidChange(self, notification):
if self._started and 'enabled' in notification.data.modified:
if self.enabled:
self._activate()
else:
self._deactivate()
def _NH_XCAPManagerDidDiscoverServerCapabilities(self, notification):
if self._started and self.xcap.discovered is False:
self.xcap.discovered = True
self.save()
notification.center.post_notification('SIPAccountDidDiscoverXCAPSupport', sender=self)
def _NH_MWISubscriberDidDeactivate(self, notification):
self._mwi_voicemail_uri = None
def _NH_MWISubscriptionGotNotify(self, notification):
body = notification.data.body.decode() if notification.data.body else None
if body and notification.data.content_type == MessageSummary.content_type:
try:
message_summary = MessageSummary.parse(body)
except ParserError:
pass
else:
self._mwi_voicemail_uri = message_summary.message_account and SIPAddress(message_summary.message_account.replace('sip:', '', 1)) or None
notification.center.post_notification('SIPAccountGotMessageSummary', sender=self, data=NotificationData(message_summary=message_summary))
def _NH_PresenceWinfoSubscriptionGotNotify(self, notification):
body = notification.data.body.decode() if notification.data.body else None
if body and notification.data.content_type == WatcherInfoDocument.content_type:
try:
watcher_info = WatcherInfoDocument.parse(body)
watcher_list = watcher_info['sip:' + self.id]
except (ParserError, KeyError):
pass
else:
if watcher_list.package != 'presence':
return
if self._pwi_version is None:
if watcher_info.state == 'partial':
self._pwi_subscriber.resubscribe()
elif watcher_info.version <= self._pwi_version:
return
elif watcher_info.state == 'partial' and watcher_info.version > self._pwi_version + 1:
self._pwi_subscriber.resubscribe()
self._pwi_version = watcher_info.version
data = NotificationData(version=watcher_info.version, state=watcher_info.state, watcher_list=watcher_list)
notification.center.post_notification('SIPAccountGotPresenceWinfo', sender=self, data=data)
def _NH_PresenceWinfoSubscriptionDidEnd(self, notification):
self._pwi_version = None
def _NH_PresenceWinfoSubscriptionDidFail(self, notification):
self._pwi_version = None
def _NH_DialogWinfoSubscriptionGotNotify(self, notification):
body = notification.data.body.decode() if notification.data.body else None
if body and notification.data.content_type == WatcherInfoDocument.content_type:
try:
watcher_info = WatcherInfoDocument.parse(body)
watcher_list = watcher_info['sip:' + self.id]
except (ParserError, KeyError):
pass
else:
if watcher_list.package != 'dialog':
return
if self._dwi_version is None:
if watcher_info.state == 'partial':
self._dwi_subscriber.resubscribe()
elif watcher_info.version <= self._dwi_version:
return
elif watcher_info.state == 'partial' and watcher_info.version > self._dwi_version + 1:
self._dwi_subscriber.resubscribe()
self._dwi_version = watcher_info.version
data = NotificationData(version=watcher_info.version, state=watcher_info.state, watcher_list=watcher_list)
notification.center.post_notification('SIPAccountGotDialogWinfo', sender=self, data=data)
def _NH_DialogWinfoSubscriptionDidEnd(self, notification):
self._dwi_version = None
def _NH_DialogWinfoSubscriptionDidFail(self, notification):
self._dwi_version = None
def _NH_PresenceSubscriptionGotNotify(self, notification):
body = notification.data.body.decode() if notification.data.body else None
if body and notification.data.content_type == RLSNotify.content_type:
try:
rls_notify = RLSNotify.parse('{content_type}\r\n\r\n{body}'.format(content_type=notification.data.headers['Content-Type'], body=body))
except ParserError:
pass
else:
if rls_notify.uri != self.xcap_manager.rls_presence_uri:
return
if self._presence_version is None:
if not rls_notify.full_state:
self._presence_subscriber.resubscribe()
elif rls_notify.version <= self._presence_version:
return
elif not rls_notify.full_state and rls_notify.version > self._presence_version + 1:
self._presence_subscriber.resubscribe()
self._presence_version = rls_notify.version
data = NotificationData(version=rls_notify.version, full_state=rls_notify.full_state, resource_map=dict((str(resource.uri), resource) for resource in rls_notify))
notification.center.post_notification('SIPAccountGotPresenceState', sender=self, data=data)
def _NH_PresenceSubscriptionDidEnd(self, notification):
self._presence_version = None
def _NH_PresenceSubscriptionDidFail(self, notification):
self._presence_version = None
def _NH_SelfPresenceSubscriptionGotNotify(self, notification):
body = notification.data.body.decode() if notification.data.body else None
if body and notification.data.content_type == PIDFDocument.content_type:
try:
pidf_doc = PIDFDocument.parse(body)
except ParserError:
pass
else:
if pidf_doc.entity.partition('sip:')[2] != self.id:
return
notification.center.post_notification('SIPAccountGotSelfPresenceState', sender=self, data=NotificationData(pidf=pidf_doc))
def _NH_DialogSubscriptionGotNotify(self, notification):
body = notification.data.body.decode() if notification.data.body else None
if body and notification.data.content_type == RLSNotify.content_type:
try:
rls_notify = RLSNotify.parse('{content_type}\r\n\r\n{body}'.format(content_type=notification.data.headers['Content-Type'], body=body))
except ParserError:
pass
else:
if rls_notify.uri != self.xcap_manager.rls_dialog_uri:
return
if self._dialog_version is None:
if not rls_notify.full_state:
self._dialog_subscriber.resubscribe()
elif rls_notify.version <= self._dialog_version:
return
elif not rls_notify.full_state and rls_notify.version > self._dialog_version + 1:
self._dialog_subscriber.resubscribe()
self._dialog_version = rls_notify.version
data = NotificationData(version=rls_notify.version, full_state=rls_notify.full_state, resource_map=dict((resource.uri, resource) for resource in rls_notify))
notification.center.post_notification('SIPAccountGotDialogState', sender=self, data=data)
def _NH_DialogSubscriptionDidEnd(self, notification):
self._dialog_version = None
def _NH_DialogSubscriptionDidFail(self, notification):
self._dialog_version = None
def _activate(self):
with self._activation_lock:
if self._active:
return
notification_center = NotificationCenter()
notification_center.post_notification('SIPAccountWillActivate', sender=self)
self._active = True
self._registrar.start()
self._mwi_subscriber.start()
self._pwi_subscriber.start()
self._dwi_subscriber.start()
self._presence_subscriber.start()
self._self_presence_subscriber.start()
self._dialog_subscriber.start()
self._presence_publisher.start()
self._dialog_publisher.start()
if self.xcap.enabled:
self.xcap_manager.start()
notification_center.post_notification('SIPAccountDidActivate', sender=self)
def _deactivate(self):
with self._activation_lock:
if not self._active:
return
notification_center = NotificationCenter()
notification_center.post_notification('SIPAccountWillDeactivate', sender=self)
self._active = False
handlers = [self._registrar, self._mwi_subscriber, self._pwi_subscriber, self._dwi_subscriber,
self._presence_subscriber, self._self_presence_subscriber, self._dialog_subscriber,
self._presence_publisher, self._dialog_publisher, self.xcap_manager]
proc.waitall([proc.spawn(handler.stop) for handler in handlers])
notification_center.post_notification('SIPAccountDidDeactivate', sender=self)
def __repr__(self):
return '%s(%r)' % (self.__class__.__name__, self.id)
def __setstate__(self, data):
# This restores the password from its previous location as a top level setting
# after it was moved under the auth group.
SettingsObject.__setstate__(self, data)
if not data.get('auth', {}).get('password') and data.get('password'):
self.auth.password = data.pop('password')
self.save()
class BonjourMSRPSettings(SettingsGroup):
transport = Setting(type=MSRPTransport, default='tls')
class BonjourAccountEnabledSetting(Setting):
def __get__(self, obj, objtype):
if obj is None:
return self
return _bonjour.available and self.values.get(obj, self.default)
def __set__(self, obj, value):
if not _bonjour.available:
raise RuntimeError('mdns support is not available')
Setting.__set__(self, obj, value)
@implementer(IObserver)
class BonjourAccount(SettingsObject):
"""
Object representing a bonjour account. Contains configuration settings and
attributes for accessing bonjour related options.
When the account is active, it will send broadcast its contact address on
the LAN.
If the object is un-pickled and its enabled flag was set, it will
automatically activate.
When the save method is called, depending on the value of the enabled flag,
the account will activate/deactivate.
Notifications sent by instances of Account:
* CFGSettingsObjectWasCreated
* CFGSettingsObjectWasActivated
* CFGSettingsObjectWasDeleted
* CFGSettingsObjectDidChange
* SIPAccountWillActivate
* SIPAccountDidActivate
* SIPAccountWillDeactivate
* SIPAccountDidDeactivate
"""
__group__ = 'Accounts'
__id__ = SIPAddress('bonjour@local')
id = property(lambda self: self.__id__)
enabled = BonjourAccountEnabledSetting(type=bool, default=True)
display_name = Setting(type=str, default=user_info.fullname, nillable=False)
msrp = BonjourMSRPSettings
presence = PresenceSettings
rtp = RTPSettings
tls = TLSSettings
def __new__(cls):
# with AccountManager.load.lock:
# if not AccountManager.load.called:
# raise RuntimeError("cannot instantiate %s before calling AccountManager.load" % cls.__name__)
return SettingsObject.__new__(cls)
def __init__(self):
self.contact = ContactURIFactory()
self.credentials = None
self._started = False
self._active = False
self._activation_lock = coros.Semaphore(1)
self._bonjour_services = BonjourServices(self)
# initialize fake settings (these are here to make the bonjour account quack like a duck)
self.nat_traversal = NATTraversalSettings()
self.nat_traversal.use_ice = False
self.nat_traversal.msrp_relay = None
self.nat_traversal.use_msrp_relay_for_outbound = False
self.xcap = XCAPSettings()
self.xcap.enabled = False
self.xcap.discovered = False
self.xcap.xcap_root = None
def __repr__(self):
return '%s()' % self.__class__.__name__
def start(self):
if self._started:
return
self._started = True
notification_center = NotificationCenter()
notification_center.add_observer(self, name='CFGSettingsObjectDidChange', sender=self)
notification_center.add_observer(self, name='CFGSettingsObjectDidChange', sender=SIPSimpleSettings())
self._bonjour_services.start()
if self.enabled:
self._activate()
def stop(self):
if not self._started:
return
self._started = False
self._deactivate()
self._bonjour_services.stop()
notification_center = NotificationCenter()
notification_center.remove_observer(self, name='CFGSettingsObjectDidChange', sender=self)
notification_center.remove_observer(self, name='CFGSettingsObjectDidChange', sender=SIPSimpleSettings())
@classproperty
def mdns_available(cls):
return _bonjour.available
@property
def registered(self):
return False
@property
def tls_credentials(self):
# This property can be optimized to cache the credentials it loads from disk,
# however this is not a time consuming operation (~ 3000 req/sec). -Luci
settings = SIPSimpleSettings()
tls_certificate = self.tls.certificate or settings.tls.certificate
if tls_certificate is not None:
certificate_data = open(tls_certificate.normalized).read()
certificate = X509Certificate(certificate_data)
private_key = X509PrivateKey(certificate_data)
else:
certificate = None
private_key = None
credentials = X509Credentials(certificate, private_key, [])
credentials.verify_peer = False
return credentials
@property
def uri(self):
return SIPURI(user=self.contact.username, host=Host.default_ip or '127.0.0.1')
@property
def presence_state(self):
return self._bonjour_services.presence_state
@presence_state.setter
def presence_state(self, state):
self._bonjour_services.presence_state = state
def handle_notification(self, notification):
handler = getattr(self, '_NH_%s' % notification.name, Null)
handler(notification)
@run_in_green_thread
def _NH_CFGSettingsObjectDidChange(self, notification):
if self._started:
if 'enabled' in notification.data.modified:
if self.enabled:
self._activate()
else:
self._deactivate()
elif self.enabled:
if 'display_name' in notification.data.modified:
self._bonjour_services.update_registrations()
if {'sip.transport_list', 'tls.certificate'}.intersection(notification.data.modified):
self._bonjour_services.update_registrations()
self._bonjour_services.restart_discovery()
def _activate(self):
with self._activation_lock:
if self._active:
return
notification_center = NotificationCenter()
notification_center.post_notification('SIPAccountWillActivate', sender=self)
self._active = True
self._bonjour_services.activate()
notification_center.post_notification('SIPAccountDidActivate', sender=self)
def _deactivate(self):
with self._activation_lock:
if not self._active:
return
notification_center = NotificationCenter()
notification_center.post_notification('SIPAccountWillDeactivate', sender=self)
self._active = False
self._bonjour_services.deactivate()
notification_center.post_notification('SIPAccountDidDeactivate', sender=self)
@implementer(IObserver)
class AccountManager(object, metaclass=Singleton):
"""
This is a singleton object which manages all the SIP accounts. It is
also used to manage the default account (the one used for outbound
sessions) using the default_account attribute:
manager = AccountManager()
manager.default_account = manager.get_account('alice@example.net')
The following notifications are sent:
* SIPAccountManagerDidRemoveAccount
* SIPAccountManagerDidAddAccount
* SIPAccountManagerDidChangeDefaultAccount
"""
def __init__(self):
self._lock = Lock()
self.accounts = {}
notification_center = NotificationCenter()
notification_center.add_observer(self, name='CFGSettingsObjectWasActivated')
notification_center.add_observer(self, name='CFGSettingsObjectWasCreated')
@execute_once
def load(self):
"""
Load all accounts from the configuration. The accounts will not be
started until the start method is called.
"""
configuration = ConfigurationManager()
bonjour_account = BonjourAccount()
names = configuration.get_names([Account.__group__])
[Account(id) for id in names if id != bonjour_account.id]
default_account = self.default_account
if default_account is None or not default_account.enabled:
try:
self.default_account = next((account for account in list(self.accounts.values()) if account.enabled))
except StopIteration:
self.default_account = None
def start(self):
"""
Start the accounts, which will determine the ones with the enabled flag
set to activate.
"""
notification_center = NotificationCenter()
notification_center.post_notification('SIPAccountManagerWillStart', sender=self)
proc.waitall([proc.spawn(account.start) for account in list(self.accounts.values())])
notification_center.post_notification('SIPAccountManagerDidStart', sender=self)
def stop(self):
"""
Stop the accounts, which will determine the ones that were enabled to
deactivate. This method returns only once the accounts were stopped
successfully or they timed out trying.
"""
notification_center = NotificationCenter()
notification_center.post_notification('SIPAccountManagerWillEnd', sender=self)
proc.waitall([proc.spawn(account.stop) for account in list(self.accounts.values())])
notification_center.post_notification('SIPAccountManagerDidEnd', sender=self)
def has_account(self, id):
return id in self.accounts
def get_account(self, id):
return self.accounts[id]
def get_accounts(self):
return list(self.accounts.values())
def iter_accounts(self):
return iter(list(self.accounts.values()))
def find_account(self, contact_uri):
# compare contact_address with account contact
exact_matches = (account for account in list(self.accounts.values()) if account.enabled and account.contact.username==contact_uri.user)
# compare username in contact URI with account username
loose_matches = (account for account in list(self.accounts.values()) if account.enabled and account.id.username==contact_uri.user)
return next(chain(exact_matches, loose_matches, [None]))
def handle_notification(self, notification):
handler = getattr(self, '_NH_%s' % notification.name, Null)
handler(notification)
def _NH_CFGSettingsObjectWasActivated(self, notification):
if isinstance(notification.sender, Account) or (isinstance(notification.sender, BonjourAccount) and _bonjour.available):
account = notification.sender
self.accounts[account.id] = account
notification.center.add_observer(self, sender=account, name='CFGSettingsObjectDidChange')
notification.center.add_observer(self, sender=account, name='CFGSettingsObjectWasDeleted')
notification.center.post_notification('SIPAccountManagerDidAddAccount', sender=self, data=NotificationData(account=account))
from sipsimple.application import SIPApplication
if SIPApplication.running:
call_in_green_thread(account.start)
def _NH_CFGSettingsObjectWasCreated(self, notification):
if isinstance(notification.sender, Account):
account = notification.sender
if account.enabled and self.default_account is None:
self.default_account = account
def _NH_CFGSettingsObjectWasDeleted(self, notification):
account = notification.sender
del self.accounts[account.id]
notification.center.remove_observer(self, sender=account, name='CFGSettingsObjectDidChange')
notification.center.remove_observer(self, sender=account, name='CFGSettingsObjectWasDeleted')
notification.center.post_notification('SIPAccountManagerDidRemoveAccount', sender=self, data=NotificationData(account=account))
def _NH_CFGSettingsObjectDidChange(self, notification):
account = notification.sender
if '__id__' in notification.data.modified:
modified_id = notification.data.modified['__id__']
self.accounts[modified_id.new] = self.accounts.pop(modified_id.old)
if 'enabled' in notification.data.modified:
if account.enabled and self.default_account is None:
self.default_account = account
elif not account.enabled and self.default_account is account:
try:
self.default_account = next((account for account in list(self.accounts.values()) if account.enabled))
except StopIteration:
self.default_account = None
@property
def default_account(self):
settings = SIPSimpleSettings()
return self.accounts.get(settings.default_account, None)
@default_account.setter
def default_account(self, account):
if account is not None and not account.enabled:
raise ValueError("account %s is not enabled" % account.id)
notification_center = NotificationCenter()
settings = SIPSimpleSettings()
with self._lock:
old_account = self.accounts.get(settings.default_account, None)
if account is old_account:
return
if account is None:
settings.default_account = None
else:
settings.default_account = account.id
settings.save()
# we need to post the notification in the file-io thread in order to have it serialized after the
# SIPAccountManagerDidAddAccount notification that is triggered when the account is saved the first
# time, because save is executed in the file-io thread while this runs in the current thread. -Dan
call_in_thread('file-io', notification_center.post_notification, 'SIPAccountManagerDidChangeDefaultAccount', sender=self, data=NotificationData(old_account=old_account, account=account))
diff --git a/sipsimple/account/xcap/__init__.py b/sipsimple/account/xcap/__init__.py
index 3799b096..9dcb141a 100644
--- a/sipsimple/account/xcap/__init__.py
+++ b/sipsimple/account/xcap/__init__.py
@@ -1,1860 +1,1867 @@
"""High-level management of XCAP documents based on OMA specifications"""
__all__ = ['Group', 'Contact', 'ContactURI', 'EventHandling', 'Policy', 'Icon', 'OfflineStatus', 'XCAPManager', 'XCAPTransaction']
import base64
import pickle
import os
import random
import socket
import weakref
from io import StringIO
from collections import OrderedDict
from datetime import datetime
from itertools import chain
from operator import attrgetter
from urllib.error import URLError
from application.notification import IObserver, NotificationCenter, NotificationData
from application.python import Null
from eventlib import api, coros, proc
from eventlib.green.httplib import BadStatusLine
from twisted.internet.error import ConnectionLost
from xcaplib.green import XCAPClient
from xcaplib.error import HTTPError
from zope.interface import implementer
from sipsimple import log
from sipsimple.account.subscription import Subscriber, Content
from sipsimple.account.xcap.storage import IXCAPStorage, XCAPStorageError
from sipsimple.configuration.datatypes import SIPAddress
from sipsimple.configuration.settings import SIPSimpleSettings
from sipsimple.lookup import DNSLookup, DNSLookupError
from sipsimple.payloads import ParserError, IterateTypes, IterateIDs, IterateItems, All
from sipsimple.payloads import addressbook, commonpolicy, dialogrules, omapolicy, pidf, prescontent, presrules, resourcelists, rlsservices, xcapcaps, xcapdiff
from sipsimple.payloads import rpid; del rpid # needs to be imported to register its namespace
from sipsimple.threading import run_in_twisted_thread
from sipsimple.threading.green import Command, Worker, run_in_green_thread
import traceback
class XCAPError(Exception): pass
class FetchRequiredError(XCAPError): pass
class Document(object):
name = None
application = None
payload_type = None
default_namespace = None
global_tree = None
filename = None
cached = True
def __init__(self, manager):
self.manager = weakref.proxy(manager)
self.content = None
self.etag = None
self.fetch_time = datetime.fromtimestamp(0)
self.update_time = datetime.fromtimestamp(0)
self.dirty = False
self.supported = False
def __bool__(self):
return self.content is not None
@property
def dirty(self):
return self.__dict__['dirty'] or (self.content is not None and self.content.__dirty__)
@dirty.setter
def dirty(self, dirty):
if self.content is not None and not dirty:
self.content.__dirty__ = dirty
self.__dict__['dirty'] = dirty
@property
def relative_url(self):
return self.url[len(self.manager.xcap_root):].lstrip('/')
@property
def url(self):
return self.manager.client.get_url(self.application, None, globaltree=self.global_tree, filename=self.filename)
def load_from_cache(self):
if not self.cached:
return
try:
doc_io = self.manager.storage.load(self.name)
document = StringIO(doc_io.decode())
self.etag = document.readline().strip() or None
self.content = self.payload_type.parse(document)
self.__dict__['dirty'] = False
except (XCAPStorageError, ParserError):
self.etag = None
self.content = None
self.dirty = False
self.fetch_time = datetime.utcnow()
def initialize(self, server_caps):
self.supported = self.application in server_caps.auids
if not self.supported:
self.reset()
def reset(self):
if self.cached and self.content is not None:
try:
self.manager.storage.delete(self.name)
except XCAPStorageError:
pass
self.content = None
self.etag = None
self.dirty = False
def fetch(self):
notification_center = NotificationCenter()
try:
document = self.manager.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
self.__dict__['dirty'] = False
except (BadStatusLine, ConnectionLost, URLError, socket.error) as e:
notification_data = NotificationData(method='GET', url=self.url, application=self.application, result='failure', reason=str(e), code=408, etag=self.etag)
notification_center.post_notification('XCAPTrace', sender=self, data=notification_data)
raise XCAPError("failed to fetch %s document: %s" % (self.name, e))
except HTTPError as e:
if e.status == 404: # Not Found
if self.content is not None:
self.reset()
self.fetch_time = datetime.utcnow()
elif e.status != 304: # Other than Not Modified:
notification_data = NotificationData(method='GET', url=self.url, application=self.application, result='failure', reason=str(e), code=e.status, etag=self.etag)
notification_center.post_notification('XCAPTrace', sender=self, data=notification_data)
raise XCAPError("failed to fetch %s document: %s" % (self.name, e))
elif e.status == 304:
notification_data = NotificationData(method='GET', url=self.url, application=self.application, result='success', reason='not_modified', code=304, etag=self.etag)
notification_center.post_notification('XCAPTrace', sender=self, data=notification_data)
except ParserError as e:
notification_data = NotificationData(method='GET', url=self.url, application=self.application, result='failure', reason=str(e), code=500, etag=self.etag)
notification_center.post_notification('XCAPTrace', sender=self, data=notification_data)
raise XCAPError("failed to parse %s document: %s" % (self.name, e))
else:
self.fetch_time = datetime.utcnow()
notification_data = NotificationData(method='GET', url=self.url, application=self.application, result='success', reason='changed', code=200, etag=self.etag, size=len(document))
notification_center.post_notification('XCAPTrace', sender=self, data=notification_data)
if self.cached:
try:
data = self.etag + os.linesep
data += document.decode() if isinstance(document, bytes) else document
self.manager.storage.save(self.name, data)
except XCAPStorageError as e:
notification_data = NotificationData(method='GET', url=self.url, application=self.application, result='failed', reason='storage failure: %s' % str(e), code=500, etag=self.etag, size=len(document))
notification_center.post_notification('XCAPTrace', sender=self, data=notification_data)
def update(self):
if not self.dirty:
return
notification_center = NotificationCenter()
data = self.content.toxml() if self.content is not None else None
method = 'PUT' if data is not None else 'DELETE'
try:
kw = dict(etag=self.etag) if self.etag is not None else dict(etagnot='*')
if data is not None:
response = self.manager.client.put(self.application, data, globaltree=self.global_tree, filename=self.filename, headers={'Content-Type': self.payload_type.content_type}, **kw)
else:
response = self.manager.client.delete(self.application, data, globaltree=self.global_tree, filename=self.filename, **kw)
except (BadStatusLine, ConnectionLost, URLError) as e:
notification_data = NotificationData(method=method, url=self.url, application=self.application, result='failure', reason=str(e), code=408, etag=self.etag)
notification_center.post_notification('XCAPTrace', sender=self, data=notification_data)
raise XCAPError("failed to update %s document: %s" % (self.name, e))
except HTTPError as e:
if e.status == 412: # Precondition Failed
notification_data = NotificationData(method=method, url=self.url, application=self.application, result='failure', reason='document modified by others', code=e.status, etag=self.etag)
notification_center.post_notification('XCAPTrace', sender=self, data=notification_data)
raise FetchRequiredError("document %s was modified externally" % self.name)
elif e.status == 404 and data is None: # attempted to delete a document that did't exist in the first place
notification_data = NotificationData(method=method, url=self.url, application=self.application, result='failure', reason='non-existent document', code=e.status, etag=self.etag)
notification_center.post_notification('XCAPTrace', sender=self, data=notification_data)
else:
notification_data = NotificationData(method=method, url=self.url, application=self.application, result='failure', reason=str(e), code=e.status, etag=self.etag)
notification_center.post_notification('XCAPTrace', sender=self, data=notification_data)
raise XCAPError("failed to update %s document: %s" % (self.name, e))
self.etag = response.etag if data is not None else None
notification_data = NotificationData(method=method, url=self.url, application=self.application, result='success', reason='changed', code=200, etag=self.etag, size=len(data))
notification_center.post_notification('XCAPTrace', sender=self, data=notification_data)
self.dirty = False
self.update_time = datetime.utcnow()
if self.cached:
try:
if data is not None:
document = self.etag + os.linesep
document += data.decode() if isinstance(data, bytes) else data
self.manager.storage.save(self.name, document)
else:
self.manager.storage.delete(self.name)
except XCAPStorageError as e:
notification_data = NotificationData(method=method, url=self.url, application=self.application, result='failed', reason='storage failure: %s' % str(e), code=500, etag=self.etag, size=len(data))
notification_center.post_notification('XCAPTrace', sender=self, data=notification_data)
class DialogRulesDocument(Document):
name = 'dialog-rules'
application = 'org.openxcap.dialog-rules'
payload_type = dialogrules.DialogRulesDocument
default_namespace = dialogrules.namespace
global_tree = False
filename = 'index'
class PresRulesDocument(Document):
name = 'pres-rules'
application = 'org.openmobilealliance.pres-rules'
payload_type = presrules.PresRulesDocument
default_namespace = presrules.namespace
global_tree = False
filename = 'index'
class ResourceListsDocument(Document):
name = 'resource-lists'
application = 'resource-lists'
payload_type = resourcelists.ResourceListsDocument
default_namespace = resourcelists.namespace
global_tree = False
filename = 'index'
def update(self):
if self.content is not None:
sipsimple_addressbook = self.content['sipsimple_addressbook']
groups = ItemCollection(sipsimple_addressbook[addressbook.Group, IterateItems])
contacts = ItemCollection(sipsimple_addressbook[addressbook.Contact, IterateItems])
policies = ItemCollection(sipsimple_addressbook[addressbook.Policy, IterateItems])
for group, missing_id in ((group, missing_id) for group in groups for missing_id in [id for id in group.contacts if id not in contacts]):
group.contacts.remove(missing_id)
if any(item.__dirty__ for item in chain(contacts, policies)):
oma_grantedcontacts = self.content['oma_grantedcontacts']
oma_blockedcontacts = self.content['oma_blockedcontacts']
dialog_grantedcontacts = self.content['dialog_grantedcontacts']
dialog_blockedcontacts = self.content['dialog_blockedcontacts']
sipsimple_presence_rls = self.content['sipsimple_presence_rls']
sipsimple_dialog_rls = self.content['sipsimple_dialog_rls']
all_contact_uris = set(uri.uri for contact in contacts for uri in contact.uris)
contact_allow_presence_uris = set(uri.uri for contact in contacts for uri in contact.uris if contact.presence.policy=='allow')
contact_block_presence_uris = set(uri.uri for contact in contacts for uri in contact.uris if contact.presence.policy=='block')
contact_allow_dialog_uris = set(uri.uri for contact in contacts for uri in contact.uris if contact.dialog.policy=='allow')
contact_block_dialog_uris = set(uri.uri for contact in contacts for uri in contact.uris if contact.dialog.policy=='block')
contact_subscribe_presence_uris = set(uri.uri for contact in contacts for uri in contact.uris if contact.presence.subscribe==True)
contact_subscribe_dialog_uris = set(uri.uri for contact in contacts for uri in contact.uris if contact.dialog.subscribe==True)
policy_allow_presence_uris = set(policy.uri for policy in policies if policy.presence.policy=='allow')
policy_block_presence_uris = set(policy.uri for policy in policies if policy.presence.policy=='block')
policy_allow_dialog_uris = set(policy.uri for policy in policies if policy.dialog.policy=='allow')
policy_block_dialog_uris = set(policy.uri for policy in policies if policy.dialog.policy=='block')
policy_subscribe_presence_uris = set(policy.uri for policy in policies if policy.presence.subscribe==True)
policy_subscribe_dialog_uris = set(policy.uri for policy in policies if policy.dialog.subscribe==True)
allowed_presence_uris = contact_allow_presence_uris - contact_block_presence_uris | policy_allow_presence_uris - policy_block_presence_uris - all_contact_uris
blocked_presence_uris = contact_block_presence_uris | policy_block_presence_uris - all_contact_uris
allowed_dialog_uris = contact_allow_dialog_uris - contact_block_dialog_uris | policy_allow_dialog_uris - policy_block_dialog_uris - all_contact_uris
blocked_dialog_uris = contact_block_dialog_uris | policy_block_dialog_uris - all_contact_uris
subscribe_presence_uris = contact_subscribe_presence_uris | policy_subscribe_presence_uris - all_contact_uris
subscribe_dialog_uris = contact_subscribe_dialog_uris | policy_subscribe_dialog_uris - all_contact_uris
if allowed_presence_uris != set(entry.uri for entry in oma_grantedcontacts):
oma_grantedcontacts.clear()
oma_grantedcontacts.update(resourcelists.Entry(uri) for uri in allowed_presence_uris)
if blocked_presence_uris != set(entry.uri for entry in oma_blockedcontacts):
oma_blockedcontacts.clear()
oma_blockedcontacts.update(resourcelists.Entry(uri) for uri in blocked_presence_uris)
if allowed_dialog_uris != set(entry.uri for entry in dialog_grantedcontacts):
dialog_grantedcontacts.clear()
dialog_grantedcontacts.update(resourcelists.Entry(uri) for uri in allowed_dialog_uris)
if blocked_dialog_uris != set(entry.uri for entry in dialog_blockedcontacts):
dialog_blockedcontacts.clear()
dialog_blockedcontacts.update(resourcelists.Entry(uri) for uri in blocked_dialog_uris)
if subscribe_presence_uris != set(entry.uri for entry in sipsimple_presence_rls):
sipsimple_presence_rls.clear()
sipsimple_presence_rls.update(resourcelists.Entry(uri) for uri in subscribe_presence_uris)
if subscribe_dialog_uris != set(entry.uri for entry in sipsimple_dialog_rls):
sipsimple_dialog_rls.clear()
sipsimple_dialog_rls.update(resourcelists.Entry(uri) for uri in subscribe_dialog_uris)
super(ResourceListsDocument, self).update()
class RLSServicesDocument(Document):
name = 'rls-services'
application = 'rls-services'
payload_type = rlsservices.RLSServicesDocument
default_namespace = rlsservices.namespace
global_tree = False
filename = 'index'
class XCAPCapsDocument(Document):
name = 'xcap-caps'
application = 'xcap-caps'
payload_type = xcapcaps.XCAPCapabilitiesDocument
default_namespace = xcapcaps.namespace
global_tree = True
filename = 'index'
cached = False
def initialize(self):
self.supported = True
class StatusIconDocument(Document):
name = 'status-icon'
application = 'org.openmobilealliance.pres-content'
payload_type = prescontent.PresenceContentDocument
default_namespace = prescontent.namespace
global_tree = False
filename = 'oma_status-icon/index'
class PIDFManipulationDocument(Document):
name = 'pidf-manipulation'
application = 'pidf-manipulation'
payload_type = pidf.PIDFDocument
default_namespace = pidf.pidf_namespace
global_tree = False
filename = 'index'
class ItemCollection(object):
def __init__(self, items):
self.items = OrderedDict((item.id, item) for item in items)
def __getitem__(self, key):
return self.items[key]
def __contains__(self, key):
return key in self.items
def __iter__(self):
return iter(list(self.items.values()))
def __reversed__(self):
return (self[id] for id in reversed(self.items))
def __len__(self):
return len(self.items)
def __eq__(self, other):
if isinstance(other, ItemCollection):
return self.items == other.items
return NotImplemented
def __ne__(self, other):
equal = self.__eq__(other)
return NotImplemented if equal is NotImplemented else not equal
def __repr__(self):
return "%s(%r)" % (self.__class__.__name__, list(self.items.values()))
def ids(self):
return list(self.items.keys())
def iterids(self):
return iter(list(self.items.keys()))
def get(self, key, default=None):
return self.items.get(key, default)
def add(self, item):
self.items[item.id] = item
def remove(self, item):
del self.items[item.id]
class ContactList(ItemCollection):
pass
class ContactURIList(ItemCollection):
def __init__(self, items, default=None):
super(ContactURIList, self).__init__(items)
self.default = default
def __eq__(self, other):
if isinstance(other, ContactURIList):
return self.items == other.items and self.default == other.default
return NotImplemented
def __repr__(self):
return "%s(%r, default=%r)" % (self.__class__.__name__, list(self.items.values()), self.default)
class Group(object):
def __init__(self, id, name, contacts, **attributes):
self.id = id
self.name = name
self.contacts = contacts
self.attributes = attributes
def __eq__(self, other):
if isinstance(other, Group):
return self is other or (self.id == other.id and self.name == other.name and self.contacts.ids() == other.contacts.ids() and self.attributes == other.attributes)
return NotImplemented
def __ne__(self, other):
equal = self.__eq__(other)
return NotImplemented if equal is NotImplemented else not equal
def __setattr__(self, name, value):
if name == 'contacts' and not isinstance(value, ContactList):
value = ContactList(value)
object.__setattr__(self, name, value)
class ContactURI(object):
def __init__(self, id, uri, type, **attributes):
self.id = id
self.uri = uri
self.type = type
self.attributes = attributes
def __eq__(self, other):
if isinstance(other, ContactURI):
return self is other or (self.id == other.id and self.uri == other.uri and self.type == other.type and self.attributes == other.attributes)
return NotImplemented
def __ne__(self, other):
equal = self.__eq__(other)
return NotImplemented if equal is NotImplemented else not equal
class EventHandling(object):
def __init__(self, policy, subscribe):
self.policy = policy
self.subscribe = subscribe
def __eq__(self, other):
if isinstance(other, EventHandling):
return self is other or (self.policy == other.policy and self.subscribe == other.subscribe)
return NotImplemented
def __ne__(self, other):
equal = self.__eq__(other)
return NotImplemented if equal is NotImplemented else not equal
def __repr__(self):
return '%s(%r, %r)' % (self.__class__.__name__, self.policy, self.subscribe)
class Contact(object):
def __init__(self, id, name, uris, presence_handling=None, dialog_handling=None, **attributes):
self.id = id
self.name = name
self.uris = uris
self.dialog = dialog_handling or EventHandling(policy='default', subscribe=False)
self.presence = presence_handling or EventHandling(policy='default', subscribe=False)
self.attributes = attributes
def __eq__(self, other):
if isinstance(other, Contact):
return self is other or (self.id == other.id and self.name == other.name and self.uris == other.uris and self.dialog == other.dialog and self.presence == other.presence and
self.attributes == other.attributes)
return NotImplemented
def __ne__(self, other):
equal = self.__eq__(other)
return NotImplemented if equal is NotImplemented else not equal
def __setattr__(self, name, value):
if name == 'uris' and not isinstance(value, ContactURIList):
value = ContactURIList(value)
object.__setattr__(self, name, value)
class Policy(object):
def __init__(self, id, uri, name, presence_handling=None, dialog_handling=None, **attributes):
self.id = id
self.uri = uri
self.name = name
self.dialog = dialog_handling or EventHandling(policy='default', subscribe=False)
self.presence = presence_handling or EventHandling(policy='default', subscribe=False)
self.attributes = attributes
def __eq__(self, other):
if isinstance(other, Policy):
return self is other or (self.id == other.id and self.uri == other.uri and self.name == other.name and self.dialog == other.dialog and self.presence == other.presence and
self.attributes == other.attributes)
return NotImplemented
def __ne__(self, other):
equal = self.__eq__(other)
return NotImplemented if equal is NotImplemented else not equal
class Addressbook(object):
def __init__(self, contacts, groups, policies):
self.contacts = contacts
self.groups = groups
self.policies = policies
def __eq__(self, other):
if isinstance(other, Addressbook):
return self is other or (self.contacts == other.contacts and self.groups == other.groups and self.policies == other.policies)
return NotImplemented
def __ne__(self, other):
equal = self.__eq__(other)
return NotImplemented if equal is NotImplemented else not equal
@classmethod
def from_payload(cls, payload):
def payload_to_contact(payload):
uris = ContactURIList((ContactURI(uri.id, uri.uri, uri.type, **(uri.attributes or {})) for uri in payload.uris), default=payload.uris.default)
presence_handling = EventHandling(payload.presence.policy.value, payload.presence.subscribe.value)
dialog_handling = EventHandling(payload.dialog.policy.value, payload.dialog.subscribe.value)
return Contact(payload.id, payload.name.value, uris, presence_handling, dialog_handling, **(payload.attributes or {}))
def payload_to_group(payload):
return Group(payload.id, payload.name.value, [contacts[contact_id] for contact_id in payload.contacts], **(payload.attributes or {}))
def payload_to_policy(payload):
presence_handling = EventHandling(payload.presence.policy.value, payload.presence.subscribe.value)
dialog_handling = EventHandling(payload.dialog.policy.value, payload.dialog.subscribe.value)
return Policy(payload.id, payload.uri, payload.name.value, presence_handling, dialog_handling, **(payload.attributes or {}))
contacts = ItemCollection(payload_to_contact(item) for item in payload[addressbook.Contact, IterateItems])
groups = ItemCollection(payload_to_group(item) for item in payload[addressbook.Group, IterateItems])
policies = ItemCollection(payload_to_policy(item) for item in payload[addressbook.Policy, IterateItems])
return cls(contacts, groups, policies)
class PresenceRules(object):
def __init__(self, default_policy):
self.default_policy = default_policy
def __eq__(self, other):
if isinstance(other, PresenceRules):
return self is other or (self.default_policy == other.default_policy)
return NotImplemented
def __ne__(self, other):
equal = self.__eq__(other)
return NotImplemented if equal is NotImplemented else not equal
@classmethod
def from_payload(cls, default_rule):
default_policy = next(item for item in default_rule.actions if isinstance(item, presrules.SubHandling)).value
return cls(default_policy)
class DialogRules(object):
def __init__(self, default_policy):
self.default_policy = default_policy
def __eq__(self, other):
if isinstance(other, DialogRules):
return self is other or (self.default_policy == other.default_policy)
return NotImplemented
def __ne__(self, other):
equal = self.__eq__(other)
return NotImplemented if equal is NotImplemented else not equal
@classmethod
def from_payload(cls, default_rule):
if default_rule is not None:
default_policy = next(item for item in default_rule.actions if isinstance(item, dialogrules.SubHandling)).value
else:
default_policy = None
return cls(default_policy)
class Icon(object):
__mimetypes__ = ('image/jpeg', 'image/png', 'image/gif')
def __init__(self, data, mime_type, description=None):
self.data = data
self.mime_type = mime_type
self.description = description
self.url = None
self.etag = None
def __eq__(self, other):
if isinstance(other, Icon):
return self is other or (self.data == other.data and self.mime_type == other.mime_type and self.description == other.description)
return NotImplemented
def __ne__(self, other):
equal = self.__eq__(other)
return NotImplemented if equal is NotImplemented else not equal
def __setattr__(self, name, value):
if name == 'mime_type' and value not in self.__mimetypes__:
raise ValueError("invalid mime type: '%s'. Should be one of: %s" % (value, ', '.join(self.__mimetypes__)))
object.__setattr__(self, name, value)
@classmethod
def from_payload(cls, payload):
try:
base64_bytes = payload.data.value.encode('ascii')
data = base64.b64decode(base64_bytes)
except Exception as e:
return None
else:
description = payload.description.value if payload.description else None
return cls(data, payload.mime_type.value, description)
class OfflineStatus(object):
__slots__ = ('pidf',)
def __init__(self, pidf):
self.pidf = pidf
def __setattr__(self, name, value):
if name == 'pidf' and not isinstance(value, pidf.PIDF):
raise ValueError("pidf must be a PIDF payload")
object.__setattr__(self, name, value)
def __eq__(self, other):
if isinstance(other, OfflineStatus):
return self is other or (self.pidf == other.pidf)
return NotImplemented
def __ne__(self, other):
equal = self.__eq__(other)
return NotImplemented if equal is NotImplemented else not equal
def __getstate__(self):
return {'pidf': self.pidf.toxml()}
def __setstate__(self, state):
self.pidf = pidf.PIDFDocument.parse(state['pidf'])
class Operation(object):
__params__ = ()
def __init__(self, **params):
for name, value in list(params.items()):
setattr(self, name, value)
for param in set(self.__params__).difference(params):
raise ValueError("missing operation parameter: '%s'" % param)
self.applied = False
self.timestamp = datetime.utcnow()
class NormalizeOperation(Operation):
__params__ = ()
class AddContactOperation(Operation):
__params__ = ('contact',)
class UpdateContactOperation(Operation):
__params__ = ('contact', 'attributes')
class RemoveContactOperation(Operation):
__params__ = ('contact',)
class AddContactURIOperation(Operation):
__params__ = ('contact', 'uri')
class UpdateContactURIOperation(Operation):
__params__ = ('contact', 'uri', 'attributes')
class RemoveContactURIOperation(Operation):
__params__ = ('contact', 'uri')
class AddGroupOperation(Operation):
__params__ = ('group',)
class UpdateGroupOperation(Operation):
__params__ = ('group', 'attributes')
class RemoveGroupOperation(Operation):
__params__ = ('group',)
class AddGroupMemberOperation(Operation):
__params__ = ('group', 'contact')
class RemoveGroupMemberOperation(Operation):
__params__ = ('group', 'contact')
class AddPolicyOperation(Operation):
__params__ = ('policy',)
class UpdatePolicyOperation(Operation):
__params__ = ('policy', 'attributes')
class RemovePolicyOperation(Operation):
__params__ = ('policy',)
class SetDefaultPresencePolicyOperation(Operation):
__params__ = ('policy',)
class SetDefaultDialogPolicyOperation(Operation):
__params__ = ('policy',)
class SetStatusIconOperation(Operation):
__params__ = ('icon',)
class SetOfflineStatusOperation(Operation):
__params__ = ('status',)
class XCAPSubscriber(Subscriber):
__transports__ = frozenset(['tls', 'tcp'])
@property
def event(self):
return 'xcap-diff'
@property
def content(self):
rlist = resourcelists.List()
for document in (doc for doc in self.account.xcap_manager.documents if doc.supported):
rlist.add(resourcelists.Entry(document.relative_url))
return Content(resourcelists.ResourceLists([rlist]).toxml(), resourcelists.ResourceListsDocument.content_type)
@implementer(IObserver)
class XCAPManager(object):
def __init__(self, account):
from sipsimple.application import SIPApplication
if SIPApplication.storage is None:
raise RuntimeError("SIPApplication.storage must be defined before instantiating XCAPManager")
storage = SIPApplication.storage.xcap_storage_factory(account.id)
if not IXCAPStorage.providedBy(storage):
raise TypeError("storage must implement the IXCAPStorage interface")
self.account = account
self.storage = storage
self.storage_factory = SIPApplication.storage.xcap_storage_factory
self.client = None
self.command_proc = None
self.command_channel = coros.queue()
self.last_fetch_time = datetime.fromtimestamp(0)
self.last_update_time = datetime.fromtimestamp(0)
self.not_executed_fetch = None
self.state = 'stopped'
self.timer = None
self.transaction_level = 0
self.xcap_subscriber = None
self.server_caps = XCAPCapsDocument(self)
self.dialog_rules = DialogRulesDocument(self)
self.pidf_manipulation = PIDFManipulationDocument(self)
self.pres_rules = PresRulesDocument(self)
self.resource_lists = ResourceListsDocument(self)
self.rls_services = RLSServicesDocument(self)
self.status_icon = StatusIconDocument(self)
for document in self.documents:
document.load_from_cache()
try:
journal = self.storage.load('journal')
except XCAPStorageError:
self.journal = []
else:
try:
self.journal = pickle.loads(journal)
except Exception:
self.journal = []
for operation in self.journal:
operation.applied = False
notification_center = NotificationCenter()
notification_center.add_observer(self, sender=account, name='CFGSettingsObjectDidChange')
notification_center.add_observer(self, sender=account, name='CFGSettingsObjectWasDeleted')
@property
def state(self):
return self.__dict__['state']
@state.setter
def 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=NotificationData(prev_state=old_value, state=value))
@property
def documents(self):
return [self.resource_lists, self.rls_services, self.pres_rules, self.dialog_rules, self.pidf_manipulation, self.status_icon]
@property
def document_names(self):
return [document.name for document in self.documents]
@property
def xcap_root(self):
return getattr(self.client, 'root', None)
@property
def rls_presence_uri(self):
return SIPAddress('%s+presence@%s' % (self.account.id.username, self.account.id.domain))
@property
def rls_dialog_uri(self):
return SIPAddress('%s+dialog@%s' % (self.account.id.username, self.account.id.domain))
@run_in_green_thread
def init(self):
"""
Initializes the XCAP manager before it can be started. Needs to be
called before any other method and in a green thread.
"""
self.command_proc = proc.spawn(self._run)
@run_in_green_thread
def start(self):
"""
Starts the XCAP manager. This method needs to be called in a green
thread.
"""
command = Command('start')
self.command_channel.send(command)
command.wait()
@run_in_green_thread
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.
"""
command = Command('stop')
self.command_channel.send(command)
command.wait()
def transaction(self):
return XCAPTransaction(self)
@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._save_journal()
self.command_channel.send(Command('update'))
def add_contact(self, contact):
notification_center = NotificationCenter()
notification_center.post_notification('XCAPManagerDidAddContact', sender=self, data=NotificationData(contact=contact))
self._schedule_operation(AddContactOperation(contact=contact))
def update_contact(self, contact, attributes):
notification_center = NotificationCenter()
notification_center.post_notification('XCAPManagerDidUpdateContact', sender=self, data=NotificationData(contact=contact))
self._schedule_operation(UpdateContactOperation(contact=contact, attributes=attributes))
def remove_contact(self, contact):
notification_center = NotificationCenter()
notification_center.post_notification('XCAPManagerDidRemoveContact', sender=self, data=NotificationData(contact=contact))
self._schedule_operation(RemoveContactOperation(contact=contact))
def add_contact_uri(self, contact, uri):
self._schedule_operation(AddContactURIOperation(contact=contact, uri=uri))
def update_contact_uri(self, contact, uri, attributes):
self._schedule_operation(UpdateContactURIOperation(contact=contact, uri=uri, attributes=attributes))
def remove_contact_uri(self, contact, uri):
self._schedule_operation(RemoveContactURIOperation(contact=contact, uri=uri))
def add_group(self, group):
notification_center = NotificationCenter()
notification_center.post_notification('XCAPManagerDidAddGroup', sender=self, data=NotificationData(group=group))
self._schedule_operation(AddGroupOperation(group=group))
def update_group(self, group, attributes):
notification_center = NotificationCenter()
notification_center.post_notification('XCAPManagerDidUpdateGroup', sender=self, data=NotificationData(group=group))
self._schedule_operation(UpdateGroupOperation(group=group, attributes=attributes))
def remove_group(self, group):
notification_center = NotificationCenter()
notification_center.post_notification('XCAPManagerDidRemoveGroup', sender=self, data=NotificationData(group=group))
self._schedule_operation(RemoveGroupOperation(group=group))
def add_group_member(self, group, contact):
notification_center = NotificationCenter()
notification_center.post_notification('XCAPManageDidAddGroupMember', sender=self, data=NotificationData(group=group, contact=contact))
self._schedule_operation(AddGroupMemberOperation(group=group, contact=contact))
def remove_group_member(self, group, contact):
notification_center = NotificationCenter()
notification_center.post_notification('XCAPManageDidRemoveGroupMember', sender=self, data=NotificationData(group=group, contact=contact))
self._schedule_operation(RemoveGroupMemberOperation(group=group, contact=contact))
def add_policy(self, policy):
self._schedule_operation(AddPolicyOperation(policy=policy))
def update_policy(self, policy, attributes):
self._schedule_operation(UpdatePolicyOperation(policy=policy, attributes=attributes))
def remove_policy(self, policy):
self._schedule_operation(RemovePolicyOperation(policy=policy))
def set_default_presence_policy(self, policy):
self._schedule_operation(SetDefaultPresencePolicyOperation(policy=presrules.SubHandlingValue(policy)))
def set_default_dialog_policy(self, policy):
self._schedule_operation(SetDefaultDialogPolicyOperation(policy=dialogrules.SubHandlingValue(policy)))
def set_status_icon(self, icon):
self._schedule_operation(SetStatusIconOperation(icon=icon))
def set_offline_status(self, status):
self._schedule_operation(SetOfflineStatusOperation(status=status))
@run_in_twisted_thread
def _schedule_operation(self, operation):
self.journal.append(operation)
if self.transaction_level == 0:
self._save_journal()
self.command_channel.send(Command('update'))
def _run(self):
while True:
command = self.command_channel.wait()
try:
handler = getattr(self, '_CH_%s' % command.name)
handler(command)
except:
self.command_proc = None
raise
# Command handlers
#
def _CH_start(self, command):
if self.state != 'stopped':
command.signal()
return
self.state = 'initializing'
- self.xcap_subscriber = XCAPSubscriber(self.account)
notification_center = NotificationCenter()
notification_center.post_notification('XCAPManagerWillStart', sender=self)
- notification_center.add_observer(self, sender=self.xcap_subscriber)
notification_center.add_observer(self, sender=SIPSimpleSettings(), name='CFGSettingsObjectDidChange')
- self.xcap_subscriber.start()
+ self.xcap_subscriber = XCAPSubscriber(self.account)
+ notification_center.add_observer(self, sender=self.xcap_subscriber)
+ if self.account.xcap.xcap_diff:
+ self.xcap_subscriber.start()
self.command_channel.send(Command('initialize'))
notification_center.post_notification('XCAPManagerDidStart', sender=self)
command.signal()
def _CH_stop(self, command):
if self.state in ('stopped', 'terminated'):
command.signal()
return
notification_center = NotificationCenter()
notification_center.post_notification('XCAPManagerWillEnd', sender=self)
notification_center.remove_observer(self, sender=self.xcap_subscriber)
notification_center.remove_observer(self, sender=SIPSimpleSettings(), name='CFGSettingsObjectDidChange')
if self.timer is not None and self.timer.active():
self.timer.cancel()
self.timer = None
self.xcap_subscriber.stop()
self.xcap_subscriber = None
self.client = None
self.state = 'stopped'
self._save_journal()
notification_center.post_notification('XCAPManagerDidEnd', sender=self)
command.signal()
def _CH_cleanup(self, command):
if self.state != 'stopped':
command.signal()
return
try:
self.storage.purge()
except XCAPStorageError:
pass
self.journal = []
self.state = 'terminated'
command.signal()
raise proc.ProcExit
def _CH_initialize(self, command):
notification_center = NotificationCenter()
notification_center.post_notification('XCAPManagerClientWillInitialize', sender=self, data=NotificationData(root=self.account.xcap.xcap_root))
self.state = 'initializing'
if self.timer is not None and self.timer.active():
self.timer.cancel()
self.timer = None
xcap_root = self.account.xcap.xcap_root
if self.account.xcap.xcap_root:
self.client = XCAPClient(self.account.xcap.xcap_root, self.account.id, password=self.account.auth.password)
else:
try:
lookup = DNSLookup()
xcap_root = random.choice(lookup.lookup_xcap_server(self.account.uri).wait())
except DNSLookupError as e:
notification_center.post_notification('XCAPManagerClientDidNotInitialize', sender=self, data=NotificationData(error=str(e)))
self.timer = self._schedule_command(60, Command('initialize', command.event))
return
else:
self.client = XCAPClient(xcap_root, self.account.id, password=self.account.auth.password)
notification_center.post_notification('XCAPManagerClientDidInitialize', sender=self, data=NotificationData(client=self.client, root=xcap_root))
try:
self.server_caps.fetch()
except XCAPError as e:
notification_center.post_notification('XCAPManagerClientError', sender=self, data=NotificationData(client=self.client, context='xcap-caps', error=str(e)))
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
if not set(self.server_caps.content.auids).issuperset(('resource-lists', 'rls-services', 'org.openmobilealliance.pres-rules')):
# Server must support at least resource-lists, rls-services and org.openmobilealliance.pres-rules
self.timer = self._schedule_command(3600, Command('initialize', command.event))
return
self.server_caps.initialize()
for document in self.documents:
document.initialize(self.server_caps.content)
notification_center.post_notification('XCAPManagerDidDiscoverServerCapabilities', sender=self, data=NotificationData(auids=self.server_caps.content.auids))
self.state = 'fetching'
self.command_channel.send(Command('fetch', documents=set(self.document_names)))
self.xcap_subscriber.activate()
def _CH_reload(self, command):
if self.state == 'terminated':
command.signal()
return
if '__id__' in command.modified:
try:
self.storage.purge()
except XCAPStorageError:
pass
self.storage = self.storage_factory(self.account.id)
self.journal = []
self._save_journal()
if {'__id__', 'xcap.xcap_root'}.intersection(command.modified):
for document in self.documents:
document.reset()
if self.state == 'stopped':
command.signal()
return
if {'__id__', 'auth.username', 'auth.password', 'xcap.xcap_root'}.intersection(command.modified):
self.state = 'initializing'
self.command_channel.send(Command('initialize'))
else:
- self.xcap_subscriber.resubscribe()
+ if self.account.xcap.xcap_diff:
+ self.xcap_subscriber.resubscribe()
command.signal()
def _CH_fetch(self, command):
if self.state not in ('insync', 'fetching'):
if self.not_executed_fetch is not None:
command.documents.update(self.not_executed_fetch.documents)
self.not_executed_fetch = command
return
if self.not_executed_fetch is not None:
command.documents.update(self.not_executed_fetch.documents)
self.not_executed_fetch = None
self.state = 'fetching'
if self.timer is not None and self.timer.active():
command.documents.update(self.timer.command.documents)
self.timer.cancel()
self.timer = None
try:
self._fetch_documents(command.documents)
except XCAPError:
self.timer = self._schedule_command(60, Command('fetch', command.event, documents=command.documents))
return
if not self.journal and 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'
if not self.journal or type(self.journal[0]) is not NormalizeOperation:
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.__class__.__name__)
try:
handler(operation)
except Exception:
# Error while applying operation, needs to be logged -Luci
log.exception()
operation.applied = True
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=set(self.document_names))) # Try to fetch them all just in case
except XCAPError:
self.timer = self._schedule_command(60, Command('update'))
else:
del self.journal[:len(journal)]
if not self.journal:
self.state = 'insync'
if any(max(doc.update_time, doc.fetch_time) > self.last_update_time for doc in self.documents):
self._load_data()
self.last_update_time = datetime.utcnow()
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()
# Operation handlers
#
def _OH_NormalizeOperation(self, operation):
# Normalize resource-lists
#
if self.resource_lists.content is None:
self.resource_lists.content = resourcelists.ResourceLists()
resource_lists = self.resource_lists.content
try:
oma_buddylist = resource_lists['oma_buddylist']
except KeyError:
oma_buddylist = resourcelists.List(name='oma_buddylist')
resource_lists.add(oma_buddylist)
try:
oma_grantedcontacts = resource_lists['oma_grantedcontacts']
except KeyError:
oma_grantedcontacts = resourcelists.List(name='oma_grantedcontacts')
resource_lists.add(oma_grantedcontacts)
try:
oma_blockedcontacts = resource_lists['oma_blockedcontacts']
except KeyError:
oma_blockedcontacts = resourcelists.List(name='oma_blockedcontacts')
resource_lists.add(oma_blockedcontacts)
try:
oma_allcontacts = resource_lists['oma_allcontacts']
except KeyError:
oma_allcontacts = resourcelists.List(name='oma_allcontacts')
oma_allcontacts.add(resourcelists.External(self.resource_lists.url + '/~~' + resource_lists.get_xpath(oma_buddylist)))
oma_allcontacts.add(resourcelists.External(self.resource_lists.url + '/~~' + resource_lists.get_xpath(oma_grantedcontacts)))
oma_allcontacts.add(resourcelists.External(self.resource_lists.url + '/~~' + resource_lists.get_xpath(oma_blockedcontacts)))
resource_lists.add(oma_allcontacts)
try:
dialog_grantedcontacts = resource_lists['dialog_grantedcontacts']
except KeyError:
dialog_grantedcontacts = resourcelists.List(name='dialog_grantedcontacts')
resource_lists.add(dialog_grantedcontacts)
try:
dialog_blockedcontacts = resource_lists['dialog_blockedcontacts']
except KeyError:
dialog_blockedcontacts = resourcelists.List(name='dialog_blockedcontacts')
resource_lists.add(dialog_blockedcontacts)
try:
sipsimple_presence_rls = resource_lists['sipsimple_presence_rls']
except KeyError:
sipsimple_presence_rls = resourcelists.List(name='sipsimple_presence_rls')
resource_lists.add(sipsimple_presence_rls)
try:
sipsimple_dialog_rls = resource_lists['sipsimple_dialog_rls']
except KeyError:
sipsimple_dialog_rls = resourcelists.List(name='sipsimple_dialog_rls')
resource_lists.add(sipsimple_dialog_rls)
try:
sipsimple_addressbook = resource_lists['sipsimple_addressbook']
except KeyError:
sipsimple_addressbook = resourcelists.List(name='sipsimple_addressbook')
resource_lists.add(sipsimple_addressbook)
for cls in (cls for cls in sipsimple_addressbook[IterateTypes] if cls not in (addressbook.Contact, addressbook.Group, addressbook.Policy)):
del sipsimple_addressbook[cls, All]
for cls in (cls for cls in oma_grantedcontacts[IterateTypes] if cls is not resourcelists.Entry):
del oma_grantedcontacts[cls, All]
for cls in (cls for cls in oma_blockedcontacts[IterateTypes] if cls is not resourcelists.Entry):
del oma_blockedcontacts[cls, All]
for cls in (cls for cls in dialog_grantedcontacts[IterateTypes] if cls is not resourcelists.Entry):
del dialog_grantedcontacts[cls, All]
for cls in (cls for cls in dialog_blockedcontacts[IterateTypes] if cls is not resourcelists.Entry):
del dialog_blockedcontacts[cls, All]
for cls in (cls for cls in sipsimple_presence_rls[IterateTypes] if cls is not resourcelists.Entry):
del sipsimple_presence_rls[cls, All]
for cls in (cls for cls in sipsimple_dialog_rls[IterateTypes] if cls is not resourcelists.Entry):
del sipsimple_dialog_rls[cls, All]
groups = ItemCollection(sipsimple_addressbook[addressbook.Group, IterateItems])
contacts = ItemCollection(sipsimple_addressbook[addressbook.Contact, IterateItems])
policies = ItemCollection(sipsimple_addressbook[addressbook.Policy, IterateItems])
for group, missing_id in [(group, missing_id) for group in groups for missing_id in (id for id in group.contacts if id not in contacts)]:
group.contacts.remove(missing_id)
all_contact_uris = set(uri.uri for contact in contacts for uri in contact.uris)
contact_allow_presence_uris = set(uri.uri for contact in contacts for uri in contact.uris if contact.presence.policy=='allow')
contact_block_presence_uris = set(uri.uri for contact in contacts for uri in contact.uris if contact.presence.policy=='block')
contact_allow_dialog_uris = set(uri.uri for contact in contacts for uri in contact.uris if contact.dialog.policy=='allow')
contact_block_dialog_uris = set(uri.uri for contact in contacts for uri in contact.uris if contact.dialog.policy=='block')
contact_subscribe_presence_uris = set(uri.uri for contact in contacts for uri in contact.uris if contact.presence.subscribe==True)
contact_subscribe_dialog_uris = set(uri.uri for contact in contacts for uri in contact.uris if contact.dialog.subscribe==True)
policy_allow_presence_uris = set(policy.uri for policy in policies if policy.presence.policy=='allow')
policy_block_presence_uris = set(policy.uri for policy in policies if policy.presence.policy=='block')
policy_allow_dialog_uris = set(policy.uri for policy in policies if policy.dialog.policy=='allow')
policy_block_dialog_uris = set(policy.uri for policy in policies if policy.dialog.policy=='block')
policy_subscribe_presence_uris = set(policy.uri for policy in policies if policy.presence.subscribe==True)
policy_subscribe_dialog_uris = set(policy.uri for policy in policies if policy.dialog.subscribe==True)
allowed_presence_uris = contact_allow_presence_uris - contact_block_presence_uris | policy_allow_presence_uris - policy_block_presence_uris - all_contact_uris
blocked_presence_uris = contact_block_presence_uris | policy_block_presence_uris - all_contact_uris
allowed_dialog_uris = contact_allow_dialog_uris - contact_block_dialog_uris | policy_allow_dialog_uris - policy_block_dialog_uris - all_contact_uris
blocked_dialog_uris = contact_block_dialog_uris | policy_block_dialog_uris - all_contact_uris
subscribe_presence_uris = contact_subscribe_presence_uris | policy_subscribe_presence_uris - all_contact_uris
subscribe_dialog_uris = contact_subscribe_dialog_uris | policy_subscribe_dialog_uris - all_contact_uris
if allowed_presence_uris != set(entry.uri for entry in oma_grantedcontacts):
oma_grantedcontacts.clear()
oma_grantedcontacts.update(resourcelists.Entry(uri) for uri in allowed_presence_uris)
if blocked_presence_uris != set(entry.uri for entry in oma_blockedcontacts):
oma_blockedcontacts.clear()
oma_blockedcontacts.update(resourcelists.Entry(uri) for uri in blocked_presence_uris)
if allowed_dialog_uris != set(entry.uri for entry in dialog_grantedcontacts):
dialog_grantedcontacts.clear()
dialog_grantedcontacts.update(resourcelists.Entry(uri) for uri in allowed_dialog_uris)
if blocked_dialog_uris != set(entry.uri for entry in dialog_blockedcontacts):
dialog_blockedcontacts.clear()
dialog_blockedcontacts.update(resourcelists.Entry(uri) for uri in blocked_dialog_uris)
if subscribe_presence_uris != set(entry.uri for entry in sipsimple_presence_rls):
sipsimple_presence_rls.clear()
sipsimple_presence_rls.update(resourcelists.Entry(uri) for uri in subscribe_presence_uris)
if subscribe_dialog_uris != set(entry.uri for entry in sipsimple_dialog_rls):
sipsimple_dialog_rls.clear()
sipsimple_dialog_rls.update(resourcelists.Entry(uri) for uri in subscribe_dialog_uris)
# Normalize rls-services
#
if self.rls_services.content is None:
self.rls_services.content = rlsservices.RLSServices()
rls_services = self.rls_services.content
rls_presence_uri = 'sip:' + self.rls_presence_uri
rls_dialog_uri = 'sip:' + self.rls_dialog_uri
rls_presence_list = rlsservices.ResourceList(self.resource_lists.url + '/~~' + resource_lists.get_xpath(sipsimple_presence_rls))
rls_dialog_list = rlsservices.ResourceList(self.resource_lists.url + '/~~' + resource_lists.get_xpath(sipsimple_dialog_rls))
try:
rls_presence_service = rls_services[rls_presence_uri]
except KeyError:
rls_presence_service = rlsservices.Service(rls_presence_uri, list=rls_presence_list, packages=['presence'])
rls_services.add(rls_presence_service)
else:
if rls_presence_service.list != rls_presence_list:
rls_presence_service.list = rls_presence_list
if list(rls_presence_service.packages) != ['presence']:
rls_presence_service.packages = ['presence']
try:
rls_dialog_service = rls_services[rls_dialog_uri]
except KeyError:
rls_dialog_service = rlsservices.Service(rls_dialog_uri, list=rls_dialog_list, packages=['dialog'])
rls_services.add(rls_dialog_service)
else:
if rls_dialog_service.list != rls_dialog_list:
rls_dialog_service.list = rls_dialog_list
if list(rls_dialog_service.packages) != ['dialog']:
rls_dialog_service.packages = ['dialog']
# Normalize pres-rules
#
if self.pres_rules.content is None:
self.pres_rules.content = presrules.PresRules()
def fix_subhandling(rule, valid_values=[]):
subhandling_elements = sorted((item for item in rule.actions if isinstance(item, presrules.SubHandling)), key=attrgetter('value.priority'))
if not subhandling_elements:
subhandling_elements = [presrules.SubHandling('block')] # spec specifies that missing SubHandling means block
rule.actions.update(subhandling_elements)
subhandling = subhandling_elements.pop()
for item in subhandling_elements: # remove any extraneous SubHandling elements
rule.actions.remove(item)
if subhandling.value not in valid_values:
subhandling.value = valid_values[0]
pres_rules = self.pres_rules.content
oma_grantedcontacts_ref = omapolicy.ExternalList([self.resource_lists.url + '/~~' + resource_lists.get_xpath(oma_grantedcontacts)])
oma_blockedcontacts_ref = omapolicy.ExternalList([self.resource_lists.url + '/~~' + resource_lists.get_xpath(oma_blockedcontacts)])
try:
wp_prs_grantedcontacts = pres_rules['wp_prs_grantedcontacts']
except KeyError:
wp_prs_grantedcontacts = commonpolicy.Rule('wp_prs_grantedcontacts', conditions=[oma_grantedcontacts_ref], actions=[presrules.SubHandling('allow')])
pres_rules.add(wp_prs_grantedcontacts)
else:
fix_subhandling(wp_prs_grantedcontacts, valid_values=['allow'])
if list(wp_prs_grantedcontacts.conditions) != [oma_grantedcontacts_ref]:
wp_prs_grantedcontacts.conditions = [oma_grantedcontacts_ref]
if wp_prs_grantedcontacts.transformations:
wp_prs_grantedcontacts.transformations = None
try:
wp_prs_blockedcontacts = pres_rules['wp_prs_blockedcontacts']
except KeyError:
wp_prs_blockedcontacts = commonpolicy.Rule('wp_prs_blockedcontacts', conditions=[oma_blockedcontacts_ref], actions=[presrules.SubHandling('polite-block')])
pres_rules.add(wp_prs_blockedcontacts)
else:
fix_subhandling(wp_prs_blockedcontacts, valid_values=['polite-block'])
if list(wp_prs_blockedcontacts.conditions) != [oma_blockedcontacts_ref]:
wp_prs_blockedcontacts.conditions = [oma_blockedcontacts_ref]
if wp_prs_blockedcontacts.transformations:
wp_prs_blockedcontacts.transformations = None
wp_prs_unlisted = pres_rules.get('wp_prs_unlisted', None)
wp_prs_allow_unlisted = pres_rules.get('wp_prs_allow_unlisted', None)
if wp_prs_unlisted is not None and wp_prs_allow_unlisted is not None:
pres_rules.remove(wp_prs_allow_unlisted)
wp_prs_allow_unlisted = None
wp_prs_unlisted_rule = wp_prs_unlisted or wp_prs_allow_unlisted
if wp_prs_unlisted_rule is None:
wp_prs_unlisted = commonpolicy.Rule('wp_prs_unlisted', conditions=[omapolicy.OtherIdentity()], actions=[presrules.SubHandling('confirm')])
pres_rules.add(wp_prs_unlisted)
wp_prs_unlisted_rule = wp_prs_unlisted
else:
if wp_prs_unlisted_rule is wp_prs_unlisted:
fix_subhandling(wp_prs_unlisted_rule, valid_values=['confirm', 'block', 'polite-block'])
else:
fix_subhandling(wp_prs_unlisted_rule, valid_values=['allow'])
if list(wp_prs_unlisted_rule.conditions) != [omapolicy.OtherIdentity()]:
wp_prs_unlisted_rule.conditions = [omapolicy.OtherIdentity()]
if wp_prs_unlisted_rule.transformations:
wp_prs_unlisted_rule.transformations = None
match_anonymous = omapolicy.AnonymousRequest()
try:
wp_prs_block_anonymous = pres_rules['wp_prs_block_anonymous']
except KeyError:
wp_prs_block_anonymous = commonpolicy.Rule('wp_prs_block_anonymous', conditions=[match_anonymous], actions=[presrules.SubHandling('block')])
pres_rules.add(wp_prs_block_anonymous)
else:
fix_subhandling(wp_prs_block_anonymous, valid_values=['block', 'polite-block'])
if list(wp_prs_block_anonymous.conditions) != [match_anonymous]:
wp_prs_block_anonymous.conditions = [match_anonymous]
if wp_prs_block_anonymous.transformations:
wp_prs_block_anonymous.transformations = None
match_self = commonpolicy.Identity([commonpolicy.IdentityOne('sip:' + self.account.id)])
try:
wp_prs_allow_own = pres_rules['wp_prs_allow_own']
except KeyError:
wp_prs_allow_own = commonpolicy.Rule('wp_prs_allow_own', conditions=[match_self], actions=[presrules.SubHandling('allow')])
pres_rules.add(wp_prs_allow_own)
else:
fix_subhandling(wp_prs_allow_own, valid_values=['allow'])
if list(wp_prs_allow_own.conditions) != [match_self]:
wp_prs_allow_own.conditions = [match_self]
if wp_prs_allow_own.transformations:
wp_prs_allow_own.transformations = None
# Remove any other rules
all_rule_names = set(pres_rules[IterateIDs])
known_rule_names = {'wp_prs_grantedcontacts', 'wp_prs_blockedcontacts', 'wp_prs_unlisted', 'wp_prs_allow_unlisted', 'wp_prs_block_anonymous', 'wp_prs_allow_own'}
for name in all_rule_names - known_rule_names:
del pres_rules[name]
del fix_subhandling
# Normalize dialog-rules
#
if self.dialog_rules.supported:
if self.dialog_rules.content is None:
self.dialog_rules.content = dialogrules.DialogRules()
elif self.dialog_rules.content.element.nsmap.get('dr') != dialogrules.namespace: # TODO: this elif branch should be removed in a later version as it is
self.dialog_rules.content = dialogrules.DialogRules() # only used to discard documents created with the old namespace. -Dan
def fix_subhandling(rule, valid_values=()):
subhandling_elements = sorted((item for item in rule.actions if isinstance(item, dialogrules.SubHandling)), key=attrgetter('value.priority'))
if not subhandling_elements:
subhandling_elements = [dialogrules.SubHandling('block')] # spec specifies that missing SubHandling means block
rule.actions.update(subhandling_elements)
subhandling = subhandling_elements.pop()
for item in subhandling_elements: # remove any extraneous SubHandling elements
rule.actions.remove(item)
if subhandling.value not in valid_values:
subhandling.value = valid_values[0]
dialog_rules = self.dialog_rules.content
dialog_grantedcontacts_ref = omapolicy.ExternalList([self.resource_lists.url + '/~~' + resource_lists.get_xpath(dialog_grantedcontacts)])
dialog_blockedcontacts_ref = omapolicy.ExternalList([self.resource_lists.url + '/~~' + resource_lists.get_xpath(dialog_blockedcontacts)])
try:
wp_dlg_grantedcontacts = dialog_rules['wp_dlg_grantedcontacts']
except KeyError:
wp_dlg_grantedcontacts = commonpolicy.Rule('wp_dlg_grantedcontacts', conditions=[dialog_grantedcontacts_ref], actions=[dialogrules.SubHandling('allow')])
dialog_rules.add(wp_dlg_grantedcontacts)
else:
fix_subhandling(wp_dlg_grantedcontacts, valid_values=['allow'])
if list(wp_dlg_grantedcontacts.conditions) != [dialog_grantedcontacts_ref]:
wp_dlg_grantedcontacts.conditions = [dialog_grantedcontacts_ref]
if wp_dlg_grantedcontacts.transformations:
wp_dlg_grantedcontacts.transformations = None
try:
wp_dlg_blockedcontacts = dialog_rules['wp_dlg_blockedcontacts']
except KeyError:
wp_dlg_blockedcontacts = commonpolicy.Rule('wp_dlg_blockedcontacts', conditions=[dialog_blockedcontacts_ref], actions=[dialogrules.SubHandling('polite-block')])
dialog_rules.add(wp_dlg_blockedcontacts)
else:
fix_subhandling(wp_dlg_blockedcontacts, valid_values=['polite-block'])
if list(wp_dlg_blockedcontacts.conditions) != [dialog_blockedcontacts_ref]:
wp_dlg_blockedcontacts.conditions = [dialog_blockedcontacts_ref]
if wp_dlg_blockedcontacts.transformations:
wp_dlg_blockedcontacts.transformations = None
wp_dlg_unlisted = dialog_rules.get('wp_dlg_unlisted', None)
wp_dlg_allow_unlisted = dialog_rules.get('wp_dlg_allow_unlisted', None)
if wp_dlg_unlisted is not None and wp_dlg_allow_unlisted is not None:
dialog_rules.remove(wp_dlg_allow_unlisted)
wp_dlg_allow_unlisted = None
wp_dlg_unlisted_rule = wp_dlg_unlisted or wp_dlg_allow_unlisted
if wp_dlg_unlisted_rule is None:
wp_dlg_unlisted = commonpolicy.Rule('wp_dlg_unlisted', conditions=[omapolicy.OtherIdentity()], actions=[dialogrules.SubHandling('confirm')])
dialog_rules.add(wp_dlg_unlisted)
wp_dlg_unlisted_rule = wp_dlg_unlisted
else:
if wp_dlg_unlisted_rule is wp_dlg_unlisted:
fix_subhandling(wp_dlg_unlisted_rule, valid_values=['confirm', 'block', 'polite-block'])
else:
fix_subhandling(wp_dlg_unlisted_rule, valid_values=['allow'])
if list(wp_dlg_unlisted_rule.conditions) != [omapolicy.OtherIdentity()]:
wp_dlg_unlisted_rule.conditions = [omapolicy.OtherIdentity()]
if wp_dlg_unlisted_rule.transformations:
wp_dlg_unlisted_rule.transformations = None
match_anonymous = omapolicy.AnonymousRequest()
try:
wp_dlg_block_anonymous = dialog_rules['wp_dlg_block_anonymous']
except KeyError:
wp_dlg_block_anonymous = commonpolicy.Rule('wp_dlg_block_anonymous', conditions=[match_anonymous], actions=[dialogrules.SubHandling('block')])
dialog_rules.add(wp_dlg_block_anonymous)
else:
fix_subhandling(wp_dlg_block_anonymous, valid_values=['block', 'polite-block'])
if list(wp_dlg_block_anonymous.conditions) != [match_anonymous]:
wp_dlg_block_anonymous.conditions = [match_anonymous]
if wp_dlg_block_anonymous.transformations:
wp_dlg_block_anonymous.transformations = None
match_self = commonpolicy.Identity([commonpolicy.IdentityOne('sip:' + self.account.id)])
try:
wp_dlg_allow_own = dialog_rules['wp_dlg_allow_own']
except KeyError:
wp_dlg_allow_own = commonpolicy.Rule('wp_dlg_allow_own', conditions=[match_self], actions=[dialogrules.SubHandling('allow')])
dialog_rules.add(wp_dlg_allow_own)
else:
fix_subhandling(wp_dlg_allow_own, valid_values=['allow'])
if list(wp_dlg_allow_own.conditions) != [match_self]:
wp_dlg_allow_own.conditions = [match_self]
if wp_dlg_allow_own.transformations:
wp_dlg_allow_own.transformations = None
# Remove any other rules
all_rule_names = set(dialog_rules[IterateIDs])
known_rule_names = {'wp_dlg_grantedcontacts', 'wp_dlg_blockedcontacts', 'wp_dlg_unlisted', 'wp_dlg_allow_unlisted', 'wp_dlg_block_anonymous', 'wp_dlg_allow_own'}
for name in all_rule_names - known_rule_names:
del dialog_rules[name]
# Normalize status icon
#
if self.status_icon.supported and self.status_icon.content is not None:
content = self.status_icon.content
if None in (content.encoding, content.mime_type) or content.encoding.value.lower() != 'base64' or content.mime_type.value.lower() not in Icon.__mimetypes__:
self.status_icon.content = None
self.status_icon.dirty = True
def _OH_AddContactOperation(self, operation):
sipsimple_addressbook = self.resource_lists.content['sipsimple_addressbook']
contact = operation.contact
presence_handling = addressbook.PresenceHandling(contact.presence.policy, contact.presence.subscribe)
dialog_handling = addressbook.DialogHandling(contact.dialog.policy, contact.dialog.subscribe)
xml_contact = addressbook.Contact(contact.id, contact.name, presence_handling=presence_handling, dialog_handling=dialog_handling)
for uri in contact.uris:
contact_uri = addressbook.ContactURI(uri.id, uri.uri, uri.type)
contact_uri.attributes = addressbook.ContactURI.attributes.type(uri.attributes)
xml_contact.uris.add(contact_uri)
xml_contact.uris.default = contact.uris.default
xml_contact.attributes = addressbook.Contact.attributes.type(contact.attributes)
sipsimple_addressbook.add(xml_contact)
def _OH_UpdateContactOperation(self, operation):
sipsimple_addressbook = self.resource_lists.content['sipsimple_addressbook']
try:
contact = sipsimple_addressbook[addressbook.Contact, operation.contact.id]
except KeyError:
return
attributes = dict(operation.attributes)
attributes.pop('id', None) # id is never modified
attributes.pop('uris', None) # uris are modified using dedicated methods
if 'name' in attributes:
contact.name = attributes.pop('name')
if 'uris.default' in attributes:
contact.uris.default = attributes.pop('uris.default')
if 'presence.policy' in attributes:
contact.presence.policy = attributes.pop('presence.policy')
if 'presence.subscribe' in attributes:
contact.presence.subscribe = attributes.pop('presence.subscribe')
if 'dialog.policy' in attributes:
contact.dialog.policy = attributes.pop('dialog.policy')
if 'dialog.subscribe' in attributes:
contact.dialog.subscribe = attributes.pop('dialog.subscribe')
if contact.attributes is None:
contact.attributes = addressbook.Contact.attributes.type()
contact.attributes.update(attributes)
def _OH_RemoveContactOperation(self, operation):
sipsimple_addressbook = self.resource_lists.content['sipsimple_addressbook']
for group in (group for group in sipsimple_addressbook[addressbook.Group, IterateItems] if operation.contact.id in group.contacts):
group.contacts.remove(operation.contact.id)
try:
del sipsimple_addressbook[addressbook.Contact, operation.contact.id]
except KeyError:
pass
def _OH_AddContactURIOperation(self, operation):
sipsimple_addressbook = self.resource_lists.content['sipsimple_addressbook']
try:
contact = sipsimple_addressbook[addressbook.Contact, operation.contact.id]
except KeyError:
return
uri = addressbook.ContactURI(operation.uri.id, operation.uri.uri, operation.uri.type)
uri.attributes = addressbook.ContactURI.attributes.type(operation.uri.attributes)
contact.uris.add(uri)
def _OH_UpdateContactURIOperation(self, operation):
sipsimple_addressbook = self.resource_lists.content['sipsimple_addressbook']
try:
contact = sipsimple_addressbook[addressbook.Contact, operation.contact.id]
uri = contact.uris[operation.uri.id]
except KeyError:
return
attributes = dict(operation.attributes)
attributes.pop('id', None) # id is never modified
if 'uri' in attributes:
uri.uri = attributes.pop('uri')
if 'type' in attributes:
uri.type = attributes.pop('type')
if uri.attributes is None:
uri.attributes = addressbook.ContactURI.attributes.type()
uri.attributes.update(attributes)
def _OH_RemoveContactURIOperation(self, operation):
sipsimple_addressbook = self.resource_lists.content['sipsimple_addressbook']
try:
contact = sipsimple_addressbook[addressbook.Contact, operation.contact.id]
del contact.uris[operation.uri.id]
except KeyError:
pass
def _OH_AddGroupOperation(self, operation):
sipsimple_addressbook = self.resource_lists.content['sipsimple_addressbook']
group = addressbook.Group(operation.group.id, operation.group.name, [contact.id for contact in operation.group.contacts])
group.attributes = addressbook.Group.attributes.type(operation.group.attributes)
sipsimple_addressbook.add(group)
def _OH_UpdateGroupOperation(self, operation):
sipsimple_addressbook = self.resource_lists.content['sipsimple_addressbook']
try:
group = sipsimple_addressbook[addressbook.Group, operation.group.id]
except KeyError:
return
attributes = dict(operation.attributes)
attributes.pop('id', None) # id is never modified
attributes.pop('contacts', None) # contacts are added/removed using dedicated methods
if 'name' in attributes:
group.name = attributes.pop('name')
if group.attributes is None:
group.attributes = addressbook.Group.attributes.type()
group.attributes.update(attributes)
def _OH_RemoveGroupOperation(self, operation):
sipsimple_addressbook = self.resource_lists.content['sipsimple_addressbook']
try:
del sipsimple_addressbook[addressbook.Group, operation.group.id]
except KeyError:
pass
def _OH_AddGroupMemberOperation(self, operation):
sipsimple_addressbook = self.resource_lists.content['sipsimple_addressbook']
try:
group = sipsimple_addressbook[addressbook.Group, operation.group.id]
except KeyError:
return
if operation.contact.id in group.contacts:
return
group.contacts.add(operation.contact.id)
def _OH_RemoveGroupMemberOperation(self, operation):
sipsimple_addressbook = self.resource_lists.content['sipsimple_addressbook']
try:
group = sipsimple_addressbook[addressbook.Group, operation.group.id]
group.contacts.remove(operation.contact.id)
except KeyError:
return
def _OH_AddPolicyOperation(self, operation):
sipsimple_addressbook = self.resource_lists.content['sipsimple_addressbook']
presence_handling = addressbook.PresenceHandling(operation.policy.presence.policy, operation.policy.presence.subscribe)
dialog_handling = addressbook.DialogHandling(operation.policy.dialog.policy, operation.policy.dialog.subscribe)
policy = addressbook.Policy(operation.policy.id, operation.policy.uri, operation.policy.name, presence_handling=presence_handling, dialog_handling=dialog_handling)
policy.attributes = addressbook.Policy.attributes.type(operation.policy.attributes)
sipsimple_addressbook.add(policy)
def _OH_UpdatePolicyOperation(self, operation):
sipsimple_addressbook = self.resource_lists.content['sipsimple_addressbook']
try:
policy = sipsimple_addressbook[addressbook.Policy, operation.policy.id]
except KeyError:
return
attributes = dict(operation.attributes)
attributes.pop('id', None) # id is never modified
if 'uri' in attributes:
policy.uri = attributes.pop('uri')
if 'name' in attributes:
policy.name = attributes.pop('name')
if 'presence.policy' in attributes:
policy.presence.policy = attributes.pop('presence.policy')
if 'presence.subscribe' in attributes:
policy.presence.subscribe = attributes.pop('presence.subscribe')
if 'dialog.policy' in attributes:
policy.dialog.policy = attributes.pop('dialog.policy')
if 'dialog.subscribe' in attributes:
policy.dialog.subscribe = attributes.pop('dialog.subscribe')
if policy.attributes is None:
policy.attributes = addressbook.Policy.attributes.type()
policy.attributes.update(attributes)
def _OH_RemovePolicyOperation(self, operation):
sipsimple_addressbook = self.resource_lists.content['sipsimple_addressbook']
try:
del sipsimple_addressbook[addressbook.Policy, operation.policy.id]
except KeyError:
pass
def _OH_SetStatusIconOperation(self, operation):
if not self.status_icon.supported:
return
icon = operation.icon
if icon is None or not icon.data:
self.status_icon.dirty = self.status_icon.content is not None
self.status_icon.content = None
else:
icon_bytes = icon.data
data = base64.b64encode(icon_bytes).decode('utf-8')
content = prescontent.PresenceContent(data=data, mime_type=icon.mime_type, encoding='base64', description=icon.description)
if self.status_icon.content == content:
return
self.status_icon.content = content
def _OH_SetOfflineStatusOperation(self, operation):
pidf = operation.status.pidf if operation.status is not None else None
if not self.pidf_manipulation.supported or pidf == self.pidf_manipulation.content:
return
self.pidf_manipulation.content = pidf
self.pidf_manipulation.dirty = True
def _OH_SetDefaultPresencePolicyOperation(self, operation):
pres_rules = self.pres_rules.content
if operation.policy == 'allow':
rule_id, other_rule_id = 'wp_prs_allow_unlisted', 'wp_prs_unlisted'
else:
rule_id, other_rule_id = 'wp_prs_unlisted', 'wp_prs_allow_unlisted'
try:
del pres_rules[other_rule_id]
except KeyError:
rule = pres_rules[rule_id]
subhandling = next(item for item in rule.actions if isinstance(item, presrules.SubHandling))
subhandling.value = operation.policy
else:
rule = commonpolicy.Rule(rule_id, conditions=[omapolicy.OtherIdentity()], actions=[presrules.SubHandling(operation.policy)])
pres_rules.add(rule)
def _OH_SetDefaultDialogPolicyOperation(self, operation):
if not self.dialog_rules.supported:
return
dialog_rules = self.dialog_rules.content
if operation.policy == 'allow':
rule_id, other_rule_id = 'wp_dlg_allow_unlisted', 'wp_dlg_unlisted'
else:
rule_id, other_rule_id = 'wp_dlg_unlisted', 'wp_dlg_allow_unlisted'
try:
del dialog_rules[other_rule_id]
except KeyError:
rule = dialog_rules[rule_id]
subhandling = next(item for item in rule.actions if isinstance(item, dialogrules.SubHandling))
subhandling.value = operation.policy
else:
rule = commonpolicy.Rule(rule_id, conditions=[omapolicy.OtherIdentity()], actions=[dialogrules.SubHandling(operation.policy)])
dialog_rules.add(rule)
# Notification handlers
#
@run_in_twisted_thread
def handle_notification(self, notification):
handler = getattr(self, '_NH_%s' % notification.name, Null)
handler(notification)
@run_in_green_thread
def _NH_CFGSettingsObjectDidChange(self, notification):
if {'__id__', 'xcap.xcap_root', 'auth.username', 'auth.password', 'sip.subscribe_interval', 'sip.transport_list'}.intersection(notification.data.modified):
self.command_channel.send(Command('reload', modified=notification.data.modified))
if 'enabled' in notification.data.modified:
return # global account activation is handled separately by the account itself
if self.account.enabled and 'xcap.enabled' in notification.data.modified:
if self.account.xcap.enabled:
self.start()
else:
self.stop()
+ if self.account.enabled and 'xcap_diff' in notification.data.modified:
+ if self.account.xcap.xcap_diff:
+ self.xcap_subscriber.start()
+ else:
+ self.xcap_subscriber.stop()
def _NH_CFGSettingsObjectWasDeleted(self, notification):
notification.center.remove_observer(self, sender=self.account, name='CFGSettingsObjectDidChange')
notification.center.remove_observer(self, sender=self.account, name='CFGSettingsObjectWasDeleted')
self.command_channel.send(Command('stop'))
self.command_channel.send(Command('cleanup'))
def _NH_XCAPSubscriptionDidStart(self, notification):
self.command_channel.send(Command('fetch', documents=set(self.document_names)))
def _NH_XCAPSubscriptionDidFail(self, notification):
self.command_channel.send(Command('fetch', documents=set(self.document_names)))
def _NH_XCAPSubscriptionGotNotify(self, notification):
if notification.data.content_type == xcapdiff.XCAPDiffDocument.content_type:
try:
xcap_diff = xcapdiff.XCAPDiffDocument.parse(notification.data.body.decode())
except ParserError as e:
import traceback
traceback.print_exc()
notification_center = NotificationCenter()
notification_data = NotificationData(root=self.xcap_root, documents=['all'])
notification_center.post_notification('XCAPDocumentsDidChange', sender=self, data=notification_data)
self.command_channel.send(Command('fetch', documents=set(self.document_names)))
else:
changed_etags = {}
for child in xcap_diff:
if isinstance(child, xcapdiff.Document):
try:
document = next(document for document in self.documents if document.application == child.selector.auid)
except StopIteration:
name = child.selector.auid
url = child.selector.auid
else:
name = document.name
url = document.url
changed_etags[name] = {'new_etag': child.new_etag,
'previous_etag': child.previous_etag,
'auid': child.selector.auid,
'url': url
}
applications = set(child.selector.auid for child in xcap_diff if isinstance(child, xcapdiff.Document))
documents = set(document.name for document in self.documents if document.application in applications and document.etag != changed_etags[name]['new_etag'])
if documents:
self.command_channel.send(Command('fetch', documents=documents))
notification_center = NotificationCenter()
notification_data = NotificationData(root=self.xcap_root, documents=documents, notified_etags=changed_etags)
notification_center.post_notification('XCAPDocumentsDidChange', sender=self, data=notification_data)
def _load_data(self):
addressbook = Addressbook.from_payload(self.resource_lists.content['sipsimple_addressbook'])
default_presence_rule = self.pres_rules.content.get('wp_prs_unlisted', None) or self.pres_rules.content.get('wp_prs_allow_unlisted', None)
if self.dialog_rules.supported:
default_dialog_rule = self.dialog_rules.content.get('wp_dlg_unlisted', None) or self.dialog_rules.content.get('wp_dlg_allow_unlisted', None)
else:
default_dialog_rule = None
presence_rules = PresenceRules.from_payload(default_presence_rule)
dialog_rules = DialogRules.from_payload(default_dialog_rule)
if self.status_icon.supported and self.status_icon.content:
status_icon = Icon.from_payload(self.status_icon.content)
if status_icon:
status_icon.url = self.status_icon.url
status_icon.etag = self.status_icon.etag
else:
status_icon = None
if self.pidf_manipulation.supported and self.pidf_manipulation.content:
offline_status = OfflineStatus(self.pidf_manipulation.content)
else:
offline_status = None
data=NotificationData(addressbook=addressbook, presence_rules=presence_rules, dialog_rules=dialog_rules, status_icon=status_icon, offline_status=offline_status)
NotificationCenter().post_notification('XCAPManagerDidReloadData', sender=self, data=data)
def _fetch_documents(self, documents):
workers = [Worker.spawn(document.fetch) for document in (doc for doc in self.documents if doc.name in documents and doc.supported)]
try:
while workers:
worker = workers.pop()
worker.wait()
finally:
for worker in workers:
worker.wait_ex()
def _save_journal(self):
try:
self.storage.save('journal', pickle.dumps(self.journal))
except XCAPStorageError:
pass
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
class XCAPTransaction(object):
def __init__(self, xcap_manager):
self.xcap_manager = xcap_manager
def __enter__(self):
self.xcap_manager.start_transaction()
return self
def __exit__(self, type, value, traceback):
self.xcap_manager.commit_transaction()

File Metadata

Mime Type
text/x-diff
Expires
Sat, Nov 23, 3:16 AM (13 h, 49 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3408644
Default Alt Text
(127 KB)

Event Timeline