├── .gitignore ├── AgileCloud.xcworkspace ├── contents.xcworkspacedata └── xcuserdata │ └── kevin.xcuserdatad │ └── UserInterfaceState.xcuserstate ├── AgileCloudSDK ├── AgileCloudSDK.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata └── AgileCloudSDK │ ├── AgileCloudSDK.h │ ├── CKAsset+AgileDictionary.h │ ├── CKAsset+AgileDictionary.m │ ├── CKAsset.h │ ├── CKAsset.m │ ├── CKAsset_Private.h │ ├── CKBlockOperation.h │ ├── CKBlockOperation.m │ ├── CKContainer.h │ ├── CKContainer.m │ ├── CKContainer_Private.h │ ├── CKDatabase.h │ ├── CKDatabase.m │ ├── CKDatabaseOperation.h │ ├── CKDatabaseOperation.m │ ├── CKDatabaseOperation_Private.h │ ├── CKDatabase_Private.h │ ├── CKDefines.h │ ├── CKDiscoveredUserInfo.h │ ├── CKDiscoveredUserInfo.m │ ├── CKError.h │ ├── CKError.m │ ├── CKFetchNotificationChangesOperation.h │ ├── CKFetchNotificationChangesOperation.m │ ├── CKFetchRecordChangesOperation.h │ ├── CKFetchRecordChangesOperation.m │ ├── CKFetchRecordZonesOperation.h │ ├── CKFetchRecordZonesOperation.m │ ├── CKFetchRecordsOperation.h │ ├── CKFetchRecordsOperation.m │ ├── CKFilter.h │ ├── CKFilter.m │ ├── CKFilterType.h │ ├── CKFilter_Private.h │ ├── CKMarkNotificationsReadOperation.h │ ├── CKMarkNotificationsReadOperation.m │ ├── CKMediator.h │ ├── CKMediator.m │ ├── CKMediatorDelegate.h │ ├── CKMediator_Private.h │ ├── CKModifyRecordZonesOperation.h │ ├── CKModifyRecordZonesOperation.m │ ├── CKModifyRecordsOperation.h │ ├── CKModifyRecordsOperation.m │ ├── CKModifySubscriptionsOperation.h │ ├── CKModifySubscriptionsOperation.m │ ├── CKNotification.h │ ├── CKNotification.m │ ├── CKNotificationInfo+AgileDictionary.h │ ├── CKNotificationInfo+AgileDictionary.m │ ├── CKNotificationInfo.h │ ├── CKNotificationInfo.m │ ├── CKNotificationInfo_Private.h │ ├── CKOperation.h │ ├── CKOperation.m │ ├── CKRecord+AgileDictionary.h │ ├── CKRecord+AgileDictionary.m │ ├── CKRecord.h │ ├── CKRecord.m │ ├── CKRecordID+AgileDictionary.h │ ├── CKRecordID+AgileDictionary.m │ ├── CKRecordID.h │ ├── CKRecordID.m │ ├── CKRecordZone+AgileDictionary.h │ ├── CKRecordZone+AgileDictionary.m │ ├── CKRecordZone.h │ ├── CKRecordZone.m │ ├── CKRecordZoneID+AgileDictionary.h │ ├── CKRecordZoneID+AgileDictionary.m │ ├── CKRecordZoneID.h │ ├── CKRecordZoneID.m │ ├── CKRecordZoneID_Private.h │ ├── CKRecord_Private.h │ ├── CKReference+AgileDictionary.h │ ├── CKReference+AgileDictionary.m │ ├── CKReference+CKFilterType.h │ ├── CKReference.h │ ├── CKReference.m │ ├── CKReference_Private.h │ ├── CKServerChangeToken+AgileDictionary.h │ ├── CKServerChangeToken+AgileDictionary.m │ ├── CKServerChangeToken.h │ ├── CKServerChangeToken.m │ ├── CKServerChangeToken_Private.h │ ├── CKSubscription+AgileDictionary.h │ ├── CKSubscription+AgileDictionary.m │ ├── CKSubscription.h │ ├── CKSubscription.m │ ├── CKSubscription_Private.h │ ├── CLLocation+AgileDictionary.h │ ├── CLLocation+AgileDictionary.m │ ├── CLLocation+CKFilterType.h │ ├── Defines.h │ ├── Info.plist │ ├── JSValue+AgileCloudSDKExtensions.h │ ├── JSValue+AgileCloudSDKExtensions.m │ ├── NSApplication+AgileCloudSDK.h │ ├── NSApplication+AgileCloudSDK.m │ ├── NSArray+AgileMap.h │ ├── NSArray+AgileMap.m │ ├── NSArray+CKFilterType.h │ ├── NSDate+CKFilterType.h │ ├── NSError+AgileCloudSDKExtensions.h │ ├── NSError+AgileCloudSDKExtensions.m │ ├── NSNumber+CKFilterType.h │ ├── NSString+CKFilterType.h │ ├── config-format.js │ ├── container-config-format.json │ └── test.html ├── CloudZone ├── CloudZone.xcodeproj │ ├── project.pbxproj │ └── xcuserdata │ │ └── kevin.xcuserdatad │ │ └── xcschemes │ │ ├── AgileCloudZone.xcscheme │ │ ├── CloudZone.xcscheme │ │ └── xcschememanagement.plist └── CloudZone │ ├── AgileCloudSDKView.h │ ├── AgileCloudSDKView.m │ ├── AgileCloudZone-Info.plist │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Base.lproj │ └── Main.storyboard │ ├── CloudSDKView.h │ ├── CloudSDKView.m │ ├── CloudZone-Info.plist │ ├── CloudZone.entitlements │ ├── Constants.h │ ├── Images.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json │ ├── ViewController.h │ ├── ViewController.m │ ├── main.m │ ├── palmtree.jpg │ └── sunset.jpg ├── License.txt ├── README.md └── Using AgileCloudSDK.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | ./build/ 3 | *.pbxuser 4 | !default.pbxuser 5 | *.mode1v3 6 | !default.mode1v3 7 | *.mode2v3 8 | !default.mode2v3 9 | *.perspectivev3 10 | !default.perspectivev3 11 | xcuserdata 12 | *.xccheckout 13 | profile 14 | *.moved-aside 15 | DerivedData 16 | .idea/ 17 | *.rej 18 | 19 | # build - danger zone, do not ignore all build folders 20 | ObjectiveC.gcno 21 | AgileCloud.xcworkspace/xcshareddata/AgileCloud.xcscmblueprint -------------------------------------------------------------------------------- /AgileCloud.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /AgileCloud.xcworkspace/xcuserdata/kevin.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agilebits/AgileCloudSDK/9193c4124e05c4443f9c323472468e2cae53bda9/AgileCloud.xcworkspace/xcuserdata/kevin.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/AgileCloudSDK.h: -------------------------------------------------------------------------------- 1 | // 2 | // AgileCloudSDK.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | #import 11 | #import 12 | #import 13 | #import 14 | #import 15 | #import 16 | #import 17 | #import 18 | #import 19 | #import 20 | #import 21 | #import 22 | #import 23 | #import 24 | #import 25 | #import 26 | #import 27 | #import 28 | #import 29 | #import 30 | #import 31 | #import 32 | #import 33 | #import 34 | #import 35 | #import 36 | #import 37 | #import 38 | #import 39 | #import 40 | #import 41 | #import 42 | #import 43 | #import 44 | #import 45 | #import 46 | #import 47 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKAsset+AgileDictionary.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKAsset+AgileDictionary.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import "CKAsset.h" 9 | 10 | @interface CKAsset (AgileDictionary) 11 | 12 | - (NSDictionary *)asAgileDictionary; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKAsset+AgileDictionary.m: -------------------------------------------------------------------------------- 1 | // 2 | // CKAsset+AgileDictionary.m 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import "CKAsset+AgileDictionary.h" 9 | #import "CKAsset_Private.h" 10 | 11 | @implementation CKAsset (AgileDictionary) 12 | 13 | - (NSDictionary *)asAgileDictionary { 14 | NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithDictionary:@{ @"fileChecksum": self.fileChecksum, 15 | @"referenceChecksum": self.referenceChecksum, 16 | @"size": @(self.fileSize), 17 | @"wrappingKey": self.wrappingKey }]; 18 | if (self.receipt) { 19 | [dict setObject:self.receipt forKey:@"receipt"]; 20 | } 21 | return dict; 22 | } 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKAsset.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKAsset.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | @interface CKAsset : NSObject 11 | 12 | - (instancetype)init NS_UNAVAILABLE; 13 | 14 | /* Initialize an asset to be saved with the content at the given file URL */ 15 | - (instancetype)initWithFileURL:(NSURL *)fileURL; 16 | 17 | /* Local file URL where fetched records are cached and saved records originate from. */ 18 | @property(nonatomic, readonly, copy) NSURL *fileURL; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKAsset.m: -------------------------------------------------------------------------------- 1 | // 2 | // CKAsset.m 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #import "CKAsset_Private.h" 9 | #import "CKDatabase_Private.h" 10 | #import "Defines.h" 11 | #import "CKMediator.h" 12 | #import "CKRecord.h" 13 | 14 | @interface CKAsset () 15 | 16 | @end 17 | 18 | @implementation CKAsset { 19 | NSString *_downloadURL; 20 | NSString *_fileChecksum; 21 | NSString *_referenceChecksum; 22 | NSUInteger _fileSize; 23 | NSString *_wrappingKey; 24 | NSString *_receipt; 25 | NSURLSessionDownloadTask *_downloadTask; 26 | NSError *_downloadError; 27 | 28 | dispatch_semaphore_t _downloadSema; 29 | void (^_progressBlock)(double); 30 | } 31 | 32 | static NSOperationQueue *_downloadQueue; 33 | 34 | 35 | + (NSOperationQueue *)downloadQueue { 36 | static dispatch_once_t onceToken; 37 | dispatch_once(&onceToken, ^{ 38 | _downloadQueue = [[NSOperationQueue alloc] init]; 39 | _downloadQueue.maxConcurrentOperationCount = 1; 40 | }); 41 | return _downloadQueue; 42 | } 43 | 44 | - (instancetype)init { 45 | if (self = [super init]) { 46 | _downloadSema = dispatch_semaphore_create(0); 47 | } 48 | return self; 49 | } 50 | 51 | - (NSError *)downloadError { 52 | return _downloadError; 53 | } 54 | 55 | /* Initialize an asset to be saved with the content at the given file URL */ 56 | - (instancetype)initWithFileURL:(NSURL *)fileURL { 57 | if (self = [super init]) { 58 | _fileURL = fileURL; 59 | _downloadSema = dispatch_semaphore_create(0); 60 | } 61 | return self; 62 | } 63 | 64 | - (instancetype)initWithDictionary:(NSDictionary *)dictionary { 65 | if (self = [super init]) { 66 | _fileURL = nil; 67 | _downloadURL = dictionary[@"downloadURL"]; 68 | _downloadSema = dispatch_semaphore_create(0); 69 | [self updateWithDictionary:dictionary]; 70 | } 71 | return self; 72 | } 73 | 74 | - (void)updateWithDictionary:(NSDictionary *)dictionary { 75 | _fileChecksum = dictionary[@"fileChecksum"]; 76 | _referenceChecksum = dictionary[@"referenceChecksum"]; 77 | _fileSize = [dictionary[@"size"] unsignedIntegerValue]; 78 | _wrappingKey = dictionary[@"wrappingKey"]; 79 | _receipt = dictionary[@"receipt"]; 80 | } 81 | 82 | - (NSString *)fileChecksum { 83 | return _fileChecksum; 84 | } 85 | 86 | - (NSString *)referenceChecksum { 87 | return _referenceChecksum; 88 | } 89 | 90 | - (NSUInteger)fileSize { 91 | return _fileSize; 92 | } 93 | 94 | - (NSString *)wrappingKey { 95 | return _wrappingKey; 96 | } 97 | 98 | - (NSString *)receipt { 99 | return _receipt; 100 | } 101 | 102 | #pragma mark - Downloading 103 | 104 | - (void)downloadSynchronouslyWithProgressBlock:(void (^)(double progress))progressBlock { 105 | NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration]; 106 | NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration delegate:self delegateQueue:[CKAsset downloadQueue]]; 107 | NSString *esc = [_downloadURL stringByReplacingOccurrencesOfString:@"{" withString:@"%7B"]; 108 | esc = [esc stringByReplacingOccurrencesOfString:@"}" withString:@"%7D"]; 109 | NSURL *downloadURL = [NSURL URLWithString:esc]; 110 | if (downloadURL && !_fileURL) { 111 | _progressBlock = progressBlock; 112 | _downloadTask = [session downloadTaskWithRequest:[NSURLRequest requestWithURL:downloadURL]]; 113 | [_downloadTask resume]; 114 | dispatch_semaphore_wait(_downloadSema, DISPATCH_TIME_FOREVER); 115 | } 116 | } 117 | 118 | - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite { 119 | // Calculate Progress 120 | double progress = (double)totalBytesWritten / (double)totalBytesExpectedToWrite; 121 | if (_progressBlock) 122 | _progressBlock(progress); 123 | } 124 | 125 | - (void)URLSession:(NSURLSession *)session 126 | downloadTask:(NSURLSessionDownloadTask *)downloadTask 127 | didFinishDownloadingToURL:(NSURL *)location { 128 | _progressBlock = nil; 129 | _fileURL = location; 130 | _downloadError = nil; 131 | dispatch_semaphore_signal(_downloadSema); 132 | } 133 | 134 | - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { 135 | if (error) { 136 | _progressBlock = nil; 137 | _downloadError = error; 138 | _fileURL = nil; 139 | } 140 | dispatch_semaphore_signal(_downloadSema); 141 | } 142 | 143 | #pragma mark - Uploading 144 | 145 | 146 | - (void)uploadSynchronouslyIntoRecord:(CKRecord *)record andField:(NSString *)fieldName inDatabase:(CKDatabase *)database { 147 | DebugLog(CKLOG_LEVEL_DEBUG, @"uploading into %@ at %@", record.recordID, fieldName); 148 | 149 | NSDictionary *requestDictionary = @{}; 150 | [database sendPOSTRequestTo:@"assets/upload" withJSON:requestDictionary completionHandler:^(id jsonResponse, NSError *error) { 151 | if (error != nil) { 152 | DebugLog(CKLOG_LEVEL_ERR, @"Error uploading asset: %@ error: %@", jsonResponse, error); 153 | } 154 | }]; 155 | } 156 | 157 | #pragma mark - Description 158 | 159 | - (NSString *)description { 160 | return [NSString stringWithFormat:@"[CKAsset: %@]", self.fileURL]; 161 | } 162 | 163 | @end 164 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKAsset_Private.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKAsset_Private.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import 9 | #import "CKAsset.h" 10 | #import "CKDatabase.h" 11 | 12 | @interface CKAsset (AgilePrivate) 13 | 14 | - (instancetype)initWithDictionary:(NSDictionary *)dictionary; 15 | 16 | - (void)updateWithDictionary:(NSDictionary *)dictionary; 17 | 18 | - (void)downloadSynchronouslyWithProgressBlock:(void (^)(double progress))progressBlock; 19 | 20 | - (void)uploadSynchronouslyIntoRecord:(CKRecord *)record andField:(NSString *)fieldName inDatabase:(CKDatabase *)database; 21 | 22 | // after downloading synchronously, an asset will either 23 | // have a fileURL or a downloadError set 24 | @property(nonatomic, readonly, copy) NSError *downloadError; 25 | 26 | @property(nonatomic, readonly) NSString *fileChecksum; 27 | @property(nonatomic, readonly) NSString *referenceChecksum; 28 | @property(nonatomic, readonly) NSUInteger fileSize; 29 | @property(nonatomic, readonly) NSString *wrappingKey; 30 | @property(nonatomic, readonly) NSString *receipt; 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKBlockOperation.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKBlockOperation.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | @interface CKBlockOperation : NSOperation 11 | 12 | // You MUST call the onComplete block in your block or it won't get removed from the queue - kevin 2016-01-05 13 | - (instancetype)initWithBlock:(void (^)(void (^onComplete)(void)))block; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKBlockOperation.m: -------------------------------------------------------------------------------- 1 | // 2 | // CKBlockOperation.m 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import "CKBlockOperation.h" 9 | 10 | @implementation CKBlockOperation { 11 | BOOL _executing; 12 | BOOL _finished; 13 | void (^_block)(void (^)(void)); 14 | } 15 | 16 | - (instancetype)initWithBlock:(void (^)(void (^onComplete)(void)))block { 17 | if (self = [super init]) { 18 | _block = block; 19 | } 20 | return self; 21 | } 22 | 23 | - (void)setExecuting:(BOOL)executing { 24 | [self willChangeValueForKey:@"isExecuting"]; 25 | _executing = executing; 26 | [self didChangeValueForKey:@"isExecuting"]; 27 | } 28 | 29 | - (BOOL)isExecuting { 30 | return _executing; 31 | } 32 | 33 | - (void)setFinished:(BOOL)finished { 34 | [self willChangeValueForKey:@"isFinished"]; 35 | _finished = finished; 36 | [self didChangeValueForKey:@"isFinished"]; 37 | } 38 | 39 | - (BOOL)isFinished { 40 | return _finished; 41 | } 42 | 43 | - (BOOL)asynchronous { 44 | return YES; 45 | } 46 | 47 | - (void)start { 48 | [self setExecuting:YES]; 49 | dispatch_async(dispatch_get_main_queue(), ^{ 50 | _block(^{ 51 | [self setExecuting:NO]; 52 | [self setFinished:YES]; 53 | }); 54 | }); 55 | } 56 | 57 | @end 58 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKContainer.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKContainer.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #import 9 | #import "CKDefines.h" 10 | #import 11 | 12 | @class CKDatabase, CKOperation, CKRecordID, CKDiscoveredUserInfo; 13 | 14 | // This constant represents the current user's ID for zone ID 15 | CK_EXTERN NSString *const CKOwnerDefaultName NS_AVAILABLE(10_10, 8_0); 16 | 17 | @interface CKContainer : NSObject 18 | 19 | - (instancetype)init NS_UNAVAILABLE; 20 | 21 | /* These calls return a CKContainer initialized with a containerIdentifier of 22 | [NSString stringWithFormat:@"iCloud.%@", applicationIdentifier], where application identifier 23 | is the calling process' application-identifier (com.apple.application-identifier on OS X) entitlement. 24 | If the application is in production mode (aka, 25 | com.apple.developer.icloud-container-environment 26 | is set to Production in your entitlements plist, and you have no override in 27 | com.apple.developer.icloud-container-development-container-identifiers 28 | ), then the production environment is used. */ 29 | + (CKContainer *)defaultContainer; 30 | + (CKContainer *)containerWithIdentifier:(NSString *)containerIdentifier; 31 | 32 | @property(nonatomic, readonly) NSString *containerIdentifier; 33 | 34 | - (void)addOperation:(CKOperation *)operation; 35 | 36 | @end 37 | 38 | @interface CKContainer (Database) 39 | 40 | /* Public vs. Private databases: 41 | Records in a public database 42 | - By default are world readable, owner writable. 43 | - Can be locked down by Roles, a process done in the Developer Portal, a web interface. Roles are not present in the client API. 44 | - Are visible to the application developer via the Developer Portal. 45 | - Do not contribute to the owner's iCloud account storage quota. 46 | Records in a private database 47 | - By default are only owner readable and owner writable. 48 | - Are not visible to the application developer via the Developer Portal. 49 | - Are counted towards the owner's iCloud account storage quota. 50 | */ 51 | @property(nonatomic, readonly) CKDatabase *privateCloudDatabase; 52 | @property(nonatomic, readonly) CKDatabase *publicCloudDatabase; 53 | 54 | @end 55 | 56 | typedef NS_ENUM(NSInteger, CKAccountStatus) { 57 | /* An error occurred when getting the account status, consult the corresponding NSError */ 58 | CKAccountStatusCouldNotDetermine = 0, 59 | /* The iCloud account credentials are available for this application */ 60 | CKAccountStatusAvailable = 1, 61 | /* Parental Controls / Device Management has denied access to iCloud account credentials */ 62 | CKAccountStatusRestricted = 2, 63 | /* No iCloud account is logged in on this device */ 64 | CKAccountStatusNoAccount = 3, 65 | } NS_ENUM_AVAILABLE(10_10, 8_0); 66 | 67 | @interface CKContainer (AccountStatus) 68 | 69 | - (void)accountStatusWithCompletionHandler:(void (^)(CKAccountStatus accountStatus, NSError *error))completionHandler; 70 | 71 | @end 72 | 73 | typedef NS_OPTIONS(NSUInteger, CKApplicationPermissions) { 74 | /* Allows the user's record to be discoverable via the user's email address */ 75 | CKApplicationPermissionUserDiscoverability = 1 << 0, 76 | } NS_AVAILABLE(10_10, 8_0); 77 | 78 | typedef NS_ENUM(NSInteger, CKApplicationPermissionStatus) { 79 | /* The user has not made a decision for this application permission. */ 80 | CKApplicationPermissionStatusInitialState = 0, 81 | /* An error occurred when getting or setting the application permission status, consult the corresponding NSError */ 82 | CKApplicationPermissionStatusCouldNotComplete = 1, 83 | /* The user has denied this application permission */ 84 | CKApplicationPermissionStatusDenied = 2, 85 | /* The user has granted this application permission */ 86 | CKApplicationPermissionStatusGranted = 3, 87 | } NS_ENUM_AVAILABLE(10_10, 8_0); 88 | 89 | typedef void (^CKApplicationPermissionBlock)(CKApplicationPermissionStatus applicationPermissionStatus, NSError *error); 90 | 91 | @interface CKContainer (ApplicationPermission) 92 | 93 | - (void)statusForApplicationPermission:(CKApplicationPermissions)applicationPermission completionHandler:(CKApplicationPermissionBlock)completionHandler; 94 | - (void)requestApplicationPermission:(CKApplicationPermissions)applicationPermission completionHandler:(CKApplicationPermissionBlock)completionHandler; 95 | 96 | @end 97 | 98 | @interface CKContainer (UserRecords) 99 | 100 | /* If there is no iCloud account configured, or if access is restricted, a CKErrorNotAuthenticated error will be returned. */ 101 | - (void)fetchUserRecordIDWithCompletionHandler:(void (^)(CKRecordID *recordID, NSError *error))completionHandler; 102 | 103 | /* This fetches all user records that match an entry in the user's address book. */ 104 | - (void)discoverAllContactUserInfosWithCompletionHandler:(void (^)(NSArray /* CKDiscoveredUserInfo */ *userInfos, NSError *error))completionHandler; 105 | - (void)discoverUserInfoWithEmailAddress:(NSString *)email completionHandler:(void (^)(CKDiscoveredUserInfo *userInfo, NSError *error))completionHandler; 106 | - (void)discoverUserInfoWithUserRecordID:(CKRecordID *)userRecordID completionHandler:(void (^)(CKDiscoveredUserInfo *userInfo, NSError *error))completionHandler; 107 | 108 | @end 109 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKContainer.m: -------------------------------------------------------------------------------- 1 | // 2 | // CKContainer.m 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #import 9 | #import 10 | #import 11 | #import 12 | #import "CKMediator_Private.h" 13 | #import "CKBlockOperation.h" 14 | #import "Defines.h" 15 | #import "CKContainer_Private.h" 16 | #import "CKDatabase.h" 17 | #import "JSValue+AgileCloudSDKExtensions.h" 18 | #import "NSError+AgileCloudSDKExtensions.h" 19 | #import "CKError.h" 20 | #import "CKOperation.h" 21 | 22 | // This constant represents the current user's ID for zone ID 23 | NSString *const CKOwnerDefaultName = @"__defaultOwner__"; 24 | 25 | @interface CKContainer (Private) 26 | 27 | - (instancetype)init NS_AVAILABLE(10_10, 8_0); 28 | 29 | @end 30 | 31 | @implementation CKContainer { 32 | CKDatabase *_publicCloudDatabase; 33 | CKDatabase *_privateCloudDatabase; 34 | NSDictionary *_containerProperties; 35 | } 36 | 37 | @synthesize containerIdentifier; 38 | 39 | static CGFloat targetInterval; 40 | static NSOperationQueue *_urlQueue; 41 | + (NSOperationQueue *)urlQueue { 42 | static dispatch_once_t onceToken; 43 | dispatch_once(&onceToken, ^{ 44 | _urlQueue = [[NSOperationQueue alloc] init]; 45 | _urlQueue.maxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount; 46 | }); 47 | return _urlQueue; 48 | } 49 | 50 | static NSURLSession *_downloadSession; 51 | + (NSURLSession *)downloadSession { 52 | static dispatch_once_t onceToken; 53 | dispatch_once(&onceToken, ^{ 54 | _downloadSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:nil delegateQueue:_urlQueue]; 55 | }); 56 | return _downloadSession; 57 | } 58 | 59 | static NSURLSession *_uploadSession; 60 | + (NSURLSession *)uploadSession { 61 | static dispatch_once_t onceToken; 62 | dispatch_once(&onceToken, ^{ 63 | _uploadSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:nil delegateQueue:_urlQueue]; 64 | }); 65 | return _uploadSession; 66 | } 67 | 68 | static CKContainer *_defaultContainer; 69 | 70 | + (CKContainer *)defaultContainer { 71 | static dispatch_once_t onceToken; 72 | dispatch_once(&onceToken, ^{ 73 | _defaultContainer = [CKContainer containerWithIdentifier:[[[CKMediator sharedMediator] containerProperties] firstObject][CloudContainerNameKey]]; 74 | }); 75 | return _defaultContainer; 76 | } 77 | 78 | static NSMutableDictionary *containers; 79 | 80 | + (CKContainer *)containerWithIdentifier:(NSString *)containerIdentifier { 81 | static dispatch_once_t onceToken; 82 | dispatch_once(&onceToken, ^{ 83 | containers = [NSMutableDictionary dictionary]; 84 | }); 85 | if (![containers objectForKey:containerIdentifier]) { 86 | NSDictionary *properties = [[CKMediator sharedMediator] infoForContainerID:containerIdentifier]; 87 | CKContainer *container = [[CKContainer alloc] initWithProperties:properties]; 88 | [containers setObject:container forKey:containerIdentifier]; 89 | } 90 | return [containers objectForKey:containerIdentifier]; 91 | } 92 | 93 | #pragma mark - Initializer and Private Properties 94 | 95 | - (instancetype)initWithProperties:(NSDictionary *)properties { 96 | if (self = [super init]) { 97 | if (!properties) { 98 | @throw [NSException exceptionWithName:@"CKContainerException" reason:@"No properties found for container" userInfo:nil]; 99 | } 100 | _containerProperties = properties; 101 | 102 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(cloudIdentityDidChange:) name:NSUbiquityIdentityDidChangeNotification object:nil]; 103 | } 104 | return self; 105 | } 106 | 107 | - (void)dealloc { 108 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 109 | } 110 | 111 | - (NSString *)containerIdentifier { 112 | return _containerProperties[CloudContainerNameKey]; 113 | } 114 | 115 | // each container contains keys for: CloudContainerName, CloudAPIToken, CloudEnvironment 116 | - (NSString *)cloudContainerName { 117 | return [[CKMediator sharedMediator] infoForContainerID:self.containerIdentifier][CloudContainerNameKey]; 118 | } 119 | 120 | - (NSString *)cloudAPIToken { 121 | return [[CKMediator sharedMediator] infoForContainerID:self.containerIdentifier][CloudAPITokenKey]; 122 | } 123 | 124 | - (NSString *)cloudEnvironment { 125 | return [[CKMediator sharedMediator] infoForContainerID:self.containerIdentifier][CloudEnvironmentKey]; 126 | } 127 | 128 | - (JSValue *)asJSValue { 129 | if (![[CKMediator sharedMediator] isInitialized]) { 130 | @throw [NSException exceptionWithName:@"CannotUseContainerUntilInitialized" reason:@"Before using this container, CKMediator must be initialized" userInfo:nil]; 131 | } 132 | __block JSValue *value; 133 | void (^block)(void) = ^{ 134 | value = [[[[CKMediator sharedMediator] context] evaluateScript:@"CloudKit"] invokeMethod:@"getContainer" withArguments:@[self.containerIdentifier]]; 135 | }; 136 | if (![NSThread isMainThread]) { 137 | dispatch_sync(dispatch_get_main_queue(), block); 138 | } 139 | else { 140 | block(); 141 | } 142 | return value; 143 | } 144 | 145 | #pragma mark - Properties 146 | 147 | - (CKDatabase *)publicCloudDatabase { 148 | if (!_publicCloudDatabase) { 149 | _publicCloudDatabase = [[[CKDatabase class] alloc] initWithContainer:self isPublic:YES]; 150 | } 151 | 152 | return _publicCloudDatabase; 153 | } 154 | 155 | - (CKDatabase *)privateCloudDatabase { 156 | if (!_privateCloudDatabase) { 157 | _privateCloudDatabase = [[[CKDatabase class] alloc] initWithContainer:self isPublic:NO]; 158 | } 159 | 160 | return _privateCloudDatabase; 161 | } 162 | 163 | #pragma mark - Methods 164 | 165 | - (void)addOperation:(CKOperation *)operation { 166 | [[[CKMediator sharedMediator] queue] addOperation:operation]; 167 | } 168 | 169 | - (void)accountStatusWithCompletionHandler:(void (^)(CKAccountStatus, NSError *))completionHandler { 170 | CKBlockOperation *blockOp = [[CKBlockOperation alloc] initWithBlock:^(void (^opCompletionBlock)(void)) { 171 | [[[[self asJSValue] agile_invokeMethod:@"fetchUserInfo"] invokeMethod:@"then" withArguments:@[^(id userinfo) { 172 | completionHandler(userinfo ? CKAccountStatusAvailable : CKAccountStatusNoAccount, nil); 173 | opCompletionBlock(); 174 | }]] invokeMethod:@"catch" 175 | withArguments:@[^(id errorDictionary) { 176 | NSError* error = [[NSError alloc] initWithCKErrorDictionary:errorDictionary]; 177 | if (error.code == CKErrorNotAuthenticated) { 178 | self.accountStatusCompletionHandler = completionHandler; 179 | [[CKMediator sharedMediator] login]; 180 | } 181 | else { 182 | completionHandler(CKAccountStatusCouldNotDetermine, error); 183 | } 184 | opCompletionBlock(); 185 | }]]; 186 | }]; 187 | [[[CKMediator sharedMediator] queue] addOperation:blockOp]; 188 | } 189 | 190 | - (void)statusForApplicationPermission:(CKApplicationPermissions)applicationPermission completionHandler:(CKApplicationPermissionBlock)completionHandler { 191 | CKBlockOperation *blockOp = [[CKBlockOperation alloc] initWithBlock:^(void (^opCompletionBlock)(void)) { 192 | [[[[self asJSValue] agile_invokeMethod:@"fetchUserInfo"] invokeMethod:@"then" withArguments:@[^(id userinfo) { 193 | if ([[userinfo objectForKey:@"isDiscoverable"] boolValue]) { 194 | completionHandler(CKApplicationPermissionStatusGranted, nil); 195 | } 196 | else { 197 | completionHandler(CKApplicationPermissionStatusInitialState, nil); 198 | } 199 | opCompletionBlock(); 200 | }]] invokeMethod:@"catch" 201 | withArguments:@[^(id errorDictionary) { 202 | NSError* error = [[NSError alloc] initWithCKErrorDictionary:errorDictionary]; 203 | completionHandler(CKApplicationPermissionStatusCouldNotComplete, error); 204 | opCompletionBlock(); 205 | }]]; 206 | }]; 207 | [[[CKMediator sharedMediator] queue] addOperation:blockOp]; 208 | } 209 | 210 | #pragma mark - Push Notifications 211 | 212 | - (void)registerForRemoteNotifications { 213 | CKBlockOperation *blockOp = [[CKBlockOperation alloc] initWithBlock:^(void (^opCompletionBlock)(void)) { 214 | NSString *createRemoteNotificationTokenURL = [NSString stringWithFormat:@"https://api.apple-cloudkit.com/device/1/%@/%@/tokens/create?ckAPIToken=%@&ckSession=%@", 215 | self.cloudContainerName, 216 | self.cloudEnvironment, 217 | self.cloudAPIToken, 218 | [CKContainer percentEscape:[CKMediator sharedMediator].sessionToken]]; 219 | 220 | NSDictionary *postData = @{ @"apnsEnvironment": self.cloudEnvironment }; 221 | 222 | NSURL *url = [NSURL URLWithString:createRemoteNotificationTokenURL]; 223 | 224 | [CKContainer sendPOSTRequestTo:url withJSON:postData completionHandler:^(id jsonResponse, NSError *error) { 225 | if (error != nil) { 226 | DebugLog(CKLOG_LEVEL_ERR, @"json response: %@ error: %@", jsonResponse, error); 227 | } 228 | 229 | if (jsonResponse[@"webcourierURL"]) { 230 | NSData* tokenData = [jsonResponse[@"apnsToken"] dataUsingEncoding:NSUTF8StringEncoding]; 231 | [[[NSApplication sharedApplication] delegate] application:[NSApplication sharedApplication] didRegisterForRemoteNotificationsWithDeviceToken:tokenData]; 232 | 233 | [self longPollAtURL:jsonResponse[@"webcourierURL"]]; 234 | } 235 | else { 236 | [[[NSApplication sharedApplication] delegate] application:[NSApplication sharedApplication] didFailToRegisterForRemoteNotificationsWithError:error]; 237 | } 238 | opCompletionBlock(); 239 | }]; 240 | }]; 241 | [[[CKMediator sharedMediator] queue] addOperation:blockOp]; 242 | } 243 | 244 | 245 | #pragma mark - Web Services 246 | 247 | + (void)sendPOSTRequestTo:(NSURL *)fetchURL withJSON:(id)postData completionHandler:(void (^)(id jsonResponse, NSError *error))completionHandler { 248 | NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init]; 249 | [request addValue:@"text/plain" forHTTPHeaderField:@"Content-Type"]; 250 | [request setHTTPMethod:@"POST"]; 251 | [request setURL:fetchURL]; 252 | 253 | NSError *jsonError; 254 | NSData *jsonData = [NSJSONSerialization dataWithJSONObject:postData options:0 error:&jsonError]; 255 | if (jsonError) { 256 | completionHandler(nil, jsonError); 257 | return; 258 | } 259 | 260 | [request setHTTPBody:jsonData]; 261 | 262 | [[[CKContainer downloadSession] dataTaskWithRequest:request completionHandler:^(NSData *_Nullable data, NSURLResponse *_Nullable response, NSError *_Nullable error) { 263 | if (error != nil) { 264 | if (completionHandler) { 265 | completionHandler(nil, error); 266 | } 267 | } 268 | else if ([response isKindOfClass:[NSHTTPURLResponse class]]) { 269 | NSError* jsonError = nil; 270 | id jsonObj = nil; 271 | if (data != nil) { 272 | jsonObj = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError]; 273 | 274 | if (jsonError) { 275 | error = jsonError; 276 | } 277 | else if ([jsonObj isKindOfClass:[NSDictionary class]] && jsonObj[@"serverErrorCode"]) { 278 | error = [[NSError alloc] initWithCKErrorDictionary:jsonObj]; 279 | } 280 | } 281 | else { 282 | DebugLog(CKLOG_LEVEL_CRIT, @"If there's no error, there should be data for request: %@ with response: %@", request, response); 283 | } 284 | 285 | if (completionHandler) { 286 | completionHandler(jsonObj, error); 287 | } 288 | } 289 | else { 290 | if (completionHandler) { 291 | completionHandler(nil, error); 292 | } 293 | } 294 | }] resume]; 295 | } 296 | 297 | 298 | + (void)sendPOSTRequestTo:(NSURL *)uploadDestination withFile:(NSURL *)localFile completionHandler:(void (^)(id jsonResponse, NSError *error))completionHandler { 299 | NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init]; 300 | [request addValue:@"text/plain" forHTTPHeaderField:@"Content-Type"]; 301 | [request setHTTPMethod:@"POST"]; 302 | [request setURL:uploadDestination]; 303 | 304 | 305 | [[[CKContainer uploadSession] uploadTaskWithRequest:request fromFile:localFile completionHandler:^(NSData *_Nullable data, NSURLResponse *_Nullable response, NSError *_Nullable error) { 306 | if (error != nil) { 307 | if (completionHandler) { 308 | completionHandler(nil, error); 309 | } 310 | } 311 | else if ([response isKindOfClass:[NSHTTPURLResponse class]]) { 312 | 313 | NSError* jsonError; 314 | id jsonObj = nil; 315 | if (data != nil) { 316 | jsonObj = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError]; 317 | 318 | if (jsonError) { 319 | error = jsonError; 320 | } 321 | else if ([jsonObj isKindOfClass:[NSDictionary class]] && jsonObj[@"serverErrorCode"]) { 322 | error = [[NSError alloc] initWithCKErrorDictionary:jsonObj]; 323 | } 324 | } 325 | else { 326 | DebugLog(CKLOG_LEVEL_CRIT, @"If there's no error, there should be data for request: %@ with response: %@", request, response); 327 | } 328 | 329 | if (completionHandler) { 330 | completionHandler(jsonObj, error); 331 | } 332 | } 333 | else { 334 | if (completionHandler) { 335 | completionHandler(nil, error); 336 | } 337 | } 338 | }] resume]; 339 | } 340 | 341 | 342 | + (NSString *)percentEscape:(NSString *)string { 343 | return [[[[string stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding] stringByReplacingOccurrencesOfString:@"/" withString:@"%2F"] stringByReplacingOccurrencesOfString:@"+" withString:@"%2B"] stringByReplacingOccurrencesOfString:@"=" withString:@"%3D"]; 344 | } 345 | 346 | #pragma mark - Long Poll for Push Notifications 347 | 348 | - (void)longPollAtURL:(NSString *)urlString { 349 | urlString = [urlString stringByReplacingOccurrencesOfString:@":443" withString:@""]; 350 | 351 | DebugLog(CKLOG_LEVEL_DEBUG, @"long polling at: %@", urlString); 352 | 353 | NSURL *longPollURL = [NSURL URLWithString:urlString]; 354 | NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init]; 355 | [request setHTTPMethod:@"GET"]; 356 | [request setURL:longPollURL]; 357 | [request setTimeoutInterval:86400]; 358 | 359 | [NSURLConnection sendAsynchronousRequest:request queue:[[CKMediator sharedMediator] queue] completionHandler:^(NSURLResponse *_Nullable response, NSData *_Nullable data, NSError *_Nullable connectionError) { 360 | 361 | NSError* jsonError; 362 | id jsonObj; 363 | if (data) { 364 | jsonObj= [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError]; 365 | } 366 | 367 | if (jsonObj) { 368 | targetInterval = 0; 369 | [[[NSApplication sharedApplication] delegate] application:[NSApplication sharedApplication] didReceiveRemoteNotification:jsonObj]; 370 | jsonError = nil; // clear out the error since we're comparing against errors below. 371 | } 372 | 373 | if (!connectionError && !jsonError) { 374 | [self longPollAtURL:urlString]; 375 | } 376 | else { 377 | dispatch_async(dispatch_get_main_queue(), ^{ 378 | // slowly retry every 1, 2, 4, 8 ... seconds up to 1 minute retry intervals 379 | targetInterval = MAX(1, MIN(60, targetInterval * 2)); 380 | DebugLog(CKLOG_LEVEL_ERR, @"Long poll failed, retrying in: %.2f connectionError:%@ jsonError:%@", targetInterval, connectionError, jsonError); 381 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(targetInterval * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 382 | [self longPollAtURL:urlString]; 383 | }); 384 | }); 385 | } 386 | }]; 387 | } 388 | 389 | #pragma mark - identity notification change 390 | 391 | - (void)cloudIdentityDidChange:(NSNotification *)note { 392 | if (self.accountStatusCompletionHandler != nil) { 393 | self.accountStatusCompletionHandler([note.userInfo[CKAccountStatusNotificationUserInfoKey] integerValue], nil); 394 | self.accountStatusCompletionHandler = nil; 395 | } 396 | } 397 | 398 | @end 399 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKContainer_Private.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKContainer_Private.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import "CKContainer.h" 9 | 10 | @interface CKContainer () 11 | 12 | + (NSOperationQueue *)urlQueue; 13 | + (NSURLSession *)downloadSession; 14 | + (NSURLSession *)uploadSession; 15 | 16 | @property(nonatomic, readonly) NSString *cloudContainerName; 17 | @property(nonatomic, readonly) NSString *cloudAPIToken; 18 | @property(nonatomic, readonly) NSString *cloudEnvironment; 19 | 20 | @property (nonatomic, copy) void (^accountStatusCompletionHandler)(CKAccountStatus, NSError *); 21 | 22 | - (JSValue *)asJSValue; 23 | 24 | #pragma mark - Push Notifications 25 | 26 | - (void)registerForRemoteNotifications; 27 | 28 | #pragma mark - Web Services 29 | 30 | + (void)sendPOSTRequestTo:(NSURL *)fetchURL withJSON:(id)postData completionHandler:(void (^)(id jsonResponse, NSError *error))completionHandler; 31 | + (void)sendPOSTRequestTo:(NSURL *)uploadDestination withFile:(NSURL *)localFile completionHandler:(void (^)(id jsonResponse, NSError *error))completionHandler; 32 | + (NSString *)percentEscape:(NSString *)str; 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKDatabase.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKDatabase.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | @class CKDatabaseOperation, CKRecord, CKRecordID, CKRecordZone, CKRecordZoneID, CKQuery, CKSubscription, CKContainer; 11 | 12 | @interface CKDatabase : NSObject 13 | - (instancetype)init NS_UNAVAILABLE; 14 | - (void)addOperation:(CKDatabaseOperation *)operation; 15 | 16 | @property(nonatomic, readonly) BOOL isPublic; 17 | 18 | @end 19 | 20 | @interface CKDatabase (ConvenienceMethods) 21 | 22 | - (instancetype)initWithContainer:(CKContainer *)container isPublic:(BOOL)_public; 23 | 24 | /* Convenience APIs 25 | These calls operate on a single item in the default zone and allow for simple operations. 26 | If you'd like to batch your requests, add dependencies between requests, set priorities, 27 | or schedule operations on your own queue, take a look at the corresponding CKOperation. 28 | */ 29 | - (void)fetchRecordWithID:(CKRecordID *)recordID completionHandler:(void (^)(CKRecord *record, NSError *error))completionHandler; 30 | - (void)saveRecord:(CKRecord *)record completionHandler:(void (^)(CKRecord *record, NSError *error))completionHandler; 31 | - (void)deleteRecordWithID:(CKRecordID *)recordID completionHandler:(void (^)(CKRecordID *recordID, NSError *error))completionHandler; 32 | 33 | - (void)performQuery:(CKQuery *)query inZoneWithID:(CKRecordZoneID *)zoneID completionHandler:(void (^)(NSArray /* CKRecord */ *results, NSError *error))completionHandler; 34 | 35 | - (void)fetchAllRecordZonesWithCompletionHandler:(void (^)(NSArray /* CKRecordZone */ *zones, NSError *error))completionHandler; 36 | - (void)fetchRecordZoneWithID:(CKRecordZoneID *)zoneID completionHandler:(void (^)(CKRecordZone *zone, NSError *error))completionHandler; 37 | - (void)saveRecordZone:(CKRecordZone *)zone completionHandler:(void (^)(CKRecordZone *zone, NSError *error))completionHandler; 38 | - (void)deleteRecordZoneWithID:(CKRecordZoneID *)zoneID completionHandler:(void (^)(CKRecordZoneID *zoneID, NSError *error))completionHandler; 39 | 40 | - (void)fetchSubscriptionWithID:(NSString *)subscriptionID completionHandler:(void (^)(CKSubscription *subscription, NSError *error))completionHandler; 41 | - (void)fetchAllSubscriptionsWithCompletionHandler:(void (^)(NSArray /* CKSubscription */ *subscriptions, NSError *error))completionHandler; 42 | - (void)saveSubscription:(CKSubscription *)subscription completionHandler:(void (^)(CKSubscription *subscription, NSError *error))completionHandler; 43 | - (void)deleteSubscriptionWithID:(NSString *)subscriptionID completionHandler:(void (^)(NSString *subscriptionID, NSError *error))completionHandler; 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKDatabase.m: -------------------------------------------------------------------------------- 1 | // 2 | // CKDatabase.m 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #import 9 | #import 10 | #import "JSValue+AgileCloudSDKExtensions.h" 11 | #import "CKDatabase_Private.h" 12 | #import "CKMediator_Private.h" 13 | #import "Defines.h" 14 | #import "CKRecordID+AgileDictionary.h" 15 | #import "CKRecord+AgileDictionary.h" 16 | #import "CKSubscription+AgileDictionary.h" 17 | #import "CKSubscription_Private.h" 18 | #import "CKRecord_Private.h" 19 | #import "CKContainer_Private.h" 20 | #import "CKBlockOperation.h" 21 | #import "CKFetchRecordsOperation.h" 22 | #import "CKModifyRecordsOperation.h" 23 | #import "CKModifyRecordZonesOperation.h" 24 | #import "NSError+AgileCloudSDKExtensions.h" 25 | #import "CKModifySubscriptionsOperation.h" 26 | #import "CKRecordZone.h" 27 | #import "CKRecordZoneID.h" 28 | #import "CKError.h" 29 | 30 | 31 | @implementation CKDatabase { 32 | BOOL _public; 33 | CKContainer *_container; 34 | } 35 | 36 | - (instancetype)initWithContainer:(CKContainer *)container isPublic:(BOOL) public { 37 | if (self = [super init]) { 38 | _public = public; 39 | _container = container; 40 | } 41 | return self; 42 | } 43 | 44 | - (CKContainer *)container { 45 | return _container; 46 | } 47 | 48 | - (BOOL)isPublic { 49 | return _public; 50 | } 51 | 52 | - (void)addOperation:(CKOperation *)operation { 53 | [[[CKMediator sharedMediator] queue] addOperation:operation]; 54 | } 55 | 56 | - (JSValue *)asJSValue { 57 | if (![[CKMediator sharedMediator] isInitialized]) { 58 | @throw [NSException exceptionWithName:@"CannotUseContainerUntilInitialized" reason:@"Before using this container, CKMediator must be initialized" userInfo:nil]; 59 | } 60 | __block JSValue *value; 61 | void (^block)(void) = ^{ 62 | if (self.isPublic) { 63 | value = [[[self container] asJSValue] valueForProperty:@"publicCloudDatabase"]; 64 | } 65 | else { 66 | value = [[[self container] asJSValue] valueForProperty:@"privateCloudDatabase"]; 67 | } 68 | }; 69 | if (![NSThread isMainThread]) { 70 | dispatch_sync(dispatch_get_main_queue(), block); 71 | } 72 | else { 73 | block(); 74 | } 75 | return value; 76 | } 77 | 78 | - (void)fetchRecordWithID:(CKRecordID *)recordID completionHandler:(void (^)(CKRecord *record, NSError *error))completionHandler { 79 | CKFetchRecordsOperation *fetchOperation = [[CKFetchRecordsOperation alloc] initWithRecordIDs:@[recordID]]; 80 | fetchOperation.fetchRecordsCompletionBlock = ^(NSDictionary *dict, NSError *error) { 81 | if (error) { 82 | NSError* recordError = error.userInfo[CKErrorUserInfoPartialErrorsKey][recordID]; 83 | completionHandler(nil, recordError); 84 | } 85 | else { 86 | completionHandler([dict allValues][0], nil); 87 | } 88 | }; 89 | [self addOperation:fetchOperation]; 90 | } 91 | 92 | - (void)saveRecord:(CKRecord *)record completionHandler:(void (^)(CKRecord *record, NSError *error))completionHandler { 93 | CKModifyRecordsOperation *modOp = [[CKModifyRecordsOperation alloc] initWithRecordsToSave:@[record] recordIDsToDelete:nil]; 94 | modOp.modifyRecordsCompletionBlock = ^(NSArray /* CKRecord */ *savedRecords, NSArray /* CKRecordID */ *deletedRecordIDs, NSError *operationError) { 95 | if (operationError) { 96 | completionHandler(nil, operationError); 97 | } 98 | else { 99 | completionHandler(savedRecords[0], nil); 100 | } 101 | }; 102 | [self addOperation:modOp]; 103 | } 104 | 105 | - (void)deleteRecordWithID:(CKRecordID *)recordID completionHandler:(void (^)(CKRecordID *recordID, NSError *error))completionHandler { 106 | CKModifyRecordsOperation *modOp = [[CKModifyRecordsOperation alloc] initWithRecordsToSave:nil recordIDsToDelete:@[recordID]]; 107 | modOp.modifyRecordsCompletionBlock = ^(NSArray /* CKRecord */ *savedRecords, NSArray /* CKRecordID */ *deletedRecordIDs, NSError *operationError) { 108 | if (operationError) { 109 | completionHandler(nil, operationError); 110 | } 111 | else { 112 | completionHandler(deletedRecordIDs[0], nil); 113 | } 114 | }; 115 | [self addOperation:modOp]; 116 | } 117 | 118 | 119 | - (void)performQuery:(CKQuery *)query inZoneWithID:(CKRecordZoneID *)zoneID completionHandler:(void (^)(NSArray /* CKRecord */ *results, NSError *error))completionHandler { 120 | @throw kAbstractMethodException; 121 | } 122 | 123 | - (void)fetchAllRecordZonesWithCompletionHandler:(void (^)(NSArray /* CKRecordZone */ *zones, NSError *error))completionHandler { 124 | [self fetchAllRecordZonesFromSender:self withCompletionHandler:completionHandler]; 125 | } 126 | 127 | - (void)fetchAllRecordZonesFromSender:(id)sender withCompletionHandler:(void (^)(NSArray /* CKRecordZone */ *zones, NSError *error))completionHandler { 128 | CKBlockOperation *blockOp = [[CKBlockOperation alloc] initWithBlock:^(void (^opCompletionBlock)(void)) { 129 | [[[[self asJSValue] agile_invokeMethod:@"fetchAllRecordZones"] invokeMethod:@"then" withArguments:@[^(id response) { 130 | if ([response[@"_errors"] count]) { 131 | NSError* error = [[NSError alloc] initWithCKErrorDictionary:response[@"_errors"][0]]; 132 | completionHandler(nil, error); 133 | } 134 | else { 135 | NSMutableArray* zones = [NSMutableArray array]; 136 | for (NSDictionary* dict in (response[@"_zones"] ?: response[@"_results"])) { 137 | [zones addObject:[[CKRecordZone alloc] initWithZoneName:dict[@"zoneID"][@"zoneName"]]]; 138 | } 139 | completionHandler(zones, nil); 140 | } 141 | opCompletionBlock(); 142 | }]] invokeMethod:@"catch" 143 | withArguments:@[^(NSDictionary *errorDictionary) { 144 | NSError* error = [[NSError alloc] initWithCKErrorDictionary:errorDictionary]; 145 | completionHandler(nil, error); 146 | opCompletionBlock(); 147 | }]]; 148 | }]; 149 | 150 | if ([sender isKindOfClass:[CKOperation class]] && ((CKOperation *)sender).isExecuting) { 151 | [[CKMediator sharedMediator] addInnerOperation:blockOp]; 152 | } 153 | else { 154 | [[CKMediator sharedMediator] addOperation:blockOp]; 155 | } 156 | } 157 | 158 | - (void)fetchRecordZoneWithID:(CKRecordZoneID *)zoneID completionHandler:(void (^)(CKRecordZone *zone, NSError *error))completionHandler { 159 | [self fetchRecordZoneWithID:zoneID fromSender:self completionHandler:completionHandler]; 160 | } 161 | 162 | - (void)fetchRecordZoneWithID:(CKRecordZoneID *)zoneID fromSender:(id)sender completionHandler:(void (^)(CKRecordZone *zone, NSError *error))completionHandler { 163 | CKBlockOperation *blockOp = [[CKBlockOperation alloc] initWithBlock:^(void (^opCompletionBlock)(void)) { 164 | [[[[self asJSValue] invokeMethod:@"fetchRecordZone" withArguments:@[@{ @"zoneName": zoneID.zoneName }]] invokeMethod:@"then" withArguments:@[^(id response) { 165 | if ([response[@"_errors"] count]) { 166 | NSError* error = [[NSError alloc] initWithCKErrorDictionary:response[@"_errors"][0]]; 167 | completionHandler(nil, error); 168 | } 169 | else { 170 | NSMutableArray* zones = [NSMutableArray array]; 171 | for (NSDictionary* dict in (response[@"_zones"] ?: response[@"_results"])) { 172 | [zones addObject:[[CKRecordZone alloc] initWithZoneName:dict[@"zoneID"][@"zoneName"]]]; 173 | } 174 | completionHandler(zones[0], nil); 175 | } 176 | opCompletionBlock(); 177 | }]] invokeMethod:@"catch" 178 | withArguments:@[^(NSDictionary *errorDictionary) { 179 | NSError* error = [[NSError alloc] initWithCKErrorDictionary:errorDictionary]; 180 | completionHandler(nil, error); 181 | opCompletionBlock(); 182 | }]]; 183 | }]; 184 | 185 | if ([sender isKindOfClass:[CKOperation class]] && ((CKOperation *)sender).isExecuting) { 186 | [[CKMediator sharedMediator] addInnerOperation:blockOp]; 187 | } 188 | else { 189 | [[CKMediator sharedMediator] addOperation:blockOp]; 190 | } 191 | } 192 | 193 | - (void)saveRecordZone:(CKRecordZone *)zone completionHandler:(void (^)(CKRecordZone *zone, NSError *error))completionHandler { 194 | CKBlockOperation *blockOp = [[CKBlockOperation alloc] initWithBlock:^(void (^opCompletionBlock)(void)) { 195 | [[[[self asJSValue] invokeMethod:@"saveRecordZone" withArguments:@[@{ @"zoneName": zone.zoneID.zoneName }]] invokeMethod:@"then" withArguments:@[^(id response) { 196 | if ([response[@"_errors"] count]) { 197 | NSError* error = [[NSError alloc] initWithCKErrorDictionary:response[@"_errors"][0]]; 198 | completionHandler(nil, error); 199 | } 200 | else { 201 | NSMutableArray* zones = [NSMutableArray array]; 202 | for (NSDictionary* dict in (response[@"_zones"] ?: response[@"_results"])) { 203 | [zones addObject:[[CKRecordZone alloc] initWithZoneName:dict[@"zoneID"][@"zoneName"]]]; 204 | } 205 | completionHandler(zones[0], nil); 206 | } 207 | opCompletionBlock(); 208 | }]] invokeMethod:@"catch" 209 | withArguments:@[^(NSDictionary *errorDictionary) { 210 | NSError* error = [[NSError alloc] initWithCKErrorDictionary:errorDictionary]; 211 | completionHandler(nil, error); 212 | opCompletionBlock(); 213 | }]]; 214 | }]; 215 | [[[CKMediator sharedMediator] queue] addOperation:blockOp]; 216 | } 217 | 218 | - (void)deleteRecordZoneWithID:(CKRecordZoneID *)zoneID completionHandler:(void (^)(CKRecordZoneID *zoneID, NSError *error))completionHandler { 219 | CKModifyRecordZonesOperation *delOp = [[CKModifyRecordZonesOperation alloc] initWithRecordZonesToSave:nil recordZoneIDsToDelete:@[zoneID]]; 220 | delOp.modifyRecordZonesCompletionBlock = ^(NSArray *savedRecordZones, NSArray *deletedRecordZoneIDs, NSError *operationError) { 221 | if (operationError) { 222 | completionHandler(nil, operationError); 223 | } 224 | else { 225 | completionHandler(zoneID, nil); 226 | } 227 | }; 228 | [self addOperation:delOp]; 229 | } 230 | 231 | 232 | - (void)fetchSubscriptionWithID:(NSString *)subscriptionID completionHandler:(void (^)(CKSubscription *subscription, NSError *error))completionHandler { 233 | @throw kAbstractMethodException; 234 | } 235 | 236 | - (void)fetchAllSubscriptionsWithCompletionHandler:(void (^)(NSArray /* CKSubscription */ *subscriptions, NSError *error))completionHandler { 237 | CKBlockOperation *blockOp = [[CKBlockOperation alloc] initWithBlock:^(void (^opCompletionBlock)(void)) { 238 | [[[[self asJSValue] agile_invokeMethod:@"fetchAllSubscriptions"] invokeMethod:@"then" withArguments:@[^(id response) { 239 | if ([response[@"_errors"] count]) { 240 | NSError* error = [[NSError alloc] initWithCKErrorDictionary:response[@"_errors"][0]]; 241 | completionHandler(nil, error); 242 | } 243 | else { 244 | NSMutableArray* subs = [NSMutableArray array]; 245 | for (NSDictionary* dict in (response[@"_subscriptions"] ?: response[@"_results"])) { 246 | [subs addObject:[[CKSubscription alloc] initWithDictionary:dict]]; 247 | } 248 | completionHandler(subs, nil); 249 | } 250 | opCompletionBlock(); 251 | }]] invokeMethod:@"catch" 252 | withArguments:@[^(NSDictionary *errorDictionary) { 253 | NSError* error = [[NSError alloc] initWithCKErrorDictionary:errorDictionary]; 254 | completionHandler(nil, error); 255 | opCompletionBlock(); 256 | }]]; 257 | }]; 258 | [[[CKMediator sharedMediator] queue] addOperation:blockOp]; 259 | } 260 | 261 | - (void)saveSubscription:(CKSubscription *)subscription completionHandler:(void (^)(CKSubscription *subscription, NSError *error))completionHandler { 262 | CKModifySubscriptionsOperation *modOp = [[CKModifySubscriptionsOperation alloc] initWithSubscriptionsToSave:@[subscription] subscriptionIDsToDelete:nil]; 263 | modOp.modifySubscriptionsCompletionBlock = ^(NSArray *savedSubscriptions, NSArray *deletedSubscriptionIDs, NSError *operationError) { 264 | if (operationError) { 265 | completionHandler(nil, operationError); 266 | } 267 | else { 268 | completionHandler(savedSubscriptions[0], nil); 269 | } 270 | }; 271 | [self addOperation:modOp]; 272 | } 273 | 274 | - (void)deleteSubscriptionWithID:(NSString *)subscriptionID completionHandler:(void (^)(NSString *subscriptionID, NSError *error))completionHandler { 275 | CKBlockOperation *blockOp = [[CKBlockOperation alloc] initWithBlock:^(void (^opCompletionBlock)(void)) { 276 | [[[[self asJSValue] invokeMethod:@"deleteSubscription" withArguments:@[@{ @"subscriptionID": subscriptionID }]] invokeMethod:@"then" withArguments:@[^(id response) { 277 | if ([response[@"_errors"] count]) { 278 | NSError* error = [[NSError alloc] initWithCKErrorDictionary:response[@"_errors"][0]]; 279 | completionHandler(nil, error); 280 | } 281 | else { 282 | completionHandler(subscriptionID, nil); 283 | } 284 | opCompletionBlock(); 285 | }]] invokeMethod:@"catch" 286 | withArguments:@[^(id errorDictionary) { 287 | NSError* error = [[NSError alloc] initWithCKErrorDictionary:errorDictionary]; 288 | completionHandler(nil, error); 289 | opCompletionBlock(); 290 | }]]; 291 | }]; 292 | [[[CKMediator sharedMediator] queue] addOperation:blockOp]; 293 | } 294 | 295 | 296 | #pragma mark - Web Services 297 | 298 | 299 | - (void)sendPOSTRequestTo:(NSString *)fragment withJSON:(id)postData completionHandler:(void (^)(id jsonResponse, NSError *error))completionHandler { 300 | NSString *fetchURLString = [NSString stringWithFormat:@"https://api.apple-cloudkit.com/database/1/%@/%@/%@/%@?ckAPIToken=%@&ckSession=%@", 301 | self.container.cloudContainerName, 302 | self.container.cloudEnvironment, 303 | self.isPublic ? @"public" : @"private", 304 | fragment, 305 | self.container.cloudAPIToken, 306 | [CKContainer percentEscape:[CKMediator sharedMediator].sessionToken]]; 307 | 308 | [CKContainer sendPOSTRequestTo:[NSURL URLWithString:fetchURLString] withJSON:postData completionHandler:completionHandler]; 309 | } 310 | 311 | 312 | @end 313 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKDatabaseOperation.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKDatabaseOperation.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #import "CKOperation.h" 9 | 10 | @class CKDatabase; 11 | 12 | @interface CKDatabaseOperation : CKOperation 13 | 14 | /* If no database is set, [self.container privateCloudDatabase] is used. */ 15 | @property(nonatomic, strong) CKDatabase *database; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKDatabaseOperation.m: -------------------------------------------------------------------------------- 1 | // 2 | // CKDatabaseOperation.m 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #import "CKDatabaseOperation.h" 9 | #import "CKMediator_Private.h" 10 | #import "Defines.h" 11 | #import "CKContainer.h" 12 | 13 | @implementation CKDatabaseOperation { 14 | BOOL _executing; 15 | BOOL _finished; 16 | } 17 | 18 | - (void)setExecuting:(BOOL)executing { 19 | [self willChangeValueForKey:@"isExecuting"]; 20 | _executing = executing; 21 | [self didChangeValueForKey:@"isExecuting"]; 22 | } 23 | 24 | - (BOOL)isExecuting { 25 | return _executing; 26 | } 27 | 28 | - (void)setFinished:(BOOL)finished { 29 | [self willChangeValueForKey:@"isFinished"]; 30 | _finished = finished; 31 | [self didChangeValueForKey:@"isFinished"]; 32 | } 33 | 34 | - (BOOL)isFinished { 35 | return _finished; 36 | } 37 | 38 | - (BOOL)asynchronous { 39 | return YES; 40 | } 41 | 42 | - (CKDatabase *)database { 43 | if (!_database) { 44 | _database = [[CKContainer defaultContainer] privateCloudDatabase]; 45 | } 46 | return _database; 47 | } 48 | 49 | @end 50 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKDatabaseOperation_Private.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKDatabaseOperation_Private.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import "CKDatabaseOperation.h" 9 | 10 | @interface CKDatabaseOperation (AgilePrivate) 11 | 12 | // subclasses that implement this should call the operation's completion block with error 13 | - (void)completeWithError:(NSError *)error; 14 | 15 | /* If no database is set, [self.container privateCloudDatabase] is used. */ 16 | @property(nonatomic, assign, getter=isExecuting) BOOL executing; 17 | @property(nonatomic, assign, getter=isFinished) BOOL finished; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKDatabase_Private.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKDatabase_Private.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import "CKDatabase.h" 9 | 10 | @interface CKDatabase (Private) 11 | 12 | @property(nonatomic, readonly) CKContainer *container; 13 | 14 | - (void)fetchAllRecordZonesFromSender:(id)sender withCompletionHandler:(void (^)(NSArray /* CKRecordZone */ *zones, NSError *error))completionHandler; 15 | - (void)fetchRecordZoneWithID:(CKRecordZoneID *)zoneID fromSender:(id)sender completionHandler:(void (^)(CKRecordZone *zone, NSError *error))completionHandler; 16 | 17 | - (void)sendPOSTRequestTo:(NSString *)fragment withJSON:(id)postData completionHandler:(void (^)(id jsonResponse, NSError *error))completionHandler; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKDefines.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKDefines.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #ifndef CK_EXTERN 9 | #ifdef __cplusplus 10 | #define CK_EXTERN extern "C" __attribute__((visibility("default"))) 11 | #else 12 | #define CK_EXTERN extern __attribute__((visibility("default"))) 13 | #endif 14 | #endif 15 | 16 | 17 | #if DEBUG 18 | #define CK_UNIT_TESTS_AVAILABLE NS_CLASS_AVAILABLE(10_10, 8_0) 19 | #define CK_UNIT_TESTS_EXTERN CK_EXTERN 20 | #else 21 | #define CK_UNIT_TESTS_AVAILABLE NS_CLASS_AVAILABLE(10_10, 8_0) 22 | #define CK_UNIT_TESTS_EXTERN CK_EXTERN 23 | #endif 24 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKDiscoveredUserInfo.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKDiscoveredUserInfo.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | @class CKRecordID; 11 | 12 | @interface CKDiscoveredUserInfo : NSObject 13 | 14 | - (instancetype)init NS_UNAVAILABLE; 15 | 16 | @property(nonatomic, readonly, copy) CKRecordID *userRecordID; 17 | @property(nonatomic, readonly, copy) NSString *firstName; 18 | @property(nonatomic, readonly, copy) NSString *lastName; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKDiscoveredUserInfo.m: -------------------------------------------------------------------------------- 1 | // 2 | // CKDiscoveredUserInfo.m 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #import 9 | #import "CKDiscoveredUserInfo.h" 10 | 11 | @implementation CKDiscoveredUserInfo 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKError.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKError.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #import 9 | #import "CKDefines.h" 10 | 11 | CK_EXTERN NSString *const CKErrorDomain NS_AVAILABLE(10_10, 8_0); 12 | 13 | CK_EXTERN NSString *const CKPartialErrorsByItemIDKey NS_AVAILABLE(10_10, 8_0); 14 | 15 | /* If the server rejects a record save because it has been modified since the last time it was read, 16 | a CKErrorServerRecordChanged error will be returned and it will contain versions of the record 17 | in its userInfo dictionary. Apply your custom conflict resolution logic to the server record (CKServerRecordKey) 18 | and attempt a save of that record. */ 19 | CK_EXTERN NSString *const CKRecordChangedErrorAncestorRecordKey NS_AVAILABLE(10_10, 8_0); 20 | CK_EXTERN NSString *const CKRecordChangedErrorServerRecordKey NS_AVAILABLE(10_10, 8_0); 21 | CK_EXTERN NSString *const CKRecordChangedErrorClientRecordKey NS_AVAILABLE(10_10, 8_0); 22 | 23 | /* On CKErrorServiceUnavailable or CKErrorRequestRateLimited errors the userInfo dictionary 24 | may contain a NSNumber instance that specifies the period of time in seconds after 25 | which the client may retry the request. 26 | */ 27 | CK_EXTERN NSString *const CKErrorRetryAfterKey NS_AVAILABLE(10_10, 8_0); 28 | 29 | typedef NS_ENUM(NSInteger, CKErrorCode) { 30 | CKErrorInternalError = 1, /* framework encountered an error. This is a non-recoverable error. */ 31 | CKErrorPartialFailure = 2, /* Some items failed, but the operation succeeded overall */ 32 | CKErrorNetworkUnavailable = 3, /* Network not available */ 33 | CKErrorNetworkFailure = 4, /* Network error (available but CFNetwork gave us an error) */ 34 | CKErrorBadContainer = 5, /* Un-provisioned or unauthorized container. Try provisioning the container before retrying the operation. */ 35 | CKErrorServiceUnavailable = 6, /* Service unavailable */ 36 | CKErrorRequestRateLimited = 7, /* Client is being rate limited */ 37 | CKErrorMissingEntitlement = 8, /* Missing entitlement */ 38 | CKErrorNotAuthenticated = 9, /* Not authenticated (writing without being logged in, no user record) */ 39 | CKErrorPermissionFailure = 10, /* Access failure (save or fetch) */ 40 | CKErrorUnknownItem = 11, /* Record does not exist */ 41 | CKErrorInvalidArguments = 12, /* Bad client request (bad record graph, malformed predicate) */ 42 | CKErrorResultsTruncated = 13, /* Query results were truncated by the server */ 43 | CKErrorServerRecordChanged = 14, /* The record was rejected because the version on the server was different */ 44 | CKErrorServerRejectedRequest = 15, /* The server rejected this request. This is a non-recoverable error */ 45 | CKErrorAssetFileNotFound = 16, /* Asset file was not found */ 46 | CKErrorAssetFileModified = 17, /* Asset file content was modified while being saved */ 47 | CKErrorIncompatibleVersion = 18, /* App version is less than the minimum allowed version */ 48 | CKErrorConstraintViolation = 19, /* The server rejected the request because there was a conflict with a unique field. */ 49 | CKErrorOperationCancelled = 20, /* A CKOperation was explicitly cancelled */ 50 | CKErrorChangeTokenExpired = 21, /* The previousServerChangeToken value is too old and the client must re-sync from scratch */ 51 | CKErrorBatchRequestFailed = 22, /* One of the items in this batch operation failed in a zone with atomic updates, so the entire batch was rejected. */ 52 | CKErrorZoneBusy = 23, /* The server is too busy to handle this zone operation. Try the operation again in a few seconds. */ 53 | CKErrorBadDatabase = 24, /* Operation could not be completed on the given database. Likely caused by attempting to modify zones in the public database. */ 54 | CKErrorQuotaExceeded = 25, /* Saving a record would exceed quota */ 55 | CKErrorZoneNotFound = 26, /* The specified zone does not exist on the server */ 56 | CKErrorLimitExceeded = 27, /* The request to the server was too large. Retry this request as a smaller batch. */ 57 | CKErrorUserDeletedZone = 28, /* The user deleted this zone through the settings UI. Your client should either remove its local data or prompt the user before attempting to re-upload any data to this zone. */ 58 | } NS_ENUM_AVAILABLE(10_10, 8_0); 59 | 60 | /* Userinfo keys for CKErrors */ 61 | CK_EXTERN NSString *const CKErrorUserInfoPartialErrorsKey NS_AVAILABLE(10_10, 8_0); 62 | CK_EXTERN NSString *const CKErrorUserInfoContainerIDKey NS_AVAILABLE(10_10, 8_0); 63 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKError.m: -------------------------------------------------------------------------------- 1 | // 2 | // CKError.m 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #import 9 | #import "CKError.h" 10 | 11 | NSString *const CKErrorDomain = @"CKErrorDomain"; 12 | 13 | NSString *const CKPartialErrorsByItemIDKey = @"CKPartialErrorsByItemIDKey"; 14 | 15 | /* If the server rejects a record save because it has been modified since the last time it was read, 16 | a CKErrorServerRecordChanged error will be returned and it will contain versions of the record 17 | in its userInfo dictionary. Apply your custom conflict resolution logic to the server record (CKServerRecordKey) 18 | and attempt a save of that record. */ 19 | NSString *const CKRecordChangedErrorAncestorRecordKey = @"CKRecordChangedErrorAncestorRecordKey"; 20 | NSString *const CKRecordChangedErrorServerRecordKey = @"CKRecordChangedErrorServerRecordKey"; 21 | NSString *const CKRecordChangedErrorClientRecordKey = @"CKRecordChangedErrorClientRecordKey"; 22 | 23 | /* On CKErrorServiceUnavailable or CKErrorRequestRateLimited errors the userInfo dictionary 24 | may contain a NSNumber instance that specifies the period of time in seconds after 25 | which the client may retry the request. 26 | */ 27 | NSString *const CKErrorRetryAfterKey = @"CKErrorRetryAfterKey"; 28 | 29 | /* Userinfo keys for CKErrors */ 30 | NSString *const CKErrorUserInfoPartialErrorsKey = @"CKPartialErrors"; 31 | NSString *const CKErrorUserInfoContainerIDKey = @"ContainerID"; -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKFetchNotificationChangesOperation.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKFetchNotificationChangesOperation.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #import "CKDatabaseOperation.h" 9 | 10 | @class CKNotification, CKServerChangeToken; 11 | 12 | @interface CKFetchNotificationChangesOperation : CKOperation 13 | 14 | /* This operation will fetch all notification changes. 15 | If a change anchor from a previous CKFetchNotificationChangesOperation is passed in, only the notifications that have changed 16 | since that anchor will be fetched. 17 | If this is your first fetch, pass nil for the change anchor. 18 | Change anchors are opaque tokens and clients should not infer any behavior based on their content. */ 19 | // not needed yet - kevin 2015-12-21 20 | //- (instancetype)initWithPreviousServerChangeToken:(CKServerChangeToken *)previousServerChangeToken; 21 | 22 | @property(nonatomic, copy) CKServerChangeToken *previousServerChangeToken; 23 | 24 | @property(nonatomic, assign) NSUInteger resultsLimit; 25 | 26 | /* Will be set before fetchNotificationChangesCompletionBlock is called. If moreComing is true then the server wasn't able to return all the changes in this response. 27 | Another CKFetchNotificationChangesOperation operation should be run with the updated serverChangeToken token from this operation. */ 28 | @property(nonatomic, readonly) BOOL moreComing; 29 | 30 | @property(nonatomic, copy) void (^notificationChangedBlock)(CKNotification *notification); 31 | 32 | /* Clients are responsible for saving the change token at the end of the operation and passing it in to the next call to CKFetchNotificationChangesOperation. 33 | Note that a fetch can fail partway. If that happens, an updated change token may be returned in the completion 34 | block so that already fetched notifications don't need to be re-downloaded on a subsequent operation. 35 | If the server returns a CKErrorChangeTokenExpired error, the previousServerChangeToken value was too old and the client should toss its local cache and 36 | re-fetch notification changes starting with a nil previousServerChangeToken. */ 37 | @property(nonatomic, copy) void (^fetchNotificationChangesCompletionBlock)(CKServerChangeToken *serverChangeToken, NSError *operationError); 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKFetchNotificationChangesOperation.m: -------------------------------------------------------------------------------- 1 | // 2 | // CKFetchNotificationChangesOperation.m 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #import "CKDatabaseOperation.h" 9 | #import "CKFetchNotificationChangesOperation.h" 10 | 11 | @implementation CKFetchNotificationChangesOperation 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKFetchRecordChangesOperation.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKFetchRecordChangesOperation.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #import "CKDatabaseOperation.h" 9 | #import "CKServerChangeToken.h" 10 | 11 | @class CKRecord, CKRecordID, CKRecordZoneID; 12 | 13 | @interface CKFetchRecordChangesOperation : CKDatabaseOperation 14 | 15 | /* This operation will fetch all records changes in the given record zone. 16 | If a change anchor from a previous CKFetchRecordChangesOperation is passed in, only the records that have changed 17 | since that anchor will be fetched. 18 | If this is your first fetch or if you wish to re-fetch all records, pass nil for the change anchor. 19 | Change anchors are opaque tokens and clients should not infer any behavior based on their content. */ 20 | - (instancetype)initWithRecordZoneID:(CKRecordZoneID *)recordZoneID previousServerChangeToken:(CKServerChangeToken *)previousServerChangeToken; 21 | 22 | @property(nonatomic, copy) CKRecordZoneID *recordZoneID; 23 | @property(nonatomic, copy) CKServerChangeToken *previousServerChangeToken; 24 | 25 | @property(nonatomic, assign) NSUInteger resultsLimit; 26 | 27 | /* Declares which user-defined keys should be fetched and added to the resulting CKRecords. If nil, declares the entire record should be downloaded. If set to an empty array, declares that no user fields should be downloaded. Defaults to nil. */ 28 | @property(nonatomic, copy) NSArray /* NSString */ *desiredKeys; 29 | 30 | @property(nonatomic, copy) void (^recordChangedBlock)(CKRecord *record); 31 | @property(nonatomic, copy) void (^recordWithIDWasDeletedBlock)(CKRecordID *recordID); 32 | 33 | /* Will be set before fetchRecordChangesCompletionBlock is called. If moreComing is true then the server wasn't able to return all the changes in this response. 34 | Another CKFetchRecordChangesOperation operation should be run with the updated serverChangeToken token from this operation. */ 35 | @property(nonatomic, readonly) BOOL moreComing; 36 | 37 | /* Clients are responsible for saving the change token at the end of the operation and passing it in to the next call to CKFetchRecordChangesOperation. 38 | Note that a fetch can fail partway. If that happens, an updated change token may be returned in the completion 39 | block so that already fetched records don't need to be re-downloaded on a subsequent operation. 40 | The clientChangeToken from the most recent CKModifyRecordsOperation is also returned, or nil if none was provided. 41 | If the server returns a CKErrorChangeTokenExpired error, the previousServerChangeToken value was too old and the client should toss its local cache and 42 | re-fetch the changes in this record zone starting with a nil previousServerChangeToken. */ 43 | @property(nonatomic, copy) void (^fetchRecordChangesCompletionBlock)(CKServerChangeToken *serverChangeToken, NSData *clientChangeTokenData, NSError *operationError); 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKFetchRecordChangesOperation.m: -------------------------------------------------------------------------------- 1 | // 2 | // CKFetchRecordChangesOperation.m 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #import "CKFetchRecordChangesOperation.h" 9 | #import "CKDatabaseOperation_Private.h" 10 | #import "NSError+AgileCloudSDKExtensions.h" 11 | #import "NSArray+AgileMap.h" 12 | #import "CKRecordID+AgileDictionary.h" 13 | #import "CKServerChangeToken+AgileDictionary.h" 14 | #import "CKDatabase_Private.h" 15 | #import "CKServerChangeToken_Private.h" 16 | #import "CKRecord_Private.h" 17 | #import "Defines.h" 18 | #import "CKRecordZoneID.h" 19 | 20 | @implementation CKFetchRecordChangesOperation 21 | 22 | /* This operation will fetch all records changes in the given record zone. 23 | If a change anchor from a previous CKFetchRecordChangesOperation is passed in, only the records that have changed 24 | since that anchor will be fetched. 25 | If this is your first fetch or if you wish to re-fetch all records, pass nil for the change anchor. 26 | Change anchors are opaque tokens and clients should not infer any behavior based on their content. */ 27 | - (instancetype)initWithRecordZoneID:(CKRecordZoneID *)recordZoneID previousServerChangeToken:(CKServerChangeToken *)previousServerChangeToken { 28 | if (self = [super init]) { 29 | _recordZoneID = recordZoneID; 30 | _previousServerChangeToken = previousServerChangeToken; 31 | } 32 | return self; 33 | } 34 | 35 | 36 | - (void)start { 37 | [self setExecuting:YES]; 38 | 39 | NSMutableDictionary *requestDictionary = [NSMutableDictionary dictionaryWithDictionary:@{ 40 | @"zoneID": @{@"zoneName": _recordZoneID.zoneName} 41 | }]; 42 | if (_desiredKeys) { 43 | requestDictionary[@"desiredKeys"] = _desiredKeys; 44 | } 45 | if (_resultsLimit) { 46 | requestDictionary[@"resultsLimit"] = @(_resultsLimit); 47 | } 48 | if (_previousServerChangeToken) { 49 | requestDictionary[@"syncToken"] = [_previousServerChangeToken asString]; 50 | } 51 | 52 | [self.database sendPOSTRequestTo:@"records/changes" withJSON:requestDictionary completionHandler:^(id jsonResponse, NSError *operationError) { 53 | 54 | __block NSError* localOperationError; 55 | 56 | _moreComing = [jsonResponse[@"moreComing"] boolValue]; 57 | CKServerChangeToken *serverChangeToken = [[CKServerChangeToken alloc] initWithString:jsonResponse[@"syncToken"]]; 58 | 59 | if ([jsonResponse isKindOfClass:[NSDictionary class]] && jsonResponse[@"records"]) { 60 | [jsonResponse[@"records"] enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 61 | NSError* recordError = nil; 62 | 63 | if ([obj[@"deleted"] boolValue]) { 64 | CKRecordID* deletedRecordID = [[CKRecordID alloc] initWithRecordName:obj[@"recordName"] zoneID:_recordZoneID]; 65 | if (self.recordWithIDWasDeletedBlock) { 66 | self.recordWithIDWasDeletedBlock(deletedRecordID); 67 | } 68 | } 69 | else { 70 | CKRecord* record = [[CKRecord alloc] initWithDictionary:obj inZone:_recordZoneID]; 71 | CKRecordID* recordID = record.recordID; 72 | if (!record) { 73 | recordError = [[NSError alloc] initWithCKErrorDictionary:obj]; 74 | if (obj[@"recordName"]) { 75 | recordID = [[CKRecordID alloc] initWithRecordName:obj[@"recordName"] zoneID:_recordZoneID]; 76 | } 77 | } 78 | else { 79 | NSArray* errs = [record synchronouslyDownloadAllAssetsWithProgressBlock:nil]; 80 | if ([errs count]) { 81 | recordError = errs[0]; 82 | record = nil; 83 | } 84 | } 85 | 86 | if (record) { 87 | if (self.recordChangedBlock) { 88 | self.recordChangedBlock(record); 89 | } 90 | } 91 | else if (!localOperationError) { 92 | localOperationError = recordError; 93 | } 94 | } 95 | }]; 96 | } 97 | else if (!operationError) { 98 | operationError = [[NSError alloc] initWithCKErrorDictionary:jsonResponse]; 99 | } 100 | if (localOperationError) { 101 | operationError = localOperationError; 102 | } 103 | 104 | if (self.fetchRecordChangesCompletionBlock) { 105 | self.fetchRecordChangesCompletionBlock(serverChangeToken, nil, operationError); 106 | } 107 | 108 | 109 | [self setExecuting:NO]; 110 | [self setFinished:YES]; 111 | }]; 112 | } 113 | 114 | @end 115 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKFetchRecordZonesOperation.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKFetchRecordZonesOperation.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #import "CKDatabaseOperation.h" 9 | 10 | @interface CKFetchRecordZonesOperation : CKDatabaseOperation 11 | 12 | + (instancetype)fetchAllRecordZonesOperation; 13 | 14 | - (instancetype)init NS_DESIGNATED_INITIALIZER; 15 | - (instancetype)initWithRecordZoneIDs:(NSArray /* CKRecordZoneID */ *)zoneIDs; 16 | 17 | @property(nonatomic, copy) NSArray /* CKRecordZoneID */ *recordZoneIDs; 18 | 19 | /* This block is called when the operation completes. 20 | The [NSOperation completionBlock] will also be called if both are set. 21 | If the error is CKErrorPartialFailure, the error's userInfo dictionary contains 22 | a dictionary of zoneIDs to errors keyed off of CKPartialErrorsByItemIDKey. 23 | */ 24 | @property(nonatomic, copy) void (^fetchRecordZonesCompletionBlock)(NSDictionary /* CKRecordZoneID -> CKRecordZone */ *recordZonesByZoneID, NSError *operationError); 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKFetchRecordZonesOperation.m: -------------------------------------------------------------------------------- 1 | // 2 | // CKFetchRecordZonesOperation.m 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #import "CKFetchRecordZonesOperation.h" 9 | #import "CKDatabaseOperation_Private.h" 10 | #import "CKDatabase_Private.h" 11 | #import "Defines.h" 12 | #import "CKRecordZone.h" 13 | #import "CKError.h" 14 | #import "CKRecordZoneID.h" 15 | 16 | @implementation CKFetchRecordZonesOperation 17 | 18 | - (instancetype)init { 19 | if (self = [super init]) { 20 | _recordZoneIDs = nil; 21 | } 22 | return self; 23 | } 24 | 25 | - (instancetype)initWithRecordZoneIDs:(NSArray *)zoneIDs { 26 | if (self = [self init]) { 27 | _recordZoneIDs = zoneIDs; 28 | } 29 | return self; 30 | } 31 | 32 | + fetchAllRecordZonesOperation { 33 | return [[CKFetchRecordZonesOperation alloc] init]; 34 | } 35 | 36 | 37 | - (void)start { 38 | if ([self isCancelled]) { 39 | [self setFinished:YES]; 40 | return; 41 | } 42 | 43 | [self setExecuting:YES]; 44 | 45 | if (!self.recordZoneIDs) { 46 | [[self database] fetchAllRecordZonesFromSender:self withCompletionHandler:^(NSArray *zones, NSError *error) { 47 | [self setExecuting:NO]; 48 | [self setFinished:YES]; 49 | 50 | if (!error) { 51 | NSMutableDictionary* results = [NSMutableDictionary dictionary]; 52 | 53 | for (CKRecordZone* zone in zones) { 54 | if (!self.recordZoneIDs || [self.recordZoneIDs containsObject:zone.zoneID]) { 55 | results[zone.zoneID] = zone; 56 | } 57 | } 58 | 59 | self.fetchRecordZonesCompletionBlock(results, nil); 60 | } 61 | else { 62 | self.fetchRecordZonesCompletionBlock(nil, error); 63 | } 64 | }]; 65 | } 66 | else { 67 | // track our pending fetch count. this doesn't need to be 68 | // thread safe 69 | __block NSInteger requestCount = [self.recordZoneIDs count]; 70 | 71 | // our output 72 | NSMutableDictionary *fetchedZones = [NSMutableDictionary dictionary]; 73 | NSMutableDictionary *errors = [NSMutableDictionary dictionary]; 74 | 75 | // call this when we're done 76 | void (^completionBlock)(void) = ^{ 77 | [self setExecuting:NO]; 78 | [self setFinished:YES]; 79 | 80 | NSError* error; 81 | if ([errors count]) { 82 | NSMutableDictionary* userInfo = [NSMutableDictionary dictionary]; 83 | userInfo[CKPartialErrorsByItemIDKey] = errors; 84 | error = [[NSError alloc] initWithDomain:CKErrorDomain code:CKErrorPartialFailure userInfo:userInfo]; 85 | } 86 | 87 | self.fetchRecordZonesCompletionBlock(fetchedZones, error); 88 | }; 89 | 90 | // now save and delete everything 91 | for (CKRecordZoneID *zoneID in self.recordZoneIDs) { 92 | [[self database] fetchRecordZoneWithID:zoneID fromSender:self completionHandler:^(CKRecordZone *zone, NSError *error) { 93 | if (error) { 94 | errors[zoneID] = error; 95 | } 96 | else { 97 | fetchedZones[zoneID] = zone; 98 | } 99 | requestCount -= 1; 100 | if (!requestCount) { 101 | completionBlock(); 102 | } 103 | }]; 104 | } 105 | } 106 | } 107 | 108 | - (void)completeWithError:(NSError *)error { 109 | self.fetchRecordZonesCompletionBlock(nil, error); 110 | } 111 | 112 | @end 113 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKFetchRecordsOperation.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKFetchRecordsOperation.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #import "CKDatabaseOperation.h" 9 | 10 | @class CKRecord, CKRecordID; 11 | 12 | @interface CKFetchRecordsOperation : CKDatabaseOperation 13 | 14 | - (instancetype)init NS_DESIGNATED_INITIALIZER; 15 | - (instancetype)initWithRecordIDs:(NSArray /* CKRecordID */ *)recordIDs; 16 | 17 | + (instancetype)fetchCurrentUserRecordOperation; 18 | 19 | @property(nonatomic, copy) NSArray /* CKRecordID */ *recordIDs; 20 | 21 | /* Declares which user-defined keys should be fetched and added to the resulting CKRecords. If nil, declares the entire record should be downloaded. If set to an empty array, declares that no user fields should be downloaded. Defaults to nil. */ 22 | @property(nonatomic, copy) NSArray /* NSString */ *desiredKeys; 23 | 24 | /* Called repeatedly during transfer. */ 25 | @property(nonatomic, copy) void (^perRecordProgressBlock)(CKRecordID *recordID, double progress); 26 | /* Called on success or failure for each record. */ 27 | @property(nonatomic, copy) void (^perRecordCompletionBlock)(CKRecord *record, CKRecordID *recordID, NSError *error); 28 | 29 | /* This block is called when the operation completes. 30 | The [NSOperation completionBlock] will also be called if both are set. 31 | If the error is CKErrorPartialFailure, the error's userInfo dictionary contains 32 | a dictionary of recordIDs to errors keyed off of CKPartialErrorsByItemIDKey. 33 | */ 34 | @property(nonatomic, copy) void (^fetchRecordsCompletionBlock)(NSDictionary /* CKRecordID * -> CKRecord */ *recordsByRecordID, NSError *operationError); 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKFetchRecordsOperation.m: -------------------------------------------------------------------------------- 1 | // 2 | // CKFetchRecordsOperation.m 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #import "CKFetchRecordsOperation.h" 9 | #import "CKDatabaseOperation_Private.h" 10 | #import "NSError+AgileCloudSDKExtensions.h" 11 | #import "NSArray+AgileMap.h" 12 | #import "CKRecordID+AgileDictionary.h" 13 | #import "CKDatabase_Private.h" 14 | #import "CKRecord_Private.h" 15 | #import "Defines.h" 16 | #import "CKRecordZoneID.h" 17 | #import "CKContainer.h" 18 | #import "CKError.h" 19 | 20 | @implementation CKFetchRecordsOperation 21 | 22 | + (instancetype)fetchCurrentUserRecordOperation { 23 | @throw kAbstractMethodException; 24 | } 25 | 26 | - (instancetype)init { 27 | if (self = [super init]) { 28 | } 29 | return self; 30 | } 31 | 32 | - (instancetype)initWithRecordIDs:(NSArray *)recordIDs { 33 | if (self = [self init]) { 34 | _recordIDs = [recordIDs copy]; 35 | } 36 | return self; 37 | } 38 | 39 | - (void)start { 40 | [self setExecuting:YES]; 41 | 42 | if ([_recordIDs count]) { 43 | CKRecordZoneID *zone = [[_recordIDs firstObject] zoneID]; 44 | 45 | NSArray *jsonRecordIds = [_recordIDs agile_mapUsingBlock:^id(id obj, NSUInteger idx) { 46 | return [obj asAgileDictionary]; 47 | }]; 48 | 49 | NSDictionary *requestDictionary = @{ 50 | @"records": jsonRecordIds, 51 | @"zoneID": @{ 52 | @"zoneName": zone.zoneName 53 | } 54 | }; 55 | 56 | [self.database sendPOSTRequestTo:@"records/lookup" withJSON:requestDictionary completionHandler:^(id jsonResponse, NSError *error) { 57 | NSMutableDictionary* fetchedRecords = [NSMutableDictionary dictionary]; 58 | NSMutableDictionary* partialFailures = [NSMutableDictionary dictionary]; 59 | if ([jsonResponse isKindOfClass:[NSDictionary class]] && jsonResponse[@"records"]) { 60 | [jsonResponse[@"records"] enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 61 | NSError* recordError = nil; 62 | CKRecord* record; 63 | CKRecordID* recordID; 64 | if (obj[@"serverErrorCode"]) { 65 | recordError = [[NSError alloc] initWithCKErrorDictionary:obj]; 66 | if (obj[@"recordName"]) { 67 | recordID = [[CKRecordID alloc] initWithRecordName:obj[@"recordName"] zoneID:zone]; 68 | } 69 | } 70 | else { 71 | record = [[CKRecord alloc] initWithDictionary:obj inZone:zone]; 72 | recordID = record.recordID; 73 | } 74 | if (record) { 75 | NSArray* errs = [record synchronouslyDownloadAllAssetsWithProgressBlock:^(double progress) { 76 | if (self.perRecordProgressBlock && progress != 1.0) { 77 | self.perRecordProgressBlock(recordID, progress); 78 | } 79 | }]; 80 | if ([errs count]) { 81 | recordError = errs[0]; 82 | } 83 | else { 84 | [fetchedRecords setObject:record forKey:recordID]; 85 | if (self.perRecordProgressBlock) { 86 | self.perRecordProgressBlock(recordID, 1.0); 87 | } 88 | } 89 | } 90 | 91 | if (self.perRecordCompletionBlock) { 92 | self.perRecordCompletionBlock(record, recordID, recordError); 93 | } 94 | 95 | if (recordError) { 96 | [partialFailures setObject:recordError forKey:recordID]; 97 | } 98 | }]; 99 | } 100 | else if (!error) { 101 | error = [[NSError alloc] initWithCKErrorDictionary:jsonResponse]; 102 | } 103 | 104 | if (!error && [[partialFailures allKeys] count]) { 105 | NSDictionary* userInfo = @{ CKErrorUserInfoContainerIDKey : self.database.container.containerIdentifier, 106 | CKErrorUserInfoPartialErrorsKey : partialFailures }; 107 | error = [[NSError alloc] initWithDomain:CKErrorDomain code:CKErrorPartialFailure userInfo:userInfo]; 108 | } 109 | 110 | if (self.fetchRecordsCompletionBlock) { 111 | self.fetchRecordsCompletionBlock(fetchedRecords, error); 112 | } 113 | 114 | [self setExecuting:NO]; 115 | [self setFinished:YES]; 116 | }]; 117 | } 118 | else { 119 | [self setExecuting:NO]; 120 | [self setFinished:YES]; 121 | } 122 | } 123 | 124 | @end 125 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKFilter.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKFilter.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import 9 | #import "CKFilterType.h" 10 | 11 | extern NSString *const CK_EQUALS; 12 | extern NSString *const CK_NOT_EQUALS; 13 | extern NSString *const CK_LESS_THAN; 14 | extern NSString *const CK_LESS_THAN_OR_EQUALS; 15 | extern NSString *const CK_GREATER_THAN; 16 | extern NSString *const CK_GREATER_THAN_OR_EQUALS; 17 | extern NSString *const CK_NEAR; 18 | extern NSString *const CK_CONTAINS_ALL_TOKENS; 19 | extern NSString *const CK_IN; 20 | extern NSString *const CK_NOT_IN; 21 | extern NSString *const CK_CONTAINS_ANY_TOKENS; 22 | extern NSString *const CK_LIST_CONTAINS; 23 | extern NSString *const CK_NOT_LIST_CONTAINS; 24 | extern NSString *const CK_NOT_LIST_CONTAINS_ANY; 25 | extern NSString *const CK_BEGINS_WITH; 26 | extern NSString *const CK_NOT_BEGINS_WITH; 27 | extern NSString *const CK_LIST_MEMBER_BEGINS_WITH; 28 | extern NSString *const CK_NOT_LIST_MEMBER_BEGINS_WITH; 29 | extern NSString *const CK_LIST_CONTAINS_ALL; 30 | extern NSString *const CK_NOT_LIST_CONTAINS_ALL; 31 | 32 | @class CKRecordZoneID; 33 | 34 | @interface CKFilter : NSObject 35 | 36 | - (instancetype)init NS_UNAVAILABLE; 37 | 38 | - (instancetype)initWithComparator:(NSString *)comparator fieldName:(NSString *)fieldName fieldType:(NSString *)fieldType fieldValue:(NSObject *)fieldValue; 39 | 40 | @property(nonatomic, readonly) NSString *comparator; 41 | @property(nonatomic, readonly) NSString *fieldName; 42 | @property(nonatomic, readonly) NSString *fieldType; 43 | @property(nonatomic, readonly) NSObject *fieldValue; 44 | 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKFilter.m: -------------------------------------------------------------------------------- 1 | // 2 | // CKFilter.m 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import "CKFilter.h" 9 | #import "CKRecord_Private.h" 10 | 11 | NSString *const CK_EQUALS = @"EQUALS"; 12 | NSString *const CK_NOT_EQUALS = @"NOT_EQUALS"; 13 | NSString *const CK_LESS_THAN = @"LESS_THAN"; 14 | NSString *const CK_LESS_THAN_OR_EQUALS = @"LESS_THAN_OR_EQUALS"; 15 | NSString *const CK_GREATER_THAN = @"GREATER_THAN"; 16 | NSString *const CK_GREATER_THAN_OR_EQUALS = @"GREATER_THAN_OR_EQUALS"; 17 | NSString *const CK_NEAR = @"NEAR"; 18 | NSString *const CK_CONTAINS_ALL_TOKENS = @"CONTAINS_ALL_TOKENS"; 19 | NSString *const CK_IN = @"IN"; 20 | NSString *const CK_NOT_IN = @"NOT_IN"; 21 | NSString *const CK_CONTAINS_ANY_TOKENS = @"CONTAINS_ANY_TOKENS"; 22 | NSString *const CK_LIST_CONTAINS = @"LIST_CONTAINS"; 23 | NSString *const CK_NOT_LIST_CONTAINS = @"NOT_LIST_CONTAINS"; 24 | NSString *const CK_NOT_LIST_CONTAINS_ANY = @"NOT_LIST_CONTAINS_ANY"; 25 | NSString *const CK_BEGINS_WITH = @"BEGINS_WITH"; 26 | NSString *const CK_NOT_BEGINS_WITH = @"NOT_BEGINS_WITH"; 27 | NSString *const CK_LIST_MEMBER_BEGINS_WITH = @"LIST_MEMBER_BEGINS_WITH"; 28 | NSString *const CK_NOT_LIST_MEMBER_BEGINS_WITH = @"NOT_LIST_MEMBER_BEGINS_WITH"; 29 | NSString *const CK_LIST_CONTAINS_ALL = @"LIST_CONTAINS_ALL"; 30 | NSString *const CK_NOT_LIST_CONTAINS_ALL = @"NOT_LIST_CONTAINS_ALL"; 31 | 32 | static NSArray *allowedComparators; 33 | 34 | @implementation CKFilter 35 | 36 | - (instancetype)initWithComparator:(NSString *)comparator fieldName:(NSString *)fieldName fieldType:(NSString *)fieldType fieldValue:(NSObject *)fieldValue { 37 | if (self = [super init]) { 38 | self.comparator = comparator; 39 | _fieldName = fieldName; 40 | _fieldType = fieldType; 41 | _fieldValue = fieldValue; 42 | } 43 | return self; 44 | } 45 | 46 | - (instancetype)initWithDictionary:(NSDictionary *)dictionary inZone:(CKRecordZoneID *)zoneID { 47 | if (self = [super init]) { 48 | self.comparator = dictionary[@"comparator"]; 49 | _fieldName = dictionary[@"fieldName"]; 50 | _fieldType = dictionary[@"fieldValue"][@"type"]; 51 | _fieldValue = (NSObject *)[CKRecord recordValueFromDictionary:dictionary[@"fieldValue"] inZone:zoneID]; 52 | } 53 | return self; 54 | } 55 | 56 | - (void)setComparator:(NSString *)comparator { 57 | if (![allowedComparators containsObject:comparator]) { 58 | @throw [NSException exceptionWithName:@"CKException" reason:[NSString stringWithFormat:@"%@ is not a valid comparator. Allowed values are %@", comparator, allowedComparators] userInfo:nil]; 59 | } 60 | _comparator = comparator; 61 | } 62 | 63 | - (NSDictionary *)asAgileDictionary { 64 | return @{ @"comparator": self.comparator, 65 | @"fieldName": self.fieldName, 66 | @"fieldValue": @{@"type": self.fieldType, 67 | @"value": [self.fieldValue respondsToSelector:@selector(asAgileDictionary)] ? [self.fieldValue asAgileDictionary] : self.fieldValue} }; 68 | } 69 | 70 | #pragma mark - NSSecureCoding 71 | 72 | + (BOOL)supportsSecureCoding { 73 | return YES; 74 | } 75 | 76 | - (void)encodeWithCoder:(NSCoder *)aCoder { 77 | [aCoder encodeObject:self.comparator forKey:@"comparator"]; 78 | [aCoder encodeObject:self.fieldName forKey:@"fieldName"]; 79 | [aCoder encodeObject:self.fieldType forKey:@"fieldType"]; 80 | [aCoder encodeObject:NSStringFromClass([self.fieldValue class]) forKey:@"valueClass"]; 81 | [aCoder encodeObject:self.fieldValue forKey:@"fieldValue"]; 82 | } 83 | 84 | - (instancetype)initWithCoder:(NSCoder *)aDecoder { 85 | if (self = [super init]) { 86 | self.comparator = [aDecoder decodeObjectOfClass:[NSString class] forKey:@"comparator"]; 87 | _fieldName = [aDecoder decodeObjectOfClass:[NSString class] forKey:@"fieldName"]; 88 | _fieldType = [aDecoder decodeObjectOfClass:[NSString class] forKey:@"fieldType"]; 89 | NSString *cName = [aDecoder decodeObjectOfClass:[NSString class] forKey:@"valueClass"]; 90 | _fieldValue = [aDecoder decodeObjectOfClass:NSClassFromString(cName) forKey:@"fieldValue"]; 91 | } 92 | return self; 93 | } 94 | 95 | #pragma mark - NSCopying 96 | 97 | - (instancetype)copyWithZone:(NSZone *)zone { 98 | return [[[self class] allocWithZone:zone] initWithDictionary:[self asAgileDictionary]]; 99 | } 100 | 101 | #pragma mark - Loading 102 | 103 | + (void)load { 104 | static dispatch_once_t onceToken; 105 | dispatch_once(&onceToken, ^{ 106 | allowedComparators = @[CK_EQUALS, 107 | CK_NOT_EQUALS, 108 | CK_LESS_THAN, 109 | CK_LESS_THAN_OR_EQUALS, 110 | CK_GREATER_THAN, 111 | CK_GREATER_THAN_OR_EQUALS, 112 | CK_NEAR, 113 | CK_CONTAINS_ALL_TOKENS, 114 | CK_IN, 115 | CK_NOT_IN, 116 | CK_CONTAINS_ANY_TOKENS, 117 | CK_LIST_CONTAINS, 118 | CK_NOT_LIST_CONTAINS, 119 | CK_NOT_LIST_CONTAINS_ANY, 120 | CK_BEGINS_WITH, 121 | CK_NOT_BEGINS_WITH, 122 | CK_LIST_MEMBER_BEGINS_WITH, 123 | CK_NOT_LIST_MEMBER_BEGINS_WITH, 124 | CK_LIST_CONTAINS_ALL, 125 | CK_NOT_LIST_CONTAINS_ALL]; 126 | }); 127 | } 128 | 129 | @end 130 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKFilterType.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKFilterType.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | @protocol CKRecordValue; 11 | 12 | @protocol CKFilterType 13 | 14 | @optional 15 | - (NSDictionary *)asAgileDictionary; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKFilter_Private.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKFilter_Private.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import "CKFilter.h" 9 | 10 | @interface CKFilter (Private) 11 | 12 | - (instancetype)initWithDictionary:(NSDictionary *)dictionary inZone:(CKRecordZoneID *)zoneID; 13 | 14 | - (NSDictionary *)asAgileDictionary; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKMarkNotificationsReadOperation.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKMarkNotificationsReadOperation.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #import 9 | #import "CKOperation.h" 10 | 11 | @interface CKMarkNotificationsReadOperation : CKOperation 12 | 13 | - (instancetype)init NS_UNAVAILABLE; 14 | 15 | - (instancetype)initWithNotificationIDsToMarkRead:(NSArray /* CKNotificationID */ *)notificationIDs NS_DESIGNATED_INITIALIZER; 16 | 17 | @property(nonatomic, copy) NSArray /* CKNotificationID */ *notificationIDs; 18 | 19 | @property(nonatomic, copy) void (^markNotificationsReadCompletionBlock)(NSArray /* CKNotificationID */ *notificationIDsMarkedRead, NSError *operationError); 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKMarkNotificationsReadOperation.m: -------------------------------------------------------------------------------- 1 | // 2 | // CKMarkNotificationsReadOperation.m 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #import 9 | #import "CKOperation.h" 10 | #import "CKMarkNotificationsReadOperation.h" 11 | 12 | @implementation CKMarkNotificationsReadOperation 13 | 14 | // this method is not used yet. Substituting init until its needed - kevin 2015-12-21 15 | - (instancetype)initWithNotificationIDsToMarkRead:(NSArray /* CKNotificationID */ *)notificationIDs { 16 | return [super init]; 17 | } 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKMediator.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKMediator.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #import 9 | #import 10 | #import "CKMediatorDelegate.h" 11 | 12 | extern NSString *const kAgileCloudSDKInitializedNotification; 13 | 14 | @interface CKMediator : NSObject 15 | 16 | + (CKMediator *)sharedMediator; 17 | 18 | - (instancetype)init NS_UNAVAILABLE; 19 | 20 | @property(nonatomic, weak) NSObject *delegate; 21 | @property(nonatomic, readonly) BOOL isInitialized; 22 | 23 | - (void)logout; 24 | - (void)login; 25 | 26 | - (void)handleGetURLEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent; 27 | - (void)handleGetURLString:(NSString *)urlString; // for handling URL from where the NSAppleEventDescriptor is not available - kevin 2017-07-24 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKMediatorDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKMediatorDelegate.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | // These match ASL_LEVELs defined in asl.h 11 | #define CKLOG_LEVEL_EMERG 0 12 | #define CKLOG_LEVEL_ALERT 1 13 | #define CKLOG_LEVEL_CRIT 2 14 | #define CKLOG_LEVEL_ERR 3 15 | #define CKLOG_LEVEL_WARNING 4 16 | #define CKLOG_LEVEL_NOTICE 5 17 | #define CKLOG_LEVEL_INFO 6 18 | #define CKLOG_LEVEL_DEBUG 7 19 | 20 | @class CKMediator; 21 | 22 | @protocol CKMediatorDelegate 23 | 24 | @required 25 | - (NSString *)loadSessionTokenForMediator:(CKMediator *)mediator; 26 | - (void)mediator:(CKMediator *)mediator saveSessionToken:(NSString *)token; 27 | 28 | @optional 29 | 30 | - (void)mediator:(CKMediator *)mediator logLevel:(int)level object:(id)object at:(SEL)method format:(NSString *)format,... NS_FORMAT_FUNCTION(5,6); 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKMediator_Private.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKMediator_Private.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import "CKMediator.h" 9 | 10 | extern NSString *const CloudContainerNameKey; 11 | extern NSString *const CloudAPITokenKey; 12 | extern NSString *const CloudEnvironmentKey; 13 | 14 | extern NSString *const CKAccountStatusNotificationUserInfoKey; 15 | 16 | @interface CKMediator () 17 | 18 | @property(nonatomic, readonly) NSOperationQueue *queue; 19 | @property(nonatomic, readonly) NSOperationQueue *innerQueue; 20 | @property(nonatomic, readonly) WebView *cloudWebView; 21 | @property(nonatomic, readonly) NSString *sessionToken; 22 | @property(nonatomic, readonly) NSArray *containerProperties; 23 | 24 | - (JSContext *)context; 25 | 26 | - (NSDictionary *)infoForContainerID:(NSString *)containerID; 27 | 28 | - (void)registerForRemoteNotifications; 29 | 30 | - (void)addOperation:(NSOperation *)operation; 31 | - (void)addInnerOperation:(NSOperation *)operation; 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKModifyRecordZonesOperation.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKModifyRecordZonesOperation.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #import "CKDatabaseOperation.h" 9 | 10 | @interface CKModifyRecordZonesOperation : CKDatabaseOperation 11 | 12 | - (instancetype)init NS_DESIGNATED_INITIALIZER; 13 | - (instancetype)initWithRecordZonesToSave:(NSArray /* CKRecordZone */ *)recordZonesToSave recordZoneIDsToDelete:(NSArray /* CKRecordZoneID */ *)recordZoneIDsToDelete; 14 | 15 | @property(nonatomic, copy) NSArray /* CKRecordZone */ *recordZonesToSave; 16 | @property(nonatomic, copy) NSArray /* CKRecordZoneID */ *recordZoneIDsToDelete; 17 | 18 | /* This block is called when the operation completes. 19 | The [NSOperation completionBlock] will also be called if both are set. 20 | If the error is CKErrorPartialFailure, the error's userInfo dictionary contains 21 | a dictionary of recordZoneIDs to errors keyed off of CKPartialErrorsByItemIDKey. 22 | This call happens as soon as the server has 23 | seen all record changes, and may be invoked while the server is processing the side effects 24 | of those changes. 25 | */ 26 | @property(nonatomic, copy) void (^modifyRecordZonesCompletionBlock)(NSArray /* CKRecordZone */ *savedRecordZones, NSArray /* CKRecordZoneID */ *deletedRecordZoneIDs, NSError *operationError); 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKModifyRecordZonesOperation.m: -------------------------------------------------------------------------------- 1 | // 2 | // CKModifyRecordZonesOperation.m 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #import "CKModifyRecordZonesOperation.h" 9 | #import "CKDatabaseOperation_Private.h" 10 | #import "NSArray+AgileMap.h" 11 | #import "CKRecord+AgileDictionary.h" 12 | #import "CKRecordID+AgileDictionary.h" 13 | #import "CKRecordZoneID+AgileDictionary.h" 14 | #import "CKRecord_Private.h" 15 | #import "CKDatabase_Private.h" 16 | #import "Defines.h" 17 | #import "NSError+AgileCloudSDKExtensions.h" 18 | #import "CKError.h" 19 | #import "CKRecordZone.h" 20 | #import "CKContainer.h" 21 | 22 | @implementation CKModifyRecordZonesOperation 23 | 24 | - (instancetype)init { 25 | if (self = [super init]) { 26 | } 27 | return self; 28 | } 29 | 30 | 31 | - (instancetype)initWithRecordZonesToSave:(NSArray *)recordZonesToSave recordZoneIDsToDelete:(NSArray *)recordZoneIDsToDelete { 32 | if (self = [self init]) { 33 | _recordZonesToSave = recordZonesToSave; 34 | _recordZoneIDsToDelete = recordZoneIDsToDelete; 35 | } 36 | return self; 37 | } 38 | 39 | - (void)start { 40 | [self setExecuting:YES]; 41 | 42 | if ([_recordZoneIDsToDelete count] || [_recordZonesToSave count]) { 43 | NSMutableDictionary *savedZoneIDToZone = [NSMutableDictionary dictionary]; 44 | // the response doesn't contain the owner name so we have to preserve it - kevin 2015-12-09 45 | NSMutableDictionary *savedZoneIDOwnerNames = [NSMutableDictionary dictionary]; 46 | 47 | NSArray *ops = @[]; 48 | ops = [ops arrayByAddingObjectsFromArray:[_recordZoneIDsToDelete agile_mapUsingBlock:^id(id obj, NSUInteger idx) { 49 | return @{ @"operationType" : @"delete", 50 | @"zone" : @{ @"zoneID": [obj asAgileDictionary] } }; 51 | }]]; 52 | 53 | 54 | ops = [ops arrayByAddingObjectsFromArray:[_recordZonesToSave agile_mapUsingBlock:^id(id obj, NSUInteger idx) { 55 | [savedZoneIDToZone setObject:obj forKey:[obj zoneID]]; 56 | [savedZoneIDOwnerNames setObject:[[obj zoneID] ownerName] forKey:[[obj zoneID] zoneName]]; 57 | return @{ @"operationType" : @"create", 58 | @"zone" : [obj asAgileDictionary] }; 59 | }]]; 60 | 61 | NSDictionary *requestDictionary = @{ @"operations": ops }; 62 | 63 | [self.database sendPOSTRequestTo:@"zones/modify" withJSON:requestDictionary completionHandler:^(id jsonResponse, NSError *error) { 64 | NSMutableArray* savedZones = [NSMutableArray array]; 65 | NSMutableArray* deletedZones = [NSMutableArray array]; 66 | NSMutableDictionary* partialFailures = [NSMutableDictionary dictionary]; 67 | 68 | if ([jsonResponse isKindOfClass:[NSDictionary class]] && jsonResponse[@"zones"]) { 69 | [jsonResponse[@"zones"] enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 70 | 71 | NSString *zoneName = obj[@"zoneID"][@"zoneName"]; 72 | CKRecordZoneID* savedZoneID = [[CKRecordZoneID alloc] initWithZoneName:zoneName ownerName:savedZoneIDOwnerNames[zoneName]]; 73 | CKRecordZone* originalZone = savedZoneIDToZone[savedZoneID]; 74 | 75 | if (originalZone) { 76 | NSError* recordError = nil; 77 | if (obj[@"serverErrorCode"]) { 78 | recordError = [[NSError alloc] initWithCKErrorDictionary:obj]; 79 | [partialFailures setObject:recordError forKey:originalZone.zoneID]; 80 | } 81 | else { 82 | [savedZones addObject:originalZone]; 83 | } 84 | } 85 | else if (obj[@"deleted"]) { 86 | // was it deleted? 87 | [deletedZones addObject:savedZoneID]; 88 | } 89 | }]; 90 | } 91 | else if (!error) { 92 | error = [[NSError alloc] initWithCKErrorDictionary:jsonResponse]; 93 | } 94 | 95 | if (!error && [[partialFailures allKeys] count]) { 96 | NSDictionary* userInfo = @{ CKErrorUserInfoPartialErrorsKey : self.database.container.containerIdentifier, 97 | CKErrorUserInfoPartialErrorsKey : partialFailures }; 98 | error = [[NSError alloc] initWithDomain:CKErrorDomain code:CKErrorPartialFailure userInfo:userInfo]; 99 | } 100 | 101 | if (self.modifyRecordZonesCompletionBlock) { 102 | self.modifyRecordZonesCompletionBlock(savedZones, deletedZones, error); 103 | } 104 | 105 | [self setExecuting:NO]; 106 | [self setFinished:YES]; 107 | }]; 108 | } 109 | } 110 | 111 | @end 112 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKModifyRecordsOperation.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKModifyRecordsOperation.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #import "CKDatabaseOperation.h" 9 | 10 | @class CKRecord; 11 | 12 | typedef NS_ENUM(NSInteger, CKRecordSavePolicy) { 13 | CKRecordSaveIfServerRecordUnchanged = 0, /* Locally-edited keys are sent to the server. If the record on the server has been modified, 14 | fail the write and return an error. */ 15 | CKRecordSaveChangedKeys = 1, /* Locally-edited keys are written to the server. Any unseen changes on the server 16 | will be overwritten to the locally-edited value. */ 17 | CKRecordSaveAllKeys = 2, /* All local keys are written to the server. Any unseen changes on the server will be 18 | overwritten to the local values. Keys present only on the server remain unchanged. 19 | There are two common ways in which a server record will contain keys not present locally: 20 | 1 - Since you've fetched this record, another client has added a new key to the record. 21 | 2 - The presence of desiredKeys on the fetch / query that returned this record meant 22 | that only a portion of the record's keys were downloaded. */ 23 | } NS_ENUM_AVAILABLE(10_10, 8_0); 24 | 25 | @interface CKModifyRecordsOperation : CKDatabaseOperation 26 | 27 | - (instancetype)init NS_DESIGNATED_INITIALIZER; 28 | - (instancetype)initWithRecordsToSave:(NSArray /* CKRecord */ *)records recordIDsToDelete:(NSArray /* CKRecordID */ *)recordIDs; 29 | 30 | @property(nonatomic, copy) NSArray /* CKRecord */ *recordsToSave; 31 | @property(nonatomic, copy) NSArray /* CKRecordID */ *recordIDsToDelete; 32 | 33 | /* The default value is CKRecordSaveIfServerRecordUnchanged. */ 34 | @property(nonatomic, assign) CKRecordSavePolicy savePolicy; 35 | 36 | /* This property is kept by the server to identify the last known request from this client. 37 | Multiple requests from the client with the same change token will be ignored by the server. */ 38 | @property(nonatomic, copy) NSData *clientChangeTokenData; 39 | 40 | /* Determines whether the batch should fail atomically or not. YES by default. 41 | This only applies to zones that support CKRecordZoneCapabilityAtomic */ 42 | @property(nonatomic, assign) BOOL atomic; 43 | 44 | /* Called repeatedly during transfer. 45 | It is possible for progress to regress when a retry is automatically triggered. 46 | */ 47 | @property(nonatomic, copy) void (^perRecordProgressBlock)(CKRecord *record, double progress); 48 | /* Called on success or failure for each record. */ 49 | @property(nonatomic, copy) void (^perRecordCompletionBlock)(CKRecord *record, NSError *error); 50 | 51 | /* This block is called when the operation completes. 52 | The [NSOperation completionBlock] will also be called if both are set. 53 | If the error is CKErrorPartialFailure, the error's userInfo dictionary contains 54 | a dictionary of recordIDs to errors keyed off of CKPartialErrorsByItemIDKey. 55 | This call happens as soon as the server has 56 | seen all record changes, and may be invoked while the server is processing the side effects 57 | of those changes. 58 | */ 59 | @property(nonatomic, copy) void (^modifyRecordsCompletionBlock)(NSArray /* CKRecord */ *savedRecords, NSArray /* CKRecordID */ *deletedRecordIDs, NSError *operationError); 60 | 61 | @end 62 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKModifyRecordsOperation.m: -------------------------------------------------------------------------------- 1 | // 2 | // CKModifyRecordsOperation.m 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #import "CKModifyRecordsOperation.h" 9 | #import "CKDatabaseOperation_Private.h" 10 | #import "CKMediator_Private.h" 11 | #import "NSArray+AgileMap.h" 12 | #import "CKRecord+AgileDictionary.h" 13 | #import "CKRecordID+AgileDictionary.h" 14 | #import "CKRecordZoneID+AgileDictionary.h" 15 | #import "CKRecord_Private.h" 16 | #import "CKDatabase_Private.h" 17 | #import "NSError+AgileCloudSDKExtensions.h" 18 | #import "CKContainer.h" 19 | #import "CKError.h" 20 | 21 | @implementation CKModifyRecordsOperation 22 | 23 | - (instancetype)init { 24 | if (self = [super init]) { 25 | } 26 | return self; 27 | } 28 | 29 | 30 | - (instancetype)initWithRecordsToSave:(NSArray *)records recordIDsToDelete:(NSArray *)recordIDs { 31 | if (self = [self init]) { 32 | _savePolicy = CKRecordSaveIfServerRecordUnchanged; 33 | _recordsToSave = records; 34 | _recordIDsToDelete = recordIDs; 35 | _atomic = YES; 36 | } 37 | return self; 38 | } 39 | 40 | 41 | - (void)start { 42 | [self setExecuting:YES]; 43 | 44 | if ([_recordIDsToDelete count] || [_recordsToSave count]) { 45 | CKRecordZoneID *zoneID = [[_recordIDsToDelete firstObject] zoneID]; 46 | if (!zoneID) { 47 | zoneID = [[[_recordsToSave firstObject] recordID] zoneID]; 48 | } 49 | 50 | NSMutableDictionary *savedRecordIDToRecord = [NSMutableDictionary dictionary]; 51 | 52 | 53 | NSArray *ops = @[]; 54 | ops = [ops arrayByAddingObjectsFromArray:[_recordIDsToDelete agile_mapUsingBlock:^id(id obj, NSUInteger idx) { 55 | return @{ @"operationType" : @"forceDelete", 56 | @"record" : [obj asAgileDictionary] }; 57 | }]]; 58 | 59 | 60 | ops = [ops arrayByAddingObjectsFromArray:[_recordsToSave agile_mapUsingBlock:^id(id obj, NSUInteger idx) { 61 | 62 | [savedRecordIDToRecord setObject:obj forKey:[obj recordID]]; 63 | 64 | NSArray* uploadErrors = [obj synchronouslyUploadAssetsIntoDatabase:self.database]; 65 | 66 | if ([uploadErrors count]) { 67 | if (self.perRecordCompletionBlock) { 68 | self.perRecordCompletionBlock(obj, [uploadErrors firstObject]); 69 | } 70 | return nil; 71 | } 72 | 73 | NSDictionary* recordDic = [obj asAgileDictionary]; 74 | NSString* opType = recordDic[@"recordChangeTag"] ? @"update" : @"create"; 75 | if (self.savePolicy == CKRecordSaveAllKeys || self.savePolicy == CKRecordSaveChangedKeys) { 76 | opType = @"forceUpdate"; 77 | } 78 | return @{ @"operationType" : opType, 79 | @"record" : recordDic }; 80 | }]]; 81 | 82 | NSDictionary *requestDictionary = @{ @"operations": ops, 83 | @"zoneID": @{@"zoneName": zoneID.zoneName}, 84 | @"atomic": @(_atomic) }; 85 | 86 | [self.database sendPOSTRequestTo:@"records/modify" withJSON:requestDictionary completionHandler:^(id jsonResponse, NSError *error) { 87 | NSMutableArray* savedRecords = [NSMutableArray array]; 88 | NSMutableArray* deletedRecords = [NSMutableArray array]; 89 | NSMutableDictionary* partialFailures = [NSMutableDictionary dictionary]; 90 | 91 | if ([jsonResponse isKindOfClass:[NSDictionary class]] && jsonResponse[@"records"]) { 92 | [jsonResponse[@"records"] enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 93 | 94 | CKRecordID* savedRecordID = [[CKRecordID alloc] initWithRecordName:obj[@"recordName"] zoneID:zoneID]; 95 | CKRecord* originalRecord = savedRecordIDToRecord[savedRecordID]; 96 | 97 | if (originalRecord) { 98 | NSError* recordError = nil; 99 | if (obj[@"serverErrorCode"]) { 100 | recordError = [[NSError alloc] initWithCKErrorDictionary:obj]; 101 | [partialFailures setObject:recordError forKey:originalRecord.recordID]; 102 | } 103 | else { 104 | [originalRecord updateWithDictionary:obj]; 105 | [savedRecords addObject:originalRecord]; 106 | 107 | if (self.perRecordProgressBlock) { 108 | self.perRecordProgressBlock(originalRecord, 1.0); 109 | } 110 | } 111 | 112 | if (self.perRecordCompletionBlock) { 113 | self.perRecordCompletionBlock(originalRecord, recordError); 114 | } 115 | } 116 | else if (obj[@"deleted"]) { 117 | // was it deleted? 118 | [deletedRecords addObject:savedRecordID]; 119 | } 120 | }]; 121 | } 122 | else if (!error) { 123 | error = [[NSError alloc] initWithCKErrorDictionary:jsonResponse]; 124 | } 125 | 126 | 127 | 128 | if (!error && [[partialFailures allKeys] count]) { 129 | NSDictionary* userInfo = @{ CKErrorUserInfoContainerIDKey : self.database.container.containerIdentifier, 130 | CKErrorUserInfoPartialErrorsKey : partialFailures }; 131 | error = [[NSError alloc] initWithDomain:CKErrorDomain code:CKErrorPartialFailure userInfo:userInfo]; 132 | } 133 | 134 | if (self.modifyRecordsCompletionBlock) { 135 | self.modifyRecordsCompletionBlock(savedRecords, deletedRecords, error); 136 | } 137 | 138 | [self setExecuting:NO]; 139 | [self setFinished:YES]; 140 | }]; 141 | } 142 | } 143 | 144 | @end 145 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKModifySubscriptionsOperation.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKModifySubscriptionOperation.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #import "CKDatabaseOperation.h" 9 | 10 | @interface CKModifySubscriptionsOperation : CKDatabaseOperation 11 | 12 | - (instancetype)initWithSubscriptionsToSave:(NSArray /* CKSubscription */ *)subscriptionsToSave subscriptionIDsToDelete:(NSArray /* NSString */ *)subscriptionIDsToDelete NS_DESIGNATED_INITIALIZER; 13 | 14 | @property(nonatomic, copy) NSArray /* CKSubscription */ *subscriptionsToSave; 15 | @property(nonatomic, copy) NSArray /* NSString */ *subscriptionIDsToDelete; 16 | 17 | /* This block is called when the operation completes. 18 | The [NSOperation completionBlock] will also be called if both are set. 19 | If the error is CKErrorPartialFailure, the error's userInfo dictionary contains 20 | a dictionary of subscriptionIDs to errors keyed off of CKPartialErrorsByItemIDKey. 21 | */ 22 | @property(nonatomic, copy) void (^modifySubscriptionsCompletionBlock)(NSArray /* CKSubscription */ *savedSubscriptions, NSArray /* NSString */ *deletedSubscriptionIDs, NSError *operationError); 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKModifySubscriptionsOperation.m: -------------------------------------------------------------------------------- 1 | // 2 | // CKModifySubscriptionOperation.m 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #import "CKDatabaseOperation.h" 9 | #import "CKModifySubscriptionsOperation.h" 10 | #import "CKDatabaseOperation_Private.h" 11 | #import "NSArray+AgileMap.h" 12 | #import "CKSubscription+AgileDictionary.h" 13 | #import "CKSubscription_Private.h" 14 | #import "CKDatabase_Private.h" 15 | #import "NSError+AgileCloudSDKExtensions.h" 16 | #import "CKContainer.h" 17 | #import "CKError.h" 18 | 19 | @implementation CKModifySubscriptionsOperation 20 | 21 | - (instancetype)init { 22 | return [self initWithSubscriptionsToSave:@[] subscriptionIDsToDelete:@[]]; 23 | } 24 | 25 | - (instancetype)initWithSubscriptionsToSave:(NSArray /* CKSubscription */ *)subscriptionsToSave subscriptionIDsToDelete:(NSArray /* NSString */ *)subscriptionIDsToDelete { 26 | if (self = [super init]) { 27 | self.subscriptionsToSave = subscriptionsToSave; 28 | self.subscriptionIDsToDelete = subscriptionIDsToDelete; 29 | } 30 | return self; 31 | } 32 | 33 | - (void)start { 34 | [self setExecuting:YES]; 35 | 36 | if ([_subscriptionIDsToDelete count] || [_subscriptionsToSave count]) { 37 | NSMutableDictionary *savedSubscriptionIDToSubscription = [NSMutableDictionary dictionary]; 38 | 39 | NSArray *ops = @[]; 40 | ops = [ops arrayByAddingObjectsFromArray:[_subscriptionIDsToDelete agile_mapUsingBlock:^id(id obj, NSUInteger idx) { 41 | return @{ @"operationType" : @"delete", 42 | @"subscription" : @{ @"subscriptionID" : obj }}; 43 | }]]; 44 | 45 | 46 | ops = [ops arrayByAddingObjectsFromArray:[_subscriptionsToSave agile_mapUsingBlock:^id(id obj, NSUInteger idx) { 47 | 48 | [savedSubscriptionIDToSubscription setObject:obj forKey:[obj subscriptionID]]; 49 | 50 | return @{ @"operationType" : @"create", 51 | @"subscription" : [obj asAgileDictionary] }; 52 | }]]; 53 | 54 | NSDictionary *requestDictionary = @{ @"operations": ops }; 55 | 56 | [self.database sendPOSTRequestTo:@"subscriptions/modify" withJSON:requestDictionary completionHandler:^(id jsonResponse, NSError *error) { 57 | NSMutableArray* savedSubs = [NSMutableArray array]; 58 | NSMutableArray* deletedSubs = [NSMutableArray array]; 59 | NSMutableDictionary* partialFailures = [NSMutableDictionary dictionary]; 60 | 61 | if ([jsonResponse isKindOfClass:[NSDictionary class]] && jsonResponse[@"subscriptions"]) { 62 | [jsonResponse[@"subscriptions"] enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 63 | 64 | NSString* savedSubID = obj[@"subscriptionID"]; 65 | CKSubscription* originalSub = savedSubscriptionIDToSubscription[savedSubID]; 66 | 67 | if (originalSub) { 68 | NSError* recordError = nil; 69 | if (obj[@"serverErrorCode"]) { 70 | recordError = [[NSError alloc] initWithCKErrorDictionary:obj]; 71 | [partialFailures setObject:recordError forKey:originalSub.subscriptionID]; 72 | } 73 | else { 74 | [originalSub updateWithDictionary:obj]; 75 | [savedSubs addObject:originalSub]; 76 | } 77 | } 78 | else if (obj[@"deleted"]) { 79 | // was it deleted? 80 | [deletedSubs addObject:savedSubID]; 81 | } 82 | }]; 83 | } 84 | else if (!error) { 85 | error = [[NSError alloc] initWithCKErrorDictionary:jsonResponse]; 86 | } 87 | 88 | if (!error && [[partialFailures allKeys] count]) { 89 | NSDictionary* userInfo = @{ CKErrorUserInfoContainerIDKey : self.database.container.containerIdentifier, 90 | CKErrorUserInfoPartialErrorsKey : partialFailures }; 91 | error = [[NSError alloc] initWithDomain:CKErrorDomain code:CKErrorPartialFailure userInfo:userInfo]; 92 | } 93 | 94 | if (self.modifySubscriptionsCompletionBlock) { 95 | self.modifySubscriptionsCompletionBlock(savedSubs, deletedSubs, error); 96 | } 97 | 98 | [self setExecuting:NO]; 99 | [self setFinished:YES]; 100 | }]; 101 | } 102 | else { 103 | if (self.modifySubscriptionsCompletionBlock) { 104 | self.modifySubscriptionsCompletionBlock(@[], @[], nil); 105 | } 106 | 107 | [self setExecuting:NO]; 108 | [self setFinished:YES]; 109 | } 110 | } 111 | 112 | @end 113 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKNotification.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKNotification.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | @class CKRecordID, CKRecordZoneID; 11 | 12 | @interface CKNotificationID : NSObject 13 | @end 14 | 15 | typedef NS_ENUM(NSInteger, CKNotificationType) { 16 | CKNotificationTypeQuery = 1, 17 | CKNotificationTypeRecordZone = 2, 18 | CKNotificationTypeReadNotification = 3, /* Indicates a notification that a client had previously marked as read. */ 19 | } NS_ENUM_AVAILABLE(10_10, 8_0); 20 | 21 | @interface CKNotification : NSObject 22 | 23 | - (instancetype)init NS_UNAVAILABLE; 24 | 25 | // not needed for now, implement when needed - kevin 2015-12-21 26 | //+ (instancetype)notificationFromRemoteNotificationDictionary:(NSDictionary *)notificationDictionary; 27 | 28 | /* When you instantiate a CKNotification from a remote notification dictionary, you will get back a concrete 29 | subclass defined below. Use the type of notification to avoid -isKindOfClass: checks */ 30 | @property(nonatomic, readonly, assign) CKNotificationType notificationType; 31 | 32 | @property(nonatomic, readonly, copy) CKNotificationID *notificationID; 33 | 34 | @property(nonatomic, readonly, copy) NSString *containerIdentifier; 35 | 36 | /* push notifications have a limited size. In some cases, servers may not be able to send you a full 37 | CKNotification's worth of info in one push. In those cases, isPruned returns YES. The order in which we'll 38 | drop properties is defined in each CKNotification subclass below. 39 | The CKNotification can be obtained in full via a CKFetchNotificationChangesOperation */ 40 | @property(nonatomic, readonly, assign) BOOL isPruned; 41 | 42 | 43 | /* These keys are parsed out of the 'aps' payload from a remote notification dictionary */ 44 | 45 | /* Optional alert string to display in a push notification. */ 46 | @property(nonatomic, readonly, copy) NSString *alertBody; 47 | /* Instead of a raw alert string, you may optionally specify a key for a localized string in your app's Localizable.strings file. */ 48 | @property(nonatomic, readonly, copy) NSString *alertLocalizationKey; 49 | /* A list of field names to take from the matching record that is used as substitution variables in a formatted alert string. */ 50 | @property(nonatomic, readonly, copy) NSArray /* NSString */ *alertLocalizationArgs; 51 | /* A key for a localized string to be used as the alert action in a modal style notification. */ 52 | @property(nonatomic, readonly, copy) NSString *alertActionLocalizationKey; 53 | /* The name of an image in your app bundle to be used as the launch image when launching in response to the notification. */ 54 | @property(nonatomic, readonly, copy) NSString *alertLaunchImage; 55 | 56 | /* The number to display as the badge of the application icon */ 57 | @property(nonatomic, readonly, copy) NSNumber *badge; 58 | /* The name of a sound file in your app bundle to play upon receiving the notification. */ 59 | @property(nonatomic, readonly, copy) NSString *soundName; 60 | 61 | @end 62 | 63 | /* notificationType == CKNotificationTypeQuery 64 | When properties must be dropped (see isPruned), here's the order of importance. The most important properties are first, 65 | they'll be the last ones to be dropped. 66 | - notificationID 67 | - badge 68 | - alertLocalizationKey 69 | - alertLocalizationArgs 70 | - alertBody 71 | - alertActionLocalizationKey 72 | - alertLaunchImage 73 | - soundName 74 | - desiredKeys 75 | - queryNotificationReason 76 | - recordID 77 | - containerIdentifier 78 | */ 79 | 80 | typedef NS_ENUM(NSInteger, CKQueryNotificationReason) { 81 | CKQueryNotificationReasonRecordCreated = 1, 82 | CKQueryNotificationReasonRecordUpdated, 83 | CKQueryNotificationReasonRecordDeleted, 84 | } NS_ENUM_AVAILABLE(10_10, 8_0); 85 | 86 | @interface CKQueryNotification : CKNotification 87 | 88 | @property(nonatomic, readonly, assign) CKQueryNotificationReason queryNotificationReason; 89 | 90 | /* A set of key->value pairs for creates and updates. You request the server fill out this property via the 91 | "desiredKeys" property of CKNotificationInfo */ 92 | @property(nonatomic, readonly, copy) NSDictionary *recordFields; 93 | 94 | @property(nonatomic, readonly, copy) CKRecordID *recordID; 95 | 96 | /* If YES, this record is in the public database. Else, it's in the private database */ 97 | @property(nonatomic, readonly, assign) BOOL isPublicDatabase; 98 | 99 | @end 100 | 101 | 102 | /* notificationType == CKNotificationTypeRecordZone 103 | When properties must be dropped (see isPruned), here's the order of importance. The most important properties are first, 104 | they'll be the last ones to be dropped. 105 | - notificationID 106 | - badge 107 | - alertLocalizationKey 108 | - alertLocalizationArgs 109 | - alertBody 110 | - alertActionLocalizationKey 111 | - alertLaunchImage 112 | - soundName 113 | - recordZoneID 114 | - containerIdentifier 115 | */ 116 | 117 | @interface CKRecordZoneNotification : CKNotification 118 | 119 | @property(nonatomic, readonly, copy) CKRecordZoneID *recordZoneID; 120 | 121 | @end 122 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKNotification.m: -------------------------------------------------------------------------------- 1 | // 2 | // CKNotification.m 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #import 9 | #import "CKNotification.h" 10 | 11 | @implementation CKNotificationID 12 | 13 | + (BOOL)supportsSecureCoding { 14 | return NO; 15 | } 16 | 17 | @end 18 | 19 | @implementation CKNotification 20 | 21 | @end 22 | 23 | @implementation CKQueryNotification 24 | 25 | @end 26 | 27 | @implementation CKRecordZoneNotification 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKNotificationInfo+AgileDictionary.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKNotificationInfo+AgileDictionary.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import "CKNotificationInfo.h" 9 | 10 | @interface CKNotificationInfo (AgileDictionary) 11 | 12 | - (NSDictionary *)asAgileDictionary; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKNotificationInfo+AgileDictionary.m: -------------------------------------------------------------------------------- 1 | // 2 | // CKNotificationInfo+AgileDictionary.m 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import "CKNotificationInfo+AgileDictionary.h" 9 | 10 | @implementation CKNotificationInfo (AgileDictionary) 11 | 12 | - (NSDictionary *)asAgileDictionary { 13 | NSMutableDictionary *infoDictionary = [NSMutableDictionary dictionary]; 14 | if (self.alertBody) { 15 | [infoDictionary setObject:self.alertBody forKey:@"alertBody"]; 16 | } 17 | if (self.alertLocalizationKey) { 18 | [infoDictionary setObject:self.alertLocalizationKey forKey:@"alertLocalizationKey"]; 19 | } 20 | if (self.alertLocalizationArgs) { 21 | [infoDictionary setObject:self.alertLocalizationArgs forKey:@"alertLocalizationArgs"]; 22 | } 23 | if (self.alertActionLocalizationKey) { 24 | [infoDictionary setObject:self.alertActionLocalizationKey forKey:@"alertalertActionLocalizationKeyBody"]; 25 | } 26 | if (self.alertLaunchImage) { 27 | [infoDictionary setObject:self.alertLaunchImage forKey:@"alertLaunchImage"]; 28 | } 29 | if (self.soundName) { 30 | [infoDictionary setObject:self.soundName forKey:@"soundName"]; 31 | } 32 | [infoDictionary setObject:@(self.shouldBadge) forKey:@"shouldBadge"]; 33 | [infoDictionary setObject:@(self.shouldSendContentAvailable) forKey:@"shouldSendContentAvailable"]; 34 | 35 | return infoDictionary; 36 | } 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKNotificationInfo.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKNotificationInfo.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | /* The payload of a push notification delivered in the UIApplication application:didReceiveRemoteNotification: delegate method contains information about the firing subscription. Use 11 | +[CKNotification notificationFromRemoteNotificationDictionary:] to parse that payload. */ 12 | 13 | @interface CKNotificationInfo : NSObject 14 | 15 | /* Optional alert string to display in a push notification. */ 16 | @property(nonatomic, copy) NSString *alertBody; 17 | 18 | /* Instead of a raw alert string, you may optionally specify a key for a localized string in your app's Localizable.strings file. */ 19 | @property(nonatomic, copy) NSString *alertLocalizationKey; 20 | 21 | /* A list of field names to take from the matching record that is used as substitution variables in a formatted alert string. */ 22 | @property(nonatomic, copy) NSArray /* NSString */ *alertLocalizationArgs; 23 | 24 | /* A key for a localized string to be used as the alert action in a modal style notification. */ 25 | @property(nonatomic, copy) NSString *alertActionLocalizationKey; 26 | 27 | /* The name of an image in your app bundle to be used as the launch image when launching in response to the notification. */ 28 | @property(nonatomic, copy) NSString *alertLaunchImage; 29 | 30 | /* The name of a sound file in your app bundle to play upon receiving the notification. */ 31 | @property(nonatomic, copy) NSString *soundName; 32 | 33 | /* A list of keys from the matching record to include in the notification payload. 34 | Only some keys are allowed. The value types associated with those keys on the server must be one of these classes: 35 | CKReference 36 | CLLocation 37 | NSDate 38 | NSNumber 39 | NSString */ 40 | @property(nonatomic, copy) NSArray /* NSString */ *desiredKeys; 41 | 42 | /* Indicates that the notification should increment the app's badge count. Default value is NO. */ 43 | @property(nonatomic, assign) BOOL shouldBadge; 44 | 45 | /* Indicates that the notification should be sent with the "content-available" flag to allow for background downloads in the application. 46 | Default value is NO. */ 47 | @property(nonatomic, assign) BOOL shouldSendContentAvailable; 48 | 49 | @end 50 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKNotificationInfo.m: -------------------------------------------------------------------------------- 1 | // 2 | // CKNotificationInfo.m 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import "CKNotificationInfo.h" 9 | #import "CKNotificationInfo_Private.h" 10 | #import "CKNotificationInfo+AgileDictionary.h" 11 | 12 | @implementation CKNotificationInfo 13 | 14 | - (instancetype)init { 15 | if (self = [super init]) { 16 | } 17 | return self; 18 | } 19 | 20 | - (instancetype)initWithDictionary:(NSDictionary *)dictionary { 21 | if (self = [super init]) { 22 | self.alertBody = [dictionary objectForKey:@"alertBody"]; 23 | self.alertLocalizationKey = [dictionary objectForKey:@"alertLocalizationKey"]; 24 | self.alertLocalizationArgs = [dictionary objectForKey:@"alertLocalizationArgs"]; 25 | self.alertActionLocalizationKey = [dictionary objectForKey:@"alertActionLocalizationKey"]; 26 | self.alertLaunchImage = [dictionary objectForKey:@"alertLaunchImage"]; 27 | self.soundName = [dictionary objectForKey:@"soundName"]; 28 | self.shouldBadge = [[dictionary objectForKey:@"shouldBadge"] boolValue]; 29 | self.shouldSendContentAvailable = [[dictionary objectForKey:@"shouldSendContentAvailable"] boolValue]; 30 | } 31 | return self; 32 | } 33 | 34 | 35 | #pragma mark - NSCoding 36 | 37 | + (BOOL)supportsSecureCoding { 38 | return YES; 39 | } 40 | 41 | - (void)encodeWithCoder:(NSCoder *)aCoder { 42 | if (self.alertBody) { 43 | [aCoder encodeObject:self.alertBody forKey:@"alertBody"]; 44 | } 45 | if (self.alertLocalizationKey) { 46 | [aCoder encodeObject:self.alertLocalizationKey forKey:@"alertLocalizationKey"]; 47 | } 48 | if (self.alertLocalizationArgs) { 49 | [aCoder encodeObject:self.alertLocalizationArgs forKey:@"alertLocalizationArgs"]; 50 | } 51 | if (self.alertActionLocalizationKey) { 52 | [aCoder encodeObject:self.alertActionLocalizationKey forKey:@"alertalertActionLocalizationKeyBody"]; 53 | } 54 | if (self.alertLaunchImage) { 55 | [aCoder encodeObject:self.alertLaunchImage forKey:@"alertLaunchImage"]; 56 | } 57 | if (self.soundName) { 58 | [aCoder encodeObject:self.soundName forKey:@"soundName"]; 59 | } 60 | [aCoder encodeBool:self.shouldBadge forKey:@"shouldBadge"]; 61 | [aCoder encodeBool:self.shouldSendContentAvailable forKey:@"shouldSendContentAvailable"]; 62 | } 63 | 64 | - (instancetype)initWithCoder:(NSCoder *)aDecoder { 65 | if (self = [super init]) { 66 | self.alertBody = [aDecoder decodeObjectOfClass:[NSString class] forKey:@"alertBody"]; 67 | self.alertLocalizationKey = [aDecoder decodeObjectOfClass:[NSString class] forKey:@"alertLocalizationKey"]; 68 | self.alertLocalizationArgs = [aDecoder decodeObjectOfClass:[NSString class] forKey:@"alertLocalizationArgs"]; 69 | self.alertActionLocalizationKey = [aDecoder decodeObjectOfClass:[NSString class] forKey:@"alertActionLocalizationKey"]; 70 | self.alertLaunchImage = [aDecoder decodeObjectOfClass:[NSString class] forKey:@"alertLaunchImage"]; 71 | self.soundName = [aDecoder decodeObjectOfClass:[NSString class] forKey:@"soundName"]; 72 | self.shouldBadge = [aDecoder decodeBoolForKey:@"shouldBadge"]; 73 | self.shouldSendContentAvailable = [aDecoder decodeBoolForKey:@"shouldSendContentAvailable"]; 74 | } 75 | return self; 76 | } 77 | 78 | #pragma mark - NSCopying 79 | 80 | - (instancetype)copyWithZone:(NSZone *)zone { 81 | return [[CKNotificationInfo allocWithZone:zone] initWithDictionary:[self asAgileDictionary]]; 82 | } 83 | 84 | @end 85 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKNotificationInfo_Private.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKNotificationInfo_Private.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import 9 | #import "CKNotificationInfo.h" 10 | 11 | @interface CKNotificationInfo () 12 | 13 | - (instancetype)initWithDictionary:(NSDictionary *)dictionary; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKOperation.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKOperation.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | @class CKContainer; 11 | 12 | @interface CKOperation : NSOperation 13 | 14 | - (instancetype)init NS_DESIGNATED_INITIALIZER; 15 | 16 | /* All CKOperations default to self.qualityOfService == NSOperationQualityOfServiceUserInitiated */ 17 | 18 | /* If no container is set, [CKContainer defaultContainer] is used */ 19 | @property(nonatomic, strong) CKContainer *container; 20 | 21 | /* If set, network traffic will happen on a background NSURLSession. 22 | Defaults to (NSOperationQualityOfServiceBackground == self.qualityOfService) */ 23 | @property(nonatomic, assign) BOOL usesBackgroundSession; 24 | 25 | /* Defaults to YES */ 26 | @property(nonatomic, assign) BOOL allowsCellularAccess; 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKOperation.m: -------------------------------------------------------------------------------- 1 | // 2 | // CKOperation.m 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #import 9 | #import "CKOperation.h" 10 | #import "CKContainer.h" 11 | 12 | @implementation CKOperation 13 | 14 | 15 | /* If no container is set, [CKContainer defaultContainer] is used */ 16 | - (CKContainer *)container { 17 | if (!_container) { 18 | return [CKContainer defaultContainer]; 19 | } 20 | return _container; 21 | } 22 | 23 | 24 | - (instancetype)init { 25 | if (self = [super init]) { 26 | } 27 | return self; 28 | } 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKRecord+AgileDictionary.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKRecord+AgileDictionary.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import "CKRecord.h" 9 | 10 | @interface CKRecord (AgileDictionary) 11 | 12 | - (NSDictionary *)asAgileDictionary; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKRecord+AgileDictionary.m: -------------------------------------------------------------------------------- 1 | // 2 | // CKRecord+AgileDictionary.m 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import "CKRecord+AgileDictionary.h" 9 | #import "CKRecordID+AgileDictionary.h" 10 | #import "CKRecordZoneID+AgileDictionary.h" 11 | #import "CKAsset+AgileDictionary.h" 12 | #import "CLLocation+AgileDictionary.h" 13 | #import "CKReference+AgileDictionary.h" 14 | #import "Defines.h" 15 | 16 | @implementation CKRecord (AgileDictionary) 17 | 18 | - (NSDictionary *)asAgileDictionary { 19 | NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithDictionary:@{ @"recordName": self.recordID.recordName, 20 | @"zoneID": self.recordID.zoneID.zoneName, 21 | @"recordType": self.recordType, 22 | @"fields": [self agileFieldsDictionary] }]; 23 | if (self.recordChangeTag) { 24 | [dict setObject:self.recordChangeTag forKey:@"recordChangeTag"]; 25 | } 26 | return dict; 27 | } 28 | 29 | - (NSDictionary *)agileFieldsDictionary { 30 | NSMutableDictionary *output = [NSMutableDictionary dictionary]; 31 | 32 | for (NSString *key in [self allKeys]) { 33 | output[key] = @{ @"value": [self encodedObject:[self objectForKey:key]] }; 34 | } 35 | 36 | return output; 37 | } 38 | 39 | - (id)encodedObject:(NSObject *)val { 40 | if ([val isKindOfClass:[NSString class]]) { 41 | return val; 42 | } 43 | else if ([val isKindOfClass:[NSNumber class]]) { 44 | if (strcmp([(NSNumber *)val objCType], @encode(BOOL)) == 0) { 45 | // make sure booleans encode as integers 46 | return [NSNumber numberWithInteger:[(NSNumber *)val integerValue]]; 47 | } 48 | else { 49 | return val; 50 | } 51 | } 52 | else if ([val isKindOfClass:[NSDate class]]) { 53 | return @((NSInteger)([(NSDate *)val timeIntervalSince1970] * 1000)); 54 | } 55 | else if ([val isKindOfClass:[NSData class]]) { 56 | return [(NSData *)val base64EncodedStringWithOptions:0]; 57 | } 58 | else if ([val isKindOfClass:[CKReference class]]) { 59 | CKReference *ref = (CKReference *)val; 60 | return [ref asAgileDictionary]; 61 | } 62 | else if ([val isKindOfClass:[CLLocation class]]) { 63 | return [(CLLocation *)val asAgileDictionary]; 64 | } 65 | else if ([val isKindOfClass:[NSArray class]]) { 66 | NSMutableArray *vals = [NSMutableArray array]; 67 | [(NSArray *)val enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) { 68 | [vals addObject:[self encodedObject:obj]]; 69 | }]; 70 | return vals; 71 | } 72 | else if ([val isKindOfClass:[CKAsset class]]) { 73 | return [(CKAsset *)val asAgileDictionary]; 74 | } 75 | NSMutableDictionary *output = [NSMutableDictionary dictionary]; 76 | 77 | for (NSString *key in [self allKeys]) { 78 | output[key] = @{ @"value": [self objectForKey:key] }; 79 | } 80 | 81 | return output; 82 | } 83 | 84 | @end 85 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKRecord.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKRecord.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #import 9 | #import 10 | #import "CKAsset.h" 11 | #import "CKReference.h" 12 | #import "CKDefines.h" 13 | 14 | @class CKRecordID, CKRecordZoneID; 15 | 16 | /* Use this constant for the recordType parameter when fetching User Records. */ 17 | CK_EXTERN NSString *const CKRecordTypeUserRecord NS_AVAILABLE(10_10, 8_0); 18 | 19 | @protocol CKRecordValue 20 | @end 21 | 22 | @interface CKRecord : NSObject 23 | 24 | - (instancetype)init NS_UNAVAILABLE; 25 | 26 | /* These create the record in the default zone. */ 27 | - (instancetype)initWithRecordType:(NSString *)recordType; 28 | - (instancetype)initWithRecordType:(NSString *)recordType recordID:(CKRecordID *)recordID; 29 | 30 | - (instancetype)initWithRecordType:(NSString *)recordType zoneID:(CKRecordZoneID *)zoneID; 31 | 32 | @property(nonatomic, readonly, copy) NSString *recordType; 33 | @property(nonatomic, readonly, copy) CKRecordID *recordID; 34 | 35 | /* Change tags are updated by the server to a unique value every time a record is modified. 36 | A different change tag necessarily means that the contents of the record are different. */ 37 | @property(nonatomic, readonly, copy) NSString *recordChangeTag; 38 | 39 | /* This is a User Record recordID, identifying the user that created this record. */ 40 | @property(nonatomic, readonly, copy) CKRecordID *creatorUserRecordID; 41 | @property(nonatomic, readonly, copy) NSDate *creationDate; 42 | 43 | /* This is a User Record recordID, identifying the user that last modified this record. */ 44 | @property(nonatomic, readonly, copy) CKRecordID *lastModifiedUserRecordID; 45 | @property(nonatomic, readonly, copy) NSDate *modificationDate; 46 | 47 | /* 48 | In addition to objectForKey: and setObject:forKey:, dictionary-style subscripting (record[key] and record[key] = value) can be used to get and set values. 49 | Acceptable value object classes are: 50 | CKReference 51 | CKAsset 52 | CLLocation 53 | NSData 54 | NSDate 55 | NSNumber 56 | NSString 57 | NSArray containing objects of any of the types above 58 | 59 | Any other classes will result in an exception with name NSInvalidArgumentException. 60 | 61 | Derived field keys are prefixed with '_'. Attempting to set a key prefixed with a '_' will result in an error. 62 | 63 | Key names roughly match C variable name restrictions. They must begin with an ASCII letter and can contain ASCII 64 | letters and numbers and the underscore character. 65 | The maximum key length is 255 characters. 66 | */ 67 | - (id)objectForKey:(NSString *)key; 68 | - (void)setObject:(id)object forKey:(NSString *)key; 69 | - (NSArray /* NSString */ *)allKeys; 70 | 71 | /* A special property that returns an array of token generated from all the string field values in the record. 72 | These tokens have been normalized for the current locale, so they are suitable for performing full-text searches. */ 73 | - (NSArray /* NSString */ *)allTokens; 74 | 75 | - (id)objectForKeyedSubscript:(NSString *)key; 76 | - (void)setObject:(id)object forKeyedSubscript:(NSString *)key; 77 | 78 | /* A list of keys that have been modified on the local CKRecord instance */ 79 | - (NSArray /* NSString */ *)changedKeys; 80 | 81 | /* CKRecord supports NSSecureCoding. When you invoke 82 | -encodeWithCoder: on a CKRecord, it encodes all its values. Including the record values you've set. 83 | If you want to store a CKRecord instance locally, AND you're already storing the record values locally, 84 | that's overkill. In that case, you can use 85 | -encodeSystemFieldsWithCoder:. This will encode all parts of a CKRecord except the record keys / values you 86 | have access to via the -changedKeys and -objectForKey: methods. 87 | If you use initWithCoder: to reconstitute a CKRecord you encoded via encodeSystemFieldsWithCoder:, then be aware that 88 | - any record values you had set on the original instance, but had not saved, will be lost 89 | - the reconstituted CKRecord's changedKeys will be empty 90 | */ 91 | - (void)encodeSystemFieldsWithCoder:(NSCoder *)coder; 92 | 93 | @end 94 | 95 | @interface NSString (CKRecordValue) 96 | @end 97 | 98 | @interface NSNumber (CKRecordValue) 99 | @end 100 | 101 | @interface NSArray (CKRecordValue) 102 | @end 103 | 104 | @interface NSDate (CKRecordValue) 105 | @end 106 | 107 | @interface NSData (CKRecordValue) 108 | @end 109 | 110 | @interface CKReference (CKRecordValue) 111 | @end 112 | 113 | @interface CKAsset (CKRecordValue) 114 | @end 115 | 116 | @interface CLLocation (CKRecordValue) 117 | @end 118 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKRecordID+AgileDictionary.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKRecordID+AgileDictionary.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import "CKRecordID.h" 9 | 10 | @interface CKRecordID (AgileDictionary) 11 | 12 | - (NSDictionary *)asAgileDictionary; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKRecordID+AgileDictionary.m: -------------------------------------------------------------------------------- 1 | // 2 | // CKRecordID+AgileDictionary.m 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import "CKRecordID+AgileDictionary.h" 9 | #import "CKRecordZoneID.h" 10 | 11 | @implementation CKRecordID (AgileDictionary) 12 | 13 | - (NSDictionary *)asAgileDictionary { 14 | return @{ @"recordName": self.recordName, 15 | @"zoneID": self.zoneID.zoneName }; 16 | } 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKRecordID.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKRecordID.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | @class CKRecordZoneID; 11 | 12 | @interface CKRecordID : NSObject 13 | 14 | - (instancetype)init NS_UNAVAILABLE; 15 | 16 | /* Record names must be 255 characters or less. Most UTF-8 characters are valid. */ 17 | /* This creates a record ID in the default zone */ 18 | - (instancetype)initWithRecordName:(NSString *)recordName; 19 | - (instancetype)initWithRecordName:(NSString *)recordName zoneID:(CKRecordZoneID *)zoneID NS_DESIGNATED_INITIALIZER; 20 | 21 | @property(nonatomic, readonly, strong) NSString *recordName; 22 | @property(nonatomic, readonly, strong) CKRecordZoneID *zoneID; 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKRecordID.m: -------------------------------------------------------------------------------- 1 | // 2 | // CKRecordID.m 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #import 9 | #import "CKRecordID.h" 10 | #import "Defines.h" 11 | #import "CKRecordZone.h" 12 | #import "CKRecordZoneID.h" 13 | 14 | @implementation CKRecordID 15 | 16 | - (instancetype)init { 17 | @throw kInvalidMethodException; 18 | } 19 | 20 | /* Record names must be 255 characters or less. Most UTF-8 characters are valid. */ 21 | /* This creates a record ID in the default zone */ 22 | - (instancetype)initWithRecordName:(NSString *)recordName { 23 | return [self initWithRecordName:recordName zoneID:[[CKRecordZone defaultRecordZone] zoneID]]; 24 | } 25 | 26 | - (instancetype)initWithRecordName:(NSString *)recordName zoneID:(CKRecordZoneID *)zoneID { 27 | if (self = [super init]) { 28 | _recordName = recordName; 29 | _zoneID = zoneID; 30 | } 31 | return self; 32 | } 33 | 34 | #pragma mark - NSCoding 35 | 36 | + (BOOL)supportsSecureCoding { 37 | return YES; 38 | } 39 | 40 | - (void)encodeWithCoder:(NSCoder *)aCoder { 41 | [aCoder encodeObject:_recordName forKey:@"recordName"]; 42 | [aCoder encodeObject:_zoneID forKey:@"zoneID"]; 43 | } 44 | 45 | - (instancetype)initWithCoder:(NSCoder *)aDecoder { 46 | NSString *recordName = [aDecoder decodeObjectOfClass:[NSString class] forKey:@"recordName"]; 47 | CKRecordZoneID *zoneID = [aDecoder decodeObjectOfClass:[CKRecordZoneID class] forKey:@"zoneID"]; 48 | return [self initWithRecordName:recordName zoneID:zoneID]; 49 | } 50 | 51 | #pragma mark - Equals 52 | 53 | - (BOOL)isEqual:(id)object { 54 | if ([object isKindOfClass:[CKRecordID class]]) { 55 | return [self.recordName isEqualToString:[(CKRecordID *)object recordName]] && [self.zoneID isEqual:[(CKRecordID *)object zoneID]]; 56 | } 57 | return NO; 58 | } 59 | 60 | - (NSUInteger)hash { 61 | return [self.recordName hash] ^ [self.zoneID hash]; 62 | } 63 | 64 | #pragma mark - NSCopying 65 | 66 | - (id)copyWithZone:(NSZone *)zone { 67 | return [[[self class] allocWithZone:zone] initWithRecordName:[self.recordName copyWithZone:zone] zoneID:[self.zoneID copyWithZone:zone]]; 68 | } 69 | 70 | #pragma mark - Description 71 | 72 | - (NSString *)description { 73 | return [NSString stringWithFormat:@"[CKRecordID: %@ %@]", self.recordName, self.zoneID]; 74 | } 75 | 76 | @end 77 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKRecordZone+AgileDictionary.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKRecordZone+AgileDictionary.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import "CKRecordZone.h" 9 | 10 | @interface CKRecordZone (AgileDictionary) 11 | 12 | - (NSDictionary *)asAgileDictionary; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKRecordZone+AgileDictionary.m: -------------------------------------------------------------------------------- 1 | // 2 | // CKRecordZone+AgileDictionary.m 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import "CKRecordZone+AgileDictionary.h" 9 | #import "CKRecordZoneID+AgileDictionary.h" 10 | 11 | @implementation CKRecordZone (AgileDictionary) 12 | 13 | - (NSDictionary *)asAgileDictionary { 14 | return @{ @"zoneID": [self.zoneID asAgileDictionary] }; 15 | } 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKRecordZone.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKRecordZone.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #import 9 | #import "CKDefines.h" 10 | 11 | @class CKRecordZoneID; 12 | 13 | typedef NS_OPTIONS(NSUInteger, CKRecordZoneCapabilities) { 14 | /* This zone supports CKFetchRecordChangesOperation */ 15 | CKRecordZoneCapabilityFetchChanges = 1 << 0, 16 | /* Batched changes to this zone happen atomically */ 17 | CKRecordZoneCapabilityAtomic = 1 << 1, 18 | } NS_AVAILABLE(10_10, 8_0); 19 | 20 | /* The default zone has no capabilities */ 21 | CK_EXTERN NSString *const CKRecordZoneDefaultName NS_AVAILABLE(10_10, 8_0); 22 | 23 | @interface CKRecordZone : NSObject 24 | 25 | + (CKRecordZone *)defaultRecordZone; 26 | 27 | - (instancetype)init NS_UNAVAILABLE; 28 | - (instancetype)initWithZoneName:(NSString *)zoneName; 29 | - (instancetype)initWithZoneID:(CKRecordZoneID *)zoneID; 30 | 31 | @property(nonatomic, readonly, strong) CKRecordZoneID *zoneID; 32 | 33 | /* Capabilities are not set until a record zone is saved */ 34 | @property(nonatomic, readonly, assign) CKRecordZoneCapabilities capabilities; 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKRecordZone.m: -------------------------------------------------------------------------------- 1 | // 2 | // CKRecordZone.m 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #import 9 | #import "CKRecordZone.h" 10 | #import "CKRecordZoneID.h" 11 | 12 | @implementation CKRecordZone 13 | 14 | + (CKRecordZone *)defaultRecordZone { 15 | return [[CKRecordZone alloc] initWithZoneName:@"_defaultZone"]; 16 | } 17 | 18 | - (CKRecordZoneCapabilities)capabilities { 19 | return CKRecordZoneCapabilityAtomic | CKRecordZoneCapabilityFetchChanges; 20 | } 21 | 22 | - (instancetype)initWithZoneName:(NSString *)zoneName { 23 | return [self initWithZoneID:[[CKRecordZoneID alloc] initWithZoneName:zoneName]]; 24 | } 25 | 26 | - (instancetype)initWithZoneID:(CKRecordZoneID *)zoneID { 27 | if (self = [super init]) { 28 | _zoneID = zoneID; 29 | } 30 | return self; 31 | } 32 | 33 | #pragma mark - NSCoding 34 | 35 | + (BOOL)supportsSecureCoding { 36 | return YES; 37 | } 38 | 39 | - (void)encodeWithCoder:(NSCoder *)aCoder { 40 | [aCoder encodeObject:_zoneID forKey:@"zoneID"]; 41 | } 42 | 43 | - (instancetype)initWithCoder:(NSCoder *)aDecoder { 44 | CKRecordZoneID *zoneID = [aDecoder decodeObjectOfClass:[CKRecordZoneID class] forKey:@"zoneID"]; 45 | return [self initWithZoneID:zoneID]; 46 | } 47 | 48 | #pragma mark - NSCopying 49 | 50 | - (id)copyWithZone:(NSZone *)zone { 51 | return [[[self class] allocWithZone:zone] initWithZoneID:[self.zoneID copyWithZone:zone]]; 52 | } 53 | 54 | @end 55 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKRecordZoneID+AgileDictionary.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKRecordZoneID+AgileDictionary.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import "CKRecordZoneID.h" 9 | 10 | @interface CKRecordZoneID (AgileDictionary) 11 | 12 | - (NSDictionary *)asAgileDictionary; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKRecordZoneID+AgileDictionary.m: -------------------------------------------------------------------------------- 1 | // 2 | // CKRecordZoneID+AgileDictionary.m 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import "CKRecordZoneID+AgileDictionary.h" 9 | 10 | @implementation CKRecordZoneID (AgileDictionary) 11 | 12 | - (NSDictionary *)asAgileDictionary { 13 | return @{ @"zoneName": self.zoneName, 14 | @"ownerName": self.ownerName }; 15 | } 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKRecordZoneID.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKRecordZoneID.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | @interface CKRecordZoneID : NSObject 11 | 12 | /* Zone names must be 255 characters or less. Most UTF-8 characters are valid. */ 13 | - (instancetype)init NS_UNAVAILABLE; 14 | - (instancetype)initWithZoneName:(NSString *)zoneName; 15 | - (instancetype)initWithZoneName:(NSString *)zoneName ownerName:(NSString *)ownerName NS_DESIGNATED_INITIALIZER; 16 | 17 | @property(nonatomic, readonly, strong) NSString *zoneName; 18 | @property(nonatomic, readonly, strong) NSString *ownerName; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKRecordZoneID.m: -------------------------------------------------------------------------------- 1 | // 2 | // CKRecordZoneID.m 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #import 9 | #import "CKRecordZoneID.h" 10 | #import "Defines.h" 11 | #import "CKRecordZoneID_Private.h" 12 | #import "CKContainer.h" 13 | 14 | @implementation CKRecordZoneID 15 | 16 | - (instancetype)init { 17 | @throw kInvalidMethodException; 18 | } 19 | 20 | - (instancetype)initWithZoneName:(NSString *)zoneName { 21 | return [self initWithZoneName:zoneName ownerName:nil]; 22 | } 23 | 24 | - (instancetype)initWithZoneName:(NSString *)zoneName ownerName:(NSString *)ownerName { 25 | if (self = [super init]) { 26 | _zoneName = zoneName; 27 | _ownerName = ownerName ? ownerName : CKOwnerDefaultName; 28 | } 29 | return self; 30 | } 31 | 32 | - (instancetype)initWithDictionary:(NSDictionary *)dictionary { 33 | return [self initWithZoneName:dictionary[@"zoneName"] ownerName:dictionary[@"ownerName"]]; 34 | } 35 | 36 | #pragma mark - NSCoding 37 | 38 | + (BOOL)supportsSecureCoding { 39 | return YES; 40 | } 41 | 42 | - (void)encodeWithCoder:(NSCoder *)aCoder { 43 | [aCoder encodeObject:self.zoneName forKey:@"zoneName"]; 44 | [aCoder encodeObject:self.ownerName forKey:@"ownerName"]; 45 | } 46 | 47 | - (instancetype)initWithCoder:(NSCoder *)aDecoder { 48 | NSString *zoneName = [aDecoder decodeObjectOfClass:[NSString class] forKey:@"zoneName"]; 49 | NSString *ownerName = [aDecoder decodeObjectOfClass:[NSString class] forKey:@"ownerName"]; 50 | return [self initWithZoneName:zoneName ownerName:ownerName]; 51 | } 52 | 53 | #pragma mark - Equals 54 | 55 | - (BOOL)isEqual:(id)object { 56 | if ([object isKindOfClass:[CKRecordZoneID class]]) { 57 | return [self.zoneName isEqualToString:[(CKRecordZoneID *)object zoneName]] && [self.ownerName isEqual:[(CKRecordZoneID *)object ownerName]]; 58 | } 59 | return NO; 60 | } 61 | 62 | - (NSUInteger)hash { 63 | return [self.zoneName hash] ^ [self.ownerName hash]; 64 | } 65 | 66 | #pragma mark - NSCopying 67 | 68 | - (id)copyWithZone:(NSZone *)zone { 69 | return [[[self class] allocWithZone:zone] initWithZoneName:self.zoneName ownerName:self.ownerName]; 70 | } 71 | 72 | #pragma mark - Description 73 | 74 | - (NSString *)description { 75 | return [NSString stringWithFormat:@"[CKRecordZoneID: %@ %@]", self.zoneName, self.ownerName]; 76 | } 77 | 78 | @end 79 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKRecordZoneID_Private.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKRecordZoneID_Private.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import "CKRecordZoneID.h" 9 | 10 | @interface CKRecordZoneID (Private) 11 | 12 | - (instancetype)initWithDictionary:(NSDictionary *)dictionary; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKRecord_Private.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKRecord_Private.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import 9 | #import "CKRecord.h" 10 | #import "CKDatabase.h" 11 | 12 | @interface CKRecord (AgilePrivate) 13 | 14 | - (instancetype)initWithDictionary:(NSDictionary *)dictionary inZone:(CKRecordZoneID *)zoneID; 15 | 16 | - (void)updateWithDictionary:(NSDictionary *)dictionary; 17 | 18 | // returns an array of all errors encountered during downloading, if any 19 | - (NSArray *)synchronouslyDownloadAllAssetsWithProgressBlock:(void (^)(double progress))progressBlock; 20 | 21 | - (NSArray *)synchronouslyUploadAssetsIntoDatabase:(CKDatabase *)database; 22 | 23 | + (NSObject *)recordValueFromDictionary:(NSDictionary *)dictionary inZone:(CKRecordZoneID *)zoneID; 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKReference+AgileDictionary.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKReference+AgileDictionary.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import "CKReference.h" 9 | 10 | @interface CKReference (AgileDictionary) 11 | 12 | - (NSDictionary *)asAgileDictionary; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKReference+AgileDictionary.m: -------------------------------------------------------------------------------- 1 | // 2 | // CKReference+AgileDictionary.m 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import "CKReference+AgileDictionary.h" 9 | #import "CKRecordID.h" 10 | 11 | @implementation CKReference (AgileDictionary) 12 | 13 | - (NSDictionary *)asAgileDictionary { 14 | return @{ @"recordName": self.recordID.recordName, 15 | @"action": @((self.referenceAction == CKReferenceActionNone) ? 0 : 1) }; 16 | } 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKReference+CKFilterType.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKReference+CKFilterType.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | @interface CKReference () 9 | 10 | @end 11 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKReference.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKReference.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | @class CKRecord, CKRecordID, CKAsset; 11 | 12 | typedef NS_ENUM(NSUInteger, CKReferenceAction) { 13 | /* When the referred record is deleted, this record is unchanged, and has a dangling pointer */ 14 | CKReferenceActionNone = 0, 15 | /* When the referred record is deleted then this record is also deleted. */ 16 | CKReferenceActionDeleteSelf = 1, 17 | } NS_ENUM_AVAILABLE(10_10, 8_0); 18 | 19 | 20 | @interface CKReference : NSObject 21 | 22 | - (instancetype)init NS_UNAVAILABLE; 23 | 24 | /* It is acceptable to relate two records that have not yet been uploaded to the server, but those records must be uploaded to the server in the same operation. 25 | If a record references a record that does not exist on the server and is not in the current save operation it will result in an error. */ 26 | - (instancetype)initWithRecordID:(CKRecordID *)recordID action:(CKReferenceAction)action NS_DESIGNATED_INITIALIZER; 27 | - (instancetype)initWithRecord:(CKRecord *)record action:(CKReferenceAction)action; 28 | 29 | @property(nonatomic, readonly, assign) CKReferenceAction referenceAction; 30 | 31 | @property(nonatomic, readonly, copy) CKRecordID *recordID; 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKReference.m: -------------------------------------------------------------------------------- 1 | // 2 | // CKReference.m 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #import 9 | #import "CKReference.h" 10 | #import "Defines.h" 11 | #import "CKRecordID.h" 12 | #import "CKRecord.h" 13 | 14 | @implementation CKReference 15 | 16 | - (instancetype)init { 17 | @throw kInvalidMethodException; 18 | } 19 | 20 | - (instancetype)initWithRecord:(CKRecord *)record action:(CKReferenceAction)action { 21 | return [self initWithRecordID:record.recordID action:action]; 22 | } 23 | 24 | /* It is acceptable to relate two records that have not yet been uploaded to the server, but those records must be uploaded to the server in the same operation. 25 | If a record references a record that does not exist on the server and is not in the current save operation it will result in an error. */ 26 | - (instancetype)initWithRecordID:(CKRecordID *)recordID action:(CKReferenceAction)action { 27 | if (self = [super init]) { 28 | _recordID = recordID; 29 | _referenceAction = action; 30 | } 31 | return self; 32 | } 33 | 34 | - (instancetype)initWithDictionary:(NSDictionary *)dictionary inZone:(CKRecordZoneID *)zoneID { 35 | CKRecordID *recordID = [[CKRecordID alloc] initWithRecordName:dictionary[@"recordName"] zoneID:zoneID]; 36 | CKReferenceAction action = [dictionary[@"action"] isEqualToString:@"NONE"] ? CKReferenceActionNone : CKReferenceActionDeleteSelf; 37 | return [self initWithRecordID:recordID action:action]; 38 | } 39 | 40 | - (void)updateWithDictionary:(NSDictionary *)dictionary { 41 | CKRecordID *recordID = [[CKRecordID alloc] initWithRecordName:dictionary[@"recordName"] zoneID:_recordID.zoneID]; 42 | CKReferenceAction action = [dictionary[@"action"] isEqualToString:@"NONE"] ? CKReferenceActionNone : CKReferenceActionDeleteSelf; 43 | 44 | _recordID = recordID; 45 | _referenceAction = action; 46 | } 47 | 48 | 49 | #pragma mark - NSCoding 50 | 51 | + (BOOL)supportsSecureCoding { 52 | return YES; 53 | } 54 | 55 | - (void)encodeWithCoder:(NSCoder *)aCoder { 56 | [aCoder encodeObject:_recordID forKey:@"recordID"]; 57 | [aCoder encodeObject:@(_referenceAction) forKey:@"referenceAction"]; 58 | } 59 | 60 | - (instancetype)initWithCoder:(NSCoder *)aDecoder { 61 | CKRecordID *recordID = [aDecoder decodeObjectOfClass:[CKRecordID class] forKey:@"recordID"]; 62 | CKReferenceAction action = [[aDecoder decodeObjectOfClass:[NSNumber class] forKey:@"referenceAction"] unsignedIntegerValue]; 63 | if (self = [self initWithRecordID:recordID action:action]) { 64 | // noop 65 | } 66 | return self; 67 | } 68 | 69 | #pragma mark - NSCopying 70 | 71 | - (id)copyWithZone:(NSZone *)zone { 72 | return [[[self class] allocWithZone:zone] initWithRecordID:[_recordID copyWithZone:zone] action:_referenceAction]; 73 | } 74 | 75 | #pragma mark - Description 76 | 77 | - (NSString *)description { 78 | return [NSString stringWithFormat:@"[CKReference: %lu %@]", self.referenceAction, self.recordID]; 79 | } 80 | 81 | @end 82 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKReference_Private.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKReference_Private.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import 9 | #import "CKRecord.h" 10 | 11 | @interface CKReference (Private) 12 | 13 | - (instancetype)initWithDictionary:(NSDictionary *)dictionary inZone:(CKRecordZoneID *)zoneID; 14 | 15 | - (void)updateWithDictionary:(NSDictionary *)dictionary; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKServerChangeToken+AgileDictionary.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKServerChangeToken+AgileDictionary.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import "CKServerChangeToken.h" 9 | 10 | @interface CKServerChangeToken (AgileDictionary) 11 | 12 | - (NSString *)asString; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKServerChangeToken+AgileDictionary.m: -------------------------------------------------------------------------------- 1 | // 2 | // CKServerChangeToken+AgileDictionary.m 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import "CKServerChangeToken_Private.h" 9 | #import "CKServerChangeToken+AgileDictionary.h" 10 | 11 | @implementation CKServerChangeToken (AgileDictionary) 12 | 13 | - (NSString *)asString { 14 | return self.token; 15 | } 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKServerChangeToken.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKServerChangeToken.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #import "CKDatabaseOperation.h" 9 | 10 | @interface CKServerChangeToken : NSObject 11 | 12 | - (instancetype)init NS_UNAVAILABLE; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKServerChangeToken.m: -------------------------------------------------------------------------------- 1 | // 2 | // CKServerChangeToken.m 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #import "CKDatabaseOperation.h" 9 | #import "CKServerChangeToken.h" 10 | #import "CKServerChangeToken_Private.h" 11 | #import "CKServerChangeToken+AgileDictionary.h" 12 | 13 | @implementation CKServerChangeToken { 14 | NSString *_token; 15 | } 16 | 17 | - (instancetype)initWithString:(NSString *)token { 18 | if (self = [super init]) { 19 | _token = token; 20 | } 21 | return self; 22 | } 23 | 24 | - (NSString *)token { 25 | return _token; 26 | } 27 | 28 | #pragma mark - NSCoding 29 | 30 | + (BOOL)supportsSecureCoding { 31 | return YES; 32 | } 33 | 34 | - (void)encodeWithCoder:(NSCoder *)aCoder { 35 | [aCoder encodeObject:self.token forKey:@"token"]; 36 | } 37 | 38 | - (instancetype)initWithCoder:(NSCoder *)aDecoder { 39 | NSString *token = [aDecoder decodeObjectOfClass:[NSString class] forKey:@"token"]; 40 | if (self = [self initWithString:token]) { 41 | // noop 42 | } 43 | return self; 44 | } 45 | 46 | #pragma mark - NSCopying 47 | 48 | - (id)copyWithZone:(NSZone *)zone { 49 | return [[[self class] allocWithZone:zone] initWithString:[_token copyWithZone:zone]]; 50 | } 51 | 52 | #pragma mark - Description 53 | 54 | - (NSString *)description { 55 | return [NSString stringWithFormat:@"[CKServerChangeToken: %@]", self.token]; 56 | } 57 | 58 | @end 59 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKServerChangeToken_Private.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKServerChangeToken_Private.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import 9 | #import "CKDatabaseOperation.h" 10 | #import "CKServerChangeToken.h" 11 | 12 | @interface CKServerChangeToken (AgilePrivate) 13 | 14 | - (instancetype)initWithString:(NSString *)token; 15 | 16 | @property(nonatomic, readonly) NSString *token; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKSubscription+AgileDictionary.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKSubscription+AgileDictionary.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import "CKSubscription.h" 9 | 10 | @interface CKSubscription (AgileDictionary) 11 | 12 | - (NSDictionary *)asAgileDictionary; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKSubscription+AgileDictionary.m: -------------------------------------------------------------------------------- 1 | // 2 | // CKSubscription+AgileDictionary.m 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import "CKSubscription.h" 9 | #import "CKSubscription+AgileDictionary.h" 10 | #import "CKRecordZoneID+AgileDictionary.h" 11 | #import "CKSubscription_Private.h" 12 | #import "NSArray+AgileMap.h" 13 | #import "CKNotificationInfo+AgileDictionary.h" 14 | 15 | @implementation CKSubscription (AgileDictionary) 16 | 17 | - (NSDictionary *)asAgileDictionary { 18 | NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; 19 | [dictionary setObject:self.subscriptionID forKey:@"subscriptionID"]; 20 | [dictionary setObject:self.subscriptionType == CKSubscriptionTypeQuery ? @"query" : @"zone" forKey:@"subscriptionType"]; 21 | if (self.zoneID) { 22 | [dictionary setObject:[self.zoneID asAgileDictionary] forKey:@"zoneID"]; 23 | } 24 | if (self.recordType) { 25 | [dictionary setObject:@{ @"recordType": self.recordType, 26 | @"filterBy": [self.filters agile_mapUsingSelector:@selector(asAgileDictionary)], 27 | @"sortBy": @[] } 28 | forKey:@"query"]; 29 | } 30 | if (self.subscriptionOptions) { 31 | NSMutableArray *opts = [NSMutableArray array]; 32 | if (self.subscriptionOptions & CKSubscriptionOptionsFiresOnRecordCreation) { 33 | [opts addObject:@"create"]; 34 | } 35 | if (self.subscriptionOptions & CKSubscriptionOptionsFiresOnRecordUpdate) { 36 | [opts addObject:@"update"]; 37 | } 38 | if (self.subscriptionOptions & CKSubscriptionOptionsFiresOnRecordDeletion) { 39 | [opts addObject:@"delete"]; 40 | } 41 | [dictionary setObject:opts forKey:@"firesOn"]; 42 | } 43 | if (self.subscriptionOptions & CKSubscriptionOptionsFiresOnce) { 44 | [dictionary setObject:@(YES) forKey:@"firesOnce"]; 45 | } 46 | else { 47 | [dictionary setObject:@(NO) forKey:@"firesOnce"]; 48 | } 49 | if (self.notificationInfo) { 50 | [dictionary setObject:[self.notificationInfo asAgileDictionary] 51 | forKey:@"notificationInfo"]; 52 | } 53 | else { 54 | [dictionary setObject:@{} 55 | forKey:@"notificationInfo"]; 56 | } 57 | if (self.subscriptionType == CKSubscriptionTypeRecordZone) { 58 | [dictionary setObject:@(YES) forKey:@"zoneWide"]; 59 | } 60 | return dictionary; 61 | } 62 | 63 | @end 64 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKSubscription.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKSubscription.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #import 9 | #import "CKDefines.h" 10 | 11 | typedef NS_ENUM(NSInteger, CKSubscriptionType) { 12 | CKSubscriptionTypeQuery = 1, 13 | CKSubscriptionTypeRecordZone = 2, 14 | } NS_ENUM_AVAILABLE(10_10, 8_0); 15 | 16 | typedef NS_OPTIONS(NSUInteger, CKSubscriptionOptions) { 17 | CKSubscriptionOptionsFiresOnRecordCreation = 1 << 0, // Applies to CKSubscriptionTypeQuery 18 | CKSubscriptionOptionsFiresOnRecordUpdate = 1 << 1, // Applies to CKSubscriptionTypeQuery 19 | CKSubscriptionOptionsFiresOnRecordDeletion = 1 << 2, // Applies to CKSubscriptionTypeQuery 20 | CKSubscriptionOptionsFiresOnce = 1 << 3, // Applies to CKSubscriptionTypeQuery 21 | } NS_ENUM_AVAILABLE(10_10, 8_0); 22 | 23 | @class CKNotificationInfo, CKRecordZoneID; 24 | 25 | @interface CKSubscription : NSObject 26 | 27 | - (instancetype)init NS_UNAVAILABLE; 28 | - (instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER; 29 | 30 | - (instancetype)initWithRecordType:(NSString *)recordType predicate:(NSPredicate *)predicate options:(CKSubscriptionOptions)subscriptionOptions /* NS_UNAVAILABLE */; 31 | 32 | - (instancetype)initWithRecordType:(NSString *)recordType predicate:(NSPredicate *)predicate subscriptionID:(NSString *)subscriptionID options:(CKSubscriptionOptions)subscriptionOptions NS_DESIGNATED_INITIALIZER /* NS_UNAVAILABLE */; 33 | 34 | - (instancetype)initWithRecordType:(NSString *)recordType filters:(NSArray *)filters options:(CKSubscriptionOptions)subscriptionOptions; 35 | 36 | - (instancetype)initWithRecordType:(NSString *)recordType filters:(NSArray *)filters subscriptionID:(NSString *)subscriptionID options:(CKSubscriptionOptions)subscriptionOptions NS_DESIGNATED_INITIALIZER; 37 | 38 | /* This subscription fires whenever any change happens in the indicated RecordZone. 39 | The RecordZone must have the capability CKRecordZoneCapabilityFetchChanges */ 40 | - (instancetype)initWithZoneID:(CKRecordZoneID *)zoneID options:(CKSubscriptionOptions)subscriptionOptions; 41 | - (instancetype)initWithZoneID:(CKRecordZoneID *)zoneID subscriptionID:(NSString *)subscriptionID options:(CKSubscriptionOptions)subscriptionOptions NS_DESIGNATED_INITIALIZER; 42 | 43 | @property(nonatomic, readonly, copy) NSString *subscriptionID; 44 | 45 | @property(nonatomic, readonly, assign) CKSubscriptionType subscriptionType; 46 | 47 | /* The record type that this subscription watches. This property is only used by query subscriptions, and must be set. */ 48 | @property(nonatomic, readonly, copy) NSString *recordType; 49 | 50 | @property(nonatomic, readonly) NSArray *filters; 51 | 52 | /* Options flags describing the firing behavior subscription. For query subscriptions, one of CKSubscriptionOptionsFiresOnRecordCreation, CKSubscriptionOptionsFiresOnRecordUpdate, or CKSubscriptionOptionsFiresOnRecordDeletion must be specified or an NSInvalidArgumentException will be thrown. */ 53 | @property(nonatomic, readonly, assign) CKSubscriptionOptions subscriptionOptions; 54 | 55 | /* Optional property describing the notification that will be sent when the subscription fires. */ 56 | @property(nonatomic, copy) CKNotificationInfo *notificationInfo; 57 | 58 | /* Query subscriptions: Optional property. If set, a query subscription is scoped to only record changes in the indicated zone. 59 | RecordZone subscriptions: */ 60 | @property(nonatomic, copy) CKRecordZoneID *zoneID; 61 | 62 | @end 63 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKSubscription.m: -------------------------------------------------------------------------------- 1 | // 2 | // CKSubscription.m 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #import 9 | #import "CKSubscription.h" 10 | #import "CKSubscription+AgileDictionary.h" 11 | #import "CKRecordZoneID_Private.h" 12 | #import "CKSubscription_Private.h" 13 | #import "NSArray+AgileMap.h" 14 | #import "CKFilter.h" 15 | #import "CKFilter_Private.h" 16 | #import "CKNotificationInfo_Private.h" 17 | #import "Defines.h" 18 | 19 | @implementation CKSubscription 20 | 21 | - (instancetype)initWithRecordType:(NSString *)recordType filters:(NSArray *)filters options:(CKSubscriptionOptions)subscriptionOptions { 22 | return [self initWithRecordType:recordType filters:filters subscriptionID:[[NSUUID UUID] UUIDString] options:subscriptionOptions]; 23 | } 24 | 25 | - (instancetype)initWithRecordType:(NSString *)recordType filters:(NSArray *)filters subscriptionID:(NSString *)subscriptionID options:(CKSubscriptionOptions)subscriptionOptions { 26 | if (self = [super init]) { 27 | _subscriptionID = subscriptionID; 28 | _subscriptionType = CKSubscriptionTypeQuery; 29 | _subscriptionOptions = subscriptionOptions; 30 | _recordType = recordType; 31 | _filters = [filters copy]; 32 | } 33 | return self; 34 | } 35 | 36 | 37 | - (instancetype)initWithRecordType:(NSString *)recordType predicate:(NSPredicate *)predicate options:(CKSubscriptionOptions)subscriptionOptions { 38 | return [self initWithRecordType:recordType predicate:predicate subscriptionID:[[NSUUID UUID] UUIDString] options:subscriptionOptions]; 39 | } 40 | 41 | - (instancetype)initWithRecordType:(NSString *)recordType predicate:(NSPredicate *)predicate subscriptionID:(NSString *)subscriptionID options:(CKSubscriptionOptions)subscriptionOptions { 42 | if (self = [super init]) { 43 | _subscriptionID = subscriptionID; 44 | _subscriptionType = CKSubscriptionTypeQuery; 45 | _subscriptionOptions = subscriptionOptions; 46 | _recordType = recordType; 47 | 48 | _filters = @[]; 49 | } 50 | return self; 51 | } 52 | 53 | /* This subscription fires whenever any change happens in the indicated RecordZone. 54 | The RecordZone must have the capability CKRecordZoneCapabilityFetchChanges */ 55 | - (instancetype)initWithZoneID:(CKRecordZoneID *)zoneID options:(CKSubscriptionOptions)subscriptionOptions { 56 | return [self initWithZoneID:zoneID subscriptionID:[[NSUUID UUID] UUIDString] options:subscriptionOptions]; 57 | } 58 | 59 | - (instancetype)initWithZoneID:(CKRecordZoneID *)zoneID subscriptionID:(NSString *)subscriptionID options:(CKSubscriptionOptions)subscriptionOptions { 60 | if (self = [super init]) { 61 | _zoneID = zoneID; 62 | _subscriptionID = subscriptionID; 63 | _subscriptionOptions = subscriptionOptions; 64 | _subscriptionType = CKSubscriptionTypeRecordZone; 65 | } 66 | return self; 67 | } 68 | 69 | 70 | - (instancetype)initWithDictionary:(NSDictionary *)dictionary { 71 | if (self = [super init]) { 72 | [self updateWithDictionary:dictionary]; 73 | } 74 | return self; 75 | } 76 | 77 | - (void)updateWithDictionary:(NSDictionary *)dictionary { 78 | _subscriptionType = [dictionary[@"subscriptionType"] isEqualToString:@"query"] ? CKSubscriptionTypeQuery : CKSubscriptionTypeRecordZone; 79 | _notificationInfo = [[CKNotificationInfo alloc] initWithDictionary:dictionary[@"notificationInfo"]]; 80 | _subscriptionID = dictionary[@"subscriptionID"]; 81 | _subscriptionOptions = [dictionary[@"firesOn"] containsObject:@"create"] ? CKSubscriptionOptionsFiresOnRecordCreation : 0; 82 | _subscriptionOptions |= [dictionary[@"firesOn"] containsObject:@"update"] ? CKSubscriptionOptionsFiresOnRecordUpdate : 0; 83 | _subscriptionOptions |= [dictionary[@"firesOn"] containsObject:@"delete"] ? CKSubscriptionOptionsFiresOnRecordDeletion : 0; 84 | 85 | if (_subscriptionType == CKSubscriptionTypeQuery) { 86 | _recordType = dictionary[@"query"][@"recordType"]; 87 | _zoneID = [[CKRecordZoneID alloc] initWithDictionary:[dictionary objectForKey:@"zoneID"]]; 88 | _filters = [dictionary[@"query"][@"filterBy"] agile_mapUsingBlock:^id(id obj, NSUInteger idx) { 89 | return [[CKFilter alloc] initWithDictionary:obj inZone:_zoneID]; 90 | }]; 91 | } 92 | else { 93 | _zoneID = [[CKRecordZoneID alloc] initWithDictionary:[dictionary objectForKey:@"zoneID"]]; 94 | } 95 | } 96 | 97 | #pragma mark - NSCoding 98 | 99 | + (BOOL)supportsSecureCoding { 100 | return YES; 101 | } 102 | 103 | - (void)encodeWithCoder:(NSCoder *)aCoder { 104 | [aCoder encodeObject:@(_subscriptionType) forKey:@"subscriptionType"]; 105 | [aCoder encodeObject:_subscriptionID forKey:@"subscriptionID"]; 106 | [aCoder encodeObject:@(_subscriptionOptions) forKey:@"subscriptionOptions"]; 107 | 108 | if (self.subscriptionType == CKSubscriptionTypeQuery) { 109 | [aCoder encodeObject:_recordType forKey:@"recordType"]; 110 | [aCoder encodeObject:_filters forKey:@"filters"]; 111 | } 112 | else { 113 | [aCoder encodeObject:_zoneID forKey:@"zoneID"]; 114 | } 115 | [aCoder encodeObject:_notificationInfo forKey:@"notificationInfo"]; 116 | } 117 | 118 | - (instancetype)initWithCoder:(NSCoder *)aDecoder { 119 | if (self = [super init]) { 120 | _subscriptionType = [aDecoder decodeIntegerForKey:@"subscriptionType"]; 121 | _notificationInfo = [aDecoder decodeObjectOfClass:[CKNotificationInfo class] forKey:@"notificationInfo"]; 122 | _subscriptionID = [aDecoder decodeObjectOfClass:[NSString class] forKey:@"subscriptionID"]; 123 | _subscriptionOptions = [aDecoder decodeIntegerForKey:@"subscriptionOptions"]; 124 | if (_subscriptionType == CKSubscriptionTypeQuery) { 125 | _recordType = [aDecoder decodeObjectOfClass:[NSString class] forKey:@"recordType"]; 126 | _filters = [aDecoder decodeObjectOfClass:[NSArray class] forKey:@"filters"]; 127 | } 128 | else { 129 | _zoneID = [aDecoder decodeObjectOfClass:[CKRecordZoneID class] forKey:@"zoneID"]; 130 | } 131 | } 132 | return self; 133 | } 134 | 135 | #pragma mark - NSCopying 136 | 137 | - (instancetype)copyWithZone:(NSZone *)zone { 138 | return [[[self class] allocWithZone:zone] initWithDictionary:[self asAgileDictionary]]; 139 | } 140 | 141 | #pragma mark - Description 142 | 143 | - (NSString *)description { 144 | return [NSString stringWithFormat:@"[CKSubscription: %@]", self.subscriptionID]; 145 | } 146 | 147 | 148 | @end 149 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CKSubscription_Private.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKSubscription_Private.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import 9 | #import "CKDatabaseOperation.h" 10 | #import "CKServerChangeToken.h" 11 | 12 | @interface CKSubscription () 13 | 14 | - (instancetype)initWithDictionary:(NSDictionary *)dictionary NS_DESIGNATED_INITIALIZER; 15 | 16 | - (void)updateWithDictionary:(NSDictionary *)dictionary; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CLLocation+AgileDictionary.h: -------------------------------------------------------------------------------- 1 | // 2 | // CLLocation+AgileDictionary.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | @interface CLLocation (AgileDictionary) 11 | 12 | - (NSDictionary *)asAgileDictionary; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CLLocation+AgileDictionary.m: -------------------------------------------------------------------------------- 1 | // 2 | // CLLocation+AgileDictionary.m 3 | // AgileCloudSDK 4 | // 5 | 6 | // Copyright (c) 2015 AgileBits. All rights reserved. 7 | // 8 | 9 | #import "CLLocation+AgileDictionary.h" 10 | 11 | @implementation CLLocation (AgileDictionary) 12 | 13 | - (NSDictionary *)asAgileDictionary 14 | { 15 | return @{ @"latitude": @(self.coordinate.latitude), 16 | @"longitude": @(self.coordinate.longitude) }; 17 | } 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/CLLocation+CKFilterType.h: -------------------------------------------------------------------------------- 1 | // 2 | // CLLocation+CKFilterType.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | @interface CLLocation () 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/Defines.h: -------------------------------------------------------------------------------- 1 | // 2 | // Defines.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #ifndef Defines_h 9 | #define Defines_h 10 | 11 | #define kUndefinedMethodException [NSException exceptionWithName:@"UndefinedMethodException" reason:@"Method not yet defined" userInfo:nil] 12 | #define kAbstractMethodException [NSException exceptionWithName:@"AbstractMethodException" reason:@"Must implement method in subclass" userInfo:nil] 13 | #define kInvalidMethodException [NSException exceptionWithName:@"InvalidMethodException" reason:@"This method cannot be called outside the class" userInfo:nil] 14 | 15 | //#define DebugLog(__FORMAT__, ...) NSLog(__FORMAT__, ##__VA_ARGS__) 16 | #define DebugLog(level, __FORMAT__, ...) if ([[CKMediator sharedMediator].delegate respondsToSelector:@selector(mediator:logLevel:object:at:format:)]) [[CKMediator sharedMediator].delegate mediator:[CKMediator sharedMediator] logLevel:level object:self at:_cmd format:__FORMAT__, ##__VA_ARGS__] 17 | 18 | #endif /* Defines_h */ 19 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSHumanReadableCopyright 24 | Copyright © 2015 AgileBits. All rights reserved. 25 | NSPrincipalClass 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/JSValue+AgileCloudSDKExtensions.h: -------------------------------------------------------------------------------- 1 | // 2 | // JSValue+AgileCloudSDKExtensions.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | @interface JSValue (AgileCloudSDKExtensions) 11 | 12 | - (JSValue *)agile_invokeMethod:(NSString *)method; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/JSValue+AgileCloudSDKExtensions.m: -------------------------------------------------------------------------------- 1 | // 2 | // JSValue+AgileCloudSDKExtensions.m 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import "JSValue+AgileCloudSDKExtensions.h" 9 | 10 | @implementation JSValue (AgileCloudSDKExtensions) 11 | 12 | - (JSValue *)agile_invokeMethod:(NSString *)method 13 | { 14 | return [self invokeMethod:method withArguments:@[]]; 15 | } 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/NSApplication+AgileCloudSDK.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSApplication+AgileCloudSDK.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | @interface NSApplication (AgileCloudSDK) 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/NSApplication+AgileCloudSDK.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSApplication+AgileCloudSDK.m 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import "NSApplication+AgileCloudSDK.h" 9 | #import "CKMediator.h" 10 | #import "CKMediator_Private.h" 11 | #import 12 | #import "Defines.h" 13 | 14 | @implementation NSApplication (AgileCloudSDK) 15 | 16 | - (void)_agilecloudsdk_swizzle_registerForRemoteNotificationTypes:(NSRemoteNotificationType)types 17 | { 18 | [[CKMediator sharedMediator] registerForRemoteNotifications]; 19 | } 20 | 21 | - (void)_agilecloudsdk_swizzle_unregisterForRemoteNotifications 22 | { 23 | DebugLog(CKLOG_LEVEL_DEBUG, @"unregisterForRemoteNotifications"); 24 | } 25 | 26 | 27 | - (NSRemoteNotificationType)_agilecloudsdk_swizzle_enabledRemoteNotificationTypes 28 | { 29 | return NSRemoteNotificationTypeNone; 30 | } 31 | 32 | 33 | + (void)load 34 | { 35 | // registerForRemoteNotificationTypes: 36 | SEL originalSelector = @selector(registerForRemoteNotificationTypes:); 37 | SEL newSelector = @selector(_agilecloudsdk_swizzle_registerForRemoteNotificationTypes:); 38 | Method originalMethod = class_getInstanceMethod(self, originalSelector); 39 | Method newMethod = class_getInstanceMethod(self, newSelector); 40 | 41 | method_exchangeImplementations(originalMethod, newMethod); 42 | 43 | // unregisterForRemoteNotifications 44 | originalSelector = @selector(unregisterForRemoteNotifications); 45 | newSelector = @selector(_agilecloudsdk_swizzle_unregisterForRemoteNotifications); 46 | originalMethod = class_getInstanceMethod(self, originalSelector); 47 | newMethod = class_getInstanceMethod(self, newSelector); 48 | 49 | method_exchangeImplementations(originalMethod, newMethod); 50 | 51 | // enabledRemoteNotificationTypes 52 | originalSelector = @selector(enabledRemoteNotificationTypes); 53 | newSelector = @selector(_agilecloudsdk_swizzle_enabledRemoteNotificationTypes); 54 | originalMethod = class_getInstanceMethod(self, originalSelector); 55 | newMethod = class_getInstanceMethod(self, newSelector); 56 | 57 | method_exchangeImplementations(originalMethod, newMethod); 58 | } 59 | 60 | @end 61 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/NSArray+AgileMap.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSArray+AgileMap.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | @interface NSArray (AgileMap) 11 | 12 | - (NSArray *)agile_mapUsingBlock:(id (^)(id obj, NSUInteger idx))block; 13 | 14 | - (NSArray *)agile_mapUsingSelector:(SEL)selector; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/NSArray+AgileMap.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSArray+AgileMap.m 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import "NSArray+AgileMap.h" 9 | 10 | @implementation NSArray (AgileMap) 11 | 12 | - (NSArray *)agile_mapUsingBlock:(id (^)(id obj, NSUInteger idx))block 13 | { 14 | NSMutableArray *result = [NSMutableArray arrayWithCapacity:[self count]]; 15 | 16 | [self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { 17 | id mappedObj = block(obj, idx); 18 | if(mappedObj){ 19 | [result addObject:mappedObj]; 20 | } 21 | }]; 22 | 23 | return result; 24 | } 25 | 26 | - (NSArray *)agile_mapUsingSelector:(SEL)selector 27 | { 28 | NSMutableArray *result = [NSMutableArray arrayWithCapacity:[self count]]; 29 | 30 | [self enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) { 31 | #pragma clang diagnostic push 32 | #pragma clang diagnostic ignored "-Warc-performSelector-leaks" 33 | [result addObject:[obj performSelector:selector]]; 34 | #pragma clang diagnostic pop 35 | }]; 36 | 37 | return result; 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/NSArray+CKFilterType.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSArray+CKFilterType.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import 9 | #import "CKFilterType.h" 10 | 11 | @interface NSArray () 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/NSDate+CKFilterType.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSDate+CKFilterType.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | @interface NSDate () 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/NSError+AgileCloudSDKExtensions.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSError+AgileCloudSDKExtensions.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | @interface NSError (AgileCloudSDKExtensions) 11 | 12 | - (instancetype)initWithCKErrorDictionary:(NSDictionary *)ckErrorDictionary; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/NSError+AgileCloudSDKExtensions.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSError+AgileCloudSDKExtensions.m 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import "NSError+AgileCloudSDKExtensions.h" 9 | #import "CKError.h" 10 | 11 | @implementation NSError (AgileCloudSDKExtensions) 12 | 13 | - (instancetype)initWithCKErrorDictionary:(NSDictionary *)ckErrorDictionary 14 | { 15 | if (ckErrorDictionary[@"serverErrorCode"]) { 16 | NSMutableDictionary *errorDict = [NSMutableDictionary dictionaryWithDictionary:ckErrorDictionary]; 17 | errorDict[@"_ckErrorCode"] = errorDict[@"serverErrorCode"]; 18 | ckErrorDictionary = errorDict; 19 | } 20 | 21 | CKErrorCode code = CKErrorInternalError; 22 | if ([ckErrorDictionary[@"_ckErrorCode"] isEqualToString:@"ACCESS_DENIED"]) { 23 | code = CKErrorPermissionFailure; 24 | } else if ([ckErrorDictionary[@"_ckErrorCode"] isEqualToString:@"ATOMIC_ERROR"]) { 25 | code = CKErrorBatchRequestFailed; 26 | } else if ([ckErrorDictionary[@"_ckErrorCode"] isEqualToString:@"AUTHENTICATION_FAILED"]) { 27 | code = CKErrorNotAuthenticated; 28 | } else if ([ckErrorDictionary[@"_ckErrorCode"] isEqualToString:@"AUTHENTICATION_REQUIRED"]) { 29 | code = CKErrorNotAuthenticated; 30 | } else if ([ckErrorDictionary[@"_ckErrorCode"] isEqualToString:@"BAD_REQUEST"]) { 31 | code = CKErrorUnknownItem; 32 | } else if ([ckErrorDictionary[@"_ckErrorCode"] isEqualToString:@"CONFLICT"]) { 33 | code = CKErrorConstraintViolation; 34 | } else if ([ckErrorDictionary[@"_ckErrorCode"] isEqualToString:@"EXISTS"]) { 35 | code = CKErrorServerRejectedRequest; 36 | } else if ([ckErrorDictionary[@"_ckErrorCode"] isEqualToString:@"INTERNAL_ERROR"]) { 37 | code = CKErrorInternalError; 38 | } else if ([ckErrorDictionary[@"_ckErrorCode"] isEqualToString:@"NOT_FOUND"]) { 39 | code = CKErrorUnknownItem; 40 | } else if ([ckErrorDictionary[@"_ckErrorCode"] isEqualToString:@"QUOTA_EXCEEDED"]) { 41 | code = CKErrorQuotaExceeded; 42 | } else if ([ckErrorDictionary[@"_ckErrorCode"] isEqualToString:@"THROTTLED"]) { 43 | code = CKErrorRequestRateLimited; 44 | } else if ([ckErrorDictionary[@"_ckErrorCode"] isEqualToString:@"TRY_AGAIN_LATER"]) { 45 | code = CKErrorZoneBusy; 46 | } else if ([ckErrorDictionary[@"_ckErrorCode"] isEqualToString:@"VALIDATING_REFERENCE_ERROR"]) { 47 | code = CKErrorConstraintViolation; 48 | } else if ([ckErrorDictionary[@"_ckErrorCode"] isEqualToString:@"UNIQUE_FIELD_ERROR"]) { 49 | code = CKErrorConstraintViolation; 50 | } else if ([ckErrorDictionary[@"_ckErrorCode"] isEqualToString:@"ZONE_NOT_FOUND"]) { 51 | code = CKErrorZoneNotFound; 52 | } else if ([ckErrorDictionary[@"_ckErrorCode"] isEqualToString:@"UNKNOWN_ERROR"]) { 53 | code = CKErrorInternalError; 54 | } else if ([ckErrorDictionary[@"_ckErrorCode"] isEqualToString:@"NETWORK_ERROR"]) { 55 | code = CKErrorNetworkFailure; 56 | } else if ([ckErrorDictionary[@"_ckErrorCode"] isEqualToString:@"SERVICE_UNAVAILABLE"]) { 57 | code = CKErrorServiceUnavailable; 58 | } else if ([ckErrorDictionary[@"_ckErrorCode"] isEqualToString:@"INVALID_ARGUMENTS"]) { 59 | code = CKErrorInvalidArguments; 60 | } 61 | if (self = [self initWithDomain:CKErrorDomain code:code userInfo:ckErrorDictionary]) { 62 | } 63 | return self; 64 | } 65 | 66 | @end 67 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/NSNumber+CKFilterType.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSNumber+CKFilterType.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | @interface NSNumber () 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/NSString+CKFilterType.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+CKFilterType.h 3 | // AgileCloudSDK 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | @interface NSString () 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/config-format.js: -------------------------------------------------------------------------------- 1 | CloudKit.configure({ 2 | containers: [%@], 3 | services: { 4 | authTokenStore: { 5 | putToken: function(cid,tok){ return window.putTokenBlock(cid, tok); }, 6 | getToken: function(cid){ return window.getTokenBlock(cid); } 7 | }, 8 | logger: { 9 | info: function(str){ window.doLog(str); }, 10 | log: function(str){ window.doLog(str); }, 11 | warn: function(str){ window.doLog(str); }, 12 | error: function(str){ window.doLog(str); }, 13 | } 14 | }, 15 | 16 | }); -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/container-config-format.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | // Change this to a container identifier you own. 4 | containerIdentifier: '%@', 5 | 6 | // And generate an API token through CloudKit Dashboard. 7 | apiToken: '%@', 8 | 9 | auth: { 10 | buttonSize: 'medium', 11 | persist: true // Sets a cookie. 12 | }, 13 | environment: '%@' 14 | } -------------------------------------------------------------------------------- /AgileCloudSDK/AgileCloudSDK/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CloudSDK Test 6 | 7 | 8 |
9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /CloudZone/CloudZone.xcodeproj/xcuserdata/kevin.xcuserdatad/xcschemes/AgileCloudZone.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /CloudZone/CloudZone.xcodeproj/xcuserdata/kevin.xcuserdatad/xcschemes/CloudZone.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /CloudZone/CloudZone.xcodeproj/xcuserdata/kevin.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | AgileCloudZone.xcscheme 8 | 9 | orderHint 10 | 1 11 | 12 | CloudZone.xcscheme 13 | 14 | orderHint 15 | 0 16 | 17 | 18 | SuppressBuildableAutocreation 19 | 20 | C5A573371B890E7700264AC9 21 | 22 | primary 23 | 24 | 25 | C5E14DB61B89012B003DE11F 26 | 27 | primary 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /CloudZone/CloudZone/AgileCloudSDKView.h: -------------------------------------------------------------------------------- 1 | // 2 | // AgileCloudSDKView.h 3 | // CloudZone 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import 9 | #import "CloudSDKView.h" 10 | 11 | @interface AgileCloudSDKView : CloudSDKView 12 | 13 | @property (nonatomic, strong) NSButton *logoutButton; 14 | @property (nonatomic, strong) NSButton *loginButton; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /CloudZone/CloudZone/AgileCloudSDKView.m: -------------------------------------------------------------------------------- 1 | // 2 | // AgileCloudSDKView 3 | // CloudZone 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import "AgileCloudSDKView.h" 9 | #import 10 | 11 | @interface AgileCloudSDKView () 12 | 13 | @end 14 | 15 | @implementation AgileCloudSDKView 16 | 17 | - (instancetype)initWithFrame:(NSRect)frameRect 18 | { 19 | if (self = [super initWithFrame:frameRect]) { 20 | _logoutButton = [[NSButton alloc] initWithFrame:NSMakeRect(self.bounds.size.width - 120, 10, 100, 20)]; 21 | [_logoutButton setButtonType:NSMomentaryPushInButton]; 22 | [_logoutButton setTitle:@"Log out"]; 23 | [_logoutButton setAutoresizingMask:NSViewMinXMargin | NSViewMaxYMargin]; 24 | [_logoutButton setHidden:YES]; 25 | [self addSubview:_logoutButton]; 26 | 27 | _loginButton = [[NSButton alloc] initWithFrame:NSMakeRect(self.bounds.size.width - 120, 10, 100, 20)]; 28 | [_loginButton setButtonType:NSMomentaryPushInButton]; 29 | [_loginButton setTitle:@"Log in"]; 30 | [_loginButton setAutoresizingMask:NSViewMinXMargin | NSViewMaxYMargin]; 31 | [_loginButton setHidden:YES]; 32 | [self addSubview:_loginButton]; 33 | 34 | } 35 | return self; 36 | } 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /CloudZone/CloudZone/AgileCloudZone-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleURLTypes 24 | 25 | 26 | CFBundleTypeRole 27 | Editor 28 | CFBundleURLName 29 | 30 | CFBundleURLSchemes 31 | 32 | cloudkit-icloud.com.companyname.cloudzone 33 | 34 | 35 | 36 | CFBundleVersion 37 | 1 38 | CloudContainers 39 | 40 | 41 | CloudAPIToken 42 | apitokenfromcloudkitdashboard 43 | CloudContainerName 44 | iCloud.com.companyname.CloudZone 45 | CloudEnvironment 46 | development 47 | 48 | 49 | LSApplicationCategoryType 50 | 51 | LSMinimumSystemVersion 52 | $(MACOSX_DEPLOYMENT_TARGET) 53 | NSAppTransportSecurity 54 | 55 | NSExceptionDomains 56 | 57 | icloud.com 58 | 59 | NSIncludesSubdomains 60 | 61 | NSThirdPartyExceptionMinimumTLSVersion 62 | TLSv1.0 63 | NSThirdPartyExceptionRequiresForwardSecrecy 64 | 65 | 66 | 67 | 68 | NSHumanReadableCopyright 69 | Copyright © 2015 AgileBits. All rights reserved. 70 | NSMainStoryboardFile 71 | Main 72 | NSPrincipalClass 73 | NSApplication 74 | 75 | 76 | -------------------------------------------------------------------------------- /CloudZone/CloudZone/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // CloudZone 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | @class ViewController; 11 | 12 | @interface AppDelegate : NSObject 13 | 14 | @property (nonatomic, strong) ViewController *viewController; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /CloudZone/CloudZone/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // CloudZone 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #import "AppDelegate.h" 9 | #import "ViewController.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { 18 | // Insert code here to initialize your application 19 | } 20 | 21 | - (void)applicationWillTerminate:(NSNotification *)aNotification { 22 | // Insert code here to tear down your application 23 | } 24 | 25 | - (void)application:(NSApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { 26 | NSLog(@"failed to register for remote notifications"); 27 | } 28 | 29 | - (void)application:(NSApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { 30 | NSLog(@"registered for remote notifications"); 31 | } 32 | 33 | - (void)application:(NSApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo { 34 | NSLog(@"received notification: %@. will load saved record", userInfo); 35 | [self.viewController loadSavedRecord]; 36 | } 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /CloudZone/CloudZone/CloudSDKView.h: -------------------------------------------------------------------------------- 1 | // 2 | // CloudSDKView.h 3 | // CloudZone 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | @interface CloudSDKView : NSView 11 | 12 | @property (nonatomic, strong) NSButton *subscribeButton; 13 | @property (nonatomic, strong) NSButton *startTestsButton; 14 | @property (nonatomic, strong) NSButton *saveRecordButton; 15 | @property (nonatomic, strong) NSTextField *recordTextField; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /CloudZone/CloudZone/CloudSDKView.m: -------------------------------------------------------------------------------- 1 | // 2 | // CloudSDKView.m 3 | // CloudZone 4 | // 5 | // Copyright (c) 2015 AgileBits. All rights reserved. 6 | // 7 | 8 | #import "CloudSDKView.h" 9 | #import "Constants.h" 10 | #import CloudSDKImport 11 | 12 | @implementation CloudSDKView 13 | 14 | - (BOOL)isFlipped 15 | { 16 | return YES; 17 | } 18 | 19 | - (instancetype)initWithFrame:(NSRect)frameRect 20 | { 21 | if (self = [super initWithFrame:frameRect]) { 22 | _startTestsButton = [[NSButton alloc] initWithFrame:NSMakeRect(self.bounds.size.width - 120, 40, 100, 20)]; 23 | [_startTestsButton setButtonType:NSMomentaryPushInButton]; 24 | [_startTestsButton setTitle:@"Start Tests"]; 25 | [_startTestsButton setAutoresizingMask:NSViewMinXMargin | NSViewMaxYMargin]; 26 | [self addSubview:_startTestsButton]; 27 | 28 | _subscribeButton = [[NSButton alloc] initWithFrame:NSMakeRect(self.bounds.size.width - 120, 70, 100, 20)]; 29 | [_subscribeButton setButtonType:NSMomentaryPushInButton]; 30 | [_subscribeButton setTitle:@"Subscribe"]; 31 | [_subscribeButton setAutoresizingMask:NSViewMinXMargin | NSViewMaxYMargin]; 32 | [self addSubview:_subscribeButton]; 33 | 34 | _saveRecordButton = [[NSButton alloc] initWithFrame:NSMakeRect(20, 70, 100, 20)]; 35 | [_saveRecordButton setButtonType:NSMomentaryPushInButton]; 36 | [_saveRecordButton setTitle:@"Save Record"]; 37 | [_saveRecordButton setAutoresizingMask:NSViewMinXMargin | NSViewMaxYMargin]; 38 | [self addSubview:_saveRecordButton]; 39 | 40 | _recordTextField = [[NSTextField alloc] initWithFrame:NSMakeRect(20, 40, 200, 22)]; 41 | [self addSubview:_recordTextField]; 42 | } 43 | 44 | return self; 45 | } 46 | 47 | @end 48 | -------------------------------------------------------------------------------- /CloudZone/CloudZone/CloudZone-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSApplicationCategoryType 26 | 27 | LSMinimumSystemVersion 28 | $(MACOSX_DEPLOYMENT_TARGET) 29 | NSHumanReadableCopyright 30 | Copyright © 2015 AgileBits. All rights reserved. 31 | NSMainStoryboardFile 32 | Main 33 | NSPrincipalClass 34 | NSApplication 35 | 36 | 37 | -------------------------------------------------------------------------------- /CloudZone/CloudZone/CloudZone.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.developer.icloud-container-identifiers 6 | 7 | iCloud.$(CFBundleIdentifier) 8 | 9 | com.apple.developer.icloud-services 10 | 11 | CloudKit 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /CloudZone/CloudZone/Constants.h: -------------------------------------------------------------------------------- 1 | // 2 | // Constants.h 3 | // CloudZone 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #ifndef CloudZone_Constants_h 9 | #define CloudZone_Constants_h 10 | 11 | #ifdef AGILECLOUDSDK 12 | #define CloudSDKImport 13 | #else 14 | #define CloudSDKImport 15 | #endif 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /CloudZone/CloudZone/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /CloudZone/CloudZone/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // CloudZone 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | @interface ViewController : NSViewController 11 | 12 | - (void)loadSavedRecord; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /CloudZone/CloudZone/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // CloudZone 4 | // 5 | // Copyright (c) 2015 AgileBits Inc. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | int main(int argc, const char *argv[]) 11 | { 12 | return NSApplicationMain(argc, argv); 13 | } 14 | -------------------------------------------------------------------------------- /CloudZone/CloudZone/palmtree.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agilebits/AgileCloudSDK/9193c4124e05c4443f9c323472468e2cae53bda9/CloudZone/CloudZone/palmtree.jpg -------------------------------------------------------------------------------- /CloudZone/CloudZone/sunset.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agilebits/AgileCloudSDK/9193c4124e05c4443f9c323472468e2cae53bda9/CloudZone/CloudZone/sunset.jpg -------------------------------------------------------------------------------- /License.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 AgileBits Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AgileCloudSDK 2 | 3 | ## Discontinued 4 | 5 | AgileCloudSDK is no longer being developed or maintained by AgileBits. No new additions or fixes will be made. 6 | 7 | ## Description 8 | 9 | AgileCloudSDK is a framework for use in non-Mac App Store apps. It uses CloudKitJS and CloudKit Web Services to communicate with iCloud. 10 | 11 | AgileCloudSDK is a proven technology currently in use by [1Password](https://1password.com) when purchased from the AgileBits Store. 1Password uses AgileCloudSDK to sync password data seamlessly with the Mac App Store and iOS App Store versions which use Apple’s native CloudKit framework. 12 | 13 | ## License 14 | 15 | See the [license file](License.txt) for AgileCloudSDK’s distribution license 16 | 17 | ## Using AgileCloudSDK 18 | 19 | See the file named [Using AgileCloudSDK](Using%20AgileCloudSDK.md) for information on how to set up AgileCloudSDK in your application. 20 | 21 | ## Still to do 22 | 23 | ### API 24 | 25 | AgileCloudSDK does not yet provide full functionality. There are a few classes that are not yet implemented. Of note are: 26 | 27 | - CKFetchSubscriptionsOperation 28 | - CKDiscoverAllContactsOperation 29 | - CKDiscoverUserInfosOperation 30 | - CKFetchWebAuthTokenOperation 31 | - CKLocationSortDescriptor 32 | - CKModifyBadgeOperation 33 | - CKQuery: this doesn’t have a direct equivalent in CloudKit JS. CloudKitJS uses filters: JSON dictionaries with query parameters. CKQuery uses NSPredicate which works quite differently from JSON query dictionaries. AgileCloudSDK contains the CKFilter class to provide query functionality but due to the different nature of the NSPredicates and CKFilters, making a direct implementation of CKOperations that use the CKQuery class would be difficult. 34 | - CKQueryOperation 35 | - CKQueryNotification 36 | 37 | Some classes do not implement every method as CloudKit, particularly those methods that have been added in OS X 10.11. 38 | 39 | ### JavaScript vs REST API 40 | 41 | AgileCloudSDK communicates with iCloud using a mix of JavaScript calls from a JavaScript context and the REST API. The long term goal is to move toward using the REST API. This has a number of advantages, one of which is eliminating the need for a Javascript context that runs on the main thread. 42 | 43 | ## CloudZone Sample Apps 44 | 45 | Two apps are bundled with the framework. The CloudZone app uses Apple's CloudKit framework, and the AgileCloudZone uses the AgileCloudSDK framework. The sample apps may not contain all functionality of the framework. They are there to act as a playground to test different aspects of the framework as needed. 46 | 47 | To use the CloudZone apps you will need to set up a test container in CloudKit Dashboard, and set the appropriate App IDs and credentials in the two apps. The CloudZone app requires the iCloud CloudKit capabilities enabled and must have Mac App Store code signing set in order to use native CloudKit. The AgileCloudZone app does not need to be signed as it uses CloudKitJS, but does need the appropriate CloudKitJS credentials and the URL type set in its Info.plist. There are placeholder values in the project that can be modified. 48 | 49 | The CloudZone apps are minimal and have a few rough edges, but have a few features: 50 | 51 | 1. Login/Logout - This button appears only in AgileCloudSDK. It's used for well, logging in and out. 52 | 2. Start Tests - Runs a suite of eight tests to test the various aspects of the AgileCloudSDK framework. 53 | 3. Subscribe - Activates the subscription, so it will receive notifications when data is changed on another device (or from the CloudZone to AgileCloudZone, and vice versa). 54 | 4. Save Record - Saves what ever text is in the text field to iCloud. When you first run one of the apps, it loads that record and populates the text field. Likewise, when a change notification comes in, that text field should update to the new value. 55 | -------------------------------------------------------------------------------- /Using AgileCloudSDK.md: -------------------------------------------------------------------------------- 1 | ## Using AgileCloudSDK 2 | 3 | AgileCloudSDK is for use with your Mac app that is not distributed through the Mac App Store. However, you must have a CloudKit container that is already set up for the App Store version of your app. 4 | 5 | #### Prepare your container on CloudKit Dashboard 6 | 7 | Log in to CloudKitDashboard with your Developer AppleID: https://icloud.developer.apple.com/dashboard 8 | 9 | 1. Go to your Development container 10 | 1. Create a new API key 11 | 1. Set the sign-in callback URL to launch your app with `cloudkit-containerID` as the scheme and `agilecklogin` as the host. For example, `cloudkit-icloud.com.company.appname://agilecsdklogin` 12 | 1. Repeat for your Production container 13 | 14 | #### Include the framework 15 | 16 | 1. Embed the AgileCloudSDK project in your project. If you do not wish to do this, build the project separately and add the framework to your project. 17 | 1. Link the AgileCloudSDK framework to the appropriate targets. 18 | 19 | #### Configure your application’s Info.plist file 20 | 21 | 1. Add a Dictionary to the array with the following key-value pairs: 22 | 1. Create an Array named `CloudContainers` 23 | - `CloudAPIToken`:`` This is the API token created in CloudKitDashboard 24 | - `CloudEnvironment`:`production` or `development` 25 | - `CloudContainerName`:your app's container id. e.g. `iCloud.com.company.app` 26 | 27 | #### Add code to your classes 28 | 29 | 1. Stand up the CKMediator. CKMediator is the class that mediates native calls to CloudKit JS. It must be instantiated and set up first. 30 | 31 | - the following code sets up the mediator and registers for notification types so your app receives notifications. 32 | ``` 33 | static dispatch_once_t onceToken; 34 | dispatch_once(&onceToken, ^{ 35 | [CKMediator sharedMediator].delegate = (id)NSApp.delegate; 36 | [NSApp registerForRemoteNotificationTypes:0xFFFF]; 37 | }); 38 | ``` 39 | 40 | 1. Implement the CKMediatorDelegate methods. These are defined in CKMediatorDelegate.h 41 | - you must implement the methods `-loadSessionTokenForMediator:` and `-mediator:saveSessionToken`, and it is recommended the token be stored securely. This allows your application to continue to communicate with CloudKit without needing to prompt the user to login each time. It can expire, for example, when the user changes their AppleID password, or for other reasons. 42 | - the optional logging method `mediator:logLevel:object:at:format:` allows your app to receive logging messages from AgileCloudSDK with varying levels of severity. Those levels are defined in CKMediatorDelegate.h 43 | 44 | 1. Handle the sign-in URL callback. 45 | 46 | - Register an event handle with the AppleEventManager to receive URLs if you are not already. For example, 47 | ``` 48 | [[NSAppleEventManager sharedAppleEventManager] setEventHandler:self andSelector:@selector(handleURLSchemeEvent:withReplyEvent:) forEventClass:kInternetEventClass andEventID:kAEGetURL]; 49 | ``` 50 | - In your handleURLSchemeEvent method, hand the cloudkit URL off to the shared mediator 51 | ``` 52 | [[CKMediator sharedMediator] handleGetURLEvent:event withReplyEvent:replyEvent]; 53 | ``` 54 | 55 | 1. In the appropriate classes, import the framework with the standard header: `#import ` 56 | 57 | 1. Logging in, logging out, and handling Authorization Errors 58 | 59 | - operations will call completion blocks with a CKError object with code `CKErrorNotAuthenticated` when the token is missing or invalid. You must call `[CKMediator sharedMediator] login]` when this happens. This will open a browser page displaying your App's name and icon, and prompt the user to log in to iCloud. Your app will receive a callback URL (the one you registered on CloudKit Dashboard) after the user successfully logs in via a web page and call the CKMediatorDelegate's saveToken method. 60 | - To invalidate your token and log out of CloudKit, call `[CKMediator sharedMediator] logout]`; 61 | 62 | --------------------------------------------------------------------------------