diff options
author | Paul Beusterien <paulbeusterien@google.com> | 2017-05-15 12:27:07 -0700 |
---|---|---|
committer | Paul Beusterien <paulbeusterien@google.com> | 2017-05-15 12:27:07 -0700 |
commit | 98ba64449a632518bd2b86fe8d927f4a960d3ddc (patch) | |
tree | 131d9c4272fa6179fcda6c5a33fcb3b1bd57ad2e /Firebase/Database/Api | |
parent | 32461366c9e204a527ca05e6e9b9404a2454ac51 (diff) |
Initial
Diffstat (limited to 'Firebase/Database/Api')
24 files changed, 2423 insertions, 0 deletions
diff --git a/Firebase/Database/Api/FIRDataEventType.h b/Firebase/Database/Api/FIRDataEventType.h new file mode 100644 index 0000000..fccc98a --- /dev/null +++ b/Firebase/Database/Api/FIRDataEventType.h @@ -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. + */ + +#ifndef Firebase_FIRDataEventType_h +#define Firebase_FIRDataEventType_h + +#import <Foundation/Foundation.h> +#import "FIRDatabaseSwiftNameSupport.h" + +/** + * This enum is the set of events that you can observe at a Firebase Database location. + */ +typedef NS_ENUM(NSInteger, FIRDataEventType) { + /// A new child node is added to a location. + FIRDataEventTypeChildAdded, + /// A child node is removed from a location. + FIRDataEventTypeChildRemoved, + /// A child node at a location changes. + FIRDataEventTypeChildChanged, + /// A child node moves relative to the other child nodes at a location. + FIRDataEventTypeChildMoved, + /// Any data changes at a location or, recursively, at any child node. + FIRDataEventTypeValue +} FIR_SWIFT_NAME(DataEventType); + +#endif diff --git a/Firebase/Database/Api/FIRDataSnapshot.h b/Firebase/Database/Api/FIRDataSnapshot.h new file mode 100644 index 0000000..e615260 --- /dev/null +++ b/Firebase/Database/Api/FIRDataSnapshot.h @@ -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 <Foundation/Foundation.h> +#import "FIRDatabaseSwiftNameSupport.h" + +NS_ASSUME_NONNULL_BEGIN + +@class FIRDatabaseReference; + +/** + * A FIRDataSnapshot contains data from a Firebase Database location. Any time you read + * Firebase data, you receive the data as a FIRDataSnapshot. + * + * FIRDataSnapshots are passed to the blocks you attach with observeEventType:withBlock: or observeSingleEvent:withBlock:. + * They are efficiently-generated immutable copies of the data at a Firebase Database location. + * They can't be modified and will never change. To modify data at a location, + * use a FIRDatabaseReference (e.g. with setValue:). + */ +FIR_SWIFT_NAME(DataSnapshot) +@interface FIRDataSnapshot : NSObject + + +#pragma mark - Navigating and inspecting a snapshot + +/** + * Gets a FIRDataSnapshot for the location at the specified relative path. + * The relative path can either be a simple child key (e.g. 'fred') + * or a deeper slash-separated path (e.g. 'fred/name/first'). If the child + * location has no data, an empty FIRDataSnapshot is returned. + * + * @param childPathString A relative path to the location of child data. + * @return The FIRDataSnapshot for the child location. + */ +- (FIRDataSnapshot *)childSnapshotForPath:(NSString *)childPathString; + + +/** + * Return YES if the specified child exists. + * + * @param childPathString A relative path to the location of a potential child. + * @return YES if data exists at the specified childPathString, else NO. + */ +- (BOOL) hasChild:(NSString *)childPathString; + + +/** + * Return YES if the DataSnapshot has any children. + * + * @return YES if this snapshot has any children, else NO. + */ +- (BOOL) hasChildren; + + +/** + * Return YES if the DataSnapshot contains a non-null value. + * + * @return YES if this snapshot contains a non-null value, else NO. + */ +- (BOOL) exists; + + +#pragma mark - Data export + +/** + * Returns the raw value at this location, coupled with any metadata, such as priority. + * + * Priorities, where they exist, are accessible under the ".priority" key in instances of NSDictionary. + * For leaf locations with priorities, the value will be under the ".value" key. + */ +- (id __nullable) valueInExportFormat; + + +#pragma mark - Properties + +/** + * Returns the contents of this data snapshot as native types. + * + * Data types returned: + * + NSDictionary + * + NSArray + * + NSNumber (also includes booleans) + * + NSString + * + * @return The data as a native object. + */ +@property (strong, readonly, nonatomic, nullable) id value; + + +/** + * Gets the number of children for this DataSnapshot. + * + * @return An integer indicating the number of children. + */ +@property (readonly, nonatomic) NSUInteger childrenCount; + + +/** + * Gets a FIRDatabaseReference for the location that this data came from. + * + * @return A FIRDatabaseReference instance for the location of this data. + */ +@property (nonatomic, readonly, strong) FIRDatabaseReference * ref; + + +/** + * The key of the location that generated this FIRDataSnapshot. + * + * @return An NSString containing the key for the location of this FIRDataSnapshot. + */ +@property (strong, readonly, nonatomic) NSString* key; + + +/** + * An iterator for snapshots of the child nodes in this snapshot. + * You can use the native for..in syntax: + * + * for (FIRDataSnapshot* child in snapshot.children) { + * ... + * } + * + * @return An NSEnumerator of the children. + */ +@property (strong, readonly, nonatomic) NSEnumerator* children; + +/** + * The priority of the data in this FIRDataSnapshot. + * + * @return The priority as a string, or nil if no priority was set. + */ +@property (strong, readonly, nonatomic, nullable) id priority; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Firebase/Database/Api/FIRDataSnapshot.m b/Firebase/Database/Api/FIRDataSnapshot.m new file mode 100644 index 0000000..9559c38 --- /dev/null +++ b/Firebase/Database/Api/FIRDataSnapshot.m @@ -0,0 +1,101 @@ +/* + * 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 "FIRDataSnapshot.h" +#import "FIRDataSnapshot_Private.h" +#import "FChildrenNode.h" +#import "FValidation.h" +#import "FTransformedEnumerator.h" +#import "FIRDatabaseReference.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 *) 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 diff --git a/Firebase/Database/Api/FIRDatabase.h b/Firebase/Database/Api/FIRDatabase.h new file mode 100644 index 0000000..e77ed31 --- /dev/null +++ b/Firebase/Database/Api/FIRDatabase.h @@ -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 <Foundation/Foundation.h> +#import "FIRDatabaseReference.h" +#import "FIRDatabaseSwiftNameSupport.h" + +@class FIRApp; + +NS_ASSUME_NONNULL_BEGIN + +/** + * The entry point for accessing a Firebase Database. You can get an instance by calling + * [FIRDatabase database]. To access a location in the database and read or write data, + * use [FIRDatabase reference]. + */ +FIR_SWIFT_NAME(Database) +@interface FIRDatabase : NSObject + +/** + * Gets the instance of FIRDatabase for the default FIRApp. + * + * @return A FIRDatabase instance. + */ ++ (FIRDatabase *) database FIR_SWIFT_NAME(database()); + +/** + * Gets an instance of FIRDatabase for a specific FIRApp. + * + * @param app The FIRApp to get a FIRDatabase for. + * @return A FIRDatabase instance. + */ ++ (FIRDatabase *) databaseForApp:(FIRApp*)app FIR_SWIFT_NAME(database(app:)); + +/** The FIRApp instance to which this FIRDatabase belongs. */ +@property (weak, readonly, nonatomic) FIRApp *app; + +/** + * Gets a FIRDatabaseReference for the root of your Firebase Database. + */ +- (FIRDatabaseReference *) reference; + +/** + * Gets a FIRDatabaseReference for the provided path. + * + * @param path Path to a location in your Firebase Database. + * @return A FIRDatabaseReference pointing to the specified path. + */ +- (FIRDatabaseReference *) referenceWithPath:(NSString *)path; + +/** + * Gets a FIRDatabaseReference for the provided URL. The URL must be a URL to a path + * within this Firebase Database. To create a FIRDatabaseReference to a different database, + * create a FIRApp} with a FIROptions object configured with the appropriate database URL. + * + * @param databaseUrl A URL to a path within your database. + * @return A FIRDatabaseReference for the provided URL. +*/ +- (FIRDatabaseReference *) referenceFromURL:(NSString *)databaseUrl; + +/** + * The Firebase Database client automatically queues writes and sends them to the server at the earliest opportunity, + * depending on network connectivity. In some cases (e.g. offline usage) there may be a large number of writes + * waiting to be sent. Calling this method will purge all outstanding writes so they are abandoned. + * + * All writes will be purged, including transactions and onDisconnect writes. The writes will + * be rolled back locally, perhaps triggering events for affected event listeners, and the client will not + * (re-)send them to the Firebase Database backend. + */ +- (void)purgeOutstandingWrites; + +/** + * Shuts down our connection to the Firebase Database backend until goOnline is called. + */ +- (void)goOffline; + +/** + * Resumes our connection to the Firebase Database backend after a previous goOffline call. + */ +- (void)goOnline; + +/** + * The Firebase Database client will cache synchronized data and keep track of all writes you've + * initiated while your application is running. It seamlessly handles intermittent network + * connections and re-sends write operations when the network connection is restored. + * + * However by default your write operations and cached data are only stored in-memory and will + * be lost when your app restarts. 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 Database reference and only needs to be called once per + * application. + * + */ +@property (nonatomic) BOOL persistenceEnabled FIR_SWIFT_NAME(isPersistenceEnabled); + +/** + * 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. Cache sizes smaller than 1 MB or greater than 100 MB are not supported. + */ +@property (nonatomic) NSUInteger persistenceCacheSizeBytes; + +/** + * Sets the dispatch queue on which all events are raised. The default queue is the main queue. + * + * Note that this must be set before creating your first Database reference. + */ +@property (nonatomic, strong) dispatch_queue_t callbackQueue; + +/** + * Enables verbose diagnostic logging. + * + * @param enabled YES to enable logging, NO to disable. + */ ++ (void) setLoggingEnabled:(BOOL)enabled; + +/** Retrieve the Firebase Database SDK version. */ ++ (NSString *) sdkVersion; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Firebase/Database/Api/FIRDatabase.m b/Firebase/Database/Api/FIRDatabase.m new file mode 100644 index 0000000..124b463 --- /dev/null +++ b/Firebase/Database/Api/FIRDatabase.m @@ -0,0 +1,268 @@ +/* + * 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 "FIRDatabase.h" +#import "FIRDatabase_Private.h" +#import "FIRDatabaseQuery_Private.h" +#import "FRepoManager.h" +#import "FValidation.h" +#import "FIRDatabaseConfig_Private.h" +#import "FRepoInfo.h" +#import "FIRDatabaseConfig.h" +#import "FIRDatabaseReference_Private.h" + +/** + * This is a hack that defines all the methods we need from FIRApp/Options. At runtime we use reflection to get the + * default FIRApp instance if we need it. Since protocols don't carry any runtime information and selectors + * are invoked by name we can write code against this protocol as long as the method signatures don't change. + * + * TODO: Consider weak-linking the actual Firebase/Core framework or something. + */ + +extern NSString *const kFIRDefaultAppName; + +@protocol FIROptionsLike <NSObject> +@property(nonatomic, readonly, copy) NSString *databaseURL; +@end + +@protocol FIRAppLike <NSObject> +@property(nonatomic, readonly) id<FIROptionsLike> options; +@property(nonatomic, copy, readonly) NSString *name; +@end + +@interface FIRDatabase () +@property (nonatomic, strong) FRepoInfo *repoInfo; +@property (nonatomic, strong) FIRDatabaseConfig *config; +@property (nonatomic, strong) FRepo *repo; +@end + +@implementation FIRDatabase + +// The STR and STR_EXPAND macro allow a numeric version passed to he compiler driver +// with a -D to be treated as a string instead of an invalid floating point value. +#define STR(x) STR_EXPAND(x) +#define STR_EXPAND(x) #x +static const char *FIREBASE_SEMVER = (const char *)STR(FIRDatabase_VERSION); + +/** + * A static NSMutableDictionary of FirebaseApp names to FirebaseDatabase instance. To ensure thread- + * safety, it should only be accessed in databaseForApp, which is synchronized. + * + * TODO: This serves a duplicate purpose as RepoManager. We should clean up. + * TODO: We should maybe be conscious of leaks and make this a weak map or similar + * but we have a lot of work to do to allow FirebaseDatabase/Repo etc. to be GC'd. + */ ++ (NSMutableDictionary *)instances { + static dispatch_once_t pred = 0; + static NSMutableDictionary *instances; + dispatch_once(&pred, ^{ + instances = [NSMutableDictionary dictionary]; + }); + return instances; +} + ++ (FIRDatabase *)database { + id<FIRAppLike> app = [FIRDatabase getDefaultApp]; + if (app == nil) { + [NSException raise:@"FIRAppNotConfigured" format:@"Failed to get default FIRDatabase instance. Must call FIRApp.configure() before using FIRDatabase."]; + } + return [FIRDatabase databaseForApp:(FIRApp*)app]; +} + ++ (FIRDatabase *)databaseForApp:(id)app { + if (app == nil) { + [NSException raise:@"InvalidFIRApp" format:@"nil FIRApp instance passed to databaseForApp."]; + } + NSMutableDictionary *instances = [self instances]; + @synchronized (instances) { + id<FIRAppLike> appLike = (id<FIRAppLike>)app; + FIRDatabase *database = instances[appLike.name]; + if (!database) { + NSString *databaseUrl = appLike.options.databaseURL; + if (databaseUrl == nil) { + [NSException raise:@"MissingDatabaseURL" format:@"Failed to get FIRDatabase instance: FIRApp object has no " + "databaseURL in its FirebaseOptions object."]; + } + + FParsedUrl *parsedUrl = [FUtilities parseUrl:databaseUrl]; + if (![parsedUrl.path isEmpty]) { + [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, [parsedUrl.path toString]]; + } + + id<FAuthTokenProvider> authTokenProvider = [FAuthTokenProvider authTokenProviderForApp:appLike]; + + // 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 (![appLike.name isEqualToString:kFIRDefaultAppName]) { + sessionIdentifier = appLike.name; + } + + FIRDatabaseConfig *config = [[FIRDatabaseConfig alloc] initWithSessionIdentifier:sessionIdentifier + authTokenProvider:authTokenProvider]; + database = [[FIRDatabase alloc] initWithApp:appLike repoInfo:parsedUrl.repoInfo config:config]; + instances[appLike.name] = database; + } + + return database; + } +} + ++ (NSString *) buildVersion { + // TODO: Restore git hash when build moves back to git + return [NSString stringWithFormat:@"%s_%s", FIREBASE_SEMVER, __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 [NSString stringWithUTF8String:FIREBASE_SEMVER]; +} + ++ (void) setLoggingEnabled:(BOOL)enabled { + [FUtilities setLoggingEnabled:enabled]; + FFLog(@"I-RDB024001", @"BUILD Version: %@", [FIRDatabase buildVersion]); +} + + +- (id)initWithApp:(id <FIRAppLike>)appLike repoInfo:(FRepoInfo *)info config:(FIRDatabaseConfig *)config { + self = [super init]; + if (self != nil) { + self->_repoInfo = info; + self->_config = config; + self->_app = (FIRApp*) appLike; + } + 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]; + if (![parsedUrl.repoInfo.host isEqualToString:_repoInfo.host]) { + [NSException raise:@"InvalidDatabaseURL" format:@"Invalid URL (%@) passed to getReference(). URL was expected " + "to match configured Database URL: %@", databaseUrl, [self reference].URL]; + } + return [[FIRDatabaseReference alloc] initWithRepo:self.repo path:parsedUrl.path]; +} + + +- (void)purgeOutstandingWrites { + [self ensureRepo]; + + dispatch_async([FIRDatabaseQuery sharedQueue], ^{ + [self.repo purgeOutstandingWrites]; + }); +} + +- (void)goOnline { + [self ensureRepo]; + + dispatch_async([FIRDatabaseQuery sharedQueue], ^{ + [self.repo resume]; + }); +} + + +- (void)goOffline { + [self ensureRepo]; + + dispatch_async([FIRDatabaseQuery sharedQueue], ^{ + [self.repo interrupt]; + }); +} + ++ (id<FIRAppLike>) getDefaultApp { + Class appClass = NSClassFromString(@"FIRApp"); + if (appClass == nil) { + [NSException raise:@"FailedToFindFIRApp" format:@"Failed to find FIRApp class."]; + return nil; + } else { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wundeclared-selector" + return [appClass performSelector:@selector(defaultApp)]; +#pragma clang diagnostic pop + } +} + +- (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 diff --git a/Firebase/Database/Api/FIRDatabaseConfig.h b/Firebase/Database/Api/FIRDatabaseConfig.h new file mode 100644 index 0000000..d41f3a8 --- /dev/null +++ b/Firebase/Database/Api/FIRDatabaseConfig.h @@ -0,0 +1,63 @@ +/* + * 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 FAuthTokenProvider; + +NS_ASSUME_NONNULL_BEGIN + +/** + * TODO: Merge FIRDatabaseConfig into FIRDatabase. + */ +@interface FIRDatabaseConfig : NSObject + +- (id)initWithSessionIdentifier:(NSString *)identifier authTokenProvider:(id<FAuthTokenProvider>)authTokenProvider; + +/** + * 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 diff --git a/Firebase/Database/Api/FIRDatabaseConfig.m b/Firebase/Database/Api/FIRDatabaseConfig.m new file mode 100644 index 0000000..f4639f9 --- /dev/null +++ b/Firebase/Database/Api/FIRDatabaseConfig.m @@ -0,0 +1,117 @@ +/* + * 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 "FIRApp.h" +#import "FIRDatabaseConfig.h" +#import "FIRDatabaseConfig_Private.h" +#import "FIRNoopAuthTokenProvider.h" +#import "FAuthTokenProvider.h" + +@interface FIRDatabaseConfig (Private) + +@property (nonatomic, strong, readwrite) NSString *sessionIdentifier; + +@end + +@implementation FIRDatabaseConfig + +- (id)init { + [NSException raise:NSInvalidArgumentException format:@"Can't create config objects!"]; + return nil; +} + +- (id)initWithSessionIdentifier:(NSString *)identifier authTokenProvider:(id<FAuthTokenProvider>)authTokenProvider { + self = [super init]; + if (self != nil) { + self->_sessionIdentifier = identifier; + self->_callbackQueue = dispatch_get_main_queue(); + self->_persistenceCacheSizeBytes = 10*1024*1024; // Default cache size is 10MB + self->_authTokenProvider = authTokenProvider; + } + return self; +} + +- (void)assertUnfrozen { + if (self.isFrozen) { + [NSException raise:NSGenericException format:@"Can't modify config objects after they are in use for FIRDatabaseReferences."]; + } +} + +- (void)setAuthTokenProvider:(id<FAuthTokenProvider>)authTokenProvider { + [self assertUnfrozen]; + self->_authTokenProvider = authTokenProvider; +} + +- (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; +} + +// TODO: Only used for tests. Migrate to FIRDatabase and remove. ++ (FIRDatabaseConfig *)defaultConfig { + static dispatch_once_t onceToken; + static FIRDatabaseConfig *defaultConfig; + dispatch_once(&onceToken, ^{ + defaultConfig = [FIRDatabaseConfig configForName:@"default"]; + }); + return defaultConfig; +} + +// TODO: This is only used for tests. We should fix them to go through FIRDatabase and remove +// this method and the sessionsConfigs dictionary (FIRDatabase automatically creates one config per app). ++ (FIRDatabaseConfig *)configForName:(NSString *)name { + NSRegularExpression *expression = [NSRegularExpression regularExpressionWithPattern:@"^[a-zA-Z0-9-_]+$" options:0 error:nil]; + if ([expression numberOfMatchesInString:name options:0 range:NSMakeRange(0, name.length)] == 0) { + [NSException raise:NSInvalidArgumentException format:@"Name can only contain [a-zA-Z0-9-_]"]; + } + + static dispatch_once_t onceToken; + static NSMutableDictionary *sessionConfigs; + dispatch_once(&onceToken, ^{ + sessionConfigs = [NSMutableDictionary dictionary]; + }); + @synchronized(sessionConfigs) { + if (!sessionConfigs[name]) { + id<FAuthTokenProvider> authTokenProvider = [FAuthTokenProvider authTokenProviderForApp:[FIRApp defaultApp]]; + sessionConfigs[name] = [[FIRDatabaseConfig alloc] initWithSessionIdentifier:name + authTokenProvider:authTokenProvider]; + } + return sessionConfigs[name]; + } +} + +@end diff --git a/Firebase/Database/Api/FIRDatabaseQuery.h b/Firebase/Database/Api/FIRDatabaseQuery.h new file mode 100644 index 0000000..be4ad27 --- /dev/null +++ b/Firebase/Database/Api/FIRDatabaseQuery.h @@ -0,0 +1,315 @@ +/* + * 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 "FIRDatabaseSwiftNameSupport.h" +#import "FIRDataEventType.h" +#import "FIRDataSnapshot.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * A FIRDatabaseHandle is used to identify listeners of Firebase Database events. These handles + * are returned by observeEventType: and and can later be passed to removeObserverWithHandle: to + * stop receiving updates. + */ +typedef NSUInteger FIRDatabaseHandle FIR_SWIFT_NAME(DatabaseHandle); + +/** + * A FIRDatabaseQuery instance represents a query over the data at a particular location. + * + * You create one by calling one of the query methods (queryOrderedByChild:, queryStartingAtValue:, etc.) + * on a FIRDatabaseReference. The query methods can be chained to further specify the data you are interested in + * observing + */ +FIR_SWIFT_NAME(DatabaseQuery) +@interface FIRDatabaseQuery : NSObject + + +#pragma mark - Attach observers to read data + +/** + * observeEventType:withBlock: is used to listen for data changes at a particular location. + * This is the primary way to read data from the Firebase Database. Your block will be triggered + * for the initial data and again whenever the data changes. + * + * Use removeObserverWithHandle: to stop receiving updates. + * + * @param eventType The type of event to listen for. + * @param block The block that should be called with initial data and updates. It is passed the data as a FIRDataSnapshot. + * @return A handle used to unregister this block later using removeObserverWithHandle: + */ +- (FIRDatabaseHandle)observeEventType:(FIRDataEventType)eventType withBlock:(void (^)(FIRDataSnapshot *snapshot))block; + + +/** + * observeEventType:andPreviousSiblingKeyWithBlock: is used to listen for data changes at a particular location. + * This is the primary way to read data from the Firebase Database. Your block will be triggered + * for the initial data and again whenever the data changes. In addition, for FIRDataEventTypeChildAdded, FIRDataEventTypeChildMoved, and + * FIRDataEventTypeChildChanged events, your block will be passed the key of the previous node by priority order. + * + * Use removeObserverWithHandle: to stop receiving updates. + * + * @param eventType The type of event to listen for. + * @param block The block that should be called with initial data and updates. It is passed the data as a FIRDataSnapshot + * and the previous child's key. + * @return A handle used to unregister this block later using removeObserverWithHandle: + */ +- (FIRDatabaseHandle)observeEventType:(FIRDataEventType)eventType andPreviousSiblingKeyWithBlock:(void (^)(FIRDataSnapshot *snapshot, NSString *__nullable prevKey))block; + + +/** + * observeEventType:withBlock: is used to listen for data changes at a particular location. + * This is the primary way to read data from the Firebase Database. Your block will be triggered + * for the initial data and again whenever the data changes. + * + * The cancelBlock will be called if you will no longer receive new events due to no longer having permission. + * + * Use removeObserverWithHandle: to stop receiving updates. + * + * @param eventType The type of event to listen for. + * @param block The block that should be called with initial data and updates. It is passed the data as a FIRDataSnapshot. + * @param cancelBlock The block that should be called if this client no longer has permission to receive these events + * @return A handle used to unregister this block later using removeObserverWithHandle: + */ +- (FIRDatabaseHandle)observeEventType:(FIRDataEventType)eventType withBlock:(void (^)(FIRDataSnapshot *snapshot))block withCancelBlock:(nullable void (^)(NSError* error))cancelBlock; + + +/** + * observeEventType:andPreviousSiblingKeyWithBlock: is used to listen for data changes at a particular location. + * This is the primary way to read data from the Firebase Database. Your block will be triggered + * for the initial data and again whenever the data changes. In addition, for FIRDataEventTypeChildAdded, FIRDataEventTypeChildMoved, and + * FIRDataEventTypeChildChanged events, your block will be passed the key of the previous node by priority order. + * + * The cancelBlock will be called if you will no longer receive new events due to no longer having permission. + * + * Use removeObserverWithHandle: to stop receiving updates. + * + * @param eventType The type of event to listen for. + * @param block The block that should be called with initial data and updates. It is passed the data as a FIRDataSnapshot + * and the previous child's key. + * @param cancelBlock The block that should be called if this client no longer has permission to receive these events + * @return A handle used to unregister this block later using removeObserverWithHandle: + */ +- (FIRDatabaseHandle)observeEventType:(FIRDataEventType)eventType andPreviousSiblingKeyWithBlock:(void (^)(FIRDataSnapshot *snapshot, NSString *__nullable prevKey))block withCancelBlock:(nullable void (^)(NSError* error))cancelBlock; + + +/** + * This is equivalent to observeEventType:withBlock:, except the block is immediately canceled after the initial data is returned. + * + * @param eventType The type of event to listen for. + * @param block The block that should be called. It is passed the data as a FIRDataSnapshot. + */ +- (void)observeSingleEventOfType:(FIRDataEventType)eventType withBlock:(void (^)(FIRDataSnapshot *snapshot))block; + + +/** + * This is equivalent to observeEventType:withBlock:, except the block is immediately canceled after the initial data is returned. In addition, for FIRDataEventTypeChildAdded, FIRDataEventTypeChildMoved, and + * FIRDataEventTypeChildChanged events, your block will be passed the key of the previous node by priority order. + * + * @param eventType The type of event to listen for. + * @param block The block that should be called. It is passed the data as a FIRDataSnapshot and the previous child's key. + */ +- (void)observeSingleEventOfType:(FIRDataEventType)eventType andPreviousSiblingKeyWithBlock:(void (^)(FIRDataSnapshot *snapshot, NSString *__nullable prevKey))block; + + +/** + * This is equivalent to observeEventType:withBlock:, except the block is immediately canceled after the initial data is returned. + * + * The cancelBlock will be called if you do not have permission to read data at this location. + * + * @param eventType The type of event to listen for. + * @param block The block that should be called. It is passed the data as a FIRDataSnapshot. + * @param cancelBlock The block that will be called if you don't have permission to access this data + */ +- (void)observeSingleEventOfType:(FIRDataEventType)eventType withBlock:(void (^)(FIRDataSnapshot *snapshot))block withCancelBlock:(nullable void (^)(NSError* error))cancelBlock; + + +/** + * This is equivalent to observeEventType:withBlock:, except the block is immediately canceled after the initial data is returned. In addition, for FIRDataEventTypeChildAdded, FIRDataEventTypeChildMoved, and + * FIRDataEventTypeChildChanged events, your block will be passed the key of the previous node by priority order. + * + * The cancelBlock will be called if you do not have permission to read data at this location. + * + * @param eventType The type of event to listen for. + * @param block The block that should be called. It is passed the data as a FIRDataSnapshot and the previous child's key. + * @param cancelBlock The block that will be called if you don't have permission to access this data + */ +- (void)observeSingleEventOfType:(FIRDataEventType)eventType andPreviousSiblingKeyWithBlock:(void (^)(FIRDataSnapshot *snapshot, NSString *__nullable prevKey))block withCancelBlock:(nullable void (^)(NSError* error))cancelBlock; + + +#pragma mark - Detaching observers + +/** + * Detach a block previously attached with observeEventType:withBlock:. + * + * @param handle The handle returned by the call to observeEventType:withBlock: which we are trying to remove. + */ +- (void) removeObserverWithHandle:(FIRDatabaseHandle)handle; + + +/** + * Detach all blocks previously attached to this Firebase Database location with observeEventType:withBlock: + */ +- (void) removeAllObservers; + +/** + * By calling `keepSynced:YES` on a location, the data for that location will automatically be downloaded and + * kept in sync, even when no listeners are attached for that location. Additionally, while a location is kept + * synced, it will not be evicted from the persistent disk cache. + * + * @param keepSynced Pass YES to keep this location synchronized, pass NO to stop synchronization. +*/ + - (void) keepSynced:(BOOL)keepSynced; + + +#pragma mark - Querying and limiting + +/** +* queryLimitedToFirst: is used to generate a reference to a limited view of the data at this location. +* The FIRDatabaseQuery instance returned by queryLimitedToFirst: will respond to at most the first limit child nodes. +* +* @param limit The upper bound, inclusive, for the number of child nodes to receive events for +* @return A FIRDatabaseQuery instance, limited to at most limit child nodes. +*/ +- (FIRDatabaseQuery *)queryLimitedToFirst:(NSUInteger)limit; + + +/** +* queryLimitedToLast: is used to generate a reference to a limited view of the data at this location. +* The FIRDatabaseQuery instance returned by queryLimitedToLast: will respond to at most the last limit child nodes. +* +* @param limit The upper bound, inclusive, for the number of child nodes to receive events for +* @return A FIRDatabaseQuery instance, limited to at most limit child nodes. +*/ +- (FIRDatabaseQuery *)queryLimitedToLast:(NSUInteger)limit; + +/** + * queryOrderBy: is used to generate a reference to a view of the data that's been sorted by the values of + * a particular child key. This method is intended to be used in combination with queryStartingAtValue:, + * queryEndingAtValue:, or queryEqualToValue:. + * + * @param key The child key to use in ordering data visible to the returned FIRDatabaseQuery + * @return A FIRDatabaseQuery instance, ordered by the values of the specified child key. +*/ +- (FIRDatabaseQuery *)queryOrderedByChild:(NSString *)key; + +/** + * queryOrderedByKey: is used to generate a reference to a view of the data that's been sorted by child key. + * This method is intended to be used in combination with queryStartingAtValue:, queryEndingAtValue:, + * or queryEqualToValue:. + * + * @return A FIRDatabaseQuery instance, ordered by child keys. + */ +- (FIRDatabaseQuery *) queryOrderedByKey; + +/** + * queryOrderedByValue: is used to generate a reference to a view of the data that's been sorted by child value. + * This method is intended to be used in combination with queryStartingAtValue:, queryEndingAtValue:, + * or queryEqualToValue:. + * + * @return A FIRDatabaseQuery instance, ordered by child value. + */ +- (FIRDatabaseQuery *) queryOrderedByValue; + +/** + * queryOrderedByPriority: is used to generate a reference to a view of the data that's been sorted by child + * priority. This method is intended to be used in combination with queryStartingAtValue:, queryEndingAtValue:, + * or queryEqualToValue:. + * + * @return A FIRDatabaseQuery instance, ordered by child priorities. + */ +- (FIRDatabaseQuery *) queryOrderedByPriority; + +/** + * queryStartingAtValue: is used to generate a reference to a limited view of the data at this location. + * The FIRDatabaseQuery instance returned by queryStartingAtValue: will respond to events at nodes with a value + * greater than or equal to startValue. + * + * @param startValue The lower bound, inclusive, for the value of data visible to the returned FIRDatabaseQuery + * @return A FIRDatabaseQuery instance, limited to data with value greater than or equal to startValue + */ +- (FIRDatabaseQuery *)queryStartingAtValue:(nullable id)startValue; + +/** + * queryStartingAtValue:childKey: is used to generate a reference to a limited view of the data at this location. + * The FIRDatabaseQuery instance returned by queryStartingAtValue:childKey will respond to events at nodes with a value + * greater than startValue, or equal to startValue and with a key greater than or equal to childKey. This is most + * useful when implementing pagination in a case where multiple nodes can match the startValue. + * + * @param startValue The lower bound, inclusive, for the value of data visible to the returned FIRDatabaseQuery + * @param childKey The lower bound, inclusive, for the key of nodes with value equal to startValue + * @return A FIRDatabaseQuery instance, limited to data with value greater than or equal to startValue + */ +- (FIRDatabaseQuery *)queryStartingAtValue:(nullable id)startValue childKey:(nullable NSString *)childKey; + +/** + * queryEndingAtValue: is used to generate a reference to a limited view of the data at this location. + * The FIRDatabaseQuery instance returned by queryEndingAtValue: will respond to events at nodes with a value + * less than or equal to endValue. + * + * @param endValue The upper bound, inclusive, for the value of data visible to the returned FIRDatabaseQuery + * @return A FIRDatabaseQuery instance, limited to data with value less than or equal to endValue + */ +- (FIRDatabaseQuery *)queryEndingAtValue:(nullable id)endValue; + +/** + * queryEndingAtValue:childKey: is used to generate a reference to a limited view of the data at this location. + * The FIRDatabaseQuery instance returned by queryEndingAtValue:childKey will respond to events at nodes with a value + * less than endValue, or equal to endValue and with a key less than or equal to childKey. This is most useful when + * implementing pagination in a case where multiple nodes can match the endValue. + * + * @param endValue The upper bound, inclusive, for the value of data visible to the returned FIRDatabaseQuery + * @param childKey The upper bound, inclusive, for the key of nodes with value equal to endValue + * @return A FIRDatabaseQuery instance, limited to data with value less than or equal to endValue + */ +- (FIRDatabaseQuery *)queryEndingAtValue:(nullable id)endValue childKey:(nullable NSString *)childKey; + +/** + * queryEqualToValue: is used to generate a reference to a limited view of the data at this location. + * The FIRDatabaseQuery instance returned by queryEqualToValue: will respond to events at nodes with a value equal + * to the supplied argument. + * + * @param value The value that the data returned by this FIRDatabaseQuery will have + * @return A FIRDatabaseQuery instance, limited to data with the supplied value. + */ +- (FIRDatabaseQuery *)queryEqualToValue:(nullable id)value; + +/** + * queryEqualToValue:childKey: is used to generate a reference to a limited view of the data at this location. + * The FIRDatabaseQuery instance returned by queryEqualToValue:childKey will respond to events at nodes with a value + * equal to the supplied argument and with their key equal to childKey. There will be at most one node that matches + * because child keys are unique. + * + * @param value The value that the data returned by this FIRDatabaseQuery will have + * @param childKey The name of nodes with the right value + * @return A FIRDatabaseQuery instance, limited to data with the supplied value and the key. + */ +- (FIRDatabaseQuery *)queryEqualToValue:(nullable id)value childKey:(nullable NSString *)childKey; + + +#pragma mark - Properties + +/** +* Gets a FIRDatabaseReference for the location of this query. +* +* @return A FIRDatabaseReference for the location of this query. +*/ +@property (nonatomic, readonly, strong) FIRDatabaseReference * ref; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Firebase/Database/Api/FIRDatabaseQuery.m b/Firebase/Database/Api/FIRDatabaseQuery.m new file mode 100644 index 0000000..bcb1733 --- /dev/null +++ b/Firebase/Database/Api/FIRDatabaseQuery.m @@ -0,0 +1,525 @@ +/* + * 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 "FIRDatabaseQuery.h" +#import "FIRDatabaseQuery_Private.h" +#import "FValidation.h" +#import "FQueryParams.h" +#import "FQuerySpec.h" +#import "FValueEventRegistration.h" +#import "FChildEventRegistration.h" +#import "FPath.h" +#import "FKeyIndex.h" +#import "FPathIndex.h" +#import "FPriorityIndex.h" +#import "FValueIndex.h" +#import "FLeafNode.h" +#import "FSnapshotUtilities.h" +#import "FConstants.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]) { + [NSException raise:INVALID_QUERY_PARAM_ERROR format:@"Can't use queryStartingAtValue: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: with other types than string in combination with queryOrderedByKey"]; + } + } + if ([params hasEnd]) { + if (params.indexEndKey != [FUtilities maxName]) { + [NSException raise:INVALID_QUERY_PARAM_ERROR format:@"Can't use queryEndingAtValue:childKey: or queryEqualToValue:childKey: in combination with queryOrderedByKey"]; + } + if (![params.indexEndValue.val isKindOfClass:[NSString class]]) { + [NSException raise:INVALID_QUERY_PARAM_ERROR format:@"Can't use queryEndingAtValue: 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:, queryEndingAtValue:, or queryEqualToValue: must be valid priorities."]; + } + } +} + +- (void)validateEqualToCall { + if ([self.queryParams hasStart]) { + [NSException raise:INVALID_QUERY_PARAM_ERROR format:@"Cannot combine queryEqualToValue: and queryStartingAtValue:"]; + } + if ([self.queryParams hasEnd]) { + [NSException raise:INVALID_QUERY_PARAM_ERROR format:@"Cannot combine queryEqualToValue: and queryEndingAtValue:"]; + } +} + +- (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]; + } + return [self queryStartingAtInternal:startValue + childKey:childKey + from:@"queryStartingAtValue:childKey:" + priorityMethod:NO]; +} + +- (FIRDatabaseQuery *)queryStartingAtInternal:(id<FNode>)startValue + childKey:(NSString *)childKey + from:(NSString *)methodName + priorityMethod:(BOOL)priorityMethod { + [self validateIndexValueType:startValue fromMethod:methodName]; + if (childKey != nil) { + [FValidation validateFrom:methodName validKey:childKey]; + } + if ([self.queryParams hasStart]) { + [NSException raise:INVALID_QUERY_PARAM_ERROR + format:@"Can't call %@ after queryStartingAtValue 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]; + } + + return [self queryEndingAtInternal:endValue + childKey:childKey + from:@"queryEndingAtValue:childKey:" + priorityMethod:NO]; +} + +- (FIRDatabaseQuery *)queryEndingAtInternal:(id)endValue + childKey:(NSString *)childKey + from:(NSString *)methodName + priorityMethod:(BOOL)priorityMethod { + [self validateIndexValueType:endValue fromMethod:methodName]; + if (childKey != nil) { + [FValidation validateFrom:methodName validKey:childKey]; + } + 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, queryEndingAtValue 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 >= 1l<<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)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 diff --git a/Firebase/Database/Api/FIRDatabaseSwiftNameSupport.h b/Firebase/Database/Api/FIRDatabaseSwiftNameSupport.h new file mode 100644 index 0000000..529adf4 --- /dev/null +++ b/Firebase/Database/Api/FIRDatabaseSwiftNameSupport.h @@ -0,0 +1,29 @@ +/* + * 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 FIR_SWIFT_NAME + +#import <Foundation/Foundation.h> + +// NS_SWIFT_NAME can only translate factory methods before the iOS 9.3 SDK. +// // Wrap it in our own macro if it's a non-compatible SDK. +#ifdef __IPHONE_9_3 +#define FIR_SWIFT_NAME(X) NS_SWIFT_NAME(X) +#else +#define FIR_SWIFT_NAME(X) // Intentionally blank. +#endif // #ifdef __IPHONE_9_3 + +#endif // FIR_SWIFT_NAME
\ No newline at end of file diff --git a/Firebase/Database/Api/FIRMutableData.h b/Firebase/Database/Api/FIRMutableData.h new file mode 100644 index 0000000..5c26024 --- /dev/null +++ b/Firebase/Database/Api/FIRMutableData.h @@ -0,0 +1,130 @@ +/* + * 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 "FIRDatabaseSwiftNameSupport.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * A FIRMutableData instance is populated with data from a Firebase Database location. + * When you are using runTransactionBlock:, you will be given an instance containing the current + * data at that location. Your block will be responsible for updating that instance to the data + * you wish to save at that location, and then returning using [FIRTransactionResult successWithValue:]. + * + * To modify the data, set its value property to any of the native types support by Firebase Database: + * + * + NSNumber (includes BOOL) + * + NSDictionary + * + NSArray + * + NSString + * + nil / NSNull to remove the data + * + * Note that changes made to a child FIRMutableData instance will be visible to the parent. + */ +FIR_SWIFT_NAME(MutableData) +@interface FIRMutableData : NSObject + + +#pragma mark - Inspecting and navigating the data + + +/** + * Returns boolean indicating whether this mutable data has children. + * + * @return YES if this data contains child nodes. + */ +- (BOOL) hasChildren; + + +/** + * Indicates whether this mutable data has a child at the given path. + * + * @param path A path string, consisting either of a single segment, like 'child', or multiple segments, 'a/deeper/child' + * @return YES if this data contains a child at the specified relative path + */ +- (BOOL) hasChildAtPath:(NSString *)path; + + +/** + * Used to obtain a FIRMutableData instance that encapsulates the data at the given relative path. + * Note that changes made to the child will be visible to the parent. + * + * @param path A path string, consisting either of a single segment, like 'child', or multiple segments, 'a/deeper/child' + * @return A FIRMutableData instance containing the data at the given path + */ +- (FIRMutableData *)childDataByAppendingPath:(NSString *)path; + + +#pragma mark - Properties + + +/** + * To modify the data contained by this instance of FIRMutableData, set this to any of the native types supported by Firebase Database: + * + * + NSNumber (includes BOOL) + * + NSDictionary + * + NSArray + * + NSString + * + nil / NSNull to remove the data + * + * Note that setting this value will override the priority at this location. + * + * @return The current data at this location as a native object + */ +@property (strong, nonatomic, nullable) id value; + + +/** + * Set this property to update the priority of the data at this location. Can be set to the following types: + * + * + NSNumber + * + NSString + * + nil / NSNull to remove the priority + * + * @return The priority of the data at this location + */ +@property (strong, nonatomic, nullable) id priority; + + +/** + * @return The number of child nodes at this location + */ +@property (readonly, nonatomic) NSUInteger childrenCount; + + +/** + * Used to iterate over the children at this location. You can use the native for .. in syntax: + * + * for (FIRMutableData* child in data.children) { + * ... + * } + * + * Note that this enumerator operates on an immutable copy of the child list. So, you can modify the instance + * during iteration, but the new additions will not be visible until you get a new enumerator. + */ +@property (readonly, nonatomic, strong) NSEnumerator* children; + + +/** + * @return The key name of this node, or nil if it is the top-most location + */ +@property (readonly, nonatomic, strong, nullable) NSString* key; + + +@end + +NS_ASSUME_NONNULL_END diff --git a/Firebase/Database/Api/FIRMutableData.m b/Firebase/Database/Api/FIRMutableData.m new file mode 100644 index 0000000..7e10dcd --- /dev/null +++ b/Firebase/Database/Api/FIRMutableData.m @@ -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 "FIRMutableData.h" +#import "FIRMutableData_Private.h" +#import "FSnapshotHolder.h" +#import "FSnapshotUtilities.h" +#import "FChildrenNode.h" +#import "FTransformedEnumerator.h" +#import "FNamedNode.h" +#import "FIndexedNode.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 *) 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 diff --git a/Firebase/Database/Api/FIRServerValue.h b/Firebase/Database/Api/FIRServerValue.h new file mode 100644 index 0000000..f5eadd5 --- /dev/null +++ b/Firebase/Database/Api/FIRServerValue.h @@ -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 "FIRDatabaseSwiftNameSupport.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * Placeholder values you may write into Firebase Database as a value or priority + * that will automatically be populated by the Firebase Database server. + */ +FIR_SWIFT_NAME(ServerValue) +@interface FIRServerValue : NSObject + +/** + * Placeholder value for the number of milliseconds since the Unix epoch + */ ++ (NSDictionary *) timestamp; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Firebase/Database/Api/FIRServerValue.m b/Firebase/Database/Api/FIRServerValue.m new file mode 100644 index 0000000..14bb745 --- /dev/null +++ b/Firebase/Database/Api/FIRServerValue.m @@ -0,0 +1,30 @@ +/* + * 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 "FIRDatabaseReference.h" +#import "FIRServerValue.h" + +@implementation FIRServerValue + ++ (NSDictionary *) timestamp { + static NSDictionary *timestamp = nil; + if (timestamp == nil) { + timestamp = @{ @".sv": @"timestamp" }; + } + return timestamp; +} + +@end diff --git a/Firebase/Database/Api/FIRTransactionResult.h b/Firebase/Database/Api/FIRTransactionResult.h new file mode 100644 index 0000000..3c2d39a --- /dev/null +++ b/Firebase/Database/Api/FIRTransactionResult.h @@ -0,0 +1,47 @@ +/* + * 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 "FIRDatabaseSwiftNameSupport.h" +#import "FIRMutableData.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * Used for runTransactionBlock:. An FIRTransactionResult instance is a container for the results of the transaction. + */ +FIR_SWIFT_NAME(TransactionResult) +@interface FIRTransactionResult : NSObject + +/** + * Used for runTransactionBlock:. Indicates that the new value should be saved at this location + * + * @param value A FIRMutableData instance containing the new value to be set + * @return An FIRTransactionResult instance that can be used as a return value from the block given to runTransactionBlock: + */ ++ (FIRTransactionResult *)successWithValue:(FIRMutableData *)value; + + +/** + * Used for runTransactionBlock:. Indicates that the current transaction should no longer proceed. + * + * @return An FIRTransactionResult instance that can be used as a return value from the block given to runTransactionBlock: + */ ++ (FIRTransactionResult *) abort; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Firebase/Database/Api/FIRTransactionResult.m b/Firebase/Database/Api/FIRTransactionResult.m new file mode 100644 index 0000000..8afc5b7 --- /dev/null +++ b/Firebase/Database/Api/FIRTransactionResult.m @@ -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 "FIRTransactionResult.h" +#import "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 diff --git a/Firebase/Database/Api/FirebaseDatabase.h b/Firebase/Database/Api/FirebaseDatabase.h new file mode 100644 index 0000000..e52f5d6 --- /dev/null +++ b/Firebase/Database/Api/FirebaseDatabase.h @@ -0,0 +1,29 @@ +/* + * 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 FirebaseDatabase_h +#define FirebaseDatabase_h + +#import "FIRDatabase.h" +#import "FIRDatabaseQuery.h" +#import "FIRDatabaseReference.h" +#import "FIRDataEventType.h" +#import "FIRDataSnapshot.h" +#import "FIRMutableData.h" +#import "FIRServerValue.h" +#import "FIRTransactionResult.h" + +#endif /* FirebaseDatabase_h */ diff --git a/Firebase/Database/Api/Private/FIRDataSnapshot_Private.h b/Firebase/Database/Api/Private/FIRDataSnapshot_Private.h new file mode 100644 index 0000000..4ff285b --- /dev/null +++ b/Firebase/Database/Api/Private/FIRDataSnapshot_Private.h @@ -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 "FIndexedNode.h" +#import "FTypedefs_Private.h" + +@interface FIRDataSnapshot () + +// in _Private for testing purposes +@property (nonatomic, strong) FIndexedNode *node; + +- (id)initWithRef:(FIRDatabaseReference *)ref indexedNode:(FIndexedNode *)node; + +@end diff --git a/Firebase/Database/Api/Private/FIRDatabaseQuery_Private.h b/Firebase/Database/Api/Private/FIRDatabaseQuery_Private.h new file mode 100644 index 0000000..3a10fe3 --- /dev/null +++ b/Firebase/Database/Api/Private/FIRDatabaseQuery_Private.h @@ -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 "FRepo.h" +#import "FPath.h" +#import "FRepoManager.h" +#import "FTypedefs_Private.h" +#import "FQueryParams.h" +#import "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 diff --git a/Firebase/Database/Api/Private/FIRDatabaseReference_Private.h b/Firebase/Database/Api/Private/FIRDatabaseReference_Private.h new file mode 100644 index 0000000..cb28feb --- /dev/null +++ b/Firebase/Database/Api/Private/FIRDatabaseReference_Private.h @@ -0,0 +1,29 @@ +/* + * 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 "FIRDatabaseReference.h" +#import "FTypedefs_Private.h" +#import "FIRDatabaseConfig.h" +#import "FRepo.h" + +@interface FIRDatabaseReference () + +- (id)initWithConfig:(FIRDatabaseConfig *)config; +- (id)initWithRepo:(FRepo *)repo path:(FPath *)path; + +// TODO: Update tests to not use this. ++ (FIRDatabaseConfig *)defaultConfig; +@end diff --git a/Firebase/Database/Api/Private/FIRDatabase_Private.h b/Firebase/Database/Api/Private/FIRDatabase_Private.h new file mode 100644 index 0000000..5b7f8cc --- /dev/null +++ b/Firebase/Database/Api/Private/FIRDatabase_Private.h @@ -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 "FIRDatabase.h" + +@class FRepo; +@class FRepoInfo; +@class FIRDatabaseConfig; + +@interface FIRDatabase () + ++ (NSString *) buildVersion; ++ (FIRDatabase *) createDatabaseForTests:(FRepoInfo *)repoInfo config:(FIRDatabaseConfig *)config; + +@end diff --git a/Firebase/Database/Api/Private/FIRMutableData_Private.h b/Firebase/Database/Api/Private/FIRMutableData_Private.h new file mode 100644 index 0000000..ee3aa96 --- /dev/null +++ b/Firebase/Database/Api/Private/FIRMutableData_Private.h @@ -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 "FIRMutableData.h" +#import "FNode.h" + +@interface FIRMutableData () + +- (id) initWithNode:(id<FNode>)node; +- (id<FNode>) nodeValue; +- (BOOL) isEqualToData:(FIRMutableData *)other; + +@end diff --git a/Firebase/Database/Api/Private/FIRTransactionResult_Private.h b/Firebase/Database/Api/Private/FIRTransactionResult_Private.h new file mode 100644 index 0000000..82290f2 --- /dev/null +++ b/Firebase/Database/Api/Private/FIRTransactionResult_Private.h @@ -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 "FIRTransactionResult.h" +#import "FIRMutableData.h" + +@interface FIRTransactionResult () + +@property (nonatomic) BOOL isSuccess; +@property (nonatomic, strong) FIRMutableData * update; + +@end diff --git a/Firebase/Database/Api/Private/FTypedefs_Private.h b/Firebase/Database/Api/Private/FTypedefs_Private.h new file mode 100644 index 0000000..73f4c9a --- /dev/null +++ b/Firebase/Database/Api/Private/FTypedefs_Private.h @@ -0,0 +1,56 @@ +/* + * 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_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 |