├── .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 |
--------------------------------------------------------------------------------