ObjXMPP  Check-in [fc38b1287a]

Overview
Comment:Add XMPPContact{,Manager} for tracking contacts
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: fc38b1287a3f544c7ea64ba9b6c2ec15c30af951504758f99ba089115fc4a54f
User & Date: florob@babelmonkeys.de on 2013-01-27 17:01:08
Other Links: manifest | tags
Context
2013-01-29
19:46
Implement -initWithElement: for XMPPPresence check-in: d75ffa4c2e user: florob@babelmonkeys.de tags: trunk
2013-01-27
17:01
Add XMPPContact{,Manager} for tracking contacts check-in: fc38b1287a user: florob@babelmonkeys.de tags: trunk
2013-01-18
23:38
Fix Makefile check-in: 0bee074775 user: florob@babelmonkeys.de tags: trunk
Changes

Modified src/Makefile from [e0db257764] to [0633fdc12c].

1
2
3
4
5
6
7
8
9
10


11
12
13
14
15
16
17
include ../extra.mk

SHARED_LIB = ${OBJXMPP_SHARED_LIB}
STATIC_LIB = ${OBJXMPP_STATIC_LIB}
LIB_MAJOR = 0
LIB_MINOR = 0

SRCS = XMPPAuthenticator.m	\
       XMPPCallback.m		\
       XMPPConnection.m		\


       XMPPExceptions.m		\
       XMPPEXTERNALAuth.m	\
       XMPPIQ.m			\
       XMPPJID.m		\
       XMPPJSONFileStorage.m	\
       XMPPMessage.m		\
       XMPPMulticastDelegate.m	\










>
>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
include ../extra.mk

SHARED_LIB = ${OBJXMPP_SHARED_LIB}
STATIC_LIB = ${OBJXMPP_STATIC_LIB}
LIB_MAJOR = 0
LIB_MINOR = 0

SRCS = XMPPAuthenticator.m	\
       XMPPCallback.m		\
       XMPPConnection.m		\
       XMPPContact.m		\
       XMPPContactManager.m	\
       XMPPExceptions.m		\
       XMPPEXTERNALAuth.m	\
       XMPPIQ.m			\
       XMPPJID.m		\
       XMPPJSONFileStorage.m	\
       XMPPMessage.m		\
       XMPPMulticastDelegate.m	\

Modified src/ObjXMPP.h from [cd1e1205a2] to [cf74b69799].

30
31
32
33
34
35
36



#import "XMPPMessage.h"
#import "XMPPPresence.h"

#import "XMPPRosterItem.h"
#import "XMPPRoster.h"

#import "XMPPStreamManagement.h"










>
>
>
30
31
32
33
34
35
36
37
38
39
#import "XMPPMessage.h"
#import "XMPPPresence.h"

#import "XMPPRosterItem.h"
#import "XMPPRoster.h"

#import "XMPPStreamManagement.h"

#import "XMPPContact.h"
#import "XMPPContactManager.h"

Added src/XMPPContact.h version [e36b41f1a9].



































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
/*
 * Copyright (c) 2013, Florian Zeitz <florob@babelmonkeys.de>
 *
 * https://webkeks.org/git/?p=objxmpp.git
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice is present in all copies.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#import <ObjFW/ObjFW.h>

@class XMPPConnection;
@class XMPPJID;
@class XMPPRosterItem;
@class XMPPMessage;
@class XMPPPresence;

/**
 * \brief A class describing a contact tracked by a XMPPContactManager
 */
@interface XMPPContact: OFObject
{
/// \cond internal
	XMPPRosterItem *rosterItem;
	OFMutableDictionary *presences;
	XMPPJID *lockedOnJID;
/// \endcond
}
#ifdef OF_HAVE_PROPERTIES
/// \brief The XMPPRosterItem corresponding to this contact
@property (readonly) XMPPRosterItem *rosterItem;
/// \brief The XMPPPresences of this contact with the resources as keys
@property (readonly) OFDictionary *presences;
#endif

/**
 * \brief Sends a message to the contact honoring resource locking
 *
 * \param message The message to send
 * \param connection The connection to use for sending the message
 */
- (void)sendMessage: (XMPPMessage*)message
	 connection: (XMPPConnection*)connection;

/// \cond internal
- (void)XMPP_setRosterItem: (XMPPRosterItem*)rosterItem;
- (void)XMPP_setPresence: (XMPPPresence*)presence
		resource: (OFString*)resource;
- (void)XMPP_removePresenceForResource: (OFString*)resource;
- (void)XMPP_setLockedOnJID: (XMPPJID*)JID;
/// \endcond
@end

Added src/XMPPContact.m version [ba595101e0].

















































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
/*
 * Copyright (c) 2013, Florian Zeitz <florob@babelmonkeys.de>
 *
 * https://webkeks.org/git/?p=objxmpp.git
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice is present in all copies.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#import "XMPPContact.h"
#import "XMPPMessage.h"
#import "XMPPConnection.h"

@implementation XMPPContact
- (XMPPRosterItem*)rosterItem
{
	OF_GETTER(rosterItem, YES);
}

- (OFDictionary*)presences
{
	OF_GETTER(presences, YES);
}

- (void)sendMessage: (XMPPMessage*)message
	 connection: (XMPPConnection*)connection
{
	if (lockedOnJID == nil)
		[message setTo: [rosterItem JID]];
	else
		[message setTo: lockedOnJID];

	[connection sendStanza: message];
}

- (void)XMPP_setRosterItem: (XMPPRosterItem*)rosterItem_
{
	OF_SETTER(rosterItem, rosterItem_, YES, 0);
}

- (void)XMPP_setPresence: (XMPPPresence*)presence
		resource: (OFString*)resource
{
	[presences setObject: presence
		      forKey: resource];
	OF_SETTER(lockedOnJID, nil, YES, 0);
}

- (void)XMPP_removePresenceForResource: (OFString*)resource
{
	[presences removeObjectForKey: resource];
	OF_SETTER(lockedOnJID, nil, YES, 0);
}

- (void)XMPP_setLockedOnJID: (XMPPJID*)JID;
{
	OF_SETTER(lockedOnJID, JID, YES, 0);
}
@end

Added src/XMPPContactManager.h version [f32fa83767].













































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
/*
 * Copyright (c) 2013, Florian Zeitz <florob@babelmonkeys.de>
 *
 * https://webkeks.org/git/?p=objxmpp.git
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice is present in all copies.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#import <ObjFW/ObjFW.h>

#import "XMPPConnection.h"
#import "XMPPRoster.h"

@class XMPPContact;
@class XMPPContactManager;
@class XMPPMulticastDelegate;
@class XMPPPresence;

/**
 * \brief A protocol that should be (partially) implemented by delegates
 *	  of a XMPPContactManager
 */
@protocol XMPPContactManagerDelegate <OFObject>
#ifdef OF_HAVE_OPTIONAL_PROTOCOLS
@optional
#endif
/**
 * \brief This callback is called whenever a new contact enters the users roster
 *
 * \param manager The contact manager that added the contact
 * \param contact The contact that was added
 */
- (void)contactManager: (XMPPContactManager*)manager
	 didAddContact: (XMPPContact*)contact;

/**
 * \brief This callback is called whenever a contact is no longer present in
 *	  the users roster
 *
 * \param manager The contact manager that removed the contact
 * \param contact The contact that was removed
 */
- (void)contactManager: (XMPPContactManager*)manager
      didRemoveContact: (XMPPContact*)contact;

/**
 * \brief This callback is called whenever a contact is about to change its
 *	  roster item
 *
 * \param contact The contact about to updated its roster item
 * \param rosterItem The roster item the contact is going to update with
 */
-	     (void)contact: (XMPPContact*)contact
  willUpdateWithRosterItem: (XMPPRosterItem*)rosterItem;

/**
 * \brief This callback is called whenever a contact send a presence stanza
 *
 * \param contact The contact that send the presence
 * \param presence The presence which was send by the contact
 */
-   (void)contact: (XMPPContact*)contact
  didSendPresence: (XMPPPresence*)presence;

/**
 * \brief This callback is called whenever a contact send a message stanza
 *
 * \param contact The contact that send the message
 * \param message The message which was send by the contact
 */
-  (void)contact: (XMPPContact*)contact
  didSendMessage: (XMPPMessage*)message;
@end

/**
 * \brief A class tracking a XMPPContact instance for each contact in the roster
 *
 * This class delegates to a XMPPConnection and a XMPPRoster, thereby tracking
 * each contacts presences and the current XMPPRosterItem.
 */
@interface XMPPContactManager: OFObject
#ifdef OF_HAVE_OPTIONAL_PROTOCOLS
    <XMPPConnectionDelegate, XMPPRosterDelegate>
#endif
{
/// \cond internal
	OFMutableDictionary *contacts;
	XMPPConnection *connection;
	XMPPRoster *roster;
	XMPPMulticastDelegate *delegates;
/// \endcond
}
#ifdef OF_HAVE_PROPERTIES
/// \brief The tracked contacts, with their bare JID as key
@property (readonly) OFDictionary *contacts;
#endif

/**
 * \brief Initializes an already allocated XMPPContactManager.
 *
 * \param connection The connection to be used to track contacts
 * \return An initialized XMPPContactManager
 */
- initWithConnection: (XMPPConnection*)connection
	      roster: (XMPPRoster*)roster;

/**
 * \brief Adds the specified delegate.
 *
 * \param delegate The delegate to add
 */
- (void)addDelegate: (id <XMPPContactManagerDelegate>)delegate;

/**
 * \brief Removes the specified delegate.
 *
 * \param delegate The delegate to remove
 */
- (void)removeDelegate: (id <XMPPContactManagerDelegate>)delegate;
@end

Added src/XMPPContactManager.m version [c51574c9fb].





















































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
/*
 * Copyright (c) 2013, Florian Zeitz <florob@babelmonkeys.de>
 *
 * https://webkeks.org/git/?p=objxmpp.git
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice is present in all copies.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#import "XMPPContact.h"
#import "XMPPContactManager.h"
#import "XMPPJID.h"
#import "XMPPMulticastDelegate.h"
#import "XMPPPresence.h"
#import "XMPPRosterItem.h"

@implementation XMPPContactManager
- initWithConnection: (XMPPConnection*)connection_
	      roster: (XMPPRoster*)roster_
{
	self = [super init];

	@try {
		connection = connection_;
		[connection addDelegate: self];
		roster = roster_;
		[roster addDelegate: self];
		contacts = [[OFMutableDictionary alloc] init];
		delegates = [[XMPPMulticastDelegate alloc] init];
	} @catch (id e) {
		[self release];
		@throw e;
	}

	return self;
}

- (void)dealloc
{
	[connection removeDelegate: self];
	[roster removeDelegate: self];
	[delegates release];
	[contacts release];

	[super dealloc];
}

- (void)addDelegate: (id <XMPPConnectionDelegate>)delegate
{
	[delegates addDelegate: delegate];
}

- (void)removeDelegate: (id <XMPPConnectionDelegate>)delegate
{
	[delegates removeDelegate: delegate];
}

- (OFDictionary*)contacts
{
	OF_GETTER(contacts, YES);
}

- (void)rosterWasReceived: (XMPPRoster*)roster_
{
	OFEnumerator *contactEnumerator;
	XMPPContact *contact;
	OFDictionary *rosterItems;
	OFEnumerator *rosterItemEnumerator;
	OFString *bareJID;

	contactEnumerator = [contacts objectEnumerator];
	while ((contact = [contactEnumerator nextObject]) != nil) {
		[delegates broadcastSelector: @selector(contactManager:
						  didRemoveContact:)
				  withObject: self
				  withObject: contact];
	}
	[contacts release];

	contacts = [[OFMutableDictionary alloc] init];
	rosterItems = [roster_ rosterItems];
	rosterItemEnumerator = [rosterItems keyEnumerator];
	while ((bareJID = [rosterItemEnumerator nextObject]) != nil) {
		contact = [[XMPPContact new] autorelease];
		[contact XMPP_setRosterItem:
		    [rosterItems objectForKey: bareJID]];
		[contacts setObject: contact
			     forKey: bareJID];
		[delegates broadcastSelector: @selector(contactManager:
						  didAddContact:)
				  withObject: self
				  withObject: contact];
	}
}

-         (void)roster: (XMPPRoster*)roster
  didReceiveRosterItem: (XMPPRosterItem*)rosterItem
{
	XMPPContact *contact;
	OFString *bareJID = [[rosterItem JID] bareJID];

	contact = [contacts objectForKey: bareJID];

	if ([[rosterItem subscription] isEqual: @"remove"]) {
		[contacts removeObjectForKey: bareJID];
		if (contact != nil)
			[delegates broadcastSelector: @selector(contactManager:
							  didRemoveContact:)
					  withObject: self
					  withObject: contact];
		return;
	}

	if (contact == nil) {
		contact = [[XMPPContact new] autorelease];
		[contact XMPP_setRosterItem: rosterItem];
		[contacts setObject: contact
			     forKey: bareJID];
		[delegates broadcastSelector: @selector(contactManager:
						  didAddContact:)
				  withObject: self
				  withObject: contact];
	} else {
		[delegates broadcastSelector: @selector(contact:
						  willUpdateWithRosterItem:)
				  withObject: contact
				  withObject: rosterItem];
		[contact XMPP_setRosterItem: rosterItem];
	}
}

-   (void)connection: (XMPPConnection*)connection
  didReceivePresence: (XMPPPresence*)presence
{
	XMPPJID *JID = [presence from];
	XMPPContact *contact = [contacts objectForKey: [JID bareJID]];

	if (contact == nil)
		return;

	// We only care for available and unavailable here, not subscriptions
	if ([[presence type] isEqual: @"available"]) {
		[contact XMPP_setPresence: presence
				 resource: [JID resource]];
		[delegates broadcastSelector: @selector(contact:
						  didSendPresence:)
				  withObject: contact
				  withObject: presence];
	} else if ([[presence type] isEqual: @"unavailable"]) {
		[contact XMPP_removePresenceForResource: [JID resource]];
		[delegates broadcastSelector: @selector(contact:
						  didSendPresence:)
				  withObject: contact
				  withObject: presence];
	}
}

-  (void)connection: (XMPPConnection*)connection
  didReceiveMessage: (XMPPMessage*)message
{
	XMPPJID *JID = [message from];
	XMPPContact *contact = [contacts objectForKey: [JID bareJID]];

	if (contact == nil)
		return;

	[contact XMPP_setLockedOnJID: JID];

	[delegates broadcastSelector: @selector(contact:didSendMessage:)
			  withObject: contact
			  withObject: message];
}
@end