diff --git a/External/JSONKit/JSONKit.m b/External/JSONKit/JSONKit.m index 0e9331f1..4994caf9 100644 --- a/External/JSONKit/JSONKit.m +++ b/External/JSONKit/JSONKit.m @@ -677,7 +677,7 @@ + (id)allocWithZone:(NSZone *)zone NSCParameterAssert((objects != NULL) && (_JKArrayClass != NULL) && (_JKArrayInstanceSize > 0UL)); JKArray *array = NULL; if(JK_EXPECT_T((array = (JKArray *)calloc(1UL, _JKArrayInstanceSize)) != NULL)) { // Directly allocate the JKArray instance via calloc. - array->isa = _JKArrayClass; + object_setClass(array, _JKArrayClass); if((array = [array init]) == NULL) { return(NULL); } array->capacity = count; array->count = count; @@ -928,7 +928,7 @@ static void _JKDictionaryResizeIfNeccessary(JKDictionary *dictionary) { NSCParameterAssert((keys != NULL) && (keyHashes != NULL) && (objects != NULL) && (_JKDictionaryClass != NULL) && (_JKDictionaryInstanceSize > 0UL)); JKDictionary *dictionary = NULL; if(JK_EXPECT_T((dictionary = (JKDictionary *)calloc(1UL, _JKDictionaryInstanceSize)) != NULL)) { // Directly allocate the JKDictionary instance via calloc. - dictionary->isa = _JKDictionaryClass; + object_setClass(dictionary, _JKDictionaryClass); if((dictionary = [dictionary init]) == NULL) { return(NULL); } dictionary->capacity = _JKDictionaryCapacityForCount(count); dictionary->count = 0UL; diff --git a/External/Lumberjack/DDASLLogger.h b/External/Lumberjack/DDASLLogger.h old mode 100644 new mode 100755 diff --git a/External/Lumberjack/DDASLLogger.m b/External/Lumberjack/DDASLLogger.m old mode 100644 new mode 100755 index 4ec1e7cb..0c35f2fc --- a/External/Lumberjack/DDASLLogger.m +++ b/External/Lumberjack/DDASLLogger.m @@ -76,15 +76,15 @@ - (void)logMessage:(DDLogMessage *)logMessage const char *msg = [logMsg UTF8String]; int aslLogLevel; - switch (logMessage->logLevel) + switch (logMessage->logFlag) { // Note: By default ASL will filter anything above level 5 (Notice). // So our mappings shouldn't go above that level. - case 1 : aslLogLevel = ASL_LEVEL_CRIT; break; - case 2 : aslLogLevel = ASL_LEVEL_ERR; break; - case 3 : aslLogLevel = ASL_LEVEL_WARNING; break; - default : aslLogLevel = ASL_LEVEL_NOTICE; break; + case LOG_FLAG_ERROR : aslLogLevel = ASL_LEVEL_CRIT; break; + case LOG_FLAG_WARN : aslLogLevel = ASL_LEVEL_ERR; break; + case LOG_FLAG_INFO : aslLogLevel = ASL_LEVEL_WARNING; break; + default : aslLogLevel = ASL_LEVEL_NOTICE; break; } asl_log(client, NULL, aslLogLevel, "%s", msg); diff --git a/External/Lumberjack/DDAbstractDatabaseLogger.h b/External/Lumberjack/DDAbstractDatabaseLogger.h old mode 100644 new mode 100755 diff --git a/External/Lumberjack/DDAbstractDatabaseLogger.m b/External/Lumberjack/DDAbstractDatabaseLogger.m old mode 100644 new mode 100755 index 1c8e0df0..4674fbbc --- a/External/Lumberjack/DDAbstractDatabaseLogger.m +++ b/External/Lumberjack/DDAbstractDatabaseLogger.m @@ -15,32 +15,6 @@ #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). #endif -/** - * Does ARC support support GCD objects? - * It does if the minimum deployment target is iOS 6+ or Mac OS X 10.8+ -**/ -#if TARGET_OS_IPHONE - - // Compiling for iOS - - #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 60000 // iOS 6.0 or later - #define NEEDS_DISPATCH_RETAIN_RELEASE 0 - #else // iOS 5.X or earlier - #define NEEDS_DISPATCH_RETAIN_RELEASE 1 - #endif - -#else - - // Compiling for Mac OS X - - #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1080 // Mac OS X 10.8 or later - #define NEEDS_DISPATCH_RETAIN_RELEASE 0 - #else - #define NEEDS_DISPATCH_RETAIN_RELEASE 1 // Mac OS X 10.7 or earlier - #endif - -#endif - @interface DDAbstractDatabaseLogger () - (void)destroySaveTimer; - (void)destroyDeleteTimer; @@ -147,7 +121,7 @@ - (void)destroySaveTimer dispatch_resume(saveTimer); saveTimerSuspended = NO; } - #if NEEDS_DISPATCH_RETAIN_RELEASE + #if !OS_OBJECT_USE_OBJC dispatch_release(saveTimer); #endif saveTimer = NULL; @@ -158,7 +132,7 @@ - (void)updateAndResumeSaveTimer { if ((saveTimer != NULL) && (saveInterval > 0.0) && (unsavedTime > 0.0)) { - uint64_t interval = saveInterval * NSEC_PER_SEC; + uint64_t interval = (uint64_t)(saveInterval * NSEC_PER_SEC); dispatch_time_t startTime = dispatch_time(unsavedTime, interval); dispatch_source_set_timer(saveTimer, startTime, interval, 1.0); @@ -192,7 +166,7 @@ - (void)destroyDeleteTimer if (deleteTimer) { dispatch_source_cancel(deleteTimer); - #if NEEDS_DISPATCH_RETAIN_RELEASE + #if !OS_OBJECT_USE_OBJC dispatch_release(deleteTimer); #endif deleteTimer = NULL; @@ -203,7 +177,7 @@ - (void)updateDeleteTimer { if ((deleteTimer != NULL) && (deleteInterval > 0.0) && (maxAge > 0.0)) { - uint64_t interval = deleteInterval * NSEC_PER_SEC; + uint64_t interval = (uint64_t)(deleteInterval * NSEC_PER_SEC); dispatch_time_t startTime; if (lastDeleteTime > 0) @@ -220,16 +194,18 @@ - (void)createAndStartDeleteTimer if ((deleteTimer == NULL) && (deleteInterval > 0.0) && (maxAge > 0.0)) { deleteTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, loggerQueue); - - dispatch_source_set_event_handler(deleteTimer, ^{ @autoreleasepool { - - [self performDelete]; - - }}); - - [self updateDeleteTimer]; - - dispatch_resume(deleteTimer); + + if (deleteTimer != NULL) { + dispatch_source_set_event_handler(deleteTimer, ^{ @autoreleasepool { + + [self performDelete]; + + }}); + + [self updateDeleteTimer]; + + if (deleteTimer != NULL) dispatch_resume(deleteTimer); + } } } @@ -239,25 +215,35 @@ - (void)createAndStartDeleteTimer - (NSUInteger)saveThreshold { - if (dispatch_get_current_queue() == loggerQueue) - { - return saveThreshold; - } - else - { - __block NSUInteger result; - + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + // Note: The internal implementation MUST access the colorsEnabled variable directly, + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. + + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax."); + + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + + __block NSUInteger result; + + dispatch_sync(globalLoggingQueue, ^{ dispatch_sync(loggerQueue, ^{ result = saveThreshold; }); - - return result; - } + }); + + return result; } - (void)setSaveThreshold:(NSUInteger)threshold { - dispatch_block_t block = ^{ + dispatch_block_t block = ^{ @autoreleasepool { if (saveThreshold != threshold) { @@ -270,42 +256,60 @@ - (void)setSaveThreshold:(NSUInteger)threshold if ((unsavedCount >= saveThreshold) && (saveThreshold > 0)) { - @autoreleasepool { - - [self performSaveAndSuspendSaveTimer]; - - } + [self performSaveAndSuspendSaveTimer]; } } - }; + }}; - if (dispatch_get_current_queue() == loggerQueue) + // The design of the setter logic below is taken from the DDAbstractLogger implementation. + // For documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) + { block(); + } else - dispatch_async(loggerQueue, block); + { + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + + dispatch_async(globalLoggingQueue, ^{ + dispatch_async(loggerQueue, block); + }); + } } - (NSTimeInterval)saveInterval { - if (dispatch_get_current_queue() == loggerQueue) - { - return saveInterval; - } - else - { - __block NSTimeInterval result; - + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + // Note: The internal implementation MUST access the colorsEnabled variable directly, + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. + + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax."); + + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + + __block NSTimeInterval result; + + dispatch_sync(globalLoggingQueue, ^{ dispatch_sync(loggerQueue, ^{ result = saveInterval; }); - - return result; - } + }); + + return result; } - (void)setSaveInterval:(NSTimeInterval)interval { - dispatch_block_t block = ^{ + dispatch_block_t block = ^{ @autoreleasepool { // C99 recommended floating point comparison macro // Read: isLessThanOrGreaterThan(floatA, floatB) @@ -329,28 +333,25 @@ - (void)setSaveInterval:(NSTimeInterval)interval if (saveInterval > 0.0) { - @autoreleasepool + if (saveTimer == NULL) + { + // Handles #2 + // + // Since the saveTimer uses the unsavedTime to calculate it's first fireDate, + // if a save is needed the timer will fire immediately. + + [self createSuspendedSaveTimer]; + [self updateAndResumeSaveTimer]; + } + else { - if (saveTimer == NULL) - { - // Handles #2 - // - // Since the saveTimer uses the unsavedTime to calculate it's first fireDate, - // if a save is needed the timer will fire immediately. - - [self createSuspendedSaveTimer]; - [self updateAndResumeSaveTimer]; - } - else - { - // Handles #3 - // Handles #4 - // - // Since the saveTimer uses the unsavedTime to calculate it's first fireDate, - // if a save is needed the timer will fire immediately. - - [self updateAndResumeSaveTimer]; - } + // Handles #3 + // Handles #4 + // + // Since the saveTimer uses the unsavedTime to calculate it's first fireDate, + // if a save is needed the timer will fire immediately. + + [self updateAndResumeSaveTimer]; } } else if (saveTimer) @@ -360,35 +361,57 @@ - (void)setSaveInterval:(NSTimeInterval)interval [self destroySaveTimer]; } } - }; + }}; - if (dispatch_get_current_queue() == loggerQueue) + // The design of the setter logic below is taken from the DDAbstractLogger implementation. + // For documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) + { block(); + } else - dispatch_async(loggerQueue, block); + { + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + + dispatch_async(globalLoggingQueue, ^{ + dispatch_async(loggerQueue, block); + }); + } } - (NSTimeInterval)maxAge { - if (dispatch_get_current_queue() == loggerQueue) - { - return maxAge; - } - else - { - __block NSTimeInterval result; - + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + // Note: The internal implementation MUST access the colorsEnabled variable directly, + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. + + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax."); + + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + + __block NSTimeInterval result; + + dispatch_sync(globalLoggingQueue, ^{ dispatch_sync(loggerQueue, ^{ result = maxAge; }); - - return result; - } + }); + + return result; } - (void)setMaxAge:(NSTimeInterval)interval { - dispatch_block_t block = ^{ + dispatch_block_t block = ^{ @autoreleasepool { // C99 recommended floating point comparison macro // Read: isLessThanOrGreaterThan(floatA, floatB) @@ -438,46 +461,65 @@ - (void)setMaxAge:(NSTimeInterval)interval if (shouldDeleteNow) { - @autoreleasepool - { - [self performDelete]; - - if (deleteTimer) - [self updateDeleteTimer]; - else - [self createAndStartDeleteTimer]; - } + [self performDelete]; + + if (deleteTimer) + [self updateDeleteTimer]; + else + [self createAndStartDeleteTimer]; } } - }; + }}; - if (dispatch_get_current_queue() == loggerQueue) + // The design of the setter logic below is taken from the DDAbstractLogger implementation. + // For documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) + { block(); + } else - dispatch_async(loggerQueue, block); + { + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + + dispatch_async(globalLoggingQueue, ^{ + dispatch_async(loggerQueue, block); + }); + } } - (NSTimeInterval)deleteInterval { - if (dispatch_get_current_queue() == loggerQueue) - { - return deleteInterval; - } - else - { - __block NSTimeInterval result; - + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + // Note: The internal implementation MUST access the colorsEnabled variable directly, + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. + + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax."); + + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + + __block NSTimeInterval result; + + dispatch_sync(globalLoggingQueue, ^{ dispatch_sync(loggerQueue, ^{ result = deleteInterval; }); - - return result; - } + }); + + return result; } - (void)setDeleteInterval:(NSTimeInterval)interval { - dispatch_block_t block = ^{ + dispatch_block_t block = ^{ @autoreleasepool { // C99 recommended floating point comparison macro // Read: isLessThanOrGreaterThan(floatA, floatB) @@ -501,27 +543,24 @@ - (void)setDeleteInterval:(NSTimeInterval)interval if (deleteInterval > 0.0) { - @autoreleasepool + if (deleteTimer == NULL) + { + // Handles #2 + // + // Since the deleteTimer uses the lastDeleteTime to calculate it's first fireDate, + // if a delete is needed the timer will fire immediately. + + [self createAndStartDeleteTimer]; + } + else { - if (deleteTimer == NULL) - { - // Handles #2 - // - // Since the deleteTimer uses the lastDeleteTime to calculate it's first fireDate, - // if a delete is needed the timer will fire immediately. - - [self createAndStartDeleteTimer]; - } - else - { - // Handles #3 - // Handles #4 - // - // Since the deleteTimer uses the lastDeleteTime to calculate it's first fireDate, - // if a save is needed the timer will fire immediately. - - [self updateDeleteTimer]; - } + // Handles #3 + // Handles #4 + // + // Since the deleteTimer uses the lastDeleteTime to calculate it's first fireDate, + // if a save is needed the timer will fire immediately. + + [self updateDeleteTimer]; } } else if (deleteTimer) @@ -531,30 +570,52 @@ - (void)setDeleteInterval:(NSTimeInterval)interval [self destroyDeleteTimer]; } } - }; + }}; - if (dispatch_get_current_queue() == loggerQueue) + // The design of the setter logic below is taken from the DDAbstractLogger implementation. + // For documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) + { block(); + } else - dispatch_async(loggerQueue, block); + { + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + + dispatch_async(globalLoggingQueue, ^{ + dispatch_async(loggerQueue, block); + }); + } } - (BOOL)deleteOnEverySave { - if (dispatch_get_current_queue() == loggerQueue) - { - return deleteOnEverySave; - } - else - { - __block BOOL result; - + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + // Note: The internal implementation MUST access the colorsEnabled variable directly, + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. + + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax."); + + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + + __block BOOL result; + + dispatch_sync(globalLoggingQueue, ^{ dispatch_sync(loggerQueue, ^{ result = deleteOnEverySave; }); - - return result; - } + }); + + return result; } - (void)setDeleteOnEverySave:(BOOL)flag @@ -564,10 +625,22 @@ - (void)setDeleteOnEverySave:(BOOL)flag deleteOnEverySave = flag; }; - if (dispatch_get_current_queue() == loggerQueue) + // The design of the setter logic below is taken from the DDAbstractLogger implementation. + // For documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) + { block(); + } else - dispatch_async(loggerQueue, block); + { + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + + dispatch_async(globalLoggingQueue, ^{ + dispatch_async(loggerQueue, block); + }); + } } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -581,7 +654,7 @@ - (void)savePendingLogEntries [self performSaveAndSuspendSaveTimer]; }}; - if (dispatch_get_current_queue() == loggerQueue) + if ([self isOnInternalLoggerQueue]) block(); else dispatch_async(loggerQueue, block); @@ -594,7 +667,7 @@ - (void)deleteOldLogEntries [self performDelete]; }}; - if (dispatch_get_current_queue() == loggerQueue) + if ([self isOnInternalLoggerQueue]) block(); else dispatch_async(loggerQueue, block); diff --git a/External/Lumberjack/DDFileLogger.h b/External/Lumberjack/DDFileLogger.h old mode 100644 new mode 100755 diff --git a/External/Lumberjack/DDFileLogger.m b/External/Lumberjack/DDFileLogger.m old mode 100644 new mode 100755 index 531b3989..8f9df36a --- a/External/Lumberjack/DDFileLogger.m +++ b/External/Lumberjack/DDFileLogger.m @@ -19,31 +19,6 @@ #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). #endif -// Does ARC support support GCD objects? -// It does if the minimum deployment target is iOS 6+ or Mac OS X 10.8+ - -#if TARGET_OS_IPHONE - - // Compiling for iOS - - #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 60000 // iOS 6.0 or later - #define NEEDS_DISPATCH_RETAIN_RELEASE 0 - #else // iOS 5.X or earlier - #define NEEDS_DISPATCH_RETAIN_RELEASE 1 - #endif - -#else - - // Compiling for Mac OS X - - #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1080 // Mac OS X 10.8 or later - #define NEEDS_DISPATCH_RETAIN_RELEASE 0 - #else - #define NEEDS_DISPATCH_RETAIN_RELEASE 1 // Mac OS X 10.7 or earlier - #endif - -#endif - // We probably shouldn't be using DDLog() statements within the DDLog implementation. // But we still want to leave our log statements for any future debugging, // and to allow other developers to trace the implementation (which is a great learning tool). @@ -522,39 +497,36 @@ - (void)dealloc - (unsigned long long)maximumFileSize { + __block unsigned long long result; + + dispatch_block_t block = ^{ + result = maximumFileSize; + }; + // The design of this method is taken from the DDAbstractLogger implementation. // For extensive documentation please refer to the DDAbstractLogger implementation. - // Note: The internal implementation should access the maximumFileSize variable directly, - // but if we forget to do this, then this method should at least work properly. + // Note: The internal implementation MUST access the maximumFileSize variable directly, + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. - dispatch_queue_t currentQueue = dispatch_get_current_queue(); - if (currentQueue == loggerQueue) - { - return maximumFileSize; - } - else - { - dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; - NSAssert(currentQueue != globalLoggingQueue, @"Core architecture requirement failure"); - - __block unsigned long long result; - - dispatch_sync(globalLoggingQueue, ^{ - dispatch_sync(loggerQueue, ^{ - result = maximumFileSize; - }); - }); - - return result; - } + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax."); + + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + + dispatch_sync(globalLoggingQueue, ^{ + dispatch_sync(loggerQueue, block); + }); + + return result; } - (void)setMaximumFileSize:(unsigned long long)newMaximumFileSize { - // The design of this method is taken from the DDAbstractLogger implementation. - // For documentation please refer to the DDAbstractLogger implementation. - dispatch_block_t block = ^{ @autoreleasepool { maximumFileSize = newMaximumFileSize; @@ -562,78 +534,82 @@ - (void)setMaximumFileSize:(unsigned long long)newMaximumFileSize }}; - dispatch_queue_t currentQueue = dispatch_get_current_queue(); - if (currentQueue == loggerQueue) - { - block(); - } - else - { - dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; - NSAssert(currentQueue != globalLoggingQueue, @"Core architecture requirement failure"); - - dispatch_async(globalLoggingQueue, ^{ - dispatch_async(loggerQueue, block); - }); - } + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + // Note: The internal implementation MUST access the maximumFileSize variable directly, + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. + + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax."); + + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + + dispatch_async(globalLoggingQueue, ^{ + dispatch_async(loggerQueue, block); + }); } - (NSTimeInterval)rollingFrequency { + __block NSTimeInterval result; + + dispatch_block_t block = ^{ + result = rollingFrequency; + }; + // The design of this method is taken from the DDAbstractLogger implementation. - // For documentation please refer to the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. // Note: The internal implementation should access the rollingFrequency variable directly, - // but if we forget to do this, then this method should at least work properly. + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. - dispatch_queue_t currentQueue = dispatch_get_current_queue(); - if (currentQueue == loggerQueue) - { - return rollingFrequency; - } - else - { - dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; - NSAssert(currentQueue != globalLoggingQueue, @"Core architecture requirement failure"); - - __block NSTimeInterval result; - - dispatch_sync(globalLoggingQueue, ^{ - dispatch_sync(loggerQueue, ^{ - result = rollingFrequency; - }); - }); - - return result; - } + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax."); + + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + + dispatch_sync(globalLoggingQueue, ^{ + dispatch_sync(loggerQueue, block); + }); + + return result; } - (void)setRollingFrequency:(NSTimeInterval)newRollingFrequency { - // The design of this method is taken from the DDAbstractLogger implementation. - // For documentation please refer to the DDAbstractLogger implementation. - dispatch_block_t block = ^{ @autoreleasepool { rollingFrequency = newRollingFrequency; [self maybeRollLogFileDueToAge]; - }}; - dispatch_queue_t currentQueue = dispatch_get_current_queue(); - if (currentQueue == loggerQueue) - { - block(); - } - else - { - dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; - NSAssert(currentQueue != globalLoggingQueue, @"Core architecture requirement failure"); - - dispatch_async(globalLoggingQueue, ^{ - dispatch_async(loggerQueue, block); - }); - } + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + // Note: The internal implementation should access the rollingFrequency variable directly, + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. + + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax."); + + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + + dispatch_async(globalLoggingQueue, ^{ + dispatch_async(loggerQueue, block); + }); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -673,14 +649,14 @@ - (void)scheduleTimerToRollLogFileDueToAge }}); - #if NEEDS_DISPATCH_RETAIN_RELEASE + #if !OS_OBJECT_USE_OBJC dispatch_source_t theRollingTimer = rollingTimer; dispatch_source_set_cancel_handler(rollingTimer, ^{ dispatch_release(theRollingTimer); }); #endif - uint64_t delay = [logFileRollingDate timeIntervalSinceNow] * NSEC_PER_SEC; + uint64_t delay = (uint64_t)([logFileRollingDate timeIntervalSinceNow] * NSEC_PER_SEC); dispatch_time_t fireTime = dispatch_time(DISPATCH_TIME_NOW, delay); dispatch_source_set_timer(rollingTimer, fireTime, DISPATCH_TIME_FOREVER, 1.0); @@ -691,24 +667,23 @@ - (void)rollLogFile { // This method is public. // We need to execute the rolling on our logging thread/queue. - // - // The design of this method is taken from the DDAbstractLogger implementation. - // For documentation please refer to the DDAbstractLogger implementation. dispatch_block_t block = ^{ @autoreleasepool { [self rollLogFileNow]; }}; - dispatch_queue_t currentQueue = dispatch_get_current_queue(); - if (currentQueue == loggerQueue) + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) { block(); } else { dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; - NSAssert(currentQueue != globalLoggingQueue, @"Core architecture requirement failure"); + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); dispatch_async(globalLoggingQueue, ^{ dispatch_async(loggerQueue, block); @@ -1042,14 +1017,14 @@ - (NSTimeInterval)age - (NSString *)description { - return [@{@"filePath": self.filePath, - @"fileName": self.fileName, - @"fileAttributes": self.fileAttributes, - @"creationDate": self.creationDate, - @"modificationDate": self.modificationDate, - @"fileSize": @(self.fileSize), - @"age": @(self.age), - @"isArchived": @(self.isArchived)} description]; + return [@{@"filePath": self.filePath ?: @"", + @"fileName": self.fileName ?: @"", + @"fileAttributes": self.fileAttributes ?: @"", + @"creationDate": self.creationDate ?: @"", + @"modificationDate": self.modificationDate ?: @"", + @"fileSize": @(self.fileSize), + @"age": @(self.age), + @"isArchived": @(self.isArchived)} description]; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/External/Lumberjack/DDLog.h b/External/Lumberjack/DDLog.h old mode 100644 new mode 100755 index 57c2f096..5da18493 --- a/External/Lumberjack/DDLog.h +++ b/External/Lumberjack/DDLog.h @@ -252,7 +252,7 @@ NSString *DDExtractFileNameWithoutExtension(const char *filePath, BOOL copy); /** * The THIS_METHOD macro gives you the name of the current objective-c method. * - * For example: DDLogWarn(@"%@ - Requires non-nil strings") -> @"setMake:model: requires non-nil strings" + * For example: DDLogWarn(@"%@ - Requires non-nil strings", THIS_METHOD) -> @"setMake:model: requires non-nil strings" * * Note: This does NOT work in straight C functions (non objective-c). * Instead you should use the predefined __FUNCTION__ macro. @@ -489,7 +489,7 @@ NSString *DDExtractFileNameWithoutExtension(const char *filePath, BOOL copy); enum { DDLogMessageCopyFile = 1 << 0, - DDLogMessageCopyFunction = 1 << 1, + DDLogMessageCopyFunction = 1 << 1 }; typedef int DDLogMessageOptions; @@ -594,4 +594,8 @@ typedef int DDLogMessageOptions; - (id )logFormatter; - (void)setLogFormatter:(id )formatter; +// For thread-safety assertions +- (BOOL)isOnGlobalLoggingQueue; +- (BOOL)isOnInternalLoggerQueue; + @end diff --git a/External/Lumberjack/DDLog.m b/External/Lumberjack/DDLog.m old mode 100644 new mode 100755 index 9b09ac1c..287fc085 --- a/External/Lumberjack/DDLog.m +++ b/External/Lumberjack/DDLog.m @@ -22,31 +22,6 @@ #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). #endif -// Does ARC support support GCD objects? -// It does if the minimum deployment target is iOS 6+ or Mac OS X 10.8+ - -#if TARGET_OS_IPHONE - - // Compiling for iOS - - #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 60000 // iOS 6.0 or later - #define NEEDS_DISPATCH_RETAIN_RELEASE 0 - #else // iOS 5.X or earlier - #define NEEDS_DISPATCH_RETAIN_RELEASE 1 - #endif - -#else - - // Compiling for Mac OS X - - #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1080 // Mac OS X 10.8 or later - #define NEEDS_DISPATCH_RETAIN_RELEASE 0 - #else - #define NEEDS_DISPATCH_RETAIN_RELEASE 1 // Mac OS X 10.7 or earlier - #endif - -#endif - // We probably shouldn't be using DDLog() statements within the DDLog implementation. // But we still want to leave our log statements for any future debugging, // and to allow other developers to trace the implementation (which is a great learning tool). @@ -71,6 +46,14 @@ #define LOG_MAX_QUEUE_SIZE 1000 // Should not exceed INT32_MAX +// The "global logging queue" refers to [DDLog loggingQueue]. +// It is the queue that all log statements go through. +// +// The logging queue sets a flag via dispatch_queue_set_specific using this key. +// We can check for this key via dispatch_get_specific() to see if we're on the "global logging queue". + +static void *const GlobalLoggingQueueIdentityKey = (void *)&GlobalLoggingQueueIdentityKey; + @interface DDLoggerNode : NSObject { @public @@ -139,6 +122,9 @@ + (void)initialize loggingQueue = dispatch_queue_create("cocoa.lumberjack", NULL); loggingGroup = dispatch_group_create(); + void *nonNullValue = GlobalLoggingQueueIdentityKey; // Whatever, just not null + dispatch_queue_set_specific(loggingQueue, GlobalLoggingQueueIdentityKey, nonNullValue, NULL); + queueSemaphore = dispatch_semaphore_create(LOG_MAX_QUEUE_SIZE); // Figure out how many processors are available. @@ -443,6 +429,7 @@ + (NSArray *)registeredClasses // So we can allocate our buffer, and get pointers to all the class definitions. Class *classes = (Class *)malloc(sizeof(Class) * numClasses); + if (classes == NULL) return nil; numClasses = objc_getClassList(classes, numClasses); @@ -794,7 +781,7 @@ - (id)initWithLogger:(id )aLogger loggerQueue:(dispatch_queue_t)aLogge if (aLoggerQueue) { loggerQueue = aLoggerQueue; - #if NEEDS_DISPATCH_RETAIN_RELEASE + #if !OS_OBJECT_USE_OBJC dispatch_retain(loggerQueue); #endif } @@ -809,7 +796,7 @@ + (DDLoggerNode *)nodeWithLogger:(id )logger loggerQueue:(dispatch_que - (void)dealloc { - #if NEEDS_DISPATCH_RETAIN_RELEASE + #if !OS_OBJECT_USE_OBJC if (loggerQueue) dispatch_release(loggerQueue); #endif } @@ -828,6 +815,7 @@ @implementation DDLogMessage size_t length = strlen(str); char * result = malloc(length + 1); + if (result == NULL) return NULL; strncpy(result, str, length); result[length] = 0; @@ -860,7 +848,7 @@ - (id)initWithLogMsg:(NSString *)msg file = (char *)aFile; if (options & DDLogMessageCopyFunction) - file = dd_str_copy(aFunction); + function = dd_str_copy(aFunction); else function = (char *)aFunction; @@ -868,7 +856,24 @@ - (id)initWithLogMsg:(NSString *)msg machThreadID = pthread_mach_thread_np(pthread_self()); - queueLabel = dd_str_copy(dispatch_queue_get_label(dispatch_get_current_queue())); + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wdeprecated-declarations" + // The documentation for dispatch_get_current_queue() states: + // + // > [This method is] "recommended for debugging and logging purposes only"... + // + // Well that's exactly how we're using it here. Literally for logging purposes only. + // However, Apple has decided to deprecate this method anyway. + // However they have not given us an alternate version of dispatch_queue_get_label() that + // automatically uses the current queue, thus dispatch_get_current_queue() is still required. + // + // If dispatch_get_current_queue() disappears, without a dispatch_queue_get_label() alternative, + // Apple will have effectively taken away our ability to properly log the name of executing dispatch queue. + + dispatch_queue_t currentQueue = dispatch_get_current_queue(); + #pragma clang diagnostic pop + + queueLabel = dd_str_copy(dispatch_queue_get_label(currentQueue)); threadName = [[NSThread currentThread] name]; } @@ -924,13 +929,32 @@ - (id)init } loggerQueue = dispatch_queue_create(loggerQueueName, NULL); + + // We're going to use dispatch_queue_set_specific() to "mark" our loggerQueue. + // Later we can use dispatch_get_specific() to determine if we're executing on our loggerQueue. + // The documentation states: + // + // > Keys are only compared as pointers and are never dereferenced. + // > Thus, you can use a pointer to a static variable for a specific subsystem or + // > any other value that allows you to identify the value uniquely. + // > Specifying a pointer to a string constant is not recommended. + // + // So we're going to use the very convenient key of "self", + // which also works when multiple logger classes extend this class, as each will have a different "self" key. + // + // This is used primarily for thread-safety assertions (via the isOnInternalLoggerQueue method below). + + void *key = (__bridge void *)self; + void *nonNullValue = (__bridge void *)self; + + dispatch_queue_set_specific(loggerQueue, key, nonNullValue, NULL); } return self; } - (void)dealloc { - #if NEEDS_DISPATCH_RETAIN_RELEASE + #if !OS_OBJECT_USE_OBJC if (loggerQueue) dispatch_release(loggerQueue); #endif } @@ -950,78 +974,70 @@ - (void)logMessage:(DDLogMessage *)logMessage // // They would expect formatter to equal myFormatter. // This functionality must be ensured by the getter and setter method. - // + // // The thread safety must not come at a cost to the performance of the logMessage method. // This method is likely called sporadically, while the logMessage method is called repeatedly. // This means, the implementation of this method: // - Must NOT require the logMessage method to acquire a lock. // - Must NOT require the logMessage method to access an atomic property (also a lock of sorts). - // + // // Thread safety is ensured by executing access to the formatter variable on the loggerQueue. // This is the same queue that the logMessage method operates on. - // + // // Note: The last time I benchmarked the performance of direct access vs atomic property access, // direct access was over twice as fast on the desktop and over 6 times as fast on the iPhone. // - // - // loggerQueue : Our own private internal queue that the logMessage method runs on. - // Operations are added to this queue from the global loggingQueue. - // - // loggingQueue : The queue that all log messages go through before they arrive in our loggerQueue. - // - // It is important to note that, while the loggerQueue is used to create thread-safety for our formatter, - // changes to the formatter variable are queued through the loggingQueue. - // - // Since this will obviously confuse the hell out of me later, here is a better description. - // Imagine the following code: - // + // Furthermore, consider the following code: + // // DDLogVerbose(@"log msg 1"); // DDLogVerbose(@"log msg 2"); // [logger setFormatter:myFormatter]; // DDLogVerbose(@"log msg 3"); - // + // // Our intuitive requirement means that the new formatter will only apply to the 3rd log message. - // But notice what happens if we have asynchronous logging enabled for verbose mode. + // This must remain true even when using asynchronous logging. + // We must keep in mind the various queue's that are in play here: // - // Log msg 1 starts executing asynchronously on the loggingQueue. - // The loggingQueue executes the log statement on each logger concurrently. - // That means it executes log msg 1 on our loggerQueue. - // While log msg 1 is executing, log msg 2 gets added to the loggingQueue. - // Then the user requests that we change our formatter. - // So at this exact moment, our queues look like this: + // loggerQueue : Our own private internal queue that the logMessage method runs on. + // Operations are added to this queue from the global loggingQueue. // - // loggerQueue : executing log msg 1, nil - // loggingQueue : executing log msg 1, log msg 2, nil + // globalLoggingQueue : The queue that all log messages go through before they arrive in our loggerQueue. // - // So direct access to the formatter is only available if requested from the loggerQueue. - // In all other circumstances we need to go through the loggingQueue to get the proper value. + // All log statements go through the serial gloabalLoggingQueue before they arrive at our loggerQueue. + // Thus this method also goes through the serial globalLoggingQueue to ensure intuitive operation. - dispatch_queue_t currentQueue = dispatch_get_current_queue(); - if (currentQueue == loggerQueue) - { - return formatter; - } - else - { - dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; - NSAssert(currentQueue != globalLoggingQueue, @"Core architecture requirement failure"); - - __block id result; - - dispatch_sync(globalLoggingQueue, ^{ - dispatch_sync(loggerQueue, ^{ - result = formatter; - }); + // IMPORTANT NOTE: + // + // Methods within the DDLogger implementation MUST access the formatter ivar directly. + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. + + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax."); + + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + + __block id result; + + dispatch_sync(globalLoggingQueue, ^{ + dispatch_sync(loggerQueue, ^{ + result = formatter; }); - - return result; - } + }); + + return result; } - (void)setLogFormatter:(id )logFormatter { // The design of this method is documented extensively in the logFormatter message (above in code). + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax."); + dispatch_block_t block = ^{ @autoreleasepool { if (formatter != logFormatter) @@ -1029,7 +1045,7 @@ - (void)setLogFormatter:(id )logFormatter if ([formatter respondsToSelector:@selector(willRemoveFromLogger:)]) { [formatter willRemoveFromLogger:self]; } - + formatter = logFormatter; if ([formatter respondsToSelector:@selector(didAddToLogger:)]) { @@ -1038,20 +1054,11 @@ - (void)setLogFormatter:(id )logFormatter } }}; - dispatch_queue_t currentQueue = dispatch_get_current_queue(); - if (currentQueue == loggerQueue) - { - block(); - } - else - { - dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; - NSAssert(currentQueue != globalLoggingQueue, @"Core architecture requirement failure"); - - dispatch_async(globalLoggingQueue, ^{ - dispatch_async(loggerQueue, block); - }); - } + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + + dispatch_async(globalLoggingQueue, ^{ + dispatch_async(loggerQueue, block); + }); } - (dispatch_queue_t)loggerQueue @@ -1064,4 +1071,15 @@ - (NSString *)loggerName return NSStringFromClass([self class]); } +- (BOOL)isOnGlobalLoggingQueue +{ + return (dispatch_get_specific(GlobalLoggingQueueIdentityKey) != NULL); +} + +- (BOOL)isOnInternalLoggerQueue +{ + void *key = (__bridge void *)self; + return (dispatch_get_specific(key) != NULL); +} + @end diff --git a/External/Lumberjack/DDTTYLogger.h b/External/Lumberjack/DDTTYLogger.h old mode 100644 new mode 100755 diff --git a/External/Lumberjack/DDTTYLogger.m b/External/Lumberjack/DDTTYLogger.m old mode 100644 new mode 100755 index 8a9c8b2d..bc616eab --- a/External/Lumberjack/DDTTYLogger.m +++ b/External/Lumberjack/DDTTYLogger.m @@ -673,11 +673,14 @@ + (void)getRed:(CGFloat *)rPtr green:(CGFloat *)gPtr blue:(CGFloat *)bPtr fromCo // iOS + BOOL done = NO; + if ([color respondsToSelector:@selector(getRed:green:blue:alpha:)]) { - [color getRed:rPtr green:gPtr blue:bPtr alpha:NULL]; + done = [color getRed:rPtr green:gPtr blue:bPtr alpha:NULL]; } - else + + if (!done) { // The method getRed:green:blue:alpha: was only available starting iOS 5. // So in iOS 4 and earlier, we have to jump through hoops. @@ -702,7 +705,9 @@ + (void)getRed:(CGFloat *)rPtr green:(CGFloat *)gPtr blue:(CGFloat *)bPtr fromCo // Mac OS X - [color getRed:rPtr green:gPtr blue:bPtr alpha:NULL]; + NSColor *safeColor = [color colorUsingColorSpaceName:NSCalibratedRGBColorSpace]; + + [safeColor getRed:rPtr green:gPtr blue:bPtr alpha:NULL]; #endif } @@ -835,6 +840,7 @@ - (id)init appLen = [appName lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; app = (char *)malloc(appLen + 1); + if (app == NULL) return nil; [appName getCString:app maxLength:(appLen+1) encoding:NSUTF8StringEncoding]; @@ -844,8 +850,10 @@ - (id)init pidLen = [processID lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; pid = (char *)malloc(pidLen + 1); + if (pid == NULL) return nil; - [processID getCString:pid maxLength:(pidLen+1) encoding:NSUTF8StringEncoding]; + BOOL processedID = [processID getCString:pid maxLength:(pidLen+1) encoding:NSUTF8StringEncoding]; + if (NO == processedID) return nil; // Initialize color stuff @@ -865,28 +873,29 @@ - (void)loadDefaultColorProfiles - (BOOL)colorsEnabled { // The design of this method is taken from the DDAbstractLogger implementation. - // For documentation please refer to the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. - dispatch_queue_t currentQueue = dispatch_get_current_queue(); - if (currentQueue == loggerQueue) - { - return colorsEnabled; - } - else - { - dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; - NSAssert(currentQueue != globalLoggingQueue, @"Core architecture requirement failure"); - - __block BOOL result; - - dispatch_sync(globalLoggingQueue, ^{ - dispatch_sync(loggerQueue, ^{ - result = colorsEnabled; - }); + // Note: The internal implementation MUST access the colorsEnabled variable directly, + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. + + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax."); + + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + + __block BOOL result; + + dispatch_sync(globalLoggingQueue, ^{ + dispatch_sync(loggerQueue, ^{ + result = colorsEnabled; }); - - return result; - } + }); + + return result; } - (void)setColorsEnabled:(BOOL)newColorsEnabled @@ -900,23 +909,24 @@ - (void)setColorsEnabled:(BOOL)newColorsEnabled } }}; - // The design of the setter logic below is taken from the DDAbstractLogger implementation. - // For documentation please refer to the DDAbstractLogger implementation. + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. - dispatch_queue_t currentQueue = dispatch_get_current_queue(); - if (currentQueue == loggerQueue) - { - block(); - } - else - { - dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; - NSAssert(currentQueue != globalLoggingQueue, @"Core architecture requirement failure"); - - dispatch_async(globalLoggingQueue, ^{ - dispatch_async(loggerQueue, block); - }); - } + // Note: The internal implementation MUST access the colorsEnabled variable directly, + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. + + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax."); + + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + + dispatch_async(globalLoggingQueue, ^{ + dispatch_async(loggerQueue, block); + }); } - (void)setForegroundColor:(OSColor *)txtColor backgroundColor:(OSColor *)bgColor forFlag:(int)mask @@ -956,15 +966,14 @@ - (void)setForegroundColor:(OSColor *)txtColor backgroundColor:(OSColor *)bgColo // The design of the setter logic below is taken from the DDAbstractLogger implementation. // For documentation please refer to the DDAbstractLogger implementation. - dispatch_queue_t currentQueue = dispatch_get_current_queue(); - if (currentQueue == loggerQueue) + if ([self isOnInternalLoggerQueue]) { block(); } else { dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; - NSAssert(currentQueue != globalLoggingQueue, @"Core architecture requirement failure"); + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); dispatch_async(globalLoggingQueue, ^{ dispatch_async(loggerQueue, block); @@ -992,15 +1001,14 @@ - (void)setForegroundColor:(OSColor *)txtColor backgroundColor:(OSColor *)bgColo // The design of the setter logic below is taken from the DDAbstractLogger implementation. // For documentation please refer to the DDAbstractLogger implementation. - dispatch_queue_t currentQueue = dispatch_get_current_queue(); - if (currentQueue == loggerQueue) + if ([self isOnInternalLoggerQueue]) { block(); } else { dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; - NSAssert(currentQueue != globalLoggingQueue, @"Core architecture requirement failure"); + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); dispatch_async(globalLoggingQueue, ^{ dispatch_async(loggerQueue, block); @@ -1037,15 +1045,14 @@ - (void)clearColorsForFlag:(int)mask context:(int)context // The design of the setter logic below is taken from the DDAbstractLogger implementation. // For documentation please refer to the DDAbstractLogger implementation. - dispatch_queue_t currentQueue = dispatch_get_current_queue(); - if (currentQueue == loggerQueue) + if ([self isOnInternalLoggerQueue]) { block(); } else { dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; - NSAssert(currentQueue != globalLoggingQueue, @"Core architecture requirement failure"); + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); dispatch_async(globalLoggingQueue, ^{ dispatch_async(loggerQueue, block); @@ -1065,15 +1072,14 @@ - (void)clearColorsForTag:(id )tag // The design of the setter logic below is taken from the DDAbstractLogger implementation. // For documentation please refer to the DDAbstractLogger implementation. - dispatch_queue_t currentQueue = dispatch_get_current_queue(); - if (currentQueue == loggerQueue) + if ([self isOnInternalLoggerQueue]) { block(); } else { dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; - NSAssert(currentQueue != globalLoggingQueue, @"Core architecture requirement failure"); + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); dispatch_async(globalLoggingQueue, ^{ dispatch_async(loggerQueue, block); @@ -1091,15 +1097,14 @@ - (void)clearColorsForAllFlags // The design of the setter logic below is taken from the DDAbstractLogger implementation. // For documentation please refer to the DDAbstractLogger implementation. - dispatch_queue_t currentQueue = dispatch_get_current_queue(); - if (currentQueue == loggerQueue) + if ([self isOnInternalLoggerQueue]) { block(); } else { dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; - NSAssert(currentQueue != globalLoggingQueue, @"Core architecture requirement failure"); + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); dispatch_async(globalLoggingQueue, ^{ dispatch_async(loggerQueue, block); @@ -1117,15 +1122,14 @@ - (void)clearColorsForAllTags // The design of the setter logic below is taken from the DDAbstractLogger implementation. // For documentation please refer to the DDAbstractLogger implementation. - dispatch_queue_t currentQueue = dispatch_get_current_queue(); - if (currentQueue == loggerQueue) + if ([self isOnInternalLoggerQueue]) { block(); } else { dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; - NSAssert(currentQueue != globalLoggingQueue, @"Core architecture requirement failure"); + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); dispatch_async(globalLoggingQueue, ^{ dispatch_async(loggerQueue, block); @@ -1144,15 +1148,14 @@ - (void)clearAllColors // The design of the setter logic below is taken from the DDAbstractLogger implementation. // For documentation please refer to the DDAbstractLogger implementation. - dispatch_queue_t currentQueue = dispatch_get_current_queue(); - if (currentQueue == loggerQueue) + if ([self isOnInternalLoggerQueue]) { block(); } else { dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; - NSAssert(currentQueue != globalLoggingQueue, @"Core architecture requirement failure"); + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); dispatch_async(globalLoggingQueue, ^{ dispatch_async(loggerQueue, block); @@ -1206,8 +1209,14 @@ - (void)logMessage:(DDLogMessage *)logMessage char msgStack[useStack ? (msgLen + 1) : 1]; // Analyzer doesn't like zero-size array, hence the 1 char *msg = useStack ? msgStack : (char *)malloc(msgLen + 1); + if (msg == NULL) return; - [logMsg getCString:msg maxLength:(msgLen + 1) encoding:NSUTF8StringEncoding]; + BOOL logMsgEnc = [logMsg getCString:msg maxLength:(msgLen + 1) encoding:NSUTF8StringEncoding]; + if (!logMsgEnc) + { + if (!useStack && msg != NULL) free(msg); + return; + } // Write the log message to STDERR @@ -1215,32 +1224,38 @@ - (void)logMessage:(DDLogMessage *)logMessage { // The log message has already been formatted. - struct iovec v[4]; + struct iovec v[5]; if (colorProfile) { v[0].iov_base = colorProfile->fgCode; v[0].iov_len = colorProfile->fgCodeLen; - - v[3].iov_base = colorProfile->resetCode; - v[3].iov_len = colorProfile->resetCodeLen; + + v[1].iov_base = colorProfile->bgCode; + v[1].iov_len = colorProfile->bgCodeLen; + + v[4].iov_base = colorProfile->resetCode; + v[4].iov_len = colorProfile->resetCodeLen; } else { v[0].iov_base = ""; v[0].iov_len = 0; - v[3].iov_base = ""; - v[3].iov_len = 0; + v[1].iov_base = ""; + v[1].iov_len = 0; + + v[4].iov_base = ""; + v[4].iov_len = 0; } - v[1].iov_base = (char *)msg; - v[1].iov_len = msgLen; + v[2].iov_base = (char *)msg; + v[2].iov_len = msgLen; - v[2].iov_base = "\n"; - v[2].iov_len = (msg[msgLen] == '\n') ? 0 : 1; + v[3].iov_base = "\n"; + v[3].iov_len = (msg[msgLen] == '\n') ? 0 : 1; - writev(STDERR_FILENO, v, 4); + writev(STDERR_FILENO, v, 5); } else { @@ -1282,56 +1297,62 @@ - (void)logMessage:(DDLogMessage *)logMessage // Here is our format: "%s %s[%i:%s] %s", timestamp, appName, processID, threadID, logMsg - struct iovec v[12]; + struct iovec v[13]; if (colorProfile) { v[0].iov_base = colorProfile->fgCode; v[0].iov_len = colorProfile->fgCodeLen; - - v[11].iov_base = colorProfile->resetCode; - v[11].iov_len = colorProfile->resetCodeLen; + + v[1].iov_base = colorProfile->bgCode; + v[1].iov_len = colorProfile->bgCodeLen; + + v[12].iov_base = colorProfile->resetCode; + v[12].iov_len = colorProfile->resetCodeLen; } else { v[0].iov_base = ""; v[0].iov_len = 0; - - v[11].iov_base = ""; - v[11].iov_len = 0; + + v[1].iov_base = ""; + v[1].iov_len = 0; + + v[12].iov_base = ""; + v[12].iov_len = 0; } - v[1].iov_base = ts; - v[1].iov_len = tsLen; + v[2].iov_base = ts; + v[2].iov_len = tsLen; - v[2].iov_base = " "; - v[2].iov_len = 1; + v[3].iov_base = " "; + v[3].iov_len = 1; - v[3].iov_base = app; - v[3].iov_len = appLen; + v[4].iov_base = app; + v[4].iov_len = appLen; - v[4].iov_base = "["; - v[4].iov_len = 1; + v[5].iov_base = "["; + v[5].iov_len = 1; - v[5].iov_base = pid; - v[5].iov_len = pidLen; + v[6].iov_base = pid; + v[6].iov_len = pidLen; - v[6].iov_base = ":"; - v[6].iov_len = 1; + v[7].iov_base = ":"; + v[7].iov_len = 1; - v[7].iov_base = tid; - v[7].iov_len = MIN((size_t)8, tidLen); // snprintf doesn't return what you might think + v[8].iov_base = tid; + v[8].iov_len = MIN((size_t)8, tidLen); // snprintf doesn't return what you might think - v[8].iov_base = "] "; - v[8].iov_len = 2; + v[9].iov_base = "] "; + v[9].iov_len = 2; - v[9].iov_base = (char *)msg; - v[9].iov_len = msgLen; + v[10].iov_base = (char *)msg; + v[10].iov_len = msgLen; - v[10].iov_base = "\n"; - v[10].iov_len = (msg[msgLen] == '\n') ? 0 : 1; + v[11].iov_base = "\n"; + v[11].iov_len = (msg[msgLen] == '\n') ? 0 : 1; - writev(STDERR_FILENO, v, 12); + writev(STDERR_FILENO, v, 13); } if (!useStack) { @@ -1389,8 +1410,9 @@ - (id)initWithForegroundColor:(OSColor *)fgColor backgroundColor:(OSColor *)bgCo NSUInteger len1 = [escapeSeq lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; NSUInteger len2 = [fgCodeRaw lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; - [escapeSeq getCString:(fgCode) maxLength:(len1+1) encoding:NSUTF8StringEncoding]; - [fgCodeRaw getCString:(fgCode+len1) maxLength:(len2+1) encoding:NSUTF8StringEncoding]; + BOOL escapeSeqEnc = [escapeSeq getCString:(fgCode) maxLength:(len1+1) encoding:NSUTF8StringEncoding]; + BOOL fgCodeRawEsc = [fgCodeRaw getCString:(fgCode+len1) maxLength:(len2+1) encoding:NSUTF8StringEncoding]; + if (!escapeSeqEnc || !fgCodeRawEsc) return nil; fgCodeLen = len1+len2; } @@ -1423,8 +1445,9 @@ - (id)initWithForegroundColor:(OSColor *)fgColor backgroundColor:(OSColor *)bgCo NSUInteger len1 = [escapeSeq lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; NSUInteger len2 = [bgCodeRaw lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; - [escapeSeq getCString:(bgCode) maxLength:(len1+1) encoding:NSUTF8StringEncoding]; - [bgCodeRaw getCString:(bgCode+len1) maxLength:(len2+1) encoding:NSUTF8StringEncoding]; + BOOL escapeSeqEnc = [escapeSeq getCString:(bgCode) maxLength:(len1+1) encoding:NSUTF8StringEncoding]; + BOOL bgCodeRawEsc = [bgCodeRaw getCString:(bgCode+len1) maxLength:(len2+1) encoding:NSUTF8StringEncoding]; + if (!escapeSeqEnc || !bgCodeRawEsc) return nil; bgCodeLen = len1+len2; } diff --git a/External/SFHFKeychainUtils/SFHFKeychainUtils.h b/External/SFHFKeychainUtils/SFHFKeychainUtils.h new file mode 100644 index 00000000..da3c18f8 --- /dev/null +++ b/External/SFHFKeychainUtils/SFHFKeychainUtils.h @@ -0,0 +1,39 @@ +// +// SFHFKeychainUtils.h +// +// Created by Buzz Andersen on 10/20/08. +// Based partly on code by Jonathan Wight, Jon Crosby, and Mike Malone. +// Copyright 2008 Sci-Fi Hi-Fi. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + + +@interface SFHFKeychainUtils : NSObject { + +} + ++ (NSString *) getPasswordForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error; ++ (BOOL) storeUsername: (NSString *) username andPassword: (NSString *) password forServiceName: (NSString *) serviceName updateExisting: (BOOL) updateExisting error: (NSError **) error; ++ (BOOL) deleteItemForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error; + +@end diff --git a/External/SFHFKeychainUtils/SFHFKeychainUtils.m b/External/SFHFKeychainUtils/SFHFKeychainUtils.m new file mode 100644 index 00000000..4b1073a7 --- /dev/null +++ b/External/SFHFKeychainUtils/SFHFKeychainUtils.m @@ -0,0 +1,375 @@ +// +// SFHFKeychainUtils.m +// + +// Created by Buzz Andersen on 10/20/08. +// Based partly on code by Jonathan Wight, Jon Crosby, and Mike Malone. +// Copyright 2008 Sci-Fi Hi-Fi. All rights reserved. +// + +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// + +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +#import "SFHFKeychainUtils.h" +#import + + +static NSString *SFHFKeychainUtilsErrorDomain = @"SFHFKeychainUtilsErrorDomain"; + +#if __IPHONE_OS_VERSION_MIN_REQUIRED < 30000 && TARGET_IPHONE_SIMULATOR + +@interface SFHFKeychainUtils (PrivateMethods) +(SecKeychainItemRef) getKeychainItemReferenceForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error; +@end + +#endif + +@implementation SFHFKeychainUtils + +#if __IPHONE_OS_VERSION_MIN_REQUIRED < 30000 && TARGET_IPHONE_SIMULATOR + ++(NSString *) getPasswordForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error { + if (!username || !serviceName) { + *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil]; + return nil; + } + + SecKeychainItemRef item = [SFHFKeychainUtils getKeychainItemReferenceForUsername: username andServiceName: serviceName error: error]; + if (*error || !item) { + return nil; + } + + // from Advanced Mac OS X Programming, ch. 16 + UInt32 length; + char *password; + SecKeychainAttribute attributes[8]; + SecKeychainAttributeList list; + attributes[0].tag = kSecAccountItemAttr; + attributes[1].tag = kSecDescriptionItemAttr; + attributes[2].tag = kSecLabelItemAttr; + attributes[3].tag = kSecModDateItemAttr; + list.count = 4; + list.attr = attributes; + OSStatus status = SecKeychainItemCopyContent(item, NULL, &list, &length, (void **)&password); + if (status != noErr) { + *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil]; + return nil; + } + NSString *passwordString = nil; + if (password != NULL) { + char passwordBuffer[1024]; + if (length > 1023) { + length = 1023; + } + strncpy(passwordBuffer, password, length); + passwordBuffer[length] = '\0'; + passwordString = [NSString stringWithCString:passwordBuffer]; + } + SecKeychainItemFreeContent(&list, password); + CFRelease(item); + return passwordString; +} + ++ (void) storeUsername: (NSString *) username andPassword: (NSString *) password forServiceName: (NSString *) serviceName updateExisting: (BOOL) updateExisting error: (NSError **) error { + + if (!username || !password || !serviceName) { + *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil]; + return; + } + OSStatus status = noErr; + SecKeychainItemRef item = [SFHFKeychainUtils getKeychainItemReferenceForUsername: username andServiceName: serviceName error: error]; + if (*error && [*error code] != noErr) { + return; + } + *error = nil; + + if (item) { + status = SecKeychainItemModifyAttributesAndData(item,NULL,strlen([password UTF8String]),[password UTF8String]); + CFRelease(item); + } + else { + status = SecKeychainAddGenericPassword(NULL,strlen([serviceName UTF8String]),[serviceName UTF8String],strlen([username UTF8String]),[username UTF8String],strlen([password UTF8String]),[password UTF8String],NULL); + + } + if (status != noErr) { + *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil]; + } +} + ++ (void) deleteItemForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error { + if (!username || !serviceName) { + *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: 2000 userInfo: nil]; + return; + } + + *error = nil; + + SecKeychainItemRef item = [SFHFKeychainUtils getKeychainItemReferenceForUsername: username andServiceName: serviceName error: error]; + if (*error && [*error code] != noErr) { + return; + } + + OSStatus status; + if (item) { + status = SecKeychainItemDelete(item); + CFRelease(item); + } + + if (status != noErr) { + *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil]; + } +} + ++ (SecKeychainItemRef) getKeychainItemReferenceForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error { + if (!username || !serviceName) { + *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil]; + return nil; + } + + *error = nil; + SecKeychainItemRef item; + OSStatus status = SecKeychainFindGenericPassword(NULL,strlen([serviceName UTF8String]),[serviceName UTF8String],strlen([username UTF8String]),[username UTF8String], NULL,NULL,&item); + + if (status != noErr) { + if (status != errSecItemNotFound) { + *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil]; + } + return nil; + } + return item; +} + +#else + ++ (NSString *) getPasswordForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error { + + if (!username || !serviceName) { + if (error != nil) { + *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil]; + } + return nil; + } + + if (error != nil) { + *error = nil; + } + // Set up a query dictionary with the base query attributes: item type (generic), username, and service + NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClass, kSecAttrAccount, kSecAttrService, nil]; + NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClassGenericPassword, username, serviceName, nil]; + NSMutableDictionary *query = [[NSMutableDictionary alloc] initWithObjects: objects forKeys: keys]; + // First do a query for attributes, in case we already have a Keychain item with no password data set. + // One likely way such an incorrect item could have come about is due to the previous (incorrect) + // version of this code (which set the password as a generic attribute instead of password data). + NSMutableDictionary *attributeQuery = [query mutableCopy]; + [attributeQuery setObject: (id) kCFBooleanTrue forKey:(__bridge_transfer id) kSecReturnAttributes]; + CFTypeRef attrResult = NULL; + + CFDictionaryRef cfQuery = (CFDictionaryRef)CFBridgingRetain(attributeQuery); + OSStatus status = SecItemCopyMatching(cfQuery, &attrResult); + CFRelease(cfQuery); + cfQuery = nil; + + //NSDictionary *attributeResult = (__bridge_transfer NSDictionary *)attrResult; + if (status != noErr) { + // No existing item found--simply return nil for the password + if (error != nil && status != errSecItemNotFound) { + //Only return an error if a real exception happened--not simply for "not found." + *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil]; + } + return nil; + } + + // We have an existing item, now query for the password data associated with it. + NSMutableDictionary *passwordQuery = [query mutableCopy]; + [passwordQuery setObject: (id) kCFBooleanTrue forKey: (__bridge_transfer id) kSecReturnData]; + CFTypeRef resData = NULL; + + CFDictionaryRef cfPasswordQuery = (CFDictionaryRef)CFBridgingRetain(passwordQuery); + status = SecItemCopyMatching(cfPasswordQuery, (CFTypeRef *) &resData); + CFRelease(cfPasswordQuery); + cfPasswordQuery = nil; + + NSData *resultData = (__bridge_transfer NSData *)resData; + if (status != noErr) { + if (status == errSecItemNotFound) { + // We found attributes for the item previously, but no password now, so return a special error. + // Users of this API will probably want to detect this error and prompt the user to + // re-enter their credentials. When you attempt to store the re-entered credentials + // using storeUsername:andPassword:forServiceName:updateExisting:error + // the old, incorrect entry will be deleted and a new one with a properly encrypted + // password will be added. + + if (error != nil) { + *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -1999 userInfo: nil]; + } + } + else { + // Something else went wrong. Simply return the normal Keychain API error code. + if (error != nil) { + *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil]; + } + } + return nil; + } + NSString *password = nil; + if (resultData) { + password = [[NSString alloc] initWithData: resultData encoding: NSUTF8StringEncoding]; + } + else { + // There is an existing item, but we weren't able to get password data for it for some reason, + // Possibly as a result of an item being incorrectly entered by the previous code. + // Set the -1999 error so the code above us can prompt the user again. + + if (error != nil) { + *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -1999 userInfo: nil]; + } + } + return password; +} + ++ (BOOL) storeUsername: (NSString *) username andPassword: (NSString *) password forServiceName: (NSString *) serviceName updateExisting: (BOOL) updateExisting error: (NSError **) error + +{ + if (!username || !password || !serviceName) + + { + if (error != nil) + { + *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil]; + } + return NO; + } + + // See if we already have a password entered for these credentials. + + NSError *getError = nil; + NSString *existingPassword = [SFHFKeychainUtils getPasswordForUsername: username andServiceName: serviceName error:&getError]; + + if ([getError code] == -1999) + { + // There is an existing entry without a password properly stored (possibly as a result of the previous incorrect version of this code. + + // Delete the existing item before moving on entering a correct one. + getError = nil; + + [self deleteItemForUsername: username andServiceName: serviceName error: &getError]; + + if ([getError code] != noErr) + { + if (error != nil) + { + *error = getError; + } + return NO; + } + } + else if ([getError code] != noErr) + { + if (error != nil) + { + *error = getError; + } + return NO; + } + if (error != nil) + { + *error = nil; + } + + OSStatus status = noErr; + + if (existingPassword) + { + + // We have an existing, properly entered item with a password. + // Update the existing item. + + if (![existingPassword isEqualToString:password] && updateExisting) + { + //Only update if we're allowed to update existing. If not, simply do nothing. + + NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClass,kSecAttrService,kSecAttrLabel,kSecAttrAccount,nil]; + + NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClassGenericPassword,serviceName,serviceName,username,nil]; + + NSDictionary *query = [[NSDictionary alloc] initWithObjects: objects forKeys: keys]; + + status = SecItemUpdate((__bridge_retained CFDictionaryRef) query, (__bridge_retained CFDictionaryRef) [NSDictionary dictionaryWithObject: [password dataUsingEncoding: NSUTF8StringEncoding] forKey: (__bridge_transfer NSString *) kSecValueData]); + } + } + else + { + // No existing entry (or an existing, improperly entered, and therefore now + + // deleted, entry). Create a new entry. + + + NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClass,kSecAttrService,kSecAttrLabel,kSecAttrAccount,kSecValueData,nil]; + + NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClassGenericPassword,serviceName,serviceName,username,[password dataUsingEncoding: NSUTF8StringEncoding],nil]; + + NSDictionary *query = [[NSDictionary alloc] initWithObjects: objects forKeys: keys]; + + status = SecItemAdd((__bridge_retained CFDictionaryRef) query, NULL); + } + if (error != nil && status != noErr) + { + // Something went wrong with adding the new item. Return the Keychain error code. + *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil]; + return NO; + } + return YES; +} + ++ (BOOL) deleteItemForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error +{ + if (!username || !serviceName) + { + if (error != nil) + { + *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil]; + } + return NO; + } + if (error != nil) + { + *error = nil; + } + NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClass, kSecAttrAccount, kSecAttrService, kSecReturnAttributes, nil]; + NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClassGenericPassword, username, serviceName, kCFBooleanTrue, nil]; + NSDictionary *query = [[NSDictionary alloc] initWithObjects: objects forKeys: keys]; + + CFDictionaryRef cfQuery = (CFDictionaryRef)CFBridgingRetain(query); + OSStatus status = SecItemDelete(cfQuery); + CFRelease(cfQuery); + cfQuery = nil; + + if (error != nil && status != noErr) + { + *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil]; + return NO; + } + return YES; +} +#endif +@end diff --git a/External/Reachability/Reachability.h b/External/SPReachability/SPReachability.h similarity index 100% rename from External/Reachability/Reachability.h rename to External/SPReachability/SPReachability.h diff --git a/External/Reachability/Reachability.m b/External/SPReachability/SPReachability.m similarity index 92% rename from External/Reachability/Reachability.m rename to External/SPReachability/SPReachability.m index 30b7452c..d58f48e5 100644 --- a/External/Reachability/Reachability.m +++ b/External/SPReachability/SPReachability.m @@ -54,7 +54,7 @@ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE #import -#import "Reachability.h" +#import "SPReachability.h" #define kShouldPrintReachabilityFlags 1 @@ -88,23 +88,21 @@ static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReach { #pragma unused (target, flags) NSCAssert(info != NULL, @"info was NULL in ReachabilityCallback"); - NSCAssert([(NSObject*) info isKindOfClass: [SPReachability class]], @"info was wrong class in ReachabilityCallback"); + NSCAssert([(__bridge NSObject*) info isKindOfClass: [SPReachability class]], @"info was wrong class in ReachabilityCallback"); //We're on the main RunLoop, so an NSAutoreleasePool is not necessary, but is added defensively // in case someon uses the Reachablity object in a different thread. - NSAutoreleasePool* myPool = [[NSAutoreleasePool alloc] init]; - - SPReachability* noteObject = (SPReachability*) info; - // Post a notification to notify the client that the network reachability changed. - [[NSNotificationCenter defaultCenter] postNotificationName: kReachabilityChangedNotification object: noteObject]; - - [myPool release]; + @autoreleasepool { + SPReachability* noteObject = (__bridge SPReachability*) info; + // Post a notification to notify the client that the network reachability changed. + [[NSNotificationCenter defaultCenter] postNotificationName: kReachabilityChangedNotification object: noteObject]; + } } - (BOOL) startNotifier { BOOL retVal = NO; - SCNetworkReachabilityContext context = {0, self, NULL, NULL, NULL}; + SCNetworkReachabilityContext context = {0, (__bridge void *)(self), NULL, NULL, NULL}; if(SCNetworkReachabilitySetCallback(reachabilityRef, ReachabilityCallback, &context)) { if(SCNetworkReachabilityScheduleWithRunLoop(reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode)) @@ -130,7 +128,6 @@ - (void) dealloc { CFRelease(reachabilityRef); } - [super dealloc]; } + (SPReachability*) reachabilityWithHostName: (NSString*) hostName; @@ -139,7 +136,7 @@ + (SPReachability*) reachabilityWithHostName: (NSString*) hostName; SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(NULL, [hostName UTF8String]); if(reachability!= NULL) { - retVal= [[[self alloc] init] autorelease]; + retVal= [[self alloc] init]; if(retVal!= NULL) { retVal->reachabilityRef = reachability; @@ -155,7 +152,7 @@ + (SPReachability*) reachabilityWithAddress: (const struct sockaddr_in*) hostAdd SPReachability* retVal = NULL; if(reachability!= NULL) { - retVal= [[[self alloc] init] autorelease]; + retVal= [[self alloc] init]; if(retVal!= NULL) { retVal->reachabilityRef = reachability; diff --git a/External/SocketRocket/NSData+SRB64Additions.h b/External/SocketRocket/NSData+SRB64Additions.h index 115108d7..7d610636 100644 --- a/External/SocketRocket/NSData+SRB64Additions.h +++ b/External/SocketRocket/NSData+SRB64Additions.h @@ -16,6 +16,7 @@ #import + @interface NSData (SRB64Additions) - (NSString *)SR_stringByBase64Encoding; diff --git a/External/SocketRocket/NSData+SRB64Additions.m b/External/SocketRocket/NSData+SRB64Additions.m index 71bae3b0..5874a188 100644 --- a/External/SocketRocket/NSData+SRB64Additions.m +++ b/External/SocketRocket/NSData+SRB64Additions.m @@ -17,6 +17,7 @@ #import "NSData+SRB64Additions.h" #import "base64.h" + @implementation NSData (SRB64Additions) - (NSString *)SR_stringByBase64Encoding; diff --git a/External/SocketRocket/SRWebSocket.h b/External/SocketRocket/SRWebSocket.h index a7e18516..2d40bb1d 100644 --- a/External/SocketRocket/SRWebSocket.h +++ b/External/SocketRocket/SRWebSocket.h @@ -22,15 +22,18 @@ typedef enum { SR_OPEN = 1, SR_CLOSING = 2, SR_CLOSED = 3, - } SRReadyState; @class SRWebSocket; extern NSString *const SRWebSocketErrorDomain; +#pragma mark - SRWebSocketDelegate + @protocol SRWebSocketDelegate; +#pragma mark - SRWebSocket + @interface SRWebSocket : NSObject @property (nonatomic, assign) id delegate; @@ -39,32 +42,43 @@ extern NSString *const SRWebSocketErrorDomain; @property (nonatomic, readonly, retain) NSURL *url; // This returns the negotiated protocol. -// It will be niluntil after the handshake completes. +// It will be nil until after the handshake completes. @property (nonatomic, readonly, copy) NSString *protocol; -// Protocols should be an array of strings that turn into Sec-WebSocket-Protocol +// Protocols should be an array of strings that turn into Sec-WebSocket-Protocol. - (id)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray *)protocols; - (id)initWithURLRequest:(NSURLRequest *)request; -// Some helper constructors +// Some helper constructors. - (id)initWithURL:(NSURL *)url protocols:(NSArray *)protocols; - (id)initWithURL:(NSURL *)url; -// SRWebSockets are intended one-time-use only. Open should be called once and only once +// Delegate queue will be dispatch_main_queue by default. +// You cannot set both OperationQueue and dispatch_queue. +- (void)setDelegateOperationQueue:(NSOperationQueue*) queue; +- (void)setDelegateDispatchQueue:(dispatch_queue_t) queue; + +// By default, it will schedule itself on +[NSRunLoop SR_networkRunLoop] using defaultModes. +- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode; +- (void)unscheduleFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode; + +// SRWebSockets are intended for one-time-use only. Open should be called once and only once. - (void)open; - (void)close; - (void)closeWithCode:(NSInteger)code reason:(NSString *)reason; -// Send a UTF8 String or Data +// Send a UTF8 String or Data. - (void)send:(id)data; @end +#pragma mark - SRWebSocketDelegate + @protocol SRWebSocketDelegate -// message will either be an NSString if the server is using text -// or NSData if the server is using binary +// message will either be an NSString if the server is using text +// or NSData if the server is using binary. - (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message; @optional @@ -75,6 +89,7 @@ extern NSString *const SRWebSocketErrorDomain; @end +#pragma mark - NSURLRequest (CertificateAdditions) @interface NSURLRequest (CertificateAdditions) @@ -82,9 +97,18 @@ extern NSString *const SRWebSocketErrorDomain; @end +#pragma mark - NSMutableURLRequest (CertificateAdditions) @interface NSMutableURLRequest (CertificateAdditions) @property (nonatomic, retain) NSArray *SR_SSLPinnedCertificates; @end + +#pragma mark - NSRunLoop (SRWebSocket) + +@interface NSRunLoop (SRWebSocket) + ++ (NSRunLoop *)SR_networkRunLoop; + +@end diff --git a/External/SocketRocket/SRWebSocket.m b/External/SocketRocket/SRWebSocket.m index ef7c65c5..3c941379 100644 --- a/External/SocketRocket/SRWebSocket.m +++ b/External/SocketRocket/SRWebSocket.m @@ -33,17 +33,33 @@ #import #import + #import "base64.h" #import "NSData+SRB64Additions.h" +#if OS_OBJECT_USE_OBJC_RETAIN_RELEASE +#define sr_dispatch_retain(x) +#define sr_dispatch_release(x) +#define maybe_bridge(x) ((__bridge void *) x) +#else +#define sr_dispatch_retain(x) dispatch_retain(x) +#define sr_dispatch_release(x) dispatch_release(x) +#define maybe_bridge(x) (x) +#endif + +#if !__has_feature(objc_arc) +#error SocketRocket muust be compiled with ARC enabled +#endif + + typedef enum { SROpCodeTextFrame = 0x1, SROpCodeBinaryFrame = 0x2, - //3-7Reserved + // 3-7 reserved. SROpCodeConnectionClose = 0x8, SROpCodePing = 0x9, SROpCodePong = 0xA, - //B-F reserved + // B-F reserved. } SROpCode; typedef enum { @@ -51,9 +67,9 @@ SRStatusCodeGoingAway = 1001, SRStatusCodeProtocolError = 1002, SRStatusCodeUnhandledType = 1003, - // 1004 reserved + // 1004 reserved. SRStatusNoStatusReceived = 1005, - // 1004-1006 reserved + // 1004-1006 reserved. SRStatusCodeInvalidUTF8 = 1007, SRStatusCodePolicyViolated = 1008, SRStatusCodeMessageTooBig = 1009, @@ -91,13 +107,20 @@ - (NSString *)stringBySHA1ThenBase64Encoding; @interface NSURL (SRWebSocket) -// The origin isn't really applicable for a native application -// So instead, just map ws -> http and wss -> https +// The origin isn't really applicable for a native application. +// So instead, just map ws -> http and wss -> https. - (NSString *)SR_origin; @end +@interface _SRRunLoopThread : NSThread + +@property (nonatomic, readonly) NSRunLoop *runLoop; + +@end + + static NSString *newSHA1String(const char *bytes, size_t length) { uint8_t md[CC_SHA1_DIGEST_LENGTH]; @@ -137,7 +160,7 @@ - (NSString *)stringBySHA1ThenBase64Encoding; NSString *const SRWebSocketErrorDomain = @"SRWebSocketErrorDomain"; -// Returns number of bytes consumed. returning 0 means you didn't match. +// Returns number of bytes consumed. Returning 0 means you didn't match. // Sends bytes to callback handler; typedef size_t (^stream_scanner)(NSData *collected_data); @@ -150,9 +173,6 @@ @interface SRIOConsumer : NSObject { BOOL _readToCurrentFrame; BOOL _unmaskBytes; } - -- (id)initWithScanner:(stream_scanner)scanner handler:(data_callback)handler bytesNeeded:(size_t)bytesNeeded readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes; - @property (nonatomic, copy, readonly) stream_scanner consumer; @property (nonatomic, copy, readonly) data_callback handler; @property (nonatomic, assign) size_t bytesNeeded; @@ -161,6 +181,15 @@ - (id)initWithScanner:(stream_scanner)scanner handler:(data_callback)handler byt @end +// This class is not thread-safe, and is expected to always be run on the same queue. +@interface SRIOConsumerPool : NSObject + +- (id)initWithBufferCapacity:(NSUInteger)poolSize; + +- (SRIOConsumer *)consumerWithScanner:(stream_scanner)scanner handler:(data_callback)handler bytesNeeded:(size_t)bytesNeeded readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes; +- (void)returnConsumer:(SRIOConsumer *)consumer; + +@end @interface SRWebSocket () @@ -188,16 +217,23 @@ - (void)_sendFrameWithOpcode:(SROpCode)opcode data:(id)data; - (BOOL)_checkHandshake:(CFHTTPMessageRef)httpMessage; - (void)_SR_commonInit; -- (void)_connectToHost:(NSString *)host port:(NSInteger)port; +- (void)_initializeStreams; +- (void)_connect; @property (nonatomic) SRReadyState readyState; +@property (nonatomic) NSOperationQueue *delegateOperationQueue; +@property (nonatomic) dispatch_queue_t delegateDispatchQueue; + @end @implementation SRWebSocket { NSInteger _webSocketVersion; - dispatch_queue_t _callbackQueue; + + NSOperationQueue *_delegateOperationQueue; + dispatch_queue_t _delegateDispatchQueue; + dispatch_queue_t _workQueue; NSMutableArray *_consumers; @@ -205,10 +241,10 @@ @implementation SRWebSocket { NSOutputStream *_outputStream; NSMutableData *_readBuffer; - NSInteger _readBufferOffset; + NSUInteger _readBufferOffset; NSMutableData *_outputBuffer; - NSInteger _outputBufferOffset; + NSUInteger _outputBufferOffset; uint8_t _currentFrameOpcode; size_t _currentFrameCount; @@ -241,10 +277,13 @@ @implementation SRWebSocket { BOOL _isPumping; + NSMutableSet *_scheduledRunloops; + // We use this to retain ourselves. __strong SRWebSocket *_selfRetain; NSArray *_requestedProtocols; + SRIOConsumerPool *_consumerPool; } @synthesize delegate = _delegate; @@ -265,16 +304,9 @@ - (id)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray *)protocols; if (self) { assert(request.URL); _url = request.URL; - NSString *scheme = [_url scheme]; - - _requestedProtocols = [protocols copy]; - - assert([scheme isEqualToString:@"ws"] || [scheme isEqualToString:@"http"] || [scheme isEqualToString:@"wss"] || [scheme isEqualToString:@"https"]); _urlRequest = request; - if ([scheme isEqualToString:@"wss"] || [scheme isEqualToString:@"https"]) { - _secure = YES; - } + _requestedProtocols = [protocols copy]; [self _SR_commonInit]; } @@ -300,16 +332,25 @@ - (id)initWithURL:(NSURL *)url protocols:(NSArray *)protocols; - (void)_SR_commonInit; { + + NSString *scheme = _url.scheme.lowercaseString; + assert([scheme isEqualToString:@"ws"] || [scheme isEqualToString:@"http"] || [scheme isEqualToString:@"wss"] || [scheme isEqualToString:@"https"]); + + if ([scheme isEqualToString:@"wss"] || [scheme isEqualToString:@"https"]) { + _secure = YES; + } + _readyState = SR_CONNECTING; - _consumerStopped = YES; - _webSocketVersion = 13; _workQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL); - _callbackQueue = dispatch_get_main_queue(); - dispatch_retain(_callbackQueue); + // Going to set a specific on the queue so we can validate we're on the work queue + dispatch_queue_set_specific(_workQueue, (__bridge void *)self, maybe_bridge(_workQueue), NULL); + + _delegateDispatchQueue = dispatch_get_main_queue(); + sr_dispatch_retain(_delegateDispatchQueue); _readBuffer = [[NSMutableData alloc] init]; _outputBuffer = [[NSMutableData alloc] init]; @@ -318,9 +359,20 @@ - (void)_SR_commonInit; _consumers = [[NSMutableArray alloc] init]; + _consumerPool = [[SRIOConsumerPool alloc] init]; + + _scheduledRunloops = [[NSMutableSet alloc] init]; + + [self _initializeStreams]; + // default handlers } +- (void)assertOnWorkQueue; +{ + assert(dispatch_get_specific((__bridge void *)self) == maybe_bridge(_workQueue)); +} + - (void)dealloc { _inputStream.delegate = nil; @@ -329,13 +381,18 @@ - (void)dealloc [_inputStream close]; [_outputStream close]; - dispatch_release(_callbackQueue); - dispatch_release(_workQueue); + sr_dispatch_release(_workQueue); + _workQueue = NULL; if (_receivedHTTPHeaders) { CFRelease(_receivedHTTPHeaders); _receivedHTTPHeaders = NULL; } + + if (_delegateDispatchQueue) { + sr_dispatch_release(_delegateDispatchQueue); + _delegateDispatchQueue = NULL; + } } #ifndef NDEBUG @@ -353,23 +410,36 @@ - (void)setReadyState:(SRReadyState)aReadyState; - (void)open; { assert(_url); - NSAssert(_readyState == SR_CONNECTING && _inputStream == nil && _outputStream == nil, @"Cannot call -(void)open on SRWebSocket more than once"); + NSAssert(_readyState == SR_CONNECTING, @"Cannot call -(void)open on SRWebSocket more than once"); _selfRetain = self; - NSInteger port = _url.port.integerValue; - if (port == 0) { - if (!_secure) { - port = 80; - } else { - port = 443; - } - } - - [self _connectToHost:_url.host port:port]; + [self _connect]; } +// Calls block on delegate queue +- (void)_performDelegateBlock:(dispatch_block_t)block; +{ + if (_delegateOperationQueue) { + [_delegateOperationQueue addOperationWithBlock:block]; + } else { + assert(_delegateDispatchQueue); + dispatch_async(_delegateDispatchQueue, block); + } +} +- (void)setDelegateDispatchQueue:(dispatch_queue_t)queue; +{ + if (queue) { + sr_dispatch_retain(queue); + } + + if (_delegateDispatchQueue) { + sr_dispatch_release(_delegateDispatchQueue); + } + + _delegateDispatchQueue = queue; +} - (BOOL)_checkHandshake:(CFHTTPMessageRef)httpMessage; { @@ -418,11 +488,11 @@ - (void)_HTTPHeadersDidFinish; [self _readFrameNew]; } - dispatch_async(_callbackQueue, ^{ + [self _performDelegateBlock:^{ if ([self.delegate respondsToSelector:@selector(webSocketDidOpen:)]) { [self.delegate webSocketDidOpen:self]; - } - }); + }; + }]; } @@ -480,8 +550,18 @@ - (void)didConnect [self _readHTTPHeader]; } -- (void)_connectToHost:(NSString *)host port:(NSInteger)port; -{ +- (void)_initializeStreams; +{ + NSInteger port = _url.port.integerValue; + if (port == 0) { + if (!_secure) { + port = 80; + } else { + port = 443; + } + } + NSString *host = _url.host; + CFReadStreamRef readStream = NULL; CFWriteStreamRef writeStream = NULL; @@ -493,7 +573,7 @@ - (void)_connectToHost:(NSString *)host port:(NSInteger)port; if (_secure) { NSMutableDictionary *SSLOptions = [[NSMutableDictionary alloc] init]; - + [_outputStream setProperty:(__bridge id)kCFStreamSocketSecurityLevelNegotiatedSSL forKey:(__bridge id)kCFStreamPropertySocketSecurityLevel]; // If we're using pinned certs, don't validate the certificate chain @@ -501,10 +581,10 @@ - (void)_connectToHost:(NSString *)host port:(NSInteger)port; [SSLOptions setValue:[NSNumber numberWithBool:NO] forKey:(__bridge id)kCFStreamSSLValidatesCertificateChain]; } - #if DEBUG +#if DEBUG [SSLOptions setValue:[NSNumber numberWithBool:NO] forKey:(__bridge id)kCFStreamSSLValidatesCertificateChain]; NSLog(@"SocketRocket: In debug mode. Allowing connection to any root cert"); - #endif +#endif [_outputStream setProperty:SSLOptions forKey:(__bridge id)kCFStreamPropertySSLSettings]; @@ -512,16 +592,35 @@ - (void)_connectToHost:(NSString *)host port:(NSInteger)port; _inputStream.delegate = self; _outputStream.delegate = self; - - // TODO schedule in a better run loop - [_outputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; - [_inputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; +} + +- (void)_connect; +{ + if (!_scheduledRunloops.count) { + [self scheduleInRunLoop:[NSRunLoop SR_networkRunLoop] forMode:NSDefaultRunLoopMode]; + } [_outputStream open]; [_inputStream open]; } +- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode; +{ + [_outputStream scheduleInRunLoop:aRunLoop forMode:mode]; + [_inputStream scheduleInRunLoop:aRunLoop forMode:mode]; + + [_scheduledRunloops addObject:@[aRunLoop, mode]]; +} + +- (void)unscheduleFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode; +{ + [_outputStream removeFromRunLoop:aRunLoop forMode:mode]; + [_inputStream removeFromRunLoop:aRunLoop forMode:mode]; + + [_scheduledRunloops removeObject:@[aRunLoop, mode]]; +} + - (void)close; { [self closeWithCode:-1 reason:nil]; @@ -530,16 +629,17 @@ - (void)close; - (void)closeWithCode:(NSInteger)code reason:(NSString *)reason; { assert(code); - if (self.readyState == SR_CLOSING || self.readyState == SR_CLOSED) { - return; - } - - BOOL wasConnecting = self.readyState == SR_CONNECTING; - - self.readyState = SR_CLOSING; - - SRFastLog(@"Closing with code %d reason %@", code, reason); dispatch_async(_workQueue, ^{ + if (self.readyState == SR_CLOSING || self.readyState == SR_CLOSED) { + return; + } + + BOOL wasConnecting = self.readyState == SR_CONNECTING; + + self.readyState = SR_CLOSING; + + SRFastLog(@"Closing with code %d reason %@", code, reason); + if (wasConnecting) { [self _disconnect]; return; @@ -574,12 +674,12 @@ - (void)closeWithCode:(NSInteger)code reason:(NSString *)reason; - (void)_closeWithProtocolError:(NSString *)message; { // Need to shunt this on the _callbackQueue first to see if they received any messages - dispatch_async(_callbackQueue, ^{ + [self _performDelegateBlock:^{ [self closeWithCode:SRStatusCodeProtocolError reason:message]; dispatch_async(_workQueue, ^{ [self _disconnect]; }); - }); + }]; } - (void)_failWithError:(NSError *)error; @@ -587,11 +687,11 @@ - (void)_failWithError:(NSError *)error; dispatch_async(_workQueue, ^{ if (self.readyState != SR_CLOSED) { _failed = YES; - dispatch_async(_callbackQueue, ^{ + [self _performDelegateBlock:^{ if ([self.delegate respondsToSelector:@selector(webSocket:didFailWithError:)]) { [self.delegate webSocket:self didFailWithError:error]; } - }); + }]; self.readyState = SR_CLOSED; _selfRetain = nil; @@ -605,7 +705,7 @@ - (void)_failWithError:(NSError *)error; - (void)_writeData:(NSData *)data; { - assert(dispatch_get_current_queue() == _workQueue); + [self assertOnWorkQueue]; if (_closeWhenFinishedWriting) { return; @@ -634,11 +734,11 @@ - (void)send:(id)data; - (void)handlePing:(NSData *)pingData; { // Need to pingpong this off _callbackQueue first to make sure messages happen in order - dispatch_async(_callbackQueue, ^{ + [self _performDelegateBlock:^{ dispatch_async(_workQueue, ^{ [self _sendFrameWithOpcode:SROpCodePong data:pingData]; }); - }); + }]; } - (void)handlePong; @@ -649,9 +749,9 @@ - (void)handlePong; - (void)_handleMessage:(id)message { SRFastLog(@"Received message"); - dispatch_async(_callbackQueue, ^{ + [self _performDelegateBlock:^{ [self.delegate webSocket:self didReceiveMessage:message]; - }); + }]; } @@ -718,7 +818,7 @@ - (void)handleCloseWithData:(NSData *)data; _closeCode = SRStatusNoStatusReceived; } - assert(dispatch_get_current_queue() == _workQueue); + [self assertOnWorkQueue]; if (self.readyState == SR_OPEN) { [self closeWithCode:1000 reason:nil]; @@ -730,7 +830,7 @@ - (void)handleCloseWithData:(NSData *)data; - (void)_disconnect; { - assert(dispatch_get_current_queue() == _workQueue); + [self assertOnWorkQueue]; SRFastLog(@"Trying to disconnect"); _closeWhenFinishedWriting = YES; [self _pumpWriting]; @@ -965,11 +1065,11 @@ - (void)_readFrameNew; - (void)_pumpWriting; { - assert(dispatch_get_current_queue() == _workQueue); + [self assertOnWorkQueue]; NSUInteger dataLength = _outputBuffer.length; if (dataLength - _outputBufferOffset > 0 && _outputStream.hasSpaceAvailable) { - NSUInteger bytesWritten = [_outputStream write:_outputBuffer.bytes + _outputBufferOffset maxLength:dataLength - _outputBufferOffset]; + NSInteger bytesWritten = [_outputStream write:_outputBuffer.bytes + _outputBufferOffset maxLength:dataLength - _outputBufferOffset]; if (bytesWritten == -1) { [self _failWithError:[NSError errorWithDomain:@"org.lolrus.SocketRocket" code:2145 userInfo:[NSDictionary dictionaryWithObject:@"Error writing to stream" forKey:NSLocalizedDescriptionKey]]]; return; @@ -993,12 +1093,17 @@ - (void)_pumpWriting; [_outputStream close]; [_inputStream close]; + + for (NSArray *runLoop in [_scheduledRunloops copy]) { + [self unscheduleFromRunLoop:[runLoop objectAtIndex:0] forMode:[runLoop objectAtIndex:1]]; + } + if (!_failed) { - dispatch_async(_callbackQueue, ^{ + [self _performDelegateBlock:^{ if ([self.delegate respondsToSelector:@selector(webSocket:didCloseWithCode:reason:wasClean:)]) { [self.delegate webSocket:self didCloseWithCode:_closeCode reason:_closeReason wasClean:YES]; } - }); + }]; } _selfRetain = nil; @@ -1007,23 +1112,23 @@ - (void)_pumpWriting; - (void)_addConsumerWithScanner:(stream_scanner)consumer callback:(data_callback)callback; { - assert(dispatch_get_current_queue() == _workQueue); + [self assertOnWorkQueue]; [self _addConsumerWithScanner:consumer callback:callback dataLength:0]; } - (void)_addConsumerWithDataLength:(size_t)dataLength callback:(data_callback)callback readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes; { - assert(dispatch_get_current_queue() == _workQueue); + [self assertOnWorkQueue]; assert(dataLength); - [_consumers addObject:[[SRIOConsumer alloc] initWithScanner:nil handler:callback bytesNeeded:dataLength readToCurrentFrame:readToCurrentFrame unmaskBytes:unmaskBytes]]; + [_consumers addObject:[_consumerPool consumerWithScanner:nil handler:callback bytesNeeded:dataLength readToCurrentFrame:readToCurrentFrame unmaskBytes:unmaskBytes]]; [self _pumpScanner]; } - (void)_addConsumerWithScanner:(stream_scanner)consumer callback:(data_callback)callback dataLength:(size_t)dataLength; { - assert(dispatch_get_current_queue() == _workQueue); - [_consumers addObject:[[SRIOConsumer alloc] initWithScanner:consumer handler:callback bytesNeeded:dataLength readToCurrentFrame:NO unmaskBytes:NO]]; + [self assertOnWorkQueue]; + [_consumers addObject:[_consumerPool consumerWithScanner:consumer handler:callback bytesNeeded:dataLength readToCurrentFrame:NO unmaskBytes:NO]]; [self _pumpScanner]; } @@ -1044,7 +1149,7 @@ - (void)_readUntilBytes:(const void *)bytes length:(size_t)length callback:(data size_t size = data.length; const unsigned char *buffer = data.bytes; - for (int i = 0; i < size; i++ ) { + for (size_t i = 0; i < size; i++ ) { if (((const unsigned char *)buffer)[i] == ((const unsigned char *)bytes)[match_count]) { match_count += 1; if (match_count == length) { @@ -1113,7 +1218,7 @@ - (BOOL)_innerPumpScanner { NSUInteger len = mutableSlice.length; uint8_t *bytes = mutableSlice.mutableBytes; - for (int i = 0; i < len; i++) { + for (NSUInteger i = 0; i < len; i++) { bytes[i] = bytes[i] ^ _currentReadMaskKey[_currentReadMaskOffset % sizeof(_currentReadMaskKey)]; _currentReadMaskOffset += 1; } @@ -1155,11 +1260,13 @@ - (BOOL)_innerPumpScanner { if (consumer.bytesNeeded == 0) { [_consumers removeObjectAtIndex:0]; consumer.handler(self, nil); + [_consumerPool returnConsumer:consumer]; didWork = YES; } } else if (foundSize) { [_consumers removeObjectAtIndex:0]; consumer.handler(self, slice); + [_consumerPool returnConsumer:consumer]; didWork = YES; } } @@ -1168,7 +1275,7 @@ - (BOOL)_innerPumpScanner { -(void)_pumpScanner; { - assert(dispatch_get_current_queue() == _workQueue); + [self assertOnWorkQueue]; if (!_isPumping) { _isPumping = YES; @@ -1189,7 +1296,7 @@ -(void)_pumpScanner; - (void)_sendFrameWithOpcode:(SROpCode)opcode data:(id)data; { - assert(dispatch_get_current_queue() == _workQueue); + [self assertOnWorkQueue]; NSAssert(data == nil || [data isKindOfClass:[NSData class]] || [data isKindOfClass:[NSString class]], @"Function expects nil, NSString or NSData"); @@ -1239,7 +1346,7 @@ - (void)_sendFrameWithOpcode:(SROpCode)opcode data:(id)data; } if (!useMask) { - for (int i = 0; i < payloadLength; i++) { + for (size_t i = 0; i < payloadLength; i++) { frame_buffer[frame_buffer_size] = unmasked_payload[i]; frame_buffer_size += 1; } @@ -1249,7 +1356,7 @@ - (void)_sendFrameWithOpcode:(SROpCode)opcode data:(id)data; frame_buffer_size += sizeof(uint32_t); // TODO: could probably optimize this with SIMD - for (int i = 0; i < payloadLength; i++) { + for (size_t i = 0; i < payloadLength; i++) { frame_buffer[frame_buffer_size] = unmasked_payload[i] ^ mask_key[i % sizeof(uint32_t)]; frame_buffer_size += 1; } @@ -1295,7 +1402,6 @@ - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode; } } - // SRFastLog(@"%@ Got stream event %d", aStream, eventCode); dispatch_async(_workQueue, ^{ switch (eventCode) { case NSStreamEventOpenCompleted: { @@ -1303,8 +1409,6 @@ - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode; if (self.readyState >= SR_CLOSING) { return; } - - assert(_readBuffer); if (self.readyState == SR_CONNECTING && aStream == _inputStream) { @@ -1339,11 +1443,11 @@ - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode; if (!_sentClose && !_failed) { _sentClose = YES; // If we get closed in this state it's probably not clean because we should be sending this when we send messages - dispatch_async(_callbackQueue, ^{ + [self _performDelegateBlock:^{ if ([self.delegate respondsToSelector:@selector(webSocket:didCloseWithCode:reason:wasClean:)]) { [self.delegate webSocket:self didCloseWithCode:0 reason:@"Stream end encountered" wasClean:NO]; } - }); + }]; } } @@ -1383,7 +1487,6 @@ - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode; break; } }); - } @end @@ -1397,20 +1500,62 @@ @implementation SRIOConsumer @synthesize readToCurrentFrame = _readToCurrentFrame; @synthesize unmaskBytes = _unmaskBytes; -- (id)initWithScanner:(stream_scanner)scanner handler:(data_callback)handler bytesNeeded:(size_t)bytesNeeded readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes; +- (void)setupWithScanner:(stream_scanner)scanner handler:(data_callback)handler bytesNeeded:(size_t)bytesNeeded readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes; +{ + _scanner = [scanner copy]; + _handler = [handler copy]; + _bytesNeeded = bytesNeeded; + _readToCurrentFrame = readToCurrentFrame; + _unmaskBytes = unmaskBytes; + assert(_scanner || _bytesNeeded); +} + + +@end + + +@implementation SRIOConsumerPool { + NSUInteger _poolSize; + NSMutableArray *_bufferedConsumers; +} + +- (id)initWithBufferCapacity:(NSUInteger)poolSize; { self = [super init]; if (self) { - _scanner = [scanner copy]; - _handler = [handler copy]; - _bytesNeeded = bytesNeeded; - _readToCurrentFrame = readToCurrentFrame; - _unmaskBytes = unmaskBytes; - assert(_scanner || _bytesNeeded); + _poolSize = poolSize; + _bufferedConsumers = [[NSMutableArray alloc] initWithCapacity:poolSize]; } return self; } +- (id)init +{ + return [self initWithBufferCapacity:8]; +} + +- (SRIOConsumer *)consumerWithScanner:(stream_scanner)scanner handler:(data_callback)handler bytesNeeded:(size_t)bytesNeeded readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes; +{ + SRIOConsumer *consumer = nil; + if (_bufferedConsumers.count) { + consumer = [_bufferedConsumers lastObject]; + [_bufferedConsumers removeLastObject]; + } else { + consumer = [[SRIOConsumer alloc] init]; + } + + [consumer setupWithScanner:scanner handler:handler bytesNeeded:bytesNeeded readToCurrentFrame:readToCurrentFrame unmaskBytes:unmaskBytes]; + + return consumer; +} + +- (void)returnConsumer:(SRIOConsumer *)consumer; +{ + if (_bufferedConsumers.count < _poolSize) { + [_bufferedConsumers addObject:consumer]; + } +} + @end @@ -1459,7 +1604,6 @@ - (NSString *)SR_origin; @end static inline dispatch_queue_t log_queue() { - static dispatch_queue_t queue = 0; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ @@ -1488,13 +1632,11 @@ static inline void SRFastLog(NSString *format, ...) { #ifdef HAS_ICU static inline int32_t validate_dispatch_data_partial_string(NSData *data) { - const void * contents = [data bytes]; long size = [data length]; const uint8_t *str = (const uint8_t *)contents; - UChar32 codepoint = 1; int32_t offset = 0; int32_t lastOffset = 0; @@ -1513,7 +1655,6 @@ static inline int32_t validate_dispatch_data_partial_string(NSData *data) { U8_MASK_LEAD_BYTE(leadByte, U8_COUNT_TRAIL_BYTES(leadByte)); for (int i = lastOffset + 1; i < offset; i++) { - if (U8_IS_SINGLE(str[i]) || U8_IS_LEAD(str[i]) || !U8_IS_TRAIL(str[i])) { size = -1; } @@ -1550,4 +1691,67 @@ static inline int32_t validate_dispatch_data_partial_string(NSData *data) { #endif +static _SRRunLoopThread *networkThread = nil; +static NSRunLoop *networkRunLoop = nil; + +@implementation NSRunLoop (SRWebSocket) + ++ (NSRunLoop *)SR_networkRunLoop { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + networkThread = [[_SRRunLoopThread alloc] init]; + networkThread.name = @"com.squareup.SocketRocket.NetworkThread"; + [networkThread start]; + networkRunLoop = networkThread.runLoop; + }); + + return networkRunLoop; +} + +@end + + +@implementation _SRRunLoopThread { + dispatch_group_t _waitGroup; +} + +@synthesize runLoop = _runLoop; + +- (void)dealloc +{ + sr_dispatch_release(_waitGroup); +} + +- (id)init +{ + self = [super init]; + if (self) { + _waitGroup = dispatch_group_create(); + dispatch_group_enter(_waitGroup); + } + return self; +} + +- (void)main; +{ + @autoreleasepool { + _runLoop = [NSRunLoop currentRunLoop]; + dispatch_group_leave(_waitGroup); + + NSTimer *timer = [[NSTimer alloc] initWithFireDate:[NSDate distantFuture] interval:0.0 target:nil selector:nil userInfo:nil repeats:NO]; + [_runLoop addTimer:timer forMode:NSDefaultRunLoopMode]; + + while ([_runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]) { + + } + assert(NO); + } +} + +- (NSRunLoop *)runLoop; +{ + dispatch_group_wait(_waitGroup, DISPATCH_TIME_FOREVER); + return _runLoop; +} +@end diff --git a/External/SocketRocket/SocketRocket-Prefix.pch b/External/SocketRocket/SocketRocket-Prefix.pch index 590f7f3a..8c32c82c 100644 --- a/External/SocketRocket/SocketRocket-Prefix.pch +++ b/External/SocketRocket/SocketRocket-Prefix.pch @@ -24,4 +24,4 @@ extern "C" { #ifdef __cplusplus } -#endif \ No newline at end of file +#endif diff --git a/External/SocketRocket/base64.c b/External/SocketRocket/base64.c index 76473855..1d76d16e 100644 --- a/External/SocketRocket/base64.c +++ b/External/SocketRocket/base64.c @@ -311,4 +311,4 @@ b64_pton(char const *src, u_char *target, size_t targsize) } #endif /* !defined(HAVE_B64_PTON) && !defined(HAVE___B64_PTON) */ -#endif \ No newline at end of file +#endif diff --git a/External/asi-http-request/ASIAuthenticationDialog.m b/External/asi-http-request/ASIAuthenticationDialog.m index 8d9b17f2..3261e4a0 100644 --- a/External/asi-http-request/ASIAuthenticationDialog.m +++ b/External/asi-http-request/ASIAuthenticationDialog.m @@ -216,7 +216,7 @@ - (UITextField *)domainField + (void)dismiss { - [[sharedDialog parentViewController] dismissModalViewControllerAnimated:YES]; + [sharedDialog.parentViewController dismissViewControllerAnimated:YES completion:nil]; } - (void)viewDidDisappear:(BOOL)animated @@ -233,7 +233,7 @@ - (void)dismiss if (self == sharedDialog) { [[self class] dismiss]; } else { - [[self parentViewController] dismissModalViewControllerAnimated:YES]; + [self.parentViewController dismissViewControllerAnimated:YES completion:nil]; } } @@ -309,7 +309,7 @@ - (void)show } #endif - [[self presentingController] presentModalViewController:self animated:YES]; + [self.presentingController presentViewController:self animated:YES completion:nil]; } #pragma mark button callbacks diff --git a/External/asi-http-request/ASIDataCompressor.m b/External/asi-http-request/ASIDataCompressor.m index 6ff1446e..4803cabd 100644 --- a/External/asi-http-request/ASIDataCompressor.m +++ b/External/asi-http-request/ASIDataCompressor.m @@ -169,7 +169,7 @@ + (BOOL)compressDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinati readLength = [inputStream read:inputData maxLength:DATA_CHUNK_SIZE]; // Make sure nothing went wrong - if ([inputStream streamStatus] == NSStreamEventErrorOccurred) { + if ([inputStream streamStatus] == NSStreamStatusError) { if (err) { *err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of %@ failed because we were unable to read from the source data file",sourcePath],NSLocalizedDescriptionKey,[inputStream streamError],NSUnderlyingErrorKey,nil]]; } @@ -195,7 +195,7 @@ + (BOOL)compressDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinati [outputStream write:[outputData bytes] maxLength:[outputData length]]; // Make sure nothing went wrong - if ([inputStream streamStatus] == NSStreamEventErrorOccurred) { + if ([inputStream streamStatus] == NSStreamStatusError) { if (err) { *err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of %@ failed because we were unable to write to the destination data file at %@",sourcePath,destinationPath],NSLocalizedDescriptionKey,[outputStream streamError],NSUnderlyingErrorKey,nil]]; } diff --git a/External/asi-http-request/ASIDataDecompressor.m b/External/asi-http-request/ASIDataDecompressor.m index 90649613..ab6ac9bf 100644 --- a/External/asi-http-request/ASIDataDecompressor.m +++ b/External/asi-http-request/ASIDataDecompressor.m @@ -166,7 +166,7 @@ + (BOOL)uncompressDataFromFile:(NSString *)sourcePath toFile:(NSString *)destina readLength = [inputStream read:inputData maxLength:DATA_CHUNK_SIZE]; // Make sure nothing went wrong - if ([inputStream streamStatus] == NSStreamEventErrorOccurred) { + if ([inputStream streamStatus] == NSStreamStatusError) { if (err) { *err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of %@ failed because we were unable to read from the source data file",sourcePath],NSLocalizedDescriptionKey,[inputStream streamError],NSUnderlyingErrorKey,nil]]; } @@ -192,7 +192,7 @@ + (BOOL)uncompressDataFromFile:(NSString *)sourcePath toFile:(NSString *)destina [outputStream write:[outputData bytes] maxLength:[outputData length]]; // Make sure nothing went wrong - if ([inputStream streamStatus] == NSStreamEventErrorOccurred) { + if ([inputStream streamStatus] == NSStreamStatusError) { if (err) { *err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of %@ failed because we were unable to write to the destination data file at %@",sourcePath,destinationPath],NSLocalizedDescriptionKey,[outputStream streamError],NSUnderlyingErrorKey,nil]]; } diff --git a/External/asi-http-request/ASIHTTPRequest.m b/External/asi-http-request/ASIHTTPRequest.m index f1bbf78c..40138124 100644 --- a/External/asi-http-request/ASIHTTPRequest.m +++ b/External/asi-http-request/ASIHTTPRequest.m @@ -4262,7 +4262,9 @@ + (void)recordBandwidthUsage for (NSNumber *bytes in bandwidthUsageTracker) { totalBytes += [bytes unsignedLongValue]; } - averageBandwidthUsedPerSecond = totalBytes/measurements; + + if (measurements > 0) + averageBandwidthUsedPerSecond = totalBytes/measurements; } + (unsigned long)averageBandwidthUsedPerSecond diff --git a/External/diffmatchpatch/DiffMatchPatch.m b/External/diffmatchpatch/DiffMatchPatch.m index e96fe26e..fffc3405 100644 --- a/External/diffmatchpatch/DiffMatchPatch.m +++ b/External/diffmatchpatch/DiffMatchPatch.m @@ -190,7 +190,7 @@ - (id)copyWithZone:(NSZone *)zone { Patch *newPatch = [[[self class] allocWithZone:zone] init]; - newPatch.diffs = [[NSMutableArray alloc] initWithArray:self.diffs copyItems:YES]; + newPatch.diffs = [[[NSMutableArray alloc] initWithArray:self.diffs copyItems:YES] autorelease]; newPatch.start1 = self.start1; newPatch.start2 = self.start2; newPatch.length1 = self.length1; @@ -494,27 +494,25 @@ - (NSMutableArray *)diff_computeFromOldString:(NSString *)text1 return diffs; } - { - // New scope so as to garbage collect longtext and shorttext. - NSString *longtext = text1.length > text2.length ? text1 : text2; - NSString *shorttext = text1.length > text2.length ? text2 : text1; - NSUInteger i = [longtext rangeOfString:shorttext].location; - if (i != NSNotFound) { - // Shorter text is inside the longer text (speedup). - Operation op = (text1.length > text2.length) ? DIFF_DELETE : DIFF_INSERT; - [diffs addObject:[Diff diffWithOperation:op andText:[longtext substringWithRange:NSMakeRange(0, i)]]]; - [diffs addObject:[Diff diffWithOperation:DIFF_EQUAL andText:shorttext]]; - [diffs addObject:[Diff diffWithOperation:op andText:[longtext substringFromIndex:(i + shorttext.length)]]]; - return diffs; - } + // MJ: remove scope + NSString *longtext = text1.length > text2.length ? text1 : text2; + NSString *shorttext = text1.length > text2.length ? text2 : text1; + NSUInteger i = [longtext rangeOfString:shorttext].location; + if (i != NSNotFound) { + // Shorter text is inside the longer text (speedup). + Operation op = (text1.length > text2.length) ? DIFF_DELETE : DIFF_INSERT; + [diffs addObject:[Diff diffWithOperation:op andText:[longtext substringWithRange:NSMakeRange(0, i)]]]; + [diffs addObject:[Diff diffWithOperation:DIFF_EQUAL andText:shorttext]]; + [diffs addObject:[Diff diffWithOperation:op andText:[longtext substringFromIndex:(i + shorttext.length)]]]; + return diffs; + } - if (shorttext.length == 1) { - // Single character string. - // After the previous speedup, the character can't be an equality. - [diffs addObject:[Diff diffWithOperation:DIFF_DELETE andText:text1]]; - [diffs addObject:[Diff diffWithOperation:DIFF_INSERT andText:text2]]; - return diffs; - } + if (shorttext.length == 1) { + // Single character string. + // After the previous speedup, the character can't be an equality. + [diffs addObject:[Diff diffWithOperation:DIFF_DELETE andText:text1]]; + [diffs addObject:[Diff diffWithOperation:DIFF_INSERT andText:text2]]; + return diffs; } // Check to see if the problem can be split in two. @@ -657,10 +655,13 @@ - (NSMutableArray *)diff_bisectOfOldString:(NSString *)_text1 andNewString:(NSString *)_text2 deadline:(NSTimeInterval)deadline; { +// MJ: free v1 and v2 for memory fix below. #define text1CharacterAtIndex(A) text1_chars[(A)] #define text2CharacterAtIndex(A) text2_chars[(A)] #define freeTextBuffers() if (text1_buffer != NULL) free(text1_buffer);\ - if (text2_buffer != NULL) free(text2_buffer); + if (text2_buffer != NULL) free(text2_buffer);\ + if (v1 != NULL) free(v1);\ + if (v2 != NULL) free(v2); CFStringRef text1 = (CFStringRef)_text1; CFStringRef text2 = (CFStringRef)_text2; @@ -671,8 +672,10 @@ - (NSMutableArray *)diff_bisectOfOldString:(NSString *)_text1 CFIndex max_d = (text1_length + text2_length + 1) / 2; CFIndex v_offset = max_d; CFIndex v_length = 2 * max_d; - CFIndex v1[v_length]; - CFIndex v2[v_length]; + + // MJ: Memory fix from http://code.google.com/p/google-diff-match-patch/issues/detail?id=81 + CFIndex *v1 = malloc(sizeof(CFIndex) * v_length); + CFIndex *v2 = malloc(sizeof(CFIndex) * v_length); for (CFIndex x = 0; x < v_length; x++) { v1[x] = -1; v2[x] = -1; @@ -2115,7 +2118,7 @@ - (NSMutableArray *)patch_makeFromOldString:(NSString *)text1 */ - (NSMutableArray *)patch_deepCopy:(NSArray *)patches; { - NSMutableArray *patchesCopy = [[NSMutableArray alloc] initWithArray:patches copyItems:YES]; + NSMutableArray *patchesCopy = [[[NSMutableArray alloc] initWithArray:patches copyItems:YES] autorelease]; return patchesCopy; } @@ -2241,7 +2244,7 @@ - (NSArray *)patch_apply:(NSArray *)sourcePatches // Strip the padding off. text = [textMutable substringWithRange:NSMakeRange(nullPadding.length, textMutable.length - 2 * nullPadding.length)]; - [patches release]; + return [NSArray arrayWithObjects:text, resultsArray, nil]; } diff --git a/External/diffmatchpatch/NSString+JavaSubstring.m b/External/diffmatchpatch/NSString+JavaSubstring.m index d159d19f..c1e68a4e 100644 --- a/External/diffmatchpatch/NSString+JavaSubstring.m +++ b/External/diffmatchpatch/NSString+JavaSubstring.m @@ -24,11 +24,6 @@ #import "DiffMatchPatchCFUtilities.h" -@interface FIXCATEGORYBUGDMPJSUB; -@end -@implementation FIXCATEGORYBUGDMPJSUB; -@end - @implementation NSString (JavaSubstring) - (NSString *)diff_javaSubstringFromStart:(NSUInteger)start toEnd:(NSUInteger)end; diff --git a/External/diffmatchpatch/NSString+UnicharUtilities.m b/External/diffmatchpatch/NSString+UnicharUtilities.m index 23770ecd..85242482 100644 --- a/External/diffmatchpatch/NSString+UnicharUtilities.m +++ b/External/diffmatchpatch/NSString+UnicharUtilities.m @@ -22,11 +22,6 @@ #import "NSString+UnicharUtilities.h" -@interface FIXCATEGORYBUGDMPUNI; -@end -@implementation FIXCATEGORYBUGDMPUNI; -@end - @implementation NSString (UnicharUtilities) + (NSString *)diff_stringFromUnichar:(unichar)ch; diff --git a/External/diffmatchpatch/NSString+UriCompatibility.m b/External/diffmatchpatch/NSString+UriCompatibility.m index 4c85499a..3b3f060d 100644 --- a/External/diffmatchpatch/NSString+UriCompatibility.m +++ b/External/diffmatchpatch/NSString+UriCompatibility.m @@ -22,12 +22,6 @@ #import "NSString+UriCompatibility.h" -@interface FIXCATEGORYBUGDMPURI; -@end -@implementation FIXCATEGORYBUGDMPURI; -@end - - @implementation NSString (UriCompatibility) /** diff --git a/README.md b/README.md index 39ac9f02..c8c9ce51 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,55 @@ -# simperium-ios +simperium-ios +============= Simperium is a simple way for developers to move data as it changes, instantly and automatically. This is the iOS / OSX library. You can [browse the documentation](http://simperium.com/docs/ios/) or [try a tutorial](https://simperium.com/tutorials/simpletodo-ios/). You can [sign up](http://simperium.com) for a hosted version of Simperium. There are Simperium libraries for [other languages](https://simperium.com/overview/) too. -### Known transgressions +Adding Simperium to your project +-------------------------------- +The easiest way to add Simperium is to [download the binary framework](https://docs.google.com/uc?export=download&id=0B_WOy42eJTXtMl90cHN1YVUzRlk) and [follow these instructions](http://simperium.com/docs/ios/). + +To use the source code instead, drag and drop Simperium.xcodeproj into your application's project, then add libSimperium.a in your target's Build Phase tab (under Link Binary with Libraries). You'll still need to [add some dependencies](http://simperium.com/docs/ios/#add). Note that you shouldn't have Simperium.xcodeproj open in another window at the same time. Xcode doesn't like this. + +If for some reason you want to build the binary framework yourself, open Simperium.xcodeproj, then select and build the Framework target for iOS Device. You can build a release version of the Framework target by choosing Product -> Build for Archiving. + +Folder structure +---------------- +**Simperium**. Everything is accessed from a `Simperium` instance. This class can be safely instantiated more than once in the same app (e.g. for unit testing). + +**Object**. Simperium does a lot of diffing in order to send only data that changes. Any object class that conforms to the `SPDiffable` protocol can be supported. `SPManagedObject` is for Core Data, and `SPObject` is a container for raw JSON (not yet supported). `SPGhost` is an object's opinion of its own state on the server (the name "ghost" was borrowed from the [Tribes Networking Model](http://www.pingz.com/wordpress/wp-content/uploads/2009/11/tribes_networking_model.pdf)). + +**Diffing**. An `SPDiffer` can perform diffs on any `SPDiffable` object. Each differ adheres to an `SPSchema`. The schema stores a list of members/properties (of type `SPMember`) for an object of a particular type. Each subclass of `SPMember` corresponds to a data type, and knows how to diff itself. In the future these will be parameterized for custom diffing, conflict resolution, validation, etc. + +**System**. An `SPBucket` provides access to a synchronized bucket of objects of a particular type. The `SPBucket` has an `SPDiffer` to perform diffs, an `SPStorageProvider` for locally reading and writing data, an `SPChangeProcessor` for processing incoming and outgoing changes, and an `SPIndexProcessor` for processing indexes retrieved from the server. The processors run in their own threads. + +**Storage**. An `SPStorageProvider` defines an interface for local reading and writing of objects. In particular it defines a `threadSafeStorage` method that returns a thread safe instance. `SPCoreDataProvider` is currently the only fully functional storage provider. + +**Authentication**. An `SPAuthenticator` handles all authentication with Simperium, and can be customized or overridden as necessary. There are companion classes for iOS and OSX that provide a code-driven UI for signing in and signing up (`SPAuthenticationViewController` and `SPAuthenticationWindowController`). + +**Networking**. An `SPNetworkProvider` defines an interface for remote reading and writing of objects in an `SPBucket`. The network provider sends local data and receives remote data in the background, passing it through threaded processors as necessary. Although there is an HTTP provider, the WebSockets provider is intended to become the default (but is still under development). + +**User**. Basic access to a user's data. In the future this will hold custom properties and presence information. + +**Helpers**. Exporter, keychain, etc. + +**Binary**. Basic support for moving binary files, either between client devices or potentially from a server to clients. Currently works by syncing a file URI and then using that to upload/download the corresponding data to/from S3. Still under development. + +Known transgressions +-------------------- If you decide to dig into the source code, please expect problems and violations of best practices. Your help in identifying these would be greatly appreciated. * ASI is still being used for HTTP requests, but WebSockets are intended to replace HTTP eventually anyway -* Some external libraries (still) haven't been properly "namespaced" (with prefixes) * Core Data threading is currently messy (iOS 4 was originally targeted) * Full WebSockets support is still being fleshed out * Support for raw JSON (without Core Data) is still being fleshed out -* No CocoaPods support yet * Some TODOs and hacks remain in the code * Some support for binary files and collaboration is committed, but not finished * Auth UI is in a .xib but could live more happily as code instead * External libraries are included as source files instead of submodules -If you spot more transgressions that you don't feel like fixing yourself, please add an issue, append to this list via a pull request, or [contact us](http://simperium.com/contact/). +If you spot more transgressions that you don't feel like fixing yourself, you can add an issue, append to this list via a pull request, or [contact us](http://simperium.com/contact/). -### License +License +------- The Simperium iOS library is available for free and commercial use under the MIT license. \ No newline at end of file diff --git a/Resources/AuthWindow.xib b/Resources/AuthWindow.xib deleted file mode 100644 index 904b1177..00000000 --- a/Resources/AuthWindow.xib +++ /dev/null @@ -1,781 +0,0 @@ - - - - 1070 - 11D50b - 2182 - 1138.32 - 568.00 - - com.apple.InterfaceBuilder.CocoaPlugin - 2182 - - - YES - NSView - NSTextField - NSWindowTemplate - NSTextFieldCell - NSSecureTextField - NSCustomView - NSButtonCell - NSButton - NSSecureTextFieldCell - NSCustomObject - - - YES - com.apple.InterfaceBuilder.CocoaPlugin - - - PluginDependencyRecalculationVersion - - - - YES - - SPAuthWindowController - - - FirstResponder - - - NSApplication - - - 13 - 2 - {{456, 215}, {365, 375}} - 544735232 - Simplenote - NSWindow - - - {365, 375} - {365, 375} - - - 256 - - YES - - - 268 - - YES - - - -2147483380 - {{77, 12}, {211, 23}} - - - - _NS:2466 - YES - - -2080244224 - 134217728 - Already have an account? Sign in - - LucidaGrande - 13 - 1044 - - _NS:2466 - - -934526721 - 162 - - - 400 - 75 - - - - - 268 - {{82, 24}, {201, 23}} - - - - _NS:2466 - YES - - -2080244224 - 134217728 - Don't have an account? Sign up - - _NS:2466 - - -934526721 - 162 - - - 400 - 75 - - - - - 268 - {{82, 68}, {200, 32}} - - - - _NS:2466 - YES - - -2080244224 - 134217728 - Sign in - - _NS:2466 - - -2038152961 - 162 - - - 400 - 75 - - - - - 268 - {{74, 196}, {106, 28}} - - - - _NS:3944 - YES - - 67239424 - 272629760 - Sign In - - HelveticaNeue - 19 - 16 - - _NS:3944 - - YES - - 6 - System - controlColor - - 3 - MC42NjY2NjY2NjY3AA - - - - 3 - MQA - - 2 - - - - - - - 268 - - YES - - - 268 - {{6, 0}, {191, 24}} - - - - _NS:3279 - YES - - 338820672 - 272663552 - - - HelveticaNeue - 12 - 16 - - Password - _NS:3279 - - - 6 - System - textBackgroundColor - - 3 - MQA - - - - 6 - System - textColor - - 3 - MAA - - - - YES - NSAllRomanInputSourcesLocaleIdentifier - - - - - {{82, 126}, {200, 28}} - - - - _NS:1192 - SPInputBoxView - - - - 268 - - YES - - - 268 - {{8, 0}, {191, 24}} - - - - _NS:903 - YES - - -1808662975 - 4228096 - - - Email - _NS:903 - - - - - - - {{82, 160}, {200, 28}} - - - - _NS:1192 - SPInputBoxView - - - {365, 375} - - - - _NS:1192 - SPSpotLightView - - - {365, 375} - - - - - {{0, 0}, {1680, 1028}} - {365, 397} - {365, 397} - YES - - - - 268 - {163, 96} - - - - _NS:1192 - NSView - - - - - YES - - - emailField - - - - 24 - - - - signinButton - - - - 44 - - - - passwordField - - - - 45 - - - - signinClicked: - - - - 46 - - - - window - - - - 47 - - - - signupToggleButton - - - - 64 - - - - signinToggleButton - - - - 65 - - - - signupToggle: - - - - 66 - - - - signinToggle: - - - - 67 - - - - signinText - - - - 17 - - - - - YES - - 0 - - YES - - - - - - -2 - - - File's Owner - - - -1 - - - First Responder - - - -3 - - - Application - - - 1 - - - YES - - - - - - 2 - - - YES - - - - - - 15 - - - YES - - - - - - - - - - - 28 - - - YES - - - - Sign in - - - 29 - - - - - 38 - - - YES - - - - - - 42 - - - YES - - - - - - 5 - - - YES - - - - - - 6 - - - - - 7 - - - YES - - - - - - 8 - - - - - 50 - - - YES - - - - Sign up Toggle - - - 51 - - - - - 61 - - - YES - - - - Sign in Toggle - - - 62 - - - - - 68 - - - - - 9 - - - YES - - - - - - 10 - - - - - - - YES - - YES - -1.IBPluginDependency - -2.IBPluginDependency - -3.IBPluginDependency - 1.IBPluginDependency - 1.IBWindowTemplateEditedContentRect - 1.NSWindowTemplate.visibleAtLaunch - 10.IBPluginDependency - 15.IBPluginDependency - 2.CustomClassName - 2.IBPluginDependency - 28.IBPluginDependency - 29.CustomClassName - 29.IBPluginDependency - 38.IBPluginDependency - 42.IBPluginDependency - 5.IBAttributePlaceholdersKey - 5.IBPluginDependency - 50.IBPluginDependency - 51.CustomClassName - 51.IBPluginDependency - 6.IBPluginDependency - 61.IBPluginDependency - 62.CustomClassName - 62.IBPluginDependency - 68.IBPluginDependency - 7.IBAttributePlaceholdersKey - 7.IBPluginDependency - 8.IBPluginDependency - 9.IBPluginDependency - - - YES - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - {{357, 418}, {480, 270}} - - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - SPAuthView - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - SPSigninButtonCell - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - - ToolTip - - ToolTip - - Email - - - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - SPToggleAuthCell - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - SPToggleAuthCell - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - - ToolTip - - ToolTip - - Password - - - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - - - - YES - - - - - - YES - - - - - 76 - - - - YES - - SPAuthView - NSView - - IBProjectSource - ./Classes/SPAuthView.h - - - - SPAuthWindowController - NSWindowController - - signinClicked: - id - - - signinClicked: - - signinClicked: - id - - - - YES - - YES - emailField - logout - passwordField - signinButton - signinText - - - YES - NSTextField - NSMenuItem - NSTextField - NSButton - NSTextFieldCell - - - - YES - - YES - emailField - logout - passwordField - signinButton - signinText - - - YES - - emailField - NSTextField - - - logout - NSMenuItem - - - passwordField - NSTextField - - - signinButton - NSButton - - - signinText - NSTextFieldCell - - - - - IBProjectSource - ./Classes/SPAuthWindowController.h - - - - SPInputBoxView - NSView - - IBProjectSource - ./Classes/SPInputBoxView.h - - - - SPSigninButtonCell - NSButtonCell - - IBProjectSource - ./Classes/SPSigninButtonCell.h - - - - SPSpotLightView - NSView - - IBProjectSource - ./Classes/SPSpotLightView.h - - - - SPToggleAuthCell - NSButtonCell - - IBProjectSource - ./Classes/SPToggleAuthCell.h - - - - - 0 - IBCocoaFramework - - com.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3 - - - YES - 3 - - diff --git a/Resources/LoginView-iPad.xib b/Resources/LoginView-iPad.xib deleted file mode 100644 index 07775060..00000000 --- a/Resources/LoginView-iPad.xib +++ /dev/null @@ -1,411 +0,0 @@ - - - - 512 - 11B26 - 1934 - 1138 - 566.00 - - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - 931 - - - YES - IBUIImageView - IBUITableView - IBUIButton - IBUIView - IBUILabel - IBProxyObject - - - YES - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - - - PluginDependencyRecalculationVersion - - - - YES - - IBFilesOwner - IBIPadFramework - - - IBFirstResponder - IBIPadFramework - - - - 274 - - YES - - - 301 - {{341, 184}, {339, 287}} - - - 1 - MCAwIDAgMAA - - NO - YES - NO - IBIPadFramework - NO - 1 - 1 - 0 - YES - 44 - 10 - 10 - - - - 301 - - YES - - - 301 - {{20, 20}, {294, 80}} - - NO - YES - 7 - NO - IBIPadFramework - - - 1 - MCAwIDAAA - - - 1 - 10 - 4 - 1 - - 1 - 17 - - - Helvetica - 17 - 16 - - - - - 301 - {{43, 115}, {248, 37}} - - NO - IBIPadFramework - 0 - 0 - 1 - Create an account - - 3 - MQA - - - 1 - MC4xOTYwNzg0MzQ2IDAuMzA5ODAzOTMyOSAwLjUyMTU2ODY1NgA - - - 3 - MC41AA - - - Helvetica-Bold - Helvetica - 2 - 15 - - - Helvetica-Bold - 15 - 16 - - - - - 301 - {{43, 160}, {248, 37}} - - NO - IBIPadFramework - 0 - 0 - 1 - I have an account - - - 1 - MC4xOTYwNzg0MzQ2IDAuMzA5ODAzOTMyOSAwLjUyMTU2ODY1NgA - - - - - - - - 301 - {{131, 242}, {72, 72}} - - NO - NO - IBIPadFramework - - NSImage - logo.png - - - - {{343, 170}, {334, 325}} - - - 3 - MCAwAA - - IBIPadFramework - - - {{0, 20}, {1024, 748}} - - - 2 - MC40NTA5ODA0MiAwLjUzNzI1NDkzIDAuNjQ3MDU4ODQAA - - - 2 - - - 3 - 3 - - IBIPadFramework - - - - - YES - - - view - - - - 6 - - - - tableView - - - - 24 - - - - welcomeLabel - - - - 32 - - - - createButton - - - - 33 - - - - loginButton - - - - 34 - - - - welcomeView - - - - 36 - - - - dataSource - - - - 16 - - - - delegate - - - - 17 - - - - - YES - - 0 - - YES - - - - - - 1 - - - YES - - - - - - - -1 - - - File's Owner - - - -2 - - - - - 14 - - - YES - - - - - 35 - - - YES - - - - - - - - - 29 - - - - - 30 - - - - - 31 - - - - - 37 - - - - - - - YES - - YES - -1.CustomClassName - -1.IBPluginDependency - -2.CustomClassName - -2.IBPluginDependency - 1.IBLastUsedUIStatusBarStylesToTargetRuntimesMap - 1.IBPluginDependency - 14.IBPluginDependency - 29.IBPluginDependency - 30.IBPluginDependency - 31.IBPluginDependency - 35.IBPluginDependency - 37.IBPluginDependency - - - YES - LoginViewController - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - UIResponder - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - - IBCocoaTouchFramework - - - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - - - - YES - - - - - - YES - - - - - 37 - - - 0 - IBIPadFramework - - com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS - - - - com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS - - - - com.apple.InterfaceBuilder.CocoaTouchPlugin.InterfaceBuilder3 - - - YES - 3 - - logo.png - {72, 72} - - 931 - - diff --git a/Resources/LoginView.xib b/Resources/LoginView.xib deleted file mode 100644 index 3e6added..00000000 --- a/Resources/LoginView.xib +++ /dev/null @@ -1,425 +0,0 @@ - - - - 1024 - 11D50b - 1938 - 1138.32 - 568.00 - - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - 933 - - - IBUIImageView - IBUITableView - IBUIButton - IBUIView - IBUILabel - IBProxyObject - - - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - - - PluginDependencyRecalculationVersion - - - - - IBFilesOwner - IBCocoaTouchFramework - - - IBFirstResponder - IBCocoaTouchFramework - - - - 274 - - - - 274 - {{0, -1}, {320, 461}} - - - - 3 - MCAwAA - - NO - YES - NO - IBCocoaTouchFramework - NO - 1 - 1 - 0 - YES - 44 - 10 - 10 - - - - 301 - - - - 301 - {{-5, 29}, {285, 50}} - - - NO - YES - 7 - NO - IBCocoaTouchFramework - - - 1 - MCAwIDAAA - - - 1 - 10 - 4 - 1 - - 1 - 17 - - - Helvetica - 17 - 16 - - - - - 301 - {{13, 87}, {248, 37}} - - - NO - IBCocoaTouchFramework - 0 - 0 - 1 - Create an account - - 3 - MQA - - - 1 - MC4xOTYwNzg0MzQ2IDAuMzA5ODAzOTMyOSAwLjUyMTU2ODY1NgA - - - 3 - MC41AA - - - Helvetica-Bold - Helvetica - 2 - 15 - - - Helvetica-Bold - 15 - 16 - - - - - 301 - {{13, 132}, {248, 37}} - - - NO - IBCocoaTouchFramework - 0 - 0 - 1 - I have an account - - - 1 - MC4xOTYwNzg0MzQ2IDAuMzA5ODAzOTMyOSAwLjUyMTU2ODY1NgA - - - - - - - - 292 - {{101, 235}, {72, 72}} - - - NO - NO - IBCocoaTouchFramework - - NSImage - icon.png - - - - {{20, 58}, {280, 341}} - - - - IBCocoaTouchFramework - - - {{0, 20}, {320, 460}} - - - - 2 - MC44OTQxMTc3MTMgMC44OTQxMTc3MTMgMC44OTQxMTc3MTMAA - - - IBCocoaTouchFramework - - - - - - - view - - - - 6 - - - - tableView - - - - 15 - - - - createButton - - - - 34 - - - - loginButton - - - - 35 - - - - welcomeLabel - - - - 36 - - - - welcomeView - - - - 37 - - - - dataSource - - - - 16 - - - - delegate - - - - 17 - - - - - - 0 - - - - - - 1 - - - - - - - - - -1 - - - File's Owner - - - -2 - - - - - 14 - - - - - 30 - - - - - - - - - - - 31 - - - - - 32 - - - - - 33 - - - - - 38 - - - - - - - LoginViewController - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - UIResponder - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - - - - - - 38 - - - - - LoginViewController - UIViewController - - UIButton - UIButton - UITextField - UITextField - UITextField - UINavigationBar - UIActivityIndicatorView - UITableView - UILabel - UIView - - - - createButton - UIButton - - - loginButton - UIButton - - - loginField - UITextField - - - loginPasswordConfirmField - UITextField - - - loginPasswordField - UITextField - - - navbar - UINavigationBar - - - progressView - UIActivityIndicatorView - - - tableView - UITableView - - - welcomeLabel - UILabel - - - welcomeView - UIView - - - - IBProjectSource - ./Classes/LoginViewController.h - - - - - 0 - IBCocoaTouchFramework - - com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS - - - - com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS - - - YES - 3 - - icon.png - {57, 57} - - 933 - - diff --git a/Resources/View.xib b/Resources/View.xib deleted file mode 100644 index 359e28eb..00000000 --- a/Resources/View.xib +++ /dev/null @@ -1,767 +0,0 @@ - - - - 768 - 10F569 - 788 - 1038.29 - 461.00 - - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - 117 - - - YES - - - - YES - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - - - YES - - YES - - - YES - - - - YES - - IBFilesOwner - IBCocoaTouchFramework - - - IBFirstResponder - IBCocoaTouchFramework - - - - 274 - - YES - - - 274 - {{0, -1}, {320, 461}} - - - 10 - - 549453824 - {84, 1} - - YES - - YES - - - - TU0AKgAAAVjFzNT/xczU/8XM1P/FzNT/xczS/8vS2P/L0tj/xczU/8XM1P/FzNT/xczU/8XM0v/L0tj/ -y9LY/8XM1P/FzNT/xczU/8XM1P/FzNL/y9LY/8vS2P/FzNT/xczU/8XM1P/FzNT/xczS/8vS2P/L0tj/ -xczU/8XM1P/FzNT/xczU/8XM0v/L0tj/y9LY/8XM1P/FzNT/xczU/8XM1P/FzNL/y9LY/8vS2P/FzNT/ -xczU/8XM1P/FzNT/xczS/8vS2P/L0tj/xczU/8XM1P/FzNT/xczU/8XM0v/L0tj/y9LY/8XM1P/FzNT/ -xczU/8XM1P/FzNL/y9LY/8vS2P/FzNT/xczU/8XM1P/FzNT/xczS/8vS2P/L0tj/xczU/8XM1P/FzNT/ -xczU/8XM0v/L0tj/y9LY/8XM1P/FzNT/xczU/8XM1P/FzNL/y9LY/8vS2P8ADQEAAAMAAAABAFQAAAEB -AAMAAAABAAEAAAECAAMAAAAEAAAB+gEDAAMAAAABAAEAAAEGAAMAAAABAAIAAAERAAQAAAABAAAACAES -AAMAAAABAAEAAAEVAAMAAAABAAQAAAEWAAMAAAABAAEAAAEXAAQAAAABAAABUAEcAAMAAAABAAEAAAFS -AAMAAAABAAEAAAFTAAMAAAAEAAACAgAAAAAACAAIAAgACAABAAEAAQABA - - - - - - 3 - MCAwAA - - - groupTableViewBackgroundColor - - NO - YES - NO - IBCocoaTouchFramework - NO - 1 - 1 - 0 - YES - 44 - 10 - 10 - - - - 301 - - YES - - - 301 - {{-5, 29}, {285, 50}} - - NO - YES - 7 - NO - IBCocoaTouchFramework - - - 1 - MCAwIDAAA - - - 1 - 10 - 4 - 1 - - - - 301 - {{13, 87}, {248, 37}} - - NO - IBCocoaTouchFramework - 0 - 0 - - Helvetica-Bold - 15 - 16 - - 1 - Create an account - - 3 - MQA - - - 1 - MC4xOTYwNzg0MzQ2IDAuMzA5ODAzOTMyOSAwLjUyMTU2ODY1NgA - - - 3 - MC41AA - - - - - 301 - {{13, 132}, {248, 37}} - - NO - IBCocoaTouchFramework - 0 - 0 - - 1 - I have an account - - - 1 - MC4xOTYwNzg0MzQ2IDAuMzA5ODAzOTMyOSAwLjUyMTU2ODY1NgA - - - - - - 292 - {{101, 235}, {72, 72}} - - NO - NO - IBCocoaTouchFramework - - NSImage - logo.png - - - - {{20, 58}, {280, 341}} - - - IBCocoaTouchFramework - - - {320, 460} - - - - IBCocoaTouchFramework - - - - - YES - - - view - - - - 6 - - - - tableView - - - - 15 - - - - dataSource - - - - 16 - - - - delegate - - - - 17 - - - - createButton - - - - 34 - - - - loginButton - - - - 35 - - - - welcomeLabel - - - - 36 - - - - welcomeView - - - - 37 - - - - - YES - - 0 - - - - - - 1 - - - YES - - - - - - - -1 - - - File's Owner - - - -2 - - - - - 14 - - - - - 30 - - - YES - - - - - - - - - 31 - - - - - 32 - - - - - 33 - - - - - 38 - - - - - - - YES - - YES - -1.CustomClassName - -2.CustomClassName - 1.IBEditorWindowLastContentRect - 1.IBPluginDependency - 14.IBEditorWindowLastContentRect - 14.IBPluginDependency - 31.IBPluginDependency - 32.IBPluginDependency - 33.IBPluginDependency - 38.IBPluginDependency - - - YES - LoginViewController - UIResponder - {{555, 216}, {320, 480}} - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - {{329, 254}, {320, 480}} - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - - - - YES - - - YES - - - - - YES - - - YES - - - - 38 - - - - YES - - LoginViewController - UIViewController - - YES - - YES - createButton - loginButton - loginField - loginPasswordConfirmField - loginPasswordField - navbar - progressView - tableView - welcomeLabel - welcomeView - - - YES - UIButton - UIButton - UITextField - UITextField - UITextField - UINavigationBar - UIActivityIndicatorView - UITableView - UILabel - UIView - - - - YES - - YES - createButton - loginButton - loginField - loginPasswordConfirmField - loginPasswordField - navbar - progressView - tableView - welcomeLabel - welcomeView - - - YES - - createButton - UIButton - - - loginButton - UIButton - - - loginField - UITextField - - - loginPasswordConfirmField - UITextField - - - loginPasswordField - UITextField - - - navbar - UINavigationBar - - - progressView - UIActivityIndicatorView - - - tableView - UITableView - - - welcomeLabel - UILabel - - - welcomeView - UIView - - - - - IBProjectSource - Classes/LoginViewController.h - - - - NSObject - - IBProjectSource - JSON/NSObject+SBJSON.h - - - - NSObject - - IBProjectSource - JSON/SBJsonWriter.h - - - - - YES - - NSObject - - IBFrameworkSource - Foundation.framework/Headers/NSError.h - - - - NSObject - - IBFrameworkSource - Foundation.framework/Headers/NSFileManager.h - - - - NSObject - - IBFrameworkSource - Foundation.framework/Headers/NSKeyValueCoding.h - - - - NSObject - - IBFrameworkSource - Foundation.framework/Headers/NSKeyValueObserving.h - - - - NSObject - - IBFrameworkSource - Foundation.framework/Headers/NSKeyedArchiver.h - - - - NSObject - - IBFrameworkSource - Foundation.framework/Headers/NSObject.h - - - - NSObject - - IBFrameworkSource - Foundation.framework/Headers/NSRunLoop.h - - - - NSObject - - IBFrameworkSource - Foundation.framework/Headers/NSThread.h - - - - NSObject - - IBFrameworkSource - Foundation.framework/Headers/NSURL.h - - - - NSObject - - IBFrameworkSource - Foundation.framework/Headers/NSURLConnection.h - - - - NSObject - - IBFrameworkSource - QuartzCore.framework/Headers/CAAnimation.h - - - - NSObject - - IBFrameworkSource - QuartzCore.framework/Headers/CALayer.h - - - - NSObject - - IBFrameworkSource - UIKit.framework/Headers/UIAccessibility.h - - - - NSObject - - IBFrameworkSource - UIKit.framework/Headers/UINibLoading.h - - - - NSObject - - IBFrameworkSource - UIKit.framework/Headers/UIResponder.h - - - - UIActivityIndicatorView - UIView - - IBFrameworkSource - UIKit.framework/Headers/UIActivityIndicatorView.h - - - - UIButton - UIControl - - IBFrameworkSource - UIKit.framework/Headers/UIButton.h - - - - UIControl - UIView - - IBFrameworkSource - UIKit.framework/Headers/UIControl.h - - - - UIImageView - UIView - - IBFrameworkSource - UIKit.framework/Headers/UIImageView.h - - - - UILabel - UIView - - IBFrameworkSource - UIKit.framework/Headers/UILabel.h - - - - UINavigationBar - UIView - - IBFrameworkSource - UIKit.framework/Headers/UINavigationBar.h - - - - UIResponder - NSObject - - - - UIScrollView - UIView - - IBFrameworkSource - UIKit.framework/Headers/UIScrollView.h - - - - UISearchBar - UIView - - IBFrameworkSource - UIKit.framework/Headers/UISearchBar.h - - - - UISearchDisplayController - NSObject - - IBFrameworkSource - UIKit.framework/Headers/UISearchDisplayController.h - - - - UITableView - UIScrollView - - IBFrameworkSource - UIKit.framework/Headers/UITableView.h - - - - UITextField - UIControl - - IBFrameworkSource - UIKit.framework/Headers/UITextField.h - - - - UIView - - - - UIView - UIResponder - - IBFrameworkSource - UIKit.framework/Headers/UIView.h - - - - UIViewController - - IBFrameworkSource - UIKit.framework/Headers/UINavigationController.h - - - - UIViewController - - IBFrameworkSource - UIKit.framework/Headers/UIPopoverController.h - - - - UIViewController - - IBFrameworkSource - UIKit.framework/Headers/UISplitViewController.h - - - - UIViewController - - IBFrameworkSource - UIKit.framework/Headers/UITabBarController.h - - - - UIViewController - UIResponder - - IBFrameworkSource - UIKit.framework/Headers/UIViewController.h - - - - - 0 - IBCocoaTouchFramework - - com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS - - - - com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS - - - - com.apple.InterfaceBuilder.CocoaTouchPlugin.InterfaceBuilder3 - - - YES - Simplenote.xcodeproj - 3 - - logo.png - {72, 72} - - 117 - - diff --git a/Simperium-OSX/Simperium-OSX.xcodeproj/project.pbxproj b/Simperium-OSX/Simperium-OSX.xcodeproj/project.pbxproj index b9e22502..203165e0 100644 --- a/Simperium-OSX/Simperium-OSX.xcodeproj/project.pbxproj +++ b/Simperium-OSX/Simperium-OSX.xcodeproj/project.pbxproj @@ -16,13 +16,11 @@ 268EDF30155F08A2006B566F /* NSDate+Simperium.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDEE5155F089C006B566F /* NSDate+Simperium.m */; }; 268EDF31155F08A2006B566F /* NSString+Simperium.h in Headers */ = {isa = PBXBuildFile; fileRef = 268EDEE6155F089C006B566F /* NSString+Simperium.h */; settings = {ATTRIBUTES = (Public, ); }; }; 268EDF32155F08A2006B566F /* NSString+Simperium.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDEE7155F089C006B566F /* NSString+Simperium.m */; }; - 268EDF33155F08A2006B566F /* Simperium-complete.h in Headers */ = {isa = PBXBuildFile; fileRef = 268EDEE8155F089C006B566F /* Simperium-complete.h */; }; + 268EDF33155F08A2006B566F /* Simperium-complete.h in Headers */ = {isa = PBXBuildFile; fileRef = 268EDEE8155F089C006B566F /* Simperium-complete.h */; settings = {ATTRIBUTES = (Public, ); }; }; 268EDF35155F08A2006B566F /* Simperium.h in Headers */ = {isa = PBXBuildFile; fileRef = 268EDEEA155F089D006B566F /* Simperium.h */; settings = {ATTRIBUTES = (Public, ); }; }; 268EDF36155F08A2006B566F /* Simperium.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDEEB155F089D006B566F /* Simperium.m */; }; - 268EDF37155F08A2006B566F /* SPAuthenticationManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 268EDEEC155F089D006B566F /* SPAuthenticationManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 268EDF38155F08A2006B566F /* SPAuthenticationManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDEED155F089D006B566F /* SPAuthenticationManager.m */; }; - 268EDF39155F08A2006B566F /* SPAuthWindowController.h in Headers */ = {isa = PBXBuildFile; fileRef = 268EDEEE155F089D006B566F /* SPAuthWindowController.h */; }; - 268EDF3A155F08A2006B566F /* SPAuthWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDEEF155F089D006B566F /* SPAuthWindowController.m */; }; + 268EDF37155F08A2006B566F /* SPAuthenticator.h in Headers */ = {isa = PBXBuildFile; fileRef = 268EDEEC155F089D006B566F /* SPAuthenticator.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 268EDF38155F08A2006B566F /* SPAuthenticator.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDEED155F089D006B566F /* SPAuthenticator.m */; }; 268EDF3B155F08A2006B566F /* SPBinaryManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 268EDEF0155F089D006B566F /* SPBinaryManager.h */; }; 268EDF3C155F08A2006B566F /* SPBinaryManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDEF1155F089D006B566F /* SPBinaryManager.m */; }; 268EDF3D155F08A2006B566F /* SPBinaryTransportDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 268EDEF2155F089D006B566F /* SPBinaryTransportDelegate.h */; }; @@ -36,8 +34,6 @@ 268EDF47155F08A2006B566F /* SPEnvironment.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDEFC155F089E006B566F /* SPEnvironment.m */; }; 268EDF48155F08A2006B566F /* SPGhost.h in Headers */ = {isa = PBXBuildFile; fileRef = 268EDEFD155F089E006B566F /* SPGhost.h */; }; 268EDF49155F08A2006B566F /* SPGhost.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDEFE155F089E006B566F /* SPGhost.m */; }; - 268EDF4A155F08A2006B566F /* SPHttpManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 268EDEFF155F089E006B566F /* SPHttpManager.h */; }; - 268EDF4B155F08A2006B566F /* SPHttpManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDF00155F089E006B566F /* SPHttpManager.m */; }; 268EDF4C155F08A2006B566F /* SPIndexProcessor.h in Headers */ = {isa = PBXBuildFile; fileRef = 268EDF01155F089E006B566F /* SPIndexProcessor.h */; }; 268EDF4D155F08A2006B566F /* SPIndexProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDF02155F089F006B566F /* SPIndexProcessor.m */; }; 268EDF50155F08A2006B566F /* SPManagedObject.h in Headers */ = {isa = PBXBuildFile; fileRef = 268EDF05155F089F006B566F /* SPManagedObject.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -62,8 +58,6 @@ 268EDF63155F08A2006B566F /* SPMemberList.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDF18155F08A1006B566F /* SPMemberList.m */; }; 268EDF64155F08A2006B566F /* SPMemberText.h in Headers */ = {isa = PBXBuildFile; fileRef = 268EDF19155F08A1006B566F /* SPMemberText.h */; }; 268EDF65155F08A2006B566F /* SPMemberText.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDF1A155F08A1006B566F /* SPMemberText.m */; }; - 268EDF66155F08A2006B566F /* SPNetworkManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 268EDF1B155F08A1006B566F /* SPNetworkManager.h */; }; - 268EDF67155F08A2006B566F /* SPNetworkManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDF1C155F08A1006B566F /* SPNetworkManager.m */; }; 268EDF6C155F08A2006B566F /* SPS3Manager.h in Headers */ = {isa = PBXBuildFile; fileRef = 268EDF21155F08A1006B566F /* SPS3Manager.h */; }; 268EDF70155F08A2006B566F /* SPStorage.h in Headers */ = {isa = PBXBuildFile; fileRef = 268EDF25155F08A2006B566F /* SPStorage.h */; }; 268EDF71155F08A2006B566F /* SPStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDF26155F08A2006B566F /* SPStorage.m */; }; @@ -77,55 +71,53 @@ 268EDF81155F3683006B566F /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 268EDF80155F3683006B566F /* SystemConfiguration.framework */; }; 268EDFC8155F36B7006B566F /* ASICacheDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 268EDF86155F36B7006B566F /* ASICacheDelegate.h */; }; 268EDFC9155F36B7006B566F /* ASIDataCompressor.h in Headers */ = {isa = PBXBuildFile; fileRef = 268EDF87155F36B7006B566F /* ASIDataCompressor.h */; }; - 268EDFCA155F36B7006B566F /* ASIDataCompressor.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDF88155F36B7006B566F /* ASIDataCompressor.m */; }; + 268EDFCA155F36B7006B566F /* ASIDataCompressor.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDF88155F36B7006B566F /* ASIDataCompressor.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 268EDFCB155F36B7006B566F /* ASIDataDecompressor.h in Headers */ = {isa = PBXBuildFile; fileRef = 268EDF89155F36B7006B566F /* ASIDataDecompressor.h */; }; - 268EDFCC155F36B7006B566F /* ASIDataDecompressor.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDF8A155F36B7006B566F /* ASIDataDecompressor.m */; }; + 268EDFCC155F36B7006B566F /* ASIDataDecompressor.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDF8A155F36B7006B566F /* ASIDataDecompressor.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 268EDFCD155F36B7006B566F /* ASIDownloadCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 268EDF8B155F36B7006B566F /* ASIDownloadCache.h */; }; - 268EDFCE155F36B7006B566F /* ASIDownloadCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDF8C155F36B7006B566F /* ASIDownloadCache.m */; }; + 268EDFCE155F36B7006B566F /* ASIDownloadCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDF8C155F36B7006B566F /* ASIDownloadCache.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 268EDFCF155F36B7006B566F /* ASIFormDataRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 268EDF8D155F36B7006B566F /* ASIFormDataRequest.h */; }; - 268EDFD0155F36B7006B566F /* ASIFormDataRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDF8E155F36B7006B566F /* ASIFormDataRequest.m */; }; + 268EDFD0155F36B7006B566F /* ASIFormDataRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDF8E155F36B7006B566F /* ASIFormDataRequest.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 268EDFD1155F36B7006B566F /* ASIHTTPRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 268EDF8F155F36B7006B566F /* ASIHTTPRequest.h */; }; - 268EDFD2155F36B7006B566F /* ASIHTTPRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDF90155F36B7006B566F /* ASIHTTPRequest.m */; }; + 268EDFD2155F36B7006B566F /* ASIHTTPRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDF90155F36B7006B566F /* ASIHTTPRequest.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 268EDFD3155F36B7006B566F /* ASIHTTPRequestConfig.h in Headers */ = {isa = PBXBuildFile; fileRef = 268EDF91155F36B7006B566F /* ASIHTTPRequestConfig.h */; }; 268EDFD4155F36B7006B566F /* ASIHTTPRequestDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 268EDF92155F36B7006B566F /* ASIHTTPRequestDelegate.h */; }; 268EDFD5155F36B7006B566F /* ASIInputStream.h in Headers */ = {isa = PBXBuildFile; fileRef = 268EDF93155F36B7006B566F /* ASIInputStream.h */; }; - 268EDFD6155F36B7006B566F /* ASIInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDF94155F36B7006B566F /* ASIInputStream.m */; }; + 268EDFD6155F36B7006B566F /* ASIInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDF94155F36B7006B566F /* ASIInputStream.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 268EDFD7155F36B7006B566F /* ASINetworkQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = 268EDF95155F36B7006B566F /* ASINetworkQueue.h */; }; - 268EDFD8155F36B7006B566F /* ASINetworkQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDF96155F36B7006B566F /* ASINetworkQueue.m */; }; + 268EDFD8155F36B7006B566F /* ASINetworkQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDF96155F36B7006B566F /* ASINetworkQueue.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 268EDFD9155F36B7006B566F /* ASIProgressDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 268EDF97155F36B7006B566F /* ASIProgressDelegate.h */; }; 268EDFDD155F36B7006B566F /* DiffMatchPatch.h in Headers */ = {isa = PBXBuildFile; fileRef = 268EDF9E155F36B7006B566F /* DiffMatchPatch.h */; }; - 268EDFDE155F36B7006B566F /* DiffMatchPatch.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDF9F155F36B7006B566F /* DiffMatchPatch.m */; }; - 268EDFDF155F36B7006B566F /* DiffMatchPatchCFUtilities.c in Sources */ = {isa = PBXBuildFile; fileRef = 268EDFA0155F36B7006B566F /* DiffMatchPatchCFUtilities.c */; }; + 268EDFDE155F36B7006B566F /* DiffMatchPatch.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDF9F155F36B7006B566F /* DiffMatchPatch.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 268EDFDF155F36B7006B566F /* DiffMatchPatchCFUtilities.c in Sources */ = {isa = PBXBuildFile; fileRef = 268EDFA0155F36B7006B566F /* DiffMatchPatchCFUtilities.c */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 268EDFE0155F36B7006B566F /* DiffMatchPatchCFUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 268EDFA1155F36B7006B566F /* DiffMatchPatchCFUtilities.h */; }; 268EDFE1155F36B7006B566F /* MinMaxMacros.h in Headers */ = {isa = PBXBuildFile; fileRef = 268EDFA2155F36B7006B566F /* MinMaxMacros.h */; }; 268EDFE2155F36B7006B566F /* NSMutableDictionary+DMPExtensions.h in Headers */ = {isa = PBXBuildFile; fileRef = 268EDFA3155F36B7006B566F /* NSMutableDictionary+DMPExtensions.h */; }; - 268EDFE3155F36B7006B566F /* NSMutableDictionary+DMPExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDFA4155F36B7006B566F /* NSMutableDictionary+DMPExtensions.m */; }; + 268EDFE3155F36B7006B566F /* NSMutableDictionary+DMPExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDFA4155F36B7006B566F /* NSMutableDictionary+DMPExtensions.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 268EDFE4155F36B7006B566F /* NSString+JavaSubstring.h in Headers */ = {isa = PBXBuildFile; fileRef = 268EDFA5155F36B7006B566F /* NSString+JavaSubstring.h */; }; - 268EDFE5155F36B7006B566F /* NSString+JavaSubstring.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDFA6155F36B7006B566F /* NSString+JavaSubstring.m */; }; + 268EDFE5155F36B7006B566F /* NSString+JavaSubstring.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDFA6155F36B7006B566F /* NSString+JavaSubstring.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 268EDFE6155F36B7006B566F /* NSString+UnicharUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 268EDFA7155F36B7006B566F /* NSString+UnicharUtilities.h */; }; - 268EDFE7155F36B7006B566F /* NSString+UnicharUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDFA8155F36B7006B566F /* NSString+UnicharUtilities.m */; }; + 268EDFE7155F36B7006B566F /* NSString+UnicharUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDFA8155F36B7006B566F /* NSString+UnicharUtilities.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 268EDFE8155F36B7006B566F /* NSString+UriCompatibility.h in Headers */ = {isa = PBXBuildFile; fileRef = 268EDFA9155F36B7006B566F /* NSString+UriCompatibility.h */; }; - 268EDFE9155F36B7006B566F /* NSString+UriCompatibility.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDFAA155F36B7006B566F /* NSString+UriCompatibility.m */; }; + 268EDFE9155F36B7006B566F /* NSString+UriCompatibility.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDFAA155F36B7006B566F /* NSString+UriCompatibility.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 268EDFEA155F36B7006B566F /* JSONKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 268EDFAC155F36B7006B566F /* JSONKit.h */; }; - 268EDFEB155F36B7006B566F /* JSONKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDFAD155F36B7006B566F /* JSONKit.m */; }; + 268EDFEB155F36B7006B566F /* JSONKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDFAD155F36B7006B566F /* JSONKit.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 268EDFEC155F36B7006B566F /* DDAbstractDatabaseLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = 268EDFAF155F36B7006B566F /* DDAbstractDatabaseLogger.h */; }; - 268EDFED155F36B7006B566F /* DDAbstractDatabaseLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDFB0155F36B7006B566F /* DDAbstractDatabaseLogger.m */; settings = {COMPILER_FLAGS = "-fobjc-arc"; }; }; + 268EDFED155F36B7006B566F /* DDAbstractDatabaseLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDFB0155F36B7006B566F /* DDAbstractDatabaseLogger.m */; }; 268EDFEE155F36B7006B566F /* DDASLLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = 268EDFB1155F36B7006B566F /* DDASLLogger.h */; }; - 268EDFEF155F36B7006B566F /* DDASLLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDFB2155F36B7006B566F /* DDASLLogger.m */; settings = {COMPILER_FLAGS = "-fobjc-arc"; }; }; + 268EDFEF155F36B7006B566F /* DDASLLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDFB2155F36B7006B566F /* DDASLLogger.m */; }; 268EDFF0155F36B7006B566F /* DDFileLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = 268EDFB3155F36B7006B566F /* DDFileLogger.h */; }; - 268EDFF1155F36B7006B566F /* DDFileLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDFB4155F36B7006B566F /* DDFileLogger.m */; settings = {COMPILER_FLAGS = "-fobjc-arc"; }; }; + 268EDFF1155F36B7006B566F /* DDFileLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDFB4155F36B7006B566F /* DDFileLogger.m */; }; 268EDFF2155F36B7006B566F /* DDLog.h in Headers */ = {isa = PBXBuildFile; fileRef = 268EDFB5155F36B7006B566F /* DDLog.h */; }; - 268EDFF3155F36B7006B566F /* DDLog.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDFB6155F36B7006B566F /* DDLog.m */; settings = {COMPILER_FLAGS = "-fobjc-arc"; }; }; + 268EDFF3155F36B7006B566F /* DDLog.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDFB6155F36B7006B566F /* DDLog.m */; }; 268EDFF4155F36B7006B566F /* DDTTYLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = 268EDFB7155F36B7006B566F /* DDTTYLogger.h */; }; - 268EDFF5155F36B7006B566F /* DDTTYLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDFB8155F36B7006B566F /* DDTTYLogger.m */; settings = {COMPILER_FLAGS = "-fobjc-arc"; }; }; + 268EDFF5155F36B7006B566F /* DDTTYLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDFB8155F36B7006B566F /* DDTTYLogger.m */; }; 268EDFF6155F36B7006B566F /* ContextFilterLogFormatter.h in Headers */ = {isa = PBXBuildFile; fileRef = 268EDFBA155F36B7006B566F /* ContextFilterLogFormatter.h */; }; - 268EDFF7155F36B7006B566F /* ContextFilterLogFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDFBB155F36B7006B566F /* ContextFilterLogFormatter.m */; settings = {COMPILER_FLAGS = "-fobjc-arc"; }; }; + 268EDFF7155F36B7006B566F /* ContextFilterLogFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDFBB155F36B7006B566F /* ContextFilterLogFormatter.m */; }; 268EDFF8155F36B7006B566F /* DispatchQueueLogFormatter.h in Headers */ = {isa = PBXBuildFile; fileRef = 268EDFBC155F36B7006B566F /* DispatchQueueLogFormatter.h */; }; - 268EDFF9155F36B7006B566F /* DispatchQueueLogFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDFBD155F36B7006B566F /* DispatchQueueLogFormatter.m */; settings = {COMPILER_FLAGS = "-fobjc-arc"; }; }; + 268EDFF9155F36B7006B566F /* DispatchQueueLogFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 268EDFBD155F36B7006B566F /* DispatchQueueLogFormatter.m */; }; 268EDFFA155F36B7006B566F /* README.txt in Resources */ = {isa = PBXBuildFile; fileRef = 268EDFBE155F36B7006B566F /* README.txt */; }; 268EE001155F37CA006B566F /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 268EDED1155F06D5006B566F /* Foundation.framework */; }; - 2693F85B15C9A57C00797A5E /* SPSimpleKeyChain.h in Headers */ = {isa = PBXBuildFile; fileRef = 2693F85915C9A57B00797A5E /* SPSimpleKeyChain.h */; }; - 2693F85C15C9A57C00797A5E /* SPSimpleKeyChain.m in Sources */ = {isa = PBXBuildFile; fileRef = 2693F85A15C9A57B00797A5E /* SPSimpleKeyChain.m */; }; 2693F85F15C9A7BE00797A5E /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2693F85E15C9A7BE00797A5E /* Security.framework */; }; 2698BE4515C908C80020565F /* NSMutableDictionary+Simperium.h in Headers */ = {isa = PBXBuildFile; fileRef = 2698BE3715C908C60020565F /* NSMutableDictionary+Simperium.h */; }; 2698BE4615C908C80020565F /* NSMutableDictionary+Simperium.m in Sources */ = {isa = PBXBuildFile; fileRef = 2698BE3815C908C60020565F /* NSMutableDictionary+Simperium.m */; }; @@ -142,33 +134,48 @@ 2698BE5115C908C80020565F /* SPSchema.m in Sources */ = {isa = PBXBuildFile; fileRef = 2698BE4315C908C70020565F /* SPSchema.m */; }; 2698BE5215C908C80020565F /* SPStorageProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = 2698BE4415C908C70020565F /* SPStorageProvider.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2698BE5615C9097A0020565F /* JRSwizzle.h in Headers */ = {isa = PBXBuildFile; fileRef = 2698BE5415C9097A0020565F /* JRSwizzle.h */; }; - 2698BE5715C9097A0020565F /* JRSwizzle.m in Sources */ = {isa = PBXBuildFile; fileRef = 2698BE5515C9097A0020565F /* JRSwizzle.m */; }; - 26C3E1491666FD7600620306 /* Reachability.h in Headers */ = {isa = PBXBuildFile; fileRef = 26C3E1471666FD7600620306 /* Reachability.h */; }; - 26C3E14A1666FD7600620306 /* Reachability.m in Sources */ = {isa = PBXBuildFile; fileRef = 26C3E1481666FD7600620306 /* Reachability.m */; }; - 26C3E1511666FE7500620306 /* SPReferenceManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 26C3E14B1666FE7400620306 /* SPReferenceManager.h */; }; - 26C3E1521666FE7500620306 /* SPReferenceManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 26C3E14C1666FE7400620306 /* SPReferenceManager.m */; }; + 2698BE5715C9097A0020565F /* JRSwizzle.m in Sources */ = {isa = PBXBuildFile; fileRef = 2698BE5515C9097A0020565F /* JRSwizzle.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 26C3E1531666FE7500620306 /* SPWebSocketChannel.h in Headers */ = {isa = PBXBuildFile; fileRef = 26C3E14D1666FE7500620306 /* SPWebSocketChannel.h */; }; 26C3E1541666FE7500620306 /* SPWebSocketChannel.m in Sources */ = {isa = PBXBuildFile; fileRef = 26C3E14E1666FE7500620306 /* SPWebSocketChannel.m */; }; - 26C3E1551666FE7500620306 /* SPWebSocketManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 26C3E14F1666FE7500620306 /* SPWebSocketManager.h */; }; - 26C3E1561666FE7500620306 /* SPWebSocketManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 26C3E1501666FE7500620306 /* SPWebSocketManager.m */; }; - 26C3E15F1666FE9300620306 /* base64.c in Sources */ = {isa = PBXBuildFile; fileRef = 26C3E1581666FE9300620306 /* base64.c */; }; - 26C3E1601666FE9300620306 /* base64.h in Headers */ = {isa = PBXBuildFile; fileRef = 26C3E1591666FE9300620306 /* base64.h */; }; - 26C3E1611666FE9300620306 /* NSData+SRB64Additions.h in Headers */ = {isa = PBXBuildFile; fileRef = 26C3E15A1666FE9300620306 /* NSData+SRB64Additions.h */; }; - 26C3E1621666FE9300620306 /* NSData+SRB64Additions.m in Sources */ = {isa = PBXBuildFile; fileRef = 26C3E15B1666FE9300620306 /* NSData+SRB64Additions.m */; }; - 26C3E1631666FE9300620306 /* SocketRocket-Prefix.pch in Headers */ = {isa = PBXBuildFile; fileRef = 26C3E15C1666FE9300620306 /* SocketRocket-Prefix.pch */; }; - 26C3E1641666FE9300620306 /* SRWebSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 26C3E15D1666FE9300620306 /* SRWebSocket.h */; }; - 26C3E1651666FE9300620306 /* SRWebSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 26C3E15E1666FE9300620306 /* SRWebSocket.m */; }; - 26F1C6EA1562E297002172D5 /* AuthWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 26F1C6E91562E297002172D5 /* AuthWindow.xib */; }; - 26F1C6F61562E75E002172D5 /* SPAuthView.h in Headers */ = {isa = PBXBuildFile; fileRef = 26F1C6EC1562E75E002172D5 /* SPAuthView.h */; }; - 26F1C6F71562E75E002172D5 /* SPAuthView.m in Sources */ = {isa = PBXBuildFile; fileRef = 26F1C6ED1562E75E002172D5 /* SPAuthView.m */; }; - 26F1C6F81562E75E002172D5 /* SPInputBoxView.h in Headers */ = {isa = PBXBuildFile; fileRef = 26F1C6EE1562E75E002172D5 /* SPInputBoxView.h */; }; - 26F1C6F91562E75E002172D5 /* SPInputBoxView.m in Sources */ = {isa = PBXBuildFile; fileRef = 26F1C6EF1562E75E002172D5 /* SPInputBoxView.m */; }; - 26F1C6FA1562E75E002172D5 /* SPSigninButtonCell.h in Headers */ = {isa = PBXBuildFile; fileRef = 26F1C6F01562E75E002172D5 /* SPSigninButtonCell.h */; }; - 26F1C6FB1562E75E002172D5 /* SPSigninButtonCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 26F1C6F11562E75E002172D5 /* SPSigninButtonCell.m */; }; - 26F1C6FC1562E75E002172D5 /* SPSpotLightView.h in Headers */ = {isa = PBXBuildFile; fileRef = 26F1C6F21562E75E002172D5 /* SPSpotLightView.h */; }; - 26F1C6FD1562E75E002172D5 /* SPSpotLightView.m in Sources */ = {isa = PBXBuildFile; fileRef = 26F1C6F31562E75E002172D5 /* SPSpotLightView.m */; }; - 26F1C6FE1562E75E002172D5 /* SPToggleAuthCell.h in Headers */ = {isa = PBXBuildFile; fileRef = 26F1C6F41562E75E002172D5 /* SPToggleAuthCell.h */; }; - 26F1C6FF1562E75E002172D5 /* SPToggleAuthCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 26F1C6F51562E75E002172D5 /* SPToggleAuthCell.m */; }; + 460A505B17DE95B20070F02B /* SPProcessorNotificationNames.m in Sources */ = {isa = PBXBuildFile; fileRef = 460A505A17DE95B20070F02B /* SPProcessorNotificationNames.m */; }; + 4617AEBB179A1A3400545BD9 /* SPReachability.h in Headers */ = {isa = PBXBuildFile; fileRef = 4617AEB9179A1A3400545BD9 /* SPReachability.h */; }; + 4617AEBC179A1A3400545BD9 /* SPReachability.m in Sources */ = {isa = PBXBuildFile; fileRef = 4617AEBA179A1A3400545BD9 /* SPReachability.m */; }; + 46263BB117A7778E00131791 /* SPAuthenticationConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = 46263BAF17A7778E00131791 /* SPAuthenticationConfiguration.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 46263BB217A7778E00131791 /* SPAuthenticationConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = 46263BB017A7778E00131791 /* SPAuthenticationConfiguration.m */; }; + 463774E5171FC6D500E2E333 /* base64.c in Sources */ = {isa = PBXBuildFile; fileRef = 463774DE171FC6D500E2E333 /* base64.c */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 463774E6171FC6D500E2E333 /* base64.h in Headers */ = {isa = PBXBuildFile; fileRef = 463774DF171FC6D500E2E333 /* base64.h */; }; + 463774E7171FC6D500E2E333 /* NSData+SRB64Additions.h in Headers */ = {isa = PBXBuildFile; fileRef = 463774E0171FC6D500E2E333 /* NSData+SRB64Additions.h */; }; + 463774E8171FC6D500E2E333 /* NSData+SRB64Additions.m in Sources */ = {isa = PBXBuildFile; fileRef = 463774E1171FC6D500E2E333 /* NSData+SRB64Additions.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 463774E9171FC6D500E2E333 /* SocketRocket-Prefix.pch in Headers */ = {isa = PBXBuildFile; fileRef = 463774E2171FC6D500E2E333 /* SocketRocket-Prefix.pch */; }; + 463774EA171FC6D500E2E333 /* SRWebSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 463774E3171FC6D500E2E333 /* SRWebSocket.h */; }; + 463774EB171FC6D500E2E333 /* SRWebSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 463774E4171FC6D500E2E333 /* SRWebSocket.m */; }; + 466EB40D17BC194D005F7599 /* SPAuthenticationValidator.h in Headers */ = {isa = PBXBuildFile; fileRef = 466EB40B17BC194D005F7599 /* SPAuthenticationValidator.h */; }; + 466EB40E17BC194D005F7599 /* SPAuthenticationValidator.m in Sources */ = {isa = PBXBuildFile; fileRef = 466EB40C17BC194D005F7599 /* SPAuthenticationValidator.m */; }; + 466FFEA217CBEE7A00399652 /* SPNetworkInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = 466FFEA117CBEE7A00399652 /* SPNetworkInterface.m */; }; + 46A691FF17387ABF0080399F /* SFHFKeychainUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 46A691FD17387ABE0080399F /* SFHFKeychainUtils.h */; }; + 46A6920017387ABF0080399F /* SFHFKeychainUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 46A691FE17387ABE0080399F /* SFHFKeychainUtils.m */; }; + 46D5BC41174C2A9B0027DBAF /* SPRelationshipResolver.h in Headers */ = {isa = PBXBuildFile; fileRef = 46D5BC3F174C2A9B0027DBAF /* SPRelationshipResolver.h */; }; + 46D5BC42174C2A9B0027DBAF /* SPRelationshipResolver.m in Sources */ = {isa = PBXBuildFile; fileRef = 46D5BC40174C2A9B0027DBAF /* SPRelationshipResolver.m */; }; + 46DD15C6179A6DAC0093678C /* SPMemberJSONList.h in Headers */ = {isa = PBXBuildFile; fileRef = 46DD15C4179A6DAC0093678C /* SPMemberJSONList.h */; }; + 46DD15C7179A6DAC0093678C /* SPMemberJSONList.m in Sources */ = {isa = PBXBuildFile; fileRef = 46DD15C5179A6DAC0093678C /* SPMemberJSONList.m */; }; + 46DD161F17A1B8490093678C /* SPAuthenticationButton.h in Headers */ = {isa = PBXBuildFile; fileRef = 46DD161317A1B8480093678C /* SPAuthenticationButton.h */; }; + 46DD162017A1B8490093678C /* SPAuthenticationButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 46DD161417A1B8480093678C /* SPAuthenticationButton.m */; }; + 46DD162117A1B8490093678C /* SPAuthenticationButtonCell.h in Headers */ = {isa = PBXBuildFile; fileRef = 46DD161517A1B8480093678C /* SPAuthenticationButtonCell.h */; }; + 46DD162217A1B8490093678C /* SPAuthenticationButtonCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 46DD161617A1B8480093678C /* SPAuthenticationButtonCell.m */; }; + 46DD162317A1B8490093678C /* SPAuthenticationTextField.h in Headers */ = {isa = PBXBuildFile; fileRef = 46DD161717A1B8480093678C /* SPAuthenticationTextField.h */; }; + 46DD162417A1B8490093678C /* SPAuthenticationTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = 46DD161817A1B8480093678C /* SPAuthenticationTextField.m */; }; + 46DD162517A1B8490093678C /* SPAuthenticationView.h in Headers */ = {isa = PBXBuildFile; fileRef = 46DD161917A1B8480093678C /* SPAuthenticationView.h */; }; + 46DD162617A1B8490093678C /* SPAuthenticationView.m in Sources */ = {isa = PBXBuildFile; fileRef = 46DD161A17A1B8480093678C /* SPAuthenticationView.m */; }; + 46DD162717A1B8490093678C /* SPAuthenticationWindow.h in Headers */ = {isa = PBXBuildFile; fileRef = 46DD161B17A1B8480093678C /* SPAuthenticationWindow.h */; }; + 46DD162817A1B8490093678C /* SPAuthenticationWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 46DD161C17A1B8480093678C /* SPAuthenticationWindow.m */; }; + 46DD162917A1B8490093678C /* SPAuthenticationWindowController.h in Headers */ = {isa = PBXBuildFile; fileRef = 46DD161D17A1B8480093678C /* SPAuthenticationWindowController.h */; }; + 46DD162A17A1B8490093678C /* SPAuthenticationWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 46DD161E17A1B8490093678C /* SPAuthenticationWindowController.m */; }; + 46DD162C17A1B9660093678C /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46DD162B17A1B9660093678C /* QuartzCore.framework */; }; + 46EA1AEB17569EA0009AC5AA /* SPNetworkInterface.h in Headers */ = {isa = PBXBuildFile; fileRef = 46EA1AEA17569EA0009AC5AA /* SPNetworkInterface.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 46EA1AEE17569EB4009AC5AA /* SPWebSocketInterface.h in Headers */ = {isa = PBXBuildFile; fileRef = 46EA1AEC17569EB4009AC5AA /* SPWebSocketInterface.h */; }; + 46EA1AEF17569EB4009AC5AA /* SPWebSocketInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = 46EA1AED17569EB4009AC5AA /* SPWebSocketInterface.m */; }; + 46EA1AF217569EC7009AC5AA /* SPHttpInterface.h in Headers */ = {isa = PBXBuildFile; fileRef = 46EA1AF017569EC7009AC5AA /* SPHttpInterface.h */; }; + 46EA1AF317569EC7009AC5AA /* SPHttpInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = 46EA1AF117569EC7009AC5AA /* SPHttpInterface.m */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -190,10 +197,8 @@ 268EDEE8155F089C006B566F /* Simperium-complete.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "Simperium-complete.h"; path = "../../Simperium/Simperium-complete.h"; sourceTree = ""; }; 268EDEEA155F089D006B566F /* Simperium.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Simperium.h; path = ../../Simperium/Simperium.h; sourceTree = ""; }; 268EDEEB155F089D006B566F /* Simperium.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Simperium.m; path = ../../Simperium/Simperium.m; sourceTree = ""; }; - 268EDEEC155F089D006B566F /* SPAuthenticationManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPAuthenticationManager.h; path = ../../Simperium/SPAuthenticationManager.h; sourceTree = ""; }; - 268EDEED155F089D006B566F /* SPAuthenticationManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SPAuthenticationManager.m; path = ../../Simperium/SPAuthenticationManager.m; sourceTree = ""; }; - 268EDEEE155F089D006B566F /* SPAuthWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPAuthWindowController.h; path = ../../Simperium/SPAuthWindowController.h; sourceTree = ""; }; - 268EDEEF155F089D006B566F /* SPAuthWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SPAuthWindowController.m; path = ../../Simperium/SPAuthWindowController.m; sourceTree = ""; }; + 268EDEEC155F089D006B566F /* SPAuthenticator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPAuthenticator.h; path = ../../Simperium/SPAuthenticator.h; sourceTree = ""; }; + 268EDEED155F089D006B566F /* SPAuthenticator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SPAuthenticator.m; path = ../../Simperium/SPAuthenticator.m; sourceTree = ""; }; 268EDEF0155F089D006B566F /* SPBinaryManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPBinaryManager.h; path = ../../Simperium/SPBinaryManager.h; sourceTree = ""; }; 268EDEF1155F089D006B566F /* SPBinaryManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SPBinaryManager.m; path = ../../Simperium/SPBinaryManager.m; sourceTree = ""; }; 268EDEF2155F089D006B566F /* SPBinaryTransportDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPBinaryTransportDelegate.h; path = ../../Simperium/SPBinaryTransportDelegate.h; sourceTree = ""; }; @@ -207,8 +212,6 @@ 268EDEFC155F089E006B566F /* SPEnvironment.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SPEnvironment.m; path = ../../Simperium/SPEnvironment.m; sourceTree = ""; }; 268EDEFD155F089E006B566F /* SPGhost.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPGhost.h; path = ../../Simperium/SPGhost.h; sourceTree = ""; }; 268EDEFE155F089E006B566F /* SPGhost.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SPGhost.m; path = ../../Simperium/SPGhost.m; sourceTree = ""; }; - 268EDEFF155F089E006B566F /* SPHttpManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPHttpManager.h; path = ../../Simperium/SPHttpManager.h; sourceTree = ""; }; - 268EDF00155F089E006B566F /* SPHttpManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SPHttpManager.m; path = ../../Simperium/SPHttpManager.m; sourceTree = ""; }; 268EDF01155F089E006B566F /* SPIndexProcessor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPIndexProcessor.h; path = ../../Simperium/SPIndexProcessor.h; sourceTree = ""; }; 268EDF02155F089F006B566F /* SPIndexProcessor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SPIndexProcessor.m; path = ../../Simperium/SPIndexProcessor.m; sourceTree = ""; }; 268EDF05155F089F006B566F /* SPManagedObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPManagedObject.h; path = ../../Simperium/SPManagedObject.h; sourceTree = ""; }; @@ -233,8 +236,6 @@ 268EDF18155F08A1006B566F /* SPMemberList.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SPMemberList.m; path = ../../Simperium/SPMemberList.m; sourceTree = ""; }; 268EDF19155F08A1006B566F /* SPMemberText.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPMemberText.h; path = ../../Simperium/SPMemberText.h; sourceTree = ""; }; 268EDF1A155F08A1006B566F /* SPMemberText.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SPMemberText.m; path = ../../Simperium/SPMemberText.m; sourceTree = ""; }; - 268EDF1B155F08A1006B566F /* SPNetworkManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPNetworkManager.h; path = ../../Simperium/SPNetworkManager.h; sourceTree = ""; }; - 268EDF1C155F08A1006B566F /* SPNetworkManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SPNetworkManager.m; path = ../../Simperium/SPNetworkManager.m; sourceTree = ""; }; 268EDF21155F08A1006B566F /* SPS3Manager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPS3Manager.h; path = ../../Simperium/SPS3Manager.h; sourceTree = ""; }; 268EDF22155F08A1006B566F /* SPS3Manager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SPS3Manager.m; path = ../../Simperium/SPS3Manager.m; sourceTree = ""; }; 268EDF25155F08A2006B566F /* SPStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPStorage.h; path = ../../Simperium/SPStorage.h; sourceTree = ""; }; @@ -293,8 +294,6 @@ 268EDFBC155F36B7006B566F /* DispatchQueueLogFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DispatchQueueLogFormatter.h; sourceTree = ""; }; 268EDFBD155F36B7006B566F /* DispatchQueueLogFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DispatchQueueLogFormatter.m; sourceTree = ""; }; 268EDFBE155F36B7006B566F /* README.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = README.txt; sourceTree = ""; }; - 2693F85915C9A57B00797A5E /* SPSimpleKeyChain.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPSimpleKeyChain.h; path = ../../Simperium/SPSimpleKeyChain.h; sourceTree = ""; }; - 2693F85A15C9A57B00797A5E /* SPSimpleKeyChain.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SPSimpleKeyChain.m; path = ../../Simperium/SPSimpleKeyChain.m; sourceTree = ""; }; 2693F85E15C9A7BE00797A5E /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; 2698BE3715C908C60020565F /* NSMutableDictionary+Simperium.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSMutableDictionary+Simperium.h"; path = "../../Simperium/NSMutableDictionary+Simperium.h"; sourceTree = ""; }; 2698BE3815C908C60020565F /* NSMutableDictionary+Simperium.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSMutableDictionary+Simperium.m"; path = "../../Simperium/NSMutableDictionary+Simperium.m"; sourceTree = ""; }; @@ -312,32 +311,48 @@ 2698BE4415C908C70020565F /* SPStorageProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPStorageProvider.h; path = ../../Simperium/SPStorageProvider.h; sourceTree = ""; }; 2698BE5415C9097A0020565F /* JRSwizzle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JRSwizzle.h; sourceTree = ""; }; 2698BE5515C9097A0020565F /* JRSwizzle.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JRSwizzle.m; sourceTree = ""; }; - 26C3E1471666FD7600620306 /* Reachability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Reachability.h; sourceTree = ""; }; - 26C3E1481666FD7600620306 /* Reachability.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Reachability.m; sourceTree = ""; }; - 26C3E14B1666FE7400620306 /* SPReferenceManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPReferenceManager.h; path = ../../Simperium/SPReferenceManager.h; sourceTree = ""; }; - 26C3E14C1666FE7400620306 /* SPReferenceManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SPReferenceManager.m; path = ../../Simperium/SPReferenceManager.m; sourceTree = ""; }; 26C3E14D1666FE7500620306 /* SPWebSocketChannel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPWebSocketChannel.h; path = ../../Simperium/SPWebSocketChannel.h; sourceTree = ""; }; 26C3E14E1666FE7500620306 /* SPWebSocketChannel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SPWebSocketChannel.m; path = ../../Simperium/SPWebSocketChannel.m; sourceTree = ""; }; - 26C3E14F1666FE7500620306 /* SPWebSocketManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPWebSocketManager.h; path = ../../Simperium/SPWebSocketManager.h; sourceTree = ""; }; - 26C3E1501666FE7500620306 /* SPWebSocketManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SPWebSocketManager.m; path = ../../Simperium/SPWebSocketManager.m; sourceTree = ""; }; - 26C3E1581666FE9300620306 /* base64.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = base64.c; sourceTree = ""; }; - 26C3E1591666FE9300620306 /* base64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = base64.h; sourceTree = ""; }; - 26C3E15A1666FE9300620306 /* NSData+SRB64Additions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+SRB64Additions.h"; sourceTree = ""; }; - 26C3E15B1666FE9300620306 /* NSData+SRB64Additions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData+SRB64Additions.m"; sourceTree = ""; }; - 26C3E15C1666FE9300620306 /* SocketRocket-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SocketRocket-Prefix.pch"; sourceTree = ""; }; - 26C3E15D1666FE9300620306 /* SRWebSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRWebSocket.h; sourceTree = ""; }; - 26C3E15E1666FE9300620306 /* SRWebSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRWebSocket.m; sourceTree = ""; }; - 26F1C6E91562E297002172D5 /* AuthWindow.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = AuthWindow.xib; path = ../Resources/AuthWindow.xib; sourceTree = ""; }; - 26F1C6EC1562E75E002172D5 /* SPAuthView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPAuthView.h; sourceTree = ""; }; - 26F1C6ED1562E75E002172D5 /* SPAuthView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPAuthView.m; sourceTree = ""; }; - 26F1C6EE1562E75E002172D5 /* SPInputBoxView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPInputBoxView.h; sourceTree = ""; }; - 26F1C6EF1562E75E002172D5 /* SPInputBoxView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPInputBoxView.m; sourceTree = ""; }; - 26F1C6F01562E75E002172D5 /* SPSigninButtonCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPSigninButtonCell.h; sourceTree = ""; }; - 26F1C6F11562E75E002172D5 /* SPSigninButtonCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPSigninButtonCell.m; sourceTree = ""; }; - 26F1C6F21562E75E002172D5 /* SPSpotLightView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPSpotLightView.h; sourceTree = ""; }; - 26F1C6F31562E75E002172D5 /* SPSpotLightView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPSpotLightView.m; sourceTree = ""; }; - 26F1C6F41562E75E002172D5 /* SPToggleAuthCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPToggleAuthCell.h; sourceTree = ""; }; - 26F1C6F51562E75E002172D5 /* SPToggleAuthCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPToggleAuthCell.m; sourceTree = ""; }; + 460A505A17DE95B20070F02B /* SPProcessorNotificationNames.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SPProcessorNotificationNames.m; path = ../Simperium/SPProcessorNotificationNames.m; sourceTree = ""; }; + 4617AEB9179A1A3400545BD9 /* SPReachability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPReachability.h; sourceTree = ""; }; + 4617AEBA179A1A3400545BD9 /* SPReachability.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPReachability.m; sourceTree = ""; }; + 46263BAF17A7778E00131791 /* SPAuthenticationConfiguration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPAuthenticationConfiguration.h; path = ../../Simperium/SPAuthenticationConfiguration.h; sourceTree = ""; }; + 46263BB017A7778E00131791 /* SPAuthenticationConfiguration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SPAuthenticationConfiguration.m; path = ../../Simperium/SPAuthenticationConfiguration.m; sourceTree = ""; }; + 463774DE171FC6D500E2E333 /* base64.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = base64.c; sourceTree = ""; }; + 463774DF171FC6D500E2E333 /* base64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = base64.h; sourceTree = ""; }; + 463774E0171FC6D500E2E333 /* NSData+SRB64Additions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+SRB64Additions.h"; sourceTree = ""; }; + 463774E1171FC6D500E2E333 /* NSData+SRB64Additions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData+SRB64Additions.m"; sourceTree = ""; }; + 463774E2171FC6D500E2E333 /* SocketRocket-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SocketRocket-Prefix.pch"; sourceTree = ""; }; + 463774E3171FC6D500E2E333 /* SRWebSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRWebSocket.h; sourceTree = ""; }; + 463774E4171FC6D500E2E333 /* SRWebSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRWebSocket.m; sourceTree = ""; }; + 466EB40B17BC194D005F7599 /* SPAuthenticationValidator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPAuthenticationValidator.h; path = ../../Simperium/SPAuthenticationValidator.h; sourceTree = ""; }; + 466EB40C17BC194D005F7599 /* SPAuthenticationValidator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SPAuthenticationValidator.m; path = ../../Simperium/SPAuthenticationValidator.m; sourceTree = ""; }; + 466FFEA117CBEE7A00399652 /* SPNetworkInterface.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SPNetworkInterface.m; path = ../../Simperium/SPNetworkInterface.m; sourceTree = ""; }; + 46A691FD17387ABE0080399F /* SFHFKeychainUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SFHFKeychainUtils.h; path = ../External/SFHFKeychainUtils/SFHFKeychainUtils.h; sourceTree = ""; }; + 46A691FE17387ABE0080399F /* SFHFKeychainUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SFHFKeychainUtils.m; path = ../External/SFHFKeychainUtils/SFHFKeychainUtils.m; sourceTree = ""; }; + 46D5BC3F174C2A9B0027DBAF /* SPRelationshipResolver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPRelationshipResolver.h; path = ../../Simperium/SPRelationshipResolver.h; sourceTree = ""; }; + 46D5BC40174C2A9B0027DBAF /* SPRelationshipResolver.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SPRelationshipResolver.m; path = ../../Simperium/SPRelationshipResolver.m; sourceTree = ""; }; + 46DD15C4179A6DAC0093678C /* SPMemberJSONList.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPMemberJSONList.h; path = ../../Simperium/SPMemberJSONList.h; sourceTree = ""; }; + 46DD15C5179A6DAC0093678C /* SPMemberJSONList.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SPMemberJSONList.m; path = ../../Simperium/SPMemberJSONList.m; sourceTree = ""; }; + 46DD161317A1B8480093678C /* SPAuthenticationButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPAuthenticationButton.h; sourceTree = ""; }; + 46DD161417A1B8480093678C /* SPAuthenticationButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPAuthenticationButton.m; sourceTree = ""; }; + 46DD161517A1B8480093678C /* SPAuthenticationButtonCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPAuthenticationButtonCell.h; sourceTree = ""; }; + 46DD161617A1B8480093678C /* SPAuthenticationButtonCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPAuthenticationButtonCell.m; sourceTree = ""; }; + 46DD161717A1B8480093678C /* SPAuthenticationTextField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPAuthenticationTextField.h; sourceTree = ""; }; + 46DD161817A1B8480093678C /* SPAuthenticationTextField.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPAuthenticationTextField.m; sourceTree = ""; }; + 46DD161917A1B8480093678C /* SPAuthenticationView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPAuthenticationView.h; sourceTree = ""; }; + 46DD161A17A1B8480093678C /* SPAuthenticationView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPAuthenticationView.m; sourceTree = ""; }; + 46DD161B17A1B8480093678C /* SPAuthenticationWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPAuthenticationWindow.h; sourceTree = ""; }; + 46DD161C17A1B8480093678C /* SPAuthenticationWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPAuthenticationWindow.m; sourceTree = ""; }; + 46DD161D17A1B8480093678C /* SPAuthenticationWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPAuthenticationWindowController.h; sourceTree = ""; }; + 46DD161E17A1B8490093678C /* SPAuthenticationWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPAuthenticationWindowController.m; sourceTree = ""; }; + 46DD162B17A1B9660093678C /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; + 46EA1AEA17569EA0009AC5AA /* SPNetworkInterface.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPNetworkInterface.h; path = ../../Simperium/SPNetworkInterface.h; sourceTree = ""; }; + 46EA1AEC17569EB4009AC5AA /* SPWebSocketInterface.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPWebSocketInterface.h; path = ../../Simperium/SPWebSocketInterface.h; sourceTree = ""; }; + 46EA1AED17569EB4009AC5AA /* SPWebSocketInterface.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SPWebSocketInterface.m; path = ../../Simperium/SPWebSocketInterface.m; sourceTree = ""; }; + 46EA1AF017569EC7009AC5AA /* SPHttpInterface.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPHttpInterface.h; path = ../../Simperium/SPHttpInterface.h; sourceTree = ""; }; + 46EA1AF117569EC7009AC5AA /* SPHttpInterface.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SPHttpInterface.m; path = ../../Simperium/SPHttpInterface.m; sourceTree = ""; }; + A2ACCF2F16CD6A0300779D83 /* Simperium-complete.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "Simperium-complete.h"; path = "../Simperium/Simperium-complete.h"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -345,6 +360,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 46DD162C17A1B9660093678C /* QuartzCore.framework in Frameworks */, 2693F85F15C9A7BE00797A5E /* Security.framework in Frameworks */, 268EE001155F37CA006B566F /* Foundation.framework in Frameworks */, 268EDF81155F3683006B566F /* SystemConfiguration.framework in Frameworks */, @@ -362,9 +378,12 @@ 268EDEBD155F06D5006B566F = { isa = PBXGroup; children = ( + 460A505A17DE95B20070F02B /* SPProcessorNotificationNames.m */, + 46DD162B17A1B9660093678C /* QuartzCore.framework */, + 46A691FD17387ABE0080399F /* SFHFKeychainUtils.h */, + 46A691FE17387ABE0080399F /* SFHFKeychainUtils.m */, + A2ACCF2F16CD6A0300779D83 /* Simperium-complete.h */, 268EDF82155F36B7006B566F /* External */, - 268EDF7C155F3674006B566F /* libz.dylib */, - 26F1C6E91562E297002172D5 /* AuthWindow.xib */, 268EDED2155F06D5006B566F /* Simperium-OSX */, 268EDECB155F06D5006B566F /* Frameworks */, 268EDECA155F06D5006B566F /* Products */, @@ -382,6 +401,7 @@ 268EDECB155F06D5006B566F /* Frameworks */ = { isa = PBXGroup; children = ( + 268EDF7C155F3674006B566F /* libz.dylib */, 2693F85E15C9A7BE00797A5E /* Security.framework */, 268EDECF155F06D5006B566F /* AppKit.framework */, 268EDED0155F06D5006B566F /* CoreData.framework */, @@ -396,14 +416,17 @@ 268EDED2155F06D5006B566F /* Simperium-OSX */ = { isa = PBXGroup; children = ( - 26C3E14B1666FE7400620306 /* SPReferenceManager.h */, - 26C3E14C1666FE7400620306 /* SPReferenceManager.m */, + 26F1C7001562E76B002172D5 /* Auth */, + 46EA1AF017569EC7009AC5AA /* SPHttpInterface.h */, + 46EA1AF117569EC7009AC5AA /* SPHttpInterface.m */, + 46EA1AEC17569EB4009AC5AA /* SPWebSocketInterface.h */, + 46EA1AED17569EB4009AC5AA /* SPWebSocketInterface.m */, + 46EA1AEA17569EA0009AC5AA /* SPNetworkInterface.h */, + 466FFEA117CBEE7A00399652 /* SPNetworkInterface.m */, + 46D5BC3F174C2A9B0027DBAF /* SPRelationshipResolver.h */, + 46D5BC40174C2A9B0027DBAF /* SPRelationshipResolver.m */, 26C3E14D1666FE7500620306 /* SPWebSocketChannel.h */, 26C3E14E1666FE7500620306 /* SPWebSocketChannel.m */, - 26C3E14F1666FE7500620306 /* SPWebSocketManager.h */, - 26C3E1501666FE7500620306 /* SPWebSocketManager.m */, - 2693F85915C9A57B00797A5E /* SPSimpleKeyChain.h */, - 2693F85A15C9A57B00797A5E /* SPSimpleKeyChain.m */, 2698BE3715C908C60020565F /* NSMutableDictionary+Simperium.h */, 2698BE3815C908C60020565F /* NSMutableDictionary+Simperium.m */, 2698BE3915C908C60020565F /* SPBucket.h */, @@ -418,7 +441,6 @@ 2698BE4215C908C70020565F /* SPSchema.h */, 2698BE4315C908C70020565F /* SPSchema.m */, 2698BE4415C908C70020565F /* SPStorageProvider.h */, - 26F1C7001562E76B002172D5 /* Auth */, 268EDEE1155F089C006B566F /* DDLogDebug.h */, 268EDEE2155F089C006B566F /* NSData+Simperium.h */, 268EDEE3155F089C006B566F /* NSData+Simperium.m */, @@ -429,10 +451,6 @@ 268EDEE8155F089C006B566F /* Simperium-complete.h */, 268EDEEA155F089D006B566F /* Simperium.h */, 268EDEEB155F089D006B566F /* Simperium.m */, - 268EDEEC155F089D006B566F /* SPAuthenticationManager.h */, - 268EDEED155F089D006B566F /* SPAuthenticationManager.m */, - 268EDEEE155F089D006B566F /* SPAuthWindowController.h */, - 268EDEEF155F089D006B566F /* SPAuthWindowController.m */, 268EDEF0155F089D006B566F /* SPBinaryManager.h */, 268EDEF1155F089D006B566F /* SPBinaryManager.m */, 268EDEF2155F089D006B566F /* SPBinaryTransportDelegate.h */, @@ -446,8 +464,6 @@ 268EDEFC155F089E006B566F /* SPEnvironment.m */, 268EDEFD155F089E006B566F /* SPGhost.h */, 268EDEFE155F089E006B566F /* SPGhost.m */, - 268EDEFF155F089E006B566F /* SPHttpManager.h */, - 268EDF00155F089E006B566F /* SPHttpManager.m */, 268EDF01155F089E006B566F /* SPIndexProcessor.h */, 268EDF02155F089F006B566F /* SPIndexProcessor.m */, 268EDF05155F089F006B566F /* SPManagedObject.h */, @@ -468,12 +484,12 @@ 268EDF14155F08A0006B566F /* SPMemberFloat.m */, 268EDF15155F08A0006B566F /* SPMemberInt.h */, 268EDF16155F08A0006B566F /* SPMemberInt.m */, + 46DD15C4179A6DAC0093678C /* SPMemberJSONList.h */, + 46DD15C5179A6DAC0093678C /* SPMemberJSONList.m */, 268EDF17155F08A0006B566F /* SPMemberList.h */, 268EDF18155F08A1006B566F /* SPMemberList.m */, 268EDF19155F08A1006B566F /* SPMemberText.h */, 268EDF1A155F08A1006B566F /* SPMemberText.m */, - 268EDF1B155F08A1006B566F /* SPNetworkManager.h */, - 268EDF1C155F08A1006B566F /* SPNetworkManager.m */, 268EDF21155F08A1006B566F /* SPS3Manager.h */, 268EDF22155F08A1006B566F /* SPS3Manager.m */, 268EDF25155F08A2006B566F /* SPStorage.h */, @@ -499,8 +515,8 @@ 268EDF82155F36B7006B566F /* External */ = { isa = PBXGroup; children = ( - 26C3E1571666FE9300620306 /* SocketRocket */, - 26C3E1461666FD7600620306 /* Reachability */, + 4617AEB8179A1A3400545BD9 /* SPReachability */, + 463774DD171FC6D500E2E333 /* SocketRocket */, 2698BE5315C9097A0020565F /* jrswizzle */, 268EDF83155F36B7006B566F /* asi-http-request */, 268EDF9D155F36B7006B566F /* diffmatchpatch */, @@ -604,44 +620,52 @@ path = jrswizzle; sourceTree = ""; }; - 26C3E1461666FD7600620306 /* Reachability */ = { + 26F1C7001562E76B002172D5 /* Auth */ = { isa = PBXGroup; children = ( - 26C3E1471666FD7600620306 /* Reachability.h */, - 26C3E1481666FD7600620306 /* Reachability.m */, + 268EDEEC155F089D006B566F /* SPAuthenticator.h */, + 268EDEED155F089D006B566F /* SPAuthenticator.m */, + 46DD161317A1B8480093678C /* SPAuthenticationButton.h */, + 46DD161417A1B8480093678C /* SPAuthenticationButton.m */, + 46DD161517A1B8480093678C /* SPAuthenticationButtonCell.h */, + 46DD161617A1B8480093678C /* SPAuthenticationButtonCell.m */, + 46DD161717A1B8480093678C /* SPAuthenticationTextField.h */, + 46DD161817A1B8480093678C /* SPAuthenticationTextField.m */, + 46DD161917A1B8480093678C /* SPAuthenticationView.h */, + 46DD161A17A1B8480093678C /* SPAuthenticationView.m */, + 46DD161B17A1B8480093678C /* SPAuthenticationWindow.h */, + 46DD161C17A1B8480093678C /* SPAuthenticationWindow.m */, + 46DD161D17A1B8480093678C /* SPAuthenticationWindowController.h */, + 46DD161E17A1B8490093678C /* SPAuthenticationWindowController.m */, + 46263BAF17A7778E00131791 /* SPAuthenticationConfiguration.h */, + 46263BB017A7778E00131791 /* SPAuthenticationConfiguration.m */, + 466EB40B17BC194D005F7599 /* SPAuthenticationValidator.h */, + 466EB40C17BC194D005F7599 /* SPAuthenticationValidator.m */, ); - path = Reachability; + name = Auth; sourceTree = ""; }; - 26C3E1571666FE9300620306 /* SocketRocket */ = { + 4617AEB8179A1A3400545BD9 /* SPReachability */ = { isa = PBXGroup; children = ( - 26C3E1581666FE9300620306 /* base64.c */, - 26C3E1591666FE9300620306 /* base64.h */, - 26C3E15A1666FE9300620306 /* NSData+SRB64Additions.h */, - 26C3E15B1666FE9300620306 /* NSData+SRB64Additions.m */, - 26C3E15C1666FE9300620306 /* SocketRocket-Prefix.pch */, - 26C3E15D1666FE9300620306 /* SRWebSocket.h */, - 26C3E15E1666FE9300620306 /* SRWebSocket.m */, + 4617AEB9179A1A3400545BD9 /* SPReachability.h */, + 4617AEBA179A1A3400545BD9 /* SPReachability.m */, ); - path = SocketRocket; + path = SPReachability; sourceTree = ""; }; - 26F1C7001562E76B002172D5 /* Auth */ = { + 463774DD171FC6D500E2E333 /* SocketRocket */ = { isa = PBXGroup; children = ( - 26F1C6EC1562E75E002172D5 /* SPAuthView.h */, - 26F1C6ED1562E75E002172D5 /* SPAuthView.m */, - 26F1C6EE1562E75E002172D5 /* SPInputBoxView.h */, - 26F1C6EF1562E75E002172D5 /* SPInputBoxView.m */, - 26F1C6F01562E75E002172D5 /* SPSigninButtonCell.h */, - 26F1C6F11562E75E002172D5 /* SPSigninButtonCell.m */, - 26F1C6F21562E75E002172D5 /* SPSpotLightView.h */, - 26F1C6F31562E75E002172D5 /* SPSpotLightView.m */, - 26F1C6F41562E75E002172D5 /* SPToggleAuthCell.h */, - 26F1C6F51562E75E002172D5 /* SPToggleAuthCell.m */, + 463774DE171FC6D500E2E333 /* base64.c */, + 463774DF171FC6D500E2E333 /* base64.h */, + 463774E0171FC6D500E2E333 /* NSData+SRB64Additions.h */, + 463774E1171FC6D500E2E333 /* NSData+SRB64Additions.m */, + 463774E2171FC6D500E2E333 /* SocketRocket-Prefix.pch */, + 463774E3171FC6D500E2E333 /* SRWebSocket.h */, + 463774E4171FC6D500E2E333 /* SRWebSocket.m */, ); - name = Auth; + path = SocketRocket; sourceTree = ""; }; /* End PBXGroup section */ @@ -654,11 +678,10 @@ 268EDF2C155F08A2006B566F /* DDLogDebug.h in Headers */, 268EDF2D155F08A2006B566F /* NSData+Simperium.h in Headers */, 268EDF2F155F08A2006B566F /* NSDate+Simperium.h in Headers */, - 268EDF31155F08A2006B566F /* NSString+Simperium.h in Headers */, 268EDF33155F08A2006B566F /* Simperium-complete.h in Headers */, + 268EDF31155F08A2006B566F /* NSString+Simperium.h in Headers */, 268EDF35155F08A2006B566F /* Simperium.h in Headers */, - 268EDF37155F08A2006B566F /* SPAuthenticationManager.h in Headers */, - 268EDF39155F08A2006B566F /* SPAuthWindowController.h in Headers */, + 268EDF37155F08A2006B566F /* SPAuthenticator.h in Headers */, 268EDF3B155F08A2006B566F /* SPBinaryManager.h in Headers */, 268EDF3D155F08A2006B566F /* SPBinaryTransportDelegate.h in Headers */, 268EDF3E155F08A2006B566F /* SPChangeProcessor.h in Headers */, @@ -666,7 +689,6 @@ 268EDF42155F08A2006B566F /* SPCoreDataStorage.h in Headers */, 268EDF46155F08A2006B566F /* SPEnvironment.h in Headers */, 268EDF48155F08A2006B566F /* SPGhost.h in Headers */, - 268EDF4A155F08A2006B566F /* SPHttpManager.h in Headers */, 268EDF4C155F08A2006B566F /* SPIndexProcessor.h in Headers */, 268EDF50155F08A2006B566F /* SPManagedObject.h in Headers */, 268EDF52155F08A2006B566F /* SPMember.h in Headers */, @@ -679,7 +701,6 @@ 268EDF60155F08A2006B566F /* SPMemberInt.h in Headers */, 268EDF62155F08A2006B566F /* SPMemberList.h in Headers */, 268EDF64155F08A2006B566F /* SPMemberText.h in Headers */, - 268EDF66155F08A2006B566F /* SPNetworkManager.h in Headers */, 268EDF6C155F08A2006B566F /* SPS3Manager.h in Headers */, 268EDF70155F08A2006B566F /* SPStorage.h in Headers */, 268EDF72155F08A2006B566F /* SPStorageObserver.h in Headers */, @@ -710,11 +731,6 @@ 268EDFF4155F36B7006B566F /* DDTTYLogger.h in Headers */, 268EDFF6155F36B7006B566F /* ContextFilterLogFormatter.h in Headers */, 268EDFF8155F36B7006B566F /* DispatchQueueLogFormatter.h in Headers */, - 26F1C6F61562E75E002172D5 /* SPAuthView.h in Headers */, - 26F1C6F81562E75E002172D5 /* SPInputBoxView.h in Headers */, - 26F1C6FA1562E75E002172D5 /* SPSigninButtonCell.h in Headers */, - 26F1C6FC1562E75E002172D5 /* SPSpotLightView.h in Headers */, - 26F1C6FE1562E75E002172D5 /* SPToggleAuthCell.h in Headers */, 2698BE4515C908C80020565F /* NSMutableDictionary+Simperium.h in Headers */, 2698BE4715C908C80020565F /* SPBucket.h in Headers */, 2698BE4915C908C80020565F /* SPDiffable.h in Headers */, @@ -724,15 +740,26 @@ 2698BE5015C908C80020565F /* SPSchema.h in Headers */, 2698BE5215C908C80020565F /* SPStorageProvider.h in Headers */, 2698BE5615C9097A0020565F /* JRSwizzle.h in Headers */, - 2693F85B15C9A57C00797A5E /* SPSimpleKeyChain.h in Headers */, - 26C3E1491666FD7600620306 /* Reachability.h in Headers */, - 26C3E1511666FE7500620306 /* SPReferenceManager.h in Headers */, 26C3E1531666FE7500620306 /* SPWebSocketChannel.h in Headers */, - 26C3E1551666FE7500620306 /* SPWebSocketManager.h in Headers */, - 26C3E1601666FE9300620306 /* base64.h in Headers */, - 26C3E1611666FE9300620306 /* NSData+SRB64Additions.h in Headers */, - 26C3E1631666FE9300620306 /* SocketRocket-Prefix.pch in Headers */, - 26C3E1641666FE9300620306 /* SRWebSocket.h in Headers */, + 463774E6171FC6D500E2E333 /* base64.h in Headers */, + 463774E7171FC6D500E2E333 /* NSData+SRB64Additions.h in Headers */, + 463774E9171FC6D500E2E333 /* SocketRocket-Prefix.pch in Headers */, + 463774EA171FC6D500E2E333 /* SRWebSocket.h in Headers */, + 46A691FF17387ABF0080399F /* SFHFKeychainUtils.h in Headers */, + 46D5BC41174C2A9B0027DBAF /* SPRelationshipResolver.h in Headers */, + 46EA1AEB17569EA0009AC5AA /* SPNetworkInterface.h in Headers */, + 46EA1AEE17569EB4009AC5AA /* SPWebSocketInterface.h in Headers */, + 46EA1AF217569EC7009AC5AA /* SPHttpInterface.h in Headers */, + 4617AEBB179A1A3400545BD9 /* SPReachability.h in Headers */, + 46DD15C6179A6DAC0093678C /* SPMemberJSONList.h in Headers */, + 46DD161F17A1B8490093678C /* SPAuthenticationButton.h in Headers */, + 46DD162117A1B8490093678C /* SPAuthenticationButtonCell.h in Headers */, + 46DD162317A1B8490093678C /* SPAuthenticationTextField.h in Headers */, + 46DD162517A1B8490093678C /* SPAuthenticationView.h in Headers */, + 46DD162717A1B8490093678C /* SPAuthenticationWindow.h in Headers */, + 46DD162917A1B8490093678C /* SPAuthenticationWindowController.h in Headers */, + 46263BB117A7778E00131791 /* SPAuthenticationConfiguration.h in Headers */, + 466EB40D17BC194D005F7599 /* SPAuthenticationValidator.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -763,7 +790,7 @@ 268EDEBF155F06D5006B566F /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0430; + LastUpgradeCheck = 0500; ORGANIZATIONNAME = Simperium; }; buildConfigurationList = 268EDEC2155F06D5006B566F /* Build configuration list for PBXProject "Simperium-OSX" */; @@ -790,7 +817,6 @@ files = ( 268EDED7155F06D5006B566F /* InfoPlist.strings in Resources */, 268EDFFA155F36B7006B566F /* README.txt in Resources */, - 26F1C6EA1562E297002172D5 /* AuthWindow.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -805,15 +831,13 @@ 268EDF30155F08A2006B566F /* NSDate+Simperium.m in Sources */, 268EDF32155F08A2006B566F /* NSString+Simperium.m in Sources */, 268EDF36155F08A2006B566F /* Simperium.m in Sources */, - 268EDF38155F08A2006B566F /* SPAuthenticationManager.m in Sources */, - 268EDF3A155F08A2006B566F /* SPAuthWindowController.m in Sources */, + 268EDF38155F08A2006B566F /* SPAuthenticator.m in Sources */, 268EDF3C155F08A2006B566F /* SPBinaryManager.m in Sources */, 268EDF3F155F08A2006B566F /* SPChangeProcessor.m in Sources */, 268EDF41155F08A2006B566F /* SPCoreDataExporter.m in Sources */, 268EDF43155F08A2006B566F /* SPCoreDataStorage.m in Sources */, 268EDF47155F08A2006B566F /* SPEnvironment.m in Sources */, 268EDF49155F08A2006B566F /* SPGhost.m in Sources */, - 268EDF4B155F08A2006B566F /* SPHttpManager.m in Sources */, 268EDF4D155F08A2006B566F /* SPIndexProcessor.m in Sources */, 268EDF51155F08A2006B566F /* SPManagedObject.m in Sources */, 268EDF53155F08A2006B566F /* SPMember.m in Sources */, @@ -826,7 +850,6 @@ 268EDF61155F08A2006B566F /* SPMemberInt.m in Sources */, 268EDF63155F08A2006B566F /* SPMemberList.m in Sources */, 268EDF65155F08A2006B566F /* SPMemberText.m in Sources */, - 268EDF67155F08A2006B566F /* SPNetworkManager.m in Sources */, 268EDF71155F08A2006B566F /* SPStorage.m in Sources */, 268EDF74155F08A2006B566F /* SPUser.m in Sources */, 268EDFCA155F36B7006B566F /* ASIDataCompressor.m in Sources */, @@ -850,11 +873,6 @@ 268EDFF5155F36B7006B566F /* DDTTYLogger.m in Sources */, 268EDFF7155F36B7006B566F /* ContextFilterLogFormatter.m in Sources */, 268EDFF9155F36B7006B566F /* DispatchQueueLogFormatter.m in Sources */, - 26F1C6F71562E75E002172D5 /* SPAuthView.m in Sources */, - 26F1C6F91562E75E002172D5 /* SPInputBoxView.m in Sources */, - 26F1C6FB1562E75E002172D5 /* SPSigninButtonCell.m in Sources */, - 26F1C6FD1562E75E002172D5 /* SPSpotLightView.m in Sources */, - 26F1C6FF1562E75E002172D5 /* SPToggleAuthCell.m in Sources */, 2698BE4615C908C80020565F /* NSMutableDictionary+Simperium.m in Sources */, 2698BE4815C908C80020565F /* SPBucket.m in Sources */, 2698BE4B15C908C80020565F /* SPDiffer.m in Sources */, @@ -862,14 +880,26 @@ 2698BE4F15C908C80020565F /* SPObject.m in Sources */, 2698BE5115C908C80020565F /* SPSchema.m in Sources */, 2698BE5715C9097A0020565F /* JRSwizzle.m in Sources */, - 2693F85C15C9A57C00797A5E /* SPSimpleKeyChain.m in Sources */, - 26C3E14A1666FD7600620306 /* Reachability.m in Sources */, - 26C3E1521666FE7500620306 /* SPReferenceManager.m in Sources */, 26C3E1541666FE7500620306 /* SPWebSocketChannel.m in Sources */, - 26C3E1561666FE7500620306 /* SPWebSocketManager.m in Sources */, - 26C3E15F1666FE9300620306 /* base64.c in Sources */, - 26C3E1621666FE9300620306 /* NSData+SRB64Additions.m in Sources */, - 26C3E1651666FE9300620306 /* SRWebSocket.m in Sources */, + 463774E5171FC6D500E2E333 /* base64.c in Sources */, + 463774E8171FC6D500E2E333 /* NSData+SRB64Additions.m in Sources */, + 463774EB171FC6D500E2E333 /* SRWebSocket.m in Sources */, + 46A6920017387ABF0080399F /* SFHFKeychainUtils.m in Sources */, + 46D5BC42174C2A9B0027DBAF /* SPRelationshipResolver.m in Sources */, + 46EA1AEF17569EB4009AC5AA /* SPWebSocketInterface.m in Sources */, + 46EA1AF317569EC7009AC5AA /* SPHttpInterface.m in Sources */, + 4617AEBC179A1A3400545BD9 /* SPReachability.m in Sources */, + 46DD15C7179A6DAC0093678C /* SPMemberJSONList.m in Sources */, + 46DD162017A1B8490093678C /* SPAuthenticationButton.m in Sources */, + 46DD162217A1B8490093678C /* SPAuthenticationButtonCell.m in Sources */, + 46DD162417A1B8490093678C /* SPAuthenticationTextField.m in Sources */, + 46DD162617A1B8490093678C /* SPAuthenticationView.m in Sources */, + 46DD162817A1B8490093678C /* SPAuthenticationWindow.m in Sources */, + 46DD162A17A1B8490093678C /* SPAuthenticationWindowController.m in Sources */, + 46263BB217A7778E00131791 /* SPAuthenticationConfiguration.m in Sources */, + 466EB40E17BC194D005F7599 /* SPAuthenticationValidator.m in Sources */, + 466FFEA217CBEE7A00399652 /* SPNetworkInterface.m in Sources */, + 460A505B17DE95B20070F02B /* SPProcessorNotificationNames.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -891,7 +921,12 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - ARCHS = "$(ARCHS_STANDARD_64_BIT)"; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; @@ -905,11 +940,14 @@ GCC_VERSION = com.apple.compilers.llvm.clang.1_0; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.7; + MACOSX_DEPLOYMENT_TARGET = 10.8; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; + SKIP_INSTALL = YES; }; name = Debug; }; @@ -917,7 +955,12 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - ARCHS = "$(ARCHS_STANDARD_64_BIT)"; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; GCC_C_LANGUAGE_STANDARD = gnu99; @@ -925,16 +968,21 @@ GCC_VERSION = com.apple.compilers.llvm.clang.1_0; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.7; + MACOSX_DEPLOYMENT_TARGET = 10.8; SDKROOT = macosx; + SKIP_INSTALL = YES; }; name = Release; }; 268EDEDF155F06D5006B566F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_OBJC_ARC = YES; + COMBINE_HIDPI_IMAGES = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -954,6 +1002,8 @@ 268EDEE0155F06D5006B566F /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_OBJC_ARC = YES; + COMBINE_HIDPI_IMAGES = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; diff --git a/Simperium-OSX/Simperium-OSX/SPAuthView.h b/Simperium-OSX/Simperium-OSX/SPAuthView.h deleted file mode 100644 index e5888970..00000000 --- a/Simperium-OSX/Simperium-OSX/SPAuthView.h +++ /dev/null @@ -1,12 +0,0 @@ -// -// SPAuthView.h -// Simplenote-OSX -// -// Created by Rainieri Ventura on 2/22/12. -// Copyright (c) 2012 Simperium. All rights reserved. -// - -#import - -@interface SPAuthView : NSView -@end diff --git a/Simperium-OSX/Simperium-OSX/SPAuthView.m b/Simperium-OSX/Simperium-OSX/SPAuthView.m deleted file mode 100644 index 9423adf6..00000000 --- a/Simperium-OSX/Simperium-OSX/SPAuthView.m +++ /dev/null @@ -1,30 +0,0 @@ -// -// SPAuthView.m -// Simplenote-OSX -// -// Created by Rainieri Ventura on 2/22/12. -// Copyright (c) 2012 Simperium. All rights reserved. -// - -#import "SPAuthView.h" - -@implementation SPAuthView - -- (id)initWithFrame:(NSRect)frame { - self = [super initWithFrame:frame]; - if (self) { - // Initialization code here. - - } - return self; -} - -- (void)drawRect:(NSRect)dirtyRect { - // set any NSColor for filling, say white: -// NSImage *image = [NSImage imageNamed:@"auth_bgnoise.png"]; -// NSColor *noise = [NSColor colorWithPatternImage:image]; -// [noise setFill]; -// NSRectFill(dirtyRect); -} - -@end diff --git a/Simperium-OSX/Simperium-OSX/SPAuthenticationButton.h b/Simperium-OSX/Simperium-OSX/SPAuthenticationButton.h new file mode 100644 index 00000000..582562c8 --- /dev/null +++ b/Simperium-OSX/Simperium-OSX/SPAuthenticationButton.h @@ -0,0 +1,13 @@ +// +// SPAuthenticationButton.h +// Simplenote-OSX +// +// Created by Michael Johnston on 7/24/13. +// Copyright (c) 2013 Simperium. All rights reserved. +// + +#import + +@interface SPAuthenticationButton : NSButton + +@end diff --git a/Simperium-OSX/Simperium-OSX/SPAuthenticationButton.m b/Simperium-OSX/Simperium-OSX/SPAuthenticationButton.m new file mode 100644 index 00000000..306dfb04 --- /dev/null +++ b/Simperium-OSX/Simperium-OSX/SPAuthenticationButton.m @@ -0,0 +1,27 @@ +// +// SPAuthenticationButton.m +// Simplenote-OSX +// +// Created by Michael Johnston on 7/24/13. +// Copyright (c) 2013 Simperium. All rights reserved. +// + +#import "SPAuthenticationButton.h" +#import "SPAuthenticationButtonCell.h" + +@implementation SPAuthenticationButton + +- (id)initWithFrame:(NSRect)frame +{ + self = [super initWithFrame:frame]; + if (self) { + } + + return self; +} + ++ (void)load { + [[self class] setCellClass:[SPAuthenticationButtonCell class]]; +} + +@end diff --git a/Simperium-OSX/Simperium-OSX/SPAuthenticationButtonCell.h b/Simperium-OSX/Simperium-OSX/SPAuthenticationButtonCell.h new file mode 100644 index 00000000..05ee8ab0 --- /dev/null +++ b/Simperium-OSX/Simperium-OSX/SPAuthenticationButtonCell.h @@ -0,0 +1,13 @@ +// +// SPAuthenticationButtonCell.h +// Simplenote-OSX +// +// Created by Michael Johnston on 7/24/13. +// Copyright (c) 2013 Simperium. All rights reserved. +// + +#import + +@interface SPAuthenticationButtonCell : NSButtonCell + +@end diff --git a/Simperium-OSX/Simperium-OSX/SPAuthenticationButtonCell.m b/Simperium-OSX/Simperium-OSX/SPAuthenticationButtonCell.m new file mode 100644 index 00000000..b4f76c05 --- /dev/null +++ b/Simperium-OSX/Simperium-OSX/SPAuthenticationButtonCell.m @@ -0,0 +1,49 @@ +// +// SPAuthenticationButtonCell.m +// Simplenote-OSX +// +// Created by Michael Johnston on 7/24/13. +// Copyright (c) 2013 Simperium. All rights reserved. +// + +#import "SPAuthenticationButtonCell.h" +#import "SPAuthenticationConfiguration.h" + +@implementation SPAuthenticationButtonCell + + +- (void)drawBezelWithFrame:(NSRect)frame inView:(NSView *)controlView { +} + +- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView { + NSBezierPath *outerClip = [NSBezierPath bezierPathWithRoundedRect:cellFrame xRadius:12.f yRadius:12.f]; + [outerClip addClip]; + + NSColor *buttonColor = [SPAuthenticationConfiguration sharedInstance].controlColor; + if ([self isHighlighted]) { + buttonColor = [buttonColor blendedColorWithFraction:0.1 ofColor:[NSColor blackColor]]; + } + + [buttonColor setFill]; + [outerClip fill]; + + int fontSize = 20; + NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init]; + [style setAlignment:NSCenterTextAlignment]; + [style setMaximumLineHeight:fontSize + 8]; + + NSFont *font = [NSFont fontWithName:[SPAuthenticationConfiguration sharedInstance].regularFontName size:fontSize]; + NSDictionary *attributes = @{NSFontAttributeName : font, + NSForegroundColorAttributeName : [NSColor whiteColor], + NSParagraphStyleAttributeName: style}; + + NSAttributedString *buttonTitle = [[NSAttributedString alloc] initWithString:self.title attributes:attributes]; + + // Vertically align the text (could be cached) + CGFloat fieldHeight = [[SPAuthenticationConfiguration sharedInstance] regularFontHeightForSize:fontSize]; + CGFloat fieldY = (controlView.frame.size.height - fieldHeight) / 2; + cellFrame.origin.y = fieldY; + [buttonTitle drawInRect:cellFrame]; +} + +@end diff --git a/Simperium-OSX/Simperium-OSX/SPAuthenticationTextField.h b/Simperium-OSX/Simperium-OSX/SPAuthenticationTextField.h new file mode 100644 index 00000000..c9c68fec --- /dev/null +++ b/Simperium-OSX/Simperium-OSX/SPAuthenticationTextField.h @@ -0,0 +1,22 @@ +// +// SPAuthenticationTextField.h +// Simplenote-OSX +// +// Created by Michael Johnston on 7/24/13. +// Copyright (c) 2013 Simperium. All rights reserved. +// + +#import + +@interface SPAuthenticationTextField : NSView + +@property (assign) id delegate; +@property (strong) NSTextField *textField; + +- (id)initWithFrame:(NSRect)frame secure:(BOOL)secure; +- (void)setPlaceholderString:(NSString *)string; +- (NSString *)stringValue; +- (void)setStringValue:(NSString *)string; +- (void)setEnabled:(BOOL)enabled; + +@end diff --git a/Simperium-OSX/Simperium-OSX/SPAuthenticationTextField.m b/Simperium-OSX/Simperium-OSX/SPAuthenticationTextField.m new file mode 100644 index 00000000..9e9de773 --- /dev/null +++ b/Simperium-OSX/Simperium-OSX/SPAuthenticationTextField.m @@ -0,0 +1,110 @@ +// +// SPAuthenticationTextField.m +// Simplenote-OSX +// +// Created by Michael Johnston on 7/24/13. +// Copyright (c) 2013 Simperium. All rights reserved. +// + +#import "SPAuthenticationTextField.h" +#import "SPAuthenticationConfiguration.h" + +@interface SPAuthenticationTextField() { + BOOL hasFocus; +} + +@end + +@implementation SPAuthenticationTextField + +- (id)initWithFrame:(NSRect)frame secure:(BOOL)secure { + self = [super initWithFrame:frame]; + if (self) { + // Center the textField vertically + int paddingX = 10; + int fontSize = 20; + CGFloat fieldHeight = [[SPAuthenticationConfiguration sharedInstance] regularFontHeightForSize:fontSize]; + CGFloat fieldY = (self.frame.size.height - fieldHeight) / 2; + CGRect textFrame = NSMakeRect(paddingX, fieldY, frame.size.width-paddingX*2, fieldHeight); + + Class textFieldClass = secure ? [NSSecureTextField class] : [NSTextField class]; + _textField = [[textFieldClass alloc] initWithFrame:textFrame]; + NSFont *font = [NSFont fontWithName:[SPAuthenticationConfiguration sharedInstance].regularFontName size:fontSize]; + [_textField setFont:font]; + [_textField setTextColor:[NSColor colorWithCalibratedWhite:0.1 alpha:1.0]]; + [_textField setDrawsBackground:NO]; + [_textField setBezeled:NO]; + [_textField setBordered:NO]; + [_textField setFocusRingType:NSFocusRingTypeNone]; + [[_textField cell] setWraps:NO]; + [[_textField cell] setScrollable:YES]; + [self addSubview:_textField]; + } + + return self; +} + +- (void)setStringValue:(NSString *)string { + _textField.stringValue = string; +} + +- (NSString *)stringValue { + return _textField.stringValue; +} + +- (void)setPlaceholderString:(NSString *)string { + [[_textField cell] setPlaceholderString:string]; +} + +- (void)setDelegate:(id)delegate { + _textField.delegate = delegate; +} + +- (id)delegate { + return _textField.delegate; +} + +- (void)setEnabled:(BOOL)enabled { + [_textField setEnabled:enabled]; + [_textField setEditable:enabled]; +} + +- (BOOL)hasFirstResponder { + BOOL hasFirstResponder = NO; + + hasFirstResponder = ([[[_textField window] firstResponder] isKindOfClass:[NSTextView class]] + && [[_textField window] fieldEditor:NO forObject:nil]!=nil + && [_textField isEqualTo:(id)[(NSTextView *)[[_textField window] firstResponder]delegate]]); + + return hasFirstResponder; +} + +- (void)drawRect:(NSRect)dirtyRect { + NSBezierPath *betterBounds = [NSBezierPath bezierPathWithRoundedRect:self.bounds xRadius:12.0 yRadius:12.0]; + [betterBounds addClip]; + + + if ([self hasFirstResponder]) { + [[NSColor colorWithCalibratedWhite:0.9 alpha:1.0] setFill]; + [betterBounds fill]; + + if (!hasFocus) { + hasFocus = YES; + [self setNeedsDisplay:YES]; + } + } else { + [[NSColor colorWithCalibratedWhite:250.f/255.f alpha:1.0] setFill]; + [betterBounds fill]; + + + [[NSColor colorWithCalibratedWhite:218.f/255.f alpha:1.0] setStroke]; + [betterBounds stroke]; + + if (hasFocus) { + hasFocus = NO; + [self setNeedsDisplay:YES]; + } + } +} + +@end diff --git a/Simperium-OSX/Simperium-OSX/SPAuthenticationView.h b/Simperium-OSX/Simperium-OSX/SPAuthenticationView.h new file mode 100755 index 00000000..99cd33c8 --- /dev/null +++ b/Simperium-OSX/Simperium-OSX/SPAuthenticationView.h @@ -0,0 +1,15 @@ +// +// SPAuthenticationView.h +// Simplenote-OSX +// +// Created by Michael Johnston on 7/20/13. +// Copyright (c) 2013 Simperium. All rights reserved. +// + +#import + +@interface SPAuthenticationView : NSView { + +} + +@end diff --git a/Simperium-OSX/Simperium-OSX/SPAuthenticationView.m b/Simperium-OSX/Simperium-OSX/SPAuthenticationView.m new file mode 100755 index 00000000..348a1966 --- /dev/null +++ b/Simperium-OSX/Simperium-OSX/SPAuthenticationView.m @@ -0,0 +1,30 @@ +// +// SPAuthenticationView.m +// Simplenote-OSX +// +// Created by Michael Johnston on 7/20/13. +// Copyright (c) 2013 Simperium. All rights reserved. +// + +#import "SPAuthenticationView.h" + +@implementation SPAuthenticationView + +- (id)initWithFrame:(NSRect)frame { + self = [super initWithFrame:frame]; + if (self) { + } + return self; +} + +- (void)drawRect:(NSRect)rect { + [[NSColor clearColor] set]; + NSRectFill([self frame]); + + [[NSColor colorWithCalibratedWhite:1.0 alpha:1.0] set]; + NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:[self frame] xRadius:12.0 yRadius:12.0]; + [path addClip]; + NSRectFill([self frame]); +} + +@end diff --git a/Simperium-OSX/Simperium-OSX/SPAuthenticationWindow.h b/Simperium-OSX/Simperium-OSX/SPAuthenticationWindow.h new file mode 100644 index 00000000..70eed64f --- /dev/null +++ b/Simperium-OSX/Simperium-OSX/SPAuthenticationWindow.h @@ -0,0 +1,17 @@ +// +// SPAuthenticationWindow.h +// Simplenote-OSX +// +// Created by Michael Johnston on 7/20/13. +// Copyright (c) 2013 Simperium. All rights reserved. +// + +#import + +@interface SPAuthenticationWindow : NSWindow { + NSPoint initialLocation; +} + +@property (assign) NSPoint initialLocation; + +@end diff --git a/Simperium-OSX/Simperium-OSX/SPAuthenticationWindow.m b/Simperium-OSX/Simperium-OSX/SPAuthenticationWindow.m new file mode 100644 index 00000000..3e89baee --- /dev/null +++ b/Simperium-OSX/Simperium-OSX/SPAuthenticationWindow.m @@ -0,0 +1,55 @@ +// +// SPAuthenticationWindow.m +// Simplenote-OSX +// +// Created by Michael Johnston on 7/20/13. +// Copyright (c) 2013 Simperium. All rights reserved. +// + +#import "SPAuthenticationWindow.h" + +@implementation SPAuthenticationWindow +@synthesize initialLocation; + +- (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)aStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)flag { + if ((self = [super initWithContentRect:contentRect styleMask:aStyle backing:bufferingType defer:flag])) { + [self setAlphaValue:1.0]; + [self setOpaque:NO]; + [self setBackgroundColor:[NSColor clearColor]]; + [self setHasShadow:YES]; + } + + return self; +} + +- (BOOL)canBecomeKeyWindow { + return YES; +} + +- (void)mouseDown:(NSEvent *)theEvent { + // Get the mouse location in window coordinates. + self.initialLocation = [theEvent locationInWindow]; +} + +- (void)mouseDragged:(NSEvent *)theEvent { + NSRect screenVisibleFrame = [[NSScreen mainScreen] visibleFrame]; + NSRect windowFrame = [self frame]; + NSPoint newOrigin = windowFrame.origin; + + // Get the mouse location in window coordinates. + NSPoint currentLocation = [theEvent locationInWindow]; + + // Update the origin with the difference between the new mouse location and the old mouse location. + newOrigin.x += (currentLocation.x - initialLocation.x); + newOrigin.y += (currentLocation.y - initialLocation.y); + + // Don't let window get dragged up under the menu bar + if ((newOrigin.y + windowFrame.size.height) > (screenVisibleFrame.origin.y + screenVisibleFrame.size.height)) { + newOrigin.y = screenVisibleFrame.origin.y + (screenVisibleFrame.size.height - windowFrame.size.height); + } + + // Move the window to the new location + [self setFrameOrigin:newOrigin]; +} + +@end diff --git a/Simperium-OSX/Simperium-OSX/SPAuthenticationWindowController.h b/Simperium-OSX/Simperium-OSX/SPAuthenticationWindowController.h new file mode 100644 index 00000000..45318e03 --- /dev/null +++ b/Simperium-OSX/Simperium-OSX/SPAuthenticationWindowController.h @@ -0,0 +1,42 @@ +// +// SPAuthenticationWindowController.h +// Simperium +// +// Created by Michael Johnston on 7/20/13. +// Copyright (c) 2013 Simperium. All rights reserved. +// + +#import +#import + +@class SPAuthenticationTextField; +@class SPAuthenticationValidator; + +@interface SPAuthenticationWindowController : NSWindowController { + NSImageView *logoImageView; + NSButton *cancelButton; + SPAuthenticationTextField *usernameField; + SPAuthenticationTextField *passwordField; + SPAuthenticationTextField *confirmField; + NSTextField *changeToSignInField; + NSTextField *changeToSignUpField; + NSTextField *errorField; + NSButton *signInButton; + NSButton *signUpButton; + NSButton *changeToSignInButton; + NSButton *changeToSignUpButton; + NSProgressIndicator *signInProgress; + NSProgressIndicator *signUpProgress; + BOOL signingIn; + BOOL optional; + CGFloat rowSize; +} + +@property (nonatomic, retain) SPAuthenticator *authenticator; +@property (nonatomic, retain) SPAuthenticationValidator *validator; +@property (assign) BOOL optional; + +- (IBAction) signUpAction:(id)sender; +- (IBAction) signInAction:(id)sender; + +@end diff --git a/Simperium-OSX/Simperium-OSX/SPAuthenticationWindowController.m b/Simperium-OSX/Simperium-OSX/SPAuthenticationWindowController.m new file mode 100644 index 00000000..db995c76 --- /dev/null +++ b/Simperium-OSX/Simperium-OSX/SPAuthenticationWindowController.m @@ -0,0 +1,433 @@ +// +// SPAuthenticationWindowController.m +// Simperium +// +// Created by Michael Johnston on 8/14/13. +// Copyright (c) 2013 Simperium. All rights reserved. +// + +#import "SPAuthenticationWindowController.h" +#import +#import +#import +#import "SPAuthenticationWindow.h" +#import "SPAuthenticationView.h" +#import "SPAuthenticationTextField.h" +#import "SPAuthenticationButton.h" +#import "SPAuthenticationConfiguration.h" +#import "SPAuthenticationValidator.h" + +static NSUInteger windowWidth = 380; +static NSUInteger windowHeight = 540; + +@interface SPAuthenticationWindowController () { + BOOL earthquaking; +} + +@end + +@implementation SPAuthenticationWindowController +@synthesize authenticator; +@synthesize validator; +@synthesize optional; + +- (id)init { + rowSize = 50; + SPAuthenticationWindow *window = [[SPAuthenticationWindow alloc] initWithContentRect:NSMakeRect(0, 0, windowWidth, windowHeight) styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO]; + + if ((self = [super initWithWindow: window])) { + self.validator = [[SPAuthenticationValidator alloc] init]; + + SPAuthenticationView *authView = [[SPAuthenticationView alloc] initWithFrame:window.frame]; + [window.contentView addSubview:authView]; + + NSUInteger paddingX = 30; + NSUInteger width = windowWidth - paddingX*2; + + int cancelWidth = 60; + NSString *cancelButtonText = NSLocalizedString(@"Skip", @"Text to display on OSX cancel button"); + + cancelButton = [self linkButtonWithText:cancelButtonText frame:NSMakeRect(windowWidth-cancelWidth, windowHeight-5-20, cancelWidth, 20)]; + cancelButton.target = self; + cancelButton.action = @selector(cancelAction:); + [authView addSubview:cancelButton]; + + NSImage *logoImage = [NSImage imageNamed:[[SPAuthenticationConfiguration sharedInstance] logoImageName]]; + CGFloat markerY = windowHeight-45-logoImage.size.height; + NSRect logoRect = NSMakeRect(windowWidth/2 - logoImage.size.width/2, markerY, logoImage.size.width, logoImage.size.height); + logoImageView = [[NSImageView alloc] initWithFrame:logoRect]; + logoImageView.image = logoImage; + [authView addSubview:logoImageView]; + + errorField = [self tipFieldWithText:@"" frame:NSMakeRect(paddingX, markerY - 30, width, 20)]; + [errorField setTextColor:[NSColor redColor]]; + [authView addSubview:errorField]; + + markerY -= 30; + usernameField = [[SPAuthenticationTextField alloc] initWithFrame:NSMakeRect(paddingX, markerY - rowSize, width, 40) secure:NO]; + + [usernameField setPlaceholderString:NSLocalizedString(@"Email Address", @"Placeholder text for login field")]; + usernameField.delegate = self; + [authView addSubview:usernameField]; + + passwordField = [[SPAuthenticationTextField alloc] initWithFrame:NSMakeRect(paddingX, markerY - rowSize*2, width, 40) secure:YES]; + [passwordField setPlaceholderString:NSLocalizedString(@"Password", @"Placeholder text for password field")]; + + passwordField.delegate = self; + [authView addSubview:passwordField]; + + confirmField = [[SPAuthenticationTextField alloc] initWithFrame:NSMakeRect(paddingX, markerY - rowSize*3, width, 40) secure:YES]; + [confirmField setPlaceholderString:NSLocalizedString(@"Confirm Password", @"Placeholder text for confirmation field")]; + confirmField.delegate = self; + [authView addSubview:confirmField]; + + markerY -= 30; + signInButton = [[SPAuthenticationButton alloc] initWithFrame:NSMakeRect(paddingX, markerY - rowSize*3, width, 40)]; + signInButton.title = NSLocalizedString(@"Sign In", @"Title of button for signing in"); + signInButton.target = self; + signInButton.action = @selector(signInAction:); + [authView addSubview:signInButton]; + + int progressSize = 20; + signInProgress = [[NSProgressIndicator alloc] initWithFrame:NSMakeRect(signInButton.frame.size.width - progressSize - paddingX, (signInButton.frame.size.height - progressSize) / 2, progressSize, progressSize)]; + [signInProgress setStyle:NSProgressIndicatorSpinningStyle]; + [signInProgress setDisplayedWhenStopped:NO]; + [signInButton addSubview:signInProgress]; + + + signUpButton = [[SPAuthenticationButton alloc] initWithFrame:NSMakeRect(paddingX, markerY - rowSize*4, width, 40)]; + signUpButton.title = NSLocalizedString(@"Sign Up", @"Title of button for signing up"); + signUpButton.target = self; + signUpButton.action = @selector(signUpAction:); + [authView addSubview:signUpButton]; + + signUpProgress = [[NSProgressIndicator alloc] initWithFrame:NSMakeRect(signUpProgress.frame.size.width - progressSize - paddingX, (signUpProgress.frame.size.height - progressSize) / 2, progressSize, progressSize)]; + [signUpProgress setStyle:NSProgressIndicatorSpinningStyle]; + [signUpProgress setDisplayedWhenStopped:NO]; + [signUpButton addSubview:signUpProgress]; + + + NSString *signUpTip = NSLocalizedString(@"Need an account?", @"Link to create an account"); + changeToSignUpField = [self tipFieldWithText:signUpTip frame:NSMakeRect(paddingX, markerY - rowSize*3 - 35, width, 20)]; + [authView addSubview:changeToSignUpField]; + + NSString *signInTip = NSLocalizedString(@"Already have an account?", @"Link to sign in to an account"); + changeToSignInField = [self tipFieldWithText:signInTip frame:NSMakeRect(paddingX, markerY - rowSize*4 - 35, width, 20)]; + [authView addSubview:changeToSignInField]; + + changeToSignUpButton = [self toggleButtonWithText:signUpButton.title frame:NSMakeRect(paddingX, changeToSignUpField.frame.origin.y - changeToSignUpField.frame.size.height - 2, width, 30)]; + [authView addSubview:changeToSignUpButton]; + + changeToSignInButton = [self toggleButtonWithText:signInButton.title frame:NSMakeRect(paddingX, changeToSignInField.frame.origin.y - changeToSignInField.frame.size.height - 2, width, 30)]; + [authView addSubview:changeToSignInButton]; + + // Enter sign up mode + [self toggleAuthenticationMode:signUpButton]; + } + + return self; +} + +- (void)setOptional:(BOOL)on { + optional = on; + [cancelButton setHidden:!optional]; +} + +- (BOOL)optional { + return optional; +} + +- (NSTextField *)tipFieldWithText:(NSString *)text frame:(CGRect)frame { + NSTextField *field = [[NSTextField alloc] initWithFrame:frame]; + NSFont *font = [NSFont fontWithName:[SPAuthenticationConfiguration sharedInstance].mediumFontName size:13]; + [field setStringValue:[text uppercaseString]]; + [field setEditable:NO]; + [field setSelectable:NO]; + [field setBordered:NO]; + [field setDrawsBackground:NO]; + [field setAlignment:NSCenterTextAlignment]; + [field setFont:font]; + [field setTextColor:[NSColor colorWithCalibratedWhite:153.f/255.f alpha:1.0]]; + + return field; +} + +- (NSButton *)linkButtonWithText:(NSString *)text frame:(CGRect)frame { + NSButton *button = [[NSButton alloc] initWithFrame:frame]; + [button setBordered:NO]; + [button setButtonType:NSMomentaryChangeButton]; + button.target = self; + button.action = @selector(toggleAuthenticationMode:); + + NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init]; + [style setAlignment:NSCenterTextAlignment]; + NSColor *linkColor = [SPAuthenticationConfiguration sharedInstance].controlColor; + + NSFont *font = [NSFont fontWithName:[SPAuthenticationConfiguration sharedInstance].mediumFontName size:13]; + NSDictionary *attributes = @{NSFontAttributeName : font, + NSForegroundColorAttributeName : linkColor, + NSParagraphStyleAttributeName : style}; + [button setAttributedTitle: [[NSAttributedString alloc] initWithString:[text uppercaseString] attributes:attributes]]; + + return button; +} + +- (NSButton *)toggleButtonWithText:(NSString *)text frame:(CGRect)frame { + NSButton *button = [self linkButtonWithText:text frame:frame]; + button.target = self; + button.action = @selector(toggleAuthenticationMode:); + + return button; +} + + +- (IBAction)toggleAuthenticationMode:(id)sender { + signingIn = sender == changeToSignInButton; + [signInButton setHidden:!signingIn]; + [signInButton setEnabled:signingIn]; + [signUpButton setHidden:signingIn]; + [signUpButton setEnabled:!signingIn]; + [changeToSignInButton setHidden:signingIn]; + [changeToSignInButton setEnabled:!signingIn]; + [changeToSignUpButton setHidden:!signingIn]; + [changeToSignUpButton setEnabled:signingIn]; + [changeToSignInField setHidden:signingIn]; + [changeToSignUpField setHidden:!signingIn]; + [confirmField setHidden:signingIn]; + + [self.window.contentView setNeedsDisplay:YES]; + [self clearAuthenticationError]; +} + + +#pragma mark Actions + +- (IBAction)signInAction:(id)sender { + if (![self validateSignIn]) { + return; + } + + signInButton.title = NSLocalizedString(@"Signing In...", @"Displayed temporarily while signing in"); + [signInProgress startAnimation:self]; + [signInButton setEnabled:NO]; + [changeToSignUpButton setEnabled:NO]; + [usernameField setEnabled:NO]; + [passwordField setEnabled:NO]; + [self.authenticator authenticateWithUsername:[usernameField stringValue] password:[passwordField stringValue] + success:^{ + } + failure:^(int responseCode, NSString *responseString) { + NSLog(@"Error signing in (%d): %@", responseCode, responseString); + [self showAuthenticationErrorForCode:responseCode]; + [signInProgress stopAnimation:self]; + signInButton.title = NSLocalizedString(@"Sign In", @"Title of button for signing in"); + [signInButton setEnabled:YES]; + [changeToSignUpButton setEnabled:YES]; + [usernameField setEnabled:YES]; + [passwordField setEnabled:YES]; + } + ]; +} + +- (IBAction)signUpAction:(id)sender { + if (![self validateSignUp]) { + return; + } + + signUpButton.title = NSLocalizedString(@"Signing Up...", @"Displayed temoprarily while signing up"); + [signUpProgress startAnimation:self]; + [signUpButton setEnabled:NO]; + [changeToSignInButton setEnabled:NO]; + [usernameField setEnabled:NO]; + [passwordField setEnabled:NO]; + [confirmField setEnabled:NO]; + + [self.authenticator createWithUsername:[usernameField stringValue] password:[passwordField stringValue] + success:^{ + //[self close]; + } + failure:^(int responseCode, NSString *responseString) { + NSLog(@"Error signing up (%d): %@", responseCode, responseString); + [self showAuthenticationErrorForCode:responseCode]; + signUpButton.title = NSLocalizedString(@"Sign Up", @"Title of button for signing up"); + [signUpProgress stopAnimation:self]; + [signUpButton setEnabled:YES]; + [changeToSignInButton setEnabled:YES]; + [usernameField setEnabled:YES]; + [passwordField setEnabled:YES]; + [confirmField setEnabled:YES]; + }]; +} + +- (IBAction)cancelAction:(id)sender { + [authenticator cancel]; +} + + +# pragma mark Validation and Error Handling + +- (BOOL)validateUsername { + if (![self.validator validateUsername:usernameField.stringValue]) { + [self earthquake:usernameField]; + [self showAuthenticationError:NSLocalizedString(@"Not a valid email address", @"Error when you enter a bad email address")]; + + return NO; + } + + return YES; +} + +- (BOOL)validatePasswordSecurity { + if (![self.validator validatePasswordSecurity:passwordField.stringValue]) { + [self earthquake:passwordField]; + [self earthquake:confirmField]; + + NSString *errorStr = NSLocalizedString(@"Password should be at least %ld characters", @"Error when your password isn't long enough"); + NSString *notLongEnough = [NSString stringWithFormat:errorStr, (long)self.validator.minimumPasswordLength]; + [self showAuthenticationError:notLongEnough]; + + return NO; + } + + return YES; +} + +- (BOOL)validatePasswordsMatch{ + if (![passwordField.stringValue isEqualToString:confirmField.stringValue]) { + [self earthquake:passwordField]; + [self earthquake:confirmField]; + + return NO; + } + + return YES; +} + +- (BOOL)validateConnection { + if (!authenticator.connected) { + [self showAuthenticationError:NSLocalizedString(@"You're not connected to the internet", @"Error when you're not connected")]; + return NO; + } + + return YES; +} + +- (BOOL)validateSignIn { + [self clearAuthenticationError]; + return [self validateConnection] && + [self validateUsername] && + [self validatePasswordSecurity]; +} + +- (BOOL)validateSignUp { + [self clearAuthenticationError]; + return [self validateConnection] && + [self validateUsername] && + [self validatePasswordsMatch] && + [self validatePasswordSecurity]; +} + +- (void)earthquake:(NSView *)view { + // Quick and dirty way to prevent overlapping animations that can move the view + if (earthquaking) + return; + + earthquaking = YES; + CAKeyframeAnimation *shakeAnimation = [self shakeAnimation:view.frame]; + [view setAnimations:@{@"frameOrigin":shakeAnimation}]; + [[view animator] setFrameOrigin:view.frame.origin]; +} + +- (CAKeyframeAnimation *)shakeAnimation:(NSRect)frame +{ + // From http://www.cimgf.com/2008/02/27/core-animation-tutorial-window-shake-effect/ + int numberOfShakes = 4; + CGFloat vigourOfShake = 0.02; + CGFloat durationOfShake = 0.5; + + CAKeyframeAnimation *shakeAnimation = [CAKeyframeAnimation animation]; + + CGMutablePathRef shakePath = CGPathCreateMutable(); + CGPathMoveToPoint(shakePath, NULL, NSMinX(frame), NSMinY(frame)); + int index; + for (index = 0; index < numberOfShakes; ++index) + { + CGPathAddLineToPoint(shakePath, NULL, NSMinX(frame) - frame.size.width * vigourOfShake, NSMinY(frame)); + CGPathAddLineToPoint(shakePath, NULL, NSMinX(frame) + frame.size.width * vigourOfShake, NSMinY(frame)); + } + CGPathCloseSubpath(shakePath); + shakeAnimation.path = shakePath; + shakeAnimation.duration = durationOfShake; + shakeAnimation.delegate = self; + + return shakeAnimation; +} + +- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag { + earthquaking = NO; +} + +- (void)showAuthenticationError:(NSString *)errorMessage { + [errorField setStringValue:errorMessage]; +} + +- (void)showAuthenticationErrorForCode:(NSUInteger)responseCode { + switch (responseCode) { + case 409: + // User already exists + [self showAuthenticationError:NSLocalizedString(@"That email is already being used", @"Error when address is in use")]; + [self earthquake:usernameField]; + [[self window] makeFirstResponder:usernameField]; + break; + case 401: + // Bad email or password + [self showAuthenticationError:NSLocalizedString(@"Bad email or password", @"Error for bad email or password")]; + break; + + default: + // General network problem + [self showAuthenticationError:NSLocalizedString(@"We're having problems. Please try again soon.", @"Generic error")]; + break; + } +} + +- (void)clearAuthenticationError { + [errorField setStringValue:@""]; +} + +#pragma mark NSTextView delegates + +- (BOOL)control:(NSControl *)control textView:(NSTextView *)fieldEditor doCommandBySelector:(SEL)commandSelector +{ + BOOL retval = NO; + + if (commandSelector == @selector(insertNewline:)) { + if (signingIn && [control isEqual:passwordField.textField]) { + [self signInAction:nil]; + } else if (!signingIn && [control isEqual:confirmField.textField]) { + [self signUpAction:nil]; + } + } + + return retval; +} + +- (BOOL)control:(NSControl *)control textShouldBeginEditing:(NSText *)fieldEditor { + [self.window.contentView setNeedsDisplay:YES]; + return YES; +} + +- (void)controlTextDidChange:(NSNotification *)obj { + // Intercept return and invoke actions + NSEvent *currentEvent = [NSApp currentEvent]; + if (currentEvent.type == NSKeyDown && [currentEvent.charactersIgnoringModifiers isEqualToString:@"\r"]) { + if (signingIn && [[obj object] isEqual:passwordField.textField]) { + [self signInAction:nil]; + } else if (!signingIn && [[obj object] isEqual:confirmField.textField]) { + [self signUpAction:nil]; + } + } +} + + +@end diff --git a/Simperium-OSX/Simperium-OSX/SPInputBoxView.h b/Simperium-OSX/Simperium-OSX/SPInputBoxView.h deleted file mode 100644 index 43d520c0..00000000 --- a/Simperium-OSX/Simperium-OSX/SPInputBoxView.h +++ /dev/null @@ -1,13 +0,0 @@ -// -// SPInputBoxView.h -// Simplenote-OSX -// -// Created by Rainieri Ventura on 2/24/12. -// Copyright (c) 2012 Simperium. All rights reserved. -// - -#import - -@interface SPInputBoxView : NSView - -@end diff --git a/Simperium-OSX/Simperium-OSX/SPInputBoxView.m b/Simperium-OSX/Simperium-OSX/SPInputBoxView.m deleted file mode 100644 index 455a118f..00000000 --- a/Simperium-OSX/Simperium-OSX/SPInputBoxView.m +++ /dev/null @@ -1,29 +0,0 @@ -// -// SPInputBoxView.m -// Simplenote-OSX -// -// Created by Rainieri Ventura on 2/24/12. -// Copyright (c) 2012 Simperium. All rights reserved. -// - -#import "SPInputBoxView.h" - -@implementation SPInputBoxView - -- (id)initWithFrame:(NSRect)frame -{ - self = [super initWithFrame:frame]; - if (self) { - // Initialization code here. - } - - return self; -} - -- (void)drawRect:(NSRect)dirtyRect -{ - [[NSColor whiteColor] setFill]; - NSRectFill(dirtyRect); -} - -@end diff --git a/Simperium-OSX/Simperium-OSX/SPSigninButtonCell.h b/Simperium-OSX/Simperium-OSX/SPSigninButtonCell.h deleted file mode 100644 index 1e12832b..00000000 --- a/Simperium-OSX/Simperium-OSX/SPSigninButtonCell.h +++ /dev/null @@ -1,14 +0,0 @@ -// -// SPSigninButton.h -// Simplenote-OSX -// -// Created by Rainieri Ventura on 2/23/12. -// Copyright (c) 2012 Simperium. All rights reserved. -// - -#import - -@interface SPSigninButtonCell : NSButtonCell - - -@end diff --git a/Simperium-OSX/Simperium-OSX/SPSigninButtonCell.m b/Simperium-OSX/Simperium-OSX/SPSigninButtonCell.m deleted file mode 100644 index 1462ff2a..00000000 --- a/Simperium-OSX/Simperium-OSX/SPSigninButtonCell.m +++ /dev/null @@ -1,157 +0,0 @@ -// -// SPSigninButton.m -// Simplenote-OSX -// -// Created by Rainieri Ventura on 2/23/12. -// Copyright (c) 2012 Simperium. All rights reserved. -// - -#import "SPSigninButtonCell.h" - -@implementation SPSigninButtonCell - -- (void)drawBezelWithFrame:(NSRect)frame inView:(NSView *)controlView -{ - NSGraphicsContext *ctx = [NSGraphicsContext currentContext]; - - CGFloat roundedRadius = 3.0f; - - // Outer stroke (drawn as gradient) - - [ctx saveGraphicsState]; - NSBezierPath *outerClip = [NSBezierPath bezierPathWithRoundedRect:frame - xRadius:roundedRadius - yRadius:roundedRadius]; - [outerClip setClip]; - - NSGradient *outerGradient = [[NSGradient alloc] initWithColorsAndLocations: - [NSColor colorWithDeviceWhite:0.20f alpha:1.0f], 0.0f, - [NSColor colorWithDeviceWhite:0.21f alpha:1.0f], 1.0f, - nil]; - - [outerGradient drawInRect:[outerClip bounds] angle:90.0f]; - [ctx restoreGraphicsState]; - - // Background gradient - - [ctx saveGraphicsState]; - NSBezierPath *backgroundPath = - [NSBezierPath bezierPathWithRoundedRect:NSInsetRect(frame, 1.3f, 1.3f) - xRadius:roundedRadius - yRadius:roundedRadius]; - [backgroundPath setClip]; - - NSGradient *backgroundGradient = [[NSGradient alloc] initWithColorsAndLocations: - [NSColor colorWithCalibratedRed:0.0/255.0 - green:105.0/255.0 - blue:176.0/255.0 - alpha:1.0f], 0.0f, - [NSColor colorWithCalibratedRed:42.0/255.0 - green:150.0/255.0 - blue:221.0/255.0 - alpha:1.0f], 1.0f, - nil]; - - [backgroundGradient drawInRect:[backgroundPath bounds] angle:270.0f]; - [ctx restoreGraphicsState]; - - // Dark stroke - - [ctx saveGraphicsState]; - [[NSColor colorWithDeviceWhite:0.12f alpha:1.0f] setStroke]; - [[NSBezierPath bezierPathWithRoundedRect:NSInsetRect(frame, 1.5f, 1.5f) - xRadius:roundedRadius - yRadius:roundedRadius] stroke]; - [ctx restoreGraphicsState]; - - // Inner light stroke - - [ctx saveGraphicsState]; - [[NSColor colorWithDeviceWhite:1.0f alpha:0.05f] setStroke]; - [[NSBezierPath bezierPathWithRoundedRect:NSInsetRect(frame, 2.5f, 2.5f) - xRadius:roundedRadius - yRadius:roundedRadius] stroke]; - [ctx restoreGraphicsState]; - - // Draw darker overlay if button is pressed - - if([self isHighlighted]) { - [ctx saveGraphicsState]; - [[NSBezierPath bezierPathWithRoundedRect:NSInsetRect(frame, 2.0f, 2.0f) - xRadius:roundedRadius - yRadius:roundedRadius] setClip]; - [[NSColor colorWithCalibratedWhite:0.0f alpha:0.35] setFill]; - NSRectFillUsingOperation(frame, NSCompositeSourceOver); - [ctx restoreGraphicsState]; - } -} - -- (void)drawImage:(NSImage*)image withFrame:(NSRect)frame inView:(NSView*)controlView -{ - NSGraphicsContext *ctx = [NSGraphicsContext currentContext]; - CGContextRef contextRef = [ctx graphicsPort]; - - NSData *data = [image TIFFRepresentation]; // open for suggestions - CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL); - if(source) { - CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, 0, NULL); - CFRelease(source); - - // Draw shadow 1px below image - - CGContextSaveGState(contextRef); - { - NSRect rect = NSOffsetRect(frame, 0.0f, 1.0f); - CGFloat white = [self isHighlighted] ? 0.2f : 0.35f; - CGContextClipToMask(contextRef, NSRectToCGRect(rect), imageRef); - [[NSColor colorWithDeviceWhite:white alpha:1.0f] setFill]; - NSRectFill(rect); - } - CGContextRestoreGState(contextRef); - - // Draw image - - CGContextSaveGState(contextRef); - { - NSRect rect = frame; - CGContextClipToMask(contextRef, NSRectToCGRect(rect), imageRef); - [[NSColor colorWithDeviceWhite:0.1f alpha:1.0f] setFill]; - NSRectFill(rect); - } - CGContextRestoreGState(contextRef); - - CFRelease(imageRef); - } - -} - -- (NSRect)drawTitle:(NSAttributedString*)title withFrame:(NSRect)frame inView:(NSView*)controlView -{ - - NSMutableAttributedString *attrString = [title mutableCopy]; - - [attrString beginEditing]; - - NSMutableParagraphStyle *ps = [[NSMutableParagraphStyle alloc] init]; - [ps setAlignment:NSCenterTextAlignment]; - - NSDictionary *attributesBtn = [NSDictionary dictionaryWithObjectsAndKeys: - [NSFont fontWithName:@"Helvetica Neue" size:12], NSFontAttributeName, - [NSColor whiteColor], NSForegroundColorAttributeName, - ps, NSParagraphStyleAttributeName, - nil]; - - NSAttributedString *coloredStringBtn = [[NSAttributedString alloc] - initWithString:[title string] attributes:attributesBtn]; - - [attrString endEditing]; - - NSRect r = [super drawTitle:coloredStringBtn withFrame:NSOffsetRect(frame, 0.0f, -1.5f) inView:controlView]; - - [NSGraphicsContext restoreGraphicsState]; - - return r; - -} - -@end diff --git a/Simperium-OSX/Simperium-OSX/SPSpotLightView.h b/Simperium-OSX/Simperium-OSX/SPSpotLightView.h deleted file mode 100644 index 6aba9b59..00000000 --- a/Simperium-OSX/Simperium-OSX/SPSpotLightView.h +++ /dev/null @@ -1,13 +0,0 @@ -// -// SPSpotLightView.h -// Simplenote-OSX -// -// Created by Rainieri Ventura on 2/23/12. -// Copyright (c) 2012 Simperium. All rights reserved. -// - -#import - -@interface SPSpotLightView : NSView - -@end diff --git a/Simperium-OSX/Simperium-OSX/SPSpotLightView.m b/Simperium-OSX/Simperium-OSX/SPSpotLightView.m deleted file mode 100644 index feb88678..00000000 --- a/Simperium-OSX/Simperium-OSX/SPSpotLightView.m +++ /dev/null @@ -1,42 +0,0 @@ -// -// SPSpotLightView.m -// Simplenote-OSX -// -// Created by Rainieri Ventura on 2/23/12. -// Copyright (c) 2012 Simperium. All rights reserved. -// - -#import "SPSpotLightView.h" - -@implementation SPSpotLightView - -- (id)initWithFrame:(NSRect)frame -{ - self = [super initWithFrame:frame]; - if (self) { - // Initialization code here. - } - - return self; -} - -- (void)drawRect:(NSRect)rect -{ - NSColor *startColor = [NSColor colorWithCalibratedWhite:0.0f alpha:0.2f]; - NSColor *endColor = [NSColor colorWithCalibratedWhite:1.0f alpha:0.2f]; - - NSRect bounds = [self bounds]; - NSGradient* aGradient = [[NSGradient alloc] - initWithStartingColor:startColor - endingColor:endColor]; - - NSPoint centerPoint = NSMakePoint(NSMidX(bounds), NSMidY(bounds)); - NSPoint otherPoint = NSMakePoint(centerPoint.x - 60.0, centerPoint.y + 95.0); - CGFloat firstRadius = MIN( ((bounds.size.width/2.0) + 100.0), - ((bounds.size.height/2.0) + 100.0) ); - [aGradient drawFromCenter:centerPoint radius:firstRadius - toCenter:otherPoint radius:5.0 - options:NSGradientDrawsAfterEndingLocation]; -} - -@end diff --git a/Simperium-OSX/Simperium-OSX/SPToggleAuthCell.h b/Simperium-OSX/Simperium-OSX/SPToggleAuthCell.h deleted file mode 100644 index dfade7af..00000000 --- a/Simperium-OSX/Simperium-OSX/SPToggleAuthCell.h +++ /dev/null @@ -1,13 +0,0 @@ -// -// SPToggleAuthCell.h -// Simplenote-OSX -// -// Created by Rainieri Ventura on 3/19/12. -// Copyright (c) 2012 Simperium. All rights reserved. -// - -#import - -@interface SPToggleAuthCell : NSButtonCell - -@end diff --git a/Simperium-OSX/Simperium-OSX/SPToggleAuthCell.m b/Simperium-OSX/Simperium-OSX/SPToggleAuthCell.m deleted file mode 100644 index 4e51bc32..00000000 --- a/Simperium-OSX/Simperium-OSX/SPToggleAuthCell.m +++ /dev/null @@ -1,43 +0,0 @@ -// -// SPToggleAuthCell.m -// Simplenote-OSX -// -// Created by Rainieri Ventura on 3/19/12. -// Copyright (c) 2012 Simperium. All rights reserved. -// - -#import "SPToggleAuthCell.h" - -@implementation SPToggleAuthCell - -- (NSRect)drawTitle:(NSAttributedString*)title withFrame:(NSRect)frame inView:(NSView*)controlView -{ - NSMutableAttributedString *attrString = [title mutableCopy]; - - [attrString beginEditing]; - - NSMutableParagraphStyle *ps = [[NSMutableParagraphStyle alloc] init]; - [ps setAlignment:NSCenterTextAlignment]; - - NSNumber *us = [NSNumber numberWithInt:NSUnderlineStyleSingle]; - - NSDictionary *attributesBtn = [NSDictionary dictionaryWithObjectsAndKeys: - [NSFont fontWithName:@"Helvetica Neue" size:12], NSFontAttributeName, - [NSColor whiteColor], NSForegroundColorAttributeName, - ps, NSParagraphStyleAttributeName, - us, NSUnderlineStyleAttributeName, - nil]; - - NSAttributedString *coloredStringBtn = [[NSAttributedString alloc] - initWithString:[title string] attributes:attributesBtn]; - - [attrString endEditing]; - - NSRect r = [super drawTitle:coloredStringBtn withFrame:NSOffsetRect(frame, 0.0f, -1.5f) inView:controlView]; - - [NSGraphicsContext restoreGraphicsState]; - - return r; -} - -@end diff --git a/Simperium.podspec b/Simperium.podspec new file mode 100644 index 00000000..70ebdcf6 --- /dev/null +++ b/Simperium.podspec @@ -0,0 +1,58 @@ +Pod::Spec.new do |s| + s.name = "Simperium" + s.version = "0.5.0" + s.summary = "Simperium libraries." + s.description = "Simperium is a simple way for developers to move data as it changes, instantly and automatically." + s.homepage = "https://github.com/Simperium/simperium-ios" + s.license = { :type => 'MIT', :file => 'LICENSE' } + s.author = { "Simperium" => "contact@simperium.com" } + + s.source = { :git => "https://github.com/Simperium/simperium-ios.git", :tag => "v{s.version}" } + + s.ios.deployment_target = '5.0' + s.osx.deployment_target = '10.7' + + s.source_files = 'Simperium/*.{h,m}', 'External/SPReachability/*', 'External/SFHFKeychainUtils/*' + s.osx.source_files = 'Simperium-OSX/**/*.{h,m}' + + s.exclude_files = 'Simperium/SPS3Manager.{h,m}' + s.ios.exclude_files = 'Simperium/SPAuthView.{h,m}' + s.osx.exclude_files = 'Simperium/SPLoginViewController.{h,m}', 'Simperium/UIImage+Simperium.{h,m}' + + # If you do not explicitly set the list of public header files, + # all headers of source_files will be made public. + # + # s.public_header_files = 'Simperium/**/*.h' + + s.ios.resources = 'Resources/LoginView.xib', 'Resources/LoginView-iPad.xib', 'Resources/View.xib' + s.osx.resources = 'Resources/AuthWindow.xib' + + # Specify a list of frameworks that the application needs to link + # against for this Pod to work. + # + # s.framework = 'SomeFramework' + s.ios.frameworks = 'Security', 'MobileCoreServices', 'CoreData', 'CFNetwork', 'SystemConfiguration', 'Foundation', 'UIKit', 'CoreGraphics' + + # Specify a list of libraries that the application needs to link + # against for this Pod to work. + # + # s.library = 'iconv' + # s.libraries = 'iconv', 'xml2' + + s.requires_arc = true + + # If you need to specify any other build settings, add them to the + # xcconfig hash. + # + # s.xcconfig = { 'HEADER_SEARCH_PATHS' => '$(SDKROOT)/usr/include/libxml2' } + + # Finally, specify any Pods that this Pod depends on. + # + s.dependency 'ASIHTTPRequest', '~> 1.8.1' + s.dependency 'CocoaLumberjack' + s.dependency 'Google-Diff-Match-Patch' + s.dependency 'JRSwizzle' + s.dependency 'JSONKit' + s.dependency 'SocketRocket' + +end diff --git a/Simperium.xcodeproj/project.pbxproj b/Simperium.xcodeproj/project.pbxproj index d04d7b3f..484bb7f2 100644 --- a/Simperium.xcodeproj/project.pbxproj +++ b/Simperium.xcodeproj/project.pbxproj @@ -22,20 +22,25 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ - 260722A315BF26A300786BD4 /* DDASLLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 26905D95142CF97D001ACF20 /* DDASLLogger.m */; settings = {COMPILER_FLAGS = "-fobjc-arc"; }; }; - 260722A515BF26A700786BD4 /* DDAbstractDatabaseLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 26905D93142CF97D001ACF20 /* DDAbstractDatabaseLogger.m */; settings = {COMPILER_FLAGS = "-fobjc-arc"; }; }; - 260722A615BF26AB00786BD4 /* DDFileLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 26905D97142CF97D001ACF20 /* DDFileLogger.m */; settings = {COMPILER_FLAGS = "-fobjc-arc"; }; }; + 1854D50F1799F6D2006C4211 /* NSArray+Simperium.h in Headers */ = {isa = PBXBuildFile; fileRef = 1854D50D1799F6D2006C4211 /* NSArray+Simperium.h */; }; + 1854D5101799F6D2006C4211 /* NSArray+Simperium.m in Sources */ = {isa = PBXBuildFile; fileRef = 1854D50E1799F6D2006C4211 /* NSArray+Simperium.m */; }; + 1854D5111799F6D2006C4211 /* NSArray+Simperium.m in Sources */ = {isa = PBXBuildFile; fileRef = 1854D50E1799F6D2006C4211 /* NSArray+Simperium.m */; }; + 1854D514179A18B1006C4211 /* DiffMatchPatchArrayTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1854D513179A18B1006C4211 /* DiffMatchPatchArrayTests.m */; }; + 246F22C8179B3EDB009547B6 /* SimperiumCoreDataTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 246F22C7179B3EDB009547B6 /* SimperiumCoreDataTests.m */; }; + 260722A315BF26A300786BD4 /* DDASLLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 26905D95142CF97D001ACF20 /* DDASLLogger.m */; }; + 260722A515BF26A700786BD4 /* DDAbstractDatabaseLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 26905D93142CF97D001ACF20 /* DDAbstractDatabaseLogger.m */; }; + 260722A615BF26AB00786BD4 /* DDFileLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 26905D97142CF97D001ACF20 /* DDFileLogger.m */; }; 2611E805149FB91100121D6E /* JSONKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 2611E803149FB91100121D6E /* JSONKit.h */; }; - 2611E806149FB91100121D6E /* JSONKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 2611E804149FB91100121D6E /* JSONKit.m */; }; - 2618987B15D2EB310013CBC9 /* base64.c in Sources */ = {isa = PBXBuildFile; fileRef = 2618987415D2EB310013CBC9 /* base64.c */; }; + 2611E806149FB91100121D6E /* JSONKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 2611E804149FB91100121D6E /* JSONKit.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 2618987B15D2EB310013CBC9 /* base64.c in Sources */ = {isa = PBXBuildFile; fileRef = 2618987415D2EB310013CBC9 /* base64.c */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 2618987C15D2EB310013CBC9 /* base64.h in Headers */ = {isa = PBXBuildFile; fileRef = 2618987515D2EB310013CBC9 /* base64.h */; }; 2618987D15D2EB310013CBC9 /* NSData+SRB64Additions.h in Headers */ = {isa = PBXBuildFile; fileRef = 2618987615D2EB310013CBC9 /* NSData+SRB64Additions.h */; }; - 2618987E15D2EB310013CBC9 /* NSData+SRB64Additions.m in Sources */ = {isa = PBXBuildFile; fileRef = 2618987715D2EB310013CBC9 /* NSData+SRB64Additions.m */; settings = {COMPILER_FLAGS = "-fobjc-arc"; }; }; + 2618987E15D2EB310013CBC9 /* NSData+SRB64Additions.m in Sources */ = {isa = PBXBuildFile; fileRef = 2618987715D2EB310013CBC9 /* NSData+SRB64Additions.m */; }; 2618987F15D2EB310013CBC9 /* SocketRocket-Prefix.pch in Headers */ = {isa = PBXBuildFile; fileRef = 2618987815D2EB310013CBC9 /* SocketRocket-Prefix.pch */; }; 2618988015D2EB310013CBC9 /* SRWebSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 2618987915D2EB310013CBC9 /* SRWebSocket.h */; }; - 2618988115D2EB310013CBC9 /* SRWebSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 2618987A15D2EB310013CBC9 /* SRWebSocket.m */; settings = {COMPILER_FLAGS = "-fobjc-arc"; }; }; - 2618988515D2EC130013CBC9 /* SPWebSocketManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 2618988315D2EC130013CBC9 /* SPWebSocketManager.h */; }; - 2618988615D2EC130013CBC9 /* SPWebSocketManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 2618988415D2EC130013CBC9 /* SPWebSocketManager.m */; }; + 2618988115D2EB310013CBC9 /* SRWebSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 2618987A15D2EB310013CBC9 /* SRWebSocket.m */; }; + 2618988515D2EC130013CBC9 /* SPWebSocketInterface.h in Headers */ = {isa = PBXBuildFile; fileRef = 2618988315D2EC130013CBC9 /* SPWebSocketInterface.h */; }; + 2618988615D2EC130013CBC9 /* SPWebSocketInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = 2618988415D2EC130013CBC9 /* SPWebSocketInterface.m */; }; 2622FD08147F249400C8EEB4 /* SPMemberText.h in Headers */ = {isa = PBXBuildFile; fileRef = 2622FD06147F249300C8EEB4 /* SPMemberText.h */; }; 2622FD09147F249400C8EEB4 /* SPMemberText.m in Sources */ = {isa = PBXBuildFile; fileRef = 2622FD07147F249400C8EEB4 /* SPMemberText.m */; }; 2622FD0C147F24A700C8EEB4 /* SPMemberDate.h in Headers */ = {isa = PBXBuildFile; fileRef = 2622FD0A147F24A700C8EEB4 /* SPMemberDate.h */; }; @@ -54,7 +59,7 @@ 2622FD25147F251200C8EEB4 /* SPMemberList.m in Sources */ = {isa = PBXBuildFile; fileRef = 2622FD23147F251100C8EEB4 /* SPMemberList.m */; }; 2622FD28147F251D00C8EEB4 /* SPMemberBase64.h in Headers */ = {isa = PBXBuildFile; fileRef = 2622FD26147F251C00C8EEB4 /* SPMemberBase64.h */; }; 2622FD29147F251D00C8EEB4 /* SPMemberBase64.m in Sources */ = {isa = PBXBuildFile; fileRef = 2622FD27147F251D00C8EEB4 /* SPMemberBase64.m */; }; - 2622FD2D147F32A500C8EEB4 /* SPNetworkProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = 2622FD2B147F32A500C8EEB4 /* SPNetworkProvider.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2622FD2D147F32A500C8EEB4 /* SPNetworkInterface.h in Headers */ = {isa = PBXBuildFile; fileRef = 2622FD2B147F32A500C8EEB4 /* SPNetworkInterface.h */; settings = {ATTRIBUTES = (Public, ); }; }; 264AE50E15D3092200E5E04E /* libicucore.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 264AE50D15D3092200E5E04E /* libicucore.dylib */; }; 264AE51115D3094D00E5E04E /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 264AE51015D3094D00E5E04E /* Security.framework */; }; 264AE51A15D45E2A00E5E04E /* SPWebSocketChannel.h in Headers */ = {isa = PBXBuildFile; fileRef = 264AE51815D45E2A00E5E04E /* SPWebSocketChannel.h */; }; @@ -77,56 +82,55 @@ 264CD943135DFE6D00C51BAD /* SPGhost.m in Sources */ = {isa = PBXBuildFile; fileRef = 264CD933135DFE6D00C51BAD /* SPGhost.m */; }; 264CD944135DFE6D00C51BAD /* SPMember.h in Headers */ = {isa = PBXBuildFile; fileRef = 264CD934135DFE6D00C51BAD /* SPMember.h */; }; 264CD945135DFE6D00C51BAD /* SPMember.m in Sources */ = {isa = PBXBuildFile; fileRef = 264CD935135DFE6D00C51BAD /* SPMember.m */; }; - 264CD946135DFE6D00C51BAD /* SPHttpManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 264CD936135DFE6D00C51BAD /* SPHttpManager.h */; }; - 264CD947135DFE6D00C51BAD /* SPHttpManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 264CD937135DFE6D00C51BAD /* SPHttpManager.m */; }; + 264CD946135DFE6D00C51BAD /* SPHttpInterface.h in Headers */ = {isa = PBXBuildFile; fileRef = 264CD936135DFE6D00C51BAD /* SPHttpInterface.h */; }; + 264CD947135DFE6D00C51BAD /* SPHttpInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = 264CD937135DFE6D00C51BAD /* SPHttpInterface.m */; }; 264CD980135DFE7E00C51BAD /* ASIAuthenticationDialog.h in Headers */ = {isa = PBXBuildFile; fileRef = 264CD94D135DFE7E00C51BAD /* ASIAuthenticationDialog.h */; }; - 264CD981135DFE7E00C51BAD /* ASIAuthenticationDialog.m in Sources */ = {isa = PBXBuildFile; fileRef = 264CD94E135DFE7E00C51BAD /* ASIAuthenticationDialog.m */; }; + 264CD981135DFE7E00C51BAD /* ASIAuthenticationDialog.m in Sources */ = {isa = PBXBuildFile; fileRef = 264CD94E135DFE7E00C51BAD /* ASIAuthenticationDialog.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 264CD982135DFE7E00C51BAD /* ASICacheDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 264CD94F135DFE7E00C51BAD /* ASICacheDelegate.h */; }; 264CD983135DFE7E00C51BAD /* ASIDataCompressor.h in Headers */ = {isa = PBXBuildFile; fileRef = 264CD950135DFE7E00C51BAD /* ASIDataCompressor.h */; }; - 264CD984135DFE7E00C51BAD /* ASIDataCompressor.m in Sources */ = {isa = PBXBuildFile; fileRef = 264CD951135DFE7E00C51BAD /* ASIDataCompressor.m */; }; + 264CD984135DFE7E00C51BAD /* ASIDataCompressor.m in Sources */ = {isa = PBXBuildFile; fileRef = 264CD951135DFE7E00C51BAD /* ASIDataCompressor.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 264CD985135DFE7E00C51BAD /* ASIDataDecompressor.h in Headers */ = {isa = PBXBuildFile; fileRef = 264CD952135DFE7E00C51BAD /* ASIDataDecompressor.h */; }; - 264CD986135DFE7E00C51BAD /* ASIDataDecompressor.m in Sources */ = {isa = PBXBuildFile; fileRef = 264CD953135DFE7E00C51BAD /* ASIDataDecompressor.m */; }; + 264CD986135DFE7E00C51BAD /* ASIDataDecompressor.m in Sources */ = {isa = PBXBuildFile; fileRef = 264CD953135DFE7E00C51BAD /* ASIDataDecompressor.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 264CD987135DFE7E00C51BAD /* ASIDownloadCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 264CD954135DFE7E00C51BAD /* ASIDownloadCache.h */; }; - 264CD988135DFE7E00C51BAD /* ASIDownloadCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 264CD955135DFE7E00C51BAD /* ASIDownloadCache.m */; }; + 264CD988135DFE7E00C51BAD /* ASIDownloadCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 264CD955135DFE7E00C51BAD /* ASIDownloadCache.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 264CD989135DFE7E00C51BAD /* ASIFormDataRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 264CD956135DFE7E00C51BAD /* ASIFormDataRequest.h */; }; - 264CD98A135DFE7E00C51BAD /* ASIFormDataRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 264CD957135DFE7E00C51BAD /* ASIFormDataRequest.m */; }; + 264CD98A135DFE7E00C51BAD /* ASIFormDataRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 264CD957135DFE7E00C51BAD /* ASIFormDataRequest.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 264CD98B135DFE7E00C51BAD /* ASIHTTPRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 264CD958135DFE7E00C51BAD /* ASIHTTPRequest.h */; }; - 264CD98C135DFE7E00C51BAD /* ASIHTTPRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 264CD959135DFE7E00C51BAD /* ASIHTTPRequest.m */; }; + 264CD98C135DFE7E00C51BAD /* ASIHTTPRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 264CD959135DFE7E00C51BAD /* ASIHTTPRequest.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 264CD98D135DFE7E00C51BAD /* ASIHTTPRequestConfig.h in Headers */ = {isa = PBXBuildFile; fileRef = 264CD95A135DFE7E00C51BAD /* ASIHTTPRequestConfig.h */; }; 264CD98E135DFE7E00C51BAD /* ASIHTTPRequestDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 264CD95B135DFE7E00C51BAD /* ASIHTTPRequestDelegate.h */; }; 264CD98F135DFE7E00C51BAD /* ASIInputStream.h in Headers */ = {isa = PBXBuildFile; fileRef = 264CD95C135DFE7E00C51BAD /* ASIInputStream.h */; }; - 264CD990135DFE7E00C51BAD /* ASIInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = 264CD95D135DFE7E00C51BAD /* ASIInputStream.m */; }; + 264CD990135DFE7E00C51BAD /* ASIInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = 264CD95D135DFE7E00C51BAD /* ASIInputStream.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 264CD991135DFE7E00C51BAD /* ASINetworkQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = 264CD95E135DFE7E00C51BAD /* ASINetworkQueue.h */; }; - 264CD992135DFE7E00C51BAD /* ASINetworkQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = 264CD95F135DFE7E00C51BAD /* ASINetworkQueue.m */; }; + 264CD992135DFE7E00C51BAD /* ASINetworkQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = 264CD95F135DFE7E00C51BAD /* ASINetworkQueue.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 264CD993135DFE7E00C51BAD /* ASIProgressDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 264CD960135DFE7E00C51BAD /* ASIProgressDelegate.h */; }; 264CD994135DFE7E00C51BAD /* ASIReachability.h in Headers */ = {isa = PBXBuildFile; fileRef = 264CD962135DFE7E00C51BAD /* ASIReachability.h */; }; - 264CD995135DFE7E00C51BAD /* ASIReachability.m in Sources */ = {isa = PBXBuildFile; fileRef = 264CD963135DFE7E00C51BAD /* ASIReachability.m */; }; + 264CD995135DFE7E00C51BAD /* ASIReachability.m in Sources */ = {isa = PBXBuildFile; fileRef = 264CD963135DFE7E00C51BAD /* ASIReachability.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 264CD996135DFE7E00C51BAD /* DiffMatchPatch.h in Headers */ = {isa = PBXBuildFile; fileRef = 264CD965135DFE7E00C51BAD /* DiffMatchPatch.h */; }; - 264CD997135DFE7E00C51BAD /* DiffMatchPatch.m in Sources */ = {isa = PBXBuildFile; fileRef = 264CD966135DFE7E00C51BAD /* DiffMatchPatch.m */; }; - 264CD998135DFE7E00C51BAD /* DiffMatchPatchCFUtilities.c in Sources */ = {isa = PBXBuildFile; fileRef = 264CD967135DFE7E00C51BAD /* DiffMatchPatchCFUtilities.c */; }; + 264CD997135DFE7E00C51BAD /* DiffMatchPatch.m in Sources */ = {isa = PBXBuildFile; fileRef = 264CD966135DFE7E00C51BAD /* DiffMatchPatch.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 264CD998135DFE7E00C51BAD /* DiffMatchPatchCFUtilities.c in Sources */ = {isa = PBXBuildFile; fileRef = 264CD967135DFE7E00C51BAD /* DiffMatchPatchCFUtilities.c */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 264CD999135DFE7E00C51BAD /* DiffMatchPatchCFUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 264CD968135DFE7E00C51BAD /* DiffMatchPatchCFUtilities.h */; }; 264CD99A135DFE7E00C51BAD /* MinMaxMacros.h in Headers */ = {isa = PBXBuildFile; fileRef = 264CD969135DFE7E00C51BAD /* MinMaxMacros.h */; }; 264CD99B135DFE7E00C51BAD /* NSMutableDictionary+DMPExtensions.h in Headers */ = {isa = PBXBuildFile; fileRef = 264CD96A135DFE7E00C51BAD /* NSMutableDictionary+DMPExtensions.h */; }; - 264CD99C135DFE7E00C51BAD /* NSMutableDictionary+DMPExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = 264CD96B135DFE7E00C51BAD /* NSMutableDictionary+DMPExtensions.m */; }; + 264CD99C135DFE7E00C51BAD /* NSMutableDictionary+DMPExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = 264CD96B135DFE7E00C51BAD /* NSMutableDictionary+DMPExtensions.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 264CD99D135DFE7E00C51BAD /* NSString+JavaSubstring.h in Headers */ = {isa = PBXBuildFile; fileRef = 264CD96C135DFE7E00C51BAD /* NSString+JavaSubstring.h */; }; - 264CD99E135DFE7E00C51BAD /* NSString+JavaSubstring.m in Sources */ = {isa = PBXBuildFile; fileRef = 264CD96D135DFE7E00C51BAD /* NSString+JavaSubstring.m */; }; + 264CD99E135DFE7E00C51BAD /* NSString+JavaSubstring.m in Sources */ = {isa = PBXBuildFile; fileRef = 264CD96D135DFE7E00C51BAD /* NSString+JavaSubstring.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 264CD99F135DFE7E00C51BAD /* NSString+UnicharUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 264CD96E135DFE7E00C51BAD /* NSString+UnicharUtilities.h */; }; - 264CD9A0135DFE7E00C51BAD /* NSString+UnicharUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 264CD96F135DFE7E00C51BAD /* NSString+UnicharUtilities.m */; }; + 264CD9A0135DFE7E00C51BAD /* NSString+UnicharUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 264CD96F135DFE7E00C51BAD /* NSString+UnicharUtilities.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 264CD9A1135DFE7E00C51BAD /* NSString+UriCompatibility.h in Headers */ = {isa = PBXBuildFile; fileRef = 264CD970135DFE7E00C51BAD /* NSString+UriCompatibility.h */; }; - 264CD9A2135DFE7E00C51BAD /* NSString+UriCompatibility.m in Sources */ = {isa = PBXBuildFile; fileRef = 264CD971135DFE7E00C51BAD /* NSString+UriCompatibility.m */; }; + 264CD9A2135DFE7E00C51BAD /* NSString+UriCompatibility.m in Sources */ = {isa = PBXBuildFile; fileRef = 264CD971135DFE7E00C51BAD /* NSString+UriCompatibility.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 264CD9B1135DFFFF00C51BAD /* CoreData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 264CD9B0135DFFFF00C51BAD /* CoreData.framework */; }; - 26563AE414FC3BDE0081C8C1 /* SPAuthenticationManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 26563AE214FC3BDE0081C8C1 /* SPAuthenticationManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 26563AE514FC3BDE0081C8C1 /* SPAuthenticationManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 26563AE314FC3BDE0081C8C1 /* SPAuthenticationManager.m */; }; + 26563AE414FC3BDE0081C8C1 /* SPAuthenticator.h in Headers */ = {isa = PBXBuildFile; fileRef = 26563AE214FC3BDE0081C8C1 /* SPAuthenticator.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 26563AE514FC3BDE0081C8C1 /* SPAuthenticator.m in Sources */ = {isa = PBXBuildFile; fileRef = 26563AE314FC3BDE0081C8C1 /* SPAuthenticator.m */; }; 265D80091475C2F500002A19 /* SPStorage.h in Headers */ = {isa = PBXBuildFile; fileRef = 265D80071475C2F400002A19 /* SPStorage.h */; }; 265D800A1475C2F500002A19 /* SPStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = 265D80081475C2F500002A19 /* SPStorage.m */; }; 265D800D1476188200002A19 /* SPCoreDataStorage.h in Headers */ = {isa = PBXBuildFile; fileRef = 265D800B1476188100002A19 /* SPCoreDataStorage.h */; }; 265D800E1476188200002A19 /* SPCoreDataStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = 265D800C1476188200002A19 /* SPCoreDataStorage.m */; }; 265D80141476461800002A19 /* SPStorageObserver.h in Headers */ = {isa = PBXBuildFile; fileRef = 265D80131476461800002A19 /* SPStorageObserver.h */; settings = {ATTRIBUTES = (); }; }; - 26653CFB15FE5DEA00461A28 /* Reachability.h in Headers */ = {isa = PBXBuildFile; fileRef = 26653CF915FE5DE900461A28 /* Reachability.h */; }; - 26653CFC15FE5DEA00461A28 /* Reachability.m in Sources */ = {isa = PBXBuildFile; fileRef = 26653CFA15FE5DE900461A28 /* Reachability.m */; }; + 26653CFB15FE5DEA00461A28 /* SPReachability.h in Headers */ = {isa = PBXBuildFile; fileRef = 26653CF915FE5DE900461A28 /* SPReachability.h */; }; + 26653CFC15FE5DEA00461A28 /* SPReachability.m in Sources */ = {isa = PBXBuildFile; fileRef = 26653CFA15FE5DE900461A28 /* SPReachability.m */; }; 2669E6E1144D5CA100C33988 /* UIImage+Simperium.h in Headers */ = {isa = PBXBuildFile; fileRef = 2669E6DF144D5CA000C33988 /* UIImage+Simperium.h */; }; 2673D71B1479F4C000B7B6B3 /* SimperiumRelationshipTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2673D7181479F42000B7B6B3 /* SimperiumRelationshipTests.m */; }; - 2673D71E1479F5DB00B7B6B3 /* SimperiumSimpleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2673D71D1479F5DB00B7B6B3 /* SimperiumSimpleTests.m */; }; 2673D722147A018000B7B6B3 /* Post.m in Sources */ = {isa = PBXBuildFile; fileRef = 2673D721147A018000B7B6B3 /* Post.m */; }; 2673D725147A018000B7B6B3 /* Comment.m in Sources */ = {isa = PBXBuildFile; fileRef = 2673D724147A018000B7B6B3 /* Comment.m */; }; 2673D728147A3BEB00B7B6B3 /* TestObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2673D727147A3BEB00B7B6B3 /* TestObject.m */; }; @@ -145,17 +149,14 @@ 267CE907156C0FD20028801C /* SPSchema.h in Headers */ = {isa = PBXBuildFile; fileRef = 267CE8F8156C0FD20028801C /* SPSchema.h */; }; 267CE908156C0FD20028801C /* SPSchema.m in Sources */ = {isa = PBXBuildFile; fileRef = 267CE8F9156C0FD20028801C /* SPSchema.m */; }; 267CE90D156C351B0028801C /* SPStorageProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = 267CE90C156C351B0028801C /* SPStorageProvider.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 268F749014F380D10080C1B8 /* libsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 268F748F14F380D10080C1B8 /* libsqlite3.dylib */; }; 26905D9C142CF97D001ACF20 /* DDAbstractDatabaseLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = 26905D92142CF97D001ACF20 /* DDAbstractDatabaseLogger.h */; }; 26905D9E142CF97D001ACF20 /* DDASLLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = 26905D94142CF97D001ACF20 /* DDASLLogger.h */; }; 26905DA0142CF97D001ACF20 /* DDFileLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = 26905D96142CF97D001ACF20 /* DDFileLogger.h */; }; 26905DA2142CF97D001ACF20 /* DDLog.h in Headers */ = {isa = PBXBuildFile; fileRef = 26905D98142CF97D001ACF20 /* DDLog.h */; }; - 26905DA3142CF97D001ACF20 /* DDLog.m in Sources */ = {isa = PBXBuildFile; fileRef = 26905D99142CF97D001ACF20 /* DDLog.m */; settings = {COMPILER_FLAGS = "-fobjc-arc"; }; }; + 26905DA3142CF97D001ACF20 /* DDLog.m in Sources */ = {isa = PBXBuildFile; fileRef = 26905D99142CF97D001ACF20 /* DDLog.m */; }; 26905DA4142CF97D001ACF20 /* DDTTYLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = 26905D9A142CF97D001ACF20 /* DDTTYLogger.h */; }; - 26905DA5142CF97D001ACF20 /* DDTTYLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 26905D9B142CF97D001ACF20 /* DDTTYLogger.m */; settings = {COMPILER_FLAGS = "-fobjc-arc"; }; }; + 26905DA5142CF97D001ACF20 /* DDTTYLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 26905D9B142CF97D001ACF20 /* DDTTYLogger.m */; }; 26905DA8142D344C001ACF20 /* DDLogDebug.h in Headers */ = {isa = PBXBuildFile; fileRef = 26905DA7142D344C001ACF20 /* DDLogDebug.h */; }; - 2693F85615C99E4300797A5E /* SPSimpleKeyChain.h in Headers */ = {isa = PBXBuildFile; fileRef = 2693F85415C99E4300797A5E /* SPSimpleKeyChain.h */; }; - 2693F85715C99E4300797A5E /* SPSimpleKeyChain.m in Sources */ = {isa = PBXBuildFile; fileRef = 2693F85515C99E4300797A5E /* SPSimpleKeyChain.m */; }; 269A7F3F147355D100CB29EE /* SPChangeProcessor.h in Headers */ = {isa = PBXBuildFile; fileRef = 269A7F3D147355D100CB29EE /* SPChangeProcessor.h */; }; 269A7F4414743A9C00CB29EE /* SPIndexProcessor.h in Headers */ = {isa = PBXBuildFile; fileRef = 269A7F4214743A9C00CB29EE /* SPIndexProcessor.h */; }; 269A7F4514743A9C00CB29EE /* SPIndexProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = 269A7F4314743A9C00CB29EE /* SPIndexProcessor.m */; }; @@ -164,16 +165,14 @@ 269CAC2F146DF86000584C56 /* Test1.xcdatamodeld in Resources */ = {isa = PBXBuildFile; fileRef = 26ED4412146CF03A00C3D7D6 /* Test1.xcdatamodeld */; }; 26B16EFF13B1B79100BEB3E5 /* NSData+Simperium.h in Headers */ = {isa = PBXBuildFile; fileRef = 26B16EFD13B1B78C00BEB3E5 /* NSData+Simperium.h */; }; 26B16F0013B1B79100BEB3E5 /* NSData+Simperium.m in Sources */ = {isa = PBXBuildFile; fileRef = 26B16EFE13B1B78F00BEB3E5 /* NSData+Simperium.m */; }; - 26B16F0313B1B85400BEB3E5 /* NSString+Simperium.h in Headers */ = {isa = PBXBuildFile; fileRef = 26B16F0113B1B85200BEB3E5 /* NSString+Simperium.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 26B16F0413B1B85400BEB3E5 /* NSString+Simperium.m in Sources */ = {isa = PBXBuildFile; fileRef = 26B16F0213B1B85300BEB3E5 /* NSString+Simperium.m */; }; 26B473F714A116B100397E7F /* SPBinaryTransportDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 26B473F614A116B100397E7F /* SPBinaryTransportDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 26B733F015E585630081CF4A /* SPReferenceManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 26B733EE15E585620081CF4A /* SPReferenceManager.h */; }; - 26B733F115E585630081CF4A /* SPReferenceManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 26B733EF15E585620081CF4A /* SPReferenceManager.m */; }; + 26B733F015E585630081CF4A /* SPRelationshipResolver.h in Headers */ = {isa = PBXBuildFile; fileRef = 26B733EE15E585620081CF4A /* SPRelationshipResolver.h */; }; + 26B733F115E585630081CF4A /* SPRelationshipResolver.m in Sources */ = {isa = PBXBuildFile; fileRef = 26B733EF15E585620081CF4A /* SPRelationshipResolver.m */; }; 26C93A6A14F8159300BE99F5 /* libSimperium.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 264CD8FB135DFD7A00C51BAD /* libSimperium.a */; }; - 26C93A6D14F850EA00BE99F5 /* SPLoginViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 26C93A6B14F850EA00BE99F5 /* SPLoginViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 26C93A6E14F850EA00BE99F5 /* SPLoginViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 26C93A6C14F850EA00BE99F5 /* SPLoginViewController.m */; }; + 26C93A6D14F850EA00BE99F5 /* SPAuthenticationViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 26C93A6B14F850EA00BE99F5 /* SPAuthenticationViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 26C93A6E14F850EA00BE99F5 /* SPAuthenticationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 26C93A6C14F850EA00BE99F5 /* SPAuthenticationViewController.m */; }; 26D4D3B3156C3DA300E822E9 /* JRSwizzle.h in Headers */ = {isa = PBXBuildFile; fileRef = 26D4D3B1156C3DA300E822E9 /* JRSwizzle.h */; }; - 26D4D3B4156C3DA300E822E9 /* JRSwizzle.m in Sources */ = {isa = PBXBuildFile; fileRef = 26D4D3B2156C3DA300E822E9 /* JRSwizzle.m */; }; + 26D4D3B4156C3DA300E822E9 /* JRSwizzle.m in Sources */ = {isa = PBXBuildFile; fileRef = 26D4D3B2156C3DA300E822E9 /* JRSwizzle.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 26E83A811395A1FC00195758 /* SPS3Manager.h in Headers */ = {isa = PBXBuildFile; fileRef = 26E83A7F1395A1FC00195758 /* SPS3Manager.h */; settings = {ATTRIBUTES = (Public, ); }; }; 26E83C2613985DBE00195758 /* SPCoreDataExporter.h in Headers */ = {isa = PBXBuildFile; fileRef = 26E83C2413985DBB00195758 /* SPCoreDataExporter.h */; settings = {ATTRIBUTES = (Public, ); }; }; 26E83C2713985DBE00195758 /* SPCoreDataExporter.m in Sources */ = {isa = PBXBuildFile; fileRef = 26E83C2513985DBC00195758 /* SPCoreDataExporter.m */; }; @@ -197,6 +196,33 @@ 26F4BBCE13DCFDA700B8AC56 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 26F4BBCD13DCFDA700B8AC56 /* CFNetwork.framework */; }; 26F4BBD013DCFDB000B8AC56 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 26F4BBCF13DCFDB000B8AC56 /* libz.dylib */; }; 26F4BBD413DCFDC100B8AC56 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 26F4BBD313DCFDC100B8AC56 /* MobileCoreServices.framework */; }; + 460A505E17DE96350070F02B /* SPProcessorNotificationNames.m in Sources */ = {isa = PBXBuildFile; fileRef = 460A505C17DE96350070F02B /* SPProcessorNotificationNames.m */; }; + 460A505F17DE96350070F02B /* SPProcessorNotificationNames.h in Headers */ = {isa = PBXBuildFile; fileRef = 460A505D17DE96350070F02B /* SPProcessorNotificationNames.h */; }; + 464DC336176946ED00E75D05 /* SFHFKeychainUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 464DC334176946ED00E75D05 /* SFHFKeychainUtils.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 464DC337176946ED00E75D05 /* SFHFKeychainUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 464DC335176946ED00E75D05 /* SFHFKeychainUtils.m */; settings = {COMPILER_FLAGS = "-fobjc-arc"; }; }; + 466EB41417BC45F5005F7599 /* SPAuthenticationConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = 466EB41017BC45F5005F7599 /* SPAuthenticationConfiguration.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 466EB41517BC45F5005F7599 /* SPAuthenticationConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = 466EB41117BC45F5005F7599 /* SPAuthenticationConfiguration.m */; }; + 466EB41617BC45F5005F7599 /* SPAuthenticationValidator.h in Headers */ = {isa = PBXBuildFile; fileRef = 466EB41217BC45F5005F7599 /* SPAuthenticationValidator.h */; }; + 466EB41717BC45F5005F7599 /* SPAuthenticationValidator.m in Sources */ = {isa = PBXBuildFile; fileRef = 466EB41317BC45F5005F7599 /* SPAuthenticationValidator.m */; }; + 466EB41A17BC4890005F7599 /* SPAuthenticationButton.h in Headers */ = {isa = PBXBuildFile; fileRef = 466EB41817BC4890005F7599 /* SPAuthenticationButton.h */; }; + 466EB41B17BC4890005F7599 /* SPAuthenticationButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 466EB41917BC4890005F7599 /* SPAuthenticationButton.m */; }; + 466EB41D17BC49DB005F7599 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 466EB41C17BC49DB005F7599 /* QuartzCore.framework */; }; + 466FFEA417CBF53200399652 /* SPNetworkInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = 466FFEA317CBF53200399652 /* SPNetworkInterface.m */; }; + 46A16A8117289FE2003AED55 /* NSString+Simperium.h in Headers */ = {isa = PBXBuildFile; fileRef = 46A16A7F17289FE2003AED55 /* NSString+Simperium.h */; }; + 46A16A8217289FE2003AED55 /* NSString+Simperium.m in Sources */ = {isa = PBXBuildFile; fileRef = 46A16A8017289FE2003AED55 /* NSString+Simperium.m */; }; + 46DD15CA179A6F4B0093678C /* SPMemberJSONList.h in Headers */ = {isa = PBXBuildFile; fileRef = 46DD15C8179A6F4B0093678C /* SPMemberJSONList.h */; }; + 46DD15CB179A6F4B0093678C /* SPMemberJSONList.m in Sources */ = {isa = PBXBuildFile; fileRef = 46DD15C9179A6F4B0093678C /* SPMemberJSONList.m */; }; + 46EE3D3C171C934300E6F0A5 /* SimperiumBasicTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 46EE3D37171C934300E6F0A5 /* SimperiumBasicTests.m */; }; + 46EE3D3D171C934300E6F0A5 /* SimperiumIndexTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 46EE3D39171C934300E6F0A5 /* SimperiumIndexTests.m */; }; + 46EE3D3E171C934300E6F0A5 /* SimperiumOfflineTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 46EE3D3B171C934300E6F0A5 /* SimperiumOfflineTests.m */; }; + 46EE3D41171C975D00E6F0A5 /* SimperiumComplexTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 46EE3D40171C975D00E6F0A5 /* SimperiumComplexTests.m */; }; + 46EE3D44171C978E00E6F0A5 /* SimperiumAuxiliaryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 46EE3D43171C978D00E6F0A5 /* SimperiumAuxiliaryTests.m */; }; + 46EE3D57171D0C8E00E6F0A5 /* SimperiumErrorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 46EE3D56171D0C8E00E6F0A5 /* SimperiumErrorTests.m */; }; + B5F5D39418219CCE0048754C /* DDFileLogger+Simperium.h in Headers */ = {isa = PBXBuildFile; fileRef = B5F5D39218219CCE0048754C /* DDFileLogger+Simperium.h */; }; + B5F5D39518219CCE0048754C /* DDFileLogger+Simperium.m in Sources */ = {isa = PBXBuildFile; fileRef = B5F5D39318219CCE0048754C /* DDFileLogger+Simperium.m */; }; + B5F5D39618219CCE0048754C /* DDFileLogger+Simperium.m in Sources */ = {isa = PBXBuildFile; fileRef = B5F5D39318219CCE0048754C /* DDFileLogger+Simperium.m */; }; + E265DBD017CD3E0A00070550 /* SPTOSViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = E265DBCE17CD3E0A00070550 /* SPTOSViewController.h */; }; + E265DBD117CD3E0A00070550 /* SPTOSViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = E265DBCF17CD3E0A00070550 /* SPTOSViewController.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -217,6 +243,12 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 1854D50D1799F6D2006C4211 /* NSArray+Simperium.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+Simperium.h"; sourceTree = ""; }; + 1854D50E1799F6D2006C4211 /* NSArray+Simperium.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+Simperium.m"; sourceTree = ""; }; + 1854D512179A18B1006C4211 /* DiffMatchPatchArrayTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DiffMatchPatchArrayTests.h; sourceTree = ""; }; + 1854D513179A18B1006C4211 /* DiffMatchPatchArrayTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DiffMatchPatchArrayTests.m; sourceTree = ""; }; + 246F22C6179B3EDA009547B6 /* SimperiumCoreDataTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SimperiumCoreDataTests.h; sourceTree = ""; }; + 246F22C7179B3EDB009547B6 /* SimperiumCoreDataTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SimperiumCoreDataTests.m; sourceTree = ""; }; 2611E803149FB91100121D6E /* JSONKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSONKit.h; sourceTree = ""; }; 2611E804149FB91100121D6E /* JSONKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSONKit.m; sourceTree = ""; }; 2618987415D2EB310013CBC9 /* base64.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = base64.c; sourceTree = ""; }; @@ -226,8 +258,8 @@ 2618987815D2EB310013CBC9 /* SocketRocket-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SocketRocket-Prefix.pch"; sourceTree = ""; }; 2618987915D2EB310013CBC9 /* SRWebSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRWebSocket.h; sourceTree = ""; }; 2618987A15D2EB310013CBC9 /* SRWebSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRWebSocket.m; sourceTree = ""; }; - 2618988315D2EC130013CBC9 /* SPWebSocketManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPWebSocketManager.h; sourceTree = ""; }; - 2618988415D2EC130013CBC9 /* SPWebSocketManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPWebSocketManager.m; sourceTree = ""; }; + 2618988315D2EC130013CBC9 /* SPWebSocketInterface.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPWebSocketInterface.h; sourceTree = ""; }; + 2618988415D2EC130013CBC9 /* SPWebSocketInterface.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPWebSocketInterface.m; sourceTree = ""; }; 2622FD06147F249300C8EEB4 /* SPMemberText.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPMemberText.h; sourceTree = ""; }; 2622FD07147F249400C8EEB4 /* SPMemberText.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPMemberText.m; sourceTree = ""; }; 2622FD0A147F24A700C8EEB4 /* SPMemberDate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPMemberDate.h; sourceTree = ""; }; @@ -246,7 +278,7 @@ 2622FD23147F251100C8EEB4 /* SPMemberList.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPMemberList.m; sourceTree = ""; }; 2622FD26147F251C00C8EEB4 /* SPMemberBase64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPMemberBase64.h; sourceTree = ""; }; 2622FD27147F251D00C8EEB4 /* SPMemberBase64.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPMemberBase64.m; sourceTree = ""; }; - 2622FD2B147F32A500C8EEB4 /* SPNetworkProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPNetworkProvider.h; sourceTree = ""; }; + 2622FD2B147F32A500C8EEB4 /* SPNetworkInterface.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPNetworkInterface.h; sourceTree = ""; }; 264AE50D15D3092200E5E04E /* libicucore.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libicucore.dylib; path = usr/lib/libicucore.dylib; sourceTree = SDKROOT; }; 264AE51015D3094D00E5E04E /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; 264AE51815D45E2A00E5E04E /* SPWebSocketChannel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPWebSocketChannel.h; sourceTree = ""; }; @@ -272,8 +304,8 @@ 264CD933135DFE6D00C51BAD /* SPGhost.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPGhost.m; sourceTree = ""; }; 264CD934135DFE6D00C51BAD /* SPMember.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPMember.h; sourceTree = ""; }; 264CD935135DFE6D00C51BAD /* SPMember.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPMember.m; sourceTree = ""; }; - 264CD936135DFE6D00C51BAD /* SPHttpManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPHttpManager.h; sourceTree = ""; }; - 264CD937135DFE6D00C51BAD /* SPHttpManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPHttpManager.m; sourceTree = ""; }; + 264CD936135DFE6D00C51BAD /* SPHttpInterface.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPHttpInterface.h; sourceTree = ""; }; + 264CD937135DFE6D00C51BAD /* SPHttpInterface.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPHttpInterface.m; sourceTree = ""; }; 264CD94D135DFE7E00C51BAD /* ASIAuthenticationDialog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASIAuthenticationDialog.h; sourceTree = ""; }; 264CD94E135DFE7E00C51BAD /* ASIAuthenticationDialog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASIAuthenticationDialog.m; sourceTree = ""; }; 264CD94F135DFE7E00C51BAD /* ASICacheDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASICacheDelegate.h; sourceTree = ""; }; @@ -310,8 +342,8 @@ 264CD970135DFE7E00C51BAD /* NSString+UriCompatibility.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+UriCompatibility.h"; sourceTree = ""; }; 264CD971135DFE7E00C51BAD /* NSString+UriCompatibility.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+UriCompatibility.m"; sourceTree = ""; }; 264CD9B0135DFFFF00C51BAD /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; - 26563AE214FC3BDE0081C8C1 /* SPAuthenticationManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPAuthenticationManager.h; sourceTree = ""; }; - 26563AE314FC3BDE0081C8C1 /* SPAuthenticationManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPAuthenticationManager.m; sourceTree = ""; }; + 26563AE214FC3BDE0081C8C1 /* SPAuthenticator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPAuthenticator.h; sourceTree = ""; }; + 26563AE314FC3BDE0081C8C1 /* SPAuthenticator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPAuthenticator.m; sourceTree = ""; }; 265D80071475C2F400002A19 /* SPStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPStorage.h; sourceTree = ""; }; 265D80081475C2F500002A19 /* SPStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPStorage.m; sourceTree = ""; }; 265D800B1476188100002A19 /* SPCoreDataStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPCoreDataStorage.h; sourceTree = ""; }; @@ -319,14 +351,12 @@ 265D80131476461800002A19 /* SPStorageObserver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPStorageObserver.h; sourceTree = ""; }; 265EF73615B8E26700115E19 /* SimperiumBinaryTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SimperiumBinaryTests.h; sourceTree = ""; }; 265EF73715B8E26700115E19 /* SimperiumBinaryTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SimperiumBinaryTests.m; sourceTree = ""; }; - 26653CF915FE5DE900461A28 /* Reachability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Reachability.h; sourceTree = ""; }; - 26653CFA15FE5DE900461A28 /* Reachability.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Reachability.m; sourceTree = ""; }; + 26653CF915FE5DE900461A28 /* SPReachability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPReachability.h; sourceTree = ""; }; + 26653CFA15FE5DE900461A28 /* SPReachability.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPReachability.m; sourceTree = ""; }; 2669E6DF144D5CA000C33988 /* UIImage+Simperium.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImage+Simperium.h"; sourceTree = ""; }; 2669E6E0144D5CA000C33988 /* UIImage+Simperium.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImage+Simperium.m"; sourceTree = ""; }; 2673D7171479F42000B7B6B3 /* SimperiumRelationshipTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SimperiumRelationshipTests.h; sourceTree = ""; }; 2673D7181479F42000B7B6B3 /* SimperiumRelationshipTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SimperiumRelationshipTests.m; sourceTree = ""; }; - 2673D71C1479F5DB00B7B6B3 /* SimperiumSimpleTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SimperiumSimpleTests.h; sourceTree = ""; }; - 2673D71D1479F5DB00B7B6B3 /* SimperiumSimpleTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SimperiumSimpleTests.m; sourceTree = ""; }; 2673D720147A018000B7B6B3 /* Post.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Post.h; sourceTree = ""; }; 2673D721147A018000B7B6B3 /* Post.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Post.m; sourceTree = ""; }; 2673D723147A018000B7B6B3 /* Comment.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Comment.h; sourceTree = ""; }; @@ -349,7 +379,6 @@ 267CE8F8156C0FD20028801C /* SPSchema.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPSchema.h; sourceTree = ""; }; 267CE8F9156C0FD20028801C /* SPSchema.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPSchema.m; sourceTree = ""; }; 267CE90C156C351B0028801C /* SPStorageProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPStorageProvider.h; sourceTree = ""; }; - 268F748F14F380D10080C1B8 /* libsqlite3.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libsqlite3.dylib; path = usr/lib/libsqlite3.dylib; sourceTree = SDKROOT; }; 26905D92142CF97D001ACF20 /* DDAbstractDatabaseLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DDAbstractDatabaseLogger.h; sourceTree = ""; }; 26905D93142CF97D001ACF20 /* DDAbstractDatabaseLogger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = DDAbstractDatabaseLogger.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 26905D94142CF97D001ACF20 /* DDASLLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = DDASLLogger.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; @@ -361,22 +390,17 @@ 26905D9A142CF97D001ACF20 /* DDTTYLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DDTTYLogger.h; sourceTree = ""; }; 26905D9B142CF97D001ACF20 /* DDTTYLogger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DDTTYLogger.m; sourceTree = ""; }; 26905DA7142D344C001ACF20 /* DDLogDebug.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DDLogDebug.h; sourceTree = ""; }; - 2693F85415C99E4300797A5E /* SPSimpleKeyChain.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPSimpleKeyChain.h; sourceTree = ""; }; - 2693F85515C99E4300797A5E /* SPSimpleKeyChain.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPSimpleKeyChain.m; sourceTree = ""; }; 269A7F3D147355D100CB29EE /* SPChangeProcessor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPChangeProcessor.h; sourceTree = ""; }; 269A7F3E147355D100CB29EE /* SPChangeProcessor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPChangeProcessor.m; sourceTree = ""; }; 269A7F4214743A9C00CB29EE /* SPIndexProcessor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPIndexProcessor.h; sourceTree = ""; }; 269A7F4314743A9C00CB29EE /* SPIndexProcessor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPIndexProcessor.m; sourceTree = ""; }; - 26B16EFC13AFEB3800BEB3E5 /* buildNumber.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = buildNumber.plist; sourceTree = SOURCE_ROOT; }; 26B16EFD13B1B78C00BEB3E5 /* NSData+Simperium.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+Simperium.h"; sourceTree = ""; }; 26B16EFE13B1B78F00BEB3E5 /* NSData+Simperium.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData+Simperium.m"; sourceTree = ""; }; - 26B16F0113B1B85200BEB3E5 /* NSString+Simperium.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+Simperium.h"; sourceTree = ""; }; - 26B16F0213B1B85300BEB3E5 /* NSString+Simperium.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+Simperium.m"; sourceTree = ""; }; 26B473F614A116B100397E7F /* SPBinaryTransportDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPBinaryTransportDelegate.h; sourceTree = ""; }; - 26B733EE15E585620081CF4A /* SPReferenceManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPReferenceManager.h; sourceTree = ""; }; - 26B733EF15E585620081CF4A /* SPReferenceManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPReferenceManager.m; sourceTree = ""; }; - 26C93A6B14F850EA00BE99F5 /* SPLoginViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPLoginViewController.h; sourceTree = ""; }; - 26C93A6C14F850EA00BE99F5 /* SPLoginViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPLoginViewController.m; sourceTree = ""; }; + 26B733EE15E585620081CF4A /* SPRelationshipResolver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPRelationshipResolver.h; sourceTree = ""; }; + 26B733EF15E585620081CF4A /* SPRelationshipResolver.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPRelationshipResolver.m; sourceTree = ""; }; + 26C93A6B14F850EA00BE99F5 /* SPAuthenticationViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPAuthenticationViewController.h; sourceTree = ""; }; + 26C93A6C14F850EA00BE99F5 /* SPAuthenticationViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPAuthenticationViewController.m; sourceTree = ""; }; 26D4D3B1156C3DA300E822E9 /* JRSwizzle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JRSwizzle.h; sourceTree = ""; }; 26D4D3B2156C3DA300E822E9 /* JRSwizzle.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JRSwizzle.m; sourceTree = ""; }; 26E83A7F1395A1FC00195758 /* SPS3Manager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPS3Manager.h; sourceTree = ""; }; @@ -399,6 +423,38 @@ 26F4BBCD13DCFDA700B8AC56 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; }; 26F4BBCF13DCFDB000B8AC56 /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; }; 26F4BBD313DCFDC100B8AC56 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; }; + 460A505C17DE96350070F02B /* SPProcessorNotificationNames.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPProcessorNotificationNames.m; sourceTree = ""; }; + 460A505D17DE96350070F02B /* SPProcessorNotificationNames.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPProcessorNotificationNames.h; sourceTree = ""; }; + 464DC334176946ED00E75D05 /* SFHFKeychainUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SFHFKeychainUtils.h; sourceTree = ""; }; + 464DC335176946ED00E75D05 /* SFHFKeychainUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SFHFKeychainUtils.m; sourceTree = ""; }; + 466EB41017BC45F5005F7599 /* SPAuthenticationConfiguration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPAuthenticationConfiguration.h; sourceTree = ""; }; + 466EB41117BC45F5005F7599 /* SPAuthenticationConfiguration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPAuthenticationConfiguration.m; sourceTree = ""; }; + 466EB41217BC45F5005F7599 /* SPAuthenticationValidator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPAuthenticationValidator.h; sourceTree = ""; }; + 466EB41317BC45F5005F7599 /* SPAuthenticationValidator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPAuthenticationValidator.m; sourceTree = ""; }; + 466EB41817BC4890005F7599 /* SPAuthenticationButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPAuthenticationButton.h; sourceTree = ""; }; + 466EB41917BC4890005F7599 /* SPAuthenticationButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPAuthenticationButton.m; sourceTree = ""; }; + 466EB41C17BC49DB005F7599 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; + 466FFEA317CBF53200399652 /* SPNetworkInterface.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPNetworkInterface.m; sourceTree = ""; }; + 46A16A7F17289FE2003AED55 /* NSString+Simperium.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+Simperium.h"; sourceTree = ""; }; + 46A16A8017289FE2003AED55 /* NSString+Simperium.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+Simperium.m"; sourceTree = ""; }; + 46DD15C8179A6F4B0093678C /* SPMemberJSONList.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPMemberJSONList.h; sourceTree = ""; }; + 46DD15C9179A6F4B0093678C /* SPMemberJSONList.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPMemberJSONList.m; sourceTree = ""; }; + 46EE3D36171C934300E6F0A5 /* SimperiumBasicTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SimperiumBasicTests.h; sourceTree = ""; }; + 46EE3D37171C934300E6F0A5 /* SimperiumBasicTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SimperiumBasicTests.m; sourceTree = ""; }; + 46EE3D38171C934300E6F0A5 /* SimperiumIndexTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SimperiumIndexTests.h; sourceTree = ""; }; + 46EE3D39171C934300E6F0A5 /* SimperiumIndexTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SimperiumIndexTests.m; sourceTree = ""; }; + 46EE3D3A171C934300E6F0A5 /* SimperiumOfflineTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SimperiumOfflineTests.h; sourceTree = ""; }; + 46EE3D3B171C934300E6F0A5 /* SimperiumOfflineTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SimperiumOfflineTests.m; sourceTree = ""; }; + 46EE3D3F171C975D00E6F0A5 /* SimperiumComplexTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SimperiumComplexTests.h; sourceTree = ""; }; + 46EE3D40171C975D00E6F0A5 /* SimperiumComplexTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SimperiumComplexTests.m; sourceTree = ""; }; + 46EE3D42171C978D00E6F0A5 /* SimperiumAuxiliaryTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SimperiumAuxiliaryTests.h; sourceTree = ""; }; + 46EE3D43171C978D00E6F0A5 /* SimperiumAuxiliaryTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SimperiumAuxiliaryTests.m; sourceTree = ""; }; + 46EE3D55171D0C8E00E6F0A5 /* SimperiumErrorTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SimperiumErrorTests.h; sourceTree = ""; }; + 46EE3D56171D0C8E00E6F0A5 /* SimperiumErrorTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SimperiumErrorTests.m; sourceTree = ""; }; + B5F5D39218219CCE0048754C /* DDFileLogger+Simperium.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "DDFileLogger+Simperium.h"; sourceTree = ""; }; + B5F5D39318219CCE0048754C /* DDFileLogger+Simperium.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "DDFileLogger+Simperium.m"; sourceTree = ""; }; + E265DBCE17CD3E0A00070550 /* SPTOSViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPTOSViewController.h; sourceTree = ""; }; + E265DBCF17CD3E0A00070550 /* SPTOSViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTOSViewController.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -406,6 +462,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 466EB41D17BC49DB005F7599 /* QuartzCore.framework in Frameworks */, 264AE51115D3094D00E5E04E /* Security.framework in Frameworks */, 264AE50E15D3092200E5E04E /* libicucore.dylib in Frameworks */, 26F4BBD413DCFDC100B8AC56 /* MobileCoreServices.framework in Frameworks */, @@ -424,7 +481,6 @@ 264AE59D15DC67C900E5E04E /* libicucore.dylib in Frameworks */, 264AE59C15DC67C300E5E04E /* Security.framework in Frameworks */, 26C93A6A14F8159300BE99F5 /* libSimperium.a in Frameworks */, - 268F749014F380D10080C1B8 /* libsqlite3.dylib in Frameworks */, 26ED43DE146CD89600C3D7D6 /* libz.dylib in Frameworks */, 26ED43DC146CD87000C3D7D6 /* CoreData.framework in Frameworks */, 26ED43DB146CD86700C3D7D6 /* SystemConfiguration.framework in Frameworks */, @@ -474,9 +530,13 @@ name = Binary; sourceTree = ""; }; - 2622FCFF147F225800C8EEB4 /* Members */ = { + 2622FCFF147F225800C8EEB4 /* Diffing */ = { isa = PBXGroup; children = ( + 267CE8F2156C0FD20028801C /* SPDiffer.h */, + 267CE8F3156C0FD20028801C /* SPDiffer.m */, + 267CE8F8156C0FD20028801C /* SPSchema.h */, + 267CE8F9156C0FD20028801C /* SPSchema.m */, 264CD934135DFE6D00C51BAD /* SPMember.h */, 264CD935135DFE6D00C51BAD /* SPMember.m */, 2622FD06147F249300C8EEB4 /* SPMemberText.h */, @@ -491,6 +551,8 @@ 2622FD17147F24E000C8EEB4 /* SPMemberFloat.m */, 2622FD1A147F24F600C8EEB4 /* SPMemberEntity.h */, 2622FD1B147F24F600C8EEB4 /* SPMemberEntity.m */, + 46DD15C8179A6F4B0093678C /* SPMemberJSONList.h */, + 46DD15C9179A6F4B0093678C /* SPMemberJSONList.m */, 2622FD22147F251100C8EEB4 /* SPMemberList.h */, 2622FD23147F251100C8EEB4 /* SPMemberList.m */, 2622FD26147F251C00C8EEB4 /* SPMemberBase64.h */, @@ -498,20 +560,20 @@ 2622FD1E147F250200C8EEB4 /* SPMemberBinary.h */, 2622FD1F147F250200C8EEB4 /* SPMemberBinary.m */, ); - name = Members; + name = Diffing; sourceTree = ""; }; 2622FD00147F227E00C8EEB4 /* Storage */ = { isa = PBXGroup; children = ( 267CE90C156C351B0028801C /* SPStorageProvider.h */, - 267CE8F4156C0FD20028801C /* SPJSONStorage.h */, - 267CE8F5156C0FD20028801C /* SPJSONStorage.m */, 265D80071475C2F400002A19 /* SPStorage.h */, 265D80081475C2F500002A19 /* SPStorage.m */, - 265D80131476461800002A19 /* SPStorageObserver.h */, + 267CE8F4156C0FD20028801C /* SPJSONStorage.h */, + 267CE8F5156C0FD20028801C /* SPJSONStorage.m */, 265D800B1476188100002A19 /* SPCoreDataStorage.h */, 265D800C1476188200002A19 /* SPCoreDataStorage.m */, + 265D80131476461800002A19 /* SPStorageObserver.h */, ); name = Storage; sourceTree = ""; @@ -519,27 +581,18 @@ 2622FD01147F237800C8EEB4 /* System */ = { isa = PBXGroup; children = ( - 267CE8EB156C0FD20028801C /* NSMutableDictionary+Simperium.h */, - 267CE8EC156C0FD20028801C /* NSMutableDictionary+Simperium.m */, + 460A505D17DE96350070F02B /* SPProcessorNotificationNames.h */, + 460A505C17DE96350070F02B /* SPProcessorNotificationNames.m */, 267CE8EF156C0FD20028801C /* SPBucket.h */, 267CE8F0156C0FD20028801C /* SPBucket.m */, - 267CE8F2156C0FD20028801C /* SPDiffer.h */, - 267CE8F3156C0FD20028801C /* SPDiffer.m */, 269A7F3D147355D100CB29EE /* SPChangeProcessor.h */, 269A7F3E147355D100CB29EE /* SPChangeProcessor.m */, 269A7F4214743A9C00CB29EE /* SPIndexProcessor.h */, 269A7F4314743A9C00CB29EE /* SPIndexProcessor.m */, - 264CD936135DFE6D00C51BAD /* SPHttpManager.h */, - 264CD937135DFE6D00C51BAD /* SPHttpManager.m */, - 2618988315D2EC130013CBC9 /* SPWebSocketManager.h */, - 2618988415D2EC130013CBC9 /* SPWebSocketManager.m */, - 264AE51815D45E2A00E5E04E /* SPWebSocketChannel.h */, - 264AE51915D45E2A00E5E04E /* SPWebSocketChannel.m */, - 2622FD2B147F32A500C8EEB4 /* SPNetworkProvider.h */, - 26563AE214FC3BDE0081C8C1 /* SPAuthenticationManager.h */, - 26563AE314FC3BDE0081C8C1 /* SPAuthenticationManager.m */, - 26B733EE15E585620081CF4A /* SPReferenceManager.h */, - 26B733EF15E585620081CF4A /* SPReferenceManager.m */, + 26B733EE15E585620081CF4A /* SPRelationshipResolver.h */, + 26B733EF15E585620081CF4A /* SPRelationshipResolver.m */, + 267CE8EB156C0FD20028801C /* NSMutableDictionary+Simperium.h */, + 267CE8EC156C0FD20028801C /* NSMutableDictionary+Simperium.m */, ); name = System; sourceTree = ""; @@ -557,42 +610,30 @@ isa = PBXGroup; children = ( 267CE8F1156C0FD20028801C /* SPDiffable.h */, - 267CE8F6156C0FD20028801C /* SPObject.h */, - 267CE8F7156C0FD20028801C /* SPObject.m */, - 267CE8F8156C0FD20028801C /* SPSchema.h */, - 267CE8F9156C0FD20028801C /* SPSchema.m */, 264CD92C135DFE6D00C51BAD /* SPManagedObject.h */, 264CD92D135DFE6D00C51BAD /* SPManagedObject.m */, + 267CE8F6156C0FD20028801C /* SPObject.h */, + 267CE8F7156C0FD20028801C /* SPObject.m */, 264CD932135DFE6D00C51BAD /* SPGhost.h */, 264CD933135DFE6D00C51BAD /* SPGhost.m */, - 2622FCFF147F225800C8EEB4 /* Members */, ); name = Object; sourceTree = ""; }; - 2622FD04147F23FB00C8EEB4 /* UI */ = { - isa = PBXGroup; - children = ( - 26C93A6B14F850EA00BE99F5 /* SPLoginViewController.h */, - 26C93A6C14F850EA00BE99F5 /* SPLoginViewController.m */, - ); - name = UI; - sourceTree = ""; - }; - 2622FD05147F241500C8EEB4 /* Auxiliary */ = { + 2622FD05147F241500C8EEB4 /* Helpers */ = { isa = PBXGroup; children = ( 26E83C2413985DBB00195758 /* SPCoreDataExporter.h */, 26E83C2513985DBC00195758 /* SPCoreDataExporter.m */, - 2693F85415C99E4300797A5E /* SPSimpleKeyChain.h */, - 2693F85515C99E4300797A5E /* SPSimpleKeyChain.m */, + 26905DA7142D344C001ACF20 /* DDLogDebug.h */, ); - name = Auxiliary; + name = Helpers; sourceTree = ""; }; 264CD8F0135DFD7900C51BAD = { isa = PBXGroup; children = ( + 466EB41C17BC49DB005F7599 /* QuartzCore.framework */, 264CD900135DFD7A00C51BAD /* Simperium */, 264CD911135DFD7A00C51BAD /* SimperiumTests */, 264CD94B135DFE7E00C51BAD /* External */, @@ -635,11 +676,13 @@ 264CD930135DFE6D00C51BAD /* SPEnvironment.h */, 264CD931135DFE6D00C51BAD /* SPEnvironment.m */, 2622FD03147F23D400C8EEB4 /* Object */, + 2622FCFF147F225800C8EEB4 /* Diffing */, 2622FD01147F237800C8EEB4 /* System */, 2622FD00147F227E00C8EEB4 /* Storage */, + 466EB40F17BC45D8005F7599 /* Authentication */, + 46B7880216B709AD001AA908 /* Networking */, 2622FD02147F23BE00C8EEB4 /* User */, - 2622FD05147F241500C8EEB4 /* Auxiliary */, - 2622FD04147F23FB00C8EEB4 /* UI */, + 2622FD05147F241500C8EEB4 /* Helpers */, 2622FCFE147F224200C8EEB4 /* Binary */, 264CD901135DFD7A00C51BAD /* Supporting Files */, 265D80061475C2CE00002A19 /* Categories */, @@ -650,9 +693,7 @@ 264CD901135DFD7A00C51BAD /* Supporting Files */ = { isa = PBXGroup; children = ( - 26B16EFC13AFEB3800BEB3E5 /* buildNumber.plist */, 264CD902135DFD7A00C51BAD /* Simperium-Prefix.pch */, - 26905DA7142D344C001ACF20 /* DDLogDebug.h */, ); name = "Supporting Files"; sourceTree = ""; @@ -660,21 +701,34 @@ 264CD911135DFD7A00C51BAD /* SimperiumTests */ = { isa = PBXGroup; children = ( - 2678E4C11656D5A10018EE35 /* SimperiumTypeTests.h */, - 2678E4C21656D5A10018EE35 /* SimperiumTypeTests.m */, - 268F748F14F380D10080C1B8 /* libsqlite3.dylib */, - 2673D71C1479F5DB00B7B6B3 /* SimperiumSimpleTests.h */, - 2673D71D1479F5DB00B7B6B3 /* SimperiumSimpleTests.m */, + 1854D512179A18B1006C4211 /* DiffMatchPatchArrayTests.h */, + 1854D513179A18B1006C4211 /* DiffMatchPatchArrayTests.m */, + 46EE3D55171D0C8E00E6F0A5 /* SimperiumErrorTests.h */, + 46EE3D56171D0C8E00E6F0A5 /* SimperiumErrorTests.m */, + 46EE3D42171C978D00E6F0A5 /* SimperiumAuxiliaryTests.h */, + 46EE3D43171C978D00E6F0A5 /* SimperiumAuxiliaryTests.m */, + 46EE3D3F171C975D00E6F0A5 /* SimperiumComplexTests.h */, + 46EE3D40171C975D00E6F0A5 /* SimperiumComplexTests.m */, + 46EE3D36171C934300E6F0A5 /* SimperiumBasicTests.h */, + 46EE3D37171C934300E6F0A5 /* SimperiumBasicTests.m */, + 46EE3D38171C934300E6F0A5 /* SimperiumIndexTests.h */, + 46EE3D39171C934300E6F0A5 /* SimperiumIndexTests.m */, + 46EE3D3A171C934300E6F0A5 /* SimperiumOfflineTests.h */, + 46EE3D3B171C934300E6F0A5 /* SimperiumOfflineTests.m */, + 26ED441C146CFAF100C3D7D6 /* TestParams.h */, + 26ED4419146CF8E500C3D7D6 /* Farm.h */, + 26ED441A146CF8E500C3D7D6 /* Farm.m */, + 264CD918135DFD7A00C51BAD /* SimperiumTests.h */, + 264CD91A135DFD7A00C51BAD /* SimperiumTests.m */, 2673D7171479F42000B7B6B3 /* SimperiumRelationshipTests.h */, 2673D7181479F42000B7B6B3 /* SimperiumRelationshipTests.m */, + 2678E4C11656D5A10018EE35 /* SimperiumTypeTests.h */, + 2678E4C21656D5A10018EE35 /* SimperiumTypeTests.m */, 265EF73615B8E26700115E19 /* SimperiumBinaryTests.h */, 265EF73715B8E26700115E19 /* SimperiumBinaryTests.m */, + 246F22C6179B3EDA009547B6 /* SimperiumCoreDataTests.h */, + 246F22C7179B3EDB009547B6 /* SimperiumCoreDataTests.m */, 26ED4412146CF03A00C3D7D6 /* Test1.xcdatamodeld */, - 26ED4419146CF8E500C3D7D6 /* Farm.h */, - 26ED441A146CF8E500C3D7D6 /* Farm.m */, - 264CD918135DFD7A00C51BAD /* SimperiumTests.h */, - 264CD91A135DFD7A00C51BAD /* SimperiumTests.m */, - 26ED441C146CFAF100C3D7D6 /* TestParams.h */, 2673D71F147A00B300B7B6B3 /* Models */, 264CD912135DFD7A00C51BAD /* Supporting Files */, ); @@ -694,7 +748,8 @@ 264CD94B135DFE7E00C51BAD /* External */ = { isa = PBXGroup; children = ( - 26653CF815FE5DE900461A28 /* Reachability */, + 464DC333176946ED00E75D05 /* SFHFKeychainUtils */, + 26653CF815FE5DE900461A28 /* SPReachability */, 2618987315D2EB310013CBC9 /* SocketRocket */, 26D4D3B0156C3DA300E822E9 /* jrswizzle */, 2611E802149FB91100121D6E /* JSONKit */, @@ -765,25 +820,29 @@ 265D80061475C2CE00002A19 /* Categories */ = { isa = PBXGroup; children = ( + 1854D50D1799F6D2006C4211 /* NSArray+Simperium.h */, + 1854D50E1799F6D2006C4211 /* NSArray+Simperium.m */, + 46A16A7F17289FE2003AED55 /* NSString+Simperium.h */, + 46A16A8017289FE2003AED55 /* NSString+Simperium.m */, 26E83DA81399D05D00195758 /* NSDate+Simperium.h */, 2669E6DF144D5CA000C33988 /* UIImage+Simperium.h */, 2669E6E0144D5CA000C33988 /* UIImage+Simperium.m */, 26E83DA91399D05E00195758 /* NSDate+Simperium.m */, 26B16EFD13B1B78C00BEB3E5 /* NSData+Simperium.h */, 26B16EFE13B1B78F00BEB3E5 /* NSData+Simperium.m */, - 26B16F0113B1B85200BEB3E5 /* NSString+Simperium.h */, - 26B16F0213B1B85300BEB3E5 /* NSString+Simperium.m */, + B5F5D39218219CCE0048754C /* DDFileLogger+Simperium.h */, + B5F5D39318219CCE0048754C /* DDFileLogger+Simperium.m */, ); name = Categories; sourceTree = ""; }; - 26653CF815FE5DE900461A28 /* Reachability */ = { + 26653CF815FE5DE900461A28 /* SPReachability */ = { isa = PBXGroup; children = ( - 26653CF915FE5DE900461A28 /* Reachability.h */, - 26653CFA15FE5DE900461A28 /* Reachability.m */, + 26653CF915FE5DE900461A28 /* SPReachability.h */, + 26653CFA15FE5DE900461A28 /* SPReachability.m */, ); - path = Reachability; + path = SPReachability; sourceTree = ""; }; 2673D71F147A00B300B7B6B3 /* Models */ = { @@ -827,6 +886,49 @@ path = jrswizzle; sourceTree = ""; }; + 464DC333176946ED00E75D05 /* SFHFKeychainUtils */ = { + isa = PBXGroup; + children = ( + 464DC334176946ED00E75D05 /* SFHFKeychainUtils.h */, + 464DC335176946ED00E75D05 /* SFHFKeychainUtils.m */, + ); + path = SFHFKeychainUtils; + sourceTree = ""; + }; + 466EB40F17BC45D8005F7599 /* Authentication */ = { + isa = PBXGroup; + children = ( + 26563AE214FC3BDE0081C8C1 /* SPAuthenticator.h */, + 26563AE314FC3BDE0081C8C1 /* SPAuthenticator.m */, + 466EB41817BC4890005F7599 /* SPAuthenticationButton.h */, + 466EB41917BC4890005F7599 /* SPAuthenticationButton.m */, + 26C93A6B14F850EA00BE99F5 /* SPAuthenticationViewController.h */, + 26C93A6C14F850EA00BE99F5 /* SPAuthenticationViewController.m */, + 466EB41017BC45F5005F7599 /* SPAuthenticationConfiguration.h */, + 466EB41117BC45F5005F7599 /* SPAuthenticationConfiguration.m */, + 466EB41217BC45F5005F7599 /* SPAuthenticationValidator.h */, + 466EB41317BC45F5005F7599 /* SPAuthenticationValidator.m */, + E265DBCE17CD3E0A00070550 /* SPTOSViewController.h */, + E265DBCF17CD3E0A00070550 /* SPTOSViewController.m */, + ); + name = Authentication; + sourceTree = ""; + }; + 46B7880216B709AD001AA908 /* Networking */ = { + isa = PBXGroup; + children = ( + 466FFEA317CBF53200399652 /* SPNetworkInterface.m */, + 2622FD2B147F32A500C8EEB4 /* SPNetworkInterface.h */, + 264CD936135DFE6D00C51BAD /* SPHttpInterface.h */, + 264CD937135DFE6D00C51BAD /* SPHttpInterface.m */, + 2618988315D2EC130013CBC9 /* SPWebSocketInterface.h */, + 2618988415D2EC130013CBC9 /* SPWebSocketInterface.m */, + 264AE51815D45E2A00E5E04E /* SPWebSocketChannel.h */, + 264AE51915D45E2A00E5E04E /* SPWebSocketChannel.m */, + ); + name = Networking; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -839,7 +941,7 @@ 264CD940135DFE6D00C51BAD /* SPEnvironment.h in Headers */, 264CD942135DFE6D00C51BAD /* SPGhost.h in Headers */, 264CD944135DFE6D00C51BAD /* SPMember.h in Headers */, - 264CD946135DFE6D00C51BAD /* SPHttpManager.h in Headers */, + 264CD946135DFE6D00C51BAD /* SPHttpInterface.h in Headers */, 264CD980135DFE7E00C51BAD /* ASIAuthenticationDialog.h in Headers */, 264CD982135DFE7E00C51BAD /* ASICacheDelegate.h in Headers */, 264CD983135DFE7E00C51BAD /* ASIDataCompressor.h in Headers */, @@ -865,7 +967,6 @@ 26E83D9913999C0F00195758 /* SPUser.h in Headers */, 26E83DAA1399D05F00195758 /* NSDate+Simperium.h in Headers */, 26B16EFF13B1B79100BEB3E5 /* NSData+Simperium.h in Headers */, - 26B16F0313B1B85400BEB3E5 /* NSString+Simperium.h in Headers */, 26905D9C142CF97D001ACF20 /* DDAbstractDatabaseLogger.h in Headers */, 26905D9E142CF97D001ACF20 /* DDASLLogger.h in Headers */, 26905DA0142CF97D001ACF20 /* DDFileLogger.h in Headers */, @@ -889,11 +990,11 @@ 2622FD20147F250400C8EEB4 /* SPMemberBinary.h in Headers */, 2622FD24147F251200C8EEB4 /* SPMemberList.h in Headers */, 2622FD28147F251D00C8EEB4 /* SPMemberBase64.h in Headers */, - 2622FD2D147F32A500C8EEB4 /* SPNetworkProvider.h in Headers */, + 2622FD2D147F32A500C8EEB4 /* SPNetworkInterface.h in Headers */, 2611E805149FB91100121D6E /* JSONKit.h in Headers */, 26B473F714A116B100397E7F /* SPBinaryTransportDelegate.h in Headers */, - 26C93A6D14F850EA00BE99F5 /* SPLoginViewController.h in Headers */, - 26563AE414FC3BDE0081C8C1 /* SPAuthenticationManager.h in Headers */, + 26C93A6D14F850EA00BE99F5 /* SPAuthenticationViewController.h in Headers */, + 26563AE414FC3BDE0081C8C1 /* SPAuthenticator.h in Headers */, 267CE8FA156C0FD20028801C /* NSMutableDictionary+Simperium.h in Headers */, 267CE8FE156C0FD20028801C /* SPBucket.h in Headers */, 267CE900156C0FD20028801C /* SPDiffable.h in Headers */, @@ -903,15 +1004,24 @@ 267CE907156C0FD20028801C /* SPSchema.h in Headers */, 267CE90D156C351B0028801C /* SPStorageProvider.h in Headers */, 26D4D3B3156C3DA300E822E9 /* JRSwizzle.h in Headers */, - 2693F85615C99E4300797A5E /* SPSimpleKeyChain.h in Headers */, 2618987C15D2EB310013CBC9 /* base64.h in Headers */, 2618987D15D2EB310013CBC9 /* NSData+SRB64Additions.h in Headers */, 2618987F15D2EB310013CBC9 /* SocketRocket-Prefix.pch in Headers */, 2618988015D2EB310013CBC9 /* SRWebSocket.h in Headers */, - 2618988515D2EC130013CBC9 /* SPWebSocketManager.h in Headers */, + 2618988515D2EC130013CBC9 /* SPWebSocketInterface.h in Headers */, 264AE51A15D45E2A00E5E04E /* SPWebSocketChannel.h in Headers */, - 26B733F015E585630081CF4A /* SPReferenceManager.h in Headers */, - 26653CFB15FE5DEA00461A28 /* Reachability.h in Headers */, + 26B733F015E585630081CF4A /* SPRelationshipResolver.h in Headers */, + E265DBD017CD3E0A00070550 /* SPTOSViewController.h in Headers */, + 26653CFB15FE5DEA00461A28 /* SPReachability.h in Headers */, + 46A16A8117289FE2003AED55 /* NSString+Simperium.h in Headers */, + B5F5D39418219CCE0048754C /* DDFileLogger+Simperium.h in Headers */, + 464DC336176946ED00E75D05 /* SFHFKeychainUtils.h in Headers */, + 1854D50F1799F6D2006C4211 /* NSArray+Simperium.h in Headers */, + 46DD15CA179A6F4B0093678C /* SPMemberJSONList.h in Headers */, + 466EB41417BC45F5005F7599 /* SPAuthenticationConfiguration.h in Headers */, + 466EB41617BC45F5005F7599 /* SPAuthenticationValidator.h in Headers */, + 466EB41A17BC4890005F7599 /* SPAuthenticationButton.h in Headers */, + 460A505F17DE96350070F02B /* SPProcessorNotificationNames.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1062,13 +1172,15 @@ buildActionMask = 2147483647; files = ( 269CAC2C146DDA7C00584C56 /* Simperium.m in Sources */, + B5F5D39518219CCE0048754C /* DDFileLogger+Simperium.m in Sources */, 264CD93D135DFE6D00C51BAD /* SPManagedObject.m in Sources */, 264CD941135DFE6D00C51BAD /* SPEnvironment.m in Sources */, 264CD943135DFE6D00C51BAD /* SPGhost.m in Sources */, 264CD945135DFE6D00C51BAD /* SPMember.m in Sources */, - 264CD947135DFE6D00C51BAD /* SPHttpManager.m in Sources */, + 264CD947135DFE6D00C51BAD /* SPHttpInterface.m in Sources */, 264CD981135DFE7E00C51BAD /* ASIAuthenticationDialog.m in Sources */, 264CD984135DFE7E00C51BAD /* ASIDataCompressor.m in Sources */, + E265DBD117CD3E0A00070550 /* SPTOSViewController.m in Sources */, 264CD986135DFE7E00C51BAD /* ASIDataDecompressor.m in Sources */, 264CD988135DFE7E00C51BAD /* ASIDownloadCache.m in Sources */, 264CD98A135DFE7E00C51BAD /* ASIFormDataRequest.m in Sources */, @@ -1086,7 +1198,6 @@ 26E83D9A13999C0F00195758 /* SPUser.m in Sources */, 26E83DAB1399D05F00195758 /* NSDate+Simperium.m in Sources */, 26B16F0013B1B79100BEB3E5 /* NSData+Simperium.m in Sources */, - 26B16F0413B1B85400BEB3E5 /* NSString+Simperium.m in Sources */, 26905DA3142CF97D001ACF20 /* DDLog.m in Sources */, 26905DA5142CF97D001ACF20 /* DDTTYLogger.m in Sources */, 269A7F4514743A9C00CB29EE /* SPIndexProcessor.m in Sources */, @@ -1104,8 +1215,8 @@ 2622FD25147F251200C8EEB4 /* SPMemberList.m in Sources */, 2622FD29147F251D00C8EEB4 /* SPMemberBase64.m in Sources */, 2611E806149FB91100121D6E /* JSONKit.m in Sources */, - 26C93A6E14F850EA00BE99F5 /* SPLoginViewController.m in Sources */, - 26563AE514FC3BDE0081C8C1 /* SPAuthenticationManager.m in Sources */, + 26C93A6E14F850EA00BE99F5 /* SPAuthenticationViewController.m in Sources */, + 26563AE514FC3BDE0081C8C1 /* SPAuthenticator.m in Sources */, 267CE8FB156C0FD20028801C /* NSMutableDictionary+Simperium.m in Sources */, 267CE8FF156C0FD20028801C /* SPBucket.m in Sources */, 267CE902156C0FD20028801C /* SPDiffer.m in Sources */, @@ -1116,14 +1227,22 @@ 260722A315BF26A300786BD4 /* DDASLLogger.m in Sources */, 260722A515BF26A700786BD4 /* DDAbstractDatabaseLogger.m in Sources */, 260722A615BF26AB00786BD4 /* DDFileLogger.m in Sources */, - 2693F85715C99E4300797A5E /* SPSimpleKeyChain.m in Sources */, 2618987B15D2EB310013CBC9 /* base64.c in Sources */, 2618987E15D2EB310013CBC9 /* NSData+SRB64Additions.m in Sources */, 2618988115D2EB310013CBC9 /* SRWebSocket.m in Sources */, - 2618988615D2EC130013CBC9 /* SPWebSocketManager.m in Sources */, + 2618988615D2EC130013CBC9 /* SPWebSocketInterface.m in Sources */, 264AE51B15D45E2A00E5E04E /* SPWebSocketChannel.m in Sources */, - 26B733F115E585630081CF4A /* SPReferenceManager.m in Sources */, - 26653CFC15FE5DEA00461A28 /* Reachability.m in Sources */, + 26B733F115E585630081CF4A /* SPRelationshipResolver.m in Sources */, + 26653CFC15FE5DEA00461A28 /* SPReachability.m in Sources */, + 46A16A8217289FE2003AED55 /* NSString+Simperium.m in Sources */, + 464DC337176946ED00E75D05 /* SFHFKeychainUtils.m in Sources */, + 1854D5101799F6D2006C4211 /* NSArray+Simperium.m in Sources */, + 46DD15CB179A6F4B0093678C /* SPMemberJSONList.m in Sources */, + 466EB41517BC45F5005F7599 /* SPAuthenticationConfiguration.m in Sources */, + 466EB41717BC45F5005F7599 /* SPAuthenticationValidator.m in Sources */, + 466EB41B17BC4890005F7599 /* SPAuthenticationButton.m in Sources */, + 466FFEA417CBF53200399652 /* SPNetworkInterface.m in Sources */, + 460A505E17DE96350070F02B /* SPProcessorNotificationNames.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1136,11 +1255,20 @@ 26ED441B146CF8E600C3D7D6 /* Farm.m in Sources */, 269CAC2E146DF84C00584C56 /* Test1.xcdatamodeld in Sources */, 2673D71B1479F4C000B7B6B3 /* SimperiumRelationshipTests.m in Sources */, - 2673D71E1479F5DB00B7B6B3 /* SimperiumSimpleTests.m in Sources */, 2673D722147A018000B7B6B3 /* Post.m in Sources */, 2673D725147A018000B7B6B3 /* Comment.m in Sources */, 2673D728147A3BEB00B7B6B3 /* TestObject.m in Sources */, 2678E4C31656D5A10018EE35 /* SimperiumTypeTests.m in Sources */, + 46EE3D3C171C934300E6F0A5 /* SimperiumBasicTests.m in Sources */, + 46EE3D3D171C934300E6F0A5 /* SimperiumIndexTests.m in Sources */, + 46EE3D3E171C934300E6F0A5 /* SimperiumOfflineTests.m in Sources */, + 46EE3D41171C975D00E6F0A5 /* SimperiumComplexTests.m in Sources */, + 46EE3D44171C978E00E6F0A5 /* SimperiumAuxiliaryTests.m in Sources */, + 46EE3D57171D0C8E00E6F0A5 /* SimperiumErrorTests.m in Sources */, + 246F22C8179B3EDB009547B6 /* SimperiumCoreDataTests.m in Sources */, + 1854D5111799F6D2006C4211 /* NSArray+Simperium.m in Sources */, + B5F5D39618219CCE0048754C /* DDFileLogger+Simperium.m in Sources */, + 1854D514179A18B1006C4211 /* DiffMatchPatchArrayTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1187,7 +1315,7 @@ GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNUSED_VARIABLE = YES; GENERATE_PROFILING_CODE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 4.3; + IPHONEOS_DEPLOYMENT_TARGET = 6.0; PUBLIC_HEADERS_FOLDER_PATH = Headers; SDKROOT = iphoneos; SKIP_INSTALL = YES; @@ -1208,7 +1336,7 @@ GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 4.3; + IPHONEOS_DEPLOYMENT_TARGET = 6.0; PUBLIC_HEADERS_FOLDER_PATH = Headers; SDKROOT = iphoneos; SKIP_INSTALL = YES; @@ -1221,6 +1349,7 @@ buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + CLANG_ENABLE_OBJC_ARC = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; DSTROOT = /tmp/Simperium.dst; FRAMEWORK_SEARCH_PATHS = ( @@ -1231,8 +1360,8 @@ GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "Simperium/Simperium-Prefix.pch"; GCC_THUMB_SUPPORT = NO; + GCC_WARN_ABOUT_MISSING_NEWLINE = YES; INSTALL_PATH = "$(BUILT_PRODUCTS_DIR)"; - IPHONEOS_DEPLOYMENT_TARGET = 4.3; OTHER_LDFLAGS = "-ObjC"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; @@ -1247,6 +1376,7 @@ buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + CLANG_ENABLE_OBJC_ARC = YES; CODE_SIGN_IDENTITY = "iPhone Distribution: Codality, Inc."; DSTROOT = /tmp/Simperium.dst; FRAMEWORK_SEARCH_PATHS = ( @@ -1257,8 +1387,8 @@ GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "Simperium/Simperium-Prefix.pch"; GCC_THUMB_SUPPORT = NO; + GCC_WARN_ABOUT_MISSING_NEWLINE = YES; INSTALL_PATH = "$(BUILT_PRODUCTS_DIR)"; - IPHONEOS_DEPLOYMENT_TARGET = 4.3; OTHER_LDFLAGS = "-ObjC"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = "CB4714D5-E48A-4B4A-95A4-9755CA700199"; @@ -1272,10 +1402,10 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ENABLE_OBJC_ARC = YES; FRAMEWORK_SEARCH_PATHS = ( "$(SDKROOT)/Developer/Library/Frameworks", "$(DEVELOPER_LIBRARY_DIR)/Frameworks", - "\"$(SRCROOT)/../../../simperium-ios-private/External\"", ); GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "SimperiumTests/SimperiumTests-Prefix.pch"; @@ -1297,10 +1427,10 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ENABLE_OBJC_ARC = YES; FRAMEWORK_SEARCH_PATHS = ( "$(SDKROOT)/Developer/Library/Frameworks", "$(DEVELOPER_LIBRARY_DIR)/Frameworks", - "\"$(SRCROOT)/../../../simperium-ios-private/External\"", ); GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "SimperiumTests/SimperiumTests-Prefix.pch"; @@ -1323,7 +1453,6 @@ buildSettings = { ARCHS = "$(ARCHS_STANDARD_32_BIT)"; GCC_THUMB_SUPPORT = NO; - IPHONEOS_DEPLOYMENT_TARGET = 4.3; PRODUCT_NAME = "$(TARGET_NAME)"; VALID_ARCHS = "armv7 armv7s i386"; }; @@ -1334,7 +1463,6 @@ buildSettings = { ARCHS = "$(ARCHS_STANDARD_32_BIT)"; GCC_THUMB_SUPPORT = NO; - IPHONEOS_DEPLOYMENT_TARGET = 4.3; PRODUCT_NAME = "$(TARGET_NAME)"; VALID_ARCHS = "armv7 armv7s i386"; }; diff --git a/Simperium/DDFileLogger+Simperium.h b/Simperium/DDFileLogger+Simperium.h new file mode 100644 index 00000000..a851d9a1 --- /dev/null +++ b/Simperium/DDFileLogger+Simperium.h @@ -0,0 +1,14 @@ +// +// DDFileLogger+Simperium.h +// Simperium +// +// Created by Jorge Leandro Perez on 10/30/13. +// Copyright (c) 2013 Simperium. All rights reserved. +// + +#import "DDFileLogger.h" + + +@interface DDFileLogger (Simperium) ++(DDFileLogger*)sharedInstance; +@end diff --git a/Simperium/DDFileLogger+Simperium.m b/Simperium/DDFileLogger+Simperium.m new file mode 100644 index 00000000..ee7cf3d0 --- /dev/null +++ b/Simperium/DDFileLogger+Simperium.m @@ -0,0 +1,26 @@ +// +// DDFileLogger+Simperium.m +// Simperium +// +// Created by Jorge Leandro Perez on 10/30/13. +// Copyright (c) 2013 Simperium. All rights reserved. +// + +#import "DDFileLogger+Simperium.h" + + +@implementation DDFileLogger (Simperium) + ++(DDFileLogger*)sharedInstance +{ + static DDFileLogger *logger; + static dispatch_once_t _once; + + dispatch_once(&_once, ^{ + logger = [[DDFileLogger alloc] init]; + }); + + return logger; +} + +@end diff --git a/Simperium/NSArray+Simperium.h b/Simperium/NSArray+Simperium.h new file mode 100644 index 00000000..66151813 --- /dev/null +++ b/Simperium/NSArray+Simperium.h @@ -0,0 +1,34 @@ +// +// NSArray+Simperium.h +// Simperium +// +// Created by Andrew Mackenzie-Ross on 19/07/13. +// Copyright (c) 2013 Simperium. All rights reserved. +// + +#import + +@class DiffMatchPatch; + +@interface NSArray (Simperium) + +// DiffMatchPatch Array Operations + +// Create a diff from the receiver using diff match patch. +- (NSString *)sp_diffDeltaWithArray:(NSArray *)obj diffMatchPatch:(DiffMatchPatch *)dmp; + +// Returns the result of applying a diff to the receiver using diff match patch. +- (NSArray *)sp_arrayByApplyingDiffDelta:(NSString *)delta diffMatchPatch:(DiffMatchPatch *)dmp; + +// Returns a transformed diff on top of another diff using diff match patch. +- (NSString *)sp_transformDelta:(NSString *)delta onto:(NSString *)otherDelta diffMatchPatch:(DiffMatchPatch *)dmp; + +// TODO: Implement OP_LIST methods +// Create a diff from the receiver +//- (NSDictionary *)sp_diffWithArray:(NSArray *)obj; +// Returns the result of applying a diff to the receiver using diff match patch. +//- (NSArray *)sp_applyDiff:(NSDictionary *)diff; +// Returns a transformed diff on top of another diff using diff match patch. +//- (NSDictionary *)sp_transformDiff:(NSDictionary *)diff onto:(NSDictionary *)otherDiff; + +@end diff --git a/Simperium/NSArray+Simperium.m b/Simperium/NSArray+Simperium.m new file mode 100644 index 00000000..b64361a7 --- /dev/null +++ b/Simperium/NSArray+Simperium.m @@ -0,0 +1,231 @@ +// +// NSArray+Simperium.m +// Simperium +// +// Created by Andrew Mackenzie-Ross on 19/07/13. +// Copyright (c) 2013 Simperium. All rights reserved. +// + +#import "NSArray+Simperium.h" +#import "SPMember.h" +#import "DiffMatchPatch.h" +#import "JSONKit.h" +#import "DDLog.h" + + +@implementation NSArray (Simperium) + +#pragma mark - List Diffs With Diff Match Patch + +- (NSString *)sp_diffDeltaWithArray:(NSArray *)obj diffMatchPatch:(DiffMatchPatch *)dmp +{ + NSParameterAssert(obj); NSParameterAssert(dmp); + return [self sp_deltaWithArray:obj diffMatchPatch:dmp]; +} + +- (NSArray *)sp_arrayByApplyingDiffDelta:(NSString *)delta diffMatchPatch:(DiffMatchPatch *)dmp +{ + NSParameterAssert(delta); NSParameterAssert(dmp); + + NSString *newLineSeparatedJSONString = [self sp_newLineSeparatedJSONString]; + + NSError __autoreleasing *error = nil; + NSMutableArray *diffs = [dmp diff_fromDeltaWithText:newLineSeparatedJSONString andDelta:delta error:&error]; + + if (error) { + [NSException raise:NSInternalInconsistencyException format:@"Simperium: Error creating diff from delta with text %@ and delta %@ due to error %@ in %s.", newLineSeparatedJSONString, delta, error, __PRETTY_FUNCTION__]; + } + + NSMutableArray *patches = [dmp patch_makeFromDiffs:diffs]; + NSString *updatedNewlineSeparatedString = [dmp patch_apply:patches toString:newLineSeparatedJSONString][0]; + + return [NSArray sp_arrayFromNewLineSeparatedJSONString:updatedNewlineSeparatedString]; +} + +- (NSString *)sp_transformDelta:(NSString *)delta onto:(NSString *)otherDelta diffMatchPatch:(DiffMatchPatch *)dmp +{ + NSParameterAssert(delta); NSParameterAssert(otherDelta); NSParameterAssert(dmp); + NSString *sourceText = [self sp_newLineSeparatedJSONString]; + + NSError __autoreleasing *error = nil; + NSMutableArray *diff1Patches = [dmp diff_fromDeltaWithText:sourceText andDelta:delta error:&error]; + if (error) [NSException raise:NSInternalInconsistencyException format:@"Simperium: Error creating diff from delta with text %@ and delta %@ due to error %@ in %s.", sourceText, delta, error, __PRETTY_FUNCTION__]; + NSMutableArray *diff2Patches = [dmp diff_fromDeltaWithText:sourceText andDelta:otherDelta error:&error]; + if (error) [NSException raise:NSInternalInconsistencyException format:@"Simperium: Error creating diff from delta with text %@ and delta %@ due to error %@ in %s.", sourceText, otherDelta, error, __PRETTY_FUNCTION__]; + + NSString *diff2Text = [dmp patch_apply:diff2Patches toString:sourceText][0]; + NSString *diff2And1Text = [dmp patch_apply:diff1Patches toString:diff2Text][0]; + + if ([diff2And1Text isEqualToString:diff2Text]) return @""; // no-op diff + + NSMutableArray *diffs = [dmp diff_lineModeFromOldString:diff2Text andNewString:diff2And1Text deadline:0]; + + return [dmp diff_toDelta:diffs]; +} + + +- (NSString *)sp_deltaWithArray:(NSArray *)obj diffMatchPatch:(DiffMatchPatch *)dmp +{ + NSParameterAssert(obj); NSParameterAssert(dmp); + + NSString *nljs1 = [self sp_newLineSeparatedJSONString]; + NSString *nljs2 = [obj sp_newLineSeparatedJSONString]; + + + NSArray *b = [dmp diff_linesToCharsForFirstString:nljs1 andSecondString:nljs2]; + NSString *text1 = (NSString *)[b objectAtIndex:0]; + NSString *text2 = (NSString *)[b objectAtIndex:1]; + NSMutableArray *linearray = (NSMutableArray *)[b objectAtIndex:2]; + + NSMutableArray *diffs = nil; + @autoreleasepool { + diffs = [dmp diff_mainOfOldString:text1 andNewString:text2 checkLines:NO deadline:0]; + } + + // Convert the diff back to original text. + [dmp diff_chars:diffs toLines:linearray]; + // Eliminate freak matches (e.g. blank lines) + [dmp diff_cleanupSemantic:diffs]; + + // Removing "-0 " as this is a no-op operations that crashes the apply patch method. + return [[dmp diff_toDelta:diffs] stringByReplacingOccurrencesOfString:@"-0 " withString:@""]; +} + +- (NSString *)sp_newLineSeparatedJSONString +{ + // Create a new line separated list of JSON objects in an array. + // e.g. + // { "a" : 1, "c" : "3" }\n{ "b" : 2 }\n + // + NSMutableString *JSONString = [[NSMutableString alloc] init]; + for (id object in self) { + if (object == (id)kCFBooleanTrue) { + [JSONString appendString:@"true\n"]; + } else if (object == (id)kCFBooleanFalse) { + [JSONString appendString:@"false\n"]; + } else if ([object isKindOfClass:[NSNumber class]]) { + [JSONString appendFormat:@"%@\n",object]; + } else if ([object isKindOfClass:[NSString class]]) { + [JSONString appendFormat:@"\"%@\"\n",object]; + } else if (object == [NSNull null]) { + [JSONString appendString:@"null\n"]; + } else if ([object isKindOfClass:[NSArray class]] || [object isKindOfClass:[NSDictionary class]]) { + [JSONString appendFormat:@"%@\n",[[object JSONString] stringByReplacingOccurrencesOfString:@"\n" withString:@""]]; + } else { + [NSException raise:NSInternalInconsistencyException format:@"Simperium: Cannot create diff match patch with non-json object %@ in %s",object,__PRETTY_FUNCTION__]; + } + } + + // Remove final \n character from string + if ([JSONString isEqualToString:@""]) return JSONString; + return [JSONString substringToIndex:[JSONString length] - 1]; +} + ++ (NSArray *)sp_arrayFromNewLineSeparatedJSONString:(NSString *)string +{ + NSParameterAssert(string); + + NSArray *JSONStrings = [string componentsSeparatedByString:@"\n"]; + // Remove any lines with nothing on them. + JSONStrings = [JSONStrings filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) { + return ![evaluatedObject isEqual:@""]; + }]]; + NSString *JSONArrayString = [NSString stringWithFormat:@"[ %@ ]", [JSONStrings componentsJoinedByString:@", "]]; + return [JSONArrayString objectFromJSONString]; +} + + + +#pragma mark - List Diff with Operations + +// TODO: Implement? +// +//- (NSArray *)sp_arrayByApplyingDiff:(NSDictionary *)diff +//{ +// NSMutableArray *array = [self mutableCopy]; +// +// NSArray *indexes = [[diff allKeys] sortedArrayUsingSelector:@selector(compare:)]; +// NSMutableIndexSet *indexesToReplace = [[NSMutableIndexSet alloc] init]; +// NSMutableArray *replacementObjects = [[NSMutableArray alloc] init]; +// NSMutableIndexSet *indexesToRemove = [[NSMutableIndexSet alloc] init]; +// NSMutableIndexSet *indexesToInsert = [[NSMutableIndexSet alloc] init]; +// NSMutableArray *insertedObjects = [[NSMutableArray alloc] init]; +// for (NSNumber *index in indexes) { +// NSDictionary *elementDiff = [diff objectForKey:index]; +// +// NSString *operation = [elementDiff objectForKey:OP_OP]; +// if ([operation isEqualToString:OP_REPLACE]) { +// [replacementObjects addObject:[elementDiff objectForKey:OP_VALUE]]; +// [indexesToReplace addIndex:[index integerValue]]; +// } else if ([operation isEqualToString:OP_LIST_DELETE]) { +// [indexesToRemove addIndex:[index integerValue]]; +// } else if ([operation isEqualToString:OP_LIST_INSERT]) { +// [insertedObjects addObject:[elementDiff objectForKey:OP_VALUE]]; +// [indexesToInsert addIndex:[index integerValue]]; +// } else { +// NSAssert(NO, @"Diff operation %@ is not supported within lists.", operation); +// } +// } +// +// [array replaceObjectsAtIndexes:indexesToReplace withObjects:replacementObjects]; +// [array removeObjectsAtIndexes:indexesToRemove]; +// [array insertObjects:insertedObjects atIndexes:indexesToInsert]; +// +// return array; +//} +// +//- (NSDictionary *)sp_diffWithArray:(NSArray *)obj2 +//{ +// if ([self isEqualToArray:obj2]) return @{}; +// NSArray *obj1 = self; +// +// NSMutableDictionary *diffs = [[NSMutableDictionary alloc] init]; +// +// NSInteger prefixCount = [obj1 sp_countOfObjectsCommonWithArray:obj2 options:0]; +// obj1 = [obj1 subarrayWithRange:NSMakeRange(prefixCount, [obj1 count] - prefixCount)]; +// obj2 = [obj2 subarrayWithRange:NSMakeRange(prefixCount, [obj2 count] - prefixCount)]; +// +// NSInteger suffixCount = [obj1 sp_countOfObjectsCommonWithArray:obj2 options:NSEnumerationReverse]; +// obj1 = [obj1 subarrayWithRange:NSMakeRange(0, [obj1 count] - suffixCount)]; +// obj2 = [obj2 subarrayWithRange:NSMakeRange(0, [obj2 count] - suffixCount)]; +// +// NSInteger obj1Count = [obj1 count]; +// NSInteger obj2Count = [obj2 count]; +// for (int i = 0; i < MAX(obj1Count, obj2Count); i++) { +// if (i < obj1Count && i < obj2Count) { +// if ([obj1[i] isEqual:obj2[i]] == NO) { +// diffs[@(i + prefixCount)] = @{ OP_OP: OP_REPLACE, OP_VALUE: obj2[i] }; +// } +// } else if (i < obj1Count) { +// diffs[@(i + prefixCount)] = @{ OP_OP: OP_LIST_DELETE }; +// } else if (i < obj2Count) { +// diffs[@(i + prefixCount)] = @{ OP_OP: OP_LIST_INSERT, OP_VALUE: obj2[i] }; +// } +// } +// +// return diffs; +//} +// +// +// +//- (NSInteger)sp_countOfObjectsCommonWithArray:(NSArray *)b options:(NSEnumerationOptions)options +//{ +// NSAssert(options ^ NSEnumerationConcurrent, @"%s doesn't support NSEnumerationConcurrent",__PRETTY_FUNCTION__); +// __block NSInteger count = 0; +// NSArray *a = self; +// NSInteger shift = (options & NSEnumerationReverse) ? [b count] - [a count] : 0; +// [self enumerateObjectsWithOptions:options usingBlock:^(id obj, NSUInteger idx, BOOL *stop) { +// NSInteger idxIntoB = idx + shift; +// if (idxIntoB >= [b count] || idxIntoB < 0 || ![obj isEqual:b[idxIntoB]]) { +// *stop = YES; +// } else { +// count++; +// } +// }]; +// +// return count; +//} + + + +@end diff --git a/Simperium/NSData+Simperium.m b/Simperium/NSData+Simperium.m index a08cf405..0329746d 100644 --- a/Simperium/NSData+Simperium.m +++ b/Simperium/NSData+Simperium.m @@ -8,11 +8,6 @@ #import "NSData+Simperium.h" -@interface FIXCATEGORYBUGDATA; -@end -@implementation FIXCATEGORYBUGDATA; -@end - // From https://github.com/mikeho/QSUtilities static const short _base64DecodingTable[256] = { -2, -2, -2, -2, -2, -2, -2, -2, -2, -1, -1, -2, -1, -1, -2, -2, @@ -37,12 +32,11 @@ @implementation NSData(NSData_Simperium) + (NSData *)decodeBase64WithString:(NSString *)strBase64 { const char * objPointer = [strBase64 cStringUsingEncoding:NSASCIIStringEncoding]; - int intLength = strlen(objPointer); + int intLength = (int)strlen(objPointer); int intCurrent; int i = 0, j = 0, k; - unsigned char * objResult; - objResult = calloc(intLength, sizeof(char)); + unsigned char * objResult = calloc(intLength, sizeof(unsigned char)); // Run through the whole string, converting as we go while ( ((intCurrent = *objPointer++) != '\0') && (intLength-- > 0) ) { @@ -105,8 +99,7 @@ + (NSData *)decodeBase64WithString:(NSString *)strBase64 { } // Cleanup and setup the return NSData - NSData * objData = [[[NSData alloc] initWithBytes:objResult length:j] autorelease]; - free(objResult); + NSData* objData = [NSData dataWithBytesNoCopy:objResult length:j freeWhenDone:YES]; return objData; } @end diff --git a/Simperium/NSDate+Simperium.m b/Simperium/NSDate+Simperium.m index 4a6f6505..738ed581 100644 --- a/Simperium/NSDate+Simperium.m +++ b/Simperium/NSDate+Simperium.m @@ -8,11 +8,6 @@ #import "NSDate+Simperium.h" -@interface FIXCATEGORYBUGDATE; -@end -@implementation FIXCATEGORYBUGDATE; -@end - @implementation NSDate(NSDate_Simperium) -(NSString *)sp_stringBeforeNow diff --git a/Simperium/NSString+Simperium.m b/Simperium/NSString+Simperium.m index 0f3a5a09..af3d0576 100644 --- a/Simperium/NSString+Simperium.m +++ b/Simperium/NSString+Simperium.m @@ -9,11 +9,6 @@ #import "NSString+Simperium.h" #import -@interface FIXCATEGORYBUGSPSTR; -@end -@implementation FIXCATEGORYBUGSPSTR; -@end - static const char _base64EncodingTable[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; @implementation NSString(NSString_Simperium) @@ -29,7 +24,7 @@ + (NSString *)sp_encodeBase64WithData:(NSData *)objData { char * strResult; // Get the Raw Data length and ensure we actually have data - int intLength = [objData length]; + int intLength = (int)[objData length]; if (intLength == 0) return nil; // Setup the String-based Result placeholder and pointer within that placeholder @@ -66,20 +61,19 @@ + (NSString *)sp_encodeBase64WithData:(NSData *)objData { *objPointer = '\0'; // Return the results as an NSString object - return [NSString stringWithCString:strResult encoding:NSASCIIStringEncoding]; + NSString *string = [NSString stringWithCString:strResult encoding:NSASCIIStringEncoding]; + free(strResult); + return string; } + (NSString *)sp_makeUUID { // From http://stackoverflow.com/questions/427180/how-to-create-a-guid-uuid-using-the-iphone-sdk CFUUIDRef theUUID = CFUUIDCreate(NULL); - CFStringRef string = CFUUIDCreateString(NULL, theUUID); + NSString *str = CFBridgingRelease(CFUUIDCreateString(NULL, theUUID)); CFRelease(theUUID); - NSString *str = [(NSString *)string autorelease]; - str = [[str stringByReplacingOccurrencesOfString:@"-" withString:@""] lowercaseString]; - - return str; + return [[str stringByReplacingOccurrencesOfString:@"-" withString:@""] lowercaseString]; } + (NSString *)sp_md5StringFromData:(NSData *)data @@ -89,7 +83,7 @@ + (NSString *)sp_md5StringFromData:(NSData *)data unsigned char resultCString[16]; [data getBytes:cData length:[data length]]; - CC_MD5(cData, [data length], resultCString); + CC_MD5(cData, (int)[data length], resultCString); free(cData); NSString *result = [NSString stringWithFormat: @@ -104,9 +98,9 @@ + (NSString *)sp_md5StringFromData:(NSData *)data - (NSString *)sp_urlEncodeString { - return (NSString *) CFURLCreateStringByAddingPercentEscapes(NULL, (CFStringRef)self, - NULL, (CFStringRef)@";/?:@&=$+{}<>!*'()%#[],", - CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding)); + return (NSString *) CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(NULL, (CFStringRef)self, + NULL, (CFStringRef)@";/?:@&=$+{}<>!*'()%#[],", + CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding))); } @end diff --git a/Simperium/SPAuthView.h b/Simperium/SPAuthView.h deleted file mode 100644 index 3594cbdd..00000000 --- a/Simperium/SPAuthView.h +++ /dev/null @@ -1,12 +0,0 @@ -// -// AuthWindowController.h -// Simplenote-OSX -// -// Created by Rainieri Ventura on 2/22/12. -// Copyright (c) 2012 Simperium. All rights reserved. -// - -#import - -@interface AuthView : NSView -@end diff --git a/Simperium/SPAuthView.m b/Simperium/SPAuthView.m deleted file mode 100644 index 26bc44b9..00000000 --- a/Simperium/SPAuthView.m +++ /dev/null @@ -1,30 +0,0 @@ -// -// AuthWindowController.m -// Simplenote-OSX -// -// Created by Rainieri Ventura on 2/22/12. -// Copyright (c) 2012 Simperium. All rights reserved. -// - -#import "AuthView.h" - -@implementation AuthView - -- (id)initWithFrame:(NSRect)frame { - self = [super initWithFrame:frame]; - if (self) { - // Initialization code here. - - } - return self; -} - -- (void)drawRect:(NSRect)dirtyRect { - // set any NSColor for filling, say white: - NSImage *image = [NSImage imageNamed:@"auth_bgnoise.png"]; - NSColor *noise = [NSColor colorWithPatternImage:image]; - [noise setFill]; - NSRectFill(dirtyRect); -} - -@end diff --git a/Simperium/SPAuthWindowController.h b/Simperium/SPAuthWindowController.h deleted file mode 100644 index 61457961..00000000 --- a/Simperium/SPAuthWindowController.h +++ /dev/null @@ -1,28 +0,0 @@ -// -// SPAuthWindowController.h -// Simplenote-OSX -// -// Created by Rainieri Ventura on 2/22/12. -// Copyright (c) 2012 Simperium. All rights reserved. -// - -#import - -@class SPAuthenticationManager; - -@interface SPAuthWindowController : NSWindowController -{ - SPAuthenticationManager *_authManager; - IBOutlet NSTextFieldCell *signinText; - IBOutlet NSButton *signinButton; - IBOutlet NSTextField *emailField; - IBOutlet NSTextField *passwordField; - IBOutlet NSMenuItem *logout; -} - -@property (nonatomic, retain) SPAuthenticationManager *authManager; - -- (IBAction)signinClicked:(id)sender; - -- (id)initWithWindowNibName:(NSString *)windowName; -@end diff --git a/Simperium/SPAuthWindowController.m b/Simperium/SPAuthWindowController.m deleted file mode 100644 index 6ae8ec4a..00000000 --- a/Simperium/SPAuthWindowController.m +++ /dev/null @@ -1,54 +0,0 @@ -// -// SPAuthWindowController.m -// Simplenote-OSX -// -// Created by Rainieri Ventura on 2/22/12. -// Copyright (c) 2012 Simperium. All rights reserved. -// - -#import "SPAuthWindowController.h" -#import "SPAuthenticationManager.h" - -@implementation SPAuthWindowController -@synthesize authManager = _authManager; - -- (id)initWithWindowNibName:(NSString *)windowName; -{ - self = [super initWithWindowNibName:windowName]; - if (self) { - } - - return self; -} - -- (void)dealloc { - self.authManager = nil; - [super dealloc]; -} - -- (void)windowDidLoad -{ - [super windowDidLoad]; - - // Implement this method to handle any initialization after your window controller's window has been loaded from its nib file. - - //Header - [signinText setBackgroundStyle:NSBackgroundStyleLowered]; - - //Button - [[signinButton cell]setBackgroundStyle:NSBackgroundStyleLowered]; - - -} - -- (IBAction)signinClicked:(id)sender{ - [self.authManager authenticateWithUsername:[emailField stringValue] password:[passwordField stringValue] - success:^{ - - } - failure:^(int responseCode, NSString *responseString){ - } - ]; -} - -@end diff --git a/Simperium/SPAuthenticationButton.h b/Simperium/SPAuthenticationButton.h new file mode 100644 index 00000000..a3ee77ad --- /dev/null +++ b/Simperium/SPAuthenticationButton.h @@ -0,0 +1,27 @@ +// +// SPAuthenticationButton.h +// Simperium +// +// Created by Tom Witkin on 8/4/13. +// Copyright (c) 2013 Simperium. All rights reserved. +// + +#import + +@interface SPAuthenticationButton : UIButton { + UIView *errorView; + UILabel *errorLabel; + + UIColor *backgroundColor; + UIColor *backgroundHighlightColor; + + NSTimer *clearErrorTimer; +} + +@property (nonatomic, strong) UIColor *backgroundHighlightColor; +@property (nonatomic, strong) UILabel *detailTitleLabel; + +- (void)showErrorMessage:(NSString *)errorMessage; +- (void)clearErrorMessage; + +@end diff --git a/Simperium/SPAuthenticationButton.m b/Simperium/SPAuthenticationButton.m new file mode 100644 index 00000000..9372fa92 --- /dev/null +++ b/Simperium/SPAuthenticationButton.m @@ -0,0 +1,128 @@ +// +// SPAutehnticationButton.m +// Simperium +// +// Created by Tom Witkin on 8/4/13. +// Copyright (c) 2013 Simperium. All rights reserved. +// + +#import "SPAuthenticationButton.h" +#import "SPAuthenticationConfiguration.h" +#import + + + +static NSString* const SPAuthenticationHighlightedKey = @"highlighted"; + + +@implementation SPAuthenticationButton +@synthesize backgroundHighlightColor; + +- (void)dealloc { + [self removeObserver:self forKeyPath:SPAuthenticationHighlightedKey]; +} + +- (id)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + [self addObserver:self + forKeyPath:SPAuthenticationHighlightedKey + options:NSKeyValueObservingOptionNew + context:NULL]; + + self.titleLabel.textAlignment = NSTextAlignmentCenter; + + _detailTitleLabel = [[UILabel alloc] init]; + _detailTitleLabel.backgroundColor = [UIColor clearColor]; + _detailTitleLabel.textAlignment = NSTextAlignmentCenter; + [self addSubview:_detailTitleLabel]; + + errorView = [[UIView alloc] initWithFrame:self.bounds]; + errorView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; + errorView.backgroundColor = [UIColor colorWithRed:222.0 / 255.0 + green:33.0 / 255.0 + blue:49.0 / 255.0 + alpha:1.0]; + [self addSubview:errorView]; + + CGRect errorLabelFrame = self.bounds; + errorLabelFrame.origin.x += 10.0; + errorLabelFrame.size.width -= 2 * 10.0; + + errorLabel = [[UILabel alloc] initWithFrame:errorLabelFrame]; + errorLabel.backgroundColor = [UIColor clearColor]; + errorLabel.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; + errorLabel.numberOfLines = 0; + errorLabel.textAlignment = NSTextAlignmentCenter; + errorLabel.textColor = [UIColor whiteColor]; + errorLabel.font = [UIFont fontWithName:[SPAuthenticationConfiguration sharedInstance].mediumFontName size:12.0]; + [errorView addSubview:errorLabel]; + + errorView.alpha = 0.0; + } + + return self; +} + +- (void)layoutSubviews { + + [super layoutSubviews]; + + // layout title labels + CGFloat titleLabelHeight = self.titleLabel.font.lineHeight; + CGFloat detailTitleLabelHeight = self.detailTitleLabel.text.length > 0 ? self.detailTitleLabel.font.lineHeight : 0; + CGFloat height = self.bounds.size.height; + CGFloat padding = 10.0; + + self.detailTitleLabel.frame = CGRectMake(padding, + (height - (titleLabelHeight + detailTitleLabelHeight)) / 2.0, + self.bounds.size.width - 2 * padding, + detailTitleLabelHeight); + + self.titleLabel.frame = CGRectMake(padding, + detailTitleLabelHeight + self.detailTitleLabel.frame.origin.y, + self.bounds.size.width - 2 * padding, + titleLabelHeight); +} + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { + for (UIView *view in self.subviews) { + if (view.class == [UIImageView class]) + [(UIImageView *)view setHighlighted:self.highlighted]; + + } + [self setNeedsDisplay]; +} + +- (void)drawRect:(CGRect)rect { + self.layer.backgroundColor = self.highlighted ? backgroundHighlightColor.CGColor : backgroundColor.CGColor; +} + +- (void)setBackgroundColor:(UIColor *)bgcolor { + backgroundColor = bgcolor; +} + +- (void)setBackgroundHighlightColor:(UIColor *)bgHighlightColor { + backgroundHighlightColor = bgHighlightColor; +} + +- (void)showErrorMessage:(NSString *)errorMessage { + [self bringSubviewToFront:errorView]; + + errorLabel.text = errorMessage.uppercaseString; + [UIView animateWithDuration:0.05 + animations:^{ + errorView.alpha = 1.0; + }]; + clearErrorTimer = [NSTimer scheduledTimerWithTimeInterval:4.0 + target:self + selector:@selector(clearErrorMessage) + userInfo:nil + repeats:NO]; +} + +- (void)clearErrorMessage { + errorView.alpha = 0.0; +} + +@end diff --git a/Simperium/SPAuthenticationConfiguration.h b/Simperium/SPAuthenticationConfiguration.h new file mode 100644 index 00000000..88880980 --- /dev/null +++ b/Simperium/SPAuthenticationConfiguration.h @@ -0,0 +1,25 @@ +// +// SPAuthenticationConfiguration.h +// Simperium-OSX +// +// Created by Michael Johnston on 7/29/13. +// Copyright (c) 2013 Simperium. All rights reserved. +// + +#import + +@interface SPAuthenticationConfiguration : NSObject + +@property (nonatomic, copy) NSString *regularFontName; +@property (nonatomic, copy) NSString *mediumFontName; +@property (nonatomic, copy) NSString *logoImageName; + +#if TARGET_OS_IPHONE +#else +@property (nonatomic, retain) NSColor *controlColor; +#endif + ++ (SPAuthenticationConfiguration *)sharedInstance; +- (float)regularFontHeightForSize:(float)size; + +@end diff --git a/Simperium/SPAuthenticationConfiguration.m b/Simperium/SPAuthenticationConfiguration.m new file mode 100644 index 00000000..005ff69b --- /dev/null +++ b/Simperium/SPAuthenticationConfiguration.m @@ -0,0 +1,76 @@ +// +// SPAuthenticationConfiguration.m +// Simperium-OSX +// +// Created by Michael Johnston on 7/29/13. +// Copyright (c) 2013 Simperium. All rights reserved. +// + +#import "SPAuthenticationConfiguration.h" + +#define kFontTestString @"Testyj" + +#if TARGET_OS_IPHONE +#import +#endif + +@implementation SPAuthenticationConfiguration + +static SPAuthenticationConfiguration *gInstance = NULL; + ++ (SPAuthenticationConfiguration *)sharedInstance +{ + @synchronized(self) + { + if (gInstance == NULL) + gInstance = [[self alloc] init]; + } + + return(gInstance); +} + +- (id)init { + if ((self = [super init])) { + _regularFontName = @"HelveticaNeue"; + _mediumFontName = @"HelveticaNeue-Medium"; + +#if TARGET_OS_IPHONE +#else + self.controlColor = [NSColor colorWithCalibratedRed:65.f/255.f green:137.f/255.f blue:199.f/255.f alpha:1.0]; +#endif + } + + return self; +} + +// Just quick and dirty fonts for now. Could be extended with colors. +// In an app this would likely be done in an external .plist file, but for a framework, +// keeping in code avoids having to include a resource. +#if TARGET_OS_IPHONE + +- (float)regularFontHeightForSize:(float)size { + // Not cached, but could be + return [kFontTestString sizeWithFont:[UIFont fontWithName:self.regularFontName size:size]].height; +} + +#else + +- (NSFont *)regularFontWithSize:(CGFloat)size { + return [NSFont fontWithName:_regularFontName size:size]; +} + +- (NSFont *)mediumFontWithSize:(CGFloat)size { + return [NSFont fontWithName:_mediumFontName size:size]; +} + +- (float)regularFontHeightForSize:(float)size { + // Not cached, but could be + NSDictionary *attributes = @{NSFontAttributeName : [self regularFontWithSize:size], + NSFontSizeAttribute : [NSString stringWithFormat:@"%f", size]}; + return [kFontTestString sizeWithAttributes:attributes].height; +} +#endif + + + +@end diff --git a/Simperium/SPAuthenticationManager.h b/Simperium/SPAuthenticationManager.h deleted file mode 100644 index f185aece..00000000 --- a/Simperium/SPAuthenticationManager.h +++ /dev/null @@ -1,39 +0,0 @@ -// -// SPAuthenticationManager.h -// Simperium -// -// Created by Michael Johnston on 12-02-27. -// Copyright (c) 2012 Simperium. All rights reserved. -// - -#import -typedef void(^SucceededBlockType)(void); -typedef void(^FailedBlockType)(int responseCode, NSString *responseString); - -@class Simperium; - -@protocol SPAuthenticationDelegate -@optional --(void)authenticationDidSucceedForUsername:(NSString *)username token:(NSString *)token; --(void)authenticationDidFail; --(void)authenticationDidCancel; -@end - -@interface SPAuthenticationManager : NSObject { - Simperium *simperium; - id delegate; - SucceededBlockType succeededBlock; - FailedBlockType failedBlock; -} - -@property(nonatomic, copy) SucceededBlockType succeededBlock; -@property(nonatomic, copy) FailedBlockType failedBlock; - --(id)initWithDelegate:(id)authDelegate simperium:(Simperium *)s; --(BOOL)authenticateIfNecessary; --(void)authenticateWithUsername:(NSString *)username password:(NSString *)password success:(SucceededBlockType)successBlock failure:(FailedBlockType)failureBlock; --(void)createWithUsername:(NSString *)username password:(NSString *)password success:(SucceededBlockType)successBlock failure:(FailedBlockType)failureBlock; --(void)reset; --(void)cancel; - -@end diff --git a/Simperium/SPAuthenticationValidator.h b/Simperium/SPAuthenticationValidator.h new file mode 100644 index 00000000..318904b2 --- /dev/null +++ b/Simperium/SPAuthenticationValidator.h @@ -0,0 +1,18 @@ +// +// SPAuthenticationValidator.h +// Simperium-OSX +// +// Created by Michael Johnston on 8/14/13. +// Copyright (c) 2013 Simperium. All rights reserved. +// + +#import + +@interface SPAuthenticationValidator : NSObject + +@property (nonatomic, assign) NSUInteger minimumPasswordLength; + +- (BOOL)validateUsername:(NSString *)username; +- (BOOL)validatePasswordSecurity:(NSString *)password; + +@end diff --git a/Simperium/SPAuthenticationValidator.m b/Simperium/SPAuthenticationValidator.m new file mode 100644 index 00000000..fb8236d6 --- /dev/null +++ b/Simperium/SPAuthenticationValidator.m @@ -0,0 +1,50 @@ +// +// SPAuthenticationValidator.m +// Simperium-OSX +// +// Created by Michael Johnston on 8/14/13. +// Copyright (c) 2013 Simperium. All rights reserved. +// + +#import "SPAuthenticationValidator.h" + +static int kDefaultMinimumPasswordLength = 4; + +@implementation SPAuthenticationValidator +@synthesize minimumPasswordLength; + +- (id)init { + if ((self = [super init])) { + self.minimumPasswordLength = kDefaultMinimumPasswordLength; + } + + return self; +} + +- (BOOL)isValidEmail:(NSString *)checkString { + // From http://stackoverflow.com/a/3638271/1379066 + BOOL stricterFilter = YES; // Discussion http://blog.logichigh.com/2010/09/02/validating-an-e-mail-address/ + NSString *stricterFilterString = @"[A-Z0-9a-z\\._%+-]+@([A-Za-z0-9-]+\\.)+[A-Za-z]{2,4}"; + NSString *laxString = @".+@([A-Za-z0-9]+\\.)+[A-Za-z]{2}[A-Za-z]*"; + NSString *emailRegex = stricterFilter ? stricterFilterString : laxString; + NSPredicate *emailTest = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", emailRegex]; + + return [emailTest evaluateWithObject:checkString]; +} + +- (BOOL)validateUsername:(NSString *)username { + // Expect email addresses by default + return [self isValidEmail:username]; +} + +- (BOOL)validatePasswordSecurity:(NSString *)password { + if ([password length] < self.minimumPasswordLength) { + return NO; + } + + // Could enforce other requirements here + return YES; +} + + +@end diff --git a/Simperium/SPAuthenticationViewController.h b/Simperium/SPAuthenticationViewController.h new file mode 100644 index 00000000..f39dd810 --- /dev/null +++ b/Simperium/SPAuthenticationViewController.h @@ -0,0 +1,41 @@ +// +// SPAuthenticationViewController.h +// Simperium +// +// Created by Michael Johnston on 24/11/11. +// Copyright 2011 Simperium. All rights reserved. +// +// You can write a subclass of SPAuthenticationViewController and then set authenticationViewControllerClass +// on your Simperium instance in order to fully customize the behavior of the authentication UI. +// +// Simperium will use the subclass and display your UI automatically. + +#import + +@class SPAuthenticator; +@class SPAuthenticationButton; +@class SPAuthenticationValidator; + +@interface SPAuthenticationViewController : UIViewController +{ + SPAuthenticator *authenticator; + SPAuthenticationValidator *validator; + BOOL creating; + UITextField *usernameField; + UITextField *passwordField; + UITextField *passwordConfirmField; + UIActivityIndicatorView *progressView; + SPAuthenticationButton *actionButton; + UIButton *termsButton; + SPAuthenticationButton *changeButton; + + BOOL editing; + + UIBarButtonItem *cancelButton; +} + +@property (nonatomic, strong) SPAuthenticator *authenticator; +@property (nonatomic, strong) UITableView* tableView; +@property (nonatomic, strong) UIImageView *logoView; + +@end diff --git a/Simperium/SPAuthenticationViewController.m b/Simperium/SPAuthenticationViewController.m new file mode 100644 index 00000000..fea01a45 --- /dev/null +++ b/Simperium/SPAuthenticationViewController.m @@ -0,0 +1,627 @@ +// +// SPAutehnticationViewController.m +// Simperium +// +// Created by Michael Johnston on 24/11/11. +// Copyright 2011 Simperium. All rights reserved. +// + +#import "SPAuthenticationViewController.h" +#import "SPAuthenticator.h" +#import +#import "ASIFormDataRequest.h" +#import "JSONKit.h" +#import "SPAuthenticationButton.h" +#import "SPAuthenticationConfiguration.h" +#import "SPAuthenticationValidator.h" +#import "SPTOSViewController.h" + +@interface SPAuthenticationViewController() + +@property (nonatomic) CGFloat keyboardHeight; + +-(void)earthquake:(UIView*)itemView; +-(void)changeAction:(id)sender; +@end + +@implementation SPAuthenticationViewController +@synthesize authenticator; + +- (void)setCreating:(BOOL)bCreating { + creating = bCreating; + + NSString *actionTitle = creating ? + NSLocalizedString(@"Sign Up", @"Title of button to create a new account (must be short)") : + NSLocalizedString(@"Sign In", @"Title of button for logging in (must be short)"); + NSString *changeTitle = creating ? + NSLocalizedString(@"Sign in", @"A short link to access the account login screen") : + NSLocalizedString(@"Sign up", @"A short link to access the account creation screen"); + NSString *changeDetailTitle = creating ? + NSLocalizedString(@"Already have an account?", @"A short description to access the account login screen") : + NSLocalizedString(@"Don't have an account?", @"A short description to access the account creation screen"); + + changeTitle = [changeTitle stringByAppendingString:@" »"]; + + [actionButton setTitle: actionTitle forState:UIControlStateNormal]; + [changeButton setTitle:changeTitle.uppercaseString forState:UIControlStateNormal]; + changeButton.detailTitleLabel.text = changeDetailTitle.uppercaseString; + + termsButton.hidden = !bCreating; +} + +- (void)viewDidLoad { + validator = [[SPAuthenticationValidator alloc] init]; + + // Should eventually be paramaterized + UIColor *whiteColor = [UIColor colorWithWhite:0.99 alpha:1.0]; + UIColor *blueColor = [UIColor colorWithRed:66.0 / 255.0 green:137 / 255.0 blue:201 / 255.0 alpha:1.0]; + UIColor *darkBlueColor = [UIColor colorWithRed:36.0 / 255.0 green:100.0 / 255.0 blue:158.0 / 255.0 alpha:1.0]; + UIColor *lightGreyColor = [UIColor colorWithWhite:0.92 alpha:1.0]; + UIColor *greyColor = [UIColor colorWithWhite:0.7 alpha:1.0]; + + self.view.backgroundColor = whiteColor; + + // The cancel button will only be visible if there's a navigation controller, which will only happen + // if authenticationOptional has been set on the Simperium instance. + NSString *cancelTitle = NSLocalizedString(@"Cancel", @"Cancel button for authentication"); + cancelButton = [[UIBarButtonItem alloc] initWithTitle:cancelTitle + style:UIBarButtonItemStyleBordered + target:self + action:@selector(cancelAction:)]; + self.navigationItem.rightBarButtonItem = cancelButton; + + self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds + style:UITableViewStyleGrouped]; + self.tableView.delegate = self; + self.tableView.dataSource = self; + self.tableView.backgroundColor = [UIColor clearColor]; + self.tableView.backgroundView = nil; + self.tableView.separatorColor = lightGreyColor; + self.tableView.clipsToBounds = NO; + [self.view addSubview:self.tableView]; + + if (self.view.bounds.size.height <= 480.0) { + self.tableView.rowHeight = 38.0; + } + + termsButton = [UIButton buttonWithType:UIButtonTypeCustom]; + [termsButton addTarget:self + action:@selector(termsAction:) + forControlEvents:UIControlEventTouchUpInside]; + termsButton.titleEdgeInsets = UIEdgeInsetsMake(3, 0, 0, 0); + termsButton.titleLabel.font = [UIFont fontWithName:[SPAuthenticationConfiguration sharedInstance].mediumFontName size:10.0]; + termsButton.frame= CGRectMake(10.0, 0.0, self.tableView.frame.size.width-20.0, 24.0); + termsButton.autoresizingMask = UIViewAutoresizingFlexibleWidth; + NSMutableAttributedString *termsTitle = [[NSMutableAttributedString alloc] initWithString:[@"By signing up, you agree to our Terms of Service »" uppercaseString] attributes:@{NSForegroundColorAttributeName: [greyColor colorWithAlphaComponent:0.4]}]; + [termsTitle setAttributes:@{NSUnderlineStyleAttributeName: [NSNumber numberWithInt:NSUnderlineStyleSingle], NSForegroundColorAttributeName : [greyColor colorWithAlphaComponent:0.4]} + range:NSMakeRange(32, 16)]; + [termsButton setAttributedTitle:termsTitle + forState:UIControlStateNormal];; + + actionButton = [[SPAuthenticationButton alloc] initWithFrame:CGRectMake(0, 30.0, self.view.frame.size.width, 44)]; + [actionButton addTarget:self + action:@selector(goAction:) + forControlEvents:UIControlEventTouchUpInside]; + [actionButton setTitleColor:whiteColor forState:UIControlStateNormal]; + actionButton.titleLabel.font = [UIFont fontWithName:[SPAuthenticationConfiguration sharedInstance].regularFontName size:22.0]; + + [actionButton setBackgroundColor:blueColor]; + [actionButton setBackgroundHighlightColor:darkBlueColor]; + actionButton.autoresizingMask = UIViewAutoresizingFlexibleWidth; + + changeButton = [[SPAuthenticationButton alloc] initWithFrame:CGRectZero]; + [changeButton addTarget:self + action:@selector(changeAction:) + forControlEvents:UIControlEventTouchUpInside]; + [changeButton setTitleColor:blueColor forState:UIControlStateNormal]; + [changeButton setTitleColor:greyColor forState:UIControlStateHighlighted]; + changeButton.detailTitleLabel.textColor = greyColor; + changeButton.detailTitleLabel.font = [UIFont fontWithName:[SPAuthenticationConfiguration sharedInstance].mediumFontName size:12.5]; + changeButton.titleLabel.font = [UIFont fontWithName:[SPAuthenticationConfiguration sharedInstance].mediumFontName size:12.5]; + changeButton.frame= CGRectMake(10.0, 80.0, self.tableView.frame.size.width-20.0, 40.0); + changeButton.autoresizingMask = UIViewAutoresizingFlexibleWidth; + + progressView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite]; + progressView.frame = CGRectMake(actionButton.frame.size.width - 30, (actionButton.frame.size.height - 20) / 2.0, 20, 20); + progressView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin; + [actionButton addSubview:progressView]; + + UIImage *logo = [UIImage imageNamed:[SPAuthenticationConfiguration sharedInstance].logoImageName]; + _logoView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, logo.size.width, logo.size.height)]; + _logoView.image = logo; + _logoView.contentMode = UIViewContentModeCenter; + [self.view addSubview:_logoView]; + + UIView *footerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, changeButton.frame.size.height + changeButton.frame.origin.y)]; + footerView.contentMode = UIViewContentModeTopLeft; + [footerView setUserInteractionEnabled:YES]; + [footerView addSubview:termsButton]; + [footerView addSubview:actionButton]; + [footerView addSubview:changeButton]; + footerView.autoresizingMask = UIViewAutoresizingFlexibleWidth; + self.tableView.tableFooterView = footerView; + + UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self + action:@selector(endEditingAction:)]; + tapGesture.numberOfTouchesRequired = 1; + tapGesture.numberOfTapsRequired = 1; + [self.tableView addGestureRecognizer:tapGesture]; + + self.creating = YES; + + // Layout views + [self layoutViewsForInterfaceOrientation:self.interfaceOrientation]; +} + +- (CGFloat)topInset { + CGFloat navigationBarHeight = self.navigationController.navigationBar.frame.size.height + self.navigationController.navigationBar.frame.origin.y; + + return navigationBarHeight > 0 ? navigationBarHeight : 20.0; // 20.0 refers to the status bar height +} + +- (void)layoutViewsForInterfaceOrientation:(UIInterfaceOrientation)orientation { + CGFloat viewWidth = UIInterfaceOrientationIsPortrait(orientation) ? MIN(self.view.frame.size.width, self.view.frame.size.height) : MAX(self.view.frame.size.width, self.view.frame.size.height); + + _logoView.frame = CGRectMake((viewWidth - _logoView.frame.size.width) / 2.0, + (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) ? 180.0 : 20.0 + self.topInset, + _logoView.frame.size.width, + _logoView.frame.size.height); + + CGFloat tableViewYOrigin = _logoView.frame.origin.y + _logoView.frame.size.height; + CGFloat tableViewWidth = (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) ? 400 : viewWidth; + + _tableView.frame = CGRectMake((viewWidth - tableViewWidth) / 2.0, + tableViewYOrigin, + tableViewWidth, + self.view.frame.size.height - tableViewYOrigin); + + [self.view sendSubviewToBack:_logoView]; +} + +- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration { + [self layoutViewsForInterfaceOrientation:toInterfaceOrientation]; +} + +- (BOOL)shouldAutorotate { + return !editing; +} + +- (NSUInteger)supportedInterfaceOrientations { + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) + return UIInterfaceOrientationMaskPortrait; + + return UIInterfaceOrientationMaskAll; +} + +- (void)viewWillAppear:(BOOL)animated { + self.tableView.scrollEnabled = NO; + + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + self.tableView.scrollEnabled = NO; + [self.tableView setBackgroundView:nil]; + } + + // register for keyboard notifications + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil]; +} + +- (void)viewWillDisappear:(BOOL)animated { + // un-register for keyboard notifications + [[NSNotificationCenter defaultCenter] removeObserver: self name:UIKeyboardWillHideNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver: self name:UIKeyboardWillShowNotification object:nil]; +} + + +#pragma mark Keyboard + +- (void)keyboardWillShow:(NSNotification *)notification { + editing = YES; + + CGRect keyboardFrame = [(NSValue *)[notification.userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue]; + _keyboardHeight = MIN(keyboardFrame.size.height, keyboardFrame.size.width); + CGFloat duration = [(NSNumber *)[notification.userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] floatValue]; + + [self positionTableViewWithDuration:duration]; +} + +- (void)keyboardWillHide:(NSNotification *)notification { + editing = NO; + + CGFloat duration = [(NSNumber *)[notification.userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] floatValue]; + + _keyboardHeight = 0; + + [self positionTableViewWithDuration:duration]; +} + +- (void)positionTableViewWithDuration:(CGFloat)duration { + CGRect newFrame = self.view.bounds; + + if (_keyboardHeight > 0) { + CGFloat maxHeight = newFrame.size.height - _keyboardHeight - self.topInset; + CGFloat tableViewHeight = [self.tableView tableFooterView].frame.origin.y + [self.tableView tableFooterView].frame.size.height; + CGFloat tableViewTopPadding = [self.tableView convertRect:[self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]].frame fromView:self.tableView].origin.y; + + newFrame.origin.y = MAX((maxHeight - tableViewHeight - tableViewTopPadding) / 2.0 + self.topInset, self.topInset - tableViewTopPadding); + newFrame.size.height = maxHeight + tableViewTopPadding; + + self.tableView.scrollEnabled = YES; + } else { + newFrame.origin.y = _logoView.frame.origin.y + _logoView.frame.size.height; + newFrame.size.height = self.view.frame.size.height - newFrame.origin.y; + self.tableView.scrollEnabled = NO; + } + + newFrame.size.width = self.tableView.frame.size.width; + newFrame.origin.x = self.tableView.frame.origin.x; + + if (!(_keyboardHeight > 0)) { + self.logoView.hidden = NO; + } + + self.tableView.tableHeaderView.alpha = _keyboardHeight > 0 ? 1.0 : 0.0; + + [UIView animateWithDuration:duration + animations:^{ + self.tableView.frame = newFrame; + self.logoView.alpha = _keyboardHeight > 0 ? 0.0 : 1.0; + } completion:^(BOOL finished) { + self.logoView.hidden = (_keyboardHeight > 0); + }]; +} + +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) + return YES; + + return (interfaceOrientation == UIInterfaceOrientationPortrait); +} + + +#pragma mark Validation +- (BOOL)validateUsername { + if (![validator validateUsername:usernameField.text]) { + [actionButton showErrorMessage:NSLocalizedString(@"Your email address is not valid.", @"Message displayed when email address is invalid")]; + + [self earthquake:[self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]]; + return NO; + } + + return YES; +} + +- (BOOL)validatePassword { + if (![validator validatePasswordSecurity:passwordField.text]) { + [actionButton showErrorMessage:NSLocalizedString(@"Password must contain at least 4 characters.", @"Message displayed when password is invalid")]; + [self earthquake:[self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForItem:1 inSection:0]]]; + return NO; + } + + return YES; +} + +- (BOOL)validateData { + if (![self validateUsername]) + return NO; + + return [self validatePassword]; +} + +- (BOOL)validatePasswordConfirmation { + if ([passwordField.text compare: passwordConfirmField.text] != NSOrderedSame) { + [self earthquake: passwordField]; + [self earthquake: passwordConfirmField]; + return NO; + } + + return YES; +} + + +#pragma mark Login + +- (void)performLogin { + actionButton.enabled = NO; + changeButton.enabled = NO; + cancelButton.enabled = NO; + + [usernameField resignFirstResponder]; + [passwordField resignFirstResponder]; + [progressView setHidden: NO]; + [progressView startAnimating]; + [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES]; + + [self.authenticator authenticateWithUsername:usernameField.text password:passwordField.text + success:^{ + [progressView setHidden: YES]; + [progressView stopAnimating]; + [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO]; + } + failure: ^(int responseCode, NSString *responseString){ + actionButton.enabled = YES; + changeButton.enabled = YES; + cancelButton.enabled = YES; + + [progressView setHidden: YES]; + [progressView stopAnimating]; + [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO]; + + [actionButton showErrorMessage:NSLocalizedString(@"Could not login with the provided email address and password.", @"Message displayed when login fails")]; + [self earthquake:[self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]]; + [self earthquake:[self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForItem:1 inSection:0]]]; + } + ]; +} + +#pragma mark Creation + +- (void)restoreCreationSettings { + actionButton.enabled = YES; + changeButton.enabled = YES; + cancelButton.enabled = YES; + [progressView setHidden: YES]; + [progressView stopAnimating]; + [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO]; + CFPreferencesSetAppValue(CFSTR("email"), @"", kCFPreferencesCurrentApplication); + CFPreferencesSetAppValue(CFSTR("password"), @"", kCFPreferencesCurrentApplication); + CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication); +} + +- (void)performCreation { + actionButton.enabled = NO; + changeButton.enabled = NO; + cancelButton.enabled = NO; + + [usernameField resignFirstResponder]; + [passwordField resignFirstResponder]; + [passwordConfirmField resignFirstResponder]; + CFPreferencesSetAppValue(CFSTR("email"), (__bridge CFPropertyListRef)(usernameField.text), kCFPreferencesCurrentApplication); + CFPreferencesSetAppValue(CFSTR("password"), (__bridge CFPropertyListRef)(passwordField.text), kCFPreferencesCurrentApplication); + CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication); + + // Try to login and sync after entering password? + [progressView setHidden: NO]; + [progressView startAnimating]; + [authenticator createWithUsername:usernameField.text password:passwordField.text + success:^{ + [progressView setHidden: YES]; + [progressView stopAnimating]; + [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO]; + } + failure:^(int responseCode, NSString *responseString){ + [self restoreCreationSettings]; + [actionButton showErrorMessage:NSLocalizedString(@"Could not create an account with the provided email address and password.", @"An error message")]; + + [self earthquake:[self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]]; + [self earthquake:[self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForItem:1 inSection:0]]]; + [self earthquake:[self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForItem:2 inSection:0]]]; + } + ]; +} + +- (void)failedDueToNetwork:(ASIHTTPRequest *)request { + [self restoreCreationSettings]; + NSString *message = NSLocalizedString(@"There's a problem with the connection. Please try again later.", + @"Details for a dialog that is displayed when there's a connection problem"); + + [actionButton showErrorMessage:message]; + + [self earthquake:[self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]]; + [self earthquake:[self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForItem:1 inSection:0]]]; +} + + +#pragma mark Actions + +- (void)termsAction:(id)sender { + + SPTOSViewController *vc = [[SPTOSViewController alloc] init]; + UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:vc]; + + UIViewController *presentingController; + if (self.navigationController) + presentingController = self.navigationController; + else + presentingController = self; + + [presentingController presentViewController:navController + animated:YES + completion:nil]; +} + +- (void)changeAction:(id)sender { + creating = !creating; + NSArray *indexPaths = [NSArray arrayWithObject:[NSIndexPath indexPathForRow:2 inSection:0]]; + if (creating) + [self.tableView insertRowsAtIndexPaths: indexPaths withRowAnimation:UITableViewRowAnimationTop]; + else + [self.tableView deleteRowsAtIndexPaths: indexPaths withRowAnimation:UITableViewRowAnimationTop]; + + [usernameField becomeFirstResponder]; + + [self setCreating:creating]; + + dispatch_async(dispatch_get_main_queue(), ^{ + [self positionTableViewWithDuration:0.3]; + }); +} + +- (void)goAction:(id)sender { + if ([self validateData]) { + if (creating && passwordConfirmField.text.length > 0) { + if ([self validatePasswordConfirmation]) + [self performCreation]; + } else { + [self performLogin]; + } + } +} + +- (void)cancelAction:(id)sender { + [authenticator cancel]; +} + +- (void)endEditingAction:(id)sender { + [self.view endEditing:YES]; +} + + +#pragma mark Text Field + +- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string { + [actionButton clearErrorMessage]; + + return YES; +} + +- (BOOL)textFieldShouldReturn:(UITextField *)theTextField { + if (theTextField == usernameField) { + if (![self validateUsername]) { + return NO; + } + + // Advance to next field and don't dismiss keyboard + [passwordField becomeFirstResponder]; + return NO; + } else if(theTextField == passwordField) { + if ([self validatePassword]) { + if (creating) { + // Advance to next field and don't dismiss keyboard + [passwordConfirmField becomeFirstResponder]; + return NO; + } else + [self performLogin]; + } + } else { + if (creating && [self validatePasswordConfirmation] && [self validateData]) + [self performCreation]; + } + + return YES; +} + + +- (UITextField *)textFieldWithPlaceholder:(NSString *)placeholder secure:(BOOL)secure { + UITextField *newTextField = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, 280, 25)]; + newTextField.autoresizingMask = UIViewAutoresizingFlexibleWidth; + newTextField.clearsOnBeginEditing = NO; + newTextField.autocorrectionType = UITextAutocorrectionTypeNo; + newTextField.autocapitalizationType = UITextAutocapitalizationTypeNone; + newTextField.secureTextEntry = secure; + newTextField.font = [UIFont fontWithName:[SPAuthenticationConfiguration sharedInstance].regularFontName size:22.0]; + newTextField.textColor = [UIColor colorWithWhite:0.3 alpha:1.0]; + [newTextField setDelegate:self]; + newTextField.returnKeyType = UIReturnKeyNext; + newTextField.clearButtonMode = UITextFieldViewModeWhileEditing; + newTextField.placeholder = placeholder; + + return newTextField; +} + +- (void)positionTextField:(UITextField *)textField inCell:(UITableViewCell *)cell { + CGFloat sidePadding = 10.0; + CGFloat fieldHeight = ceilf(textField.font.lineHeight); + + textField.frame = CGRectMake(sidePadding, + floorf((cell.bounds.size.height - fieldHeight) / 2.0), + cell.bounds.size.width - 2 * sidePadding, + fieldHeight); + +} + + +#pragma mark Table Data Source Methods + +- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section { + + return 4.0; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return creating ? 3 : 2; +} + +- (UITableViewCell *)tableView:(UITableView *)tView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + static NSString *EmailCellIdentifier = @"EmailCellIdentifier"; + static NSString *PasswordCellIdentifier = @"PasswordCellIdentifier"; + static NSString *ConfirmCellIdentifier = @"ConfirmCellIdentifier"; + + UITableViewCell *cell; + if (indexPath.row == 0) { + cell = [tView dequeueReusableCellWithIdentifier:EmailCellIdentifier]; + // Email + if (cell == nil) { + cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault + reuseIdentifier:EmailCellIdentifier]; + + usernameField = [self textFieldWithPlaceholder:@"email@email.com" + secure:NO]; + usernameField.keyboardType = UIKeyboardTypeEmailAddress; + [self positionTextField:usernameField inCell:cell]; + [cell.contentView addSubview:usernameField]; + } + } else if (indexPath.row == 1) { + cell = [tView dequeueReusableCellWithIdentifier:PasswordCellIdentifier]; + // Password + if (cell == nil) { + cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:PasswordCellIdentifier]; + + passwordField = [self textFieldWithPlaceholder:NSLocalizedString(@"Password", @"Hint displayed in the password field") + secure:YES]; + + [self positionTextField:passwordField inCell:cell]; + [cell.contentView addSubview:passwordField]; + } + + passwordField.returnKeyType = creating ? UIReturnKeyNext : UIReturnKeyGo; + } else { + cell = [tView dequeueReusableCellWithIdentifier:ConfirmCellIdentifier]; + // Password + if (cell == nil) { + cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ConfirmCellIdentifier]; + + passwordConfirmField = [self textFieldWithPlaceholder:NSLocalizedString(@"Confirm", @"Hint displayed in the password confirmation field") secure:YES]; + passwordConfirmField.returnKeyType = UIReturnKeyGo; + + [self positionTextField:passwordConfirmField inCell:cell]; + [cell.contentView addSubview:passwordConfirmField]; + } + } + cell.selectionStyle = UITableViewCellSelectionStyleNone; + cell.backgroundColor = [UIColor colorWithWhite:0.98 alpha:1.0]; + + return cell; +} + + +#pragma mark Helpers +- (void)earthquake:(UIView*)itemView { + // From http://stackoverflow.com/a/1827373/1379066 + CGFloat t = 2.0; + + CGAffineTransform leftQuake = CGAffineTransformTranslate(CGAffineTransformIdentity, t, 0); + CGAffineTransform rightQuake = CGAffineTransformTranslate(CGAffineTransformIdentity, -t, 0); + + itemView.transform = leftQuake; // starting point + + [UIView beginAnimations:@"earthquake" context:(__bridge void *)(itemView)]; + [UIView setAnimationRepeatAutoreverses:YES]; // important + [UIView setAnimationRepeatCount:5]; + [UIView setAnimationDuration:0.07]; + [UIView setAnimationDelegate:self]; + [UIView setAnimationDidStopSelector:@selector(earthquakeEnded:finished:context:)]; + + itemView.transform = rightQuake; // end here & auto-reverse + + [UIView commitAnimations]; +} + +- (void)earthquakeEnded:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context { + if ([finished boolValue]) { + UIView* item = (__bridge UIView *)context; + item.transform = CGAffineTransformIdentity; + } +} + +@end diff --git a/Simperium/SPAuthenticator.h b/Simperium/SPAuthenticator.h new file mode 100644 index 00000000..1917fa29 --- /dev/null +++ b/Simperium/SPAuthenticator.h @@ -0,0 +1,43 @@ +// +// SPAuthenticator.h +// Simperium +// +// Created by Michael Johnston on 12-02-27. +// Copyright (c) 2012 Simperium. All rights reserved. +// + +#import +typedef void(^SucceededBlockType)(void); +typedef void(^FailedBlockType)(int responseCode, NSString *responseString); + +@class Simperium; + +@protocol SPAuthenticatorDelegate +@optional +-(void)authenticationDidSucceedForUsername:(NSString *)username token:(NSString *)token; +-(void)authenticationDidFail; +-(void)authenticationDidCancel; +@end + +@interface SPAuthenticator : NSObject { + Simperium *__weak simperium; + id __weak delegate; + SucceededBlockType succeededBlock; + FailedBlockType failedBlock; + BOOL connected; +} + +@property(nonatomic, weak) Simperium *simperium; +@property(nonatomic, copy) SucceededBlockType succeededBlock; +@property(nonatomic, copy) FailedBlockType failedBlock; +@property(nonatomic, copy) NSString *providerString; +@property(assign) BOOL connected; + +- (id)initWithDelegate:(id)authDelegate simperium:(Simperium *)s; +- (BOOL)authenticateIfNecessary; +- (void)authenticateWithUsername:(NSString *)username password:(NSString *)password success:(SucceededBlockType)successBlock failure:(FailedBlockType)failureBlock; +- (void)createWithUsername:(NSString *)username password:(NSString *)password success:(SucceededBlockType)successBlock failure:(FailedBlockType)failureBlock; +- (void)reset; +- (void)cancel; + +@end diff --git a/Simperium/SPAuthenticationManager.m b/Simperium/SPAuthenticator.m similarity index 64% rename from Simperium/SPAuthenticationManager.m rename to Simperium/SPAuthenticator.m index c9a183ab..a31aee87 100644 --- a/Simperium/SPAuthenticationManager.m +++ b/Simperium/SPAuthenticator.m @@ -1,5 +1,5 @@ // -// SPAuthenticationManager.m +// SPAuthenticator.m // Simperium // // Created by Michael Johnston on 12-02-27. @@ -9,17 +9,16 @@ #import "Simperium.h" #import "SPEnvironment.h" #import "SPUser.h" -#import "SPAuthenticationManager.h" +#import "SPAuthenticator.h" #import "SPBinaryManager.h" #import "ASIFormDataRequest.h" #import "ASIHTTPRequest.h" #import "DDLog.h" #import "JSONKit.h" -#import "SPSimpleKeyChain.h" +#import "SFHFKeychainUtils.h" +#import "SPReachability.h" -#define AUTH_TOKEN_KEY @"SPAuthToken" #define USERNAME_KEY @"SPUsername" -#define PASSWORD_KEY @"SPPassword" #if TARGET_OS_IPHONE #import // for UIDevice @@ -29,13 +28,19 @@ static int ddLogLevel = LOG_LEVEL_INFO; -@interface SPAuthenticationManager() +@interface SPAuthenticator() { + SPReachability *reachability; +} + -(void)authDidFail:(ASIHTTPRequest *)request; @end -@implementation SPAuthenticationManager +@implementation SPAuthenticator @synthesize succeededBlock; @synthesize failedBlock; +@synthesize simperium; +@synthesize connected; +@synthesize providerString; + (int)ddLogLevel { return ddLogLevel; @@ -45,31 +50,43 @@ + (void)ddSetLogLevel:(int)logLevel { ddLogLevel = logLevel; } --(id)initWithDelegate:(id)authDelegate simperium:(Simperium *)s { +- (id)initWithDelegate:(id)authDelegate simperium:(Simperium *)s { if ((self = [super init])) { delegate = authDelegate; simperium = s; + + reachability = [SPReachability reachabilityForInternetConnection]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNetworkChange:) name:kReachabilityChangedNotification object:nil]; + connected = [reachability currentReachabilityStatus] != NotReachable; + [reachability startNotifier]; + } return self; } --(void)dealloc { - [super dealloc]; - self.failedBlock = nil; - self.succeededBlock = nil; +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +- (void)handleNetworkChange:(NSNotification *)notification { + if ([reachability currentReachabilityStatus] == NotReachable) { + connected = NO; + } else { + connected = YES; + } } // Open a UI to handle authentication if necessary --(BOOL)authenticateIfNecessary -{ +- (BOOL)authenticateIfNecessary { // Look up a stored token (if it exists) and try authenticating - NSMutableDictionary *credentials = [SPSimpleKeychain load:simperium.appID]; - NSString *token = [credentials objectForKey:AUTH_TOKEN_KEY]; - NSString *username = [credentials objectForKey:USERNAME_KEY]; + NSString *username = nil, *token = nil; + username = [[NSUserDefaults standardUserDefaults] objectForKey:USERNAME_KEY]; + + if (username) + token = [SFHFKeychainUtils getPasswordForUsername:username andServiceName:simperium.appID error:nil]; - if (!token || token.length == 0) - { - DDLogInfo(@"Simperium didn't find an existing auth token"); + if (!username || username.length == 0 || !token || token.length == 0) { + DDLogInfo(@"Simperium didn't find an existing auth token (username %@; token %@; appID: %@)", username, token, simperium.appID); if ([delegate respondsToSelector:@selector(authenticationDidFail)]) [delegate authenticationDidFail]; @@ -82,7 +99,6 @@ -(BOOL)authenticateIfNecessary // Set the Simperium user SPUser *aUser = [[SPUser alloc] initWithEmail:username token:token]; simperium.user = aUser; - [aUser release]; if ([delegate respondsToSelector:@selector(authenticationDidSucceedForUsername:token:)]) [delegate authenticationDidSucceedForUsername:username token:token]; @@ -91,16 +107,8 @@ -(BOOL)authenticateIfNecessary } // Perform the actual authentication calls to Simperium --(void)authenticateWithUsername:(NSString *)username password:(NSString *)password success:(SucceededBlockType)successBlock failure:(FailedBlockType)failureBlock -{ - // Set the user's details - NSMutableDictionary *credentials = [SPSimpleKeychain load:simperium.appID]; - if (!credentials) - credentials = [NSMutableDictionary dictionary]; - [credentials setObject:username forKey:USERNAME_KEY]; - [credentials setObject:password forKey:PASSWORD_KEY]; - [SPSimpleKeychain save:simperium.appID data: credentials]; - +- (void)authenticateWithUsername:(NSString *)username password:(NSString *)password success:(SucceededBlockType)successBlock failure:(FailedBlockType)failureBlock +{ NSURL *tokenURL = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@/authorize/", SPAuthURL, simperium.appID]]; DDLogInfo(@"Simperium authenticating: %@", [NSString stringWithFormat:@"%@%@/authorize/", SPAuthURL, simperium.appID]); DDLogVerbose(@"Simperium username is %@", username); @@ -126,10 +134,14 @@ -(void)authenticateWithUsername:(NSString *)username password:(NSString *)passwo [tokenRequest startAsynchronous]; } --(void)delayedAuthenticationDidFinish -{ - if (self.succeededBlock) +- (void)delayedAuthenticationDidFinish { + if (self.succeededBlock) { self.succeededBlock(); + + // Cleanup! + self.failedBlock = nil; + self.succeededBlock = nil; + } DDLogInfo(@"Simperium authentication success!"); @@ -137,7 +149,7 @@ -(void)delayedAuthenticationDidFinish [delegate authenticationDidSucceedForUsername:simperium.user.email token:simperium.user.authToken]; } --(void)authDidSucceed:(ASIHTTPRequest *)request { +- (void)authDidSucceed:(ASIHTTPRequest *)request { NSString *tokenResponse = [request responseString]; int code = [request responseStatusCode]; if (code != 200) { @@ -150,24 +162,25 @@ -(void)authDidSucceed:(ASIHTTPRequest *)request { NSString *token = [userDict objectForKey:@"access_token"]; // Set the user's details - NSMutableDictionary *credentials = [SPSimpleKeychain load:simperium.appID]; - if (!credentials) - credentials = [NSMutableDictionary dictionary]; - [credentials setObject:username forKey:USERNAME_KEY]; - [credentials setObject:token forKey:AUTH_TOKEN_KEY]; - [SPSimpleKeychain save:simperium.appID data: credentials]; + [[NSUserDefaults standardUserDefaults] setObject:username forKey:USERNAME_KEY]; + [[NSUserDefaults standardUserDefaults] synchronize]; + [SFHFKeychainUtils storeUsername:username andPassword:token forServiceName:simperium.appID updateExisting:YES error:nil]; // Set the Simperium user SPUser *aUser = [[SPUser alloc] initWithEmail:username token:token]; simperium.user = aUser; - [aUser release]; [self performSelector:@selector(delayedAuthenticationDidFinish) withObject:nil afterDelay:0.1]; } --(void)authDidFail:(ASIHTTPRequest *)request { - if (self.failedBlock) +- (void)authDidFail:(ASIHTTPRequest *)request { + if (self.failedBlock) { self.failedBlock([request responseStatusCode], [request responseString]); + + // Cleanup! + self.failedBlock = nil; + self.succeededBlock = nil; + } DDLogError(@"Simperium authentication error (%d): %@",[request responseStatusCode], [request responseString]); @@ -175,14 +188,19 @@ -(void)authDidFail:(ASIHTTPRequest *)request { [delegate authenticationDidFail]; } --(void)createWithUsername:(NSString *)username password:(NSString *)password success:(SucceededBlockType)successBlock failure:(FailedBlockType)failureBlock -{ +- (void)createWithUsername:(NSString *)username password:(NSString *)password success:(SucceededBlockType)successBlock failure:(FailedBlockType)failureBlock { NSURL *tokenURL = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@/create/", SPAuthURL, simperium.appID]]; ASIFormDataRequest *tokenRequest = [[ASIFormDataRequest alloc] initWithURL:tokenURL]; - NSDictionary *authData = [NSDictionary dictionaryWithObjectsAndKeys: + NSMutableDictionary *authData = [NSMutableDictionary dictionaryWithObjectsAndKeys: username, @"username", - password, @"password", nil]; + password, @"password", + nil]; + + // Backend authentication may need extra data + if ([providerString length] > 0) + [authData setObject:providerString forKey:@"provider"]; + NSString *jsonData = [authData JSONString]; [tokenRequest appendPostData:[jsonData dataUsingEncoding:NSUTF8StringEncoding]]; [tokenRequest addRequestHeader:@"Content-Type" value:@"application/json"]; @@ -200,7 +218,14 @@ -(void)createWithUsername:(NSString *)username password:(NSString *)password suc } - (void)reset { - [SPSimpleKeychain delete:simperium.appID]; + NSString *username = [[NSUserDefaults standardUserDefaults] objectForKey:USERNAME_KEY]; + if (!username || username.length == 0) + username = simperium.user.email; + + [[NSUserDefaults standardUserDefaults] setObject:nil forKey:USERNAME_KEY]; + + if (username && username.length > 0) + [SFHFKeychainUtils deleteItemForUsername:simperium.user.email andServiceName:simperium.appID error:nil]; } - (void)cancel { @@ -210,19 +235,4 @@ - (void)cancel { [delegate authenticationDidCancel]; } -#if !TARGET_OS_IPHONE -- (void)sheetDidEnd:(NSWindow *)sheet returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo -{ - // [sheet orderOut:self]; - // if (returnCode == NSCancelButton) { - // for (iddelegate in simperium.delegates) { - // if ([delegate respondsToSelector:@selector(authenticationDidCancel)]) - // [delegate authenticationDidCancel]; - // } - // } -} -#endif - - - @end diff --git a/Simperium/SPBinaryManager.h b/Simperium/SPBinaryManager.h index 14834497..b14c0818 100644 --- a/Simperium/SPBinaryManager.h +++ b/Simperium/SPBinaryManager.h @@ -9,7 +9,6 @@ #import #import "SPBinaryTransportDelegate.h" -// TODO: consider best way to move this to SPReferenceManager #define BIN_KEY @"SPPathKey" #define BIN_BUCKET @"SPPathBucket" #define BIN_ATTRIBUTE @"SPPathAttribute" @@ -32,11 +31,11 @@ @property (nonatomic, copy) NSString *binaryAuthURL; @property (nonatomic, copy) NSString *directory; @property (nonatomic, copy) NSString *keyPrefix; -@property(nonatomic, retain, readonly) NSMutableDictionary *pendingBinaryDownloads; -@property(nonatomic, retain, readonly) NSMutableDictionary *pendingBinaryUploads; -@property(nonatomic, retain, readonly) NSMutableDictionary *transmissionProgress; +@property(nonatomic, strong, readonly) NSMutableDictionary *pendingBinaryDownloads; +@property(nonatomic, strong, readonly) NSMutableDictionary *pendingBinaryUploads; +@property(nonatomic, strong, readonly) NSMutableDictionary *transmissionProgress; -@property (nonatomic,assign, readonly) NSMutableSet *delegates; +@property (nonatomic, readonly, strong) NSMutableSet *delegates; -(id)initWithSimperium:(Simperium *)aSimperium; -(void)setupAuth:(SPUser *)user; diff --git a/Simperium/SPBinaryManager.m b/Simperium/SPBinaryManager.m index 66cbab9d..cb4bd69b 100644 --- a/Simperium/SPBinaryManager.m +++ b/Simperium/SPBinaryManager.m @@ -38,10 +38,10 @@ -(id)initWithSimperium:(Simperium *)aSimperium { NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); self.directory = [paths objectAtIndex:0]; - pendingBinaryDownloads = [[NSMutableDictionary dictionaryWithCapacity:3] retain]; - pendingBinaryUploads = [[NSMutableDictionary dictionaryWithCapacity:3] retain]; - delegates = [[NSMutableArray arrayWithCapacity:3] retain]; - transmissionProgress = [[NSMutableDictionary dictionaryWithCapacity:3] retain]; + pendingBinaryDownloads = [NSMutableDictionary dictionaryWithCapacity:3]; + pendingBinaryUploads = [NSMutableDictionary dictionaryWithCapacity:3]; + delegates = [NSMutableSet setWithCapacity:3]; + transmissionProgress = [NSMutableDictionary dictionaryWithCapacity:3]; [self loadPendingBinaryDownloads]; [self loadPendingBinaryUploads]; } @@ -49,13 +49,6 @@ -(id)initWithSimperium:(Simperium *)aSimperium { return self; } --(void)dealloc { - self.directory = nil; - [binaryAuthURL release]; - [pendingBinaryDownloads release]; - [pendingBinaryUploads release]; - [super dealloc]; -} -(void)loadPendingBinaryDownloads { NSString *pendingKey = [NSString stringWithFormat:@"SPPendingBinaryDownloads"]; @@ -276,7 +269,6 @@ -(BOOL)createLocalDirectoryForPrefix: (NSString *)prefixString { { return NO; } - [filemgr release]; return YES; } diff --git a/Simperium/SPBucket.h b/Simperium/SPBucket.h index 71f33550..af147978 100644 --- a/Simperium/SPBucket.h +++ b/Simperium/SPBucket.h @@ -8,7 +8,7 @@ #import #import "SPStorageProvider.h" -#import "SPNetworkProvider.h" +#import "SPNetworkInterface.h" @class SPDiffer; @class SPSchema; @@ -16,7 +16,7 @@ @class SPBucket; @class SPChangeProcessor; @class SPIndexProcessor; -@class SPReferenceManager; +@class SPRelationshipResolver; /** SPBucketChangeType is used in the bucket:didChangeObjectForKey:forChangeType: method of SPBucketDelegate. It's similar to NSFetchedResultsChangeType, which is used with an NSFetchedResultsControllerDelegate. */ @@ -25,26 +25,26 @@ enum { SPBucketChangeDelete = 2, SPBucketChangeMove = 3, // not yet implemented SPBucketChangeUpdate = 4, - SPBucketChangeWillUpdate = 5, - SPBucketChangeAcknowledge = 6 + SPBucketChangeAcknowledge = 5 }; typedef NSUInteger SPBucketChangeType; /** Delegate protocol for Simperium bucket notifications. - You can use this delegate to respond to object changes and errors that happen as a result of data moving over the network. + You can use this delegate to respond to object changes and errors that happen as a result of data moving over the network. Note + that these are currently NOT fired during indexing (i.e. on a clean install when there is data already stored in Simperium). */ @protocol SPBucketDelegate @optional --(void)bucket:(SPBucket *)bucket didChangeObjectForKey:(NSString *)key forChangeType:(SPBucketChangeType)change; --(void)bucket:(SPBucket *)bucket willChangeObjectsForKeys:(NSSet *)keys; --(void)bucketWillStartIndexing:(SPBucket *)bucket; --(void)bucketDidFinishIndexing:(SPBucket *)bucket; --(void)bucket:(SPBucket *)bucket didReceiveObjectForKey:(NSString *)key version:(NSString *)version data:(NSDictionary *)data; --(void)bucketDidAcknowledgeDelete:(SPBucket *)bucket; --(void)bucket:(SPBucket *)bucket didFailWithError:(NSError *)error; --(void)bucket:(SPBucket *)bucket didShareObjectForKey:(NSString *)key withEmail:(NSString *)email; +- (void)bucket:(SPBucket *)bucket didChangeObjectForKey:(NSString *)key forChangeType:(SPBucketChangeType)changeType memberNames:(NSArray *)memberNames; +- (void)bucket:(SPBucket *)bucket willChangeObjectsForKeys:(NSSet *)keys; +- (void)bucketWillStartIndexing:(SPBucket *)bucket; +- (void)bucketDidFinishIndexing:(SPBucket *)bucket; +- (void)bucket:(SPBucket *)bucket didReceiveObjectForKey:(NSString *)key version:(NSString *)version data:(NSDictionary *)data; +- (void)bucketDidAcknowledgeDelete:(SPBucket *)bucket; +- (void)bucket:(SPBucket *)bucket didFailWithError:(NSError *)error; +- (void)bucket:(SPBucket *)bucket didShareObjectForKey:(NSString *)key withEmail:(NSString *)email; @end /** An SPBucket instance is conceptually a collection of all objects of a particular type. If you're using Core Data, there is one SPBucket per Entity in your model, and it's used to track all objects corresponding to that Entity type. @@ -52,64 +52,85 @@ typedef NSUInteger SPBucketChangeType; @interface SPBucket : NSObject { NSString *name; NSString *instanceLabel; - id network; - SPReferenceManager *referenceManager; + BOOL notifyWhileIndexing; + id network; + SPRelationshipResolver *relationshipResolver; SPDiffer *differ; - id storage; + id __weak storage; SPSchema *schema; - dispatch_queue_t processorQueue; - - id delegate; + + id __weak delegate; NSString *lastChangeSignature; } /// Assign this delegate to be notified when objects in this bucket change (see SPBucketDelegate above) -@property (assign) id delegate; +@property (weak) id delegate; @property (nonatomic, readonly) NSString *name; +/// Enable this to receive SPBucketDelegate notifications during indexing (disabled by default because it's slow) +@property BOOL notifyWhileIndexing; + /** The following are convenience methods for accessing, inserting and deleting objects. If you're using Core Data, you can instead just access your context directly and Simperium will identify any changes accordingly. */ // Retrieve an object that has a particular simperiumKey --(id)objectForKey:(NSString *)key; +- (id)objectForKey:(NSString *)simperiumKey; // Retrieve all objects in the bucket --(NSArray *)allObjects; +- (NSArray *)allObjects; // Insert a new object in the bucket (and optionally specify a particular simperiumKey) --(id)insertNewObject; --(id)insertNewObjectForKey:(NSString *)simperiumKey; +- (id)insertNewObject; +- (id)insertNewObjectForKey:(NSString *)simperiumKey; // Retrieve objects for a particular set of keys --(NSArray *)objectsForKeys:(NSSet *)keys; +- (NSArray *)objectsForKeys:(NSSet *)keys; + +// Retrieve objects filtered by a predicate +- (NSArray *)objectsForPredicate:(NSPredicate *)predicate; + +// Retrieve a certain number of past versions for a particular object key +// (will result in didReceiveObjectForKey:version:data: getting fired) +- (void)requestVersions:(int)numVersions key:(NSString *)simperiumKey; // Delete a particular object --(void)deleteObject:(id)object; +- (void)deleteObject:(id)object; // Delete all objects in the bucket --(void)deleteAllObjects; +- (void)deleteAllObjects; // Efficiently returns the number of objects in the bucket (optionally specifying a predicate). --(NSInteger)numObjects; --(NSInteger)numObjectsForPredicate:(NSPredicate *)predicate; +- (NSInteger)numObjects; +- (NSInteger)numObjectsForPredicate:(NSPredicate *)predicate; /** For internal use */ + +typedef void (^SPBucketForceSyncCompletion)(void); + @property (nonatomic, copy) NSString *instanceLabel; -@property (nonatomic, retain) id storage; -@property (nonatomic, retain) id network; -@property (nonatomic, retain) SPDiffer *differ; -@property (nonatomic, retain) SPReferenceManager *referenceManager; -@property (retain) SPChangeProcessor* changeProcessor; -@property (retain) SPIndexProcessor* indexProcessor; -@property (assign) dispatch_queue_t processorQueue; +@property (nonatomic, weak) id storage; +@property (nonatomic, strong) id network; +@property (nonatomic, strong) SPDiffer *differ; +@property (nonatomic, strong) SPRelationshipResolver *relationshipResolver; +@property (strong) SPChangeProcessor* changeProcessor; +@property (strong) SPIndexProcessor* indexProcessor; +@property (nonatomic, strong) dispatch_queue_t processorQueue; @property (nonatomic, copy) NSString *lastChangeSignature; - --(id)initWithSchema:(SPSchema *)aSchema storage:(id)aStorage networkProvider:(id)netProvider referenceManager:(SPReferenceManager *)refManager label:(NSString *)label; --(void)validateObjects; --(void)unloadAllObjects; +@property (nonatomic, copy) SPBucketForceSyncCompletion forceSyncCompletion; + +- (id)initWithSchema:(SPSchema *)aSchema + storage:(id)aStorage + networkInterface:(id)netInterface +relationshipResolver:(SPRelationshipResolver *)resolver + label:(NSString *)label; +- (void)validateObjects; +- (void)unloadAllObjects; +- (void)resolvePendingRelationshipsToKeys:(NSSet *)keys; +- (void)forceSyncWithCompletion:(SPBucketForceSyncCompletion)completion; +- (void)bucketDidSync; @end diff --git a/Simperium/SPBucket.m b/Simperium/SPBucket.m index 5d86a6dc..2633ef8d 100644 --- a/Simperium/SPBucket.m +++ b/Simperium/SPBucket.m @@ -11,13 +11,13 @@ #import "SPDiffer.h" #import "SPStorage.h" #import "SPSchema.h" -#import "SPNetworkProvider.h" +#import "SPNetworkInterface.h" #import "SPChangeProcessor.h" #import "SPIndexProcessor.h" #import "DDLog.h" #import "SPGhost.h" #import "JSONKit.h" -#import "SPReferenceManager.h" +#import "SPRelationshipResolver.h" static int ddLogLevel = LOG_LEVEL_INFO; @@ -27,11 +27,12 @@ @interface SPBucket() @implementation SPBucket @synthesize delegate; @synthesize name; +@synthesize notifyWhileIndexing; @synthesize instanceLabel; @synthesize storage; @synthesize differ; @synthesize network; -@synthesize referenceManager; +@synthesize relationshipResolver; @synthesize changeProcessor; @synthesize indexProcessor; @synthesize processorQueue; @@ -45,34 +46,32 @@ + (void)ddSetLogLevel:(int)logLevel { ddLogLevel = logLevel; } --(id)initWithSchema:(SPSchema *)aSchema storage:(id)aStorage networkProvider:(id)netProvider referenceManager:(SPReferenceManager *)refManager label:(NSString *)label +- (id)initWithSchema:(SPSchema *)aSchema storage:(id)aStorage networkInterface:(id)netInterface +relationshipResolver:(SPRelationshipResolver *)resolver label:(NSString *)label { if ((self = [super init])) { name = [aSchema.bucketName copy]; self.storage = aStorage; - self.network = netProvider; - self.referenceManager = refManager; + self.network = netInterface; + self.relationshipResolver = resolver; SPDiffer *aDiffer = [[SPDiffer alloc] initWithSchema:aSchema]; self.differ = aDiffer; - [aDiffer release]; // Label is used to support multiple simperium instances (e.g. unit testing) self.instanceLabel = [NSString stringWithFormat:@"%@%@", self.name, label]; SPChangeProcessor *cp = [[SPChangeProcessor alloc] initWithLabel:self.instanceLabel]; self.changeProcessor = cp; - [cp release]; SPIndexProcessor *ip = [[SPIndexProcessor alloc] init]; self.indexProcessor = ip; - [ip release]; NSString *queueLabel = [@"com.simperium.processor." stringByAppendingString:self.name]; processorQueue = dispatch_queue_create([queueLabel cStringUsingEncoding:NSUTF8StringEncoding], NULL); - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(objectsDidChange:) - name:ProcessorDidChangeObjectsNotification object:self]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(objectDidChange:) + name:ProcessorDidChangeObjectNotification object:self]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(objectsAdded:) name:ProcessorDidAddObjectsNotification object:self]; @@ -89,69 +88,66 @@ -(id)initWithSchema:(SPSchema *)aSchema storage:(id)aStorage [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(acknowledgedObjectDeletion:) name:ProcessorDidAcknowledgeDeleteNotification object:self]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(getLatestVersions) - name:@"simperiumIndexRefreshNeeded" object:self]; - - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(objectsAdded:) - name:@"SimperiumObjectAdded" object:self]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(requestLatestVersions) + name:ProcessorRequestsReindexing object:self]; } return self; } --(void)dealloc { - [[NSNotificationCenter defaultCenter] removeObserver:self name:@"simperiumIndexRefreshNeeded" object:self]; - [name release]; - name = nil; - self.storage = nil; - self.differ = nil; - self.network = nil; - self.changeProcessor = nil; - self.indexProcessor = nil; - [lastChangeSignature release]; - [super dealloc]; +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self name:ProcessorRequestsReindexing object:self]; } --(id)objectForKey:(NSString *)key -{ +- (id)objectForKey:(NSString *)simperiumKey { // Typically used on startup to get a dictionary from storage; Simperium doesn't keep it in memory though - iddiffable = [storage objectForKey:key bucketName:self.name]; + iddiffable = [storage objectForKey:simperiumKey bucketName:self.name]; return [diffable object]; } --(id)objectAtIndex:(NSUInteger)index { +- (id)objectAtIndex:(NSUInteger)index { iddiffable = [storage objectAtIndex:index bucketName:self.name]; return [diffable object]; } --(NSArray *)allObjects { - return [storage objectsForBucketName:self.name]; +- (void)requestVersions:(int)numVersions key:(NSString *)simperiumKey { + iddiffable = [storage objectForKey:simperiumKey bucketName:self.name]; + [network requestVersions:numVersions object:diffable]; +} + + +- (NSArray *)allObjects { + return [storage objectsForBucketName:self.name predicate:nil]; } --(id)insertNewObject { +- (NSArray *)allObjectKeys { + return [storage objectKeysForBucketName:self.name]; +} + +- (id)insertNewObject { iddiffable = [storage insertNewObjectForBucketName:self.name simperiumKey:nil]; diffable.bucket = self; return [diffable object]; } --(id)insertNewObjectForKey:(NSString *)simperiumKey { +- (id)insertNewObjectForKey:(NSString *)simperiumKey { iddiffable = [storage insertNewObjectForBucketName:self.name simperiumKey:simperiumKey]; diffable.bucket = self; return [diffable object]; } --(void)insertObject:(id)object { +- (void)insertObject:(id)object { //iddiffable = [storage insertObject:object bucketName:self.name]; } --(void)deleteAllObjects { +- (void)deleteAllObjects { [storage deleteAllObjectsForBucketName:self.name]; } --(void)deleteObject:(id)object { +- (void)deleteObject:(id)object { [storage deleteObject:object]; } --(void)updateDictionaryForKey:(NSString *)key { +- (void)updateDictionaryForKey:(NSString *)key { // idobject = [storage objectForKey:key entityName:self.name]; // if (!object) { // object = [storage insertNewObjectForEntityForName:self.name simperiumKey:key]; @@ -159,36 +155,40 @@ -(void)updateDictionaryForKey:(NSString *)key { // [object loadMemberData:data]; } --(void)validateObjects -{ +- (void)validateObjects { // Allow the storage to determine the most efficient way to validate everything [storage validateObjectsForBucketName: name]; [storage save]; } --(void)unloadAllObjects { +- (void)unloadAllObjects { [storage unloadAllObjects]; - [referenceManager reset]; + [relationshipResolver reset:storage]; } --(void)insertObject:(NSDictionary *)object atIndex:(NSUInteger)index { +- (void)insertObject:(NSDictionary *)object atIndex:(NSUInteger)index { } --(NSArray *)objectsForKeys:(NSSet *)keys { +- (NSArray *)objectsForKeys:(NSSet *)keys { return [storage objectsForKeys:keys bucketName:name]; } --(NSInteger)numObjects { +- (NSArray *)objectsForPredicate:(NSPredicate *)predicate { + return [storage objectsForBucketName:name predicate:predicate]; +} + + +- (NSInteger)numObjects { return [storage numObjectsForBucketName:name predicate:nil]; } --(NSInteger)numObjectsForPredicate:(NSPredicate *)predicate { +- (NSInteger)numObjectsForPredicate:(NSPredicate *)predicate { return [storage numObjectsForBucketName:name predicate:predicate]; } --(NSString *)lastChangeSignature { +- (NSString *)lastChangeSignature { if (!lastChangeSignature) { // Load it NSString *sigKey = [NSString stringWithFormat:@"lastChangeSignature-%@", self.instanceLabel]; @@ -198,8 +198,7 @@ -(NSString *)lastChangeSignature { return lastChangeSignature; } --(void)setLastChangeSignature:(NSString *)signature { - [lastChangeSignature release]; +- (void)setLastChangeSignature:(NSString *)signature { lastChangeSignature = [signature copy]; // Persist it @@ -210,84 +209,91 @@ -(void)setLastChangeSignature:(NSString *)signature { #pragma mark Notifications --(void)objectsDidChange:(NSNotification *)notification { - if ([delegate respondsToSelector:@selector(bucket:didChangeObjectForKey:forChangeType:)]) { +- (void)objectDidChange:(NSNotification *)notification { + if ([delegate respondsToSelector:@selector(bucket:didChangeObjectForKey:forChangeType:memberNames:)]) { + // Only one object changed; get it NSSet *set = (NSSet *)[notification.userInfo objectForKey:@"keys"]; - for (NSString *key in set) { - [delegate bucket:self didChangeObjectForKey:key forChangeType:SPBucketChangeUpdate]; - } + NSString *key = [[set allObjects] objectAtIndex:0]; + NSArray *changedMembers = (NSArray *)[notification.userInfo objectForKey:@"changedMembers"]; + [delegate bucket:self didChangeObjectForKey:key forChangeType:SPBucketChangeUpdate memberNames:changedMembers]; } } --(void)objectsAdded:(NSNotification *)notification { +- (void)objectsAdded:(NSNotification *)notification { + // When objects are added, resolve any references to them that hadn't yet been fulfilled + // Note: this notification isn't currently triggered from SPIndexProcessor when adding objects from the index. Instead, + // references are resolved from within SPIndexProcessor itself NSSet *set = (NSSet *)[notification.userInfo objectForKey:@"keys"]; - for (NSString *key in set) { - [self.referenceManager resolvePendingReferencesToKey:key bucketName:self.name storage:storage]; // references must be intra-storage only - [delegate bucket:self didChangeObjectForKey:key forChangeType:SPBucketChangeInsert]; + [self resolvePendingRelationshipsToKeys:set]; + + // Also notify the delegate since the referenced objects are now accessible + if ([delegate respondsToSelector:@selector(bucket:didChangeObjectForKey:forChangeType:memberNames:)]) { + for (NSString *key in set) { + [delegate bucket:self didChangeObjectForKey:key forChangeType:SPBucketChangeInsert memberNames:nil]; + } } } --(void)objectKeysDeleted:(NSNotification *)notification { +- (void)objectKeysDeleted:(NSNotification *)notification { NSSet *set = (NSSet *)[notification.userInfo objectForKey:@"keys"]; - BOOL delegateRespondsToSelector = [delegate respondsToSelector:@selector(bucket:didChangeObjectForKey:forChangeType:)]; + BOOL delegateRespondsToSelector = [delegate respondsToSelector:@selector(bucket:didChangeObjectForKey:forChangeType:memberNames:)]; for (NSString *key in set) { [storage stopManagingObjectWithKey:key]; if (delegateRespondsToSelector) - [delegate bucket:self didChangeObjectForKey:key forChangeType:SPBucketChangeDelete]; + [delegate bucket:self didChangeObjectForKey:key forChangeType:SPBucketChangeDelete memberNames:nil]; } } --(void)objectsAcknowledged:(NSNotification *)notification { - if ([delegate respondsToSelector:@selector(bucket:didChangeObjectForKey:forChangeType:)]) { +- (void)objectsAcknowledged:(NSNotification *)notification { + if ([delegate respondsToSelector:@selector(bucket:didChangeObjectForKey:forChangeType:memberNames:)]) { NSSet *set = (NSSet *)[notification.userInfo objectForKey:@"keys"]; for (NSString *key in set) { - [delegate bucket:self didChangeObjectForKey:key forChangeType:SPBucketChangeAcknowledge]; + [delegate bucket:self didChangeObjectForKey:key forChangeType:SPBucketChangeAcknowledge memberNames:nil]; } } } --(void)objectsWillChange:(NSNotification *)notification { - if ([delegate respondsToSelector:@selector(bucket:willChangeObjectsForKeys:forChangeType:)]) { +- (void)objectsWillChange:(NSNotification *)notification { + if ([delegate respondsToSelector:@selector(bucket:willChangeObjectsForKeys:)]) { NSSet *set = (NSSet *)[notification.userInfo objectForKey:@"keys"]; [delegate bucket:self willChangeObjectsForKeys:set]; } } --(void)acknowledgedObjectDeletion:(NSNotification *)notification { +- (void)acknowledgedObjectDeletion:(NSNotification *)notification { if ([delegate respondsToSelector:@selector(bucketDidAcknowledgeDelete:)]) { [delegate bucketDidAcknowledgeDelete:self]; } } --(void)getLatestVersions { - [network getLatestVersionsForBucket:self]; +- (void)requestLatestVersions { + [network requestLatestVersionsForBucket:self]; } --(SPSchema *)schema { +- (SPSchema *)schema { return differ.schema; } --(void)setSchema:(SPSchema *)aSchema { +- (void)setSchema:(SPSchema *)aSchema { differ.schema = aSchema; } --(void)resolvePendingReferencesToKeys:(NSSet *)keys -{ +- (void)resolvePendingRelationshipsToKeys:(NSSet *)keys { for (NSString *key in keys) - [self.referenceManager resolvePendingReferencesToKey:key bucketName:self.name storage:self.storage]; + [self.relationshipResolver resolvePendingRelationshipsToKey:key bucketName:self.name storage:self.storage]; } +- (void)forceSyncWithCompletion:(SPBucketForceSyncCompletion)completion { + self.forceSyncCompletion = completion; + [self.network forceSyncBucket:self]; +} -// Potential future support for multiple delegates -//-(void)addDelegate:(id)delegate { -// if (![delegates containsObject:delegate]) -// [delegates addObject: delegate]; -//} -// -//-(void)removeDelegate:(id)delegate { -// [delegates removeObject: delegate]; -//} - +- (void)bucketDidSync { + if(self.changeProcessor.numChangesPending == 0 && self.forceSyncCompletion) { + self.forceSyncCompletion(); + self.forceSyncCompletion = nil; + } +} @end diff --git a/Simperium/SPChangeProcessor.h b/Simperium/SPChangeProcessor.h index 3a904a25..bb354cf0 100644 --- a/Simperium/SPChangeProcessor.h +++ b/Simperium/SPChangeProcessor.h @@ -7,6 +7,7 @@ // #import +#import "SPProcessorNotificationNames.h" @class SPBucket; @@ -28,22 +29,16 @@ extern NSString * const CH_START_VERSION; extern NSString * const CH_END_VERSION; extern NSString * const CH_LOCAL_ID; -extern NSString * const ProcessorDidAddObjectsNotification; -extern NSString * const ProcessorDidChangeObjectsNotification; -extern NSString * const ProcessorDidDeleteObjectKeysNotification; -extern NSString * const ProcessorDidAcknowledgeObjectsNotification; -extern NSString * const ProcessorWillChangeObjectsNotification; -extern NSString * const ProcessorDidAcknowledgeDeleteNotification; --(id)initWithLabel:(NSString *)label; --(void)reset; --(BOOL)processRemoteResponseForChanges:(NSArray *)changes bucket:(SPBucket *)bucket; --(void)processRemoteChanges:(NSArray *)changes bucket:(SPBucket *)bucket clientID:(NSString *)clientID; --(void)processLocalChange:(NSDictionary *)change key:(NSString *)key; --(NSDictionary *)processLocalObjectWithKey:(NSString *)key bucket:(SPBucket *)bucket later:(BOOL)later; --(NSDictionary *)processLocalDeletionWithKey:(NSString *)key; --(int)numChangesPending; --(int)numKeysForObjectsWithMoreChanges; --(NSArray *)processPendingChanges:(SPBucket *)bucket; --(NSArray *)processKeysForObjectsWithMoreChanges:(SPBucket *)bucket; +- (id)initWithLabel:(NSString *)label; +- (void)reset; +- (BOOL)processRemoteResponseForChanges:(NSArray *)changes bucket:(SPBucket *)bucket; +- (void)processRemoteChanges:(NSArray *)changes bucket:(SPBucket *)bucket clientID:(NSString *)clientID; +- (void)processLocalChange:(NSDictionary *)change key:(NSString *)key; +- (NSDictionary *)processLocalObjectWithKey:(NSString *)key bucket:(SPBucket *)bucket later:(BOOL)later; +- (NSDictionary *)processLocalDeletionWithKey:(NSString *)key; +- (int)numChangesPending; +- (int)numKeysForObjectsWithMoreChanges; +- (NSArray *)processPendingChanges:(SPBucket *)bucket onlyQueuedChanges:(BOOL)onlyQueuedChanges; +- (NSArray *)processKeysForObjectsWithMoreChanges:(SPBucket *)bucket; @end diff --git a/Simperium/SPChangeProcessor.m b/Simperium/SPChangeProcessor.m index 268149db..408c9261 100644 --- a/Simperium/SPChangeProcessor.m +++ b/Simperium/SPChangeProcessor.m @@ -33,13 +33,11 @@ NSString * const CH_ERROR = @"error"; NSString * const CH_DATA = @"d"; -// Notifications -NSString * const ProcessorDidAddObjectsNotification = @"ProcessorDidAddObjectsNotification"; -NSString * const ProcessorDidChangeObjectsNotification = @"ProcessorDidChangeObjectsNotification"; -NSString * const ProcessorDidDeleteObjectKeysNotification = @"ProcessorDidDeleteObjectKeysNotification"; -NSString * const ProcessorDidAcknowledgeObjectsNotification = @"ProcessorDidAcknowledgeObjectsNotification"; -NSString * const ProcessorWillChangeObjectsNotification = @"ProcessorWillChangeObjectsNotification"; -NSString * const ProcessorDidAcknowledgeDeleteNotification = @"ProcessorDidAcknowledgeDeleteNotification"; +typedef NS_ENUM(NSUInteger, CH_ERRORS) { + CH_ERRORS_EXPECTATION_FAILED = 417, // (e.g. foreign key doesn't exist just yet) + CH_ERRORS_INVALID_DIFF = 440 +}; + @interface SPChangeProcessor() -(void)loadSerializedChanges; @@ -57,11 +55,11 @@ + (void)ddSetLogLevel:(int)logLevel { ddLogLevel = logLevel; } --(id)initWithLabel:(NSString *)label { +- (id)initWithLabel:(NSString *)label { if (self = [super init]) { self.instanceLabel = label; - changesPending = [[NSMutableDictionary dictionaryWithCapacity:3] retain]; - keysForObjectsWithMoreChanges = [[NSMutableSet setWithCapacity:3] retain]; + changesPending = [NSMutableDictionary dictionaryWithCapacity:3]; + keysForObjectsWithMoreChanges = [NSMutableSet setWithCapacity:3]; [self loadSerializedChanges]; [self loadKeysForObjectsWithMoreChanges]; @@ -70,37 +68,8 @@ -(id)initWithLabel:(NSString *)label { return self; } --(void)dealloc { - self.instanceLabel = nil; - [changesPending release]; - [keysForObjectsWithMoreChanges release]; - [super dealloc]; -} - --(NSMutableDictionary *)createChangeForKey:(NSString *)key operation:(NSString *)operation version:(NSString *)version data:(NSDictionary *)data -{ - // The change applies to this particular entity instance, so use its unique key as an identifier - NSMutableDictionary *change = [NSMutableDictionary dictionaryWithObject:key forKey:CH_KEY]; - - // Every change must be marked with a unique ID - NSString *uuid = [NSString sp_makeUUID]; - [change setObject:uuid forKey: CH_LOCAL_ID]; - - // Set the change's operation - [change setObject:operation forKey:CH_OPERATION]; - - // Set the data as the value for the operation (e.g. a diff dictionary for modify operations) - if (data) - [change setObject:data forKey:CH_VALUE]; - - // If it's a modify operation, also include the object's version as the last known version - if (operation == CH_MODIFY && version != nil && [version intValue] != 0) - [change setObject: version forKey: CH_START_VERSION]; - - return change; -} --(BOOL)awaitingAcknowledgementForKey:(NSString *)key { +- (BOOL)awaitingAcknowledgementForKey:(NSString *)key { if (key == nil) return NO; @@ -108,24 +77,21 @@ -(BOOL)awaitingAcknowledgementForKey:(NSString *)key { return awaitingAcknowledgement; } --(void)serializeChangesPending -{ +- (void)serializeChangesPending { NSString *pendingJSON = [changesPending JSONString]; NSString *key = [NSString stringWithFormat:@"changesPending-%@", instanceLabel]; [[NSUserDefaults standardUserDefaults] setObject:pendingJSON forKey: key]; [[NSUserDefaults standardUserDefaults] synchronize]; } --(void)serializeKeysForObjectsWithMoreChanges -{ +- (void)serializeKeysForObjectsWithMoreChanges { NSString *json = [[keysForObjectsWithMoreChanges allObjects] JSONString]; NSString *key = [NSString stringWithFormat:@"keysForObjectsWithMoreChanges-%@", instanceLabel]; [[NSUserDefaults standardUserDefaults] setObject:json forKey: key]; [[NSUserDefaults standardUserDefaults] synchronize]; } --(void)loadSerializedChanges -{ +- (void)loadSerializedChanges { // Load changes that didn't get a chance to send NSString *pendingKey = [NSString stringWithFormat:@"changesPending-%@", instanceLabel]; NSString *pendingJSON = [[NSUserDefaults standardUserDefaults] objectForKey:pendingKey]; @@ -134,8 +100,7 @@ -(void)loadSerializedChanges [changesPending setValuesForKeysWithDictionary:pendingDict]; } --(void)loadKeysForObjectsWithMoreChanges -{ +- (void)loadKeysForObjectsWithMoreChanges { // Load keys for entities that have more changes to send NSString *key = [NSString stringWithFormat:@"keysForObjectsWithMoreChanges-%@", instanceLabel]; NSString *json = [[NSUserDefaults standardUserDefaults] objectForKey:key]; @@ -144,8 +109,7 @@ -(void)loadKeysForObjectsWithMoreChanges [keysForObjectsWithMoreChanges addObjectsFromArray:list]; } --(void)reset -{ +- (void)reset { [changesPending removeAllObjects]; [keysForObjectsWithMoreChanges removeAllObjects]; [self serializeChangesPending]; @@ -153,44 +117,47 @@ -(void)reset } // For debugging --(void)softReset -{ +- (void)softReset { [changesPending removeAllObjects]; [keysForObjectsWithMoreChanges removeAllObjects]; [self loadSerializedChanges]; [self loadKeysForObjectsWithMoreChanges]; } --(BOOL)processRemoteResponseForChanges:(NSArray *)changes bucket:(SPBucket *)bucket -{ + +#pragma mark Remote changes + +- (BOOL)change:(NSDictionary *)change equals:(NSDictionary *)anotherChange { + return [[change objectForKey:CH_KEY] compare:[anotherChange objectForKey:CH_KEY]] == NSOrderedSame && + [[change objectForKey:CH_LOCAL_ID] compare:[anotherChange objectForKey:CH_LOCAL_ID]] == NSOrderedSame; +} + +- (BOOL)processRemoteResponseForChanges:(NSArray *)changes bucket:(SPBucket *)bucket { BOOL repostNeeded = NO; for (NSDictionary *change in changes) { - if ([change objectForKey:CH_ERROR] != nil) { - int errorCode = [[change objectForKey:CH_ERROR] integerValue]; - DDLogError(@"Simperium POST returned error %d for change %@", errorCode, change); + if (change[CH_ERROR] != nil) { + long errorCode = [change[CH_ERROR] integerValue]; + DDLogError(@"Simperium POST returned error %ld for change %@", errorCode, change); - // 440: invalid diff - // 417: expectation failed (e.g. foreign key doesn't exist just yet) - if (errorCode == 440 || errorCode == 417) { + if (errorCode == CH_ERRORS_EXPECTATION_FAILED || errorCode == CH_ERRORS_INVALID_DIFF) { // Resubmit with all data // Create a new context (to be thread-safe) and fetch the entity from it - NSString *key = [change objectForKey:CH_KEY]; + NSString *key = change[CH_KEY]; idthreadSafeStorage = [bucket.storage threadSafeStorage]; idobject = [threadSafeStorage objectForKey:key bucketName :bucket.name]; if (!object) { - [changesPending removeObjectForKey:[change objectForKey:CH_KEY]]; + [changesPending removeObjectForKey:change[CH_KEY]]; continue; } - NSMutableDictionary *newChange = [[changesPending objectForKey:key] mutableCopy]; + NSMutableDictionary *newChange = [changesPending[key] mutableCopy]; [object simperiumKey]; // fire fault [newChange setObject:[object dictionary] forKey:CH_DATA]; [changesPending setObject:newChange forKey:key]; - [newChange release]; repostNeeded = YES; } else { // Catch all, don't resubmit - [changesPending removeObjectForKey:[change objectForKey:CH_KEY]]; + [changesPending removeObjectForKey:change[CH_KEY]]; } } } @@ -198,13 +165,8 @@ -(BOOL)processRemoteResponseForChanges:(NSArray *)changes bucket:(SPBucket *)buc return repostNeeded; } --(BOOL)change:(NSDictionary *)change equals:(NSDictionary *)anotherChange -{ - return [[change objectForKey:CH_KEY] compare:[anotherChange objectForKey:CH_KEY]] == NSOrderedSame && - [[change objectForKey:CH_LOCAL_ID] compare:[anotherChange objectForKey:CH_LOCAL_ID]] == NSOrderedSame; -} - --(BOOL)processRemoteDelete:(id)object acknowledged:(BOOL)acknowledged bucket:(SPBucket *)bucket storage:(id)threadSafeStorage +- (BOOL)processRemoteDelete:(id)object acknowledged:(BOOL)acknowledged bucket:(SPBucket *)bucket + storage:(id)threadSafeStorage { // REMOVE operation // If this wasn't just an ack, perform the deletion @@ -233,14 +195,14 @@ -(BOOL)processRemoteDelete:(id)object acknowledged:(BOOL)acknowledge return YES; } --(BOOL)processRemoteModify:(id)object bucket:(SPBucket *)bucket change:(NSDictionary *)change acknowledged:(BOOL)acknowledged storage:(id)threadSafeStorage +- (BOOL)processRemoteModify:(id)object bucket:(SPBucket *)bucket change:(NSDictionary *)change + acknowledged:(BOOL)acknowledged storage:(id)threadSafeStorage { BOOL newlyAdded = NO; NSString *key = [change objectForKey:CH_KEY]; // MODIFY operation - if (!object) - { + if (!object) { newlyAdded = YES; // It doesn't exist yet, so ADD it @@ -253,7 +215,6 @@ -(BOOL)processRemoteModify:(id)object bucket:(SPBucket *)bucket chan SPGhost *ghost = [[SPGhost alloc] initWithKey:[object simperiumKey] memberData:nil]; ghost.version = @"0"; object.ghost = ghost; - [ghost release]; // If this wasn't just an ack, send a notification and load the data DDLogVerbose(@"Simperium non-local ADD ENTITY received"); @@ -275,9 +236,9 @@ -(BOOL)processRemoteModify:(id)object bucket:(SPBucket *)bucket chan // Store versions as strings, but if they come off the wire as numbers, then handle that too if ([startVersion isKindOfClass:[NSNumber class]]) - startVersion = [NSString stringWithFormat:@"%d", [startVersion integerValue]]; + startVersion = [NSString stringWithFormat:@"%ld", (long)[startVersion integerValue]]; if ([endVersion isKindOfClass:[NSNumber class]]) - endVersion = [NSString stringWithFormat:@"%d", [endVersion integerValue]]; + endVersion = [NSString stringWithFormat:@"%ld", (long)[endVersion integerValue]]; DDLogVerbose(@"Simperium received version = %@, previous version = %@", startVersion, oldVersion); // If the versions are equal or there's no start version (new object), process the change @@ -293,7 +254,6 @@ -(BOOL)processRemoteModify:(id)object bucket:(SPBucket *)bucket chan // Slight hack to ensure Core Data realizes the object has changed and needs a save NSString *ghostDataCopy = [[[object.ghost dictionary] JSONString] copy]; object.ghostData = ghostDataCopy; - [ghostDataCopy release]; DDLogVerbose(@"Simperium MODIFIED ghost version %@ (%@-%@)", endVersion, bucket.name, instanceLabel); @@ -315,7 +275,6 @@ -(BOOL)processRemoteModify:(id)object bucket:(SPBucket *)bucket chan DDLogVerbose(@"Simperium transform resulted in empty diff (invalid ack?)"); } } - [oldGhost release]; // Apply the diff to the object itself if (!acknowledged && [diff count] > 0) { @@ -325,31 +284,32 @@ -(BOOL)processRemoteModify:(id)object bucket:(SPBucket *)bucket chan [threadSafeStorage save]; dispatch_async(dispatch_get_main_queue(), ^{ - NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys: + NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: bucket.name, @"bucketName", [NSSet setWithObject:key], @"keys", nil]; NSString *notificationName; if (newlyAdded) { notificationName = ProcessorDidAddObjectsNotification; - } else if (acknowledged) + } else if (acknowledged) { notificationName = ProcessorDidAcknowledgeObjectsNotification; - else - notificationName = ProcessorDidChangeObjectsNotification; + } else { + notificationName = ProcessorDidChangeObjectNotification; + [userInfo setObject:[diff allKeys] forKey:@"changedMembers"]; + } [[NSNotificationCenter defaultCenter] postNotificationName:notificationName object:bucket userInfo:userInfo]; }); } else { DDLogWarn(@"Simperium warning: couldn't apply change due to version mismatch (duplicate? start %@, old %@): change %@", startVersion, oldVersion, change); dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:@"simperiumIndexRefreshNeeded" object:bucket]; + [[NSNotificationCenter defaultCenter] postNotificationName:ProcessorRequestsReindexing object:bucket]; }); } return YES; } --(BOOL)processRemoteChange:(NSDictionary *)change bucket:(SPBucket *)bucket clientID:(NSString *)clientID -{ +- (BOOL)processRemoteChange:(NSDictionary *)change bucket:(SPBucket *)bucket clientID:(NSString *)clientID { // Create a new context (to be thread-safe) and fetch the entity from it idthreadSafeStorage = [bucket.storage threadSafeStorage]; @@ -361,8 +321,15 @@ -(BOOL)processRemoteChange:(NSDictionary *)change bucket:(SPBucket *)bucket clie DDLogVerbose(@"Simperium client %@ received change (%@) %@: %@", clientID, bucket.name, changeClientID, change); + // Check for an error + if ([change objectForKey:CH_ERROR]) { + DDLogVerbose(@"Simperium error received (%@) for %@, should reload the object here to be safe", bucket.name, key); + return NO; + } + + // Process BOOL clientMatches = [changeClientID compare:clientID] == NSOrderedSame; - BOOL remove = [operation compare: CH_REMOVE] == NSOrderedSame; + BOOL remove = operation && [operation compare: CH_REMOVE] == NSOrderedSame; BOOL acknowledged = [self awaitingAcknowledgementForKey:key] && clientMatches; // If the entity already exists locally, or it's being removed, then check for an ack @@ -374,81 +341,123 @@ -(BOOL)processRemoteChange:(NSDictionary *)change bucket:(SPBucket *)bucket clie [changesPending removeObjectForKey:key]; } - // Check for an error - if ([change objectForKey:CH_ERROR]) { - DDLogVerbose(@"Simperium error received (%@) for %@, should reload the object here to be safe", bucket.name, key); - [changesPending removeObjectForKey:key]; - return NO; - } - DDLogVerbose(@"Simperium performing change operation: %@", operation); - if (remove) - { + if (remove) { if (object || acknowledged) return [self processRemoteDelete: object acknowledged:acknowledged bucket:bucket storage:threadSafeStorage]; - } else if ([operation compare: CH_MODIFY] == NSOrderedSame) { - return [self processRemoteModify: object bucket:bucket change: change acknowledged:acknowledged storage:threadSafeStorage]; + } else if (operation && [operation compare: CH_MODIFY] == NSOrderedSame) { + return [self processRemoteModify:object bucket:bucket change:change acknowledged:acknowledged storage:threadSafeStorage]; } // invalid - DDLogError(@"Simperium error (%@), received an invalid change for (%@)", bucket.name, key); + DDLogError(@"Simperium error (%@), received an invalid change for (%@): %@", bucket.name, key, change); return NO; } --(void)processRemoteChanges:(NSArray *)changes bucket:(SPBucket *)bucket clientID:(NSString *)clientID -{ +- (void)processRemoteChanges:(NSArray *)changes bucket:(SPBucket *)bucket clientID:(NSString *)clientID { NSMutableSet *changedKeys = [NSMutableSet setWithCapacity:[changes count]]; + + // Construct a list of keys for a willChange notification (and ignore acks) for (NSDictionary *change in changes) { - NSString *key = [change objectForKey:CH_KEY]; - [changedKeys addObject:key]; + NSString *key = change[CH_KEY]; + if (![self awaitingAcknowledgementForKey:key]) { + [changedKeys addObject:key]; + } } - NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys: - bucket.name, @"bucketName", - changedKeys, @"keys", nil]; - [[NSNotificationCenter defaultCenter] postNotificationName:ProcessorWillChangeObjectsNotification object:bucket userInfo:userInfo]; + dispatch_async(dispatch_get_main_queue(), ^{ + + if (changedKeys.count > 0) { + NSDictionary *userInfo = @{ + @"bucketName" : bucket.name, + @"keys" : changedKeys + }; + + [[NSNotificationCenter defaultCenter] postNotificationName:ProcessorWillChangeObjectsNotification object:bucket userInfo:userInfo]; + } + + // The above notification needs to give the main thread a chance to react before we continue + dispatch_async(bucket.processorQueue, ^{ + + for (NSDictionary *change in changes) { + // Process the change (this is necessary even if it's an ack, so the ghost data gets set accordingly) + if (![self processRemoteChange:change bucket:bucket clientID:clientID]) { + continue; + } + + // Remember the last version + // This persists...do it inside the loop in case something happens to abort the loop + NSString *changeVersion = change[@"cv"]; + + dispatch_async(dispatch_get_main_queue(), ^{ + [bucket setLastChangeSignature: changeVersion]; + }); + } + + [self serializeChangesPending]; + + if(changesPending.count == 0) { + [bucket bucketDidSync]; + } + }); + }); +} - for (NSDictionary *change in changes) { - // Process the change (this is necessary even if it's an ack, so the ghost data gets set accordingly) - if (![self processRemoteChange:change bucket:bucket clientID:clientID]) { - continue; - } - - // Remember the last version - // This persists...do it inside the loop in case something happens to abort the loop - NSString *changeVersion = [change objectForKey:@"cv"]; - - dispatch_async(dispatch_get_main_queue(), ^{ - [bucket setLastChangeSignature: changeVersion]; - }); - } + +#pragma mark Local changes + +- (NSMutableDictionary *)createChangeForKey:(NSString *)key operation:(NSString *)operation version:(NSString *)version data:(NSDictionary *)data { + // The change applies to this particular entity instance, so use its unique key as an identifier + NSMutableDictionary *change = [NSMutableDictionary dictionaryWithObject:key forKey:CH_KEY]; + + // Every change must be marked with a unique ID + NSString *uuid = [NSString sp_makeUUID]; + [change setObject:uuid forKey: CH_LOCAL_ID]; + + // Set the change's operation + [change setObject:operation forKey:CH_OPERATION]; - [self serializeChangesPending]; + // Set the data as the value for the operation (e.g. a diff dictionary for modify operations) + if (data) + [change setObject:data forKey:CH_VALUE]; + + // If it's a modify operation, also include the object's version as the last known version + if (operation == CH_MODIFY && version != nil && [version intValue] != 0) + [change setObject: version forKey: CH_START_VERSION]; + + return change; } --(void)processLocalChange:(NSDictionary *)change key:(NSString *)key -{ +- (void)processLocalChange:(NSDictionary *)change key:(NSString *)key { [changesPending setObject:change forKey: key]; [self serializeChangesPending]; + + // Support delayed app termination to ensure local changes have a chance to fully save +#if TARGET_OS_IPHONE +#else + [[NSApplication sharedApplication] replyToApplicationShouldTerminate:YES]; +#endif } --(NSDictionary *)processLocalDeletionWithKey:(NSString *)key -{ +- (NSDictionary *)processLocalDeletionWithKey:(NSString *)key { NSDictionary *change = [self createChangeForKey:key operation:CH_REMOVE version:nil data:nil]; return change; } --(NSDictionary *)processLocalObjectWithKey:(NSString *)key bucket:(SPBucket *)bucket later:(BOOL)later -{ +- (NSDictionary *)processLocalObjectWithKey:(NSString *)key bucket:(SPBucket *)bucket later:(BOOL)later { // Create a new context (to be thread-safe) and fetch the entity from it id storage = [bucket.storage threadSafeStorage]; id object = [storage objectForKey:key bucketName:bucket.name]; - // If the object no longer exists, it might have been previously deleted, in which case this change is no longer + // If the object no longer exists, it was likely previously deleted, in which case this change is no longer // relevant if (!object) { - DDLogWarn(@"Simperium warning: couldn't processLocalObjectWithKey %@ because the object no longer exists", key); + //DDLogWarn(@"Simperium warning: couldn't processLocalObjectWithKey %@ because the object no longer exists", key); + [changesPending removeObjectForKey:key]; + [keysForObjectsWithMoreChanges removeObject:key]; + [self serializeChangesPending]; + [self serializeKeysForObjectsWithMoreChanges]; return nil; } @@ -470,7 +479,7 @@ -(NSDictionary *)processLocalObjectWithKey:(NSString *)key bucket:(SPBucket *)bu // Get a diff of the object (in dictionary form) newData = [bucket.differ diff:object withDictionary: [object.ghost memberData]]; - DDLogVerbose(@"Simperium entity diff found %d changed members", [newData count]); + DDLogVerbose(@"Simperium entity diff found %lu changed members", (unsigned long)[newData count]); if ([newData count] > 0) { change = [self createChangeForKey: object.simperiumKey operation: CH_MODIFY version:object.ghost.version data: newData]; } else { @@ -501,44 +510,51 @@ -(NSDictionary *)processLocalObjectWithKey:(NSString *)key bucket:(SPBucket *)bu return change; } --(NSArray *)processPendingChanges:(SPBucket *)bucket -{ +- (NSArray *)processPendingChanges:(SPBucket *)bucket onlyQueuedChanges:(BOOL)onlyQueuedChanges { // Check if there are more changes that need to be sent + NSMutableDictionary *queuedChanges = [NSMutableDictionary dictionaryWithCapacity:[keysForObjectsWithMoreChanges count]]; + if ([keysForObjectsWithMoreChanges count] > 0) { - DDLogVerbose(@"Simperium found %u objects with more changes to send (%@)", [keysForObjectsWithMoreChanges count], bucket.name); - // TODO: Robust handling of offline deletions - - NSMutableSet *keysProcessed = [NSMutableSet setWithCapacity:[keysForObjectsWithMoreChanges count]]; - // Create changes for any objects that have more changes + DDLogVerbose(@"Simperium found %lu objects with more changes to send (%@)", (unsigned long)[keysForObjectsWithMoreChanges count], bucket.name); + NSMutableSet *pendingKeys = [NSMutableSet setWithCapacity:[keysForObjectsWithMoreChanges count]]; + + //Create a list of the keys to be processed for (NSString *key in keysForObjectsWithMoreChanges) { // If there are already changes pending, don't add any more // Importantly, this prevents a potential mutation of keysForObjectsWithMoreChanges in processLocalObjectWithKey:later: if ([changesPending objectForKey: key] != nil) continue; - - NSDictionary *change = [self processLocalObjectWithKey:key bucket:bucket later:NO]; - - if (change) - [changesPending setObject:change forKey: key]; - [keysProcessed addObject:key]; + + [pendingKeys addObject:key]; } + + // Create changes for any objects that have more changes + [pendingKeys enumerateObjectsUsingBlock:^(NSString *key, BOOL *stop) { + NSDictionary *change = [self processLocalObjectWithKey:key bucket:bucket later:NO]; + + if (change) { + [changesPending setObject:change forKey:key]; + [queuedChanges setObject:change forKey:key]; + } else { + [keysForObjectsWithMoreChanges removeObject:key]; + } + }]; + // Clear any keys that were processed into pending changes - [keysForObjectsWithMoreChanges minusSet:keysProcessed]; + [keysForObjectsWithMoreChanges minusSet: [NSSet setWithArray:[queuedChanges allKeys]]]; } [self serializeChangesPending]; [self serializeKeysForObjectsWithMoreChanges]; - return [changesPending allValues]; + return onlyQueuedChanges ? [queuedChanges allValues] : [changesPending allValues]; } --(NSArray *)processKeysForObjectsWithMoreChanges:(SPBucket *)bucket -{ +- (NSArray *)processKeysForObjectsWithMoreChanges:(SPBucket *)bucket { // Check if there are more changes that need to be sent NSMutableArray *newChangesPending = [NSMutableArray arrayWithCapacity:3]; if ([keysForObjectsWithMoreChanges count] > 0) { - DDLogVerbose(@"Simperium found %u objects with more changes to send (%@)", [keysForObjectsWithMoreChanges count], bucket.name); - // TODO: OFFLINE DELETIONS? + DDLogVerbose(@"Simperium found %lu objects with more changes to send (%@)", (unsigned long)[keysForObjectsWithMoreChanges count], bucket.name); NSMutableSet *keysProcessed = [NSMutableSet setWithCapacity:[keysForObjectsWithMoreChanges count]]; // Create changes for any objects that have more changes @@ -568,12 +584,12 @@ -(NSArray *)processKeysForObjectsWithMoreChanges:(SPBucket *)bucket } --(int)numChangesPending { - return [changesPending count]; +- (int)numChangesPending { + return (int)[changesPending count]; } --(int)numKeysForObjectsWithMoreChanges { - return [keysForObjectsWithMoreChanges count]; +- (int)numKeysForObjectsWithMoreChanges { + return (int)[keysForObjectsWithMoreChanges count]; } @end diff --git a/Simperium/SPCoreDataExporter.m b/Simperium/SPCoreDataExporter.m index 84967f69..5fac4ba6 100644 --- a/Simperium/SPCoreDataExporter.m +++ b/Simperium/SPCoreDataExporter.m @@ -90,6 +90,9 @@ -(void)addMembersFrom:(NSEntityDescription *)entityDesc to:(NSMutableArray *)mem NSString *type = [self simperiumTypeForAttribute: attr]; NSAssert1(type != nil, @"Simperium couldn't load member %@ (unsupported type)", [attr name]); [member setObject: type forKey:@"type"]; + if (attr.attributeType == NSTransformableAttributeType && attr.valueTransformerName != nil) { + [member setObject:attr.valueTransformerName forKey:@"valueTransformerName"]; + } [members addObject: member]; } diff --git a/Simperium/SPCoreDataStorage.h b/Simperium/SPCoreDataStorage.h index f80b8b17..7a973cb9 100644 --- a/Simperium/SPCoreDataStorage.h +++ b/Simperium/SPCoreDataStorage.h @@ -10,24 +10,21 @@ #import "SPStorageObserver.h" #import "SPStorageProvider.h" -@interface SPCoreDataStorage : SPStorage { - id delegate; - SPCoreDataStorage *sibling; - NSMutableDictionary *classMappings; -} - -@property (nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext; -@property (nonatomic, retain, readonly) NSManagedObjectModel *managedObjectModel; -@property (nonatomic, retain, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator; -@property (nonatomic, assign) iddelegate; - -+(char const * const)bucketListKey; -+(BOOL)newCoreDataStack:(NSString *)modelName - managedObjectContext:(NSManagedObjectContext **)managedObjectContext - managedObjectModel:(NSManagedObjectModel **)managedObjectModel -persistentStoreCoordinator:(NSPersistentStoreCoordinator **)persistentStoreCoordinator; - --(id)initWithModel:(NSManagedObjectModel *)model context:(NSManagedObjectContext *)context coordinator:(NSPersistentStoreCoordinator *)coordinator; + + +@interface SPCoreDataStorage : SPStorage + +@property (nonatomic, strong, readonly) NSManagedObjectContext *writerManagedObjectContext; +@property (nonatomic, strong, readonly) NSManagedObjectContext *mainManagedObjectContext; +@property (nonatomic, strong, readonly) NSManagedObjectModel *managedObjectModel; +@property (nonatomic, strong, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator; +@property (nonatomic, weak, readwrite) id delegate; + +extern NSString* const SPCoreDataBucketListKey; + ++(BOOL)newCoreDataStack:(NSString *)modelName mainContext:(NSManagedObjectContext **)mainContext model:(NSManagedObjectModel **)model coordinator:(NSPersistentStoreCoordinator **)coordinator; + +-(id)initWithModel:(NSManagedObjectModel *)model mainContext:(NSManagedObjectContext *)mainContext coordinator:(NSPersistentStoreCoordinator *)coordinator; -(NSArray *)exportSchemas; -(void)setBucketList:(NSDictionary *)dict; diff --git a/Simperium/SPCoreDataStorage.m b/Simperium/SPCoreDataStorage.m index 3fb7fff4..404bb133 100644 --- a/Simperium/SPCoreDataStorage.m +++ b/Simperium/SPCoreDataStorage.m @@ -12,25 +12,26 @@ #import "SPCoreDataExporter.h" #import "SPSchema.h" #import "DDLog.h" -#import + + +NSString* const SPCoreDataBucketListKey = @"SPCoreDataBucketListKey"; static int ddLogLevel = LOG_LEVEL_INFO; -static char const * const BucketListKey = "bucketList"; -@interface SPCoreDataStorage() --(void)addObserversForContext:(NSManagedObjectContext *)context; +@interface SPCoreDataStorage () +@property (nonatomic, strong, readwrite) NSManagedObjectContext *writerManagedObjectContext; +@property (nonatomic, strong, readwrite) NSManagedObjectContext *mainManagedObjectContext; +@property (nonatomic, strong, readwrite) NSManagedObjectModel *managedObjectModel; +@property (nonatomic, strong, readwrite) NSPersistentStoreCoordinator *persistentStoreCoordinator; +@property (nonatomic, strong, readwrite) NSMutableDictionary *classMappings; +@property (nonatomic, weak, readwrite) SPCoreDataStorage *sibling; +-(void)addObserversForMainContext:(NSManagedObjectContext *)context; +-(void)addObserversForChildrenContext:(NSManagedObjectContext *)context; @end -@implementation SPCoreDataStorage -@synthesize managedObjectContext=__managedObjectContext; -@synthesize managedObjectModel=__managedObjectModel; -@synthesize persistentStoreCoordinator=__persistentStoreCoordinator; -@synthesize delegate; -+(char const * const)bucketListKey { - return BucketListKey; -} +@implementation SPCoreDataStorage + (int)ddLogLevel { return ddLogLevel; @@ -40,17 +41,24 @@ + (void)ddSetLogLevel:(int)logLevel { ddLogLevel = logLevel; } --(id)initWithModel:(NSManagedObjectModel *)model context:(NSManagedObjectContext *)context coordinator:(NSPersistentStoreCoordinator *)coordinator +-(id)initWithModel:(NSManagedObjectModel *)model mainContext:(NSManagedObjectContext *)mainContext coordinator:(NSPersistentStoreCoordinator *)coordinator { if (self = [super init]) { - stashedObjects = [[NSMutableSet setWithCapacity:3] retain]; - classMappings = [[NSMutableDictionary dictionary] retain]; - - __persistentStoreCoordinator = [coordinator retain]; - __managedObjectModel = [model retain]; - __managedObjectContext = [context retain]; - - [self addObserversForContext:context]; + // Create a writer MOC + self.writerManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; + + stashedObjects = [NSMutableSet setWithCapacity:3]; + self.classMappings = [NSMutableDictionary dictionary]; + + self.persistentStoreCoordinator = coordinator; + self.managedObjectModel = model; + self.mainManagedObjectContext = mainContext; + + // The new writer MOC will be the only one with direct access to the persistentStoreCoordinator + self.writerManagedObjectContext.persistentStoreCoordinator = self.persistentStoreCoordinator; + self.mainManagedObjectContext.parentContext = self.writerManagedObjectContext; + + [self addObserversForMainContext:self.mainManagedObjectContext]; } return self; } @@ -58,30 +66,25 @@ -(id)initWithModel:(NSManagedObjectModel *)model context:(NSManagedObjectContext -(id)initWithSibling:(SPCoreDataStorage *)aSibling { if (self = [super init]) { - // Create an ephemeral, thread-safe context that will merge back to the sibling automatically - sibling = aSibling; - NSManagedObjectContext *newContext = [[NSManagedObjectContext alloc] init]; - __managedObjectContext = [newContext retain]; - [__managedObjectContext release]; - - [__managedObjectContext setPersistentStoreCoordinator:sibling.managedObjectContext.persistentStoreCoordinator]; - + self.sibling = aSibling; + + // Create an ephemeral, thread-safe context that will push its changes directly to the writer MOC, + // and will also post the changes to the MainQueue + self.mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType]; + self.writerManagedObjectContext = aSibling.writerManagedObjectContext; + + // Wire the Thread Confined Context, directly to the writer MOC + self.mainManagedObjectContext.parentContext = self.writerManagedObjectContext; + // Simperium's context always trumps the app's local context (potentially stomping in-memory changes) - [sibling.managedObjectContext setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy]; - [__managedObjectContext setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy]; + [self.sibling.mainManagedObjectContext setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy]; + [self.mainManagedObjectContext setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy]; // For efficiency - [__managedObjectContext setUndoManager:nil]; + [self.mainManagedObjectContext setUndoManager:nil]; // An observer is expected to handle merges for otherContext when the threaded context is saved - [[NSNotificationCenter defaultCenter] addObserver:sibling - selector:@selector(mergeChanges:) - name:NSManagedObjectContextDidSaveNotification - object:__managedObjectContext]; - - // Be sure to copy the bucket list - NSDictionary *dict = objc_getAssociatedObject(aSibling.managedObjectContext, BucketListKey); - objc_setAssociatedObject(__managedObjectContext, BucketListKey, dict, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + [aSibling addObserversForChildrenContext:self.mainManagedObjectContext]; } return self; @@ -89,44 +92,33 @@ -(id)initWithSibling:(SPCoreDataStorage *)aSibling -(void)dealloc { - objc_setAssociatedObject(__managedObjectContext, BucketListKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - - if (sibling) { + if (self.sibling) { // If a sibling was used, then this context was ephemeral and needs to be cleaned up - [[NSNotificationCenter defaultCenter] removeObserver:sibling name:NSManagedObjectContextDidSaveNotification object:__managedObjectContext]; - [__managedObjectContext release]; - __managedObjectContext = nil; - } - - [classMappings release]; - [stashedObjects release]; - [super dealloc]; -} - --(NSManagedObjectModel *)managedObjectModel { - return __managedObjectModel; -} - --(NSPersistentStoreCoordinator *)persistentStoreCoordinator { - return __persistentStoreCoordinator; -} - --(NSManagedObjectContext *)managedObjectContext { - return __managedObjectContext; + [[NSNotificationCenter defaultCenter] removeObserver:self.sibling name:NSManagedObjectContextWillSaveNotification object:self.mainManagedObjectContext]; + [[NSNotificationCenter defaultCenter] removeObserver:self.sibling name:NSManagedObjectContextDidSaveNotification object:self.mainManagedObjectContext]; + } else { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + } } -(void)setBucketList:(NSDictionary *)dict { // Set a custom field on the context so that objects can figure out their own buckets when they wake up - // (this could use userInfo on iOS5, but it doesn't exist on iOS4) - objc_setAssociatedObject(__managedObjectContext, BucketListKey, dict, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + NSMutableDictionary* bucketList = self.writerManagedObjectContext.userInfo[SPCoreDataBucketListKey]; + + if(!bucketList) + { + bucketList = [NSMutableDictionary dictionary]; + [self.writerManagedObjectContext.userInfo setObject:bucketList forKey:SPCoreDataBucketListKey]; + } + + [bucketList addEntriesFromDictionary:dict]; } -(NSArray *)exportSchemas { SPCoreDataExporter *exporter = [[SPCoreDataExporter alloc] init]; - NSDictionary *definitionDict = [exporter exportModel:__managedObjectModel classMappings:classMappings]; - [exporter release]; + NSDictionary *definitionDict = [exporter exportModel:self.managedObjectModel classMappings:self.classMappings]; - DDLogInfo(@"Simperium loaded %u entity definitions", [definitionDict count]); + DDLogInfo(@"Simperium loaded %lu entity definitions", (unsigned long)[definitionDict count]); NSUInteger numEntities = [[definitionDict allKeys] count]; NSMutableArray *schemas = [NSMutableArray arrayWithCapacity:numEntities]; @@ -135,64 +127,107 @@ -(NSArray *)exportSchemas { SPSchema *schema = [[SPSchema alloc] initWithBucketName:entityName data:entityDict]; [schemas addObject:schema]; - [schema release]; } return schemas; } -(id)threadSafeStorage { - return [[[SPCoreDataStorage alloc] initWithSibling:self] autorelease]; + return [[SPCoreDataStorage alloc] initWithSibling:self]; } -(id)objectForKey: (NSString *)key bucketName:(NSString *)bucketName { NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; - NSEntityDescription *entityDescription = [NSEntityDescription entityForName:bucketName inManagedObjectContext:__managedObjectContext]; + NSEntityDescription *entityDescription = [NSEntityDescription entityForName:bucketName inManagedObjectContext:self.mainManagedObjectContext]; [fetchRequest setEntity:entityDescription]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"simperiumKey == %@", key]; [fetchRequest setPredicate:predicate]; NSError *error; - NSArray *items = [__managedObjectContext executeFetchRequest:fetchRequest error:&error]; - [fetchRequest release]; + NSArray *items = [self.mainManagedObjectContext executeFetchRequest:fetchRequest error:&error]; - if ([items count] == 0) + if ([items count] == 0) { return nil; + } - return [items objectAtIndex:0]; + return items[0]; } - -(NSArray *)objectsForKeys:(NSSet *)keys bucketName:(NSString *)bucketName { return [[self faultObjectsForKeys:[keys allObjects] bucketName:bucketName] allValues]; } --(NSArray *)objectsForBucketName:(NSString *)bucketName +-(NSArray *)objectsForBucketName:(NSString *)bucketName predicate:(NSPredicate *)predicate { NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; - NSEntityDescription *entity = [NSEntityDescription entityForName:bucketName inManagedObjectContext:__managedObjectContext]; + NSEntityDescription *entity = [NSEntityDescription entityForName:bucketName inManagedObjectContext:self.mainManagedObjectContext]; [fetchRequest setEntity:entity]; [fetchRequest setReturnsObjectsAsFaults:YES]; + if (predicate) { + [fetchRequest setPredicate:predicate]; + } + NSError *error; - NSArray *items = [__managedObjectContext executeFetchRequest:fetchRequest error:&error]; - [fetchRequest release]; + NSArray *items = [self.mainManagedObjectContext executeFetchRequest:fetchRequest error:&error]; return items; } +-(NSArray *)objectKeysAndIdsForBucketName:(NSString *)bucketName { + NSEntityDescription *entity = [NSEntityDescription entityForName:bucketName inManagedObjectContext:self.mainManagedObjectContext]; + if (entity == nil) { + //DDLogWarn(@"Simperium warning: couldn't find any instances for entity named %@", entityName); + return nil; + } + NSFetchRequest *request = [[NSFetchRequest alloc] init]; + [request setEntity:entity]; + + // Execute a targeted fetch to preserve faults so that only simperiumKeys are loaded in to memory + // http://stackoverflow.com/questions/3956406/core-data-how-to-get-nsmanagedobjects-objectid-when-nsfetchrequest-returns-nsdi + NSExpressionDescription* objectIdDesc = [NSExpressionDescription new]; + objectIdDesc.name = @"objectID"; + objectIdDesc.expression = [NSExpression expressionForEvaluatedObject]; + objectIdDesc.expressionResultType = NSObjectIDAttributeType; + NSDictionary *properties = [entity propertiesByName]; + request.resultType = NSDictionaryResultType; + request.propertiesToFetch = [NSArray arrayWithObjects:[properties objectForKey:@"simperiumKey"], objectIdDesc, nil]; + + NSError *error = nil; + NSArray *results = [self.mainManagedObjectContext executeFetchRequest:request error:&error]; + if (results == nil) { + // Handle the error. + NSAssert1(0, @"Simperium error: couldn't load array of entities (%@)", bucketName); + } + + return results; + +} + +-(NSArray *)objectKeysForBucketName:(NSString *)bucketName { + NSArray *results = [self objectKeysAndIdsForBucketName:bucketName]; + + NSMutableArray *objectKeys = [NSMutableArray arrayWithCapacity:[results count]]; + for (NSDictionary *result in results) { + NSString *key = [result objectForKey:@"simperiumKey"]; + [objectKeys addObject:key]; + } + + return objectKeys; +} + -(NSInteger)numObjectsForBucketName:(NSString *)bucketName predicate:(NSPredicate *)predicate { NSFetchRequest *request = [[NSFetchRequest alloc] init]; - [request setEntity:[NSEntityDescription entityForName:bucketName inManagedObjectContext:__managedObjectContext]]; + [request setEntity:[NSEntityDescription entityForName:bucketName inManagedObjectContext:self.mainManagedObjectContext]]; [request setIncludesSubentities:NO]; //Omit subentities. Default is YES (i.e. include subentities) - if (predicate) + if (predicate) { [request setPredicate:predicate]; - + } + NSError *err; - NSUInteger count = [__managedObjectContext countForFetchRequest:request error:&err]; - [request release]; + NSUInteger count = [self.mainManagedObjectContext countForFetchRequest:request error:&err]; if(count == NSNotFound) { //Handle error return 0; @@ -200,6 +235,7 @@ -(NSInteger)numObjectsForBucketName:(NSString *)bucketName predicate:(NSPredicat return count; } + -(id)objectAtIndex:(NSUInteger)index bucketName:(NSString *)bucketName { // Not supported return nil; @@ -209,20 +245,18 @@ -(void)insertObject:(id)object bucketName:(NSString *)bucketName { // Not supported } - -(NSDictionary *)faultObjectsForKeys:(NSArray *)keys bucketName:(NSString *)bucketName { // Batch fault a bunch of objects for efficiency NSPredicate *predicate = [NSPredicate predicateWithFormat:@"simperiumKey IN %@", keys]; NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; - NSEntityDescription *entityDescription = [NSEntityDescription entityForName:bucketName inManagedObjectContext:__managedObjectContext]; + NSEntityDescription *entityDescription = [NSEntityDescription entityForName:bucketName inManagedObjectContext:self.mainManagedObjectContext]; [fetchRequest setEntity:entityDescription]; [fetchRequest setPredicate:predicate]; [fetchRequest setReturnsObjectsAsFaults:NO]; NSError *error; - NSArray *objectArray = [__managedObjectContext executeFetchRequest:fetchRequest error:&error]; - [fetchRequest release]; + NSArray *objectArray = [self.mainManagedObjectContext executeFetchRequest:fetchRequest error:&error]; NSMutableDictionary *objects = [NSMutableDictionary dictionaryWithCapacity:[keys count]]; for (SPManagedObject *object in objectArray) { @@ -233,7 +267,7 @@ -(NSDictionary *)faultObjectsForKeys:(NSArray *)keys bucketName:(NSString *)buck -(void)refaultObjects:(NSArray *)objects { for (SPManagedObject *object in objects) { - [__managedObjectContext refreshObject:object mergeChanges:NO]; + [self.mainManagedObjectContext refreshObject:object mergeChanges:NO]; } } @@ -241,7 +275,7 @@ -(id)insertNewObjectForBucketName:(NSString *)bucketName simperiumKey:(NSString { // Every object has its persistent storage managed automatically SPManagedObject *object = [NSEntityDescription insertNewObjectForEntityForName:bucketName - inManagedObjectContext:__managedObjectContext]; + inManagedObjectContext:self.mainManagedObjectContext]; object.simperiumKey = key ? key : [NSString sp_makeUUID]; @@ -260,54 +294,31 @@ -(void)deleteObject:(id)object -(void)deleteAllObjectsForBucketName:(NSString *)bucketName { NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; - NSEntityDescription *entity = [NSEntityDescription entityForName:bucketName inManagedObjectContext:__managedObjectContext]; + NSEntityDescription *entity = [NSEntityDescription entityForName:bucketName inManagedObjectContext:self.mainManagedObjectContext]; [fetchRequest setEntity:entity]; // No need to fault everything [fetchRequest setIncludesPropertyValues:NO]; NSError *error; - NSArray *items = [__managedObjectContext executeFetchRequest:fetchRequest error:&error]; - [fetchRequest release]; + NSArray *items = [self.mainManagedObjectContext executeFetchRequest:fetchRequest error:&error]; for (NSManagedObject *managedObject in items) { - [__managedObjectContext deleteObject:managedObject]; + [self.mainManagedObjectContext deleteObject:managedObject]; } - if (![__managedObjectContext save:&error]) { + + if (![self.mainManagedObjectContext save:&error]) { NSLog(@"Simperium error deleting %@ - error:%@",bucketName,error); } } -(void)validateObjectsForBucketName:(NSString *)bucketName { - NSEntityDescription *entity = [NSEntityDescription entityForName:bucketName inManagedObjectContext:__managedObjectContext]; - if (entity == nil) { - //DDLogWarn(@"Simperium warning: couldn't find any instances for entity named %@", entityName); - return; - } - NSFetchRequest *request = [[NSFetchRequest alloc] init]; - [request setEntity:entity]; - - // Execute a targeted fetch to preserve faults so that only simperiumKeys are loaded in to memory - // http://stackoverflow.com/questions/3956406/core-data-how-to-get-nsmanagedobjects-objectid-when-nsfetchrequest-returns-nsdi - NSExpressionDescription* objectIdDesc = [[NSExpressionDescription new] autorelease]; - objectIdDesc.name = @"objectID"; - objectIdDesc.expression = [NSExpression expressionForEvaluatedObject]; - objectIdDesc.expressionResultType = NSObjectIDAttributeType; - NSDictionary *properties = [entity propertiesByName]; - request.resultType = NSDictionaryResultType; - request.propertiesToFetch = [NSArray arrayWithObjects:[properties objectForKey:@"simperiumKey"], objectIdDesc, nil]; - - NSError *error = nil; - NSArray *results = [__managedObjectContext executeFetchRequest:request error:&error]; - if (results == nil) { - // Handle the error. - NSAssert1(0, @"Simperium error: couldn't load array of entities (%@)", bucketName); - } + NSArray *results = [self objectKeysAndIdsForBucketName:bucketName]; // Check each entity instance for (NSDictionary *result in results) { - SPManagedObject *object = (SPManagedObject *)[__managedObjectContext objectWithID:[result objectForKey:@"objectID"]]; + SPManagedObject *object = (SPManagedObject *)[self.mainManagedObjectContext objectWithID:result[@"objectID"]]; NSString *key = [result objectForKey:@"simperiumKey"]; // In apps like Simplenote where legacy data might exist on the device, the simperiumKey might need to // be set manually. Provide that opportunity here. @@ -337,21 +348,19 @@ -(void)validateObjectsForBucketName:(NSString *)bucketName } } - NSLog(@"Simperium managing %u %@ object instances", [results count], bucketName); - - [request release]; + NSLog(@"Simperium managing %lu %@ object instances", (unsigned long)[results count], bucketName); } -(BOOL)save { // Standard way to save an NSManagedObjectContext NSError *error = nil; - if (__managedObjectContext != nil) + if (self.mainManagedObjectContext != nil) { @try { - BOOL bChanged = [__managedObjectContext hasChanges]; - if (bChanged && ![__managedObjectContext save:&error]) + BOOL bChanged = [self.mainManagedObjectContext hasChanges]; + if (bChanged && ![self.mainManagedObjectContext save:&error]) { NSLog(@"Critical Simperium error while saving context: %@, %@", error, [error userInfo]); return NO; @@ -365,6 +374,17 @@ -(BOOL)save return YES; } +-(void)setMetadata:(NSDictionary *)metadata { + NSPersistentStore *store = [self.persistentStoreCoordinator.persistentStores objectAtIndex:0]; + [self.persistentStoreCoordinator setMetadata:metadata forPersistentStore:store]; +} + +-(NSDictionary *)metadata { + NSPersistentStore *store = [self.persistentStoreCoordinator.persistentStores objectAtIndex:0]; + return [store metadata]; +} + + // CD specific # pragma mark Stashing and unstashing entities -(NSArray *)allUpdatedAndInsertedObjects @@ -372,10 +392,10 @@ -(NSArray *)allUpdatedAndInsertedObjects NSMutableSet *unsavedEntities = [NSMutableSet setWithCapacity:3]; // Add updated objects - [unsavedEntities addObjectsFromArray:[[__managedObjectContext updatedObjects] allObjects]]; + [unsavedEntities addObjectsFromArray:[[self.mainManagedObjectContext updatedObjects] allObjects]]; // Also check for newly inserted objects - [unsavedEntities addObjectsFromArray:[[__managedObjectContext insertedObjects] allObjects]]; + [unsavedEntities addObjectsFromArray:[[self.mainManagedObjectContext insertedObjects] allObjects]]; return [unsavedEntities allObjects]; } @@ -385,33 +405,50 @@ -(void)stashUnsavedObjects NSArray *entitiesToStash = [self allUpdatedAndInsertedObjects]; if ([entitiesToStash count] > 0) { - DDLogVerbose(@"Simperium stashing changes for %u entities", [entitiesToStash count]); + DDLogVerbose(@"Simperium stashing changes for %lu entities", (unsigned long)[entitiesToStash count]); [stashedObjects addObjectsFromArray: entitiesToStash]; } } --(void)contextDidSave:(NSNotification *)notification { + +# pragma mark Main MOC Notification Handlers + +-(void)mainContextWillSave:(NSNotification *)notification +{ + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"objectID.isTemporaryID == YES"]; + NSArray *unpersistedObjects = [[self.mainManagedObjectContext.insertedObjects filteredSetUsingPredicate:predicate] allObjects]; + if(unpersistedObjects.count == 0) { + return; + } + + // Obtain permanentID's for newly inserted objects + NSError *error = nil; + BOOL success = [(NSManagedObjectContext *)notification.object obtainPermanentIDsForObjects:unpersistedObjects error:&error]; + if (!success) { + DDLogVerbose(@"Unable to obtain permanent IDs for objects newly inserted into the main context: %@", error); + } +} + +-(void)mainContextDidSave:(NSNotification *)notification { + + // Now that the changes have been pushed to the writerMOC, persist to disk + [self saveWriterContext]; + // This bypass allows saving to be performed without triggering a sync, as is needed // when storing changes that come off the wire - if (![delegate objectsShouldSync]) + if (![self.delegate objectsShouldSync]) { return; - - NSSet *insertedObjects = [notification.userInfo objectForKey:NSInsertedObjectsKey]; - NSSet *updatedObjects = [notification.userInfo objectForKey:NSUpdatedObjectsKey]; - NSSet *deletedObjects = [notification.userInfo objectForKey:NSDeletedObjectsKey]; + } // Sync all changes - [delegate storage:self updatedObjects:updatedObjects insertedObjects:insertedObjects deletedObjects:deletedObjects]; + NSDictionary *userInfo = notification.userInfo; + [self.delegate storage:self updatedObjects:userInfo[NSUpdatedObjectsKey] insertedObjects:userInfo[NSInsertedObjectsKey] deletedObjects:userInfo[NSDeletedObjectsKey]]; } --(void)contextWillSave:(NSNotification *)notification { - // Not currently used -} - --(void)contextObjectsDidChange:(NSNotification *)notification { +-(void)mainContextObjectsDidChange:(NSNotification *)notification { // Check for inserted objects and init them NSSet *insertedObjects = [notification.userInfo objectForKey:NSInsertedObjectsKey]; - + for (NSManagedObject *insertedObject in insertedObjects) { if ([insertedObject isKindOfClass:[SPManagedObject class]]) { SPManagedObject *object = (SPManagedObject *)insertedObject; @@ -420,43 +457,87 @@ -(void)contextObjectsDidChange:(NSNotification *)notification { } } --(void)addObserversForContext:(NSManagedObjectContext *)moc { - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(contextDidSave:) - name:NSManagedObjectContextDidSaveNotification - object:moc]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(contextObjectsDidChange:) - name:NSManagedObjectContextObjectsDidChangeNotification - object:moc]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(contextWillSave:) - name:NSManagedObjectContextWillSaveNotification - object:moc]; +-(void)addObserversForMainContext:(NSManagedObjectContext *)moc { + NSNotificationCenter* nc = [NSNotificationCenter defaultCenter]; + [nc addObserver:self selector:@selector(mainContextWillSave:) name:NSManagedObjectContextWillSaveNotification object:moc]; + [nc addObserver:self selector:@selector(mainContextDidSave:) name:NSManagedObjectContextDidSaveNotification object:moc]; + [nc addObserver:self selector:@selector(mainContextObjectsDidChange:) name:NSManagedObjectContextObjectsDidChangeNotification object:moc]; } -// Called when threaded contexts need to merge back -- (void)mergeChanges:(NSNotification*)notification + +# pragma mark Children MOC Notification Handlers + +-(void)childrenContextDidSave:(NSNotification*)notification { - // Fault in all updated objects - // (fixes NSFetchedResultsControllers that have predicates, see http://www.mlsite.net/blog/?p=518) -// NSArray* updates = [[notification.userInfo objectForKey:@"updated"] allObjects]; -// for (NSInteger i = [updates count]-1; i >= 0; i--) -// { -// [[__managedObjectContext objectWithID:[[updates objectAtIndex:i] objectID]] willAccessValueForKey:nil]; -// } + // Persist to "disk"! + [self saveWriterContext]; + + // Move the changes to the main MOC. This will NOT trigger main MOC's hasChanges flag. + // NOTE: setting the mainMOC as the childrenMOC's parent will trigger 'mainMOC hasChanges' flag. + // Which, in turn, can cause changes retrieved from the backend to get posted as local changes. + [self.mainManagedObjectContext performBlock:^{ + + // Proceed with the regular merge. This should trigger a contextDidChange note + [self.mainManagedObjectContext mergeChangesFromContextDidSaveNotification:notification]; + + // Force fault & refresh any updated objects. + // Ref.: http://lists.apple.com/archives/cocoa-dev/2008/Jun/msg00264.html + // Ref.: http://stackoverflow.com/questions/16296364/nsfetchedresultscontroller-is-not-showing-all-results-after-merging-an-nsmanage/16296538#16296538 + NSDictionary *userInfo = notification.userInfo; + NSMutableSet *upsertedObjects = [NSMutableSet set]; + + [upsertedObjects unionSet:userInfo[NSUpdatedObjectsKey]]; + [upsertedObjects unionSet:userInfo[NSRefreshedObjectsKey]]; + [upsertedObjects unionSet:userInfo[NSInsertedObjectsKey]]; + + for(NSManagedObject* mo in upsertedObjects) { + NSManagedObject* localMO = [self.mainManagedObjectContext objectWithID:mo.objectID]; + if (localMO.isFault) { + [localMO willAccessValueForKey:nil]; + [self.mainManagedObjectContext refreshObject:localMO mergeChanges:NO]; + } else { + [self.mainManagedObjectContext refreshObject:localMO mergeChanges:YES]; + } + } + + NSSet* deletedObjects = userInfo[NSDeletedObjectsKey]; + for(NSManagedObject* mo in deletedObjects) { + NSManagedObject* localMO = [self.mainManagedObjectContext objectWithID:mo.objectID]; + if(localMO && !localMO.isDeleted) { + [self.mainManagedObjectContext deleteObject:localMO]; + } + } + }]; +} + +-(void)addObserversForChildrenContext:(NSManagedObjectContext *)context { + NSNotificationCenter* nc = [NSNotificationCenter defaultCenter]; + [nc addObserver:self selector:@selector(childrenContextDidSave:) name:NSManagedObjectContextDidSaveNotification object:context]; +} + - dispatch_sync(dispatch_get_main_queue(), ^{ - [__managedObjectContext mergeChangesFromContextDidSaveNotification:notification]; - }); -// [__managedObjectContext performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:) -// withObject:notification -// waitUntilDone:YES]; +# pragma mark Writer MOC Helpers + +-(void)saveWriterContext +{ + [self.writerManagedObjectContext performBlock:^{ + @try + { + NSError *error = nil; + if ([self.writerManagedObjectContext hasChanges] && ![self.writerManagedObjectContext save:&error]) + { + NSLog(@"Critical Simperium error while persisting writer context's changes: %@, %@", error, error.userInfo); + } + } + @catch (NSException *exception) + { + NSLog(@"Simperium exception while persisting writer context's changes: %@", exception.userInfo ? : exception.reason); + } + }]; } -// Standard stack + +#pragma mark - Standard stack +(BOOL)isMigrationNecessary:(NSURL *)storeURL managedObjectModel:(NSManagedObjectModel *)managedObjectModel { @@ -472,10 +553,7 @@ +(BOOL)isMigrationNecessary:(NSURL *)storeURL managedObjectModel:(NSManagedObjec return !pscCompatibile; } -+(BOOL)newCoreDataStack:(NSString *)modelName - managedObjectContext:(NSManagedObjectContext **)managedObjectContext - managedObjectModel:(NSManagedObjectModel **)managedObjectModel -persistentStoreCoordinator:(NSPersistentStoreCoordinator **)persistentStoreCoordinator ++(BOOL)newCoreDataStack:(NSString *)modelName mainContext:(NSManagedObjectContext **)mainContext model:(NSManagedObjectModel **)model coordinator:(NSPersistentStoreCoordinator **)coordinator { NSLog(@"Setting up Core Data: %@", modelName); //NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"Simplenote" withExtension:@"momd"]; @@ -483,7 +561,7 @@ +(BOOL)newCoreDataStack:(NSString *)modelName NSURL *developerModelURL; @try { developerModelURL = [NSURL fileURLWithPath: [[NSBundle mainBundle] pathForResource:modelName ofType:@"momd"]]; - *managedObjectModel = [[[NSManagedObjectModel alloc] initWithContentsOfURL:developerModelURL] autorelease]; + *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:developerModelURL]; } @catch (NSException *e) { NSLog(@"Simperium error: could not find the specified model file (%@.xcdatamodeld)", modelName); @throw; // rethrow the exception @@ -498,15 +576,18 @@ +(BOOL)newCoreDataStack:(NSString *)modelName NSString *path = [documentsDirectory stringByAppendingPathComponent:databaseFilename]; NSURL *storeURL = [NSURL fileURLWithPath:path]; NSError *error = nil; - *persistentStoreCoordinator = [[[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:*managedObjectModel] autorelease]; + *coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:*model]; // Determine if lightweight migration is going to be necessary; this will be used to notify the app in case further action is needed - BOOL lightweightMigrationNeeded = [SPCoreDataStorage isMigrationNecessary:storeURL managedObjectModel:*managedObjectModel]; + BOOL lightweightMigrationNeeded = [SPCoreDataStorage isMigrationNecessary:storeURL managedObjectModel:*model]; // Perform automatic, lightweight migration - NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil]; - - if (![*persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) + NSDictionary *options = @{ + NSMigratePersistentStoresAutomaticallyOption : @(YES), + NSInferMappingModelAutomaticallyOption : @(YES) + }; + + if (![*coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) { //TODO: this can occur the first time you launch a Simperium app after adding Simperium to it. The existing data store lacks the dynamically added members, so it must be upgraded first, and then the opening of the persistent store must be attempted again. @@ -514,18 +595,18 @@ +(BOOL)newCoreDataStack:(NSString *)modelName } // Setup the context - if (persistentStoreCoordinator != nil) + if (mainContext != nil) { - *managedObjectContext = [[[NSManagedObjectContext alloc] init] autorelease]; - [*managedObjectContext setPersistentStoreCoordinator:*persistentStoreCoordinator]; - [*managedObjectContext setUndoManager:nil]; + *mainContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; + [*mainContext setUndoManager:nil]; } return lightweightMigrationNeeded; } // Need to perform a manual migration in a particular case. Do this according to Apple's guidelines. -- (BOOL)migrateStore:(NSURL *)storeURL sourceModel:(NSManagedObjectModel *)srcModel +- (BOOL)migrateStore:(NSURL *)storeURL + sourceModel:(NSManagedObjectModel *)srcModel destinationModel:(NSManagedObjectModel *)dstModel { NSError *error; @@ -553,10 +634,8 @@ - (BOOL)migrateStore:(NSURL *)storeURL sourceModel:(NSManagedObjectModel *)srcMo NSString *message = [NSString stringWithFormat:@"Migration failed %@ [%@]", [error description], ([error userInfo] ? [[error userInfo] description] : @"no user info")]; NSLog(@"Migration failure message: %@", message); - [manager release]; return NO; } - [manager release]; return YES; } @@ -623,4 +702,3 @@ - (BOOL)migrateStore:(NSURL *)storeURL sourceModel:(NSManagedObjectModel *)srcMo // } // [bucketObjects addObject:object]; // } - diff --git a/Simperium/SPDiffable.h b/Simperium/SPDiffable.h index 0adae22d..55654186 100644 --- a/Simperium/SPDiffable.h +++ b/Simperium/SPDiffable.h @@ -6,17 +6,17 @@ @protocol SPDiffable @required -@property (nonatomic, retain) SPGhost *ghost; +@property (nonatomic, strong) SPGhost *ghost; @property (nonatomic, copy) NSString *ghostData; @property (nonatomic, copy) NSString *simperiumKey; -@property (nonatomic, assign) SPBucket *bucket; +@property (nonatomic, weak) SPBucket *bucket; --(void)simperiumSetValue:(id)value forKey:(NSString *)key; --(id)simperiumValueForKey:(NSString *)key; --(void)loadMemberData:(NSDictionary *)data; --(void)willBeRead; --(NSDictionary *)dictionary; --(NSString *)version; --(id)object; +- (void)simperiumSetValue:(id)value forKey:(NSString *)key; +- (id)simperiumValueForKey:(NSString *)key; +- (void)loadMemberData:(NSDictionary *)data; +- (void)willBeRead; +- (NSDictionary *)dictionary; +- (NSString *)version; +- (id)object; -@end \ No newline at end of file +@end diff --git a/Simperium/SPDiffer.h b/Simperium/SPDiffer.h index a5e7463b..74c1aa96 100644 --- a/Simperium/SPDiffer.h +++ b/Simperium/SPDiffer.h @@ -18,7 +18,7 @@ SPSchema *schema; } -@property (nonatomic, retain) SPSchema *schema; +@property (nonatomic, strong) SPSchema *schema; -(id)initWithSchema:(SPSchema *)schema; -(NSMutableDictionary *)diffForAddition: (id)object; diff --git a/Simperium/SPDiffer.m b/Simperium/SPDiffer.m index 231b868f..3749d536 100644 --- a/Simperium/SPDiffer.m +++ b/Simperium/SPDiffer.m @@ -31,7 +31,7 @@ + (void)ddSetLogLevel:(int)logLevel { } --(id)initWithSchema:(SPSchema *)aSchema { +- (id)initWithSchema:(SPSchema *)aSchema { if ((self = [super init])) { self.schema = aSchema; } @@ -39,17 +39,12 @@ -(id)initWithSchema:(SPSchema *)aSchema { return self; } --(void)dealloc { - [super dealloc]; - self.schema = nil; -} // Construct a diff for newly added entities --(NSMutableDictionary *)diffForAddition:(id)object -{ +- (NSMutableDictionary *)diffForAddition:(id)object { NSMutableDictionary *diff = [NSMutableDictionary dictionaryWithCapacity: [schema.members count]]; - for (SPMember *member in schema.members) { + for (SPMember *member in [schema.members allValues]) { NSString *key = [member keyName]; id data = [object simperiumValueForKey: key]; @@ -62,8 +57,7 @@ -(NSMutableDictionary *)diffForAddition:(id)object } // Construct a diff against a particular dictionary of data, such as a ghost dictionary --(NSDictionary *)diff:(id)object withDictionary:(NSDictionary *)dict -{ +- (NSDictionary *)diff:(id)object withDictionary:(NSDictionary *)dict { // changes contains the operations for every key that is different NSMutableDictionary *changes = [NSMutableDictionary dictionaryWithCapacity:3]; @@ -72,11 +66,11 @@ -(NSDictionary *)diff:(id)object withDictionary:(NSDictionary *)dict // but not the other; ignore this functionality for now NSDictionary *currentDiff = nil; - for (SPMember *member in schema.members) + for (SPMember *member in [schema.members allValues]) { NSString *key = [member keyName]; // Make sure the member exists and is tracked by Simperium - SPMember *thisMember = [schema memberNamed: key]; + SPMember *thisMember = [schema memberForKey: key]; if (!thisMember) { DDLogWarn(@"Simperium warning: trying to diff a member that doesn't exist (%@) from ghost: %@", key, [dict description]); continue; @@ -115,15 +109,14 @@ -(NSDictionary *)diff:(id)object withDictionary:(NSDictionary *)dict } // Apply an incoming diff to this entity instance --(void)applyDiff:(NSDictionary *)diff to:(id)object -{ +- (void)applyDiff:(NSDictionary *)diff to:(id)object { // Process each change in the diff for (NSString *key in [diff allKeys]) { NSDictionary *change = [diff objectForKey:key]; NSString *operation = [change objectForKey:OP_OP]; // Make sure the member exists and is tracked by Simperium - SPMember *member = [schema memberNamed: key]; + SPMember *member = [schema memberForKey: key]; if (!member) { DDLogWarn(@"Simperium warning: applyDiff for a member that doesn't exist (%@): %@", key, [change description]); continue; @@ -158,11 +151,10 @@ -(void)applyDiff:(NSDictionary *)diff to:(id)object // Same strategy as applyDiff, but do it to the ghost's memberData // Note that no conversions are necessary here since all data is in JSON-compatible format already --(void)applyGhostDiff:(NSDictionary *)diff to:(id)object -{ +- (void)applyGhostDiff:(NSDictionary *)diff to:(id)object { // Create a copy of the ghost's data and update any members that have changed NSMutableDictionary *ghostMemberData = [[object ghost] memberData]; - NSMutableDictionary *newMemberData = ghostMemberData ? [[ghostMemberData mutableCopy] autorelease] : [NSMutableDictionary dictionaryWithCapacity: [diff count]]; + NSMutableDictionary *newMemberData = ghostMemberData ? [ghostMemberData mutableCopy] : [NSMutableDictionary dictionaryWithCapacity: [diff count]]; for (NSString *key in [diff allKeys]) { NSDictionary *change = [diff objectForKey:key]; @@ -171,7 +163,7 @@ -(void)applyGhostDiff:(NSDictionary *)diff to:(id)object continue; NSString *operation = [change objectForKey:OP_OP]; - SPMember *member = [schema memberNamed: key]; + SPMember *member = [schema memberForKey: key]; // Make sure the member exists and is tracked by Simperium if (!member) { @@ -209,8 +201,7 @@ -(void)applyGhostDiff:(NSDictionary *)diff to:(id)object [object ghost].memberData = newMemberData; } --(NSDictionary *)transform:(id)object diff:(NSDictionary *)diff oldDiff:(NSDictionary *)oldDiff oldGhost:(SPGhost *)oldGhost -{ +- (NSDictionary *)transform:(id)object diff:(NSDictionary *)diff oldDiff:(NSDictionary *)oldDiff oldGhost:(SPGhost *)oldGhost { NSMutableDictionary *newDiff = [NSMutableDictionary dictionary]; // Transform diff first, and then apply it for (NSString *key in [diff allKeys]) { @@ -218,7 +209,7 @@ -(NSDictionary *)transform:(id)object diff:(NSDictionary *)diff oldD NSDictionary *oldChange = [oldDiff objectForKey:key]; // Make sure the member exists and is tracked by Simperium - SPMember *member = [schema memberNamed: key]; + SPMember *member = [schema memberForKey: key]; id ghostValue = [member getValueFromDictionary:oldGhost.memberData key:key object:object]; if (!member) { DDLogError(@"Simperium error: transform diff for a member that doesn't exist (%@): %@", key, [change description]); @@ -252,7 +243,6 @@ -(NSDictionary *)transform:(id)object diff:(NSDictionary *)diff oldD NSDictionary *changeCopy = [change copy]; // If there was no transformation required, just use the original change [newDiff setObject:changeCopy forKey:key]; - [changeCopy release]; } } diff --git a/Simperium/SPEnvironment.h b/Simperium/SPEnvironment.h index 6d1e6209..d4d7d89a 100644 --- a/Simperium/SPEnvironment.h +++ b/Simperium/SPEnvironment.h @@ -4,4 +4,4 @@ extern NSString* const SPBaseURL; extern NSString* const SPAuthURL; -extern NSString* const SPBinaryURL; \ No newline at end of file +extern NSString* const SPBinaryURL; diff --git a/Simperium/SPEnvironment.m b/Simperium/SPEnvironment.m index 1e180ba6..e826eb08 100644 --- a/Simperium/SPEnvironment.m +++ b/Simperium/SPEnvironment.m @@ -5,4 +5,4 @@ // Production NSString* const SPBaseURL = @"https://api.simperium.com/1/"; -NSString* const SPAuthURL = @"https://auth.simperium.com/1/"; \ No newline at end of file +NSString* const SPAuthURL = @"https://auth.simperium.com/1/"; diff --git a/Simperium/SPGhost.h b/Simperium/SPGhost.h index 8a2a3ac3..5971d46a 100644 --- a/Simperium/SPGhost.h +++ b/Simperium/SPGhost.h @@ -19,7 +19,7 @@ @property (copy, nonatomic) NSString *key; @property (copy, nonatomic) NSMutableDictionary *memberData; @property (copy, nonatomic) NSString *version; -@property (assign, nonatomic) BOOL needsSave; +@property (nonatomic) BOOL needsSave; -(id)initFromDictionary:(NSDictionary *)dict; -(id)initWithKey:(NSString *)k memberData:(NSMutableDictionary *)data; diff --git a/Simperium/SPGhost.m b/Simperium/SPGhost.m index b7ac2ae1..39017aa0 100644 --- a/Simperium/SPGhost.m +++ b/Simperium/SPGhost.m @@ -50,27 +50,23 @@ -(id)mutableCopyWithZone: (NSZone *) zone { NSMutableDictionary *memberDataCopy = [[self memberData] mutableCopyWithZone:zone]; newGhost.memberData = memberDataCopy; - [memberDataCopy release]; return newGhost; } -(void)setMemberData:(NSMutableDictionary *)newMemberData { - [memberData release]; memberData = [newMemberData mutableCopy]; needsSave = YES; } -(void)setKey:(NSString *)newKey { - [key release]; key = [newKey copy]; needsSave = YES; } -(void)setVersion:(NSString *)newVersion { - [version release]; version = [newVersion copy]; needsSave = YES; } @@ -85,13 +81,6 @@ -(NSDictionary *)dictionary self.key, @"key", self.version, @"version", self.memberData, @"obj", nil]; } --(void)dealloc -{ - [key release]; - [memberData release]; - [version release]; - [super dealloc]; -} @end diff --git a/Simperium/SPHttpManager.h b/Simperium/SPHttpInterface.h similarity index 81% rename from Simperium/SPHttpManager.h rename to Simperium/SPHttpInterface.h index aa2b28da..d947d8aa 100644 --- a/Simperium/SPHttpManager.h +++ b/Simperium/SPHttpInterface.h @@ -7,14 +7,14 @@ // #import -#import "SPNetworkProvider.h" +#import "SPNetworkInterface.h" @class Simperium; @class ASIHTTPRequest; @class SPBucket; -@interface SPHttpManager : NSObject { - SPBucket *bucket; +@interface SPHttpInterface : NSObject { + SPBucket *__weak bucket; ASIHTTPRequest *getRequest; ASIHTTPRequest *postRequest; BOOL requestCancelled; @@ -27,11 +27,11 @@ } @property (nonatomic, copy) NSString *nextMark; -@property (nonatomic, retain) NSMutableArray *indexArray; +@property (nonatomic, strong) NSMutableArray *indexArray; @property(nonatomic, copy) NSString *pendingLastChangeSignature; +(void)setNetworkActivityIndicatorEnabled:(BOOL)enabled; -(id)initWithSimperium:(Simperium *)s appURL:(NSString *)url clientID:(NSString *)cid; -(void)setBucket:(SPBucket *)aBucket overrides:(NSDictionary *)overrides; -@end \ No newline at end of file +@end diff --git a/Simperium/SPHttpManager.m b/Simperium/SPHttpInterface.m similarity index 93% rename from Simperium/SPHttpManager.m rename to Simperium/SPHttpInterface.m index 510a1719..03083924 100644 --- a/Simperium/SPHttpManager.m +++ b/Simperium/SPHttpInterface.m @@ -8,7 +8,7 @@ #define DEBUG_REQUEST_STATUS #import "SPEnvironment.h" -#import "SPHttpManager.h" +#import "SPHttpInterface.h" #import "Simperium.h" #import "SPDiffer.h" #import "SPBucket.h" @@ -35,13 +35,12 @@ static BOOL networkActivity = NO; static int ddLogLevel = LOG_LEVEL_INFO; -NSString * const AuthenticationDidFailNotification = @"AuthenticationDidFailNotification"; -@interface SPHttpManager() -@property (nonatomic, assign) Simperium *simperium; -@property (nonatomic, assign) SPBucket *bucket; -@property (nonatomic, retain) NSMutableArray *responseBatch; -@property (nonatomic, retain) NSMutableDictionary *versionsWithErrors; +@interface SPHttpInterface() +@property (nonatomic, weak) Simperium *simperium; +@property (nonatomic, weak) SPBucket *bucket; +@property (nonatomic, strong) NSMutableArray *responseBatch; +@property (nonatomic, strong) NSMutableDictionary *versionsWithErrors; @property (nonatomic, copy) NSString *clientID; @property (nonatomic, copy) NSString *remoteBucketName; @@ -51,7 +50,7 @@ -(void)getIndexFailed:(ASIHTTPRequest *)request; -(void)getVersionFailed:(ASIHTTPRequest *)request; @end -@implementation SPHttpManager +@implementation SPHttpInterface @synthesize simperium; @synthesize bucket; @synthesize responseBatch; @@ -110,16 +109,7 @@ -(id)initWithSimperium:(Simperium *)s appURL:(NSString *)url clientID:(NSString -(void)dealloc { [getRequest clearDelegatesAndCancel]; - [getRequest release]; [postRequest clearDelegatesAndCancel]; - [postRequest release]; - self.clientID = nil; - self.indexArray = nil; - self.responseBatch = nil; - self.versionsWithErrors = nil; - self.nextMark = nil; - self.remoteBucketName = nil; - [super dealloc]; } -(void)setBucket:(SPBucket *)aBucket overrides:(NSDictionary *)overrides { @@ -132,7 +122,7 @@ -(void)setBucket:(SPBucket *)aBucket overrides:(NSDictionary *)overrides { -(void)authenticationDidFail { DDLogWarn(@"Simperium authentication failed for token %@", simperium.user.authToken); - [[NSNotificationCenter defaultCenter] postNotificationName:AuthenticationDidFailNotification object:self]; + [[NSNotificationCenter defaultCenter] postNotificationName:SPAuthenticationDidFail object:self]; } -(void)sendChange:(NSDictionary *)change forKey:(NSString *)key @@ -174,10 +164,10 @@ -(void)sendObjectDeletion:(id)object } dispatch_async(bucket.processorQueue, ^{ - if (started) { - NSDictionary *change = [bucket.changeProcessor processLocalDeletionWithKey: key]; - [self sendChange: change forKey: key]; - } + NSDictionary *change = [bucket.changeProcessor processLocalDeletionWithKey: key]; + + // If client is offline and another change is pending, this will overwrite it, which is OK since the object won't exist anymore + [self sendChange: change forKey: key]; }); } @@ -222,10 +212,8 @@ -(void)getChanges // PERFORM GET Content/json on retrieveURL, but it's a long poll // Need callbacks for when changes come in, and when there's an error (or cancelled) NSURL *url = [NSURL URLWithString:getURL]; - [getURL release]; - [getRequest release]; - getRequest = [[ASIHTTPRequest requestWithURL:url] retain]; + getRequest = [ASIHTTPRequest requestWithURL:url]; [getRequest addRequestHeader:@"Content-Type" value:@"application/json"]; [getRequest addRequestHeader:@"X-Simperium-Token" value:[simperium.user authToken]]; [getRequest setDelegate:self]; @@ -242,7 +230,7 @@ -(void)postChanges return; dispatch_async(bucket.processorQueue, ^{ - NSArray *changes = [bucket.changeProcessor processPendingChanges:bucket]; + NSArray *changes = [bucket.changeProcessor processPendingChanges:bucket onlyQueuedChanges:NO]; dispatch_async(dispatch_get_main_queue(), ^{ if ([changes count] == 0) { [self getChanges]; @@ -259,13 +247,11 @@ -(void)postChanges // PERFORM GET NSURL *url = [NSURL URLWithString:sendURL]; - [sendURL release]; NSString *jsonStr = [changes JSONString]; DDLogVerbose(@" post data = %@", jsonStr); - [postRequest release]; - postRequest = [[ASIHTTPRequest requestWithURL:url] retain]; + postRequest = [ASIHTTPRequest requestWithURL:url]; [postRequest appendPostData:[jsonStr dataUsingEncoding:NSUTF8StringEncoding]]; //[sendRequest addRequestHeader:@"Content-Type" value:@"application/json"]; [postRequest addRequestHeader:@"X-Simperium-Token" value:[simperium.user authToken]]; @@ -331,7 +317,7 @@ -(void)start:(SPBucket *)startBucket name:(NSString *)name // TODO: Is this the best and only way to detect when an index of latest versions is needed? BOOL bFirstStart = bucket.lastChangeSignature == nil; if (bFirstStart) { - [self getLatestVersionsForBucket:startBucket]; + [self requestLatestVersionsForBucket:startBucket]; } else [self startProcessingChanges]; } @@ -345,10 +331,8 @@ -(void)stop:(SPBucket *)bucket started = NO; // TODO: Make sure it's safe to arbitrarily cancel these requests [getRequest clearDelegatesAndCancel]; - [getRequest release]; getRequest = nil; [postRequest clearDelegatesAndCancel]; - [postRequest release]; postRequest = nil; // TODO: Consider ensuring threads are done their work and sending a notification @@ -399,7 +383,7 @@ - (void)getChangesFinished:(ASIHTTPRequest *)request if (responseCode == 404) { // Perform a re-indexing here DDLogVerbose(@"Simperium version not found, initiating re-indexing (%@)", [request originalURL]); - [self getLatestVersionsForBucket:self.bucket]; + [self requestLatestVersionsForBucket:self.bucket]; return; } @@ -412,7 +396,7 @@ - (void)getChangesFinished:(ASIHTTPRequest *)request return; } NSArray *changes = [responseString objectFromJSONStringWithParseOptions:JKParseOptionLooseUnicode]; - DDLogVerbose(@"GET response received (%@), handling %d changes...", bucket.name, [changes count] ); + DDLogVerbose(@"GET response received (%@), handling %lu changes...", bucket.name, (unsigned long)[changes count] ); DDLogDebug(@" GET response was: %@", responseString); [self resetRetryDelay]; @@ -509,7 +493,7 @@ - (void)postChangesFailed:(ASIHTTPRequest *)request #pragma mark Index handling --(void)getLatestVersionsMark:(NSString *)mark +-(void)requestLatestVersionsMark:(NSString *)mark { if (!simperium.user) { DDLogError(@"Simperium critical error: tried to retrieve index with no user set"); @@ -551,7 +535,7 @@ -(void)getLatestVersionsMark:(NSString *)mark [indexRequest startAsynchronous]; } --(void)getLatestVersionsForBucket:(SPBucket *)b { +-(void)requestLatestVersionsForBucket:(SPBucket *)b { // Multiple errors could try to trigger multiple index refreshes if (gettingVersions) return; @@ -562,7 +546,7 @@ -(void)getLatestVersionsForBucket:(SPBucket *)b { return; } - [self getLatestVersionsMark:nil]; + [self requestLatestVersionsMark:nil]; } -(ASIHTTPRequest *)getRequestForKey:(NSString *)key version:(NSString *)version @@ -595,16 +579,16 @@ -(void)getVersionsForKeys:(NSArray *)currentIndexArray { self.responseBatch = [NSMutableArray arrayWithCapacity:INDEX_BATCH_SIZE]; // Get all the latest versions - __block ASINetworkQueue *networkQueue = [[ASINetworkQueue queue] retain]; + ASINetworkQueue *networkQueue = [ASINetworkQueue queue]; [networkQueue setDelegate:self]; [networkQueue setQueueDidFinishSelector:@selector(indexQueueFinished:)]; [networkQueue setRequestDidFinishSelector:@selector(getVersionFinished:)]; [networkQueue setRequestDidFailSelector:@selector(getVersionFailed:)]; [networkQueue setMaxConcurrentOperationCount:INDEX_QUEUE_SIZE]; - DDLogInfo(@"Simperium processing %d objects from index (%@)", [currentIndexArray count], bucket.name); + DDLogInfo(@"Simperium processing %lu objects from index (%@)", (unsigned long)[currentIndexArray count], bucket.name); - __block NSArray *indexArrayCopy = [currentIndexArray copy]; + NSArray *indexArrayCopy = [currentIndexArray copy]; __block int numVersionRequests = 0; dispatch_async(bucket.processorQueue, ^{ if (started) { @@ -657,7 +641,7 @@ -(void)getIndexFinished:(ASIHTTPRequest *)request // Store versions as strings, but if they come off the wire as numbers, then handle that too if ([current isKindOfClass:[NSNumber class]]) - current = [NSString stringWithFormat:@"%d", [current integerValue]]; + current = [NSString stringWithFormat:@"%ld", (long)[current integerValue]]; self.pendingLastChangeSignature = [current length] > 0 ? [NSString stringWithFormat:@"%@", current] : nil; self.nextMark = [responseDict objectForKey:@"mark"]; numTransfers--; @@ -676,7 +660,7 @@ -(void)getIndexFinished:(ASIHTTPRequest *)request // If there's another page, get those too (this will repeat until there are none left) if (self.nextMark.length > 0) { DDLogVerbose(@"Simperium found another index page mark (%@): %@", bucket.name, self.nextMark); - [self getLatestVersionsMark:self.nextMark]; + [self requestLatestVersionsMark:self.nextMark]; return; } @@ -699,7 +683,6 @@ -(void)processBatch { }]; } }); - [batch release]; [self.responseBatch removeAllObjects]; } @@ -773,7 +756,7 @@ -(void)indexQueueFinished:(ASINetworkQueue *)networkQueue { if (self.nextMark.length > 0) // More index pages to get - [self getLatestVersionsMark:self.nextMark]; + [self requestLatestVersionsMark:self.nextMark]; else // The entire index has been retrieved [self allVersionsFinished:networkQueue]; @@ -782,7 +765,6 @@ -(void)indexQueueFinished:(ASINetworkQueue *)networkQueue -(void)allVersionsFinished:(ASINetworkQueue *)networkQueue { [self processBatch]; - [networkQueue release]; [self resetRetryDelay]; DDLogVerbose(@"Simperium finished processing all objects from index (%@)", bucket.name); @@ -793,7 +775,7 @@ -(void)allVersionsFinished:(ASINetworkQueue *)networkQueue if ([self.versionsWithErrors count] > 0) { // Try the index refresh again; this could be more efficient since we could know which version requests // failed, but it should happen rarely so take the easy approach for now - DDLogWarn(@"Index refresh complete (%@) but %d versions didn't load, retrying...", bucket.name, [self.versionsWithErrors count]); + DDLogWarn(@"Index refresh complete (%@) but %lu versions didn't load, retrying...", bucket.name, (unsigned long)[self.versionsWithErrors count]); // Create an array in the expected format NSMutableArray *errorArray = [NSMutableArray arrayWithCapacity: [self.versionsWithErrors count]]; @@ -804,7 +786,7 @@ -(void)allVersionsFinished:(ASINetworkQueue *)networkQueue [errorArray addObject:versionDict]; } [self performSelector:@selector(getVersionsForKeys:) withObject: errorArray afterDelay:1]; - //[self performSelector:@selector(getLatestVersions) withObject:nil afterDelay:10]; + //[self performSelector:@selector(requestLatestVersions) withObject:nil afterDelay:10]; return; } @@ -849,15 +831,15 @@ -(void)getIndexFailed:(ASIHTTPRequest *)request numTransfers--; [[self class] updateNetworkActivityIndictator]; - [self performSelector:@selector(getLatestVersionsForBucket:) withObject:self.bucket afterDelay:retry]; + [self performSelector:@selector(requestLatestVersionsForBucket:) withObject:self.bucket afterDelay:retry]; } #pragma mark Versions --(void)getVersions:(int)numVersions forObject:(id)object +-(void)requestVersions:(int)numVersions object:(id)object { // Get all the latest versions - ASINetworkQueue *networkQueue = [[ASINetworkQueue queue] retain]; + ASINetworkQueue *networkQueue = [ASINetworkQueue queue]; [networkQueue setDelegate:self]; [networkQueue setQueueDidFinishSelector:@selector(allObjectVersionsFinished:)]; [networkQueue setRequestDidFinishSelector:@selector(getObjectVersionFinished:)]; @@ -867,7 +849,7 @@ -(void)getVersions:(int)numVersions forObject:(id)object NSInteger startVersion = [object.ghost.version integerValue]-1; for (NSInteger i=startVersion; i>=1 && i>=startVersion-numVersions; i--) { - NSString *versionStr = [NSString stringWithFormat:@"%d", i]; + NSString *versionStr = [NSString stringWithFormat:@"%ld", (long)i]; ASIHTTPRequest *versionRequest = [self getRequestForKey:[object simperiumKey] version:versionStr]; if (!versionRequest) return; @@ -916,7 +898,6 @@ -(void)getObjectVersionFinished:(ASIHTTPRequest *)request -(void)allObjectVersionsFinished:(ASINetworkQueue *)networkQueue { DDLogInfo(@"Simperium finished retrieving all versions"); - [networkQueue release]; } -(void)getObjectVersionFailed:(ASIHTTPRequest *)request @@ -994,5 +975,8 @@ -(void)shareFinished:(ASIHTTPRequest *)request DDLogVerbose(@"Simperium sharing successful"); } +-(void)forceSyncBucket:(SPBucket *)bucket { + // TODO +} @end diff --git a/Simperium/SPIndexProcessor.h b/Simperium/SPIndexProcessor.h index 53c4f17d..61a20e7b 100644 --- a/Simperium/SPIndexProcessor.h +++ b/Simperium/SPIndexProcessor.h @@ -7,13 +7,12 @@ // #import +#import "SPProcessorNotificationNames.h" @class Simperium; @class SPDiffer; -@interface SPIndexProcessor : NSObject { -} - +@interface SPIndexProcessor : NSObject -(void)processIndex:(NSArray *)indexArray bucket:(SPBucket *)bucket versionHandler:(void(^)(NSString *key, NSString *version))versionHandler; -(void)processVersions:(NSArray *)versions bucket:(SPBucket *)bucket firstSync:(BOOL)firstSync changeHandler:(void(^)(NSString *key))changeHandler; @end diff --git a/Simperium/SPIndexProcessor.m b/Simperium/SPIndexProcessor.m index 946d7216..68f3cf28 100644 --- a/Simperium/SPIndexProcessor.m +++ b/Simperium/SPIndexProcessor.m @@ -41,10 +41,6 @@ -(id)init return self; } --(void)dealloc -{ - [super dealloc]; -} // Process an index of keys from the Simperium service for a particular bucket -(void)processIndex:(NSArray *)indexArray bucket:(SPBucket *)bucket versionHandler:(void(^)(NSString *key, NSString *version))versionHandler @@ -59,8 +55,8 @@ -(void)processIndex:(NSArray *)indexArray bucket:(SPBucket *)bucket versionHandl [batchLists addObject: [NSMutableArray arrayWithCapacity:kBatchSize]]; } - int currentBatch = 0; // Build the batches + int currentBatch = 0; NSMutableArray *currentBatchList = [batchLists objectAtIndex:currentBatch]; for (NSDictionary *dict in indexArray) { NSString *key = [dict objectForKey:@"id"]; @@ -76,146 +72,191 @@ -(void)processIndex:(NSArray *)indexArray bucket:(SPBucket *)bucket versionHandl } } + // Take this opportunity to check for any objects that exist locally but not remotely, and remove them + // (this can happen after reindexing if the client missed some remote deletion changes) + + /* TODO: re-enable this after more thorough unit testing. Suspect it could be the cause of + data loss. Disabling this will mean that remotely deleted notes may linger if the + deletion didn't sync before a reindexing. + NSSet *remoteKeySet = [NSSet setWithArray:[indexDict allKeys]]; + [self reconcileLocalAndRemoteIndex:remoteKeySet bucket:bucket storage:threadSafeStorage]; + */ + // Process each batch while being efficient with memory and faulting for (NSMutableArray *batchList in batchLists) { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + @autoreleasepool { // Batch fault the entities for efficiency - NSDictionary *objects = [threadSafeStorage faultObjectsForKeys:batchList bucketName:bucket.name]; - - for (NSString *key in batchList) { - id version = [indexDict objectForKey: key]; + NSDictionary *objects = [threadSafeStorage faultObjectsForKeys:batchList bucketName:bucket.name]; - // Store versions as strings, but if they come off the wire as numbers, then handle that too - if ([version isKindOfClass:[NSNumber class]]) - version = [NSString stringWithFormat:@"%d", [version integerValue]]; + for (NSString *key in batchList) { + id version = [indexDict objectForKey: key]; + + // Store versions as strings, but if they come off the wire as numbers, then handle that too + if ([version isKindOfClass:[NSNumber class]]) + version = [NSString stringWithFormat:@"%ld", (long)[version integerValue]]; + + // Check to see if this entity already exists locally and is up to date + id object = [objects objectForKey:key]; + if (object && object.ghost != nil && object.ghost.version != nil && [version isEqualToString:object.ghost.version]) + continue; + + // Allow caller to use the key and version + versionHandler(key, version); + } - // Check to see if this entity already exists locally and is up to date - id object = [objects objectForKey:key]; - if (object && object.ghost != nil && object.ghost.version != nil && [version isEqualToString:object.ghost.version]) - continue; + // Refault to free up the memory + [threadSafeStorage refaultObjects: [objects allValues]]; + } + } +} + +-(void)reconcileLocalAndRemoteIndex:(NSSet *)remoteKeySet bucket:(SPBucket *)bucket storage:(id)threadSafeStorage { + NSArray *localKeys = [threadSafeStorage objectKeysForBucketName:bucket.name]; + NSMutableSet *localKeySet = [NSMutableSet setWithArray:localKeys]; + [localKeySet minusSet:remoteKeySet]; + + // If any objects exist locally but not remotely, get rid of them + if ([localKeySet count] > 0) { + NSMutableSet *keysForDeletedObjects = [NSMutableSet setWithCapacity:[localKeySet count]]; + NSArray *objectsToDelete = [threadSafeStorage objectsForKeys:localKeySet bucketName:bucket.name]; + + for (idobjectToDelete in objectsToDelete) { + NSString *key = [objectToDelete simperiumKey]; - // Allow caller to use the key and version - dispatch_async(dispatch_get_main_queue(), ^{ - versionHandler(key, version); - }); + // If the object has never synced, be careful not to delete it (it won't exist in the remote index yet) + if ([[objectToDelete ghost] memberData] == nil) { + DDLogWarn(@"Simperium found local object that doesn't exist remotely yet: %@ (%@)", key, bucket.name); + continue; + } + [keysForDeletedObjects addObject:key]; + [threadSafeStorage deleteObject:objectToDelete]; } + DDLogVerbose(@"Simperium deleting %ld objects after re-indexing", (long)[keysForDeletedObjects count]); + [threadSafeStorage save]; - // Refault to free up the memory - [threadSafeStorage refaultObjects: [objects allValues]]; - [pool release]; + NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys: + bucket.name, @"bucketName", + keysForDeletedObjects, @"keys", nil]; + [[NSNotificationCenter defaultCenter] postNotificationName:ProcessorDidDeleteObjectKeysNotification object:bucket userInfo:userInfo]; } } // Process actual version data from the Simperium service for a particular bucket -(void)processVersions:(NSArray *)versions bucket:(SPBucket *)bucket firstSync:(BOOL)firstSync changeHandler:(void(^)(NSString *key))changeHandler { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - NSMutableSet *addedKeys = [NSMutableSet setWithCapacity:5]; - NSMutableSet *changedKeys = [NSMutableSet setWithCapacity:5]; - id threadSafeStorage = [bucket.storage threadSafeStorage]; - - // Batch fault all the objects into a dictionary for efficiency - NSMutableArray *objectKeys = [NSMutableArray arrayWithCapacity:[versions count]]; - for (NSArray *versionData in versions) { - [objectKeys addObject:[versionData objectAtIndex:0]]; - } - NSDictionary *objects = [threadSafeStorage faultObjectsForKeys:objectKeys bucketName:bucket.name]; - - // Process all version data - for (NSArray *versionData in versions) - { - // Unmarshal the data - NSString *key = [versionData objectAtIndex:0]; - NSString *responseString = [versionData objectAtIndex:1]; - NSString *version = [versionData objectAtIndex:2]; - NSMutableDictionary *data = [responseString objectFromJSONStringWithParseOptions:JKParseOptionLooseUnicode]; - - id object = [objects objectForKey:key]; - SPGhost *ghost = nil; + @autoreleasepool { + NSMutableSet *addedKeys = [NSMutableSet setWithCapacity:5]; + NSMutableSet *changedKeys = [NSMutableSet setWithCapacity:5]; + id threadSafeStorage = [bucket.storage threadSafeStorage]; + + // Batch fault all the objects into a dictionary for efficiency + NSMutableArray *objectKeys = [NSMutableArray arrayWithCapacity:[versions count]]; + for (NSArray *versionData in versions) { + [objectKeys addObject:[versionData objectAtIndex:0]]; + } + NSDictionary *objects = [threadSafeStorage faultObjectsForKeys:objectKeys bucketName:bucket.name]; - if (!object) { - // The object doesn't exist locally yet, so create it - object = [threadSafeStorage insertNewObjectForBucketName:bucket.name simperiumKey:key]; - object.bucket = bucket; // set it manually since it won't be set automatically yet - [object loadMemberData:data]; - [addedKeys addObject:key]; - - NSMutableDictionary *newMemberData = [[object dictionary] mutableCopy]; - ghost = [[SPGhost alloc] initWithKey:[object simperiumKey] memberData:newMemberData]; - [newMemberData release]; - DDLogVerbose(@"Simperium added object from index (%@): %@", bucket.name, [object simperiumKey]); - } else { - // The object already exists locally; update it if necessary - BOOL overwriteLocalData = NO; + // Process all version data + for (NSArray *versionData in versions) + { + // Unmarshal the data + NSString *key = [versionData objectAtIndex:0]; + NSString *responseString = [versionData objectAtIndex:1]; + NSString *version = [versionData objectAtIndex:2]; + NSMutableDictionary *data = [responseString objectFromJSONStringWithParseOptions:JKParseOptionLooseUnicode]; + + id object = [objects objectForKey:key]; + SPGhost *ghost = nil; - // The firstSync flag is set if there has not yet been a successful sync. In that case, additional checks - // are performed to see if the local data should be preserved instead. This handles migrations from existing - // sync systems (e.g. Simplenote GAE), and in particular, cases where there are local, unsynced changes that - // should be preserved. - if (firstSync) { - NSDictionary *diff = [bucket.differ diff:object withDictionary:data]; - if ([diff count] > 0 && [object respondsToSelector:@selector(shouldOverwriteLocalChangesFromIndex)]) { - DDLogVerbose(@"Simperium object %@ has changes: %@", [object simperiumKey], diff); - if ([object performSelector:@selector(shouldOverwriteLocalChangesFromIndex)]) { - // The app has determined this object's local changes should be taken from index regardless of any local changes - DDLogVerbose(@"Simperium local object found (%@) with local changes, and OVERWRITING those changes", bucket.name); - overwriteLocalData = YES; - } else - // There's a local, unsynced change, which can only happen on first sync when migrating from an earlier version of an app. - // Allow the caller to deal with this case - changeHandler(key); + if (!object) { + // The object doesn't exist locally yet, so create it + object = [threadSafeStorage insertNewObjectForBucketName:bucket.name simperiumKey:key]; + object.bucket = bucket; // set it manually since it won't be set automatically yet + [object loadMemberData:data]; + [addedKeys addObject:key]; + + NSMutableDictionary *newMemberData = [[object dictionary] mutableCopy]; + ghost = [[SPGhost alloc] initWithKey:[object simperiumKey] memberData:newMemberData]; + DDLogVerbose(@"Simperium added object from index (%@): %@", bucket.name, [object simperiumKey]); + } else { + // The object already exists locally; update it if necessary + BOOL overwriteLocalData = NO; + + // The firstSync flag is set if there has not yet been a successful sync. In that case, additional checks + // are performed to see if the local data should be preserved instead. This handles migrations from existing + // sync systems (e.g. Simplenote GAE), and in particular, cases where there are local, unsynced changes that + // should be preserved. + if (firstSync) { + NSDictionary *diff = [bucket.differ diff:object withDictionary:data]; + if ([diff count] > 0 && [object respondsToSelector:@selector(shouldOverwriteLocalChangesFromIndex)]) { + DDLogVerbose(@"Simperium object %@ has changes: %@", [object simperiumKey], diff); + if ([object performSelector:@selector(shouldOverwriteLocalChangesFromIndex)]) { + // The app has determined this object's local changes should be taken from index regardless of any local changes + DDLogVerbose(@"Simperium local object found (%@) with local changes, and OVERWRITING those changes", bucket.name); + overwriteLocalData = YES; + } else + // There's a local, unsynced change, which can only happen on first sync when migrating from an earlier version of an app. + // Allow the caller to deal with this case + changeHandler(key); + } + + // Set the ghost data (this expects all properties to be present in memberData) + ghost = [[SPGhost alloc] initWithKey:[object simperiumKey] memberData: data]; + } else if (object.version != nil && ![version isEqualToString:object.version]) { + // Safe to do here since the local change has already been posted + overwriteLocalData = YES; } - // Set the ghost data (this expects all properties to be present in memberData) - ghost = [[SPGhost alloc] initWithKey:[object simperiumKey] memberData: data]; - } else if (object.version != nil && ![version isEqualToString:object.version]) { - // Safe to do here since the local change has already been posted - overwriteLocalData = YES; + // Overwrite local changes if necessary + if (overwriteLocalData) { + [object loadMemberData:data]; + + // Be sure to load all members into ghost (since the version results might only contain a subset of members that were changed) + NSMutableDictionary *ghostMemberData = [[object dictionary] mutableCopy]; + // might have already been allocated above + ghost = [[SPGhost alloc] initWithKey:[object simperiumKey] memberData: ghostMemberData]; + [changedKeys addObject:key]; + DDLogVerbose(@"Simperium loaded new data into object %@ (%@)", [object simperiumKey], bucket.name); + } + } - // Overwrite local changes if necessary - if (overwriteLocalData) { - [object loadMemberData:data]; - - // Be sure to load all members into ghost (since the version results might only contain a subset of members that were changed) - NSMutableDictionary *ghostMemberData = [[object dictionary] mutableCopy]; - [ghost release]; // might have already been allocated above - ghost = [[SPGhost alloc] initWithKey:[object simperiumKey] memberData: ghostMemberData]; - [ghostMemberData release]; - [changedKeys addObject:key]; - DDLogVerbose(@"Simperium loaded new data into object %@ (%@)", [object simperiumKey], bucket.name); + // If there is a new/changed ghost, store it + if (ghost) { + DDLogVerbose(@"Simperium updating ghost data for object %@ (%@)", [object simperiumKey], bucket.name); + ghost.version = version; + object.ghost = ghost; + object.simperiumKey = object.simperiumKey; // ugly hack to force entity to save since ghost isn't transient } - } - // If there is a new/changed ghost, store it - if (ghost) { - DDLogVerbose(@"Simperium updating ghost data for object %@ (%@)", [object simperiumKey], bucket.name); - ghost.version = version; - object.ghost = ghost; - object.simperiumKey = object.simperiumKey; // ugly hack to force entity to save since ghost isn't transient - [ghost release]; - } + // Store after processing the batch for efficiency + [threadSafeStorage save]; + [threadSafeStorage refaultObjects:[objects allValues]]; + + // Do all main thread work afterwards as well + dispatch_async(dispatch_get_main_queue(), ^{ + // Manually resolve any pending references to added objects + [bucket resolvePendingRelationshipsToKeys:addedKeys]; + [bucket.storage save]; + + // Revisit the use of NSNotification if there is demand. Currently it's too slow when lots of data is being + // indexed across buckets, so it's not done by default + if (bucket.notifyWhileIndexing) { + NSDictionary *userInfoAdded = [NSDictionary dictionaryWithObjectsAndKeys: + bucket.name, @"bucketName", + addedKeys, @"keys", nil]; + [[NSNotificationCenter defaultCenter] postNotificationName:ProcessorDidAddObjectsNotification object:bucket userInfo:userInfoAdded]; + + for (NSString *key in changedKeys) { + NSDictionary *userInfoChanged = [NSDictionary dictionaryWithObjectsAndKeys: + bucket.name, @"bucketName", + key, @"key", nil]; + [[NSNotificationCenter defaultCenter] postNotificationName:ProcessorDidChangeObjectNotification object:bucket userInfo:userInfoChanged]; + } + } + }); } - - // Store after processing the batch for efficiency - [threadSafeStorage save]; - [threadSafeStorage refaultObjects:[objects allValues]]; - - // Do all main thread work afterwards as well - dispatch_async(dispatch_get_main_queue(), ^{ - NSDictionary *userInfoAdded = [NSDictionary dictionaryWithObjectsAndKeys: - bucket.name, @"bucketName", - addedKeys, @"keys", nil]; - [[NSNotificationCenter defaultCenter] postNotificationName:@"ProcessorDidAddObjectsNotification" object:bucket userInfo:userInfoAdded]; - - NSDictionary *userInfoChanged = [NSDictionary dictionaryWithObjectsAndKeys: - bucket.name, @"bucketName", - changedKeys, @"keys", nil]; - [[NSNotificationCenter defaultCenter] postNotificationName:@"ProcessorDidChangeObjectsNotification" object:bucket userInfo:userInfoChanged]; - }); - - [pool release]; } @end diff --git a/Simperium/SPJSONStorage.h b/Simperium/SPJSONStorage.h index 0d8a696f..9b959374 100644 --- a/Simperium/SPJSONStorage.h +++ b/Simperium/SPJSONStorage.h @@ -18,10 +18,10 @@ dispatch_queue_t storageQueue; } -@property (nonatomic, retain) NSMutableDictionary *objects; -@property (nonatomic, retain) NSMutableDictionary *ghosts; -@property (nonatomic, retain) NSMutableArray *objectList; -@property (nonatomic, retain) NSMutableDictionary *allObjects; +@property (nonatomic, strong) NSMutableDictionary *objects; +@property (nonatomic, strong) NSMutableDictionary *ghosts; +@property (nonatomic, strong) NSMutableArray *objectList; +@property (nonatomic, strong) NSMutableDictionary *allObjects; -(id)initWithDelegate:(id)aDelegate; diff --git a/Simperium/SPJSONStorage.m b/Simperium/SPJSONStorage.m index 524f3544..9dc2f867 100644 --- a/Simperium/SPJSONStorage.m +++ b/Simperium/SPJSONStorage.m @@ -45,14 +45,6 @@ -(id)initWithDelegate:(id)aDelegate return self; } --(void)dealloc -{ - [super dealloc]; - self.objects = nil; - self.objectList = nil; - self.allObjects = nil; - self.ghosts = nil; -} -(void)object:(id)object forKey:(NSString *)simperiumKey didChangeValue:(id)value forKey:(NSString *)key { // Update the schema if applicable @@ -88,12 +80,14 @@ -(NSArray *)objectsForKeys:(NSSet *)keys bucketName:(NSString *)bucketName __block NSArray *someObjects = nil; dispatch_sync(storageQueue, ^{ NSDictionary *objectDict = [objects objectForKey:bucketName]; - if (objectDict) + if (objectDict) { someObjects = [objectDict objectsForKeys:[keys allObjects] notFoundMarker:[NSNull null]]; + } }); if (!someObjects) someObjects = [NSArray array]; + return someObjects; } @@ -101,13 +95,17 @@ -(id)objectAtIndex:(NSUInteger)index bucketName:(NSString *)bucketName { return nil; } --(NSArray *)objectsForBucketName:(NSString *)bucketName -{ +-(NSArray *)objectsForBucketName:(NSString *)bucketName predicate:(NSPredicate *)predicate +{ __block NSArray *bucketObjects = nil; dispatch_sync(storageQueue, ^{ NSDictionary *objectDict = [objects objectForKey:bucketName]; - if (objectDict) + if (objectDict) { bucketObjects = [objects allValues]; + + if (predicate) + bucketObjects = [bucketObjects filteredArrayUsingPredicate:predicate]; + } }); if (!bucketObjects) @@ -115,6 +113,18 @@ -(NSArray *)objectsForBucketName:(NSString *)bucketName return bucketObjects; } +-(NSArray *)objectKeysForBucketName:(NSString *)bucketName { + __block NSArray *bucketObjects = [self objectsForBucketName:bucketName predicate:nil]; + + NSMutableArray *keys = [NSMutableArray arrayWithCapacity:[bucketObjects count]]; + for (idobject in bucketObjects) { + [keys addObject:[object simperiumKey]]; + } + + return keys; +} + + -(NSInteger)numObjectsForBucketName:(NSString *)bucketName predicate:(NSPredicate *)predicate { __block NSInteger count = 0; @@ -180,6 +190,15 @@ -(void)insertObject:(id)dict bucketName:(NSString *)bucketName { }); } +-(void)setMetadata:(NSDictionary *)metadata { + // TODO: support metadata for JSON store + // [[NSUserDefaults standardUserDefaults] setObject:json forKey: key]; + // [[NSUserDefaults standardUserDefaults] synchronize]; +} + +-(NSDictionary *)metadata { + return nil; +} -(void)deleteObject:(id)dict { @@ -198,7 +217,6 @@ -(void)deleteAllObjectsForBucketName:(NSString *)bucketName { // // NSError *error; // NSArray *items = [context executeFetchRequest:fetchRequest error:&error]; -// [fetchRequest release]; // // for (NSManagedObject *managedObject in items) { // [context deleteObject:managedObject]; @@ -220,7 +238,7 @@ -(void)validateObjectsForBucketName:(NSString *)bucketName // // // Execute a targeted fetch to preserve faults so that only simperiumKeys are loaded in to memory // // http://stackoverflow.com/questions/3956406/core-data-how-to-get-nsmanagedobjects-objectid-when-nsfetchrequest-returns-nsdi -// NSExpressionDescription* objectIdDesc = [[NSExpressionDescription new] autorelease]; +// NSExpressionDescription* objectIdDesc = [NSExpressionDescription new]; // objectIdDesc.name = @"objectID"; // objectIdDesc.expression = [NSExpression expressionForEvaluatedObject]; // objectIdDesc.expressionResultType = NSObjectIDAttributeType; @@ -263,9 +281,7 @@ -(void)validateObjectsForBucketName:(NSString *)bucketName // } // } // -// NSLog(@"Simperium managing %u %@ object instances", [results count], entityName); -// -// [request release]; +// NSLog(@"Simperium managing %u %@ object instances", [results count], entityName); } -(BOOL)save diff --git a/Simperium/SPLoginViewController.h b/Simperium/SPLoginViewController.h deleted file mode 100644 index 355f3cae..00000000 --- a/Simperium/SPLoginViewController.h +++ /dev/null @@ -1,40 +0,0 @@ -// -// SPLoginViewController.h -// Simperium -// -// Created by Michael Johnston on 24/11/11. -// Copyright 2011 Simperium. All rights reserved. -// -// You can write a subclass of SPLoginViewController and then set loginViewControllerClass on your -// Simperium instance in order to fully customize the behavior of the authentication UI. You can use -// your own .xib as well, but currently it must be named LoginView.xib (or LoginView-iPad.xib). -// -// Simperium will use the subclass and display your UI automatically. - -#import - -@class SPAuthenticationManager; - -@interface SPLoginViewController : UIViewController -{ - SPAuthenticationManager *authManager; - BOOL creating; - IBOutlet UITextField *loginField; - IBOutlet UITextField *loginPasswordField; - IBOutlet UITextField *loginPasswordConfirmField; - IBOutlet UIActivityIndicatorView *progressView; - IBOutlet UINavigationBar *navbar; - UIButton *actionButton, *changeButton; - IBOutlet UIView *welcomeView; - IBOutlet UITableView *tableView; - IBOutlet UILabel *welcomeLabel; - IBOutlet UIButton *createButton; - IBOutlet UIButton *loginButton; - UIBarButtonItem *cancelButton; -} - -@property (nonatomic, retain) SPAuthenticationManager *authManager; -@property (nonatomic, retain) UITableView* tableView; -@property (nonatomic, assign) BOOL creating; - -@end diff --git a/Simperium/SPLoginViewController.m b/Simperium/SPLoginViewController.m deleted file mode 100644 index 8b7fb26d..00000000 --- a/Simperium/SPLoginViewController.m +++ /dev/null @@ -1,547 +0,0 @@ -// -// SPLoginViewController.m -// Simperium -// -// Created by Michael Johnston on 24/11/11. -// Copyright 2011 Simperium. All rights reserved. -// - -#import "SPLoginViewController.h" -#import "SPAuthenticationManager.h" -#import -#import "ASIFormDataRequest.h" -#import "JSONKit.h" - -@interface SPLoginViewController() --(void)earthquake:(UIView*)itemView; --(void)changeAction:(id)sender; -@end - -@implementation SPLoginViewController -@synthesize tableView; -@synthesize authManager; - -- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil -{ - if ((self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])) { - } - return self; -} - -- (void)dealloc { - self.tableView = nil; - self.authManager = nil; - [cancelButton release]; - [super dealloc]; -} - --(void)setCreating:(BOOL)bCreating -{ - creating = bCreating; - - NSString *actionTitle = creating ? - NSLocalizedString(@"Sign Up", @"Title of button to create a new account (must be short)") : - NSLocalizedString(@"Sign In", @"Title of button for logging in (must be short)"); - NSString *changeTitle = creating ? - NSLocalizedString(@"Already have an account? Sign in", @"A short link to access the account login screen") : - NSLocalizedString(@"Don't have an account? Sign up", @"A short link to access the account creation screen"); - [actionButton setTitle: actionTitle forState:UIControlStateNormal]; - [changeButton setTitle: changeTitle forState:UIControlStateNormal]; -} - --(BOOL)creating { - return creating; -} - -- (void)viewDidLoad { - // The cancel button will only be visible if there's a navigation controller, which will only happen - // if authenticationOptional has been set on the Simperium instance. - cancelButton = [[UIBarButtonItem alloc] initWithTitle:@"Cancel" style:UIBarButtonItemStylePlain target:self action:@selector(cancelAction:)]; - self.navigationItem.rightBarButtonItem = cancelButton; - - [createButton addTarget:self action:@selector(showCreateAction:) forControlEvents:UIControlEventTouchUpInside]; - createButton.titleLabel.font = [UIFont boldSystemFontOfSize:14]; - [createButton setTitle: NSLocalizedString(@"Create an account", @"Button to create an account") forState:UIControlStateNormal]; - - [loginButton addTarget:self action:@selector(showLoginAction:) forControlEvents:UIControlEventTouchUpInside]; - loginButton.titleLabel.font = [UIFont boldSystemFontOfSize:14]; - [loginButton setTitle: NSLocalizedString(@"I have an account", @"Button to use an existing account") forState:UIControlStateNormal]; - self.tableView.hidden = YES; - - actionButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; - [actionButton addTarget:self action:@selector(goAction:) forControlEvents:UIControlEventTouchUpInside]; - actionButton.titleLabel.font = [UIFont boldSystemFontOfSize:14]; - actionButton.frame = CGRectMake(self.tableView.frame.size.width/2-200/2, 0.0, 200, 40); - actionButton.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin; - - changeButton = [UIButton buttonWithType:UIButtonTypeCustom]; - [changeButton addTarget:self action:@selector(changeAction:) forControlEvents:UIControlEventTouchUpInside]; - changeButton.titleLabel.font = [UIFont systemFontOfSize:14]; - [changeButton setTitleColor:[UIColor colorWithRed:59.0/255.0 green:86.0/255.0 blue:137.0/255.0 alpha:1.0] forState:UIControlStateNormal]; - [changeButton setTitleColor:[UIColor darkGrayColor] forState:UIControlStateHighlighted]; - changeButton.frame= CGRectMake(10, 50, self.tableView.frame.size.width-20, 40); - changeButton.autoresizingMask = UIViewAutoresizingFlexibleWidth; - - progressView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; - progressView.frame = CGRectMake(actionButton.frame.size.width - 30, 9, 20, 20); - [actionButton addSubview:progressView]; - - UIView *footerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.tableView.frame.size.width, 200)]; - footerView.contentMode = UIViewContentModeTopLeft; - [footerView setUserInteractionEnabled:YES]; - [footerView addSubview:actionButton]; - [footerView addSubview:changeButton]; - footerView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin; - self.tableView.tableFooterView = footerView; - [footerView release]; - - self.creating = NO; -} - -- (void)viewWillAppear:(BOOL)animated -{ - self.tableView.scrollEnabled = NO; - - if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { - self.tableView.scrollEnabled = NO; - [self.tableView setBackgroundView:nil]; - } - - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil]; -} - -- (void)viewWillDisappear:(BOOL)animated -{ - [[NSNotificationCenter defaultCenter] removeObserver: self name:UIKeyboardWillHideNotification object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver: self name:UIKeyboardWillShowNotification object:nil]; -} - --(void)keyboardWillHide:(NSNotification *)notification -{ - int keyboardHeight = [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height; - if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { - [UIView beginAnimations:nil context:nil]; - [UIView setAnimationCurve:[[[notification userInfo] objectForKey:UIKeyboardAnimationCurveUserInfoKey] intValue]]; - [UIView setAnimationDuration:[[[notification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]]; - [UIView setAnimationBeginsFromCurrentState:YES]; - CGRect rect = self.tableView.frame; - rect.size.height -= keyboardHeight; - rect.origin.y += keyboardHeight/4; - self.tableView.frame = rect; - - [UIView commitAnimations]; - } else { - CGRect rect = self.tableView.frame; - rect.size.height += keyboardHeight; - self.tableView.frame = rect; - } -} - --(void)keyboardWillShow:(NSNotification *)notification -{ - int keyboardHeight = [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height; - if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { - [UIView beginAnimations:nil context:nil]; - [UIView setAnimationCurve:[[[notification userInfo] objectForKey:UIKeyboardAnimationCurveUserInfoKey] intValue]]; - [UIView setAnimationDuration:[[[notification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]]; - [UIView setAnimationBeginsFromCurrentState:YES]; - CGRect rect = self.tableView.frame; - rect.size.height += keyboardHeight; - rect.origin.y -= keyboardHeight/4; - self.tableView.frame = rect; - - [UIView commitAnimations]; - } else { - CGRect rect = self.tableView.frame; - rect.size.height -= keyboardHeight; - self.tableView.frame = rect; - } -} - --(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation { - -} - -- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { - if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) - return YES; - - return (interfaceOrientation == UIInterfaceOrientationPortrait); -} - -#pragma mark Validation --(BOOL)validateEmailWithAlerts:(BOOL)alert -{ - if (loginField.text.length == 0) - return NO; - NSString *emailRegEx = @"[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}"; - NSPredicate *emailTest = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", emailRegEx]; - if (loginField.text != nil && [emailTest evaluateWithObject:loginField.text] == NO) { - if (alert) { - // Bad email address - NSString *title = NSLocalizedString(@"Invalid email", @"Title of dialog displayed when email address is invalid"); - NSString *message = NSLocalizedString(@"Your email address is not valid.", @"Message displayed when email address is invalid"); - UIAlertView *alert = [[UIAlertView alloc] initWithTitle: title message:message - delegate:self cancelButtonTitle:@"OK" otherButtonTitles: nil]; - [alert show]; - [alert release]; - } - return NO; - } - return YES; -} - --(BOOL)validatePasswordWithAlerts:(BOOL)alert -{ - if (loginPasswordField.text == nil || [loginPasswordField.text length] < 4) - { - if (alert) { - // Bad password - NSString *title = NSLocalizedString(@"Invalid password", @"Title of a dialog displayed when password is invalid"); - NSString *message = NSLocalizedString(@"Password must contain at least 4 characters.", @"Message displayed when password is invalid"); - UIAlertView *alert = [[UIAlertView alloc] initWithTitle:title message:message - delegate:self cancelButtonTitle:@"OK" otherButtonTitles: nil]; - [alert show]; - [alert release]; - } - return NO; - } - return YES; -} - --(BOOL)validateDataWithAlerts:(BOOL)alert -{ - if (![self validateEmailWithAlerts:alert]) - return NO; - - return [self validatePasswordWithAlerts:alert]; -} - --(BOOL)validatePasswordConfirmation -{ - if ([loginPasswordField.text compare: loginPasswordConfirmField.text] != NSOrderedSame) { - [self earthquake: loginPasswordField]; - [self earthquake: loginPasswordConfirmField]; - return NO; - } - return YES; -} - -#pragma mark Login - --(void)showLoginAction:(id)sender -{ - [loginField becomeFirstResponder]; - tableView.alpha = 0; - tableView.hidden = NO; - [self.view bringSubviewToFront:tableView]; - [UIView beginAnimations:nil context:nil]; - tableView.alpha = 1.0; - welcomeView.alpha = 0.0; - [UIView commitAnimations]; -} - --(void)performLogin -{ - actionButton.enabled = NO; - changeButton.enabled = NO; - cancelButton.enabled = NO; - - [loginField resignFirstResponder]; - [loginPasswordField resignFirstResponder]; - [progressView setHidden: NO]; - [progressView startAnimating]; - [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES]; - - [self.authManager authenticateWithUsername:loginField.text password:loginPasswordField.text - success:^{ - [progressView setHidden: YES]; - [progressView stopAnimating]; - [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO]; - //[self closeLoginScreenAnimated:YES]; - } - failure: ^(int responseCode, NSString *responseString){ - actionButton.enabled = YES; - changeButton.enabled = YES; - cancelButton.enabled = YES; - - [progressView setHidden: YES]; - [progressView stopAnimating]; - [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO]; - - NSString *title = NSLocalizedString(@"Login failed", @"Title of a dialog displayed when login fails"); - NSString *message = NSLocalizedString(@"Could not login with the provided email address and password.", @"Message displayed when login fails"); - UIAlertView *alert = [[UIAlertView alloc] initWithTitle:title message:message - delegate:self cancelButtonTitle:@"OK" otherButtonTitles: nil]; - [alert show]; - [alert release]; - } - ]; -} - -#pragma mark Creation - --(void)showCreateAction:(id)sender -{ - [self changeAction:sender]; - [loginField becomeFirstResponder]; - tableView.alpha = 0; - tableView.hidden = NO; - [self.view bringSubviewToFront:tableView]; - [UIView beginAnimations:nil context:nil]; - tableView.alpha = 1.0; - welcomeView.alpha = 0.0; - [UIView commitAnimations]; -} - --(void)restoreCreationSettings -{ - actionButton.enabled = YES; - changeButton.enabled = YES; - cancelButton.enabled = YES; - [progressView setHidden: YES]; - [progressView stopAnimating]; - [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO]; - CFPreferencesSetAppValue(CFSTR("email"), @"", kCFPreferencesCurrentApplication); - CFPreferencesSetAppValue(CFSTR("password"), @"", kCFPreferencesCurrentApplication); - CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication); -} - --(void)performCreation -{ - actionButton.enabled = NO; - changeButton.enabled = NO; - cancelButton.enabled = NO; - - [loginField resignFirstResponder]; - [loginPasswordField resignFirstResponder]; - [loginPasswordConfirmField resignFirstResponder]; - CFPreferencesSetAppValue(CFSTR("email"), loginField.text, kCFPreferencesCurrentApplication); - CFPreferencesSetAppValue(CFSTR("password"), loginPasswordField.text, kCFPreferencesCurrentApplication); - CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication); - - // Try to login and sync after entering password? - [progressView setHidden: NO]; - [progressView startAnimating]; - [authManager createWithUsername:loginField.text password:loginPasswordField.text - success:^{ - [progressView setHidden: YES]; - [progressView stopAnimating]; - [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO]; - - //[appDelegate closeLoginScreenAnimated:YES]; - -#ifdef TESTFLIGHT - [TestFlight passCheckpoint:@"Account created"]; -#endif - } - failure:^(int responseCode, NSString *responseString){ - [self restoreCreationSettings]; - NSString *title = NSLocalizedString(@"Account creation failed", - @"The title for a dialog that notifies you when account creation fails"); - NSString *message = NSLocalizedString(@"Could not create an account with the provided email address and password.", @"An error message"); - UIAlertView *alert = [[UIAlertView alloc] initWithTitle:title message: message - delegate:self cancelButtonTitle:@"OK" otherButtonTitles: nil]; - [alert show]; - [alert release]; - } - ]; -} - --(void)failedDueToNetwork:(ASIHTTPRequest *)request -{ - [self restoreCreationSettings]; - NSString *title = NSLocalizedString(@"No connection", - @"The title for a dialog that is displayed when there's a connection problem"); - NSString *message = NSLocalizedString(@"There's a problem with the connection. Please try again later.", - @"Details for a dialog that is displayed when there's a connection problem"); - UIAlertView *alert = [[UIAlertView alloc] initWithTitle:title message:message - delegate:self cancelButtonTitle:@"OK" otherButtonTitles: nil]; - [alert show]; - [alert release]; -} - -#pragma mark Actions - --(void)changeAction:(id)sender -{ - self.creating = !self.creating; - NSArray *indexPaths = [NSArray arrayWithObject:[NSIndexPath indexPathForRow:2 inSection:0]]; - if (self.creating) - [self.tableView insertRowsAtIndexPaths: indexPaths withRowAnimation:UITableViewRowAnimationTop]; - else - [self.tableView deleteRowsAtIndexPaths: indexPaths withRowAnimation:UITableViewRowAnimationTop]; - [loginField becomeFirstResponder]; -} - --(void)goAction:(id)sender -{ - if ([self validateDataWithAlerts:YES]) { - if (self.creating) { - if ([self validatePasswordConfirmation]) - [self performCreation]; - } else - [self performLogin]; - } -} - --(void)cancelAction:(id)sender -{ - [authManager cancel]; -} - -#pragma mark Text Field - -- (BOOL)textFieldShouldReturn:(UITextField *)theTextField { - if (theTextField == loginField) - { - if (![self validateEmailWithAlerts:YES]) { - return NO; - } - - // Advance to next field and don't dismiss keyboard - [loginPasswordField becomeFirstResponder]; - return NO; - } - else if(theTextField == loginPasswordField) - { - if ([self validatePasswordWithAlerts:YES]) { - if (self.creating) { - // Advance to next field and don't dismiss keyboard - [loginPasswordConfirmField becomeFirstResponder]; - return NO; - } else - [self performLogin]; - } - } - else - { - if (creating && [self validatePasswordConfirmation] && [self validateDataWithAlerts:YES]) - [self performCreation]; - } - - return YES; -} - - -#pragma mark - -#pragma mark Table Data Source Methods -- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section -{ - return self.creating ? 3 : 2; -} - --(NSString*) tableView:(UITableView*) tView titleForHeaderInSection:(NSInteger)section -{ - return @""; -} - --(NSString*) tableView:(UITableView*) tView titleForFooterInSection:(NSInteger)section -{ - return @""; -} - -- (UITableViewCell *)tableView:(UITableView *)tView cellForRowAtIndexPath:(NSIndexPath *)indexPath -{ - static NSString *EmailCellIdentifier = @"EmailCellIdentifier"; - static NSString *PasswordCellIdentifier = @"PasswordCellIdentifier"; - static NSString *ConfirmCellIdentifier = @"ConfirmCellIdentifier"; - - UITableViewCell *cell; - if (indexPath.row == 0) { - cell = [tView dequeueReusableCellWithIdentifier:EmailCellIdentifier]; - // Email - if (cell == nil) { - cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:EmailCellIdentifier] autorelease]; - cell.textLabel.font = [UIFont boldSystemFontOfSize:14]; - - loginField = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, 280, 25)]; - loginField.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleWidth; - loginField.clearsOnBeginEditing = NO; - loginField.autocorrectionType = UITextAutocorrectionTypeNo; - loginField.autocapitalizationType = UITextAutocapitalizationTypeNone; - loginField.keyboardType = UIKeyboardTypeEmailAddress; - [loginField setDelegate:self]; - - loginField.returnKeyType = UIReturnKeyNext; - loginField.clearButtonMode = UITextFieldViewModeWhileEditing; - loginField.placeholder = @"email@email.com"; - cell.accessoryView = loginField; - } - } else if (indexPath.row == 1) { - cell = [tView dequeueReusableCellWithIdentifier:PasswordCellIdentifier]; - // Password - if (cell == nil) { - cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:PasswordCellIdentifier] autorelease]; - cell.textLabel.font = [UIFont boldSystemFontOfSize:14]; - - loginPasswordField = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, 280, 25)]; - loginPasswordField.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleWidth; - loginPasswordField.autocorrectionType = UITextAutocorrectionTypeNo; - loginPasswordField.autocapitalizationType = UITextAutocapitalizationTypeNone; - [loginPasswordField setDelegate:self]; - loginPasswordField.secureTextEntry = YES; - loginPasswordField.clearsOnBeginEditing = YES; - loginPasswordField.placeholder = NSLocalizedString(@"Password", @"Hint displayed in the password field"); - cell.accessoryView = loginPasswordField; - } - - loginPasswordField.returnKeyType = self.creating ? UIReturnKeyNext : UIReturnKeyGo; - } else { - cell = [tView dequeueReusableCellWithIdentifier:ConfirmCellIdentifier]; - // Password - if (cell == nil) { - cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ConfirmCellIdentifier] autorelease]; - cell.textLabel.font = [UIFont boldSystemFontOfSize:14]; - - loginPasswordConfirmField = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, 280, 25)]; - loginPasswordConfirmField.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleWidth; - loginPasswordConfirmField.autocorrectionType = UITextAutocorrectionTypeNo; - loginPasswordConfirmField.autocapitalizationType = UITextAutocapitalizationTypeNone; - [loginPasswordConfirmField setDelegate:self]; - loginPasswordConfirmField.returnKeyType = UIReturnKeyGo; - loginPasswordConfirmField.secureTextEntry = YES; - loginPasswordConfirmField.clearsOnBeginEditing = YES; - loginPasswordConfirmField.placeholder = NSLocalizedString(@"Confirm", @"Hint displayed in the password confirmation field"); - cell.accessoryView = loginPasswordConfirmField; - } - } - cell.selectionStyle = UITableViewCellSelectionStyleNone; - - return cell; -} - -#pragma mark Helpers -- (void)earthquake:(UIView*)itemView -{ - // From http://stackoverflow.com/a/1827373/1379066 - CGFloat t = 2.0; - - CGAffineTransform leftQuake = CGAffineTransformTranslate(CGAffineTransformIdentity, t, -t); - CGAffineTransform rightQuake = CGAffineTransformTranslate(CGAffineTransformIdentity, -t, t); - - itemView.transform = leftQuake; // starting point - - [UIView beginAnimations:@"earthquake" context:itemView]; - [UIView setAnimationRepeatAutoreverses:YES]; // important - [UIView setAnimationRepeatCount:5]; - [UIView setAnimationDuration:0.07]; - [UIView setAnimationDelegate:self]; - [UIView setAnimationDidStopSelector:@selector(earthquakeEnded:finished:context:)]; - - itemView.transform = rightQuake; // end here & auto-reverse - - [UIView commitAnimations]; -} - -- (void)earthquakeEnded:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context -{ - if ([finished boolValue]) - { - UIView* item = (UIView *)context; - item.transform = CGAffineTransformIdentity; - } -} - -@end diff --git a/Simperium/SPManagedObject.h b/Simperium/SPManagedObject.h index 75b5f339..cc6e1d5d 100644 --- a/Simperium/SPManagedObject.h +++ b/Simperium/SPManagedObject.h @@ -19,7 +19,7 @@ // The entity's member data as last seen by the server, stored in dictionary form for diffing // has key, data, and signature SPGhost *ghost; - SPBucket *bucket; + SPBucket *__weak bucket; NSString *simperiumKey; NSString *ghostData; @@ -28,14 +28,14 @@ BOOL updateWaiting; } -@property (retain, nonatomic) SPGhost *ghost; -@property (assign, nonatomic) SPBucket *bucket; +@property (strong, nonatomic) SPGhost *ghost; +@property (weak, nonatomic) SPBucket *bucket; @property (copy, nonatomic) NSString *ghostData; @property (copy, nonatomic) NSString *simperiumKey; -@property (assign, nonatomic) BOOL updateWaiting; +@property (nonatomic) BOOL updateWaiting; --(void)loadMemberData:(NSDictionary *)dictionary; --(NSDictionary *)dictionary; --(NSString *)version; +- (void)loadMemberData:(NSDictionary *)dictionary; +- (NSDictionary *)dictionary; +- (NSString *)version; @end diff --git a/Simperium/SPManagedObject.m b/Simperium/SPManagedObject.m index f2868318..65bda4f1 100644 --- a/Simperium/SPManagedObject.m +++ b/Simperium/SPManagedObject.m @@ -15,7 +15,8 @@ #import "SPGhost.h" #import "JSONKit.h" #import "DDLog.h" -#import + + @implementation SPManagedObject @synthesize ghost; @@ -43,35 +44,40 @@ -(id)simperiumValueForKey:(NSString *)key { } --(void)configureBucket -{ - char const * const bucketListKey = [SPCoreDataStorage bucketListKey]; - NSDictionary *bucketList = objc_getAssociatedObject(self.managedObjectContext, bucketListKey); - - if (!bucketList) +- (void)configureBucket { + + NSDictionary *bucketList = nil; + NSManagedObjectContext *managedObjectContext = self.managedObjectContext; + + // Get the MOC's Grandpa (writerContext) + while (managedObjectContext.parentContext != nil) { + managedObjectContext = managedObjectContext.parentContext; + } + + // Check + bucketList = managedObjectContext.userInfo[SPCoreDataBucketListKey]; + + if (!bucketList) { NSLog(@"Simperium error: bucket list not loaded. Ensure Simperium is started before any objects are fetched."); + } + bucket = [bucketList objectForKey:[[self entity] name]]; } --(void)awakeFromFetch -{ +- (void)awakeFromFetch { [super awakeFromFetch]; SPGhost *newGhost = [[SPGhost alloc] initFromDictionary: [self.ghostData objectFromJSONString]]; self.ghost = newGhost; - [newGhost release]; [self.managedObjectContext userInfo]; [self configureBucket]; } --(void)awakeFromInsert -{ +- (void)awakeFromInsert { [super awakeFromInsert]; [self configureBucket]; } --(void)didTurnIntoFault -{ - [ghost release]; +- (void)didTurnIntoFault { ghost = nil; [super didTurnIntoFault]; } @@ -80,12 +86,10 @@ -(void)didTurnIntoFault //{ //} --(void)willSave -{ +- (void)willSave { // When the entity is saved, check to see if its ghost has changed, in which case its data needs to be converted // to a string for storage if (ghost.needsSave) { - [ghostData release]; // Careful not to use self.ghostData here, which would trigger KVC and cause strange things to happen (since willSave itself is related to Core Data's KVC triggerings). This manifested itself as an erroneous insertion notification being sent to fetchedResultsControllers after an object had been deleted. The underlying cause seemed to be that the deleted object sticks around as a fault, but probably shouldn't. ghostData = [[[ghost dictionary] JSONString] copy]; ghost.needsSave = NO; @@ -93,9 +97,7 @@ -(void)willSave } //- (void)setGhost:(SPGhost *)aGhost { -// [ghost release]; -// ghost = [aGhost retain]; -// [ghostData release]; +// ghost = aGhost; // ghostData = [[[aGhost dictionary] JSONRepresentation] copy]; //} @@ -105,7 +107,6 @@ - (void)setGhostData:(NSString *)aString { // NSString implements NSCopying, so copy the attribute value NSString *newStr = [aString copy]; [self setPrimitiveValue:newStr forKey:@"ghostData"]; // setPrimitiveContent will make it nil if the string is empty - [newStr release]; [self didChangeValueForKey:@"ghostData"]; } @@ -116,44 +117,42 @@ - (void)setSimperiumKey:(NSString *)aString { // NSString implements NSCopying, so copy the attribute value NSString *newStr = [aString copy]; [self setPrimitiveValue:newStr forKey:@"simperiumKey"]; // setPrimitiveContent will make it nil if the string is empty - [newStr release]; [self didChangeValueForKey:@"simperiumKey"]; } -- (NSString *)localID -{ +- (NSString *)localID { NSManagedObjectID *key = [self objectID]; if ([key isTemporaryID]) return nil; return [[key URIRepresentation] absoluteString]; } --(void)loadMemberData:(NSDictionary *)memberData -{ +- (void)loadMemberData:(NSDictionary *)memberData { // Copy data for each member from the dictionary - for (SPMember *member in bucket.differ.schema.members) { - NSString *memberKey = [member keyName]; - id data = [member getValueFromDictionary:memberData key:memberKey object:self]; - - // This sets the actual instance data - [self setValue: data forKey: [member keyName]]; - } + for (NSString *memberKey in [memberData allKeys]) { + SPMember *member = [bucket.differ.schema memberForKey:memberKey]; + if (member) { + id data = [member getValueFromDictionary:memberData key:memberKey object:self]; + + // This sets the actual instance data + [self setValue: data forKey: [member keyName]]; + } + } } --(void)willBeRead { +- (void)willBeRead { // Bit of a hack to force fire the fault if ([self isFault]) [self simperiumKey]; } --(NSDictionary *)dictionary -{ +- (NSDictionary *)dictionary { // Return a dictionary that contains member names as keys and actual member data as values // This can be used for diffing, serialization, networking, etc. NSMutableDictionary *dict = [NSMutableDictionary dictionary]; - for (SPMember *member in bucket.differ.schema.members) { + for (SPMember *member in [bucket.differ.schema.members allValues]) { id data = [self valueForKey:[member keyName]]; // The setValue:forKey:inDictionary: method can perform conversions to JSON-compatible formats @@ -164,11 +163,11 @@ -(NSDictionary *)dictionary return dict; } --(NSString *)version { +- (NSString *)version { return ghost.version; } --(id)object { +- (id)object { return self; } diff --git a/Simperium/SPMember.h b/Simperium/SPMember.h index 46dd0b1b..1f08911a 100644 --- a/Simperium/SPMember.h +++ b/Simperium/SPMember.h @@ -20,17 +20,20 @@ extern NSString * const OP_OBJECT_ADD; extern NSString * const OP_OBJECT_REMOVE; extern NSString * const OP_INTEGER; extern NSString * const OP_LIST; +extern NSString * const OP_LIST_DMP; extern NSString * const OP_OBJECT; extern NSString * const OP_STRING; @interface SPMember : NSObject { NSString *keyName; NSString *type; + NSString *valueTransformerName; id modelDefaultValue; } -@property (nonatomic, assign, readonly) NSString *keyName; -@property (nonatomic, assign, readonly) id modelDefaultValue; +@property (nonatomic, readonly, strong) NSString *keyName; +@property (nonatomic, readonly, strong) NSString *valueTransformerName; +@property (nonatomic, readonly, strong) id modelDefaultValue; -(id)initFromDictionary:(NSDictionary *)dict; -(id)defaultValue; @@ -39,16 +42,8 @@ extern NSString * const OP_STRING; -(NSDictionary *)diffForRemoval; -(id)getValueFromDictionary:(NSDictionary *)dict key:(NSString *)key object:(id)object; -(void)setValue:(id)value forKey:(NSString *)key inDictionary:(NSMutableDictionary *)dict; --(id)fromJSON:(id)value; --(id)toJSON:(id)value; -(NSDictionary *)diff:(id)thisValue otherValue:(id)otherValue; -(id)applyDiff:(id)thisValue otherValue:(id)otherValue; -(NSDictionary *)transform:(id)thisValue otherValue:(id)otherValue oldValue:(id)oldValue; -// Could potentially support raw SQL -//-(NSString *)defaultValueAsStringForSQL; -//-(NSString *)typeAsStringForSQL; -//-(id)sqlLoadWithStatement:(sqlite3_stmt *) statement queryPosition:(int)position; -//-(void)sqlBind:(id)data withStatement:(sqlite3_stmt *)statement queryPosition:(int)position; - -@end \ No newline at end of file +@end diff --git a/Simperium/SPMember.m b/Simperium/SPMember.m index 297ab543..015cbd49 100644 --- a/Simperium/SPMember.m +++ b/Simperium/SPMember.m @@ -12,6 +12,7 @@ @implementation SPMember @synthesize keyName; +@synthesize valueTransformerName; @synthesize modelDefaultValue; // Operations used for diff and transform @@ -24,6 +25,7 @@ @implementation SPMember NSString * const OP_OBJECT_REMOVE = @"-"; NSString * const OP_INTEGER = @"I"; NSString * const OP_LIST = @"L"; +NSString * const OP_LIST_DMP = @"dL"; NSString * const OP_OBJECT = @"O"; NSString * const OP_STRING = @"d"; @@ -32,6 +34,7 @@ -(id)initFromDictionary:(NSDictionary *)dict if ((self = [self init])) { keyName = [[dict objectForKey:@"name"] copy]; type = [[dict objectForKey:@"type"] copy]; + valueTransformerName = [[dict objectForKey:@"valueTransformerName"] copy]; modelDefaultValue = [[dict objectForKey:@"defaultValue"] copy]; } @@ -42,13 +45,6 @@ - (NSString *)description { return [NSString stringWithFormat:@"%@ of type %@", keyName, type]; } --(void)dealloc { - [super dealloc]; - [keyName release]; - [type release]; - [modelDefaultValue release]; -} - -(NSDictionary *)diffForAddition:(id)data { NSMutableDictionary *diff = [NSMutableDictionary dictionaryWithObjectsAndKeys: OP_OBJECT_ADD, OP_OP, @@ -80,15 +76,6 @@ -(id)defaultValue { return nil; } --(id)toJSON:(id)value { - return value; -} - --(id)fromJSON:(id)value { - return value; -} - - -(id)getValueFromDictionary:(NSDictionary *)dict key:(NSString *)key object:(id)object { id value = [dict objectForKey: key]; return value; @@ -114,34 +101,13 @@ -(NSDictionary *)transform:(id)thisValue otherValue:(id)otherValue oldValue:(id) @end -// Could support raw SQL - -//-(id)defaultValueAsStringForSQL { -// return @"NULL"; -//} -// -//-(NSString *)typeAsStringForSQL { -// return @"NULL"; -//} -// -//-(void)sqlBind:(id)data withStatement:(sqlite3_stmt *)statement queryPosition:(int)position { -//} -// -// -//-(id)sqlLoadWithStatement:(sqlite3_stmt *) statement queryPosition:(int)position -//{ -// // Needs to be overridden by subclasses, one for each type that we support -// return nil; -//} - - /* Could make SPEntity itself a supported member class, and perform diff this way: -(NSDictionary *)diff: (SPEntity *)otherEntity { // changes contains the operations for every key that is different - NSMutableDictionary *changes = [[NSMutableDictionary dictionary] autorelease]; + NSMutableDictionary *changes = [NSMutableDictionary dictionary]; if (![self isKindOfClass:[otherEntity class]]) { diff --git a/Simperium/SPMemberBase64.m b/Simperium/SPMemberBase64.m index 6c4eb3dc..ebebc89e 100644 --- a/Simperium/SPMemberBase64.m +++ b/Simperium/SPMemberBase64.m @@ -16,13 +16,30 @@ -(id)defaultValue { return nil; } --(id)fromJSON:(id)value { +-(NSString *)stringValueFromTransformable:(id)value { + if (value == nil) + return @""; + + // Convert from a Transformable class to a base64 string + NSData *data = (self.valueTransformerName ? + [[NSValueTransformer valueTransformerForName:self.valueTransformerName] transformedValue:value] : + [NSKeyedArchiver archivedDataWithRootObject:value]); + + NSString *base64 = [NSString sp_encodeBase64WithData:data]; + //NSLog(@"Simperium transformed base64 (%@) %@ to %@", keyName, value, base64); + return base64; +} + +-(id)getValueFromDictionary:(NSDictionary *)dict key:(NSString *)key object:(id)object { + id value = [dict objectForKey: key]; if (![value isKindOfClass:[NSString class]]) return value; // Convert from NSString (base64) to NSData NSData *data = [NSData decodeBase64WithString:value]; - id obj = [NSKeyedUnarchiver unarchiveObjectWithData: data]; + id obj = (self.valueTransformerName ? + [[NSValueTransformer valueTransformerForName:self.valueTransformerName] reverseTransformedValue:data] : + [NSKeyedUnarchiver unarchiveObjectWithData:data]); //NSLog(@"Simperium transforming base64 (%@) %@ from %@", keyName, obj, value); @@ -35,25 +52,8 @@ -(id)fromJSON:(id)value { return obj; } --(id)toJSON:(id)value { - if (value == nil) - return @""; - - // Convert from a Transformable class to a base64 string - NSData *data = [NSKeyedArchiver archivedDataWithRootObject:value]; - NSString *base64 = [NSString sp_encodeBase64WithData:data]; - //NSLog(@"Simperium transformed base64 (%@) %@ to %@", keyName, value, base64); - return base64; -} - --(id)getValueFromDictionary:(NSDictionary *)dict key:(NSString *)key object:(id)object { - id value = [dict objectForKey: key]; - value = [self fromJSON: value]; - return value; -} - -(void)setValue:(id)value forKey:(NSString *)key inDictionary:(NSMutableDictionary *)dict { - id convertedValue = [self toJSON: value]; + id convertedValue = [self stringValueFromTransformable: value]; [dict setValue:convertedValue forKey:key]; } @@ -65,15 +65,15 @@ -(NSDictionary *)diff:(id)thisValue otherValue:(id)otherValue { // Some binary data, like UIImages, won't detect equality with isEqual: // Therefore, compare base64 instead; this can be very slow // TODO: think of better ways to handle this - NSString *thisStr = [self toJSON:thisValue]; - NSString *otherStr = [self toJSON:otherValue]; + NSString *thisStr = [self stringValueFromTransformable:thisValue]; + NSString *otherStr = [self stringValueFromTransformable:otherValue]; if ([thisStr compare:otherStr] == NSOrderedSame) return [NSDictionary dictionary]; // Construct the diff in the expected format return [NSDictionary dictionaryWithObjectsAndKeys: OP_REPLACE, OP_OP, - [self toJSON: otherValue], OP_VALUE, nil]; + [self stringValueFromTransformable: otherValue], OP_VALUE, nil]; } -(id)applyDiff:(id)thisValue otherValue:(id)otherValue { diff --git a/Simperium/SPMemberBinary.h b/Simperium/SPMemberBinary.h index 0cc7bf01..ee1a4e6b 100644 --- a/Simperium/SPMemberBinary.h +++ b/Simperium/SPMemberBinary.h @@ -14,6 +14,6 @@ SPBinaryManager *binaryManager; } -@property (nonatomic, retain) SPBinaryManager *binaryManager; +@property (nonatomic, strong) SPBinaryManager *binaryManager; @end diff --git a/Simperium/SPMemberBinary.m b/Simperium/SPMemberBinary.m index 8bd8b9cf..e0f39024 100644 --- a/Simperium/SPMemberBinary.m +++ b/Simperium/SPMemberBinary.m @@ -13,10 +13,6 @@ @implementation SPMemberBinary @synthesize binaryManager; --(void)dealloc { - [super dealloc]; - self.binaryManager = nil; -} -(id)defaultValue { return @""; @@ -43,8 +39,6 @@ -(id)getValueFromDictionary:(NSDictionary *)dict key:(NSString *)key object:(id< fromKey:objectKey bucketName:bucketName attributeName:self.keyName]; - [objectKey release]; - [bucketName release]; }); } return value; diff --git a/Simperium/SPMemberDate.m b/Simperium/SPMemberDate.m index f2b1bee6..c8347031 100644 --- a/Simperium/SPMemberDate.m +++ b/Simperium/SPMemberDate.m @@ -14,19 +14,7 @@ -(id)defaultValue { return [NSDate date]; } --(id)fromJSON:(id)value { - if (!value) - return nil; - - if ([value isKindOfClass:[NSDate class]]) - return value; - - // Convert from NSNumber to NSDate - //NSInteger gmtOffset = [[NSTimeZone localTimeZone] secondsFromGMT]; - return [NSDate dateWithTimeIntervalSince1970:[(NSString *)value doubleValue]];//-gmtOffset]; -} - --(id)toJSON:(id)value { +-(id)dateValueFromNumber:(id)value { if ([value isKindOfClass:[NSNumber class]]) return value; @@ -37,24 +25,22 @@ -(id)toJSON:(id)value { -(id)getValueFromDictionary:(NSDictionary *)dict key:(NSString *)key object:(id)object { id value = [dict objectForKey: key]; - value = [self fromJSON: value]; - return value; + if (!value) + return nil; + + if ([value isKindOfClass:[NSDate class]]) + return value; + + // Convert from NSNumber to NSDate + //NSInteger gmtOffset = [[NSTimeZone localTimeZone] secondsFromGMT]; + return [NSDate dateWithTimeIntervalSince1970:[(NSString *)value doubleValue]];//-gmtOffset]; } -(void)setValue:(id)value forKey:(NSString *)key inDictionary:(NSMutableDictionary *)dict { - id convertedValue = [self toJSON: value]; + id convertedValue = [self dateValueFromNumber: value]; [dict setValue:convertedValue forKey:key]; } - --(id)defaultValueAsStringForSQL { - return @"0"; -} - --(NSString *)typeAsStringForSQL { - return @"REAL"; -} - -(NSDictionary *)diff:(id)thisValue otherValue:(id)otherValue { NSAssert([thisValue isKindOfClass:[NSDate class]] && [otherValue isKindOfClass:[NSDate class]], @"Simperium error: couldn't diff dates because their classes weren't NSDate"); @@ -69,7 +55,7 @@ -(NSDictionary *)diff:(id)thisValue otherValue:(id)otherValue { // Construct the diff in the expected format return [NSDictionary dictionaryWithObjectsAndKeys: OP_REPLACE, OP_OP, - [self toJSON: otherValue], OP_VALUE, nil]; + [self dateValueFromNumber: otherValue], OP_VALUE, nil]; } -(id)applyDiff:(id)thisValue otherValue:(id)otherValue { @@ -83,18 +69,5 @@ -(id)applyDiff:(id)thisValue otherValue:(id)otherValue { return otherValue; } -//-(id)sqlLoadWithStatement:(sqlite3_stmt *)statement queryPosition:(int)position -//{ -// return [NSNumber numberWithDouble: sqlite3_column_double(statement, position)]; -// // return [NSDate dateWithTimeIntervalSince1970:sqlite3_column_double(statement, position)]; -//} -// -//-(void)sqlBind:(id)data withStatement:(sqlite3_stmt *)statement queryPosition:(int)position -//{ -// sqlite3_bind_double(statement, position, [data doubleValue]); -// // sqlite3_bind_double(statement, position, [data timeIntervalSince1970]); -//} - - @end diff --git a/Simperium/SPMemberDouble.m b/Simperium/SPMemberDouble.m index 823c28ec..54fdfd4a 100644 --- a/Simperium/SPMemberDouble.m +++ b/Simperium/SPMemberDouble.m @@ -8,21 +8,12 @@ #import "SPMemberDouble.h" - @implementation SPMemberDouble -(id)defaultValue { return [NSNumber numberWithDouble:0]; } --(NSString *)defaultValueAsStringForSQL { - return @"0"; -} - --(NSString *)typeAsStringForSQL { - return @"DOUBLE"; -} - -(NSDictionary *)diff:(id)thisValue otherValue:(id)otherValue { NSAssert([thisValue isKindOfClass:[NSNumber class]] && [otherValue isKindOfClass:[NSNumber class]], @"Simperium error: couldn't diff doubles because their classes weren't NSNumber"); @@ -49,15 +40,4 @@ -(id)applyDiff:(id)thisValue otherValue:(id)otherValue { return otherValue; } -//-(id)sqlLoadWithStatement:(sqlite3_stmt *)statement queryPosition:(int)position -//{ -// return [NSNumber numberWithDouble: sqlite3_column_double(statement, position)]; -//} -// -//-(void)sqlBind:(id)data withStatement:(sqlite3_stmt *)statement queryPosition:(int)position -//{ -// sqlite3_bind_double(statement, position, [data doubleValue]); -//} - - @end diff --git a/Simperium/SPMemberEntity.h b/Simperium/SPMemberEntity.h index b375e3b6..28180119 100644 --- a/Simperium/SPMemberEntity.h +++ b/Simperium/SPMemberEntity.h @@ -7,13 +7,11 @@ // #import "SPMember.h" -#import "SPStorageProvider.h" @interface SPMemberEntity : SPMember { NSString *entityName; } @property (nonatomic, copy) NSString *entityName; -@property (assign) idstorage; -@end \ No newline at end of file +@end diff --git a/Simperium/SPMemberEntity.m b/Simperium/SPMemberEntity.m index 5ffd4ec3..5bf72ba5 100644 --- a/Simperium/SPMemberEntity.m +++ b/Simperium/SPMemberEntity.m @@ -8,13 +8,11 @@ #import "SPMemberEntity.h" #import "SPManagedObject.h" -#import "SPStorage.h" #import "SPBucket.h" -#import "SPReferenceManager.h" +#import "SPRelationshipResolver.h" @implementation SPMemberEntity @synthesize entityName; -@synthesize storage; -(id)initFromDictionary:(NSDictionary *)dict { @@ -25,21 +23,12 @@ -(id)initFromDictionary:(NSDictionary *)dict return self; } --(void)dealloc { - [super dealloc]; - [entityName release]; -} -(id)defaultValue { return nil; } --(id)fromJSON:(id)value { - idthreadSafeStorage = [storage threadSafeStorage]; - return [threadSafeStorage objectForKey:value bucketName: entityName]; -} - --(id)toJSON:(id)value { +-(id)simperiumKeyForObject:(id)value { NSString *simperiumKey = [value simperiumKey]; return simperiumKey == nil ? @"" : simperiumKey; } @@ -55,7 +44,6 @@ -(SPManagedObject *)objectForKey:(NSString *)key context:(NSManagedObjectContext NSError *error; NSArray *items = [context executeFetchRequest:fetchRequest error:&error]; - [fetchRequest release]; if ([items count] == 0) return nil; @@ -76,36 +64,30 @@ -(id)getValueFromDictionary:(NSDictionary *)dict key:(NSString *)key object:(id< if (value == nil) { // The object isn't here YET...but it will be LATER - NSString *fromKey = [object.simperiumKey copy]; + // This is a convenient place to track references because it's guaranteed to be called from loadMemberData in + // SPManagedObject when it arrives off the wire. + NSString *fromKey = object.simperiumKey; dispatch_async(dispatch_get_main_queue(), ^{ // Let Simperium store the reference so it can be properly resolved when the object gets synced - [bucket.referenceManager addPendingReferenceToKey:simperiumKey fromKey:fromKey bucketName:bucket.name attributeName:self.keyName]; - [fromKey release]; + [bucket.relationshipResolver addPendingRelationshipToKey:simperiumKey fromKey:fromKey bucketName:bucket.name + attributeName:self.keyName storage:bucket.storage]; }); } return value; } -(void)setValue:(id)value forKey:(NSString *)key inDictionary:(NSMutableDictionary *)dict { - id convertedValue = [self toJSON: value]; + id convertedValue = [self simperiumKeyForObject: value]; [dict setValue:convertedValue forKey:key]; } --(NSString *)defaultValueAsStringForSQL { - return @"''"; -} - --(NSString *)typeAsStringForSQL { - return @"TEXT"; -} - -(NSDictionary *)diff:(id)thisValue otherValue:(id)otherValue { - NSString *otherKey = [self toJSON:otherValue]; + NSString *otherKey = [self simperiumKeyForObject:otherValue]; NSAssert([thisValue isKindOfClass:[SPManagedObject class]] && [otherValue isKindOfClass:[SPManagedObject class]], @"Simperium error: couldn't diff objects because their classes weren't SPManagedObject"); - NSString *thisKey = [self toJSON:thisValue]; + NSString *thisKey = [self simperiumKeyForObject:thisValue]; // No change if the entity keys are equal if ([thisKey isEqualToString:otherKey]) diff --git a/Simperium/SPMemberFloat.m b/Simperium/SPMemberFloat.m index 162c8f72..bcd38755 100644 --- a/Simperium/SPMemberFloat.m +++ b/Simperium/SPMemberFloat.m @@ -14,14 +14,6 @@ -(id)defaultValue { return [NSNumber numberWithFloat:0]; } --(NSString *)defaultValueAsStringForSQL { - return @"0"; -} - --(NSString *)typeAsStringForSQL { - return @"FLOAT"; -} - -(NSDictionary *)diff:(id)thisValue otherValue:(id)otherValue { NSAssert([thisValue isKindOfClass:[NSNumber class]] && [otherValue isKindOfClass:[NSNumber class]], @"Simperium error: couldn't diff floats because their classes weren't NSNumber"); @@ -48,15 +40,4 @@ -(id)applyDiff:(id)thisValue otherValue:(id)otherValue { return otherValue; } -//-(id)sqlLoadWithStatement:(sqlite3_stmt *)statement queryPosition:(int)position -//{ -// return [NSNumber numberWithDouble: sqlite3_column_double(statement, position)]; -//} -// -//-(void)sqlBind:(id)data withStatement:(sqlite3_stmt *)statement queryPosition:(int)position -//{ -// sqlite3_bind_double(statement, position, [data doubleValue]); -//} - - @end diff --git a/Simperium/SPMemberInt.m b/Simperium/SPMemberInt.m index 0fdd1251..7250b5d6 100644 --- a/Simperium/SPMemberInt.m +++ b/Simperium/SPMemberInt.m @@ -14,14 +14,6 @@ -(id)defaultValue { return [NSNumber numberWithInt:0]; } --(NSString *)defaultValueAsStringForSQL { - return @"0"; -} - --(NSString *)typeAsStringForSQL { - return @"INTEGER"; -} - -(NSDictionary *)diff:(id)thisValue otherValue:(id)otherValue { NSAssert([thisValue isKindOfClass:[NSNumber class]] && [otherValue isKindOfClass:[NSNumber class]], @"Simperium error: couldn't diff ints because their classes weren't NSNumber"); @@ -44,15 +36,4 @@ -(id)applyDiff:(id)thisValue otherValue:(id)otherValue { return otherValue; } -//-(id)sqlLoadWithStatement:(sqlite3_stmt *)statement queryPosition:(int)position -//{ -// return [NSNumber numberWithInt: sqlite3_column_int(statement, position)]; -//} -// -//-(void)sqlBind:(id)data withStatement:(sqlite3_stmt *)statement queryPosition:(int)position -//{ -// sqlite3_bind_int(statement, position, [data intValue]); -//} - - @end diff --git a/Simperium/SPMemberJSONList.h b/Simperium/SPMemberJSONList.h new file mode 100644 index 00000000..376bf323 --- /dev/null +++ b/Simperium/SPMemberJSONList.h @@ -0,0 +1,13 @@ +// +// SPMemberJSONList.h +// Simperium +// +// Created by Michael Johnston on 11-11-24. +// Copyright (c) 2011 Simperium. All rights reserved. +// + +#import "SPMember.h" + +@interface SPMemberJSONList : SPMember + +@end diff --git a/Simperium/SPMemberJSONList.m b/Simperium/SPMemberJSONList.m new file mode 100644 index 00000000..dee01bf7 --- /dev/null +++ b/Simperium/SPMemberJSONList.m @@ -0,0 +1,55 @@ +// +// SPMemberJSONList.m +// Simperium +// +// Created by Michael Johnston on 11-11-24. +// Copyright (c) 2011 Simperium. All rights reserved. +// + +#import "SPMemberJSONList.h" +#import "JSONKit.h" + +@implementation SPMemberJSONList + +-(id)defaultValue { + return @"[]"; +} + +-(id)stringValueFromArray:(id)value { + if ([value length] == 0) + return [[self defaultValue] objectFromJSONString]; + return [value objectFromJSONString]; +} + +-(id)getValueFromDictionary:(NSDictionary *)dict key:(NSString *)key object:(id)object { + id value = [dict objectForKey: key]; + return [value JSONString]; +} + +-(void)setValue:(id)value forKey:(NSString *)key inDictionary:(NSMutableDictionary *)dict { + id convertedValue = [self stringValueFromArray: value]; + [dict setValue:convertedValue forKey:key]; +} + +-(NSDictionary *)diff:(id)thisValue otherValue:(id)otherValue { + NSAssert([thisValue isKindOfClass:[NSString class]] && [otherValue isKindOfClass:[NSString class]], + @"Simperium error: couldn't diff JSON lists because their classes weren't NSString"); + + // TODO: proper list diff; for now just replace + + if ([thisValue isEqualToString: otherValue]) + return [NSDictionary dictionary]; + + // Construct the diff in the expected format + return [NSDictionary dictionaryWithObjectsAndKeys: + OP_REPLACE, OP_OP, + [self stringValueFromArray: otherValue], OP_VALUE, nil]; +} + +-(id)applyDiff:(id)thisValue otherValue:(id)otherValue { + // TODO: proper list diff, including transform + + return otherValue; +} + +@end diff --git a/Simperium/SPMemberList.m b/Simperium/SPMemberList.m index 649e37a0..f8909277 100644 --- a/Simperium/SPMemberList.m +++ b/Simperium/SPMemberList.m @@ -8,80 +8,85 @@ #import "SPMemberList.h" #import "JSONKit.h" +#import "DiffMatchPatch.h" +#import "NSArray+Simperium.h" -@implementation SPMemberList +@interface SPMemberList () +@property (nonatomic, strong, readonly) DiffMatchPatch *diffMatchPatch; +@end --(id)defaultValue { - return @"[]"; -} +@implementation SPMemberList +@synthesize diffMatchPatch = _diffMatchPatch; --(id)getValueFromDictionary:(NSDictionary *)dict key:(NSString *)key object:(id)object { - id value = [dict objectForKey: key]; - value = [self fromJSON: value]; - return value; +- (DiffMatchPatch *)diffMatchPatch +{ + if (!_diffMatchPatch) { + _diffMatchPatch = [[DiffMatchPatch alloc] init]; + } + return _diffMatchPatch; } --(void)setValue:(id)value forKey:(NSString *)key inDictionary:(NSMutableDictionary *)dict { - id convertedValue = [self toJSON: value]; - [dict setValue:convertedValue forKey:key]; +-(id)defaultValue { + return @"[]"; } --(id)toJSON:(id)value { +-(id)arrayFromJSONString:(id)value { if ([value length] == 0) return [[self defaultValue] objectFromJSONString]; return [value objectFromJSONString]; } --(id)fromJSON:(id)value { +-(id)getValueFromDictionary:(NSDictionary *)dict key:(NSString *)key object:(id)object { + return [self getValueFromJSON:dict key:key object:object]; +} + +- (id)getValueFromJSON:(NSDictionary *)json key:(NSString *)key object:(id)object +{ + id value = [json objectForKey:key]; return [value JSONString]; } -//-(id)defaultValueAsStringForSQL { -// return @"0"; -//} -// -//-(NSString *)typeAsStringForSQL { -// return @"REAL"; -//} +-(void)setValue:(id)value forKey:(NSString *)key inDictionary:(NSMutableDictionary *)dict { + id convertedValue = [self arrayFromJSONString: value]; + [dict setValue:convertedValue forKey:key]; +} --(NSDictionary *)diff:(id)thisValue otherValue:(id)otherValue { - NSAssert([thisValue isKindOfClass:[NSString class]] && [otherValue isKindOfClass:[NSString class]], - @"Simperium error: couldn't diff dates because their classes weren't NSString"); - - // TODO: proper list diff; for now just replace +-(NSDictionary *)diff:(NSArray *)a otherValue:(NSArray *)b { + NSAssert([a isKindOfClass:[NSArray class]] && [b isKindOfClass:[NSArray class]], + @"Simperium error: couldn't diff list because their classes weren't NSArray"); - if ([thisValue isEqualToString: otherValue]) + if ([a isEqualToArray:b]) return [NSDictionary dictionary]; - - // Construct the diff in the expected format - return [NSDictionary dictionaryWithObjectsAndKeys: - OP_REPLACE, OP_OP, - [self toJSON: otherValue], OP_VALUE, nil]; + + // For the moment we can only create OP_LIST_DMP + return @{ OP_OP: OP_LIST_DMP, OP_VALUE: [a sp_diffDeltaWithArray:b diffMatchPatch:self.diffMatchPatch] }; } + + -(id)applyDiff:(id)thisValue otherValue:(id)otherValue { - // TODO: proper list diff, including transform - // Expect dates in Number format - //NSAssert([thisValue isKindOfClass:[NSNumber class]] && [otherValue isKindOfClass:[NSNumber class]], - // @"Simperium error: couldn't diff dates because their classes weren't NSNumber (NSDate not supported directly)"); - // Date changes replaces the previous value by default (like ints) - // TODO: Not sure if this should be a copy or not - return otherValue; + // Assuming OP_LIST_DMP. This code will have to change when OP_LIST is + // implemented and it will have to take the full change diff in order + // to apply the right diffing method. + NSString *delta = otherValue; + NSArray *source = thisValue; + + return [source sp_arrayByApplyingDiffDelta:delta diffMatchPatch:self.diffMatchPatch]; } -//-(id)sqlLoadWithStatement:(sqlite3_stmt *)statement queryPosition:(int)position -//{ -// return [NSNumber numberWithDouble: sqlite3_column_double(statement, position)]; -// // return [NSDate dateWithTimeIntervalSince1970:sqlite3_column_double(statement, position)]; -//} -// -//-(void)sqlBind:(id)data withStatement:(sqlite3_stmt *)statement queryPosition:(int)position -//{ -// sqlite3_bind_double(statement, position, [data doubleValue]); -// // sqlite3_bind_double(statement, position, [data timeIntervalSince1970]); -//} +- (NSDictionary *)transform:(id)thisValue otherValue:(id)otherValue oldValue:(id)oldValue +{ + NSArray *source = oldValue; + NSString *delta1 = thisValue; + NSString *delta2 = otherValue; + + return @{ OP_OP: OP_LIST_DMP, OP_VALUE: [source sp_transformDelta:delta1 onto:delta2 diffMatchPatch:self.diffMatchPatch] }; +} @end + + + diff --git a/Simperium/SPMemberText.m b/Simperium/SPMemberText.m index 3e1e1406..6e457015 100644 --- a/Simperium/SPMemberText.m +++ b/Simperium/SPMemberText.m @@ -13,28 +13,16 @@ @implementation SPMemberText -(id)initFromDictionary:(NSDictionary *)dict { if (self = [super initFromDictionary:dict]) { - dmp = [[[DiffMatchPatch alloc] init] retain]; + dmp = [[DiffMatchPatch alloc] init]; } return self; } --(void)dealloc { - [dmp release]; - [super dealloc]; -} -(id)defaultValue { return @""; } --(id)defaultValueAsStringForSQL { - return @"''"; -} - --(NSString *)typeAsStringForSQL { - return @"TEXT"; -} - -(NSDictionary *)diff:(id)thisValue otherValue:(id)otherValue { NSAssert([thisValue isKindOfClass:[NSString class]] && [otherValue isKindOfClass:[NSString class]], @"Simperium error: couldn't diff strings because their classes weren't NSString"); @@ -102,16 +90,4 @@ -(NSDictionary *)transform:(id)thisValue otherValue:(id)otherValue oldValue:(id) return [NSDictionary dictionary]; } -//-(id)sqlLoadWithStatement:(sqlite3_stmt *)statement queryPosition:(int)position -//{ -// char *str = (char *)sqlite3_column_text(statement, position); -// return str == NULL ? @"" : [NSString stringWithUTF8String:str]; -//} -// -//-(void)sqlBind:(id)data withStatement:(sqlite3_stmt *)statement queryPosition:(int)position -//{ -// sqlite3_bind_text(statement, position, [data UTF8String], -1, SQLITE_TRANSIENT); -//} - - @end diff --git a/Simperium/SPNetworkProvider.h b/Simperium/SPNetworkInterface.h similarity index 67% rename from Simperium/SPNetworkProvider.h rename to Simperium/SPNetworkInterface.h index 0b605714..720b66a6 100644 --- a/Simperium/SPNetworkProvider.h +++ b/Simperium/SPNetworkInterface.h @@ -11,14 +11,16 @@ @class SPBucket; -@protocol SPNetworkProvider +@protocol SPNetworkInterface -(void)start:(SPBucket *)bucket name:(NSString *)name; -(void)stop:(SPBucket *)bucket; -(void)resetBucketAndWait:(SPBucket *)bucket; --(void)getLatestVersionsForBucket:(SPBucket *)bucket; --(void)getVersions:(int)numVersions forObject:(id)object; +-(void)requestLatestVersionsForBucket:(SPBucket *)bucket; +-(void)requestVersions:(int)numVersions object:(id)object; -(void)sendObjectDeletion:(id)object; -(void)sendObjectChanges:(id)object; -(void)shareObject:(id)object withEmail:(NSString *)email; +-(void)forceSyncBucket:(SPBucket *)bucket; @end +extern NSString * const SPAuthenticationDidFail; diff --git a/Simperium/SPNetworkInterface.m b/Simperium/SPNetworkInterface.m new file mode 100644 index 00000000..de0191c7 --- /dev/null +++ b/Simperium/SPNetworkInterface.m @@ -0,0 +1 @@ +NSString * const SPAuthenticationDidFail = @"SPAuthenticationDidFail"; diff --git a/Simperium/SPObject.h b/Simperium/SPObject.h index 29846ddb..1e53188f 100644 --- a/Simperium/SPObject.h +++ b/Simperium/SPObject.h @@ -14,8 +14,8 @@ NSString *simperiumKey; } -@property (nonatomic, retain) NSMutableDictionary *dict; -@property (nonatomic, retain) SPGhost *ghost; +@property (nonatomic, strong) NSMutableDictionary *dict; +@property (nonatomic, strong) SPGhost *ghost; @property (nonatomic, copy) NSString *ghostData; @property (nonatomic, copy) NSString *simperiumKey; @property (nonatomic, copy) NSString *version; diff --git a/Simperium/SPObject.m b/Simperium/SPObject.m index 8695ece2..c62eff9c 100644 --- a/Simperium/SPObject.m +++ b/Simperium/SPObject.m @@ -29,26 +29,15 @@ -(id)initWithDictionary:(NSMutableDictionary *)dictionary { [self.dict associateObject:self]; SPGhost *newGhost = [[SPGhost alloc] init]; self.ghost = newGhost; - [newGhost release]; } return self; } --(void)dealloc { - self.dict = nil; - self.ghost = nil; - self.ghostData = nil; - self.simperiumKey = nil; - self.version = nil; - [super dealloc]; -} - -(NSString *)simperiumKey { return simperiumKey; } -(void)setSimperiumKey:(NSString *)key { - [simperiumKey release]; simperiumKey = [key copy]; [self.dict associateSimperiumKey:simperiumKey]; @@ -69,9 +58,9 @@ -(void)simperiumSetValue:(id)value forKey:(NSString *)key { -(id)simperiumValueForKey:(NSString *)key { __block id obj; dispatch_sync(dispatch_get_main_queue(), ^{ - obj = [[dict objectForKey: key] retain]; + obj = [dict objectForKey: key]; }); - return [obj autorelease]; + return obj; } -(void)loadMemberData:(NSDictionary *)data { diff --git a/Simperium/SPProcessorNotificationNames.h b/Simperium/SPProcessorNotificationNames.h new file mode 100644 index 00000000..7e072fca --- /dev/null +++ b/Simperium/SPProcessorNotificationNames.h @@ -0,0 +1,16 @@ +// +// SPProcessorNotificationsNames.h +// Simperium-OSX +// +// Created by Michael Johnston on 9/9/13. +// Copyright (c) 2013 Simperium. All rights reserved. +// + +extern NSString * const ProcessorDidAddObjectsNotification; +extern NSString * const ProcessorDidChangeObjectNotification; +extern NSString * const ProcessorDidDeleteObjectKeysNotification; +extern NSString * const ProcessorDidAcknowledgeObjectsNotification; +extern NSString * const ProcessorWillChangeObjectsNotification; +extern NSString * const ProcessorDidAcknowledgeDeleteNotification; +extern NSString * const ProcessorRequestsReindexing; + diff --git a/Simperium/SPProcessorNotificationNames.m b/Simperium/SPProcessorNotificationNames.m new file mode 100644 index 00000000..b4f505c5 --- /dev/null +++ b/Simperium/SPProcessorNotificationNames.m @@ -0,0 +1,15 @@ +// +// ProcessorNotificationsNames.h +// Simperium-OSX +// +// Created by Michael Johnston on 9/9/13. +// Copyright (c) 2013 Simperium. All rights reserved. +// + +NSString * const ProcessorDidAddObjectsNotification = @"ProcessorDidAddObjectsNotification"; +NSString * const ProcessorDidChangeObjectNotification = @"ProcessorDidChangeObjectNotification"; +NSString * const ProcessorDidDeleteObjectKeysNotification = @"ProcessorDidDeleteObjectKeysNotification"; +NSString * const ProcessorDidAcknowledgeObjectsNotification = @"ProcessorDidAcknowledgeObjectsNotification"; +NSString * const ProcessorWillChangeObjectsNotification = @"ProcessorWillChangeObjectsNotification"; +NSString * const ProcessorDidAcknowledgeDeleteNotification = @"ProcessorDidAcknowledgeDeleteNotification"; +NSString * const ProcessorRequestsReindexing = @"ProcessorRequestsReindexing"; diff --git a/Simperium/SPReferenceManager.h b/Simperium/SPReferenceManager.h deleted file mode 100644 index 3226fda4..00000000 --- a/Simperium/SPReferenceManager.h +++ /dev/null @@ -1,23 +0,0 @@ -// -// SPReferenceManager.h -// Simperium -// -// Created by Michael Johnston on 2012-08-22. -// Copyright (c) 2012 Simperium. All rights reserved. -// - -#import - -#import "SPStorageProvider.h" - -@interface SPReferenceManager : NSObject { - NSMutableDictionary *pendingReferences; -} - -@property (nonatomic, retain) NSMutableDictionary *pendingReferences; - --(void)addPendingReferenceToKey:(NSString *)key fromKey:(NSString *)fromKey bucketName:(NSString *)bucketName attributeName:(NSString *)attributeName; --(void)resolvePendingReferencesToKey:(NSString *)toKey bucketName:(NSString *)bucketName storage:(id)storage; --(void)reset; - -@end diff --git a/Simperium/SPReferenceManager.m b/Simperium/SPReferenceManager.m deleted file mode 100644 index f9c0ed45..00000000 --- a/Simperium/SPReferenceManager.m +++ /dev/null @@ -1,151 +0,0 @@ -// -// SPReferenceManager.m -// Simperium -// -// Created by Michael Johnston on 2012-08-22. -// Copyright (c) 2012 Simperium. All rights reserved. -// - -#import "SPReferenceManager.h" -#import "SPDiffable.h" -#import "SPStorage.h" -#import "JSONKit.h" -#import "SPGhost.h" -#import "DDLog.h" - -#define PATH_KEY @"SPPathKey" -#define PATH_BUCKET @"SPPathBucket" -#define PATH_ATTRIBUTE @"SPPathAttribute" - -static int ddLogLevel = LOG_LEVEL_INFO; - -@interface SPReferenceManager() --(void)loadPendingReferences; --(void)savePendingReferences; -@end - - -@implementation SPReferenceManager -@synthesize pendingReferences; - -+ (int)ddLogLevel { - return ddLogLevel; -} - -+ (void)ddSetLogLevel:(int)logLevel { - ddLogLevel = logLevel; -} - --(id)init -{ - if ((self = [super init])) { - self.pendingReferences = [NSMutableDictionary dictionaryWithCapacity:10]; - [self loadPendingReferences]; - } - - return self; -} - --(void)dealloc { - self.pendingReferences = nil; - [super dealloc]; -} - --(void)savePendingReferences -{ - if ([pendingReferences count] == 0) { - // If there's already nothing there, save some CPU by not writing anything - NSString *pendingKey = [NSString stringWithFormat:@"SPPendingReferences"]; - NSString *pendingJSON = [[NSUserDefaults standardUserDefaults] objectForKey:pendingKey]; - if (!pendingJSON) - return; - } - - NSString *json = [pendingReferences JSONString]; - NSString *key = [NSString stringWithFormat:@"SPPendingReferences"]; - [[NSUserDefaults standardUserDefaults] setObject:json forKey: key]; - [[NSUserDefaults standardUserDefaults] synchronize]; -} - --(void)loadPendingReferences -{ - // Load changes that didn't get a chance to send - NSString *pendingKey = [NSString stringWithFormat:@"SPPendingReferences"]; - NSString *pendingJSON = [[NSUserDefaults standardUserDefaults] objectForKey:pendingKey]; - NSDictionary *pendingDict = [pendingJSON objectFromJSONString]; - for (NSString *key in [pendingDict allKeys]) { - // Manually create mutable children - NSArray *loadPaths = [pendingDict objectForKey:key]; - NSMutableArray *paths = [NSMutableArray arrayWithArray:loadPaths]; - [pendingReferences setValue:paths forKey:key]; - } -} - - --(BOOL)hasPendingReferenceToKey:(NSString *)key { - return [pendingReferences objectForKey:key] != nil; -} - --(void)addPendingReferenceToKey:(NSString *)key fromKey:(NSString *)fromKey bucketName:(NSString *)bucketName attributeName:(NSString *)attributeName -{ - if (key.length == 0) { - DDLogWarn(@"Simperium warning: received empty pending reference to attribute %@", attributeName); - return; - } - - NSMutableDictionary *path = [NSMutableDictionary dictionaryWithObjectsAndKeys: - fromKey, PATH_KEY, - bucketName, PATH_BUCKET, - attributeName, PATH_ATTRIBUTE, nil]; - DDLogVerbose(@"Simperium adding pending reference from %@ (%@) to %@ (%@)", fromKey, attributeName, key, bucketName); - - // Check to see if any references are already being tracked for this entity - NSMutableArray *paths = [pendingReferences objectForKey: key]; - if (paths == nil) { - paths = [NSMutableArray arrayWithCapacity:3]; - [pendingReferences setObject: paths forKey: key]; - } - [paths addObject:path]; - [self savePendingReferences]; -} - --(void)resolvePendingReferencesToKey:(NSString *)toKey bucketName:(NSString *)bucketName storage:(id)storage; -{ - // The passed entity is now synced, so check for any pending references to it that can now be resolved - NSMutableArray *paths = [pendingReferences objectForKey: toKey]; - if (paths != nil) { - idtoObject = [storage objectForKey:toKey bucketName:bucketName]; - - if (!toObject) { - DDLogError(@"Simperium error, tried to resolve reference to an object that doesn't exist yet (%@): %@", bucketName, toKey); - return; - } - - for (NSDictionary *path in paths) { - // There'd be no way to get the entityName here since there's no way to look at an instance's members - // Get it from the "path" instead - NSString *fromKey = [path objectForKey:PATH_KEY]; - NSString *fromBucketName = [path objectForKey:PATH_BUCKET]; - NSString *attributeName = [path objectForKey:PATH_ATTRIBUTE]; - id fromObject = [storage objectForKey:fromKey bucketName:fromBucketName]; - DDLogVerbose(@"Simperium resolving pending reference for %@.%@=%@", fromKey, attributeName, toKey); - [fromObject simperiumSetValue:toObject forKey: attributeName]; - - // Get the key reference into the ghost as well - [fromObject.ghost.memberData setObject:toKey forKey: attributeName]; - fromObject.ghost.needsSave = YES; - } - - // All references to entity were resolved above, so remove it from the pending array - [pendingReferences removeObjectForKey:toKey]; - } - [storage save]; - [self savePendingReferences]; -} - --(void)reset { - [self.pendingReferences removeAllObjects]; - [self savePendingReferences]; -} - -@end diff --git a/Simperium/SPRelationshipResolver.h b/Simperium/SPRelationshipResolver.h new file mode 100644 index 00000000..e82fc63e --- /dev/null +++ b/Simperium/SPRelationshipResolver.h @@ -0,0 +1,22 @@ +// +// SPRelationshipResolver.h +// Simperium +// +// Created by Michael Johnston on 2012-08-22. +// Copyright (c) 2012 Simperium. All rights reserved. +// + +#import +#import "SPStorageProvider.h" + +@interface SPRelationshipResolver : NSObject { + NSMutableDictionary *pendingRelationships; +} + +- (void)loadPendingRelationships:(id)storage; +- (void)addPendingRelationshipToKey:(NSString *)key fromKey:(NSString *)fromKey bucketName:(NSString *)bucketName + attributeName:(NSString *)attributeName storage:(id)storage; +- (void)resolvePendingRelationshipsToKey:(NSString *)toKey bucketName:(NSString *)bucketName storage:(id)storage; +- (void)reset:(id)storage; + +@end diff --git a/Simperium/SPRelationshipResolver.m b/Simperium/SPRelationshipResolver.m new file mode 100644 index 00000000..4fddd865 --- /dev/null +++ b/Simperium/SPRelationshipResolver.m @@ -0,0 +1,184 @@ +// +// SPRelationshipResolver.m +// Simperium +// +// Created by Michael Johnston on 2012-08-22. +// Copyright (c) 2012 Simperium. All rights reserved. +// + +#import "SPRelationshipResolver.H" +#import "SPDiffable.h" +#import "SPStorage.h" +#import "SPStorageProvider.h" +#import "JSONKit.h" +#import "SPGhost.h" +#import "DDLog.h" + +#define PATH_KEY @"SPPathKey" +#define PATH_BUCKET @"SPPathBucket" +#define PATH_ATTRIBUTE @"SPPathAttribute" + +static int ddLogLevel = LOG_LEVEL_INFO; + +@interface SPRelationshipResolver() { + dispatch_queue_t queue; +} + +@property (nonatomic, strong) NSMutableDictionary *pendingRelationships; +@property (nonatomic, strong) dispatch_queue_t queue; + +@end + + +@implementation SPRelationshipResolver +@synthesize pendingRelationships; +@synthesize queue; + ++ (int)ddLogLevel { + return ddLogLevel; +} + ++ (void)ddSetLogLevel:(int)logLevel { + ddLogLevel = logLevel; +} + +- (id)init { + if ((self = [super init])) { + self.pendingRelationships = [NSMutableDictionary dictionaryWithCapacity:10]; + NSString *queueLabel = [@"com.simperium." stringByAppendingString:[[self class] description]]; + queue = dispatch_queue_create([queueLabel cStringUsingEncoding:NSUTF8StringEncoding], NULL); + } + + return self; +} + + +- (void)writePendingReferences:(id)storage { + if ([pendingRelationships count] == 0) { + // If there's already nothing there, save some CPU by not writing anything + NSDictionary *metadata = [storage metadata]; + NSString *pendingKey = [NSString stringWithFormat:@"SPPendingReferences"]; + NSDictionary *pendingDict = [metadata objectForKey:pendingKey]; + if (!pendingDict) + return; + } + + NSMutableDictionary *metadata = [[storage metadata] mutableCopy]; + NSString *key = [NSString stringWithFormat:@"SPPendingReferences"]; + [metadata setObject:pendingRelationships forKey: key]; + [storage setMetadata:metadata]; +} + +- (void)loadPendingRelationships:(id)storage { + // Load changes that didn't get a chance to send + NSString *pendingKey = [NSString stringWithFormat:@"SPPendingReferences"]; + NSDictionary *pendingDict = [[storage metadata] objectForKey:pendingKey]; + for (NSString *key in [pendingDict allKeys]) { + // Manually create mutable children + NSArray *loadPaths = [pendingDict objectForKey:key]; + NSMutableArray *paths = [NSMutableArray arrayWithArray:loadPaths]; + [pendingRelationships setValue:paths forKey:key]; + } +} + + +- (BOOL)hasPendingReferenceToKey:(NSString *)key { + return [pendingRelationships objectForKey:key] != nil; +} + +- (void)addPendingRelationshipToKey:(NSString *)key fromKey:(NSString *)fromKey bucketName:(NSString *)bucketName + attributeName:(NSString *)attributeName storage:(id)storage { + if (key.length == 0) { + DDLogWarn(@"Simperium warning: received empty pending reference to attribute %@", attributeName); + return; + } + + NSMutableDictionary *path = [NSMutableDictionary dictionaryWithObjectsAndKeys: + fromKey, PATH_KEY, + bucketName, PATH_BUCKET, + attributeName, PATH_ATTRIBUTE, nil]; + DDLogVerbose(@"Simperium adding pending reference from %@ (%@) to %@ (%@)", fromKey, attributeName, key, bucketName); + + // Check to see if any references are already being tracked for this entity + NSMutableArray *paths = [pendingRelationships objectForKey: key]; + if (paths == nil) { + paths = [NSMutableArray arrayWithCapacity:3]; + [pendingRelationships setObject: paths forKey: key]; + } + [paths addObject:path]; + [self writePendingReferences:storage]; +} + +- (void)resolvePendingRelationshipsToKey:(NSString *)toKey bucketName:(NSString *)bucketName storage:(id)storage { + // The passed entity is now synced, so check for any pending references to it that can now be resolved + NSMutableArray *paths = [pendingRelationships objectForKey: toKey]; + if (paths != nil) { + + // The following code could batch fault all the objects that will be touched, but is probably overkill +/* + // Construct lists of keys from the paths for batch faulting + NSMutableDictionary *batchDict = [NSMutableDictionary dictionaryWithCapacity:3]; + for (NSDictionary *path in paths) { + NSString *fromKey = [path objectForKey:PATH_KEY]; + NSString *fromBucketName = [path objectForKey:PATH_BUCKET]; + + NSMutableArray *keyList = [batchDict objectForKey:fromBucketName]; + if (keyList == nil) { + keyList = [NSMutableArray arrayWithCapacity:3]; + [batchDict setObject:keyList forKey: fromBucketName]; + } + [keyList addObject:fromKey]; + } + + // Do the faulting for each bucket + NSMutableDictionary *faultedObjects = [NSMutableDictionary dictionaryWithCapacity:[paths count]]; + for (NSString *key in [batchDict allKeys]) { + NSDictionary *fromObjects = [threadSafeStorage faultObjectsForKeys:[batchDict objectForKey:key] bucketName:key]; + [faultedObjects addEntriesFromDictionary:fromObjects]; + } +*/ + // Resolve the references but do it in the background + dispatch_async(queue, ^{ + id threadSafeStorage = [storage threadSafeStorage]; + idtoObject = [threadSafeStorage objectForKey:toKey bucketName:bucketName]; + + if (!toObject) { + DDLogError(@"Simperium error, tried to resolve reference to an object that doesn't exist yet (%@): %@", bucketName, toKey); + return; + } + + for (NSDictionary *path in paths) { + // There'd be no way to get the entityName here since there's no way to look at an instance's members + // Get it from the "path" instead + NSString *fromKey = [path objectForKey:PATH_KEY]; + NSString *fromBucketName = [path objectForKey:PATH_BUCKET]; + NSString *attributeName = [path objectForKey:PATH_ATTRIBUTE]; + id fromObject = [threadSafeStorage objectForKey:fromKey bucketName:fromBucketName]; + DDLogVerbose(@"Simperium resolving pending reference for %@.%@=%@", fromKey, attributeName, toKey); + [fromObject simperiumSetValue:toObject forKey: attributeName]; + + // Get the key reference into the ghost as well + [fromObject.ghost.memberData setObject:toKey forKey: attributeName]; + fromObject.ghost.needsSave = YES; + } + [threadSafeStorage save]; + + dispatch_async(dispatch_get_main_queue(), ^{ + // All references to entity were resolved above, so remove it from the pending array + [pendingRelationships removeObjectForKey:toKey]; + [self writePendingReferences:storage]; + + // Expect the context to be saved elsewhere + //[storage save]; + }); + }); + } +} + +- (void)reset:(id)storage { + [self.pendingRelationships removeAllObjects]; + [self writePendingReferences:storage]; + [storage save]; +} + +@end diff --git a/Simperium/SPS3Manager.h b/Simperium/SPS3Manager.h index 0435bfeb..5e204b4f 100644 --- a/Simperium/SPS3Manager.h +++ b/Simperium/SPS3Manager.h @@ -35,11 +35,11 @@ } -@property(nonatomic, retain, readonly) NSMutableDictionary *downloadsInProgressData; -@property(nonatomic, retain, readonly) NSMutableDictionary *downloadsInProgressRequests; -@property(nonatomic, retain, readonly) NSMutableDictionary *uploadsInProgressRequests; -@property(nonatomic, retain, readonly) NSMutableDictionary *bgTasks; -@property(nonatomic, retain) NSString *bucketName; +@property(nonatomic, strong, readonly) NSMutableDictionary *downloadsInProgressData; +@property(nonatomic, strong, readonly) NSMutableDictionary *downloadsInProgressRequests; +@property(nonatomic, strong, readonly) NSMutableDictionary *uploadsInProgressRequests; +@property(nonatomic, strong, readonly) NSMutableDictionary *bgTasks; +@property(nonatomic, strong) NSString *bucketName; -(id)initWithSimperium:(Simperium *)aSimperium; -(int)sizeOfRemoteFile:(NSString *)filename; diff --git a/Simperium/SPS3Manager.m b/Simperium/SPS3Manager.m index 8484fa76..d0b00a02 100644 --- a/Simperium/SPS3Manager.m +++ b/Simperium/SPS3Manager.m @@ -2,7 +2,7 @@ // SPS3Manager.m // Simperium // -// Created by Michael Johnston on 11-05-31. +// Created by John Carter on 11-05-31. // Copyright 2011 Simperium. All rights reserved. // @@ -21,14 +21,14 @@ #import @interface SPS3Manager() -@property (nonatomic, retain) AmazonCredentials *awsCredentials; -@property (nonatomic, retain) AmazonS3Client *awsConnection; -@property (nonatomic, retain) NSDate *binaryAuthTokenExpiry; +@property (nonatomic, strong) AmazonCredentials *awsCredentials; +@property (nonatomic, strong) AmazonS3Client *awsConnection; +@property (nonatomic, strong) NSDate *binaryAuthTokenExpiry; @property (nonatomic, copy) NSString *binaryAuthID; @property (nonatomic, copy) NSString *binaryAuthSecret; @property (nonatomic, copy) NSString *binaryAuthSessionToken; @property (nonatomic, copy) NSString *remoteURL; -@property (nonatomic, retain) NSMutableDictionary *remoteFilesizeCache; +@property (nonatomic, strong) NSMutableDictionary *remoteFilesizeCache; -(BOOL)binaryTokenExpired; -(BOOL)checkOrGetBinaryAuthentication; @@ -55,33 +55,17 @@ -(id)initWithSimperium:(Simperium *)aSimperium { NSLog(@"Simperium initializing binary manager"); if ((self = [super initWithSimperium:aSimperium])) { - downloadsInProgressData = [[NSMutableDictionary dictionaryWithCapacity: 3] retain]; - downloadsInProgressRequests = [[NSMutableDictionary dictionaryWithCapacity: 3] retain]; - uploadsInProgressRequests = [[NSMutableDictionary dictionaryWithCapacity: 3] retain]; - remoteFilesizeCache = [[NSMutableDictionary dictionaryWithCapacity: 3] retain]; - bgTasks = [[NSMutableDictionary dictionaryWithCapacity: 3] retain]; + downloadsInProgressData = [NSMutableDictionary dictionaryWithCapacity: 3]; + downloadsInProgressRequests = [NSMutableDictionary dictionaryWithCapacity: 3]; + uploadsInProgressRequests = [NSMutableDictionary dictionaryWithCapacity: 3]; + remoteFilesizeCache = [NSMutableDictionary dictionaryWithCapacity: 3]; + bgTasks = [NSMutableDictionary dictionaryWithCapacity: 3]; backgroundQueue = dispatch_queue_create("com.simperium.simperium.backgroundQueue", NULL); } return self; } --(void)dealloc -{ - self.awsConnection = nil; - self.awsCredentials = nil; - self.remoteURL = nil; - self.binaryAuthTokenExpiry = nil; - self.binaryAuthID = nil; - self.binaryAuthSecret = nil; - self.binaryAuthSessionToken = nil; - [downloadsInProgressData release]; - [downloadsInProgressRequests release]; - [uploadsInProgressRequests release]; - [remoteFilesizeCache release]; - [super dealloc]; -} - -(BOOL)binaryTokenExpired { @@ -135,11 +119,9 @@ -(BOOL)checkOrGetBinaryAuthentication [dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSSS'Z'"]; self.binaryAuthTokenExpiry = [dateFormatter dateFromString:[NSString stringWithString:[userDict objectForKey:@"expiration"]]]; - [dateFormatter release]; AmazonCredentials *credentials = [[AmazonCredentials alloc] initWithAccessKey: self.binaryAuthID withSecretKey: self.binaryAuthSecret withSecurityToken:self.binaryAuthSessionToken]; self.awsCredentials = credentials; - [credentials release]; [self createLocalDirectoryForPrefix:self.keyPrefix]; @@ -173,7 +155,6 @@ -(BOOL)connectToAWS { //[AmazonLogger verboseLogging]; AmazonS3Client *connection = [[AmazonS3Client alloc] initWithCredentials: self.awsCredentials]; self.awsConnection = connection; - [connection release]; } return YES; @@ -226,10 +207,10 @@ -(int)sizeOfRemoteFile:(NSString *)filename if (cached) { return [cached intValue]; } - S3GetObjectRequest *headRequest = [[[S3GetObjectRequest alloc] initWithKey:filename withBucket: [self getS3BucketName]] autorelease]; + S3GetObjectRequest *headRequest = [[S3GetObjectRequest alloc] initWithKey:filename withBucket: [self getS3BucketName]]; headRequest.httpMethod = @"HEAD"; - S3Response *headResponse = [[[S3Response alloc] init] autorelease]; + S3Response *headResponse = [[S3Response alloc] init]; headResponse = [self.awsConnection invoke:headRequest]; if (headResponse.httpStatusCode == 200) { @@ -247,7 +228,6 @@ -(void)startDownloading:(NSString *)filename [self checkOrGetBinaryAuthentication]; [self connectToAWS]; - [hackFilename release]; hackFilename = [filename copy]; UIApplication *app = [UIApplication sharedApplication]; @@ -273,14 +253,13 @@ -(void)startDownloading:(NSString *)filename // @TODO error handling ? [transmissionProgress setObject:[NSNumber numberWithInt:sizeOfRemoteFile] forKey:filename]; - S3GetObjectRequest *downloadRequest = [[[S3GetObjectRequest alloc] initWithKey:filename withBucket: [self getS3BucketName]] autorelease]; + S3GetObjectRequest *downloadRequest = [[S3GetObjectRequest alloc] initWithKey:filename withBucket: [self getS3BucketName]]; [downloadRequest setDelegate:self]; downloadResponse = [self.awsConnection getObject: downloadRequest]; NSMutableData *fileData = [[NSMutableData alloc] initWithCapacity:1024]; [downloadsInProgressData setObject: fileData forKey:filename]; [downloadsInProgressRequests setObject: downloadRequest forKey:filename]; - [fileData release]; for (iddelegate in delegates) { if ([delegate respondsToSelector:@selector(binaryDownloadStarted:)]) @@ -317,20 +296,20 @@ -(void)startUploading:(NSString *)filename } @try { - //AmazonS3Client *s3 = [[[AmazonS3Client alloc] initWithAccessKey:ACCESS_KEY_ID withSecretKey:SECRET_KEY] autorelease]; - //AmazonS3Client *s3 = [[[AmazonS3Client alloc] initWithAccessKey:self.binaryAuthID withSecretKey:self.binaryAuthSecret] autorelease]; + //AmazonS3Client *s3 = [[AmazonS3Client alloc] initWithAccessKey:ACCESS_KEY_ID withSecretKey:SECRET_KEY]; + //AmazonS3Client *s3 = [[AmazonS3Client alloc] initWithAccessKey:self.binaryAuthID withSecretKey:self.binaryAuthSecret]; - //[self.awsConnection createBucket:[[[S3CreateBucketRequest alloc] initWithName:[self getS3BucketName]] autorelease]]; + //[self.awsConnection createBucket:[[S3CreateBucketRequest alloc] initWithName:[self getS3BucketName]]]; NSString *s3bucketName = [self getS3BucketName]; NSString *s3filename = [self prefixFilename:filename]; - S3PutObjectRequest *uploadRequest = [[[S3PutObjectRequest alloc] initWithKey:s3filename inBucket:s3bucketName] autorelease]; + S3PutObjectRequest *uploadRequest = [[S3PutObjectRequest alloc] initWithKey:s3filename inBucket:s3bucketName]; // Create the picture bucket. - //[s3 createBucket:[[[S3CreateBucketRequest alloc] initWithName:BUCKET] autorelease]]; + //[s3 createBucket:[[S3CreateBucketRequest alloc] initWithName:BUCKET]]; // Upload image data. Remember to set the content type. - //S3PutObjectRequest *por = [[[S3PutObjectRequest alloc] initWithKey:@"NameOfThePicture" inBucket:BUCKET] autorelease]; + //S3PutObjectRequest *por = [[S3PutObjectRequest alloc] initWithKey:@"NameOfThePicture" inBucket:BUCKET]; //por.data = data; [uploadRequest setDelegate: self]; //uploadRequest.contentType = @"image/jpeg"; diff --git a/Simperium/SPSchema.h b/Simperium/SPSchema.h index 49162729..68c096bd 100644 --- a/Simperium/SPSchema.h +++ b/Simperium/SPSchema.h @@ -15,18 +15,18 @@ @interface SPSchema : NSObject { NSString *bucketName; - NSMutableArray *members; // ALL members + NSMutableDictionary *members; // ALL members NSMutableArray *binaryMembers; // JUST binary members (for optimization) BOOL dynamic; } @property (nonatomic, copy) NSString *bucketName; -@property (nonatomic, retain) NSMutableArray *members; -@property (nonatomic, retain) NSMutableArray *binaryMembers; -@property (assign) BOOL dynamic; +@property (nonatomic, strong) NSMutableDictionary *members; +@property (nonatomic, strong) NSMutableArray *binaryMembers; +@property BOOL dynamic; -(id)initWithBucketName:(NSString *)name data:(NSDictionary *)definition; --(SPMember *)memberNamed:(NSString *)memberName; +-(SPMember *)memberForKey:(NSString *)memberName; -(void)setDefaults:(id)object; -(void)addMemberForObject:(id)object key:(NSString *)key; diff --git a/Simperium/SPSchema.m b/Simperium/SPSchema.m index 167ebd79..c1d12470 100644 --- a/Simperium/SPSchema.m +++ b/Simperium/SPSchema.m @@ -15,6 +15,7 @@ #import "SPMemberFloat.h" #import "SPMemberDouble.h" #import "SPMemberEntity.h" +#import "SPMemberJSONList.h" #import "SPMemberList.h" #import "SPMemberBase64.h" #import "SPMemberBinary.h" @@ -42,6 +43,8 @@ -(Class)memberClassForType:(NSString *)type return [SPMemberBinary class]; else if ([type isEqualToString:@"list"]) return [SPMemberList class]; + else if ([type isEqualToString:@"jsonlist"]) + return [SPMemberJSONList class]; else if ([type isEqualToString:@"base64"]) return [SPMemberBase64 class]; @@ -55,27 +58,24 @@ -(id)initWithBucketName:(NSString *)name data:(NSDictionary *)definition if (self = [super init]) { bucketName = [name copy]; NSArray *memberList = [definition valueForKey:@"members"]; - members = [[NSMutableArray arrayWithCapacity:3] retain]; - binaryMembers = [[NSMutableArray arrayWithCapacity:3] retain]; + members = [NSMutableDictionary dictionaryWithCapacity:3]; + binaryMembers = [NSMutableArray arrayWithCapacity:3]; for (NSDictionary *memberDict in memberList) { NSString *typeStr = [memberDict valueForKey:@"type"]; SPMember *member = [[[self memberClassForType:typeStr] alloc] initFromDictionary:memberDict]; - [members addObject: member]; - + + if(member) { + [members setObject:member forKey:member.keyName]; + } + if ([member isKindOfClass:[SPMemberBinary class]]) [binaryMembers addObject: member]; - [member release]; } } return self; } --(void)dealloc { - [members release]; - [binaryMembers release]; - [super dealloc]; -} -(NSString *)bucketName { return bucketName; @@ -85,8 +85,7 @@ -(void)addMemberForObject:(id)object key:(NSString *)key { if (!dynamic) return; - // Make this faster via dictionary lookup - if ([self memberNamed:key]) + if ([self memberForKey:key]) return; NSString *type = @"unsupported"; @@ -99,18 +98,14 @@ -(void)addMemberForObject:(id)object key:(NSString *)key { type, @"type", key, @"name", nil]; SPMember *member = [[[self memberClassForType:type] alloc] initFromDictionary:memberDict]; - [members addObject:member]; - [member release]; + if(member) { + [members setObject:member forKey:member.keyName]; + } } --(SPMember *)memberNamed:(NSString *)memberName { - // Would be more efficient to use NSSet? - for (SPMember *member in self.members) { - if ([[member keyName] compare: memberName] == NSOrderedSame) - return member; - } - return nil; +-(SPMember *)memberForKey:(NSString *)memberName { + return [members objectForKey:memberName]; } -(void)setDefaults:(id)object @@ -118,7 +113,7 @@ -(void)setDefaults:(id)object // Set default values for all members that don't already have them // This now gets called after some data might already have been set, so be careful // not to overwrite it - for (SPMember *member in members) { + for (SPMember *member in [members allValues]) { if (member.modelDefaultValue == nil && [object simperiumValueForKey:member.keyName] == nil) [object simperiumSetValue:[member defaultValue] forKey:member.keyName]; } diff --git a/Simperium/SPSimpleKeyChain.h b/Simperium/SPSimpleKeyChain.h deleted file mode 100644 index 3e6467d5..00000000 --- a/Simperium/SPSimpleKeyChain.h +++ /dev/null @@ -1,20 +0,0 @@ -// -// SPSimpleKeyChain.h -// Simperium -// -// Created by Michael Johnston on 12-08-01. -// Copyright (c) 2012 Simperium. All rights reserved. -// -// http://stackoverflow.com/questions/5247912/saving-email-password-to-keychain-in-ios/5251820#5251820 - -#import - -#import - -@interface SPSimpleKeychain : NSObject - -+ (void)save:(NSString *)service data:(id)data; -+ (id)load:(NSString *)service; -+ (void)delete:(NSString *)service; - -@end \ No newline at end of file diff --git a/Simperium/SPSimpleKeyChain.m b/Simperium/SPSimpleKeyChain.m deleted file mode 100644 index 7c9e6b10..00000000 --- a/Simperium/SPSimpleKeyChain.m +++ /dev/null @@ -1,56 +0,0 @@ -// -// SPSimpleKeyChain.m -// Simperium -// -// Created by Michael Johnston on 12-08-01. -// Copyright (c) 2012 Simperium. All rights reserved. -// -// http://stackoverflow.com/questions/5247912/saving-email-password-to-keychain-in-ios/5251820#5251820 - -#import "SPSimpleKeychain.h" - -@implementation SPSimpleKeychain - -+ (NSMutableDictionary *)getKeychainQuery:(NSString *)service { - return [NSMutableDictionary dictionaryWithObjectsAndKeys: - (id)kSecClassGenericPassword, (id)kSecClass, - service, (id)kSecAttrService, - service, (id)kSecAttrAccount, -#if TARGET_OS_IPHONE - (id)kSecAttrAccessibleAfterFirstUnlock, (id)kSecAttrAccessible, -#endif - nil]; -} - -+ (void)save:(NSString *)service data:(id)data { - NSMutableDictionary *keychainQuery = [self getKeychainQuery:service]; - SecItemDelete((CFDictionaryRef)keychainQuery); - [keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(id)kSecValueData]; - SecItemAdd((CFDictionaryRef)keychainQuery, NULL); -} - -+ (id)load:(NSString *)service { - id ret = nil; - NSMutableDictionary *keychainQuery = [self getKeychainQuery:service]; - [keychainQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData]; - [keychainQuery setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit]; - CFDataRef keyData = NULL; - if (SecItemCopyMatching((CFDictionaryRef)keychainQuery, (CFTypeRef *)&keyData) == noErr) { - @try { - ret = [NSKeyedUnarchiver unarchiveObjectWithData:(NSData *)keyData]; - } - @catch (NSException *e) { - NSLog(@"Unarchive of %@ failed: %@", service, e); - } - @finally {} - } - if (keyData) CFRelease(keyData); - return ret; -} - -+ (void)delete:(NSString *)service { - NSMutableDictionary *keychainQuery = [self getKeychainQuery:service]; - SecItemDelete((CFDictionaryRef)keychainQuery); -} - -@end \ No newline at end of file diff --git a/Simperium/SPStorage.m b/Simperium/SPStorage.m index f43d9a52..57ad69b4 100644 --- a/Simperium/SPStorage.m +++ b/Simperium/SPStorage.m @@ -42,18 +42,19 @@ -(void)configureNewGhost:(id)object SPGhost *ghost = [[SPGhost alloc] initWithKey: [object simperiumKey] memberData: nil]; object.ghost = ghost; object.ghost.version = @"0"; - [ghost release]; } } -(void)configureInsertedObject:(id)object { - if (object.simperiumKey == nil || object.simperiumKey.length == 0) + if (object.simperiumKey == nil || object.simperiumKey.length == 0) { object.simperiumKey = [NSString sp_makeUUID]; + } [self configureNewGhost:object]; // nil values should be OK now...try disabling defaults //[entityManager setDefaults:entity]; } + @end diff --git a/Simperium/SPStorageObserver.h b/Simperium/SPStorageObserver.h index 841354bf..8fa0bc5d 100644 --- a/Simperium/SPStorageObserver.h +++ b/Simperium/SPStorageObserver.h @@ -9,4 +9,4 @@ @protocol SPStorageObserver -(BOOL)objectsShouldSync; -(void)storage:(SPStorage *)storage updatedObjects:(NSSet *)updatedObjects insertedObjects:(NSSet *)insertedObjects deletedObjects:(NSSet *)deletedObjects; -@end \ No newline at end of file +@end diff --git a/Simperium/SPStorageProvider.h b/Simperium/SPStorageProvider.h index 6aa3637e..d7c5cd72 100644 --- a/Simperium/SPStorageProvider.h +++ b/Simperium/SPStorageProvider.h @@ -10,7 +10,8 @@ @protocol SPStorageProvider -(BOOL)save; --(NSArray *)objectsForBucketName:(NSString *)bucketName; +-(NSArray *)objectsForBucketName:(NSString *)bucketName predicate:(NSPredicate *)predicate; +-(NSArray *)objectKeysForBucketName:(NSString *)bucketName; -(id)objectForKey:(NSString *)key bucketName:(NSString *)bucketName; -(NSArray *)objectsForKeys:(NSSet *)keys bucketName:(NSString *)bucketName; -(id)objectAtIndex:(NSUInteger)index bucketName:(NSString *)bucketName; @@ -24,7 +25,8 @@ -(void)validateObjectsForBucketName:(NSString *)bucketName; -(void)stopManagingObjectWithKey:(NSString *)key; -(id)threadSafeStorage; - +-(void)setMetadata:(NSDictionary *)metadata; +-(NSDictionary *)metadata; -(void)stashUnsavedObjects; -(NSArray *)stashedObjects; -(void)unstashUnsavedObjects; @@ -33,4 +35,4 @@ @optional -(void)object:(id)object forKey:(NSString *)simperiumKey didChangeValue:(id)value forKey:(NSString *)key; -@end \ No newline at end of file +@end diff --git a/Simperium/SPTOSViewController.h b/Simperium/SPTOSViewController.h new file mode 100644 index 00000000..1758c218 --- /dev/null +++ b/Simperium/SPTOSViewController.h @@ -0,0 +1,17 @@ +// +// SPTOSViewController.h +// Simperium +// +// Created by Tom Witkin on 8/27/13. +// Copyright (c) 2013 Simperium. All rights reserved. +// + +#import + +@interface SPTOSViewController : UIViewController { + + UIWebView *webView; + UIActivityIndicatorView *activityIndicator; +} + +@end diff --git a/Simperium/SPTOSViewController.m b/Simperium/SPTOSViewController.m new file mode 100644 index 00000000..e68a7631 --- /dev/null +++ b/Simperium/SPTOSViewController.m @@ -0,0 +1,67 @@ +// +// SPTOSViewController.m +// Simperium +// +// Created by Tom Witkin on 8/27/13. +// Copyright (c) 2013 Simperium. All rights reserved. +// + +NSString *const TOSUrl = @"http://simperium.com/tos/"; + +#import "SPTOSViewController.h" + +@interface SPTOSViewController () + +@end + +@implementation SPTOSViewController + +- (void)loadView { + + if (!webView) { + webView = [[UIWebView alloc] init]; + webView.delegate = self; + self.view = webView; + } +} + +- (void)dismissAction:(id)sender { + + [self dismissViewControllerAnimated:YES completion:nil]; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + // Do any additional setup after loading the view. + + activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; + [activityIndicator hidesWhenStopped]; + UIBarButtonItem *activityContainer = [[UIBarButtonItem alloc] initWithCustomView:activityIndicator]; + self.navigationItem.leftBarButtonItem = activityContainer; + + self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(dismissAction:)]; + + NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:TOSUrl]]; + [webView loadRequest:request]; +} + +#pragma mark UIWebViewDelegate Methods + +- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { + + return [[NSString stringWithFormat:@"%@", request.URL] isEqualToString:TOSUrl]; +} + +- (void)webViewDidStartLoad:(UIWebView *)webView { + + [activityIndicator startAnimating]; +} + +- (void)webViewDidFinishLoad:(UIWebView *)webView { + + [activityIndicator stopAnimating]; +} + + +@end diff --git a/Simperium/SPUser.m b/Simperium/SPUser.m index 6a908463..38596bd1 100644 --- a/Simperium/SPUser.m +++ b/Simperium/SPUser.m @@ -22,11 +22,6 @@ -(id)initWithEmail:(NSString *)username token:(NSString *)token return self; } --(void)dealloc { - [email release]; - [authToken release]; - [super dealloc]; -} -(NSString *)hashedEmail { diff --git a/Simperium/SPWebSocketChannel.h b/Simperium/SPWebSocketChannel.h index eeff1bb1..bab202b9 100644 --- a/Simperium/SPWebSocketChannel.h +++ b/Simperium/SPWebSocketChannel.h @@ -10,41 +10,29 @@ @class Simperium; @class SPBucket; -@class SRWebSocket; +@class SPWebSocketInterface; @protocol SPDiffable; @interface SPWebSocketChannel : NSObject -{ - BOOL gettingVersions; - BOOL started; - int retryDelay; - NSString *nextMark; - NSMutableArray *indexArray; - NSString *pendingLastChangeSignature; - SRWebSocket *webSocket; - NSString *name; - int number; - NSInteger numTransfers; -} -@property (nonatomic, assign) SRWebSocket *webSocket; +@property (nonatomic, weak) SPWebSocketInterface *webSocketManager; +@property (nonatomic, strong) NSMutableArray *indexArray; @property (nonatomic, copy) NSString *nextMark; -@property (nonatomic, retain) NSMutableArray *indexArray; @property (nonatomic, copy) NSString *pendingLastChangeSignature; -@property (nonatomic) int number; @property (nonatomic, copy) NSString *name; -@property (nonatomic) BOOL started; +@property (nonatomic, assign) int number; +@property (nonatomic, assign) BOOL started; -+(void)setNetworkActivityIndicatorEnabled:(BOOL)enabled; --(id)initWithSimperium:(Simperium *)s clientID:(NSString *)cid; --(void)getVersions:(int)numVersions forObject:(id)object; --(void)getLatestVersionsForBucket:(SPBucket *)bucket; --(void)sendObjectDeletion:(id)object; --(void)sendObjectChanges:(id)object; --(void)shareObject:(id)object withEmail:(NSString *)email; --(void)handleRemoteChanges:(NSArray *)changes bucket:(SPBucket *)bucket; --(void)handleIndexResponse:(NSString *)responseString bucket:(SPBucket *)bucket; --(void)handleVersionResponse:(NSString *)responseString bucket:(SPBucket *)bucket; --(void)startProcessingChangesForBucket:(SPBucket *)bucket; ++ (void)setNetworkActivityIndicatorEnabled:(BOOL)enabled; +- (id)initWithSimperium:(Simperium *)s clientID:(NSString *)cid; +- (void)requestVersions:(int)numVersions object:(id)object; +- (void)requestLatestVersionsForBucket:(SPBucket *)bucket; +- (void)sendObjectDeletion:(id)object; +- (void)sendObjectChanges:(id)object; +- (void)shareObject:(id)object withEmail:(NSString *)email; +- (void)handleRemoteChanges:(NSArray *)changes bucket:(SPBucket *)bucket; +- (void)handleIndexResponse:(NSString *)responseString bucket:(SPBucket *)bucket; +- (void)handleVersionResponse:(NSString *)responseString bucket:(SPBucket *)bucket; +- (void)startProcessingChangesForBucket:(SPBucket *)bucket; @end diff --git a/Simperium/SPWebSocketChannel.m b/Simperium/SPWebSocketChannel.m index fce86255..2d252086 100644 --- a/Simperium/SPWebSocketChannel.m +++ b/Simperium/SPWebSocketChannel.m @@ -19,6 +19,8 @@ #import "SPIndexProcessor.h" #import "SPMember.h" #import "SPGhost.h" +#import "SPWebSocketChannel.h" +#import "SPWebSocketInterface.h" #import "JSONKit.h" #import "NSString+Simperium.h" #import "DDLog.h" @@ -28,7 +30,6 @@ #define INDEX_PAGE_SIZE 500 #define INDEX_BATCH_SIZE 10 -#define INDEX_QUEUE_SIZE 5 #define CHAN_NUMBER_INDEX 0 #define CHAN_COMMAND_INDEX 1 @@ -39,29 +40,17 @@ static int ddLogLevel = LOG_LEVEL_INFO; @interface SPWebSocketChannel() -@property (nonatomic, assign) Simperium *simperium; -@property (nonatomic, retain) NSMutableArray *responseBatch; -@property (nonatomic, retain) NSMutableDictionary *versionsWithErrors; -@property (nonatomic, copy) NSString *clientID; - -//-(void)indexQueueFinished:(ASINetworkQueue *)queue; -//-(void)allVersionsFinished:(ASINetworkQueue *)queue; -//-(void)getIndexFailed:(ASIHTTPRequest *)request; -//-(void)getVersionFailed:(ASIHTTPRequest *)request; +@property (nonatomic, weak) Simperium *simperium; +@property (nonatomic, strong) NSMutableArray *responseBatch; +@property (nonatomic, strong) NSMutableDictionary *versionsWithErrors; +@property (nonatomic, copy) NSString *clientID; +@property (nonatomic, assign) NSInteger retryDelay; +@property (nonatomic, assign) NSInteger objectVersionsPending; +@property (nonatomic, assign) BOOL indexing; +@property (nonatomic, assign) BOOL retrievingObjectHistory; @end @implementation SPWebSocketChannel -@synthesize simperium; -@synthesize webSocket; -@synthesize responseBatch; -@synthesize versionsWithErrors; -@synthesize nextMark; -@synthesize indexArray; -@synthesize clientID; -@synthesize pendingLastChangeSignature; -@synthesize started; -@synthesize name; -@synthesize number; + (int)ddLogLevel { return ddLogLevel; @@ -71,105 +60,71 @@ + (void)ddSetLogLevel:(int)logLevel { ddLogLevel = logLevel; } -+ (void)updateNetworkActivityIndictator -{ -#if TARGET_OS_IPHONE - //BOOL visible = useNetworkActivityIndicator && numTransfers > 0; - //[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:visible]; - //DDLogInfo(@"Simperium numTransfers = %d", numTransfers); -#endif ++ (void)updateNetworkActivityIndictator { + // For now at least, don't display the indicator when using WebSockets } -+ (void)setNetworkActivityIndicatorEnabled:(BOOL)enabled -{ ++ (void)setNetworkActivityIndicatorEnabled:(BOOL)enabled { useNetworkActivityIndicator = enabled; } --(id)initWithSimperium:(Simperium *)s clientID:(NSString *)cid -{ +- (id)initWithSimperium:(Simperium *)s clientID:(NSString *)cid { if ((self = [super init])) { self.simperium = s; self.indexArray = [NSMutableArray arrayWithCapacity:200]; self.clientID = cid; self.versionsWithErrors = [NSMutableDictionary dictionaryWithCapacity:3]; - numTransfers = 0; } return self; } --(void)dealloc -{ - self.webSocket = nil; - self.clientID = nil; - self.indexArray = nil; - self.responseBatch = nil; - self.versionsWithErrors = nil; - self.nextMark = nil; - self.name = nil; - [super dealloc]; -} - --(void)sendAllChangesForBucket:(SPBucket *)bucket -{ +- (void)sendChangesForBucket:(SPBucket *)bucket onlyQueuedChanges:(BOOL)onlyQueuedChanges completionBlock:(void(^)())completionBlock { // This gets called after remote changes have been handled in order to pick up any local changes that happened in the meantime dispatch_async(bucket.processorQueue, ^{ - NSArray *changes = [bucket.changeProcessor processPendingChanges:bucket]; - if ([changes count] == 0) - return; - - dispatch_async(dispatch_get_main_queue(), ^{ - if (started) { - DDLogVerbose(@"Simperium sending all changes (%d) for bucket %@", [changes count], bucket.name); - for (NSString *change in changes) { - NSString *jsonStr = [change JSONString]; - NSString *message = [NSString stringWithFormat:@"%d:c:%@", number, jsonStr]; - DDLogVerbose(@"Simperium sending change (%@-%@) %@",bucket.name, bucket.instanceLabel, message); - [self.webSocket send:message]; - } - } - }); - }); -} - --(void)processKeysForObjectsWithMoreChanges:(SPBucket *)bucket -{ - // This gets called after remote changes have been handled in order to pick up any local changes that happened in the meantime - dispatch_async(bucket.processorQueue, ^{ - NSArray *changes = [bucket.changeProcessor processKeysForObjectsWithMoreChanges:bucket]; - if ([changes count] == 0) + + NSArray *changes = [bucket.changeProcessor processPendingChanges:bucket onlyQueuedChanges:onlyQueuedChanges]; + if ([changes count] == 0) { + dispatch_async(dispatch_get_main_queue(), ^{ + if (completionBlock) { + completionBlock(); + } + }); return; + } dispatch_async(dispatch_get_main_queue(), ^{ - if (started) { - DDLogVerbose(@"Simperium sending all changes (%d) for bucket %@", [changes count], bucket.name); + if (self.started) { + DDLogVerbose(@"Simperium sending all changes (%lu) for bucket %@", (unsigned long)[changes count], bucket.name); for (NSString *change in changes) { NSString *jsonStr = [change JSONString]; - NSString *message = [NSString stringWithFormat:@"%d:c:%@", number, jsonStr]; + NSString *message = [NSString stringWithFormat:@"%d:c:%@", self.number, jsonStr]; DDLogVerbose(@"Simperium sending change (%@-%@) %@",bucket.name, bucket.instanceLabel, message); - [self.webSocket send:message]; + [self.webSocketManager send:message]; } - } + } + + // Done! + if (completionBlock) { + completionBlock(); + } }); }); } - --(void)sendChange:(NSDictionary *)change forKey:(NSString *)key bucket:(SPBucket *)bucket -{ +- (void)sendChange:(NSDictionary *)change forKey:(NSString *)key bucket:(SPBucket *)bucket { DDLogVerbose(@"Simperium adding pending change (%@): %@", self.name, key); [bucket.changeProcessor processLocalChange:change key:key]; dispatch_async(dispatch_get_main_queue(), ^{ NSString *jsonStr = [change JSONString]; - NSString *message = [NSString stringWithFormat:@"%d:c:%@", number, jsonStr]; + NSString *message = [NSString stringWithFormat:@"%d:c:%@", self.number, jsonStr]; DDLogVerbose(@"Simperium sending change (%@-%@) %@",bucket.name, bucket.instanceLabel, message); - [self.webSocket send:message]; + [self.webSocketManager send:message]; }); } --(void)sendObjectDeletion:(id)object -{ +- (void)sendObjectDeletion:(id)object { NSString *key = [object simperiumKey]; DDLogVerbose(@"Simperium sending entity DELETION change: %@/%@", self.name, key); @@ -187,8 +142,7 @@ -(void)sendObjectDeletion:(id)object }); } --(void)sendObjectChanges:(id)object -{ +- (void)sendObjectChanges:(id)object { // Consider being more careful about faulting here (since only the simperiumKey is needed) NSString *key = [object simperiumKey]; if (key == nil) { @@ -197,40 +151,34 @@ -(void)sendObjectChanges:(id)object } dispatch_async(object.bucket.processorQueue, ^{ - NSDictionary *change = [object.bucket.changeProcessor processLocalObjectWithKey:key bucket:object.bucket later:gettingVersions || !started]; - if (change) + NSDictionary *change = [object.bucket.changeProcessor processLocalObjectWithKey:key bucket:object.bucket later:_indexing || !_started]; + if (change) { [self sendChange: change forKey: key bucket:object.bucket]; + } }); } --(void)startProcessingChangesForBucket:(SPBucket *)bucket -{ +- (void)startProcessingChangesForBucket:(SPBucket *)bucket { __block int numChangesPending; __block int numKeysForObjectsWithMoreChanges; dispatch_async(bucket.processorQueue, ^{ - if (started) { + if (self.started) { numChangesPending = [bucket.changeProcessor numChangesPending]; numKeysForObjectsWithMoreChanges = [bucket.changeProcessor numKeysForObjectsWithMoreChanges]; dispatch_async(dispatch_get_main_queue(), ^{ -// if (numChangesPending > 0 || numKeysForObjectsWithMoreChanges > 0) { -// // Send the offline changes -// DDLogVerbose(@"Simperium sending %u pending offline changes (%@) plus %d objects with more", numChangesPending, self.name, numKeysForObjectsWithMoreChanges); -// [self sendAllChangesForBucket:bucket]; -// [self postChanges]; - //} else { - // Nothing to send, so start getting changes right away - NSString *message = [NSString stringWithFormat:@"%d:cv:%@", number, bucket.lastChangeSignature ? bucket.lastChangeSignature : @""]; - DDLogVerbose(@"Simperium sending cv %@", message); - [self.webSocket send:message]; + // Start getting changes from the last cv + NSString *getMessage = [NSString stringWithFormat:@"%d:cv:%@", self.number, bucket.lastChangeSignature ? bucket.lastChangeSignature : @""]; + DDLogVerbose(@"Simperium client %@ sending cv %@", self.simperium.clientID, getMessage); + [self.webSocketManager send:getMessage]; if (numChangesPending > 0 || numKeysForObjectsWithMoreChanges > 0) { - // Send the offline changes + // There are also offline changes; send them right away + // This needs to happen after the above cv is sent, otherwise acks will arrive prematurely if there + // have been remote changes that need to be processed first DDLogVerbose(@"Simperium sending %u pending offline changes (%@) plus %d objects with more", numChangesPending, self.name, numKeysForObjectsWithMoreChanges); - [self sendAllChangesForBucket:bucket]; + [self sendChangesForBucket:bucket onlyQueuedChanges:NO completionBlock:nil]; } -// [self getChanges]; - //} }); } }); @@ -247,35 +195,46 @@ - (int)nextRetryDelay { } - (void)resetRetryDelay { - retryDelay = 2; + self.retryDelay = 2; } --(void)handleRemoteChanges:(NSArray *)changes bucket:(SPBucket *)bucket -{ - // Changing entities and saving the context will clear Core Data's updatedObjects. Stash them so - // sync will still work for any unsaved changes. - [bucket.storage stashUnsavedObjects]; - - numTransfers++; - [[self class] updateNetworkActivityIndictator]; - - dispatch_async(bucket.processorQueue, ^{ - if (started) { - [bucket.changeProcessor processRemoteChanges:changes bucket:bucket clientID:clientID]; - dispatch_async(dispatch_get_main_queue(), ^{ - numTransfers--; - [[self class] updateNetworkActivityIndictator]; - [self processKeysForObjectsWithMoreChanges:bucket]; - }); - } - }); +- (void)handleRemoteChanges:(NSArray *)changes bucket:(SPBucket *)bucket { + + // Signal that the bucket was sync'ed. We need this, in case the sync was manually triggered + if (changes.count == 0) { + [bucket bucketDidSync]; + return; + } + + DDLogVerbose(@"Simperium handling changes %@", changes); + + // Changing entities and saving the context will clear Core Data's updatedObjects. Stash them so + // sync will still work for any unsaved changes. + [bucket.storage stashUnsavedObjects]; + + dispatch_async(bucket.processorQueue, ^{ + if (!self.started) { + return; + } + + BOOL needsRepost = [bucket.changeProcessor processRemoteResponseForChanges:changes bucket:bucket]; + [bucket.changeProcessor processRemoteChanges:changes bucket:bucket clientID:self.clientID]; + + dispatch_async(dispatch_get_main_queue(), ^{ + + // Note #1: After remote changes have been processed, check to see if any local changes were attempted (and + // queued) in the meantime, and send them. + + // Note #2: If we need to repost, we'll need to re-send everything. Not just the queued changes. + [self sendChangesForBucket:bucket onlyQueuedChanges:!needsRepost completionBlock:nil]; + }); + }); } #pragma mark Index handling --(void)getLatestVersionsForBucket:(SPBucket *)bucket mark:(NSString *)mark -{ - if (!simperium.user) { +- (void)requestLatestVersionsForBucket:(SPBucket *)bucket mark:(NSString *)mark { + if (!self.simperium.user) { DDLogError(@"Simperium critical error: tried to retrieve index with no user set"); return; } @@ -287,86 +246,78 @@ -(void)getLatestVersionsForBucket:(SPBucket *)bucket mark:(NSString *)mark // } // // // Get an index of all objects and fetch their latest versions - gettingVersions = YES; + self.indexing = YES; - NSString *message = [NSString stringWithFormat:@"%d:i::%@::%d", number, mark ? mark : @"", INDEX_PAGE_SIZE]; + NSString *message = [NSString stringWithFormat:@"%d:i::%@::%d", self.number, mark ? mark : @"", INDEX_PAGE_SIZE]; DDLogVerbose(@"Simperium requesting index (%@): %@", self.name, message); - [self.webSocket send:message]; + [self.webSocketManager send:message]; } --(void)getLatestVersionsForBucket:(SPBucket *)bucket { +-(void)requestLatestVersionsForBucket:(SPBucket *)bucket { // Multiple errors could try to trigger multiple index refreshes - if (gettingVersions) + if (self.indexing) { return; + } - [self getLatestVersionsForBucket:bucket mark:nil]; + // Send any pending changes first + // This could potentially lead to some duplicate changes being sent if there are some that are awaiting + // acknowledgment, but the server will safely ignore them + [self sendChangesForBucket:bucket onlyQueuedChanges:NO completionBlock: ^{ + [self requestLatestVersionsForBucket:bucket mark:nil]; + }]; } -//-(ASIHTTPRequest *)getRequestForKey:(NSString *)key version:(NSString *)version -//{ -// // Otherwise, need to get the latest version -// NSURL *url = [NSURL URLWithString:[appURL stringByAppendingFormat:@"%@/i/%@/v/%@", -// remoteBucketName, -// key,//[key urlEncodeString], -// version]]; -// ASIHTTPRequest *versionRequest = [ASIHTTPRequest requestWithURL: url]; -// [versionRequest addRequestHeader:@"X-Simperium-Token" value:[simperium.user authToken]]; -// return versionRequest; -//} - --(void)getVersionsForKeys:(NSArray *)currentIndexArray bucket:(SPBucket *)bucket { +- (void)requestVersionsForKeys:(NSArray *)currentIndexArray bucket:(SPBucket *)bucket { // Changing entities and saving the context will clear Core Data's updatedObjects. Stash them so // sync will still work later for any unsaved changes. // In the time between now and when the index refresh completes, any local changes will get marked // since regular syncing is disabled during index retrieval. [bucket.storage stashUnsavedObjects]; - if ([bucket.delegate respondsToSelector:@selector(bucketWillStartIndexing:)]) + if ([bucket.delegate respondsToSelector:@selector(bucketWillStartIndexing:)]) { [bucket.delegate bucketWillStartIndexing:bucket]; + } self.responseBatch = [NSMutableArray arrayWithCapacity:INDEX_BATCH_SIZE]; // Get all the latest versions - DDLogInfo(@"Simperium processing %d objects from index (%@)", [currentIndexArray count], self.name); + DDLogInfo(@"Simperium processing %lu objects from index (%@)", (unsigned long)[currentIndexArray count], self.name); - __block NSArray *indexArrayCopy = [currentIndexArray copy]; - __block int numVersionRequests = 0; + NSArray *indexArrayCopy = [currentIndexArray copy]; + __block int objectRequests = 0; dispatch_async(bucket.processorQueue, ^{ - if (started) { + if (self.started) { [bucket.indexProcessor processIndex:indexArrayCopy bucket:bucket versionHandler: ^(NSString *key, NSString *version) { - numVersionRequests++; + objectRequests++; // For each version that is processed, create a network request - dispatch_async(dispatch_get_main_queue(), ^{ - numTransfers += 1; - [[self class] updateNetworkActivityIndictator]; - NSString *message = [NSString stringWithFormat:@"%d:e:%@.%@", number, key, version]; + NSString *message = [NSString stringWithFormat:@"%d:e:%@.%@", self.number, key, version]; DDLogVerbose(@"Simperium sending object request (%@): %@", self.name, message); - [self.webSocket send:message]; + [self.webSocketManager send:message]; }); }]; dispatch_async(dispatch_get_main_queue(), ^{ // If no requests need to be queued, then all is good; back to processing - if (numVersionRequests == 0) { + self.objectVersionsPending = objectRequests; + if (self.objectVersionsPending == 0) { if (self.nextMark.length > 0) // More index pages to get - [self getLatestVersionsForBucket: bucket mark:self.nextMark]; + [self requestLatestVersionsForBucket: bucket mark:self.nextMark]; else // The entire index has been retrieved [self allVersionsFinishedForBucket:bucket]; return; } - DDLogInfo(@"Simperium enqueuing %d object requests (%@)", numVersionRequests, bucket.name); + DDLogInfo(@"Simperium enqueuing %ld object requests (%@)", (long)self.objectVersionsPending, bucket.name); }); } }); } --(void)handleIndexResponse:(NSString *)responseString bucket:(SPBucket *)bucket -{ +- (void)handleIndexResponse:(NSString *)responseString bucket:(SPBucket *)bucket { DDLogVerbose(@"Simperium received index (%@): %@", self.name, responseString); NSDictionary *responseDict = [responseString objectFromJSONString]; NSArray *currentIndexArray = [responseDict objectForKey:@"index"]; @@ -374,72 +325,60 @@ -(void)handleIndexResponse:(NSString *)responseString bucket:(SPBucket *)bucket // Store versions as strings, but if they come off the wire as numbers, then handle that too if ([current isKindOfClass:[NSNumber class]]) - current = [NSString stringWithFormat:@"%d", [current integerValue]]; + current = [NSString stringWithFormat:@"%ld", (long)[current integerValue]]; self.pendingLastChangeSignature = [current length] > 0 ? [NSString stringWithFormat:@"%@", current] : nil; self.nextMark = [responseDict objectForKey:@"mark"]; - numTransfers--; - + // Remember all the retrieved data in case there's more to get [self.indexArray addObjectsFromArray:currentIndexArray]; - - // If there aren't any instances remotely, just start getting changes - if ([self.indexArray count] == 0) { - gettingVersions = NO; - [[self class] updateNetworkActivityIndictator]; - [self allVersionsFinishedForBucket:bucket]; - return; - } - + // If there's another page, get those too (this will repeat until there are none left) if (self.nextMark.length > 0) { DDLogVerbose(@"Simperium found another index page mark (%@): %@", self.name, self.nextMark); - [self getLatestVersionsForBucket:bucket mark:self.nextMark]; + [self requestLatestVersionsForBucket:bucket mark:self.nextMark]; return; } // Index retrieval is complete, so get all the versions - [self getVersionsForKeys:self.indexArray bucket:bucket]; + [self requestVersionsForKeys:self.indexArray bucket:bucket]; [self.indexArray removeAllObjects]; } --(void)processBatchForBucket:(SPBucket *)bucket { - if ([self.responseBatch count] == 0) +- (void)processBatchForBucket:(SPBucket *)bucket { + if ([self.responseBatch count] == 0) { return; + } NSMutableArray *batch = [self.responseBatch copy]; BOOL firstSync = bucket.lastChangeSignature == nil; dispatch_async(bucket.processorQueue, ^{ - if (started) { + if (self.started) { [bucket.indexProcessor processVersions: batch bucket:bucket firstSync: firstSync changeHandler:^(NSString *key) { // Local version was different, so process it as a local change [bucket.changeProcessor processLocalObjectWithKey:key bucket:bucket later:YES]; }]; + + // Now check if indexing is complete + dispatch_async(dispatch_get_main_queue(), ^{ + if (_objectVersionsPending > 0) { + _objectVersionsPending--; + } + if (_objectVersionsPending == 0) { + [self allVersionsFinishedForBucket:bucket]; + } + }); } }); - [batch release]; [self.responseBatch removeAllObjects]; } --(void)handleVersionResponse:(NSString *)responseString bucket:(SPBucket *)bucket -{ - gettingVersions = NO; -// NSURL *url = [request originalURL]; -// NSString *responseString = [request responseString]; - - numTransfers--; - [[self class] updateNetworkActivityIndictator]; - -// if ([request responseStatusCode] != 200 || [[url pathComponents] count] < 6) { -// [self getVersionFailed:request]; -// return; -// } - - //DDLogDebug(@"Simperium received version (%@) code %d: %@", bucket.name, [request responseStatusCode], responseString); - //DDLogDebug(@" (url was %@)", [url absoluteString]); - - //NSString *version = [[request responseHeaders] objectForKey:@"X-Simperium-Version"]; - //NSString *key = [[url pathComponents] objectAtIndex:5]; // 0:/ 1:apiversion 2:app 3:bucket 4:i 5:id 6: v 7:version +- (void)handleVersionResponse:(NSString *)responseString bucket:(SPBucket *)bucket { + if ([responseString isEqualToString:@"?"]) { + DDLogError(@"Simperium error: version not found during version retrieval (%@)", bucket.name); + _objectVersionsPending--; + return; + } NSRange range = [responseString rangeOfString:@"."]; NSString *key = [responseString substringToIndex:range.location]; @@ -453,40 +392,42 @@ -(void)handleVersionResponse:(NSString *)responseString bucket:(SPBucket *)bucke // This processing should be moved off the main thread (or fixed at the protocol level) NSDictionary *versionDict = [versionString objectFromJSONString]; NSDictionary *dataDict = [versionDict objectForKey:@"data"]; + + if ([dataDict class] == [NSNull class] || dataDict == nil) { + // No data + DDLogError(@"Simperium error: version had no data (%@): %@", bucket.name, key); + _objectVersionsPending--; + return; + } + versionString = [dataDict JSONString]; // If there was an error previously, unflag it [self.versionsWithErrors removeObjectForKey:key]; - // Marshal stuff into an array for later processing - NSArray *responseData = [NSArray arrayWithObjects: key, versionString, version, nil]; - [self.responseBatch addObject:responseData]; - - // Batch responses for more efficient processing - // (process the last handful individually though) - if (numTransfers < INDEX_BATCH_SIZE || [self.responseBatch count] % INDEX_BATCH_SIZE == 0) - [self processBatchForBucket:bucket]; + // If retrieving object versions (e.g. for going back in time), return the result directly to the delegate + if (_retrievingObjectHistory) { + if (--_objectVersionsPending == 0) { + _retrievingObjectHistory = NO; + } + if ([bucket.delegate respondsToSelector:@selector(bucket:didReceiveObjectForKey:version:data:)]) { + [bucket.delegate bucket:bucket didReceiveObjectForKey:key version:version data:dataDict]; + } + } else { + // Otherwise, process the result for indexing + // Marshal stuff into an array for later processing + NSArray *responseData = [NSArray arrayWithObjects: key, versionString, version, nil]; + [self.responseBatch addObject:responseData]; + + // Batch responses for more efficient processing + // (process the last handful individually though) + if ([self.responseBatch count] < INDEX_BATCH_SIZE || [self.responseBatch count] % INDEX_BATCH_SIZE == 0) { + [self processBatchForBucket:bucket]; + } + } } -// -//-(void)getVersionFailed:(ASIHTTPRequest *)request { -// NSURL *url = [request originalURL]; -// DDLogWarn(@"Simperium failed to retrieve version (%d): %@",[request responseStatusCode], url); -// NSString *version = [url lastPathComponent]; -// NSString *key = [[url pathComponents] objectAtIndex:5]; // 0:/ 1:apiversion 2:app 3:bucket 4:i 5:id 6: v 7:version -// -// numTransfers--; -// [[self class] updateNetworkActivityIndictator]; -// -// if (version == nil || key == nil) { -// DDLogError(@"Simperium error: nil version/key during version retrieval (%@)", bucket.name); -// return; -// } -// -// [self.versionsWithErrors setObject:version forKey:key]; -//} --(void)allVersionsFinishedForBucket:(SPBucket *)bucket -{ +- (void)allVersionsFinishedForBucket:(SPBucket *)bucket { [self processBatchForBucket:bucket]; [self resetRetryDelay]; @@ -498,49 +439,45 @@ -(void)allVersionsFinishedForBucket:(SPBucket *)bucket if ([self.versionsWithErrors count] > 0) { // Try the index refresh again; this could be more efficient since we could know which version requests // failed, but it should happen rarely so take the easy approach for now - DDLogWarn(@"Index refresh complete (%@) but %d versions didn't load, retrying...", self.name, [self.versionsWithErrors count]); + DDLogWarn(@"Index refresh complete (%@) but %lu versions didn't load, retrying...", self.name, (unsigned long)[self.versionsWithErrors count]); // Create an array in the expected format NSMutableArray *errorArray = [NSMutableArray arrayWithCapacity: [self.versionsWithErrors count]]; for (NSString *key in [self.versionsWithErrors allKeys]) { id errorVersion = [self.versionsWithErrors objectForKey:key]; - NSDictionary *versionDict = [NSDictionary dictionaryWithObjectsAndKeys:errorVersion, @"v", - key, @"id", nil]; + NSDictionary *versionDict = @{ @"v" : errorVersion, + @"id" : key}; [errorArray addObject:versionDict]; } - [self performSelector:@selector(getVersionsForKeys:) withObject: errorArray afterDelay:1]; - //[self performSelector:@selector(getLatestVersions) withObject:nil afterDelay:10]; + + dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 1.0f * NSEC_PER_SEC); + dispatch_after(popTime, dispatch_get_main_queue(), ^(void) { + [self performSelector:@selector(requestVersionsForKeys:bucket:) withObject: errorArray withObject:bucket]; + }); + return; } // All versions were received successfully, so update the lastChangeSignature - [bucket setLastChangeSignature:pendingLastChangeSignature]; + [bucket setLastChangeSignature:self.pendingLastChangeSignature]; self.pendingLastChangeSignature = nil; - - gettingVersions = NO; + self.nextMark = nil; + self.indexing = NO; // There could be some processing happening on the queue still, so don't start until they're done - // Fake a network transfer so the progress indicator stays up until completion - numTransfers += 1; - [[self class] updateNetworkActivityIndictator]; dispatch_async(bucket.processorQueue, ^{ - if (started) { + if (self.started) { dispatch_async(dispatch_get_main_queue(), ^{ - numTransfers -= 1; - [[self class] updateNetworkActivityIndictator]; - - if ([bucket.delegate respondsToSelector:@selector(bucketDidFinishIndexing:)]) + if ([bucket.delegate respondsToSelector:@selector(bucketDidFinishIndexing:)]) { [bucket.delegate bucketDidFinishIndexing:bucket]; + } [self startProcessingChangesForBucket:bucket]; }); - } else dispatch_async(dispatch_get_main_queue(), ^{ - numTransfers = 0; - [[self class] updateNetworkActivityIndictator]; - }); + } }); } -// + //-(void)getIndexFailed:(ASIHTTPRequest *)request //{ // gettingVersions = NO; @@ -549,127 +486,37 @@ -(void)allVersionsFinishedForBucket:(SPBucket *)bucket // numTransfers--; // [[self class] updateNetworkActivityIndictator]; // -// [self performSelector:@selector(getLatestVersions) withObject:nil afterDelay:retry]; +// [self performSelector:@selector(requestLatestVersions) withObject:nil afterDelay:retry]; //} -#pragma mark Versions --(void)getVersions:(int)numVersions forObject:(id)object -{ - // Get all the latest versions - // ASINetworkQueue *networkQueue = [[ASINetworkQueue queue] retain]; - // [networkQueue setDelegate:self]; - // [networkQueue setQueueDidFinishSelector:@selector(allObjectVersionsFinished:)]; - // [networkQueue setRequestDidFinishSelector:@selector(getObjectVersionFinished:)]; - // [networkQueue setRequestDidFailSelector:@selector(getObjectVersionFailed:)]; - // - // DDLogInfo(@"Simperium enqueuing %d version requests for %@ (%@)", numVersions, [object simperiumKey], bucket.name); - // - // NSInteger startVersion = [object.ghost.version integerValue]-1; - // for (NSInteger i=startVersion; i>=1 && i>=startVersion-numVersions; i--) { - // NSString *versionStr = [NSString stringWithFormat:@"%d", i]; - // ASIHTTPRequest *versionRequest = [self getRequestForKey:[object simperiumKey] version:versionStr]; - //#if TARGET_OS_IPHONE - // versionRequest.shouldContinueWhenAppEntersBackground = YES; - //#endif - // DDLogDebug(@"Simperium enqueuing version request (%@): %@", bucket.name, [[versionRequest url] absoluteString]); - // numTransfers++; - // [networkQueue addOperation:versionRequest]; - // } - // - // [networkQueue go]; +#pragma mark Object Versions + +- (void)requestVersions:(int)numVersions object:(id)object { + // If already retrieving versions on this channel, don't do it again + if (self.retrievingObjectHistory) { + return; + } + + NSInteger startVersion = [object.ghost.version integerValue]; + self.retrievingObjectHistory = YES; + self.objectVersionsPending = MIN(startVersion, numVersions); + + for (NSInteger i=startVersion; i>=1 && i>=startVersion-_objectVersionsPending; i--) { + NSString *versionStr = [NSString stringWithFormat:@"%ld", (long)i]; + NSString *message = [NSString stringWithFormat:@"%d:e:%@.%@", self.number, object.simperiumKey, versionStr]; + DDLogVerbose(@"Simperium sending object version request (%@): %@", self.name, message); + [self.webSocketManager send:message]; + } } -//-(void)getObjectVersionFinished:(ASIHTTPRequest *)request -//{ -// gettingVersions = NO; -// NSURL *url = [request originalURL]; -// NSString *responseString = [request responseString]; -// DDLogDebug(@"Simperium received object version (%@) code %d: %@", bucket.name, [request responseStatusCode], responseString); -// DDLogDebug(@" (url was %@)", [url absoluteString]); -// -// numTransfers--; -// [[self class] updateNetworkActivityIndictator]; -// -// if ([request responseStatusCode] != 200) { -// DDLogWarn(@"Simperium failed to retrieve object version"); -// return; -// } -// -// // lastPathComponent is > iOS 4.0, so deconstruct it manually for now -// //NSString *version = [url lastPathComponent]; -// //NSString *key = [[url URLByDeletingLastPathComponent] lastPathComponent]; -// NSString *urlString = [url absoluteString]; -// NSArray *urlComponents = [urlString componentsSeparatedByString:@"/"]; -// NSString *version = [urlComponents lastObject]; -// NSString *key = [urlComponents objectAtIndex:[urlComponents count] - 3]; -// -// NSDictionary *memberData = [responseString objectFromJSONStringWithParseOptions:JKParseOptionLooseUnicode]; -// -// if ([bucket.delegate respondsToSelector:@selector(bucket:didReceiveObjectForKey:version:data:)]) -// [bucket.delegate bucket:bucket didReceiveObjectForKey:key version:version data:memberData]; -//} -// -// -//-(void)allObjectVersionsFinished:(ASINetworkQueue *)networkQueue -//{ -// DDLogInfo(@"Simperium finished retrieving all versions"); -// [networkQueue release]; -//} -// -//-(void)getObjectVersionFailed:(ASIHTTPRequest *)request -//{ -// gettingVersions = NO; -// DDLogWarn(@"Simperium warning: couldn't get object versions (%@): %d - %@", bucket.name, [request responseStatusCode], [request responseString]); -//} #pragma mark Sharing --(void)shareObject:(id)object withEmail:(NSString *)email -{ - // NSURL *url = [NSURL URLWithString:[appURL stringByAppendingFormat:@"%@/i/%@/share/v/%@", remoteBucketName, - // [object simperiumKey], email]]; - // - // DDLogVerbose(@"Simperium sharing object: %@", url); - // - // ASIHTTPRequest *shareRequest = [ASIHTTPRequest requestWithURL:url]; - // numTransfers++; - // [[self class] updateNetworkActivityIndictator]; - // - // [shareRequest addRequestHeader:@"X-Simperium-Token" value:[simperium.user authToken]]; - // NSDictionary *postData = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:FALSE] forKey:@"write_access"]; - // NSString *jsonStr = [postData JSONString]; - // [shareRequest appendPostData:[jsonStr dataUsingEncoding:NSUTF8StringEncoding]]; - // [shareRequest setDelegate:self]; - // [shareRequest setDidFinishSelector:@selector(shareFinished:)]; - // [shareRequest setDidFailSelector:@selector(shareFailed:)]; - //#if TARGET_OS_IPHONE - // shareRequest.shouldContinueWhenAppEntersBackground = YES; - //#endif - // [shareRequest startAsynchronous]; - // +- (void)shareObject:(id)object withEmail:(NSString *)email { + // Not yet implemented with WebSockets } -//-(void)shareFailed:(ASIHTTPRequest *)request -//{ -// DDLogWarn(@"Simperium sharing failed (%d): %@", [request responseStatusCode], [request responseString]); -// numTransfers--; -// [[self class] updateNetworkActivityIndictator]; -//} -// -//-(void)shareFinished:(ASIHTTPRequest *)request -//{ -// numTransfers--; -// [[self class] updateNetworkActivityIndictator]; -// -// if ([request responseStatusCode] != 200) { -// [self shareFailed:request]; -// return; -// } -// DDLogVerbose(@"Simperium sharing successful"); -//} - - @end diff --git a/Simperium/SPWebSocketInterface.h b/Simperium/SPWebSocketInterface.h new file mode 100644 index 00000000..51f618a3 --- /dev/null +++ b/Simperium/SPWebSocketInterface.h @@ -0,0 +1,23 @@ +// +// SPWebSocketManager +// Simperium +// +// Created by Michael Johnston on 12-08-06. +// Copyright 2011 Simperium. All rights reserved. +// + +#import +#import "SPNetworkInterface.h" + +@class Simperium; +@class SRWebSocket; + +@interface SPWebSocketInterface : NSObject + +@property (nonatomic, strong, readonly) SRWebSocket *webSocket; + +- (id)initWithSimperium:(Simperium *)s appURL:(NSString *)url clientID:(NSString *)cid; +- (void)loadChannelsForBuckets:(NSDictionary *)bucketList overrides:(NSDictionary *)overrides; +- (void)send:(NSString *)message; + +@end diff --git a/Simperium/SPWebSocketInterface.m b/Simperium/SPWebSocketInterface.m new file mode 100644 index 00000000..9a7e12eb --- /dev/null +++ b/Simperium/SPWebSocketInterface.m @@ -0,0 +1,353 @@ +// +// SPWebSocketManager +// Simperium +// +// Created by Michael Johnston on 11-03-07. +// Copyright 2011 Simperium. All rights reserved. +// +#import "SPWebSocketInterface.h" +#import "Simperium.h" +#import "SPChangeProcessor.h" +#import "SPUser.h" +#import "SPBucket.h" +#import "JSONKit.h" +#import "NSString+Simperium.h" +#import "DDLog.h" +#import "DDLogDebug.h" +#import "SRWebSocket.h" +#import "SPWebSocketChannel.h" + +#define WEBSOCKET_URL @"wss://api.simperium.com/sock/1" +#define INDEX_PAGE_SIZE 500 +#define INDEX_BATCH_SIZE 10 +#define HEARTBEAT 30 + +#if TARGET_OS_IPHONE +#define LIBRARY_ID @"ios" +#else +#define LIBRARY_ID @"osx" +#endif + +#define LIBRARY_VERSION @0 + +NSString * const COM_AUTH = @"auth"; +NSString * const COM_INDEX = @"i"; +NSString * const COM_CHANGE = @"c"; +NSString * const COM_ENTITY = @"e"; +NSString * const COM_ERROR = @"?"; + +static int ddLogLevel = LOG_LEVEL_INFO; +NSString * const WebSocketAuthenticationDidFailNotification = @"AuthenticationDidFailNotification"; + +@interface SPWebSocketInterface() +@property (nonatomic, strong, readwrite) SRWebSocket *webSocket; +@property (nonatomic, weak, readwrite) Simperium *simperium; +@property (nonatomic, strong, readwrite) NSMutableDictionary *channels; +@property (nonatomic, copy, readwrite) NSString *clientID; +@property (nonatomic, strong, readwrite) NSDictionary *bucketNameOverrides; +@property (nonatomic, strong, readwrite) NSTimer *heartbeatTimer; +@property (nonatomic, assign, readwrite) BOOL open; +@end + +@implementation SPWebSocketInterface + ++ (int)ddLogLevel { + return ddLogLevel; +} + ++ (void)ddSetLogLevel:(int)logLevel { + ddLogLevel = logLevel; +} + +- (id)initWithSimperium:(Simperium *)s appURL:(NSString *)url clientID:(NSString *)cid { + if ((self = [super init])) { + self.simperium = s; + self.clientID = cid; + self.channels = [NSMutableDictionary dictionaryWithCapacity:20]; + } + + return self; +} + +- (SPWebSocketChannel *)channelForName:(NSString *)str { + return [self.channels objectForKey:str]; +} + +- (SPWebSocketChannel *)channelForNumber:(NSNumber *)num { + for (SPWebSocketChannel *channel in [self.channels allValues]) { + if ([num intValue] == channel.number) + return channel; + } + return nil; +} + +- (SPWebSocketChannel *)loadChannelForBucket:(SPBucket *)bucket { + int channelNumber = (int)[self.channels count]; + SPWebSocketChannel *channel = [[SPWebSocketChannel alloc] initWithSimperium:self.simperium clientID:self.clientID]; + channel.number = channelNumber; + channel.name = bucket.name; + [self.channels setObject:channel forKey:bucket.name]; + + return [self.channels objectForKey:bucket.name]; +} + +- (void)loadChannelsForBuckets:(NSDictionary *)bucketList overrides:(NSDictionary *)overrides { + self.bucketNameOverrides = overrides; + + for (SPBucket *bucket in [bucketList allValues]) + [self loadChannelForBucket:bucket]; +} + +-(void)startChannels { + for (SPWebSocketChannel *channel in [self.channels allValues]) { + channel.webSocketManager = self; + [self authenticateChannel:channel]; + } +} + +-(void)stopChannels { + for (SPWebSocketChannel *channel in [self.channels allValues]) { + channel.started = NO; + } +} + +- (void)sendObjectDeletion:(id)object { + SPWebSocketChannel *channel = [self channelForName:object.bucket.name]; + [channel sendObjectDeletion:object]; +} + +- (void)sendObjectChanges:(id)object { + SPWebSocketChannel *channel = [self channelForName:object.bucket.name]; + [channel sendObjectChanges:object]; +} + +- (void)authenticateChannel:(SPWebSocketChannel *)channel { + // NSString *message = @"1:command:parameters"; + NSString *remoteBucketName = [self.bucketNameOverrides objectForKey:channel.name]; + if (!remoteBucketName || remoteBucketName.length == 0) + remoteBucketName = channel.name; + + NSDictionary *jsonData = @{ + @"api" : @1, + @"clientid" : self.simperium.clientID, + @"app_id" : self.simperium.appID, + @"token" : self.simperium.user.authToken, + @"name" : remoteBucketName, + @"library" : LIBRARY_ID, + @"version" : LIBRARY_VERSION + }; + + DDLogVerbose(@"Simperium initializing websocket channel %d:%@", channel.number, jsonData); + NSString *message = [NSString stringWithFormat:@"%d:init:%@", channel.number, [jsonData JSONString]]; + [self.webSocket send:message]; +} + +- (void)openWebSocket { + NSString *urlString = [NSString stringWithFormat:@"%@/%@/websocket", WEBSOCKET_URL, self.simperium.appID]; + SRWebSocket *newWebSocket = [[SRWebSocket alloc] initWithURLRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:urlString]]]; + self.webSocket = newWebSocket; + self.webSocket.delegate = self; + + DDLogVerbose(@"Simperium opening WebSocket connection..."); + [self.webSocket open]; +} + +- (void)start:(SPBucket *)bucket name:(NSString *)name { + //[self resetRetryDelay]; + + SPWebSocketChannel *channel = [self channelForName:bucket.name]; + if (!channel) + channel = [self loadChannelForBucket:bucket]; + + if (channel.started) + return; + + if (self.webSocket == nil) { + [self openWebSocket]; + // Channels will get setup after successfully connection + } else if (self.open) { + [self authenticateChannel:channel]; + } +} + +- (void)stop:(SPBucket *)bucket { + SPWebSocketChannel *channel = [self channelForName:bucket.name]; + channel.started = NO; + channel.webSocketManager = nil; + + // Can't remove the channel because it's needed for offline changes; this is weird and should be fixed + //[channels removeObjectForKey:bucket.name]; + + DDLogVerbose(@"Simperium stopping network manager (%@)", bucket.name); + + // Mark it closed so it doesn't reopen + self.open = NO; + [self.webSocket close]; + self.webSocket = nil; + + // TODO: Consider ensuring threads are done their work and sending a notification +} + +- (void)resetHeartbeatTimer { + if (self.heartbeatTimer != nil) { + [self.heartbeatTimer invalidate]; + } + self.heartbeatTimer = [NSTimer scheduledTimerWithTimeInterval:HEARTBEAT target:self selector:@selector(sendHeartbeat:) userInfo:nil repeats:NO]; +} + +- (void)send:(NSString *)message { + [self.webSocket send:message]; + [self resetHeartbeatTimer]; +} + +- (void)sendHeartbeat:(NSTimer *)timer { + if (self.webSocket.readyState == SR_OPEN) { + // Send it (will also schedule another one) + //NSLog(@"Simperium sending heartbeat"); + [self send:@"h:1"]; + } +} + +- (void)webSocketDidOpen:(SRWebSocket *)theWebSocket { + // Reconnection failsafe + if(theWebSocket != self.webSocket) { + return; + } + + self.open = YES; + [self startChannels]; + [self resetHeartbeatTimer]; +} + +- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error { + [self stopChannels]; + self.webSocket = nil; + self.open = NO; + + // Network enabled = YES: There was a networking glitch, yet, reachability flags are OK. We should retry + if (self.simperium.networkEnabled) { + DDLogVerbose(@"Simperium websocket failed (will retry) with error %@", error); + [self performSelector:@selector(openWebSocket) withObject:nil afterDelay:2]; + // Otherwise, the device lost reachability, and the interfaces were shut down by the framework + } else { + DDLogVerbose(@"Simperium websocket failed (will NOT retry) with error %@", error); + } +} + +- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message { + // Parse CHANNELNUM:COMMAND:DATA + NSRange range = [message rangeOfString:@":"]; + + if (range.location == NSNotFound) { + DDLogError(@"Simperium websocket received invalid message: %@", message); + return; + } + + NSString *channelStr = [message substringToIndex:range.location]; + + // Handle heartbeat + if ([channelStr isEqualToString:@"h"]) { + //DDLogVerbose(@"Simperium heartbeat acknowledged"); + return; + } + + DDLogVerbose(@"Simperium (%@) received \"%@\"", self.simperium.label, message); + + // It's an actual message; parse/handle it + NSNumber *channelNumber = [NSNumber numberWithInt:[channelStr intValue]]; + SPWebSocketChannel *channel = [self channelForNumber:channelNumber]; + SPBucket *bucket = [self.simperium bucketForName:channel.name]; + + NSString *commandStr = [message substringFromIndex:range.location+range.length]; + range = [commandStr rangeOfString:@":"]; + if (range.location == NSNotFound) { + DDLogWarn(@"Simperium received unrecognized websocket message: %@", message); + } + NSString *command = [commandStr substringToIndex:range.location]; + NSString *data = [commandStr substringFromIndex:range.location+range.length]; + + if ([command isEqualToString:COM_AUTH]) { + if ([data isEqualToString:@"expired"]) { + // Ignore this; legacy + } else if ([data isEqualToString:[self.simperium.user.email lowercaseString]]) { + channel.started = YES; + BOOL bFirstStart = bucket.lastChangeSignature == nil; + if (bFirstStart) { + [channel requestLatestVersionsForBucket:bucket]; + } else + [channel startProcessingChangesForBucket:bucket]; + } else { + DDLogWarn(@"Simperium received unexpected auth response: %@", data); + NSDictionary *authPayload = [data objectFromJSONStringWithParseOptions:JKParseOptionLooseUnicode]; + NSNumber *code = authPayload[@"code"]; + if ([code isEqualToNumber:@401]) { + // Let Simperium proper deal with it + [[NSNotificationCenter defaultCenter] postNotificationName:SPAuthenticationDidFail object:self]; + } + } + } else if ([command isEqualToString:COM_INDEX]) { + [channel handleIndexResponse:data bucket:bucket]; + } else if ([command isEqualToString:COM_CHANGE]) { + if ([data isEqualToString:@"?"]) { + // The requested change version didn't exist, so re-index + DDLogVerbose(@"Simperium change version is out of date (%@), re-indexing", bucket.name); + [channel requestLatestVersionsForBucket:bucket]; + } else { + // Incoming changes, handle them + NSArray *changes = [data objectFromJSONStringWithParseOptions:JKParseOptionLooseUnicode]; + [channel handleRemoteChanges: changes bucket:bucket]; + } + } else if ([command isEqualToString:COM_ENTITY]) { + // todo: handle ? if entity doesn't exist or it has been deleted + [channel handleVersionResponse:data bucket:bucket]; + } else if ([command isEqualToString:COM_ERROR]) { + DDLogVerbose(@"Simperium returned a command error (?) for bucket %@", bucket.name); + } +} + +- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean { + if (self.open) { + // Closed unexpectedly, retry + [self performSelector:@selector(openWebSocket) withObject:nil afterDelay:2]; + DDLogVerbose(@"Simperium connection closed (will retry): %ld, %@", (long)code, reason); + } else { + // Closed on purpose + DDLogInfo(@"Simperium connection closed"); + } + + [self stopChannels]; + self.webSocket = nil; + self.open = NO; +} + +-(void)resetBucketAndWait:(SPBucket *)bucket { + // Careful, this will block if the queue has work on it; however, enqueued tasks should empty quickly if the + // started flag is set to false + dispatch_sync(bucket.processorQueue, ^{ + [bucket.changeProcessor reset]; + }); + [bucket setLastChangeSignature:nil]; +} + +-(void)requestVersions:(int)numVersions object:(id)object { + SPWebSocketChannel *channel = [self channelForName:object.bucket.name]; + [channel requestVersions:numVersions object:object]; +} + +-(void)shareObject:(id)object withEmail:(NSString *)email { + SPWebSocketChannel *channel = [self channelForName:object.bucket.name]; + [channel shareObject:object withEmail:email]; +} + +-(void)requestLatestVersionsForBucket:(SPBucket *)b { + SPWebSocketChannel *channel = [self channelForName:b.name]; + [channel requestLatestVersionsForBucket:b]; +} + +-(void)forceSyncBucket:(SPBucket *)bucket { + // Let's reuse the start mechanism. This will post the latest CV + publish pending changes + SPWebSocketChannel *channel = [self channelForName:bucket.name]; + [channel startProcessingChangesForBucket:bucket]; +} + +@end diff --git a/Simperium/SPWebSocketManager.h b/Simperium/SPWebSocketManager.h deleted file mode 100644 index 7140b1b3..00000000 --- a/Simperium/SPWebSocketManager.h +++ /dev/null @@ -1,29 +0,0 @@ -// -// SPWebSocketManager -// Simperium -// -// Created by Michael Johnston on 12-08-06. -// Copyright 2011 Simperium. All rights reserved. -// - -#import -#import "SPNetworkProvider.h" - -@class Simperium; -@class SPBucket; -@class SRWebSocket; -@class SRWebSocketChannel; - -@interface SPWebSocketManager : NSObject { - BOOL open; - NSMutableArray *webSocketChannels; - SRWebSocket *webSocket; -} - -@property (nonatomic, retain) SRWebSocket *webSocket; - -+(void)setNetworkActivityIndicatorEnabled:(BOOL)enabled; --(id)initWithSimperium:(Simperium *)s appURL:(NSString *)url clientID:(NSString *)cid; --(void)loadChannelsForBuckets:(NSDictionary *)bucketList overrides:(NSDictionary *)overrides; - -@end \ No newline at end of file diff --git a/Simperium/SPWebSocketManager.m b/Simperium/SPWebSocketManager.m deleted file mode 100644 index a262d2ad..00000000 --- a/Simperium/SPWebSocketManager.m +++ /dev/null @@ -1,328 +0,0 @@ -// -// SPWebSocketManager -// Simperium -// -// Created by Michael Johnston on 11-03-07. -// Copyright 2011 Simperium. All rights reserved. -// -#import "SPWebSocketManager.h" -#import "Simperium.h" -#import "SPUser.h" -#import "SPBucket.h" -#import "JSONKit.h" -#import "NSString+Simperium.h" -#import "DDLog.h" -#import "DDLogDebug.h" -#import "SRWebSocket.h" -#import "SPWebSocketChannel.h" - -#define INDEX_PAGE_SIZE 500 -#define INDEX_BATCH_SIZE 10 -#define INDEX_QUEUE_SIZE 5 - -NSString * const COM_AUTH = @"auth"; -NSString * const COM_INDEX = @"i"; -NSString * const COM_CHANGE = @"c"; -NSString * const COM_ENTITY = @"e"; - -//static NSUInteger numTransfers = 0; -static BOOL useNetworkActivityIndicator = 0; -static int ddLogLevel = LOG_LEVEL_INFO; -NSString * const WebSocketAuthenticationDidFailNotification = @"AuthenticationDidFailNotification"; - -@interface SPWebSocketManager() -@property (nonatomic, assign) Simperium *simperium; -@property (nonatomic, retain) NSMutableDictionary *channels; -@property (nonatomic, copy) NSString *clientID; -@property (nonatomic, retain) NSDictionary *bucketNameOverrides; -@end - -@implementation SPWebSocketManager -@synthesize simperium; -@synthesize channels; -@synthesize clientID; -@synthesize webSocket; - -+ (int)ddLogLevel { - return ddLogLevel; -} - -+ (void)ddSetLogLevel:(int)logLevel { - ddLogLevel = logLevel; -} - -+ (void)updateNetworkActivityIndictator -{ -#if TARGET_OS_IPHONE -// BOOL visible = useNetworkActivityIndicator && numTransfers > 0; -// [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:visible]; - //DDLogInfo(@"Simperium numTransfers = %d", numTransfers); -#endif -} - -+ (void)setNetworkActivityIndicatorEnabled:(BOOL)enabled -{ - useNetworkActivityIndicator = enabled; -} - --(id)initWithSimperium:(Simperium *)s appURL:(NSString *)url clientID:(NSString *)cid -{ - if ((self = [super init])) { - self.simperium = s; - self.clientID = cid; - self.channels = [NSMutableDictionary dictionaryWithCapacity:20]; - } - - return self; -} - --(void)dealloc -{ - self.clientID = nil; - self.channels = nil; - self.webSocket.delegate = nil; - self.webSocket = nil; - [super dealloc]; -} - --(SPWebSocketChannel *)channelForName:(NSString *)str { - return [self.channels objectForKey:str]; -} - --(SPWebSocketChannel *)channelForNumber:(NSNumber *)num { - for (SPWebSocketChannel *channel in [self.channels allValues]) { - if ([num intValue] == channel.number) - return channel; - } - return nil; -} - --(SPWebSocketChannel *)loadChannelForBucket:(SPBucket *)bucket { - int channelNumber = [self.channels count]; - SPWebSocketChannel *channel = [[SPWebSocketChannel alloc] initWithSimperium:simperium clientID:clientID]; - channel.number = channelNumber; - channel.name = bucket.name; - [self.channels setObject:channel forKey:bucket.name]; - - return channel; -} - --(void)loadChannelsForBuckets:(NSDictionary *)bucketList overrides:(NSDictionary *)overrides { - self.bucketNameOverrides = overrides; - - for (SPBucket *bucket in [bucketList allValues]) - [self loadChannelForBucket:bucket]; -} - - --(void)sendObjectDeletion:(id)object -{ - SPWebSocketChannel *channel = [self channelForName:object.bucket.name]; - [channel sendObjectDeletion:object]; -} - --(void)sendObjectChanges:(id)object -{ - SPWebSocketChannel *channel = [self channelForName:object.bucket.name]; - [channel sendObjectChanges:object]; -} - - -//- (int)nextRetryDelay { -// int currentDelay = retryDelay; -// retryDelay *= 2; -// if (retryDelay > 24) -// retryDelay = 24; -// -// return currentDelay; -//} -// -//- (void)resetRetryDelay { -// retryDelay = 2; -//} - --(void)authenticationDidFail { - DDLogWarn(@"Simperium authentication failed for token %@", simperium.user.authToken); - [[NSNotificationCenter defaultCenter] postNotificationName:WebSocketAuthenticationDidFailNotification object:self]; -} - --(void)authenticateChannel:(SPWebSocketChannel *)channel { - // NSString *message = @"1:command:parameters"; - NSString *remoteBucketName = [self.bucketNameOverrides objectForKey:channel.name]; - if (!remoteBucketName || remoteBucketName.length == 0) - remoteBucketName = channel.name; - - NSDictionary *jsonData = [NSDictionary dictionaryWithObjectsAndKeys: - [NSNumber numberWithInt:1], @"api", - simperium.clientID, @"clientid", - simperium.appID, @"app_id", - simperium.user.authToken, @"token", - remoteBucketName, @"name", - //@"i", @"cmd", - nil]; - - DDLogVerbose(@"Simperium initializing websocket channel %d:%@", channel.number, remoteBucketName); - NSString *message = [NSString stringWithFormat:@"%d:init:%@", channel.number, [jsonData JSONString]]; - [self.webSocket send:message]; -} - --(void)openWebSocket -{ - NSString *url = @"wss://api.simperium.com/sock/websocket"; - SRWebSocket *newWebSocket = [[SRWebSocket alloc] initWithURLRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:url]]]; - self.webSocket = newWebSocket; - [newWebSocket release]; - self.webSocket.delegate = self; - - NSLog(@"Opening Connection..."); - [self.webSocket open]; -} - --(void)start:(SPBucket *)bucket name:(NSString *)name -{ - //[self resetRetryDelay]; - - SPWebSocketChannel *channel = [self channelForName:bucket.name]; - if (!channel) - channel = [self loadChannelForBucket:bucket]; - - if (self.webSocket == nil) { - [self openWebSocket]; - // Channels will get setup after successfully connection - } else if (open) { - [self authenticateChannel:channel]; - } -} - --(void)stop:(SPBucket *)bucket -{ - SPWebSocketChannel *channel = [self channelForName:bucket.name]; - channel.started = NO; - channel.webSocket = nil; - - // Can't remove the channel because it's needed for offline changes; this is weird and should be fixed - //[channels removeObjectForKey:bucket.name]; - - if (!open) - return; - - DDLogVerbose(@"Simperium stopping network manager (%@)", bucket.name); - - // Mark it closed so it doesn't reopen - open = NO; - [self.webSocket close]; - - // TODO: Consider ensuring threads are done their work and sending a notification -} - -- (void)webSocketDidOpen:(SRWebSocket *)webSocket; -{ - //NSLog(@"Websocket Connected"); - open = YES; - - // Start all channels - for (SPWebSocketChannel *channel in [self.channels allValues]) { - channel.webSocket = self.webSocket; - [self authenticateChannel:channel]; - } -} - -- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error; -{ - NSLog(@"Simperium websocket failed with error %@", error); - - self.webSocket = nil; - open = NO; -} - -// todo: send h:# for heartbeat - -- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message; -{ - //DDLogVerbose(@"Received \"%@\"", message); - - // Parse CHANNELNUM:COMMAND:DATA - NSRange range = [message rangeOfString:@":"]; - NSString *channelStr = [message substringToIndex:range.location]; - NSNumber *channelNumber = [NSNumber numberWithInt:[channelStr intValue]]; - SPWebSocketChannel *channel = [self channelForNumber:channelNumber]; - SPBucket *bucket = [self.simperium bucketForName:channel.name]; - - NSString *commandStr = [message substringFromIndex:range.location+range.length]; - range = [commandStr rangeOfString:@":"]; - if (range.location == NSNotFound) { - DDLogWarn(@"Simperium received unrecognized websocket message: %@", message); - } - NSString *command = [commandStr substringToIndex:range.location]; - NSString *data = [commandStr substringFromIndex:range.location+range.length]; - - if ([command isEqualToString:COM_AUTH]) { - // todo: handle "expired" - channel.started = YES; - BOOL bFirstStart = bucket.lastChangeSignature == nil; - if (bFirstStart) { - [channel getLatestVersionsForBucket:bucket]; - } else - [channel startProcessingChangesForBucket:bucket]; - } else if ([command isEqualToString:COM_INDEX]) { - // todo: handle ? if bad command - [channel handleIndexResponse:data bucket:bucket]; - } else if ([command isEqualToString:COM_CHANGE]) { - // todo: handle ? if cv doesn't exist - NSArray *changes = [data objectFromJSONStringWithParseOptions:JKParseOptionLooseUnicode]; - if ([changes count] > 0) { - DDLogVerbose(@"Simperium handling changes %@", changes); - [channel handleRemoteChanges: changes bucket:bucket]; - } - } else if ([command isEqualToString:COM_ENTITY]) { - // todo: handle ? if entity doesn't exist or it has been deleted - [channel handleVersionResponse:data bucket:bucket]; - } -} - -- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean; -{ - if (open) { - // Closed unexpectedly, retry - [self performSelector:@selector(openWebSocket) withObject:nil afterDelay:2]; - DDLogVerbose(@"Simperium connection closed (will retry): %d, %@", code, reason); - } else { - // Closed on purpose - DDLogInfo(@"Simperium connection closed"); - } - - self.webSocket = nil; - open = NO; -} - - --(void)resetBucketAndWait:(SPBucket *)bucket -{ - // Careful, this will block if the queue has work on it; however, enqueued tasks should empty quickly if the - // started flag is set to false - dispatch_sync(bucket.processorQueue, ^{ - [bucket.changeProcessor reset]; - }); - [bucket setLastChangeSignature:nil]; - -// numTransfers = 0; -// [[self class] updateNetworkActivityIndictator]; -} - --(void)getVersions:(int)numVersions forObject:(id)object -{ - SPWebSocketChannel *channel = [self channelForName:object.bucket.name]; - [channel getVersions:numVersions forObject:object]; -} - --(void)shareObject:(id)object withEmail:(NSString *)email -{ - SPWebSocketChannel *channel = [self channelForName:object.bucket.name]; - [channel shareObject:object withEmail:email]; -} - --(void)getLatestVersionsForBucket:(SPBucket *)b { - // Not yet implemented -} - -@end diff --git a/Simperium/Simperium-complete.h b/Simperium/Simperium-complete.h index a0c160d2..5f7d1923 100644 --- a/Simperium/Simperium-complete.h +++ b/Simperium/Simperium-complete.h @@ -336,7 +336,7 @@ extern NSString * const host; @see SPUser */ -@property (nonatomic,retain) SPUser *user; +@property (nonatomic,strong) SPUser *user; ///--------------------------------------------------------------------------------------- /// @name Getting the Simperium App Details @@ -355,9 +355,9 @@ extern NSString * const host; @property (nonatomic, readonly) NSString *accessKey; #if TARGET_OS_IPHONE -@property (nonatomic, assign) UIViewController *rootViewController; +@property (nonatomic, weak) UIViewController *rootViewController; #else -@property (nonatomic, assign) NSWindow *window; +@property (nonatomic, weak) NSWindow *window; #endif @property (nonatomic, copy) NSString *iconFilename; @@ -368,17 +368,17 @@ extern NSString * const host; ///--------------------------------------------------------------------------------------- /// The NSManagedObjectContext used by Simperium. -@property (nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext; +@property (nonatomic, strong, readonly) NSManagedObjectContext *managedObjectContext; /// The NSManagedObjectModel used by Simperium. -@property (nonatomic, retain, readonly) NSManagedObjectModel *managedObjectModel; +@property (nonatomic, strong, readonly) NSManagedObjectModel *managedObjectModel; /// The NSPersistentStoreCoordinator used by Simperium. -@property (nonatomic, retain, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator; +@property (nonatomic, strong, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator; // Not intended to be accessed directly -@property (nonatomic, retain) SPBinaryManager *binaryManager; -@property (nonatomic,assign,readonly) NSMutableSet *delegates; +@property (nonatomic, strong) SPBinaryManager *binaryManager; +@property (nonatomic,weak,readonly) NSMutableSet *delegates; @end diff --git a/Simperium/Simperium.h b/Simperium/Simperium.h index 03905065..97bbfa33 100644 --- a/Simperium/Simperium.h +++ b/Simperium/Simperium.h @@ -10,8 +10,9 @@ #import #import "SPBucket.h" #import "SPManagedObject.h" -#import "SPAuthenticationManager.h" +#import "SPAuthenticator.h" #import "SPUser.h" +#import "SPAuthenticationConfiguration.h" @class Simperium; @class SPBinaryManager; @@ -34,16 +35,22 @@ @end // The main class through which you access Simperium. -@interface Simperium : NSObject { +@interface Simperium : NSObject { SPUser *user; NSString *label; NSString *appID; NSString *APIKey; NSString *appURL; NSString *clientID; - id delegate; + id __weak delegate; SPBinaryManager *binaryManager; - Class loginViewControllerClass; + SPAuthenticator *authenticator; + +#if TARGET_OS_IPHONE + Class __weak authenticationViewControllerClass; +#else + Class __weak authWindowControllerClass; +#endif } // Initializes Simperium. @@ -56,21 +63,26 @@ // Starts Simperium with the given credentials (from simperium.com) and an existing Core Data stack. - (void)startWithAppID:(NSString *)identifier - APIKey:(NSString *)key - model:(NSManagedObjectModel *)model - context:(NSManagedObjectContext *)context - coordinator:(NSPersistentStoreCoordinator *)coordinator; + APIKey:(NSString *)key + model:(NSManagedObjectModel *)model + context:(NSManagedObjectContext *)context + coordinator:(NSPersistentStoreCoordinator *)coordinator; // Save and sync all changed objects. If you're using Core Data, this is just a convenience method // (you can also just save your context and Simperium will see the changes). - (BOOL)save; +// Force Simperium to sync all its buckets. Success return value will be false if the timeout is reached, and the sync wasn't completed. +typedef void (^SimperiumForceSyncCompletion)(BOOL success); +- (void)forceSyncWithTimeout:(NSTimeInterval)timeoutSeconds completion:(SimperiumForceSyncCompletion)completion; + // Get a particular bucket (which, for Core Data, corresponds to a particular Entity name in your model). // Once you have a bucket instance, you can set a SPBucketDelegate to react to changes. - (SPBucket *)bucketForName:(NSString *)name; // Convenience methods for accessing the Core Data stack. - (NSManagedObjectContext *)managedObjectContext; +- (NSManagedObjectContext *)writerManagedObjectContext; - (NSManagedObjectModel *)managedObjectModel; - (NSPersistentStoreCoordinator *)persistentStoreCoordinator; @@ -83,9 +95,6 @@ // Shares an object with a particular user's email address (forthcoming). //- (void)shareObject:(SPManagedObject *)object withEmail:(NSString *)email; -// Retrieve past versions of data for a particular object. -- (void)getVersions:(int)numVersions forObject:(SPManagedObject *)object; - // Alternative to setting delegates on each individual bucket (if you want a single handler // for everything). If you need to, call this after starting Simperium. - (void)setAllBucketDelegates:(id)aDelegate; @@ -97,62 +106,72 @@ - (NSString *)addBinary:(NSData *)binaryData toObject:(SPManagedObject *)object bucketName:(NSString *)bucketName attributeName:(NSString *)attributeName; - (void)addBinaryWithFilename:(NSString *)filename toObject:(SPManagedObject *)object bucketName:(NSString *)bucketName attributeName:(NSString *)attributeName; -/// Saves without syncing (typically not used). +// Saves without syncing (typically not used). - (BOOL)saveWithoutSyncing; +// Exports Simperium Internal logfiles. Note: you should enable verboseLogging before! +- (NSData*)exportLogfiles; + -/// Set this to true if you need to be able to cancel the authentication dialog. -@property (nonatomic, assign) BOOL authenticationOptional; +// Set this to true if you need to be able to cancel the authentication dialog. +@property (nonatomic) BOOL authenticationOptional; -/// A SimperiumDelegate for system callbacks. -@property (nonatomic,assign) id delegate; +// A SimperiumDelegate for system callbacks. +@property (nonatomic, weak) id delegate; -/// Toggle verbose logging. +// Toggle verbose logging. @property (nonatomic) BOOL verboseLoggingEnabled; -/// Enables or disables the network. +// Enables or disables the network. @property (nonatomic) BOOL networkEnabled; // Overrides the built-in authentication flow so you can customize the behavior. @property (nonatomic) BOOL authenticationEnabled; // Toggle websockets (should only be done before starting Simperium). -@property (nonatomic, assign) BOOL useWebSockets; +@property (nonatomic) BOOL useWebSockets; -/// Returns the currently authenticated Simperium user. -@property (nonatomic,retain) SPUser *user; +// Returns the currently authenticated Simperium user. +@property (nonatomic,strong) SPUser *user; -/// The full URL used to communicate with Simperium. +// The full URL used to communicate with Simperium. @property (nonatomic,readonly) NSString *appURL; -/// URL to a Simperium server (can be changed to point to a custom installation). +// URL to a Simperium server (can be changed to point to a custom installation). @property (nonatomic,copy) NSString *rootURL; -/// A unique ID for this app (configured at simperium.com). +// A unique ID for this app (configured at simperium.com). @property (nonatomic,readonly) NSString *appID; -/// An access token for this app (generated at simperium.com) +// An access token for this app (generated at simperium.com). @property (nonatomic, readonly) NSString *APIKey; -/// A hashed, unique ID for this client. -@property (nonatomic, readonly) NSString *clientID; +// A hashed, unique ID for this client. +@property (nonatomic, readonly, copy) NSString *clientID; -/// You can implement your own subclass of SPLoginViewController to customize authentication. -@property (nonatomic, assign) Class loginViewControllerClass; - -/// Set this if for some reason you want to use multiple Simperium instances (e.g. unit testing). +// Set this if for some reason you want to use multiple Simperium instances (e.g. unit testing). @property (copy) NSString *label; -/// Optional overrides (used for unit testing). +// You can implement your own subclass of SPAuthenticationViewController (iOS) or +// SPAuthenticationWindowController (OSX) to customize authentication. +#if TARGET_OS_IPHONE +@property (nonatomic, weak) Class authenticationViewControllerClass; +#else +@property (nonatomic, weak) Class authenticationWindowControllerClass; +#endif + +// Optional overrides (used for unit testing). @property (nonatomic, copy) NSDictionary *bucketOverrides; -@property (nonatomic, retain) SPBinaryManager *binaryManager; +@property (nonatomic, strong) SPBinaryManager *binaryManager; + +@property (nonatomic, strong) SPAuthenticator *authenticator; #if TARGET_OS_IPHONE -@property (nonatomic, assign) UIViewController *rootViewController; +@property (nonatomic, weak) UIViewController *rootViewController; #else -@property (nonatomic, assign) NSWindow *window; +@property (nonatomic, weak) NSWindow *window; #endif @end diff --git a/Simperium/Simperium.m b/Simperium/Simperium.m index 72157eb8..dd206a44 100644 --- a/Simperium/Simperium.m +++ b/Simperium/Simperium.m @@ -17,45 +17,45 @@ #import "SPDiffer.h" #import "SPGhost.h" #import "SPEnvironment.h" -#import "SPHttpManager.h" -#import "SPWebSocketManager.h" +#import "SPHttpInterface.h" +#import "SPWebSocketInterface.h" #import "ASIHTTPRequest.h" #import "JSONKit.h" #import "NSString+Simperium.h" #import "DDLog.h" #import "DDASLLogger.h" #import "DDTTYLogger.h" +#import "DDFileLogger+Simperium.h" #import "SPCoreDataStorage.h" -#import "SPAuthenticationManager.h" +#import "SPAuthenticator.h" #import "SPBucket.h" -#import "SPReferenceManager.h" -#import "Reachability.h" +#import "SPRelationshipResolver.h" +#import "SPReachability.h" #if TARGET_OS_IPHONE -#import "SPLoginViewController.h" +#import "SPAuthenticationViewController.h" #else -#import "SPAuthWindowController.h" +#import "SPAuthenticationWindowController.h" #endif @interface Simperium() -@property (nonatomic, retain) SPCoreDataStorage *coreDataStorage; -@property (nonatomic, retain) SPJSONStorage *JSONStorage; -@property (nonatomic, retain) NSMutableDictionary *buckets; -@property (nonatomic, retain) SPAuthenticationManager *authManager; -@property (nonatomic, retain) id network; -@property (nonatomic, retain) SPReferenceManager *referenceManager; -@property (nonatomic, assign) BOOL skipContextProcessing; -@property (nonatomic, assign) BOOL networkManagersStarted; -@property (nonatomic, assign) BOOL dynamicSchemaEnabled; -@property (nonatomic, retain) SPReachability *reachability; +@property (nonatomic, strong) SPCoreDataStorage *coreDataStorage; +@property (nonatomic, strong) SPJSONStorage *JSONStorage; +@property (nonatomic, strong) NSMutableDictionary *buckets; +@property (nonatomic, strong) id network; +@property (nonatomic, strong) SPRelationshipResolver *relationshipResolver; +@property (nonatomic) BOOL skipContextProcessing; +@property (nonatomic) BOOL networkManagersStarted; +@property (nonatomic) BOOL dynamicSchemaEnabled; +@property (nonatomic, strong) SPReachability *reachability; #if TARGET_OS_IPHONE -@property (nonatomic, retain) SPLoginViewController *loginViewController; +@property (nonatomic, strong) SPAuthenticationViewController *authenticationViewController; #else -@property (nonatomic, retain) SPAuthWindowController *authWindowController; +@property (nonatomic, strong) SPAuthenticationWindowController *authenticationWindowController; #endif -(BOOL)save; @@ -76,11 +76,10 @@ @implementation Simperium @synthesize networkEnabled = _networkEnabled; @synthesize authenticationEnabled = _authenticationEnabled; @synthesize useWebSockets = _useWebSockets; -@synthesize authManager; +@synthesize authenticator; @synthesize network; -@synthesize referenceManager; +@synthesize relationshipResolver; @synthesize binaryManager; -@synthesize loginViewControllerClass; @synthesize buckets; @synthesize appID; @synthesize APIKey; @@ -93,10 +92,12 @@ @implementation Simperium #if TARGET_OS_IPHONE @synthesize rootViewController; -@synthesize loginViewController; +@synthesize authenticationViewController; +@synthesize authenticationViewControllerClass; #else @synthesize window; -@synthesize authWindowController; +@synthesize authenticationWindowController; +@synthesize authenticationWindowControllerClass; #endif @@ -106,26 +107,34 @@ @implementation Simperium static int ddLogLevel = LOG_LEVEL_INFO; #endif -+ (int)ddLogLevel { ++(int)ddLogLevel +{ return ddLogLevel; } -+ (void)ddSetLogLevel:(int)logLevel { ++(void)ddSetLogLevel:(int)logLevel +{ ddLogLevel = logLevel; } ++(void)setupLogging +{ + // Handle multiple Simperium instances by ensuring logging only gets started once + static dispatch_once_t _once; + dispatch_once(&_once, ^{ + [DDLog addLogger:[DDASLLogger sharedInstance]]; + [DDLog addLogger:[DDTTYLogger sharedInstance]]; + [DDLog addLogger:[DDFileLogger sharedInstance]]; + }); +} + + #pragma mark - Constructors -(id)init { if ((self = [super init])) { - - // Handle multiple Simperium instances by ensuring logging only gets started once - static BOOL loggingStarted; - if (!loggingStarted) { - [DDLog addLogger:[DDASLLogger sharedInstance]]; - [DDLog addLogger:[DDTTYLogger sharedInstance]]; - loggingStarted = YES; - } + + [[self class] setupLogging]; self.label = @""; _networkEnabled = YES; @@ -135,19 +144,19 @@ -(id)init [ASIHTTPRequest setShouldUpdateNetworkActivityIndicator:NO]; self.buckets = [NSMutableDictionary dictionary]; - SPAuthenticationManager *manager = [[SPAuthenticationManager alloc] initWithDelegate:self simperium:self]; - self.authManager = manager; - [manager release]; + SPAuthenticator *auth = [[SPAuthenticator alloc] initWithDelegate:self simperium:self]; + self.authenticator = auth; - SPReferenceManager *refManager = [[SPReferenceManager alloc] init]; - self.referenceManager = refManager; - [refManager release]; + SPRelationshipResolver *resolver = [[SPRelationshipResolver alloc] init]; + self.relationshipResolver = resolver; #if TARGET_OS_IPHONE - loginViewControllerClass = [SPLoginViewController class]; -#endif + authenticationViewControllerClass = [SPAuthenticationViewController class]; +#else + authenticationWindowControllerClass = [SPAuthenticationWindowController class]; +#endif [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(authenticationDidFail) - name:@"AuthenticationDidFailNotification" object:nil]; + name:SPAuthenticationDidFail object:nil]; } return self; @@ -179,62 +188,36 @@ -(id)initWithWindow:(NSWindow *)aWindow -(void)dealloc { [self stopNetworking]; - self.buckets = nil; - self.binaryManager = nil; - self.user = nil; - self.authManager = nil; - self.coreDataStorage = nil; - self.JSONStorage = nil; - self.bucketOverrides = nil; - self.referenceManager = nil; self.rootURL = nil; - self.reachability = nil; - [appID release]; - [APIKey release]; - [appURL release]; - [label release]; -#if TARGET_OS_IPHONE - self.loginViewController = nil; -#else - self.authWindowController = nil; -#endif - - [super dealloc]; + [[NSNotificationCenter defaultCenter] removeObserver:self]; } -(void)setClientID:(NSString *)cid { - [clientID release]; clientID = [cid copy]; } -(NSString *)clientID { if (!clientID || clientID.length == 0) { - // Hashed UDID - // TODO: revisit due to iOS 5 deprecation NSString *agentPrefix; #if TARGET_OS_IPHONE - NSString *udid = [[UIDevice currentDevice] uniqueIdentifier]; agentPrefix = @"ios"; #else - // TODO: how should a Mac be identified? - NSString *udid = [NSString sp_makeUUID]; agentPrefix = @"osx"; #endif - clientID = [NSString sp_md5StringFromData:[udid dataUsingEncoding:NSUTF8StringEncoding]]; - clientID = [[NSString stringWithFormat:@"%@-%@",agentPrefix, clientID] copy]; + // Unique client ID per session is sufficient + NSString *uuid = [NSString sp_makeUUID]; + clientID = [[NSString stringWithFormat:@"%@-%@", agentPrefix, uuid] copy]; } return clientID; } -(void)setLabel:(NSString *)aLabel { - [label release]; label = [aLabel copy]; // Set the clientID as well, otherwise certain change operations won't work (since they'll appear to come from // the same Simperium instance) - [clientID release]; clientID = [label copy]; } @@ -281,17 +264,15 @@ -(SPBucket *)bucketForName:(NSString *)name { // Create and start a network manager for it SPSchema *schema = [[SPSchema alloc] initWithBucketName:name data:nil]; schema.dynamic = YES; - SPHttpManager *netManager = [[SPHttpManager alloc] initWithSimperium:self appURL:self.appURL clientID:self.clientID]; + SPHttpInterface *netManager = [[SPHttpInterface alloc] initWithSimperium:self appURL:self.appURL clientID:self.clientID]; // New buckets use JSONStorage by default (you can't manually create a Core Data bucket) - bucket = [[SPBucket alloc] initWithSchema:schema storage:self.JSONStorage networkProvider:network referenceManager:self.referenceManager label:self.label]; + bucket = [[SPBucket alloc] initWithSchema:schema storage:self.JSONStorage networkInterface:network + relationshipResolver:self.relationshipResolver label:self.label]; [netManager setBucket:bucket overrides:self.bucketOverrides]; [buckets setObject:bucket forKey:name]; [netManager start:bucket name:bucket.name]; - [bucket release]; - [netManager release]; - [schema release]; } else return nil; @@ -300,25 +281,35 @@ -(SPBucket *)bucketForName:(NSString *)name { return bucket; } --(void)getVersions:(int)numVersions forObject:(id)object -{ - SPBucket *bucket = [object bucket]; - [bucket.network getVersions: numVersions forObject: object]; -} - -(void)shareObject:(SPManagedObject *)object withEmail:(NSString *)email { SPBucket *bucket = [buckets objectForKey:object.bucket.name]; [bucket.network shareObject: object withEmail:email]; } --(void)setVerboseLoggingEnabled:(BOOL)on { +-(void)setVerboseLoggingEnabled:(BOOL)on +{ _verboseLoggingEnabled = on; for (Class cls in [DDLog registeredClasses]) { [DDLog setLogLevel:on ? LOG_LEVEL_VERBOSE : LOG_LEVEL_INFO forClass:cls]; } } +-(NSData*)exportLogfiles +{ + NSArray *logfiles = [[[DDFileLogger sharedInstance] logFileManager] sortedLogFilePaths]; + NSMutableData *export = [NSMutableData data]; + + for(NSString *path in logfiles) { + NSData *logfile = [NSData dataWithContentsOfFile:path]; + if(logfile.length) { + [export appendData:logfile]; + } + } + + return export; +} + -(void)startNetworkManagers { if (!self.networkEnabled || networkManagersStarted) @@ -347,23 +338,24 @@ -(void)stopNetworkManagers -(void)startNetworking { - //self.reachability = [SPReachability reachabilityForInternetConnection]; + // Create a new one each time to make sure it fires (and causes networking to start) self.reachability = [SPReachability reachabilityWithHostName:@"api.simperium.com"]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNetworkChange:) name:kReachabilityChangedNotification object:nil]; - [self.reachability startNotifier]; + [self.reachability startNotifier]; } -(void)stopNetworking { - [self.reachability stopNotifier]; [self stopNetworkManagers]; } -(void)handleNetworkChange:(NSNotification *)notification { - if ([self.reachability currentReachabilityStatus] == NotReachable) + + if ([self.reachability currentReachabilityStatus] == NotReachable) { [self stopNetworkManagers]; - else + } else if(self.user.authenticated) { [self startNetworkManagers]; + } } -(void)setNetworkEnabled:(BOOL)enabled @@ -394,23 +386,25 @@ -(NSMutableDictionary *)loadBuckets:(NSArray *)schemas if (self.useWebSockets) { // For websockets, one network manager for all buckets - if (!self.network) - self.network = [[SPWebSocketManager alloc] initWithSimperium:self appURL:self.appURL clientID:self.clientID]; - bucket = [[SPBucket alloc] initWithSchema:schema storage:self.coreDataStorage networkProvider:self.network referenceManager:self.referenceManager label:self.label]; + if (!self.network) { + SPWebSocketInterface *webSocketManager = [[SPWebSocketInterface alloc] initWithSimperium:self appURL:self.appURL clientID:self.clientID]; + self.network = webSocketManager; + } + bucket = [[SPBucket alloc] initWithSchema:schema storage:self.coreDataStorage networkInterface:self.network + relationshipResolver:self.relationshipResolver label:self.label]; } else { // For http, each bucket has its own network manager - SPHttpManager *netProvider = [[SPHttpManager alloc] initWithSimperium:self appURL:self.appURL clientID:self.clientID]; - bucket = [[SPBucket alloc] initWithSchema:schema storage:self.coreDataStorage networkProvider:netProvider referenceManager:self.referenceManager label:self.label]; - [(SPHttpManager *)netProvider setBucket:bucket overrides:self.bucketOverrides]; // tightly coupled for now; will fix in websockets netmanager - [netProvider release]; + SPHttpInterface *netInterface = [[SPHttpInterface alloc] initWithSimperium:self appURL:self.appURL clientID:self.clientID]; + bucket = [[SPBucket alloc] initWithSchema:schema storage:self.coreDataStorage networkInterface:netInterface + relationshipResolver:self.relationshipResolver label:self.label]; + [(SPHttpInterface *)netInterface setBucket:bucket overrides:self.bucketOverrides]; // tightly coupled for now; will fix in websockets netmanager } - + [bucketList setObject:bucket forKey:schema.bucketName]; - [bucket release]; } if (self.useWebSockets) { - [(SPWebSocketManager *)self.network loadChannelsForBuckets:bucketList overrides:self.bucketOverrides]; + [(SPWebSocketInterface *)self.network loadChannelsForBuckets:bucketList overrides:self.bucketOverrides]; } return bucketList; @@ -435,7 +429,6 @@ -(NSString *)rootURL { } -(void)setRootURL:(NSString *)url { - [_rootURL release]; _rootURL = [url copy]; appURL = [[_rootURL stringByAppendingFormat:@"%@/", appID] copy]; @@ -443,6 +436,12 @@ -(void)setRootURL:(NSString *)url { -(void)startWithAppID:(NSString *)identifier APIKey:(NSString *)key { DDLogInfo(@"Simperium starting... %@", label); + + // Enforce required parameters + NSParameterAssert(identifier); + NSParameterAssert(key); + + // Keep the keys! appID = [identifier copy]; APIKey = [key copy]; self.rootURL = SPBaseURL; @@ -450,7 +449,6 @@ -(void)startWithAppID:(NSString *)identifier APIKey:(NSString *)key { // Setup JSON storage SPJSONStorage *storage = [[SPJSONStorage alloc] initWithDelegate:self]; self.JSONStorage = storage; - [storage release]; // Network managers (load but don't start yet) //[self loadNetworkManagers]; @@ -466,15 +464,26 @@ -(void)startWithAppID:(NSString *)identifier APIKey:(NSString *)key { -(void)startWithAppID:(NSString *)identifier APIKey:(NSString *)key model:(NSManagedObjectModel *)model context:(NSManagedObjectContext *)context coordinator:(NSPersistentStoreCoordinator *)coordinator { DDLogInfo(@"Simperium starting... %@", label); + + // Enforce required parameters + NSParameterAssert(identifier); + NSParameterAssert(key); + NSParameterAssert(model); + NSParameterAssert(context); + NSParameterAssert(coordinator); + + NSAssert((context.concurrencyType == NSMainQueueConcurrencyType), NSLocalizedString(@"Error: you must initialize your context with 'NSMainQueueConcurrencyType' concurrency type.", nil)); + NSAssert((context.persistentStoreCoordinator == nil), NSLocalizedString(@"Error: NSManagedObjectContext's persistentStoreCoordinator must be nil. Simperium will handle CoreData connections for you.", nil)); + + // Keep the keys! appID = [identifier copy]; APIKey = [key copy]; self.rootURL = SPBaseURL; // Setup Core Data storage - SPCoreDataStorage *storage = [[SPCoreDataStorage alloc] initWithModel:model context:context coordinator:coordinator]; + SPCoreDataStorage *storage = [[SPCoreDataStorage alloc] initWithModel:model mainContext:context coordinator:coordinator]; self.coreDataStorage = storage; self.coreDataStorage.delegate = self; - [storage release]; // Get the schema from Core Data NSArray *schemas = [self.coreDataStorage exportSchemas]; @@ -485,8 +494,12 @@ -(void)startWithAppID:(NSString *)identifier APIKey:(NSString *)key model:(NSMan // Each NSManagedObject stores a reference to the bucket in which it's stored [self.coreDataStorage setBucketList: self.buckets]; - if (self.binaryManager) + // Load metadata for pending references among objects + [self.relationshipResolver loadPendingRelationships:self.coreDataStorage]; + + if (self.binaryManager) { [self configureBinaryManager:self.binaryManager]; + } // With everything configured, all objects can now be validated. This will pick up any objects that aren't yet // known to Simperium (for the case where you're adding Simperium to an existing app). @@ -517,7 +530,6 @@ -(void)storage:(id)storage updatedObjects:(NSSet *)updatedObj for (idobject in unsavedObjects) { [object.bucket.network sendObjectChanges: object]; } - [unsavedObjects release]; // Send changes for all unsaved, inserted and updated objects // The changes will automatically get batched and synced in the next tick @@ -550,6 +562,36 @@ -(BOOL)save return YES; } +- (void)forceSyncWithTimeout:(NSTimeInterval)timeoutSeconds completion:(SimperiumForceSyncCompletion)completion +{ + dispatch_group_t group = dispatch_group_create(); + __block BOOL notified = NO; + + // Sync every bucket + for(SPBucket* bucket in self.buckets.allValues) { + dispatch_group_enter(group); + [bucket forceSyncWithCompletion:^() { + dispatch_group_leave(group); + }]; + } + + // Wait until the workers are ready + dispatch_group_notify(group, dispatch_get_main_queue(), ^ { + if(!notified) { + completion(YES); + notified = YES; + } + }); + + // Notify anyways after timeout + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, timeoutSeconds * NSEC_PER_SEC), dispatch_get_main_queue(), ^(void){ + if(!notified) { + completion(NO); + notified = YES; + } + }); +} + -(BOOL)saveWithoutSyncing { skipContextProcessing = YES; BOOL result = [self save]; @@ -582,15 +624,18 @@ -(void)signOutAndRemoveLocalData:(BOOL)remove } // Clear the token and user - [authManager reset]; - [user release]; - user = nil; - + [authenticator reset]; + self.user = nil; + // Don't start network managers again; expect app to handle that } -(NSManagedObjectContext *)managedObjectContext { - return coreDataStorage.managedObjectContext; + return coreDataStorage.mainManagedObjectContext; +} + +-(NSManagedObjectContext *)writerManagedObjectContext { + return coreDataStorage.writerManagedObjectContext; } -(NSManagedObjectModel *)managedObjectModel { @@ -604,11 +649,6 @@ -(NSPersistentStoreCoordinator *)persistentStoreCoordinator { -(void)authenticationDidSucceedForUsername:(NSString *)username token:(NSString *)token { -#if TARGET_OS_IPHONE -#else - [self.window makeKeyAndOrderFront:nil]; -#endif - [binaryManager setupAuth:user]; // It's now safe to start the network managers @@ -619,14 +659,14 @@ -(void)authenticationDidSucceedForUsername:(NSString *)username token:(NSString -(void)authenticationDidCancel { [self stopNetworking]; - [self.authManager reset]; + [self.authenticator reset]; user.authToken = nil; [self closeAuthViewControllerAnimated:YES]; } -(void)authenticationDidFail { [self stopNetworking]; - [self.authManager reset]; + [self.authenticator reset]; user.authToken = nil; if (self.authenticationEnabled) @@ -641,7 +681,7 @@ -(BOOL)authenticateIfNecessary [self stopNetworking]; - return [self.authManager authenticateIfNecessary]; + return [self.authenticator authenticateIfNecessary]; } -(void)delayedOpenAuthViewController { @@ -651,11 +691,12 @@ -(void)delayedOpenAuthViewController { -(void)openAuthViewControllerAnimated:(BOOL)animated { #if TARGET_OS_IPHONE - if (self.loginViewController && self.rootViewController.presentedViewController == self.loginViewController) + if (self.authenticationViewController && self.rootViewController.presentedViewController == self.authenticationViewController) return; - self.loginViewController = [[self.loginViewControllerClass alloc] initWithNibName:@"LoginView" bundle:nil]; - self.loginViewController.authManager = self.authManager; + SPAuthenticationViewController *loginController = [[self.authenticationViewControllerClass alloc] init]; + self.authenticationViewController = loginController; + self.authenticationViewController.authenticator = self.authenticator; if (!self.rootViewController) { UIWindow *window = [[[UIApplication sharedApplication] windows] objectAtIndex:0]; @@ -663,36 +704,25 @@ -(void)openAuthViewControllerAnimated:(BOOL)animated NSAssert(self.rootViewController, @"Simperium error: to use built-in authentication, you must configure a rootViewController when you initialize Simperium, or call setParentViewControllerForAuthentication:. This is how Simperium knows where to present a modal view. See enableManualAuthentication in the documentation if you want to use your own authentication interface."); } - UIViewController *controller = self.loginViewController; + UIViewController *controller = self.authenticationViewController; + UINavigationController *navController = nil; if (self.authenticationOptional) { - UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController: self.loginViewController]; + navController = [[UINavigationController alloc] initWithRootViewController: self.authenticationViewController]; controller = navController; } - [self.rootViewController presentModalViewController:controller animated:animated]; - [controller release]; + [self.rootViewController presentViewController:controller animated:animated completion:nil]; #else - if (!authWindowController) { - SPAuthWindowController *anAuthWindowController = [[SPAuthWindowController alloc] initWithWindowNibName:@"AuthWindow"]; - anAuthWindowController.authManager = self.authManager; - - authWindowController = [anAuthWindowController retain]; - [anAuthWindowController release]; + if (!authenticationWindowController) { + authenticationWindowController = [[self.authenticationWindowControllerClass alloc] init]; + authenticationWindowController.authenticator = self.authenticator; + authenticationWindowController.optional = authenticationOptional; } - [[authWindowController window] center]; - [[authWindowController window] makeKeyAndOrderFront:self]; - - - // [NSApp beginSheet:[authWindowController window] - // modalForWindow:window - // modalDelegate:self - // didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) - // contextInfo:nil]; - // [NSApp runModalForWindow: [authWindowController window]]; - // // Dialog is up here. - // [NSApp endSheet: [authWindowController window]]; - // [[authWindowController window] orderOut: self]; + // Hide the main window and show the auth window instead + [self.window setIsVisible:NO]; + [[authenticationWindowController window] center]; + [[authenticationWindowController window] makeKeyAndOrderFront:self]; #endif } @@ -702,13 +732,15 @@ -(void)closeAuthViewControllerAnimated:(BOOL)animated NSArray *childViewControllers = self.rootViewController.presentedViewController.childViewControllers; // Login can either be its own root, or the first child of a nav controller if auth is optional - BOOL navLogin = [childViewControllers count] > 0 && [childViewControllers objectAtIndex:0] == self.loginViewController; - if ((self.rootViewController.presentedViewController == self.loginViewController && self.loginViewController) || navLogin) - [self.rootViewController dismissModalViewControllerAnimated:animated]; - self.loginViewController = nil; + BOOL navLogin = [childViewControllers count] > 0 && [childViewControllers objectAtIndex:0] == self.authenticationViewController; + if ((self.rootViewController.presentedViewController == self.authenticationViewController && self.authenticationViewController) || navLogin) { + [self.rootViewController dismissViewControllerAnimated:animated completion:nil]; + } + self.authenticationViewController = nil; #else - //[NSApp endSheet:[authWindowController window] returnCode:NSOKButton]; - [[authWindowController window] close]; + [self.window setIsVisible:YES]; + [[authenticationWindowController window] close]; + authenticationWindowController = nil; #endif } diff --git a/Simperium/UIImage+Simperium.m b/Simperium/UIImage+Simperium.m index 092046b1..e581cc07 100644 --- a/Simperium/UIImage+Simperium.m +++ b/Simperium/UIImage+Simperium.m @@ -16,7 +16,6 @@ @implementation FIXCATEGORYBUGIMAGE; @implementation UIImage (NSCoding) - (id)initWithCoder:(NSCoder *)decoder { NSData *pngData = [decoder decodeObjectForKey:@"PNGRepresentation"]; - [self autorelease]; self = [[UIImage alloc] initWithData:pngData]; return self; } diff --git a/SimperiumTests/Comment.h b/SimperiumTests/Comment.h index 348442a1..dffc04bf 100644 --- a/SimperiumTests/Comment.h +++ b/SimperiumTests/Comment.h @@ -14,7 +14,7 @@ @interface Comment : TestObject -@property (nonatomic, retain) NSString * content; -@property (nonatomic, retain) Post *post; +@property (nonatomic, strong) NSString * content; +@property (nonatomic, strong) Post *post; @end diff --git a/SimperiumTests/Config.h b/SimperiumTests/Config.h index 29162327..fc319845 100644 --- a/SimperiumTests/Config.h +++ b/SimperiumTests/Config.h @@ -23,14 +23,14 @@ NSDate *date; } -@property (nonatomic, retain) NSNumber *warpSpeed; -@property (nonatomic, retain) NSString *binaryFile; -@property (nonatomic, retain) NSString *captainsLog; -@property (nonatomic, retain) NSNumber *shieldsUp; -@property (nonatomic, retain) UIColor *shieldColor; -@property (nonatomic, retain) NSNumber *shieldPercent; -@property (nonatomic, retain) NSDecimalNumber *cost; -@property (nonatomic, retain) UIImage *smallImageTest; -@property (nonatomic, retain) NSDate *date; +@property (nonatomic, strong) NSNumber *warpSpeed; +@property (nonatomic, strong) NSString *binaryFile; +@property (nonatomic, strong) NSString *captainsLog; +@property (nonatomic, strong) NSNumber *shieldsUp; +@property (nonatomic, strong) UIColor *shieldColor; +@property (nonatomic, strong) NSNumber *shieldPercent; +@property (nonatomic, strong) NSDecimalNumber *cost; +@property (nonatomic, strong) UIImage *smallImageTest; +@property (nonatomic, strong) NSDate *date; @end diff --git a/SimperiumTests/DiffMatchPatchArrayTests.h b/SimperiumTests/DiffMatchPatchArrayTests.h new file mode 100644 index 00000000..bfcb6dd5 --- /dev/null +++ b/SimperiumTests/DiffMatchPatchArrayTests.h @@ -0,0 +1,13 @@ +// +// DiffMatchPatchArrayTests.h +// Simperium +// +// Created by Andrew Mackenzie-Ross on 19/07/13. +// Copyright (c) 2013 Simperium. All rights reserved. +// + +#import + +@interface DiffMatchPatchArrayTests : SenTestCase + +@end diff --git a/SimperiumTests/DiffMatchPatchArrayTests.m b/SimperiumTests/DiffMatchPatchArrayTests.m new file mode 100644 index 00000000..687fc066 --- /dev/null +++ b/SimperiumTests/DiffMatchPatchArrayTests.m @@ -0,0 +1,83 @@ +// +// DiffMatchPatchArrayTests.m +// Simperium +// +// Created by Andrew Mackenzie-Ross on 19/07/13. +// Copyright (c) 2013 Simperium. All rights reserved. +// + +#import "DiffMatchPatchArrayTests.h" +#import "DiffMatchPatch.h" +#import "NSArray+Simperium.h" + +@interface DiffMatchPatchArrayTests () +{ + DiffMatchPatch *_dmp; +} + +@end + +@implementation DiffMatchPatchArrayTests + +- (void)setUp +{ + _dmp = [[DiffMatchPatch alloc] init]; +} + +- (void)testDiffAndApply +{ + NSDictionary *testsOneElementOperationPairs = @{ + @[ @"a" ]: @[ @1 ] , // replace one element + @[ @"a" ]: @[], // remove element + @[]: @[ @"a" ], // add element to empty array + @[ @"a" ]: @[ @"a", @1 ], // add element to existing + @[ @"a", @1 ]: @[ @"a" ], // remove last element + @[ @"a", @1 ]: @[ @1 ], // remove first element + }; + NSDictionary *testTwoElementOperationsPairs = @{ + @[ @"a", @1 ]: @[ @YES, @1 ], // replace first element + @[ @"a", @1 ]: @[ @"a" , @2 ], // replace last element + @[ @"a", @1 ]: @[ @1, @"a" ], // inverse two elements + @[ @"a", @1 ]: @[ @"b", @2 ], // two new elements + @[ @"a", @1 ]: @[ @"b", @"a", @1 ], // insert new element at head + @[ @"b", @"a", @1 ]: @[ @"a", @1 ], // remove element from head + @[ @"a", @1 ]: @[ @"a", @"b", @1 ], // insert element in midde + @[ @"a", @"b", @1 ]: @[ @"a", @1 ], // remove element from middle + @[ @"a", @1 ]: @[ @"a", @1, @"b" ], // insert element at tail + @[ @"a", @1, @"b" ]: @[ @"a", @1 ], // remove element from tail + @[] : @[ @"a", @1 ], // insert two elements + @[ @"a", @1 ]: @[] // remove two elements + }; + + NSDictionary *testThreeElementOperationsPairs = @{ + @[ @"a", @1 ]: @[ @YES, @1 ], // replace first element + @[ @"a", @1, [NSNull null] ]: @[ @"a" , @2, @[ @YES, @"NO", @{ @"something" : @"with objects" } ] ], // replace last element + @[ @"a", @1, [NSNull null] ]: @[ @1, @"a", [NSNull null] ], // inverse two elements + }; + + [testsOneElementOperationPairs enumerateKeysAndObjectsUsingBlock:^(id array1, id array2, BOOL *stop) { + STAssertEqualObjects(array1, [self createDiffAndApplyDiffArray1:array2 array2:array1], @"replace diff 1 element"); + }]; + [testTwoElementOperationsPairs enumerateKeysAndObjectsUsingBlock:^(id array1, id array2, BOOL *stop) { + STAssertEqualObjects(array1, [self createDiffAndApplyDiffArray1:array2 array2:array1], @"replace diff 1 element"); + }]; + + [testThreeElementOperationsPairs enumerateKeysAndObjectsUsingBlock:^(id array1, id array2, BOOL *stop) { + STAssertEqualObjects(array1, [self createDiffAndApplyDiffArray1:array2 array2:array1], @"replace diff 1 element"); + }]; +} + +- (void)testReplaceDiffFirstOfTwoElements +{ + +} + +- (NSArray *)createDiffAndApplyDiffArray1:(NSArray *)array1 array2:(NSArray *)array2 +{ + NSString *delta = [array1 sp_diffDeltaWithArray:array2 diffMatchPatch:_dmp]; + return [array1 sp_arrayByApplyingDiffDelta:delta diffMatchPatch:_dmp]; +} + + + +@end diff --git a/SimperiumTests/Farm.h b/SimperiumTests/Farm.h index a6b7e1ba..aed1263f 100644 --- a/SimperiumTests/Farm.h +++ b/SimperiumTests/Farm.h @@ -21,19 +21,23 @@ int expectedAdditions; int expectedDeletions; int expectedChanges; + int expectedVersions; + int expectedIndexCompletions; } -@property (nonatomic, retain) Simperium *simperium; -@property (nonatomic, retain) Config *config; +@property (nonatomic, strong) Simperium *simperium; +@property (nonatomic, strong) Config *config; @property (nonatomic, copy) NSString *token; @property (nonatomic) BOOL done; @property (nonatomic) int expectedAcknowledgments; @property (nonatomic) int expectedAdditions; @property (nonatomic) int expectedDeletions; @property (nonatomic) int expectedChanges; -@property (nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext; -@property (nonatomic, retain, readonly) NSManagedObjectModel *managedObjectModel; -@property (nonatomic, retain, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator; +@property (nonatomic) int expectedVersions; +@property (nonatomic) int expectedIndexCompletions; +@property (nonatomic, strong, readonly) NSManagedObjectContext *managedObjectContext; +@property (nonatomic, strong, readonly) NSManagedObjectModel *managedObjectModel; +@property (nonatomic, strong, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator; -(id)initWithToken:(NSString *)token bucketOverrides:(NSDictionary *)bucketOverrides label:(NSString *)label; -(void)start; @@ -41,7 +45,8 @@ -(void)disconnect; -(BOOL)waitForCompletion:(NSTimeInterval)timeoutSecs; -(BOOL)isDone; -- (void) logUnfulfilledExpectations; +-(void)resetExpectations; +-(void)logUnfulfilledExpectations; @end diff --git a/SimperiumTests/Farm.m b/SimperiumTests/Farm.m index 9afd044b..1dfa6a23 100644 --- a/SimperiumTests/Farm.m +++ b/SimperiumTests/Farm.m @@ -19,6 +19,8 @@ @implementation Farm @synthesize expectedAdditions; @synthesize expectedChanges; @synthesize expectedDeletions; +@synthesize expectedVersions; +@synthesize expectedIndexCompletions; @synthesize managedObjectContext = __managedObjectContext; @synthesize managedObjectModel = __managedObjectModel; @synthesize persistentStoreCoordinator = __persistentStoreCoordinator; @@ -29,6 +31,8 @@ - (id)initWithToken:(NSString *)aToken bucketOverrides:(NSDictionary *)bucketOve done = NO; self.simperium = [[Simperium alloc] initWithRootViewController:nil]; + // Some stuff is stored in user prefs / keychain, so be sure to remove it + [simperium signOutAndRemoveLocalData:YES]; // Setting a label allows each Simperium instance to store user prefs under a different key // (be sure to do this before the call to clearLocalData) @@ -37,7 +41,7 @@ - (id)initWithToken:(NSString *)aToken bucketOverrides:(NSDictionary *)bucketOve [simperium setAuthenticationEnabled:NO]; [simperium setBucketOverrides:bucketOverrides]; [simperium setVerboseLoggingEnabled:YES]; - simperium.useWebSockets = NO; + simperium.useWebSockets = YES; self.token = aToken; } return self; @@ -48,27 +52,25 @@ - (void)start { //[simperium startWithAppName:APP_ID APIKey:API_KEY]; // Core Data testing - [simperium startWithAppID:APP_ID APIKey:API_KEY model:[self managedObjectModel] - context:[self managedObjectContext] coordinator:[self persistentStoreCoordinator]]; + [simperium startWithAppID:APP_ID + APIKey:API_KEY + model:[self managedObjectModel] + context:[self managedObjectContext] + coordinator:[self persistentStoreCoordinator]]; [simperium setAllBucketDelegates: self]; - // Some stuff is stored in user prefs, so be sure to remove it - // (needs to be done after Simperium and its network managers have been started) - //[simperium clearLocalData]; - simperium.user = [[SPUser alloc] initWithEmail:USERNAME token:token]; + for (NSString *bucketName in [simperium.bucketOverrides allKeys]) { + [simperium bucketForName:bucketName].notifyWhileIndexing = YES; + } } -- (void) dealloc { - [simperium clearLocalData]; - [simperium release]; - [config release]; - [super dealloc]; +- (void)dealloc { + [simperium signOutAndRemoveLocalData:YES]; } -- (BOOL)waitForCompletion:(NSTimeInterval)timeoutSecs -{ +- (BOOL)waitForCompletion:(NSTimeInterval)timeoutSecs { NSDate *timeoutDate = [NSDate dateWithTimeIntervalSinceNow:timeoutSecs]; do { @@ -80,30 +82,43 @@ - (BOOL)waitForCompletion:(NSTimeInterval)timeoutSecs return done; } -- (BOOL) isDone { - return expectedAcknowledgments == 0 && expectedChanges == 0 && expectedAdditions == 0 && expectedDeletions == 0; +- (BOOL)isDone { + return expectedAcknowledgments == 0 && expectedChanges == 0 && expectedAdditions == 0 && expectedDeletions == 0 + && expectedVersions == 0 && expectedIndexCompletions == 0; +} + +- (void)resetExpectations { + self.expectedAcknowledgments = 0; + self.expectedAdditions = 0; + self.expectedChanges = 0; + self.expectedDeletions = 0; + self.expectedVersions = 0; + self.expectedIndexCompletions = 0; } -- (void) logUnfulfilledExpectations { +- (void)logUnfulfilledExpectations { if (![self isDone]) - NSLog(@"acks: %d changes: %d adds: %d dels: %d", expectedAcknowledgments, expectedChanges, expectedAdditions, expectedDeletions); + NSLog(@"acks: %d changes: %d adds: %d dels: %d idxs: %d", expectedAcknowledgments, expectedChanges, expectedAdditions, + expectedDeletions, expectedIndexCompletions); } -- (void) connect { +- (void)connect { [simperium performSelector:@selector(startNetworkManagers)]; } -- (void) disconnect { +- (void)disconnect { [simperium performSelector:@selector(stopNetworkManagers)]; } --(void)bucket:(SPBucket *)bucket didChangeObjectForKey:(NSString *)key forChangeType:(SPBucketChangeType)change { +-(void)bucket:(SPBucket *)bucket didChangeObjectForKey:(NSString *)key forChangeType:(SPBucketChangeType)change memberNames:(NSArray *)memberNames { switch(change) { case SPBucketChangeAcknowledge: expectedAcknowledgments -= 1; +// NSLog(@"%@ acknowledged (%d)", simperium.label, expectedAcknowledgments); break; case SPBucketChangeDelete: expectedDeletions -= 1; +// NSLog(@"%@ received deletion (%d)", simperium.label, expectedDeletions); break; case SPBucketChangeInsert: expectedAdditions -= 1; @@ -113,23 +128,28 @@ -(void)bucket:(SPBucket *)bucket didChangeObjectForKey:(NSString *)key forChange } } --(void)bucket:(SPBucket *)bucket willChangeObjectsForKeys:(NSSet *)keys { +- (void)bucket:(SPBucket *)bucket willChangeObjectsForKeys:(NSSet *)keys { } --(void)bucketWillStartIndexing:(SPBucket *)bucket { +- (void)bucketWillStartIndexing:(SPBucket *)bucket { } --(void)bucketDidFinishIndexing:(SPBucket *)bucket { +- (void)bucketDidFinishIndexing:(SPBucket *)bucket { + NSLog(@"Simperium bucketDidFinishIndexing: %@", bucket.name); + // These aren't always used in the tests, so only decrease it if it's been set + if (expectedIndexCompletions > 0) + expectedIndexCompletions -= 1; } --(void)bucketDidAcknowledgeDelete:(SPBucket *)bucket { +- (void)bucketDidAcknowledgeDelete:(SPBucket *)bucket { expectedAcknowledgments -= 1; +// NSLog(@"%@ acknowledged deletion (%d)", simperium.label, expectedAcknowledgments); } --(void)bucket:(SPBucket *)bucket didReceiveObjectForKey:(NSString *)key version:(NSString *)version data:(NSDictionary *)data { - +- (void)bucket:(SPBucket *)bucket didReceiveObjectForKey:(NSString *)key version:(NSString *)version data:(NSDictionary *)data { + expectedVersions -= 1; } @@ -162,13 +182,7 @@ - (NSManagedObjectContext *)managedObjectContext { return __managedObjectContext; } - - NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator]; - if (coordinator != nil) - { - __managedObjectContext = [[NSManagedObjectContext alloc] init]; - [__managedObjectContext setPersistentStoreCoordinator:coordinator]; - } + __managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; return __managedObjectContext; } @@ -182,7 +196,7 @@ - (NSManagedObjectModel *)managedObjectModel { return __managedObjectModel; } - __managedObjectModel = [[NSManagedObjectModel mergedModelFromBundles:[NSBundle allBundles]] retain]; + __managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:[NSBundle allBundles]]; return __managedObjectModel; } diff --git a/SimperiumTests/Post.h b/SimperiumTests/Post.h index a3ec94fb..b25e9fa0 100644 --- a/SimperiumTests/Post.h +++ b/SimperiumTests/Post.h @@ -14,8 +14,8 @@ @interface Post : TestObject -@property (nonatomic, retain) NSString * title; -@property (nonatomic, retain) NSSet *comments; +@property (nonatomic, strong) NSString * title; +@property (nonatomic, strong) NSSet *comments; @end @interface Post (CoreDataGeneratedAccessors) diff --git a/SimperiumTests/SimperiumAuxiliaryTests.h b/SimperiumTests/SimperiumAuxiliaryTests.h new file mode 100644 index 00000000..787b595d --- /dev/null +++ b/SimperiumTests/SimperiumAuxiliaryTests.h @@ -0,0 +1,14 @@ +// +// SimperiumAuxiliaryTests.h +// SimperiumAuxiliaryTests +// +// Created by Michael Johnston on 11-04-19. +// Copyright 2011 Simperium. All rights reserved. +// + +#import "SimperiumTests.h" + +@interface SimperiumAuxiliaryTests : SimperiumTests { +} + +@end diff --git a/SimperiumTests/SimperiumAuxiliaryTests.m b/SimperiumTests/SimperiumAuxiliaryTests.m new file mode 100644 index 00000000..450423d0 --- /dev/null +++ b/SimperiumTests/SimperiumAuxiliaryTests.m @@ -0,0 +1,98 @@ +// +// SimperiumAuxiliaryTests.m +// SimperiumAuxiliaryTests +// +// Created by Michael Johnston on 11-04-19. +// Copyright 2011 Simperium. All rights reserved. +// + +#import "SimperiumAuxiliaryTests.h" +#import "Config.h" +#import "Farm.h" +#import "SPBucket.h" +#import "DiffMatchPatch.h" + +@implementation SimperiumAuxiliaryTests + +-(void)testDMP +{ + NSString *a = @"a"; + NSString *b = @"ab"; + NSString *c = @"ac"; + + // Assorted hocus pocus ported from JS code + NSError *error; + DiffMatchPatch *dmp = [[DiffMatchPatch alloc] init]; + + NSMutableArray * ac_diff =[dmp diff_mainOfOldString:a andNewString:c]; + NSMutableArray * ab_diff = [dmp diff_mainOfOldString:a andNewString:b]; + + NSString *ac_diffs_delta = [dmp diff_toDelta:ac_diff]; + NSString *ab_diffs_delta = [dmp diff_toDelta:ab_diff]; + + NSMutableArray *ac_diffs = [dmp diff_fromDeltaWithText:a andDelta:ac_diffs_delta error:&error]; + NSMutableArray *ac_patches = [dmp patch_makeFromOldString:a andDiffs:ac_diffs]; + NSLog(@"ac_diffs:%@", [ac_diffs description]); + NSLog(@"ac_patches:%@", [ac_patches description]); + + NSMutableArray *ab_diffs = [dmp diff_fromDeltaWithText:a andDelta:ab_diffs_delta error:&error]; + NSMutableArray *ab_patches = [dmp patch_makeFromOldString:a andDiffs:ab_diffs]; + NSLog(@"ab_diffs:%@", [ab_diffs description]); + NSLog(@"ab_patches:%@", [ab_patches description]); + + + + NSArray *ac_patch_apply = [dmp patch_apply:ac_patches toString:a]; + NSLog(@"ac_patch_apply: %@", [ac_patch_apply description]); + NSString *interim_text = [[dmp patch_apply:ac_patches toString:a] objectAtIndex:0]; + NSLog(@"interim: %@, c:%@", interim_text, c); + + NSString *final_text = [[dmp patch_apply:ab_patches toString:interim_text] objectAtIndex:0]; + NSLog(@"final: %@", final_text); +} + +//- (void)testSeededData +//{ +// NSLog(@"%@ start", self.name); +// [self createFarms]; +// +// // Leader seeds an object +// Farm *leader = [farms objectAtIndex:0]; +// +// NSNumber *refWarpSpeed = [NSNumber numberWithInt:2]; +// leader.config = [NSEntityDescription insertNewObjectForEntityForName:@"Config" inManagedObjectContext:leader.managedObjectContext]; +// //leader.config.simperiumKey = @"config"; +// leader.config.warpSpeed = refWarpSpeed; +// +// [leader.managedObjectContext save:nil]; +// +// // Now go online +// leader.simperium.networkEnabled = NO; +// +// // Problem: the above changes are marked by simperium, but starting the farm here will clear those changes +// // Solution? Add an alternative start: that doesn't clear? +// [leader start]; +// leader.simperium.networkEnabled = YES; +// [leader connect]; +// +// leader.expectedAcknowledgments = 1; +// STAssertTrue([self waitForCompletion], @"timed out"); +// +// // The object was synced, now check followers to see if data was fully seeded +// for (Farm *farm in farms) { +// if (farm == leader) +// continue; +// [farm start]; +// [farm connect]; +// } +// [self resetExpectations: farms]; +// [self expectAdditions:1 deletions:0 changes:0 fromLeader:leader expectAcks:NO]; +// +// STAssertTrue([self waitForCompletion], @"timed out"); +// +// [self ensureFarmsEqual:farms entityName:@"Config"]; +// NSLog(@"%@ end", self.name); +//} + + +@end diff --git a/SimperiumTests/SimperiumSimpleTests.h b/SimperiumTests/SimperiumBasicTests.h similarity index 53% rename from SimperiumTests/SimperiumSimpleTests.h rename to SimperiumTests/SimperiumBasicTests.h index c155906c..3a1c6f7a 100644 --- a/SimperiumTests/SimperiumSimpleTests.h +++ b/SimperiumTests/SimperiumBasicTests.h @@ -1,6 +1,6 @@ // -// SimperiumSimpleTests.h -// SimperiumSimpleTests +// SimperiumBasicTests.h +// SimperiumBasicTests // // Created by Michael Johnston on 11-04-19. // Copyright 2011 Simperium. All rights reserved. @@ -8,7 +8,7 @@ #import "SimperiumTests.h" -@interface SimperiumSimpleTests : SimperiumTests { +@interface SimperiumBasicTests : SimperiumTests { } @end diff --git a/SimperiumTests/SimperiumBasicTests.m b/SimperiumTests/SimperiumBasicTests.m new file mode 100644 index 00000000..e5888d49 --- /dev/null +++ b/SimperiumTests/SimperiumBasicTests.m @@ -0,0 +1,203 @@ +// +// SimperiumBasicTests.m +// SimperiumBasicTests +// +// Created by Michael Johnston on 11-04-19. +// Copyright 2011 Simperium. All rights reserved. +// + +#import "SimperiumBasicTests.h" +#import "Config.h" +#import "Farm.h" +#import "SPBucket.h" +#import "DiffMatchPatch.h" + +@implementation SimperiumBasicTests + +- (void)testAuth +{ + NSLog(@"%@ start", self.name); + STAssertTrue(token.length > 0, @""); + NSLog(@"token is %@", token); + NSLog(@"%@ end", self.name); +} + +- (void)testAddingSingleObject +{ + NSLog(@"%@ start", self.name); + [self createAndStartFarms]; + + // Leader sends an object to followers + Farm *leader = [farms objectAtIndex:0]; + [self connectFarms]; + + NSNumber *refWarpSpeed = [NSNumber numberWithInt:2]; + SPBucket *leaderBucket = [leader.simperium bucketForName:@"Config"]; + leaderBucket.delegate = leader; + leader.config = [leaderBucket insertNewObject]; + [leader.config setValue:refWarpSpeed forKey:@"warpSpeed"]; + //leader.config.warpSpeed = refWarpSpeed; + [leader.simperium save]; + [self expectAdditions:1 deletions:0 changes:0 fromLeader:leader expectAcks:YES]; + STAssertTrue([self waitForCompletion], @"timed out"); +// STAssertTrue([leader.config.warpSpeed isEqualToNumber: refWarpSpeed], @""); + STAssertTrue([[leader.config valueForKey:@"warpSpeed"] isEqualToNumber: refWarpSpeed], @""); + + // This is failing for the JSON case because the follower farms don't know what bucket to start listening + // to. This can be worked around by adding a special prep method to farms. However they'll still fail because + // I need to add dynamic schema support to the REMOTE ADD and REMOTE MODIFY cases as well, so that followers + // can consruct their schemas as new data comes off the wire. + + [self ensureFarmsEqual:farms entityName:@"Config"]; + + NSLog(@"%@ end", self.name); +} + +- (void)testDeletingSingleObject +{ + NSLog(@"%@ start", self.name); + [self createAndStartFarms]; + + // Leader sends an object to followers, then removes it + Farm *leader = [farms objectAtIndex:0]; + [self connectFarms]; + + SPBucket *bucket = [leader.simperium bucketForName:@"Config"]; + leader.config = [bucket insertNewObject]; + leader.config.warpSpeed = [NSNumber numberWithInt:2]; + [leader.simperium save]; + NSString *configKey = [leader.config.simperiumKey copy]; + [self expectAdditions:1 deletions:0 changes:0 fromLeader:leader expectAcks:YES]; + STAssertTrue([self waitForCompletion], @"timed out (adding)"); + + [bucket deleteObject:leader.config]; + [leader.simperium save]; + [self expectAdditions:0 deletions:1 changes:0 fromLeader:leader expectAcks:YES]; + STAssertTrue([self waitForCompletion], @"timed out (deleting)"); + + int i=0; + for (Farm *farm in farms) { + farm.config = (Config *)[[farm.simperium bucketForName:@"Config"] objectForKey:configKey]; + STAssertNil(farm.config, @"config %d wasn't deleted: %@", i, farm.config); + i += 1; + } + NSLog(@"%@ end", self.name); +} + +- (void)testChangesToSingleObject +{ + NSLog(@"%@ start", self.name); + [self createAndStartFarms]; + + // Leader sends an object to followers, then changes multiple fields + Farm *leader = [farms objectAtIndex:0]; + [self connectFarms]; + [self waitFor:1.0]; + + leader.config = [[leader.simperium bucketForName:@"Config"] insertNewObject]; + leader.config.warpSpeed = [NSNumber numberWithInt:2]; + leader.config.captainsLog = @"Hi"; + leader.config.shieldPercent = [NSNumber numberWithFloat:3.14]; + leader.config.cost = [NSDecimalNumber decimalNumberWithString:@"3.00"]; + [leader.simperium save]; + [self expectAdditions:1 deletions:0 changes:0 fromLeader:leader expectAcks:YES]; + STAssertTrue([self waitForCompletion], @"timed out (adding)"); + STAssertNotNil(leader.config.ghostData, @""); + + NSNumber *refWarpSpeed = [NSNumber numberWithInt:4]; + NSString *refCaptainsLog = @"Hi!!!"; + NSNumber *refShieldPercent = [NSNumber numberWithFloat:2.718]; + NSDecimalNumber *refCost = [NSDecimalNumber decimalNumberWithString:@"4.00"]; + leader.config.warpSpeed = refWarpSpeed; + leader.config.captainsLog = refCaptainsLog; + leader.config.shieldPercent = refShieldPercent; + leader.config.cost = refCost; + [leader.simperium save]; + [self expectAdditions:0 deletions:0 changes:1 fromLeader:leader expectAcks:YES]; + STAssertTrue([self waitForCompletion], @"timed out (changing)"); + + STAssertTrue([refWarpSpeed isEqualToNumber: leader.config.warpSpeed], @""); + STAssertTrue([refCaptainsLog isEqualToString: leader.config.captainsLog], @""); + STAssertTrue([refShieldPercent isEqualToNumber: leader.config.shieldPercent], @""); + STAssertTrue([refCost isEqualToNumber: leader.config.cost], @""); + + [self ensureFarmsEqual:farms entityName:@"Config"]; + NSLog(@"%@ end", self.name); +} + +- (void)testPendingChange +{ + NSLog(@"%@ start", self.name); + [self createAndStartFarms]; + + // Leader sends an object to followers, then changes multiple fields + Farm *leader = [farms objectAtIndex:0]; + [self connectFarms]; + + [self waitFor:1]; + + leader.config = [[leader.simperium bucketForName:@"Config"] insertNewObject]; + leader.config.warpSpeed = [NSNumber numberWithInt:2]; + leader.config.captainsLog = @"Hi"; + leader.config.shieldPercent = [NSNumber numberWithFloat:3.14]; + [leader.simperium save]; + [self expectAdditions:1 deletions:0 changes:0 fromLeader:leader expectAcks:YES]; + + // Wait just enough time for the change to be sent, but not enough time for an ack to come back + // (a better test will be to send a bunch of changes with random delays from 0..1s) + [self waitFor:0.01]; + + // Now change right away without waiting for the object insertion to be acked + NSNumber *refWarpSpeed = [NSNumber numberWithInt:4]; + NSString *refCaptainsLog = @"Hi!!!"; + NSNumber *refShieldPercent = [NSNumber numberWithFloat:2.718]; + leader.config.warpSpeed = refWarpSpeed; + leader.config.captainsLog = refCaptainsLog; + leader.config.shieldPercent = refShieldPercent; + [leader.simperium save]; + [self expectAdditions:0 deletions:0 changes:1 fromLeader:leader expectAcks:YES]; + STAssertTrue([self waitForCompletion], @"timed out (changing)"); + + STAssertTrue([refWarpSpeed isEqualToNumber: leader.config.warpSpeed], @""); + STAssertTrue([refCaptainsLog isEqualToString: leader.config.captainsLog], @""); + STAssertTrue([refShieldPercent isEqualToNumber: leader.config.shieldPercent], @""); + [self ensureFarmsEqual:farms entityName:@"Config"]; + NSLog(@"%@ end", self.name); +} + +- (void)testObjectVersions +{ + NSLog(@"%@ start", self.name); + [self createAndStartFarms]; + + // Leader sends an object to followers, then changes a string repeatedly + Farm *leader = [farms objectAtIndex:0]; + [self connectFarms]; + + int changeNumber = 0; + NSString *refString = [NSString stringWithFormat:@"%d", changeNumber]; + SPBucket *bucket = [leader.simperium bucketForName:@"Config"]; + leader.config = [bucket insertNewObject]; + leader.config.captainsLog = refString; + [leader.simperium save]; + [self expectAdditions:1 deletions:0 changes:0 fromLeader:leader expectAcks:YES]; + STAssertTrue([self waitForCompletion], @"timed out (adding)"); + + int numChanges = 11; + for (changeNumber=1; changeNumber { +} + +@end diff --git a/SimperiumTests/SimperiumComplexTests.m b/SimperiumTests/SimperiumComplexTests.m new file mode 100644 index 00000000..25a33fd0 --- /dev/null +++ b/SimperiumTests/SimperiumComplexTests.m @@ -0,0 +1,153 @@ +// +// SimperiumComplexTests.m +// SimperiumComplexTests +// +// Created by Michael Johnston on 11-04-19. +// Copyright 2011 Simperium. All rights reserved. +// + +#import "SimperiumComplexTests.h" +#import "Config.h" +#import "Farm.h" +#import "SPBucket.h" +#import "DiffMatchPatch.h" + +@implementation SimperiumComplexTests + +- (void)testChangesToMultipleObjects +{ + NSLog(@"%@ start", self.name); + [self createAndStartFarms]; + + NSUInteger numConfigs = NUM_MULTIPLE_CONFIGS; + + // Leader sends an object to followers, then changes multiple fields + Farm *leader = [farms objectAtIndex:0]; + [self connectFarms]; + + + NSLog(@"****************************ADD*************************"); + for (int i=0; i +#import "SimperiumTests.h" + + +@interface SimperiumCoreDataTests : SimperiumTests + +@end diff --git a/SimperiumTests/SimperiumCoreDataTests.m b/SimperiumTests/SimperiumCoreDataTests.m new file mode 100644 index 00000000..b15b0954 --- /dev/null +++ b/SimperiumTests/SimperiumCoreDataTests.m @@ -0,0 +1,350 @@ +// +// SimperiumCoreDataTests.m +// Simperium +// +// Created by Jorge Leandro Perez on 7/20/13. +// Copyright (c) 2013 Simperium. All rights reserved. +// + +#import "SimperiumCoreDataTests.h" +#import "Farm.h" +#import "Config.h" +#import "NSString+Simperium.h" + + + +#pragma mark ==================================================================================== +#pragma mark Constants +#pragma mark ==================================================================================== + +static NSUInteger const kObjectsCount = 10; +static NSTimeInterval const kLocalTestTimeout = 1; +static NSTimeInterval const kRemoteTestTimeout = 10; + +static NSString* const kInsertedKey = @"inserted"; +static NSString* const kUpdatedKey = @"updated"; +static NSString* const kDeletedKey = @"deleted"; + + +#pragma mark ==================================================================================== +#pragma mark Private Properties +#pragma mark ==================================================================================== + +@interface SimperiumCoreDataTests () +@property (nonatomic, strong, readwrite) NSManagedObjectContext* writerContext; +@property (nonatomic, strong, readwrite) NSManagedObjectContext* mainContext; +@property (nonatomic, strong, readwrite) NSMutableDictionary* changesByContext; +@end + + +#pragma mark ==================================================================================== +#pragma mark SimperiumCoreDataTests +#pragma mark ==================================================================================== + +@implementation SimperiumCoreDataTests + +-(void)setUp { + + [super setUp]; + + // Fire up Simperium + [self createAndStartFarms]; + [self connectFarms]; + + // Load the contexts + Farm *leader = [farms lastObject]; + self.writerContext = leader.simperium.writerManagedObjectContext; + self.mainContext = leader.simperium.managedObjectContext; + self.changesByContext = [NSMutableDictionary dictionary]; + + STAssertTrue((self.mainContext.concurrencyType == NSMainQueueConcurrencyType), @"CoreData mainContext Setup Error"); + STAssertTrue((self.writerContext.concurrencyType == NSPrivateQueueConcurrencyType), @"CoreData writerContext Setup Error"); +} + +-(void)tearDown { + + [super tearDown]; + + // Cleanup + self.writerContext = nil; + self.mainContext = nil; + self.changesByContext = nil; + + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + + +#pragma mark ==================================================================================== +#pragma mark Tests! +#pragma mark ==================================================================================== + +-(void)testWriterMOC { + + NSLog(@"%@ start", self.name); + + // Let's insert new objects + for(NSInteger i = 0; ++i <= kObjectsCount; ) + { + Config* config = [NSEntityDescription insertNewObjectForEntityForName:@"Config" inManagedObjectContext:self.mainContext]; + config.warpSpeed = @( arc4random_uniform(UINT_MAX) ); + } + + // Listen to the follower MainMOC changes & WriterMOC save notifications + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleContextNote:) name:NSManagedObjectContextDidSaveNotification object:self.writerContext]; + + // Scotty, beam the changes down! + STAssertTrue(self.mainContext.hasChanges, @"Main MOC should have changes"); + STAssertFalse(self.writerContext.hasChanges, @"Writer MOC should not have changes"); + + NSError* error = nil; + [self.mainContext save:&error]; + STAssertFalse(error, @"Error Saving mainContext"); + + // The writer save is async, and automatic. Hold the runloop just a sec + [self waitFor:kLocalTestTimeout]; + + // The writerContext should have persisted the changes + NSArray *savedChanges = [[self changesForContext:self.writerContext] objectForKey:kInsertedKey]; + + STAssertTrue( (savedChanges.count == kObjectsCount), @"Writer MOC failed to persist the inserted objects"); + + NSLog(@"%@ end", self.name); +} + + + +-(void)testBucketMechanism { + + NSLog(@"%@ start", self.name); + + NSManagedObjectContext* workerContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; + NSManagedObjectContext* deepContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; + + workerContext.parentContext = self.mainContext; + deepContext.parentContext = workerContext; + + // Let's insert new objects + Config* config = nil; + + config = [NSEntityDescription insertNewObjectForEntityForName:@"Config" inManagedObjectContext:self.mainContext]; + STAssertTrue( (config.bucket != nil), @"The MainContext newObject's bucket should not be nil"); + + config = [NSEntityDescription insertNewObjectForEntityForName:@"Config" inManagedObjectContext:workerContext]; + STAssertTrue( (config.bucket != nil), @"The WorkerContext newObject's bucket should not be nil"); + + config = [NSEntityDescription insertNewObjectForEntityForName:@"Config" inManagedObjectContext:deepContext]; + STAssertTrue( (config.bucket != nil), @"The DeepContext newObject's bucket should not be nil"); + + NSLog(@"%@ end", self.name); +} + + + +-(void)testNestedInsert { + + NSLog(@"%@ start", self.name); + + NSManagedObjectContext *workerContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; + NSManagedObjectContext *deepContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; + + workerContext.parentContext = self.mainContext; + deepContext.parentContext = workerContext; + + [deepContext performBlockAndWait:^{ + + // Insert an object into the last context of the chain + Config* inserted = [NSEntityDescription insertNewObjectForEntityForName:@"Config" inManagedObjectContext:deepContext]; + STAssertNotNil(inserted, @"Error inserting object in child"); + STAssertTrue( (deepContext.hasChanges), @"Error inserting into Deep Context"); + STAssertFalse( (workerContext.hasChanges), @"Worker context shouldn't have changes"); + + // Push the changes one level up (to the 'workerContext') + NSError* error = nil; + [deepContext save:&error]; + STAssertNil(error, @"Error saving deep context"); + STAssertTrue( (workerContext.hasChanges), @"Worker context SHOULD have changes"); + + [workerContext performBlockAndWait:^{ + + // Push one level up (mainContext) + NSError* error = nil; + [workerContext save:&error]; + STAssertNil(error, @"Error saving worker context"); + STAssertTrue( (self.mainContext.hasChanges), @"Main context SHOULD have changes"); + + // Finally, this will reach the writer + [self.mainContext performBlockAndWait:^{ + + NSError* error = nil; + [self.mainContext save:&error]; + STAssertNil(error, @"Error saving Main context"); + }]; + }]; + }]; + + NSLog(@"%@ end", self.name); +} + + + +-(void)testRemoteCRUD { + + NSLog(@"%@ start", self.name); + + // We'll need a follower farm + Farm *follower = [self createFarm:@"follower"]; + [follower start]; + [follower connect]; + + [self waitFor:1.0]; + + // Prepare everything we need + NSManagedObjectContext *followerMainMOC = follower.simperium.managedObjectContext; + NSManagedObjectContext *followerWriterMOC = follower.simperium.writerManagedObjectContext; + + // Listen to the follower MainMOC changes & WriterMOC save notifications + NSNotificationCenter* nc = [NSNotificationCenter defaultCenter]; + [nc addObserver:self selector:@selector(handleContextNote:) name:NSManagedObjectContextObjectsDidChangeNotification object:followerMainMOC]; + [nc addObserver:self selector:@selector(handleContextNote:) name:NSManagedObjectContextDidSaveNotification object:followerWriterMOC]; + + + // ==================================================================================== + // Insert Test + // - If a "lead" Simperium client inserts objects, the "follower" should insert those + // objects into the writer + main Contexts + // ==================================================================================== + // + + NSMutableSet* objects = [NSMutableSet set]; + for(NSInteger i = 0; ++i <= kObjectsCount; ) + { + Config* config = [NSEntityDescription insertNewObjectForEntityForName:@"Config" inManagedObjectContext:self.mainContext]; + [objects addObject:config]; + } + + // Verify that the objects make it to the Follower's writer & main MOC's + NSError* error = nil; + [self.mainContext save:&error]; + STAssertNil(error, @"Error saving Leader MOC"); + + [self waitFor:kRemoteTestTimeout]; + + NSArray *mainInserted = [[self changesForContext:followerMainMOC] objectForKey:kInsertedKey]; + NSArray *writerInserted = [[self changesForContext:followerWriterMOC] objectForKey:kInsertedKey]; + + STAssertTrue( (mainInserted.count == kObjectsCount), @"The follower's mainMOC didn't get the new objects"); + STAssertTrue( (writerInserted.count == kObjectsCount), @"The follower's writerMOC didn't persist the new objects"); + + + // ==================================================================================== + // Update Test + // - If a "lead" Simperium client updates a set of objects, the "follower" should + // update those objects in both the writer AND main Contexts + // ==================================================================================== + // + + for(Config* config in objects) + { + config.warpSpeed = @(31337); + config.shieldsUp = @(YES); + config.shieldPercent = @(100); + config.captainsLog = @"You damn dirty borgs!"; + } + + error = nil; + [self.mainContext save:&error]; + STAssertNil(error, @"Error saving Main Context"); + + [self waitFor:kRemoteTestTimeout]; + + NSArray *mainUpdated = [self changesForContext:followerWriterMOC][kUpdatedKey]; + STAssertTrue( (mainUpdated.count == objects.count), @"Error Updating Objects" ); + + for(Config* config in mainUpdated) + { + STAssertTrue([config.warpSpeed isEqual:@(31337)], @"Update Test Failed"); + STAssertTrue([config.shieldsUp isEqual:@(YES)], @"Update Test Failed"); + STAssertTrue([config.shieldPercent isEqual:@(100)], @"Update Test Failed"); + STAssertTrue([config.captainsLog isEqual:@"You damn dirty borgs!"], @"Update Test Failed"); + } + + + // ==================================================================================== + // Delete Test + // - If a "lead" Simperium client delets a set of objects, the "follower" should delete + // those objects from the writer AND main Contexts + // ==================================================================================== + // + + for(Config* config in objects) + { + [self.mainContext deleteObject:config]; + } + + error = nil; + [self.mainContext save:&error]; + STAssertNil(error, @"Error saving Leader MOC"); + + [self waitFor:kRemoteTestTimeout]; + + NSArray *mainDeleted = [[self changesForContext:followerMainMOC] objectForKey:kDeletedKey]; + NSArray *writerDeleted = [[self changesForContext:followerWriterMOC] objectForKey:kDeletedKey]; + + STAssertTrue( (mainDeleted.count == kObjectsCount), @"The follower's mainMOC failed to delete objects"); + STAssertTrue( (writerDeleted.count == kObjectsCount), @"The follower's writerMOC failed to delete objects"); + + NSLog(@"%@ end", self.name); +} + + +#pragma mark ==================================================================================== +#pragma mark Helpers +#pragma mark ==================================================================================== + +-(void)handleContextNote:(NSNotification*)note +{ + NSValue* wrappedSender = [NSValue valueWithNonretainedObject:note.object]; + NSMutableDictionary* changes = self.changesByContext[wrappedSender]; + + // First time? + if(!changes) + { + changes = [NSMutableDictionary dictionary]; + changes[kInsertedKey] = [NSMutableArray array]; + changes[kUpdatedKey] = [NSMutableArray array]; + changes[kDeletedKey] = [NSMutableArray array]; + self.changesByContext[wrappedSender] = changes; + } + + // Track everything + NSDictionary* userInfo = note.userInfo; + NSSet* receivedInserts = userInfo[kInsertedKey]; + NSSet* receivedUpdates = userInfo[kUpdatedKey]; + NSSet* receivedDeletions = userInfo[kDeletedKey]; + + if(receivedInserts) + { + NSMutableArray* inserted = changes[kInsertedKey]; + [inserted addObjectsFromArray:[receivedInserts allObjects]]; + } + + if(receivedUpdates) + { + NSMutableArray* updated = changes[kUpdatedKey]; + [updated addObjectsFromArray:[receivedUpdates allObjects]]; + } + + if(receivedDeletions) + { + NSMutableArray* deleted = changes[kDeletedKey]; + [deleted addObjectsFromArray:[receivedDeletions allObjects]]; + } +} + +-(NSDictionary*)changesForContext:(NSManagedObjectContext*)context +{ + NSValue* wrappedContext = [NSValue valueWithNonretainedObject:context]; + return self.changesByContext[wrappedContext]; +} + +@end diff --git a/SimperiumTests/SimperiumErrorTests.h b/SimperiumTests/SimperiumErrorTests.h new file mode 100644 index 00000000..78228793 --- /dev/null +++ b/SimperiumTests/SimperiumErrorTests.h @@ -0,0 +1,14 @@ +// +// SimperiumErrorTests.h +// SimperiumErrorTests +// +// Created by Michael Johnston on 11-04-19. +// Copyright 2011 Simperium. All rights reserved. +// + +#import "SimperiumTests.h" + +@interface SimperiumErrorTests : SimperiumTests { +} + +@end diff --git a/SimperiumTests/SimperiumErrorTests.m b/SimperiumTests/SimperiumErrorTests.m new file mode 100644 index 00000000..ee08bfdb --- /dev/null +++ b/SimperiumTests/SimperiumErrorTests.m @@ -0,0 +1,62 @@ +// +// SimperiumErrorTests.m +// SimperiumErrorTests +// +// Created by Michael Johnston on 11-04-19. +// Copyright 2011 Simperium. All rights reserved. +// + +#import "SimperiumErrorTests.h" +#import "Config.h" +#import "Farm.h" +#import "SPBucket.h" +#import "DiffMatchPatch.h" + +@implementation SimperiumErrorTests + +- (void)testDeletion404 +{ + // Leader sends an object to a follower, follower goes offline, both make changes, follower reconnects + Farm *leader = [self createFarm:@"leader"]; + Farm *follower = [self createFarm:@"follower"]; + [leader start]; + [follower start]; + + NSArray *farmArray = [NSArray arrayWithObjects:leader, follower, nil]; + [leader connect]; + [follower connect]; + [self waitFor:1.0]; + + SPBucket *leaderBucket = [leader.simperium bucketForName:@"Config"]; + SPBucket *followerBucket = [follower.simperium bucketForName:@"Config"]; + + leader.config = [leaderBucket insertNewObject]; + leader.config.captainsLog = @"1"; + [leader.simperium save]; + leader.expectedAcknowledgments = 1; + follower.expectedAdditions = 1; + STAssertTrue([self waitForCompletion: 4.0 farmArray:farmArray], @"timed out (adding)"); + [self resetExpectations: farmArray]; + [self ensureFarmsEqual:farmArray entityName:@"Config"]; + NSLog(@"****************************DISCONNECT*************************"); + [follower disconnect]; + + // Delete on leader and sync + [leaderBucket deleteObject:leader.config]; + leader.expectedAcknowledgments = 1; + [leader.simperium save]; + STAssertTrue([self waitForCompletion: 4.0 farmArray:farmArray], @"timed out (deleting)"); + [self resetExpectations: farmArray]; + + // Delete on follower before it syncs to force a 404 + [followerBucket deleteObject:follower.config]; + follower.expectedAcknowledgments = 1; + [follower.simperium save]; + [self waitFor:0.01]; + NSLog(@"****************************RECONNECT*************************"); + [follower connect]; + STAssertTrue([self waitForCompletion:4.0 farmArray:farmArray], @"timed out (changing)"); + NSLog(@"%@ end", self.name); +} + +@end diff --git a/SimperiumTests/SimperiumIndexTests.h b/SimperiumTests/SimperiumIndexTests.h new file mode 100644 index 00000000..885faaad --- /dev/null +++ b/SimperiumTests/SimperiumIndexTests.h @@ -0,0 +1,14 @@ +// +// SimperiumIndexTests.h +// SimperiumIndexTests +// +// Created by Michael Johnston on 11-04-19. +// Copyright 2011 Simperium. All rights reserved. +// + +#import "SimperiumTests.h" + +@interface SimperiumIndexTests : SimperiumTests { +} + +@end diff --git a/SimperiumTests/SimperiumIndexTests.m b/SimperiumTests/SimperiumIndexTests.m new file mode 100644 index 00000000..ce66fc42 --- /dev/null +++ b/SimperiumTests/SimperiumIndexTests.m @@ -0,0 +1,249 @@ +// +// SimperiumIndexTests.m +// SimperiumIndexTests +// +// Created by Michael Johnston on 11-04-19. +// Copyright 2011 Simperium. All rights reserved. +// + +#import "SimperiumIndexTests.h" +#import "Config.h" +#import "Farm.h" +#import "SPBucket.h" +#import "DiffMatchPatch.h" + +@implementation SimperiumIndexTests + +- (void)testIndex +{ + NSLog(@"%@ start", self.name); + + // Leader sends an object to follower, but make follower get it from the index + Farm *leader = [self createFarm:@"leader"]; + Farm *follower = [self createFarm:@"follower"]; + [leader start]; + [leader connect]; + leader.expectedIndexCompletions = 1; + STAssertTrue([self waitForCompletion], @"timed out"); + + NSNumber *refWarpSpeed = [NSNumber numberWithInt:2]; + leader.config = [[leader.simperium bucketForName:@"Config"] insertNewObject]; + leader.config.warpSpeed = refWarpSpeed; + [leader.simperium save]; + leader.expectedAcknowledgments = 1; + STAssertTrue([self waitForCompletion], @"timed out"); + + // Make a change to ensure version numbers increase + refWarpSpeed = [NSNumber numberWithInt:4]; + NSString *refCaptainsLog = @"Hi!!!"; + NSNumber *refShieldPercent = [NSNumber numberWithFloat:2.718]; + leader.config.warpSpeed = refWarpSpeed; + leader.config.captainsLog = refCaptainsLog; + leader.config.shieldPercent = refShieldPercent; + [leader.simperium save]; + leader.expectedAcknowledgments = 1; + STAssertTrue([self waitForCompletion], @"timed out (changing)"); + + // The object was synced, now connect with the follower + [follower start]; + + [self resetExpectations: farms]; + follower.expectedIndexCompletions = 1; + [self expectAdditions:1 deletions:0 changes:0 fromLeader:leader expectAcks:NO]; + [follower connect]; + + STAssertTrue([self waitForCompletion], @"timed out"); + + [self ensureFarmsEqual:farms entityName:@"Config"]; + NSLog(@"%@ end", self.name); +} + +- (void)testLargerIndex +{ + NSLog(@"%@ start", self.name); + [self createAndStartFarms]; + + // Leader sends an object to followers, but make followers get it from the index + Farm *leader = [farms objectAtIndex:0]; + [leader connect]; + [self waitFor:5.0]; + + NSNumber *refWarpSpeed = [NSNumber numberWithInt:2]; + int numObjects = 2; + for (int i=0; i { +} + +@end diff --git a/SimperiumTests/SimperiumOfflineTests.m b/SimperiumTests/SimperiumOfflineTests.m new file mode 100644 index 00000000..bc7a31e4 --- /dev/null +++ b/SimperiumTests/SimperiumOfflineTests.m @@ -0,0 +1,200 @@ +// +// SimperiumOfflineTests.m +// SimperiumOfflineTests +// +// Created by Michael Johnston on 11-04-19. +// Copyright 2011 Simperium. All rights reserved. +// + +#import "SimperiumOfflineTests.h" +#import "Config.h" +#import "Farm.h" +#import "SPBucket.h" +#import "DiffMatchPatch.h" + +@implementation SimperiumOfflineTests + +- (void)testSingleOfflineStringChange +{ + NSLog(@"%@ start", self.name); + + // Leader sends an object to a follower, follower goes offline, both make changes, follower reconnects + Farm *leader = [self createFarm:@"leader"]; + Farm *follower = [self createFarm:@"follower"]; + [leader start]; + [follower start]; + + NSArray *farmArray = [NSArray arrayWithObjects:leader, follower, nil]; + [leader connect]; + [follower connect]; + [self waitFor:1.0]; + + leader.config = [[leader.simperium bucketForName:@"Config"] insertNewObject]; + leader.config.captainsLog = @"1"; + [leader.simperium save]; + NSString *configKey = leader.config.simperiumKey; + leader.expectedAcknowledgments = 1; + follower.expectedAdditions = 1; + STAssertTrue([self waitForCompletion: 4.0 farmArray:farmArray], @"timed out (adding)"); + [self resetExpectations: farmArray]; + [self ensureFarmsEqual:farmArray entityName:@"Config"]; + NSLog(@"****************************DISCONNECT*************************"); + [follower disconnect]; + + follower.config = (Config *)[[follower.simperium bucketForName:@"Config"] objectForKey:configKey]; + follower.config.captainsLog = @"12"; + follower.expectedAcknowledgments = 1; + leader.expectedChanges = 1; + [follower.simperium save]; + + [self waitFor:1.0]; + NSLog(@"****************************RECONNECT*************************"); + [follower connect]; + STAssertTrue([self waitForCompletion:4.0 farmArray:farmArray], @"timed out (changing)"); + + // Make sure there's no residual weirdness + [self waitFor:1.0]; + + NSString *refString = @"12"; + STAssertTrue([refString isEqualToString: leader.config.captainsLog], + @"leader %@ != ref %@", leader.config.captainsLog, refString); + [self ensureFarmsEqual:farmArray entityName:@"Config"]; + NSLog(@"%@ end", self.name); +} + + +- (void)testSimultaneousOfflineStringChange +{ + NSLog(@"%@ start", self.name); + + // Leader sends an object to a follower, follower goes offline, both make changes, follower reconnects + Farm *leader = [self createFarm:@"leader"]; + Farm *follower = [self createFarm:@"follower"]; + [leader start]; + [follower start]; + [leader connect]; + [follower connect]; + [self waitFor:1.5]; + + leader.config = [[leader.simperium bucketForName:@"Config"] insertNewObject]; + leader.config.captainsLog = @"a"; + leader.expectedAcknowledgments = 1; + follower.expectedAdditions = 1; + [leader.simperium save]; + NSString *configKey = leader.config.simperiumKey; + STAssertTrue([self waitForCompletion: 6.0 farmArray:farms], @"timed out (adding)"); + [self resetExpectations:farms]; + [self ensureFarmsEqual:farms entityName:@"Config"]; + [follower disconnect]; + [self waitFor:1.0]; + + leader.config.captainsLog = @"ab"; + leader.expectedAcknowledgments = 1; + [leader.simperium save]; + STAssertTrue([self waitForCompletion:6.0 farmArray:farms], @"timed out (changing)"); + [self resetExpectations:farms]; + + follower.config = (Config *)[[follower.simperium bucketForName:@"Config"] objectForKey:configKey]; + follower.config.captainsLog = @"ac"; + follower.expectedAcknowledgments = 1; + follower.expectedChanges = 1; + leader.expectedChanges = 1; + [follower.simperium save]; + [self waitFor:0.5]; + [follower connect]; + STAssertTrue([self waitForCompletion:6.0 farmArray:farms], @"timed out (changing)"); + + // Make sure there's no residual weirdness + [self waitFor:1.0]; + + NSString *refString = @"abc"; + STAssertTrue([refString isEqualToString: leader.config.captainsLog], + @"leader %@ != ref %@", leader.config.captainsLog, refString); + [self ensureFarmsEqual:farms entityName:@"Config"]; + NSLog(@"%@ end", self.name); +} + +- (void)testOfflineCreationAndEditing +{ + NSLog(@"%@ start", self.name); + + // Leader creates an object offline, changes it, then connects + Farm *leader = [self createFarm:@"leader"]; + + // Change URL to an invalid one to simulate airplane mode (crude) + [leader start]; + leader.simperium.rootURL = @"http://iosunittest.simperium.com:1234/1/"; + [leader connect]; + + SPBucket *bucket = [leader.simperium bucketForName:@"Config"]; + NSArray *farmArray = [NSArray arrayWithObjects:leader, nil]; + + [self waitFor:2]; + + leader.config = [bucket insertNewObject]; + leader.config.captainsLog = @"1"; + [leader.simperium save]; + [self waitFor:1]; + + // Wait a tick, make a change + leader.config.captainsLog = @"123"; + [leader.simperium save]; + [self waitFor:1]; + + // Wait a tick, make a change + leader.config.captainsLog = @"123456"; + [leader.simperium save]; + [self waitFor:1]; + + // Wait a tick, make a change + leader.config.captainsLog = @"123456 09876"; + [leader.simperium save]; + [self waitFor:1]; + + + // Again with a second object + Config *config2 = [bucket insertNewObject]; + config2.simperiumKey = @"config2"; + config2.captainsLog = @"a"; + [leader.simperium save]; + [self waitFor:1]; + + config2.captainsLog = @"abc"; + [leader.simperium save]; + [self waitFor:1]; + + config2.captainsLog = @"abcdef"; + [leader.simperium save]; + [self waitFor:1]; + + + NSLog(@"*****RECONNECTING******"); + [leader disconnect]; + + [self waitFor:1]; + leader.simperium.rootURL = @"https://api.simperium.com/1/"; + [leader connect]; + [self waitFor:4]; + + //leader.expectedAcknowledgments = 1; + //STAssertTrue([self waitForCompletion: 4.0 farmArray:farmArray], @"timed out (adding)"); +// [self resetExpectations: farmArray]; +// [self ensureFarmsEqual:farmArray entityName:@"Config"]; + + //STAssertTrue([self waitForCompletion:4.0 farmArray:farmArray], @"timed out (changing)"); + + + NSString *refString = @"123456 09876"; + STAssertTrue([refString isEqualToString: leader.config.captainsLog], + @"leader %@ != ref %@", leader.config.captainsLog, refString); + + NSString *refString2 = @"abcdef"; + STAssertTrue([refString2 isEqualToString: config2.captainsLog], + @"leader %@ != ref %@", config2.captainsLog, refString2); + + [self ensureFarmsEqual:farmArray entityName:@"Config"]; + NSLog(@"%@ end", self.name); +} + + +@end diff --git a/SimperiumTests/SimperiumRelationshipTests.m b/SimperiumTests/SimperiumRelationshipTests.m index 25384636..46f4ea17 100644 --- a/SimperiumTests/SimperiumRelationshipTests.m +++ b/SimperiumTests/SimperiumRelationshipTests.m @@ -18,8 +18,8 @@ - (NSDictionary *)bucketOverrides { // Each farm for each test case should share bucket overrides if (overrides == nil) { self.overrides = [NSDictionary dictionaryWithObjectsAndKeys: - [self uniqueBucketFor:@"Post"], @"Post", - [self uniqueBucketFor:@"Comment"], @"Comment", nil]; + [self uniqueBucketFor:@"Post"], @"Post", + [self uniqueBucketFor:@"Comment"], @"Comment", nil]; } return overrides; } @@ -33,31 +33,33 @@ - (void)testSingleRelationship Farm *follower = [self createFarm:@"follower"]; [leader start]; [follower start]; - NSArray *farmArray = [NSArray arrayWithObjects:leader, follower, nil]; + leader.expectedIndexCompletions = 1; + follower.expectedIndexCompletions = 1; [leader connect]; [follower connect]; + STAssertTrue([self waitForCompletion: 4.0 farmArray:farms], @"timed out (initial index)"); + [self resetExpectations:farms]; + SPBucket *leaderPosts = [leader.simperium bucketForName:@"Post"]; Post *post = (Post *)[leaderPosts insertNewObject]; - post.simperiumKey = @"post"; post.title = @"post title"; SPBucket *leaderComments = [leader.simperium bucketForName:@"Comment"]; Comment *comment = (Comment *)[leaderComments insertNewObject]; - comment.simperiumKey = @"comment"; comment.content = @"a comment"; comment.post = post; leader.expectedAcknowledgments = 2; follower.expectedAdditions = 2; [leader.simperium save]; - STAssertTrue([self waitForCompletion: 4.0 farmArray:farmArray], @"timed out (adding)"); + STAssertTrue([self waitForCompletion: 4.0 farmArray:farms], @"timed out (adding)"); // Ensure pending references have an opportunity to resolve [self waitFor:0.5]; - [self ensureFarmsEqual:farmArray entityName:@"Post"]; - [self ensureFarmsEqual:farmArray entityName:@"Comment"]; + [self ensureFarmsEqual:farms entityName:@"Post"]; + [self ensureFarmsEqual:farms entityName:@"Comment"]; NSLog(@"%@ end", self.name); } diff --git a/SimperiumTests/SimperiumSimpleTests.m b/SimperiumTests/SimperiumSimpleTests.m deleted file mode 100644 index 6752a6da..00000000 --- a/SimperiumTests/SimperiumSimpleTests.m +++ /dev/null @@ -1,655 +0,0 @@ -// -// SimperiumSimpleTests.m -// SimperiumSimpleTests -// -// Created by Michael Johnston on 11-04-19. -// Copyright 2011 Simperium. All rights reserved. -// - -#import "SimperiumSimpleTests.h" -#import "Config.h" -#import "Farm.h" -#import "SPBucket.h" -#import "DiffMatchPatch.h" - -@implementation SimperiumSimpleTests - -- (NSDictionary *)bucketOverrides { - // Each farm for each test case should share bucket overrides - if (overrides == nil) { - self.overrides = [NSDictionary dictionaryWithObjectsAndKeys: - [self uniqueBucketFor:@"Config"], @"Config", nil]; - } - return overrides; -} - -- (void)testAuth -{ - NSLog(@"%@ start", self.name); - STAssertTrue(token.length > 0, @""); - NSLog(@"token is %@", token); - NSLog(@"%@ end", self.name); -} - -- (void)testAddingSingleObject -{ - NSLog(@"%@ start", self.name); - [self createAndStartFarms]; - - // Leader sends an object to followers - Farm *leader = [farms objectAtIndex:0]; - [self connectFarms]; - - NSNumber *refWarpSpeed = [NSNumber numberWithInt:2]; - SPBucket *leaderBucket = [leader.simperium bucketForName:@"Config"]; - leaderBucket.delegate = leader; - leader.config = [leaderBucket insertNewObjectForKey:@"config"]; - [leader.config setValue:refWarpSpeed forKey:@"warpSpeed"]; - //leader.config.warpSpeed = refWarpSpeed; - [leader.simperium save]; - [self expectAdditions:1 deletions:0 changes:0 fromLeader:leader expectAcks:YES]; - STAssertTrue([self waitForCompletion], @"timed out"); -// STAssertTrue([leader.config.warpSpeed isEqualToNumber: refWarpSpeed], @""); - STAssertTrue([[leader.config valueForKey:@"warpSpeed"] isEqualToNumber: refWarpSpeed], @""); - - // This is failing for the JSON case because the follower farms don't know what bucket to start listening - // to. This can be worked around by adding a special prep method to farms. However they'll still fail because - // I need to add dynamic schema support to the REMOTE ADD and REMOTE MODIFY cases as well, so that followers - // can consruct their schemas as new data comes off the wire. - - [self ensureFarmsEqual:farms entityName:@"Config"]; - - NSLog(@"%@ end", self.name); -} - -- (void)testDeletingSingleObject -{ - NSLog(@"%@ start", self.name); - [self createAndStartFarms]; - - // Leader sends an object to followers, then removes it - Farm *leader = [farms objectAtIndex:0]; - [self connectFarms]; - - SPBucket *bucket = [leader.simperium bucketForName:@"Config"]; - leader.config = [bucket insertNewObject]; - leader.config.simperiumKey = @"config"; - leader.config.warpSpeed = [NSNumber numberWithInt:2]; - [leader.simperium save]; - [self expectAdditions:1 deletions:0 changes:0 fromLeader:leader expectAcks:YES]; - STAssertTrue([self waitForCompletion], @"timed out (adding)"); - - [bucket deleteObject:leader.config]; - [leader.simperium save]; - [self expectAdditions:0 deletions:1 changes:0 fromLeader:leader expectAcks:YES]; - STAssertTrue([self waitForCompletion], @"timed out (deleting)"); - - int i=0; - for (Farm *farm in farms) { - farm.config = (Config *)[[farm.simperium bucketForName:@"Config"] objectForKey:@"config"]; - STAssertNil(farm.config, @"config %d wasn't deleted: %@", i, farm.config); - i += 1; - } - NSLog(@"%@ end", self.name); -} - -- (void)testChangesToSingleObject -{ - NSLog(@"%@ start", self.name); - [self createAndStartFarms]; - - // Leader sends an object to followers, then changes multiple fields - Farm *leader = [farms objectAtIndex:0]; - [self connectFarms]; - [self waitFor:1.0]; - - leader.config = [[leader.simperium bucketForName:@"Config"] insertNewObject]; - leader.config.simperiumKey = @"config"; - leader.config.warpSpeed = [NSNumber numberWithInt:2]; - leader.config.captainsLog = @"Hi"; - leader.config.shieldPercent = [NSNumber numberWithFloat:3.14]; - leader.config.cost = [NSDecimalNumber decimalNumberWithString:@"3.00"]; - [leader.simperium save]; - [self expectAdditions:1 deletions:0 changes:0 fromLeader:leader expectAcks:YES]; - STAssertTrue([self waitForCompletion], @"timed out (adding)"); - STAssertNotNil(leader.config.ghostData, @""); - - NSNumber *refWarpSpeed = [NSNumber numberWithInt:4]; - NSString *refCaptainsLog = @"Hi!!!"; - NSNumber *refShieldPercent = [NSNumber numberWithFloat:2.718]; - NSDecimalNumber *refCost = [NSDecimalNumber decimalNumberWithString:@"4.00"]; - leader.config.warpSpeed = refWarpSpeed; - leader.config.captainsLog = refCaptainsLog; - leader.config.shieldPercent = refShieldPercent; - leader.config.cost = refCost; - [leader.simperium save]; - [self expectAdditions:0 deletions:0 changes:1 fromLeader:leader expectAcks:YES]; - STAssertTrue([self waitForCompletion], @"timed out (changing)"); - - STAssertTrue([refWarpSpeed isEqualToNumber: leader.config.warpSpeed], @""); - STAssertTrue([refCaptainsLog isEqualToString: leader.config.captainsLog], @""); - STAssertTrue([refShieldPercent isEqualToNumber: leader.config.shieldPercent], @""); - STAssertTrue([refCost isEqualToNumber: leader.config.cost], @""); - - [self ensureFarmsEqual:farms entityName:@"Config"]; - NSLog(@"%@ end", self.name); -} - -- (void)testChangesToMultipleObjects -{ - NSLog(@"%@ start", self.name); - [self createAndStartFarms]; - - NSUInteger numConfigs = NUM_MULTIPLE_CONFIGS; - - // Leader sends an object to followers, then changes multiple fields - Farm *leader = [farms objectAtIndex:0]; - [self connectFarms]; - - - NSLog(@"****************************ADD*************************"); - for (int i=0; i 0, @"invalid token from request: %@", tokenURL); - [[NSUserDefaults standardUserDefaults] setObject: token forKey:@"spAuthToken"]; - NSMutableDictionary *credentials = [SPSimpleKeychain load:APP_ID]; - if (!credentials) - credentials = [NSMutableDictionary dictionary]; - [credentials setObject:token forKey:@"SPAuthToken"]; - [SPSimpleKeychain save:APP_ID data: credentials]; + [[NSUserDefaults standardUserDefaults] setObject:USERNAME forKey:@"SPUsername"]; + [[NSUserDefaults standardUserDefaults] synchronize]; + [SFHFKeychainUtils storeUsername:@"SPUsername" andPassword:token forServiceName:APP_ID updateExisting:YES error:nil]; NSLog(@"auth token is %@", self.token); } - (void)tearDown { - [farms release]; - [overrides release]; [super tearDown]; } @@ -253,10 +253,7 @@ - (void)expectAdditions:(int)additions deletions:(int)deletions changes:(int)cha - (void)resetExpectations:(NSArray *)farmArray { for (Farm *farm in farmArray) { - farm.expectedAcknowledgments = 0; - farm.expectedAdditions = 0; - farm.expectedChanges = 0; - farm.expectedDeletions = 0; + [farm resetExpectations]; } } diff --git a/SimperiumTests/SimperiumTypeTests.m b/SimperiumTests/SimperiumTypeTests.m index cff7df49..67425ef9 100644 --- a/SimperiumTests/SimperiumTypeTests.m +++ b/SimperiumTests/SimperiumTypeTests.m @@ -13,14 +13,7 @@ @implementation SimperiumTypeTests -- (NSDictionary *)bucketOverrides { - // Each farm for each test case should share bucket overrides - if (overrides == nil) { - self.overrides = [NSDictionary dictionaryWithObjectsAndKeys: - [self uniqueBucketFor:@"Config"], @"Config", nil]; - } - return overrides; -} + - (void)testDate { NSLog(@"%@ start", self.name); @@ -36,7 +29,6 @@ - (void)testDate [self waitFor:1.0]; leader.config = [[leader.simperium bucketForName:@"Config"] insertNewObject]; - leader.config.simperiumKey = @"config"; leader.config.captainsLog = @"1"; [leader.simperium save]; leader.expectedAcknowledgments = 1; diff --git a/SimperiumTests/TestParams.h b/SimperiumTests/TestParams.h index 995741f3..95076826 100644 --- a/SimperiumTests/TestParams.h +++ b/SimperiumTests/TestParams.h @@ -6,14 +6,15 @@ // Copyright (c) 2011 Simperium. All rights reserved. // -#define NUM_FARMS 5 -#define NUM_MULTIPLE_CONFIGS 1 +#define NUM_FARMS 1 +#define NUM_MULTIPLE_CONFIGS 2 // TODO: move these to a local-only plist // To run the unit tests, create a test app at simperium.com along with a test user, and enter // the credentials here. -#define APP_ID @"SIMPERIUM_APP_ID" -#define API_KEY @"SIMPERIUM_API_KEY" + +#define APP_ID @"interpreters-hub-38c" +#define API_KEY @"eed3f07f949843e9bcbd5d97273c6478" #define SERVER @"https://auth.simperium.com" -#define USERNAME @"USER_EMAIL" -#define PASSWORD @"USER_PASSWORD" \ No newline at end of file +#define USERNAME @"test@simperium.com" +#define PASSWORD @"test3s" \ No newline at end of file diff --git a/samples/Simpletodo/Simpletodo.xcodeproj/project.pbxproj b/samples/Simpletodo/Simpletodo.xcodeproj/project.pbxproj index 32e5ea0a..6de56c75 100644 --- a/samples/Simpletodo/Simpletodo.xcodeproj/project.pbxproj +++ b/samples/Simpletodo/Simpletodo.xcodeproj/project.pbxproj @@ -299,6 +299,7 @@ GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "Simpletodo/Simpletodo-Prefix.pch"; INFOPLIST_FILE = "Simpletodo/Simpletodo-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 6.0; LIBRARY_SEARCH_PATHS = "$(inherited)"; OTHER_LDFLAGS = "-ObjC"; PRODUCT_NAME = Simpletodo; @@ -316,6 +317,7 @@ GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "Simpletodo/Simpletodo-Prefix.pch"; INFOPLIST_FILE = "Simpletodo/Simpletodo-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 6.0; LIBRARY_SEARCH_PATHS = "$(inherited)"; OTHER_LDFLAGS = "-ObjC"; PRODUCT_NAME = Simpletodo; diff --git a/samples/Simpletodo/Simpletodo/SPAppDelegate.m b/samples/Simpletodo/Simpletodo/SPAppDelegate.m index ccfc1407..ad116bff 100644 --- a/samples/Simpletodo/Simpletodo/SPAppDelegate.m +++ b/samples/Simpletodo/Simpletodo/SPAppDelegate.m @@ -104,13 +104,9 @@ - (NSManagedObjectContext *)managedObjectContext { return __managedObjectContext; } - - NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator]; - if (coordinator != nil) - { - __managedObjectContext = [[NSManagedObjectContext alloc] init]; - [__managedObjectContext setPersistentStoreCoordinator:coordinator]; - } + + __managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; + return __managedObjectContext; } diff --git a/samples/Simpletodo/Simpletodo/Simpletodo.xcdatamodeld/.xccurrentversion b/samples/Simpletodo/Simpletodo/Simpletodo.xcdatamodeld/.xccurrentversion index c8afb5de..0c67376e 100644 --- a/samples/Simpletodo/Simpletodo/Simpletodo.xcdatamodeld/.xccurrentversion +++ b/samples/Simpletodo/Simpletodo/Simpletodo.xcdatamodeld/.xccurrentversion @@ -1,8 +1,5 @@ - - _XCCurrentVersionName - Simpletodo.xcdatamodel - + diff --git a/samples/Simpletodo/SimpletodoFinal.xcodeproj/project.pbxproj b/samples/Simpletodo/SimpletodoFinal.xcodeproj/project.pbxproj index 233e56b3..e06ad7ac 100644 --- a/samples/Simpletodo/SimpletodoFinal.xcodeproj/project.pbxproj +++ b/samples/Simpletodo/SimpletodoFinal.xcodeproj/project.pbxproj @@ -28,23 +28,28 @@ 26CEF5FA14EC418A00BEC0BF /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 26CEF5F914EC418A00BEC0BF /* MobileCoreServices.framework */; }; 26CEF5FC14EC419100BEC0BF /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 26CEF5FB14EC419100BEC0BF /* SystemConfiguration.framework */; }; 26CEF5FE14EC41AD00BEC0BF /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 26CEF5FD14EC41AD00BEC0BF /* libz.dylib */; }; - 46CC657316AE299000D5DC22 /* Simperium.xcodeproj in Resources */ = {isa = PBXBuildFile; fileRef = 46CC657216AE299000D5DC22 /* Simperium.xcodeproj */; }; - 46CC658916B09A6800D5DC22 /* LoginView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 46CC658816B09A6800D5DC22 /* LoginView.xib */; }; - 46CC658D16B09AB500D5DC22 /* libSimperium.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 46CC658C16B09AB500D5DC22 /* libSimperium.a */; }; + 46B787EA16B70429001AA908 /* libSimperium.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 46B787E716B70402001AA908 /* libSimperium.a */; }; 46CC658F16B09C7700D5DC22 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 46CC658E16B09C7700D5DC22 /* Default-568h@2x.png */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - 46CC659C16B0B51700D5DC22 /* PBXContainerItemProxy */ = { + 2457B1931798E134008266B3 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; - containerPortal = 46CC657216AE299000D5DC22 /* Simperium.xcodeproj */; + containerPortal = 46B787DD16B70402001AA908 /* Simperium.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 264CD8FA135DFD7A00C51BAD; + remoteInfo = Simperium; + }; + 46B787E616B70402001AA908 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 46B787DD16B70402001AA908 /* Simperium.xcodeproj */; proxyType = 2; remoteGlobalIDString = 264CD8FB135DFD7A00C51BAD; remoteInfo = Simperium; }; - 46CC659E16B0B51700D5DC22 /* PBXContainerItemProxy */ = { + 46B787E816B70402001AA908 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; - containerPortal = 46CC657216AE299000D5DC22 /* Simperium.xcodeproj */; + containerPortal = 46B787DD16B70402001AA908 /* Simperium.xcodeproj */; proxyType = 2; remoteGlobalIDString = 264CD908135DFD7A00C51BAD; remoteInfo = SimperiumTests; @@ -80,9 +85,7 @@ 26CEF5F914EC418A00BEC0BF /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; }; 26CEF5FB14EC419100BEC0BF /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; 26CEF5FD14EC41AD00BEC0BF /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; }; - 46CC657216AE299000D5DC22 /* Simperium.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Simperium.xcodeproj; path = ../../Simperium.xcodeproj; sourceTree = ""; }; - 46CC658816B09A6800D5DC22 /* LoginView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = LoginView.xib; path = ../../Resources/LoginView.xib; sourceTree = ""; }; - 46CC658C16B09AB500D5DC22 /* libSimperium.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libSimperium.a; path = "../../build/Release-iphoneos/libSimperium.a"; sourceTree = ""; }; + 46B787DD16B70402001AA908 /* Simperium.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Simperium.xcodeproj; path = ../../Simperium.xcodeproj; sourceTree = ""; }; 46CC658E16B09C7700D5DC22 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -91,7 +94,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 46CC658D16B09AB500D5DC22 /* libSimperium.a in Frameworks */, + 46B787EA16B70429001AA908 /* libSimperium.a in Frameworks */, 266F06E515E60C3F00308C5D /* libicucore.dylib in Frameworks */, 266F06E215E60C3700308C5D /* Security.framework in Frameworks */, 26CEF5FE14EC41AD00BEC0BF /* libz.dylib in Frameworks */, @@ -111,10 +114,7 @@ 26156ABC14EC3D4C003332BA = { isa = PBXGroup; children = ( - 46CC658C16B09AB500D5DC22 /* libSimperium.a */, - 46CC658816B09A6800D5DC22 /* LoginView.xib */, - 46CC657216AE299000D5DC22 /* Simperium.xcodeproj */, - 266F06E415E60C3F00308C5D /* libicucore.dylib */, + 46B787DD16B70402001AA908 /* Simperium.xcodeproj */, 26C93A7214F86D1800BE99F5 /* icon.png */, 26C93A7314F86D1800BE99F5 /* icon@2x.png */, 46CC658E16B09C7700D5DC22 /* Default-568h@2x.png */, @@ -137,6 +137,7 @@ children = ( 266F06E115E60C3700308C5D /* Security.framework */, 26CEF5FD14EC41AD00BEC0BF /* libz.dylib */, + 266F06E415E60C3F00308C5D /* libicucore.dylib */, 26CEF5FB14EC419100BEC0BF /* SystemConfiguration.framework */, 26CEF5F914EC418A00BEC0BF /* MobileCoreServices.framework */, 26CEF5F714EC418300BEC0BF /* CFNetwork.framework */, @@ -179,11 +180,11 @@ name = "Supporting Files"; sourceTree = ""; }; - 46CC659716B0B51700D5DC22 /* Products */ = { + 46B787DE16B70402001AA908 /* Products */ = { isa = PBXGroup; children = ( - 46CC659D16B0B51700D5DC22 /* libSimperium.a */, - 46CC659F16B0B51700D5DC22 /* SimperiumTests.octest */, + 46B787E716B70402001AA908 /* libSimperium.a */, + 46B787E916B70402001AA908 /* SimperiumTests.octest */, ); name = Products; sourceTree = ""; @@ -202,6 +203,7 @@ buildRules = ( ); dependencies = ( + 2457B1941798E134008266B3 /* PBXTargetDependency */, ); name = SimpletodoFinal; productName = Simpletodo; @@ -228,8 +230,8 @@ projectDirPath = ""; projectReferences = ( { - ProductGroup = 46CC659716B0B51700D5DC22 /* Products */; - ProjectRef = 46CC657216AE299000D5DC22 /* Simperium.xcodeproj */; + ProductGroup = 46B787DE16B70402001AA908 /* Products */; + ProjectRef = 46B787DD16B70402001AA908 /* Simperium.xcodeproj */; }, ); projectRoot = ""; @@ -240,18 +242,18 @@ /* End PBXProject section */ /* Begin PBXReferenceProxy section */ - 46CC659D16B0B51700D5DC22 /* libSimperium.a */ = { + 46B787E716B70402001AA908 /* libSimperium.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; path = libSimperium.a; - remoteRef = 46CC659C16B0B51700D5DC22 /* PBXContainerItemProxy */; + remoteRef = 46B787E616B70402001AA908 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; - 46CC659F16B0B51700D5DC22 /* SimperiumTests.octest */ = { + 46B787E916B70402001AA908 /* SimperiumTests.octest */ = { isa = PBXReferenceProxy; fileType = wrapper.cfbundle; path = SimperiumTests.octest; - remoteRef = 46CC659E16B0B51700D5DC22 /* PBXContainerItemProxy */; + remoteRef = 46B787E816B70402001AA908 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXReferenceProxy section */ @@ -266,8 +268,6 @@ 26156AE414EC3D4C003332BA /* MainStoryboard_iPad.storyboard in Resources */, 26C93A7514F86D1800BE99F5 /* icon.png in Resources */, 26C93A7614F86D1800BE99F5 /* icon@2x.png in Resources */, - 46CC657316AE299000D5DC22 /* Simperium.xcodeproj in Resources */, - 46CC658916B09A6800D5DC22 /* LoginView.xib in Resources */, 46CC658F16B09C7700D5DC22 /* Default-568h@2x.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -290,6 +290,14 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 2457B1941798E134008266B3 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = Simperium; + targetProxy = 2457B1931798E134008266B3 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ 26156AD614EC3D4C003332BA /* InfoPlist.strings */ = { isa = PBXVariantGroup; @@ -375,6 +383,7 @@ GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "SimpletodoFinal/SimpletodoFinal-Prefix.pch"; INFOPLIST_FILE = "SimpletodoFinal/SimpletodoFinal-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 6.0; LIBRARY_SEARCH_PATHS = "$(inherited)"; OTHER_LDFLAGS = "-ObjC"; PRODUCT_NAME = SimpletodoFinal; @@ -393,6 +402,7 @@ GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "SimpletodoFinal/SimpletodoFinal-Prefix.pch"; INFOPLIST_FILE = "SimpletodoFinal/SimpletodoFinal-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 6.0; LIBRARY_SEARCH_PATHS = "$(inherited)"; OTHER_LDFLAGS = "-ObjC"; PRODUCT_NAME = SimpletodoFinal; diff --git a/samples/Simpletodo/SimpletodoFinal/SPAppDelegate.m b/samples/Simpletodo/SimpletodoFinal/SPAppDelegate.m index 7ca57382..3130fcd0 100644 --- a/samples/Simpletodo/SimpletodoFinal/SPAppDelegate.m +++ b/samples/Simpletodo/SimpletodoFinal/SPAppDelegate.m @@ -45,7 +45,7 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( [self.simperium startWithAppID:@"SIMPERIUM_APP_ID" APIKey:@"SIMPERIUM_API_KEY" model:[self managedObjectModel] - context:[self managedObjectContext] + context:[self managedObjectContext] coordinator:[self persistentStoreCoordinator]]; return YES; @@ -118,13 +118,9 @@ - (NSManagedObjectContext *)managedObjectContext { return __managedObjectContext; } - - NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator]; - if (coordinator != nil) - { - __managedObjectContext = [[NSManagedObjectContext alloc] init]; - [__managedObjectContext setPersistentStoreCoordinator:coordinator]; - } + + __managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; + return __managedObjectContext; } diff --git a/samples/Simpletodo/SimpletodoFinal/Simpletodo.xcdatamodeld/.xccurrentversion b/samples/Simpletodo/SimpletodoFinal/Simpletodo.xcdatamodeld/.xccurrentversion index c8afb5de..0c67376e 100644 --- a/samples/Simpletodo/SimpletodoFinal/Simpletodo.xcdatamodeld/.xccurrentversion +++ b/samples/Simpletodo/SimpletodoFinal/Simpletodo.xcdatamodeld/.xccurrentversion @@ -1,8 +1,5 @@ - - _XCCurrentVersionName - Simpletodo.xcdatamodel - + diff --git a/samples/Simpletodo/SimpletodoFinal/en.lproj/MainStoryboard_iPad.storyboard b/samples/Simpletodo/SimpletodoFinal/en.lproj/MainStoryboard_iPad.storyboard index 220dc57d..38fe4f23 100644 --- a/samples/Simpletodo/SimpletodoFinal/en.lproj/MainStoryboard_iPad.storyboard +++ b/samples/Simpletodo/SimpletodoFinal/en.lproj/MainStoryboard_iPad.storyboard @@ -1,13 +1,13 @@ - + - - + + + - @@ -16,19 +16,20 @@ + + - - + - + - - - + + - - + + + - - - - + + + - + - + @@ -89,8 +89,7 @@ - - + @@ -102,12 +101,13 @@ + + - @@ -116,12 +116,13 @@ + - +