#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: // // NSString *subscription = itemAttributes[@"subscription"]; NSString *ask = itemAttributes[@"ask"]; if ([subscription isEqualToString:@"none"] || [subscription isEqualToString:@"from"]) { if([ask isEqualToString:@"subscribe"]) { return YES; } } return NO; } - (id )primaryResource { return primaryResource; } - (id )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:@"", self, [jid bare]]; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark KVO Compliance methods //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + (NSSet *)keyPathsForValuesAffectingIsOnline { return [NSSet setWithObject:@"primaryResource"]; } + (NSSet *)keyPathsForValuesAffectingAllResources { return [NSSet setWithObject:@"resources"]; } @end