Files
firebase-ios-sdk/Firestore/Source/Core/FSTEventManager.mm
T

333 lines
11 KiB
Plaintext

/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "Firestore/Source/Core/FSTEventManager.h"
#include <utility>
#include <vector>
#import "Firestore/Source/Core/FSTQuery.h"
#import "Firestore/Source/Core/FSTSyncEngine.h"
#import "Firestore/Source/Model/FSTDocumentSet.h"
#include "Firestore/core/src/firebase/firestore/util/hard_assert.h"
using firebase::firestore::core::DocumentViewChange;
using firebase::firestore::model::OnlineState;
using firebase::firestore::model::TargetId;
NS_ASSUME_NONNULL_BEGIN
#pragma mark - FSTListenOptions
@implementation FSTListenOptions
+ (instancetype)defaultOptions {
static FSTListenOptions *defaultOptions;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
defaultOptions = [[FSTListenOptions alloc] initWithIncludeQueryMetadataChanges:NO
includeDocumentMetadataChanges:NO
waitForSyncWhenOnline:NO];
});
return defaultOptions;
}
- (instancetype)initWithIncludeQueryMetadataChanges:(BOOL)includeQueryMetadataChanges
includeDocumentMetadataChanges:(BOOL)includeDocumentMetadataChanges
waitForSyncWhenOnline:(BOOL)waitForSyncWhenOnline {
if (self = [super init]) {
_includeQueryMetadataChanges = includeQueryMetadataChanges;
_includeDocumentMetadataChanges = includeDocumentMetadataChanges;
_waitForSyncWhenOnline = waitForSyncWhenOnline;
}
return self;
}
- (instancetype)init {
HARD_FAIL("FSTListenOptions init not supported");
return nil;
}
@end
#pragma mark - FSTQueryListenersInfo
/**
* Holds the listeners and the last received ViewSnapshot for a query being tracked by
* EventManager.
*/
@interface FSTQueryListenersInfo : NSObject
@property(nonatomic, strong, nullable, readwrite) FSTViewSnapshot *viewSnapshot;
@property(nonatomic, assign, readwrite) TargetId targetID;
@property(nonatomic, strong, readonly) NSMutableArray<FSTQueryListener *> *listeners;
@end
@implementation FSTQueryListenersInfo
- (instancetype)init {
if (self = [super init]) {
_listeners = [NSMutableArray array];
}
return self;
}
@end
#pragma mark - FSTQueryListener
@interface FSTQueryListener ()
/** The last received view snapshot. */
@property(nonatomic, strong, nullable) FSTViewSnapshot *snapshot;
@property(nonatomic, strong, readonly) FSTListenOptions *options;
/**
* Initial snapshots (e.g. from cache) may not be propagated to the FSTViewSnapshotHandler.
* This flag is set to YES once we've actually raised an event.
*/
@property(nonatomic, assign, readwrite) BOOL raisedInitialEvent;
/** The last online state this query listener got. */
@property(nonatomic, assign, readwrite) OnlineState onlineState;
/** The FSTViewSnapshotHandler associated with this query listener. */
@property(nonatomic, copy, nullable) FSTViewSnapshotHandler viewSnapshotHandler;
@end
@implementation FSTQueryListener
- (instancetype)initWithQuery:(FSTQuery *)query
options:(FSTListenOptions *)options
viewSnapshotHandler:(FSTViewSnapshotHandler)viewSnapshotHandler {
if (self = [super init]) {
_query = query;
_options = options;
_viewSnapshotHandler = viewSnapshotHandler;
_raisedInitialEvent = NO;
}
return self;
}
- (void)queryDidChangeViewSnapshot:(FSTViewSnapshot *)snapshot {
HARD_ASSERT(!snapshot.documentChanges.empty() || snapshot.syncStateChanged,
"We got a new snapshot with no changes?");
if (!self.options.includeDocumentMetadataChanges) {
// Remove the metadata-only changes.
std::vector<DocumentViewChange> changes;
for (const DocumentViewChange &change : snapshot.documentChanges) {
if (change.type() != DocumentViewChange::Type::kMetadata) {
changes.push_back(change);
}
}
snapshot = [[FSTViewSnapshot alloc] initWithQuery:snapshot.query
documents:snapshot.documents
oldDocuments:snapshot.oldDocuments
documentChanges:std::move(changes)
fromCache:snapshot.fromCache
mutatedKeys:snapshot.mutatedKeys
syncStateChanged:snapshot.syncStateChanged
excludesMetadataChanges:YES];
}
if (!self.raisedInitialEvent) {
if ([self shouldRaiseInitialEventForSnapshot:snapshot onlineState:self.onlineState]) {
[self raiseInitialEventForSnapshot:snapshot];
}
} else if ([self shouldRaiseEventForSnapshot:snapshot]) {
self.viewSnapshotHandler(snapshot, nil);
}
self.snapshot = snapshot;
}
- (void)queryDidError:(NSError *)error {
self.viewSnapshotHandler(nil, error);
}
- (void)applyChangedOnlineState:(OnlineState)onlineState {
self.onlineState = onlineState;
if (self.snapshot && !self.raisedInitialEvent &&
[self shouldRaiseInitialEventForSnapshot:self.snapshot onlineState:onlineState]) {
[self raiseInitialEventForSnapshot:self.snapshot];
}
}
- (BOOL)shouldRaiseInitialEventForSnapshot:(FSTViewSnapshot *)snapshot
onlineState:(OnlineState)onlineState {
HARD_ASSERT(!self.raisedInitialEvent,
"Determining whether to raise initial event, but already had first event.");
// Always raise the first event when we're synced
if (!snapshot.fromCache) {
return YES;
}
// NOTE: We consider OnlineState.Unknown as online (it should become Offline or Online if we
// wait long enough).
BOOL maybeOnline = onlineState != OnlineState::Offline;
// Don't raise the event if we're online, aren't synced yet (checked
// above) and are waiting for a sync.
if (self.options.waitForSyncWhenOnline && maybeOnline) {
HARD_ASSERT(snapshot.fromCache, "Waiting for sync, but snapshot is not from cache.");
return NO;
}
// Raise data from cache if we have any documents or we are offline
return !snapshot.documents.isEmpty || onlineState == OnlineState::Offline;
}
- (BOOL)shouldRaiseEventForSnapshot:(FSTViewSnapshot *)snapshot {
// We don't need to handle includeDocumentMetadataChanges here because the Metadata only changes
// have already been stripped out if needed. At this point the only changes we will see are the
// ones we should propagate.
if (!snapshot.documentChanges.empty()) {
return YES;
}
BOOL hasPendingWritesChanged =
self.snapshot && self.snapshot.hasPendingWrites != snapshot.hasPendingWrites;
if (snapshot.syncStateChanged || hasPendingWritesChanged) {
return self.options.includeQueryMetadataChanges;
}
// Generally we should have hit one of the cases above, but it's possible to get here if there
// were only metadata docChanges and they got stripped out.
return NO;
}
- (void)raiseInitialEventForSnapshot:(FSTViewSnapshot *)snapshot {
HARD_ASSERT(!self.raisedInitialEvent, "Trying to raise initial events for second time");
snapshot = [FSTViewSnapshot snapshotForInitialDocuments:snapshot.documents
query:snapshot.query
mutatedKeys:snapshot.mutatedKeys
fromCache:snapshot.fromCache
excludesMetadataChanges:snapshot.excludesMetadataChanges];
self.raisedInitialEvent = YES;
self.viewSnapshotHandler(snapshot, nil);
}
@end
#pragma mark - FSTEventManager
@interface FSTEventManager () <FSTSyncEngineDelegate>
- (instancetype)initWithSyncEngine:(FSTSyncEngine *)syncEngine NS_DESIGNATED_INITIALIZER;
@property(nonatomic, strong, readonly) FSTSyncEngine *syncEngine;
@property(nonatomic, strong, readonly)
NSMutableDictionary<FSTQuery *, FSTQueryListenersInfo *> *queries;
@property(nonatomic, assign) OnlineState onlineState;
@end
@implementation FSTEventManager
+ (instancetype)eventManagerWithSyncEngine:(FSTSyncEngine *)syncEngine {
return [[FSTEventManager alloc] initWithSyncEngine:syncEngine];
}
- (instancetype)initWithSyncEngine:(FSTSyncEngine *)syncEngine {
if (self = [super init]) {
_syncEngine = syncEngine;
_queries = [NSMutableDictionary dictionary];
_syncEngine.syncEngineDelegate = self;
}
return self;
}
- (TargetId)addListener:(FSTQueryListener *)listener {
FSTQuery *query = listener.query;
BOOL firstListen = NO;
FSTQueryListenersInfo *queryInfo = self.queries[query];
if (!queryInfo) {
firstListen = YES;
queryInfo = [[FSTQueryListenersInfo alloc] init];
self.queries[query] = queryInfo;
}
[queryInfo.listeners addObject:listener];
[listener applyChangedOnlineState:self.onlineState];
if (queryInfo.viewSnapshot) {
[listener queryDidChangeViewSnapshot:queryInfo.viewSnapshot];
}
if (firstListen) {
queryInfo.targetID = [self.syncEngine listenToQuery:query];
}
return queryInfo.targetID;
}
- (void)removeListener:(FSTQueryListener *)listener {
FSTQuery *query = listener.query;
BOOL lastListen = NO;
FSTQueryListenersInfo *queryInfo = self.queries[query];
if (queryInfo) {
[queryInfo.listeners removeObject:listener];
lastListen = (queryInfo.listeners.count == 0);
}
if (lastListen) {
[self.queries removeObjectForKey:query];
[self.syncEngine stopListeningToQuery:query];
}
}
- (void)handleViewSnapshots:(NSArray<FSTViewSnapshot *> *)viewSnapshots {
for (FSTViewSnapshot *viewSnapshot in viewSnapshots) {
FSTQuery *query = viewSnapshot.query;
FSTQueryListenersInfo *queryInfo = self.queries[query];
if (queryInfo) {
for (FSTQueryListener *listener in queryInfo.listeners) {
[listener queryDidChangeViewSnapshot:viewSnapshot];
}
queryInfo.viewSnapshot = viewSnapshot;
}
}
}
- (void)handleError:(NSError *)error forQuery:(FSTQuery *)query {
FSTQueryListenersInfo *queryInfo = self.queries[query];
if (queryInfo) {
for (FSTQueryListener *listener in queryInfo.listeners) {
[listener queryDidError:error];
}
}
// Remove all listeners. NOTE: We don't need to call [FSTSyncEngine stopListening] after an error.
[self.queries removeObjectForKey:query];
}
- (void)applyChangedOnlineState:(OnlineState)onlineState {
self.onlineState = onlineState;
for (FSTQueryListenersInfo *info in self.queries.objectEnumerator) {
for (FSTQueryListener *listener in info.listeners) {
[listener applyChangedOnlineState:onlineState];
}
}
}
@end
NS_ASSUME_NONNULL_END