adding pods method of package managing

This commit is contained in:
talksik
2021-12-13 12:34:20 -08:00
parent dad674aca7
commit 705203d7bd
5871 changed files with 1259393 additions and 3 deletions
@@ -0,0 +1,48 @@
/*
* Copyright 2020 Google LLC
*
* 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 <Foundation/Foundation.h>
@protocol FIRAppCheckTokenResultInterop;
NS_ASSUME_NONNULL_BEGIN
NS_SWIFT_NAME(AppCheckTokenHandlerInterop)
typedef void (^FIRAppCheckTokenHandlerInterop)(id<FIRAppCheckTokenResultInterop> tokenResult);
@protocol FIRAppCheckInterop <NSObject>
/// Retrieve a cached or generate a new FAA Token. If forcingRefresh == YES always generates a new
/// token and updates the cache.
- (void)getTokenForcingRefresh:(BOOL)forcingRefresh
completion:(FIRAppCheckTokenHandlerInterop)handler
NS_SWIFT_NAME(getToken(forcingRefresh:completion:));
/// A notification with the specified name is sent to the default notification center
/// (`NotificationCenter.default`) each time a Firebase app check token is refreshed.
/// The user info dictionary contains `-[self notificationTokenKey]` and
/// `-[self notificationAppNameKey]` keys.
- (NSString *)tokenDidChangeNotificationName;
/// `userInfo` key for the FAC token in a notification for `tokenDidChangeNotificationName`.
- (NSString *)notificationTokenKey;
/// `userInfo` key for the `FirebaseApp.name` in a notification for
/// `tokenDidChangeNotificationName`.
- (NSString *)notificationAppNameKey;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,32 @@
/*
* Copyright 2020 Google LLC
*
* 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 <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@protocol FIRAppCheckTokenResultInterop <NSObject>
/// App Check token in the case of success or a dummy token in the case of a failure.
/// In general, the value of the token should always be set to the request header.
@property(nonatomic, readonly) NSString *token;
/// A token fetch error in the case of a failure or `nil` in the case of success.
@property(nonatomic, readonly, nullable) NSError *error;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,153 @@
/*
* 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 <FirebaseCore/FIRApp.h>
@class FIRComponentContainer;
@protocol FIRLibrary;
/**
* The internal interface to FIRApp. This is meant for first-party integrators, who need to receive
* FIRApp notifications, log info about the success or failure of their configuration, and access
* other internal functionality of FIRApp.
*
* TODO(b/28296561): Restructure this header.
*/
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSInteger, FIRConfigType) {
FIRConfigTypeCore = 1,
FIRConfigTypeSDK = 2,
};
extern NSString *const kFIRDefaultAppName;
extern NSString *const kFIRAppReadyToConfigureSDKNotification;
extern NSString *const kFIRAppDeleteNotification;
extern NSString *const kFIRAppIsDefaultAppKey;
extern NSString *const kFIRAppNameKey;
extern NSString *const kFIRGoogleAppIDKey;
extern NSString *const kFirebaseCoreErrorDomain;
/** The NSUserDefaults suite name for FirebaseCore, for those storage locations that use it. */
extern NSString *const kFirebaseCoreDefaultsSuiteName;
/**
* The format string for the User Defaults key used for storing the data collection enabled flag.
* This includes formatting to append the Firebase App's name.
*/
extern NSString *const kFIRGlobalAppDataCollectionEnabledDefaultsKeyFormat;
/**
* The plist key used for storing the data collection enabled flag.
*/
extern NSString *const kFIRGlobalAppDataCollectionEnabledPlistKey;
/** @var FIRAuthStateDidChangeInternalNotification
@brief The name of the @c NSNotificationCenter notification which is posted when the auth state
changes (e.g. a new token has been produced, a user logs in or out). The object parameter of
the notification is a dictionary possibly containing the key:
@c FIRAuthStateDidChangeInternalNotificationTokenKey (the new access token.) If it does not
contain this key it indicates a sign-out event took place.
*/
extern NSString *const FIRAuthStateDidChangeInternalNotification;
/** @var FIRAuthStateDidChangeInternalNotificationTokenKey
@brief A key present in the dictionary object parameter of the
@c FIRAuthStateDidChangeInternalNotification notification. The value associated with this
key will contain the new access token.
*/
extern NSString *const FIRAuthStateDidChangeInternalNotificationTokenKey;
/** @var FIRAuthStateDidChangeInternalNotificationAppKey
@brief A key present in the dictionary object parameter of the
@c FIRAuthStateDidChangeInternalNotification notification. The value associated with this
key will contain the FIRApp associated with the auth instance.
*/
extern NSString *const FIRAuthStateDidChangeInternalNotificationAppKey;
/** @var FIRAuthStateDidChangeInternalNotificationUIDKey
@brief A key present in the dictionary object parameter of the
@c FIRAuthStateDidChangeInternalNotification notification. The value associated with this
key will contain the new user's UID (or nil if there is no longer a user signed in).
*/
extern NSString *const FIRAuthStateDidChangeInternalNotificationUIDKey;
@interface FIRApp ()
/**
* A flag indicating if this is the default app (has the default app name).
*/
@property(nonatomic, readonly) BOOL isDefaultApp;
/*
* The container of interop SDKs for this app.
*/
@property(nonatomic) FIRComponentContainer *container;
/**
* Checks if the default app is configured without trying to configure it.
*/
+ (BOOL)isDefaultAppConfigured;
/**
* Registers a given third-party library with the given version number to be reported for
* analytics.
*
* @param name Name of the library.
* @param version Version of the library.
*/
+ (void)registerLibrary:(nonnull NSString *)name withVersion:(nonnull NSString *)version;
/**
* Registers a given internal library to be reported for analytics.
*
* @param library Optional parameter for component registration.
* @param name Name of the library.
*/
+ (void)registerInternalLibrary:(nonnull Class<FIRLibrary>)library
withName:(nonnull NSString *)name;
/**
* Registers a given internal library with the given version number to be reported for
* analytics. This should only be used for non-Firebase libraries that have their own versioning
* scheme.
*
* @param library Optional parameter for component registration.
* @param name Name of the library.
* @param version Version of the library.
*/
+ (void)registerInternalLibrary:(nonnull Class<FIRLibrary>)library
withName:(nonnull NSString *)name
withVersion:(nonnull NSString *)version;
/**
* A concatenated string representing all the third-party libraries and version numbers.
*/
+ (NSString *)firebaseUserAgent;
/**
* Can be used by the unit tests in eack SDK to reset FIRApp. This method is thread unsafe.
*/
+ (void)resetApps;
/**
* Can be used by the unit tests in each SDK to set customized options.
*/
- (instancetype)initInstanceWithName:(NSString *)name options:(FIROptions *)options;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,91 @@
/*
* Copyright 2018 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 <Foundation/Foundation.h>
@class FIRApp;
@class FIRComponentContainer;
NS_ASSUME_NONNULL_BEGIN
/// Provides a system to clean up cached instances returned from the component system.
NS_SWIFT_NAME(ComponentLifecycleMaintainer)
@protocol FIRComponentLifecycleMaintainer
/// The associated app will be deleted, clean up any resources as they are about to be deallocated.
- (void)appWillBeDeleted:(FIRApp *)app;
@end
typedef _Nullable id (^FIRComponentCreationBlock)(FIRComponentContainer *container,
BOOL *isCacheable)
NS_SWIFT_NAME(ComponentCreationBlock);
@class FIRDependency;
/// Describes the timing of instantiation. Note: new components should default to lazy unless there
/// is a strong reason to be eager.
typedef NS_ENUM(NSInteger, FIRInstantiationTiming) {
FIRInstantiationTimingLazy,
FIRInstantiationTimingAlwaysEager,
FIRInstantiationTimingEagerInDefaultApp
} NS_SWIFT_NAME(InstantiationTiming);
/// A component that can be used from other Firebase SDKs.
NS_SWIFT_NAME(Component)
@interface FIRComponent : NSObject
/// The protocol describing functionality provided from the Component.
@property(nonatomic, strong, readonly) Protocol *protocol;
/// The timing of instantiation.
@property(nonatomic, readonly) FIRInstantiationTiming instantiationTiming;
/// An array of dependencies for the component.
@property(nonatomic, copy, readonly) NSArray<FIRDependency *> *dependencies;
/// A block to instantiate an instance of the component with the appropriate dependencies.
@property(nonatomic, copy, readonly) FIRComponentCreationBlock creationBlock;
// There's an issue with long NS_SWIFT_NAMES that causes compilation to fail, disable clang-format
// for the next two methods.
// clang-format off
/// Creates a component with no dependencies that will be lazily initialized.
+ (instancetype)componentWithProtocol:(Protocol *)protocol
creationBlock:(FIRComponentCreationBlock)creationBlock
NS_SWIFT_NAME(init(_:creationBlock:));
/// Creates a component to be registered with the component container.
///
/// @param protocol - The protocol describing functionality provided by the component.
/// @param instantiationTiming - When the component should be initialized. Use .lazy unless there's
/// a good reason to be instantiated earlier.
/// @param dependencies - Any dependencies the `implementingClass` has, optional or required.
/// @param creationBlock - A block to instantiate the component with a container, and if
/// @return A component that can be registered with the component container.
+ (instancetype)componentWithProtocol:(Protocol *)protocol
instantiationTiming:(FIRInstantiationTiming)instantiationTiming
dependencies:(NSArray<FIRDependency *> *)dependencies
creationBlock:(FIRComponentCreationBlock)creationBlock
NS_SWIFT_NAME(init(_:instantiationTiming:dependencies:creationBlock:));
// clang-format on
/// Unavailable.
- (instancetype)init NS_UNAVAILABLE;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,41 @@
/*
* Copyright 2018 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 <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/// A type-safe macro to retrieve a component from a container. This should be used to retrieve
/// components instead of using the container directly.
#define FIR_COMPONENT(type, container) \
[FIRComponentType<id<type>> instanceForProtocol:@protocol(type) inContainer:container]
@class FIRApp;
/// A container that holds different components that are registered via the
/// `registerAsComponentRegistrant:` call. These classes should conform to `FIRComponentRegistrant`
/// in order to properly register components for Core.
NS_SWIFT_NAME(FirebaseComponentContainer)
@interface FIRComponentContainer : NSObject
/// A weak reference to the app that an instance of the container belongs to.
@property(nonatomic, weak, readonly) FIRApp *app;
/// Unavailable. Use the `container` property on `FIRApp`.
- (instancetype)init NS_UNAVAILABLE;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,34 @@
/*
* Copyright 2018 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 <Foundation/Foundation.h>
@class FIRComponentContainer;
NS_ASSUME_NONNULL_BEGIN
/// Do not use directly. A placeholder type in order to provide a macro that will warn users of
/// mis-matched protocols.
NS_SWIFT_NAME(ComponentType)
@interface FIRComponentType<__covariant T> : NSObject
/// Do not use directly. A factory method to retrieve an instance that provides a specific
/// functionality.
+ (T)instanceForProtocol:(Protocol *)protocol inContainer:(FIRComponentContainer *)container;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,35 @@
/*
* Copyright 2019 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 <Foundation/Foundation.h>
@class FIRDiagnosticsData;
@class FIROptions;
NS_ASSUME_NONNULL_BEGIN
/** Connects FIRCore with the CoreDiagnostics library. */
@interface FIRCoreDiagnosticsConnector : NSObject
/** Logs FirebaseCore related data.
*
* @param options The options object containing data to log.
*/
+ (void)logCoreTelemetryWithOptions:(FIROptions *)options;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,45 @@
/*
* Copyright 2018 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 <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/// A dependency on a specific protocol's functionality.
NS_SWIFT_NAME(Dependency)
@interface FIRDependency : NSObject
/// The protocol describing functionality being depended on.
@property(nonatomic, strong, readonly) Protocol *protocol;
/// A flag to specify if the dependency is required or not.
@property(nonatomic, readonly) BOOL isRequired;
/// Initializes a dependency that is required. Calls `initWithProtocol:isRequired` with `YES` for
/// the required parameter.
/// Creates a required dependency on the specified protocol's functionality.
+ (instancetype)dependencyWithProtocol:(Protocol *)protocol;
/// Creates a dependency on the specified protocol's functionality and specify if it's required for
/// the class's functionality.
+ (instancetype)dependencyWithProtocol:(Protocol *)protocol isRequired:(BOOL)required;
/// Use `dependencyWithProtocol:isRequired:` instead.
- (instancetype)init NS_UNAVAILABLE;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,39 @@
// Copyright 2019 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 <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface FIRHeartbeatInfo : NSObject
// Enum representing the different heartbeat codes.
typedef NS_ENUM(NSInteger, FIRHeartbeatInfoCode) {
FIRHeartbeatInfoCodeNone = 0,
FIRHeartbeatInfoCodeSDK = 1,
FIRHeartbeatInfoCodeGlobal = 2,
FIRHeartbeatInfoCodeCombined = 3,
};
/**
* Get heartbeat code required for the sdk.
* @param heartbeatTag String representing the sdk heartbeat tag.
* @return Heartbeat code indicating whether or not an sdk/global heartbeat
* needs to be sent
*/
+ (FIRHeartbeatInfoCode)heartbeatCodeForTag:(NSString *)heartbeatTag;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,44 @@
/*
* Copyright 2018 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.
*/
#ifndef FIRLibrary_h
#define FIRLibrary_h
#import <Foundation/Foundation.h>
@class FIRApp;
@class FIRComponent;
NS_ASSUME_NONNULL_BEGIN
/// Provide an interface to register a library for userAgent logging and availability to others.
NS_SWIFT_NAME(Library)
@protocol FIRLibrary
/// Returns one or more FIRComponents that will be registered in
/// FIRApp and participate in dependency resolution and injection.
+ (NSArray<FIRComponent *> *)componentsToRegister;
@optional
/// Implement this method if the library needs notifications for lifecycle events. This method is
/// called when the developer calls `FirebaseApp.configure()`.
+ (void)configureWithApp:(FIRApp *)app;
@end
NS_ASSUME_NONNULL_END
#endif /* FIRLibrary_h */
@@ -0,0 +1,146 @@
/*
* 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 <Foundation/Foundation.h>
#import <FirebaseCore/FIRLoggerLevel.h>
NS_ASSUME_NONNULL_BEGIN
/**
* The Firebase services used in Firebase logger.
*/
typedef NSString *const FIRLoggerService;
extern FIRLoggerService kFIRLoggerAnalytics;
extern FIRLoggerService kFIRLoggerCrash;
extern FIRLoggerService kFIRLoggerCore;
extern FIRLoggerService kFIRLoggerRemoteConfig;
/**
* The key used to store the logger's error count.
*/
extern NSString *const kFIRLoggerErrorCountKey;
/**
* The key used to store the logger's warning count.
*/
extern NSString *const kFIRLoggerWarningCountKey;
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
/**
* Enables or disables Analytics debug mode.
* If set to YES, the logging level for Analytics will be set to FIRLoggerLevelDebug.
* Enabling the debug mode has no effect if the app is running from App Store.
* (required) analytics debug mode flag.
*/
void FIRSetAnalyticsDebugMode(BOOL analyticsDebugMode);
/**
* Changes the default logging level of FIRLoggerLevelNotice to a user-specified level.
* The default level cannot be set above FIRLoggerLevelNotice if the app is running from App Store.
* (required) log level (one of the FIRLoggerLevel enum values).
*/
void FIRSetLoggerLevel(FIRLoggerLevel loggerLevel);
/**
* Checks if the specified logger level is loggable given the current settings.
* (required) log level (one of the FIRLoggerLevel enum values).
* (required) whether or not this function is called from the Analytics component.
*/
BOOL FIRIsLoggableLevel(FIRLoggerLevel loggerLevel, BOOL analyticsComponent);
/**
* Logs a message to the Xcode console and the device log. If running from AppStore, will
* not log any messages with a level higher than FIRLoggerLevelNotice to avoid log spamming.
* (required) log level (one of the FIRLoggerLevel enum values).
* (required) service name of type FIRLoggerService.
* (required) message code starting with "I-" which means iOS, followed by a capitalized
* three-character service identifier and a six digit integer message ID that is unique
* within the service.
* An example of the message code is @"I-COR000001".
* (required) message string which can be a format string.
* (optional) variable arguments list obtained from calling va_start, used when message is a format
* string.
*/
extern void FIRLogBasic(FIRLoggerLevel level,
FIRLoggerService service,
NSString *messageCode,
NSString *message,
// On 64-bit simulators, va_list is not a pointer, so cannot be marked nullable
// See: http://stackoverflow.com/q/29095469
#if __LP64__ && TARGET_OS_SIMULATOR || TARGET_OS_OSX
va_list args_ptr
#else
va_list _Nullable args_ptr
#endif
);
/**
* The following functions accept the following parameters in order:
* (required) service name of type FIRLoggerService.
* (required) message code starting from "I-" which means iOS, followed by a capitalized
* three-character service identifier and a six digit integer message ID that is unique
* within the service.
* An example of the message code is @"I-COR000001".
* See go/firebase-log-proposal for details.
* (required) message string which can be a format string.
* (optional) the list of arguments to substitute into the format string.
* Example usage:
* FIRLogError(kFIRLoggerCore, @"I-COR000001", @"Configuration of %@ failed.", app.name);
*/
extern void FIRLogError(FIRLoggerService service, NSString *messageCode, NSString *message, ...)
NS_FORMAT_FUNCTION(3, 4);
extern void FIRLogWarning(FIRLoggerService service, NSString *messageCode, NSString *message, ...)
NS_FORMAT_FUNCTION(3, 4);
extern void FIRLogNotice(FIRLoggerService service, NSString *messageCode, NSString *message, ...)
NS_FORMAT_FUNCTION(3, 4);
extern void FIRLogInfo(FIRLoggerService service, NSString *messageCode, NSString *message, ...)
NS_FORMAT_FUNCTION(3, 4);
extern void FIRLogDebug(FIRLoggerService service, NSString *messageCode, NSString *message, ...)
NS_FORMAT_FUNCTION(3, 4);
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
@interface FIRLoggerWrapper : NSObject
/**
* Objective-C wrapper for FIRLogBasic to allow weak linking to FIRLogger
* (required) log level (one of the FIRLoggerLevel enum values).
* (required) service name of type FIRLoggerService.
* (required) message code starting with "I-" which means iOS, followed by a capitalized
* three-character service identifier and a six digit integer message ID that is unique
* within the service.
* An example of the message code is @"I-COR000001".
* (required) message string which can be a format string.
* (optional) variable arguments list obtained from calling va_start, used when message is a format
* string.
*/
+ (void)logWithLevel:(FIRLoggerLevel)level
withService:(FIRLoggerService)service
withCode:(NSString *)messageCode
withMessage:(NSString *)message
withArgs:(va_list)args;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,115 @@
/*
* 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 <FirebaseCore/FIROptions.h>
/**
* Keys for the strings in the plist file.
*/
extern NSString *const kFIRAPIKey;
extern NSString *const kFIRTrackingID;
extern NSString *const kFIRGoogleAppID;
extern NSString *const kFIRClientID;
extern NSString *const kFIRGCMSenderID;
extern NSString *const kFIRAndroidClientID;
extern NSString *const kFIRDatabaseURL;
extern NSString *const kFIRStorageBucket;
extern NSString *const kFIRBundleID;
extern NSString *const kFIRProjectID;
/**
* Keys for the plist file name
*/
extern NSString *const kServiceInfoFileName;
extern NSString *const kServiceInfoFileType;
/**
* This header file exposes the initialization of FIROptions to internal use.
*/
@interface FIROptions ()
/**
* resetDefaultOptions and initInternalWithOptionsDictionary: are exposed only for unit tests.
*/
+ (void)resetDefaultOptions;
/**
* Initializes the options with dictionary. The above strings are the keys of the dictionary.
* This is the designated initializer.
*/
- (instancetype)initInternalWithOptionsDictionary:(NSDictionary *)serviceInfoDictionary
NS_DESIGNATED_INITIALIZER;
/**
* defaultOptions and defaultOptionsDictionary are exposed in order to be used in FIRApp and
* other first party services.
*/
+ (FIROptions *)defaultOptions;
+ (NSDictionary *)defaultOptionsDictionary;
/**
* Indicates whether or not Analytics collection was explicitly enabled via a plist flag or at
* runtime.
*/
@property(nonatomic, readonly) BOOL isAnalyticsCollectionExplicitlySet;
/**
* Whether or not Analytics Collection was enabled. Analytics Collection is enabled unless
* explicitly disabled in GoogleService-Info.plist.
*/
@property(nonatomic, readonly) BOOL isAnalyticsCollectionEnabled;
/**
* Whether or not Analytics Collection was completely disabled. If YES, then
* isAnalyticsCollectionEnabled will be NO.
*/
@property(nonatomic, readonly) BOOL isAnalyticsCollectionDeactivated;
/**
* The version ID of the client library, e.g. @"1100000".
*/
@property(nonatomic, readonly, copy) NSString *libraryVersionID;
/**
* The flag indicating whether this object was constructed with the values in the default plist
* file.
*/
@property(nonatomic) BOOL usingOptionsFromDefaultPlist;
/**
* Whether or not Measurement was enabled. Measurement is enabled unless explicitly disabled in
* GoogleService-Info.plist.
*/
@property(nonatomic, readonly) BOOL isMeasurementEnabled;
/**
* Whether or not Analytics was enabled in the developer console.
*/
@property(nonatomic, readonly) BOOL isAnalyticsEnabled;
/**
* Whether or not SignIn was enabled in the developer console.
*/
@property(nonatomic, readonly) BOOL isSignInEnabled;
/**
* Whether or not editing is locked. This should occur after FIROptions has been set on a FIRApp.
*/
@property(nonatomic, getter=isEditingLocked) BOOL editingLocked;
@end
@@ -0,0 +1,28 @@
// Copyright 2020 Google LLC
//
// 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.
// An umbrella header, for any other libraries in this repo to access Firebase Public and Private
// headers. Any package manager complexity should be handled here.
#import <FirebaseCore/FirebaseCore.h>
#import "FirebaseCore/Sources/Private/FIRAppInternal.h"
#import "FirebaseCore/Sources/Private/FIRComponent.h"
#import "FirebaseCore/Sources/Private/FIRComponentContainer.h"
#import "FirebaseCore/Sources/Private/FIRComponentType.h"
#import "FirebaseCore/Sources/Private/FIRDependency.h"
#import "FirebaseCore/Sources/Private/FIRHeartbeatInfo.h"
#import "FirebaseCore/Sources/Private/FIRLibrary.h"
#import "FirebaseCore/Sources/Private/FIRLogger.h"
#import "FirebaseCore/Sources/Private/FIROptionsInternal.h"
@@ -0,0 +1,105 @@
/*
* 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 "FirebaseDatabase/Sources/Public/FirebaseDatabase/FIRDataSnapshot.h"
#import "FirebaseDatabase/Sources/Api/Private/FIRDataSnapshot_Private.h"
#import "FirebaseDatabase/Sources/FTransformedEnumerator.h"
#import "FirebaseDatabase/Sources/Public/FirebaseDatabase/FIRDatabaseReference.h"
#import "FirebaseDatabase/Sources/Snapshot/FChildrenNode.h"
#import "FirebaseDatabase/Sources/Utilities/FValidation.h"
@interface FIRDataSnapshot ()
@property(nonatomic, strong) FIRDatabaseReference *ref;
@end
@implementation FIRDataSnapshot
- (id)initWithRef:(FIRDatabaseReference *)ref indexedNode:(FIndexedNode *)node {
self = [super init];
if (self != nil) {
self->_ref = ref;
self->_node = node;
}
return self;
}
- (id)value {
return [self.node.node val];
}
- (id)valueInExportFormat {
return [self.node.node valForExport:YES];
}
- (FIRDataSnapshot *)childSnapshotForPath:(NSString *)childPathString {
[FValidation validateFrom:@"child:" validPathString:childPathString];
FPath *childPath = [[FPath alloc] initWith:childPathString];
FIRDatabaseReference *childRef = [self.ref child:childPathString];
id<FNode> childNode = [self.node.node getChild:childPath];
return [[FIRDataSnapshot alloc]
initWithRef:childRef
indexedNode:[FIndexedNode indexedNodeWithNode:childNode]];
}
- (BOOL)hasChild:(NSString *)childPathString {
[FValidation validateFrom:@"hasChild:" validPathString:childPathString];
FPath *childPath = [[FPath alloc] initWith:childPathString];
return ![[self.node.node getChild:childPath] isEmpty];
}
- (id)priority {
id<FNode> priority = [self.node.node getPriority];
return priority.val;
}
- (BOOL)hasChildren {
if ([self.node.node isLeafNode]) {
return false;
} else {
return ![self.node.node isEmpty];
}
}
- (BOOL)exists {
return ![self.node.node isEmpty];
}
- (NSString *)key {
return [self.ref key];
}
- (NSUInteger)childrenCount {
return [self.node.node numChildren];
}
- (NSEnumerator<FIRDataSnapshot *> *)children {
return [[FTransformedEnumerator alloc]
initWithEnumerator:self.node.childEnumerator
andTransform:^id(FNamedNode *node) {
FIRDatabaseReference *childRef = [self.ref child:node.name];
return [[FIRDataSnapshot alloc]
initWithRef:childRef
indexedNode:[FIndexedNode indexedNodeWithNode:node.node]];
}];
}
- (NSString *)description {
return
[NSString stringWithFormat:@"Snap (%@) %@", self.key, self.node.node];
}
@end
@@ -0,0 +1,260 @@
/*
* 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 <Foundation/Foundation.h>
#import "FirebaseCore/Sources/Private/FirebaseCoreInternal.h"
#import "Interop/Auth/Public/FIRAuthInterop.h"
#import "FirebaseDatabase/Sources/Api/FIRDatabaseComponent.h"
#import "FirebaseDatabase/Sources/Api/Private/FIRDatabaseQuery_Private.h"
#import "FirebaseDatabase/Sources/Api/Private/FIRDatabaseReference_Private.h"
#import "FirebaseDatabase/Sources/Api/Private/FIRDatabase_Private.h"
#import "FirebaseDatabase/Sources/Core/FRepoInfo.h"
#import "FirebaseDatabase/Sources/FIRDatabaseConfig_Private.h"
#import "FirebaseDatabase/Sources/Public/FirebaseDatabase/FIRDatabase.h"
#import "FirebaseDatabase/Sources/Utilities/FValidation.h"
@implementation FIRDatabase
+ (FIRDatabase *)database {
if (![FIRApp isDefaultAppConfigured]) {
[NSException
raise:@"FIRAppNotConfigured"
format:@"The default FirebaseApp instance must be "
@"configured before the default Database instance "
@"can be initialized. One way to ensure this is to "
@"call `FirebaseApp.configure()` in the App Delegate's "
@"`application(_:didFinishLaunchingWithOptions:)` "
@"(or the `@main` struct's initializer in SwiftUI)."];
}
return [FIRDatabase databaseForApp:[FIRApp defaultApp]];
}
+ (FIRDatabase *)databaseWithURL:(NSString *)url {
FIRApp *app = [FIRApp defaultApp];
if (app == nil) {
[NSException
raise:@"FIRAppNotConfigured"
format:
@"Failed to get default Firebase Database instance. "
@"Must call `[FIRApp configure]` (`FirebaseApp.configure()` in "
@"Swift) before using Firebase Database."];
}
return [FIRDatabase databaseForApp:app URL:url];
}
+ (FIRDatabase *)databaseForApp:(FIRApp *)app {
if (app == nil) {
[NSException raise:@"InvalidFIRApp"
format:@"nil FIRApp instance passed to databaseForApp."];
}
NSString *url = app.options.databaseURL;
if (!url) {
if (!app.options.projectID) {
[NSException
raise:@"MissingProjectId"
format:@"Can't determine Firebase Database URL. Be sure to "
@"include a Project ID when calling "
@"`FirebaseApp.configure()`."];
}
FFLog(@"I-RDB024002", @"Using default host for project %@",
app.options.projectID);
url = [NSString
stringWithFormat:@"https://%@-default-rtdb.firebaseio.com",
app.options.projectID];
}
return [FIRDatabase databaseForApp:app URL:url];
}
+ (FIRDatabase *)databaseForApp:(FIRApp *)app URL:(NSString *)url {
if (app == nil) {
[NSException raise:@"InvalidFIRApp"
format:@"nil FIRApp instance passed to databaseForApp."];
}
if (url == nil) {
[NSException raise:@"MissingDatabaseURL"
format:@"Failed to get FirebaseDatabase instance: "
@"Specify DatabaseURL within FIRApp or from your "
@"databaseForApp:URL: call."];
}
id<FIRDatabaseProvider> provider =
FIR_COMPONENT(FIRDatabaseProvider, app.container);
return [provider databaseForApp:app URL:url];
}
+ (NSString *)buildVersion {
// TODO: Restore git hash when build moves back to git
return [NSString stringWithFormat:@"%@_%s", FIRFirebaseVersion(), __DATE__];
}
+ (FIRDatabase *)createDatabaseForTests:(FRepoInfo *)repoInfo
config:(FIRDatabaseConfig *)config {
FIRDatabase *db = [[FIRDatabase alloc] initWithApp:nil
repoInfo:repoInfo
config:config];
[db ensureRepo];
return db;
}
+ (NSString *)sdkVersion {
return FIRFirebaseVersion();
}
+ (void)setLoggingEnabled:(BOOL)enabled {
[FUtilities setLoggingEnabled:enabled];
FFLog(@"I-RDB024001", @"BUILD Version: %@", [FIRDatabase buildVersion]);
}
- (id)initWithApp:(FIRApp *)app
repoInfo:(FRepoInfo *)info
config:(FIRDatabaseConfig *)config {
self = [super init];
if (self != nil) {
self->_repoInfo = info;
self->_config = config;
self->_app = app;
}
return self;
}
- (FIRDatabaseReference *)reference {
[self ensureRepo];
return [[FIRDatabaseReference alloc] initWithRepo:self.repo
path:[FPath empty]];
}
- (FIRDatabaseReference *)referenceWithPath:(NSString *)path {
[self ensureRepo];
[FValidation validateFrom:@"referenceWithPath" validRootPathString:path];
FPath *childPath = [[FPath alloc] initWith:path];
return [[FIRDatabaseReference alloc] initWithRepo:self.repo path:childPath];
}
- (FIRDatabaseReference *)referenceFromURL:(NSString *)databaseUrl {
[self ensureRepo];
if (databaseUrl == nil) {
[NSException raise:@"InvalidDatabaseURL"
format:@"Invalid nil url passed to referenceFromURL:"];
}
FParsedUrl *parsedUrl = [FUtilities parseUrl:databaseUrl];
[FValidation validateFrom:@"referenceFromURL:" validURL:parsedUrl];
BOOL isInvalidHost =
!parsedUrl.repoInfo.isCustomHost &&
![_repoInfo.host isEqualToString:parsedUrl.repoInfo.host];
if (isInvalidHost) {
[NSException raise:@"InvalidDatabaseURL"
format:@"Invalid URL (%@) passed to getReference(). URL "
@"was expected to match configured Database URL: %@",
databaseUrl, _repoInfo.host];
}
return [[FIRDatabaseReference alloc] initWithRepo:self.repo
path:parsedUrl.path];
}
- (void)purgeOutstandingWrites {
[self ensureRepo];
dispatch_async([FIRDatabaseQuery sharedQueue], ^{
[self.repo purgeOutstandingWrites];
});
}
- (void)useEmulatorWithHost:(NSString *)host port:(NSInteger)port {
if (host.length == 0) {
[NSException raise:NSInvalidArgumentException
format:@"Cannot connect to nil or empty host."];
}
if (self.repo != nil) {
[NSException
raise:NSInternalInconsistencyException
format:@"Cannot connect to emulator after database initialization. "
@"Call useEmulator(host:port:) before creating a database "
@"reference or trying to load data."];
}
NSString *fullHost =
[NSString stringWithFormat:@"%@:%li", host, (long)port];
FRepoInfo *emulatorInfo = [[FRepoInfo alloc] initWithInfo:self.repoInfo
emulatedHost:fullHost];
self->_repoInfo = emulatorInfo;
}
- (void)goOnline {
[self ensureRepo];
dispatch_async([FIRDatabaseQuery sharedQueue], ^{
[self.repo resume];
});
}
- (void)goOffline {
[self ensureRepo];
dispatch_async([FIRDatabaseQuery sharedQueue], ^{
[self.repo interrupt];
});
}
- (void)setPersistenceEnabled:(BOOL)persistenceEnabled {
[self assertUnfrozen:@"setPersistenceEnabled"];
self->_config.persistenceEnabled = persistenceEnabled;
}
- (BOOL)persistenceEnabled {
return self->_config.persistenceEnabled;
}
- (void)setPersistenceCacheSizeBytes:(NSUInteger)persistenceCacheSizeBytes {
[self assertUnfrozen:@"setPersistenceCacheSizeBytes"];
self->_config.persistenceCacheSizeBytes = persistenceCacheSizeBytes;
}
- (NSUInteger)persistenceCacheSizeBytes {
return self->_config.persistenceCacheSizeBytes;
}
- (void)setCallbackQueue:(dispatch_queue_t)callbackQueue {
[self assertUnfrozen:@"setCallbackQueue"];
self->_config.callbackQueue = callbackQueue;
}
- (dispatch_queue_t)callbackQueue {
return self->_config.callbackQueue;
}
- (void)assertUnfrozen:(NSString *)methodName {
if (self.repo != nil) {
[NSException
raise:@"FIRDatabaseAlreadyInUse"
format:@"Calls to %@ must be made before any other usage of "
"FIRDatabase instance.",
methodName];
}
}
- (void)ensureRepo {
if (self.repo == nil) {
self.repo = [FRepoManager createRepo:self.repoInfo
config:self.config
database:self];
}
}
@end
@@ -0,0 +1,46 @@
/*
* Copyright 2018 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 <Foundation/Foundation.h>
@class FIRApp;
@class FIRDatabase;
NS_ASSUME_NONNULL_BEGIN
/// This protocol is used in the interop registration process to register an
/// instance provider for individual FIRApps.
@protocol FIRDatabaseProvider
/// Gets a FirebaseDatabase instance for the specified URL, using the specified
/// FirebaseApp.
- (FIRDatabase *)databaseForApp:(FIRApp *)app URL:(NSString *)url;
@end
/// A concrete implementation for FIRDatabaseProvider to create Database
/// instances.
@interface FIRDatabaseComponent : NSObject <FIRDatabaseProvider>
/// The FIRApp that instances will be set up with.
@property(nonatomic, weak, readonly) FIRApp *app;
/// Unavailable, use `databaseForApp:URL:` instead.
- (instancetype)init NS_UNAVAILABLE;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,172 @@
/*
* Copyright 2018 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 "FirebaseDatabase/Sources/Api/FIRDatabaseComponent.h"
#import "FirebaseDatabase/Sources/Api/Private/FIRDatabase_Private.h"
#import "FirebaseDatabase/Sources/Core/FRepoManager.h"
#import "FirebaseDatabase/Sources/FIRDatabaseConfig_Private.h"
#import "FirebaseAppCheck/Sources/Interop/FIRAppCheckInterop.h"
#import "FirebaseCore/Sources/Private/FirebaseCoreInternal.h"
#import "Interop/Auth/Public/FIRAuthInterop.h"
NS_ASSUME_NONNULL_BEGIN
/** A NSMutableDictionary of FirebaseApp name and FRepoInfo to FirebaseDatabase
* instance. */
typedef NSMutableDictionary<NSString *, FIRDatabase *> FIRDatabaseDictionary;
@interface FIRDatabaseComponent () <FIRComponentLifecycleMaintainer, FIRLibrary>
@property(nonatomic) FIRDatabaseDictionary *instances;
/// Internal intializer.
- (instancetype)initWithApp:(FIRApp *)app;
@end
@implementation FIRDatabaseComponent
#pragma mark - Initialization
- (instancetype)initWithApp:(FIRApp *)app {
self = [super init];
if (self) {
_app = app;
_instances = [NSMutableDictionary dictionary];
}
return self;
}
#pragma mark - Lifecycle
+ (void)load {
[FIRApp registerInternalLibrary:(Class<FIRLibrary>)self
withName:@"fire-db"];
}
#pragma mark - FIRComponentRegistrant
+ (NSArray<FIRComponent *> *)componentsToRegister {
FIRDependency *authDep =
[FIRDependency dependencyWithProtocol:@protocol(FIRAuthInterop)
isRequired:NO];
FIRComponentCreationBlock creationBlock =
^id _Nullable(FIRComponentContainer *container, BOOL *isCacheable) {
*isCacheable = YES;
return [[FIRDatabaseComponent alloc] initWithApp:container.app];
};
FIRComponent *databaseProvider =
[FIRComponent componentWithProtocol:@protocol(FIRDatabaseProvider)
instantiationTiming:FIRInstantiationTimingLazy
dependencies:@[ authDep ]
creationBlock:creationBlock];
return @[ databaseProvider ];
}
#pragma mark - Instance management.
- (void)appWillBeDeleted:(FIRApp *)app {
NSString *appName = app.name;
if (appName == nil) {
return;
}
FIRDatabaseDictionary *instances = [self instances];
@synchronized(instances) {
// Clean up the deleted instance in an effort to remove any resources
// still in use. Note: Any leftover instances of this exact database
// will be invalid.
for (FIRDatabase *database in [instances allValues]) {
[FRepoManager disposeRepos:database.config];
}
[instances removeAllObjects];
}
}
#pragma mark - FIRDatabaseProvider Conformance
- (FIRDatabase *)databaseForApp:(FIRApp *)app URL:(NSString *)url {
if (app == nil) {
[NSException raise:@"InvalidFIRApp"
format:@"nil FIRApp instance passed to databaseForApp."];
}
if (url == nil) {
[NSException raise:@"MissingDatabaseURL"
format:@"Failed to get FirebaseDatabase instance: "
"Specify DatabaseURL within FIRApp or from your "
"databaseForApp:URL: call."];
}
NSURL *databaseUrl = [NSURL URLWithString:url];
if (databaseUrl == nil) {
[NSException raise:@"InvalidDatabaseURL"
format:@"The Database URL '%@' cannot be parsed. "
"Specify a valid DatabaseURL within FIRApp or from "
"your databaseForApp:URL: call.",
url];
} else if (![databaseUrl.path isEqualToString:@""] &&
![databaseUrl.path isEqualToString:@"/"]) {
[NSException
raise:@"InvalidDatabaseURL"
format:@"Configured Database URL '%@' is invalid. It should point "
"to the root of a Firebase Database but it includes a "
"path: %@",
databaseUrl, databaseUrl.path];
}
FIRDatabaseDictionary *instances = [self instances];
@synchronized(instances) {
FParsedUrl *parsedUrl =
[FUtilities parseUrl:databaseUrl.absoluteString];
NSString *urlIndex =
[NSString stringWithFormat:@"%@:%@", parsedUrl.repoInfo.host,
[parsedUrl.path toString]];
FIRDatabase *database = instances[urlIndex];
if (!database) {
id<FIRDatabaseConnectionContextProvider> contextProvider =
[FIRDatabaseConnectionContextProvider
contextProviderWithAuth:FIR_COMPONENT(FIRAuthInterop,
app.container)
appCheck:FIR_COMPONENT(FIRAppCheckInterop,
app.container)];
// If this is the default app, don't set the session persistence key
// so that we use our default ("default") instead of the FIRApp
// default ("[DEFAULT]") so that we preserve the default location
// used by the legacy Firebase SDK.
NSString *sessionIdentifier = @"default";
if (![FIRApp isDefaultAppConfigured] ||
app != [FIRApp defaultApp]) {
sessionIdentifier = app.name;
}
FIRDatabaseConfig *config = [[FIRDatabaseConfig alloc]
initWithSessionIdentifier:sessionIdentifier
googleAppID:app.options.googleAppID
contextProvider:contextProvider];
database = [[FIRDatabase alloc] initWithApp:app
repoInfo:parsedUrl.repoInfo
config:config];
instances[urlIndex] = database;
}
return database;
}
}
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,72 @@
/*
* 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 <Foundation/Foundation.h>
@protocol FIRDatabaseConnectionContextProvider;
NS_ASSUME_NONNULL_BEGIN
/**
* TODO: Merge FIRDatabaseConfig into FIRDatabase.
*/
@interface FIRDatabaseConfig : NSObject
- (id)initWithSessionIdentifier:(NSString *)identifier
googleAppID:(NSString *)googleAppID
contextProvider:
(id<FIRDatabaseConnectionContextProvider>)contextProvider;
/**
* By default the Firebase Database client will keep data in memory while your
* application is running, but not when it is restarted. By setting this value
* to YES, the data will be persisted to on-device (disk) storage and will thus
* be available again when the app is restarted (even when there is no network
* connectivity at that time). Note that this property must be set before
* creating your first FIRDatabaseReference and only needs to be called once per
* application.
*
* If your app uses Firebase Authentication, the client will automatically
* persist the user's authentication token across restarts, even without
* persistence enabled. But if the auth token expired while offline and you've
* enabled persistence, the client will pause write operations until you
* successfully re-authenticate (or explicitly unauthenticate) to prevent your
* writes from being sent unauthenticated and failing due to security rules.
*/
@property(nonatomic) BOOL persistenceEnabled;
/**
* By default the Firebase Database client will use up to 10MB of disk space to
* cache data. If the cache grows beyond this size, the client will start
* removing data that hasn't been recently used. If you find that your
* application caches too little or too much data, call this method to change
* the cache size. This property must be set before creating your first
* FIRDatabaseReference and only needs to be called once per application.
*
* Note that the specified cache size is only an approximation and the size on
* disk may temporarily exceed it at times.
*/
@property(nonatomic) NSUInteger persistenceCacheSizeBytes;
/**
* Sets the dispatch queue on which all events are raised. The default queue is
* the main queue.
*/
@property(nonatomic, strong) dispatch_queue_t callbackQueue;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,96 @@
/*
* 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 "FirebaseDatabase/Sources/Api/FIRDatabaseConfig.h"
#import "FirebaseDatabase/Sources/FIRDatabaseConfig_Private.h"
#import "FirebaseDatabase/Sources/Login/FIRDatabaseConnectionContextProvider.h"
@interface FIRDatabaseConfig (Private)
@property(nonatomic, strong, readwrite) NSString *sessionIdentifier;
@property(nonatomic, strong, readwrite) NSString *googleAppID;
@end
@implementation FIRDatabaseConfig
- (id)init {
[NSException raise:NSInvalidArgumentException
format:@"Can't create config objects!"];
return nil;
}
- (id)initWithSessionIdentifier:(NSString *)identifier
googleAppID:(NSString *)googleAppID
contextProvider:
(id<FIRDatabaseConnectionContextProvider>)contextProvider {
self = [super init];
if (self != nil) {
self->_sessionIdentifier = identifier;
self->_callbackQueue = dispatch_get_main_queue();
self->_googleAppID = googleAppID;
self->_persistenceCacheSizeBytes =
10 * 1024 * 1024; // Default cache size is 10MB
self->_contextProvider = contextProvider;
}
return self;
}
- (void)assertUnfrozen {
if (self.isFrozen) {
[NSException raise:NSGenericException
format:@"Can't modify config objects after they are in use "
@"for FIRDatabaseReferences."];
}
}
- (void)setContextProvider:
(id<FIRDatabaseConnectionContextProvider>)contextProvider {
[self assertUnfrozen];
self->_contextProvider = contextProvider;
}
- (void)setPersistenceEnabled:(BOOL)persistenceEnabled {
[self assertUnfrozen];
self->_persistenceEnabled = persistenceEnabled;
}
- (void)setPersistenceCacheSizeBytes:(NSUInteger)persistenceCacheSizeBytes {
[self assertUnfrozen];
// Can't be less than 1MB
if (persistenceCacheSizeBytes < 1024 * 1024) {
[NSException raise:NSInvalidArgumentException
format:@"The minimum cache size must be at least 1MB"];
}
if (persistenceCacheSizeBytes > 100 * 1024 * 1024) {
[NSException raise:NSInvalidArgumentException
format:@"Firebase Database currently doesn't support a "
@"cache size larger than 100MB"];
}
self->_persistenceCacheSizeBytes = persistenceCacheSizeBytes;
}
- (void)setCallbackQueue:(dispatch_queue_t)callbackQueue {
[self assertUnfrozen];
self->_callbackQueue = callbackQueue;
}
- (void)freeze {
self->_isFrozen = YES;
}
@end
@@ -0,0 +1,765 @@
/*
* 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 "FirebaseDatabase/Sources/Public/FirebaseDatabase/FIRDatabaseQuery.h"
#import "FirebaseDatabase/Sources/Api/Private/FIRDatabaseQuery_Private.h"
#import "FirebaseDatabase/Sources/Constants/FConstants.h"
#import "FirebaseDatabase/Sources/Core/FQueryParams.h"
#import "FirebaseDatabase/Sources/Core/FQuerySpec.h"
#import "FirebaseDatabase/Sources/Core/Utilities/FPath.h"
#import "FirebaseDatabase/Sources/Core/View/FChildEventRegistration.h"
#import "FirebaseDatabase/Sources/Core/View/FValueEventRegistration.h"
#import "FirebaseDatabase/Sources/FKeyIndex.h"
#import "FirebaseDatabase/Sources/FPathIndex.h"
#import "FirebaseDatabase/Sources/FPriorityIndex.h"
#import "FirebaseDatabase/Sources/FValueIndex.h"
#import "FirebaseDatabase/Sources/Snapshot/FLeafNode.h"
#import "FirebaseDatabase/Sources/Snapshot/FSnapshotUtilities.h"
#import "FirebaseDatabase/Sources/Utilities/FNextPushId.h"
#import "FirebaseDatabase/Sources/Utilities/FValidation.h"
@implementation FIRDatabaseQuery
@synthesize repo;
@synthesize path;
@synthesize queryParams;
#define INVALID_QUERY_PARAM_ERROR @"InvalidQueryParameter"
+ (dispatch_queue_t)sharedQueue {
// We use this shared queue across all of the FQueries so things happen FIFO
// (as opposed to dispatch_get_global_queue(0, 0) which is concurrent)
static dispatch_once_t pred;
static dispatch_queue_t sharedDispatchQueue;
dispatch_once(&pred, ^{
sharedDispatchQueue = dispatch_queue_create("FirebaseWorker", NULL);
});
return sharedDispatchQueue;
}
- (id)initWithRepo:(FRepo *)theRepo path:(FPath *)thePath {
return [self initWithRepo:theRepo
path:thePath
params:nil
orderByCalled:NO
priorityMethodCalled:NO];
}
- (id)initWithRepo:(FRepo *)theRepo
path:(FPath *)thePath
params:(FQueryParams *)theParams
orderByCalled:(BOOL)orderByCalled
priorityMethodCalled:(BOOL)priorityMethodCalled {
self = [super init];
if (self) {
self.repo = theRepo;
self.path = thePath;
if (!theParams) {
theParams = [FQueryParams defaultInstance];
}
if (![theParams isValid]) {
@throw [[NSException alloc]
initWithName:@"InvalidArgumentError"
reason:@"Queries are limited to two constraints"
userInfo:nil];
}
self.queryParams = theParams;
self.orderByCalled = orderByCalled;
self.priorityMethodCalled = priorityMethodCalled;
}
return self;
}
- (FQuerySpec *)querySpec {
return [[FQuerySpec alloc] initWithPath:self.path params:self.queryParams];
}
- (void)validateQueryEndpointsForParams:(FQueryParams *)params {
if ([params.index isEqual:[FKeyIndex keyIndex]]) {
if ([params hasStart]) {
if (params.indexStartKey != [FUtilities minName] &&
params.indexStartKey != [FUtilities maxName]) {
[NSException raise:INVALID_QUERY_PARAM_ERROR
format:@"Can't use queryStartingAtValue:childKey:, "
@"queryStartingAfterValue:childKey:, "
@"or queryEqualTo:andChildKey: in "
@"combination with queryOrderedByKey"];
}
if (![params.indexStartValue.val isKindOfClass:[NSString class]]) {
[NSException raise:INVALID_QUERY_PARAM_ERROR
format:@"Can't use queryStartingAtValue: or "
@"queryStartingAfterValue: "
@"with non-string types when used with "
@"queryOrderedByKey"];
}
}
if ([params hasEnd]) {
if (params.indexEndKey != [FUtilities maxName] &&
params.indexEndKey != [FUtilities minName]) {
[NSException raise:INVALID_QUERY_PARAM_ERROR
format:@"Can't use queryEndingAtValue:childKey: or "
@"queryEndingBeforeValue:childKey: "
@"queryEqualToValue:childKey: in "
@"combination with queryOrderedByKey"];
}
if (![params.indexEndValue.val isKindOfClass:[NSString class]]) {
[NSException
raise:INVALID_QUERY_PARAM_ERROR
format:@"Can't use queryEndingAtValue: or "
@"queryEndingBeforeValue: "
@"with other types than string in combination with "
@"queryOrderedByKey"];
}
}
} else if ([params.index isEqual:[FPriorityIndex priorityIndex]]) {
if (([params hasStart] &&
![FValidation validatePriorityValue:params.indexStartValue.val]) ||
([params hasEnd] &&
![FValidation validatePriorityValue:params.indexEndValue.val])) {
[NSException
raise:INVALID_QUERY_PARAM_ERROR
format:@"When using queryOrderedByPriority, values provided to "
@"queryStartingAtValue:, queryStartingAfterValue:, "
@"queryEndingAtValue:, queryEndingBeforeValue:, or "
@"queryEqualToValue: must be valid priorities."];
}
}
}
- (void)validateEqualToCall {
if ([self.queryParams hasStart]) {
[NSException
raise:INVALID_QUERY_PARAM_ERROR
format:
@"Cannot combine queryEqualToValue: and queryStartingAtValue: "
@"or queryStartingAfterValue:"];
}
if ([self.queryParams hasEnd]) {
[NSException
raise:INVALID_QUERY_PARAM_ERROR
format:@"Cannot combine queryEqualToValue: and queryEndingAtValue: "
@"or queryEndingBeforeValue:"];
}
}
- (void)validateNoPreviousOrderByCalled {
if (self.orderByCalled) {
[NSException raise:INVALID_QUERY_PARAM_ERROR
format:@"Cannot use multiple queryOrderedBy calls!"];
}
}
- (void)validateIndexValueType:(id)type fromMethod:(NSString *)method {
if (type != nil && ![type isKindOfClass:[NSNumber class]] &&
![type isKindOfClass:[NSString class]] &&
![type isKindOfClass:[NSNull class]]) {
[NSException raise:INVALID_QUERY_PARAM_ERROR
format:@"You can only pass nil, NSString or NSNumber to %@",
method];
}
}
- (FIRDatabaseQuery *)queryStartingAtValue:(id)startValue {
return [self queryStartingAtInternal:startValue
childKey:nil
from:@"queryStartingAtValue:"
priorityMethod:NO];
}
- (FIRDatabaseQuery *)queryStartingAtValue:(id)startValue
childKey:(NSString *)childKey {
if ([self.queryParams.index isEqual:[FKeyIndex keyIndex]]) {
@throw [[NSException alloc]
initWithName:INVALID_QUERY_PARAM_ERROR
reason:@"You must use queryStartingAtValue: instead of "
@"queryStartingAtValue:childKey: when using "
@"queryOrderedByKey:"
userInfo:nil];
}
NSString *methodName = @"queryStartingAtValue:childKey:";
if (childKey != nil) {
[FValidation validateFrom:methodName validKey:childKey];
}
return [self queryStartingAtInternal:startValue
childKey:childKey
from:methodName
priorityMethod:NO];
}
- (FIRDatabaseQuery *)queryStartingAfterValue:(id)startAfterValue {
return [self queryStartingAfterValue:startAfterValue childKey:nil];
}
- (FIRDatabaseQuery *)queryStartingAfterValue:(id)startAfterValue
childKey:(NSString *)childKey {
if ([self.queryParams.index isEqual:[FKeyIndex keyIndex]]) {
if (childKey != nil) {
@throw [[NSException alloc]
initWithName:INVALID_QUERY_PARAM_ERROR
reason:
@"You must use queryStartingAfterValue: instead of "
@"queryStartingAfterValue:childKey: when using "
@"queryOrderedByKey:"
userInfo:nil];
}
if ([startAfterValue isKindOfClass:[NSString class]]) {
startAfterValue = [FNextPushId successor:startAfterValue];
}
} else {
if (childKey == nil) {
childKey = [FUtilities maxName];
} else {
childKey = [FNextPushId successor:childKey];
}
}
NSString *methodName = @"queryStartingAfterValue:childKey:";
if (childKey != nil && ![childKey isEqual:[FUtilities maxName]]) {
[FValidation validateFrom:methodName validKey:childKey];
}
return [self queryStartingAtInternal:startAfterValue
childKey:childKey
from:methodName
priorityMethod:NO];
}
- (FIRDatabaseQuery *)queryStartingAtInternal:(id<FNode>)startValue
childKey:(NSString *)childKey
from:(NSString *)methodName
priorityMethod:(BOOL)priorityMethod {
[self validateIndexValueType:startValue fromMethod:methodName];
if ([self.queryParams hasStart]) {
[NSException raise:INVALID_QUERY_PARAM_ERROR
format:@"Can't call %@ after queryStartingAtValue, "
@"queryStartingAfterValue, or "
@"queryEqualToValue was previously called",
methodName];
}
id<FNode> startNode = [FSnapshotUtilities nodeFrom:startValue];
FQueryParams *params = [self.queryParams startAt:startNode
childKey:childKey];
[self validateQueryEndpointsForParams:params];
return [[FIRDatabaseQuery alloc]
initWithRepo:self.repo
path:self.path
params:params
orderByCalled:self.orderByCalled
priorityMethodCalled:priorityMethod || self.priorityMethodCalled];
}
- (FIRDatabaseQuery *)queryEndingAtValue:(id)endValue {
return [self queryEndingAtInternal:endValue
childKey:nil
from:@"queryEndingAtValue:"
priorityMethod:NO];
}
- (FIRDatabaseQuery *)queryEndingAtValue:(id)endValue
childKey:(NSString *)childKey {
if ([self.queryParams.index isEqual:[FKeyIndex keyIndex]]) {
@throw [[NSException alloc]
initWithName:INVALID_QUERY_PARAM_ERROR
reason:@"You must use queryEndingAtValue: instead of "
@"queryEndingAtValue:childKey: when using "
@"queryOrderedByKey:"
userInfo:nil];
}
NSString *methodName = @"queryEndingAtValue:childKey:";
if (childKey != nil) {
[FValidation validateFrom:methodName validKey:childKey];
}
return [self queryEndingAtInternal:endValue
childKey:childKey
from:methodName
priorityMethod:NO];
}
- (FIRDatabaseQuery *)queryEndingBeforeValue:(id)endValue {
return [self queryEndingBeforeValue:endValue childKey:nil];
}
- (FIRDatabaseQuery *)queryEndingBeforeValue:(id)endValue
childKey:(NSString *)childKey {
if ([self.queryParams.index isEqual:[FKeyIndex keyIndex]]) {
if (childKey != nil) {
@throw [[NSException alloc]
initWithName:INVALID_QUERY_PARAM_ERROR
reason:@"You must use queryEndingBeforeValue: instead of "
@"queryEndingBeforeValue:childKey: when using "
@"queryOrderedByKey:"
userInfo:nil];
}
if ([endValue isKindOfClass:[NSString class]]) {
endValue = [FNextPushId predecessor:endValue];
}
} else {
if (childKey == nil) {
childKey = [FUtilities minName];
} else {
childKey = [FNextPushId predecessor:childKey];
}
}
NSString *methodName = @"queryEndingBeforeValue:childKey:";
if (childKey != nil && ![childKey isEqual:[FUtilities minName]]) {
[FValidation validateFrom:methodName validKey:childKey];
}
return [self queryEndingAtInternal:endValue
childKey:childKey
from:methodName
priorityMethod:NO];
}
- (FIRDatabaseQuery *)queryEndingAtInternal:(id)endValue
childKey:(NSString *)childKey
from:(NSString *)methodName
priorityMethod:(BOOL)priorityMethod {
[self validateIndexValueType:endValue fromMethod:methodName];
if ([self.queryParams hasEnd]) {
[NSException raise:INVALID_QUERY_PARAM_ERROR
format:@"Can't call %@ after queryEndingAtValue or "
@"queryEqualToValue was previously called",
methodName];
}
id<FNode> endNode = [FSnapshotUtilities nodeFrom:endValue];
FQueryParams *params = [self.queryParams endAt:endNode childKey:childKey];
[self validateQueryEndpointsForParams:params];
return [[FIRDatabaseQuery alloc]
initWithRepo:self.repo
path:self.path
params:params
orderByCalled:self.orderByCalled
priorityMethodCalled:priorityMethod || self.priorityMethodCalled];
}
- (FIRDatabaseQuery *)queryEqualToValue:(id)value {
return [self queryEqualToInternal:value
childKey:nil
from:@"queryEqualToValue:"
priorityMethod:NO];
}
- (FIRDatabaseQuery *)queryEqualToValue:(id)value
childKey:(NSString *)childKey {
if ([self.queryParams.index isEqual:[FKeyIndex keyIndex]]) {
@throw [[NSException alloc]
initWithName:INVALID_QUERY_PARAM_ERROR
reason:@"You must use queryEqualToValue: instead of "
@"queryEqualTo:childKey: when using queryOrderedByKey:"
userInfo:nil];
}
return [self queryEqualToInternal:value
childKey:childKey
from:@"queryEqualToValue:childKey:"
priorityMethod:NO];
}
- (FIRDatabaseQuery *)queryEqualToInternal:(id)value
childKey:(NSString *)childKey
from:(NSString *)methodName
priorityMethod:(BOOL)priorityMethod {
[self validateIndexValueType:value fromMethod:methodName];
if (childKey != nil) {
[FValidation validateFrom:methodName validKey:childKey];
}
if ([self.queryParams hasEnd] || [self.queryParams hasStart]) {
[NSException raise:INVALID_QUERY_PARAM_ERROR
format:@"Can't call %@ after queryStartingAtValue, "
@"queryStartingAfterValue, queryEndingAtValue, "
@"queryEndingBeforeValue or queryEqualToValue "
@"was previously called",
methodName];
}
id<FNode> node = [FSnapshotUtilities nodeFrom:value];
FQueryParams *params = [[self.queryParams startAt:node
childKey:childKey] endAt:node
childKey:childKey];
[self validateQueryEndpointsForParams:params];
return [[FIRDatabaseQuery alloc]
initWithRepo:self.repo
path:self.path
params:params
orderByCalled:self.orderByCalled
priorityMethodCalled:priorityMethod || self.priorityMethodCalled];
}
- (void)validateLimitRange:(NSUInteger)limit {
// No need to check for negative ranges, since limit is unsigned
if (limit == 0) {
[NSException raise:INVALID_QUERY_PARAM_ERROR
format:@"Limit can't be zero"];
}
if (limit >= 1ul << 31) {
[NSException raise:INVALID_QUERY_PARAM_ERROR
format:@"Limit must be less than 2,147,483,648"];
}
}
- (FIRDatabaseQuery *)queryLimitedToFirst:(NSUInteger)limit {
if (self.queryParams.limitSet) {
[NSException raise:INVALID_QUERY_PARAM_ERROR
format:@"Can't call queryLimitedToFirst: if a limit was "
@"previously set"];
}
[self validateLimitRange:limit];
FQueryParams *params = [self.queryParams limitToFirst:limit];
return [[FIRDatabaseQuery alloc] initWithRepo:self.repo
path:self.path
params:params
orderByCalled:self.orderByCalled
priorityMethodCalled:self.priorityMethodCalled];
}
- (FIRDatabaseQuery *)queryLimitedToLast:(NSUInteger)limit {
if (self.queryParams.limitSet) {
[NSException raise:INVALID_QUERY_PARAM_ERROR
format:@"Can't call queryLimitedToLast: if a limit was "
@"previously set"];
}
[self validateLimitRange:limit];
FQueryParams *params = [self.queryParams limitToLast:limit];
return [[FIRDatabaseQuery alloc] initWithRepo:self.repo
path:self.path
params:params
orderByCalled:self.orderByCalled
priorityMethodCalled:self.priorityMethodCalled];
}
- (FIRDatabaseQuery *)queryOrderedByChild:(NSString *)indexPathString {
if ([indexPathString isEqualToString:@"$key"] ||
[indexPathString isEqualToString:@".key"]) {
@throw [[NSException alloc]
initWithName:INVALID_QUERY_PARAM_ERROR
reason:[NSString stringWithFormat:
@"(queryOrderedByChild:) %@ is invalid. "
@" Use queryOrderedByKey: instead.",
indexPathString]
userInfo:nil];
} else if ([indexPathString isEqualToString:@"$priority"] ||
[indexPathString isEqualToString:@".priority"]) {
@throw [[NSException alloc]
initWithName:INVALID_QUERY_PARAM_ERROR
reason:[NSString stringWithFormat:
@"(queryOrderedByChild:) %@ is invalid. "
@" Use queryOrderedByPriority: instead.",
indexPathString]
userInfo:nil];
} else if ([indexPathString isEqualToString:@"$value"] ||
[indexPathString isEqualToString:@".value"]) {
@throw [[NSException alloc]
initWithName:INVALID_QUERY_PARAM_ERROR
reason:[NSString stringWithFormat:
@"(queryOrderedByChild:) %@ is invalid. "
@" Use queryOrderedByValue: instead.",
indexPathString]
userInfo:nil];
}
[self validateNoPreviousOrderByCalled];
[FValidation validateFrom:@"queryOrderedByChild:"
validPathString:indexPathString];
FPath *indexPath = [FPath pathWithString:indexPathString];
if (indexPath.isEmpty) {
@throw [[NSException alloc]
initWithName:INVALID_QUERY_PARAM_ERROR
reason:[NSString
stringWithFormat:@"(queryOrderedByChild:) with an "
@"empty path is invalid. Use "
@"queryOrderedByValue: instead."]
userInfo:nil];
}
id<FIndex> index = [[FPathIndex alloc] initWithPath:indexPath];
FQueryParams *params = [self.queryParams orderBy:index];
[self validateQueryEndpointsForParams:params];
return [[FIRDatabaseQuery alloc] initWithRepo:self.repo
path:self.path
params:params
orderByCalled:YES
priorityMethodCalled:self.priorityMethodCalled];
}
- (FIRDatabaseQuery *)queryOrderedByKey {
[self validateNoPreviousOrderByCalled];
FQueryParams *params = [self.queryParams orderBy:[FKeyIndex keyIndex]];
[self validateQueryEndpointsForParams:params];
return [[FIRDatabaseQuery alloc] initWithRepo:self.repo
path:self.path
params:params
orderByCalled:YES
priorityMethodCalled:self.priorityMethodCalled];
}
- (FIRDatabaseQuery *)queryOrderedByValue {
[self validateNoPreviousOrderByCalled];
FQueryParams *params = [self.queryParams orderBy:[FValueIndex valueIndex]];
return [[FIRDatabaseQuery alloc] initWithRepo:self.repo
path:self.path
params:params
orderByCalled:YES
priorityMethodCalled:self.priorityMethodCalled];
}
- (FIRDatabaseQuery *)queryOrderedByPriority {
[self validateNoPreviousOrderByCalled];
FQueryParams *params =
[self.queryParams orderBy:[FPriorityIndex priorityIndex]];
return [[FIRDatabaseQuery alloc] initWithRepo:self.repo
path:self.path
params:params
orderByCalled:YES
priorityMethodCalled:self.priorityMethodCalled];
}
- (FIRDatabaseHandle)observeEventType:(FIRDataEventType)eventType
withBlock:(void (^)(FIRDataSnapshot *))block {
[FValidation validateFrom:@"observeEventType:withBlock:"
knownEventType:eventType];
return [self observeEventType:eventType
withBlock:block
withCancelBlock:nil];
}
- (FIRDatabaseHandle)observeEventType:(FIRDataEventType)eventType
andPreviousSiblingKeyWithBlock:(fbt_void_datasnapshot_nsstring)block {
[FValidation
validateFrom:@"observeEventType:andPreviousSiblingKeyWithBlock:"
knownEventType:eventType];
return [self observeEventType:eventType
andPreviousSiblingKeyWithBlock:block
withCancelBlock:nil];
}
- (FIRDatabaseHandle)observeEventType:(FIRDataEventType)eventType
withBlock:(fbt_void_datasnapshot)block
withCancelBlock:(fbt_void_nserror)cancelBlock {
[FValidation validateFrom:@"observeEventType:withBlock:withCancelBlock:"
knownEventType:eventType];
if (eventType == FIRDataEventTypeValue) {
// Handle FIRDataEventTypeValue specially because they shouldn't have
// prevName callbacks
NSUInteger handle = [[FUtilities LUIDGenerator] integerValue];
[self observeValueEventWithHandle:handle
withBlock:block
cancelCallback:cancelBlock];
return handle;
} else {
// Wrap up the userCallback so we can treat everything as a callback
// that has a prevName
fbt_void_datasnapshot userCallback = [block copy];
return [self observeEventType:eventType
andPreviousSiblingKeyWithBlock:^(FIRDataSnapshot *snapshot,
NSString *prevName) {
if (userCallback != nil) {
userCallback(snapshot);
}
}
withCancelBlock:cancelBlock];
}
}
- (FIRDatabaseHandle)observeEventType:(FIRDataEventType)eventType
andPreviousSiblingKeyWithBlock:(fbt_void_datasnapshot_nsstring)block
withCancelBlock:(fbt_void_nserror)cancelBlock {
[FValidation validateFrom:@"observeEventType:"
@"andPreviousSiblingKeyWithBlock:withCancelBlock:"
knownEventType:eventType];
if (eventType == FIRDataEventTypeValue) {
// TODO: This gets hit by observeSingleEventOfType. Need to fix.
/*
@throw [[NSException alloc] initWithName:@"InvalidEventTypeForObserver"
reason:@"(observeEventType:andPreviousSiblingKeyWithBlock:withCancelBlock:)
Cannot use
observeEventType:andPreviousSiblingKeyWithBlock:withCancelBlock: with
FIRDataEventTypeValue. Use observeEventType:withBlock:withCancelBlock:
instead." userInfo:nil];
*/
}
NSUInteger handle = [[FUtilities LUIDGenerator] integerValue];
NSDictionary *callbacks =
@{[NSNumber numberWithInteger:eventType] : [block copy]};
[self observeChildEventWithHandle:handle
withCallbacks:callbacks
cancelCallback:cancelBlock];
return handle;
}
// If we want to distinguish between value event listeners and child event
// listeners, like in the Java client, we can consider exporting this. If we do,
// add argument validation. Otherwise, arguments are validated in the
// public-facing portions of the API. Also, move the FIRDatabaseHandle logic.
- (void)observeValueEventWithHandle:(FIRDatabaseHandle)handle
withBlock:(fbt_void_datasnapshot)block
cancelCallback:(fbt_void_nserror)cancelBlock {
// Note that we don't need to copy the callbacks here, FEventRegistration
// callback properties set to copy
FValueEventRegistration *registration =
[[FValueEventRegistration alloc] initWithRepo:self.repo
handle:handle
callback:block
cancelCallback:cancelBlock];
dispatch_async([FIRDatabaseQuery sharedQueue], ^{
[self.repo addEventRegistration:registration forQuery:self.querySpec];
});
}
// Note: as with the above method, we may wish to expose this at some point.
- (void)observeChildEventWithHandle:(FIRDatabaseHandle)handle
withCallbacks:(NSDictionary *)callbacks
cancelCallback:(fbt_void_nserror)cancelBlock {
// Note that we don't need to copy the callbacks here, FEventRegistration
// callback properties set to copy
FChildEventRegistration *registration =
[[FChildEventRegistration alloc] initWithRepo:self.repo
handle:handle
callbacks:callbacks
cancelCallback:cancelBlock];
dispatch_async([FIRDatabaseQuery sharedQueue], ^{
[self.repo addEventRegistration:registration forQuery:self.querySpec];
});
}
- (void)removeObserverWithHandle:(FIRDatabaseHandle)handle {
FValueEventRegistration *event =
[[FValueEventRegistration alloc] initWithRepo:self.repo
handle:handle
callback:nil
cancelCallback:nil];
dispatch_async([FIRDatabaseQuery sharedQueue], ^{
[self.repo removeEventRegistration:event forQuery:self.querySpec];
});
}
- (void)removeAllObservers {
[self removeObserverWithHandle:NSNotFound];
}
- (void)keepSynced:(BOOL)keepSynced {
if ([self.path.getFront isEqualToString:kDotInfoPrefix]) {
[NSException raise:NSInvalidArgumentException
format:@"Can't keep query on .info tree synced (this "
@"already is the case)."];
}
dispatch_async([FIRDatabaseQuery sharedQueue], ^{
[self.repo keepQuery:self.querySpec synced:keepSynced];
});
}
- (void)getDataWithCompletionBlock:(void (^)(NSError *__nullable error,
FIRDataSnapshot *snapshot))block {
dispatch_async([FIRDatabaseQuery sharedQueue], ^{
[self.repo getData:self withCompletionBlock:block];
});
}
- (void)observeSingleEventOfType:(FIRDataEventType)eventType
withBlock:(fbt_void_datasnapshot)block {
[self observeSingleEventOfType:eventType
withBlock:block
withCancelBlock:nil];
}
- (void)observeSingleEventOfType:(FIRDataEventType)eventType
andPreviousSiblingKeyWithBlock:(fbt_void_datasnapshot_nsstring)block {
[self observeSingleEventOfType:eventType
andPreviousSiblingKeyWithBlock:block
withCancelBlock:nil];
}
- (void)observeSingleEventOfType:(FIRDataEventType)eventType
withBlock:(fbt_void_datasnapshot)block
withCancelBlock:(fbt_void_nserror)cancelBlock {
// XXX: user reported memory leak in method
// "When you copy a block, any references to other blocks from within that
// block are copied if necessary—an entire tree may be copied (from the
// top). If you have block variables and you reference a block from within
// the block, that block will be copied."
// http://developer.apple.com/library/ios/#documentation/cocoa/Conceptual/Blocks/Articles/bxVariables.html#//apple_ref/doc/uid/TP40007502-CH6-SW1
// So... we don't need to do this since inside the on: we copy this block
// off the stack to the heap.
// __block fbt_void_datasnapshot userCallback = [callback copy];
[self observeSingleEventOfType:eventType
andPreviousSiblingKeyWithBlock:^(FIRDataSnapshot *snapshot,
NSString *prevName) {
if (block != nil) {
block(snapshot);
}
}
withCancelBlock:cancelBlock];
}
/**
* Attaches a listener, waits for the first event, and then removes the listener
*/
- (void)observeSingleEventOfType:(FIRDataEventType)eventType
andPreviousSiblingKeyWithBlock:(fbt_void_datasnapshot_nsstring)block
withCancelBlock:(fbt_void_nserror)cancelBlock {
// XXX: user reported memory leak in method
// "When you copy a block, any references to other blocks from within that
// block are copied if necessary—an entire tree may be copied (from the
// top). If you have block variables and you reference a block from within
// the block, that block will be copied."
// http://developer.apple.com/library/ios/#documentation/cocoa/Conceptual/Blocks/Articles/bxVariables.html#//apple_ref/doc/uid/TP40007502-CH6-SW1
// So... we don't need to do this since inside the on: we copy this block
// off the stack to the heap.
// __block fbt_void_datasnapshot userCallback = [callback copy];
__block FIRDatabaseHandle handle;
__block BOOL firstCall = YES;
fbt_void_datasnapshot_nsstring callback = [block copy];
fbt_void_datasnapshot_nsstring wrappedCallback =
^(FIRDataSnapshot *snap, NSString *prevName) {
if (firstCall) {
firstCall = NO;
[self removeObserverWithHandle:handle];
callback(snap, prevName);
}
};
fbt_void_nserror cancelCallback = [cancelBlock copy];
handle = [self observeEventType:eventType
andPreviousSiblingKeyWithBlock:wrappedCallback
withCancelBlock:^(NSError *error) {
[self removeObserverWithHandle:handle];
if (cancelCallback) {
cancelCallback(error);
}
}];
}
- (NSString *)description {
return [NSString
stringWithFormat:@"(%@ %@)", self.path, self.queryParams.description];
}
- (FIRDatabaseReference *)ref {
return [[FIRDatabaseReference alloc] initWithRepo:self.repo path:self.path];
}
@end
@@ -0,0 +1,149 @@
/*
* 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 "FirebaseDatabase/Sources/Public/FirebaseDatabase/FIRMutableData.h"
#import "FirebaseDatabase/Sources/Api/Private/FIRMutableData_Private.h"
#import "FirebaseDatabase/Sources/Core/FSnapshotHolder.h"
#import "FirebaseDatabase/Sources/FNamedNode.h"
#import "FirebaseDatabase/Sources/FTransformedEnumerator.h"
#import "FirebaseDatabase/Sources/Snapshot/FChildrenNode.h"
#import "FirebaseDatabase/Sources/Snapshot/FIndexedNode.h"
#import "FirebaseDatabase/Sources/Snapshot/FSnapshotUtilities.h"
@interface FIRMutableData ()
- (id)initWithPrefixPath:(FPath *)path
andSnapshotHolder:(FSnapshotHolder *)snapshotHolder;
@property(strong, nonatomic) FSnapshotHolder *data;
@property(strong, nonatomic) FPath *prefixPath;
@end
@implementation FIRMutableData
@synthesize data;
@synthesize prefixPath;
- (id)initWithNode:(id<FNode>)node {
FSnapshotHolder *holder = [[FSnapshotHolder alloc] init];
FPath *path = [FPath empty];
[holder updateSnapshot:path withNewSnapshot:node];
return [self initWithPrefixPath:path andSnapshotHolder:holder];
}
- (id)initWithPrefixPath:(FPath *)path
andSnapshotHolder:(FSnapshotHolder *)snapshotHolder {
self = [super init];
if (self) {
self.prefixPath = path;
self.data = snapshotHolder;
}
return self;
}
- (FIRMutableData *)childDataByAppendingPath:(NSString *)path {
FPath *wholePath = [self.prefixPath childFromString:path];
return [[FIRMutableData alloc] initWithPrefixPath:wholePath
andSnapshotHolder:self.data];
}
- (FIRMutableData *)parent {
if ([self.prefixPath isEmpty]) {
return nil;
} else {
FPath *path = [self.prefixPath parent];
return [[FIRMutableData alloc] initWithPrefixPath:path
andSnapshotHolder:self.data];
}
}
- (void)setValue:(id)aValue {
id<FNode> node = [FSnapshotUtilities nodeFrom:aValue
withValidationFrom:@"setValue:"];
[self.data updateSnapshot:self.prefixPath withNewSnapshot:node];
}
- (void)setPriority:(id)aPriority {
id<FNode> node = [self.data getNode:self.prefixPath];
id<FNode> pri = [FSnapshotUtilities nodeFrom:aPriority];
node = [node updatePriority:pri];
[self.data updateSnapshot:self.prefixPath withNewSnapshot:node];
}
- (id)value {
return [[self.data getNode:self.prefixPath] val];
}
- (id)priority {
return [[[self.data getNode:self.prefixPath] getPriority] val];
}
- (BOOL)hasChildren {
id<FNode> node = [self.data getNode:self.prefixPath];
return ![node isLeafNode] && ![(FChildrenNode *)node isEmpty];
}
- (BOOL)hasChildAtPath:(NSString *)path {
id<FNode> node = [self.data getNode:self.prefixPath];
FPath *childPath = [[FPath alloc] initWith:path];
return ![[node getChild:childPath] isEmpty];
}
- (NSUInteger)childrenCount {
return [[self.data getNode:self.prefixPath] numChildren];
}
- (NSString *)key {
return [self.prefixPath getBack];
}
- (id<FNode>)nodeValue {
return [self.data getNode:self.prefixPath];
}
- (NSEnumerator<FIRMutableData *> *)children {
FIndexedNode *indexedNode =
[FIndexedNode indexedNodeWithNode:self.nodeValue];
return [[FTransformedEnumerator alloc]
initWithEnumerator:[indexedNode childEnumerator]
andTransform:^id(FNamedNode *node) {
FPath *childPath = [self.prefixPath childFromString:node.name];
FIRMutableData *childData =
[[FIRMutableData alloc] initWithPrefixPath:childPath
andSnapshotHolder:self.data];
return childData;
}];
}
- (BOOL)isEqualToData:(FIRMutableData *)other {
return self.data == other.data &&
[[self.prefixPath description]
isEqualToString:[other.prefixPath description]];
}
- (NSString *)description {
if (self.key == nil) {
return [NSString
stringWithFormat:@"FIRMutableData (top-most transaction) %@ %@",
self.key, self.value];
} else {
return [NSString
stringWithFormat:@"FIRMutableData (%@) %@", self.key, self.value];
}
}
@end
@@ -0,0 +1,33 @@
/*
* 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 "FirebaseDatabase/Sources/Public/FirebaseDatabase/FIRServerValue.h"
@implementation FIRServerValue
+ (NSDictionary *)timestamp {
static NSDictionary *timestamp = nil;
if (timestamp == nil) {
timestamp = @{@".sv" : @"timestamp"};
}
return timestamp;
}
+ (NSDictionary *)increment:(NSNumber *)delta {
return @{@".sv" : @{@"increment" : delta}};
}
@end
@@ -0,0 +1,39 @@
/*
* 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 "FirebaseDatabase/Sources/Public/FirebaseDatabase/FIRTransactionResult.h"
#import "FirebaseDatabase/Sources/Api/Private/FIRTransactionResult_Private.h"
@implementation FIRTransactionResult
@synthesize update;
@synthesize isSuccess;
+ (FIRTransactionResult *)successWithValue:(FIRMutableData *)value {
FIRTransactionResult *result = [[FIRTransactionResult alloc] init];
result.isSuccess = YES;
result.update = value;
return result;
}
+ (FIRTransactionResult *)abort {
FIRTransactionResult *result = [[FIRTransactionResult alloc] init];
result.isSuccess = NO;
result.update = nil;
return result;
}
@end
@@ -0,0 +1,28 @@
/*
* 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 "FirebaseDatabase/Sources/Api/Private/FTypedefs_Private.h"
#import "FirebaseDatabase/Sources/Public/FirebaseDatabase/FIRDataSnapshot.h"
#import "FirebaseDatabase/Sources/Snapshot/FIndexedNode.h"
@interface FIRDataSnapshot ()
// in _Private for testing purposes
@property(nonatomic, strong) FIndexedNode *node;
- (id)initWithRef:(FIRDatabaseReference *)ref indexedNode:(FIndexedNode *)node;
@end
@@ -0,0 +1,43 @@
/*
* 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 "FirebaseDatabase/Sources/Api/Private/FTypedefs_Private.h"
#import "FirebaseDatabase/Sources/Core/FQueryParams.h"
#import "FirebaseDatabase/Sources/Core/FRepo.h"
#import "FirebaseDatabase/Sources/Core/FRepoManager.h"
#import "FirebaseDatabase/Sources/Core/Utilities/FPath.h"
#import "FirebaseDatabase/Sources/Public/FirebaseDatabase/FIRDatabaseQuery.h"
@interface FIRDatabaseQuery ()
+ (dispatch_queue_t)sharedQueue;
- (id)initWithRepo:(FRepo *)repo path:(FPath *)path;
- (id)initWithRepo:(FRepo *)repo
path:(FPath *)path
params:(FQueryParams *)params
orderByCalled:(BOOL)orderByCalled
priorityMethodCalled:(BOOL)priorityMethodCalled;
@property(nonatomic, strong) FRepo *repo;
@property(nonatomic, strong) FPath *path;
@property(nonatomic, strong) FQueryParams *queryParams;
@property(nonatomic) BOOL orderByCalled;
@property(nonatomic) BOOL priorityMethodCalled;
- (FQuerySpec *)querySpec;
@end
@@ -0,0 +1,27 @@
/*
* 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 "FirebaseDatabase/Sources/Api/FIRDatabaseConfig.h"
#import "FirebaseDatabase/Sources/Api/Private/FTypedefs_Private.h"
#import "FirebaseDatabase/Sources/Core/FRepo.h"
#import "FirebaseDatabase/Sources/Public/FirebaseDatabase/FIRDatabaseReference.h"
@interface FIRDatabaseReference ()
- (id)initWithConfig:(FIRDatabaseConfig *)config;
- (id)initWithRepo:(FRepo *)repo path:(FPath *)path;
@end
@@ -0,0 +1,37 @@
/*
* 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 "FirebaseDatabase/Sources/Public/FirebaseDatabase/FIRDatabase.h"
@class FRepo;
@class FRepoInfo;
@class FIRDatabaseConfig;
@interface FIRDatabase ()
@property(nonatomic, strong) FRepoInfo *repoInfo;
@property(nonatomic, strong) FIRDatabaseConfig *config;
@property(nonatomic, strong) FRepo *repo;
- (id)initWithApp:(FIRApp *)app
repoInfo:(FRepoInfo *)info
config:(FIRDatabaseConfig *)config;
+ (NSString *)buildVersion;
+ (FIRDatabase *)createDatabaseForTests:(FRepoInfo *)repoInfo
config:(FIRDatabaseConfig *)config;
@end
@@ -0,0 +1,26 @@
/*
* 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 "FirebaseDatabase/Sources/Public/FirebaseDatabase/FIRMutableData.h"
#import "FirebaseDatabase/Sources/Snapshot/FNode.h"
@interface FIRMutableData ()
- (id)initWithNode:(id<FNode>)node;
- (id<FNode>)nodeValue;
- (BOOL)isEqualToData:(FIRMutableData *)other;
@end
@@ -0,0 +1,25 @@
/*
* 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 "FirebaseDatabase/Sources/Public/FirebaseDatabase/FIRMutableData.h"
#import "FirebaseDatabase/Sources/Public/FirebaseDatabase/FIRTransactionResult.h"
@interface FIRTransactionResult ()
@property(nonatomic) BOOL isSuccess;
@property(nonatomic, strong) FIRMutableData *update;
@end
@@ -0,0 +1,62 @@
/*
* 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.
*/
#ifndef __FTYPEDEFS_PRIVATE__
#define __FTYPEDEFS_PRIVATE__
#import <Foundation/Foundation.h>
typedef NS_ENUM(NSInteger, FTransactionStatus) {
FTransactionInitializing, // 0
FTransactionRun, // 1
FTransactionSent, // 2
FTransactionCompleted, // 3
FTransactionSentNeedsAbort, // 4
FTransactionNeedsAbort // 5
};
@protocol FNode;
@class FPath;
@class FIRTransactionResult;
@class FIRMutableData;
@class FIRDataSnapshot;
@class FCompoundHash;
typedef void (^fbt_void_nserror_bool_datasnapshot)(NSError *error,
BOOL committed,
FIRDataSnapshot *snapshot);
typedef FIRTransactionResult * (^fbt_transactionresult_mutabledata)(
FIRMutableData *currentData);
typedef void (^fbt_void_path_node)(FPath *, id<FNode>);
typedef void (^fbt_void_nsstring)(NSString *);
typedef BOOL (^fbt_bool_nsstring_node)(NSString *, id<FNode>);
typedef void (^fbt_void_path_node_marray)(FPath *, id<FNode>, NSMutableArray *);
typedef BOOL (^fbt_bool_void)(void);
typedef void (^fbt_void_nsstring_nsstring)(NSString *str1, NSString *str2);
typedef void (^fbt_void_nsstring_id_nsstring)(NSString *str1, id dict1,
NSString *str2);
typedef void (^fbt_void_nsstring_nserror)(NSString *str, NSError *error);
typedef BOOL (^fbt_bool_path)(FPath *str);
typedef void (^fbt_void_id)(id data);
typedef NSString * (^fbt_nsstring_void)(void);
typedef FCompoundHash * (^fbt_compoundhash_void)(void);
typedef NSArray * (^fbt_nsarray_nsstring_id)(NSString *status, id Data);
typedef NSArray * (^fbt_nsarray_nsstring)(NSString *status);
// WWDC 2012 session 712 starting in page 83 for saving blocks in properties
// (use @property (strong) type name).
#endif
@@ -0,0 +1,201 @@
/*
* 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.
*/
#ifndef Firebase_FConstants_h
#define Firebase_FConstants_h
#import <Foundation/Foundation.h>
#pragma mark -
#pragma mark Wire Protocol Envelope Constants
FOUNDATION_EXPORT NSString *const kFWPRequestType;
FOUNDATION_EXPORT NSString *const kFWPRequestTypeData;
FOUNDATION_EXPORT NSString *const kFWPRequestDataPayload;
FOUNDATION_EXPORT NSString *const kFWPRequestNumber;
FOUNDATION_EXPORT NSString *const kFWPRequestPayloadBody;
FOUNDATION_EXPORT NSString *const kFWPRequestError;
FOUNDATION_EXPORT NSString *const kFWPRequestAction;
FOUNDATION_EXPORT NSString *const kFWPResponseForRNData;
FOUNDATION_EXPORT NSString *const kFWPResponseForActionStatus;
FOUNDATION_EXPORT NSString *const kFWPResponseForActionStatusOk;
FOUNDATION_EXPORT NSString *const kFWPResponseForActionStatusFailed;
FOUNDATION_EXPORT NSString *const kFWPResponseForActionStatusDataStale;
FOUNDATION_EXPORT NSString *const kFWPResponseForActionData;
FOUNDATION_EXPORT NSString *const kFWPResponseDataWarnings;
FOUNDATION_EXPORT NSString *const kFWPAsyncServerAction;
FOUNDATION_EXPORT NSString *const kFWPAsyncServerPayloadBody;
FOUNDATION_EXPORT NSString *const kFWPAsyncServerDataUpdate;
FOUNDATION_EXPORT NSString *const kFWPAsyncServerDataMerge;
FOUNDATION_EXPORT NSString *const kFWPAsyncServerDataRangeMerge;
FOUNDATION_EXPORT NSString *const kFWPAsyncServerAuthRevoked;
FOUNDATION_EXPORT NSString *const kFWPASyncServerListenCancelled;
FOUNDATION_EXPORT NSString *const kFWPAsyncServerSecurityDebug;
FOUNDATION_EXPORT NSString
*const kFWPAsyncServerDataUpdateBodyPath; // {"a": "d", "b": {"p": "/", "d":
// "<data>""}}
FOUNDATION_EXPORT NSString *const kFWPAsyncServerDataUpdateBodyData;
FOUNDATION_EXPORT NSString *const kFWPAsyncServerDataUpdateStartPath;
FOUNDATION_EXPORT NSString *const kFWPAsyncServerDataUpdateEndPath;
FOUNDATION_EXPORT NSString *const kFWPAsyncServerDataUpdateRangeMerge;
FOUNDATION_EXPORT NSString *const kFWPAsyncServerDataUpdateBodyTag;
FOUNDATION_EXPORT NSString *const kFWPAsyncServerDataQueries;
FOUNDATION_EXPORT NSString *const kFWPAsyncServerEnvelopeType;
FOUNDATION_EXPORT NSString *const kFWPAsyncServerEnvelopeData;
FOUNDATION_EXPORT NSString *const kFWPAsyncServerControlMessage;
FOUNDATION_EXPORT NSString *const kFWPAsyncServerControlMessageType;
FOUNDATION_EXPORT NSString *const kFWPAsyncServerControlMessageData;
FOUNDATION_EXPORT NSString *const kFWPAsyncServerDataMessage;
FOUNDATION_EXPORT NSString *const kFWPAsyncServerHello;
FOUNDATION_EXPORT NSString *const kFWPAsyncServerHelloTimestamp;
FOUNDATION_EXPORT NSString *const kFWPAsyncServerHelloVersion;
FOUNDATION_EXPORT NSString *const kFWPAsyncServerHelloConnectedHost;
FOUNDATION_EXPORT NSString *const kFWPAsyncServerHelloSession;
FOUNDATION_EXPORT NSString *const kFWPAsyncServerControlMessageShutdown;
FOUNDATION_EXPORT NSString *const kFWPAsyncServerControlMessageReset;
#pragma mark -
#pragma mark Wire Protocol Payload Constants
FOUNDATION_EXPORT NSString *const kFWPRequestActionPut;
FOUNDATION_EXPORT NSString *const kFWPRequestActionMerge;
FOUNDATION_EXPORT NSString *const kFWPRequestActionGet;
FOUNDATION_EXPORT NSString *const kFWPRequestActionTaggedListen;
FOUNDATION_EXPORT NSString *const kFWPRequestActionTaggedUnlisten;
FOUNDATION_EXPORT NSString
*const kFWPRequestActionListen; // {"t": "d", "d": {"r": 1, "a": "l", "b": {
// "p": "/" } } }
FOUNDATION_EXPORT NSString *const kFWPRequestActionUnlisten;
FOUNDATION_EXPORT NSString *const kFWPRequestActionStats;
FOUNDATION_EXPORT NSString *const kFWPRequestActionDisconnectPut;
FOUNDATION_EXPORT NSString *const kFWPRequestActionDisconnectMerge;
FOUNDATION_EXPORT NSString *const kFWPRequestActionDisconnectCancel;
FOUNDATION_EXPORT NSString *const kFWPRequestActionAppCheck;
FOUNDATION_EXPORT NSString *const kFWPRequestActionAuth;
FOUNDATION_EXPORT NSString *const kFWPRequestActionUnauth;
FOUNDATION_EXPORT NSString *const kFWPRequestAppCheckToken;
FOUNDATION_EXPORT NSString *const kFWPRequestCredential;
FOUNDATION_EXPORT NSString *const kFWPRequestPath;
FOUNDATION_EXPORT NSString *const kFWPRequestCounters;
FOUNDATION_EXPORT NSString *const kFWPRequestQueries;
FOUNDATION_EXPORT NSString *const kFWPRequestTag;
FOUNDATION_EXPORT NSString *const kFWPRequestData;
FOUNDATION_EXPORT NSString *const kFWPRequestHash;
FOUNDATION_EXPORT NSString *const kFWPRequestCompoundHash;
FOUNDATION_EXPORT NSString *const kFWPRequestCompoundHashPaths;
FOUNDATION_EXPORT NSString *const kFWPRequestCompoundHashHashes;
FOUNDATION_EXPORT NSString *const kFWPRequestStatus;
#pragma mark -
#pragma mark Websock Transport Constants
FOUNDATION_EXPORT NSString *const kWireProtocolVersionParam;
FOUNDATION_EXPORT NSString *const kWebsocketProtocolVersion;
FOUNDATION_EXPORT NSString *const kWebsocketServerKillPacket;
FOUNDATION_EXPORT NSString *const kPersistentConnectionOffline;
FOUNDATION_EXPORT const int kWebsocketMaxFrameSize;
FOUNDATION_EXPORT NSUInteger const kWebsocketKeepaliveInterval;
FOUNDATION_EXPORT NSUInteger const kWebsocketConnectTimeout;
FOUNDATION_EXPORT UInt64 const kPersistentConnectionGetConnectTimeout;
FOUNDATION_EXPORT float const kPersistentConnReconnectMinDelay;
FOUNDATION_EXPORT float const kPersistentConnReconnectMaxDelay;
FOUNDATION_EXPORT float const kPersistentConnReconnectMultiplier;
FOUNDATION_EXPORT float const
kPersistentConnSuccessfulConnectionEstablishedDelay;
#pragma mark -
#pragma mark Query / QueryParams constants
FOUNDATION_EXPORT NSString *const kQueryDefault;
FOUNDATION_EXPORT NSString *const kQueryDefaultObject;
FOUNDATION_EXPORT NSString *const kViewManagerDictConstView;
FOUNDATION_EXPORT NSString *const kFQPIndexStartValue;
FOUNDATION_EXPORT NSString *const kFQPIndexStartName;
FOUNDATION_EXPORT NSString *const kFQPIndexEndValue;
FOUNDATION_EXPORT NSString *const kFQPIndexEndName;
FOUNDATION_EXPORT NSString *const kFQPLimit;
FOUNDATION_EXPORT NSString *const kFQPViewFrom;
FOUNDATION_EXPORT NSString *const kFQPViewFromLeft;
FOUNDATION_EXPORT NSString *const kFQPViewFromRight;
FOUNDATION_EXPORT NSString *const kFQPIndex;
#pragma mark -
#pragma mark Interrupt Reasons
FOUNDATION_EXPORT NSString *const kFInterruptReasonServerKill;
FOUNDATION_EXPORT NSString *const kFInterruptReasonWaitingForOpen;
FOUNDATION_EXPORT NSString *const kFInterruptReasonRepoInterrupt;
FOUNDATION_EXPORT NSString *const kFInterruptReasonAuthExpired;
#pragma mark -
#pragma mark Payload constants
FOUNDATION_EXPORT NSString *const kPayloadPriority;
FOUNDATION_EXPORT NSString *const kPayloadValue;
FOUNDATION_EXPORT NSString *const kPayloadMetadataPrefix;
#pragma mark -
#pragma mark ServerValue constants
FOUNDATION_EXPORT NSString *const kServerValueSubKey;
FOUNDATION_EXPORT NSString *const kServerValuePriority;
#pragma mark -
#pragma mark.info/ constants
FOUNDATION_EXPORT NSString *const kDotInfoPrefix;
FOUNDATION_EXPORT NSString *const kDotInfoConnected;
FOUNDATION_EXPORT NSString *const kDotInfoServerTimeOffset;
#pragma mark -
#pragma mark ObjectiveC to JavaScript type constants
FOUNDATION_EXPORT NSString *const kJavaScriptObject;
FOUNDATION_EXPORT NSString *const kJavaScriptString;
FOUNDATION_EXPORT NSString *const kJavaScriptBoolean;
FOUNDATION_EXPORT NSString *const kJavaScriptNumber;
FOUNDATION_EXPORT NSString *const kJavaScriptNull;
FOUNDATION_EXPORT NSString *const kJavaScriptTrue;
FOUNDATION_EXPORT NSString *const kJavaScriptFalse;
#pragma mark -
#pragma mark Error handling constants
FOUNDATION_EXPORT NSString *const kFErrorDomain;
FOUNDATION_EXPORT NSUInteger const kFAuthError;
FOUNDATION_EXPORT NSString *const kFErrorWriteCanceled;
#pragma mark -
#pragma mark Validation Constants
FOUNDATION_EXPORT NSUInteger const kFirebaseMaxObjectDepth;
FOUNDATION_EXPORT const unsigned int kFirebaseMaxLeafSize;
#pragma mark -
#pragma mark Transaction Constants
FOUNDATION_EXPORT NSUInteger const kFTransactionMaxRetries;
FOUNDATION_EXPORT NSString *const kFTransactionTooManyRetries;
FOUNDATION_EXPORT NSString *const kFTransactionNoData;
FOUNDATION_EXPORT NSString *const kFTransactionSet;
FOUNDATION_EXPORT NSString *const kFTransactionDisconnect;
#endif
@@ -0,0 +1,191 @@
/*
* 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 "FirebaseDatabase/Sources/Constants/FConstants.h"
#pragma mark -
#pragma mark Wire Protocol Envelope Constants
NSString *const kFWPRequestType = @"t";
NSString *const kFWPRequestTypeData = @"d";
NSString *const kFWPRequestDataPayload = @"d";
NSString *const kFWPRequestNumber = @"r";
NSString *const kFWPRequestPayloadBody = @"b";
NSString *const kFWPRequestError = @"error";
NSString *const kFWPRequestAction = @"a";
NSString *const kFWPResponseForRNData = @"b";
NSString *const kFWPResponseForActionStatus = @"s";
NSString *const kFWPResponseForActionStatusOk = @"ok";
NSString *const kFWPResponseForActionStatusFailed = @"failed";
NSString *const kFWPResponseForActionStatusDataStale = @"datastale";
NSString *const kFWPResponseForActionData = @"d";
NSString *const kFWPResponseDataWarnings = @"w";
NSString *const kFWPAsyncServerAction = @"a";
NSString *const kFWPAsyncServerPayloadBody = @"b";
NSString *const kFWPAsyncServerDataUpdate = @"d";
NSString *const kFWPAsyncServerDataMerge = @"m";
NSString *const kFWPAsyncServerDataRangeMerge = @"rm";
NSString *const kFWPAsyncServerAuthRevoked = @"ac";
NSString *const kFWPASyncServerListenCancelled = @"c";
NSString *const kFWPAsyncServerSecurityDebug = @"sd";
NSString *const kFWPAsyncServerDataUpdateBodyPath =
@"p"; // {"a": "d", "b": {"p": "/", "d": "<data>"}}
NSString *const kFWPAsyncServerDataUpdateBodyData = @"d";
NSString *const kFWPAsyncServerDataUpdateStartPath = @"s";
NSString *const kFWPAsyncServerDataUpdateEndPath = @"e";
NSString *const kFWPAsyncServerDataUpdateRangeMerge = @"m";
NSString *const kFWPAsyncServerDataUpdateBodyTag = @"t";
NSString *const kFWPAsyncServerDataQueries = @"q";
NSString *const kFWPAsyncServerEnvelopeType = @"t";
NSString *const kFWPAsyncServerEnvelopeData = @"d";
NSString *const kFWPAsyncServerControlMessage = @"c";
NSString *const kFWPAsyncServerControlMessageType = @"t";
NSString *const kFWPAsyncServerControlMessageData = @"d";
NSString *const kFWPAsyncServerDataMessage = @"d";
NSString *const kFWPAsyncServerHello = @"h";
NSString *const kFWPAsyncServerHelloTimestamp = @"ts";
NSString *const kFWPAsyncServerHelloVersion = @"v";
NSString *const kFWPAsyncServerHelloConnectedHost = @"h";
NSString *const kFWPAsyncServerHelloSession = @"s";
NSString *const kFWPAsyncServerControlMessageShutdown = @"s";
NSString *const kFWPAsyncServerControlMessageReset = @"r";
#pragma mark -
#pragma mark Wire Protocol Payload Constants
NSString *const kFWPRequestActionPut = @"p";
NSString *const kFWPRequestActionMerge = @"m";
NSString *const kFWPRequestActionGet = @"g";
NSString *const kFWPRequestActionListen =
@"l"; // {"t": "d", "d": {"r": 1, "a": "l", "b": { "p": "/" } } }
NSString *const kFWPRequestActionUnlisten = @"u";
NSString *const kFWPRequestActionStats = @"s";
NSString *const kFWPRequestActionTaggedListen = @"q";
NSString *const kFWPRequestActionTaggedUnlisten = @"n";
NSString *const kFWPRequestActionDisconnectPut = @"o";
NSString *const kFWPRequestActionDisconnectMerge = @"om";
NSString *const kFWPRequestActionDisconnectCancel = @"oc";
NSString *const kFWPRequestActionAuth = @"auth";
NSString *const kFWPRequestActionAppCheck = @"appcheck";
NSString *const kFWPRequestActionUnauth = @"unauth";
NSString *const kFWPRequestAppCheckToken = @"token";
NSString *const kFWPRequestCredential = @"cred";
NSString *const kFWPRequestPath = @"p";
NSString *const kFWPRequestCounters = @"c";
NSString *const kFWPRequestQueries = @"q";
NSString *const kFWPRequestTag = @"t";
NSString *const kFWPRequestData = @"d";
NSString *const kFWPRequestHash = @"h";
NSString *const kFWPRequestCompoundHash = @"ch";
NSString *const kFWPRequestCompoundHashPaths = @"ps";
NSString *const kFWPRequestCompoundHashHashes = @"hs";
NSString *const kFWPRequestStatus = @"s";
#pragma mark -
#pragma mark Websock Transport Constants
NSString *const kWireProtocolVersionParam = @"v";
NSString *const kWebsocketProtocolVersion = @"5";
NSString *const kWebsocketServerKillPacket = @"kill";
NSString *const kPersistentConnectionOffline = @"Client is offline.";
const int kWebsocketMaxFrameSize = 16384;
NSUInteger const kWebsocketKeepaliveInterval = 45;
NSUInteger const kWebsocketConnectTimeout = 30;
UInt64 const kPersistentConnectionGetConnectTimeout = 3 * NSEC_PER_SEC;
float const kPersistentConnReconnectMinDelay = 1.0;
float const kPersistentConnReconnectMaxDelay = 30.0;
float const kPersistentConnReconnectMultiplier = 1.3f;
float const kPersistentConnSuccessfulConnectionEstablishedDelay = 30.0;
#pragma mark -
#pragma mark Query constants
NSString *const kQueryDefault = @"default";
NSString *const kQueryDefaultObject = @"{}";
NSString *const kViewManagerDictConstView = @"view";
NSString *const kFQPIndexStartValue = @"sp";
NSString *const kFQPIndexStartName = @"sn";
NSString *const kFQPIndexEndValue = @"ep";
NSString *const kFQPIndexEndName = @"en";
NSString *const kFQPLimit = @"l";
NSString *const kFQPViewFrom = @"vf";
NSString *const kFQPViewFromLeft = @"l";
NSString *const kFQPViewFromRight = @"r";
NSString *const kFQPIndex = @"i";
#pragma mark -
#pragma mark Interrupt Reasons
NSString *const kFInterruptReasonServerKill = @"server_kill";
NSString *const kFInterruptReasonWaitingForOpen = @"waiting_for_open";
NSString *const kFInterruptReasonRepoInterrupt = @"repo_interrupt";
#pragma mark -
#pragma mark Payload constants
NSString *const kPayloadPriority = @".priority";
NSString *const kPayloadValue = @".value";
NSString *const kPayloadMetadataPrefix = @".";
#pragma mark -
#pragma mark ServerValue constants
NSString *const kServerValueSubKey = @".sv";
NSString *const kServerValuePriority = @"timestamp";
#pragma mark -
#pragma mark.info/ constants
NSString *const kDotInfoPrefix = @".info";
NSString *const kDotInfoConnected = @"connected";
NSString *const kDotInfoServerTimeOffset = @"serverTimeOffset";
#pragma mark -
#pragma mark ObjectiveC to JavaScript type constants
NSString *const kJavaScriptObject = @"object";
NSString *const kJavaScriptString = @"string";
NSString *const kJavaScriptBoolean = @"boolean";
NSString *const kJavaScriptNumber = @"number";
NSString *const kJavaScriptNull = @"null";
NSString *const kJavaScriptTrue = @"true";
NSString *const kJavaScriptFalse = @"false";
#pragma mark -
#pragma mark Error handling constants
NSString *const kFErrorDomain = @"com.firebase";
NSUInteger const kFAuthError = 1;
NSString *const kFErrorWriteCanceled = @"write_canceled";
#pragma mark -
#pragma mark Validation Constants
NSUInteger const kFirebaseMaxObjectDepth = 1000;
const unsigned int kFirebaseMaxLeafSize = 1024 * 1024 * 10; // 10 MB
#pragma mark -
#pragma mark Transaction Constants
NSUInteger const kFTransactionMaxRetries = 25;
NSString *const kFTransactionTooManyRetries = @"maxretry";
NSString *const kFTransactionNoData = @"nodata";
NSString *const kFTransactionSet = @"set";
NSString *const kFTransactionDisconnect = @"disconnect";
@@ -0,0 +1,38 @@
/*
* 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 <Foundation/Foundation.h>
#import "FirebaseDatabase/Sources/Snapshot/FNode.h"
@interface FCompoundHashBuilder : NSObject
- (FPath *)currentPath;
@end
typedef BOOL (^FCompoundHashSplitStrategy)(FCompoundHashBuilder *builder);
@interface FCompoundHash : NSObject
@property(nonatomic, strong, readonly) NSArray *posts;
@property(nonatomic, strong, readonly) NSArray *hashes;
+ (FCompoundHash *)fromNode:(id<FNode>)node;
+ (FCompoundHash *)fromNode:(id<FNode>)node
splitStrategy:(FCompoundHashSplitStrategy)strategy;
@end
@@ -0,0 +1,259 @@
/*
* 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 "FirebaseDatabase/Sources/Core/FCompoundHash.h"
#import "FirebaseDatabase/Sources/Snapshot/FChildrenNode.h"
#import "FirebaseDatabase/Sources/Snapshot/FLeafNode.h"
#import "FirebaseDatabase/Sources/Snapshot/FSnapshotUtilities.h"
#import "FirebaseDatabase/Sources/Utilities/FStringUtilities.h"
@interface FCompoundHashBuilder ()
@property(nonatomic, strong) FCompoundHashSplitStrategy splitStrategy;
@property(nonatomic, strong) NSMutableArray *currentPaths;
@property(nonatomic, strong) NSMutableArray *currentHashes;
@end
@implementation FCompoundHashBuilder {
// NOTE: We use the existence of this to know if we've started building a
// range (i.e. encountered a leaf node).
NSMutableString *optHashValueBuilder;
// The current path as a stack. This is used in combination with
// currentPathDepth to simultaneously store the last leaf node path. The
// depth is changed when descending and ascending, at the same time the
// current key is set for the current depth. Because the keys are left
// unchanged for ascending the path will also contain the path of the last
// visited leaf node (using lastLeafDepth elements)
NSMutableArray *currentPath;
NSInteger lastLeafDepth;
NSInteger currentPathDepth;
BOOL needsComma;
}
- (instancetype)initWithSplitStrategy:(FCompoundHashSplitStrategy)strategy {
self = [super init];
if (self != nil) {
self->_splitStrategy = strategy;
self->optHashValueBuilder = nil;
self->currentPath = [NSMutableArray array];
self->lastLeafDepth = -1;
self->currentPathDepth = 0;
self->needsComma = YES;
self->_currentPaths = [NSMutableArray array];
self->_currentHashes = [NSMutableArray array];
}
return self;
}
- (BOOL)isBuildingRange {
return self->optHashValueBuilder != nil;
}
- (NSUInteger)currentHashLength {
return self->optHashValueBuilder.length;
}
- (FPath *)currentPath {
return [self currentPathWithDepth:self->currentPathDepth];
}
- (FPath *)currentPathWithDepth:(NSInteger)depth {
NSArray *pieces =
[self->currentPath subarrayWithRange:NSMakeRange(0, depth)];
return [[FPath alloc] initWithPieces:pieces andPieceNum:0];
}
- (void)enumerateCurrentPathToDepth:(NSInteger)depth
withBlock:(void (^)(NSString *key))block {
for (NSInteger i = 0; i < depth; i++) {
block(self->currentPath[i]);
}
}
- (void)appendKey:(NSString *)key toString:(NSMutableString *)string {
[FSnapshotUtilities appendHashV2RepresentationForString:key
toString:string];
}
- (void)ensureRange {
if (![self isBuildingRange]) {
optHashValueBuilder = [NSMutableString string];
[optHashValueBuilder appendString:@"("];
[self
enumerateCurrentPathToDepth:self->currentPathDepth
withBlock:^(NSString *key) {
[self appendKey:key
toString:self->optHashValueBuilder];
[self->optHashValueBuilder appendString:@":("];
}];
self->needsComma = NO;
}
}
- (void)processLeaf:(FLeafNode *)leafNode {
[self ensureRange];
self->lastLeafDepth = self->currentPathDepth;
[FSnapshotUtilities
appendHashRepresentationForLeafNode:leafNode
toString:self->optHashValueBuilder
hashVersion:FDataHashVersionV2];
self->needsComma = YES;
if (self.splitStrategy(self)) {
[self endRange];
}
}
- (void)startChild:(NSString *)key {
[self ensureRange];
if (self->needsComma) {
[self->optHashValueBuilder appendString:@","];
}
[self appendKey:key toString:self->optHashValueBuilder];
[self->optHashValueBuilder appendString:@":("];
if (self->currentPathDepth == currentPath.count) {
[self->currentPath addObject:key];
} else {
self->currentPath[self->currentPathDepth] = key;
}
self->currentPathDepth++;
self->needsComma = NO;
}
- (void)endChild {
self->currentPathDepth--;
if ([self isBuildingRange]) {
[self->optHashValueBuilder appendString:@")"];
}
self->needsComma = YES;
}
- (void)finishHashing {
NSAssert(self->currentPathDepth == 0,
@"Can't finish hashing in the middle of processing a child");
if ([self isBuildingRange]) {
[self endRange];
}
// Always close with the empty hash for the remaining range to allow simple
// appending
[self.currentHashes addObject:@""];
}
- (void)endRange {
NSAssert([self isBuildingRange],
@"Can't end range without starting a range!");
// Add closing parenthesis for current depth
for (NSUInteger i = 0; i < currentPathDepth; i++) {
[self->optHashValueBuilder appendString:@")"];
}
[self->optHashValueBuilder appendString:@")"];
FPath *lastLeafPath = [self currentPathWithDepth:self->lastLeafDepth];
NSString *hash =
[FStringUtilities base64EncodedSha1:self->optHashValueBuilder];
[self.currentHashes addObject:hash];
[self.currentPaths addObject:lastLeafPath];
self->optHashValueBuilder = nil;
}
@end
@interface FCompoundHash ()
@property(nonatomic, strong, readwrite) NSArray *posts;
@property(nonatomic, strong, readwrite) NSArray *hashes;
@end
@implementation FCompoundHash
- (id)initWithPosts:(NSArray *)posts hashes:(NSArray *)hashes {
self = [super init];
if (self != nil) {
if (posts.count != hashes.count - 1) {
[NSException raise:NSInvalidArgumentException
format:@"Number of posts need to be n-1 for n hashes "
@"in FCompoundHash"];
}
self.posts = posts;
self.hashes = hashes;
}
return self;
}
+ (FCompoundHashSplitStrategy)simpleSizeSplitStrategyForNode:(id<FNode>)node {
NSUInteger estimatedSize =
[FSnapshotUtilities estimateSerializedNodeSize:node];
// Splits for
// 1k -> 512 (2 parts)
// 5k -> 715 (7 parts)
// 100k -> 3.2k (32 parts)
// 500k -> 7k (71 parts)
// 5M -> 23k (228 parts)
NSUInteger splitThreshold = MAX(512, (NSUInteger)sqrt(estimatedSize * 100));
return ^BOOL(FCompoundHashBuilder *builder) {
// Never split on priorities
return [builder currentHashLength] > splitThreshold &&
![[[builder currentPath] getBack] isEqualToString:@".priority"];
};
}
+ (FCompoundHash *)fromNode:(id<FNode>)node {
return [FCompoundHash
fromNode:node
splitStrategy:[FCompoundHash simpleSizeSplitStrategyForNode:node]];
}
+ (FCompoundHash *)fromNode:(id<FNode>)node
splitStrategy:(FCompoundHashSplitStrategy)strategy {
if ([node isEmpty]) {
return [[FCompoundHash alloc] initWithPosts:@[] hashes:@[ @"" ]];
} else {
FCompoundHashBuilder *builder =
[[FCompoundHashBuilder alloc] initWithSplitStrategy:strategy];
[FCompoundHash processNode:node builder:builder];
[builder finishHashing];
return [[FCompoundHash alloc] initWithPosts:builder.currentPaths
hashes:builder.currentHashes];
}
}
+ (void)processNode:(id<FNode>)node builder:(FCompoundHashBuilder *)builder {
if ([node isLeafNode]) {
[builder processLeaf:node];
} else {
NSAssert(![node isEmpty], @"Can't calculate hash on empty node!");
FChildrenNode *childrenNode = (FChildrenNode *)node;
[childrenNode enumerateChildrenAndPriorityUsingBlock:^(
NSString *key, id<FNode> node, BOOL *stop) {
[builder startChild:key];
[self processNode:node builder:builder];
[builder endChild];
}];
}
}
@end
@@ -0,0 +1,32 @@
/*
* 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 "FirebaseDatabase/Sources/Api/Private/FTypedefs_Private.h"
@class FQuerySpec;
@protocol FSyncTreeHash;
typedef NSArray * (^fbt_startListeningBlock)(FQuerySpec *query, NSNumber *tagId,
id<FSyncTreeHash> hash,
fbt_nsarray_nsstring onComplete);
typedef void (^fbt_stopListeningBlock)(FQuerySpec *query, NSNumber *tagId);
@interface FListenProvider : NSObject
@property(nonatomic, copy) fbt_startListeningBlock startListening;
@property(nonatomic, copy) fbt_stopListeningBlock stopListening;
@end
@@ -0,0 +1,25 @@
/*
* 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 "FirebaseDatabase/Sources/Core/FListenProvider.h"
#import "FirebaseDatabase/Sources/Public/FirebaseDatabase/FIRDatabaseQuery.h"
@implementation FListenProvider
@synthesize startListening;
@synthesize stopListening;
@end
@@ -0,0 +1,103 @@
/*
* 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 "FirebaseDatabase/Sources/Api/Private/FTypedefs_Private.h"
#import "FirebaseDatabase/Sources/Core/FRepoInfo.h"
#import "FirebaseDatabase/Sources/Realtime/FConnection.h"
#import "FirebaseDatabase/Sources/Utilities/FTypedefs.h"
#import <Foundation/Foundation.h>
@protocol FPersistentConnectionDelegate;
@protocol FSyncTreeHash;
@class FQuerySpec;
@class FIRDatabaseConfig;
@interface FPersistentConnection : NSObject <FConnectionDelegate>
@property(nonatomic, weak) id<FPersistentConnectionDelegate> delegate;
@property(nonatomic) BOOL pauseWrites;
- (id)initWithRepoInfo:(FRepoInfo *)repoInfo
dispatchQueue:(dispatch_queue_t)queue
config:(FIRDatabaseConfig *)config;
- (void)open;
- (void)putData:(id)data
forPath:(NSString *)pathString
withHash:(NSString *)hash
withCallback:(fbt_void_nsstring_nsstring)onComplete;
- (void)mergeData:(id)data
forPath:(NSString *)pathString
withCallback:(fbt_void_nsstring_nsstring)onComplete;
- (void)listen:(FQuerySpec *)query
tagId:(NSNumber *)tagId
hash:(id<FSyncTreeHash>)hash
onComplete:(fbt_void_nsstring)onComplete;
- (void)unlisten:(FQuerySpec *)query tagId:(NSNumber *)tagId;
- (void)refreshAuthToken:(NSString *)token;
- (void)refreshAppCheckToken:(NSString *)token;
- (void)onDisconnectPutData:(id)data
forPath:(FPath *)path
withCallback:(fbt_void_nsstring_nsstring)callback;
- (void)onDisconnectMergeData:(id)data
forPath:(FPath *)path
withCallback:(fbt_void_nsstring_nsstring)callback;
- (void)onDisconnectCancelPath:(FPath *)path
withCallback:(fbt_void_nsstring_nsstring)callback;
- (void)ackPuts;
- (void)getDataAtPath:(NSString *)pathString
withParams:(NSDictionary *)queryWireProtocolParams
withCallback:(fbt_void_nsstring_id_nsstring)onComplete;
- (void)purgeOutstandingWrites;
- (void)interruptForReason:(NSString *)reason;
- (void)resumeForReason:(NSString *)reason;
- (BOOL)isInterruptedForReason:(NSString *)reason;
// FConnection delegate methods
- (void)onReady:(FConnection *)fconnection
atTime:(NSNumber *)timestamp
sessionID:(NSString *)sessionID;
- (void)onDataMessage:(FConnection *)fconnection
withMessage:(NSDictionary *)message;
- (void)onDisconnect:(FConnection *)fconnection
withReason:(FDisconnectReason)reason;
- (void)onKill:(FConnection *)fconnection withReason:(NSString *)reason;
// Testing methods
- (NSDictionary *)dumpListens;
@end
@protocol FPersistentConnectionDelegate <NSObject>
- (void)onDataUpdate:(FPersistentConnection *)fpconnection
forPath:(NSString *)pathString
message:(id)message
isMerge:(BOOL)isMerge
tagId:(NSNumber *)tagId;
- (void)onRangeMerge:(NSArray *)ranges
forPath:(NSString *)path
tagId:(NSNumber *)tag;
- (void)onConnect:(FPersistentConnection *)fpconnection;
- (void)onDisconnect:(FPersistentConnection *)fpconnection;
- (void)onServerInfoUpdate:(FPersistentConnection *)fpconnection
updates:(NSDictionary *)updates;
@end
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,60 @@
/*
* 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 <Foundation/Foundation.h>
@protocol FIndex
, FNodeFilter, FNode;
@interface FQueryParams : NSObject <NSCopying>
@property(nonatomic, readonly) BOOL limitSet;
@property(nonatomic, readonly) NSInteger limit;
@property(nonatomic, strong, readonly) NSString *viewFrom;
@property(nonatomic, strong, readonly) id<FNode> indexStartValue;
@property(nonatomic, strong, readonly) NSString *indexStartKey;
@property(nonatomic, strong, readonly) id<FNode> indexEndValue;
@property(nonatomic, strong, readonly) NSString *indexEndKey;
@property(nonatomic, strong, readonly) id<FIndex> index;
- (BOOL)loadsAllData;
- (BOOL)isDefault;
- (BOOL)isValid;
- (BOOL)hasAnchoredLimit;
- (FQueryParams *)limitTo:(NSInteger)limit;
- (FQueryParams *)limitToFirst:(NSInteger)newLimit;
- (FQueryParams *)limitToLast:(NSInteger)newLimit;
- (FQueryParams *)startAt:(id<FNode>)indexValue childKey:(NSString *)key;
- (FQueryParams *)startAt:(id<FNode>)indexValue;
- (FQueryParams *)endAt:(id<FNode>)indexValue childKey:(NSString *)key;
- (FQueryParams *)endAt:(id<FNode>)indexValue;
- (FQueryParams *)orderBy:(id<FIndex>)index;
+ (FQueryParams *)defaultInstance;
+ (FQueryParams *)fromQueryObject:(NSDictionary *)dict;
- (BOOL)hasStart;
- (BOOL)hasEnd;
- (NSDictionary *)wireProtocolParams;
- (BOOL)isViewFromLeft;
- (id<FNodeFilter>)nodeFilter;
@end
@@ -0,0 +1,393 @@
/*
* 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 "FirebaseDatabase/Sources/Core/FQueryParams.h"
#import "FirebaseDatabase/Sources/Constants/FConstants.h"
#import "FirebaseDatabase/Sources/Core/View/Filter/FIndexedFilter.h"
#import "FirebaseDatabase/Sources/Core/View/Filter/FLimitedFilter.h"
#import "FirebaseDatabase/Sources/Core/View/Filter/FNodeFilter.h"
#import "FirebaseDatabase/Sources/FIndex.h"
#import "FirebaseDatabase/Sources/FPriorityIndex.h"
#import "FirebaseDatabase/Sources/FRangedFilter.h"
#import "FirebaseDatabase/Sources/Snapshot/FNode.h"
#import "FirebaseDatabase/Sources/Snapshot/FSnapshotUtilities.h"
#import "FirebaseDatabase/Sources/Utilities/FUtilities.h"
#import "FirebaseDatabase/Sources/Utilities/FValidation.h"
@interface FQueryParams ()
@property(nonatomic, readwrite) BOOL limitSet;
@property(nonatomic, readwrite) NSInteger limit;
@property(nonatomic, strong, readwrite) NSString *viewFrom;
/**
* indexStartValue is anything you can store as a priority / value.
*/
@property(nonatomic, strong, readwrite) id<FNode> indexStartValue;
@property(nonatomic, strong, readwrite) NSString *indexStartKey;
/**
* indexStartValue is anything you can store as a priority / value.
*/
@property(nonatomic, strong, readwrite) id<FNode> indexEndValue;
@property(nonatomic, strong, readwrite) NSString *indexEndKey;
@property(nonatomic, strong, readwrite) id<FIndex> index;
@end
@implementation FQueryParams
+ (FQueryParams *)defaultInstance {
static FQueryParams *defaultParams = nil;
static dispatch_once_t defaultParamsToken;
dispatch_once(&defaultParamsToken, ^{
defaultParams = [[FQueryParams alloc] init];
});
return defaultParams;
}
- (id)init {
self = [super init];
if (self) {
self->_limitSet = NO;
self->_limit = 0;
self->_viewFrom = nil;
self->_indexStartValue = nil;
self->_indexStartKey = nil;
self->_indexEndValue = nil;
self->_indexEndKey = nil;
self->_index = [FPriorityIndex priorityIndex];
}
return self;
}
/**
* Only valid if hasStart is true
*/
- (id)indexStartValue {
NSAssert([self hasStart], @"Only valid if start has been set");
return _indexStartValue;
}
/**
* Only valid if hasStart is true.
* @return The starting key name for the range defined by these query parameters
*/
- (NSString *)indexStartKey {
NSAssert([self hasStart], @"Only valid if start has been set");
if (_indexStartKey == nil) {
return [FUtilities minName];
} else {
return _indexStartKey;
}
}
/**
* Only valid if hasEnd is true.
*/
- (id)indexEndValue {
NSAssert([self hasEnd], @"Only valid if end has been set");
return _indexEndValue;
}
/**
* Only valid if hasEnd is true.
* @return The end key name for the range defined by these query parameters
*/
- (NSString *)indexEndKey {
NSAssert([self hasEnd], @"Only valid if end has been set");
if (_indexEndKey == nil) {
return [FUtilities maxName];
} else {
return _indexEndKey;
}
}
/**
* @return true if a limit has been set and has been explicitly anchored
*/
- (BOOL)hasAnchoredLimit {
return self.limitSet && self.viewFrom != nil;
}
/**
* Only valid to call if limitSet returns true
*/
- (NSInteger)limit {
NSAssert(self.limitSet, @"Only valid if limit has been set");
return _limit;
}
- (BOOL)hasStart {
return self->_indexStartValue != nil;
}
- (BOOL)hasEnd {
return self->_indexEndValue != nil;
}
- (id)copyWithZone:(NSZone *)zone {
// Immutable
return self;
}
- (id)mutableCopy {
FQueryParams *other = [[[self class] alloc] init];
// Maybe need to do extra copying here
other->_limitSet = _limitSet;
other->_limit = _limit;
other->_indexStartValue = _indexStartValue;
other->_indexStartKey = _indexStartKey;
other->_indexEndValue = _indexEndValue;
other->_indexEndKey = _indexEndKey;
other->_viewFrom = _viewFrom;
other->_index = _index;
return other;
}
- (FQueryParams *)limitTo:(NSInteger)newLimit {
FQueryParams *newParams = [self mutableCopy];
newParams->_limitSet = YES;
newParams->_limit = newLimit;
newParams->_viewFrom = nil;
return newParams;
}
- (FQueryParams *)limitToFirst:(NSInteger)newLimit {
FQueryParams *newParams = [self mutableCopy];
newParams->_limitSet = YES;
newParams->_limit = newLimit;
newParams->_viewFrom = kFQPViewFromLeft;
return newParams;
}
- (FQueryParams *)limitToLast:(NSInteger)newLimit {
FQueryParams *newParams = [self mutableCopy];
newParams->_limitSet = YES;
newParams->_limit = newLimit;
newParams->_viewFrom = kFQPViewFromRight;
return newParams;
}
- (FQueryParams *)startAt:(id<FNode>)indexValue childKey:(NSString *)key {
NSAssert([indexValue isLeafNode] || [indexValue isEmpty], nil);
FQueryParams *newParams = [self mutableCopy];
newParams->_indexStartValue = indexValue;
newParams->_indexStartKey = key;
return newParams;
}
- (FQueryParams *)startAt:(id<FNode>)indexValue {
return [self startAt:indexValue childKey:nil];
}
- (FQueryParams *)endAt:(id<FNode>)indexValue childKey:(NSString *)key {
NSAssert([indexValue isLeafNode] || [indexValue isEmpty], nil);
FQueryParams *newParams = [self mutableCopy];
newParams->_indexEndValue = indexValue;
newParams->_indexEndKey = key;
return newParams;
}
- (FQueryParams *)endAt:(id<FNode>)indexValue {
return [self endAt:indexValue childKey:nil];
}
- (FQueryParams *)orderBy:(id)newIndex {
FQueryParams *newParams = [self mutableCopy];
newParams->_index = newIndex;
return newParams;
}
- (NSDictionary *)wireProtocolParams {
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
if ([self hasStart]) {
[dict setObject:[self.indexStartValue valForExport:YES]
forKey:kFQPIndexStartValue];
// Don't use property as it will be [MIN-NAME]
if (self->_indexStartKey != nil) {
[dict setObject:self->_indexStartKey forKey:kFQPIndexStartName];
}
}
if ([self hasEnd]) {
[dict setObject:[self.indexEndValue valForExport:YES]
forKey:kFQPIndexEndValue];
// Don't use property as it will be [MAX-NAME]
if (self->_indexEndKey != nil) {
[dict setObject:self->_indexEndKey forKey:kFQPIndexEndName];
}
}
if (self.limitSet) {
[dict setObject:[NSNumber numberWithInteger:self.limit]
forKey:kFQPLimit];
NSString *vf = self.viewFrom;
if (vf == nil) {
// limit() rather than limitToFirst or limitToLast was called.
// This means that only one of startSet or endSet is true. Use them
// to calculate which side of the view to anchor to. If neither is
// set, Anchor to end
if ([self hasStart]) {
vf = kFQPViewFromLeft;
} else {
vf = kFQPViewFromRight;
}
}
[dict setObject:vf forKey:kFQPViewFrom];
}
// For now, priority index is the default, so we only specify if it's some
// other index.
if (![self.index isEqual:[FPriorityIndex priorityIndex]]) {
[dict setObject:[self.index queryDefinition] forKey:kFQPIndex];
}
return dict;
}
+ (FQueryParams *)fromQueryObject:(NSDictionary *)dict {
if (dict.count == 0) {
return [FQueryParams defaultInstance];
}
FQueryParams *params = [[FQueryParams alloc] init];
if (dict[kFQPLimit] != nil) {
params->_limitSet = YES;
params->_limit = [dict[kFQPLimit] integerValue];
}
if (dict[kFQPIndexStartValue] != nil) {
params->_indexStartValue =
[FSnapshotUtilities nodeFrom:dict[kFQPIndexStartValue]];
if (dict[kFQPIndexStartName] != nil) {
params->_indexStartKey = dict[kFQPIndexStartName];
}
}
if (dict[kFQPIndexEndValue] != nil) {
params->_indexEndValue =
[FSnapshotUtilities nodeFrom:dict[kFQPIndexEndValue]];
if (dict[kFQPIndexEndName] != nil) {
params->_indexEndKey = dict[kFQPIndexEndName];
}
}
if (dict[kFQPViewFrom] != nil) {
NSString *viewFrom = dict[kFQPViewFrom];
if (![viewFrom isEqualToString:kFQPViewFromLeft] &&
![viewFrom isEqualToString:kFQPViewFromRight]) {
[NSException raise:NSInvalidArgumentException
format:@"Unknown view from paramter: %@", viewFrom];
}
params->_viewFrom = viewFrom;
}
NSString *index = dict[kFQPIndex];
if (index != nil) {
params->_index = [FIndex indexFromQueryDefinition:index];
}
return params;
}
- (BOOL)isViewFromLeft {
if (self.viewFrom != nil) {
// Not null, we can just check
return [self.viewFrom isEqualToString:kFQPViewFromLeft];
} else {
// If start is set, it's view from left. Otherwise not.
return self.hasStart;
}
}
- (id<FNodeFilter>)nodeFilter {
if (self.loadsAllData) {
return [[FIndexedFilter alloc] initWithIndex:self.index];
} else if (self.limitSet) {
return [[FLimitedFilter alloc] initWithQueryParams:self];
} else {
return [[FRangedFilter alloc] initWithQueryParams:self];
}
}
- (BOOL)isValid {
return !(self.hasStart && self.hasEnd && self.limitSet &&
!self.hasAnchoredLimit);
}
- (BOOL)loadsAllData {
return !(self.hasStart || self.hasEnd || self.limitSet);
}
- (BOOL)isDefault {
return [self loadsAllData] &&
[self.index isEqual:[FPriorityIndex priorityIndex]];
}
- (NSString *)description {
return [[self wireProtocolParams] description];
}
- (BOOL)isEqual:(id)obj {
if (self == obj) {
return YES;
}
if (![obj isKindOfClass:[self class]]) {
return NO;
}
FQueryParams *other = (FQueryParams *)obj;
if (self->_limitSet != other->_limitSet)
return NO;
if (self->_limit != other->_limit)
return NO;
if ((self->_index != other->_index) &&
![self->_index isEqual:other->_index])
return NO;
if ((self->_indexStartKey != other->_indexStartKey) &&
![self->_indexStartKey isEqualToString:other->_indexStartKey])
return NO;
if ((self->_indexStartValue != other->_indexStartValue) &&
![self->_indexStartValue isEqual:other->_indexStartValue])
return NO;
if ((self->_indexEndKey != other->_indexEndKey) &&
![self->_indexEndKey isEqualToString:other->_indexEndKey])
return NO;
if ((self->_indexEndValue != other->_indexEndValue) &&
![self->_indexEndValue isEqual:other->_indexEndValue])
return NO;
if ([self isViewFromLeft] != [other isViewFromLeft])
return NO;
return YES;
}
- (NSUInteger)hash {
NSUInteger result = _limitSet ? _limit : 0;
result = 31 * result + ([self isViewFromLeft] ? 1231 : 1237);
result = 31 * result + [_indexStartKey hash];
result = 31 * result + [_indexStartValue hash];
result = 31 * result + [_indexEndKey hash];
result = 31 * result + [_indexEndValue hash];
result = 31 * result + [_index hash];
return result;
}
@end
@@ -0,0 +1,36 @@
/*
* 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 <Foundation/Foundation.h>
#import "FirebaseDatabase/Sources/Core/FQueryParams.h"
#import "FirebaseDatabase/Sources/Core/Utilities/FPath.h"
#import "FirebaseDatabase/Sources/FIndex.h"
@interface FQuerySpec : NSObject <NSCopying>
@property(nonatomic, strong, readonly) FPath *path;
@property(nonatomic, strong, readonly) FQueryParams *params;
- (id)initWithPath:(FPath *)path params:(FQueryParams *)params;
+ (FQuerySpec *)defaultQueryAtPath:(FPath *)path;
- (id<FIndex>)index;
- (BOOL)isDefault;
- (BOOL)loadsAllData;
@end
@@ -0,0 +1,86 @@
/*
* 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 "FirebaseDatabase/Sources/Core/FQuerySpec.h"
@interface FQuerySpec ()
@property(nonatomic, strong, readwrite) FPath *path;
@property(nonatomic, strong, readwrite) FQueryParams *params;
@end
@implementation FQuerySpec
- (id)initWithPath:(FPath *)path params:(FQueryParams *)params {
self = [super init];
if (self != nil) {
self->_path = path;
self->_params = params;
}
return self;
}
+ (FQuerySpec *)defaultQueryAtPath:(FPath *)path {
return [[FQuerySpec alloc] initWithPath:path
params:[FQueryParams defaultInstance]];
}
- (id)copyWithZone:(NSZone *)zone {
// Immutable
return self;
}
- (id<FIndex>)index {
return self.params.index;
}
- (BOOL)isDefault {
return self.params.isDefault;
}
- (BOOL)loadsAllData {
return self.params.loadsAllData;
}
- (BOOL)isEqual:(id)object {
if (self == object) {
return YES;
}
if (![object isKindOfClass:[FQuerySpec class]]) {
return NO;
}
FQuerySpec *other = (FQuerySpec *)object;
if (![self.path isEqual:other.path]) {
return NO;
}
return [self.params isEqual:other.params];
}
- (NSUInteger)hash {
return self.path.hash * 31 + self.params.hash;
}
- (NSString *)description {
return [NSString stringWithFormat:@"FQuerySpec (path: %@, params: %@)",
self.path, self.params];
}
@end
@@ -0,0 +1,39 @@
/*
* 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 <Foundation/Foundation.h>
#import "FirebaseDatabase/Sources/Snapshot/FNode.h"
/**
* Applies a merge of a snap for a given interval of paths.
* Each leaf in the current node which the relative path lies *after* (the
* optional) start and lies *before or at* (the optional) end will be deleted.
* Each leaf in snap that lies in the interval will be added to the resulting
* node. Nodes outside of the range are ignored. nil for start and end are
* sentinel values that represent -infinity and +infinity respectively (aka
* includes any path). Priorities of children nodes are treated as leaf children
* of that node.
*/
@interface FRangeMerge : NSObject
- (instancetype)initWithStart:(FPath *)start
end:(FPath *)end
updates:(id<FNode>)updates;
- (id<FNode>)applyToNode:(id<FNode>)node;
@end
@@ -0,0 +1,134 @@
/*
* 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 "FirebaseDatabase/Sources/Core/FRangeMerge.h"
#import "FirebaseDatabase/Sources/Snapshot/FEmptyNode.h"
@interface FRangeMerge ()
@property(nonatomic, strong) FPath *optExclusiveStart;
@property(nonatomic, strong) FPath *optInclusiveEnd;
@property(nonatomic, strong) id<FNode> updates;
@end
@implementation FRangeMerge
- (instancetype)initWithStart:(FPath *)start
end:(FPath *)end
updates:(id<FNode>)updates {
self = [super init];
if (self != nil) {
self->_optExclusiveStart = start;
self->_optInclusiveEnd = end;
self->_updates = updates;
}
return self;
}
- (id<FNode>)applyToNode:(id<FNode>)node {
return [self updateRangeInNode:[FPath empty]
node:node
updates:self.updates];
}
- (id<FNode>)updateRangeInNode:(FPath *)currentPath
node:(id<FNode>)node
updates:(id<FNode>)updates {
NSComparisonResult startComparison =
(self.optExclusiveStart == nil)
? NSOrderedDescending
: [currentPath compare:self.optExclusiveStart];
NSComparisonResult endComparison =
(self.optInclusiveEnd == nil)
? NSOrderedAscending
: [currentPath compare:self.optInclusiveEnd];
BOOL startInNode = self.optExclusiveStart != nil &&
[currentPath contains:self.optExclusiveStart];
BOOL endInNode = self.optInclusiveEnd != nil &&
[currentPath contains:self.optInclusiveEnd];
if (startComparison == NSOrderedDescending &&
endComparison == NSOrderedAscending && !endInNode) {
// child is completly contained
return updates;
} else if (startComparison == NSOrderedDescending && endInNode &&
[updates isLeafNode]) {
return updates;
} else if (startComparison == NSOrderedDescending &&
endComparison == NSOrderedSame) {
NSAssert(endInNode, @"End not in node");
NSAssert(![updates isLeafNode], @"Found leaf node update, this case "
@"should have been handled above.");
if ([node isLeafNode]) {
// Update node was not a leaf node, so we can delete it
return [FEmptyNode emptyNode];
} else {
// Unaffected by range, ignore
return node;
}
} else if (startInNode || endInNode) {
// There is a partial update we need to do, so collect all relevant
// children
NSMutableSet *allChildren = [NSMutableSet set];
[node enumerateChildrenUsingBlock:^(NSString *key, id<FNode> node,
BOOL *stop) {
[allChildren addObject:key];
}];
[updates enumerateChildrenUsingBlock:^(NSString *key, id<FNode> node,
BOOL *stop) {
[allChildren addObject:key];
}];
__block id<FNode> newNode = node;
void (^action)(id, BOOL *) = ^void(NSString *key, BOOL *stop) {
id<FNode> currentChild = [node getImmediateChild:key];
id<FNode> updatedChild =
[self updateRangeInNode:[currentPath childFromString:key]
node:currentChild
updates:[updates getImmediateChild:key]];
// Only need to update if the node changed
if (updatedChild != currentChild) {
newNode = [newNode updateImmediateChild:key
withNewChild:updatedChild];
}
};
[allChildren enumerateObjectsUsingBlock:action];
// Add priority last, so the node is not empty when applying
if (!updates.getPriority.isEmpty || !node.getPriority.isEmpty) {
BOOL stop = NO;
action(@".priority", &stop);
}
return newNode;
} else {
// Unaffected by this range
NSAssert(endComparison == NSOrderedDescending ||
startComparison <= NSOrderedSame,
@"Invalid range for update");
return node;
}
}
- (NSString *)description {
return [NSString stringWithFormat:@"RangeMerge (optExclusiveStart = %@, "
@"optExclusiveEng = %@, updates = %@)",
self.optExclusiveStart,
self.optInclusiveEnd, self.updates];
}
@end
@@ -0,0 +1,102 @@
/*
* 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 "FirebaseDatabase/Sources/Core/FPersistentConnection.h"
#import "FirebaseDatabase/Sources/Core/FRepoInfo.h"
#import "FirebaseDatabase/Sources/Public/FirebaseDatabase/FIRDataEventType.h"
#import "FirebaseDatabase/Sources/Public/FirebaseDatabase/FIRDatabaseQuery.h"
#import "FirebaseDatabase/Sources/Utilities/Tuples/FTupleUserCallback.h"
#import <Foundation/Foundation.h>
@class FQuerySpec;
@class FPersistence;
@class FAuthenticationManager;
@class FIRDatabaseConfig;
@protocol FEventRegistration;
@class FCompoundWrite;
@protocol FClock;
@class FIRDatabase;
@interface FRepo : NSObject <FPersistentConnectionDelegate>
@property(nonatomic, strong) FIRDatabaseConfig *_Nullable config;
- (id _Nonnull)initWithRepoInfo:(FRepoInfo *_Nullable)info
config:(FIRDatabaseConfig *_Nullable)config
database:(FIRDatabase *_Nullable)database;
- (void)set:(FPath *_Nullable)path
withNode:(id _Nullable)node
withCallback:(fbt_void_nserror_ref _Nullable)onComplete;
- (void)update:(FPath *_Nullable)path
withNodes:(FCompoundWrite *_Nullable)compoundWrite
withCallback:(fbt_void_nserror_ref _Nullable)callback;
- (void)purgeOutstandingWrites;
- (void)getData:(FIRDatabaseQuery *_Nullable)query
withCompletionBlock:
(void (^_Nonnull)(NSError *_Nullable error,
FIRDataSnapshot *_Nullable snapshot))block;
- (void)addEventRegistration:(id<FEventRegistration> _Nullable)eventRegistration
forQuery:(FQuerySpec *_Nullable)query;
- (void)removeEventRegistration:
(id<FEventRegistration> _Nullable)eventRegistration
forQuery:(FQuerySpec *_Nullable)query;
- (void)keepQuery:(FQuerySpec *_Nullable)query synced:(BOOL)synced;
- (NSString *_Nullable)name;
- (NSTimeInterval)serverTime;
- (void)onDataUpdate:(FPersistentConnection *_Nullable)fpconnection
forPath:(NSString *_Nullable)pathString
message:(id _Nullable)message
isMerge:(BOOL)isMerge
tagId:(NSNumber *_Nullable)tagId;
- (void)onConnect:(FPersistentConnection *_Nullable)fpconnection;
- (void)onDisconnect:(FPersistentConnection *_Nullable)fpconnection;
// Disconnect methods
- (void)onDisconnectCancel:(FPath *_Nullable)path
withCallback:(fbt_void_nserror_ref _Nullable)callback;
- (void)onDisconnectSet:(FPath *_Nullable)path
withNode:(id<FNode> _Nullable)node
withCallback:(fbt_void_nserror_ref _Nullable)callback;
- (void)onDisconnectUpdate:(FPath *_Nullable)path
withNodes:(FCompoundWrite *_Nullable)compoundWrite
withCallback:(fbt_void_nserror_ref _Nullable)callback;
// Connection Management.
- (void)interrupt;
- (void)resume;
// Transactions
- (void)startTransactionOnPath:(FPath *_Nullable)path
update:
(fbt_transactionresult_mutabledata _Nullable)update
onComplete:
(fbt_void_nserror_bool_datasnapshot _Nullable)onComplete
withLocalEvents:(BOOL)applyLocally;
// Testing methods
- (NSDictionary *_Nullable)dumpListens;
- (void)dispose;
- (void)setHijackHash:(BOOL)hijack;
@property(nonatomic, strong, readonly) FAuthenticationManager *_Nullable auth;
@property(nonatomic, strong, readonly) FIRDatabase *_Nullable database;
@end
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,53 @@
/*
* 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 <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface FRepoInfo : NSObject <NSCopying>
/// The host that the database should connect to.
@property(nonatomic, readonly, copy) NSString *host;
@property(nonatomic, readonly, copy) NSString *namespace;
@property(nonatomic, readwrite, copy) NSString *internalHost;
@property(nonatomic, readonly, assign) BOOL secure;
/// Returns YES if the host is not a *.firebaseio.com host.
@property(nonatomic, readonly) BOOL isCustomHost;
- (instancetype)initWithHost:(NSString *)host
isSecure:(BOOL)secure
withNamespace:(NSString *)namespace NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithInfo:(FRepoInfo *)info emulatedHost:(NSString *)host;
- (NSString *)connectionURLWithLastSessionID:(NSString *_Nullable)lastSessionID;
- (NSString *)connectionURL;
- (void)clearInternalHostCache;
- (BOOL)isDemoHost;
- (BOOL)isCustomHost;
- (id)copyWithZone:(NSZone *_Nullable)zone;
- (NSUInteger)hash;
- (BOOL)isEqual:(id)anObject;
- (instancetype)init NS_UNAVAILABLE;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,153 @@
/*
* 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 "FirebaseDatabase/Sources/Core/FRepoInfo.h"
#import "FirebaseDatabase/Sources/Constants/FConstants.h"
@interface FRepoInfo ()
@property(nonatomic, strong) NSString *domain;
@end
@implementation FRepoInfo
@synthesize internalHost;
- (instancetype)init {
[NSException
raise:@"FIRDatabaseInvalidInitializer"
format:@"Invalid initializer invoked. This is probably a bug in RTDB."];
abort();
}
- (instancetype)initWithHost:(NSString *)aHost
isSecure:(BOOL)isSecure
withNamespace:(NSString *)aNamespace {
self = [super init];
if (self) {
_host = [aHost copy];
_domain =
[_host containsString:@"."]
? [_host
substringFromIndex:[_host rangeOfString:@"."].location +
1]
: _host;
_secure = isSecure;
_namespace = aNamespace;
// Get cached internal host if it exists
NSString *internalHostKey =
[NSString stringWithFormat:@"firebase:host:%@", _host];
NSString *cachedInternalHost = [[NSUserDefaults standardUserDefaults]
stringForKey:internalHostKey];
if (cachedInternalHost != nil) {
internalHost = cachedInternalHost;
} else {
internalHost = [_host copy];
}
}
return self;
}
- (instancetype)initWithInfo:(FRepoInfo *)info emulatedHost:(NSString *)host {
self = [self initWithHost:host isSecure:NO withNamespace:info.namespace];
return self;
}
- (NSString *)description {
// The namespace is encoded in the hostname, so we can just return this.
return [NSString
stringWithFormat:@"http%@://%@", (_secure ? @"s" : @""), _host];
}
- (void)setInternalHost:(NSString *)newHost {
if (![internalHost isEqualToString:newHost]) {
internalHost = newHost;
// Cache the internal host so we don't need to redirect later on
NSString *internalHostKey =
[NSString stringWithFormat:@"firebase:host:%@", self.host];
NSUserDefaults *cache = [NSUserDefaults standardUserDefaults];
[cache setObject:internalHost forKey:internalHostKey];
[cache synchronize];
}
}
- (void)clearInternalHostCache {
self.internalHost = self.host;
// Remove the cached entry
NSString *internalHostKey =
[NSString stringWithFormat:@"firebase:host:%@", self.host];
NSUserDefaults *cache = [NSUserDefaults standardUserDefaults];
[cache removeObjectForKey:internalHostKey];
[cache synchronize];
}
- (BOOL)isDemoHost {
return [self.domain isEqualToString:@"firebaseio-demo.com"];
}
- (BOOL)isCustomHost {
return ![self.domain isEqualToString:@"firebaseio-demo.com"] &&
![self.domain isEqualToString:@"firebaseio.com"];
}
- (NSString *)connectionURL {
return [self connectionURLWithLastSessionID:nil];
}
- (NSString *)connectionURLWithLastSessionID:(NSString *)lastSessionID {
NSString *scheme;
if (self.secure) {
scheme = @"wss";
} else {
scheme = @"ws";
}
NSString *url =
[NSString stringWithFormat:@"%@://%@/.ws?%@=%@&ns=%@", scheme,
self.internalHost, kWireProtocolVersionParam,
kWebsocketProtocolVersion, self.namespace];
if (lastSessionID != nil) {
url = [NSString stringWithFormat:@"%@&ls=%@", url, lastSessionID];
}
return url;
}
- (id)copyWithZone:(NSZone *)zone {
return self; // Immutable
}
- (NSUInteger)hash {
NSUInteger result = _host.hash;
result = 31 * result + (_secure ? 1 : 0);
result = 31 * result + _namespace.hash;
result = 31 * result + _host.hash;
return result;
}
- (BOOL)isEqual:(id)anObject {
if (![anObject isKindOfClass:[FRepoInfo class]]) {
return NO;
}
FRepoInfo *other = (FRepoInfo *)anObject;
return _secure == other.secure && [_host isEqualToString:other.host] &&
[_namespace isEqualToString:other.namespace];
}
@end
@@ -0,0 +1,34 @@
/*
* 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 "FirebaseDatabase/Sources/Api/FIRDatabaseConfig.h"
#import "FirebaseDatabase/Sources/Core/FRepo.h"
#import "FirebaseDatabase/Sources/Core/FRepoInfo.h"
#import <Foundation/Foundation.h>
@interface FRepoManager : NSObject
+ (FRepo *)getRepo:(FRepoInfo *)repoInfo config:(FIRDatabaseConfig *)config;
+ (FRepo *)createRepo:(FRepoInfo *)repoInfo
config:(FIRDatabaseConfig *)config
database:(FIRDatabase *)database;
+ (void)interruptAll;
+ (void)interrupt:(FIRDatabaseConfig *)config;
+ (void)resumeAll;
+ (void)resume:(FIRDatabaseConfig *)config;
+ (void)disposeRepos:(FIRDatabaseConfig *)config;
@end
@@ -0,0 +1,148 @@
/*
* 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 "FirebaseDatabase/Sources/Core/FRepoManager.h"
#import "FirebaseCore/Sources/Private/FirebaseCoreInternal.h"
#import "FirebaseDatabase/Sources/Api/Private/FIRDatabaseQuery_Private.h"
#import "FirebaseDatabase/Sources/Api/Private/FIRDatabase_Private.h"
#import "FirebaseDatabase/Sources/Core/FRepo.h"
#import "FirebaseDatabase/Sources/FIRDatabaseConfig_Private.h"
#import "FirebaseDatabase/Sources/Utilities/FAtomicNumber.h"
@implementation FRepoManager
typedef NSMutableDictionary<NSString *,
NSMutableDictionary<FRepoInfo *, FRepo *> *>
FRepoDictionary;
+ (FRepoDictionary *)configs {
static dispatch_once_t pred = 0;
static FRepoDictionary *configs;
dispatch_once(&pred, ^{
configs = [NSMutableDictionary dictionary];
});
return configs;
}
/**
* Used for legacy unit tests. The public API should go through
* FirebaseDatabase which calls createRepo.
*/
+ (FRepo *)getRepo:(FRepoInfo *)repoInfo config:(FIRDatabaseConfig *)config {
[config freeze];
FRepoDictionary *configs = [FRepoManager configs];
@synchronized(configs) {
NSMutableDictionary<FRepoInfo *, FRepo *> *repos =
configs[config.sessionIdentifier];
if (!repos || repos[repoInfo] == nil) {
// Calling this should create the repo.
[FIRDatabase createDatabaseForTests:repoInfo config:config];
}
return configs[config.sessionIdentifier][repoInfo];
}
}
+ (FRepo *)createRepo:(FRepoInfo *)repoInfo
config:(FIRDatabaseConfig *)config
database:(FIRDatabase *)database {
[config freeze];
FRepoDictionary *configs = [FRepoManager configs];
@synchronized(configs) {
NSMutableDictionary<FRepoInfo *, FRepo *> *repos =
configs[config.sessionIdentifier];
if (!repos) {
repos = [NSMutableDictionary dictionary];
configs[config.sessionIdentifier] = repos;
}
FRepo *repo = repos[repoInfo];
if (repo == nil) {
repo = [[FRepo alloc] initWithRepoInfo:repoInfo
config:config
database:database];
repos[repoInfo] = repo;
return repo;
} else {
[NSException
raise:@"RepoExists"
format:@"createRepo called for Repo that already exists."];
return nil;
}
}
}
+ (void)interrupt:(FIRDatabaseConfig *)config {
dispatch_async([FIRDatabaseQuery sharedQueue], ^{
FRepoDictionary *configs = [FRepoManager configs];
NSMutableDictionary<FRepoInfo *, FRepo *> *repos =
configs[config.sessionIdentifier];
for (FRepo *repo in [repos allValues]) {
[repo interrupt];
}
});
}
+ (void)interruptAll {
dispatch_async([FIRDatabaseQuery sharedQueue], ^{
FRepoDictionary *configs = [FRepoManager configs];
for (NSMutableDictionary<FRepoInfo *, FRepo *> *repos in
[configs allValues]) {
for (FRepo *repo in [repos allValues]) {
[repo interrupt];
}
}
});
}
+ (void)resume:(FIRDatabaseConfig *)config {
dispatch_async([FIRDatabaseQuery sharedQueue], ^{
FRepoDictionary *configs = [FRepoManager configs];
NSMutableDictionary<FRepoInfo *, FRepo *> *repos =
configs[config.sessionIdentifier];
for (FRepo *repo in [repos allValues]) {
[repo resume];
}
});
}
+ (void)resumeAll {
dispatch_async([FIRDatabaseQuery sharedQueue], ^{
FRepoDictionary *configs = [FRepoManager configs];
for (NSMutableDictionary<FRepoInfo *, FRepo *> *repos in
[configs allValues]) {
for (FRepo *repo in [repos allValues]) {
[repo resume];
}
}
});
}
+ (void)disposeRepos:(FIRDatabaseConfig *)config {
// Do this synchronously to make sure we release our references to LevelDB
// before returning, allowing LevelDB to close and release its exclusive
// locks.
dispatch_sync([FIRDatabaseQuery sharedQueue], ^{
FFLog(@"I-RDB040001", @"Disposing all repos for Config with name %@",
config.sessionIdentifier);
NSMutableDictionary *configs = [FRepoManager configs];
for (FRepo *repo in [configs[config.sessionIdentifier] allValues]) {
[repo dispose];
}
[configs removeObjectForKey:config.sessionIdentifier];
});
}
@end
@@ -0,0 +1,42 @@
/*
* 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 "FirebaseDatabase/Sources/Core/FRepo.h"
#import "FirebaseDatabase/Sources/Core/FSparseSnapshotTree.h"
@class FSyncTree;
@class FAtomicNumber;
@class FEventRaiser;
@class FSnapshotHolder;
@interface FRepo ()
- (void)runOnDisconnectEvents;
@property(nonatomic, strong) FRepoInfo *repoInfo;
@property(nonatomic, strong) FPersistentConnection *connection;
@property(nonatomic, strong) FSnapshotHolder *infoData;
@property(nonatomic, strong) FSparseSnapshotTree *onDisconnect;
@property(nonatomic, strong) FEventRaiser *eventRaiser;
@property(nonatomic, strong) FSyncTree *serverSyncTree;
// For testing.
@property(nonatomic) long dataUpdateCount;
@property(nonatomic) long rangeMergeUpdateCount;
- (NSInteger)nextWriteId;
@end
@@ -0,0 +1,40 @@
/*
* 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 "FirebaseDatabase/Sources/Core/FSparseSnapshotTree.h"
#import "FirebaseDatabase/Sources/Core/FSyncTree.h"
#import "FirebaseDatabase/Sources/FClock.h"
#import "FirebaseDatabase/Sources/Snapshot/FCompoundWrite.h"
#import "FirebaseDatabase/Sources/Snapshot/FNode.h"
#import <Foundation/Foundation.h>
@interface FServerValues : NSObject
+ (NSDictionary *)generateServerValues:(id<FClock>)clock;
+ (FCompoundWrite *)resolveDeferredValueCompoundWrite:(FCompoundWrite *)write
withSyncTree:(FSyncTree *)tree
atPath:(FPath *)path
serverValues:
(NSDictionary *)serverValues;
+ (id<FNode>)resolveDeferredValueSnapshot:(id<FNode>)node
withSyncTree:(FSyncTree *)existing
atPath:(FPath *)path
serverValues:(NSDictionary *)serverValues;
+ (id<FNode>)resolveDeferredValueSnapshot:(id<FNode>)node
withExisting:(id<FNode>)existing
serverValues:(NSDictionary *)serverValues;
@end
@@ -0,0 +1,269 @@
/*
* 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 "FirebaseDatabase/Sources/Core/FServerValues.h"
#import "FirebaseDatabase/Sources/Constants/FConstants.h"
#import "FirebaseDatabase/Sources/Snapshot/FChildrenNode.h"
#import "FirebaseDatabase/Sources/Snapshot/FLeafNode.h"
#import "FirebaseDatabase/Sources/Snapshot/FSnapshotUtilities.h"
const NSString *kTimestamp = @"timestamp";
const NSString *kIncrement = @"increment";
BOOL canBeRepresentedAsLong(NSNumber *num) {
switch (num.objCType[0]) {
case 'f': // float; fallthrough
case 'd': // double
return NO;
case 'L': // unsigned long; fallthrough
case 'Q': // unsigned long long; fallthrough
// Only use ulong(long) if there isn't an overflow.
if (num.unsignedLongLongValue > LONG_MAX) {
return NO;
}
}
return YES;
}
// Running through CompoundWrites for all update paths has been shown to
// be a 20% pessimization in microbenchmarks. This is because it slows
// down by O(N) of the write queue length. To eliminate the performance
// hit, we wrap around existing data of either snapshot or CompoundWrite
// (allowing us to share code) and read from the CompoundWrite only when/where
// we need to calculate an incremented value's prior state.
@protocol ValueProvider <NSObject>
- (id<ValueProvider>)getChild:(NSString *)pathSegment;
- (id<FNode>)value;
@end
@interface DeferredValueProvider : NSObject <ValueProvider>
- (instancetype)initWithSyncTree:(FSyncTree *)tree atPath:(FPath *)path;
- (id<ValueProvider>)getChild:(NSString *)pathSegment;
- (id<FNode>)value;
@property FPath *path;
@property FSyncTree *tree;
@end
@interface ExistingValueProvider : NSObject <ValueProvider>
- (instancetype)initWithSnapshot:(id<FNode>)snapshot;
- (id<ValueProvider>)getChild:(NSString *)pathSegment;
- (id<FNode>)value;
@property id<FNode> snapshot;
@end
@implementation DeferredValueProvider
- (instancetype)initWithSyncTree:(FSyncTree *)tree atPath:(FPath *)path {
self.tree = tree;
self.path = path;
return self;
}
- (id<ValueProvider>)getChild:(NSString *)pathSegment {
FPath *child = [self.path childFromString:pathSegment];
return [[DeferredValueProvider alloc] initWithSyncTree:self.tree
atPath:child];
}
- (id<FNode>)value {
return [self.tree calcCompleteEventCacheAtPath:self.path
excludeWriteIds:@[]];
}
@end
@implementation ExistingValueProvider
- (instancetype)initWithSnapshot:(id<FNode>)snapshot {
self.snapshot = snapshot;
return self;
}
- (id<ValueProvider>)getChild:(NSString *)pathSegment {
return [[ExistingValueProvider alloc]
initWithSnapshot:[self.snapshot getImmediateChild:pathSegment]];
}
- (id<FNode>)value {
return self.snapshot;
}
@end
@interface FServerValues ()
+ (id)resolveScalarServerOp:(NSString *)op
withServerValues:(NSDictionary *)serverValues;
+ (id)resolveComplexServerOp:(NSDictionary *)op
withValueProvider:(id<ValueProvider>)existing
serverValues:(NSDictionary *)serverValues;
+ (id<FNode>)resolveDeferredValueSnapshot:(id<FNode>)node
withValueProvider:(id<ValueProvider>)existing
serverValues:(NSDictionary *)serverValues;
@end
@implementation FServerValues
+ (NSDictionary *)generateServerValues:(id<FClock>)clock {
long long millis = (long long)([clock currentTime] * 1000);
return @{kTimestamp : [NSNumber numberWithLongLong:millis]};
}
+ (id)resolveDeferredValue:(id)val
withExisting:(id<ValueProvider>)existing
serverValues:(NSDictionary *)serverValues {
if (![val isKindOfClass:[NSDictionary class]]) {
return val;
}
NSDictionary *dict = val;
id op = dict[kServerValueSubKey];
if (op == nil) {
return val;
} else if ([op isKindOfClass:NSString.class]) {
return [FServerValues resolveScalarServerOp:op
withServerValues:serverValues];
} else if ([op isKindOfClass:NSDictionary.class]) {
return [FServerValues resolveComplexServerOp:op
withValueProvider:existing
serverValues:serverValues];
}
return val;
}
+ (id)resolveScalarServerOp:(NSString *)op
withServerValues:(NSDictionary *)serverValues {
return serverValues[op];
}
+ (id)resolveComplexServerOp:(NSDictionary *)op
withValueProvider:(id<ValueProvider>)jitExisting
serverValues:(NSDictionary *)serverValues {
// Only increment is supported as of now
if (op[kIncrement] == nil) {
return nil;
}
// Incrementing a non-number sets the value to the incremented amount
NSNumber *delta = op[kIncrement];
id<FNode> existing = jitExisting.value;
if (![existing isLeafNode]) {
return delta;
}
FLeafNode *existingLeaf = existing;
if (![existingLeaf.value isKindOfClass:NSNumber.class]) {
return delta;
}
NSNumber *existingNum = existingLeaf.value;
BOOL incrLong = canBeRepresentedAsLong(delta);
BOOL baseLong = canBeRepresentedAsLong(existingNum);
if (incrLong && baseLong) {
long x = delta.longValue;
long y = existingNum.longValue;
long r = x + y;
// See "Hacker's Delight" 2-12: Overflow if both arguments have the
// opposite sign of the result
if (((x ^ r) & (y ^ r)) >= 0) {
return @(r);
}
}
return @(delta.doubleValue + existingNum.doubleValue);
}
+ (FCompoundWrite *)resolveDeferredValueCompoundWrite:(FCompoundWrite *)write
withSyncTree:(FSyncTree *)tree
atPath:(FPath *)path
serverValues:
(NSDictionary *)serverValues {
__block FCompoundWrite *resolved = write;
[write enumerateWrites:^(FPath *subPath, id<FNode> node, BOOL *stop) {
id<ValueProvider> existing =
[[DeferredValueProvider alloc] initWithSyncTree:tree
atPath:[path child:subPath]];
id<FNode> resolvedNode =
[FServerValues resolveDeferredValueSnapshot:node
withValueProvider:existing
serverValues:serverValues];
// Node actually changed, use pointer inequality here
if (resolvedNode != node) {
resolved = [resolved addWrite:resolvedNode atPath:subPath];
}
}];
return resolved;
}
+ (id<FNode>)resolveDeferredValueSnapshot:(id<FNode>)node
withSyncTree:(FSyncTree *)tree
atPath:(FPath *)path
serverValues:(NSDictionary *)serverValues {
id<ValueProvider> jitExisting =
[[DeferredValueProvider alloc] initWithSyncTree:tree atPath:path];
return [FServerValues resolveDeferredValueSnapshot:node
withValueProvider:jitExisting
serverValues:serverValues];
}
+ (id<FNode>)resolveDeferredValueSnapshot:(id<FNode>)node
withExisting:(id<FNode>)existing
serverValues:(NSDictionary *)serverValues {
id<ValueProvider> jitExisting =
[[ExistingValueProvider alloc] initWithSnapshot:existing];
return [FServerValues resolveDeferredValueSnapshot:node
withValueProvider:jitExisting
serverValues:serverValues];
}
+ (id<FNode>)resolveDeferredValueSnapshot:(id<FNode>)node
withValueProvider:(id<ValueProvider>)existing
serverValues:(NSDictionary *)serverValues {
id priorityVal =
[FServerValues resolveDeferredValue:[[node getPriority] val]
withExisting:[existing getChild:@".priority"]
serverValues:serverValues];
id<FNode> priority = [FSnapshotUtilities nodeFrom:priorityVal];
if ([node isLeafNode]) {
id value = [self resolveDeferredValue:[node val]
withExisting:existing
serverValues:serverValues];
if (![value isEqual:[node val]] ||
![priority isEqual:[node getPriority]]) {
return [[FLeafNode alloc] initWithValue:value
withPriority:priority];
} else {
return node;
}
} else {
__block FChildrenNode *newNode = node;
if (![priority isEqual:[node getPriority]]) {
newNode = [newNode updatePriority:priority];
}
[node enumerateChildrenUsingBlock:^(NSString *childKey,
id<FNode> childNode, BOOL *stop) {
id newChildNode = [FServerValues
resolveDeferredValueSnapshot:childNode
withValueProvider:[existing getChild:childKey]
serverValues:serverValues];
if (![newChildNode isEqual:childNode]) {
newNode = [newNode updateImmediateChild:childKey
withNewChild:newChildNode];
}
}];
return newNode;
}
}
@end
@@ -0,0 +1,27 @@
/*
* 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 "FirebaseDatabase/Sources/Snapshot/FNode.h"
#import <Foundation/Foundation.h>
@interface FSnapshotHolder : NSObject
- (id<FNode>)getNode:(FPath *)path;
- (void)updateSnapshot:(FPath *)path withNewSnapshot:(id<FNode>)newSnapshotNode;
@property(nonatomic, strong) id<FNode> rootNode;
@end
@@ -0,0 +1,46 @@
/*
* 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 "FirebaseDatabase/Sources/Core/FSnapshotHolder.h"
#import "FirebaseDatabase/Sources/Snapshot/FEmptyNode.h"
@interface FSnapshotHolder ()
@end
@implementation FSnapshotHolder
@synthesize rootNode;
- (id)init {
self = [super init];
if (self) {
self.rootNode = [FEmptyNode emptyNode];
}
return self;
}
- (id<FNode>)getNode:(FPath *)path {
return [self.rootNode getChild:path];
}
- (void)updateSnapshot:(FPath *)path
withNewSnapshot:(id<FNode>)newSnapshotNode {
self.rootNode = [self.rootNode updateChild:path
withNewChild:newSnapshotNode];
}
@end
@@ -0,0 +1,34 @@
/*
* 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 "FirebaseDatabase/Sources/Api/Private/FTypedefs_Private.h"
#import "FirebaseDatabase/Sources/Core/Utilities/FPath.h"
#import "FirebaseDatabase/Sources/Snapshot/FNode.h"
#import <Foundation/Foundation.h>
@class FSparseSnapshotTree;
typedef void (^fbt_void_nsstring_sstree)(NSString *, FSparseSnapshotTree *);
@interface FSparseSnapshotTree : NSObject
- (id<FNode>)findPath:(FPath *)path;
- (void)rememberData:(id<FNode>)data onPath:(FPath *)path;
- (BOOL)forgetPath:(FPath *)path;
- (void)forEachTreeAtPath:(FPath *)prefixPath do:(fbt_void_path_node)func;
- (void)forEachChild:(fbt_void_nsstring_sstree)func;
@end
@@ -0,0 +1,144 @@
/*
* 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 "FirebaseDatabase/Sources/Core/FSparseSnapshotTree.h"
#import "FirebaseDatabase/Sources/Snapshot/FChildrenNode.h"
@interface FSparseSnapshotTree () {
id<FNode> value;
NSMutableDictionary *children;
}
@end
@implementation FSparseSnapshotTree
- (id)init {
self = [super init];
if (self) {
value = nil;
children = nil;
}
return self;
}
- (id<FNode>)findPath:(FPath *)path {
if (value != nil) {
return [value getChild:path];
} else if (![path isEmpty] && children != nil) {
NSString *childKey = [path getFront];
path = [path popFront];
FSparseSnapshotTree *childTree = children[childKey];
if (childTree != nil) {
return [childTree findPath:path];
} else {
return nil;
}
} else {
return nil;
}
}
- (void)rememberData:(id<FNode>)data onPath:(FPath *)path {
if ([path isEmpty]) {
value = data;
children = nil;
} else if (value != nil) {
value = [value updateChild:path withNewChild:data];
} else {
if (children == nil) {
children = [[NSMutableDictionary alloc] init];
}
NSString *childKey = [path getFront];
if (children[childKey] == nil) {
children[childKey] = [[FSparseSnapshotTree alloc] init];
}
FSparseSnapshotTree *child = children[childKey];
path = [path popFront];
[child rememberData:data onPath:path];
}
}
- (BOOL)forgetPath:(FPath *)path {
if ([path isEmpty]) {
value = nil;
children = nil;
return YES;
} else {
if (value != nil) {
if ([value isLeafNode]) {
// non-empty path at leaf. the path leads to nowhere
return NO;
} else {
id<FNode> tmp = value;
value = nil;
[tmp enumerateChildrenUsingBlock:^(NSString *key,
id<FNode> node, BOOL *stop) {
[self rememberData:node onPath:[[FPath alloc] initWith:key]];
}];
// we've cleared out the value and set children. Call ourself
// again to hit the next case
return [self forgetPath:path];
}
} else if (children != nil) {
NSString *childKey = [path getFront];
path = [path popFront];
if (children[childKey] != nil) {
FSparseSnapshotTree *child = children[childKey];
BOOL safeToRemove = [child forgetPath:path];
if (safeToRemove) {
[children removeObjectForKey:childKey];
}
}
if ([children count] == 0) {
children = nil;
return YES;
} else {
return NO;
}
} else {
return YES;
}
}
}
- (void)forEachTreeAtPath:(FPath *)prefixPath do:(fbt_void_path_node)func {
if (value != nil) {
func(prefixPath, value);
} else {
[self forEachChild:^(NSString *key, FSparseSnapshotTree *tree) {
FPath *path = [prefixPath childFromString:key];
[tree forEachTreeAtPath:path do:func];
}];
}
}
- (void)forEachChild:(fbt_void_nsstring_sstree)func {
if (children != nil) {
for (NSString *key in children) {
FSparseSnapshotTree *tree = [children objectForKey:key];
func(key, tree);
}
}
}
@end
@@ -0,0 +1,74 @@
/*
* 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 <Foundation/Foundation.h>
@protocol FOperation;
@class FWriteTreeRef;
@protocol FNode;
@protocol FEventRegistration;
@class FQuerySpec;
@class FChildrenNode;
@class FTupleRemovedQueriesEvents;
@class FView;
@class FPath;
@class FCacheNode;
@class FPersistenceManager;
@interface FSyncPoint : NSObject
- (id)initWithPersistenceManager:(FPersistenceManager *)persistence;
- (BOOL)isEmpty;
/**
* Returns array of FEvent
*/
- (NSArray *)applyOperation:(id<FOperation>)operation
writesCache:(FWriteTreeRef *)writesCache
serverCache:(id<FNode>)optCompleteServerCache;
- (FView *)getView:(FQuerySpec *)query
writesCache:(FWriteTreeRef *)writesCache
serverCache:(FCacheNode *)serverCache;
/**
* Returns array of FEvent
*/
- (NSArray *)addEventRegistration:(id<FEventRegistration>)eventRegistration
forNonExistingViewForQuery:(FQuerySpec *)query
writesCache:(FWriteTreeRef *)writesCache
serverCache:(FCacheNode *)serverCache;
- (NSArray *)addEventRegistration:(id<FEventRegistration>)eventRegistration
forExistingViewForQuery:(FQuerySpec *)query;
- (FTupleRemovedQueriesEvents *)removeEventRegistration:
(id<FEventRegistration>)eventRegistration
forQuery:(FQuerySpec *)query
cancelError:(NSError *)cancelError;
/**
* Returns array of FViews
*/
- (NSArray *)queryViews;
- (id<FNode>)completeServerCacheAtPath:(FPath *)path;
- (id<FNode>)completeEventCacheAtPath:(FPath *)path;
- (FView *)viewForQuery:(FQuerySpec *)query;
- (BOOL)viewExistsForQuery:(FQuerySpec *)query;
- (BOOL)hasCompleteView;
- (FView *)completeView;
@end
@@ -0,0 +1,325 @@
/*
* 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 "FirebaseDatabase/Sources/Core/FSyncPoint.h"
#import "FirebaseDatabase/Sources/Core/FQueryParams.h"
#import "FirebaseDatabase/Sources/Core/FQuerySpec.h"
#import "FirebaseDatabase/Sources/Core/FWriteTreeRef.h"
#import "FirebaseDatabase/Sources/Core/Operation/FOperation.h"
#import "FirebaseDatabase/Sources/Core/Operation/FOperationSource.h"
#import "FirebaseDatabase/Sources/Core/Utilities/FPath.h"
#import "FirebaseDatabase/Sources/Core/View/FCacheNode.h"
#import "FirebaseDatabase/Sources/Core/View/FDataEvent.h"
#import "FirebaseDatabase/Sources/Core/View/FEventRegistration.h"
#import "FirebaseDatabase/Sources/Core/View/FView.h"
#import "FirebaseDatabase/Sources/Core/View/FViewCache.h"
#import "FirebaseDatabase/Sources/Persistence/FPersistenceManager.h"
#import "FirebaseDatabase/Sources/Public/FirebaseDatabase/FIRDatabaseQuery.h"
#import "FirebaseDatabase/Sources/Snapshot/FChildrenNode.h"
#import "FirebaseDatabase/Sources/Snapshot/FEmptyNode.h"
#import "FirebaseDatabase/Sources/Snapshot/FNode.h"
#import "FirebaseDatabase/Sources/Utilities/Tuples/FTupleRemovedQueriesEvents.h"
/**
* SyncPoint represents a single location in a SyncTree with 1 or more event
* registrations, meaning we need to maintain 1 or more Views at this location
* to cache server data and raise appropriate events for server changes and user
* writes (set, transaction, update).
*
* It's responsible for:
* - Maintaining the set of 1 or more views necessary at this location (a
* SyncPoint with 0 views should be removed).
* - Proxying user / server operations to the views as appropriate (i.e.
* applyServerOverwrite, applyUserOverwrite, etc.)
*/
@interface FSyncPoint ()
/**
* The Views being tracked at this location in the tree, stored as a map where
* the key is a queryParams and the value is the View for that query.
*
* NOTE: This list will be quite small (usually 1, but perhaps 2 or 3; any more
* is an odd use case).
*
* Maps NSString -> FView
*/
@property(nonatomic, strong) NSMutableDictionary *views;
@property(nonatomic, strong) FPersistenceManager *persistenceManager;
@end
@implementation FSyncPoint
- (id)initWithPersistenceManager:(FPersistenceManager *)persistence {
self = [super init];
if (self) {
self.persistenceManager = persistence;
self.views = [[NSMutableDictionary alloc] init];
}
return self;
}
- (BOOL)isEmpty {
return [self.views count] == 0;
}
- (NSArray *)applyOperation:(id<FOperation>)operation
toView:(FView *)view
writesCache:(FWriteTreeRef *)writesCache
serverCache:(id<FNode>)optCompleteServerCache {
FViewOperationResult *result = [view applyOperation:operation
writesCache:writesCache
serverCache:optCompleteServerCache];
if (!view.query.loadsAllData) {
NSMutableSet *removed = [NSMutableSet set];
NSMutableSet *added = [NSMutableSet set];
[result.changes enumerateObjectsUsingBlock:^(
FChange *change, NSUInteger idx, BOOL *stop) {
if (change.type == FIRDataEventTypeChildAdded) {
[added addObject:change.childKey];
} else if (change.type == FIRDataEventTypeChildRemoved) {
[removed addObject:change.childKey];
}
}];
if ([removed count] > 0 || [added count] > 0) {
[self.persistenceManager
updateTrackedQueryKeysWithAddedKeys:added
removedKeys:removed
forQuery:view.query];
}
}
return result.events;
}
- (NSArray *)applyOperation:(id<FOperation>)operation
writesCache:(FWriteTreeRef *)writesCache
serverCache:(id<FNode>)optCompleteServerCache {
FQueryParams *queryParams = operation.source.queryParams;
if (queryParams != nil) {
FView *view = [self.views objectForKey:queryParams];
NSAssert(view != nil, @"SyncTree gave us an op for an invalid query.");
return [self applyOperation:operation
toView:view
writesCache:writesCache
serverCache:optCompleteServerCache];
} else {
NSMutableArray *events = [[NSMutableArray alloc] init];
[self.views enumerateKeysAndObjectsUsingBlock:^(
FQueryParams *key, FView *view, BOOL *stop) {
NSArray *eventsForView = [self applyOperation:operation
toView:view
writesCache:writesCache
serverCache:optCompleteServerCache];
[events addObjectsFromArray:eventsForView];
}];
return events;
}
}
- (FView *)getView:(FQuerySpec *)query
writesCache:(FWriteTreeRef *)writesCache
serverCache:(FCacheNode *)serverCache {
FView *view = self.views[query.params];
if (view != nil) {
return view;
}
id<FNode> eventCache = [writesCache
calculateCompleteEventCacheWithCompleteServerCache:
serverCache.isFullyInitialized ? serverCache.node : nil];
BOOL eventCacheComplete;
if (eventCache != nil) {
eventCacheComplete = YES;
} else {
eventCache = [writesCache
calculateCompleteEventChildrenWithCompleteServerChildren:
serverCache.node != nil ? serverCache.node
: [FEmptyNode emptyNode]];
eventCacheComplete = NO;
}
FIndexedNode *indexed = [FIndexedNode indexedNodeWithNode:eventCache
index:query.index];
FCacheNode *eventCacheNode =
[[FCacheNode alloc] initWithIndexedNode:indexed
isFullyInitialized:eventCacheComplete
isFiltered:NO];
FViewCache *viewCache =
[[FViewCache alloc] initWithEventCache:eventCacheNode
serverCache:serverCache];
return [[FView alloc] initWithQuery:query initialViewCache:viewCache];
}
/**
* Add an event callback for the specified query
* Returns an array of events to raise.
*/
- (NSArray *)addEventRegistration:(id<FEventRegistration>)eventRegistration
forNonExistingViewForQuery:(FQuerySpec *)query
writesCache:(FWriteTreeRef *)writesCache
serverCache:(FCacheNode *)serverCache {
NSAssert(self.views[query.params] == nil, @"Found view for query: %@",
query.params);
// TODO: make writesCache take flag for complete server node
FView *view = [self getView:query
writesCache:writesCache
serverCache:serverCache];
// If this is a non-default query we need to tell persistence our current
// view of the data
if (!query.loadsAllData) {
NSMutableSet *allKeys = [NSMutableSet set];
[view.eventCache enumerateChildrenUsingBlock:^(
NSString *key, id<FNode> node, BOOL *stop) {
[allKeys addObject:key];
}];
[self.persistenceManager setTrackedQueryKeys:allKeys forQuery:query];
}
self.views[query.params] = view;
return [self addEventRegistration:eventRegistration
forExistingViewForQuery:query];
}
- (NSArray *)addEventRegistration:(id<FEventRegistration>)eventRegistration
forExistingViewForQuery:(FQuerySpec *)query {
FView *view = self.views[query.params];
NSAssert(view != nil, @"No view for query: %@", query);
[view addEventRegistration:eventRegistration];
return [view initialEvents:eventRegistration];
}
/**
* Remove event callback(s). Return cancelEvents if a cancelError is specified.
*
* If query is the default query, we'll check all views for the specified
* eventRegistration. If eventRegistration is nil, we'll remove all callbacks
* for the specified view(s).
*
* @return FTupleRemovedQueriesEvents removed queries and any cancel events
*/
- (FTupleRemovedQueriesEvents *)removeEventRegistration:
(id<FEventRegistration>)eventRegistration
forQuery:(FQuerySpec *)query
cancelError:(NSError *)cancelError {
NSMutableArray *removedQueries = [[NSMutableArray alloc] init];
__block NSMutableArray *cancelEvents = [[NSMutableArray alloc] init];
BOOL hadCompleteView = [self hasCompleteView];
if ([query isDefault]) {
// When you do [ref removeObserverWithHandle:], we search all views for
// the registration to remove.
[self.views enumerateKeysAndObjectsUsingBlock:^(
FQueryParams *viewQueryParams, FView *view,
BOOL *stop) {
[cancelEvents
addObjectsFromArray:[view
removeEventRegistration:eventRegistration
cancelError:cancelError]];
if ([view isEmpty]) {
[self.views removeObjectForKey:viewQueryParams];
// We'll deal with complete views later
if (![view.query loadsAllData]) {
[removedQueries addObject:view.query];
}
}
}];
} else {
// remove the callback from the specific view
FView *view = [self.views objectForKey:query.params];
if (view != nil) {
[cancelEvents addObjectsFromArray:
[view removeEventRegistration:eventRegistration
cancelError:cancelError]];
if ([view isEmpty]) {
[self.views removeObjectForKey:query.params];
// We'll deal with complete views later
if (![view.query loadsAllData]) {
[removedQueries addObject:view.query];
}
}
}
}
if (hadCompleteView && ![self hasCompleteView]) {
// We removed our last complete view
[removedQueries addObject:[FQuerySpec defaultQueryAtPath:query.path]];
}
return [[FTupleRemovedQueriesEvents alloc]
initWithRemovedQueries:removedQueries
cancelEvents:cancelEvents];
}
- (NSArray *)queryViews {
__block NSMutableArray *filteredViews = [[NSMutableArray alloc] init];
[self.views enumerateKeysAndObjectsUsingBlock:^(FQueryParams *key,
FView *view, BOOL *stop) {
if (![view.query loadsAllData]) {
[filteredViews addObject:view];
}
}];
return filteredViews;
}
- (id<FNode>)completeServerCacheAtPath:(FPath *)path {
__block id<FNode> serverCache = nil;
[self.views enumerateKeysAndObjectsUsingBlock:^(FQueryParams *key,
FView *view, BOOL *stop) {
serverCache = [view completeServerCacheFor:path];
*stop = (serverCache != nil);
}];
return serverCache;
}
- (id<FNode>)completeEventCacheAtPath:(FPath *)path {
__block id<FNode> eventCache = nil;
[self.views enumerateKeysAndObjectsUsingBlock:^(FQueryParams *key,
FView *view, BOOL *stop) {
eventCache = [view completeEventCacheFor:path];
*stop = (eventCache != nil);
}];
return eventCache;
}
- (FView *)viewForQuery:(FQuerySpec *)query {
return [self.views objectForKey:query.params];
}
- (BOOL)viewExistsForQuery:(FQuerySpec *)query {
return [self viewForQuery:query] != nil;
}
- (BOOL)hasCompleteView {
return [self completeView] != nil;
}
- (FView *)completeView {
__block FView *completeView = nil;
[self.views enumerateKeysAndObjectsUsingBlock:^(FQueryParams *key,
FView *view, BOOL *stop) {
if ([view.query loadsAllData]) {
completeView = view;
*stop = YES;
}
}];
return completeView;
}
@end
@@ -0,0 +1,85 @@
/*
* 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 <Foundation/Foundation.h>
@class FIndexedNode;
@class FListenProvider;
@protocol FNode;
@class FPath;
@protocol FEventRegistration;
@protocol FPersistedServerCache;
@class FQuerySpec;
@class FCompoundWrite;
@class FPersistenceManager;
@class FCompoundHash;
@protocol FClock;
@protocol FSyncTreeHash <NSObject>
- (NSString *)simpleHash;
- (FCompoundHash *)compoundHash;
- (BOOL)includeCompoundHash;
@end
@interface FSyncTree : NSObject
- (id)initWithListenProvider:(FListenProvider *)provider;
- (id)initWithPersistenceManager:(FPersistenceManager *)persistenceManager
listenProvider:(FListenProvider *)provider;
// These methods all return NSArray of FEvent
- (NSArray *)applyUserOverwriteAtPath:(FPath *)path
newData:(id<FNode>)newData
writeId:(NSInteger)writeId
isVisible:(BOOL)visible;
- (NSArray *)applyUserMergeAtPath:(FPath *)path
changedChildren:(FCompoundWrite *)changedChildren
writeId:(NSInteger)writeId;
- (NSArray *)ackUserWriteWithWriteId:(NSInteger)writeId
revert:(BOOL)revert
persist:(BOOL)persist
clock:(id<FClock>)clock;
- (NSArray *)applyServerOverwriteAtPath:(FPath *)path
newData:(id<FNode>)newData;
- (NSArray *)applyServerMergeAtPath:(FPath *)path
changedChildren:(FCompoundWrite *)changedChildren;
- (NSArray *)applyServerRangeMergeAtPath:(FPath *)path
updates:(NSArray *)ranges;
- (NSArray *)applyTaggedQueryOverwriteAtPath:(FPath *)path
newData:(id<FNode>)newData
tagId:(NSNumber *)tagId;
- (NSArray *)applyTaggedQueryMergeAtPath:(FPath *)path
changedChildren:(FCompoundWrite *)changedChildren
tagId:(NSNumber *)tagId;
- (NSArray *)applyTaggedServerRangeMergeAtPath:(FPath *)path
updates:(NSArray *)ranges
tagId:(NSNumber *)tagId;
- (NSArray *)addEventRegistration:(id<FEventRegistration>)eventRegistration
forQuery:(FQuerySpec *)query;
- (NSArray *)removeEventRegistration:(id<FEventRegistration>)eventRegistration
forQuery:(FQuerySpec *)query
cancelError:(NSError *)cancelError;
- (void)keepQuery:(FQuerySpec *)query synced:(BOOL)keepSynced;
- (NSArray *)removeAllWrites;
- (FIndexedNode *)persistenceServerCache:(FQuerySpec *)querySpec;
- (id<FNode>)getServerValue:(FQuerySpec *)query;
- (id<FNode>)calcCompleteEventCacheAtPath:(FPath *)path
excludeWriteIds:(NSArray *)writeIdsToExclude;
@end
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,45 @@
/*
* 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 <Foundation/Foundation.h>
@class FPath;
@class FCompoundWrite;
@protocol FNode;
@interface FWriteRecord : NSObject
- initWithPath:(FPath *)path
overwrite:(id<FNode>)overwrite
writeId:(NSInteger)writeId
visible:(BOOL)isVisible;
- initWithPath:(FPath *)path
merge:(FCompoundWrite *)merge
writeId:(NSInteger)writeId;
@property(nonatomic, readonly) NSInteger writeId;
@property(nonatomic, strong, readonly) FPath *path;
@property(nonatomic, strong, readonly) id<FNode> overwrite;
/**
* Maps NSString -> id<FNode>
*/
@property(nonatomic, strong, readonly) FCompoundWrite *merge;
@property(nonatomic, readonly) BOOL visible;
- (BOOL)isMerge;
- (BOOL)isOverwrite;
@end
@@ -0,0 +1,139 @@
/*
* 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 "FirebaseDatabase/Sources/Core/FWriteRecord.h"
#import "FirebaseDatabase/Sources/Core/Utilities/FPath.h"
#import "FirebaseDatabase/Sources/Snapshot/FCompoundWrite.h"
#import "FirebaseDatabase/Sources/Snapshot/FNode.h"
@interface FWriteRecord ()
@property(nonatomic, readwrite) NSInteger writeId;
@property(nonatomic, strong, readwrite) FPath *path;
@property(nonatomic, strong, readwrite) id<FNode> overwrite;
@property(nonatomic, strong, readwrite) FCompoundWrite *merge;
@property(nonatomic, readwrite) BOOL visible;
@end
@implementation FWriteRecord
- (id)initWithPath:(FPath *)path
overwrite:(id<FNode>)overwrite
writeId:(NSInteger)writeId
visible:(BOOL)isVisible {
self = [super init];
if (self) {
self.path = path;
if (overwrite == nil) {
[NSException raise:NSInvalidArgumentException
format:@"Can't pass nil as overwrite parameter to an "
@"overwrite write record"];
}
self.overwrite = overwrite;
self.merge = nil;
self.writeId = writeId;
self.visible = isVisible;
}
return self;
}
- (id)initWithPath:(FPath *)path
merge:(FCompoundWrite *)merge
writeId:(NSInteger)writeId {
self = [super init];
if (self) {
self.path = path;
if (merge == nil) {
[NSException raise:NSInvalidArgumentException
format:@"Can't pass nil as merge parameter to an merge "
@"write record"];
}
self.overwrite = nil;
self.merge = merge;
self.writeId = writeId;
self.visible = YES;
}
return self;
}
- (id<FNode>)overwrite {
if (self->_overwrite == nil) {
[NSException raise:NSInvalidArgumentException
format:@"Can't get overwrite for merge write record!"];
}
return self->_overwrite;
}
- (FCompoundWrite *)compoundWrite {
if (self->_merge == nil) {
[NSException raise:NSInvalidArgumentException
format:@"Can't get merge for overwrite write record!"];
}
return self->_merge;
}
- (BOOL)isMerge {
return self->_merge != nil;
}
- (BOOL)isOverwrite {
return self->_overwrite != nil;
}
- (NSString *)description {
if (self.isOverwrite) {
return
[NSString stringWithFormat:@"FWriteRecord { writeId = %lu, path = "
@"%@, overwrite = %@, visible = %d }",
(unsigned long)self.writeId, self.path,
self.overwrite, self.visible];
} else {
return [NSString
stringWithFormat:
@"FWriteRecord { writeId = %lu, path = %@, merge = %@ }",
(unsigned long)self.writeId, self.path, self.merge];
}
}
- (BOOL)isEqual:(id)object {
if (![object isKindOfClass:[self class]]) {
return NO;
}
FWriteRecord *other = (FWriteRecord *)object;
if (self->_writeId != other->_writeId)
return NO;
if (self->_path != other->_path && ![self->_path isEqual:other->_path])
return NO;
if (self->_overwrite != other->_overwrite &&
![self->_overwrite isEqual:other->_overwrite])
return NO;
if (self->_merge != other->_merge && ![self->_merge isEqual:other->_merge])
return NO;
if (self->_visible != other->_visible)
return NO;
return YES;
}
- (NSUInteger)hash {
NSUInteger hash = self->_writeId * 17;
hash = hash * 31 + self->_path.hash;
hash = hash * 31 + self->_overwrite.hash;
hash = hash * 31 + self->_merge.hash;
hash = hash * 31 + ((self->_visible) ? 1 : 0);
return hash;
}
@end
@@ -0,0 +1,70 @@
/*
* 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 <Foundation/Foundation.h>
@class FPath;
@protocol FNode;
@class FCompoundWrite;
@class FWriteTreeRef;
@class FChildrenNode;
@class FNamedNode;
@class FWriteRecord;
@protocol FIndex;
@class FCacheNode;
@interface FWriteTree : NSObject
- (FWriteTreeRef *)childWritesForPath:(FPath *)path;
- (void)addOverwriteAtPath:(FPath *)path
newData:(id<FNode>)newData
writeId:(NSInteger)writeId
isVisible:(BOOL)visible;
- (void)addMergeAtPath:(FPath *)path
changedChildren:(FCompoundWrite *)changedChildren
writeId:(NSInteger)writeId;
- (BOOL)removeWriteId:(NSInteger)writeId;
- (NSArray *)removeAllWrites;
- (FWriteRecord *)writeForId:(NSInteger)writeId;
- (id<FNode>)calculateCompleteEventCacheAtPath:(FPath *)treePath
completeServerCache:(id<FNode>)completeServerCache
excludeWriteIds:(NSArray *)writeIdsToExclude
includeHiddenWrites:(BOOL)includeHiddenWrites;
- (id<FNode>)calculateCompleteEventChildrenAtPath:(FPath *)treePath
completeServerChildren:
(id<FNode>)completeServerChildren;
- (id<FNode>)
calculateEventCacheAfterServerOverwriteAtPath:(FPath *)treePath
childPath:(FPath *)childPath
existingEventSnap:(id<FNode>)existingEventSnap
existingServerSnap:(id<FNode>)existingServerSnap;
- (id<FNode>)calculateCompleteChildAtPath:(FPath *)treePath
childKey:(NSString *)childKey
cache:(FCacheNode *)existingServerCache;
- (id<FNode>)shadowingWriteAtPath:(FPath *)path;
- (FNamedNode *)calculateNextNodeAfterPost:(FNamedNode *)post
atPath:(FPath *)path
completeServerData:(id<FNode>)completeServerData
reverse:(BOOL)reverse
index:(id<FIndex>)index;
@end
@@ -0,0 +1,577 @@
/*
* 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 "FirebaseDatabase/Sources/Core/FWriteTree.h"
#import "FirebaseDatabase/Sources/Core/FWriteRecord.h"
#import "FirebaseDatabase/Sources/Core/FWriteTreeRef.h"
#import "FirebaseDatabase/Sources/Core/Utilities/FImmutableTree.h"
#import "FirebaseDatabase/Sources/Core/Utilities/FPath.h"
#import "FirebaseDatabase/Sources/Core/View/FCacheNode.h"
#import "FirebaseDatabase/Sources/FIndex.h"
#import "FirebaseDatabase/Sources/FNamedNode.h"
#import "FirebaseDatabase/Sources/Snapshot/FChildrenNode.h"
#import "FirebaseDatabase/Sources/Snapshot/FCompoundWrite.h"
#import "FirebaseDatabase/Sources/Snapshot/FEmptyNode.h"
#import "FirebaseDatabase/Sources/Snapshot/FNode.h"
@interface FWriteTree ()
/**
* A tree tracking the results of applying all visible writes. This does not
* include transactions with applyLocally=false or writes that are completely
* shadowed by other writes. Contains id<FNode> as values.
*/
@property(nonatomic, strong) FCompoundWrite *visibleWrites;
/**
* A list of pending writes, regardless of visibility and shadowed-ness. Used to
* calcuate arbitrary sets of the changed data, such as hidden writes (from
* transactions) or changes with certain writes excluded (also used by
* transactions). Contains FWriteRecords.
*/
@property(nonatomic, strong) NSMutableArray *allWrites;
@property(nonatomic) NSInteger lastWriteId;
@end
/**
* FWriteTree tracks all pending user-initiated writes and has methods to
* calcuate the result of merging them with underlying server data (to create
* "event cache" data). Pending writes are added with addOverwriteAtPath: and
* addMergeAtPath: and removed with removeWriteId:.
*/
@implementation FWriteTree
@synthesize allWrites;
@synthesize lastWriteId;
- (id)init {
self = [super init];
if (self) {
self.visibleWrites = [FCompoundWrite emptyWrite];
self.allWrites = [[NSMutableArray alloc] init];
self.lastWriteId = -1;
}
return self;
}
/**
* Create a new WriteTreeRef for the given path. For use with a new sync point
* at the given path.
*/
- (FWriteTreeRef *)childWritesForPath:(FPath *)path {
return [[FWriteTreeRef alloc] initWithPath:path writeTree:self];
}
/**
* Record a new overwrite from user code.
* @param visible Is set to false by some transactions. It should be excluded
* from event caches.
*/
- (void)addOverwriteAtPath:(FPath *)path
newData:(id<FNode>)newData
writeId:(NSInteger)writeId
isVisible:(BOOL)visible {
NSAssert(writeId > self.lastWriteId,
@"Stacking an older write on top of a newer one");
FWriteRecord *record = [[FWriteRecord alloc] initWithPath:path
overwrite:newData
writeId:writeId
visible:visible];
[self.allWrites addObject:record];
if (visible) {
self.visibleWrites = [self.visibleWrites addWrite:newData atPath:path];
}
self.lastWriteId = writeId;
}
/**
* Record a new merge from user code.
* @param changedChildren maps NSString -> id<FNode>
*/
- (void)addMergeAtPath:(FPath *)path
changedChildren:(FCompoundWrite *)changedChildren
writeId:(NSInteger)writeId {
NSAssert(writeId > self.lastWriteId,
@"Stacking an older merge on top of newer one");
FWriteRecord *record = [[FWriteRecord alloc] initWithPath:path
merge:changedChildren
writeId:writeId];
[self.allWrites addObject:record];
self.visibleWrites = [self.visibleWrites addCompoundWrite:changedChildren
atPath:path];
self.lastWriteId = writeId;
}
- (FWriteRecord *)writeForId:(NSInteger)writeId {
NSUInteger index = [self.allWrites
indexOfObjectPassingTest:^BOOL(FWriteRecord *write, NSUInteger idx,
BOOL *stop) {
return write.writeId == writeId;
}];
return (index == NSNotFound) ? nil : self.allWrites[index];
}
/**
* Remove a write (either an overwrite or merge) that has been successfully
* acknowledged by the server. Recalculates the tree if necessary. We return the
* path of the write and whether it may have been visible, meaning views need to
* reevaluate.
*
* @return YES if the write may have been visible (meaning we'll need to
* reevaluate / raise events as a result).
*/
- (BOOL)removeWriteId:(NSInteger)writeId {
NSUInteger index = [self.allWrites
indexOfObjectPassingTest:^BOOL(FWriteRecord *record, NSUInteger idx,
BOOL *stop) {
if (record.writeId == writeId) {
return YES;
} else {
return NO;
}
}];
NSAssert(index != NSNotFound,
@"[FWriteTree removeWriteId:] called with nonexistent writeId.");
FWriteRecord *writeToRemove = self.allWrites[index];
[self.allWrites removeObjectAtIndex:index];
BOOL removedWriteWasVisible = writeToRemove.visible;
BOOL removedWriteOverlapsWithOtherWrites = NO;
NSInteger i = [self.allWrites count] - 1;
while (removedWriteWasVisible && i >= 0) {
FWriteRecord *currentWrite = [self.allWrites objectAtIndex:i];
if (currentWrite.visible) {
if (i >= index && [self record:currentWrite
containsPath:writeToRemove.path]) {
// The removed write was completely shadowed by a subsequent
// write.
removedWriteWasVisible = NO;
} else if ([writeToRemove.path contains:currentWrite.path]) {
// Either we're covering some writes or they're covering part of
// us (depending on which came first).
removedWriteOverlapsWithOtherWrites = YES;
}
}
i--;
}
if (!removedWriteWasVisible) {
return NO;
} else if (removedWriteOverlapsWithOtherWrites) {
// There's some shadowing going on. Just rebuild the visible writes from
// scratch.
[self resetTree];
return YES;
} else {
// There's no shadowing. We can safely just remove the write(s) from
// visibleWrites.
if ([writeToRemove isOverwrite]) {
self.visibleWrites =
[self.visibleWrites removeWriteAtPath:writeToRemove.path];
} else {
FCompoundWrite *merge = writeToRemove.merge;
[merge enumerateWrites:^(FPath *path, id<FNode> node, BOOL *stop) {
self.visibleWrites = [self.visibleWrites
removeWriteAtPath:[writeToRemove.path child:path]];
}];
}
return YES;
}
}
- (NSArray *)removeAllWrites {
NSArray *writes = self.allWrites;
self.visibleWrites = [FCompoundWrite emptyWrite];
self.allWrites = [NSMutableArray array];
return writes;
}
/**
* @return A complete snapshot for the given path if there's visible write data
* at that path, else nil. No server data is considered.
*/
- (id<FNode>)completeWriteDataAtPath:(FPath *)path {
return [self.visibleWrites completeNodeAtPath:path];
}
/**
* Given optional, underlying server data, and an optional set of constraints
* (exclude some sets, include hidden writes), attempt to calculate a complete
* snapshot for the given path
* @param includeHiddenWrites Defaults to false, whether or not to layer on
* writes with visible set to false
*/
- (id<FNode>)calculateCompleteEventCacheAtPath:(FPath *)treePath
completeServerCache:(id<FNode>)completeServerCache
excludeWriteIds:(NSArray *)writeIdsToExclude
includeHiddenWrites:(BOOL)includeHiddenWrites {
if (writeIdsToExclude == nil && !includeHiddenWrites) {
id<FNode> shadowingNode =
[self.visibleWrites completeNodeAtPath:treePath];
if (shadowingNode != nil) {
return shadowingNode;
} else {
// No cache here. Can't claim complete knowledge.
FCompoundWrite *subMerge =
[self.visibleWrites childCompoundWriteAtPath:treePath];
if (subMerge.isEmpty) {
return completeServerCache;
} else if (completeServerCache == nil &&
![subMerge hasCompleteWriteAtPath:[FPath empty]]) {
// We wouldn't have a complete snapshot since there's no
// underlying data and no complete shadow
return nil;
} else {
id<FNode> layeredCache = completeServerCache != nil
? completeServerCache
: [FEmptyNode emptyNode];
return [subMerge applyToNode:layeredCache];
}
}
} else {
FCompoundWrite *merge =
[self.visibleWrites childCompoundWriteAtPath:treePath];
if (!includeHiddenWrites && merge.isEmpty) {
return completeServerCache;
} else {
// If the server cache is null and we don't have a complete cache,
// we need to return nil
if (!includeHiddenWrites && completeServerCache == nil &&
![merge hasCompleteWriteAtPath:[FPath empty]]) {
return nil;
} else {
BOOL (^filter)(FWriteRecord *) = ^(FWriteRecord *record) {
return (BOOL)((record.visible || includeHiddenWrites) &&
(writeIdsToExclude == nil ||
![writeIdsToExclude
containsObject:[NSNumber
numberWithInteger:
record.writeId]]) &&
([record.path contains:treePath] ||
[treePath contains:record.path]));
};
FCompoundWrite *mergeAtPath =
[FWriteTree layerTreeFromWrites:self.allWrites
filter:filter
treeRoot:treePath];
id<FNode> layeredCache = completeServerCache
? completeServerCache
: [FEmptyNode emptyNode];
return [mergeAtPath applyToNode:layeredCache];
}
}
}
}
/**
* With optional, underlying server data, attempt to return a children node of
* children that we have complete data for. Used when creating new views, to
* pre-fill their complete event children snapshot.
*/
- (FChildrenNode *)calculateCompleteEventChildrenAtPath:(FPath *)treePath
completeServerChildren:
(id<FNode>)completeServerChildren {
__block id<FNode> completeChildren = [FEmptyNode emptyNode];
id<FNode> topLevelSet = [self.visibleWrites completeNodeAtPath:treePath];
if (topLevelSet != nil) {
if (![topLevelSet isLeafNode]) {
// We're shadowing everything. Return the children.
FChildrenNode *topChildrenNode = topLevelSet;
[topChildrenNode enumerateChildrenUsingBlock:^(
NSString *key, id<FNode> node, BOOL *stop) {
completeChildren = [completeChildren updateImmediateChild:key
withNewChild:node];
}];
}
return completeChildren;
} else {
// Layer any children we have on top of this
// We know we don't have a top-level set, so just enumerate existing
// children, and apply any updates
FCompoundWrite *merge =
[self.visibleWrites childCompoundWriteAtPath:treePath];
[completeServerChildren enumerateChildrenUsingBlock:^(
NSString *key, id<FNode> node, BOOL *stop) {
FCompoundWrite *childMerge =
[merge childCompoundWriteAtPath:[[FPath alloc] initWith:key]];
id<FNode> newChildNode = [childMerge applyToNode:node];
completeChildren =
[completeChildren updateImmediateChild:key
withNewChild:newChildNode];
}];
// Add any complete children we have from the set.
for (FNamedNode *node in merge.completeChildren) {
completeChildren =
[completeChildren updateImmediateChild:node.name
withNewChild:node.node];
}
return completeChildren;
}
}
/**
* Given that the underlying server data has updated, determine what, if
* anything, needs to be applied to the event cache.
*
* Possibilities
*
* 1. No write are shadowing. Events should be raised, the snap to be applied
* comes from the server data.
*
* 2. Some write is completely shadowing. No events to be raised.
*
* 3. Is partially shadowed. Events ..
*
* Either existingEventSnap or existingServerSnap must exist.
*/
- (id<FNode>)calculateEventCacheAfterServerOverwriteAtPath:(FPath *)treePath
childPath:(FPath *)childPath
existingEventSnap:
(id<FNode>)existingEventSnap
existingServerSnap:
(id<FNode>)existingServerSnap {
NSAssert(existingEventSnap != nil || existingServerSnap != nil,
@"Either existingEventSnap or existingServerSanp must exist.");
FPath *path = [treePath child:childPath];
if ([self.visibleWrites hasCompleteWriteAtPath:path]) {
// At this point we can probably guarantee that we're in case 2, meaning
// no events May need to check visibility while doing the
// findRootMostValueAndPath call
return nil;
} else {
// This could be more efficient if the serverNode + updates doesn't
// change the eventSnap However this is tricky to find out, since user
// updates don't necessary change the server snap, e.g. priority updates
// on empty nodes, or deep deletes. Another special case is if the
// server adds nodes, but doesn't change any existing writes. It is
// therefore not enough to only check if the updates change the
// serverNode. Maybe check if the merge tree contains these special
// cases and only do a full overwrite in that case?
FCompoundWrite *childMerge =
[self.visibleWrites childCompoundWriteAtPath:path];
if (childMerge.isEmpty) {
// We're not shadowing at all. Case 1
return [existingServerSnap getChild:childPath];
} else {
return [childMerge
applyToNode:[existingServerSnap getChild:childPath]];
}
}
}
/**
* Returns a complete child for a given server snap after applying all user
* writes or nil if there is no complete child for this child key.
*/
- (id<FNode>)calculateCompleteChildAtPath:(FPath *)treePath
childKey:(NSString *)childKey
cache:(FCacheNode *)existingServerCache {
FPath *path = [treePath childFromString:childKey];
id<FNode> shadowingNode = [self.visibleWrites completeNodeAtPath:path];
if (shadowingNode != nil) {
return shadowingNode;
} else {
if ([existingServerCache isCompleteForChild:childKey]) {
FCompoundWrite *childMerge =
[self.visibleWrites childCompoundWriteAtPath:path];
return [childMerge applyToNode:[existingServerCache.node
getImmediateChild:childKey]];
} else {
return nil;
}
}
}
/**
* Returns a node if there is a complete overwrite for this path. More
* specifically, if there is a write at a higher path, this will return the
* child of that write relative to the write and this path. Returns null if
* there is no write at this path.
*/
- (id<FNode>)shadowingWriteAtPath:(FPath *)path {
return [self.visibleWrites completeNodeAtPath:path];
}
/**
* This method is used when processing child remove events on a query. If we
* can, we pull in children that were outside the window, but may now be in the
* window.
*/
- (FNamedNode *)calculateNextNodeAfterPost:(FNamedNode *)post
atPath:(FPath *)treePath
completeServerData:(id<FNode>)completeServerData
reverse:(BOOL)reverse
index:(id<FIndex>)index {
__block id<FNode> toIterate;
FCompoundWrite *merge =
[self.visibleWrites childCompoundWriteAtPath:treePath];
id<FNode> shadowingNode = [merge completeNodeAtPath:[FPath empty]];
if (shadowingNode != nil) {
toIterate = shadowingNode;
} else if (completeServerData != nil) {
toIterate = [merge applyToNode:completeServerData];
} else {
return nil;
}
__block NSString *currentNextKey = nil;
__block id<FNode> currentNextNode = nil;
[toIterate enumerateChildrenUsingBlock:^(NSString *key, id<FNode> node,
BOOL *stop) {
if ([index compareKey:key
andNode:node
toOtherKey:post.name
andNode:post.node
reverse:reverse] > NSOrderedSame &&
(!currentNextKey || [index compareKey:key
andNode:node
toOtherKey:currentNextKey
andNode:currentNextNode
reverse:reverse] < NSOrderedSame)) {
currentNextKey = key;
currentNextNode = node;
}
}];
if (currentNextKey != nil) {
return [FNamedNode nodeWithName:currentNextKey node:currentNextNode];
} else {
return nil;
}
}
#pragma mark -
#pragma mark Private Methods
- (BOOL)record:(FWriteRecord *)record containsPath:(FPath *)path {
if ([record isOverwrite]) {
return [record.path contains:path];
} else {
__block BOOL contains = NO;
[record.merge
enumerateWrites:^(FPath *childPath, id<FNode> node, BOOL *stop) {
contains = [[record.path child:childPath] contains:path];
*stop = contains;
}];
return contains;
}
}
/**
* Re-layer the writes and merges into a tree so we can efficiently calculate
* event snapshots
*/
- (void)resetTree {
self.visibleWrites =
[FWriteTree layerTreeFromWrites:self.allWrites
filter:[FWriteTree defaultFilter]
treeRoot:[FPath empty]];
if ([self.allWrites count] > 0) {
FWriteRecord *lastRecord = self.allWrites[[self.allWrites count] - 1];
self.lastWriteId = lastRecord.writeId;
} else {
self.lastWriteId = -1;
}
}
/**
* The default filter used when constructing the tree. Keep everything that's
* visible.
*/
+ (BOOL (^)(FWriteRecord *record))defaultFilter {
static BOOL (^filter)(FWriteRecord *);
static dispatch_once_t filterToken;
dispatch_once(&filterToken, ^{
filter = ^(FWriteRecord *record) {
return YES;
};
});
return filter;
}
/**
* Static method. Given an array of WriteRecords, a filter for which ones to
* include, and a path, construct a merge at that path
* @return An FImmutableTree of id<FNode>s.
*/
+ (FCompoundWrite *)layerTreeFromWrites:(NSArray *)writes
filter:(BOOL (^)(FWriteRecord *record))filter
treeRoot:(FPath *)treeRoot {
__block FCompoundWrite *compoundWrite = [FCompoundWrite emptyWrite];
[writes enumerateObjectsUsingBlock:^(FWriteRecord *record, NSUInteger idx,
BOOL *stop) {
// Theory, a later set will either:
// a) abort a relevant transaction, so no need to worry about excluding it
// from calculating that transaction b) not be relevant to a transaction
// (separate branch), so again will not affect the data for that
// transaction
if (filter(record)) {
FPath *writePath = record.path;
if ([record isOverwrite]) {
if ([treeRoot contains:writePath]) {
FPath *relativePath = [FPath relativePathFrom:treeRoot
to:writePath];
compoundWrite = [compoundWrite addWrite:record.overwrite
atPath:relativePath];
} else if ([writePath contains:treeRoot]) {
id<FNode> child = [record.overwrite
getChild:[FPath relativePathFrom:writePath to:treeRoot]];
compoundWrite = [compoundWrite addWrite:child
atPath:[FPath empty]];
} else {
// There is no overlap between root path and write path,
// ignore write
}
} else {
if ([treeRoot contains:writePath]) {
FPath *relativePath = [FPath relativePathFrom:treeRoot
to:writePath];
compoundWrite = [compoundWrite addCompoundWrite:record.merge
atPath:relativePath];
} else if ([writePath contains:treeRoot]) {
FPath *relativePath = [FPath relativePathFrom:writePath
to:treeRoot];
if (relativePath.isEmpty) {
compoundWrite =
[compoundWrite addCompoundWrite:record.merge
atPath:[FPath empty]];
} else {
id<FNode> child =
[record.merge completeNodeAtPath:relativePath];
if (child != nil) {
// There exists a child in this node that matches the
// root path
id<FNode> deepNode =
[child getChild:[relativePath popFront]];
compoundWrite =
[compoundWrite addWrite:deepNode
atPath:[FPath empty]];
}
}
} else {
// There is no overlap between root path and write path,
// ignore write
}
}
}
}];
return compoundWrite;
}
@end
@@ -0,0 +1,57 @@
/*
* 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 <Foundation/Foundation.h>
@protocol FNode;
@class FChildrenNode;
@class FPath;
@class FNamedNode;
@class FWriteRecord;
@class FWriteTree;
@protocol FIndex;
@class FCacheNode;
@interface FWriteTreeRef : NSObject
- (id)initWithPath:(FPath *)aPath writeTree:(FWriteTree *)tree;
- (id<FNode>)calculateCompleteEventCacheWithCompleteServerCache:
(id<FNode>)completeServerCache;
- (FChildrenNode *)calculateCompleteEventChildrenWithCompleteServerChildren:
(FChildrenNode *)completeServerChildren;
- (id<FNode>)
calculateEventCacheAfterServerOverwriteWithChildPath:(FPath *)childPath
existingEventSnap:
(id<FNode>)existingEventSnap
existingServerSnap:
(id<FNode>)existingServerSnap;
- (id<FNode>)shadowingWriteAtPath:(FPath *)path;
- (FNamedNode *)calculateNextNodeAfterPost:(FNamedNode *)post
completeServerData:(id<FNode>)completeServerData
reverse:(BOOL)reverse
index:(id<FIndex>)index;
- (id<FNode>)calculateCompleteChild:(NSString *)childKey
cache:(FCacheNode *)existingServerCache;
- (FWriteTreeRef *)childWriteTreeRef:(NSString *)childKey;
@end
@@ -0,0 +1,159 @@
/*
* 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 "FirebaseDatabase/Sources/Core/FWriteTreeRef.h"
#import "FirebaseDatabase/Sources/Core/FWriteRecord.h"
#import "FirebaseDatabase/Sources/Core/FWriteTree.h"
#import "FirebaseDatabase/Sources/Core/Utilities/FPath.h"
#import "FirebaseDatabase/Sources/Core/View/FCacheNode.h"
#import "FirebaseDatabase/Sources/FIndex.h"
#import "FirebaseDatabase/Sources/FNamedNode.h"
#import "FirebaseDatabase/Sources/Snapshot/FChildrenNode.h"
#import "FirebaseDatabase/Sources/Snapshot/FNode.h"
@interface FWriteTreeRef ()
/**
* The path to this particular FWriteTreeRef. Used for calling methods on
* writeTree while exposing a simpler interface to callers.
*/
@property(nonatomic, strong) FPath *path;
/**
* A reference to the actual tree of the write data. All methods are
* pass-through to the tree, but with the appropriate path prefixed.
*
* This lets us make cheap references to points in the tree for sync points
* without having to copy and maintain all of the data.
*/
@property(nonatomic, strong) FWriteTree *writeTree;
@end
/**
* A FWriteTreeRef wraps a FWriteTree and a FPath, for convenient access to a
* particular subtree. All the methods just proxy to the underlying FWriteTree.
*/
@implementation FWriteTreeRef
- (id)initWithPath:(FPath *)aPath writeTree:(FWriteTree *)tree {
self = [super init];
if (self) {
self.path = aPath;
self.writeTree = tree;
}
return self;
}
/**
* @return If possible, returns a complete event cache, using the underlying
* server data if possible. In addition, can be used to get a cache that
* includes hidden writes, and excludes arbitrary writes. Note that customizing
* the returned node can lead to a more expensive calculation.
*/
- (id<FNode>)calculateCompleteEventCacheWithCompleteServerCache:
(id<FNode>)completeServerCache {
return [self.writeTree calculateCompleteEventCacheAtPath:self.path
completeServerCache:completeServerCache
excludeWriteIds:nil
includeHiddenWrites:NO];
}
/**
* @return If possible, returns a children node containing all of the complete
* children we have data for. The returned data is a mix of the given server
* data and write data.
*/
- (FChildrenNode *)calculateCompleteEventChildrenWithCompleteServerChildren:
(id<FNode>)completeServerChildren {
return [self.writeTree
calculateCompleteEventChildrenAtPath:self.path
completeServerChildren:completeServerChildren];
}
/**
* Given that either the underlying server data has updated or the outstanding
* writes have been updating, determine what, if anything, needs to be applied
* to the event cache.
*
* Possibilities:
*
* 1. No writes are shadowing. Events should be raised, the snap to be applied
* comes from the server data.
*
* 2. Some writes are completly shadowing. No events to be raised.
*
* 3. Is partially shadowed. Events should be raised.
*
* Either existingEventSnap or existingServerSnap must exist, this is validated
* via an assert.
*/
- (id<FNode>)
calculateEventCacheAfterServerOverwriteWithChildPath:(FPath *)childPath
existingEventSnap:
(id<FNode>)existingEventSnap
existingServerSnap:
(id<FNode>)existingServerSnap {
return [self.writeTree
calculateEventCacheAfterServerOverwriteAtPath:self.path
childPath:childPath
existingEventSnap:existingEventSnap
existingServerSnap:existingServerSnap];
}
/**
* Returns a node if there is a complete overwrite for this path. More
* specifically, if there is a write at a higher path, this will return the
* child of that write relative to the write and this path. Returns nil if there
* is no write at this path.
*/
- (id<FNode>)shadowingWriteAtPath:(FPath *)path {
return [self.writeTree shadowingWriteAtPath:[self.path child:path]];
}
/**
* This method is used when processing child remove events on a query. If we
* can, we pull in children that are outside the window, but may now be in the
* window.
*/
- (FNamedNode *)calculateNextNodeAfterPost:(FNamedNode *)post
completeServerData:(id<FNode>)completeServerData
reverse:(BOOL)reverse
index:(id<FIndex>)index {
return [self.writeTree calculateNextNodeAfterPost:post
atPath:self.path
completeServerData:completeServerData
reverse:reverse
index:index];
}
/**
* Returns a complete child for a given server snap after applying all user
* writes or nil if there is no complete child for this child key.
*/
- (id<FNode>)calculateCompleteChild:(NSString *)childKey
cache:(FCacheNode *)existingServerCache {
return [self.writeTree calculateCompleteChildAtPath:self.path
childKey:childKey
cache:existingServerCache];
}
/**
* @return a WriteTreeref for a child.
*/
- (FWriteTreeRef *)childWriteTreeRef:(NSString *)childKey {
return
[[FWriteTreeRef alloc] initWithPath:[self.path childFromString:childKey]
writeTree:self.writeTree];
}
@end
@@ -0,0 +1,37 @@
/*
* 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 "FirebaseDatabase/Sources/Core/Operation/FOperation.h"
@class FPath;
@class FOperationSource;
@class FImmutableTree;
@interface FAckUserWrite : NSObject <FOperation>
- initWithPath:(FPath *)operationPath
affectedTree:(FImmutableTree *)affectedTree
revert:(BOOL)shouldRevert;
@property(nonatomic, strong, readonly) FOperationSource *source;
@property(nonatomic, readonly) FOperationType type;
@property(nonatomic, strong, readonly) FPath *path;
// A FImmutableTree, containing @YES for each affected path. Affected paths
// can't overlap.
@property(nonatomic, strong, readonly) FImmutableTree *affectedTree;
@property(nonatomic, readonly) BOOL revert;
@end
@@ -0,0 +1,66 @@
/*
* 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 "FirebaseDatabase/Sources/Core/Operation/FAckUserWrite.h"
#import "FirebaseDatabase/Sources/Core/Operation/FOperationSource.h"
#import "FirebaseDatabase/Sources/Core/Utilities/FImmutableTree.h"
#import "FirebaseDatabase/Sources/Core/Utilities/FPath.h"
@implementation FAckUserWrite
- (id)initWithPath:(FPath *)operationPath
affectedTree:(FImmutableTree *)tree
revert:(BOOL)shouldRevert {
self = [super init];
if (self) {
self->_source = [FOperationSource userInstance];
self->_type = FOperationTypeAckUserWrite;
self->_path = operationPath;
self->_affectedTree = tree;
self->_revert = shouldRevert;
}
return self;
}
- (FAckUserWrite *)operationForChild:(NSString *)childKey {
if (![self.path isEmpty]) {
NSAssert([self.path.getFront isEqualToString:childKey],
@"operationForChild called for unrelated child.");
return [[FAckUserWrite alloc] initWithPath:[self.path popFront]
affectedTree:self.affectedTree
revert:self.revert];
} else if (self.affectedTree.value != nil) {
NSAssert(self.affectedTree.children.isEmpty,
@"affectedTree should not have overlapping affected paths.");
// All child locations are affected as well; just return same operation.
return self;
} else {
FImmutableTree *childTree =
[self.affectedTree subtreeAtPath:[[FPath alloc] initWith:childKey]];
return [[FAckUserWrite alloc] initWithPath:[FPath empty]
affectedTree:childTree
revert:self.revert];
}
}
- (NSString *)description {
return
[NSString stringWithFormat:
@"FAckUserWrite { path=%@, revert=%d, affectedTree=%@ }",
self.path, self.revert, self.affectedTree];
}
@end
@@ -0,0 +1,32 @@
/*
* 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 "FirebaseDatabase/Sources/Core/Operation/FOperation.h"
@class FCompoundWrite;
@interface FMerge : NSObject <FOperation>
- (id)initWithSource:(FOperationSource *)aSource
path:(FPath *)aPath
children:(FCompoundWrite *)children;
@property(nonatomic, strong, readonly) FOperationSource *source;
@property(nonatomic, readonly) FOperationType type;
@property(nonatomic, strong, readonly) FPath *path;
@property(nonatomic, strong, readonly) FCompoundWrite *children;
@end
@@ -0,0 +1,85 @@
/*
* 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 "FirebaseDatabase/Sources/Core/Operation/FMerge.h"
#import "FirebaseDatabase/Sources/Core/Operation/FOperationSource.h"
#import "FirebaseDatabase/Sources/Core/Operation/FOverwrite.h"
#import "FirebaseDatabase/Sources/Core/Utilities/FPath.h"
#import "FirebaseDatabase/Sources/Snapshot/FCompoundWrite.h"
#import "FirebaseDatabase/Sources/Snapshot/FNode.h"
@interface FMerge ()
@property(nonatomic, strong, readwrite) FOperationSource *source;
@property(nonatomic, readwrite) FOperationType type;
@property(nonatomic, strong, readwrite) FPath *path;
@property(nonatomic, strong) FCompoundWrite *children;
@end
@implementation FMerge
@synthesize source;
@synthesize type;
@synthesize path;
@synthesize children;
- (id)initWithSource:(FOperationSource *)aSource
path:(FPath *)aPath
children:(FCompoundWrite *)someChildren {
self = [super init];
if (self) {
self.source = aSource;
self.type = FOperationTypeMerge;
self.path = aPath;
self.children = someChildren;
}
return self;
}
- (id<FOperation>)operationForChild:(NSString *)childKey {
if ([self.path isEmpty]) {
FCompoundWrite *childTree = [self.children
childCompoundWriteAtPath:[[FPath alloc] initWith:childKey]];
if (childTree.isEmpty) {
return nil;
} else if (childTree.rootWrite != nil) {
// We have a snapshot for the child in question. This becomes an
// overwrite of the child.
return [[FOverwrite alloc] initWithSource:self.source
path:[FPath empty]
snap:childTree.rootWrite];
} else {
// This is a merge at a deeper level
return [[FMerge alloc] initWithSource:self.source
path:[FPath empty]
children:childTree];
}
} else {
NSAssert(
[self.path.getFront isEqualToString:childKey],
@"Can't get a merge for a child not on the path of the operation");
return [[FMerge alloc] initWithSource:self.source
path:[self.path popFront]
children:self.children];
}
}
- (NSString *)description {
return
[NSString stringWithFormat:@"FMerge { path=%@, soruce=%@ children=%@}",
self.path, self.source, self.children];
}
@end
@@ -0,0 +1,34 @@
/*
* 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 <Foundation/Foundation.h>
@class FOperationSource;
@class FPath;
typedef NS_ENUM(NSInteger, FOperationType) {
FOperationTypeOverwrite = 0,
FOperationTypeMerge = 1,
FOperationTypeAckUserWrite = 2,
FOperationTypeListenComplete = 3
};
@protocol FOperation <NSObject>
@property(nonatomic, strong, readonly) FOperationSource *source;
@property(nonatomic, readonly) FOperationType type;
@property(nonatomic, strong, readonly) FPath *path;
- (id<FOperation>)operationForChild:(NSString *)childKey;
@end
@@ -0,0 +1,37 @@
/*
* 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 <Foundation/Foundation.h>
@class FQueryParams;
@interface FOperationSource : NSObject
@property(nonatomic, readonly) BOOL fromUser;
@property(nonatomic, readonly) BOOL fromServer;
@property(nonatomic, readonly) BOOL isTagged;
@property(nonatomic, strong, readonly) FQueryParams *queryParams;
- initWithFromUser:(BOOL)isFromUser
fromServer:(BOOL)isFromServer
queryParams:(FQueryParams *)params
tagged:(BOOL)isTagged;
+ (FOperationSource *)userInstance;
+ (FOperationSource *)serverInstance;
+ (FOperationSource *)forServerTaggedQuery:(FQueryParams *)params;
@end
@@ -0,0 +1,86 @@
/*
* 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 "FirebaseDatabase/Sources/Core/Operation/FOperationSource.h"
#import "FirebaseDatabase/Sources/Core/FQueryParams.h"
#import "FirebaseDatabase/Sources/Core/Utilities/FPath.h"
@interface FOperationSource ()
@property(nonatomic, readwrite) BOOL fromUser;
@property(nonatomic, readwrite) BOOL fromServer;
@property(nonatomic, readwrite) BOOL isTagged;
@property(nonatomic, strong, readwrite) FQueryParams *queryParams;
@end
@implementation FOperationSource
@synthesize fromUser;
@synthesize fromServer;
@synthesize queryParams;
- (id)initWithFromUser:(BOOL)isFromUser
fromServer:(BOOL)isFromServer
queryParams:(FQueryParams *)params
tagged:(BOOL)tagged {
self = [super init];
if (self) {
self.fromUser = isFromUser;
self.fromServer = isFromServer;
self.queryParams = params;
self.isTagged = tagged;
}
return self;
}
+ (FOperationSource *)userInstance {
static FOperationSource *user = nil;
static dispatch_once_t userToken;
dispatch_once(&userToken, ^{
user = [[FOperationSource alloc] initWithFromUser:YES
fromServer:NO
queryParams:nil
tagged:NO];
});
return user;
}
+ (FOperationSource *)serverInstance {
static FOperationSource *server = nil;
static dispatch_once_t serverToken;
dispatch_once(&serverToken, ^{
server = [[FOperationSource alloc] initWithFromUser:NO
fromServer:YES
queryParams:nil
tagged:NO];
});
return server;
}
+ (FOperationSource *)forServerTaggedQuery:(FQueryParams *)params {
return [[FOperationSource alloc] initWithFromUser:NO
fromServer:YES
queryParams:params
tagged:YES];
}
- (NSString *)description {
return [NSString stringWithFormat:@"FOperationSource { fromUser=%d, "
@"fromServer=%d, queryId=%@, tagged=%d }",
self.fromUser, self.fromServer,
self.queryParams, self.isTagged];
}
@end
@@ -0,0 +1,32 @@
/*
* 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 "FirebaseDatabase/Sources/Core/Operation/FOperation.h"
@protocol FNode;
@interface FOverwrite : NSObject <FOperation>
- (id)initWithSource:(FOperationSource *)aSource
path:(FPath *)aPath
snap:(id<FNode>)aSnap;
@property(nonatomic, strong, readonly) FOperationSource *source;
@property(nonatomic, readonly) FOperationType type;
@property(nonatomic, strong, readonly) FPath *path;
@property(nonatomic, strong, readonly) id<FNode> snap;
@end
@@ -0,0 +1,67 @@
/*
* 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 "FirebaseDatabase/Sources/Core/Operation/FOverwrite.h"
#import "FirebaseDatabase/Sources/Core/Operation/FOperationSource.h"
#import "FirebaseDatabase/Sources/Snapshot/FNode.h"
@interface FOverwrite ()
@property(nonatomic, strong, readwrite) FOperationSource *source;
@property(nonatomic, readwrite) FOperationType type;
@property(nonatomic, strong, readwrite) FPath *path;
@property(nonatomic, strong) id<FNode> snap;
@end
@implementation FOverwrite
@synthesize source;
@synthesize type;
@synthesize path;
@synthesize snap;
- (id)initWithSource:(FOperationSource *)aSource
path:(FPath *)aPath
snap:(id<FNode>)aSnap {
self = [super init];
if (self) {
self.source = aSource;
self.type = FOperationTypeOverwrite;
self.path = aPath;
self.snap = aSnap;
}
return self;
}
- (FOverwrite *)operationForChild:(NSString *)childKey {
if ([self.path isEmpty]) {
return [[FOverwrite alloc]
initWithSource:self.source
path:[FPath empty]
snap:[self.snap getImmediateChild:childKey]];
} else {
return [[FOverwrite alloc] initWithSource:self.source
path:[self.path popFront]
snap:self.snap];
}
}
- (NSString *)description {
return [NSString
stringWithFormat:@"FOverwrite { path=%@, source=%@, snapshot=%@ }",
self.path, self.source, self.snap];
}
@end
@@ -0,0 +1,33 @@
/*
* 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 <Foundation/Foundation.h>
@interface FIRRetryHelper : NSObject
- (instancetype)initWithDispatchQueue:(dispatch_queue_t)dispatchQueue
minRetryDelayAfterFailure:(NSTimeInterval)minRetryDelayAfterFailure
maxRetryDelay:(NSTimeInterval)maxRetryDelay
retryExponent:(double)retryExponent
jitterFactor:(double)jitterFactor;
- (void)retry:(void (^)(void))block;
- (void)cancel;
- (void)signalSuccess;
@end
@@ -0,0 +1,140 @@
/*
* 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 "FirebaseDatabase/Sources/Core/Utilities/FIRRetryHelper.h"
#import "FirebaseCore/Sources/Private/FirebaseCoreInternal.h"
#import "FirebaseDatabase/Sources/Utilities/FUtilities.h"
@interface FIRRetryHelperTask : NSObject
@property(nonatomic, strong) void (^block)(void);
@end
@implementation FIRRetryHelperTask
- (instancetype)initWithBlock:(void (^)(void))block {
self = [super init];
if (self != nil) {
self->_block = [block copy];
}
return self;
}
- (BOOL)isCanceled {
return self.block == nil;
}
- (void)cancel {
self.block = nil;
}
- (void)execute {
if (self.block) {
self.block();
}
}
@end
@interface FIRRetryHelper ()
@property(nonatomic, strong) dispatch_queue_t dispatchQueue;
@property(nonatomic) NSTimeInterval minRetryDelayAfterFailure;
@property(nonatomic) NSTimeInterval maxRetryDelay;
@property(nonatomic) double retryExponent;
@property(nonatomic) double jitterFactor;
@property(nonatomic) BOOL lastWasSuccess;
@property(nonatomic) NSTimeInterval currentRetryDelay;
@property(nonatomic, strong) FIRRetryHelperTask *scheduledRetry;
@end
@implementation FIRRetryHelper
- (instancetype)initWithDispatchQueue:(dispatch_queue_t)dispatchQueue
minRetryDelayAfterFailure:(NSTimeInterval)minRetryDelayAfterFailure
maxRetryDelay:(NSTimeInterval)maxRetryDelay
retryExponent:(double)retryExponent
jitterFactor:(double)jitterFactor {
self = [super init];
if (self != nil) {
self->_dispatchQueue = dispatchQueue;
self->_minRetryDelayAfterFailure = minRetryDelayAfterFailure;
self->_maxRetryDelay = maxRetryDelay;
self->_retryExponent = retryExponent;
self->_jitterFactor = jitterFactor;
self->_lastWasSuccess = YES;
}
return self;
}
- (void)retry:(void (^)(void))block {
if (self.scheduledRetry != nil) {
FFLog(@"I-RDB054001", @"Canceling existing retry attempt");
[self.scheduledRetry cancel];
self.scheduledRetry = nil;
}
NSTimeInterval delay;
if (self.lastWasSuccess) {
delay = 0;
} else {
if (self.currentRetryDelay == 0) {
self.currentRetryDelay = self.minRetryDelayAfterFailure;
} else {
NSTimeInterval newDelay =
(self.currentRetryDelay * self.retryExponent);
self.currentRetryDelay = MIN(newDelay, self.maxRetryDelay);
}
delay = ((1 - self.jitterFactor) * self.currentRetryDelay) +
(self.jitterFactor * self.currentRetryDelay *
[FUtilities randomDouble]);
FFLog(@"I-RDB054002", @"Scheduling retry in %fs", delay);
}
self.lastWasSuccess = NO;
FIRRetryHelperTask *task = [[FIRRetryHelperTask alloc] initWithBlock:block];
self.scheduledRetry = task;
dispatch_time_t popTime =
dispatch_time(DISPATCH_TIME_NOW, (long long)(delay * NSEC_PER_SEC));
dispatch_after(popTime, self.dispatchQueue, ^{
if (![task isCanceled]) {
self.scheduledRetry = nil;
[task execute];
}
});
}
- (void)signalSuccess {
self.lastWasSuccess = YES;
self.currentRetryDelay = 0;
}
- (void)cancel {
if (self.scheduledRetry != nil) {
FFLog(@"I-RDB054003", @"Canceling existing retry attempt");
[self.scheduledRetry cancel];
self.scheduledRetry = nil;
} else {
FFLog(@"I-RDB054004", @"No existing retry attempt to cancel");
}
self.currentRetryDelay = 0;
}
@end
@@ -0,0 +1,59 @@
/*
* 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 "FirebaseDatabase/Sources/Core/Utilities/FPath.h"
#import "FirebaseDatabase/Sources/Utilities/Tuples/FTuplePathValue.h"
#import "FirebaseDatabase/Sources/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FImmutableSortedDictionary.h"
@interface FImmutableTree : NSObject
- (id)initWithValue:(id)aValue;
- (id)initWithValue:(id)aValue
children:(FImmutableSortedDictionary *)childrenMap;
+ (FImmutableTree *)empty;
- (BOOL)isEmpty;
- (FTuplePathValue *)findRootMostMatchingPath:(FPath *)relativePath
predicate:(BOOL (^)(id))predicate;
- (FTuplePathValue *)findRootMostValueAndPath:(FPath *)relativePath;
- (FImmutableTree *)subtreeAtPath:(FPath *)relativePath;
- (FImmutableTree *)setValue:(id)newValue atPath:(FPath *)relativePath;
- (FImmutableTree *)removeValueAtPath:(FPath *)relativePath;
- (id)valueAtPath:(FPath *)relativePath;
- (id)rootMostValueOnPath:(FPath *)path;
- (id)rootMostValueOnPath:(FPath *)path matching:(BOOL (^)(id))predicate;
- (id)leafMostValueOnPath:(FPath *)path;
- (id)leafMostValueOnPath:(FPath *)relativePath
matching:(BOOL (^)(id))predicate;
- (BOOL)containsValueMatching:(BOOL (^)(id))predicate;
- (FImmutableTree *)setTree:(FImmutableTree *)newTree
atPath:(FPath *)relativePath;
- (id)foldWithBlock:(id (^)(FPath *path, id value,
NSDictionary *foldedChildren))block;
- (id)findOnPath:(FPath *)path
andApplyBlock:(id (^)(FPath *path, id value))block;
- (FPath *)forEachOnPath:(FPath *)path
whileBlock:(BOOL (^)(FPath *path, id value))block;
- (FImmutableTree *)forEachOnPath:(FPath *)path
performBlock:(void (^)(FPath *path, id value))block;
- (void)forEach:(void (^)(FPath *path, id value))block;
- (void)forEachChild:(void (^)(NSString *childKey, id childValue))block;
@property(nonatomic, strong, readonly) id value;
@property(nonatomic, strong, readonly) FImmutableSortedDictionary *children;
@end
@@ -0,0 +1,486 @@
/*
* 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 "FirebaseDatabase/Sources/Core/Utilities/FImmutableTree.h"
#import "FirebaseDatabase/Sources/Core/Utilities/FPath.h"
#import "FirebaseDatabase/Sources/Utilities/FUtilities.h"
#import "FirebaseDatabase/Sources/third_party/FImmutableSortedDictionary/FImmutableSortedDictionary/FImmutableSortedDictionary.h"
@interface FImmutableTree ()
@property(nonatomic, strong, readwrite) id value;
/**
* Maps NSString -> FImmutableTree<T>, where <T> is type of value.
*/
@property(nonatomic, strong, readwrite) FImmutableSortedDictionary *children;
@end
@implementation FImmutableTree
@synthesize value;
@synthesize children;
- (id)initWithValue:(id)aValue {
self = [super init];
if (self) {
self.value = aValue;
self.children = [FImmutableTree emptyChildren];
}
return self;
}
- (id)initWithValue:(id)aValue
children:(FImmutableSortedDictionary *)childrenMap {
self = [super init];
if (self) {
self.value = aValue;
self.children = childrenMap;
}
return self;
}
+ (FImmutableSortedDictionary *)emptyChildren {
static dispatch_once_t emptyChildrenToken;
static FImmutableSortedDictionary *emptyChildren;
dispatch_once(&emptyChildrenToken, ^{
emptyChildren = [FImmutableSortedDictionary
dictionaryWithComparator:[FUtilities stringComparator]];
});
return emptyChildren;
}
+ (FImmutableTree *)empty {
static dispatch_once_t emptyImmutableTreeToken;
static FImmutableTree *emptyTree = nil;
dispatch_once(&emptyImmutableTreeToken, ^{
emptyTree = [[FImmutableTree alloc] initWithValue:nil];
});
return emptyTree;
}
- (BOOL)isEmpty {
return self.value == nil && [self.children isEmpty];
}
/**
* Given a path and a predicate, return the first node and the path to that node
* where the predicate returns true
* // TODO Do a perf test. If we're creating a bunch of FTuplePathValue objects
* on the way back out, it may be better to pass down a pathSoFar FPath
*/
- (FTuplePathValue *)findRootMostMatchingPath:(FPath *)relativePath
predicate:(BOOL (^)(id value))predicate {
if (self.value != nil && predicate(self.value)) {
return [[FTuplePathValue alloc] initWithPath:[FPath empty]
value:self.value];
} else {
if ([relativePath isEmpty]) {
return nil;
} else {
NSString *front = [relativePath getFront];
FImmutableTree *child = [self.children get:front];
if (child != nil) {
FTuplePathValue *childExistingPathAndValue =
[child findRootMostMatchingPath:[relativePath popFront]
predicate:predicate];
if (childExistingPathAndValue != nil) {
FPath *fullPath = [[[FPath alloc] initWith:front]
child:childExistingPathAndValue.path];
return [[FTuplePathValue alloc]
initWithPath:fullPath
value:childExistingPathAndValue.value];
} else {
return nil;
}
} else {
// No child matching path
return nil;
}
}
}
}
/**
* Find, if it exists, the shortest subpath of the given path that points a
* defined value in the tree
*/
- (FTuplePathValue *)findRootMostValueAndPath:(FPath *)relativePath {
return [self findRootMostMatchingPath:relativePath
predicate:^BOOL(__unsafe_unretained id value) {
return YES;
}];
}
- (id)rootMostValueOnPath:(FPath *)path {
return [self rootMostValueOnPath:path
matching:^BOOL(id value) {
return YES;
}];
}
- (id)rootMostValueOnPath:(FPath *)path matching:(BOOL (^)(id))predicate {
if (self.value != nil && predicate(self.value)) {
return self.value;
} else if (path.isEmpty) {
return nil;
} else {
return [[self.children get:path.getFront]
rootMostValueOnPath:[path popFront]
matching:predicate];
}
}
- (id)leafMostValueOnPath:(FPath *)path {
return [self leafMostValueOnPath:path
matching:^BOOL(id value) {
return YES;
}];
}
- (id)leafMostValueOnPath:(FPath *)relativePath
matching:(BOOL (^)(id))predicate {
__block id currentValue = self.value;
__block FImmutableTree *currentTree = self;
[relativePath enumerateComponentsUsingBlock:^(NSString *key, BOOL *stop) {
currentTree = [currentTree.children get:key];
if (currentTree == nil) {
*stop = YES;
} else {
id treeValue = currentTree.value;
if (treeValue != nil && predicate(treeValue)) {
currentValue = treeValue;
}
}
}];
return currentValue;
}
- (BOOL)containsValueMatching:(BOOL (^)(id))predicate {
if (self.value != nil && predicate(self.value)) {
return YES;
} else {
__block BOOL found = NO;
[self.children enumerateKeysAndObjectsUsingBlock:^(
NSString *key, FImmutableTree *subtree, BOOL *stop) {
found = [subtree containsValueMatching:predicate];
if (found)
*stop = YES;
}];
return found;
}
}
- (FImmutableTree *)subtreeAtPath:(FPath *)relativePath {
if ([relativePath isEmpty]) {
return self;
} else {
NSString *front = [relativePath getFront];
FImmutableTree *childTree = [self.children get:front];
if (childTree != nil) {
return [childTree subtreeAtPath:[relativePath popFront]];
} else {
return [FImmutableTree empty];
}
}
}
/**
* Sets a value at the specified path
*/
- (FImmutableTree *)setValue:(id)newValue atPath:(FPath *)relativePath {
if ([relativePath isEmpty]) {
return [[FImmutableTree alloc] initWithValue:newValue
children:self.children];
} else {
NSString *front = [relativePath getFront];
FImmutableTree *child = [self.children get:front];
if (child == nil) {
child = [FImmutableTree empty];
}
FImmutableTree *newChild = [child setValue:newValue
atPath:[relativePath popFront]];
FImmutableSortedDictionary *newChildren =
[self.children insertKey:front withValue:newChild];
return [[FImmutableTree alloc] initWithValue:self.value
children:newChildren];
}
}
/**
* Remove the value at the specified path
*/
- (FImmutableTree *)removeValueAtPath:(FPath *)relativePath {
if ([relativePath isEmpty]) {
if ([self.children isEmpty]) {
return [FImmutableTree empty];
} else {
return [[FImmutableTree alloc] initWithValue:nil
children:self.children];
}
} else {
NSString *front = [relativePath getFront];
FImmutableTree *child = [self.children get:front];
if (child) {
FImmutableTree *newChild =
[child removeValueAtPath:[relativePath popFront]];
FImmutableSortedDictionary *newChildren;
if ([newChild isEmpty]) {
newChildren = [self.children removeKey:front];
} else {
newChildren = [self.children insertKey:front
withValue:newChild];
}
if (self.value == nil && [newChildren isEmpty]) {
return [FImmutableTree empty];
} else {
return [[FImmutableTree alloc] initWithValue:self.value
children:newChildren];
}
} else {
return self;
}
}
}
/**
* Gets a value from the tree
*/
- (id)valueAtPath:(FPath *)relativePath {
if ([relativePath isEmpty]) {
return self.value;
} else {
NSString *front = [relativePath getFront];
FImmutableTree *child = [self.children get:front];
if (child) {
return [child valueAtPath:[relativePath popFront]];
} else {
return nil;
}
}
}
/**
* Replaces the subtree at the specified path with the given new tree
*/
- (FImmutableTree *)setTree:(FImmutableTree *)newTree
atPath:(FPath *)relativePath {
if ([relativePath isEmpty]) {
return newTree;
} else {
NSString *front = [relativePath getFront];
FImmutableTree *child = [self.children get:front];
if (child == nil) {
child = [FImmutableTree empty];
}
FImmutableTree *newChild = [child setTree:newTree
atPath:[relativePath popFront]];
FImmutableSortedDictionary *newChildren;
if ([newChild isEmpty]) {
newChildren = [self.children removeKey:front];
} else {
newChildren = [self.children insertKey:front withValue:newChild];
}
return [[FImmutableTree alloc] initWithValue:self.value
children:newChildren];
}
}
/**
* Performs a depth first fold on this tree. Transforms a tree into a single
* value, given a function that operates on the path to a node, an optional
* current value, and a map of the child names to folded subtrees
*/
- (id)foldWithBlock:(id (^)(FPath *path, id value,
NSDictionary *foldedChildren))block {
return [self foldWithPathSoFar:[FPath empty] withBlock:block];
}
/**
* Recursive helper for public facing foldWithBlock: method
*/
- (id)foldWithPathSoFar:(FPath *)pathSoFar
withBlock:(id (^)(FPath *path, id value,
NSDictionary *foldedChildren))block {
__block NSMutableDictionary *accum = [[NSMutableDictionary alloc] init];
[self.children
enumerateKeysAndObjectsUsingBlock:^(
NSString *childKey, FImmutableTree *childTree, BOOL *stop) {
accum[childKey] =
[childTree foldWithPathSoFar:[pathSoFar childFromString:childKey]
withBlock:block];
}];
return block(pathSoFar, self.value, accum);
}
/**
* Find the first matching value on the given path. Return the result of
* applying block to it.
*/
- (id)findOnPath:(FPath *)path
andApplyBlock:(id (^)(FPath *path, id value))block {
return [self findOnPath:path pathSoFar:[FPath empty] andApplyBlock:block];
}
- (id)findOnPath:(FPath *)pathToFollow
pathSoFar:(FPath *)pathSoFar
andApplyBlock:(id (^)(FPath *path, id value))block {
id result = self.value ? block(pathSoFar, self.value) : nil;
if (result != nil) {
return result;
} else {
if ([pathToFollow isEmpty]) {
return nil;
} else {
NSString *front = [pathToFollow getFront];
FImmutableTree *nextChild = [self.children get:front];
if (nextChild != nil) {
return [nextChild findOnPath:[pathToFollow popFront]
pathSoFar:[pathSoFar childFromString:front]
andApplyBlock:block];
} else {
return nil;
}
}
}
}
/**
* Call the block on each value along the path for as long as that function
* returns true
* @return The path to the deepest location inspected
*/
- (FPath *)forEachOnPath:(FPath *)path whileBlock:(BOOL (^)(FPath *, id))block {
return [self forEachOnPath:path pathSoFar:[FPath empty] whileBlock:block];
}
- (FPath *)forEachOnPath:(FPath *)pathToFollow
pathSoFar:(FPath *)pathSoFar
whileBlock:(BOOL (^)(FPath *, id))block {
if ([pathToFollow isEmpty]) {
if (self.value) {
block(pathSoFar, self.value);
}
return pathSoFar;
} else {
BOOL shouldContinue = YES;
if (self.value) {
shouldContinue = block(pathSoFar, self.value);
}
if (shouldContinue) {
NSString *front = [pathToFollow getFront];
FImmutableTree *nextChild = [self.children get:front];
if (nextChild) {
return
[nextChild forEachOnPath:[pathToFollow popFront]
pathSoFar:[pathSoFar childFromString:front]
whileBlock:block];
} else {
return pathSoFar;
}
} else {
return pathSoFar;
}
}
}
- (FImmutableTree *)forEachOnPath:(FPath *)path
performBlock:(void (^)(FPath *path, id value))block {
return [self forEachOnPath:path pathSoFar:[FPath empty] performBlock:block];
}
- (FImmutableTree *)forEachOnPath:(FPath *)pathToFollow
pathSoFar:(FPath *)pathSoFar
performBlock:(void (^)(FPath *path, id value))block {
if ([pathToFollow isEmpty]) {
return self;
} else {
if (self.value) {
block(pathSoFar, self.value);
}
NSString *front = [pathToFollow getFront];
FImmutableTree *nextChild = [self.children get:front];
if (nextChild) {
return [nextChild forEachOnPath:[pathToFollow popFront]
pathSoFar:[pathSoFar childFromString:front]
performBlock:block];
} else {
return [FImmutableTree empty];
}
}
}
/**
* Calls the given block for each node in the tree that has a value. Called in
* depth-first order
*/
- (void)forEach:(void (^)(FPath *path, id value))block {
[self forEachPathSoFar:[FPath empty] withBlock:block];
}
- (void)forEachPathSoFar:(FPath *)pathSoFar
withBlock:(void (^)(FPath *path, id value))block {
[self.children
enumerateKeysAndObjectsUsingBlock:^(
NSString *childKey, FImmutableTree *childTree, BOOL *stop) {
[childTree forEachPathSoFar:[pathSoFar childFromString:childKey]
withBlock:block];
}];
if (self.value) {
block(pathSoFar, self.value);
}
}
- (void)forEachChild:(void (^)(NSString *childKey, id childValue))block {
[self.children
enumerateKeysAndObjectsUsingBlock:^(
NSString *childKey, FImmutableTree *childTree, BOOL *stop) {
if (childTree.value) {
block(childKey, childTree.value);
}
}];
}
- (BOOL)isEqual:(id)object {
if (![object isKindOfClass:[FImmutableTree class]]) {
return NO;
}
FImmutableTree *other = (FImmutableTree *)object;
return (self.value == other.value || [self.value isEqual:other.value]) &&
[self.children isEqual:other.children];
}
- (NSUInteger)hash {
return self.children.hash * 31 + [self.value hash];
}
- (NSString *)description {
NSMutableString *string = [[NSMutableString alloc] init];
[string appendString:@"FImmutableTree { value="];
[string appendString:(self.value ? [self.value description] : @"<nil>")];
[string appendString:@", children={"];
[self.children
enumerateKeysAndObjectsUsingBlock:^(
NSString *childKey, FImmutableTree *childTree, BOOL *stop) {
[string appendString:@" "];
[string appendString:childKey];
[string appendString:@"="];
[string appendString:[childTree.value description]];
}];
[string appendString:@" } }"];
return [NSString stringWithString:string];
}
- (NSString *)debugDescription {
return [self description];
}
@end
@@ -0,0 +1,46 @@
/*
* 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 <Foundation/Foundation.h>
@interface FPath : NSObject <NSCopying>
+ (FPath *)relativePathFrom:(FPath *)outer to:(FPath *)inner;
+ (FPath *)empty;
+ (FPath *)pathWithString:(NSString *)string;
- (id)initWith:(NSString *)path;
- (id)initWithPieces:(NSArray *)somePieces andPieceNum:(NSInteger)aPieceNum;
- (id)copyWithZone:(NSZone *)zone;
- (void)enumerateComponentsUsingBlock:(void (^)(NSString *key,
BOOL *stop))block;
- (NSString *)getFront;
- (NSUInteger)length;
- (FPath *)popFront;
- (NSString *)getBack;
- (NSString *)toString;
- (NSString *)toStringWithTrailingSlash;
- (NSString *)wireFormat;
- (FPath *)parent;
- (FPath *)child:(FPath *)childPathObj;
- (FPath *)childFromString:(NSString *)childPath;
- (BOOL)isEmpty;
- (BOOL)contains:(FPath *)other;
- (NSComparisonResult)compare:(FPath *)other;
@end
@@ -0,0 +1,304 @@
/*
* 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 "FirebaseDatabase/Sources/Core/Utilities/FPath.h"
#import "FirebaseDatabase/Sources/Utilities/FUtilities.h"
@interface FPath ()
@property(nonatomic, readwrite, assign) NSInteger pieceNum;
@property(nonatomic, strong) NSArray *pieces;
@end
@implementation FPath
#pragma mark -
#pragma mark Initializers
+ (FPath *)relativePathFrom:(FPath *)outer to:(FPath *)inner {
NSString *outerFront = [outer getFront];
NSString *innerFront = [inner getFront];
if (outerFront == nil) {
return inner;
} else if ([outerFront isEqualToString:innerFront]) {
return [self relativePathFrom:[outer popFront] to:[inner popFront]];
} else {
@throw [[NSException alloc]
initWithName:@"FirebaseDatabaseInternalError"
reason:[NSString
stringWithFormat:
@"innerPath (%@) is not within outerPath (%@)",
inner, outer]
userInfo:nil];
}
}
+ (FPath *)pathWithString:(NSString *)string {
return [[FPath alloc] initWith:string];
}
- (id)initWith:(NSString *)path {
self = [super init];
if (self) {
NSArray *pathPieces = [path componentsSeparatedByString:@"/"];
NSMutableArray *newPieces = [[NSMutableArray alloc] init];
for (NSInteger i = 0; i < pathPieces.count; i++) {
NSString *piece = [pathPieces objectAtIndex:i];
if (piece.length > 0) {
[newPieces addObject:piece];
}
}
self.pieces = newPieces;
self.pieceNum = 0;
}
return self;
}
- (id)initWithPieces:(NSArray *)somePieces andPieceNum:(NSInteger)aPieceNum {
self = [super init];
if (self) {
self.pieceNum = aPieceNum;
self.pieces = somePieces;
}
return self;
}
- (id)copyWithZone:(NSZone *)zone {
// Immutable, so it's safe to return self
return self;
}
- (NSString *)description {
return [self toString];
}
#pragma mark -
#pragma mark Public methods
- (NSString *)getFront {
if (self.pieceNum >= self.pieces.count) {
return nil;
}
return [self.pieces objectAtIndex:self.pieceNum];
}
/**
* @return The number of segments in this path
*/
- (NSUInteger)length {
return self.pieces.count - self.pieceNum;
}
- (FPath *)popFront {
NSInteger newPieceNum = self.pieceNum;
if (newPieceNum < self.pieces.count) {
newPieceNum++;
}
return [[FPath alloc] initWithPieces:self.pieces andPieceNum:newPieceNum];
}
- (NSString *)getBack {
if (self.pieceNum < self.pieces.count) {
return [self.pieces lastObject];
} else {
return nil;
}
}
- (NSString *)toString {
return [self toStringWithTrailingSlash:NO];
}
- (NSString *)toStringWithTrailingSlash {
return [self toStringWithTrailingSlash:YES];
}
- (NSString *)toStringWithTrailingSlash:(BOOL)trailingSlash {
NSMutableString *pathString = [[NSMutableString alloc] init];
for (NSInteger i = self.pieceNum; i < self.pieces.count; i++) {
[pathString appendString:@"/"];
[pathString appendString:[self.pieces objectAtIndex:i]];
}
if ([pathString length] == 0) {
return @"/";
} else {
if (trailingSlash) {
[pathString appendString:@"/"];
}
return pathString;
}
}
- (NSString *)wireFormat {
if ([self isEmpty]) {
return @"/";
} else {
NSMutableString *pathString = [[NSMutableString alloc] init];
for (NSInteger i = self.pieceNum; i < self.pieces.count; i++) {
if (i > self.pieceNum) {
[pathString appendString:@"/"];
}
[pathString appendString:[self.pieces objectAtIndex:i]];
}
return pathString;
}
}
- (FPath *)parent {
if (self.pieceNum >= self.pieces.count) {
return nil;
} else {
NSMutableArray *newPieces = [[NSMutableArray alloc] init];
for (NSInteger i = self.pieceNum; i < self.pieces.count - 1; i++) {
[newPieces addObject:[self.pieces objectAtIndex:i]];
}
return [[FPath alloc] initWithPieces:newPieces andPieceNum:0];
}
}
- (FPath *)child:(FPath *)childPathObj {
NSMutableArray *newPieces = [[NSMutableArray alloc] init];
for (NSInteger i = self.pieceNum; i < self.pieces.count; i++) {
[newPieces addObject:[self.pieces objectAtIndex:i]];
}
for (NSInteger i = childPathObj.pieceNum; i < childPathObj.pieces.count;
i++) {
[newPieces addObject:[childPathObj.pieces objectAtIndex:i]];
}
return [[FPath alloc] initWithPieces:newPieces andPieceNum:0];
}
- (FPath *)childFromString:(NSString *)childPath {
NSMutableArray *newPieces = [[NSMutableArray alloc] init];
for (NSInteger i = self.pieceNum; i < self.pieces.count; i++) {
[newPieces addObject:[self.pieces objectAtIndex:i]];
}
NSArray *pathPieces = [childPath componentsSeparatedByString:@"/"];
for (unsigned int i = 0; i < pathPieces.count; i++) {
NSString *piece = [pathPieces objectAtIndex:i];
if (piece.length > 0) {
[newPieces addObject:piece];
}
}
return [[FPath alloc] initWithPieces:newPieces andPieceNum:0];
}
/**
* @return True if there are no segments in this path
*/
- (BOOL)isEmpty {
return self.pieceNum >= self.pieces.count;
}
/**
* @return Singleton to represent an empty path
*/
+ (FPath *)empty {
static dispatch_once_t oneEmptyPath;
static FPath *emptyPath;
dispatch_once(&oneEmptyPath, ^{
emptyPath = [[FPath alloc] initWith:@""];
});
return emptyPath;
}
- (BOOL)contains:(FPath *)other {
if (self.length > other.length) {
return NO;
}
NSInteger i = self.pieceNum;
NSInteger j = other.pieceNum;
while (i < self.pieces.count) {
NSString *thisSeg = [self.pieces objectAtIndex:i];
NSString *otherSeg = [other.pieces objectAtIndex:j];
if (![thisSeg isEqualToString:otherSeg]) {
return NO;
}
++i;
++j;
}
return YES;
}
- (void)enumerateComponentsUsingBlock:(void (^)(NSString *, BOOL *))block {
BOOL stop = NO;
for (NSInteger i = self.pieceNum; !stop && i < self.pieces.count; i++) {
block(self.pieces[i], &stop);
}
}
- (NSComparisonResult)compare:(FPath *)other {
NSInteger myCount = self.pieces.count;
NSInteger otherCount = other.pieces.count;
for (NSInteger i = self.pieceNum, j = other.pieceNum;
i < myCount && j < otherCount; i++, j++) {
NSComparisonResult comparison = [FUtilities compareKey:self.pieces[i]
toKey:other.pieces[j]];
if (comparison != NSOrderedSame) {
return comparison;
}
}
if (self.length < other.length) {
return NSOrderedAscending;
} else if (other.length < self.length) {
return NSOrderedDescending;
} else {
NSAssert(self.length == other.length,
@"Paths must be the same lengths");
return NSOrderedSame;
}
}
/**
* @return YES if paths are the same
*/
- (BOOL)isEqual:(id)other {
if (other == self) {
return YES;
}
if (!other || ![other isKindOfClass:[self class]]) {
return NO;
}
FPath *otherPath = (FPath *)other;
if (self.length != otherPath.length) {
return NO;
}
for (NSUInteger i = self.pieceNum, j = otherPath.pieceNum;
i < self.pieces.count; i++, j++) {
if (![self.pieces[i] isEqualToString:otherPath.pieces[j]]) {
return NO;
}
}
return YES;
}
- (NSUInteger)hash {
NSUInteger hashCode = 0;
for (NSInteger i = self.pieceNum; i < self.pieces.count; i++) {
hashCode = hashCode * 37 + [self.pieces[i] hash];
}
return hashCode;
}
@end
@@ -0,0 +1,52 @@
/*
* 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 "FirebaseDatabase/Sources/Core/Utilities/FPath.h"
#import "FirebaseDatabase/Sources/Core/Utilities/FTreeNode.h"
#import <Foundation/Foundation.h>
@interface FTree : NSObject
- (id)init;
- (id)initWithName:(NSString *)aName
withParent:(FTree *)aParent
withNode:(FTreeNode *)aNode;
- (FTree *)subTree:(FPath *)path;
- (id)getValue;
- (void)setValue:(id)value;
- (void)clear;
- (BOOL)hasChildren;
- (BOOL)isEmpty;
- (void)forEachChildMutationSafe:(void (^)(FTree *))action;
- (void)forEachChild:(void (^)(FTree *))action;
- (void)forEachDescendant:(void (^)(FTree *))action;
- (void)forEachDescendant:(void (^)(FTree *))action
includeSelf:(BOOL)incSelf
childrenFirst:(BOOL)childFirst;
- (BOOL)forEachAncestor:(BOOL (^)(FTree *))action;
- (BOOL)forEachAncestor:(BOOL (^)(FTree *))action includeSelf:(BOOL)incSelf;
- (void)forEachImmediateDescendantWithValue:(void (^)(FTree *))action;
- (BOOL)valueExistsAtOrAbove:(FPath *)path;
- (FPath *)path;
- (void)updateParents;
- (void)updateChild:(NSString *)childName withNode:(FTree *)child;
@property(nonatomic, strong) NSString *name;
@property(nonatomic, strong) FTree *parent;
@property(nonatomic, strong) FTreeNode *node;
@end
@@ -0,0 +1,193 @@
/*
* 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 "FirebaseDatabase/Sources/Core/Utilities/FTree.h"
#import "FirebaseDatabase/Sources/Core/Utilities/FPath.h"
#import "FirebaseDatabase/Sources/Core/Utilities/FTreeNode.h"
#import "FirebaseDatabase/Sources/Utilities/FUtilities.h"
@implementation FTree
@synthesize name;
@synthesize parent;
@synthesize node;
- (id)init {
self = [super init];
if (self) {
self.name = @"";
self.parent = nil;
self.node = [[FTreeNode alloc] init];
}
return self;
}
- (id)initWithName:(NSString *)aName
withParent:(FTree *)aParent
withNode:(FTreeNode *)aNode {
self = [super init];
if (self) {
self.name = aName != nil ? aName : @"";
self.parent = aParent != nil ? aParent : nil;
self.node = aNode != nil ? aNode : [[FTreeNode alloc] init];
}
return self;
}
- (FTree *)subTree:(FPath *)path {
FTree *child = self;
NSString *next = [path getFront];
while (next != nil) {
FTreeNode *childNode = child.node.children[next];
if (childNode == nil) {
childNode = [[FTreeNode alloc] init];
}
child = [[FTree alloc] initWithName:next
withParent:child
withNode:childNode];
path = [path popFront];
next = [path getFront];
}
return child;
}
- (id)getValue {
return self.node.value;
}
- (void)setValue:(id)value {
self.node.value = value;
[self updateParents];
}
- (void)clear {
self.node.value = nil;
[self.node.children removeAllObjects];
self.node.childCount = 0;
[self updateParents];
}
- (BOOL)hasChildren {
return self.node.childCount > 0;
}
- (BOOL)isEmpty {
return [self getValue] == nil && ![self hasChildren];
}
- (void)forEachChild:(void (^)(FTree *))action {
for (NSString *key in self.node.children) {
action([[FTree alloc]
initWithName:key
withParent:self
withNode:[self.node.children objectForKey:key]]);
}
}
- (void)forEachChildMutationSafe:(void (^)(FTree *))action {
for (NSString *key in [self.node.children copy]) {
action([[FTree alloc]
initWithName:key
withParent:self
withNode:[self.node.children objectForKey:key]]);
}
}
- (void)forEachDescendant:(void (^)(FTree *))action {
[self forEachDescendant:action includeSelf:NO childrenFirst:NO];
}
- (void)forEachDescendant:(void (^)(FTree *))action
includeSelf:(BOOL)incSelf
childrenFirst:(BOOL)childFirst {
if (incSelf && !childFirst) {
action(self);
}
[self forEachChild:^(FTree *child) {
[child forEachDescendant:action includeSelf:YES childrenFirst:childFirst];
}];
if (incSelf && childFirst) {
action(self);
}
}
- (BOOL)forEachAncestor:(BOOL (^)(FTree *))action {
return [self forEachAncestor:action includeSelf:NO];
}
- (BOOL)forEachAncestor:(BOOL (^)(FTree *))action includeSelf:(BOOL)incSelf {
FTree *aNode = (incSelf) ? self : self.parent;
while (aNode != nil) {
if (action(aNode)) {
return YES;
}
aNode = aNode.parent;
}
return NO;
}
- (void)forEachImmediateDescendantWithValue:(void (^)(FTree *))action {
[self forEachChild:^(FTree *child) {
if ([child getValue] != nil) {
action(child);
} else {
[child forEachImmediateDescendantWithValue:action];
}
}];
}
- (BOOL)valueExistsAtOrAbove:(FPath *)path {
FTreeNode *aNode = self.node;
while (aNode != nil) {
if (aNode.value != nil) {
return YES;
}
aNode = [aNode.children objectForKey:path.getFront];
path = [path popFront];
}
// XXX Check with Michael if this is correct; deviates from JS.
return NO;
}
- (FPath *)path {
return [[FPath alloc]
initWith:(self.parent == nil)
? self.name
: [NSString stringWithFormat:@"%@/%@", [self.parent path],
self.name]];
}
- (void)updateParents {
[self.parent updateChild:self.name withNode:self];
}
- (void)updateChild:(NSString *)childName withNode:(FTree *)child {
BOOL childEmpty = [child isEmpty];
BOOL childExists = self.node.children[childName] != nil;
if (childEmpty && childExists) {
[self.node.children removeObjectForKey:childName];
self.node.childCount = self.node.childCount - 1;
[self updateParents];
} else if (!childEmpty && !childExists) {
[self.node.children setObject:child.node forKey:childName];
self.node.childCount = self.node.childCount + 1;
[self updateParents];
}
}
@end
@@ -0,0 +1,25 @@
/*
* 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 <Foundation/Foundation.h>
@interface FTreeNode : NSObject
@property(nonatomic, strong) NSMutableDictionary *children;
@property(nonatomic, readwrite, assign) int childCount;
@property(nonatomic, strong) id value;
@end
@@ -0,0 +1,35 @@
/*
* 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 "FirebaseDatabase/Sources/Core/Utilities/FTreeNode.h"
@implementation FTreeNode
@synthesize children;
@synthesize childCount;
@synthesize value;
- (id)init {
self = [super init];
if (self) {
self.children = [[NSMutableDictionary alloc] init];
self.childCount = 0;
self.value = nil;
}
return self;
}
@end
@@ -0,0 +1,46 @@
/*
* 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 <Foundation/Foundation.h>
@protocol FNode;
@class FIndexedNode;
@class FPath;
/**
* A cache node only stores complete children. Additionally it holds a flag
* whether the node can be considered fully initialized in the sense that we
* know at one point in time, this represented a valid state of the world, e.g.
* initialized with data from the server, or a complete overwrite by the client.
* It is not necessarily complete because it may have been from a tagged query.
* The filtered flag also tracks whether a node potentially had children removed
* due to a filter.
*/
@interface FCacheNode : NSObject
- (id)initWithIndexedNode:(FIndexedNode *)indexedNode
isFullyInitialized:(BOOL)fullyInitialized
isFiltered:(BOOL)filtered;
- (BOOL)isCompleteForPath:(FPath *)path;
- (BOOL)isCompleteForChild:(NSString *)childKey;
@property(nonatomic, readonly) BOOL isFullyInitialized;
@property(nonatomic, readonly) BOOL isFiltered;
@property(nonatomic, strong, readonly) FIndexedNode *indexedNode;
@property(nonatomic, strong, readonly) id<FNode> node;
@end
@@ -0,0 +1,60 @@
/*
* 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 "FirebaseDatabase/Sources/Core/View/FCacheNode.h"
#import "FirebaseDatabase/Sources/Core/Utilities/FPath.h"
#import "FirebaseDatabase/Sources/Snapshot/FEmptyNode.h"
#import "FirebaseDatabase/Sources/Snapshot/FIndexedNode.h"
#import "FirebaseDatabase/Sources/Snapshot/FNode.h"
@interface FCacheNode ()
@property(nonatomic, readwrite) BOOL isFullyInitialized;
@property(nonatomic, readwrite) BOOL isFiltered;
@property(nonatomic, strong, readwrite) FIndexedNode *indexedNode;
@end
@implementation FCacheNode
- (id)initWithIndexedNode:(FIndexedNode *)indexedNode
isFullyInitialized:(BOOL)fullyInitialized
isFiltered:(BOOL)filtered {
self = [super init];
if (self) {
self.indexedNode = indexedNode;
self.isFullyInitialized = fullyInitialized;
self.isFiltered = filtered;
}
return self;
}
- (BOOL)isCompleteForPath:(FPath *)path {
if (path.isEmpty) {
return self.isFullyInitialized && !self.isFiltered;
} else {
NSString *childKey = [path getFront];
return [self isCompleteForChild:childKey];
}
}
- (BOOL)isCompleteForChild:(NSString *)childKey {
return (self.isFullyInitialized && !self.isFiltered) ||
[self.node hasChild:childKey];
}
- (id<FNode>)node {
return self.indexedNode.node;
}
@end
@@ -0,0 +1,31 @@
/*
* 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 "FirebaseDatabase/Sources/Core/View/FEvent.h"
#import <Foundation/Foundation.h>
@protocol FEventRegistration;
@interface FCancelEvent : NSObject <FEvent>
- initWithEventRegistration:(id<FEventRegistration>)eventRegistration
error:(NSError *)error
path:(FPath *)path;
@property(nonatomic, strong, readonly) NSError *error;
@property(nonatomic, strong, readonly) FPath *path;
@end
@@ -0,0 +1,57 @@
/*
* 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 "FirebaseDatabase/Sources/Core/View/FCancelEvent.h"
#import "FirebaseDatabase/Sources/Core/Utilities/FPath.h"
#import "FirebaseDatabase/Sources/Core/View/FEventRegistration.h"
@interface FCancelEvent ()
@property(nonatomic, strong) id<FEventRegistration> eventRegistration;
@property(nonatomic, strong, readwrite) NSError *error;
@property(nonatomic, strong, readwrite) FPath *path;
@end
@implementation FCancelEvent
@synthesize eventRegistration;
@synthesize error;
@synthesize path;
- (id)initWithEventRegistration:(id<FEventRegistration>)registration
error:(NSError *)anError
path:(FPath *)aPath {
self = [super init];
if (self) {
self.eventRegistration = registration;
self.error = anError;
self.path = aPath;
}
return self;
}
- (void)fireEventOnQueue:(dispatch_queue_t)queue {
[self.eventRegistration fireEvent:self queue:queue];
}
- (BOOL)isCancelEvent {
return YES;
}
- (NSString *)description {
return [NSString stringWithFormat:@"%@: cancel", self.path];
}
@end
@@ -0,0 +1,41 @@
/*
* 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 "FirebaseDatabase/Sources/Public/FirebaseDatabase/FIRDatabaseReference.h"
#import "FirebaseDatabase/Sources/Snapshot/FIndexedNode.h"
#import "FirebaseDatabase/Sources/Snapshot/FNode.h"
#import <Foundation/Foundation.h>
@interface FChange : NSObject
@property(nonatomic, readonly) FIRDataEventType type;
@property(nonatomic, strong, readonly) FIndexedNode *indexedNode;
@property(nonatomic, strong, readonly) NSString *childKey;
@property(nonatomic, strong, readonly) NSString *prevKey;
@property(nonatomic, strong, readonly) FIndexedNode *oldIndexedNode;
- (id)initWithType:(FIRDataEventType)type
indexedNode:(FIndexedNode *)indexedNode;
- (id)initWithType:(FIRDataEventType)type
indexedNode:(FIndexedNode *)indexedNode
childKey:(NSString *)childKey;
- (id)initWithType:(FIRDataEventType)type
indexedNode:(FIndexedNode *)indexedNode
childKey:(NSString *)childKey
oldIndexedNode:(FIndexedNode *)oldIndexedNode;
- (FChange *)changeWithPrevKey:(NSString *)prevKey;
@end
@@ -0,0 +1,72 @@
/*
* 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 "FirebaseDatabase/Sources/Core/View/FChange.h"
@interface FChange ()
@property(nonatomic, strong, readwrite) NSString *prevKey;
@end
@implementation FChange
- (id)initWithType:(FIRDataEventType)type
indexedNode:(FIndexedNode *)indexedNode {
return [self initWithType:type
indexedNode:indexedNode
childKey:nil
oldIndexedNode:nil];
}
- (id)initWithType:(FIRDataEventType)type
indexedNode:(FIndexedNode *)indexedNode
childKey:(NSString *)childKey {
return [self initWithType:type
indexedNode:indexedNode
childKey:childKey
oldIndexedNode:nil];
}
- (id)initWithType:(FIRDataEventType)type
indexedNode:(FIndexedNode *)indexedNode
childKey:(NSString *)childKey
oldIndexedNode:(FIndexedNode *)oldIndexedNode {
self = [super init];
if (self != nil) {
self->_type = type;
self->_indexedNode = indexedNode;
self->_childKey = childKey;
self->_oldIndexedNode = oldIndexedNode;
}
return self;
}
- (FChange *)changeWithPrevKey:(NSString *)prevKey {
FChange *newChange = [[FChange alloc] initWithType:self.type
indexedNode:self.indexedNode
childKey:self.childKey
oldIndexedNode:self.oldIndexedNode];
newChange.prevKey = prevKey;
return newChange;
}
- (NSString *)description {
return [NSString stringWithFormat:@"event: %d, data: %@", (int)self.type,
[self.indexedNode.node val]];
}
@end
@@ -0,0 +1,37 @@
/*
* 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 "FirebaseDatabase/Sources/Core/View/FEventRegistration.h"
#import "FirebaseDatabase/Sources/Utilities/FTypedefs.h"
#import <Foundation/Foundation.h>
@class FRepo;
@interface FChildEventRegistration : NSObject <FEventRegistration>
- (id)initWithRepo:(FRepo *)repo
handle:(FIRDatabaseHandle)fHandle
callbacks:(NSDictionary *)callbackBlocks
cancelCallback:(fbt_void_nserror)cancelCallbackBlock;
/**
* Maps FIRDataEventType (as NSNumber) to fbt_void_datasnapshot_nsstring
*/
@property(nonatomic, copy, readonly) NSDictionary *callbacks;
@property(nonatomic, copy, readonly) fbt_void_nserror cancelCallback;
@property(nonatomic, readonly) FIRDatabaseHandle handle;
@end
@@ -0,0 +1,112 @@
/*
* 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 "FirebaseDatabase/Sources/Core/View/FChildEventRegistration.h"
#import "FirebaseCore/Sources/Private/FirebaseCoreInternal.h"
#import "FirebaseDatabase/Sources/Api/Private/FIRDataSnapshot_Private.h"
#import "FirebaseDatabase/Sources/Api/Private/FIRDatabaseQuery_Private.h"
#import "FirebaseDatabase/Sources/Core/FQueryParams.h"
#import "FirebaseDatabase/Sources/Core/FQuerySpec.h"
#import "FirebaseDatabase/Sources/Core/View/FCancelEvent.h"
#import "FirebaseDatabase/Sources/Core/View/FDataEvent.h"
@interface FChildEventRegistration ()
@property(nonatomic, strong) FRepo *repo;
@property(nonatomic, copy, readwrite) NSDictionary *callbacks;
@property(nonatomic, copy, readwrite) fbt_void_nserror cancelCallback;
@property(nonatomic, readwrite) FIRDatabaseHandle handle;
@end
@implementation FChildEventRegistration
- (id)initWithRepo:(id)repo
handle:(FIRDatabaseHandle)fHandle
callbacks:(NSDictionary *)callbackBlocks
cancelCallback:(fbt_void_nserror)cancelCallbackBlock {
self = [super init];
if (self) {
self.repo = repo;
self.handle = fHandle;
self.callbacks = callbackBlocks;
self.cancelCallback = cancelCallbackBlock;
}
return self;
}
- (BOOL)responseTo:(FIRDataEventType)eventType {
return self.callbacks != nil &&
[self.callbacks
objectForKey:[NSNumber numberWithInteger:eventType]] != nil;
}
- (FDataEvent *)createEventFrom:(FChange *)change query:(FQuerySpec *)query {
FIRDatabaseReference *ref = [[FIRDatabaseReference alloc]
initWithRepo:self.repo
path:[query.path childFromString:change.childKey]];
FIRDataSnapshot *snapshot =
[[FIRDataSnapshot alloc] initWithRef:ref
indexedNode:change.indexedNode];
FDataEvent *eventData =
[[FDataEvent alloc] initWithEventType:change.type
eventRegistration:self
dataSnapshot:snapshot
prevName:change.prevKey];
return eventData;
}
- (void)fireEvent:(id<FEvent>)event queue:(dispatch_queue_t)queue {
if ([event isCancelEvent]) {
FCancelEvent *cancelEvent = event;
FFLog(@"I-RDB061001", @"Raising cancel value event on %@", event.path);
NSAssert(
self.cancelCallback != nil,
@"Raising a cancel event on a listener with no cancel callback");
dispatch_async(queue, ^{
self.cancelCallback(cancelEvent.error);
});
} else if (self.callbacks != nil) {
FDataEvent *dataEvent = event;
FFLog(@"I-RDB061002", @"Raising event callback (%ld) on %@",
(long)dataEvent.eventType, dataEvent.path);
fbt_void_datasnapshot_nsstring callback = [self.callbacks
objectForKey:[NSNumber numberWithInteger:dataEvent.eventType]];
if (callback != nil) {
dispatch_async(queue, ^{
callback(dataEvent.snapshot, dataEvent.prevName);
});
}
}
}
- (FCancelEvent *)createCancelEventFromError:(NSError *)error
path:(FPath *)path {
if (self.cancelCallback != nil) {
return [[FCancelEvent alloc] initWithEventRegistration:self
error:error
path:path];
} else {
return nil;
}
}
- (BOOL)matches:(id<FEventRegistration>)other {
return self.handle == NSNotFound || other.handle == NSNotFound ||
self.handle == other.handle;
}
@end
@@ -0,0 +1,41 @@
/*
* 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 "FirebaseDatabase/Sources/Core/View/FEvent.h"
#import "FirebaseDatabase/Sources/Public/FirebaseDatabase/FIRDataSnapshot.h"
#import "FirebaseDatabase/Sources/Public/FirebaseDatabase/FIRDatabaseReference.h"
#import "FirebaseDatabase/Sources/Utilities/Tuples/FTupleUserCallback.h"
#import <Foundation/Foundation.h>
@protocol FEventRegistration;
@protocol FIndex;
@interface FDataEvent : NSObject <FEvent>
- initWithEventType:(FIRDataEventType)type
eventRegistration:(id<FEventRegistration>)eventRegistration
dataSnapshot:(FIRDataSnapshot *)dataSnapshot;
- initWithEventType:(FIRDataEventType)type
eventRegistration:(id<FEventRegistration>)eventRegistration
dataSnapshot:(FIRDataSnapshot *)snapshot
prevName:(NSString *)prevName;
@property(nonatomic, strong, readonly) id<FEventRegistration> eventRegistration;
@property(nonatomic, strong, readonly) FIRDataSnapshot *snapshot;
@property(nonatomic, strong, readonly) NSString *prevName;
@property(nonatomic, readonly) FIRDataEventType eventType;
@end
@@ -0,0 +1,83 @@
/*
* 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 "FirebaseDatabase/Sources/Core/View/FDataEvent.h"
#import "FirebaseDatabase/Sources/Api/Private/FIRDatabaseQuery_Private.h"
#import "FirebaseDatabase/Sources/Core/View/FEventRegistration.h"
#import "FirebaseDatabase/Sources/FIndex.h"
@interface FDataEvent ()
@property(nonatomic, strong, readwrite) id<FEventRegistration>
eventRegistration;
@property(nonatomic, strong, readwrite) FIRDataSnapshot *snapshot;
@property(nonatomic, strong, readwrite) NSString *prevName;
@property(nonatomic, readwrite) FIRDataEventType eventType;
@end
@implementation FDataEvent
@synthesize eventRegistration;
@synthesize snapshot;
@synthesize prevName;
@synthesize eventType;
- (id)initWithEventType:(FIRDataEventType)type
eventRegistration:(id<FEventRegistration>)registration
dataSnapshot:(FIRDataSnapshot *)dataSnapshot {
return [self initWithEventType:type
eventRegistration:registration
dataSnapshot:dataSnapshot
prevName:nil];
}
- (id)initWithEventType:(FIRDataEventType)type
eventRegistration:(id<FEventRegistration>)registration
dataSnapshot:(FIRDataSnapshot *)dataSnapshot
prevName:(NSString *)previousName {
self = [super init];
if (self) {
self.eventRegistration = registration;
self.snapshot = dataSnapshot;
self.prevName = previousName;
self.eventType = type;
}
return self;
}
- (FPath *)path {
// Used for logging, so delay calculation
FIRDatabaseReference *ref = self.snapshot.ref;
if (self.eventType == FIRDataEventTypeValue) {
return ref.path;
} else {
return ref.parent.path;
}
}
- (void)fireEventOnQueue:(dispatch_queue_t)queue {
[self.eventRegistration fireEvent:self queue:queue];
}
- (BOOL)isCancelEvent {
return NO;
}
- (NSString *)description {
return [NSString stringWithFormat:@"event %d, data: %@", (int)eventType,
[snapshot value]];
}
@end
@@ -0,0 +1,27 @@
/*
* 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 "FirebaseDatabase/Sources/Public/FirebaseDatabase/FIRDataEventType.h"
#import <Foundation/Foundation.h>
@class FPath;
@protocol FEvent <NSObject>
- (FPath *)path;
- (void)fireEventOnQueue:(dispatch_queue_t)queue;
- (BOOL)isCancelEvent;
- (NSString *)description;
@end
@@ -0,0 +1,36 @@
/*
* 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 "FirebaseDatabase/Sources/Utilities/FTypedefs.h"
@class FPath;
@class FRepo;
@class FIRDatabaseConfig;
/**
* Left as instance methods rather than class methods so that we could
* potentially callback on different queues for different repos. This is
* semi-parallel to JS's FEventQueue
*/
@interface FEventRaiser : NSObject
- (id)initWithQueue:(dispatch_queue_t)queue;
- (void)raiseEvents:(NSArray *)eventDataList;
- (void)raiseCallback:(fbt_void_void)callback;
- (void)raiseCallbacks:(NSArray *)callbackList;
@end
@@ -0,0 +1,74 @@
/*
* 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 "FirebaseDatabase/Sources/Core/View/FEventRaiser.h"
#import "FirebaseDatabase/Sources/Core/FRepo.h"
#import "FirebaseDatabase/Sources/Core/FRepoManager.h"
#import "FirebaseDatabase/Sources/Core/View/FDataEvent.h"
#import "FirebaseDatabase/Sources/Utilities/FTypedefs.h"
#import "FirebaseDatabase/Sources/Utilities/FUtilities.h"
#import "FirebaseDatabase/Sources/Utilities/Tuples/FTupleUserCallback.h"
@interface FEventRaiser ()
@property(nonatomic, strong) dispatch_queue_t queue;
@end
/**
* This class exists for symmetry with other clients, but since events are
* async, we don't need to do the complicated stuff the JS client does to
* preserve event order.
*/
@implementation FEventRaiser
- (id)init {
[NSException raise:NSInternalInconsistencyException
format:@"Can't use default constructor"];
return nil;
}
- (id)initWithQueue:(dispatch_queue_t)queue {
self = [super init];
if (self != nil) {
self->_queue = queue;
}
return self;
}
- (void)raiseEvents:(NSArray *)eventDataList {
for (id<FEvent> event in eventDataList) {
[event fireEventOnQueue:self.queue];
}
}
- (void)raiseCallback:(fbt_void_void)callback {
dispatch_async(self.queue, callback);
}
- (void)raiseCallbacks:(NSArray *)callbackList {
for (fbt_void_void callback in callbackList) {
dispatch_async(self.queue, callback);
}
}
+ (void)raiseCallbacks:(NSArray *)callbackList queue:(dispatch_queue_t)queue {
for (fbt_void_void callback in callbackList) {
dispatch_async(queue, callback);
}
}
@end
@@ -0,0 +1,38 @@
/*
* 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 "FirebaseDatabase/Sources/Core/View/FChange.h"
#import "FirebaseDatabase/Sources/Public/FirebaseDatabase/FIRDataEventType.h"
#import <Foundation/Foundation.h>
@protocol FEvent;
@class FDataEvent;
@class FCancelEvent;
@class FQuerySpec;
@protocol FEventRegistration <NSObject>
- (BOOL)responseTo:(FIRDataEventType)eventType;
- (FDataEvent *)createEventFrom:(FChange *)change query:(FQuerySpec *)query;
- (void)fireEvent:(id<FEvent>)event queue:(dispatch_queue_t)queue;
- (FCancelEvent *)createCancelEventFromError:(NSError *)error
path:(FPath *)path;
/**
* Used to figure out what event registration match the event registration that
* needs to be removed.
*/
- (BOOL)matches:(id<FEventRegistration>)other;
@property(nonatomic, readonly) FIRDatabaseHandle handle;
@end

Some files were not shown because too many files have changed in this diff Show More