FIRDatabase.m 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. /*
  2. * Copyright 2017 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import <Foundation/Foundation.h>
  17. #import "FIRDatabase.h"
  18. #import "FIRDatabase_Private.h"
  19. #import "FIRDatabaseQuery_Private.h"
  20. #import "FRepoManager.h"
  21. #import "FValidation.h"
  22. #import "FIRDatabaseConfig_Private.h"
  23. #import "FRepoInfo.h"
  24. #import "FIRDatabaseConfig.h"
  25. #import "FIRDatabaseReference_Private.h"
  26. /**
  27. * This is a hack that defines all the methods we need from FIRApp/Options. At runtime we use reflection to get the
  28. * default FIRApp instance if we need it. Since protocols don't carry any runtime information and selectors
  29. * are invoked by name we can write code against this protocol as long as the method signatures don't change.
  30. *
  31. * TODO: Consider weak-linking the actual Firebase/Core framework or something.
  32. */
  33. extern NSString *const kFIRDefaultAppName;
  34. @protocol FIROptionsLike <NSObject>
  35. @property(nonatomic, readonly, copy) NSString *databaseURL;
  36. @end
  37. @protocol FIRAppLike <NSObject>
  38. @property(nonatomic, readonly) id<FIROptionsLike> options;
  39. @property(nonatomic, copy, readonly) NSString *name;
  40. @end
  41. @interface FIRDatabase ()
  42. @property (nonatomic, strong) FRepoInfo *repoInfo;
  43. @property (nonatomic, strong) FIRDatabaseConfig *config;
  44. @property (nonatomic, strong) FRepo *repo;
  45. @end
  46. @implementation FIRDatabase
  47. // The STR and STR_EXPAND macro allow a numeric version passed to he compiler driver
  48. // with a -D to be treated as a string instead of an invalid floating point value.
  49. #define STR(x) STR_EXPAND(x)
  50. #define STR_EXPAND(x) #x
  51. static const char *FIREBASE_SEMVER = (const char *)STR(FIRDatabase_VERSION);
  52. /**
  53. * A static NSMutableDictionary of FirebaseApp names to FirebaseDatabase instance. To ensure thread-
  54. * safety, it should only be accessed in databaseForApp, which is synchronized.
  55. *
  56. * TODO: This serves a duplicate purpose as RepoManager. We should clean up.
  57. * TODO: We should maybe be conscious of leaks and make this a weak map or similar
  58. * but we have a lot of work to do to allow FirebaseDatabase/Repo etc. to be GC'd.
  59. */
  60. + (NSMutableDictionary *)instances {
  61. static dispatch_once_t pred = 0;
  62. static NSMutableDictionary *instances;
  63. dispatch_once(&pred, ^{
  64. instances = [NSMutableDictionary dictionary];
  65. });
  66. return instances;
  67. }
  68. + (FIRDatabase *)database {
  69. id<FIRAppLike> app = [FIRDatabase getDefaultApp];
  70. if (app == nil) {
  71. [NSException raise:@"FIRAppNotConfigured" format:@"Failed to get default FIRDatabase instance. Must call FIRApp.configure() before using FIRDatabase."];
  72. }
  73. return [FIRDatabase databaseForApp:(FIRApp*)app];
  74. }
  75. + (FIRDatabase *)databaseForApp:(id)app {
  76. if (app == nil) {
  77. [NSException raise:@"InvalidFIRApp" format:@"nil FIRApp instance passed to databaseForApp."];
  78. }
  79. NSMutableDictionary *instances = [self instances];
  80. @synchronized (instances) {
  81. id<FIRAppLike> appLike = (id<FIRAppLike>)app;
  82. FIRDatabase *database = instances[appLike.name];
  83. if (!database) {
  84. NSString *databaseUrl = appLike.options.databaseURL;
  85. if (databaseUrl == nil) {
  86. [NSException raise:@"MissingDatabaseURL" format:@"Failed to get FIRDatabase instance: FIRApp object has no "
  87. "databaseURL in its FirebaseOptions object."];
  88. }
  89. FParsedUrl *parsedUrl = [FUtilities parseUrl:databaseUrl];
  90. if (![parsedUrl.path isEmpty]) {
  91. [NSException raise:@"InvalidDatabaseURL" format:@"Configured Database URL '%@' is invalid. It should "
  92. "point to the root of a Firebase Database but it includes a path: %@",
  93. databaseUrl, [parsedUrl.path toString]];
  94. }
  95. id<FAuthTokenProvider> authTokenProvider = [FAuthTokenProvider authTokenProviderForApp:appLike];
  96. // If this is the default app, don't set the session persistence key so that we use our
  97. // default ("default") instead of the FIRApp default ("[DEFAULT]") so that we
  98. // preserve the default location used by the legacy Firebase SDK.
  99. NSString *sessionIdentifier = @"default";
  100. if (![appLike.name isEqualToString:kFIRDefaultAppName]) {
  101. sessionIdentifier = appLike.name;
  102. }
  103. FIRDatabaseConfig *config = [[FIRDatabaseConfig alloc] initWithSessionIdentifier:sessionIdentifier
  104. authTokenProvider:authTokenProvider];
  105. database = [[FIRDatabase alloc] initWithApp:appLike repoInfo:parsedUrl.repoInfo config:config];
  106. instances[appLike.name] = database;
  107. }
  108. return database;
  109. }
  110. }
  111. + (NSString *) buildVersion {
  112. // TODO: Restore git hash when build moves back to git
  113. return [NSString stringWithFormat:@"%s_%s", FIREBASE_SEMVER, __DATE__];
  114. }
  115. + (FIRDatabase *)createDatabaseForTests:(FRepoInfo *)repoInfo config:(FIRDatabaseConfig *)config {
  116. FIRDatabase *db = [[FIRDatabase alloc] initWithApp:nil repoInfo:repoInfo config:config];
  117. [db ensureRepo];
  118. return db;
  119. }
  120. + (NSString *) sdkVersion {
  121. return [NSString stringWithUTF8String:FIREBASE_SEMVER];
  122. }
  123. + (void) setLoggingEnabled:(BOOL)enabled {
  124. [FUtilities setLoggingEnabled:enabled];
  125. FFLog(@"I-RDB024001", @"BUILD Version: %@", [FIRDatabase buildVersion]);
  126. }
  127. - (id)initWithApp:(id <FIRAppLike>)appLike repoInfo:(FRepoInfo *)info config:(FIRDatabaseConfig *)config {
  128. self = [super init];
  129. if (self != nil) {
  130. self->_repoInfo = info;
  131. self->_config = config;
  132. self->_app = (FIRApp*) appLike;
  133. }
  134. return self;
  135. }
  136. - (FIRDatabaseReference *)reference {
  137. [self ensureRepo];
  138. return [[FIRDatabaseReference alloc] initWithRepo:self.repo path:[FPath empty]];
  139. }
  140. - (FIRDatabaseReference *)referenceWithPath:(NSString *)path {
  141. [self ensureRepo];
  142. [FValidation validateFrom:@"referenceWithPath" validRootPathString:path];
  143. FPath *childPath = [[FPath alloc] initWith:path];
  144. return [[FIRDatabaseReference alloc] initWithRepo:self.repo path:childPath];
  145. }
  146. - (FIRDatabaseReference *)referenceFromURL:(NSString *)databaseUrl {
  147. [self ensureRepo];
  148. if (databaseUrl == nil) {
  149. [NSException raise:@"InvalidDatabaseURL" format:@"Invalid nil url passed to referenceFromURL:"];
  150. }
  151. FParsedUrl *parsedUrl = [FUtilities parseUrl:databaseUrl];
  152. [FValidation validateFrom:@"referenceFromURL:" validURL:parsedUrl];
  153. if (![parsedUrl.repoInfo.host isEqualToString:_repoInfo.host]) {
  154. [NSException raise:@"InvalidDatabaseURL" format:@"Invalid URL (%@) passed to getReference(). URL was expected "
  155. "to match configured Database URL: %@", databaseUrl, [self reference].URL];
  156. }
  157. return [[FIRDatabaseReference alloc] initWithRepo:self.repo path:parsedUrl.path];
  158. }
  159. - (void)purgeOutstandingWrites {
  160. [self ensureRepo];
  161. dispatch_async([FIRDatabaseQuery sharedQueue], ^{
  162. [self.repo purgeOutstandingWrites];
  163. });
  164. }
  165. - (void)goOnline {
  166. [self ensureRepo];
  167. dispatch_async([FIRDatabaseQuery sharedQueue], ^{
  168. [self.repo resume];
  169. });
  170. }
  171. - (void)goOffline {
  172. [self ensureRepo];
  173. dispatch_async([FIRDatabaseQuery sharedQueue], ^{
  174. [self.repo interrupt];
  175. });
  176. }
  177. + (id<FIRAppLike>) getDefaultApp {
  178. Class appClass = NSClassFromString(@"FIRApp");
  179. if (appClass == nil) {
  180. [NSException raise:@"FailedToFindFIRApp" format:@"Failed to find FIRApp class."];
  181. return nil;
  182. } else {
  183. #pragma clang diagnostic push
  184. #pragma clang diagnostic ignored "-Wundeclared-selector"
  185. return [appClass performSelector:@selector(defaultApp)];
  186. #pragma clang diagnostic pop
  187. }
  188. }
  189. - (void)setPersistenceEnabled:(BOOL)persistenceEnabled {
  190. [self assertUnfrozen:@"setPersistenceEnabled"];
  191. self->_config.persistenceEnabled = persistenceEnabled;
  192. }
  193. - (BOOL)persistenceEnabled {
  194. return self->_config.persistenceEnabled;
  195. }
  196. - (void)setPersistenceCacheSizeBytes:(NSUInteger)persistenceCacheSizeBytes {
  197. [self assertUnfrozen:@"setPersistenceCacheSizeBytes"];
  198. self->_config.persistenceCacheSizeBytes = persistenceCacheSizeBytes;
  199. }
  200. - (NSUInteger)persistenceCacheSizeBytes {
  201. return self->_config.persistenceCacheSizeBytes;
  202. }
  203. - (void)setCallbackQueue:(dispatch_queue_t)callbackQueue {
  204. [self assertUnfrozen:@"setCallbackQueue"];
  205. self->_config.callbackQueue = callbackQueue;
  206. }
  207. - (dispatch_queue_t)callbackQueue {
  208. return self->_config.callbackQueue;
  209. }
  210. - (void) assertUnfrozen:(NSString*)methodName {
  211. if (self.repo != nil) {
  212. [NSException raise:@"FIRDatabaseAlreadyInUse" format:@"Calls to %@ must be made before any other usage of "
  213. "FIRDatabase instance.", methodName];
  214. }
  215. }
  216. - (void) ensureRepo {
  217. if (self.repo == nil) {
  218. self.repo = [FRepoManager createRepo:self.repoInfo config:self.config database:self];
  219. }
  220. }
  221. @end