This commit is contained in:
giuseppenuc committed 2016-02-24 16:56:39 +01:00
1 parent d281d765ea
commit 5b074a5176
1261 files changed
+199158 -7303

No files matched your search

@@ -0,0 +1,28 @@
#import <Foundation/Foundation.h>
#import "XMPPResource.h"
@class XMPPJID;
@class XMPPIQ;
@class XMPPPresence;
@interface XMPPResourceMemoryStorageObject : NSObject <XMPPResource, NSCopying, NSSecureCoding>
{
XMPPJID *jid;
XMPPPresence *presence;
NSDate *presenceDate;
}
/* From the XMPPResource protocol
- (XMPPJID *)jid;
- (XMPPPresence *)presence;
- (NSDate *)presenceDate;
- (NSComparisonResult)compare:(id <XMPPResource>)another;
*/
@end
@@ -0,0 +1,206 @@
#import "XMPP.h"
#import "NSXMLElement+XEP_0203.h"
#import "XMPPRosterMemoryStoragePrivate.h"
#if ! __has_feature(objc_arc)
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
#endif
@implementation XMPPResourceMemoryStorageObject
- (id)initWithPresence:(XMPPPresence *)aPresence
{
if((self = [super init]))
{
jid = [aPresence from];
presence = aPresence;
presenceDate = [presence delayedDeliveryDate];
if (presenceDate == nil)
{
presenceDate = [[NSDate alloc] init];
}
}
return self;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Copying
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (id)copyWithZone:(NSZone *)zone
{
// We use [self class] to support subclassing
XMPPResourceMemoryStorageObject *deepCopy = (XMPPResourceMemoryStorageObject *)[[[self class] alloc] init];
deepCopy->jid = [jid copy];
deepCopy->presence = presence; // No need to bother with a copy sicne we don't alter presence
deepCopy->presenceDate = [presenceDate copy];
return deepCopy;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Encoding, Decoding
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#if ! TARGET_OS_IPHONE
- (id)replacementObjectForPortCoder:(NSPortCoder *)encoder
{
if([encoder isBycopy])
return self;
else
return [super replacementObjectForPortCoder:encoder];
// return [NSDistantObject proxyWithLocal:self connection:[encoder connection]];
}
#endif
- (id)initWithCoder:(NSCoder *)coder
{
if ((self = [super init]))
{
if([coder allowsKeyedCoding])
{
if([coder respondsToSelector:@selector(requiresSecureCoding)] &&
[coder requiresSecureCoding])
{
jid = [coder decodeObjectOfClass:[XMPPJID class] forKey:@"jid"];
presence = [coder decodeObjectOfClass:[XMPPPresence class] forKey:@"presence"];
presenceDate = [coder decodeObjectOfClass:[NSDate class] forKey:@"presenceDate"];
}
else
{
jid = [coder decodeObjectForKey:@"jid"];
presence = [coder decodeObjectForKey:@"presence"];
presenceDate = [coder decodeObjectForKey:@"presenceDate"];
}
}
else
{
jid = [coder decodeObject];
presence = [coder decodeObject];
presenceDate = [coder decodeObject];
}
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder
{
if ([coder allowsKeyedCoding])
{
[coder encodeObject:jid forKey:@"jid"];
[coder encodeObject:presence forKey:@"presence"];
[coder encodeObject:presenceDate forKey:@"presenceDate"];
}
else
{
[coder encodeObject:jid];
[coder encodeObject:presence];
[coder encodeObject:presenceDate];
}
}
+ (BOOL) supportsSecureCoding
{
return YES;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Standard Methods
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (XMPPJID *)jid
{
return jid;
}
- (XMPPPresence *)presence
{
return presence;
}
- (NSDate *)presenceDate
{
return presenceDate;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Update Methods
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (void)updateWithPresence:(XMPPPresence *)aPresence
{
presence = aPresence;
presenceDate = [presence delayedDeliveryDate];
if (presenceDate == nil)
{
presenceDate = [[NSDate alloc] init];
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Comparison Methods
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (NSComparisonResult)compare:(id <XMPPResource>)another
{
XMPPPresence *mp = [self presence];
XMPPPresence *ap = [another presence];
int mpp = [mp priority];
int app = [ap priority];
if(mpp < app)
return NSOrderedDescending;
if(mpp > app)
return NSOrderedAscending;
// Priority is the same.
// Determine who is more available based on their show.
int mps = [mp intShow];
int aps = [ap intShow];
if(mps < aps)
return NSOrderedDescending;
if(mps > aps)
return NSOrderedAscending;
// Priority and Show are the same.
// Determine based on who was the last to receive a presence element.
NSDate *mpd = [self presenceDate];
NSDate *apd = [another presenceDate];
return [mpd compare:apd];
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark NSObject Methods
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (NSUInteger)hash
{
return [jid hash];
}
- (BOOL)isEqual:(id)anObject
{
if([anObject isMemberOfClass:[self class]])
{
XMPPResourceMemoryStorageObject *another = (XMPPResourceMemoryStorageObject *)anObject;
return [jid isEqualToJID:[another jid]];
}
return NO;
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<XMPPResource[%p]: %@>", self, [jid full]];
}
@end
@@ -0,0 +1,128 @@
#import <Foundation/Foundation.h>
#import "XMPPRoster.h"
#import "XMPPUserMemoryStorageObject.h"
#import "XMPPResourceMemoryStorageObject.h"
/**
* This class is an example implementation of the XMPPRosterStorage protocol.
* It simply keeps all roster informatin in memory.
*
* You are free to substitute your own storage class.
**/
@interface XMPPRosterMemoryStorage : NSObject <XMPPRosterStorage>
{
#if __has_feature(objc_arc_weak)
__weak XMPPRoster *parent;
#else
__unsafe_unretained XMPPRoster *parent;
#endif
dispatch_queue_t parentQueue;
void *parentQueueTag;
Class userClass;
Class resourceClass;
BOOL isRosterPopulation;
NSMutableDictionary *roster;
XMPPJID *myJID;
XMPPUserMemoryStorageObject *myUser;
}
- (id)init;
@property (readonly) XMPPRoster *parent;
/**
* You can optionally extend the XMPPUserMemoryStorage and XMPPResourceMemoryStorage classes.
* Then just set the classes here, and your subclasses will automatically get used.
**/
@property (readwrite, assign) Class userClass;
@property (readwrite, assign) Class resourceClass;
/**
* The methods below provide access to the roster data.
*
* If invoked from a dispatch queue other than the roster's queue,
* the methods return snapshots (copies) of the roster data.
* These snapshots provide a thread-safe version of the roster data.
* The thread-safety comes from the fact that the copied data will not be altered,
* so it can therefore be used from multiple threads/queues if needed.
**/
- (XMPPUserMemoryStorageObject *)myUser;
- (XMPPResourceMemoryStorageObject *)myResource;
- (XMPPUserMemoryStorageObject *)userForJID:(XMPPJID *)jid;
- (XMPPResourceMemoryStorageObject *)resourceForJID:(XMPPJID *)jid;
- (NSArray *)sortedUsersByName;
- (NSArray *)sortedUsersByAvailabilityName;
- (NSArray *)sortedAvailableUsersByName;
- (NSArray *)sortedUnavailableUsersByName;
- (NSArray *)unsortedUsers;
- (NSArray *)unsortedAvailableUsers;
- (NSArray *)unsortedUnavailableUsers;
- (NSArray *)sortedResources:(BOOL)includeResourcesForMyUserExcludingMyself;
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@protocol XMPPRosterMemoryStorageDelegate
@optional
/**
* The XMPPRosterStorage classes use the same delegate as their parent XMPPRoster.
**/
/**
* Catch-all change notification.
*
* When the roster changes, for any of the reasons listed below, this delegate method fires.
* This method always fires after the more precise delegate methods listed below.
**/
- (void)xmppRosterDidChange:(XMPPRosterMemoryStorage *)sender;
/**
* Notification that the roster has received the roster from the server.
*
* If parent.autoFetchRoster is YES, the roster will automatically be fetched once the user authenticates.
**/
- (void)xmppRosterDidPopulate:(XMPPRosterMemoryStorage *)sender;
/**
* Notifications that the roster has changed.
*
* This includes times when users are added or removed from our roster, or when a nickname is changed,
* including when other resources logged in under the same user account as us make changes to our roster.
*
* This does not include when resources simply go online / offline.
**/
- (void)xmppRoster:(XMPPRosterMemoryStorage *)sender didAddUser:(XMPPUserMemoryStorageObject *)user;
- (void)xmppRoster:(XMPPRosterMemoryStorage *)sender didUpdateUser:(XMPPUserMemoryStorageObject *)user;
- (void)xmppRoster:(XMPPRosterMemoryStorage *)sender didRemoveUser:(XMPPUserMemoryStorageObject *)user;
/**
* Notifications when resources go online / offline.
**/
- (void)xmppRoster:(XMPPRosterMemoryStorage *)sender
didAddResource:(XMPPResourceMemoryStorageObject *)resource
withUser:(XMPPUserMemoryStorageObject *)user;
- (void)xmppRoster:(XMPPRosterMemoryStorage *)sender
didUpdateResource:(XMPPResourceMemoryStorageObject *)resource
withUser:(XMPPUserMemoryStorageObject *)user;
- (void)xmppRoster:(XMPPRosterMemoryStorage *)sender
didRemoveResource:(XMPPResourceMemoryStorageObject *)resource
withUser:(XMPPUserMemoryStorageObject *)user;
@end
@@ -0,0 +1,860 @@
#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 <XMPPRosterMemoryStorageDelegate> *)multicastDelegate
{
return (GCDMulticastDelegate <XMPPRosterMemoryStorageDelegate> *)[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 <XMPPUser> 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 <XMPPUser> 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<XMPPUser> 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<XMPPResource> 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
@@ -0,0 +1,50 @@
#import "XMPPUserMemoryStorageObject.h"
#import "XMPPResourceMemoryStorageObject.h"
/**
* The following methods are designed to be invoked ONLY from
* within the XMPPRosterMemoryStorage implementation.
*
* Warning: XMPPUserMemoryStorage and XMPPResourceMemoryStorage are not explicitly thread-safe.
* Only copies that are no longer being actively
* altered by the XMPPRosterMemoryStorage class should be considered read-only and therefore thread-safe.
**/
#define XMPP_USER_NO_CHANGE 0
#define XMPP_USER_ADDED_RESOURCE 1
#define XMPP_USER_UPDATED_RESOURCE 2
#define XMPP_USER_REMOVED_RESOURCE 3
@interface XMPPUserMemoryStorageObject ()
- (void)commonInit;
- (id)initWithJID:(XMPPJID *)aJid;
- (id)initWithItem:(NSXMLElement *)item;
- (void)updateWithItem:(NSXMLElement *)item;
- (int)updateWithPresence:(XMPPPresence *)presence
resourceClass:(Class)resourceClass
andGetResource:(XMPPResourceMemoryStorageObject **)resourcePtr;
- (void)clearAllResources;
#if TARGET_OS_IPHONE
@property (nonatomic, strong, readwrite) UIImage *photo;
#else
@property (nonatomic, strong, readwrite) NSImage *photo;
#endif
@end
@interface XMPPResourceMemoryStorageObject ()
- (id)initWithPresence:(XMPPPresence *)aPresence;
- (void)updateWithPresence:(XMPPPresence *)presence;
- (XMPPPresence *)presence;
@end
@@ -0,0 +1,82 @@
#import <Foundation/Foundation.h>
#import "XMPPUser.h"
#import "XMPP.h"
#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
#else
#import <Cocoa/Cocoa.h>
#endif
@class XMPPResourceMemoryStorageObject;
@interface XMPPUserMemoryStorageObject : NSObject <XMPPUser, NSCopying, NSSecureCoding>
{
XMPPJID *jid;
NSMutableDictionary *itemAttributes;
NSMutableArray *groups;
NSMutableDictionary *resources;
XMPPResourceMemoryStorageObject *primaryResource;
#if TARGET_OS_IPHONE
UIImage *photo;
#else
NSImage *photo;
#endif
}
/* From the XMPPUser protocol
- (XMPPJID *)jid;
- (NSString *)nickname;
- (BOOL)isOnline;
- (BOOL)isPendingApproval;
- (id <XMPPResource>)primaryResource;
- (id <XMPPResource>)resourceForJID:(XMPPJID *)jid;
- (NSArray *)allResources;
*/
- (NSString *)subscription;
- (NSString *)ask;
/**
* Simple convenience method.
* If a nickname exists for the user, the nickname is returned.
* Otherwise the jid is returned (as a string).
**/
- (NSString *)displayName;
/**
* An array of Group Names.
**/
- (NSArray *)groups;
/**
* If XMPPvCardAvatarModule is included in the framework, the XMPPRoster will automatically integrate with it,
* and we'll save the the user photos after they've been downloaded.
**/
#if TARGET_OS_IPHONE
@property (nonatomic, strong, readonly) UIImage *photo;
#else
@property (nonatomic, strong, readonly) NSImage *photo;
#endif
/**
* Simple comparison methods.
**/
- (NSComparisonResult)compareByName:(XMPPUserMemoryStorageObject *)another;
- (NSComparisonResult)compareByName:(XMPPUserMemoryStorageObject *)another options:(NSStringCompareOptions)mask;
- (NSComparisonResult)compareByAvailabilityName:(XMPPUserMemoryStorageObject *)another;
- (NSComparisonResult)compareByAvailabilityName:(XMPPUserMemoryStorageObject *)another
options:(NSStringCompareOptions)mask;
@end
@@ -0,0 +1,512 @@
#import "XMPP.h"
#import "XMPPRosterMemoryStoragePrivate.h"
#if ! __has_feature(objc_arc)
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
#endif
@interface XMPPUserMemoryStorageObject (PrivateAPI)
- (void)recalculatePrimaryResource;
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@implementation XMPPUserMemoryStorageObject
- (void)commonInit
{
// This method is here to more easily support subclassing.
// That way subclasses can optionally override just commonInit, instead of each individual init method.
//
// If you override this method, don't forget to invoke [super commonInit];
resources = [[NSMutableDictionary alloc] initWithCapacity:1];
}
- (id)initWithJID:(XMPPJID *)aJid
{
if ((self = [super init]))
{
jid = [aJid bareJID];
itemAttributes = [[NSMutableDictionary alloc] initWithCapacity:0];
groups = [[NSMutableArray alloc] initWithCapacity:0];
[self commonInit];
}
return self;
}
- (id)initWithItem:(NSXMLElement *)item
{
if ((self = [super init]))
{
NSString *jidStr = [item attributeStringValueForName:@"jid"];
jid = [[XMPPJID jidWithString:jidStr] bareJID];
itemAttributes = [item attributesAsDictionary];
groups = [[NSMutableArray alloc] initWithCapacity:0];
NSArray *groupElements = [item elementsForName:@"group"];
for (NSXMLElement *groupElement in groupElements) {
NSString *groupName = [groupElement stringValue];
if ([groupName length])
{
[groups addObject:groupName];
}
}
[self commonInit];
}
return self;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Copying
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (id)copyWithZone:(NSZone *)zone
{
// We use [self class] to support subclassing
XMPPUserMemoryStorageObject *deepCopy = (XMPPUserMemoryStorageObject *)[[[self class] alloc] init];
deepCopy->jid = [jid copy];
deepCopy->itemAttributes = [itemAttributes mutableCopy];
deepCopy->groups = [groups mutableCopy];
deepCopy->resources = [[NSMutableDictionary alloc] initWithCapacity:[resources count]];
for (XMPPJID *key in resources)
{
XMPPResourceMemoryStorageObject *resourceCopy = [resources[key] copy];
deepCopy->resources[key] = resourceCopy;
}
[deepCopy recalculatePrimaryResource];
return deepCopy;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Encoding, Decoding
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#if ! TARGET_OS_IPHONE
- (id)replacementObjectForPortCoder:(NSPortCoder *)encoder
{
if ([encoder isBycopy])
return self;
else
return [super replacementObjectForPortCoder:encoder];
// return [NSDistantObject proxyWithLocal:self connection:[encoder connection]];
}
#endif
- (id)initWithCoder:(NSCoder *)coder
{
if ((self = [super init]))
{
if ([coder allowsKeyedCoding])
{
if([coder respondsToSelector:@selector(requiresSecureCoding)] &&
[coder requiresSecureCoding])
{
jid = [coder decodeObjectOfClass:[XMPPJID class] forKey:@"jid"];
itemAttributes = [[coder decodeObjectOfClass:[NSDictionary class] forKey:@"itemAttributes"] mutableCopy];
groups = [[coder decodeObjectOfClass:[NSArray class] forKey:@"groups"] mutableCopy];
#if TARGET_OS_IPHONE
photo = [[UIImage alloc] initWithData:[coder decodeObjectOfClass:[NSData class] forKey:@"photo"]];
#else
photo = [[NSImage alloc] initWithData:[coder decodeObjectOfClass:[NSData class] forKey:@"photo"]];
#endif
resources = [[coder decodeObjectOfClass:[NSDictionary class] forKey:@"resources"] mutableCopy];
primaryResource = [coder decodeObjectOfClass:[XMPPResourceMemoryStorageObject class] forKey:@"primaryResource"];
}
else
{
jid = [coder decodeObjectForKey:@"jid"];
itemAttributes = [[coder decodeObjectForKey:@"itemAttributes"] mutableCopy];
groups = [[coder decodeObjectForKey:@"groups"] mutableCopy];
#if TARGET_OS_IPHONE
photo = [[UIImage alloc] initWithData:[coder decodeObjectForKey:@"photo"]];
#else
photo = [[NSImage alloc] initWithData:[coder decodeObjectForKey:@"photo"]];
#endif
resources = [[coder decodeObjectForKey:@"resources"] mutableCopy];
primaryResource = [coder decodeObjectForKey:@"primaryResource"];
}
}
else
{
jid = [coder decodeObject];
itemAttributes = [[coder decodeObject] mutableCopy];
groups = [[coder decodeObject] mutableCopy];
#if TARGET_OS_IPHONE
photo = [[UIImage alloc] initWithData:[coder decodeObject]];
#else
photo = [[NSImage alloc] initWithData:[coder decodeObject]];
#endif
resources = [[coder decodeObject] mutableCopy];
primaryResource = [coder decodeObject];
}
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder
{
if ([coder allowsKeyedCoding])
{
[coder encodeObject:jid forKey:@"jid"];
[coder encodeObject:itemAttributes forKey:@"itemAttributes"];
[coder encodeObject:groups forKey:@"groups"];
#if TARGET_OS_IPHONE
[coder encodeObject:UIImagePNGRepresentation(photo) forKey:@"photo"];
#else
[coder encodeObject:[photo TIFFRepresentation] forKey:@"photo"];
#endif
[coder encodeObject:resources forKey:@"resources"];
[coder encodeObject:primaryResource forKey:@"primaryResource"];
}
else
{
[coder encodeObject:jid];
[coder encodeObject:itemAttributes];
[coder encodeObject:groups];
#if TARGET_OS_IPHONE
[coder encodeObject:UIImagePNGRepresentation(photo)];
#else
[coder encodeObject:[photo TIFFRepresentation]];
#endif
[coder encodeObject:resources];
[coder encodeObject:primaryResource];
}
}
+ (BOOL) supportsSecureCoding
{
return YES;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Standard Methods
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@synthesize photo;
- (XMPPJID *)jid
{
return jid;
}
- (NSString *)nickname
{
return (NSString *) itemAttributes[@"name"];
}
- (NSString *)subscription
{
return (NSString *) itemAttributes[@"subscription"];
}
- (NSString *)ask
{
return (NSString *) itemAttributes[@"ask"];
}
- (NSString *)displayName
{
NSString *nickname = [self nickname];
if (nickname)
return nickname;
else
return [jid bare];
}
- (NSArray *)groups
{
return [groups copy];
}
- (BOOL)isOnline
{
return (primaryResource != nil);
}
- (BOOL)isPendingApproval
{
// Either of the following mean we're waiting to have our presence subscription approved:
// <item ask='subscribe' subscription='none' jid='robbiehanson@deusty.com'/>
// <item ask='subscribe' subscription='from' jid='robbiehanson@deusty.com'/>
NSString *subscription = itemAttributes[@"subscription"];
NSString *ask = itemAttributes[@"ask"];
if ([subscription isEqualToString:@"none"] || [subscription isEqualToString:@"from"])
{
if([ask isEqualToString:@"subscribe"])
{
return YES;
}
}
return NO;
}
- (id <XMPPResource>)primaryResource
{
return primaryResource;
}
- (id <XMPPResource>)resourceForJID:(XMPPJID *)aJid
{
return resources[aJid];
}
- (NSArray *)allResources
{
return [resources allValues];
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Hooks
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (void)didAddResource:(XMPPResourceMemoryStorageObject *)resource withPresence:(XMPPPresence *)presence
{
// Override / customization hook
}
- (void)willUpdateResource:(XMPPResourceMemoryStorageObject *)resource withPresence:(XMPPPresence *)presence
{
// Override / customization hook
}
- (void)didUpdateResource:(XMPPResourceMemoryStorageObject *)resource withPresence:(XMPPPresence *)presence
{
// Override / customization hook
}
- (void)didRemoveResource:(XMPPResourceMemoryStorageObject *)resource withPresence:(XMPPPresence *)presence
{
// Override / customization hook
}
- (void)recalculatePrimaryResource
{
// Override me to customize how the primary resource is chosen.
//
// This method uses the [XMPPResourceMemoryStorage compare:] method to sort the resources,
// and properly supports negative (bot) priorities.
primaryResource = nil;
NSArray *sortedResources = [[self allResources] sortedArrayUsingSelector:@selector(compare:)];
if ([sortedResources count] > 0)
{
XMPPResourceMemoryStorageObject *possiblePrimary = sortedResources[0];
// Primary resource must have a non-negative priority
if ([[possiblePrimary presence] priority] >= 0)
{
primaryResource = possiblePrimary;
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Update Methods
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (void)clearAllResources
{
[resources removeAllObjects];
primaryResource = nil;
}
- (void)updateWithItem:(NSXMLElement *)item
{
[itemAttributes removeAllObjects];
for (NSXMLNode *node in [item attributes])
{
NSString *key = [node name];
NSString *value = [node stringValue];
itemAttributes[key] = value;
}
[groups removeAllObjects];
NSArray *groupElements = [item elementsForName:@"group"];
for (NSXMLElement *groupElement in groupElements) {
NSString *groupName = [groupElement stringValue];
if ([groupName length])
{
[groups addObject:groupName];
}
}
}
- (int)updateWithPresence:(XMPPPresence *)presence
resourceClass:(Class)resourceClass
andGetResource:(XMPPResourceMemoryStorageObject **)resourcePtr
{
int result = XMPP_USER_NO_CHANGE;
XMPPResourceMemoryStorageObject *resource;
XMPPJID *key = [presence from];
NSString *presenceType = [presence type];
if ([presenceType isEqualToString:@"unavailable"] || [presenceType isEqualToString:@"error"])
{
resource = resources[key];
if (resource)
{
[resources removeObjectForKey:key];
[self didRemoveResource:resource withPresence:presence];
result = XMPP_USER_REMOVED_RESOURCE;
}
}
else
{
resource = resources[key];
if (resource)
{
[self willUpdateResource:resource withPresence:presence];
[resource updateWithPresence:presence];
[self didUpdateResource:resource withPresence:presence];
result = XMPP_USER_UPDATED_RESOURCE;
}
else
{
resource = (XMPPResourceMemoryStorageObject *)[[resourceClass alloc] initWithPresence:presence];
resources[key] = resource;
[self didAddResource:resource withPresence:presence];
result = XMPP_USER_ADDED_RESOURCE;
}
}
[self recalculatePrimaryResource];
if (resourcePtr)
*resourcePtr = resource;
return result;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Comparisons
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Returns the result of invoking compareByName:options: with no options.
**/
- (NSComparisonResult)compareByName:(XMPPUserMemoryStorageObject *)another
{
return [self compareByName:another options:0];
}
/**
* This method compares the two users according to their display name.
*
* Options for the search — you can combine any of the following using a C bitwise OR operator:
* NSCaseInsensitiveSearch, NSLiteralSearch, NSNumericSearch.
* See "String Programming Guide for Cocoa" for details on these options.
**/
- (NSComparisonResult)compareByName:(XMPPUserMemoryStorageObject *)another options:(NSStringCompareOptions)mask
{
NSString *myName = [self displayName];
NSString *theirName = [another displayName];
return [myName compare:theirName options:mask];
}
/**
* Returns the result of invoking compareByAvailabilityName:options: with no options.
**/
- (NSComparisonResult)compareByAvailabilityName:(XMPPUserMemoryStorageObject *)another
{
return [self compareByAvailabilityName:another options:0];
}
/**
* This method compares the two users according to availability first, and then display name.
* Thus available users come before unavailable users.
* If both users are available, or both users are not available,
* this method follows the same functionality as the compareByName:options: as documented above.
**/
- (NSComparisonResult)compareByAvailabilityName:(XMPPUserMemoryStorageObject *)another
options:(NSStringCompareOptions)mask
{
if ([self isOnline])
{
if ([another isOnline])
return [self compareByName:another options:mask];
else
return NSOrderedAscending;
}
else
{
if ([another isOnline])
return NSOrderedDescending;
else
return [self compareByName:another options:mask];
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark NSObject Methods
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (NSUInteger)hash
{
return [jid hash];
}
- (BOOL)isEqual:(id)anObject
{
if ([anObject isMemberOfClass:[self class]])
{
XMPPUserMemoryStorageObject *another = (XMPPUserMemoryStorageObject *)anObject;
return [jid isEqualToJID:[another jid]];
}
return NO;
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<XMPPUser[%p]: %@>", self, [jid bare]];
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark KVO Compliance methods
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ (NSSet *)keyPathsForValuesAffectingIsOnline
{
return [NSSet setWithObject:@"primaryResource"];
}
+ (NSSet *)keyPathsForValuesAffectingAllResources {
return [NSSet setWithObject:@"resources"];
}
@end