presence.py 25.6 KB
Newer Older
1 2 3 4 5 6

import base64
import hashlib
import re
import socket
import uuid
Adrian Georgescu's avatar
Adrian Georgescu committed
7
import urllib
8

Dan Pascu's avatar
Dan Pascu committed
9 10
from PyQt5 import uic
from PyQt5.QtCore import Qt, QTimer
11 12 13 14

from application.notification import IObserver, NotificationCenter, NotificationData
from application.python import Null, limit
from datetime import datetime
15
from dateutil.tz import tzutc
16 17
from itertools import chain
from twisted.internet import reactor
18
from twisted.internet.error import ConnectionLost
19
from zope.interface import implementer
20 21 22

from sipsimple import addressbook
from sipsimple.account import AccountManager, BonjourAccount
23
from sipsimple.account.bonjour import BonjourPresenceState
24 25
from sipsimple.account.xcap import Icon, OfflineStatus
from sipsimple.configuration.settings import SIPSimpleSettings
26
from sipsimple.payloads import caps, pidf, prescontent, rpid
Dan Pascu's avatar
Dan Pascu committed
27
from sipsimple.payloads import cipid
28 29 30
from sipsimple.threading.green import run_in_green_thread
from sipsimple.util import ISOTimestamp

31
from blink.configuration.datatypes import IconDescriptor, FileURL, PresenceState
32 33 34 35
from blink.configuration.settings import BlinkSettings
from blink.resources import IconManager, Resources
from blink.util import run_in_gui_thread

Dan Pascu's avatar
Dan Pascu committed
36 37
del cipid  # this only needs to be imported to register its namespace and extensions

38

39 40 41
__all__ = ['PresenceManager', 'PendingWatcherDialog']


42
epoch = datetime.fromtimestamp(0, tzutc())
43

44

45 46 47
class BlinkPresenceState(object):
    def __init__(self, account):
        self.account = account
48

49 50 51
    @property
    def online_state(self):
        blink_settings = BlinkSettings()
52

53 54
        state = blink_settings.presence.current_state.state
        note = blink_settings.presence.current_state.note
55

56
        state = 'offline' if state == 'Invisible' else state.lower()
57

58 59
        if self.account is BonjourAccount():
            return BonjourPresenceState(state, note)
60

61 62 63 64
        try:
            hostname = socket.gethostname()
        except Exception:
            hostname = 'localhost'
Adrian Georgescu's avatar
Adrian Georgescu committed
65
        account_id = hashlib.md5(self.account.id.encode()).hexdigest()
66 67
        timestamp = ISOTimestamp.now()

68
        doc = pidf.PIDF(str(self.account.uri))
69

70 71
        person = pidf.Person('PID-%s' % account_id)
        person.timestamp = timestamp
72
        person.activities = rpid.Activities()
73 74
        person.activities.add(state)
        doc.add(person)
75

76 77 78 79 80 81 82
        if state == 'offline':
            service = pidf.Service('SID-%s' % account_id)
            service.status = 'closed'
            service.status.extended = state
            service.contact = str(self.account.uri)
            service.timestamp = timestamp
            service.capabilities = caps.ServiceCapabilities()
83 84
            service.display_name = self.account.display_name or None
            service.icon = "%s#blink-icon%s" % (self.account.xcap.icon.url, self.account.xcap.icon.etag) if self.account.xcap.icon is not None else None
85 86 87 88 89 90 91 92 93 94 95 96
            doc.add(service)
        else:
            settings = SIPSimpleSettings()
            instance_id = str(uuid.UUID(settings.instance_id))
            service = pidf.Service('SID-%s' % instance_id)
            service.status = 'open'
            service.status.extended = state
            service.contact = str(self.account.contact.public_gruu or self.account.uri)
            service.timestamp = timestamp
            service.capabilities = caps.ServiceCapabilities()
            service.capabilities.audio = True
            service.capabilities.text = False
97
            service.capabilities.message = True
98
            service.capabilities.file_transfer = True
Dan Pascu's avatar
Dan Pascu committed
99 100
            service.capabilities.screen_sharing_server = True
            service.capabilities.screen_sharing_client = True
101
            service.display_name = self.account.display_name or None
102
            service.icon = "%s#blink-icon%s" % (self.account.xcap.icon.url, self.account.xcap.icon.etag) if self.account.xcap.icon is not None else None
103 104 105 106 107 108 109 110 111 112 113 114
            service.device_info = pidf.DeviceInfo(instance_id, description=hostname, user_agent=settings.user_agent)
            service.device_info.time_offset = pidf.TimeOffset()
            # TODO: Add real user input data -Saul
            service.user_input = rpid.UserInput()
            service.user_input.idle_threshold = 600
            service.add(pidf.DeviceID(instance_id))
            if note:
                service.notes.add(note)
            doc.add(service)

            device = pidf.Device('DID-%s' % instance_id, device_id=pidf.DeviceID(instance_id))
            device.timestamp = timestamp
Adrian Georgescu's avatar
Adrian Georgescu committed
115
            device.notes.add('%s at %s' % (settings.user_agent, hostname))
116
            doc.add(device)
117 118 119

        return doc

120 121
    @property
    def offline_state(self):
122
        if self.account is BonjourAccount():
123 124
            return None

125 126
        blink_settings = BlinkSettings()

127
        account_id = hashlib.md5(self.account.id.encode()).hexdigest()
128 129
        timestamp = ISOTimestamp.now()

130
        doc = pidf.PIDF(str(self.account.uri))
131

132 133
        person = pidf.Person('PID-%s' % account_id)
        person.timestamp = timestamp
134
        person.activities = rpid.Activities()
135 136
        person.activities.add('offline')
        doc.add(person)
137

138 139 140 141 142
        service = pidf.Service('SID-%s' % account_id)
        service.status = 'closed'
        service.status.extended = 'offline'
        service.contact = str(self.account.uri)
        service.timestamp = timestamp
143
        service.capabilities = caps.ServiceCapabilities()
144 145
        service.display_name = self.account.display_name or None
        service.icon = "%s#blink-icon%s" % (self.account.xcap.icon.url, self.account.xcap.icon.etag) if self.account.xcap.icon is not None else None
146 147
        if blink_settings.presence.offline_note:
            service.notes.add(blink_settings.presence.offline_note)
148 149 150 151 152
        doc.add(service)

        return doc


153
@implementer(IObserver)
154
class PresencePublicationHandler(object):
155

156 157 158
    def __init__(self):
        self._should_set_offline_status = set()

159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
    def start(self):
        notification_center = NotificationCenter()
        notification_center.add_observer(self, name='SIPAccountWillActivate')
        notification_center.add_observer(self, name='SIPAccountWillDeactivate')
        notification_center.add_observer(self, name='SIPAccountDidDiscoverXCAPSupport')
        notification_center.add_observer(self, name='XCAPManagerDidReloadData')
        notification_center.add_observer(self, sender=BlinkSettings(), name='CFGSettingsObjectDidChange')

    def stop(self):
        notification_center = NotificationCenter()
        notification_center.remove_observer(self, name='SIPAccountWillActivate')
        notification_center.remove_observer(self, name='SIPAccountWillDeactivate')
        notification_center.remove_observer(self, name='SIPAccountDidDiscoverXCAPSupport')
        notification_center.remove_observer(self, name='XCAPManagerDidReloadData')
        notification_center.remove_observer(self, sender=BlinkSettings(), name='CFGSettingsObjectDidChange')
174 175 176 177 178 179

    def handle_notification(self, notification):
        handler = getattr(self, '_NH_%s' % notification.name, Null)
        handler(notification)

    def _NH_CFGSettingsObjectDidChange(self, notification):
Dan Pascu's avatar
Dan Pascu committed
180
        if notification.sender is BlinkSettings():
181
            account_manager = AccountManager()
182
            if 'presence.offline_note' in notification.data.modified:
183
                for account in (account for account in account_manager.get_accounts() if account.xcap.discovered):
184 185 186
                    state = BlinkPresenceState(account).offline_state
                    account.xcap_manager.set_offline_status(OfflineStatus(state) if state is not None else None)
            if 'presence.icon' in notification.data.modified:
187 188
                icon = IconManager().get('avatar')
                status_icon = Icon(icon.content, icon.content_type) if icon is not None else None
189
                for account in (account for account in account_manager.get_accounts() if account.xcap.discovered):
190
                    account.xcap_manager.set_status_icon(status_icon)
191
            if 'presence.current_state' in notification.data.modified:
192 193
                for account in (account for account in account_manager.get_accounts() if account.enabled and account.presence.enabled):
                    account.presence_state = BlinkPresenceState(account).online_state
194 195
        else:
            account = notification.sender
196
            if {'xcap.enabled', 'xcap.xcap_root'}.intersection(notification.data.modified):
197 198
                account.xcap.icon = None
                account.save()
199
            elif {'presence.enabled', 'display_name', 'xcap.icon'}.intersection(notification.data.modified) and account.presence.enabled:
200
                account.presence_state = BlinkPresenceState(account).online_state
201
                if account.xcap.discovered and (set(notification.data.modified) != {'xcap.icon'} or account.id in self._should_set_offline_status):
202 203
                    state = BlinkPresenceState(account).offline_state
                    account.xcap_manager.set_offline_status(OfflineStatus(state) if state is not None else None)
204 205
                if account.id in self._should_set_offline_status:  # do not use set.discard() here to avoid race conditions. it should only be removed if present.
                    self._should_set_offline_status.remove(account.id)
206 207

    def _NH_SIPAccountWillActivate(self, notification):
208 209
        account = notification.sender
        notification.center.add_observer(self, sender=account, name='CFGSettingsObjectDidChange')
210 211
        notification.center.add_observer(self, sender=account, name='SIPAccountGotSelfPresenceState')
        account.presence_state = BlinkPresenceState(account).online_state
212 213

    def _NH_SIPAccountWillDeactivate(self, notification):
214 215
        account = notification.sender
        notification.center.remove_observer(self, sender=account, name='CFGSettingsObjectDidChange')
216
        notification.center.remove_observer(self, sender=account, name='SIPAccountGotSelfPresenceState')
217 218 219 220 221 222

    def _NH_SIPAccountGotSelfPresenceState(self, notification):
        pidf_doc = notification.data.pidf
        services = [service for service in pidf_doc.services if service.status.extended is not None]
        if not services:
            return
Dan Pascu's avatar
Dan Pascu committed
223
        blink_settings = BlinkSettings()
224 225
        services.sort(key=lambda obj: obj.timestamp.value if obj.timestamp else epoch, reverse=True)
        service = services[0]
226
        if service.id in ('SID-%s' % uuid.UUID(SIPSimpleSettings().instance_id), 'SID-%s' % hashlib.md5(notification.sender.id.encode()).hexdigest()):
227 228
            # Our current state is the winning one
            return
Adrian Georgescu's avatar
Adrian Georgescu committed
229 230
        status = str(service.status.extended).title()
        note = None if not service.notes else str(list(service.notes)[0])
231 232 233 234
        if status == 'Offline':
            status = 'Invisible'
            note = None
        new_state = PresenceState(status, note)
Dan Pascu's avatar
Dan Pascu committed
235
        blink_settings.presence.current_state = new_state
236 237
        if new_state.note:
            try:
238
                next(state for state in blink_settings.presence.state_history if state == new_state)
239
            except StopIteration:
Dan Pascu's avatar
Dan Pascu committed
240
                blink_settings.presence.state_history = [new_state] + blink_settings.presence.state_history
241
            else:
242
                blink_settings.presence.state_history = [new_state] + [state for state in blink_settings.presence.state_history if state != new_state]
Dan Pascu's avatar
Dan Pascu committed
243
        blink_settings.save()
244

245 246
    def _NH_SIPAccountDidDiscoverXCAPSupport(self, notification):
        account = notification.sender
247 248 249
        icon = IconManager().get('avatar')
        if icon is not None:
            account.xcap_manager.set_status_icon(Icon(icon.content, icon.content_type))
250

251
    @run_in_gui_thread
252 253
    def _NH_XCAPManagerDidReloadData(self, notification):
        account = notification.sender.account
Dan Pascu's avatar
Dan Pascu committed
254
        blink_settings = BlinkSettings()
255
        icon_manager = IconManager()
256 257 258 259

        offline_status = notification.data.offline_status
        status_icon = notification.data.status_icon

Dan Pascu's avatar
Dan Pascu committed
260 261 262 263
        try:
            offline_note = next(note for service in offline_status.pidf.services for note in service.notes)
        except (AttributeError, StopIteration):
            offline_note = None
264

Dan Pascu's avatar
Dan Pascu committed
265 266
        blink_settings.presence.offline_note = offline_note
        blink_settings.save()
267

268 269 270 271 272 273 274
        try:
            offline_icon = next(service.icon for service in offline_status.pidf.services)
        except (AttributeError, StopIteration):
            offline_icon_hash = None
        else:
            offline_icon_hash = str(offline_icon).partition('#blink-icon')[2] or None

275
        if status_icon:
276
            icon_hash = hashlib.sha1(status_icon.data).hexdigest()
277
            icon_desc = IconDescriptor(status_icon.url, icon_hash)
278 279
            if not blink_settings.presence.icon or blink_settings.presence.icon.etag != icon_hash:
                icon = icon_manager.store_data('avatar', status_icon.data)
280
                blink_settings.presence.icon = IconDescriptor(FileURL(icon.filename), icon_hash) if icon is not None else None
281
                blink_settings.save()
282 283
            elif account.xcap.icon != icon_desc and icon_hash != offline_icon_hash:
                self._should_set_offline_status.add(account.id)
284
        else:
285 286 287 288 289
            if blink_settings.presence.icon is None is not account.xcap.icon:
                self._should_set_offline_status.add(account.id)
            elif blink_settings.presence.icon is account.xcap.icon is offline_status is None and account.xcap_manager.pidf_manipulation.supported:
                state = BlinkPresenceState(account).offline_state
                account.xcap_manager.set_offline_status(OfflineStatus(state) if state is not None else None)
290
            icon_desc = None
291 292 293
            icon_manager.remove('avatar')
            blink_settings.presence.icon = None
            blink_settings.save()
294 295 296

        account.xcap.icon = icon_desc
        account.save()
297 298


299 300 301 302 303 304 305 306
class ContactIcon(object):
    def __init__(self, data, descriptor):
        self.data = data
        self.descriptor = descriptor

    @classmethod
    def fetch(cls, url, etag=None, descriptor_etag=None):
        headers = {'If-None-Match': etag} if etag else {}
Adrian Georgescu's avatar
Adrian Georgescu committed
307
        req = urllib.request.Request(url, headers=headers)
308
        try:
Adrian Georgescu's avatar
Adrian Georgescu committed
309
            response = urllib.request.urlopen(req)
310 311
            content = response.read()
            info = response.info()
Adrian Georgescu's avatar
Adrian Georgescu committed
312
        except (ConnectionLost, urllib.error.URLError, urllib.error.HTTPError):
313
            return None
314 315
        content_type = info.get('content-type')
        etag = info.get('etag')
316 317 318 319 320 321
        if etag.startswith('W/'):
            etag = etag[2:]
        etag = etag.replace('\"', '')
        if content_type == prescontent.PresenceContentDocument.content_type:
            try:
                pres_content = prescontent.PresenceContentDocument.parse(content)
322
                data = base64.b64decode(pres_content.data.value)
323 324
            except Exception:
                return None
325 326 327
            return cls(data, IconDescriptor(url, descriptor_etag or etag))
        else:
            return None
328 329


330
@implementer(IObserver)
331 332
class PresenceSubscriptionHandler(object):

333 334
    sip_prefix_re = re.compile("^sips?:")

335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354
    def __init__(self):
        self._pidf_map = {}
        self._winfo_map = {}
        self._winfo_timers = {}

    def start(self):
        notification_center = NotificationCenter()
        notification_center.add_observer(self, name='SIPAccountWillActivate')
        notification_center.add_observer(self, name='SIPAccountWillDeactivate')
        notification_center.add_observer(self, name='SIPAccountGotPresenceState')
        notification_center.add_observer(self, name='SIPAccountGotPresenceWinfo')

    def stop(self):
        notification_center = NotificationCenter()
        notification_center.remove_observer(self, name='SIPAccountWillActivate')
        notification_center.remove_observer(self, name='SIPAccountWillDeactivate')
        notification_center.remove_observer(self, name='SIPAccountGotPresenceState')
        notification_center.remove_observer(self, name='SIPAccountGotPresenceWinfo')
        self._pidf_map.clear()
        self._winfo_map.clear()
Adrian Georgescu's avatar
Adrian Georgescu committed
355
        for timer in list(self._winfo_timers.values()):
356 357 358 359 360 361 362 363
            if timer.active():
                timer.cancel()
        self._winfo_timers.clear()

    @run_in_green_thread
    def _process_presence_data(self, uris=None):
        addressbook_manager = addressbook.AddressbookManager()

364 365 366
        def service_sort_key(service):
            timestamp = service.timestamp.value if service.timestamp else epoch
            if service.status.extended is not None:
367
                return 100, timestamp
368
            elif service.status.basic == 'open':
369
                return 10, timestamp
370
            else:
371
                return 0, timestamp
372

373 374 375 376 377
        current_pidf_map = {}
        contact_pidf_map = {}

        # If no URIs were provided, process all of them
        if not uris:
Adrian Georgescu's avatar
Adrian Georgescu committed
378
            uris = list(chain(*(iter(item.keys()) for item in self._pidf_map.values())))
379

Adrian Georgescu's avatar
Adrian Georgescu committed
380
        for uri, pidf_list in chain(*(iter(x.items()) for x in self._pidf_map.values())):
381 382 383 384
            current_pidf_map.setdefault(uri, []).extend(pidf_list)

        for uri in uris:
            pidf_list = current_pidf_map.get(uri, [])
385
            for contact in (contact for contact in addressbook_manager.get_contacts() if uri in (self.sip_prefix_re.sub('', contact_uri.uri) for contact_uri in contact.uris)):
386 387
                contact_pidf_map.setdefault(contact, []).extend(pidf_list)

Adrian Georgescu's avatar
Adrian Georgescu committed
388
        for contact, pidf_list in contact_pidf_map.items():
389
            if not pidf_list:
390
                state = note = icon = None
391 392
            else:
                services = list(chain(*(list(pidf_doc.services) for pidf_doc in pidf_list)))
393
                services.sort(key=service_sort_key, reverse=True)
394 395
                service = services[0]
                if service.status.extended:
Adrian Georgescu's avatar
Adrian Georgescu committed
396
                    state = str(service.status.extended)
397
                else:
398
                    state = 'available' if service.status.basic == 'open' else 'offline'
Adrian Georgescu's avatar
Adrian Georgescu committed
399 400
                note = str(next(iter(service.notes))) if service.notes else None
                icon_url = str(service.icon) if service.icon else None
401

402
                if icon_url:
Dan Pascu's avatar
Dan Pascu committed
403
                    url, token, icon_hash = icon_url.partition('#blink-icon')
404 405 406
                    if token:
                        if contact.icon and icon_hash == contact.icon.etag:
                            # Fast path, icon hasn't changed
407
                            icon = None
408 409
                        else:
                            # New icon, client uses fast path mechanism
410
                            icon = ContactIcon.fetch(icon_url, etag=None, descriptor_etag=icon_hash)
411
                    else:
412
                        icon = ContactIcon.fetch(icon_url, etag=contact.icon.etag if contact.icon else None)
413
                else:
414 415
                    icon = None
            self._update_presence_state(contact, state, note, icon)
416

417
    @run_in_gui_thread
418
    def _update_presence_state(self, contact, state, note, icon):
419
        icon_manager = IconManager()
420 421
        contact.presence.state = state
        contact.presence.note = note
422 423 424
        if icon is not None:
            icon_manager.store_data(contact.id, icon.data)
            contact.icon = icon.descriptor
425
        contact.save()
426 427 428 429 430 431 432 433 434 435 436 437 438

    def handle_notification(self, notification):
        handler = getattr(self, '_NH_%s' % notification.name, Null)
        handler(notification)

    def _NH_CFGSettingsObjectDidChange(self, notification):
        account = notification.sender
        if '__id__' in notification.data.modified:
            old_id = notification.data.modified['__id__'].old
            self._pidf_map.pop(old_id, None)
            self._winfo_map.pop(old_id, None)
            self._process_presence_data()
            return
439
        if {'enabled', 'presence.enabled'}.intersection(notification.data.modified):
440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458
            if not account.enabled or not account.presence.enabled:
                self._pidf_map.pop(account.id, None)
                self._winfo_map.pop(account.id, None)
                self._process_presence_data()

    def _NH_SIPAccountWillActivate(self, notification):
        if notification.sender is not BonjourAccount():
            notification.center.add_observer(self, sender=notification.sender, name='CFGSettingsObjectDidChange')
            notification.center.add_observer(self, sender=notification.sender, name='SIPAccountGotPresenceState')
            notification.center.add_observer(self, sender=notification.sender, name='SIPAccountGotPresenceWinfo')

    def _NH_SIPAccountWillDeactivate(self, notification):
        if notification.sender is not BonjourAccount():
            notification.center.remove_observer(self, sender=notification.sender, name='CFGSettingsObjectDidChange')
            notification.center.remove_observer(self, sender=notification.sender, name='SIPAccountGotPresenceState')
            notification.center.remove_observer(self, sender=notification.sender, name='SIPAccountGotPresenceWinfo')

    def _NH_SIPAccountGotPresenceState(self, notification):
        account = notification.sender
Adrian Georgescu's avatar
Adrian Georgescu committed
459
        new_pidf_map = dict((self.sip_prefix_re.sub('', uri), resource.pidf_list) for uri, resource in notification.data.resource_map.items())
460
        account_map = self._pidf_map.setdefault(account.id, {})
461
        if notification.data.full_state:
462 463
            account_map.clear()
        account_map.update(new_pidf_map)
Adrian Georgescu's avatar
Adrian Georgescu committed
464
        self._process_presence_data(list(new_pidf_map.keys()))
465 466 467 468 469 470

    def _NH_SIPAccountGotPresenceWinfo(self, notification):
        addressbook_manager = addressbook.AddressbookManager()
        account = notification.sender
        watcher_list = notification.data.watcher_list

471
        self._winfo_map.setdefault(account.id, {})
472
        if notification.data.state == 'full':
473 474
            self._winfo_map[account.id].clear()

475
        for watcher in watcher_list:
476
            uri = self.sip_prefix_re.sub('', watcher.sipuri)
477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513
            if uri != account.id:
                # Skip own URI, XCAP may be down and policy may not be inplace yet
                self._winfo_map[account.id].setdefault(watcher.status, set()).add(uri)

        pending_watchers = self._winfo_map[account.id].setdefault('pending', set()) | self._winfo_map[account.id].setdefault('waiting', set())
        for uri in pending_watchers:
            # check if there is a policy
            try:
                next(policy for policy in addressbook_manager.get_policies() if policy.uri == uri and policy.presence.policy != 'default')
            except StopIteration:
                # check if there is a contact
                try:
                    next(contact for contact in addressbook_manager.get_contacts() if contact.presence.policy != 'default' and uri in (addr.uri for addr in contact.uris))
                except StopIteration:
                    # TODO: add display name -Saul
                    if uri not in self._winfo_timers:
                        self._winfo_timers[uri] = reactor.callLater(600, self._winfo_timers.pop, uri, None)
                        notification.center.post_notification('SIPAccountGotPendingWatcher', sender=account, data=NotificationData(uri=uri, display_name=None, event='presence'))


class PresenceManager(object):

    def __init__(self):
        self.publication_handler = PresencePublicationHandler()
        self.subscription_handler = PresenceSubscriptionHandler()

    def start(self):
        self.publication_handler.start()
        self.subscription_handler.start()

    def stop(self):
        self.publication_handler.stop()
        self.subscription_handler.stop()


ui_class, base_class = uic.loadUiType(Resources.get('pending_watcher.ui'))

514

515 516 517
class PendingWatcherDialog(base_class, ui_class):
    def __init__(self, account, uri, display_name, parent=None):
        super(PendingWatcherDialog, self).__init__(parent)
518
        self.setWindowFlags(self.windowFlags() | Qt.WindowStaysOnTopHint)
519 520 521
        self.setAttribute(Qt.WA_DeleteOnClose)
        with Resources.directory:
            self.setupUi(self)
522 523 524 525 526
        default_font_size = self.uri_label.fontInfo().pointSizeF()
        name_font_size = limit(default_font_size + 3, max=14)
        font = self.name_label.font()
        font.setPointSizeF(name_font_size)
        self.name_label.setFont(font)
527 528 529 530 531 532 533
        addressbook_manager = addressbook.AddressbookManager()
        try:
            self.contact = next(contact for contact in addressbook_manager.get_contacts() if uri in (addr.uri for addr in contact.uris))
        except StopIteration:
            self.contact = None
        else:
            display_name = self.contact.name
Dan Pascu's avatar
Dan Pascu committed
534 535 536
            icon_manager = IconManager()
            icon = icon_manager.get(self.contact.id)
            if icon is not None:
537
                self.user_icon.setPixmap(icon.pixmap(48))
Adrian Georgescu's avatar
Adrian Georgescu committed
538
        self.description_label.setText('Wants to subscribe to your availability information at {}'.format(account.id))
539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572
        self.name_label.setText(display_name or uri)
        self.uri_label.setText(uri)
        self.accept_button.released.connect(self._accept_watcher)
        self.block_button.released.connect(self._block_watcher)
        self.position = None
        self.timer = QTimer()
        self.timer.timeout.connect(self._SH_TimerFired)
        self.timer.start(60000)

    def _SH_TimerFired(self):
        self.timer.stop()
        self.close()

    def _accept_watcher(self):
        self.timer.stop()
        if not self.contact:
            self.contact = addressbook.Contact()
            self.contact.name = self.name_label.text()
            self.contact.uris = [addressbook.ContactURI(uri=self.uri_label.text())]
        self.contact.presence.policy = 'allow'
        self.contact.presence.subscribe = True
        self.contact.save()

    def _block_watcher(self):
        self.timer.stop()
        policy = addressbook.Policy()
        policy.uri = self.uri_label.text()
        policy.name = self.name_label.text()
        policy.presence.policy = 'block'
        policy.save()

del ui_class, base_class