Index: src/Makefile ================================================================== --- src/Makefile +++ src/Makefile @@ -6,10 +6,12 @@ LIB_MINOR = 0 SRCS = XMPPAuthenticator.m \ XMPPCallback.m \ XMPPConnection.m \ + XMPPContact.m \ + XMPPContactManager.m \ XMPPExceptions.m \ XMPPEXTERNALAuth.m \ XMPPIQ.m \ XMPPJID.m \ XMPPJSONFileStorage.m \ Index: src/ObjXMPP.h ================================================================== --- src/ObjXMPP.h +++ src/ObjXMPP.h @@ -32,5 +32,8 @@ #import "XMPPRosterItem.h" #import "XMPPRoster.h" #import "XMPPStreamManagement.h" + +#import "XMPPContact.h" +#import "XMPPContactManager.h" ADDED src/XMPPContact.h Index: src/XMPPContact.h ================================================================== --- src/XMPPContact.h +++ src/XMPPContact.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2013, Florian Zeitz + * + * 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 + +@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 Index: src/XMPPContact.m ================================================================== --- src/XMPPContact.m +++ src/XMPPContact.m @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2013, Florian Zeitz + * + * 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 Index: src/XMPPContactManager.h ================================================================== --- src/XMPPContactManager.h +++ src/XMPPContactManager.h @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2013, Florian Zeitz + * + * 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 + +#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 +#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 + +#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 )delegate; + +/** + * \brief Removes the specified delegate. + * + * \param delegate The delegate to remove + */ +- (void)removeDelegate: (id )delegate; +@end ADDED src/XMPPContactManager.m Index: src/XMPPContactManager.m ================================================================== --- src/XMPPContactManager.m +++ src/XMPPContactManager.m @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2013, Florian Zeitz + * + * 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 )delegate +{ + [delegates addDelegate: delegate]; +} + +- (void)removeDelegate: (id )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