]>
Commit | Line | Data |
---|---|---|
866f8763 A |
1 | /* |
2 | * Copyright (c) 2017 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 | #import <Foundation/Foundation.h> | |
25 | ||
26 | #if OCTAGON | |
27 | ||
28 | #import <dispatch/dispatch.h> | |
29 | ||
30 | #import "keychain/ckks/CKKSZoneChangeFetcher.h" | |
31 | #import "keychain/ckks/CKKSFetchAllRecordZoneChangesOperation.h" | |
32 | #import "keychain/ckks/CKKSKeychainView.h" | |
33 | #import "keychain/ckks/CKKSNearFutureScheduler.h" | |
34 | #import "keychain/ckks/CloudKitCategories.h" | |
79b9da22 A |
35 | #import "keychain/ckks/CKKSReachabilityTracker.h" |
36 | #import "keychain/categories/NSError+UsefulConstructors.h" | |
b54c578e A |
37 | #import "keychain/analytics/SecEventMetric.h" |
38 | #import "keychain/analytics/SecMetrics.h" | |
39 | #import "keychain/ot/ObjCImprovements.h" | |
866f8763 A |
40 | |
41 | CKKSFetchBecause* const CKKSFetchBecauseAPNS = (CKKSFetchBecause*) @"apns"; | |
42 | CKKSFetchBecause* const CKKSFetchBecauseAPIFetchRequest = (CKKSFetchBecause*) @"api"; | |
43 | CKKSFetchBecause* const CKKSFetchBecauseCurrentItemFetchRequest = (CKKSFetchBecause*) @"currentitemcheck"; | |
44 | CKKSFetchBecause* const CKKSFetchBecauseInitialStart = (CKKSFetchBecause*) @"initialfetch"; | |
45 | CKKSFetchBecause* const CKKSFetchBecauseSecuritydRestart = (CKKSFetchBecause*) @"restart"; | |
46 | CKKSFetchBecause* const CKKSFetchBecausePreviousFetchFailed = (CKKSFetchBecause*) @"fetchfailed"; | |
ecaf5866 | 47 | CKKSFetchBecause* const CKKSFetchBecauseNetwork = (CKKSFetchBecause*) @"network"; |
866f8763 A |
48 | CKKSFetchBecause* const CKKSFetchBecauseKeyHierarchy = (CKKSFetchBecause*) @"keyhierarchy"; |
49 | CKKSFetchBecause* const CKKSFetchBecauseTesting = (CKKSFetchBecause*) @"testing"; | |
ecaf5866 | 50 | CKKSFetchBecause* const CKKSFetchBecauseResync = (CKKSFetchBecause*) @"resync"; |
b54c578e | 51 | CKKSFetchBecause* const CKKSFetchBecauseMoreComing = (CKKSFetchBecause*) @"more-coming"; |
ecaf5866 A |
52 | |
53 | #pragma mark - CKKSZoneChangeFetchDependencyOperation | |
54 | @interface CKKSZoneChangeFetchDependencyOperation : CKKSResultOperation | |
7fb2cbd2 | 55 | @property (weak) CKKSZoneChangeFetcher* owner; |
79b9da22 A |
56 | @property NSMutableArray<CKKSZoneChangeFetchDependencyOperation*>* chainDependents; |
57 | - (void)chainDependency:(CKKSZoneChangeFetchDependencyOperation*)newDependency; | |
ecaf5866 A |
58 | @end |
59 | ||
60 | @implementation CKKSZoneChangeFetchDependencyOperation | |
79b9da22 A |
61 | - (instancetype)init { |
62 | if((self = [super init])) { | |
63 | _chainDependents = [NSMutableArray array]; | |
64 | } | |
65 | return self; | |
66 | } | |
67 | ||
ecaf5866 A |
68 | - (NSError* _Nullable)descriptionError { |
69 | return [NSError errorWithDomain:CKKSResultDescriptionErrorDomain | |
70 | code:CKKSResultDescriptionPendingSuccessfulFetch | |
71 | description:@"Fetch failed" | |
72 | underlying:self.owner.lastCKFetchError]; | |
73 | } | |
79b9da22 A |
74 | |
75 | - (void)chainDependency:(CKKSZoneChangeFetchDependencyOperation*)newDependency { | |
76 | [self addSuccessDependency:newDependency]; | |
77 | ||
78 | // There's no need to build a chain more than two links long. Move all our children up to depend on the new dependency. | |
79 | for(CKKSZoneChangeFetchDependencyOperation* op in self.chainDependents) { | |
80 | [newDependency.chainDependents addObject:op]; | |
81 | [op addSuccessDependency:newDependency]; | |
82 | [op removeDependency:self]; | |
83 | } | |
84 | [self.chainDependents removeAllObjects]; | |
85 | } | |
ecaf5866 A |
86 | @end |
87 | ||
88 | #pragma mark - CKKSZoneChangeFetcher | |
866f8763 A |
89 | |
90 | @interface CKKSZoneChangeFetcher () | |
91 | @property NSString* name; | |
79b9da22 | 92 | @property NSOperationQueue* operationQueue; |
866f8763 A |
93 | @property dispatch_queue_t queue; |
94 | ||
ecaf5866 A |
95 | @property NSError* lastCKFetchError; |
96 | ||
79b9da22 A |
97 | @property NSMapTable<CKRecordZoneID*, id<CKKSChangeFetcherClient>>* clientMap; |
98 | ||
866f8763 A |
99 | @property CKKSFetchAllRecordZoneChangesOperation* currentFetch; |
100 | @property CKKSResultOperation* currentProcessResult; | |
101 | ||
102 | @property NSMutableSet<CKKSFetchBecause*>* currentFetchReasons; | |
79b9da22 | 103 | @property NSMutableSet<CKRecordZoneNotification*>* apnsPushes; |
866f8763 | 104 | @property bool newRequests; // true if there's someone pending on successfulFetchDependency |
79b9da22 | 105 | @property CKKSZoneChangeFetchDependencyOperation* successfulFetchDependency; |
8a50f688 | 106 | |
d64be36e A |
107 | @property (nullable) CKKSZoneChangeFetchDependencyOperation* inflightFetchDependency; |
108 | ||
8a50f688 | 109 | @property CKKSResultOperation* holdOperation; |
866f8763 A |
110 | @end |
111 | ||
112 | @implementation CKKSZoneChangeFetcher | |
113 | ||
79b9da22 A |
114 | - (instancetype)initWithContainer:(CKContainer*)container |
115 | fetchClass:(Class<CKKSFetchRecordZoneChangesOperation>)fetchRecordZoneChangesOperationClass | |
116 | reachabilityTracker:(CKKSReachabilityTracker *)reachabilityTracker | |
117 | { | |
866f8763 | 118 | if((self = [super init])) { |
79b9da22 A |
119 | _container = container; |
120 | _fetchRecordZoneChangesOperationClass = fetchRecordZoneChangesOperationClass; | |
121 | _reachabilityTracker = reachabilityTracker; | |
866f8763 A |
122 | |
123 | _currentFetchReasons = [[NSMutableSet alloc] init]; | |
79b9da22 A |
124 | _apnsPushes = [[NSMutableSet alloc] init]; |
125 | ||
126 | _clientMap = [NSMapTable strongToWeakObjectsMapTable]; | |
866f8763 | 127 | |
79b9da22 | 128 | _name = @"zone-change-fetcher"; |
7512f6be | 129 | _queue = dispatch_queue_create([_name UTF8String], DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL); |
79b9da22 | 130 | _operationQueue = [[NSOperationQueue alloc] init]; |
ecaf5866 | 131 | _successfulFetchDependency = [self createSuccesfulFetchDependency]; |
d64be36e | 132 | _inflightFetchDependency = nil; |
866f8763 A |
133 | |
134 | _newRequests = false; | |
135 | ||
d64be36e A |
136 | // If we're testing, for the initial delay, use 0.5 second. Otherwise, 2s. |
137 | dispatch_time_t initialDelay = (SecCKKSReduceRateLimiting() ? 500 * NSEC_PER_MSEC : 2 * NSEC_PER_SEC); | |
866f8763 | 138 | |
7fb2cbd2 A |
139 | // If we're testing, for the maximum delay, use 6 second. Otherwise, 2m. |
140 | dispatch_time_t maximumDelay = (SecCKKSReduceRateLimiting() ? 6 * NSEC_PER_SEC : 120 * NSEC_PER_SEC); | |
866f8763 | 141 | |
b54c578e | 142 | WEAKIFY(self); |
79b9da22 | 143 | _fetchScheduler = [[CKKSNearFutureScheduler alloc] initWithName:@"zone-change-fetch-scheduler" |
866f8763 | 144 | initialDelay:initialDelay |
7fb2cbd2 A |
145 | expontialBackoff:2 |
146 | maximumDelay:maximumDelay | |
866f8763 | 147 | keepProcessAlive:false |
ecaf5866 | 148 | dependencyDescriptionCode:CKKSResultDescriptionPendingZoneChangeFetchScheduling |
866f8763 | 149 | block:^{ |
b54c578e A |
150 | STRONGIFY(self); |
151 | [self maybeCreateNewFetch]; | |
866f8763 A |
152 | }]; |
153 | } | |
154 | return self; | |
155 | } | |
156 | ||
157 | - (NSString*)description { | |
158 | NSDate* nextFetchAt = self.fetchScheduler.nextFireTime; | |
159 | if(nextFetchAt) { | |
160 | NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init]; | |
161 | [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"]; | |
162 | return [NSString stringWithFormat: @"<CKKSZoneChangeFetcher(%@): next fetch at %@", self.name, [dateFormatter stringFromDate: nextFetchAt]]; | |
163 | } else { | |
164 | return [NSString stringWithFormat: @"<CKKSZoneChangeFetcher(%@): no pending fetches", self.name]; | |
165 | } | |
166 | } | |
167 | ||
79b9da22 A |
168 | - (void)registerClient:(id<CKKSChangeFetcherClient>)client |
169 | { | |
170 | @synchronized(self.clientMap) { | |
171 | [self.clientMap setObject:client forKey:client.zoneID]; | |
172 | } | |
173 | } | |
174 | ||
805875f8 A |
175 | - (NSArray<id<CKKSChangeFetcherClient>>*)clients { |
176 | NSMutableArray<id<CKKSChangeFetcherClient>> *clients = [NSMutableArray array]; | |
177 | @synchronized (self.clientMap) { | |
178 | for(id<CKKSChangeFetcherClient> client in [self.clientMap objectEnumerator]) { | |
179 | if (client) { | |
180 | [clients addObject:client]; | |
181 | } | |
182 | } | |
183 | } | |
184 | return clients; | |
185 | } | |
79b9da22 | 186 | |
866f8763 | 187 | - (CKKSResultOperation*)requestSuccessfulFetch:(CKKSFetchBecause*)why { |
79b9da22 A |
188 | return [self requestSuccessfulFetchForManyReasons:[NSSet setWithObject:why]]; |
189 | } | |
190 | ||
d64be36e A |
191 | - (void)notifyZoneChange:(CKRecordZoneNotification* _Nullable)notification |
192 | { | |
193 | ckksnotice_global("ckkspush", "received a zone change notification for %@ %@", self, notification); | |
194 | [self requestFetchDueToAPNS:notification]; | |
195 | } | |
196 | ||
805875f8 | 197 | - (CKKSResultOperation*)requestFetchDueToAPNS:(CKRecordZoneNotification*)notification |
79b9da22 | 198 | { |
805875f8 | 199 | __block BOOL notReady = YES; |
866f8763 | 200 | |
805875f8 A |
201 | // make sure we don't hold the self.queue when we call out to clients since that will lead |
202 | // to lock inversions | |
203 | ||
204 | NSArray<id<CKKSChangeFetcherClient>> *clients = [self clients]; | |
205 | ||
206 | for(id<CKKSChangeFetcherClient> client in clients) { | |
207 | if([client zoneIsReadyForFetching]) { | |
208 | notReady = NO; | |
d64be36e | 209 | break; |
805875f8 A |
210 | } |
211 | } | |
866f8763 | 212 | |
866f8763 | 213 | dispatch_sync(self.queue, ^{ |
805875f8 | 214 | |
79b9da22 A |
215 | if(notification) { |
216 | [self.apnsPushes addObject:notification]; | |
79b9da22 A |
217 | if(notification.ckksPushTracingEnabled) { |
218 | // Report that we saw this notification before doing anything else | |
d64be36e | 219 | ckksnotice_global("ckksfetch", "Submitting initial CKEventMetric due to notification %@", notification); |
79b9da22 A |
220 | |
221 | CKEventMetric *metric = [[CKEventMetric alloc] initWithEventName:@"APNSPushMetrics"]; | |
222 | metric.isPushTriggerFired = true; | |
223 | metric[@"push_token_uuid"] = notification.ckksPushTracingUUID; | |
224 | metric[@"push_received_date"] = notification.ckksPushReceivedDate; | |
225 | metric[@"push_event_name"] = @"CKKS APNS Push Received"; | |
805875f8 | 226 | metric[@"zones_status"] = notReady ? @"not-ready" : @"ready"; |
79b9da22 A |
227 | |
228 | [self.container submitEventMetric:metric]; | |
b54c578e A |
229 | |
230 | SecEventMetric *metric2 = [[SecEventMetric alloc] initWithEventName:@"APNSPushMetrics"]; | |
231 | metric2[@"push_token_uuid"] = notification.ckksPushTracingUUID; | |
232 | metric2[@"push_received_date"] = notification.ckksPushReceivedDate; | |
233 | metric2[@"push_event_name"] = @"CKKS APNS Push Received-webtunnel"; | |
805875f8 | 234 | metric[@"zones_status"] = notReady ? @"not-ready" : @"ready"; |
b54c578e A |
235 | |
236 | [[SecMetrics managerObject] submitEvent:metric2]; | |
79b9da22 | 237 | } |
79b9da22 | 238 | } |
866f8763 | 239 | |
805875f8 A |
240 | }); |
241 | ||
242 | if (notReady) { | |
d64be36e | 243 | ckksnotice_global("ckksfetch", "Skipping fetching size no zone is ready"); |
805875f8 A |
244 | return NULL; |
245 | } | |
246 | ||
247 | return [self requestSuccessfulFetchForManyReasons:[NSSet setWithObject:CKKSFetchBecauseAPNS]]; | |
248 | } | |
249 | ||
250 | - (CKKSResultOperation*)requestSuccessfulFetchForManyReasons:(NSSet<CKKSFetchBecause*>*)why | |
251 | { | |
252 | __block CKKSResultOperation* dependency = nil; | |
253 | dispatch_sync(self.queue, ^{ | |
254 | dependency = self.successfulFetchDependency; | |
255 | self.newRequests = true; | |
256 | [self.currentFetchReasons unionSet:why]; | |
257 | ||
866f8763 A |
258 | [self.fetchScheduler trigger]; |
259 | }); | |
260 | ||
261 | return dependency; | |
262 | } | |
263 | ||
d64be36e A |
264 | - (CKKSResultOperation* _Nullable)inflightFetch |
265 | { | |
266 | __block CKKSResultOperation* dependency = nil; | |
267 | dispatch_sync(self.queue, ^{ | |
268 | ||
269 | // If we'll have a new fetch in the future, return its status. | |
270 | if(self.newRequests || self.inflightFetchDependency == nil) { | |
271 | dependency = self.successfulFetchDependency; | |
272 | } else { | |
273 | // Otherwise, return the last triggered fetch | |
274 | dependency = self.inflightFetchDependency; | |
275 | } | |
276 | }); | |
277 | ||
278 | return dependency; | |
279 | } | |
280 | ||
b54c578e A |
281 | -(void)maybeCreateNewFetchOnQueue { |
282 | dispatch_assert_queue(self.queue); | |
283 | if(self.newRequests && | |
284 | (self.currentFetch == nil || [self.currentFetch isFinished]) && | |
285 | (self.currentProcessResult == nil || [self.currentProcessResult isFinished])) { | |
286 | [self _onqueueCreateNewFetch]; | |
287 | } | |
288 | } | |
289 | ||
866f8763 A |
290 | -(void)maybeCreateNewFetch { |
291 | dispatch_sync(self.queue, ^{ | |
b54c578e | 292 | [self maybeCreateNewFetchOnQueue]; |
866f8763 A |
293 | }); |
294 | } | |
295 | ||
296 | -(void)_onqueueCreateNewFetch { | |
297 | dispatch_assert_queue(self.queue); | |
298 | ||
b54c578e | 299 | WEAKIFY(self); |
866f8763 | 300 | |
79b9da22 | 301 | CKKSZoneChangeFetchDependencyOperation* dependency = self.successfulFetchDependency; |
d64be36e A |
302 | self.inflightFetchDependency = dependency; |
303 | ||
ecaf5866 | 304 | NSMutableSet<CKKSFetchBecause*>* lastFetchReasons = self.currentFetchReasons; |
866f8763 | 305 | self.currentFetchReasons = [[NSMutableSet alloc] init]; |
79b9da22 | 306 | |
b54c578e A |
307 | NSString *reasonsString = [[lastFetchReasons sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES]]] componentsJoinedByString:@","]; |
308 | ||
d64be36e | 309 | ckksnotice_global("ckksfetcher", "Starting a new fetch, reasons: %@", reasonsString); |
b54c578e | 310 | |
79b9da22 A |
311 | NSMutableSet<CKRecordZoneNotification*>* lastAPNSPushes = self.apnsPushes; |
312 | self.apnsPushes = [[NSMutableSet alloc] init]; | |
866f8763 | 313 | |
b54c578e | 314 | CKOperationGroup* operationGroup = [CKOperationGroup CKKSGroupWithName: reasonsString]; |
866f8763 | 315 | |
805875f8 | 316 | NSArray<id<CKKSChangeFetcherClient>> *clients = [self clients]; |
79b9da22 A |
317 | |
318 | if(clients.count == 0u) { | |
d64be36e | 319 | ckksnotice_global("ckksfetcher", "No clients"); |
79b9da22 A |
320 | // Nothing to do, really. |
321 | } | |
322 | ||
323 | CKKSFetchAllRecordZoneChangesOperation* fetchAllChanges = [[CKKSFetchAllRecordZoneChangesOperation alloc] initWithContainer:self.container | |
324 | fetchClass:self.fetchRecordZoneChangesOperationClass | |
325 | clients:clients | |
326 | fetchReasons:lastFetchReasons | |
327 | apnsPushes:lastAPNSPushes | |
328 | forceResync:false | |
329 | ckoperationGroup:operationGroup]; | |
330 | ||
ecaf5866 | 331 | if ([lastFetchReasons containsObject:CKKSFetchBecauseNetwork]) { |
d64be36e | 332 | ckksnotice_global("ckksfetcher", "blocking fetch on network reachability"); |
79b9da22 | 333 | [fetchAllChanges addNullableDependency: self.reachabilityTracker.reachabilityDependency]; // wait on network, if its unavailable |
ecaf5866 | 334 | } |
8a50f688 | 335 | [fetchAllChanges addNullableDependency: self.holdOperation]; |
866f8763 A |
336 | |
337 | self.currentProcessResult = [CKKSResultOperation operationWithBlock: ^{ | |
b54c578e A |
338 | STRONGIFY(self); |
339 | if(!self) { | |
d64be36e | 340 | ckkserror_global("ckksfetcher", "Received a null self pointer; strange."); |
866f8763 A |
341 | return; |
342 | } | |
343 | ||
b54c578e A |
344 | bool attemptAnotherFetch = false; |
345 | if(fetchAllChanges.error != nil) { | |
d64be36e | 346 | ckkserror_global("ckksfetcher", "Interrogating clients about fetch error: %@", fetchAllChanges.error); |
b54c578e A |
347 | |
348 | // Check in with clients: should we keep fetching for them? | |
349 | @synchronized(self.clientMap) { | |
350 | for(CKRecordZoneID* zoneID in fetchAllChanges.fetchedZoneIDs) { | |
351 | id<CKKSChangeFetcherClient> client = [self.clientMap objectForKey:zoneID]; | |
352 | if(client) { | |
353 | attemptAnotherFetch |= [client shouldRetryAfterFetchError:fetchAllChanges.error]; | |
354 | } | |
355 | } | |
356 | } | |
357 | } | |
358 | ||
359 | dispatch_sync(self.queue, ^{ | |
ecaf5866 A |
360 | self.lastCKFetchError = fetchAllChanges.error; |
361 | ||
7fb2cbd2 A |
362 | if(fetchAllChanges.error == nil) { |
363 | // success! notify the listeners. | |
364 | [self.operationQueue addOperation: dependency]; | |
365 | self.currentFetch = nil; | |
866f8763 | 366 | |
7fb2cbd2 A |
367 | // Did new people show up and want another fetch? |
368 | if(self.newRequests) { | |
b54c578e | 369 | [self.fetchScheduler trigger]; |
866f8763 A |
370 | } |
371 | } else { | |
ecaf5866 | 372 | // The operation errored. Chain the dependency on the current one... |
b54c578e A |
373 | [dependency chainDependency:self.successfulFetchDependency]; |
374 | [self.operationQueue addOperation: dependency]; | |
ecaf5866 | 375 | |
79b9da22 | 376 | if(!attemptAnotherFetch) { |
d64be36e | 377 | ckkserror_global("ckksfetcher", "All clients thought %@ is a fatal error. Not restarting fetch.", fetchAllChanges.error); |
866f8763 A |
378 | return; |
379 | } | |
380 | ||
866f8763 | 381 | // And in a bit, try the fetch again. |
b54c578e A |
382 | NSTimeInterval delay = CKRetryAfterSecondsForError(fetchAllChanges.error); |
383 | if (delay) { | |
d64be36e | 384 | ckksnotice_global("ckksfetcher", "Fetch failed with rate-limiting error, restarting in %.1f seconds: %@", delay, fetchAllChanges.error); |
b54c578e | 385 | [self.fetchScheduler waitUntil:NSEC_PER_SEC * delay]; |
866f8763 | 386 | } else { |
d64be36e | 387 | ckksnotice_global("ckksfetcher", "Fetch failed with error, restarting soon: %@", fetchAllChanges.error); |
866f8763 A |
388 | } |
389 | ||
390 | // Add the failed fetch reasons to the new fetch reasons | |
b54c578e A |
391 | [self.currentFetchReasons unionSet:lastFetchReasons]; |
392 | [self.apnsPushes unionSet:lastAPNSPushes]; | |
79b9da22 | 393 | |
ecaf5866 | 394 | // If its a network error, make next try depend on network availability |
79b9da22 | 395 | if ([self.reachabilityTracker isNetworkError:fetchAllChanges.error]) { |
b54c578e | 396 | [self.currentFetchReasons addObject:CKKSFetchBecauseNetwork]; |
ecaf5866 | 397 | } else { |
b54c578e | 398 | [self.currentFetchReasons addObject:CKKSFetchBecausePreviousFetchFailed]; |
ecaf5866 | 399 | } |
b54c578e A |
400 | self.newRequests = true; |
401 | [self.fetchScheduler trigger]; | |
866f8763 A |
402 | } |
403 | }); | |
404 | }]; | |
b54c578e A |
405 | |
406 | // creata a new fetch dependency, for all those who come in while this operation is executing | |
407 | self.newRequests = false; | |
408 | self.successfulFetchDependency = [self createSuccesfulFetchDependency]; | |
409 | ||
410 | // now let new new fetch go and process it's results | |
866f8763 A |
411 | self.currentProcessResult.name = @"zone-change-fetcher-worker"; |
412 | [self.currentProcessResult addDependency: fetchAllChanges]; | |
413 | ||
79b9da22 | 414 | [self.operationQueue addOperation:self.currentProcessResult]; |
866f8763 A |
415 | |
416 | self.currentFetch = fetchAllChanges; | |
79b9da22 | 417 | [self.operationQueue addOperation:self.currentFetch]; |
866f8763 A |
418 | } |
419 | ||
ecaf5866 A |
420 | -(CKKSZoneChangeFetchDependencyOperation*)createSuccesfulFetchDependency { |
421 | CKKSZoneChangeFetchDependencyOperation* dep = [[CKKSZoneChangeFetchDependencyOperation alloc] init]; | |
866f8763 | 422 | |
866f8763 | 423 | dep.name = @"successful-fetch-dependency"; |
ecaf5866 A |
424 | dep.descriptionErrorCode = CKKSResultDescriptionPendingSuccessfulFetch; |
425 | dep.owner = self; | |
866f8763 | 426 | |
ecaf5866 | 427 | return dep; |
866f8763 A |
428 | } |
429 | ||
8a50f688 A |
430 | - (void)holdFetchesUntil:(CKKSResultOperation*)holdOperation { |
431 | self.holdOperation = holdOperation; | |
432 | } | |
433 | ||
866f8763 A |
434 | -(void)cancel { |
435 | [self.fetchScheduler cancel]; | |
436 | } | |
437 | ||
438 | @end | |
439 | ||
440 | #endif | |
441 | ||
442 |