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