- Update
This commit is contained in:
1 parent
d281d765ea
commit
5b074a5176
1261 files changed
+199158
-7303
No files matched your search
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// XMPPGroupCoreDataStorageObject.h
|
||||
//
|
||||
// Created by Eric Chamberlain on 3/20/11.
|
||||
// Copyright (c) 2011 RF.com. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <CoreData/CoreData.h>
|
||||
|
||||
|
||||
@class XMPPUserCoreDataStorageObject;
|
||||
|
||||
|
||||
@interface XMPPGroupCoreDataStorageObject : NSManagedObject {
|
||||
@private
|
||||
}
|
||||
|
||||
@property (nonatomic, strong) NSString * name;
|
||||
@property (nonatomic, strong) NSSet* users;
|
||||
|
||||
+ (void)clearEmptyGroupsInManagedObjectContext:(NSManagedObjectContext *)moc;
|
||||
|
||||
+ (id)fetchOrInsertGroupName:(NSString *)groupName inManagedObjectContext:(NSManagedObjectContext *)moc;
|
||||
|
||||
+ (id)insertGroupName:(NSString *)groupName inManagedObjectContext:(NSManagedObjectContext *)moc;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,115 @@
|
||||
//
|
||||
// XMPPGroupCoreDataStorageObject.m
|
||||
//
|
||||
// Created by Eric Chamberlain on 3/20/11.
|
||||
// Copyright (c) 2011 RF.com. All rights reserved.
|
||||
//
|
||||
|
||||
#if ! __has_feature(objc_arc)
|
||||
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
|
||||
#endif
|
||||
|
||||
#import "XMPPGroupCoreDataStorageObject.h"
|
||||
#import "XMPPUserCoreDataStorageObject.h"
|
||||
|
||||
|
||||
@implementation XMPPGroupCoreDataStorageObject
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Public
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
+ (void)clearEmptyGroupsInManagedObjectContext:(NSManagedObjectContext *)moc
|
||||
{
|
||||
NSEntityDescription *entity = [NSEntityDescription entityForName:NSStringFromClass([self class])
|
||||
inManagedObjectContext:moc];
|
||||
|
||||
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"users.@count == 0"];
|
||||
|
||||
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
|
||||
[fetchRequest setEntity:entity];
|
||||
[fetchRequest setPredicate:predicate];
|
||||
[fetchRequest setIncludesPendingChanges:YES];
|
||||
|
||||
NSArray *results = [moc executeFetchRequest:fetchRequest error:nil];
|
||||
|
||||
|
||||
for (NSManagedObject *group in results) {
|
||||
[moc deleteObject:group];
|
||||
}
|
||||
}
|
||||
|
||||
+ (id)insertGroupName:(NSString *)groupName inManagedObjectContext:(NSManagedObjectContext *)moc
|
||||
{
|
||||
if (groupName == nil) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
XMPPGroupCoreDataStorageObject *newGroup = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([self class])
|
||||
inManagedObjectContext:moc];
|
||||
newGroup.name = groupName;
|
||||
|
||||
return newGroup;
|
||||
}
|
||||
|
||||
|
||||
+ (id)fetchOrInsertGroupName:(NSString *)groupName inManagedObjectContext:(NSManagedObjectContext *)moc {
|
||||
if (groupName == nil) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSEntityDescription *entity = [NSEntityDescription entityForName:NSStringFromClass([self class])
|
||||
inManagedObjectContext:moc];
|
||||
|
||||
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name == %@", groupName];
|
||||
|
||||
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
|
||||
[fetchRequest setEntity:entity];
|
||||
[fetchRequest setPredicate:predicate];
|
||||
[fetchRequest setIncludesPendingChanges:YES];
|
||||
[fetchRequest setFetchLimit:1];
|
||||
|
||||
NSArray *results = [moc executeFetchRequest:fetchRequest error:nil];
|
||||
|
||||
|
||||
if ([results count] > 0) {
|
||||
return results[0];
|
||||
}
|
||||
|
||||
return [self insertGroupName:groupName inManagedObjectContext:moc];
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Accessors
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@dynamic name;
|
||||
@dynamic users;
|
||||
|
||||
- (void)addUsersObject:(XMPPUserCoreDataStorageObject *)value {
|
||||
NSSet *changedObjects = [[NSSet alloc] initWithObjects:&value count:1];
|
||||
[self willChangeValueForKey:@"users" withSetMutation:NSKeyValueUnionSetMutation usingObjects:changedObjects];
|
||||
[[self primitiveValueForKey:@"users"] addObject:value];
|
||||
[self didChangeValueForKey:@"users" withSetMutation:NSKeyValueUnionSetMutation usingObjects:changedObjects];
|
||||
}
|
||||
|
||||
- (void)removeUsersObject:(XMPPUserCoreDataStorageObject *)value {
|
||||
NSSet *changedObjects = [[NSSet alloc] initWithObjects:&value count:1];
|
||||
[self willChangeValueForKey:@"users" withSetMutation:NSKeyValueMinusSetMutation usingObjects:changedObjects];
|
||||
[[self primitiveValueForKey:@"users"] removeObject:value];
|
||||
[self didChangeValueForKey:@"users" withSetMutation:NSKeyValueMinusSetMutation usingObjects:changedObjects];
|
||||
}
|
||||
|
||||
- (void)addUsers:(NSSet *)value {
|
||||
[self willChangeValueForKey:@"users" withSetMutation:NSKeyValueUnionSetMutation usingObjects:value];
|
||||
[[self primitiveValueForKey:@"users"] unionSet:value];
|
||||
[self didChangeValueForKey:@"users" withSetMutation:NSKeyValueUnionSetMutation usingObjects:value];
|
||||
}
|
||||
|
||||
- (void)removeUsers:(NSSet *)value {
|
||||
[self willChangeValueForKey:@"users" withSetMutation:NSKeyValueMinusSetMutation usingObjects:value];
|
||||
[[self primitiveValueForKey:@"users"] minusSet:value];
|
||||
[self didChangeValueForKey:@"users" withSetMutation:NSKeyValueMinusSetMutation usingObjects:value];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,42 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <CoreData/CoreData.h>
|
||||
#import "XMPPResource.h"
|
||||
|
||||
@class XMPPStream;
|
||||
@class XMPPPresence;
|
||||
@class XMPPUserCoreDataStorageObject;
|
||||
|
||||
|
||||
@interface XMPPResourceCoreDataStorageObject : NSManagedObject <XMPPResource>
|
||||
|
||||
@property (nonatomic, strong) XMPPJID *jid;
|
||||
@property (nonatomic, strong) XMPPPresence *presence;
|
||||
|
||||
@property (nonatomic, assign) int priority;
|
||||
@property (nonatomic, assign) int intShow;
|
||||
|
||||
@property (nonatomic, strong) NSString * jidStr;
|
||||
@property (nonatomic, strong) NSString * presenceStr;
|
||||
|
||||
@property (nonatomic, strong) NSString * streamBareJidStr;
|
||||
|
||||
@property (nonatomic, strong) NSString * type;
|
||||
@property (nonatomic, strong) NSString * show;
|
||||
@property (nonatomic, strong) NSString * status;
|
||||
|
||||
@property (nonatomic, strong) NSDate * presenceDate;
|
||||
|
||||
@property (nonatomic, strong) NSNumber * priorityNum;
|
||||
@property (nonatomic, strong) NSNumber * showNum;
|
||||
|
||||
@property (nonatomic, strong) XMPPUserCoreDataStorageObject * user;
|
||||
|
||||
+ (id)insertInManagedObjectContext:(NSManagedObjectContext *)moc
|
||||
withPresence:(XMPPPresence *)presence
|
||||
streamBareJidStr:(NSString *)streamBareJidStr;
|
||||
|
||||
- (void)updateWithPresence:(XMPPPresence *)presence;
|
||||
|
||||
- (NSComparisonResult)compare:(id <XMPPResource>)another;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,217 @@
|
||||
#import "XMPP.h"
|
||||
#import "XMPPLogging.h"
|
||||
#import "XMPPRosterCoreDataStorage.h"
|
||||
#import "XMPPUserCoreDataStorageObject.h"
|
||||
#import "XMPPResourceCoreDataStorageObject.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;
|
||||
#else
|
||||
static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN;
|
||||
#endif
|
||||
|
||||
@interface XMPPResourceCoreDataStorageObject (CoreDataGeneratedPrimitiveAccessors)
|
||||
- (NSDate *)primitivePresenceDate;
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@implementation XMPPResourceCoreDataStorageObject
|
||||
|
||||
@dynamic jid; // Implementation below
|
||||
@dynamic presence; // Implementation below
|
||||
@dynamic priority; // Implementation below
|
||||
@dynamic intShow; // Implementation below
|
||||
|
||||
@dynamic jidStr;
|
||||
@dynamic presenceStr;
|
||||
|
||||
@dynamic streamBareJidStr;
|
||||
|
||||
@dynamic type;
|
||||
@dynamic show;
|
||||
@dynamic status;
|
||||
|
||||
@dynamic presenceDate;
|
||||
|
||||
@dynamic priorityNum;
|
||||
@dynamic showNum;
|
||||
|
||||
@dynamic user;
|
||||
|
||||
- (XMPPJID *)jid
|
||||
{
|
||||
return [XMPPJID jidWithString:[self jidStr]];
|
||||
}
|
||||
|
||||
- (void)setJid:(XMPPJID *)jid
|
||||
{
|
||||
self.jidStr = [jid full];
|
||||
}
|
||||
|
||||
- (XMPPPresence *)presence
|
||||
{
|
||||
[self willAccessValueForKey:@"presence"];
|
||||
XMPPPresence *presence = [self primitiveValueForKey:@"presence"];
|
||||
[self didAccessValueForKey:@"presence"];
|
||||
|
||||
if (presence == nil)
|
||||
{
|
||||
NSString *presenceStr = self.presenceStr;
|
||||
if (presenceStr != nil)
|
||||
{
|
||||
NSXMLElement *element = [[NSXMLElement alloc] initWithXMLString:presenceStr error:nil];
|
||||
|
||||
presence = [XMPPPresence presenceFromElement:element];
|
||||
[self setPrimitiveValue:presence forKey:@"presence"];
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return presence;
|
||||
}
|
||||
|
||||
- (void)setPresence:(XMPPPresence *)newPresence
|
||||
{
|
||||
[self willChangeValueForKey:@"presence"];
|
||||
[self setPrimitiveValue:newPresence forKey:@"presence"];
|
||||
[self didChangeValueForKey:@"presence"];
|
||||
|
||||
self.presenceStr = [newPresence compactXMLString];
|
||||
}
|
||||
|
||||
- (int)priority
|
||||
{
|
||||
return [[self priorityNum] intValue];
|
||||
}
|
||||
|
||||
- (void)setPriority:(int)priority
|
||||
{
|
||||
self.priorityNum = @(priority);
|
||||
}
|
||||
|
||||
- (int)intShow
|
||||
{
|
||||
return [[self showNum] intValue];
|
||||
}
|
||||
|
||||
- (void)setIntShow:(int)intShow
|
||||
{
|
||||
self.showNum = @(intShow);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Compiler Workarounds
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#if TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR
|
||||
|
||||
/**
|
||||
* This method is here to quiet the compiler.
|
||||
* Without it the compiler complains that this method is not implemented as required by the protocol.
|
||||
* This only seems to be a problem when compiling for the device.
|
||||
**/
|
||||
- (NSDate *)presenceDate
|
||||
{
|
||||
NSDate * tmpValue;
|
||||
|
||||
[self willAccessValueForKey:@"presenceDate"];
|
||||
tmpValue = [self primitivePresenceDate];
|
||||
[self didAccessValueForKey:@"presenceDate"];
|
||||
|
||||
return tmpValue;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Creation & Updates
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
+ (id)insertInManagedObjectContext:(NSManagedObjectContext *)moc
|
||||
withPresence:(XMPPPresence *)presence
|
||||
streamBareJidStr:(NSString *)streamBareJidStr
|
||||
{
|
||||
XMPPJID *jid = [presence from];
|
||||
|
||||
if (jid == nil)
|
||||
{
|
||||
XMPPLogWarn(@"%@: %@ - Invalid presence (missing or invalid jid): %@", [self class], THIS_METHOD, presence);
|
||||
return nil;
|
||||
}
|
||||
|
||||
XMPPResourceCoreDataStorageObject *newResource;
|
||||
newResource = [NSEntityDescription insertNewObjectForEntityForName:@"XMPPResourceCoreDataStorageObject"
|
||||
inManagedObjectContext:moc];
|
||||
|
||||
newResource.streamBareJidStr = streamBareJidStr;
|
||||
|
||||
[newResource updateWithPresence:presence];
|
||||
|
||||
return newResource;
|
||||
}
|
||||
|
||||
- (void)updateWithPresence:(XMPPPresence *)presence
|
||||
{
|
||||
XMPPJID *jid = [presence from];
|
||||
|
||||
if (jid == nil)
|
||||
{
|
||||
XMPPLogWarn(@"%@: %@ - Invalid presence (missing or invalid jid): %@", [self class], THIS_METHOD, presence);
|
||||
return;
|
||||
}
|
||||
|
||||
self.jid = jid;
|
||||
self.presence = presence;
|
||||
|
||||
self.priority = [presence priority];
|
||||
self.intShow = [presence intShow];
|
||||
|
||||
self.type = [presence type];
|
||||
self.show = [presence show];
|
||||
self.status = [presence status];
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Comparing
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (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];
|
||||
}
|
||||
|
||||
@end
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,75 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <CoreData/CoreData.h>
|
||||
|
||||
#import "XMPPRoster.h"
|
||||
#import "XMPPCoreDataStorage.h"
|
||||
#import "XMPPUserCoreDataStorageObject.h"
|
||||
#import "XMPPGroupCoreDataStorageObject.h"
|
||||
#import "XMPPResourceCoreDataStorageObject.h"
|
||||
|
||||
/**
|
||||
* This class is an example implementation of XMPPRosterStorage using core data.
|
||||
* You are free to substitute your own roster storage class.
|
||||
**/
|
||||
|
||||
@interface XMPPRosterCoreDataStorage : XMPPCoreDataStorage <XMPPRosterStorage>
|
||||
{
|
||||
// Inherits protected variables from XMPPCoreDataStorage
|
||||
|
||||
#if __has_feature(objc_arc_weak)
|
||||
__weak XMPPRoster *parent;
|
||||
#else
|
||||
__unsafe_unretained XMPPRoster *parent;
|
||||
#endif
|
||||
dispatch_queue_t parentQueue;
|
||||
void *parentQueueTag;
|
||||
|
||||
NSMutableSet *rosterPopulationSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to get an instance with the default database name.
|
||||
*
|
||||
* IMPORTANT:
|
||||
* You are NOT required to use the sharedInstance.
|
||||
*
|
||||
* If your application uses multiple xmppStreams, and you use a sharedInstance of this class,
|
||||
* then all of your streams share the same database store. You might get better performance if you create
|
||||
* multiple instances of this class instead (using different database filenames), as this way you can have
|
||||
* concurrent writes to multiple databases.
|
||||
**/
|
||||
+ (instancetype)sharedInstance;
|
||||
|
||||
|
||||
/* Inherited from XMPPCoreDataStorage
|
||||
* Please see the XMPPCoreDataStorage header file for extensive documentation.
|
||||
|
||||
- (id)initWithDatabaseFilename:(NSString *)databaseFileName storeOptions:(NSDictionary *)storeOptions;
|
||||
- (id)initWithInMemoryStore;
|
||||
|
||||
@property (readonly) NSString *databaseFileName;
|
||||
|
||||
@property (readwrite) NSUInteger saveThreshold;
|
||||
|
||||
@property (readonly) NSManagedObjectModel *managedObjectModel;
|
||||
@property (readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator;
|
||||
|
||||
@property (readonly) NSManagedObjectContext *mainThreadManagedObjectContext;
|
||||
|
||||
*/
|
||||
|
||||
- (XMPPUserCoreDataStorageObject *)myUserForXMPPStream:(XMPPStream *)stream
|
||||
managedObjectContext:(NSManagedObjectContext *)moc;
|
||||
|
||||
- (XMPPResourceCoreDataStorageObject *)myResourceForXMPPStream:(XMPPStream *)stream
|
||||
managedObjectContext:(NSManagedObjectContext *)moc;
|
||||
|
||||
- (XMPPUserCoreDataStorageObject *)userForJID:(XMPPJID *)jid
|
||||
xmppStream:(XMPPStream *)stream
|
||||
managedObjectContext:(NSManagedObjectContext *)moc;
|
||||
|
||||
- (XMPPResourceCoreDataStorageObject *)resourceForJID:(XMPPJID *)jid
|
||||
xmppStream:(XMPPStream *)stream
|
||||
managedObjectContext:(NSManagedObjectContext *)moc;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,575 @@
|
||||
#import "XMPPRosterCoreDataStorage.h"
|
||||
#import "XMPPGroupCoreDataStorageObject.h"
|
||||
#import "XMPPUserCoreDataStorageObject.h"
|
||||
#import "XMPPResourceCoreDataStorageObject.h"
|
||||
#import "XMPPRosterPrivate.h"
|
||||
#import "XMPPCoreDataStorageProtected.h"
|
||||
#import "XMPP.h"
|
||||
#import "XMPPLogging.h"
|
||||
#import "NSNumber+XMPP.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_INFO; // | XMPP_LOG_FLAG_TRACE;
|
||||
#else
|
||||
static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN;
|
||||
#endif
|
||||
|
||||
#define AssertPrivateQueue() \
|
||||
NSAssert(dispatch_get_specific(storageQueueTag), @"Private method: MUST run on storageQueue");
|
||||
|
||||
|
||||
@implementation XMPPRosterCoreDataStorage
|
||||
|
||||
static XMPPRosterCoreDataStorage *sharedInstance;
|
||||
|
||||
+ (instancetype)sharedInstance
|
||||
{
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
|
||||
sharedInstance = [[XMPPRosterCoreDataStorage alloc] initWithDatabaseFilename:nil storeOptions:nil];
|
||||
});
|
||||
|
||||
return sharedInstance;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Setup
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (void)commonInit
|
||||
{
|
||||
XMPPLogTrace();
|
||||
[super commonInit];
|
||||
|
||||
// This method is invoked by all public init methods of the superclass
|
||||
autoRemovePreviousDatabaseFile = YES;
|
||||
autoRecreateDatabaseFile = YES;
|
||||
|
||||
rosterPopulationSet = [[NSMutableSet alloc] init];
|
||||
}
|
||||
|
||||
- (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 Utilities
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (void)_clearAllResourcesForXMPPStream:(XMPPStream *)stream
|
||||
{
|
||||
XMPPLogTrace();
|
||||
AssertPrivateQueue();
|
||||
|
||||
NSManagedObjectContext *moc = [self managedObjectContext];
|
||||
|
||||
NSEntityDescription *entity = [NSEntityDescription entityForName:@"XMPPResourceCoreDataStorageObject"
|
||||
inManagedObjectContext:moc];
|
||||
|
||||
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
|
||||
[fetchRequest setEntity:entity];
|
||||
[fetchRequest setFetchBatchSize:saveThreshold];
|
||||
|
||||
if (stream)
|
||||
{
|
||||
NSPredicate *predicate;
|
||||
predicate = [NSPredicate predicateWithFormat:@"streamBareJidStr == %@",
|
||||
[[self myJIDForXMPPStream:stream] bare]];
|
||||
|
||||
[fetchRequest setPredicate:predicate];
|
||||
}
|
||||
|
||||
NSArray *allResources = [moc executeFetchRequest:fetchRequest error:nil];
|
||||
|
||||
NSUInteger unsavedCount = [self numberOfUnsavedChanges];
|
||||
|
||||
for (XMPPResourceCoreDataStorageObject *resource in allResources)
|
||||
{
|
||||
XMPPUserCoreDataStorageObject *user = resource.user;
|
||||
[moc deleteObject:resource];
|
||||
[user recalculatePrimaryResource];
|
||||
|
||||
if (++unsavedCount >= saveThreshold)
|
||||
{
|
||||
[self save];
|
||||
unsavedCount = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Overrides
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (void)didCreateManagedObjectContext
|
||||
{
|
||||
// This method is overriden from the XMPPCoreDataStore superclass.
|
||||
// From the documentation:
|
||||
//
|
||||
// Override me, if needed, to provide customized behavior.
|
||||
//
|
||||
// For example, you may want to perform cleanup of any non-persistent data before you start using the database.
|
||||
//
|
||||
// The default implementation does nothing.
|
||||
|
||||
|
||||
// Reserved for future use (directory versioning).
|
||||
// Perhaps invoke [self _clearAllResourcesForXMPPStream:nil] ?
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Public API
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (XMPPUserCoreDataStorageObject *)myUserForXMPPStream:(XMPPStream *)stream
|
||||
managedObjectContext:(NSManagedObjectContext *)moc
|
||||
{
|
||||
// This is a public method, so it may be invoked on any thread/queue.
|
||||
|
||||
XMPPLogTrace();
|
||||
|
||||
XMPPJID *myJID = stream.myJID;
|
||||
if (myJID == nil)
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
return [self userForJID:myJID xmppStream:stream managedObjectContext:moc];
|
||||
}
|
||||
|
||||
- (XMPPResourceCoreDataStorageObject *)myResourceForXMPPStream:(XMPPStream *)stream
|
||||
managedObjectContext:(NSManagedObjectContext *)moc
|
||||
{
|
||||
// This is a public method, so it may be invoked on any thread/queue.
|
||||
|
||||
XMPPLogTrace();
|
||||
|
||||
XMPPJID *myJID = stream.myJID;
|
||||
if (myJID == nil)
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
return [self resourceForJID:myJID xmppStream:stream managedObjectContext:moc];
|
||||
}
|
||||
|
||||
- (XMPPUserCoreDataStorageObject *)userForJID:(XMPPJID *)jid
|
||||
xmppStream:(XMPPStream *)stream
|
||||
managedObjectContext:(NSManagedObjectContext *)moc
|
||||
{
|
||||
// This is a public method, so it may be invoked on any thread/queue.
|
||||
|
||||
XMPPLogTrace();
|
||||
|
||||
if (jid == nil) return nil;
|
||||
if (moc == nil) return nil;
|
||||
|
||||
NSString *bareJIDStr = [jid bare];
|
||||
|
||||
NSEntityDescription *entity = [NSEntityDescription entityForName:@"XMPPUserCoreDataStorageObject"
|
||||
inManagedObjectContext:moc];
|
||||
|
||||
NSPredicate *predicate;
|
||||
if (stream == nil)
|
||||
predicate = [NSPredicate predicateWithFormat:@"jidStr == %@", bareJIDStr];
|
||||
else
|
||||
predicate = [NSPredicate predicateWithFormat:@"jidStr == %@ AND streamBareJidStr == %@",
|
||||
bareJIDStr, [[self myJIDForXMPPStream:stream] bare]];
|
||||
|
||||
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
|
||||
[fetchRequest setEntity:entity];
|
||||
[fetchRequest setPredicate:predicate];
|
||||
[fetchRequest setIncludesPendingChanges:YES];
|
||||
[fetchRequest setFetchLimit:1];
|
||||
|
||||
NSArray *results = [moc executeFetchRequest:fetchRequest error:nil];
|
||||
|
||||
return (XMPPUserCoreDataStorageObject *)[results lastObject];
|
||||
}
|
||||
|
||||
- (XMPPResourceCoreDataStorageObject *)resourceForJID:(XMPPJID *)jid
|
||||
xmppStream:(XMPPStream *)stream
|
||||
managedObjectContext:(NSManagedObjectContext *)moc
|
||||
{
|
||||
// This is a public method, so it may be invoked on any thread/queue.
|
||||
|
||||
XMPPLogTrace();
|
||||
|
||||
if (jid == nil) return nil;
|
||||
if (moc == nil) return nil;
|
||||
|
||||
NSString *fullJIDStr = [jid full];
|
||||
|
||||
NSEntityDescription *entity = [NSEntityDescription entityForName:@"XMPPResourceCoreDataStorageObject"
|
||||
inManagedObjectContext:moc];
|
||||
|
||||
NSPredicate *predicate;
|
||||
if (stream == nil)
|
||||
predicate = [NSPredicate predicateWithFormat:@"jidStr == %@", fullJIDStr];
|
||||
else
|
||||
predicate = [NSPredicate predicateWithFormat:@"jidStr == %@ AND streamBareJidStr == %@",
|
||||
fullJIDStr, [[self myJIDForXMPPStream:stream] bare]];
|
||||
|
||||
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
|
||||
[fetchRequest setEntity:entity];
|
||||
[fetchRequest setPredicate:predicate];
|
||||
[fetchRequest setIncludesPendingChanges:YES];
|
||||
[fetchRequest setFetchLimit:1];
|
||||
|
||||
NSArray *results = [moc executeFetchRequest:fetchRequest error:nil];
|
||||
|
||||
return (XMPPResourceCoreDataStorageObject *)[results lastObject];
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Protocol Private API
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (void)beginRosterPopulationForXMPPStream:(XMPPStream *)stream withVersion:(NSString *)version
|
||||
{
|
||||
XMPPLogTrace();
|
||||
|
||||
[self scheduleBlock:^{
|
||||
|
||||
[rosterPopulationSet addObject:[NSNumber xmpp_numberWithPtr:(__bridge void *)stream]];
|
||||
|
||||
// Clear anything already in the roster core data store.
|
||||
//
|
||||
// Note: Deleting a user will delete all associated resources
|
||||
// because of the cascade rule in our core data model.
|
||||
|
||||
NSManagedObjectContext *moc = [self managedObjectContext];
|
||||
|
||||
NSEntityDescription *entity = [NSEntityDescription entityForName:@"XMPPUserCoreDataStorageObject"
|
||||
inManagedObjectContext:moc];
|
||||
|
||||
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
|
||||
[fetchRequest setEntity:entity];
|
||||
[fetchRequest setFetchBatchSize:saveThreshold];
|
||||
|
||||
if (stream)
|
||||
{
|
||||
NSPredicate *predicate;
|
||||
predicate = [NSPredicate predicateWithFormat:@"streamBareJidStr == %@",
|
||||
[[self myJIDForXMPPStream:stream] bare]];
|
||||
|
||||
[fetchRequest setPredicate:predicate];
|
||||
}
|
||||
|
||||
NSArray *allUsers = [moc executeFetchRequest:fetchRequest error:nil];
|
||||
|
||||
for (XMPPUserCoreDataStorageObject *user in allUsers)
|
||||
{
|
||||
[moc deleteObject:user];
|
||||
}
|
||||
|
||||
[XMPPGroupCoreDataStorageObject clearEmptyGroupsInManagedObjectContext:moc];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)endRosterPopulationForXMPPStream:(XMPPStream *)stream
|
||||
{
|
||||
XMPPLogTrace();
|
||||
|
||||
[self scheduleBlock:^{
|
||||
|
||||
[rosterPopulationSet removeObject:[NSNumber xmpp_numberWithPtr:(__bridge void *)stream]];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)handleRosterItem:(NSXMLElement *)itemSubElement xmppStream:(XMPPStream *)stream
|
||||
{
|
||||
XMPPLogTrace();
|
||||
|
||||
// Remember XML heirarchy memory management rules.
|
||||
// The passed parameter is a subnode of the IQ, and we need to pass it to an asynchronous operation.
|
||||
NSXMLElement *item = [itemSubElement copy];
|
||||
|
||||
[self scheduleBlock:^{
|
||||
|
||||
NSManagedObjectContext *moc = [self managedObjectContext];
|
||||
|
||||
if ([rosterPopulationSet containsObject:[NSNumber xmpp_numberWithPtr:(__bridge void *)stream]])
|
||||
{
|
||||
NSString *streamBareJidStr = [[self myJIDForXMPPStream:stream] bare];
|
||||
|
||||
[XMPPUserCoreDataStorageObject insertInManagedObjectContext:moc
|
||||
withItem:item
|
||||
streamBareJidStr:streamBareJidStr];
|
||||
}
|
||||
else
|
||||
{
|
||||
NSString *jidStr = [item attributeStringValueForName:@"jid"];
|
||||
XMPPJID *jid = [[XMPPJID jidWithString:jidStr] bareJID];
|
||||
|
||||
XMPPUserCoreDataStorageObject *user = [self userForJID:jid xmppStream:stream managedObjectContext:moc];
|
||||
|
||||
NSString *subscription = [item attributeStringValueForName:@"subscription"];
|
||||
if ([subscription isEqualToString:@"remove"])
|
||||
{
|
||||
if (user)
|
||||
{
|
||||
[moc deleteObject:user];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (user)
|
||||
{
|
||||
[user updateWithItem:item];
|
||||
}
|
||||
else
|
||||
{
|
||||
NSString *streamBareJidStr = [[self myJIDForXMPPStream:stream] bare];
|
||||
|
||||
[XMPPUserCoreDataStorageObject insertInManagedObjectContext:moc
|
||||
withItem:item
|
||||
streamBareJidStr:streamBareJidStr];
|
||||
}
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)handlePresence:(XMPPPresence *)presence xmppStream:(XMPPStream *)stream
|
||||
{
|
||||
XMPPLogTrace();
|
||||
|
||||
BOOL allowRosterlessOperation = [parent allowRosterlessOperation];
|
||||
|
||||
[self scheduleBlock:^{
|
||||
|
||||
XMPPJID *jid = [presence from];
|
||||
NSManagedObjectContext *moc = [self managedObjectContext];
|
||||
|
||||
NSString *streamBareJidStr = [[self myJIDForXMPPStream:stream] bare];
|
||||
|
||||
XMPPUserCoreDataStorageObject *user = [self userForJID:jid xmppStream:stream managedObjectContext:moc];
|
||||
|
||||
if (user == nil && allowRosterlessOperation)
|
||||
{
|
||||
// This may happen if the roster is in rosterlessOperation mode.
|
||||
|
||||
user = [XMPPUserCoreDataStorageObject insertInManagedObjectContext:moc
|
||||
withJID:[presence from]
|
||||
streamBareJidStr:streamBareJidStr];
|
||||
}
|
||||
|
||||
[user updateWithPresence:presence streamBareJidStr:streamBareJidStr];
|
||||
}];
|
||||
}
|
||||
|
||||
- (BOOL)userExistsWithJID:(XMPPJID *)jid xmppStream:(XMPPStream *)stream
|
||||
{
|
||||
XMPPLogTrace();
|
||||
|
||||
__block BOOL result = NO;
|
||||
|
||||
[self executeBlock:^{
|
||||
|
||||
NSManagedObjectContext *moc = [self managedObjectContext];
|
||||
XMPPUserCoreDataStorageObject *user = [self userForJID:jid xmppStream:stream managedObjectContext:moc];
|
||||
|
||||
result = (user != nil);
|
||||
}];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#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();
|
||||
|
||||
[self scheduleBlock:^{
|
||||
|
||||
NSManagedObjectContext *moc = [self managedObjectContext];
|
||||
XMPPUserCoreDataStorageObject *user = [self userForJID:jid xmppStream:stream managedObjectContext:moc];
|
||||
|
||||
if (user)
|
||||
{
|
||||
user.photo = photo;
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)clearAllResourcesForXMPPStream:(XMPPStream *)stream
|
||||
{
|
||||
XMPPLogTrace();
|
||||
|
||||
[self scheduleBlock:^{
|
||||
|
||||
[self _clearAllResourcesForXMPPStream:stream];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)clearAllUsersAndResourcesForXMPPStream:(XMPPStream *)stream
|
||||
{
|
||||
XMPPLogTrace();
|
||||
|
||||
[self scheduleBlock:^{
|
||||
|
||||
// Note: Deleting a user will delete all associated resources
|
||||
// because of the cascade rule in our core data model.
|
||||
|
||||
NSManagedObjectContext *moc = [self managedObjectContext];
|
||||
|
||||
NSEntityDescription *entity = [NSEntityDescription entityForName:@"XMPPUserCoreDataStorageObject"
|
||||
inManagedObjectContext:moc];
|
||||
|
||||
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
|
||||
[fetchRequest setEntity:entity];
|
||||
[fetchRequest setFetchBatchSize:saveThreshold];
|
||||
|
||||
if (stream)
|
||||
{
|
||||
NSPredicate *predicate;
|
||||
predicate = [NSPredicate predicateWithFormat:@"streamBareJidStr == %@",
|
||||
[[self myJIDForXMPPStream:stream] bare]];
|
||||
|
||||
[fetchRequest setPredicate:predicate];
|
||||
}
|
||||
|
||||
NSArray *allUsers = [moc executeFetchRequest:fetchRequest error:nil];
|
||||
|
||||
NSUInteger unsavedCount = [self numberOfUnsavedChanges];
|
||||
|
||||
for (XMPPUserCoreDataStorageObject *user in allUsers)
|
||||
{
|
||||
[moc deleteObject:user];
|
||||
|
||||
if (++unsavedCount >= saveThreshold)
|
||||
{
|
||||
[self save];
|
||||
unsavedCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
[XMPPGroupCoreDataStorageObject clearEmptyGroupsInManagedObjectContext:moc];
|
||||
}];
|
||||
}
|
||||
|
||||
- (NSArray *)jidsForXMPPStream:(XMPPStream *)stream{
|
||||
|
||||
XMPPLogTrace();
|
||||
|
||||
__block NSMutableArray *results = [NSMutableArray array];
|
||||
|
||||
[self executeBlock:^{
|
||||
|
||||
NSManagedObjectContext *moc = [self managedObjectContext];
|
||||
|
||||
NSEntityDescription *entity = [NSEntityDescription entityForName:@"XMPPUserCoreDataStorageObject"
|
||||
inManagedObjectContext:moc];
|
||||
|
||||
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
|
||||
[fetchRequest setEntity:entity];
|
||||
[fetchRequest setFetchBatchSize:saveThreshold];
|
||||
|
||||
if (stream)
|
||||
{
|
||||
NSPredicate *predicate;
|
||||
predicate = [NSPredicate predicateWithFormat:@"streamBareJidStr == %@",
|
||||
[[self myJIDForXMPPStream:stream] bare]];
|
||||
|
||||
[fetchRequest setPredicate:predicate];
|
||||
}
|
||||
|
||||
NSArray *allUsers = [moc executeFetchRequest:fetchRequest error:nil];
|
||||
|
||||
for(XMPPUserCoreDataStorageObject *user in allUsers){
|
||||
[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();
|
||||
|
||||
[self executeBlock:^{
|
||||
|
||||
NSManagedObjectContext *moc = [self managedObjectContext];
|
||||
XMPPUserCoreDataStorageObject *user = [self userForJID:jid xmppStream:stream managedObjectContext:moc];
|
||||
|
||||
if(user)
|
||||
{
|
||||
if(subscription)
|
||||
{
|
||||
*subscription = user.subscription;
|
||||
}
|
||||
|
||||
if(ask)
|
||||
{
|
||||
*ask = user.ask;
|
||||
}
|
||||
|
||||
if(nickname)
|
||||
{
|
||||
*nickname = user.nickname;
|
||||
}
|
||||
|
||||
if(groups)
|
||||
{
|
||||
if([user.groups count])
|
||||
{
|
||||
NSMutableArray *groupNames = [NSMutableArray array];
|
||||
|
||||
for(XMPPGroupCoreDataStorageObject *group in user.groups){
|
||||
[groupNames addObject:group.name];
|
||||
}
|
||||
|
||||
*groups = groupNames;
|
||||
}
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,79 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <CoreData/CoreData.h>
|
||||
|
||||
#if TARGET_OS_IPHONE
|
||||
#import <UIKit/UIKit.h>
|
||||
#else
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#endif
|
||||
|
||||
#import "XMPPUser.h"
|
||||
#import "XMPP.h"
|
||||
|
||||
@class XMPPGroupCoreDataStorageObject;
|
||||
@class XMPPResourceCoreDataStorageObject;
|
||||
|
||||
|
||||
@interface XMPPUserCoreDataStorageObject : NSManagedObject <XMPPUser>
|
||||
{
|
||||
NSInteger section;
|
||||
}
|
||||
|
||||
@property (nonatomic, strong) XMPPJID *jid;
|
||||
@property (nonatomic, strong) NSString * jidStr;
|
||||
@property (nonatomic, strong) NSString * streamBareJidStr;
|
||||
|
||||
@property (nonatomic, strong) NSString * nickname;
|
||||
@property (nonatomic, strong) NSString * displayName;
|
||||
@property (nonatomic, strong) NSString * subscription;
|
||||
@property (nonatomic, strong) NSString * ask;
|
||||
@property (nonatomic, strong) NSNumber * unreadMessages;
|
||||
|
||||
#if TARGET_OS_IPHONE
|
||||
@property (nonatomic, strong) UIImage *photo;
|
||||
#else
|
||||
@property (nonatomic, strong) NSImage *photo;
|
||||
#endif
|
||||
|
||||
@property (nonatomic, assign) NSInteger section;
|
||||
@property (nonatomic, strong) NSString * sectionName;
|
||||
@property (nonatomic, strong) NSNumber * sectionNum;
|
||||
|
||||
@property (nonatomic, strong) NSSet * groups;
|
||||
@property (nonatomic, strong) XMPPResourceCoreDataStorageObject * primaryResource;
|
||||
@property (nonatomic, strong) NSSet * resources;
|
||||
|
||||
+ (id)insertInManagedObjectContext:(NSManagedObjectContext *)moc
|
||||
withJID:(XMPPJID *)jid
|
||||
streamBareJidStr:(NSString *)streamBareJidStr;
|
||||
|
||||
+ (id)insertInManagedObjectContext:(NSManagedObjectContext *)moc
|
||||
withItem:(NSXMLElement *)item
|
||||
streamBareJidStr:(NSString *)streamBareJidStr;
|
||||
|
||||
- (void)updateWithItem:(NSXMLElement *)item;
|
||||
- (void)updateWithPresence:(XMPPPresence *)presence streamBareJidStr:(NSString *)streamBareJidStr;
|
||||
- (void)recalculatePrimaryResource;
|
||||
|
||||
- (NSComparisonResult)compareByName:(XMPPUserCoreDataStorageObject *)another;
|
||||
- (NSComparisonResult)compareByName:(XMPPUserCoreDataStorageObject *)another options:(NSStringCompareOptions)mask;
|
||||
|
||||
- (NSComparisonResult)compareByAvailabilityName:(XMPPUserCoreDataStorageObject *)another;
|
||||
- (NSComparisonResult)compareByAvailabilityName:(XMPPUserCoreDataStorageObject *)another
|
||||
options:(NSStringCompareOptions)mask;
|
||||
|
||||
@end
|
||||
|
||||
@interface XMPPUserCoreDataStorageObject (CoreDataGeneratedAccessors)
|
||||
|
||||
- (void)addResourcesObject:(XMPPResourceCoreDataStorageObject *)value;
|
||||
- (void)removeResourcesObject:(XMPPResourceCoreDataStorageObject *)value;
|
||||
- (void)addResources:(NSSet *)value;
|
||||
- (void)removeResources:(NSSet *)value;
|
||||
|
||||
- (void)addGroupsObject:(XMPPGroupCoreDataStorageObject *)value;
|
||||
- (void)removeGroupsObject:(XMPPGroupCoreDataStorageObject *)value;
|
||||
- (void)addGroups:(NSSet *)value;
|
||||
- (void)removeGroups:(NSSet *)value;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,493 @@
|
||||
#import "XMPP.h"
|
||||
#import "XMPPRosterCoreDataStorage.h"
|
||||
#import "XMPPUserCoreDataStorageObject.h"
|
||||
#import "XMPPResourceCoreDataStorageObject.h"
|
||||
#import "XMPPGroupCoreDataStorageObject.h"
|
||||
#import "NSNumber+XMPP.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_INFO; // | XMPP_LOG_FLAG_TRACE;
|
||||
#else
|
||||
static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN;
|
||||
#endif
|
||||
|
||||
@interface XMPPUserCoreDataStorageObject ()
|
||||
|
||||
@property(nonatomic,strong) XMPPJID *primitiveJid;
|
||||
@property(nonatomic,strong) NSString *primitiveJidStr;
|
||||
|
||||
@property(nonatomic,strong) NSString *primitiveDisplayName;
|
||||
@property(nonatomic,assign) NSInteger primitiveSection;
|
||||
@property(nonatomic,strong) NSString *primitiveSectionName;
|
||||
@property(nonatomic,strong) NSNumber *primitiveSectionNum;
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@implementation XMPPUserCoreDataStorageObject
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Accessors
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
@dynamic jid, primitiveJid;
|
||||
@dynamic jidStr, primitiveJidStr;
|
||||
@dynamic streamBareJidStr;
|
||||
|
||||
@dynamic nickname;
|
||||
@dynamic displayName, primitiveDisplayName;
|
||||
@dynamic subscription;
|
||||
@dynamic ask;
|
||||
@dynamic unreadMessages;
|
||||
@dynamic photo;
|
||||
|
||||
@dynamic section, primitiveSection;
|
||||
@dynamic sectionName, primitiveSectionName;
|
||||
@dynamic sectionNum, primitiveSectionNum;
|
||||
|
||||
@dynamic groups;
|
||||
@dynamic primaryResource;
|
||||
@dynamic resources;
|
||||
|
||||
- (XMPPJID *)jid
|
||||
{
|
||||
// Create and cache the jid on demand
|
||||
|
||||
[self willAccessValueForKey:@"jid"];
|
||||
XMPPJID *tmp = [self primitiveJid];
|
||||
[self didAccessValueForKey:@"jid"];
|
||||
|
||||
if (tmp == nil) {
|
||||
tmp = [XMPPJID jidWithString:[self jidStr]];
|
||||
|
||||
[self setPrimitiveJid:tmp];
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
|
||||
- (void)setJid:(XMPPJID *)jid
|
||||
{
|
||||
self.jidStr = [jid bare];
|
||||
}
|
||||
|
||||
- (void)setJidStr:(NSString *)jidStr
|
||||
{
|
||||
[self willChangeValueForKey:@"jidStr"];
|
||||
[self setPrimitiveJidStr:jidStr];
|
||||
[self didChangeValueForKey:@"jidStr"];
|
||||
|
||||
// If the jidStr changes, the jid becomes invalid.
|
||||
[self setPrimitiveJid:nil];
|
||||
}
|
||||
|
||||
- (NSInteger)section
|
||||
{
|
||||
// Create and cache the section on demand
|
||||
[self willAccessValueForKey:@"section"];
|
||||
NSInteger tmp = [self primitiveSection];
|
||||
[self didAccessValueForKey:@"section"];
|
||||
|
||||
// section uses zero, so to distinguish unset values, use NSNotFound
|
||||
if (tmp == NSNotFound) {
|
||||
tmp = [[self sectionNum] integerValue];
|
||||
|
||||
[self setPrimitiveSection:tmp];
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
|
||||
- (void)setSection:(NSInteger)value
|
||||
{
|
||||
self.sectionNum = @(value);
|
||||
}
|
||||
|
||||
- (NSInteger)primitiveSection
|
||||
{
|
||||
return section;
|
||||
}
|
||||
|
||||
- (void)setPrimitiveSection:(NSInteger)primitiveSection
|
||||
{
|
||||
section = primitiveSection;
|
||||
}
|
||||
|
||||
|
||||
|
||||
- (void)setSectionNum:(NSNumber *)sectionNum
|
||||
{
|
||||
[self willChangeValueForKey:@"sectionNum"];
|
||||
[self setPrimitiveSectionNum:sectionNum];
|
||||
[self didChangeValueForKey:@"sectionNum"];
|
||||
|
||||
// If the sectionNum changes, the section becomes invalid.
|
||||
// section uses zero, so to distinguish unset values, use NSNotFound
|
||||
[self setPrimitiveSection:NSNotFound];
|
||||
}
|
||||
|
||||
- (NSString *)sectionName
|
||||
{
|
||||
// Create and cache the sectionName on demand
|
||||
|
||||
[self willAccessValueForKey:@"sectionName"];
|
||||
NSString *tmp = [self primitiveSectionName];
|
||||
[self didAccessValueForKey:@"sectionName"];
|
||||
|
||||
if (tmp == nil) {
|
||||
// Section names are organized by capitalizing the first letter of the displayName
|
||||
|
||||
NSString *upperCase = [self.displayName uppercaseString];
|
||||
|
||||
// return the first character with support UTF-16:
|
||||
tmp = [upperCase substringWithRange:[upperCase rangeOfComposedCharacterSequenceAtIndex:0]];
|
||||
|
||||
[self setPrimitiveSectionName:tmp];
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
|
||||
- (void)setDisplayName:(NSString *)displayName
|
||||
{
|
||||
[self willChangeValueForKey:@"displayName"];
|
||||
[self setPrimitiveDisplayName:displayName];
|
||||
[self didChangeValueForKey:@"displayName"];
|
||||
|
||||
// If the displayName changes, the sectionName becomes invalid.
|
||||
[self setPrimitiveSectionName:nil];
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark NSManagedObject
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (void)awakeFromInsert
|
||||
{
|
||||
// Section uses zero, so to distinguish unset values, use NSNotFound.
|
||||
|
||||
self.primitiveSection = NSNotFound;
|
||||
}
|
||||
|
||||
- (void)awakeFromFetch
|
||||
{
|
||||
// Section uses zero, so to distinguish unset values, use NSNotFound.
|
||||
//
|
||||
// Note: Do NOT use "self.section = NSNotFound" as this will in turn set the sectionNum.
|
||||
|
||||
self.primitiveSection = NSNotFound;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Creation & Updates
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
+ (id)insertInManagedObjectContext:(NSManagedObjectContext *)moc
|
||||
withJID:(XMPPJID *)jid
|
||||
streamBareJidStr:(NSString *)streamBareJidStr
|
||||
{
|
||||
if (jid == nil)
|
||||
{
|
||||
XMPPLogVerbose(@"XMPPUserCoreDataStorageObject: invalid jid (nil)");
|
||||
return nil;
|
||||
}
|
||||
|
||||
XMPPUserCoreDataStorageObject *newUser;
|
||||
newUser = [NSEntityDescription insertNewObjectForEntityForName:@"XMPPUserCoreDataStorageObject"
|
||||
inManagedObjectContext:moc];
|
||||
|
||||
newUser.streamBareJidStr = streamBareJidStr;
|
||||
|
||||
newUser.jid = jid;
|
||||
newUser.nickname = nil;
|
||||
|
||||
newUser.displayName = [jid bare];
|
||||
|
||||
return newUser;
|
||||
}
|
||||
|
||||
+ (id)insertInManagedObjectContext:(NSManagedObjectContext *)moc
|
||||
withItem:(NSXMLElement *)item
|
||||
streamBareJidStr:(NSString *)streamBareJidStr
|
||||
{
|
||||
NSString *jidStr = [item attributeStringValueForName:@"jid"];
|
||||
XMPPJID *jid = [XMPPJID jidWithString:jidStr];
|
||||
|
||||
if (jid == nil)
|
||||
{
|
||||
XMPPLogVerbose(@"XMPPUserCoreDataStorageObject: invalid item (missing or invalid jid): %@", item);
|
||||
return nil;
|
||||
}
|
||||
|
||||
XMPPUserCoreDataStorageObject *newUser;
|
||||
newUser = [NSEntityDescription insertNewObjectForEntityForName:@"XMPPUserCoreDataStorageObject"
|
||||
inManagedObjectContext:moc];
|
||||
|
||||
newUser.streamBareJidStr = streamBareJidStr;
|
||||
|
||||
[newUser updateWithItem:item];
|
||||
|
||||
return newUser;
|
||||
}
|
||||
|
||||
- (void)updateGroupsWithItem:(NSXMLElement *)item
|
||||
{
|
||||
XMPPGroupCoreDataStorageObject *group = nil;
|
||||
|
||||
// clear existing group memberships first
|
||||
if ([self.groups count] > 0) {
|
||||
[self removeGroups:self.groups];
|
||||
}
|
||||
|
||||
NSArray *groupItems = [item elementsForName:@"group"];
|
||||
NSString *groupName = nil;
|
||||
|
||||
for (NSXMLElement *groupElement in groupItems) {
|
||||
groupName = [groupElement stringValue];
|
||||
|
||||
group = [XMPPGroupCoreDataStorageObject fetchOrInsertGroupName:groupName
|
||||
inManagedObjectContext:[self managedObjectContext]];
|
||||
|
||||
if (group != nil) {
|
||||
[self addGroupsObject:group];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateWithItem:(NSXMLElement *)item
|
||||
{
|
||||
NSString *jidStr = [item attributeStringValueForName:@"jid"];
|
||||
XMPPJID *jid = [XMPPJID jidWithString:jidStr];
|
||||
|
||||
if (jid == nil)
|
||||
{
|
||||
XMPPLogVerbose(@"XMPPUserCoreDataStorageObject: invalid item (missing or invalid jid): %@", item);
|
||||
return;
|
||||
}
|
||||
|
||||
self.jid = jid;
|
||||
self.nickname = [item attributeStringValueForName:@"name"];
|
||||
|
||||
self.displayName = self.nickname ? : jidStr;
|
||||
|
||||
self.subscription = [item attributeStringValueForName:@"subscription"];
|
||||
self.ask = [item attributeStringValueForName:@"ask"];
|
||||
|
||||
[self updateGroupsWithItem:item];
|
||||
}
|
||||
|
||||
- (void)recalculatePrimaryResource
|
||||
{
|
||||
self.primaryResource = nil;
|
||||
|
||||
NSArray *sortedResources = [[self allResources] sortedArrayUsingSelector:@selector(compare:)];
|
||||
if ([sortedResources count] > 0)
|
||||
{
|
||||
XMPPResourceCoreDataStorageObject *resource = sortedResources[0];
|
||||
|
||||
// Primary resource must have a non-negative priority
|
||||
if ([resource priority] >= 0)
|
||||
{
|
||||
self.primaryResource = resource;
|
||||
|
||||
if (resource.intShow >= 3)
|
||||
self.section = 0;
|
||||
else
|
||||
self.section = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (self.primaryResource == nil)
|
||||
{
|
||||
self.section = 2;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateWithPresence:(XMPPPresence *)presence streamBareJidStr:(NSString *)streamBareJidStr
|
||||
{
|
||||
XMPPResourceCoreDataStorageObject *resource =
|
||||
(XMPPResourceCoreDataStorageObject *)[self resourceForJID:[presence from]];
|
||||
|
||||
if ([[presence type] isEqualToString:@"unavailable"] || [presence isErrorPresence])
|
||||
{
|
||||
if (resource)
|
||||
{
|
||||
[self removeResourcesObject:resource];
|
||||
[[self managedObjectContext] deleteObject:resource];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (resource)
|
||||
{
|
||||
[resource updateWithPresence:presence];
|
||||
}
|
||||
else
|
||||
{
|
||||
XMPPResourceCoreDataStorageObject *newResource;
|
||||
newResource = [XMPPResourceCoreDataStorageObject insertInManagedObjectContext:[self managedObjectContext]
|
||||
withPresence:presence
|
||||
streamBareJidStr:streamBareJidStr];
|
||||
|
||||
[self addResourcesObject:newResource];
|
||||
}
|
||||
}
|
||||
|
||||
[self recalculatePrimaryResource];
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark XMPPUser Protocol
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (BOOL)isOnline
|
||||
{
|
||||
return (self.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 = self.subscription;
|
||||
NSString *ask = self.ask;
|
||||
|
||||
if ([subscription isEqualToString:@"none"] || [subscription isEqualToString:@"from"])
|
||||
{
|
||||
if ([ask isEqualToString:@"subscribe"])
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (id <XMPPResource>)resourceForJID:(XMPPJID *)jid
|
||||
{
|
||||
NSString *jidStr = [jid full];
|
||||
|
||||
for (XMPPResourceCoreDataStorageObject *resource in [self resources])
|
||||
{
|
||||
if ([jidStr isEqualToString:[resource jidStr]])
|
||||
{
|
||||
return resource;
|
||||
}
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSArray *)allResources
|
||||
{
|
||||
NSMutableArray *allResources = [NSMutableArray array];
|
||||
|
||||
for (XMPPResourceCoreDataStorageObject *resource in [[self resources] allObjects]) {
|
||||
|
||||
if(![resource isDeleted])
|
||||
{
|
||||
[allResources addObject:resource];
|
||||
}
|
||||
}
|
||||
|
||||
return allResources;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Comparisons
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Returns the result of invoking compareByName:options: with no options.
|
||||
**/
|
||||
- (NSComparisonResult)compareByName:(XMPPUserCoreDataStorageObject *)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:(XMPPUserCoreDataStorageObject *)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:(XMPPUserCoreDataStorageObject *)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:(XMPPUserCoreDataStorageObject *)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 KVO compliance methods
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingJid {
|
||||
// If the jidStr changes, the jid may change as well.
|
||||
return [NSSet setWithObject:@"jidStr"];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingIsOnline {
|
||||
return [NSSet setWithObject:@"primaryResource"];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingSection {
|
||||
// If the value of sectionNum changes, the section may change as well.
|
||||
return [NSSet setWithObject:@"sectionNum"];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingSectionName {
|
||||
// If the value of displayName changes, the sectionName may change as well.
|
||||
return [NSSet setWithObject:@"displayName"];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingAllResources {
|
||||
return [NSSet setWithObject:@"resources"];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -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
|
||||
@@ -0,0 +1,15 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "XMPP.h"
|
||||
|
||||
|
||||
@protocol XMPPResource <NSObject>
|
||||
@required
|
||||
|
||||
- (XMPPJID *)jid;
|
||||
- (XMPPPresence *)presence;
|
||||
|
||||
- (NSDate *)presenceDate;
|
||||
|
||||
- (NSComparisonResult)compare:(id <XMPPResource>)another;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,406 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#if TARGET_OS_IPHONE
|
||||
#import <UIKit/UIKit.h>
|
||||
#else
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#endif
|
||||
|
||||
#import "XMPP.h"
|
||||
#import "XMPPUser.h"
|
||||
#import "XMPPResource.h"
|
||||
|
||||
#define _XMPP_ROSTER_H
|
||||
|
||||
@protocol XMPPRosterStorage;
|
||||
@class DDList;
|
||||
@class XMPPIDTracker;
|
||||
|
||||
/**
|
||||
* The XMPPRoster provides the scaffolding for a roster solution.
|
||||
* It handles the basics of the xmpp roster communication,
|
||||
* and leaves the rest up to the xmppRosterStorage classes.
|
||||
*
|
||||
* You are free to extend, change, customize, or tweak the sample storage classes that come with the framework:
|
||||
* XMPPRosterMemoryStorage
|
||||
* XMPPRosterCoreDataStorage
|
||||
*
|
||||
* You can also completely implment your own roster storage class if you'd like.
|
||||
* The point of all this customizability is simple:
|
||||
* The roster is the component of the xmpp stack that is most often customized for a particular application.
|
||||
*
|
||||
*
|
||||
* Inter-Module Interaction:
|
||||
*
|
||||
* If you use XMPPvCardAvatarModule, the roster will automatically support user photos.
|
||||
**/
|
||||
@interface XMPPRoster : XMPPModule
|
||||
{
|
||||
/* Inherited from XMPPModule:
|
||||
|
||||
XMPPStream *xmppStream;
|
||||
|
||||
dispatch_queue_t moduleQueue;
|
||||
id multicastDelegate;
|
||||
*/
|
||||
__strong id <XMPPRosterStorage> xmppRosterStorage;
|
||||
|
||||
XMPPIDTracker *xmppIDTracker;
|
||||
|
||||
Byte config;
|
||||
Byte flags;
|
||||
|
||||
NSMutableArray *earlyPresenceElements;
|
||||
|
||||
DDList *mucModules;
|
||||
}
|
||||
|
||||
- (id)initWithRosterStorage:(id <XMPPRosterStorage>)storage;
|
||||
- (id)initWithRosterStorage:(id <XMPPRosterStorage>)storage dispatchQueue:(dispatch_queue_t)queue;
|
||||
|
||||
/* Inherited from XMPPModule:
|
||||
|
||||
- (BOOL)activate:(XMPPStream *)xmppStream;
|
||||
- (void)deactivate;
|
||||
|
||||
@property (readonly) XMPPStream *xmppStream;
|
||||
|
||||
- (void)addDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue;
|
||||
- (void)removeDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue;
|
||||
- (void)removeDelegate:(id)delegate;
|
||||
|
||||
- (NSString *)moduleName;
|
||||
|
||||
*/
|
||||
|
||||
@property (strong, readonly) id <XMPPRosterStorage> xmppRosterStorage;
|
||||
|
||||
/**
|
||||
* Whether or not to automatically fetch the roster from the server.
|
||||
*
|
||||
* The default value is YES.
|
||||
**/
|
||||
@property (assign) BOOL autoFetchRoster;
|
||||
|
||||
/**
|
||||
* Whether or not to automatically clear all Users and Resources when the stream disconnects.
|
||||
* If you are using XMPPRosterCoreDataStorage you may want to set autoRemovePreviousDatabaseFile to NO.
|
||||
*
|
||||
* All Users and Resources will be cleared when the roster is next populated regardless of this property.
|
||||
*
|
||||
* The default value is YES.
|
||||
**/
|
||||
@property (assign) BOOL autoClearAllUsersAndResources;
|
||||
|
||||
/**
|
||||
* In traditional IM applications, the "buddy" system is rather straightforward.
|
||||
* User A sends a request to become "friends" with user B.
|
||||
* User B accepts the friend request.
|
||||
* And now user A and B appear in each other's roster.
|
||||
*
|
||||
* But, internally, XMPP presence actually works more like twitter.
|
||||
* User A sends a presence subscription request to user B.
|
||||
* Think about this more like following on twitter.
|
||||
* User A is requesting permission to receive presence information from user B (following their presence).
|
||||
* User B can accept this request, but may not be interested in receiving presence info from user B.
|
||||
* Again, this is more like twitter following, but with the addition of required permission.
|
||||
* So when user B accepts the request, this *only* means that user A will receive presence from user B.
|
||||
* It does *not* mean that user B will receive presence from user A.
|
||||
* Again, twitter-style, user A is now "following" user B.
|
||||
* In diagram form, presence is flowing like this:
|
||||
*
|
||||
* A <-- B
|
||||
*
|
||||
* Now, if B also wants to receive presence from user A, then user B must request this permission.
|
||||
* And furthermore, user A must accept the request.
|
||||
* Just because B has granted A permission to receive presence,
|
||||
* doesn't mean that B gets a free pass to receive presence from A.
|
||||
* Presence always requires permission.
|
||||
*
|
||||
* This property does the following:
|
||||
* If a presence subscription request is received from user X,
|
||||
* and user X has already been added to our roster (is a "known" user),
|
||||
* then the presence subscription request is automatically accepted.
|
||||
*
|
||||
* Continuing the example from above, if we were user A, and we've already added B to our roster,
|
||||
* then if B sends a presence susbcription request, we'll auto accept it.
|
||||
*
|
||||
* This makes the roster act more like traditional IM applications.
|
||||
*
|
||||
* The default value is YES.
|
||||
**/
|
||||
@property (assign) BOOL autoAcceptKnownPresenceSubscriptionRequests;
|
||||
|
||||
/**
|
||||
* Allows the roster module to function without ever fetching the full roster.
|
||||
* This is helpful for situations in which the roster is very big, yet the application only cares about online users.
|
||||
*
|
||||
* Typically, the roster module creates users based on the fetched full roster,
|
||||
* and then creates resources based on received presence.
|
||||
*
|
||||
* In this mode, the roster module will automatically create a user once a presence is received,
|
||||
* if the user has never been seen before.
|
||||
*
|
||||
* If allowRosterlessOperation is enabled, and autoFetchRoster is disabled (and roster is never manually fetched),
|
||||
* then XMPPUser's will be missing certain information that is only available via a roster fetch
|
||||
* (such as nickname, group, and subscription information).
|
||||
*
|
||||
* The default value is NO.
|
||||
**/
|
||||
@property (assign) BOOL allowRosterlessOperation;
|
||||
|
||||
/**
|
||||
* The roster has either been requested manually (fetchRoster:)
|
||||
* or automatically (autoFetchRoster) but has yet to be populated.
|
||||
**/
|
||||
@property (assign, getter = hasRequestedRoster, readonly) BOOL requestedRoster;
|
||||
|
||||
/**
|
||||
* The initial roster has been received by client and is currently being populated.
|
||||
* @see xmppRosterDidBeginPopulating:withVersion:
|
||||
* @see xmppRosterDidEndPopulating:
|
||||
**/
|
||||
@property (assign, getter = isPopulating, readonly) BOOL populating;
|
||||
|
||||
/**
|
||||
* The initial roster has been received by client and populated.
|
||||
**/
|
||||
@property (assign, readonly) BOOL hasRoster;
|
||||
|
||||
/**
|
||||
* Manually fetch the roster from the server.
|
||||
* Useful if you disable autoFetchRoster.
|
||||
**/
|
||||
- (void)fetchRoster;
|
||||
- (void)fetchRosterVersion:(NSString *)version;
|
||||
|
||||
/**
|
||||
* Adds the given user to the roster with an optional nickname
|
||||
* and requests permission to receive presence information from them.
|
||||
**/
|
||||
- (void)addUser:(XMPPJID *)jid withNickname:(NSString *)optionalName;
|
||||
|
||||
/**
|
||||
* Adds the given user to the roster with an optional nickname,
|
||||
* adds the given user to groups
|
||||
* and requests permission to receive presence information from them.
|
||||
**/
|
||||
- (void)addUser:(XMPPJID *)jid withNickname:(NSString *)optionalName groups:(NSArray *)groups;
|
||||
|
||||
/**
|
||||
* Adds the given user to the roster with an optional nickname,
|
||||
* adds the given user to groups
|
||||
* and optionally requests permission to receive presence information from them.
|
||||
**/
|
||||
- (void)addUser:(XMPPJID *)jid withNickname:(NSString *)optionalName groups:(NSArray *)groups subscribeToPresence:(BOOL)subscribe;
|
||||
|
||||
/**
|
||||
* Sets/modifies the nickname for the given user.
|
||||
**/
|
||||
- (void)setNickname:(NSString *)nickname forUser:(XMPPJID *)jid;
|
||||
|
||||
/**
|
||||
* Remove the user from the roster, unsubscribe from their presence, AND
|
||||
* revoke given user's permission to receive our presence (if they have such permission).
|
||||
*
|
||||
* This is similar to removing a buddy in a traditional IM model.
|
||||
*
|
||||
* @see unsubscribePresenceFromUser:
|
||||
* @see revokePresencePermissionFromUser:
|
||||
**/
|
||||
- (void)removeUser:(XMPPJID *)jid;
|
||||
|
||||
/**
|
||||
* If we don't currently receive presence from the given user,
|
||||
* this method requests a subscription to start receiving presence updates from the given user.
|
||||
*
|
||||
* This is similar to following in the twitter model.
|
||||
*
|
||||
* Note: If the given user isn't already in the roster, it is recommended to instead use addUser:withNickname:.
|
||||
*
|
||||
* @see addUser:withNickname:
|
||||
**/
|
||||
- (void)subscribePresenceToUser:(XMPPJID *)jid;
|
||||
|
||||
/**
|
||||
* If we currently have a presence subscription to the given user,
|
||||
* this method then removes the subscription.
|
||||
*
|
||||
* This is similar to unfollowing in the twitter model.
|
||||
*
|
||||
* If the given user has a presence subscription to us (they're following our presence),
|
||||
* then their presence subscription to us is left intact.
|
||||
*
|
||||
* @see removeUser:
|
||||
* @see revokePresencePermissionFromUser:
|
||||
**/
|
||||
- (void)unsubscribePresenceFromUser:(XMPPJID *)jid;
|
||||
|
||||
/**
|
||||
* If we have previously accepted a presence subscription request from the given user,
|
||||
* this method revokes the previously granted permission.
|
||||
*
|
||||
* This is similar to forcing the given user to unfollow us in the twitter model.
|
||||
*
|
||||
* If we have a presence subscription to the given user (we're following their presence),
|
||||
* then our presence subscription to them is left intact.
|
||||
*
|
||||
* @see removeUser:
|
||||
* @see unsubscribePresenceFromUser:
|
||||
**/
|
||||
- (void)revokePresencePermissionFromUser:(XMPPJID *)jid;
|
||||
|
||||
/**
|
||||
* Accepts the presence subscription request the given user.
|
||||
*
|
||||
* If you also choose, you can add the user to your roster.
|
||||
* Doing so is similar to the traditional IM model.
|
||||
**/
|
||||
- (void)acceptPresenceSubscriptionRequestFrom:(XMPPJID *)jid andAddToRoster:(BOOL)flag;
|
||||
|
||||
/**
|
||||
* Rejects the presence subscription request from the given user.
|
||||
*
|
||||
* If you are already subscribed to the given user's presence,
|
||||
* rejecting they subscription request will not affect your subscription to their presence.
|
||||
**/
|
||||
- (void)rejectPresenceSubscriptionRequestFrom:(XMPPJID *)jid;
|
||||
|
||||
//
|
||||
//
|
||||
// You can access/enumerate the users & resources via the roster storage class (xmppRosterStorage property).
|
||||
//
|
||||
// Rember, XMPPRoster is just the scaffolding for a complete and customizable roster solution.
|
||||
// The roster storage classes hold the majority of the magic.
|
||||
//
|
||||
// And since you're free to plug-n-play storage classes, and customize them as much as you want.
|
||||
// This is where you can really tailor the xmpp stack to meet the needs of your application.
|
||||
//
|
||||
//
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@protocol XMPPRosterStorage <NSObject>
|
||||
@required
|
||||
|
||||
//
|
||||
//
|
||||
// -- PUBLIC METHODS --
|
||||
//
|
||||
// There are no public methods required by this protocol.
|
||||
//
|
||||
// Each individual roster storage class will provide a proper way to access/enumerate the
|
||||
// users/resources according to the underlying storage mechanism.
|
||||
//
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
// -- PRIVATE METHODS --
|
||||
//
|
||||
// These methods are designed to be used ONLY by the XMPPRoster class.
|
||||
//
|
||||
//
|
||||
|
||||
/**
|
||||
* Configures the storage class, passing its parent and parent's dispatch queue.
|
||||
*
|
||||
* This method is called by the init method of the XMPPRoster class.
|
||||
* This method is designed to inform the storage class of its parent
|
||||
* and of the dispatch queue the parent will be operating on.
|
||||
*
|
||||
* The storage class may choose to operate on the same queue as its parent,
|
||||
* or it may operate on its own internal dispatch queue.
|
||||
*
|
||||
* This method should return YES if it was configured properly.
|
||||
* If a storage class is designed to be used with a single parent at a time, this method may return NO.
|
||||
* The XMPPRoster class is configured to ignore the passed
|
||||
* storage class in its init method if this method returns NO.
|
||||
**/
|
||||
- (BOOL)configureWithParent:(XMPPRoster *)aParent queue:(dispatch_queue_t)queue;
|
||||
|
||||
- (void)beginRosterPopulationForXMPPStream:(XMPPStream *)stream withVersion:(NSString *)version;
|
||||
- (void)endRosterPopulationForXMPPStream:(XMPPStream *)stream;
|
||||
|
||||
- (void)handleRosterItem:(NSXMLElement *)item xmppStream:(XMPPStream *)stream;
|
||||
- (void)handlePresence:(XMPPPresence *)presence xmppStream:(XMPPStream *)stream;
|
||||
|
||||
- (BOOL)userExistsWithJID:(XMPPJID *)jid xmppStream:(XMPPStream *)stream;
|
||||
|
||||
- (void)clearAllResourcesForXMPPStream:(XMPPStream *)stream;
|
||||
- (void)clearAllUsersAndResourcesForXMPPStream:(XMPPStream *)stream;
|
||||
|
||||
- (NSArray *)jidsForXMPPStream:(XMPPStream *)stream;
|
||||
|
||||
- (void)getSubscription:(NSString **)subscription
|
||||
ask:(NSString **)ask
|
||||
nickname:(NSString **)nickname
|
||||
groups:(NSArray **)groups
|
||||
forJID:(XMPPJID *)jid
|
||||
xmppStream:(XMPPStream *)stream;
|
||||
|
||||
@optional
|
||||
|
||||
/**
|
||||
* When XMPPvCardAvatarModule is included in the framework, the roster will integrate with it.
|
||||
* Implement this method to provide support for storing the downloaded user photos.
|
||||
**/
|
||||
#if TARGET_OS_IPHONE
|
||||
- (void)setPhoto:(UIImage *)image forUserWithJID:(XMPPJID *)jid xmppStream:(XMPPStream *)stream;
|
||||
#else
|
||||
- (void)setPhoto:(NSImage *)image forUserWithJID:(XMPPJID *)jid xmppStream:(XMPPStream *)stream;
|
||||
#endif
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@protocol XMPPRosterDelegate
|
||||
@optional
|
||||
|
||||
/**
|
||||
* Sent when a presence subscription request is received.
|
||||
* That is, another user has added you to their roster,
|
||||
* and is requesting permission to receive presence broadcasts that you send.
|
||||
*
|
||||
* The entire presence packet is provided for proper extensibility.
|
||||
* You can use [presence from] to get the JID of the user who sent the request.
|
||||
*
|
||||
* The methods acceptPresenceSubscriptionRequestFrom: and rejectPresenceSubscriptionRequestFrom: can
|
||||
* be used to respond to the request.
|
||||
**/
|
||||
- (void)xmppRoster:(XMPPRoster *)sender didReceivePresenceSubscriptionRequest:(XMPPPresence *)presence;
|
||||
|
||||
/**
|
||||
* Sent when a Roster Push is received as specified in Section 2.1.6 of RFC 6121.
|
||||
**/
|
||||
- (void)xmppRoster:(XMPPRoster *)sender didReceiveRosterPush:(XMPPIQ *)iq;
|
||||
|
||||
/**
|
||||
* Sent when the initial roster is received.
|
||||
**/
|
||||
- (void)xmppRosterDidBeginPopulating:(XMPPRoster *)sender withVersion:(NSString *)version;
|
||||
|
||||
/**
|
||||
* Sent when the initial roster has been populated into storage.
|
||||
**/
|
||||
- (void)xmppRosterDidEndPopulating:(XMPPRoster *)sender;
|
||||
|
||||
/**
|
||||
* Sent when the roster receives a roster item.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* <item jid='romeo@example.net' name='Romeo' subscription='both'>
|
||||
* <group>Friends</group>
|
||||
* </item>
|
||||
**/
|
||||
- (void)xmppRoster:(XMPPRoster *)sender didReceiveRosterItem:(NSXMLElement *)item;
|
||||
|
||||
@end
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,16 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "XMPPRoster.h"
|
||||
|
||||
@interface XMPPRoster (PrivateInternalAPI)
|
||||
|
||||
/**
|
||||
* XMPPRosterStorage classes may optionally use the same delegate(s) as their parent XMPPRoster.
|
||||
* This method allows such storage classes to access the delegate(s).
|
||||
*
|
||||
* Note: If the storage class operates on a different queue than its parent,
|
||||
* it MUST dispatch all calls to the multicastDelegate onto its parent's queue.
|
||||
* The parent's dispatch queue is passed in the configureWithParent:queue: method.
|
||||
**/
|
||||
- (GCDMulticastDelegate *)multicastDelegate;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,21 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "XMPP.h"
|
||||
|
||||
@protocol XMPPResource;
|
||||
|
||||
|
||||
@protocol XMPPUser <NSObject>
|
||||
@required
|
||||
|
||||
- (XMPPJID *)jid;
|
||||
- (NSString *)nickname;
|
||||
|
||||
- (BOOL)isOnline;
|
||||
- (BOOL)isPendingApproval;
|
||||
|
||||
- (id <XMPPResource>)primaryResource;
|
||||
- (id <XMPPResource>)resourceForJID:(XMPPJID *)jid;
|
||||
|
||||
- (NSArray *)allResources;
|
||||
|
||||
@end
|
||||
Reference in new issue
Block a user