1/* 2 * Copyright (c) 2012-2014 Apple Inc. All Rights Reserved. 3 * 4 * @APPLE_LICENSE_HEADER_START@ 5 * 6 * This file contains Original Code and/or Modifications of Original Code 7 * as defined in and that are subject to the Apple Public Source License 8 * Version 2.0 (the 'License'). You may not use this file except in 9 * compliance with the License. Please obtain a copy of the License at 10 * http://www.opensource.apple.com/apsl/ and read it before using this 11 * file. 12 * 13 * The Original Code and all software distributed under the License are 14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER 15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, 16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, 17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. 18 * Please see the License for the specific language governing rights and 19 * limitations under the License. 20 * 21 * @APPLE_LICENSE_HEADER_END@ 22 */ 23 24// 25// CKDKeyValueStore.m 26// sec 27// 28 29#import "CKDKeyValueStore.h" 30#import "CKDPersistentState.h" 31 32/* 33 pseudo-code 34 35 g = [CKDKeyValueStore defaultStore:mystoreID]; 36 [g setObject:obj forKey:@"foo"]; 37 [g synchronize]; 38*/ 39 40#define DONTUSENOTIFICATIONS true 41 42static const int verboseCKDKVSDebugging = false; 43 44#ifndef NDEBUG 45 #define pdebug(format...) \ 46 do { \ 47 if (verboseCKDKVSDebugging) \ 48 NSLog(format); \ 49 } while (0) 50#else 51 //empty 52 #define pdebug(format...) 53#endif 54 55 56extern CFStringRef kCKDKVSRemoteStoreID; 57CFStringRef kCKDKVSRemoteStoreID = CFSTR("REMOTE"); 58CFStringRef kCKDAWSRemoteStoreID = CFSTR("AWS"); 59 60NSString * const kCKDKVSWhoChangedItemKey = @"WhoChangedItemKey"; 61 62static NSString * const ourNSUbiquitousKeyValueStoreDidChangeExternallyNotification = @"ourNSUbiquitousKeyValueStoreDidChangeExternallyNotification"; 63 64// MARK: ----- CKDKeyValueStore ----- 65 66@implementation CKDKeyValueStore 67 68- (id)initWithIdentifier:(NSString *)identifier itemsChangedBlock:(CloudItemsChangedBlock)itemsChangedBlock 69{ 70 if (self = [super init]) 71 { 72 self.delegate = self; 73 self.identifier = identifier; 74 self->localKVS = true; 75 76 // copy blocks onto heap 77 itemsChangedCallback = Block_copy(itemsChangedBlock); 78 79 [[NSNotificationCenter defaultCenter] addObserver: self 80 selector: @selector (iCloudAccountAvailabilityChanged:) 81 name: NSUbiquityIdentityDidChangeNotification 82 object: nil]; 83 84 [[NSNotificationCenter defaultCenter] addObserver:self 85 selector:@selector(cloudChanged:) 86 name:ourNSUbiquitousKeyValueStoreDidChangeExternallyNotification 87 object:nil]; 88 } 89 return self; 90} 91 92+ (CKDKeyValueStore *)defaultStore:(NSString *)identifier itemsChangedBlock:(CloudItemsChangedBlock)itemsChangedBlock 93{ 94 return [CKDKeyValueStoreCollection defaultStore:identifier itemsChangedBlock:itemsChangedBlock]; 95} 96 97- (BOOL)synchronize 98{ 99 BOOL value = NO; 100 value = [CKDKeyValueStoreCollection enqueueSyncWithReply]; 101 102 return value; 103} 104 105- (BOOL)isLocalKVS 106{ 107 return YES; 108} 109 110- (id)objectForKey:(NSString *)aKey 111{ 112 pdebug(@"retrieving value for key \"%@\"", aKey); 113 id value = [CKDKeyValueStoreCollection enqueueWithReply:aKey]; 114 pdebug(@"retrieved value for key \"%@\": %@", aKey, value); 115 return value; 116} 117 118- (void)setObject:(id)anObject forKey:(NSString *)aKey 119{ 120 pdebug(@"setting value for key \"%@\"", aKey); 121 [CKDKeyValueStoreCollection enqueueWrite:anObject forKey:aKey from:self.identifier]; 122} 123 124- (void)removeObjectForKey:(NSString *)aKey 125{ 126 pdebug(@"removing value for key \"%@\"", aKey); 127 [CKDKeyValueStoreCollection enqueueWrite:NULL forKey:aKey from:self.identifier]; 128} 129 130- (NSDictionary *)dictionaryRepresentation 131{ 132 pdebug(@"retrieving dictionaryRepresentation"); 133 id value = [CKDKeyValueStoreCollection enqueueWithReply:NULL]; 134 pdebug(@"retrieved dictionaryRepresentation: %@", value); 135 return value; 136} 137 138- (void)setDictionaryRepresentation:(NSMutableDictionary *)initialValue 139{ 140 // DEBUG 141 [CKDKeyValueStoreCollection enqueueWrite:initialValue forKey:NULL from:self.identifier]; 142} 143 144- (void)clearPersistentStores 145{ 146} 147 148+ (CFStringRef)remoteStoreID 149{ 150 return kCKDKVSRemoteStoreID; 151} 152 153// MARK: ----- copied from real kvs ----- 154 155- (id)initWithItemsChangedBlock:(CloudItemsChangedBlock)itemsChangedBlock 156{ 157 if (self = [super init]) 158 { 159 // copy blocks onto heap 160 itemsChangedCallback = Block_copy(itemsChangedBlock); 161 162 [[NSNotificationCenter defaultCenter] addObserver: self 163 selector: @selector (iCloudAccountAvailabilityChanged:) 164 name: NSUbiquityIdentityDidChangeNotification 165 object: nil]; 166 167 [[NSNotificationCenter defaultCenter] addObserver:self 168 selector:@selector(cloudChanged:) 169 name:ourNSUbiquitousKeyValueStoreDidChangeExternallyNotification 170 object:nil]; 171 } 172 return self; 173} 174 175- (void)dealloc 176{ 177 178 [[NSNotificationCenter defaultCenter] removeObserver:self 179 name:ourNSUbiquitousKeyValueStoreDidChangeExternallyNotification object:nil]; 180 181 [[NSNotificationCenter defaultCenter] removeObserver:self 182 name:NSUbiquityIdentityDidChangeNotification object:nil]; 183 184 Block_release(itemsChangedCallback); 185 [super dealloc]; 186} 187 188- (void)cloudChanged:(NSNotification*)notification 189{ 190 /* 191 Posted when the value of one or more keys in the local key-value store changed due to incoming data pushed from iCloud. 192 This notification is sent only upon a change received from iCloud; it is not sent when your app sets a value. 193 194 The user info dictionary can contain the reason for the notification as well as a list of which values changed, as follows: 195 196 The value of the NSUbiquitousKeyValueStoreChangeReasonKey key, when present, indicates why the key-value store changed. 197 Its value is one of the constants in “Change Reason Values .” The value of the NSUbiquitousKeyValueStoreChangedKeysKey, 198 when present, is an array of strings, each the name of a key whose value changed. The notification object is the 199 NSUbiquitousKeyValueStore object whose contents changed. 200 201 enum { 202 NSUbiquitousKeyValueStoreServerChange NS_ENUM_AVAILABLE(10_7, 5_0), 203 NSUbiquitousKeyValueStoreInitialSyncChange NS_ENUM_AVAILABLE(10_7, 5_0), 204 NSUbiquitousKeyValueStoreQuotaViolationChange NS_ENUM_AVAILABLE(10_7, 5_0), 205 NSUbiquitousKeyValueStoreAccountChange NS_ENUM_AVAILABLE(10_8, 6_0) 206 }; 207 208 */ 209 210 NSDictionary *userInfo = [notification userInfo]; 211 NSNumber *reason = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangeReasonKey]; 212 NSInteger reasonValue = -1; 213 214 pdebug(@"cloudChanged notification: %@", notification); 215 216 NSString *whoChangedIt = [userInfo objectForKey:kCKDKVSWhoChangedItemKey]; 217 218 if (self.identifier && whoChangedIt && [self.identifier isEqualToString:whoChangedIt]) 219 { 220 pdebug(@"cloudChanged by us (%@), ignoring event", self.identifier); 221 return; 222 } 223 224 if (reason) 225 { 226 reasonValue = [reason integerValue]; 227 NSArray *reasonStrings = [NSArray arrayWithObjects:@"Server", @"InitialSync", @"QuotaViolation", @"Account", @"unknown", nil]; 228 long ridx = (NSUbiquitousKeyValueStoreServerChange <= reasonValue && reasonValue <= NSUbiquitousKeyValueStoreAccountChange)?reasonValue : 5; 229 pdebug(@"cloudChanged with reason %ld (%@ Change)", (long)reasonValue, [reasonStrings objectAtIndex:ridx]); 230 } 231 232 if ((reasonValue == NSUbiquitousKeyValueStoreServerChange) || 233 (reasonValue == NSUbiquitousKeyValueStoreInitialSyncChange)) 234 { 235 NSArray *keysChangedInCloud = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangedKeysKey]; 236 pdebug(@"keysChangedInCloud: %@", keysChangedInCloud); 237 NSMutableDictionary *changedValues = [NSMutableDictionary dictionaryWithCapacity:0]; 238 [keysChangedInCloud enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) 239 { 240 NSString *key = (NSString *)obj; 241 // itemChangedCallback(key, [self.store objectForKey:key]); 242 id anObject = @"FIXME"; //[self.store objectForKey:key]; 243 [changedValues setObject:anObject forKey:key]; 244 245 pdebug(@"storeChanged updated value for %@", key); 246 }]; 247 itemsChangedCallback((CFDictionaryRef)changedValues); // fix me ************************* 248 } 249} 250 251 252 253@end 254 255// MARK: ----- CKDKeyValueStoreCollection ----- 256 257@implementation CKDKeyValueStoreCollection 258 259- (id)init 260{ 261 if (self = [super init]) 262 { 263 self.collection = [NSMutableDictionary dictionaryWithCapacity:0]; 264 self->syncrequestqueue = dispatch_queue_create("syncrequestqueue", DISPATCH_QUEUE_SERIAL); 265 self->store = [NSMutableDictionary dictionaryWithCapacity:0]; 266 } 267 return self; 268} 269 270// maybe should return (CKDKeyValueStore *), main thing is that it matches the protocol 271+ (id <CKDKVSDelegate>)defaultStore:(NSString *)identifier itemsChangedBlock:(CloudItemsChangedBlock)itemsChangedBlock 272{ 273 // look it up in the collection and return singleton 274 if (identifier == NULL) 275 return (id <CKDKVSDelegate>)[NSUbiquitousKeyValueStore defaultStore]; 276 277 CKDKeyValueStoreCollection *mall = [CKDKeyValueStoreCollection sharedInstance]; 278 id <CKDKVSDelegate> store = mall.collection[identifier]; 279 if (!store) 280 { 281 store = [[CKDKeyValueStore alloc] initWithIdentifier:identifier itemsChangedBlock:itemsChangedBlock]; 282 mall->_collection[identifier] = store; 283 } 284 return store; 285 286} 287 288+ (id)sharedInstance 289{ 290 static dispatch_once_t once; 291 static CKDKeyValueStoreCollection *sharedStoreCollection; 292 dispatch_once(&once, ^ { sharedStoreCollection = [[self alloc] init]; }); 293 return sharedStoreCollection; 294} 295 296+ (void)enqueueWrite:(id)anObject forKey:(NSString *)aKey from:(NSString *)identifier 297{ 298 CKDKeyValueStoreCollection *mall = [CKDKeyValueStoreCollection sharedInstance]; 299 dispatch_async(mall->syncrequestqueue, ^void () 300 { 301 if (aKey==NULL && (CFGetTypeID(anObject)==CFDictionaryGetTypeID())) 302 { 303 [mall->store setDictionary:anObject]; 304 [self postItemsChangedNotification:[anObject allKeys] from:identifier]; 305 } 306 else 307 { 308 if (anObject) 309 [mall->store setObject:anObject forKey:aKey]; 310 else 311 [mall->store removeObjectForKey:aKey]; 312 [CKDKeyValueStoreCollection postItemChangedNotification:aKey from:identifier]; 313 } 314 315 }); 316} 317 318+ (id)enqueueWithReply:(NSString *)aKey 319{ 320 __block id value = NULL; 321 CKDKeyValueStoreCollection *mall = [CKDKeyValueStoreCollection sharedInstance]; 322 dispatch_sync(mall->syncrequestqueue, ^void () 323 { 324 value = (aKey==NULL)?mall->store:[mall->store objectForKey:aKey]; 325 }); 326 return value; 327} 328 329+ (BOOL)enqueueSyncWithReply 330{ 331 // basically a barrier 332 __block BOOL value = false; 333 CKDKeyValueStoreCollection *mall = [CKDKeyValueStoreCollection sharedInstance]; 334 dispatch_sync(mall->syncrequestqueue, ^void () 335 { 336 value = true; 337 }); 338 return value; 339} 340 341+ (void)postItemChangedNotification:(NSString *)keyThatChanged from:(NSString *)identifier 342{ 343 // convenience routine when a single key changes 344 NSArray *keysThatChanged = [NSArray arrayWithObject:keyThatChanged]; 345 [self postItemsChangedNotification:keysThatChanged from:identifier]; 346// [keysThatChanged release]; 347} 348 349+ (void)postItemsChangedNotification:(NSArray *)keysThatChanged from:(NSString *)identifier 350{ 351 // add in array of keys plus the id of who changed it 352 NSDictionary *aUserInfo = [NSDictionary dictionaryWithObjectsAndKeys: 353 keysThatChanged, NSUbiquitousKeyValueStoreChangedKeysKey, 354 @(NSUbiquitousKeyValueStoreServerChange), NSUbiquitousKeyValueStoreChangeReasonKey, 355 identifier, kCKDKVSWhoChangedItemKey, 356 nil]; 357 358 [[NSNotificationCenter defaultCenter] postNotificationName:ourNSUbiquitousKeyValueStoreDidChangeExternallyNotification 359 object:nil userInfo:aUserInfo]; 360 // NSArray *keysChangedInCloud = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangedKeysKey]; 361} 362 363 364@end 365 366