Files
firebase-ios-sdk/Firestore/Source/Core/FSTFirestoreClient.mm
T
Gil 7a7eced747 Don't use using statements in Objective-C headers ... (#2827)
use namespace aliases instead.

This makes the Objective-C code much more like what it's going to end up
being like when translated to C++: headers will include classes declared
in a namespace relative to firebase::firestore so everything else will
be named relative to firebase::firestore.

Meanwhile the using statements in the headers were causing us to miss
adding using statements in source files which was moving us away from
where we wanted to be.
2019-04-12 17:03:26 -07:00

436 lines
18 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/FSTFirestoreClient.h"
#include <chrono> // NOLINT(build/c++11)
#include <future> // NOLINT(build/c++11)
#include <memory>
#include <utility>
#import "FIRFirestoreErrors.h"
#import "FIRFirestoreSettings.h"
#import "Firestore/Source/API/FIRDocumentReference+Internal.h"
#import "Firestore/Source/API/FIRDocumentSnapshot+Internal.h"
#import "Firestore/Source/API/FIRFirestore+Internal.h"
#import "Firestore/Source/API/FIRQuery+Internal.h"
#import "Firestore/Source/API/FIRQuerySnapshot+Internal.h"
#import "Firestore/Source/API/FIRSnapshotMetadata+Internal.h"
#import "Firestore/Source/Core/FSTEventManager.h"
#import "Firestore/Source/Core/FSTQuery.h"
#import "Firestore/Source/Core/FSTSyncEngine.h"
#import "Firestore/Source/Core/FSTView.h"
#import "Firestore/Source/Local/FSTLRUGarbageCollector.h"
#import "Firestore/Source/Local/FSTLevelDB.h"
#import "Firestore/Source/Local/FSTLocalSerializer.h"
#import "Firestore/Source/Local/FSTLocalStore.h"
#import "Firestore/Source/Local/FSTMemoryPersistence.h"
#import "Firestore/Source/Model/FSTDocument.h"
#import "Firestore/Source/Remote/FSTSerializerBeta.h"
#import "Firestore/Source/Util/FSTClasses.h"
#include "Firestore/core/src/firebase/firestore/auth/credentials_provider.h"
#include "Firestore/core/src/firebase/firestore/core/database_info.h"
#include "Firestore/core/src/firebase/firestore/model/database_id.h"
#include "Firestore/core/src/firebase/firestore/model/document_set.h"
#include "Firestore/core/src/firebase/firestore/remote/datastore.h"
#include "Firestore/core/src/firebase/firestore/remote/remote_store.h"
#include "Firestore/core/src/firebase/firestore/util/async_queue.h"
#include "Firestore/core/src/firebase/firestore/util/hard_assert.h"
#include "Firestore/core/src/firebase/firestore/util/log.h"
#include "Firestore/core/src/firebase/firestore/util/status.h"
#include "Firestore/core/src/firebase/firestore/util/statusor.h"
#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
#include "absl/memory/memory.h"
namespace util = firebase::firestore::util;
using firebase::firestore::FirestoreErrorCode;
using firebase::firestore::api::DocumentReference;
using firebase::firestore::api::DocumentSnapshot;
using firebase::firestore::api::SnapshotMetadata;
using firebase::firestore::auth::CredentialsProvider;
using firebase::firestore::auth::User;
using firebase::firestore::core::DatabaseInfo;
using firebase::firestore::core::ListenOptions;
using firebase::firestore::core::QueryListener;
using firebase::firestore::core::ViewSnapshot;
using firebase::firestore::local::LruParams;
using firebase::firestore::model::DatabaseId;
using firebase::firestore::model::DocumentKeySet;
using firebase::firestore::model::DocumentMap;
using firebase::firestore::model::MaybeDocumentMap;
using firebase::firestore::model::OnlineState;
using firebase::firestore::remote::Datastore;
using firebase::firestore::remote::RemoteStore;
using firebase::firestore::util::Path;
using firebase::firestore::util::Status;
using firebase::firestore::util::AsyncQueue;
using firebase::firestore::util::DelayedOperation;
using firebase::firestore::util::Executor;
using firebase::firestore::util::Status;
using firebase::firestore::util::StatusOr;
using firebase::firestore::util::StatusOrCallback;
using firebase::firestore::util::TimerId;
NS_ASSUME_NONNULL_BEGIN
/** How long we wait to try running LRU GC after SDK initialization. */
static const std::chrono::milliseconds FSTLruGcInitialDelay = std::chrono::minutes(1);
/** Minimum amount of time between GC checks, after the first one. */
static const std::chrono::milliseconds FSTLruGcRegularDelay = std::chrono::minutes(5);
@interface FSTFirestoreClient () {
DatabaseInfo _databaseInfo;
}
- (instancetype)initWithDatabaseInfo:(const DatabaseInfo &)databaseInfo
settings:(FIRFirestoreSettings *)settings
credentialsProvider:
(CredentialsProvider *)credentialsProvider // no passing ownership
userExecutor:(std::unique_ptr<Executor>)userExecutor
workerQueue:(std::unique_ptr<AsyncQueue>)queue NS_DESIGNATED_INITIALIZER;
@property(nonatomic, assign, readonly) const DatabaseInfo *databaseInfo;
@property(nonatomic, strong, readonly) FSTEventManager *eventManager;
@property(nonatomic, strong, readonly) id<FSTPersistence> persistence;
@property(nonatomic, strong, readonly) FSTSyncEngine *syncEngine;
@property(nonatomic, strong, readonly) FSTLocalStore *localStore;
// Does not own the CredentialsProvider instance.
@property(nonatomic, assign, readonly) CredentialsProvider *credentialsProvider;
@end
@implementation FSTFirestoreClient {
/**
* Async queue responsible for all of our internal processing. When we get incoming work from
* the user (via public API) or the network (incoming gRPC messages), we should always dispatch
* onto this queue. This ensures our internal data structures are never accessed from multiple
* threads simultaneously.
*/
std::unique_ptr<AsyncQueue> _workerQueue;
std::unique_ptr<RemoteStore> _remoteStore;
std::unique_ptr<Executor> _userExecutor;
std::chrono::milliseconds _initialGcDelay;
std::chrono::milliseconds _regularGcDelay;
BOOL _gcHasRun;
_Nullable id<FSTLRUDelegate> _lruDelegate;
DelayedOperation _lruCallback;
}
- (Executor *)userExecutor {
return _userExecutor.get();
}
- (AsyncQueue *)workerQueue {
return _workerQueue.get();
}
+ (instancetype)clientWithDatabaseInfo:(const DatabaseInfo &)databaseInfo
settings:(FIRFirestoreSettings *)settings
credentialsProvider:
(CredentialsProvider *)credentialsProvider // no passing ownership
userExecutor:(std::unique_ptr<Executor>)userExecutor
workerQueue:(std::unique_ptr<AsyncQueue>)workerQueue {
return [[FSTFirestoreClient alloc] initWithDatabaseInfo:databaseInfo
settings:settings
credentialsProvider:credentialsProvider
userExecutor:std::move(userExecutor)
workerQueue:std::move(workerQueue)];
}
- (instancetype)initWithDatabaseInfo:(const DatabaseInfo &)databaseInfo
settings:(FIRFirestoreSettings *)settings
credentialsProvider:
(CredentialsProvider *)credentialsProvider // no passing ownership
userExecutor:(std::unique_ptr<Executor>)userExecutor
workerQueue:(std::unique_ptr<AsyncQueue>)workerQueue {
if (self = [super init]) {
_databaseInfo = databaseInfo;
_credentialsProvider = credentialsProvider;
_userExecutor = std::move(userExecutor);
_workerQueue = std::move(workerQueue);
_gcHasRun = NO;
_initialGcDelay = FSTLruGcInitialDelay;
_regularGcDelay = FSTLruGcRegularDelay;
auto userPromise = std::make_shared<std::promise<User>>();
bool initialized = false;
__weak __typeof__(self) weakSelf = self;
auto credentialChangeListener = [initialized, userPromise, weakSelf](User user) mutable {
__typeof__(self) strongSelf = weakSelf;
if (!strongSelf) return;
if (!initialized) {
initialized = true;
userPromise->set_value(user);
} else {
strongSelf->_workerQueue->Enqueue(
[strongSelf, user] { [strongSelf credentialDidChangeWithUser:user]; });
}
};
_credentialsProvider->SetCredentialChangeListener(credentialChangeListener);
// Defer initialization until we get the current user from the credentialChangeListener. This is
// guaranteed to be synchronously dispatched onto our worker queue, so we will be initialized
// before any subsequently queued work runs.
_workerQueue->Enqueue([self, userPromise, settings] {
User user = userPromise->get_future().get();
[self initializeWithUser:user settings:settings];
});
}
return self;
}
- (void)initializeWithUser:(const User &)user settings:(FIRFirestoreSettings *)settings {
// Do all of our initialization on our own dispatch queue.
_workerQueue->VerifyIsCurrentQueue();
LOG_DEBUG("Initializing. Current user: %s", user.uid());
// Note: The initialization work must all be synchronous (we can't dispatch more work) since
// external write/listen operations could get queued to run before that subsequent work
// completes.
if (settings.isPersistenceEnabled) {
Path dir = [FSTLevelDB storageDirectoryForDatabaseInfo:*self.databaseInfo
documentsDirectory:[FSTLevelDB documentsDirectory]];
FSTSerializerBeta *remoteSerializer =
[[FSTSerializerBeta alloc] initWithDatabaseID:&self.databaseInfo->database_id()];
FSTLocalSerializer *serializer =
[[FSTLocalSerializer alloc] initWithRemoteSerializer:remoteSerializer];
FSTLevelDB *ldb;
Status levelDbStatus =
[FSTLevelDB dbWithDirectory:std::move(dir)
serializer:serializer
lruParams:LruParams::WithCacheSize(settings.cacheSizeBytes)
ptr:&ldb];
if (!levelDbStatus.ok()) {
// If leveldb fails to start then just throw up our hands: the error is unrecoverable.
// There's nothing an end-user can do and nearly all failures indicate the developer is doing
// something grossly wrong so we should stop them cold in their tracks with a failure they
// can't ignore.
[NSException raise:NSInternalInconsistencyException
format:@"Failed to open DB: %s", levelDbStatus.ToString().c_str()];
}
_lruDelegate = ldb.referenceDelegate;
_persistence = ldb;
[self scheduleLruGarbageCollection];
} else {
_persistence = [FSTMemoryPersistence persistenceWithEagerGC];
}
_localStore = [[FSTLocalStore alloc] initWithPersistence:_persistence initialUser:user];
auto datastore =
std::make_shared<Datastore>(*self.databaseInfo, _workerQueue.get(), _credentialsProvider);
_remoteStore = absl::make_unique<RemoteStore>(
_localStore, std::move(datastore), _workerQueue.get(),
[self](OnlineState onlineState) { [self.syncEngine applyChangedOnlineState:onlineState]; });
_syncEngine = [[FSTSyncEngine alloc] initWithLocalStore:_localStore
remoteStore:_remoteStore.get()
initialUser:user];
_eventManager = [FSTEventManager eventManagerWithSyncEngine:_syncEngine];
// Setup wiring for remote store.
_remoteStore->set_sync_engine(_syncEngine);
// NOTE: RemoteStore depends on LocalStore (for persisting stream tokens, refilling mutation
// queue, etc.) so must be started after LocalStore.
[_localStore start];
_remoteStore->Start();
}
/**
* Schedules a callback to try running LRU garbage collection. Reschedules itself after the GC has
* run.
*/
- (void)scheduleLruGarbageCollection {
std::chrono::milliseconds delay = _gcHasRun ? _regularGcDelay : _initialGcDelay;
_lruCallback = _workerQueue->EnqueueAfterDelay(delay, TimerId::GarbageCollectionDelay, [self]() {
[self->_localStore collectGarbage:self->_lruDelegate.gc];
self->_gcHasRun = YES;
[self scheduleLruGarbageCollection];
});
}
- (void)credentialDidChangeWithUser:(const User &)user {
_workerQueue->VerifyIsCurrentQueue();
LOG_DEBUG("Credential Changed. Current user: %s", user.uid());
[self.syncEngine credentialDidChangeWithUser:user];
}
- (void)disableNetworkWithCompletion:(nullable FSTVoidErrorBlock)completion {
_workerQueue->Enqueue([self, completion] {
_remoteStore->DisableNetwork();
if (completion) {
self->_userExecutor->Execute([=] { completion(nil); });
}
});
}
- (void)enableNetworkWithCompletion:(nullable FSTVoidErrorBlock)completion {
_workerQueue->Enqueue([self, completion] {
_remoteStore->EnableNetwork();
if (completion) {
self->_userExecutor->Execute([=] { completion(nil); });
}
});
}
- (void)shutdownWithCompletion:(nullable FSTVoidErrorBlock)completion {
_workerQueue->Enqueue([self, completion] {
self->_credentialsProvider->SetCredentialChangeListener(nullptr);
// If we've scheduled LRU garbage collection, cancel it.
if (self->_lruCallback) {
self->_lruCallback.Cancel();
}
_remoteStore->Shutdown();
[self.persistence shutdown];
if (completion) {
self->_userExecutor->Execute([=] { completion(nil); });
}
});
}
- (std::shared_ptr<QueryListener>)listenToQuery:(FSTQuery *)query
options:(ListenOptions)options
listener:(ViewSnapshot::SharedListener &&)listener {
auto query_listener = QueryListener::Create(query, std::move(options), std::move(listener));
_workerQueue->Enqueue([self, query_listener] { [self.eventManager addListener:query_listener]; });
return query_listener;
}
- (void)removeListener:(const std::shared_ptr<QueryListener> &)listener {
_workerQueue->Enqueue([self, listener] { [self.eventManager removeListener:listener]; });
}
- (void)getDocumentFromLocalCache:(const DocumentReference &)doc
completion:(DocumentSnapshot::Listener &&)completion {
auto shared_completion = absl::ShareUniquePtr(std::move(completion));
_workerQueue->Enqueue([self, doc, shared_completion] {
FSTMaybeDocument *maybeDoc = [self.localStore readDocument:doc.key()];
StatusOr<DocumentSnapshot> maybe_snapshot;
if ([maybeDoc isKindOfClass:[FSTDocument class]]) {
FSTDocument *document = (FSTDocument *)maybeDoc;
maybe_snapshot = DocumentSnapshot{doc.firestore(), doc.key(), document,
/*from_cache=*/true,
/*has_pending_writes=*/document.hasLocalMutations};
} else if ([maybeDoc isKindOfClass:[FSTDeletedDocument class]]) {
maybe_snapshot = DocumentSnapshot{doc.firestore(), doc.key(), nil,
/*from_cache=*/true,
/*has_pending_writes=*/false};
} else {
maybe_snapshot = Status{FirestoreErrorCode::Unavailable,
"Failed to get document from cache. (However, this document "
"may exist on the server. Run again without setting source to "
"FirestoreSourceCache to attempt to retrieve the document "};
}
if (shared_completion) {
self->_userExecutor->Execute([=] { shared_completion->OnEvent(std::move(maybe_snapshot)); });
}
});
}
- (void)getDocumentsFromLocalCache:(FIRQuery *)query
completion:(void (^)(FIRQuerySnapshot *_Nullable query,
NSError *_Nullable error))completion {
_workerQueue->Enqueue([self, query, completion] {
DocumentMap docs = [self.localStore executeQuery:query.query];
FSTView *view = [[FSTView alloc] initWithQuery:query.query remoteDocuments:DocumentKeySet{}];
FSTViewDocumentChanges *viewDocChanges =
[view computeChangesWithDocuments:docs.underlying_map()];
FSTViewChange *viewChange = [view applyChangesToDocuments:viewDocChanges];
HARD_ASSERT(viewChange.limboChanges.count == 0,
"View returned limbo documents during local-only query execution.");
HARD_ASSERT(viewChange.snapshot.has_value(), "Expected a snapshot");
ViewSnapshot snapshot = std::move(viewChange.snapshot).value();
SnapshotMetadata metadata(snapshot.has_pending_writes(), snapshot.from_cache());
FIRQuerySnapshot *result = [[FIRQuerySnapshot alloc] initWithFirestore:query.firestore.wrapped
originalQuery:query.query
snapshot:std::move(snapshot)
metadata:std::move(metadata)];
if (completion) {
self->_userExecutor->Execute([=] { completion(result, nil); });
}
});
}
- (void)writeMutations:(std::vector<FSTMutation *> &&)mutations
completion:(nullable FSTVoidErrorBlock)completion {
// TODO(c++14): move `mutations` into lambda (C++14).
_workerQueue->Enqueue([self, mutations, completion]() mutable {
if (mutations.empty()) {
if (completion) {
self->_userExecutor->Execute([=] { completion(nil); });
}
} else {
[self.syncEngine writeMutations:std::move(mutations)
completion:^(NSError *error) {
// Dispatch the result back onto the user dispatch queue.
if (completion) {
self->_userExecutor->Execute([=] { completion(error); });
}
}];
}
});
};
- (void)transactionWithRetries:(int)retries
updateBlock:(FSTTransactionBlock)updateBlock
completion:(FSTVoidIDErrorBlock)completion {
_workerQueue->Enqueue([self, retries, updateBlock, completion] {
[self.syncEngine
transactionWithRetries:retries
workerQueue:_workerQueue.get()
updateBlock:updateBlock
completion:^(id _Nullable result, NSError *_Nullable error) {
// Dispatch the result back onto the user dispatch queue.
if (completion) {
self->_userExecutor->Execute([=] { completion(result, error); });
}
}];
});
}
- (const DatabaseInfo *)databaseInfo {
return &_databaseInfo;
}
- (const DatabaseId *)databaseID {
return &_databaseInfo.database_id();
}
@end
NS_ASSUME_NONNULL_END