~ubuntu-branches/ubuntu/precise/pymsn/precise

« back to all changes in this revision

Viewing changes to pymsn/profile.py

  • Committer: Bazaar Package Importer
  • Author(s): Laurent Bigonville, Sjoerd Simons, Laurent Bigonville, Jonny Lamb
  • Date: 2008-01-17 18:23:14 UTC
  • mfrom: (1.1.2 upstream)
  • Revision ID: james.westby@ubuntu.com-20080117182314-lwymmpnk2ut3rvr1
Tags: 0.3.1-0ubuntu1
[ Sjoerd Simons ]
* debian/rules: remove dh_python, it's no longer needed

[ Laurent Bigonville ]
* New upstream release (0.3.1)
* debian/control:
  - Add myself as an Uploaders
  - Add python:Provides for binary package
  - Add python-ctypes and python-crypto to build-deps/deps
* debian/rules: remove binary-install rule
* Add watch file
* remove pycompat file, not needed anymore
* Modify Maintainer value to match the DebianMaintainerField
  specification.

[ Jonny Lamb ]
* Added python-adns to build-deps/deps.
* Added python-pyopenssl to build-deps/deps.
* Updated copyright.
* Upped Standards-Version to 3.7.3.
* Added "XS-Dm-Upload-Allowed: yes" under the request of Sjoerd Simons.
* Added myself to Uploaders.
* Added Homepage to control.
* Added Vcs-Bzr to control.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- coding: utf-8 -*-
 
2
#
 
3
# pymsn - a python client library for Msn
 
4
#
 
5
# Copyright (C) 2005-2006 Ali Sabil <ali.sabil@gmail.com>
 
6
# Copyright (C) 2007-2008 Johann Prieur <johann.prieur@gmail.com>
 
7
#
 
8
# This program is free software; you can redistribute it and/or modify
 
9
# it under the terms of the GNU General Public License as published by
 
10
# the Free Software Foundation; either version 2 of the License, or
 
11
# (at your option) any later version.
 
12
#
 
13
# This program is distributed in the hope that it will be useful,
 
14
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
15
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
16
# GNU General Public License for more details.
 
17
#
 
18
# You should have received a copy of the GNU General Public License
 
19
# along with this program; if not, write to the Free Software
 
20
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
21
 
 
22
"""Profile of the User connecting to the service, as well as the profile of
 
23
contacts in his/her contact list.
 
24
 
 
25
    @sort: Profile, Contact, Group, ClientCapabilities
 
26
    @group Enums: Presence, Membership, Privacy, NetworkID
 
27
    @sort: Presence, Membership, Privacy, NetworkID"""
 
28
 
 
29
from pymsn.util.decorator import rw_property
 
30
 
 
31
import gobject
 
32
 
 
33
__all__ = ['Profile', 'Contact', 'Group', 
 
34
        'Presence', 'Membership', 'ContactType', 'Privacy', 'NetworkID', 'ClientCapabilities']
 
35
 
 
36
 
 
37
class ClientCapabilities(object):
 
38
    """Capabilities of the client. This allow adverstising what the User Agent
 
39
    is capable of, for example being able to receive video stream, and being
 
40
    able to receive nudges...
 
41
    
 
42
        @ivar is_bot: is the client a bot
 
43
        @type is_bot: bool
 
44
 
 
45
        @ivar is_mobile_device: is the client running on a mobile device
 
46
        @type is_mobile_device: bool
 
47
 
 
48
        @ivar is_msn_mobile: is the client an MSN Mobile device
 
49
        @type is_msn_mobile: bool
 
50
 
 
51
        @ivar is_msn_direct_device: is the client an MSN Direct device
 
52
        @type is_msn_direct_device: bool
 
53
 
 
54
        @ivar is_media_center_user: is the client running on a Media Center
 
55
        @type is_media_center_user: bool
 
56
 
 
57
        @ivar is_msn8_user: is the client using WLM 8
 
58
        @type is_msn8_user: bool
 
59
 
 
60
        @ivar is_web_client: is the client web based
 
61
        @type is_web_client: bool
 
62
 
 
63
        @ivar is_tgw_client: is the client a gateway
 
64
        @type is_tgw_client: bool
 
65
 
 
66
        @ivar has_space: does the user has a space account
 
67
        @type has_space: bool
 
68
 
 
69
        @ivar has_webcam: does the user has a webcam plugged in
 
70
        @type has_webcam: bool
 
71
 
 
72
        @ivar has_onecare: does the user has the OneCare service
 
73
        @type has_onecare: bool
 
74
 
 
75
        @ivar renders_gif: can the client render gif (for ink)
 
76
        @type renders_gif: bool
 
77
 
 
78
        @ivar renders_isf: can the client render ISF (for ink)
 
79
        @type renders_isf: bool
 
80
 
 
81
        @ivar supports_chunking: does the client supports chunking messages
 
82
        @type supports_chunking: bool
 
83
 
 
84
        @ivar supports_direct_im: does the client supports direct IM
 
85
        @type supports_direct_im: bool
 
86
 
 
87
        @ivar supports_winks: does the client supports Winks
 
88
        @type supports_winks: bool
 
89
 
 
90
        @ivar supports_shared_search: does the client supports Shared Search
 
91
        @type supports_shared_search: bool
 
92
 
 
93
        @ivar supports_voice_im: does the client supports voice clips
 
94
        @type supports_voice_im: bool
 
95
 
 
96
        @ivar supports_secure_channel: does the client supports secure channels
 
97
        @type supports_secure_channel: bool
 
98
 
 
99
        @ivar supports_sip_invite: does the client supports SIP
 
100
        @type supports_sip_invite: bool
 
101
 
 
102
        @ivar supports_shared_drive: does the client supports File sharing
 
103
        @type supports_shared_drive: bool
 
104
 
 
105
        @ivar p2p_supports_turn: does the client supports TURN for p2p transfer
 
106
        @type p2p_supports_turn: bool
 
107
 
 
108
        @ivar p2p_bootstrap_via_uun: is the client able to use and understand UUN commands
 
109
        @type p2p_bootstrap_via_uun: bool
 
110
 
 
111
        @undocumented: __getattr__, __setattr__, __str__
 
112
        """
 
113
 
 
114
    _CAPABILITIES = {
 
115
            'is_bot': 0x00020000,
 
116
            'is_mobile_device': 0x00000001,
 
117
            'is_msn_mobile': 0x00000040,
 
118
            'is_msn_direct_device': 0x00000080,
 
119
 
 
120
            'is_media_center_user': 0x00002000,
 
121
            'is_msn8_user': 0x00000002,
 
122
 
 
123
            'is_web_client': 0x00000200,
 
124
            'is_tgw_client': 0x00000800,
 
125
 
 
126
            'has_space': 0x00001000,
 
127
            'has_webcam': 0x00000010,
 
128
            'has_onecare': 0x01000000,
 
129
 
 
130
            'renders_gif': 0x00000004,
 
131
            'renders_isf': 0x00000008,
 
132
 
 
133
            'supports_chunking': 0x00000020,
 
134
            'supports_direct_im': 0x00004000,
 
135
            'supports_winks': 0x00008000,
 
136
            'supports_shared_search': 0x00010000,
 
137
            'supports_voice_im': 0x00040000,
 
138
            'supports_secure_channel': 0x00080000,
 
139
            'supports_sip_invite': 0x00100000,
 
140
            'supports_shared_drive': 0x00400000,
 
141
 
 
142
            'p2p_supports_turn': 0x02000000,
 
143
            'p2p_bootstrap_via_uun': 0x04000000
 
144
            }
 
145
 
 
146
    def __init__(self, msnc=0, client_id=0):
 
147
        """Initializer
 
148
 
 
149
            @param msnc: The MSNC version
 
150
            @type msnc: integer < 8 and >= 0
 
151
 
 
152
            @param client_id: the full client ID"""
 
153
        MSNC = (0x0,        # MSNC0
 
154
                0x10000000, # MSNC1
 
155
                0x20000000, # MSNC2
 
156
                0x30000000, # MSNC3
 
157
                0x40000000, # MSNC4
 
158
                0x50000000, # MSNC5
 
159
                0x60000000, # MSNC6
 
160
                0x70000000) # MSNC7
 
161
        object.__setattr__(self, 'client_id', MSNC[msnc] | client_id)
 
162
 
 
163
    def __getattr__(self, name):
 
164
        if name == "p2p_aware":
 
165
            mask = 0xf0000000
 
166
        elif name in self._CAPABILITIES:
 
167
            mask = self._CAPABILITIES[name]
 
168
        else:
 
169
            raise AttributeError("object 'ClientCapabilities' has no attribute '%s'" % name)
 
170
        return (self.client_id & mask != 0)
 
171
 
 
172
    def __setattr__(self, name, value):
 
173
        if name in self._CAPABILITIES:
 
174
            mask = self._CAPABILITIES[name]
 
175
            if value:
 
176
                object.__setattr__(self, 'client_id', self.client_id | mask)
 
177
            else:
 
178
                object.__setattr__(self, 'client_id', self.client_id ^ mask)
 
179
        else:
 
180
            raise AttributeError("object 'ClientCapabilities' has no attribute '%s'" % name)
 
181
 
 
182
    def __str__(self):
 
183
        return str(self.client_id)
 
184
 
 
185
 
 
186
class NetworkID(object):
 
187
    """Refers to the contact Network ID"""
 
188
 
 
189
    MSN = 1
 
190
    """Microsoft Network"""
 
191
 
 
192
    LCS = 2
 
193
    """Microsoft Live Communication Server"""
 
194
 
 
195
    MOBILE = 4
 
196
    """Mobile phones"""
 
197
 
 
198
    EXTERNAL = 32
 
199
    """External IM etwork, currently Yahoo!"""
 
200
 
 
201
 
 
202
class Presence(object):
 
203
    """Presence states.
 
204
 
 
205
    The members of this class are used to identify the Presence that a user
 
206
    wants to advertise to the contacts on his/her contact list.
 
207
 
 
208
        @cvar ONLINE: online
 
209
        @cvar BUSY: busy
 
210
        @cvar IDLE: idle
 
211
        @cvar AWAY: away
 
212
        @cvar BE_RIGHT_BACK: be right back
 
213
        @cvar ON_THE_PHONE: on the phone
 
214
        @cvar OUT_TO_LUNCH: out to lunch
 
215
        @cvar INVISIBLE: status hidden from contacts
 
216
        @cvar OFFLINE: offline"""
 
217
    ONLINE = 'NLN'
 
218
    BUSY = 'BSY'
 
219
    IDLE = 'IDL'
 
220
    AWAY = 'AWY'
 
221
    BE_RIGHT_BACK = 'BRB'
 
222
    ON_THE_PHONE = 'PHN'
 
223
    OUT_TO_LUNCH = 'LUN'
 
224
    INVISIBLE = 'HDN'
 
225
    OFFLINE = 'FLN'
 
226
 
 
227
 
 
228
class Privacy(object):
 
229
    """User privacy, defines the default policy concerning contacts not
 
230
    belonging to the ALLOW list nor to the BLOCK list.
 
231
 
 
232
        @cvar ALLOW: allow by default
 
233
        @cvar BLOCK: block by default"""
 
234
    ALLOW = 'AL'
 
235
    BLOCK = 'BL'
 
236
 
 
237
 
 
238
class Membership(object):
 
239
    """Contact Membership"""
 
240
 
 
241
    NONE = 0
 
242
    """Contact doesn't belong to the contact list, but belongs to the address book"""
 
243
 
 
244
    FORWARD = 1
 
245
    """Contact belongs to our contact list"""
 
246
 
 
247
    ALLOW   = 2
 
248
    """Contact is explicitely allowed to see our presence regardless of the
 
249
    currently set L{Privacy<pymsn.profile.Privacy>}"""
 
250
 
 
251
    BLOCK   = 4
 
252
    """Contact is explicitely forbidden from seeing our presence regardless of
 
253
    the currently set L{Privacy<pymsn.profile.Privacy>}"""
 
254
 
 
255
    REVERSE = 8
 
256
    """We belong to the FORWARD list of the contact"""
 
257
 
 
258
    PENDING = 16
 
259
    """Contact pending"""
 
260
 
 
261
 
 
262
class ContactType(object):
 
263
    """Automatic update status flag"""
 
264
 
 
265
    ME = "Me"
 
266
    """Contact is the user so there's no automatic update relationship"""
 
267
 
 
268
    EXTERNAL = "Messenger2"
 
269
    """Contact is part of an external messenger service so there's no automatic
 
270
    update relationship with the user"""
 
271
 
 
272
    REGULAR = "Regular"
 
273
    """Contact has no automatic update relationship with the user"""
 
274
 
 
275
    LIVE = "Live"
 
276
    """Contact has an automatic update relationship with the user and an 
 
277
    automatic update already occured"""
 
278
 
 
279
    LIVE_PENDING = "LivePending"
 
280
    """Contact was requested automatic update from the user and didn't
 
281
    give its authorization yet"""
 
282
 
 
283
    LIVE_REJECTED = "LiveRejected"
 
284
    """Contact was requested automatic update from the user and rejected
 
285
    the request"""
 
286
 
 
287
    LIVE_DROPPED = "LiveDropped"
 
288
    """Contact had an automatic update relationship with the user but
 
289
    the contact dropped it"""
 
290
 
 
291
 
 
292
class Profile(gobject.GObject):
 
293
    """Profile of the User connecting to the service
 
294
 
 
295
        @undocumented: __gsignals__, __gproperties__, do_get_property"""
 
296
 
 
297
    __gproperties__ = {
 
298
            "display-name": (gobject.TYPE_STRING,
 
299
                "Friendly name",
 
300
                "A nickname that the user chooses to display to others",
 
301
                "",
 
302
                gobject.PARAM_READABLE),
 
303
 
 
304
            "personal-message": (gobject.TYPE_STRING,
 
305
                "Personal message",
 
306
                "The personal message that the user wants to display",
 
307
                "",
 
308
                gobject.PARAM_READABLE),
 
309
 
 
310
            "current-media": (gobject.TYPE_PYOBJECT,
 
311
                "Current media",
 
312
                "The current media that the user wants to display",
 
313
                gobject.PARAM_READABLE),
 
314
 
 
315
            "profile": (gobject.TYPE_STRING,
 
316
                "Profile",
 
317
                "the text/x-msmsgsprofile sent by the server",
 
318
                "",
 
319
                gobject.PARAM_READABLE),
 
320
 
 
321
            "presence": (gobject.TYPE_STRING,
 
322
                "Presence",
 
323
                "The presence to show to others",
 
324
                Presence.OFFLINE,
 
325
                gobject.PARAM_READABLE),
 
326
 
 
327
            "privacy": (gobject.TYPE_STRING,
 
328
                "Privacy",
 
329
                "The privacy policy to use",
 
330
                Privacy.BLOCK,
 
331
                gobject.PARAM_READABLE),
 
332
 
 
333
            "msn-object": (gobject.TYPE_STRING,
 
334
                "MSN Object",
 
335
                "MSN Object attached to the user, this generally represent "
 
336
                "its display picture",
 
337
                "",
 
338
                gobject.PARAM_READABLE),
 
339
            }
 
340
 
 
341
    def __init__(self, account, ns_client):
 
342
        gobject.GObject.__init__(self)
 
343
        self._ns_client = ns_client
 
344
        self._account = account[0]
 
345
        self._password = account[1]
 
346
 
 
347
        self._profile = ""
 
348
        self._display_name = self._account.split("@", 1)[0]
 
349
        self._presence = Presence.OFFLINE
 
350
        self._privacy = Privacy.BLOCK
 
351
        self._personal_message = ""
 
352
        self._current_media = None
 
353
 
 
354
        self.client_id = ClientCapabilities(7)
 
355
        #self.client_id.supports_sip_invite = True
 
356
        #FIXME: this should only be advertised when a webcam is plugged
 
357
        #self.client_id.has_webcam = True
 
358
 
 
359
        self._msn_object = None
 
360
 
 
361
        self.__pending_set_presence = [self._presence, self.client_id, self._msn_object]
 
362
        self.__pending_set_personal_message = [self._personal_message, self._current_media]
 
363
 
 
364
    @property
 
365
    def account(self):
 
366
        """The user account
 
367
            @type: utf-8 encoded string"""
 
368
        return self._account
 
369
 
 
370
    @property
 
371
    def password(self):
 
372
        """The user password
 
373
            @type: utf-8 encoded string"""
 
374
        return self._password
 
375
 
 
376
    @property
 
377
    def profile(self):
 
378
        """The user profile retrieved from the MSN servers
 
379
            @type: utf-8 encoded string"""
 
380
        return self._profile
 
381
 
 
382
    @property
 
383
    def id(self):
 
384
        """The user identifier in a GUID form
 
385
            @type: GUID string"""
 
386
        return "00000000-0000-0000-0000-000000000000"
 
387
 
 
388
    @rw_property
 
389
    def display_name():
 
390
        """The display name shown to you contacts
 
391
            @type: utf-8 encoded string"""
 
392
        def fset(self, display_name):
 
393
            if not display_name:
 
394
                return
 
395
            self._ns_client.set_display_name(display_name)
 
396
        def fget(self):
 
397
            return self._display_name
 
398
        return locals()
 
399
 
 
400
    @rw_property
 
401
    def presence():
 
402
        """The presence displayed to you contacts
 
403
            @type: L{Presence<pymsn.profile.Presence>}"""
 
404
        def fset(self, presence):
 
405
            if presence == self._presence:
 
406
                return
 
407
            self.__pending_set_presence[0] = presence
 
408
            self._ns_client.set_presence(*self.__pending_set_presence)
 
409
        def fget(self):
 
410
            return self._presence
 
411
        return locals()
 
412
 
 
413
    @rw_property
 
414
    def privacy():
 
415
        """The default privacy, can be either Privacy.ALLOW or Privacy.BLOCK
 
416
            @type: L{Privacy<pymsn.profile.Privacy>}"""
 
417
        def fset(self, privacy):
 
418
            pass #FIXME: set the privacy setting
 
419
        def fget(self):
 
420
            return self._privacy
 
421
        return locals()
 
422
 
 
423
    @rw_property
 
424
    def personal_message():
 
425
        """The personal message displayed to you contacts
 
426
            @type: utf-8 encoded string"""
 
427
        def fset(self, personal_message):
 
428
            if personal_message == self._personal_message:
 
429
                return
 
430
            self.__pending_set_personal_message[0] = personal_message
 
431
            self._ns_client.set_personal_message(*self.__pending_set_personal_message)
 
432
        def fget(self):
 
433
            return self._personal_message
 
434
        return locals()
 
435
 
 
436
    @rw_property
 
437
    def current_media():
 
438
        """The current media displayed to you contacts
 
439
            @type: (artist: string, track: string)"""
 
440
        def fset(self, current_media):
 
441
            if current_media == self._current_media:
 
442
                return
 
443
            self.__pending_set_personal_message[1] = current_media
 
444
            self._ns_client.set_personal_message(*self.__pending_set_personal_message)
 
445
        def fget(self):
 
446
            return self._current_media
 
447
        return locals()
 
448
 
 
449
    @rw_property
 
450
    def msn_object():
 
451
        """The MSNObject attached to your contact, this MSNObject represents the
 
452
        display picture to be shown to your peers
 
453
            @type: L{MSNObject<pymsn.p2p.MSNObject>}"""
 
454
        def fset(self, msn_object):
 
455
            if msn_object == self._msn_object:
 
456
                return
 
457
            self.__pending_set_presence[2] = msn_object
 
458
            self._ns_client.set_presence(*self.__pending_set_presence)
 
459
        def fget(self):
 
460
            return self._msn_object
 
461
        return locals()
 
462
 
 
463
    @rw_property
 
464
    def presence_msn_object():
 
465
        def fset(self, args):
 
466
            presence, msn_object = args
 
467
            if presence == self._presence and msn_object == self._msn_object:
 
468
                return
 
469
            self.__pending_set_presence[0] = presence
 
470
            self.__pending_set_presence[2] = msn_object
 
471
            self._ns_client.set_presence(*self.__pending_set_presence)
 
472
        def fget(self):
 
473
            return self._presence, self._msn_object
 
474
        return locals()
 
475
 
 
476
    @rw_property
 
477
    def personal_message_current_media():
 
478
        def fset(self, args):
 
479
            personal_message, current_media = args
 
480
            if personal_message == self._personal_message and \
 
481
                    current_media == self._current_media:
 
482
                return
 
483
            self.__pending_set_personal_message[0] = personal_message
 
484
            self.__pending_set_personal_message[1] = current_media
 
485
            self._ns_client.set_personal_message(*self.__pending_set_personal_message)
 
486
        def fget(self):
 
487
            return self._personal_message, self._current_media
 
488
        return locals()
 
489
 
 
490
    def _server_property_changed(self, name, value):
 
491
        attr_name = "_" + name.lower().replace("-", "_")
 
492
        old_value = getattr(self, attr_name)
 
493
        if value != old_value:
 
494
            setattr(self, attr_name, value)
 
495
            self.notify(name)
 
496
 
 
497
    def do_get_property(self, pspec):
 
498
        name = pspec.name.lower().replace("-", "_")
 
499
        return getattr(self, name)
 
500
gobject.type_register(Profile)
 
501
 
 
502
 
 
503
class Contact(gobject.GObject):
 
504
    """Contact related information
 
505
        @undocumented: __gsignals__, __gproperties__, do_get_property"""
 
506
 
 
507
    __gsignals__ =  {
 
508
            "infos-changed": (gobject.SIGNAL_RUN_FIRST,
 
509
                gobject.TYPE_NONE,
 
510
                (object,)),
 
511
            }
 
512
 
 
513
    __gproperties__ = {
 
514
            "memberships": (gobject.TYPE_UINT,
 
515
                "Memberships",
 
516
                "Membership relation with the contact.",
 
517
                0, 15, 0, gobject.PARAM_READABLE),
 
518
 
 
519
            "display-name": (gobject.TYPE_STRING,
 
520
                "Friendly name",
 
521
                "A nickname that the user chooses to display to others",
 
522
                "",
 
523
                gobject.PARAM_READWRITE),
 
524
 
 
525
            "personal-message": (gobject.TYPE_STRING,
 
526
                "Personal message",
 
527
                "The personal message that the user wants to display",
 
528
                "",
 
529
                gobject.PARAM_READABLE),
 
530
 
 
531
            "current-media": (gobject.TYPE_PYOBJECT,
 
532
                "Current media",
 
533
                "The current media that the user wants to display",
 
534
                gobject.PARAM_READABLE),
 
535
 
 
536
            "presence": (gobject.TYPE_STRING,
 
537
                "Presence",
 
538
                "The presence to show to others",
 
539
                Presence.OFFLINE,
 
540
                gobject.PARAM_READABLE),
 
541
 
 
542
             "groups": (gobject.TYPE_PYOBJECT,
 
543
                 "Groups",
 
544
                 "The groups the contact belongs to",
 
545
                 gobject.PARAM_READABLE),
 
546
 
 
547
            "infos": (gobject.TYPE_PYOBJECT,
 
548
                "Informations",
 
549
                "The contact informations",
 
550
                gobject.PARAM_READABLE),
 
551
 
 
552
            "contact-type": (gobject.TYPE_PYOBJECT,
 
553
                "Contact type",
 
554
                "The contact automatic update status flag",
 
555
                 gobject.PARAM_READABLE),        
 
556
 
 
557
            "client-capabilities": (gobject.TYPE_UINT64,
 
558
                "Client capabilities",
 
559
                "The client capabilities of the contact 's client",
 
560
                0, 0xFFFFFFFF, 0,
 
561
                gobject.PARAM_READABLE),
 
562
 
 
563
            "msn-object": (gobject.TYPE_STRING,
 
564
                "MSN Object",
 
565
                "MSN Object attached to the contact, this generally represent "
 
566
                "its display picture",
 
567
                "",
 
568
                gobject.PARAM_READABLE),
 
569
            }
 
570
 
 
571
    def __init__(self, id, network_id, account, display_name, cid=None,
 
572
            memberships=Membership.NONE, contact_type=ContactType.REGULAR):
 
573
        """Initializer"""
 
574
        gobject.GObject.__init__(self)
 
575
        self._id = id
 
576
        self._cid = cid or "00000000-0000-0000-0000-000000000000"
 
577
        self._network_id = network_id
 
578
        self._account = account
 
579
 
 
580
        self._display_name = display_name
 
581
        self._presence = Presence.OFFLINE
 
582
        self._personal_message = ""
 
583
        self._current_media = None
 
584
        self._groups = set()
 
585
 
 
586
        self._memberships = memberships
 
587
        self._contact_type = contact_type
 
588
        self._client_capabilities = ClientCapabilities()
 
589
        self._msn_object = None
 
590
        self._infos = {}
 
591
        self._attributes = {'icon_url' : None}
 
592
 
 
593
    def __repr__(self):
 
594
        def memberships_str():
 
595
            m = []
 
596
            memberships = self._memberships
 
597
            if memberships & Membership.FORWARD:
 
598
                m.append('FORWARD')
 
599
            if memberships & Membership.ALLOW:
 
600
                m.append('ALLOW')
 
601
            if memberships & Membership.BLOCK:
 
602
                m.append('BLOCK')
 
603
            if memberships & Membership.REVERSE:
 
604
                m.append('REVERSE')
 
605
            if memberships & Membership.PENDING:
 
606
                m.append('PENDING')
 
607
            return " | ".join(m)
 
608
        template = "<pymsn.Contact id='%s' network='%u' account='%s' memberships='%s'>"
 
609
        return template % (self._id, self._network_id, self._account, memberships_str())
 
610
 
 
611
    @property
 
612
    def id(self):
 
613
        """Contact identifier in a GUID form
 
614
            @type: GUID string"""
 
615
        return self._id
 
616
 
 
617
    @property
 
618
    def attributes(self):
 
619
        """Contact attributes
 
620
            @type: {key: string => value: string}"""
 
621
        return self._attributes.copy()
 
622
 
 
623
    @property
 
624
    def cid(self):
 
625
        """Contact ID
 
626
            @type: GUID string"""
 
627
        return self._cid
 
628
 
 
629
    @property
 
630
    def network_id(self):
 
631
        """Contact network ID
 
632
            @type: L{NetworkID<pymsn.profile.NetworkID>}"""
 
633
        return self._network_id
 
634
 
 
635
    @property
 
636
    def account(self):
 
637
        """Contact account
 
638
            @type: utf-8 encoded string"""
 
639
        return self._account
 
640
    
 
641
    @property
 
642
    def presence(self):
 
643
        """Contact presence
 
644
            @type: L{Presence<pymsn.profile.Presence>}"""
 
645
        return self._presence
 
646
 
 
647
    @property
 
648
    def display_name(self):
 
649
        """Contact display name
 
650
            @type: utf-8 encoded string"""
 
651
        return self._display_name
 
652
 
 
653
    @property
 
654
    def personal_message(self):
 
655
        """Contact personal message
 
656
            @type: utf-8 encoded string"""
 
657
        return self._personal_message
 
658
 
 
659
    @property
 
660
    def current_media(self):
 
661
        """Contact current media
 
662
            @type: (artist: string, track: string)"""
 
663
        return self._current_media
 
664
 
 
665
    @property
 
666
    def groups(self):
 
667
        """Contact list of groups
 
668
            @type: set(L{Group<pymsn.profile.Group>}...)"""
 
669
        return self._groups
 
670
 
 
671
    @property
 
672
    def infos(self):
 
673
        """Contact informations
 
674
            @type: {key: string => value: string}"""
 
675
        return self._infos
 
676
 
 
677
    @property
 
678
    def memberships(self):
 
679
        """Contact membership value
 
680
            @type: bitmask of L{Membership<pymsn.profile.Membership>}s"""
 
681
        return self._memberships
 
682
 
 
683
    @property
 
684
    def contact_type(self):
 
685
        """Contact automatic update status flag
 
686
            @type: L{ContactType<pymsn.profile.ContactType>}"""
 
687
        return self._contact_type
 
688
 
 
689
    @property
 
690
    def client_capabilities(self):
 
691
        """Contact client capabilities
 
692
            @type: L{ClientCapabilities}"""
 
693
        return self._client_capabilities
 
694
    
 
695
    @property
 
696
    def msn_object(self):
 
697
        """Contact MSN Object
 
698
            @type: L{MSNObject<pymsn.p2p.MSNObject>}"""
 
699
        return self._msn_object
 
700
 
 
701
    @property
 
702
    def domain(self):
 
703
        """Contact domain, which is basically the part after @ in the account
 
704
            @type: utf-8 encoded string"""
 
705
        result = self._account.split('@', 1)
 
706
        if len(result) > 1:
 
707
            return result[1]
 
708
        else:
 
709
            return ""
 
710
 
 
711
    ### membership management
 
712
    def is_member(self, memberships):
 
713
        """Determines if this contact belongs to the specified memberships
 
714
            @type memberships: bitmask of L{Membership<pymsn.profile.Membership>}s"""
 
715
        return (self.memberships & memberships) == memberships
 
716
 
 
717
    def _set_memberships(self, memberships):
 
718
        self._memberships = memberships
 
719
        self.notify("memberships")
 
720
 
 
721
    def _add_membership(self, membership):
 
722
        self._memberships |= membership
 
723
        self.notify("memberships")
 
724
 
 
725
    def _remove_membership(self, membership):
 
726
        """removes the given membership from the contact
 
727
 
 
728
            @param membership: the membership to remove
 
729
            @type membership: int L{Membership}"""
 
730
        self._memberships ^= membership
 
731
        self.notify("memberships")
 
732
 
 
733
    def _server_property_changed(self, name, value): #FIXME, should not be used for memberships
 
734
        if name == "client-capabilities":
 
735
            value = ClientCapabilities(client_id=value)
 
736
        attr_name = "_" + name.lower().replace("-", "_")
 
737
        old_value = getattr(self, attr_name)
 
738
        if value != old_value:
 
739
            setattr(self, attr_name, value)
 
740
            self.notify(name)
 
741
 
 
742
    def _server_attribute_changed(self, name, value):
 
743
        self._attributes[name] = value
 
744
 
 
745
    def _server_infos_changed(self, updated_infos):
 
746
        self._infos.update(updated_infos)
 
747
        self.emit("infos-changed", updated_infos)
 
748
        self.notify("infos")
 
749
 
 
750
    ### group management
 
751
    def _add_group_ownership(self, group):
 
752
        self._groups.add(group)
 
753
 
 
754
    def _delete_group_ownership(self, group):
 
755
        self._groups.discard(group)
 
756
 
 
757
    def do_get_property(self, pspec):
 
758
        name = pspec.name.lower().replace("-", "_")
 
759
        return getattr(self, name)
 
760
gobject.type_register(Contact)
 
761
 
 
762
 
 
763
class Group(gobject.GObject):
 
764
    """Group
 
765
        @undocumented: __gsignals__, __gproperties__, do_get_property"""
 
766
 
 
767
    __gproperties__ = {
 
768
        "name": (gobject.TYPE_STRING,
 
769
                 "Group name",
 
770
                 "Name that the user chooses for the group",
 
771
                 "",
 
772
                 gobject.PARAM_READABLE)
 
773
        }
 
774
 
 
775
    def __init__(self, id, name):
 
776
        """Initializer"""
 
777
        gobject.GObject.__init__(self)
 
778
        self._id = id
 
779
        self._name = name
 
780
 
 
781
    @property
 
782
    def id(self):
 
783
        """Group identifier in a GUID form
 
784
            @type: GUID string"""
 
785
        return self._id
 
786
 
 
787
    @property
 
788
    def name(self):
 
789
        """Group name
 
790
            @type: utf-8 encoded string"""
 
791
        return self._name
 
792
 
 
793
    def _server_property_changed(self, name, value):
 
794
        attr_name = "_" + name.lower().replace("-", "_")
 
795
        old_value = getattr(self, attr_name)
 
796
        if value != old_value:
 
797
            setattr(self, attr_name, value)
 
798
            self.notify(name)
 
799
 
 
800
    def do_get_property(self, pspec):
 
801
        name = pspec.name.lower().replace("-", "_")
 
802
        return getattr(self, name)
 
803
gobject.type_register(Group)
 
804