PNXMPPFramework/Extensions/Roster/MemoryStorage/XMPPUserMemoryStorageObject.m
2016-02-24 16:56:39 +01:00

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