513 lines
14 KiB
Objective-C
513 lines
14 KiB
Objective-C
#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
|