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

No files matched your search

@@ -0,0 +1,28 @@
//
// 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
@@ -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