FSyncTree.m 47 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155
  1. /*
  2. * Copyright 2017 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import "FirebaseDatabase/Sources/Core/FSyncTree.h"
  17. #import "FirebaseCore/Extension/FirebaseCoreInternal.h"
  18. #import "FirebaseDatabase/Sources/Core/FCompoundHash.h"
  19. #import "FirebaseDatabase/Sources/Core/FListenProvider.h"
  20. #import "FirebaseDatabase/Sources/Core/FQueryParams.h"
  21. #import "FirebaseDatabase/Sources/Core/FQuerySpec.h"
  22. #import "FirebaseDatabase/Sources/Core/FRangeMerge.h"
  23. #import "FirebaseDatabase/Sources/Core/FServerValues.h"
  24. #import "FirebaseDatabase/Sources/Core/FSnapshotHolder.h"
  25. #import "FirebaseDatabase/Sources/Core/FSyncPoint.h"
  26. #import "FirebaseDatabase/Sources/Core/FWriteRecord.h"
  27. #import "FirebaseDatabase/Sources/Core/FWriteTree.h"
  28. #import "FirebaseDatabase/Sources/Core/FWriteTreeRef.h"
  29. #import "FirebaseDatabase/Sources/Core/Operation/FAckUserWrite.h"
  30. #import "FirebaseDatabase/Sources/Core/Operation/FMerge.h"
  31. #import "FirebaseDatabase/Sources/Core/Operation/FOperation.h"
  32. #import "FirebaseDatabase/Sources/Core/Operation/FOperationSource.h"
  33. #import "FirebaseDatabase/Sources/Core/Operation/FOverwrite.h"
  34. #import "FirebaseDatabase/Sources/Core/Utilities/FImmutableTree.h"
  35. #import "FirebaseDatabase/Sources/Core/Utilities/FPath.h"
  36. #import "FirebaseDatabase/Sources/Core/View/FCacheNode.h"
  37. #import "FirebaseDatabase/Sources/Core/View/FEventRaiser.h"
  38. #import "FirebaseDatabase/Sources/Core/View/FEventRegistration.h"
  39. #import "FirebaseDatabase/Sources/Core/View/FKeepSyncedEventRegistration.h"
  40. #import "FirebaseDatabase/Sources/Core/View/FView.h"
  41. #import "FirebaseDatabase/Sources/FListenComplete.h"
  42. #import "FirebaseDatabase/Sources/Persistence/FPersistenceManager.h"
  43. #import "FirebaseDatabase/Sources/Snapshot/FChildrenNode.h"
  44. #import "FirebaseDatabase/Sources/Snapshot/FCompoundWrite.h"
  45. #import "FirebaseDatabase/Sources/Snapshot/FEmptyNode.h"
  46. #import "FirebaseDatabase/Sources/Snapshot/FNode.h"
  47. #import "FirebaseDatabase/Sources/Snapshot/FSnapshotUtilities.h"
  48. #import "FirebaseDatabase/Sources/Utilities/FAtomicNumber.h"
  49. #import "FirebaseDatabase/Sources/Utilities/FUtilities.h"
  50. #import "FirebaseDatabase/Sources/Utilities/Tuples/FTupleRemovedQueriesEvents.h"
  51. // Size after which we start including the compound hash
  52. static const NSUInteger kFSizeThresholdForCompoundHash = 1024;
  53. @interface FListenContainer : NSObject <FSyncTreeHash>
  54. @property(nonatomic, strong) FView *view;
  55. @property(nonatomic, copy) fbt_nsarray_nsstring onComplete;
  56. @end
  57. @implementation FListenContainer
  58. - (instancetype)initWithView:(FView *)view
  59. onComplete:(fbt_nsarray_nsstring)onComplete {
  60. self = [super init];
  61. if (self != nil) {
  62. self->_view = view;
  63. self->_onComplete = onComplete;
  64. }
  65. return self;
  66. }
  67. - (id<FNode>)serverCache {
  68. return self.view.serverCache;
  69. }
  70. - (FCompoundHash *)compoundHash {
  71. return [FCompoundHash fromNode:[self serverCache]];
  72. }
  73. - (NSString *)simpleHash {
  74. return [[self serverCache] dataHash];
  75. }
  76. - (BOOL)includeCompoundHash {
  77. return [FSnapshotUtilities estimateSerializedNodeSize:[self serverCache]] >
  78. kFSizeThresholdForCompoundHash;
  79. }
  80. @end
  81. @interface FSyncTree ()
  82. /**
  83. * Tree of SyncPoints. There's a SyncPoint at any location that has 1 or more
  84. * views.
  85. */
  86. @property(nonatomic, strong) FImmutableTree *syncPointTree;
  87. /**
  88. * A tree of all pending user writes (user-initiated set, transactions, updates,
  89. * etc)
  90. */
  91. @property(nonatomic, strong) FWriteTree *pendingWriteTree;
  92. /**
  93. * Maps tagId -> FTuplePathQueryParams
  94. */
  95. @property(nonatomic, strong) NSMutableDictionary *tagToQueryMap;
  96. @property(nonatomic, strong) NSMutableDictionary *queryToTagMap;
  97. @property(nonatomic, strong) FListenProvider *listenProvider;
  98. @property(nonatomic, strong) FPersistenceManager *persistenceManager;
  99. @property(nonatomic, strong) FAtomicNumber *queryTagCounter;
  100. @property(nonatomic, strong) NSMutableSet *keepSyncedQueries;
  101. @end
  102. /**
  103. * SyncTree is the central class for managing event callback registration, data
  104. * caching, views (query processing), and event generation. There are typically
  105. * two SyncTree instances for each Repo, one for the normal Firebase data, and
  106. * one for the .info data.
  107. *
  108. * It has a number of responsibilities, including:
  109. * - Tracking all user event callbacks (registered via addEventRegistration:
  110. * and removeEventRegistration:).
  111. * - Applying and caching data changes for user setValue:,
  112. * runTransactionBlock:, and updateChildValues: calls
  113. * (applyUserOverwriteAtPath:, applyUserMergeAtPath:).
  114. * - Applying and caching data changes for server data changes
  115. * (applyServerOverwriteAtPath:, applyServerMergeAtPath:).
  116. * - Generating user-facing events for server and user changes (all of the
  117. * apply* methods return the set of events that need to be raised as a result).
  118. * - Maintaining the appropriate set of server listens to ensure we are always
  119. * subscribed to the correct set of paths and queries to satisfy the current set
  120. * of user event callbacks (listens are started/stopped using the provided
  121. * listenProvider).
  122. *
  123. * NOTE: Although SyncTree tracks event callbacks and calculates events to
  124. * raise, the actual events are returned to the caller rather than raised
  125. * synchronously.
  126. */
  127. @implementation FSyncTree
  128. - (id)initWithListenProvider:(FListenProvider *)provider {
  129. return [self initWithPersistenceManager:nil listenProvider:provider];
  130. }
  131. - (id)initWithPersistenceManager:(FPersistenceManager *)persistenceManager
  132. listenProvider:(FListenProvider *)provider {
  133. self = [super init];
  134. if (self) {
  135. self.syncPointTree = [FImmutableTree empty];
  136. self.pendingWriteTree = [[FWriteTree alloc] init];
  137. self.tagToQueryMap = [[NSMutableDictionary alloc] init];
  138. self.queryToTagMap = [[NSMutableDictionary alloc] init];
  139. self.listenProvider = provider;
  140. self.persistenceManager = persistenceManager;
  141. self.queryTagCounter = [[FAtomicNumber alloc] init];
  142. self.keepSyncedQueries = [NSMutableSet set];
  143. }
  144. return self;
  145. }
  146. - (NSNumber *)tagQuery:(FQuerySpec *)query {
  147. NSAssert(self.queryToTagMap[query] == nil,
  148. @"View does not exist, but we have a tag");
  149. NSNumber *tagId = [self.queryTagCounter getAndIncrement];
  150. self.queryToTagMap[query] = tagId;
  151. self.tagToQueryMap[tagId] = query;
  152. return tagId;
  153. }
  154. #pragma mark -
  155. #pragma mark Apply Operations
  156. /**
  157. * Apply data changes for a user-generated setValue: runTransactionBlock:
  158. * updateChildValues:, etc.
  159. * @return NSArray of FEvent to raise.
  160. */
  161. - (NSArray *)applyUserOverwriteAtPath:(FPath *)path
  162. newData:(id<FNode>)newData
  163. writeId:(NSInteger)writeId
  164. isVisible:(BOOL)visible {
  165. // Record pending write
  166. [self.pendingWriteTree addOverwriteAtPath:path
  167. newData:newData
  168. writeId:writeId
  169. isVisible:visible];
  170. if (!visible) {
  171. return @[];
  172. } else {
  173. FOverwrite *operation =
  174. [[FOverwrite alloc] initWithSource:[FOperationSource userInstance]
  175. path:path
  176. snap:newData];
  177. return [self applyOperationToSyncPoints:operation];
  178. }
  179. }
  180. /**
  181. * Apply the data from a user-generated updateChildValues: call
  182. * @return NSArray of FEvent to raise.
  183. */
  184. - (NSArray *)applyUserMergeAtPath:(FPath *)path
  185. changedChildren:(FCompoundWrite *)changedChildren
  186. writeId:(NSInteger)writeId {
  187. // Record pending merge
  188. [self.pendingWriteTree addMergeAtPath:path
  189. changedChildren:changedChildren
  190. writeId:writeId];
  191. FMerge *operation =
  192. [[FMerge alloc] initWithSource:[FOperationSource userInstance]
  193. path:path
  194. children:changedChildren];
  195. return [self applyOperationToSyncPoints:operation];
  196. }
  197. /**
  198. * Acknowledge a pending user write that was previously registered with
  199. * applyUserOverwriteAtPath: or applyUserMergeAtPath:
  200. * TODO[offline]: Taking a serverClock here is awkward, but server values are
  201. * awkward. :-(
  202. * @return NSArray of FEvent to raise.
  203. */
  204. - (NSArray *)ackUserWriteWithWriteId:(NSInteger)writeId
  205. revert:(BOOL)revert
  206. persist:(BOOL)persist
  207. clock:(id<FClock>)clock {
  208. FWriteRecord *write = [self.pendingWriteTree writeForId:writeId];
  209. BOOL needToReevaluate = [self.pendingWriteTree removeWriteId:writeId];
  210. if (write.visible) {
  211. if (persist) {
  212. [self.persistenceManager removeUserWrite:writeId];
  213. }
  214. if (!revert) {
  215. NSDictionary *serverValues =
  216. [FServerValues generateServerValues:clock];
  217. if ([write isOverwrite]) {
  218. id<FNode> resolvedNode =
  219. [FServerValues resolveDeferredValueSnapshot:write.overwrite
  220. withSyncTree:self
  221. atPath:write.path
  222. serverValues:serverValues];
  223. [self.persistenceManager applyUserWrite:resolvedNode
  224. toServerCacheAtPath:write.path];
  225. } else {
  226. FCompoundWrite *resolvedMerge = [FServerValues
  227. resolveDeferredValueCompoundWrite:write.merge
  228. withSyncTree:self
  229. atPath:write.path
  230. serverValues:serverValues];
  231. [self.persistenceManager applyUserMerge:resolvedMerge
  232. toServerCacheAtPath:write.path];
  233. }
  234. }
  235. }
  236. if (!needToReevaluate) {
  237. return @[];
  238. } else {
  239. __block FImmutableTree *affectedTree = [FImmutableTree empty];
  240. if (write.isOverwrite) {
  241. affectedTree = [affectedTree setValue:@YES atPath:[FPath empty]];
  242. } else {
  243. [write.merge
  244. enumerateWrites:^(FPath *path, id<FNode> node, BOOL *stop) {
  245. affectedTree = [affectedTree setValue:@YES atPath:path];
  246. }];
  247. }
  248. FAckUserWrite *operation =
  249. [[FAckUserWrite alloc] initWithPath:write.path
  250. affectedTree:affectedTree
  251. revert:revert];
  252. return [self applyOperationToSyncPoints:operation];
  253. }
  254. }
  255. /**
  256. * Apply new server data for the specified path
  257. * @return NSArray of FEvent to raise.
  258. */
  259. - (NSArray *)applyServerOverwriteAtPath:(FPath *)path
  260. newData:(id<FNode>)newData {
  261. [self.persistenceManager
  262. updateServerCacheWithNode:newData
  263. forQuery:[FQuerySpec defaultQueryAtPath:path]];
  264. FOverwrite *operation =
  265. [[FOverwrite alloc] initWithSource:[FOperationSource serverInstance]
  266. path:path
  267. snap:newData];
  268. return [self applyOperationToSyncPoints:operation];
  269. }
  270. /**
  271. * Applied new server data to be merged in at the specified path
  272. * @return NSArray of FEvent to raise.
  273. */
  274. - (NSArray *)applyServerMergeAtPath:(FPath *)path
  275. changedChildren:(FCompoundWrite *)changedChildren {
  276. [self.persistenceManager updateServerCacheWithMerge:changedChildren
  277. atPath:path];
  278. FMerge *operation =
  279. [[FMerge alloc] initWithSource:[FOperationSource serverInstance]
  280. path:path
  281. children:changedChildren];
  282. return [self applyOperationToSyncPoints:operation];
  283. }
  284. - (NSArray *)applyServerRangeMergeAtPath:(FPath *)path
  285. updates:(NSArray *)ranges {
  286. FSyncPoint *syncPoint = [self.syncPointTree valueAtPath:path];
  287. if (syncPoint == nil) {
  288. // Removed view, so it's safe to just ignore this update
  289. return @[];
  290. } else {
  291. // This could be for any "complete" (unfiltered) view, and if there is
  292. // more than one complete view, they should each have the same cache so
  293. // it doesn't matter which one we use.
  294. FView *view = [syncPoint completeView];
  295. if (view != nil) {
  296. id<FNode> serverNode = [view serverCache];
  297. for (FRangeMerge *merge in ranges) {
  298. serverNode = [merge applyToNode:serverNode];
  299. }
  300. return [self applyServerOverwriteAtPath:path newData:serverNode];
  301. } else {
  302. // There doesn't exist a view for this update, so it was removed and
  303. // it's safe to just ignore this range merge
  304. return @[];
  305. }
  306. }
  307. }
  308. /**
  309. * Apply a listen complete to a path
  310. * @return NSArray of FEvent to raise.
  311. */
  312. - (NSArray *)applyListenCompleteAtPath:(FPath *)path {
  313. [self.persistenceManager
  314. setQueryComplete:[FQuerySpec defaultQueryAtPath:path]];
  315. id<FOperation> operation = [[FListenComplete alloc]
  316. initWithSource:[FOperationSource serverInstance]
  317. path:path];
  318. return [self applyOperationToSyncPoints:operation];
  319. }
  320. /**
  321. * Apply a listen complete to a path
  322. * @return NSArray of FEvent to raise.
  323. */
  324. - (NSArray *)applyTaggedListenCompleteAtPath:(FPath *)path
  325. tagId:(NSNumber *)tagId {
  326. FQuerySpec *query = [self queryForTag:tagId];
  327. if (query != nil) {
  328. [self.persistenceManager setQueryComplete:query];
  329. FPath *relativePath = [FPath relativePathFrom:query.path to:path];
  330. id<FOperation> op = [[FListenComplete alloc]
  331. initWithSource:[FOperationSource forServerTaggedQuery:query.params]
  332. path:relativePath];
  333. return [self applyTaggedOperation:op atPath:query.path];
  334. } else {
  335. // We've already removed the query. No big deal, ignore the update.
  336. return @[];
  337. }
  338. }
  339. /**
  340. * Internal helper method to apply tagged operation
  341. */
  342. - (NSArray *)applyTaggedOperation:(id<FOperation>)operation
  343. atPath:(FPath *)path {
  344. FSyncPoint *syncPoint = [self.syncPointTree valueAtPath:path];
  345. NSAssert(syncPoint != nil,
  346. @"Missing sync point for query tag that we're tracking.");
  347. FWriteTreeRef *writesCache =
  348. [self.pendingWriteTree childWritesForPath:path];
  349. return [syncPoint applyOperation:operation
  350. writesCache:writesCache
  351. serverCache:nil];
  352. }
  353. /**
  354. * Apply new server data for the specified tagged query
  355. * @return NSArray of FEvent to raise.
  356. */
  357. - (NSArray *)applyTaggedQueryOverwriteAtPath:(FPath *)path
  358. newData:(id<FNode>)newData
  359. tagId:(NSNumber *)tagId {
  360. FQuerySpec *query = [self queryForTag:tagId];
  361. if (query != nil) {
  362. FPath *relativePath = [FPath relativePathFrom:query.path to:path];
  363. FQuerySpec *queryToOverwrite =
  364. relativePath.isEmpty ? query : [FQuerySpec defaultQueryAtPath:path];
  365. [self.persistenceManager updateServerCacheWithNode:newData
  366. forQuery:queryToOverwrite];
  367. FOverwrite *operation = [[FOverwrite alloc]
  368. initWithSource:[FOperationSource forServerTaggedQuery:query.params]
  369. path:relativePath
  370. snap:newData];
  371. return [self applyTaggedOperation:operation atPath:query.path];
  372. } else {
  373. // Query must have been removed already
  374. return @[];
  375. }
  376. }
  377. /**
  378. * Apply server data to be merged in for the specified tagged query
  379. * @return NSArray of FEvent to raise.
  380. */
  381. - (NSArray *)applyTaggedQueryMergeAtPath:(FPath *)path
  382. changedChildren:(FCompoundWrite *)changedChildren
  383. tagId:(NSNumber *)tagId {
  384. FQuerySpec *query = [self queryForTag:tagId];
  385. if (query != nil) {
  386. FPath *relativePath = [FPath relativePathFrom:query.path to:path];
  387. [self.persistenceManager updateServerCacheWithMerge:changedChildren
  388. atPath:path];
  389. FMerge *operation = [[FMerge alloc]
  390. initWithSource:[FOperationSource forServerTaggedQuery:query.params]
  391. path:relativePath
  392. children:changedChildren];
  393. return [self applyTaggedOperation:operation atPath:query.path];
  394. } else {
  395. // We've already removed the query. No big deal, ignore the update.
  396. return @[];
  397. }
  398. }
  399. - (NSArray *)applyTaggedServerRangeMergeAtPath:(FPath *)path
  400. updates:(NSArray *)ranges
  401. tagId:(NSNumber *)tagId {
  402. FQuerySpec *query = [self queryForTag:tagId];
  403. if (query != nil) {
  404. NSAssert([path isEqual:query.path],
  405. @"Tagged update path and query path must match");
  406. FSyncPoint *syncPoint = [self.syncPointTree valueAtPath:path];
  407. NSAssert(syncPoint != nil,
  408. @"Missing sync point for query tag that we're tracking.");
  409. FView *view = [syncPoint viewForQuery:query];
  410. NSAssert(view != nil,
  411. @"Missing view for query tag that we're tracking");
  412. id<FNode> serverNode = [view serverCache];
  413. for (FRangeMerge *merge in ranges) {
  414. serverNode = [merge applyToNode:serverNode];
  415. }
  416. return [self applyTaggedQueryOverwriteAtPath:path
  417. newData:serverNode
  418. tagId:tagId];
  419. } else {
  420. // We've already removed the query. No big deal, ignore the update.
  421. return @[];
  422. }
  423. }
  424. - (NSNumber *)registerQuery:(FQuerySpec *)query {
  425. FPath *path = query.path;
  426. __block BOOL foundAncestorDefaultView = NO;
  427. [self.syncPointTree
  428. forEachOnPath:query.path
  429. whileBlock:^BOOL(FPath *pathToSyncPoint, FSyncPoint *syncPoint) {
  430. foundAncestorDefaultView =
  431. foundAncestorDefaultView || [syncPoint hasCompleteView];
  432. return !foundAncestorDefaultView;
  433. }];
  434. [self.persistenceManager setQueryActive:query];
  435. FSyncPoint *syncPoint = [self.syncPointTree valueAtPath:path];
  436. if (syncPoint == nil) {
  437. syncPoint = [[FSyncPoint alloc]
  438. initWithPersistenceManager:self.persistenceManager];
  439. self.syncPointTree = [self.syncPointTree setValue:syncPoint
  440. atPath:path];
  441. }
  442. NSNumber *tag = nil;
  443. BOOL viewAlreadyExists = [syncPoint viewExistsForQuery:query];
  444. if (!viewAlreadyExists) {
  445. FWriteTreeRef *writesCache =
  446. [self.pendingWriteTree childWritesForPath:path];
  447. FCacheNode *serverCache = [self serverCacheForQuery:query];
  448. [syncPoint registerQuery:query
  449. writesCache:writesCache
  450. serverCache:serverCache];
  451. } else if (![query loadsAllData]) {
  452. tag = [self tagQuery:query];
  453. }
  454. return tag;
  455. }
  456. /**
  457. * Add an event callback for the specified query
  458. * @return NSArray of FEvent to raise.
  459. */
  460. - (NSArray *)addEventRegistration:(id<FEventRegistration>)eventRegistration
  461. forQuery:(FQuerySpec *)query {
  462. FPath *path = query.path;
  463. __block BOOL foundAncestorDefaultView = NO;
  464. [self.syncPointTree
  465. forEachOnPath:query.path
  466. whileBlock:^BOOL(FPath *pathToSyncPoint, FSyncPoint *syncPoint) {
  467. foundAncestorDefaultView =
  468. foundAncestorDefaultView || [syncPoint hasCompleteView];
  469. return !foundAncestorDefaultView;
  470. }];
  471. [self.persistenceManager setQueryActive:query];
  472. FSyncPoint *syncPoint = [self.syncPointTree valueAtPath:path];
  473. if (syncPoint == nil) {
  474. syncPoint = [[FSyncPoint alloc]
  475. initWithPersistenceManager:self.persistenceManager];
  476. self.syncPointTree = [self.syncPointTree setValue:syncPoint
  477. atPath:path];
  478. }
  479. BOOL viewAlreadyExists = [syncPoint viewExistsForQuery:query];
  480. NSArray *events;
  481. if (viewAlreadyExists) {
  482. events = [syncPoint addEventRegistration:eventRegistration
  483. forExistingViewForQuery:query];
  484. } else {
  485. if (![query loadsAllData]) {
  486. // We need to track a tag for this query
  487. NSAssert(self.queryToTagMap[query] == nil,
  488. @"View does not exist, but we have a tag");
  489. NSNumber *tagId = [self.queryTagCounter getAndIncrement];
  490. self.queryToTagMap[query] = tagId;
  491. self.tagToQueryMap[tagId] = query;
  492. }
  493. FWriteTreeRef *writesCache =
  494. [self.pendingWriteTree childWritesForPath:path];
  495. FCacheNode *serverCache = [self serverCacheForQuery:query];
  496. events = [syncPoint addEventRegistration:eventRegistration
  497. forNonExistingViewForQuery:query
  498. writesCache:writesCache
  499. serverCache:serverCache];
  500. // There was no view and no default listen
  501. if (!foundAncestorDefaultView) {
  502. FView *view = [syncPoint viewForQuery:query];
  503. NSMutableArray *mutableEvents = [events mutableCopy];
  504. [mutableEvents
  505. addObjectsFromArray:[self setupListenerOnQuery:query
  506. view:view]];
  507. events = mutableEvents;
  508. }
  509. }
  510. return events;
  511. }
  512. - (FCacheNode *)serverCacheForQuery:(FQuerySpec *)query {
  513. __block id<FNode> serverCacheNode = nil;
  514. [self.syncPointTree
  515. forEachOnPath:query.path
  516. whileBlock:^BOOL(FPath *pathToSyncPoint, FSyncPoint *syncPoint) {
  517. FPath *relativePath = [FPath relativePathFrom:pathToSyncPoint
  518. to:query.path];
  519. serverCacheNode =
  520. [syncPoint completeServerCacheAtPath:relativePath];
  521. return serverCacheNode == nil;
  522. }];
  523. FCacheNode *serverCache;
  524. if (serverCacheNode != nil) {
  525. FIndexedNode *indexed =
  526. [FIndexedNode indexedNodeWithNode:serverCacheNode
  527. index:query.index];
  528. serverCache = [[FCacheNode alloc] initWithIndexedNode:indexed
  529. isFullyInitialized:YES
  530. isFiltered:NO];
  531. } else {
  532. FCacheNode *persistenceServerCache =
  533. [self.persistenceManager serverCacheForQuery:query];
  534. if (persistenceServerCache.isFullyInitialized) {
  535. serverCache = persistenceServerCache;
  536. } else {
  537. serverCacheNode = [FEmptyNode emptyNode];
  538. FImmutableTree *subtree =
  539. [self.syncPointTree subtreeAtPath:query.path];
  540. [subtree
  541. forEachChild:^(NSString *childKey, FSyncPoint *childSyncPoint) {
  542. id<FNode> completeCache =
  543. [childSyncPoint completeServerCacheAtPath:[FPath empty]];
  544. if (completeCache) {
  545. serverCacheNode =
  546. [serverCacheNode updateImmediateChild:childKey
  547. withNewChild:completeCache];
  548. }
  549. }];
  550. // Fill the node with any available children we have
  551. [persistenceServerCache.node
  552. enumerateChildrenUsingBlock:^(NSString *key, id<FNode> node,
  553. BOOL *stop) {
  554. if (![serverCacheNode hasChild:key]) {
  555. serverCacheNode =
  556. [serverCacheNode updateImmediateChild:key
  557. withNewChild:node];
  558. }
  559. }];
  560. FIndexedNode *indexed =
  561. [FIndexedNode indexedNodeWithNode:serverCacheNode
  562. index:query.index];
  563. serverCache = [[FCacheNode alloc] initWithIndexedNode:indexed
  564. isFullyInitialized:NO
  565. isFiltered:NO];
  566. }
  567. }
  568. return serverCache;
  569. }
  570. - (void)unregisterQuery:(FQuerySpec *)query {
  571. FPath *path = query.path;
  572. FSyncPoint *maybeSyncPoint = [self.syncPointTree valueAtPath:path];
  573. if (maybeSyncPoint &&
  574. ([query isDefault] || [maybeSyncPoint viewExistsForQuery:query])) {
  575. BOOL removed = [maybeSyncPoint unregisterQuery:query];
  576. if ([maybeSyncPoint isEmpty]) {
  577. self.syncPointTree = [self.syncPointTree removeValueAtPath:path];
  578. }
  579. if (removed) {
  580. [self removeTags:@[ query ]];
  581. [self.persistenceManager setQueryInactive:query];
  582. }
  583. }
  584. }
  585. /**
  586. * Remove event callback(s).
  587. *
  588. * If query is the default query, we'll check all queries for the specified
  589. * eventRegistration. If eventRegistration is null, we'll remove all callbacks
  590. * for the specified query/queries.
  591. *
  592. * @param eventRegistration if nil, all callbacks are removed
  593. * @param cancelError If provided, appropriate cancel events will be returned
  594. * @return NSArray of FEvent to raise.
  595. */
  596. - (NSArray *)removeEventRegistration:(id<FEventRegistration>)eventRegistration
  597. forQuery:(FQuerySpec *)query
  598. cancelError:(NSError *)cancelError {
  599. // Find the syncPoint first. Then deal with whether or not it has matching
  600. // listeners
  601. FPath *path = query.path;
  602. FSyncPoint *maybeSyncPoint = [self.syncPointTree valueAtPath:path];
  603. NSArray *cancelEvents = @[];
  604. // A removal on a default query affects all queries at that location. A
  605. // removal on an indexed query, even one without other query constraints,
  606. // does *not* affect all queries at that location. So this check must be for
  607. // 'default', and not loadsAllData:
  608. if (maybeSyncPoint &&
  609. ([query isDefault] || [maybeSyncPoint viewExistsForQuery:query])) {
  610. FTupleRemovedQueriesEvents *removedAndEvents =
  611. [maybeSyncPoint removeEventRegistration:eventRegistration
  612. forQuery:query
  613. cancelError:cancelError];
  614. if ([maybeSyncPoint isEmpty]) {
  615. self.syncPointTree = [self.syncPointTree removeValueAtPath:path];
  616. }
  617. NSArray *removed = removedAndEvents.removedQueries;
  618. cancelEvents = removedAndEvents.cancelEvents;
  619. // We may have just removed one of many listeners and can short-circuit
  620. // this whole process We may also not have removed a default listener,
  621. // in which case all of the descendant listeners should already be
  622. // properly set up.
  623. //
  624. // Since indexed queries can shadow if they don't have other query
  625. // constraints, check for loadsAllData: instead of isDefault:
  626. NSUInteger defaultQueryIndex = [removed
  627. indexOfObjectPassingTest:^BOOL(FQuerySpec *q, NSUInteger idx,
  628. BOOL *stop) {
  629. return [q loadsAllData];
  630. }];
  631. BOOL removingDefault = defaultQueryIndex != NSNotFound;
  632. [removed enumerateObjectsUsingBlock:^(FQuerySpec *query, NSUInteger idx,
  633. BOOL *stop) {
  634. [self.persistenceManager setQueryInactive:query];
  635. }];
  636. NSNumber *covered = [self.syncPointTree
  637. findOnPath:path
  638. andApplyBlock:^id(FPath *relativePath,
  639. FSyncPoint *parentSyncPoint) {
  640. return
  641. [NSNumber numberWithBool:[parentSyncPoint hasCompleteView]];
  642. }];
  643. if (removingDefault && ![covered boolValue]) {
  644. FImmutableTree *subtree = [self.syncPointTree subtreeAtPath:path];
  645. // There are potentially child listeners. Determine what if any
  646. // listens we need to send before executing the removal
  647. if (![subtree isEmpty]) {
  648. // We need to fold over our subtree and collect the listeners to
  649. // send
  650. NSArray *newViews =
  651. [self collectDistinctViewsForSubTree:subtree];
  652. // Ok, we've collected all the listens we need. Set them up.
  653. [newViews enumerateObjectsUsingBlock:^(
  654. FView *view, NSUInteger idx, BOOL *stop) {
  655. FQuerySpec *newQuery = view.query;
  656. FListenContainer *listenContainer =
  657. [self createListenerForView:view];
  658. self.listenProvider.startListening(
  659. [self queryForListening:newQuery],
  660. [self tagForQuery:newQuery], listenContainer,
  661. listenContainer.onComplete);
  662. }];
  663. } else {
  664. // There's nothing below us, so nothing we need to start
  665. // listening on
  666. }
  667. }
  668. // If we removed anything and we're not covered by a higher up listen,
  669. // we need to stop listening on this query. The above block has us
  670. // covered in terms of making sure we're set up on listens lower in the
  671. // tree. Also, note that if we have a cancelError, it's already been
  672. // removed at the provider level.
  673. if (![covered boolValue] && [removed count] > 0 && cancelError == nil) {
  674. // If we removed a default, then we weren't listening on any of the
  675. // other queries here. Just cancel the one default. Otherwise, we
  676. // need to iterate through and cancel each individual query
  677. if (removingDefault) {
  678. // We don't tag default listeners
  679. self.listenProvider.stopListening(
  680. [self queryForListening:query], nil);
  681. } else {
  682. [removed
  683. enumerateObjectsUsingBlock:^(FQuerySpec *queryToRemove,
  684. NSUInteger idx, BOOL *stop) {
  685. NSNumber *tagToRemove =
  686. [self.queryToTagMap objectForKey:queryToRemove];
  687. self.listenProvider.stopListening(
  688. [self queryForListening:queryToRemove], tagToRemove);
  689. }];
  690. }
  691. }
  692. // Now, clear all the tags we're tracking for the removed listens.
  693. [self removeTags:removed];
  694. } else {
  695. // No-op, this listener must've been already removed
  696. }
  697. return cancelEvents;
  698. }
  699. - (void)keepQuery:(FQuerySpec *)query synced:(BOOL)keepSynced {
  700. // Only do something if we actually need to add/remove an event registration
  701. if (keepSynced && ![self.keepSyncedQueries containsObject:query]) {
  702. [self addEventRegistration:[FKeepSyncedEventRegistration instance]
  703. forQuery:query];
  704. [self.keepSyncedQueries addObject:query];
  705. } else if (!keepSynced && [self.keepSyncedQueries containsObject:query]) {
  706. [self removeEventRegistration:[FKeepSyncedEventRegistration instance]
  707. forQuery:query
  708. cancelError:nil];
  709. [self.keepSyncedQueries removeObject:query];
  710. }
  711. }
  712. - (NSArray *)removeAllWrites {
  713. [self.persistenceManager removeAllUserWrites];
  714. NSArray *removedWrites = [self.pendingWriteTree removeAllWrites];
  715. if (removedWrites.count > 0) {
  716. FImmutableTree *affectedTree =
  717. [[FImmutableTree empty] setValue:@YES atPath:[FPath empty]];
  718. return [self applyOperationToSyncPoints:[[FAckUserWrite alloc]
  719. initWithPath:[FPath empty]
  720. affectedTree:affectedTree
  721. revert:YES]];
  722. } else {
  723. return @[];
  724. }
  725. }
  726. /** Returns a non-empty cache node if one exists. Otherwise returns null. */
  727. - (FIndexedNode *)persistenceServerCache:(FQuerySpec *)querySpec {
  728. FCacheNode *cacheNode =
  729. [self.persistenceManager serverCacheForQuery:querySpec];
  730. if (cacheNode == nil || cacheNode.node.isEmpty) {
  731. return nil;
  732. }
  733. return cacheNode.indexedNode;
  734. }
  735. - (id<FNode>)getServerValue:(FQuerySpec *)query {
  736. __block id<FNode> serverCacheNode = nil;
  737. __block FSyncPoint *targetSyncPoint = nil;
  738. [self.syncPointTree
  739. forEachOnPath:query.path
  740. whileBlock:^BOOL(FPath *pathToSyncPoint, FSyncPoint *syncPoint) {
  741. FPath *relativePath = [FPath relativePathFrom:pathToSyncPoint
  742. to:query.path];
  743. serverCacheNode =
  744. [syncPoint completeEventCacheAtPath:relativePath];
  745. targetSyncPoint = syncPoint;
  746. return serverCacheNode == nil;
  747. }];
  748. if (targetSyncPoint == nil) {
  749. targetSyncPoint = [[FSyncPoint alloc]
  750. initWithPersistenceManager:self.persistenceManager];
  751. self.syncPointTree = [self.syncPointTree setValue:targetSyncPoint
  752. atPath:[query path]];
  753. } else {
  754. serverCacheNode =
  755. serverCacheNode != nil
  756. ? serverCacheNode
  757. : [targetSyncPoint completeServerCacheAtPath:[FPath empty]];
  758. }
  759. FIndexedNode *indexed = [FIndexedNode
  760. indexedNodeWithNode:serverCacheNode != nil ? serverCacheNode
  761. : [FEmptyNode emptyNode]
  762. index:query.index];
  763. FCacheNode *serverCache =
  764. [[FCacheNode alloc] initWithIndexedNode:indexed
  765. isFullyInitialized:serverCacheNode != nil
  766. isFiltered:NO];
  767. FView *view = [targetSyncPoint
  768. getView:query
  769. writesCache:[_pendingWriteTree childWritesForPath:[query path]]
  770. serverCache:serverCache];
  771. return [view completeEventCache];
  772. }
  773. /**
  774. * Returns a complete cache, if we have one, of the data at a particular path.
  775. * The location must have a listener above it, but as this is only used by
  776. * transaction code, that should always be the case anyways.
  777. *
  778. * Note: this method will *include* hidden writes from transaction with
  779. * applyLocally set to false.
  780. * @param path The path to the data we want
  781. * @param writeIdsToExclude A specific set to be excluded
  782. */
  783. - (id<FNode>)calcCompleteEventCacheAtPath:(FPath *)path
  784. excludeWriteIds:(NSArray *)writeIdsToExclude {
  785. BOOL includeHiddenSets = YES;
  786. FWriteTree *writeTree = self.pendingWriteTree;
  787. id<FNode> serverCache = [self.syncPointTree
  788. findOnPath:path
  789. andApplyBlock:^id<FNode>(FPath *pathSoFar, FSyncPoint *syncPoint) {
  790. FPath *relativePath = [FPath relativePathFrom:pathSoFar to:path];
  791. id<FNode> serverCache =
  792. [syncPoint completeServerCacheAtPath:relativePath];
  793. if (serverCache) {
  794. return serverCache;
  795. } else {
  796. return nil;
  797. }
  798. }];
  799. return [writeTree calculateCompleteEventCacheAtPath:path
  800. completeServerCache:serverCache
  801. excludeWriteIds:writeIdsToExclude
  802. includeHiddenWrites:includeHiddenSets];
  803. }
  804. #pragma mark -
  805. #pragma mark Private Methods
  806. /**
  807. * This collapses multiple unfiltered views into a single view, since we only
  808. * need a single listener for them.
  809. * @return NSArray of FView
  810. */
  811. - (NSArray *)collectDistinctViewsForSubTree:(FImmutableTree *)subtree {
  812. return [subtree foldWithBlock:^NSArray *(FPath *relativePath,
  813. FSyncPoint *maybeChildSyncPoint,
  814. NSDictionary *childMap) {
  815. if (maybeChildSyncPoint && [maybeChildSyncPoint hasCompleteView]) {
  816. FView *completeView = [maybeChildSyncPoint completeView];
  817. return @[ completeView ];
  818. } else {
  819. // No complete view here, flatten any deeper listens into an array
  820. NSMutableArray *views = [[NSMutableArray alloc] init];
  821. if (maybeChildSyncPoint) {
  822. views = [[maybeChildSyncPoint queryViews] mutableCopy];
  823. }
  824. [childMap enumerateKeysAndObjectsUsingBlock:^(
  825. NSString *childKey, NSArray *childViews, BOOL *stop) {
  826. [views addObjectsFromArray:childViews];
  827. }];
  828. return views;
  829. }
  830. }];
  831. }
  832. /**
  833. * @param queries NSArray of FQuerySpec
  834. */
  835. - (void)removeTags:(NSArray *)queries {
  836. [queries enumerateObjectsUsingBlock:^(FQuerySpec *removedQuery,
  837. NSUInteger idx, BOOL *stop) {
  838. if (![removedQuery loadsAllData]) {
  839. // We should have a tag for this
  840. NSNumber *removedQueryTag = self.queryToTagMap[removedQuery];
  841. [self.queryToTagMap removeObjectForKey:removedQuery];
  842. [self.tagToQueryMap removeObjectForKey:removedQueryTag];
  843. }
  844. }];
  845. }
  846. - (FQuerySpec *)queryForListening:(FQuerySpec *)query {
  847. if (query.loadsAllData && !query.isDefault) {
  848. // We treat queries that load all data as default queries
  849. return [FQuerySpec defaultQueryAtPath:query.path];
  850. } else {
  851. return query;
  852. }
  853. }
  854. /**
  855. * For a given new listen, manage the de-duplication of outstanding
  856. * subscriptions.
  857. * @return NSArray of FEvent events to support synchronous data sources
  858. */
  859. - (NSArray *)setupListenerOnQuery:(FQuerySpec *)query view:(FView *)view {
  860. FPath *path = query.path;
  861. NSNumber *tagId = [self tagForQuery:query];
  862. FListenContainer *listenContainer = [self createListenerForView:view];
  863. NSArray *events = self.listenProvider.startListening(
  864. [self queryForListening:query], tagId, listenContainer,
  865. listenContainer.onComplete);
  866. FImmutableTree *subtree = [self.syncPointTree subtreeAtPath:path];
  867. // The root of this subtree has our query. We're here because we definitely
  868. // need to send a listen for that, but we may need to shadow other listens
  869. // as well.
  870. if (tagId != nil) {
  871. NSAssert(![subtree.value hasCompleteView],
  872. @"If we're adding a query, it shouldn't be shadowed");
  873. } else {
  874. // Shadow everything at or below this location, this is a default
  875. // listener.
  876. NSArray *queriesToStop =
  877. [subtree foldWithBlock:^id(FPath *relativePath,
  878. FSyncPoint *maybeChildSyncPoint,
  879. NSDictionary *childMap) {
  880. if (![relativePath isEmpty] && maybeChildSyncPoint != nil &&
  881. [maybeChildSyncPoint hasCompleteView]) {
  882. return @[ [maybeChildSyncPoint completeView].query ];
  883. } else {
  884. // No default listener here, flatten any deeper queries into
  885. // an array
  886. NSMutableArray *queries = [[NSMutableArray alloc] init];
  887. if (maybeChildSyncPoint != nil) {
  888. for (FView *view in [maybeChildSyncPoint queryViews]) {
  889. [queries addObject:view.query];
  890. }
  891. }
  892. [childMap
  893. enumerateKeysAndObjectsUsingBlock:^(
  894. NSString *key, NSArray *childQueries, BOOL *stop) {
  895. [queries addObjectsFromArray:childQueries];
  896. }];
  897. return queries;
  898. }
  899. }];
  900. for (FQuerySpec *queryToStop in queriesToStop) {
  901. self.listenProvider.stopListening(
  902. [self queryForListening:queryToStop],
  903. [self tagForQuery:queryToStop]);
  904. }
  905. }
  906. return events;
  907. }
  908. - (FListenContainer *)createListenerForView:(FView *)view {
  909. FQuerySpec *query = view.query;
  910. NSNumber *tagId = [self tagForQuery:query];
  911. FListenContainer *listenContainer = [[FListenContainer alloc]
  912. initWithView:view
  913. onComplete:^(NSString *status) {
  914. if ([status isEqualToString:@"ok"]) {
  915. if (tagId != nil) {
  916. return [self applyTaggedListenCompleteAtPath:query.path
  917. tagId:tagId];
  918. } else {
  919. return [self applyListenCompleteAtPath:query.path];
  920. }
  921. } else {
  922. // If a listen failed, kill all of the listeners here, not just
  923. // the one that triggered the error. Note that this may need to
  924. // be scoped to just this listener if we change permissions on
  925. // filtered children
  926. NSError *error = [FUtilities errorForStatus:status
  927. andReason:nil];
  928. FFWarn(@"I-RDB038012", @"Listener at %@ failed: %@", query.path,
  929. status);
  930. return [self removeEventRegistration:nil
  931. forQuery:query
  932. cancelError:error];
  933. }
  934. }];
  935. return listenContainer;
  936. }
  937. /**
  938. * @return The query associated with the given tag, if we have one
  939. */
  940. - (FQuerySpec *)queryForTag:(NSNumber *)tagId {
  941. return self.tagToQueryMap[tagId];
  942. }
  943. /**
  944. * @return The tag associated with the given query
  945. */
  946. - (NSNumber *)tagForQuery:(FQuerySpec *)query {
  947. return self.queryToTagMap[query];
  948. }
  949. #pragma mark -
  950. #pragma mark applyOperation Helpers
  951. /**
  952. * A helper method that visits all descendant and ancestor SyncPoints, applying
  953. the operation.
  954. *
  955. * NOTES:
  956. * - Descendant SyncPoints will be visited first (since we raise events
  957. depth-first).
  958. * - We call applyOperation: on each SyncPoint passing three things:
  959. * 1. A version of the Operation that has been made relative to the SyncPoint
  960. location.
  961. * 2. A WriteTreeRef of any writes we have cached at the SyncPoint location.
  962. * 3. A snapshot Node with cached server data, if we have it.
  963. * - We concatenate all of the events returned by each SyncPoint and return the
  964. result.
  965. *
  966. * @return Array of FEvent
  967. */
  968. - (NSArray *)applyOperationToSyncPoints:(id<FOperation>)operation {
  969. return [self applyOperationHelper:operation
  970. syncPointTree:self.syncPointTree
  971. serverCache:nil
  972. writesCache:[self.pendingWriteTree
  973. childWritesForPath:[FPath empty]]];
  974. }
  975. /**
  976. * Recursive helper for applyOperationToSyncPoints_
  977. */
  978. - (NSArray *)applyOperationHelper:(id<FOperation>)operation
  979. syncPointTree:(FImmutableTree *)syncPointTree
  980. serverCache:(id<FNode>)serverCache
  981. writesCache:(FWriteTreeRef *)writesCache {
  982. if ([operation.path isEmpty]) {
  983. return [self applyOperationDescendantsHelper:operation
  984. syncPointTree:syncPointTree
  985. serverCache:serverCache
  986. writesCache:writesCache];
  987. } else {
  988. FSyncPoint *syncPoint = syncPointTree.value;
  989. // If we don't have cached server data, see if we can get it from this
  990. // SyncPoint
  991. if (serverCache == nil && syncPoint != nil) {
  992. serverCache = [syncPoint completeServerCacheAtPath:[FPath empty]];
  993. }
  994. NSMutableArray *events = [[NSMutableArray alloc] init];
  995. NSString *childKey = [operation.path getFront];
  996. id<FOperation> childOperation = [operation operationForChild:childKey];
  997. FImmutableTree *childTree = [syncPointTree.children get:childKey];
  998. if (childTree != nil && childOperation != nil) {
  999. id<FNode> childServerCache =
  1000. serverCache ? [serverCache getImmediateChild:childKey] : nil;
  1001. FWriteTreeRef *childWritesCache =
  1002. [writesCache childWriteTreeRef:childKey];
  1003. [events
  1004. addObjectsFromArray:[self
  1005. applyOperationHelper:childOperation
  1006. syncPointTree:childTree
  1007. serverCache:childServerCache
  1008. writesCache:childWritesCache]];
  1009. }
  1010. if (syncPoint) {
  1011. [events addObjectsFromArray:[syncPoint applyOperation:operation
  1012. writesCache:writesCache
  1013. serverCache:serverCache]];
  1014. }
  1015. return events;
  1016. }
  1017. }
  1018. /**
  1019. * Recursive helper for applyOperationToSyncPoints:
  1020. */
  1021. - (NSArray *)applyOperationDescendantsHelper:(id<FOperation>)operation
  1022. syncPointTree:(FImmutableTree *)syncPointTree
  1023. serverCache:(id<FNode>)serverCache
  1024. writesCache:(FWriteTreeRef *)writesCache {
  1025. FSyncPoint *syncPoint = syncPointTree.value;
  1026. // If we don't have cached server data, see if we can get it from this
  1027. // SyncPoint
  1028. id<FNode> resolvedServerCache;
  1029. if (serverCache == nil & syncPoint != nil) {
  1030. resolvedServerCache =
  1031. [syncPoint completeServerCacheAtPath:[FPath empty]];
  1032. } else {
  1033. resolvedServerCache = serverCache;
  1034. }
  1035. NSMutableArray *events = [[NSMutableArray alloc] init];
  1036. [syncPointTree.children enumerateKeysAndObjectsUsingBlock:^(
  1037. NSString *childKey, FImmutableTree *childTree,
  1038. BOOL *stop) {
  1039. id<FNode> childServerCache = nil;
  1040. if (resolvedServerCache != nil) {
  1041. childServerCache = [resolvedServerCache getImmediateChild:childKey];
  1042. }
  1043. FWriteTreeRef *childWritesCache =
  1044. [writesCache childWriteTreeRef:childKey];
  1045. id<FOperation> childOperation = [operation operationForChild:childKey];
  1046. if (childOperation != nil) {
  1047. [events addObjectsFromArray:
  1048. [self applyOperationDescendantsHelper:childOperation
  1049. syncPointTree:childTree
  1050. serverCache:childServerCache
  1051. writesCache:childWritesCache]];
  1052. }
  1053. }];
  1054. if (syncPoint) {
  1055. [events
  1056. addObjectsFromArray:[syncPoint applyOperation:operation
  1057. writesCache:writesCache
  1058. serverCache:resolvedServerCache]];
  1059. }
  1060. return events;
  1061. }
  1062. @end