2016-02-24 16:56:39 +01:00

1007 lines
26 KiB
Objective-C

#import "XMPPRoster.h"
#import "XMPP.h"
#import "XMPPIDTracker.h"
#import "XMPPLogging.h"
#import "XMPPFramework.h"
#import "DDList.h"
#if ! __has_feature(objc_arc)
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
#endif
// Log levels: off, error, warn, info, verbose
// Log flags: trace
#if DEBUG
static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN; // | XMPP_LOG_FLAG_TRACE;
#else
static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN;
#endif
enum XMPPRosterConfig
{
kAutoFetchRoster = 1 << 0, // If set, we automatically fetch roster after authentication
kAutoAcceptKnownPresenceSubscriptionRequests = 1 << 1, // See big description in header file... :D
kRosterlessOperation = 1 << 2,
kAutoClearAllUsersAndResources = 1 << 3,
};
enum XMPPRosterFlags
{
kRequestedRoster = 1 << 0, // If set, we have requested the roster
kHasRoster = 1 << 1, // If set, we have received the roster
kPopulatingRoster = 1 << 2, // If set, we are populating the roster
};
@interface XMPPRoster (PrivateAPI)
- (void)xmppStream:(XMPPStream *)sender didReceivePresence:(XMPPPresence *)presence;
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@implementation XMPPRoster
- (id)init
{
return [self initWithRosterStorage:nil dispatchQueue:NULL];
}
- (id)initWithDispatchQueue:(dispatch_queue_t)queue
{
return [self initWithRosterStorage:nil dispatchQueue:queue];
}
- (id)initWithRosterStorage:(id <XMPPRosterStorage>)storage
{
return [self initWithRosterStorage:storage dispatchQueue:NULL];
}
- (id)initWithRosterStorage:(id <XMPPRosterStorage>)storage dispatchQueue:(dispatch_queue_t)queue
{
NSParameterAssert(storage != nil);
if ((self = [super initWithDispatchQueue:queue]))
{
if ([storage configureWithParent:self queue:moduleQueue])
{
xmppRosterStorage = storage;
}
else
{
XMPPLogError(@"%@: %@ - Unable to configure storage!", THIS_FILE, THIS_METHOD);
}
config = kAutoFetchRoster | kAutoAcceptKnownPresenceSubscriptionRequests | kAutoClearAllUsersAndResources;
flags = 0;
earlyPresenceElements = [[NSMutableArray alloc] initWithCapacity:2];
mucModules = [[DDList alloc] init];
}
return self;
}
- (BOOL)activate:(XMPPStream *)aXmppStream
{
XMPPLogTrace();
if ([super activate:aXmppStream])
{
XMPPLogVerbose(@"%@: Activated", THIS_FILE);
xmppIDTracker = [[XMPPIDTracker alloc] initWithStream:xmppStream dispatchQueue:moduleQueue];
#ifdef _XMPP_VCARD_AVATAR_MODULE_H
{
// Automatically tie into the vCard system so we can store user photos.
[xmppStream autoAddDelegate:self
delegateQueue:moduleQueue
toModulesOfClass:[XMPPvCardAvatarModule class]];
}
#endif
#ifdef _XMPP_MUC_H
{
// Automatically tie into the MUC system so we can ignore non-roster presence stanzas.
[xmppStream enumerateModulesWithBlock:^(XMPPModule *module, NSUInteger idx, BOOL *stop) {
if ([module isKindOfClass:[XMPPMUC class]])
{
[mucModules add:(__bridge void *)module];
}
}];
}
#endif
return YES;
}
return NO;
}
- (void)deactivate
{
XMPPLogTrace();
dispatch_block_t block = ^{ @autoreleasepool {
[xmppIDTracker removeAllIDs];
xmppIDTracker = nil;
}};
if (dispatch_get_specific(moduleQueueTag))
block();
else
dispatch_sync(moduleQueue, block);
#ifdef _XMPP_VCARD_AVATAR_MODULE_H
{
[xmppStream removeAutoDelegate:self delegateQueue:moduleQueue fromModulesOfClass:[XMPPvCardAvatarModule class]];
}
#endif
[super deactivate];
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Internal
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* This method may optionally be used by XMPPRosterStorage classes (declared in XMPPRosterPrivate.h).
**/
- (GCDMulticastDelegate *)multicastDelegate
{
return multicastDelegate;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Configuration
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (id <XMPPRosterStorage>)xmppRosterStorage
{
// Note: The xmppRosterStorage variable is read-only (set in the init method)
return xmppRosterStorage;
}
- (BOOL)autoFetchRoster
{
__block BOOL result = NO;
dispatch_block_t block = ^{
result = (config & kAutoFetchRoster) ? YES : NO;
};
if (dispatch_get_specific(moduleQueueTag))
block();
else
dispatch_sync(moduleQueue, block);
return result;
}
- (void)setAutoFetchRoster:(BOOL)flag
{
dispatch_block_t block = ^{
if (flag)
config |= kAutoFetchRoster;
else
config &= ~kAutoFetchRoster;
};
if (dispatch_get_specific(moduleQueueTag))
block();
else
dispatch_async(moduleQueue, block);
}
- (BOOL)autoClearAllUsersAndResources
{
__block BOOL result = NO;
dispatch_block_t block = ^{
result = (config & kAutoClearAllUsersAndResources) ? YES : NO;
};
if (dispatch_get_specific(moduleQueueTag))
block();
else
dispatch_sync(moduleQueue, block);
return result;
}
- (void)setAutoClearAllUsersAndResources:(BOOL)flag
{
dispatch_block_t block = ^{
if (flag)
config |= kAutoClearAllUsersAndResources;
else
config &= ~kAutoClearAllUsersAndResources;
};
if (dispatch_get_specific(moduleQueueTag))
block();
else
dispatch_async(moduleQueue, block);
}
- (BOOL)autoAcceptKnownPresenceSubscriptionRequests
{
__block BOOL result = NO;
dispatch_block_t block = ^{
result = (config & kAutoAcceptKnownPresenceSubscriptionRequests) ? YES : NO;
};
if (dispatch_get_specific(moduleQueueTag))
block();
else
dispatch_sync(moduleQueue, block);
return result;
}
- (void)setAutoAcceptKnownPresenceSubscriptionRequests:(BOOL)flag
{
dispatch_block_t block = ^{
if (flag)
config |= kAutoAcceptKnownPresenceSubscriptionRequests;
else
config &= ~kAutoAcceptKnownPresenceSubscriptionRequests;
};
if (dispatch_get_specific(moduleQueueTag))
block();
else
dispatch_async(moduleQueue, block);
}
- (BOOL)allowRosterlessOperation
{
__block BOOL result = NO;
dispatch_block_t block = ^{
result = (config & kRosterlessOperation) ? YES : NO;
};
if (dispatch_get_specific(moduleQueueTag))
block();
else
dispatch_sync(moduleQueue, block);
return result;
}
- (void)setAllowRosterlessOperation:(BOOL)flag
{
dispatch_block_t block = ^{
if (flag)
config |= kRosterlessOperation;
else
config &= ~kRosterlessOperation;
};
if (dispatch_get_specific(moduleQueueTag))
block();
else
dispatch_async(moduleQueue, block);
}
- (BOOL)hasRequestedRoster
{
__block BOOL result = NO;
dispatch_block_t block = ^{
result = (flags & kRequestedRoster) ? YES : NO;
};
if (dispatch_get_specific(moduleQueueTag))
block();
else
dispatch_sync(moduleQueue, block);
return result;
}
- (BOOL)isPopulating{
__block BOOL result = NO;
dispatch_block_t block = ^{
result = (flags & kPopulatingRoster) ? YES : NO;
};
if (dispatch_get_specific(moduleQueueTag))
block();
else
dispatch_sync(moduleQueue, block);
return result;
}
- (BOOL)hasRoster
{
__block BOOL result = NO;
dispatch_block_t block = ^{
result = (flags & kHasRoster) ? YES : NO;
};
if (dispatch_get_specific(moduleQueueTag))
block();
else
dispatch_sync(moduleQueue, block);
return result;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Utilities
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (BOOL)_requestedRoster
{
NSAssert(dispatch_get_specific(moduleQueueTag) , @"Invoked on incorrect queue");
return (flags & kRequestedRoster) ? YES : NO;
}
- (void)_setRequestedRoster:(BOOL)flag
{
NSAssert(dispatch_get_specific(moduleQueueTag) , @"Invoked on incorrect queue");
if (flag)
flags |= kRequestedRoster;
else
flags &= ~kRequestedRoster;
}
- (void)_setHasRoster:(BOOL)flag
{
NSAssert(dispatch_get_specific(moduleQueueTag) , @"Invoked on incorrect queue");
if (flag)
flags |= kHasRoster;
else
flags &= ~kHasRoster;
}
- (BOOL)_populatingRoster
{
NSAssert(dispatch_get_specific(moduleQueueTag) , @"Invoked on incorrect queue");
return (flags & kPopulatingRoster) ? YES : NO;
}
- (void)_setPopulatingRoster:(BOOL)flag
{
NSAssert(dispatch_get_specific(moduleQueueTag) , @"Invoked on incorrect queue");
if (flag)
flags |= kPopulatingRoster;
else
flags &= ~kPopulatingRoster;
}
- (void)_addRosterItems:(NSArray *)rosterItems
{
NSAssert(dispatch_get_specific(moduleQueueTag) , @"Invoked on incorrect queue");
BOOL hasRoster = [self hasRoster];
for (NSXMLElement *item in rosterItems)
{
// During roster population, we need to filter out items for users who aren't actually in our roster.
// That is, those users who have requested to be our buddy, but we haven't approved yet.
// This is described in more detail in the method isRosterItem above.
[multicastDelegate xmppRoster:self didReceiveRosterItem:item];
if (hasRoster || [self isRosterItem:item])
{
[xmppRosterStorage handleRosterItem:item xmppStream:xmppStream];
}
}
}
/**
* Some server's include in our roster the JID's of user's NOT in our roster.
* This happens when another user adds us to their roster, and requests permission to receive our presence.
*
* As discussed in RFC 3921, the state of the other user is "None + Pending In",
* and the server "SHOULD NOT" include these JID's in the roster it sends us.
*
* Nonetheless, some servers do anyway.
* This method filters out such rogue entries in our roster.
*
* Note that the server will automatically send us the proper presence subscription request,
* and it will continue to do so everytime we sign in.
* From the RFC:
* the user's server MUST keep a record of the subscription request and deliver the request when the
* user next creates an available resource, until the user either approves or denies the request.
*
* So there is absolutely NO reason to process these entries, or include them in the roster's storage.
* Furthermore, it isn't reliable to depend on these entires being there.
* The RFC has clearly defined recommendations on the matter, and servers that currently send these rogue items
* may very likely stop doing so in future versions.
**/
- (BOOL)isRosterItem:(NSXMLElement *)item
{
NSString *subscription = [item attributeStringValueForName:@"subscription"];
if ([subscription isEqualToString:@"none"])
{
NSString *ask = [item attributeStringValueForName:@"ask"];
if ([ask isEqualToString:@"subscribe"])
{
return YES;
}
else
{
return NO;
}
}
return YES;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Roster Management
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (void)addUser:(XMPPJID *)jid withNickname:(NSString *)optionalName{
[self addUser:jid withNickname:optionalName groups:nil subscribeToPresence:YES];
}
- (void)addUser:(XMPPJID *)jid withNickname:(NSString *)optionalName groups:(NSArray *)groups{
[self addUser:jid withNickname:optionalName groups:groups subscribeToPresence:YES];
}
- (void)addUser:(XMPPJID *)jid withNickname:(NSString *)optionalName groups:(NSArray *)groups subscribeToPresence:(BOOL)subscribe{
if (jid == nil) return;
XMPPJID *myJID = xmppStream.myJID;
if ([myJID isEqualToJID:jid options:XMPPJIDCompareBare])
{
// You don't need to add yourself to the roster.
// XMPP will automatically send you presence from all resources signed in under your username.
//
// E.g. If you sign in with robbiehanson@deusty.com/home you'll automatically
// receive presence from robbiehanson@deusty.com/work
XMPPLogInfo(@"%@: %@ - Ignoring request to add myself to my own roster", [self class], THIS_METHOD);
return;
}
// Add the buddy to our roster
//
// <iq type="set">
// <query xmlns="jabber:iq:roster">
// <item jid="bareJID" name="optionalName">
// <group>family</group>
// </item>
// </query>
// </iq>
NSXMLElement *item = [NSXMLElement elementWithName:@"item"];
[item addAttributeWithName:@"jid" stringValue:[jid bare]];
if(optionalName)
{
[item addAttributeWithName:@"name" stringValue:optionalName];
}
for (NSString *group in groups) {
NSXMLElement *groupElement = [NSXMLElement elementWithName:@"group"];
[groupElement setStringValue:group];
[item addChild:groupElement];
}
NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:@"jabber:iq:roster"];
[query addChild:item];
NSXMLElement *iq = [NSXMLElement elementWithName:@"iq"];
[iq addAttributeWithName:@"type" stringValue:@"set"];
[iq addChild:query];
[xmppStream sendElement:iq];
if(subscribe)
{
[self subscribePresenceToUser:jid];
}
}
- (void)setNickname:(NSString *)nickname forUser:(XMPPJID *)jid
{
// This is a public method, so it may be invoked on any thread/queue.
if (jid == nil) return;
// <iq type="set">
// <query xmlns="jabber:iq:roster">
// <item jid="bareJID" name="nickname"/>
// </query>
// </iq>
NSXMLElement *item = [NSXMLElement elementWithName:@"item"];
[item addAttributeWithName:@"jid" stringValue:[jid bare]];
[item addAttributeWithName:@"name" stringValue:nickname];
NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:@"jabber:iq:roster"];
[query addChild:item];
XMPPIQ *iq = [XMPPIQ iqWithType:@"set"];
[iq addChild:query];
[xmppStream sendElement:iq];
}
- (void)subscribePresenceToUser:(XMPPJID *)jid
{
// This is a public method, so it may be invoked on any thread/queue.
if (jid == nil) return;
XMPPJID *myJID = xmppStream.myJID;
if ([myJID isEqualToJID:jid options:XMPPJIDCompareBare])
{
XMPPLogInfo(@"%@: %@ - Ignoring request to subscribe presence to myself", [self class], THIS_METHOD);
return;
}
// <presence to='bareJID' type='subscribe'/>
XMPPPresence *presence = [XMPPPresence presenceWithType:@"subscribe" to:[jid bareJID]];
[xmppStream sendElement:presence];
}
- (void)unsubscribePresenceFromUser:(XMPPJID *)jid
{
// This is a public method, so it may be invoked on any thread/queue.
if (jid == nil) return;
XMPPJID *myJID = xmppStream.myJID;
if ([myJID isEqualToJID:jid options:XMPPJIDCompareBare])
{
XMPPLogInfo(@"%@: %@ - Ignoring request to unsubscribe presence from myself", [self class], THIS_METHOD);
return;
}
// <presence to="bareJID" type="unsubscribe">
XMPPPresence *presence = [XMPPPresence presenceWithType:@"unsubscribe" to:[jid bareJID]];
[xmppStream sendElement:presence];
}
- (void)revokePresencePermissionFromUser:(XMPPJID *)jid
{
// This is a public method, so it may be invoked on any thread/queue.
if (jid == nil) return;
XMPPJID *myJID = xmppStream.myJID;
if ([myJID isEqualToJID:jid options:XMPPJIDCompareBare])
{
XMPPLogInfo(@"%@: %@ - Ignoring request to revoke presence from myself", [self class], THIS_METHOD);
return;
}
// <presence to="bareJID" type="unsubscribed">
XMPPPresence *presence = [XMPPPresence presenceWithType:@"unsubscribed" to:[jid bareJID]];
[xmppStream sendElement:presence];
}
- (void)removeUser:(XMPPJID *)jid
{
// This is a public method, so it may be invoked on any thread/queue.
if (jid == nil) return;
XMPPJID *myJID = xmppStream.myJID;
if ([myJID isEqualToJID:jid options:XMPPJIDCompareBare])
{
XMPPLogInfo(@"%@: %@ - Ignoring request to remove myself from my own roster", [self class], THIS_METHOD);
return;
}
// Remove the user from our roster.
// And unsubscribe from presence.
// And revoke contact's subscription to our presence.
// ...all in one step
// <iq type="set">
// <query xmlns="jabber:iq:roster">
// <item jid="bareJID" subscription="remove"/>
// </query>
// </iq>
NSXMLElement *item = [NSXMLElement elementWithName:@"item"];
[item addAttributeWithName:@"jid" stringValue:[jid bare]];
[item addAttributeWithName:@"subscription" stringValue:@"remove"];
NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:@"jabber:iq:roster"];
[query addChild:item];
XMPPIQ *iq = [XMPPIQ iqWithType:@"set"];
[iq addChild:query];
[xmppStream sendElement:iq];
}
- (void)acceptPresenceSubscriptionRequestFrom:(XMPPJID *)jid andAddToRoster:(BOOL)flag
{
// This is a public method, so it may be invoked on any thread/queue.
// Send presence response
//
// <presence to="bareJID" type="subscribed"/>
XMPPPresence *presence = [XMPPPresence presenceWithType:@"subscribed" to:[jid bareJID]];
[xmppStream sendElement:presence];
// Add optionally add user to our roster
if (flag)
{
[self addUser:jid withNickname:nil];
}
}
- (void)rejectPresenceSubscriptionRequestFrom:(XMPPJID *)jid
{
// This is a public method, so it may be invoked on any thread/queue.
// Send presence response
//
// <presence to="bareJID" type="unsubscribed"/>
XMPPPresence *presence = [XMPPPresence presenceWithType:@"unsubscribed" to:[jid bareJID]];
[xmppStream sendElement:presence];
}
- (void)fetchRoster
{
// This is a public method, so it may be invoked on any thread/queue.
dispatch_block_t block = ^{ @autoreleasepool {
[self fetchRosterVersion:nil];
}};
if (dispatch_get_specific(moduleQueueTag))
block();
else
dispatch_async(moduleQueue, block);
}
- (void)fetchRosterVersion:(NSString *)version
{
// This is a public method, so it may be invoked on any thread/queue.
dispatch_block_t block = ^{ @autoreleasepool {
if ([self _requestedRoster])
{
// We've already requested the roster from the server.
return;
}
// <iq type="get">
// <query xmlns="jabber:iq:roster" ver="ver14"/>
// </iq>
NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:@"jabber:iq:roster"];
if (version)
[query addAttributeWithName:@"ver" stringValue:version];
XMPPIQ *iq = [XMPPIQ iqWithType:@"get" elementID:[xmppStream generateUUID]];
[iq addChild:query];
[xmppIDTracker addElement:iq
target:self
selector:@selector(handleFetchRosterQueryIQ:withInfo:)
timeout:60];
[xmppStream sendElement:iq];
[self _setRequestedRoster:YES];
}};
if (dispatch_get_specific(moduleQueueTag))
block();
else
dispatch_async(moduleQueue, block);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark XMPPIDTracker
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (void)handleFetchRosterQueryIQ:(XMPPIQ *)iq withInfo:(XMPPBasicTrackingInfo *)basicTrackingInfo{
dispatch_block_t block = ^{ @autoreleasepool {
NSXMLElement *query = [iq elementForName:@"query" xmlns:@"jabber:iq:roster"];
NSString * version = [query attributeStringValueForName:@"ver"];
BOOL hasRoster = [self hasRoster];
if (!hasRoster)
{
[xmppRosterStorage clearAllUsersAndResourcesForXMPPStream:xmppStream];
[self _setPopulatingRoster:YES];
[multicastDelegate xmppRosterDidBeginPopulating:self withVersion:version];
[xmppRosterStorage beginRosterPopulationForXMPPStream:xmppStream withVersion:version];
}
NSArray *items = [query elementsForName:@"item"];
[self _addRosterItems:items];
if (!hasRoster)
{
// We should have our roster now
[self _setHasRoster:YES];
[self _setPopulatingRoster:NO];
[multicastDelegate xmppRosterDidEndPopulating:self];
[xmppRosterStorage endRosterPopulationForXMPPStream:xmppStream];
// Process any premature presence elements we received.
for (XMPPPresence *presence in earlyPresenceElements)
{
[self xmppStream:xmppStream didReceivePresence:presence];
}
[earlyPresenceElements removeAllObjects];
}
}};
if (dispatch_get_specific(moduleQueueTag))
block();
else
dispatch_async(moduleQueue, block);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark XMPPStream Delegate
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender
{
// This method is invoked on the moduleQueue.
XMPPLogTrace();
if ([self autoFetchRoster])
{
[self fetchRoster];
}
}
- (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq
{
// This method is invoked on the moduleQueue.
XMPPLogTrace();
// Note: Some jabber servers send an iq element with an xmlns.
// Because of the bug in Apple's NSXML (documented in our elementForName method),
// it is important we specify the xmlns for the query.
NSXMLElement *query = [iq elementForName:@"query" xmlns:@"jabber:iq:roster"];
if (query)
{
if([iq isSetIQ])
{
[multicastDelegate xmppRoster:self didReceiveRosterPush:iq];
NSArray *items = [query elementsForName:@"item"];
[self _addRosterItems:items];
}
else if([iq isResultIQ])
{
[xmppIDTracker invokeForElement:iq withObject:iq];
}
return YES;
}
return NO;
}
- (void)xmppStream:(XMPPStream *)sender didReceivePresence:(XMPPPresence *)presence
{
// This method is invoked on the moduleQueue.
XMPPLogTrace();
if (![self hasRoster] && ![self allowRosterlessOperation])
{
// We received a presence notification,
// but we don't have a roster to apply it to yet.
//
// This is possible if we send our presence before we've received our roster.
// It's even possible if we send our presence after we've requested our roster.
// There is no guarantee the server will process our requests serially,
// and the server may start sending presence elements before it sends our roster.
//
// However, if we've requested the roster,
// then it shouldn't be too long before we receive it.
// So we should be able to simply queue the presence elements for later processing.
if ([self _requestedRoster])
{
// We store the presence element until we get our roster.
[earlyPresenceElements addObject:presence];
}
else
{
// The user has not requested the roster.
// This is a rogue presence element, or the user is simply not using our roster management.
}
return;
}
if ([[presence type] isEqualToString:@"subscribe"])
{
XMPPJID *userJID = [[presence from] bareJID];
BOOL knownUser = [xmppRosterStorage userExistsWithJID:userJID xmppStream:xmppStream];
if (knownUser && [self autoAcceptKnownPresenceSubscriptionRequests])
{
// Presence subscription request from someone who's already in our roster.
// Automatically approve.
//
// <presence to="bareJID" type="subscribed"/>
XMPPPresence *response = [XMPPPresence presenceWithType:@"subscribed" to:userJID];
[xmppStream sendElement:response];
}
else
{
// Presence subscription request from someone who's NOT in our roster
[multicastDelegate xmppRoster:self didReceivePresenceSubscriptionRequest:presence];
}
}
else
{
#ifdef _XMPP_MUC_H
// Ignore MUC related presence items
for (XMPPMUC *muc in mucModules)
{
if ([muc isMUCRoomPresence:presence])
{
return;
}
}
#endif
[xmppRosterStorage handlePresence:presence xmppStream:xmppStream];
}
}
- (void)xmppStream:(XMPPStream *)sender didSendPresence:(XMPPPresence *)presence
{
// This method is invoked on the moduleQueue.
XMPPLogTrace();
// We check the toStr, so we don't dump the resources when a user leaves a MUC room.
if ([[presence type] isEqualToString:@"unavailable"] && [presence toStr] == nil)
{
// We don't receive presence notifications when we're offline.
// So we need to remove all resources from our roster when we're offline.
// When we become available again, we'll automatically receive the
// presence from every available user in our roster.
//
// We will receive general roster updates as long as we're still connected though.
// So there's no need to refetch the roster.
[xmppRosterStorage clearAllResourcesForXMPPStream:xmppStream];
[earlyPresenceElements removeAllObjects];
}
}
- (void)xmppStreamDidDisconnect:(XMPPStream *)sender withError:(NSError *)error
{
// This method is invoked on the moduleQueue.
XMPPLogTrace();
if([self autoClearAllUsersAndResources])
{
[xmppRosterStorage clearAllUsersAndResourcesForXMPPStream:xmppStream];
}
else
{
[xmppRosterStorage clearAllResourcesForXMPPStream:xmppStream];
}
[self _setRequestedRoster:NO];
[self _setHasRoster:NO];
[earlyPresenceElements removeAllObjects];
}
#ifdef _XMPP_MUC_H
- (void)xmppStream:(XMPPStream *)sender didRegisterModule:(id)module
{
if ([module isKindOfClass:[XMPPMUC class]])
{
if (![mucModules contains:(__bridge void *)module])
{
[mucModules add:(__bridge void *)module];
}
}
}
- (void)xmppStream:(XMPPStream *)sender willUnregisterModule:(id)module
{
if ([module isKindOfClass:[XMPPMUC class]])
{
[mucModules remove:(__bridge void *)module];
}
}
#endif
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark XMPPvCardAvatarDelegate
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#ifdef _XMPP_VCARD_AVATAR_MODULE_H
#if TARGET_OS_IPHONE
- (void)xmppvCardAvatarModule:(XMPPvCardAvatarModule *)vCardTempModule
didReceivePhoto:(UIImage *)photo
forJID:(XMPPJID *)jid
#else
- (void)xmppvCardAvatarModule:(XMPPvCardAvatarModule *)vCardTempModule
didReceivePhoto:(NSImage *)photo
forJID:(XMPPJID *)jid
#endif
{
if ([xmppRosterStorage respondsToSelector:@selector(setPhoto:forUserWithJID:xmppStream:)])
{
[xmppRosterStorage setPhoto:photo forUserWithJID:[jid bareJID] xmppStream:xmppStream];
}
}
#endif
@end