#import "XMPP.h" #import "XMPPRosterPrivate.h" #import "XMPPRosterMemoryStorage.h" #import "XMPPRosterMemoryStoragePrivate.h" #import "XMPPLogging.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 #if DEBUG static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN; // | XMPP_LOG_FLAG_TRACE; #else static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN; #endif #define AssertPrivateQueue() \ NSAssert(dispatch_get_specific(parentQueueTag), @"Private method: MUST run on parentQueue"); #define AssertParentQueue() \ NSAssert(dispatch_get_specific(parentQueueTag), @"Private protocol method: MUST run on parentQueue"); @interface XMPPRosterMemoryStorage () @property (readonly) dispatch_queue_t parentQueue; @end //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @implementation XMPPRosterMemoryStorage - (id)init { if ((self = [super init])) { userClass = [XMPPUserMemoryStorageObject class]; resourceClass = [XMPPResourceMemoryStorageObject class]; roster = [[NSMutableDictionary alloc] init]; } return self; } - (BOOL)configureWithParent:(XMPPRoster *)aParent queue:(dispatch_queue_t)queue { NSParameterAssert(aParent != nil); NSParameterAssert(queue != NULL); @synchronized(self) { if ((parent == nil) && (parentQueue == NULL)) { parent = aParent; parentQueue = queue; parentQueueTag = &parentQueueTag; dispatch_queue_set_specific(parentQueue, parentQueueTag, parentQueueTag, NULL); #if !OS_OBJECT_USE_OBJC dispatch_retain(parentQueue); #endif return YES; } } return NO; } - (void)dealloc { #if !OS_OBJECT_USE_OBJC if (parentQueue) dispatch_release(parentQueue); #endif } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Properties //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @synthesize userClass; @synthesize resourceClass; - (XMPPRoster *)parent { XMPPRoster *result = nil; @synchronized(self) // synchronized with configureWithParent:queue: { result = parent; } return result; } - (dispatch_queue_t)parentQueue { dispatch_queue_t result = NULL; @synchronized(self) // synchronized with configureWithParent:queue: { result = parentQueue; } return result; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Internal API //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - (GCDMulticastDelegate *)multicastDelegate { return (GCDMulticastDelegate *)[parent multicastDelegate]; } - (XMPPUserMemoryStorageObject *)_userForJID:(XMPPJID *)jid { AssertPrivateQueue(); XMPPUserMemoryStorageObject *result = roster[[jid bareJID]]; if (result) { return result; } XMPPJID *myBareJID = [myJID bareJID]; XMPPJID *bareJID = [jid bareJID]; if ([bareJID isEqualToJID:myBareJID]) { return myUser; } return nil; } - (XMPPResourceMemoryStorageObject *)_resourceForJID:(XMPPJID *)jid { AssertPrivateQueue(); XMPPUserMemoryStorageObject *user = [self _userForJID:jid]; return (XMPPResourceMemoryStorageObject *)[user resourceForJID:jid]; } - (NSArray *)_unsortedUsers { AssertPrivateQueue(); return [roster allValues]; } - (NSArray *)_unsortedAvailableUsers { AssertPrivateQueue(); NSArray *allUsers = [roster allValues]; NSMutableArray *result = [NSMutableArray arrayWithCapacity:[allUsers count]]; for (id user in allUsers) { if ([user isOnline]) { [result addObject:user]; } } return result; } - (NSArray *)_unsortedUnavailableUsers { AssertPrivateQueue(); NSArray *allUsers = [roster allValues]; NSMutableArray *result = [NSMutableArray arrayWithCapacity:[allUsers count]]; for (id user in allUsers) { if (![user isOnline]) { [result addObject:user]; } } return result; } - (NSArray *)_sortedUsersByName { AssertPrivateQueue(); return [[roster allValues] sortedArrayUsingSelector:@selector(compareByName:)]; } - (NSArray *)_sortedUsersByAvailabilityName { AssertPrivateQueue(); return [[roster allValues] sortedArrayUsingSelector:@selector(compareByAvailabilityName:)]; } - (NSArray *)_sortedAvailableUsersByName { AssertPrivateQueue(); return [[self _unsortedAvailableUsers] sortedArrayUsingSelector:@selector(compareByName:)]; } - (NSArray *)_sortedUnavailableUsersByName { AssertPrivateQueue(); return [[self _unsortedUnavailableUsers] sortedArrayUsingSelector:@selector(compareByName:)]; } - (NSArray *)_sortedResources:(BOOL)includeResourcesForMyUserExcludingMyself { AssertPrivateQueue(); // Add all the resouces from all the available users in the roster // // Remember: There may be multiple resources per user NSArray *availableUsers = [self unsortedAvailableUsers]; NSMutableArray *result = [NSMutableArray arrayWithCapacity:[availableUsers count]]; for (id user in availableUsers) { [result addObjectsFromArray:[user allResources]]; } if (includeResourcesForMyUserExcludingMyself) { // Now add all the available resources from our own user account (excluding ourselves) NSArray *myResources = [myUser allResources]; for (id resource in myResources) { if (![myJID isEqualToJID:[resource jid]]) { [result addObject:resource]; } } } return [result sortedArrayUsingSelector:@selector(compare:)]; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Roster Management //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - (XMPPUserMemoryStorageObject *)myUser { // This is a public method, so it may be invoked on any thread/queue. if (self.parentQueue == NULL) { // Haven't been attached to parent yet return nil; } if (dispatch_get_specific(parentQueueTag)) { return myUser; } else { __block XMPPUserMemoryStorageObject *result; dispatch_sync(parentQueue, ^{ result = [myUser copy]; }); return result; } } - (XMPPResourceMemoryStorageObject *)myResource { // This is a public method, so it may be invoked on any thread/queue. if (self.parentQueue == NULL) { // Haven't been attached to parent yet return nil; } if (dispatch_get_specific(parentQueueTag)) { return (XMPPResourceMemoryStorageObject *)[myUser resourceForJID:myJID]; } else { __block XMPPResourceMemoryStorageObject *result; dispatch_sync(parentQueue, ^{ XMPPResourceMemoryStorageObject *resource = (XMPPResourceMemoryStorageObject *)[myUser resourceForJID:myJID]; result = [resource copy]; }); return result; } } - (XMPPUserMemoryStorageObject *)userForJID:(XMPPJID *)jid { // This is a public method, so it may be invoked on any thread/queue. if (self.parentQueue == NULL) { // Haven't been attached to parent yet return nil; } if (dispatch_get_specific(parentQueueTag)) { return [self _userForJID:jid]; } else { __block XMPPUserMemoryStorageObject *result; dispatch_sync(parentQueue, ^{ @autoreleasepool { XMPPUserMemoryStorageObject *user = [self _userForJID:jid]; result = [user copy]; }}); return result; } } - (XMPPResourceMemoryStorageObject *)resourceForJID:(XMPPJID *)jid { // This is a public method, so it may be invoked on any thread/queue. if (self.parentQueue == NULL) { // Haven't been attached to parent yet return nil; } if (dispatch_get_specific(parentQueueTag)) { return [self _resourceForJID:jid]; } else { __block XMPPResourceMemoryStorageObject *result; dispatch_sync(parentQueue, ^{ @autoreleasepool { XMPPResourceMemoryStorageObject *resource = [self _resourceForJID:jid]; result = [resource copy]; }}); return result; } } - (NSArray *)sortedUsersByName { // This is a public method, so it may be invoked on any thread/queue. if (self.parentQueue == NULL) { // Haven't been attached to parent yet return nil; } if (dispatch_get_specific(parentQueueTag)) { return [self _sortedUsersByName]; } else { __block NSArray *result; dispatch_sync(parentQueue, ^{ @autoreleasepool { NSArray *temp = [self _sortedUsersByName]; result = [[NSArray alloc] initWithArray:temp copyItems:YES]; }}); return result; } } - (NSArray *)sortedUsersByAvailabilityName { // This is a public method, so it may be invoked on any thread/queue. if (self.parentQueue == NULL) { // Haven't been attached to parent yet return nil; } if (dispatch_get_specific(parentQueueTag)) { return [self _sortedUsersByAvailabilityName]; } else { __block NSArray *result; dispatch_sync(parentQueue, ^{ @autoreleasepool { NSArray *temp = [self _sortedUsersByAvailabilityName]; result = [[NSArray alloc] initWithArray:temp copyItems:YES]; }}); return result; } } - (NSArray *)sortedAvailableUsersByName { // This is a public method, so it may be invoked on any thread/queue. if (self.parentQueue == NULL) { // Haven't been attached to parent yet return nil; } if (dispatch_get_specific(parentQueueTag)) { return [self _sortedAvailableUsersByName]; } else { __block NSArray *result; dispatch_sync(parentQueue, ^{ @autoreleasepool { NSArray *temp = [self _sortedAvailableUsersByName]; result = [[NSArray alloc] initWithArray:temp copyItems:YES]; }}); return result; } } - (NSArray *)sortedUnavailableUsersByName { // This is a public method, so it may be invoked on any thread/queue. if (self.parentQueue == NULL) { // Haven't been attached to parent yet return nil; } if (dispatch_get_specific(parentQueueTag)) { return [self _sortedUnavailableUsersByName]; } else { __block NSArray *result; dispatch_sync(parentQueue, ^{ @autoreleasepool { NSArray *temp = [self _sortedUnavailableUsersByName]; result = [[NSArray alloc] initWithArray:temp copyItems:YES]; }}); return result; } } - (NSArray *)unsortedUsers { // This is a public method, so it may be invoked on any thread/queue. if (self.parentQueue == NULL) { // Haven't been attached to parent yet return nil; } if (dispatch_get_specific(parentQueueTag)) { return [self _unsortedUsers]; } else { __block NSArray *result; dispatch_sync(parentQueue, ^{ @autoreleasepool { NSArray *temp = [self _unsortedUsers]; result = [[NSArray alloc] initWithArray:temp copyItems:YES]; }}); return result; } } - (NSArray *)unsortedAvailableUsers { // This is a public method, so it may be invoked on any thread/queue. if (self.parentQueue == NULL) { // Haven't been attached to parent yet return nil; } if (dispatch_get_specific(parentQueueTag)) { return [self _unsortedAvailableUsers]; } else { __block NSArray *result; dispatch_sync(parentQueue, ^{ @autoreleasepool { NSArray *temp = [self _unsortedAvailableUsers]; result = [[NSArray alloc] initWithArray:temp copyItems:YES]; }}); return result; } } - (NSArray *)unsortedUnavailableUsers { // This is a public method, so it may be invoked on any thread/queue. if (self.parentQueue == NULL) { // Haven't been attached to parent yet return nil; } if (dispatch_get_specific(parentQueueTag)) { return [self _unsortedUnavailableUsers]; } else { __block NSArray *result; dispatch_sync(parentQueue, ^{ @autoreleasepool { NSArray *temp = [self _unsortedUnavailableUsers]; result = [[NSArray alloc] initWithArray:temp copyItems:YES]; }}); return result; } } - (NSArray *)sortedResources:(BOOL)includeResourcesForMyUserExcludingMyself { // This is a public method, so it may be invoked on any thread/queue. if (self.parentQueue == NULL) { // Haven't been attached to parent yet return nil; } if (dispatch_get_specific(parentQueueTag)) { return [self _sortedResources:includeResourcesForMyUserExcludingMyself]; } else { __block NSArray *result; dispatch_sync(parentQueue, ^{ @autoreleasepool { NSArray *temp = [self _sortedResources:includeResourcesForMyUserExcludingMyself]; result = [[NSArray alloc] initWithArray:temp copyItems:YES]; }}); return result; } } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark XMPPRosterStorage Protocol //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - (void)beginRosterPopulationForXMPPStream:(XMPPStream *)stream withVersion:(NSString *)version { XMPPLogTrace(); AssertParentQueue(); isRosterPopulation = YES; myJID = self.parent.xmppStream.myJID; myUser = [[self.userClass alloc] initWithJID:myJID]; } - (void)endRosterPopulationForXMPPStream:(XMPPStream *)stream { XMPPLogTrace(); AssertParentQueue(); isRosterPopulation = NO; [[self multicastDelegate] xmppRosterDidPopulate:self]; [[self multicastDelegate] xmppRosterDidChange:self]; } - (void)handleRosterItem:(NSXMLElement *)item xmppStream:(XMPPStream *)stream { XMPPLogTrace(); AssertParentQueue(); NSString *jidStr = [item attributeStringValueForName:@"jid"]; XMPPJID *jid = [[XMPPJID jidWithString:jidStr] bareJID]; if (isRosterPopulation) { XMPPUserMemoryStorageObject *newUser = (XMPPUserMemoryStorageObject *)[[self.userClass alloc] initWithItem:item]; roster[jid] = newUser; XMPPLogVerbose(@"roster(%lu): %@", (unsigned long)[roster count], roster); } else { NSString *subscription = [item attributeStringValueForName:@"subscription"]; if ([subscription isEqualToString:@"remove"]) { XMPPUserMemoryStorageObject *user = roster[jid]; if (user) { [roster removeObjectForKey:jid]; XMPPLogVerbose(@"roster(%lu): %@", (unsigned long)[roster count], roster); [[self multicastDelegate] xmppRoster:self didRemoveUser:user]; [[self multicastDelegate] xmppRosterDidChange:self]; } } else { XMPPUserMemoryStorageObject *user = roster[jid]; if (user) { [user updateWithItem:item]; XMPPLogVerbose(@"roster(%lu): %@", (unsigned long)[roster count], roster); [[self multicastDelegate] xmppRoster:self didUpdateUser:user]; [[self multicastDelegate] xmppRosterDidChange:self]; } else { XMPPUserMemoryStorageObject *newUser = (XMPPUserMemoryStorageObject *)[[self.userClass alloc] initWithItem:item]; roster[jid] = newUser; XMPPLogVerbose(@"roster(%lu): %@", (unsigned long)[roster count], roster); [[self multicastDelegate] xmppRoster:self didAddUser:newUser]; [[self multicastDelegate] xmppRosterDidChange:self]; } } } } - (void)handlePresence:(XMPPPresence *)presence xmppStream:(XMPPStream *)stream { XMPPLogTrace(); AssertParentQueue(); int change = XMPP_USER_NO_CHANGE; XMPPUserMemoryStorageObject *user = nil; XMPPResourceMemoryStorageObject *resource = nil; XMPPJID *jidKey = [[presence from] bareJID]; user = roster[jidKey]; if (user == nil) { // Not a presence element from anyone in our roster (that we know of). if ([[myJID bareJID] isEqualToJID:jidKey]) { // It's a presence element for our user, either our resource or another resource. user = myUser; } else if([parent allowRosterlessOperation]) { // Unknown user (this is the first time we've encountered them). // This happens if the roster is in rosterlessOperation mode. user = (XMPPUserMemoryStorageObject *)[[self.userClass alloc] initWithJID:jidKey]; roster[jidKey] = user; [[self multicastDelegate] xmppRoster:self didAddUser:user]; [[self multicastDelegate] xmppRosterDidChange:self]; } } change = [user updateWithPresence:presence resourceClass:self.resourceClass andGetResource:&resource]; XMPPLogVerbose(@"roster(%lu): %@", (unsigned long)[roster count], roster); if (change == XMPP_USER_ADDED_RESOURCE) [[self multicastDelegate] xmppRoster:self didAddResource:resource withUser:user]; if (change == XMPP_USER_UPDATED_RESOURCE) [[self multicastDelegate] xmppRoster:self didUpdateResource:resource withUser:user]; if (change == XMPP_USER_REMOVED_RESOURCE) [[self multicastDelegate] xmppRoster:self didRemoveResource:resource withUser:user]; if (change != XMPP_USER_NO_CHANGE) [[self multicastDelegate] xmppRosterDidChange:self]; } - (BOOL)userExistsWithJID:(XMPPJID *)jid xmppStream:(XMPPStream *)stream { XMPPLogTrace(); AssertParentQueue(); XMPPJID *jidKey = [jid bareJID]; XMPPUserMemoryStorageObject *rosterUser = roster[jidKey]; return (rosterUser != nil); } #if TARGET_OS_IPHONE - (void)setPhoto:(UIImage *)photo forUserWithJID:(XMPPJID *)jid xmppStream:(XMPPStream *)stream #else - (void)setPhoto:(NSImage *)photo forUserWithJID:(XMPPJID *)jid xmppStream:(XMPPStream *)stream #endif { XMPPLogTrace(); AssertParentQueue(); XMPPJID *jidKey = [jid bareJID]; XMPPUserMemoryStorageObject *rosterUser = roster[jidKey]; if (rosterUser) { [rosterUser setPhoto:photo]; } } - (void)clearAllResourcesForXMPPStream:(XMPPStream *)stream { XMPPLogTrace(); AssertParentQueue(); for (XMPPUserMemoryStorageObject *user in [roster objectEnumerator]) { [user clearAllResources]; } [[self multicastDelegate] xmppRosterDidChange:self]; } - (void)clearAllUsersAndResourcesForXMPPStream:(XMPPStream *)stream { XMPPLogTrace(); AssertParentQueue(); [roster removeAllObjects]; myUser = nil; [[self multicastDelegate] xmppRosterDidChange:self]; } - (NSArray *)jidsForXMPPStream:(XMPPStream *)stream { XMPPLogTrace(); AssertParentQueue(); NSMutableArray *results = [NSMutableArray array]; for (XMPPUserMemoryStorageObject *user in [roster objectEnumerator]) { [results addObject:[user.jid bareJID]]; } return results; } - (void)getSubscription:(NSString **)subscription ask:(NSString **)ask nickname:(NSString **)nickname groups:(NSArray **)groups forJID:(XMPPJID *)jid xmppStream:(XMPPStream *)stream { XMPPLogTrace(); AssertParentQueue(); XMPPJID *jidKey = [jid bareJID]; XMPPUserMemoryStorageObject *rosterUser = roster[jidKey]; if(rosterUser) { if(subscription) { *subscription = rosterUser.subscription; } if(ask) { *ask = rosterUser.ask; } if(nickname) { *nickname = rosterUser.nickname; } if(groups) { *groups = rosterUser.groups; } } } @end