FSTRemoteEvent.mm 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648
  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 "Firestore/Source/Remote/FSTRemoteEvent.h"
  17. #include <map>
  18. #include <utility>
  19. #import "Firestore/Source/Local/FSTQueryData.h"
  20. #import "Firestore/Source/Model/FSTDocument.h"
  21. #import "Firestore/Source/Remote/FSTWatchChange.h"
  22. #import "Firestore/Source/Util/FSTClasses.h"
  23. #include "Firestore/core/src/firebase/firestore/model/document_key.h"
  24. #include "Firestore/core/src/firebase/firestore/util/hard_assert.h"
  25. #include "Firestore/core/src/firebase/firestore/util/hashing.h"
  26. #include "Firestore/core/src/firebase/firestore/util/log.h"
  27. using firebase::firestore::model::DocumentKey;
  28. using firebase::firestore::model::SnapshotVersion;
  29. using firebase::firestore::util::Hash;
  30. using firebase::firestore::model::DocumentKeySet;
  31. NS_ASSUME_NONNULL_BEGIN
  32. #pragma mark - FSTTargetMapping
  33. @interface FSTTargetMapping ()
  34. /** Private mutator method to add a document key to the mapping */
  35. - (void)addDocumentKey:(const DocumentKey &)documentKey;
  36. /** Private mutator method to remove a document key from the mapping */
  37. - (void)removeDocumentKey:(const DocumentKey &)documentKey;
  38. @end
  39. @implementation FSTTargetMapping
  40. - (void)addDocumentKey:(const DocumentKey &)documentKey {
  41. @throw FSTAbstractMethodException(); // NOLINT
  42. }
  43. - (void)removeDocumentKey:(const DocumentKey &)documentKey {
  44. @throw FSTAbstractMethodException(); // NOLINT
  45. }
  46. - (void)filterUpdatesUsingExistingKeys:(const DocumentKeySet &)existingKeys {
  47. @throw FSTAbstractMethodException(); // NOLINT
  48. }
  49. @end
  50. #pragma mark - FSTResetMapping
  51. @implementation FSTResetMapping {
  52. DocumentKeySet _documents;
  53. }
  54. + (instancetype)mappingWithDocuments:(NSArray<FSTDocument *> *)documents {
  55. DocumentKeySet keys;
  56. for (FSTDocument *doc in documents) {
  57. keys = keys.insert(doc.key);
  58. }
  59. return [[FSTResetMapping alloc] initWithDocuments:std::move(keys)];
  60. }
  61. - (instancetype)initWithDocuments:(DocumentKeySet)documents {
  62. self = [super init];
  63. if (self) {
  64. _documents = std::move(documents);
  65. }
  66. return self;
  67. }
  68. - (const DocumentKeySet &)documents {
  69. return _documents;
  70. }
  71. - (BOOL)isEqual:(id)other {
  72. if (other == self) {
  73. return YES;
  74. }
  75. if (![other isMemberOfClass:[FSTResetMapping class]]) {
  76. return NO;
  77. }
  78. FSTResetMapping *otherMapping = (FSTResetMapping *)other;
  79. return _documents == otherMapping.documents;
  80. }
  81. - (NSUInteger)hash {
  82. return Hash(_documents);
  83. }
  84. - (void)addDocumentKey:(const DocumentKey &)documentKey {
  85. _documents = _documents.insert(documentKey);
  86. }
  87. - (void)removeDocumentKey:(const DocumentKey &)documentKey {
  88. _documents = _documents.erase(documentKey);
  89. }
  90. - (void)filterUpdatesUsingExistingKeys:(const DocumentKeySet &)existingKeys {
  91. // No-op. Resets are not filtered.
  92. }
  93. @end
  94. #pragma mark - FSTUpdateMapping
  95. @implementation FSTUpdateMapping {
  96. DocumentKeySet _addedDocuments;
  97. DocumentKeySet _removedDocuments;
  98. }
  99. + (FSTUpdateMapping *)mappingWithAddedDocuments:(NSArray<FSTDocument *> *)added
  100. removedDocuments:(NSArray<FSTDocument *> *)removed {
  101. DocumentKeySet addedDocuments;
  102. DocumentKeySet removedDocuments;
  103. for (FSTDocument *doc in added) {
  104. addedDocuments = addedDocuments.insert(doc.key);
  105. }
  106. for (FSTDocument *doc in removed) {
  107. removedDocuments = removedDocuments.insert(doc.key);
  108. }
  109. return [[FSTUpdateMapping alloc] initWithAddedDocuments:std::move(addedDocuments)
  110. removedDocuments:std::move(removedDocuments)];
  111. }
  112. - (instancetype)initWithAddedDocuments:(DocumentKeySet)addedDocuments
  113. removedDocuments:(DocumentKeySet)removedDocuments {
  114. self = [super init];
  115. if (self) {
  116. _addedDocuments = std::move(addedDocuments);
  117. _removedDocuments = std::move(removedDocuments);
  118. }
  119. return self;
  120. }
  121. - (const DocumentKeySet &)addedDocuments {
  122. return _addedDocuments;
  123. }
  124. - (const DocumentKeySet &)removedDocuments {
  125. return _removedDocuments;
  126. }
  127. - (BOOL)isEqual:(id)other {
  128. if (other == self) {
  129. return YES;
  130. }
  131. if (![other isMemberOfClass:[FSTUpdateMapping class]]) {
  132. return NO;
  133. }
  134. FSTUpdateMapping *otherMapping = (FSTUpdateMapping *)other;
  135. return _addedDocuments == otherMapping.addedDocuments &&
  136. _removedDocuments == otherMapping.removedDocuments;
  137. }
  138. - (NSUInteger)hash {
  139. return Hash(_addedDocuments, _removedDocuments);
  140. }
  141. - (DocumentKeySet)applyTo:(const DocumentKeySet &)keys {
  142. DocumentKeySet result = keys;
  143. for (const DocumentKey &key : _addedDocuments) {
  144. result = result.insert(key);
  145. }
  146. for (const DocumentKey &key : _removedDocuments) {
  147. result = result.erase(key);
  148. }
  149. return result;
  150. }
  151. - (void)addDocumentKey:(const DocumentKey &)documentKey {
  152. _addedDocuments = _addedDocuments.insert(documentKey);
  153. _removedDocuments = _removedDocuments.erase(documentKey);
  154. }
  155. - (void)removeDocumentKey:(const DocumentKey &)documentKey {
  156. _addedDocuments = _addedDocuments.erase(documentKey);
  157. _removedDocuments = _removedDocuments.insert(documentKey);
  158. }
  159. - (void)filterUpdatesUsingExistingKeys:(const DocumentKeySet &)existingKeys {
  160. DocumentKeySet result = _addedDocuments;
  161. for (const DocumentKey &key : _addedDocuments) {
  162. if (existingKeys.contains(key)) {
  163. result = result.erase(key);
  164. }
  165. }
  166. _addedDocuments = result;
  167. }
  168. @end
  169. #pragma mark - FSTTargetChange
  170. @interface FSTTargetChange ()
  171. @property(nonatomic, assign) FSTCurrentStatusUpdate currentStatusUpdate;
  172. @property(nonatomic, strong, nullable) FSTTargetMapping *mapping;
  173. @property(nonatomic, strong) NSData *resumeToken;
  174. @end
  175. @implementation FSTTargetChange {
  176. SnapshotVersion _snapshotVersion;
  177. }
  178. - (instancetype)init {
  179. if (self = [super init]) {
  180. _currentStatusUpdate = FSTCurrentStatusUpdateNone;
  181. _resumeToken = [NSData data];
  182. }
  183. return self;
  184. }
  185. - (instancetype)initWithSnapshotVersion:(SnapshotVersion)snapshotVersion {
  186. if (self = [self init]) {
  187. _snapshotVersion = std::move(snapshotVersion);
  188. }
  189. return self;
  190. }
  191. - (const SnapshotVersion &)snapshotVersion {
  192. return _snapshotVersion;
  193. }
  194. + (instancetype)changeWithDocuments:(NSArray<FSTMaybeDocument *> *)docs
  195. currentStatusUpdate:(FSTCurrentStatusUpdate)currentStatusUpdate {
  196. DocumentKeySet addedDocuments;
  197. DocumentKeySet removedDocuments;
  198. for (FSTMaybeDocument *doc in docs) {
  199. if ([doc isKindOfClass:[FSTDeletedDocument class]]) {
  200. removedDocuments = removedDocuments.insert(doc.key);
  201. } else {
  202. addedDocuments = addedDocuments.insert(doc.key);
  203. }
  204. }
  205. FSTUpdateMapping *mapping =
  206. [[FSTUpdateMapping alloc] initWithAddedDocuments:std::move(addedDocuments)
  207. removedDocuments:std::move(removedDocuments)];
  208. FSTTargetChange *change = [[FSTTargetChange alloc] init];
  209. change.mapping = mapping;
  210. change.currentStatusUpdate = currentStatusUpdate;
  211. return change;
  212. }
  213. + (instancetype)changeWithMapping:(FSTTargetMapping *)mapping
  214. snapshotVersion:(SnapshotVersion)snapshotVersion
  215. currentStatusUpdate:(FSTCurrentStatusUpdate)currentStatusUpdate {
  216. FSTTargetChange *change = [[FSTTargetChange alloc] init];
  217. change.mapping = mapping;
  218. change->_snapshotVersion = std::move(snapshotVersion);
  219. change.currentStatusUpdate = currentStatusUpdate;
  220. return change;
  221. }
  222. - (FSTTargetMapping *)mapping {
  223. if (!_mapping) {
  224. // Create an FSTUpdateMapping by default, since resets are always explicit
  225. _mapping = [[FSTUpdateMapping alloc] init];
  226. }
  227. return _mapping;
  228. }
  229. /**
  230. * Sets the resume token but only when it has a new value. Empty resumeTokens are
  231. * discarded.
  232. */
  233. - (void)setResumeToken:(NSData *)resumeToken {
  234. if (resumeToken.length > 0) {
  235. _resumeToken = resumeToken;
  236. }
  237. }
  238. @end
  239. #pragma mark - FSTRemoteEvent
  240. @implementation FSTRemoteEvent {
  241. SnapshotVersion _snapshotVersion;
  242. NSMutableDictionary<FSTBoxedTargetID *, FSTTargetChange *> *_targetChanges;
  243. std::map<DocumentKey, FSTMaybeDocument *> _documentUpdates;
  244. DocumentKeySet _limboDocumentChanges;
  245. }
  246. - (instancetype)initWithSnapshotVersion:(SnapshotVersion)snapshotVersion
  247. targetChanges:
  248. (NSMutableDictionary<NSNumber *, FSTTargetChange *> *)targetChanges
  249. documentUpdates:(std::map<DocumentKey, FSTMaybeDocument *>)documentUpdates
  250. limboDocuments:(DocumentKeySet)limboDocuments {
  251. self = [super init];
  252. if (self) {
  253. _snapshotVersion = std::move(snapshotVersion);
  254. _targetChanges = targetChanges;
  255. _documentUpdates = std::move(documentUpdates);
  256. _limboDocumentChanges = std::move(limboDocuments);
  257. }
  258. return self;
  259. }
  260. - (NSDictionary<FSTBoxedTargetID *, FSTTargetChange *> *)targetChanges {
  261. return _targetChanges;
  262. }
  263. - (const DocumentKeySet &)limboDocumentChanges {
  264. return _limboDocumentChanges;
  265. }
  266. - (const std::map<DocumentKey, FSTMaybeDocument *> &)documentUpdates {
  267. return _documentUpdates;
  268. }
  269. - (const SnapshotVersion &)snapshotVersion {
  270. return _snapshotVersion;
  271. }
  272. - (void)synthesizeDeleteForLimboTargetChange:(FSTTargetChange *)targetChange
  273. key:(const DocumentKey &)key {
  274. if (targetChange.currentStatusUpdate == FSTCurrentStatusUpdateMarkCurrent &&
  275. _documentUpdates.find(key) == _documentUpdates.end()) {
  276. // When listening to a query the server responds with a snapshot containing documents
  277. // matching the query and a current marker telling us we're now in sync. It's possible for
  278. // these to arrive as separate remote events or as a single remote event. For a document
  279. // query, there will be no documents sent in the response if the document doesn't exist.
  280. //
  281. // If the snapshot arrives separately from the current marker, we handle it normally and
  282. // updateTrackedLimboDocumentsWithChanges:targetID: will resolve the limbo status of the
  283. // document, removing it from limboDocumentRefs. This works because clients only initiate
  284. // limbo resolution when a target is current and because all current targets are always at a
  285. // consistent snapshot.
  286. //
  287. // However, if the document doesn't exist and the current marker arrives, the document is
  288. // not present in the snapshot and our normal view handling would consider the document to
  289. // remain in limbo indefinitely because there are no updates to the document. To avoid this,
  290. // we specially handle this case here: synthesizing a delete.
  291. //
  292. // TODO(dimond): Ideally we would have an explicit lookup query instead resulting in an
  293. // explicit delete message and we could remove this special logic.
  294. _documentUpdates[key] = [FSTDeletedDocument documentWithKey:key version:_snapshotVersion];
  295. _limboDocumentChanges = _limboDocumentChanges.insert(key);
  296. }
  297. }
  298. /** Adds a document update to this remote event */
  299. - (void)addDocumentUpdate:(FSTMaybeDocument *)document {
  300. _documentUpdates[document.key] = document;
  301. }
  302. /** Handles an existence filter mismatch */
  303. - (void)handleExistenceFilterMismatchForTargetID:(FSTBoxedTargetID *)targetID {
  304. // An existence filter mismatch will reset the query and we need to reset the mapping to contain
  305. // no documents and an empty resume token.
  306. //
  307. // Note:
  308. // * The reset mapping is empty, specifically forcing the consumer of the change to
  309. // forget all keys for this targetID;
  310. // * The resume snapshot for this target must be reset
  311. // * The target must be unacked because unwatching and rewatching introduces a race for
  312. // changes.
  313. //
  314. // TODO(dimond): keep track of reset targets not to raise.
  315. FSTTargetChange *targetChange =
  316. [FSTTargetChange changeWithMapping:[[FSTResetMapping alloc] init]
  317. snapshotVersion:SnapshotVersion::None()
  318. currentStatusUpdate:FSTCurrentStatusUpdateMarkNotCurrent];
  319. _targetChanges[targetID] = targetChange;
  320. }
  321. @end
  322. #pragma mark - FSTWatchChangeAggregator
  323. @interface FSTWatchChangeAggregator ()
  324. /** Keeps track of the current target mappings */
  325. @property(nonatomic, strong, readonly)
  326. NSMutableDictionary<FSTBoxedTargetID *, FSTTargetChange *> *targetChanges;
  327. /** The set of open listens on the client */
  328. @property(nonatomic, strong, readonly)
  329. NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *listenTargets;
  330. /** Whether this aggregator was frozen and can no longer be modified */
  331. @property(nonatomic, assign) BOOL frozen;
  332. @end
  333. @implementation FSTWatchChangeAggregator {
  334. NSMutableDictionary<FSTBoxedTargetID *, FSTExistenceFilter *> *_existenceFilters;
  335. /** Keeps track of document to update */
  336. std::map<DocumentKey, FSTMaybeDocument *> _documentUpdates;
  337. DocumentKeySet _limboDocuments;
  338. /** The snapshot version for every target change this creates. */
  339. SnapshotVersion _snapshotVersion;
  340. }
  341. - (instancetype)
  342. initWithSnapshotVersion:(SnapshotVersion)snapshotVersion
  343. listenTargets:(NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *)listenTargets
  344. pendingTargetResponses:(NSDictionary<FSTBoxedTargetID *, NSNumber *> *)pendingTargetResponses {
  345. self = [super init];
  346. if (self) {
  347. _snapshotVersion = std::move(snapshotVersion);
  348. _frozen = NO;
  349. _targetChanges = [NSMutableDictionary dictionary];
  350. _listenTargets = listenTargets;
  351. _pendingTargetResponses = [NSMutableDictionary dictionaryWithDictionary:pendingTargetResponses];
  352. _limboDocuments = DocumentKeySet{};
  353. _existenceFilters = [NSMutableDictionary dictionary];
  354. }
  355. return self;
  356. }
  357. - (NSDictionary<FSTBoxedTargetID *, FSTExistenceFilter *> *)existenceFilters {
  358. return _existenceFilters;
  359. }
  360. - (FSTTargetChange *)targetChangeForTargetID:(FSTBoxedTargetID *)targetID {
  361. FSTTargetChange *change = self.targetChanges[targetID];
  362. if (!change) {
  363. change = [[FSTTargetChange alloc] initWithSnapshotVersion:_snapshotVersion];
  364. self.targetChanges[targetID] = change;
  365. }
  366. return change;
  367. }
  368. - (void)addWatchChanges:(NSArray<FSTWatchChange *> *)watchChanges {
  369. HARD_ASSERT(!self.frozen, "Trying to modify frozen FSTWatchChangeAggregator");
  370. for (FSTWatchChange *watchChange in watchChanges) {
  371. [self addWatchChange:watchChange];
  372. }
  373. }
  374. - (void)addWatchChange:(FSTWatchChange *)watchChange {
  375. HARD_ASSERT(!self.frozen, "Trying to modify frozen FSTWatchChangeAggregator");
  376. if ([watchChange isKindOfClass:[FSTDocumentWatchChange class]]) {
  377. [self addDocumentChange:(FSTDocumentWatchChange *)watchChange];
  378. } else if ([watchChange isKindOfClass:[FSTWatchTargetChange class]]) {
  379. [self addTargetChange:(FSTWatchTargetChange *)watchChange];
  380. } else if ([watchChange isKindOfClass:[FSTExistenceFilterWatchChange class]]) {
  381. [self addExistenceFilterChange:(FSTExistenceFilterWatchChange *)watchChange];
  382. } else {
  383. HARD_FAIL("Unknown watch change: %s", watchChange);
  384. }
  385. }
  386. /**
  387. * Updates limbo document tracking for a given target-document mapping change. If the target is a
  388. * limbo target, and the change for the document has only seen limbo targets so far, and we are not
  389. * already tracking a change for this document, then consider this document a limbo document update.
  390. * Otherwise, ensure that we don't consider this document a limbo document. Returns true if the
  391. * change still has only seen limbo resolution changes.
  392. */
  393. - (BOOL)updateLimboDocumentsForKey:(const DocumentKey &)documentKey
  394. queryData:(FSTQueryData *)queryData
  395. isOnlyLimbo:(BOOL)isOnlyLimbo {
  396. if (!isOnlyLimbo) {
  397. // It wasn't a limbo doc before, so it definitely isn't now.
  398. return NO;
  399. }
  400. if (_documentUpdates.find(documentKey) == _documentUpdates.end()) {
  401. // We haven't seen a document update for this key yet.
  402. if (queryData.purpose == FSTQueryPurposeLimboResolution) {
  403. // We haven't seen this document before, and this target is a limbo target.
  404. _limboDocuments = _limboDocuments.insert(documentKey);
  405. return YES;
  406. } else {
  407. // We haven't seen the document before, but this is a non-limbo target.
  408. // Since we haven't seen it, we know it's not in our set of limbo docs. Return NO to ensure
  409. // that this key is marked as non-limbo.
  410. return NO;
  411. }
  412. } else if (queryData.purpose == FSTQueryPurposeLimboResolution) {
  413. // We have only seen limbo targets so far for this document, and this is another limbo target.
  414. return YES;
  415. } else {
  416. // We haven't marked this as non-limbo yet, but this target is not a limbo target.
  417. // Mark the key as non-limbo and make sure it isn't in our set.
  418. _limboDocuments = _limboDocuments.erase(documentKey);
  419. return NO;
  420. }
  421. }
  422. - (void)addDocumentChange:(FSTDocumentWatchChange *)docChange {
  423. BOOL relevant = NO;
  424. BOOL isOnlyLimbo = YES;
  425. for (FSTBoxedTargetID *targetID in docChange.updatedTargetIDs) {
  426. FSTQueryData *queryData = [self queryDataForActiveTarget:targetID];
  427. if (queryData) {
  428. FSTTargetChange *change = [self targetChangeForTargetID:targetID];
  429. isOnlyLimbo = [self updateLimboDocumentsForKey:docChange.documentKey
  430. queryData:queryData
  431. isOnlyLimbo:isOnlyLimbo];
  432. [change.mapping addDocumentKey:docChange.documentKey];
  433. relevant = YES;
  434. }
  435. }
  436. for (FSTBoxedTargetID *targetID in docChange.removedTargetIDs) {
  437. FSTQueryData *queryData = [self queryDataForActiveTarget:targetID];
  438. if (queryData) {
  439. FSTTargetChange *change = [self targetChangeForTargetID:targetID];
  440. isOnlyLimbo = [self updateLimboDocumentsForKey:docChange.documentKey
  441. queryData:queryData
  442. isOnlyLimbo:isOnlyLimbo];
  443. [change.mapping removeDocumentKey:docChange.documentKey];
  444. relevant = YES;
  445. }
  446. }
  447. // Only update the document if there is a new document to replace, this might be just a target
  448. // update instead.
  449. if (docChange.document && relevant) {
  450. _documentUpdates[docChange.documentKey] = docChange.document;
  451. }
  452. }
  453. - (void)addTargetChange:(FSTWatchTargetChange *)targetChange {
  454. for (FSTBoxedTargetID *targetID in targetChange.targetIDs) {
  455. FSTTargetChange *change = [self targetChangeForTargetID:targetID];
  456. switch (targetChange.state) {
  457. case FSTWatchTargetChangeStateNoChange:
  458. if ([self isActiveTarget:targetID]) {
  459. // Creating the change above satisfies the semantics of no-change.
  460. change.resumeToken = targetChange.resumeToken;
  461. }
  462. break;
  463. case FSTWatchTargetChangeStateAdded:
  464. [self recordResponseForTargetID:targetID];
  465. if (!self.pendingTargetResponses[targetID]) {
  466. // We have a freshly added target, so we need to reset any state that we had previously
  467. // This can happen e.g. when remove and add back a target for existence filter
  468. // mismatches.
  469. change.mapping = nil;
  470. change.currentStatusUpdate = FSTCurrentStatusUpdateNone;
  471. [_existenceFilters removeObjectForKey:targetID];
  472. }
  473. change.resumeToken = targetChange.resumeToken;
  474. break;
  475. case FSTWatchTargetChangeStateRemoved:
  476. // We need to keep track of removed targets to we can post-filter and remove any target
  477. // changes.
  478. [self recordResponseForTargetID:targetID];
  479. HARD_ASSERT(!targetChange.cause, "WatchChangeAggregator does not handle errored targets.");
  480. break;
  481. case FSTWatchTargetChangeStateCurrent:
  482. if ([self isActiveTarget:targetID]) {
  483. change.currentStatusUpdate = FSTCurrentStatusUpdateMarkCurrent;
  484. change.resumeToken = targetChange.resumeToken;
  485. }
  486. break;
  487. case FSTWatchTargetChangeStateReset:
  488. if ([self isActiveTarget:targetID]) {
  489. // Overwrite any existing target mapping with a reset mapping. Every subsequent update
  490. // will modify the reset mapping, not an update mapping.
  491. change.mapping = [[FSTResetMapping alloc] init];
  492. change.resumeToken = targetChange.resumeToken;
  493. }
  494. break;
  495. default:
  496. LOG_WARN("Unknown target watch change type: %s", targetChange.state);
  497. }
  498. }
  499. }
  500. /**
  501. * Records that we got a watch target add/remove by decrementing the number of pending target
  502. * responses that we have.
  503. */
  504. - (void)recordResponseForTargetID:(FSTBoxedTargetID *)targetID {
  505. NSNumber *count = self.pendingTargetResponses[targetID];
  506. int newCount = count ? [count intValue] - 1 : -1;
  507. if (newCount == 0) {
  508. [self.pendingTargetResponses removeObjectForKey:targetID];
  509. } else {
  510. self.pendingTargetResponses[targetID] = @(newCount);
  511. }
  512. }
  513. /**
  514. * Returns true if the given targetId is active. Active targets are those for which there are no
  515. * pending requests to add a listen and are in the current list of targets the client cares about.
  516. *
  517. * Clients can repeatedly listen and stop listening to targets, so this check is useful in
  518. * preventing in preventing race conditions for a target where events arrive but the server hasn't
  519. * yet acknowledged the intended change in state.
  520. */
  521. - (BOOL)isActiveTarget:(FSTBoxedTargetID *)targetID {
  522. return [self queryDataForActiveTarget:targetID] != nil;
  523. }
  524. - (FSTQueryData *_Nullable)queryDataForActiveTarget:(FSTBoxedTargetID *)targetID {
  525. FSTQueryData *queryData = self.listenTargets[targetID];
  526. return (queryData && !self.pendingTargetResponses[targetID]) ? queryData : nil;
  527. }
  528. - (void)addExistenceFilterChange:(FSTExistenceFilterWatchChange *)existenceFilterChange {
  529. FSTBoxedTargetID *targetID = @(existenceFilterChange.targetID);
  530. if ([self isActiveTarget:targetID]) {
  531. _existenceFilters[targetID] = existenceFilterChange.filter;
  532. }
  533. }
  534. - (FSTRemoteEvent *)remoteEvent {
  535. NSMutableDictionary<FSTBoxedTargetID *, FSTTargetChange *> *targetChanges = self.targetChanges;
  536. NSMutableArray *targetsToRemove = [NSMutableArray array];
  537. // Apply any inactive targets.
  538. for (FSTBoxedTargetID *targetID in [targetChanges keyEnumerator]) {
  539. if (![self isActiveTarget:targetID]) {
  540. [targetsToRemove addObject:targetID];
  541. }
  542. }
  543. [targetChanges removeObjectsForKeys:targetsToRemove];
  544. // Mark this aggregator as frozen so no further modifications are made.
  545. self.frozen = YES;
  546. return [[FSTRemoteEvent alloc] initWithSnapshotVersion:_snapshotVersion
  547. targetChanges:targetChanges
  548. documentUpdates:_documentUpdates
  549. limboDocuments:_limboDocuments];
  550. }
  551. @end
  552. NS_ASSUME_NONNULL_END