Files
firebase-ios-sdk/Firestore/Source/Core/FSTFirestoreClient.mm
T
zxu a441190635 Implement Firestore DatabaseInfo and port both Database{Id,Info} C++ to the iOS code (#738)
* implement Firestore DatabaseInfo in C++

* temporary stash changes; blocking on the massive renaming of .m to .mm

* add database_info_test to project

* finish port DatabaseId and fix style, modular fixing DatabaseInfo

* port DatabaseInfo

* remove FSTDatabase{ID,Info} and their tests from project

* fix unit test

* use namespace alias

* use namespace alias, leftover

* address more changes

* refactoring to use raw pointer instead of value for property

* address changes

* remove self->

* fix style

* remove the name suffix Alloc

* fix a bug
2018-02-06 13:51:11 -05:00

314 lines
12 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"
#import "Firestore/Source/Auth/FSTCredentialsProvider.h"
#import "Firestore/Source/Core/FSTEventManager.h"
#import "Firestore/Source/Core/FSTSyncEngine.h"
#import "Firestore/Source/Core/FSTTransaction.h"
#import "Firestore/Source/Local/FSTEagerGarbageCollector.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/Local/FSTNoOpGarbageCollector.h"
#import "Firestore/Source/Remote/FSTDatastore.h"
#import "Firestore/Source/Remote/FSTRemoteStore.h"
#import "Firestore/Source/Remote/FSTSerializerBeta.h"
#import "Firestore/Source/Util/FSTAssert.h"
#import "Firestore/Source/Util/FSTClasses.h"
#import "Firestore/Source/Util/FSTDispatchQueue.h"
#import "Firestore/Source/Util/FSTLogger.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/util/string_apple.h"
using firebase::firestore::core::DatabaseInfo;
using firebase::firestore::model::DatabaseId;
NS_ASSUME_NONNULL_BEGIN
@interface FSTFirestoreClient () {
DatabaseInfo _databaseInfo;
}
- (instancetype)initWithDatabaseInfo:(const DatabaseInfo &)databaseInfo
usePersistence:(BOOL)usePersistence
credentialsProvider:(id<FSTCredentialsProvider>)credentialsProvider
userDispatchQueue:(FSTDispatchQueue *)userDispatchQueue
workerDispatchQueue:(FSTDispatchQueue *)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) FSTRemoteStore *remoteStore;
@property(nonatomic, strong, readonly) FSTLocalStore *localStore;
/**
* Dispatch 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.
*/
@property(nonatomic, strong, readonly) FSTDispatchQueue *workerDispatchQueue;
@property(nonatomic, strong, readonly) id<FSTCredentialsProvider> credentialsProvider;
@end
@implementation FSTFirestoreClient
+ (instancetype)clientWithDatabaseInfo:(const DatabaseInfo &)databaseInfo
usePersistence:(BOOL)usePersistence
credentialsProvider:(id<FSTCredentialsProvider>)credentialsProvider
userDispatchQueue:(FSTDispatchQueue *)userDispatchQueue
workerDispatchQueue:(FSTDispatchQueue *)workerDispatchQueue {
return [[FSTFirestoreClient alloc] initWithDatabaseInfo:databaseInfo
usePersistence:usePersistence
credentialsProvider:credentialsProvider
userDispatchQueue:userDispatchQueue
workerDispatchQueue:workerDispatchQueue];
}
- (instancetype)initWithDatabaseInfo:(const DatabaseInfo &)databaseInfo
usePersistence:(BOOL)usePersistence
credentialsProvider:(id<FSTCredentialsProvider>)credentialsProvider
userDispatchQueue:(FSTDispatchQueue *)userDispatchQueue
workerDispatchQueue:(FSTDispatchQueue *)workerDispatchQueue {
if (self = [super init]) {
_databaseInfo = databaseInfo;
_credentialsProvider = credentialsProvider;
_userDispatchQueue = userDispatchQueue;
_workerDispatchQueue = workerDispatchQueue;
dispatch_semaphore_t initialUserAvailable = dispatch_semaphore_create(0);
__block FSTUser *initialUser;
FSTWeakify(self);
_credentialsProvider.userChangeListener = ^(FSTUser *user) {
FSTStrongify(self);
if (self) {
if (!initialUser) {
initialUser = user;
dispatch_semaphore_signal(initialUserAvailable);
} else {
[workerDispatchQueue dispatchAsync:^{
[self userDidChange:user];
}];
}
}
};
// Defer initialization until we get the current user from the userChangeListener. This is
// guaranteed to be synchronously dispatched onto our worker queue, so we will be initialized
// before any subsequently queued work runs.
[_workerDispatchQueue dispatchAsync:^{
dispatch_semaphore_wait(initialUserAvailable, DISPATCH_TIME_FOREVER);
[self initializeWithUser:initialUser usePersistence:usePersistence];
}];
}
return self;
}
- (void)initializeWithUser:(FSTUser *)user usePersistence:(BOOL)usePersistence {
// Do all of our initialization on our own dispatch queue.
[self.workerDispatchQueue verifyIsCurrentQueue];
// 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.
id<FSTGarbageCollector> garbageCollector;
if (usePersistence) {
// TODO(http://b/33384523): For now we just disable garbage collection when persistence is
// enabled.
garbageCollector = [[FSTNoOpGarbageCollector alloc] init];
NSString *dir = [FSTLevelDB storageDirectoryForDatabaseInfo:*self.databaseInfo
documentsDirectory:[FSTLevelDB documentsDirectory]];
FSTSerializerBeta *remoteSerializer =
[[FSTSerializerBeta alloc] initWithDatabaseID:&self.databaseInfo->database_id()];
FSTLocalSerializer *serializer =
[[FSTLocalSerializer alloc] initWithRemoteSerializer:remoteSerializer];
_persistence = [[FSTLevelDB alloc] initWithDirectory:dir serializer:serializer];
} else {
garbageCollector = [[FSTEagerGarbageCollector alloc] init];
_persistence = [FSTMemoryPersistence persistence];
}
NSError *error;
if (![_persistence start:&error]) {
// If local storage 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: %@", error];
}
_localStore = [[FSTLocalStore alloc] initWithPersistence:_persistence
garbageCollector:garbageCollector
initialUser:user];
FSTDatastore *datastore = [FSTDatastore datastoreWithDatabase:self.databaseInfo
workerDispatchQueue:self.workerDispatchQueue
credentials:self.credentialsProvider];
_remoteStore = [FSTRemoteStore remoteStoreWithLocalStore:_localStore datastore:datastore];
_syncEngine = [[FSTSyncEngine alloc] initWithLocalStore:_localStore
remoteStore:_remoteStore
initialUser:user];
_eventManager = [FSTEventManager eventManagerWithSyncEngine:_syncEngine];
// Setup wiring for remote store.
_remoteStore.syncEngine = _syncEngine;
_remoteStore.onlineStateDelegate = self;
// NOTE: RemoteStore depends on LocalStore (for persisting stream tokens, refilling mutation
// queue, etc.) so must be started after LocalStore.
[_localStore start];
[_remoteStore start];
}
- (void)userDidChange:(FSTUser *)user {
[self.workerDispatchQueue verifyIsCurrentQueue];
FSTLog(@"User Changed: %@", user);
[self.syncEngine userDidChange:user];
}
- (void)applyChangedOnlineState:(FSTOnlineState)onlineState {
[self.syncEngine applyChangedOnlineState:onlineState];
[self.eventManager applyChangedOnlineState:onlineState];
}
- (void)disableNetworkWithCompletion:(nullable FSTVoidErrorBlock)completion {
[self.workerDispatchQueue dispatchAsync:^{
[self.remoteStore disableNetwork];
if (completion) {
[self.userDispatchQueue dispatchAsync:^{
completion(nil);
}];
}
}];
}
- (void)enableNetworkWithCompletion:(nullable FSTVoidErrorBlock)completion {
[self.workerDispatchQueue dispatchAsync:^{
[self.remoteStore enableNetwork];
if (completion) {
[self.userDispatchQueue dispatchAsync:^{
completion(nil);
}];
}
}];
}
- (void)shutdownWithCompletion:(nullable FSTVoidErrorBlock)completion {
[self.workerDispatchQueue dispatchAsync:^{
self.credentialsProvider.userChangeListener = nil;
[self.remoteStore shutdown];
[self.localStore shutdown];
[self.persistence shutdown];
if (completion) {
[self.userDispatchQueue dispatchAsync:^{
completion(nil);
}];
}
}];
}
- (FSTQueryListener *)listenToQuery:(FSTQuery *)query
options:(FSTListenOptions *)options
viewSnapshotHandler:(FSTViewSnapshotHandler)viewSnapshotHandler {
FSTQueryListener *listener = [[FSTQueryListener alloc] initWithQuery:query
options:options
viewSnapshotHandler:viewSnapshotHandler];
[self.workerDispatchQueue dispatchAsync:^{
[self.eventManager addListener:listener];
}];
return listener;
}
- (void)removeListener:(FSTQueryListener *)listener {
[self.workerDispatchQueue dispatchAsync:^{
[self.eventManager removeListener:listener];
}];
}
- (void)writeMutations:(NSArray<FSTMutation *> *)mutations
completion:(nullable FSTVoidErrorBlock)completion {
[self.workerDispatchQueue dispatchAsync:^{
if (mutations.count == 0) {
if (completion) {
[self.userDispatchQueue dispatchAsync:^{
completion(nil);
}];
}
} else {
[self.syncEngine writeMutations:mutations
completion:^(NSError *error) {
// Dispatch the result back onto the user dispatch queue.
if (completion) {
[self.userDispatchQueue dispatchAsync:^{
completion(error);
}];
}
}];
}
}];
};
- (void)transactionWithRetries:(int)retries
updateBlock:(FSTTransactionBlock)updateBlock
completion:(FSTVoidIDErrorBlock)completion {
[self.workerDispatchQueue dispatchAsync:^{
[self.syncEngine transactionWithRetries:retries
workerDispatchQueue:self.workerDispatchQueue
updateBlock:updateBlock
completion:^(id _Nullable result, NSError *_Nullable error) {
// Dispatch the result back onto the user dispatch queue.
if (completion) {
[self.userDispatchQueue dispatchAsync:^{
completion(result, error);
}];
}
}];
}];
}
- (const DatabaseInfo *)databaseInfo {
return &_databaseInfo;
}
- (const DatabaseId *)databaseID {
return &_databaseInfo.database_id();
}
@end
NS_ASSUME_NONNULL_END