FIRDatabase.m 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  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 <FirebaseCore/FIRAppInternal.h>
  18. #import <FirebaseCore/FIRLogger.h>
  19. #import "FIRDatabase.h"
  20. #import "FIRDatabase_Private.h"
  21. #import "FIRDatabaseQuery_Private.h"
  22. #import "FRepoManager.h"
  23. #import "FValidation.h"
  24. #import "FIRDatabaseConfig_Private.h"
  25. #import "FRepoInfo.h"
  26. #import "FIRDatabaseConfig.h"
  27. #import "FIRDatabaseReference_Private.h"
  28. #import <FirebaseCore/FIROptions.h>
  29. @interface FIRDatabase ()
  30. @property (nonatomic, strong) FRepoInfo *repoInfo;
  31. @property (nonatomic, strong) FIRDatabaseConfig *config;
  32. @property (nonatomic, strong) FRepo *repo;
  33. @end
  34. @implementation FIRDatabase
  35. /** A NSMutableDictionary of FirebaseApp name and FRepoInfo to FirebaseDatabase instance. */
  36. typedef NSMutableDictionary<NSString *, NSMutableDictionary<FRepoInfo *, FIRDatabase *> *> FIRDatabaseDictionary;
  37. // The STR and STR_EXPAND macro allow a numeric version passed to he compiler driver
  38. // with a -D to be treated as a string instead of an invalid floating point value.
  39. #define STR(x) STR_EXPAND(x)
  40. #define STR_EXPAND(x) #x
  41. static const char *FIREBASE_SEMVER = (const char *)STR(FIRDatabase_VERSION);
  42. + (void)load {
  43. NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
  44. [center addObserverForName:kFIRAppDeleteNotification
  45. object:nil
  46. queue:nil
  47. usingBlock:^(NSNotification * _Nonnull note) {
  48. NSString *appName = note.userInfo[kFIRAppNameKey];
  49. if (appName == nil) { return; }
  50. FIRDatabaseDictionary* instances = [self instances];
  51. @synchronized (instances) {
  52. NSMutableDictionary<FRepoInfo *, FIRDatabase *> *databaseInstances = instances[appName];
  53. if (databaseInstances) {
  54. // Clean up the deleted instance in an effort to remove any resources still in use.
  55. // Note: Any leftover instances of this exact database will be invalid.
  56. for (FIRDatabase * database in [databaseInstances allValues]) {
  57. [FRepoManager disposeRepos:database.config];
  58. }
  59. [instances removeObjectForKey:appName];
  60. }
  61. }
  62. }];
  63. }
  64. /**
  65. * A static NSMutableDictionary of FirebaseApp name and FRepoInfo to
  66. * FirebaseDatabase instance. To ensure thread-safety, it should only be
  67. * accessed in databaseForApp:URL:, which is synchronized.
  68. *
  69. * TODO: This serves a duplicate purpose as RepoManager. We should clean up.
  70. * TODO: We should maybe be conscious of leaks and make this a weak map or
  71. * similar but we have a lot of work to do to allow FirebaseDatabase/Repo etc.
  72. * to be GC'd.
  73. */
  74. + (FIRDatabaseDictionary *)instances {
  75. static dispatch_once_t pred = 0;
  76. static FIRDatabaseDictionary *instances;
  77. dispatch_once(&pred, ^{
  78. instances = [NSMutableDictionary dictionary];
  79. });
  80. return instances;
  81. }
  82. + (FIRDatabase *)database {
  83. if (![FIRApp isDefaultAppConfigured]) {
  84. [NSException raise:@"FIRAppNotConfigured"
  85. format:@"Failed to get default Firebase Database instance. Must call `[FIRApp "
  86. @"configure]` (`FirebaseApp.configure()` in Swift) before using "
  87. @"Firebase Database."];
  88. }
  89. FIRApp *app = [FIRApp defaultApp];
  90. return [FIRDatabase databaseForApp:app];
  91. }
  92. + (FIRDatabase *)databaseWithURL:(NSString *)url {
  93. FIRApp *app = [FIRApp defaultApp];
  94. if (app == nil) {
  95. [NSException raise:@"FIRAppNotConfigured"
  96. format:@"Failed to get default Firebase Database instance. "
  97. @"Must call `[FIRApp configure]` (`FirebaseApp.configure()` in Swift) "
  98. @"before using Firebase Database."];
  99. }
  100. return [FIRDatabase databaseForApp:app URL:url];
  101. }
  102. + (FIRDatabase *)databaseForApp:(FIRApp *)app {
  103. if (app == nil) {
  104. [NSException raise:@"InvalidFIRApp" format:@"nil FIRApp instance passed to databaseForApp."];
  105. }
  106. return [FIRDatabase databaseForApp:app URL:app.options.databaseURL];
  107. }
  108. + (FIRDatabase *)databaseForApp:(FIRApp *)app URL:(NSString *)url {
  109. if (app == nil) {
  110. [NSException raise:@"InvalidFIRApp"
  111. format:@"nil FIRApp instance passed to databaseForApp."];
  112. }
  113. if (url == nil) {
  114. [NSException raise:@"MissingDatabaseURL"
  115. format:@"Failed to get FirebaseDatabase instance: "
  116. "Specify DatabaseURL within FIRApp or from your databaseForApp:URL: call."];
  117. }
  118. NSURL *databaseUrl = [NSURL URLWithString:url];
  119. if (databaseUrl == nil) {
  120. [NSException raise:@"InvalidDatabaseURL" format:@"The Database URL '%@' cannot be parsed. "
  121. "Specify a valid DatabaseURL within FIRApp or from your databaseForApp:URL: call.", databaseUrl];
  122. } else if (![databaseUrl.path isEqualToString:@""] && ![databaseUrl.path isEqualToString:@"/"]) {
  123. [NSException raise:@"InvalidDatabaseURL" format:@"Configured Database URL '%@' is invalid. It should point "
  124. "to the root of a Firebase Database but it includes a path: %@",databaseUrl, databaseUrl.path];
  125. }
  126. FIRDatabaseDictionary *instances = [self instances];
  127. @synchronized (instances) {
  128. NSMutableDictionary<FRepoInfo *, FIRDatabase *> *urlInstanceMap =
  129. instances[app.name];
  130. if (!urlInstanceMap) {
  131. urlInstanceMap = [NSMutableDictionary dictionary];
  132. instances[app.name] = urlInstanceMap;
  133. }
  134. FParsedUrl *parsedUrl = [FUtilities parseUrl:databaseUrl.absoluteString];
  135. FIRDatabase *database = urlInstanceMap[parsedUrl.repoInfo];
  136. if (!database) {
  137. id<FAuthTokenProvider> authTokenProvider = [FAuthTokenProvider authTokenProviderForApp:app];
  138. // If this is the default app, don't set the session persistence key so that we use our
  139. // default ("default") instead of the FIRApp default ("[DEFAULT]") so that we
  140. // preserve the default location used by the legacy Firebase SDK.
  141. NSString *sessionIdentifier = @"default";
  142. if (![FIRApp isDefaultAppConfigured] || app != [FIRApp defaultApp]) {
  143. sessionIdentifier = app.name;
  144. }
  145. FIRDatabaseConfig *config = [[FIRDatabaseConfig alloc] initWithSessionIdentifier:sessionIdentifier
  146. authTokenProvider:authTokenProvider];
  147. database = [[FIRDatabase alloc] initWithApp:app
  148. repoInfo:parsedUrl.repoInfo
  149. config:config];
  150. urlInstanceMap[parsedUrl.repoInfo] = database;
  151. }
  152. return database;
  153. }
  154. }
  155. + (NSString *) buildVersion {
  156. // TODO: Restore git hash when build moves back to git
  157. return [NSString stringWithFormat:@"%s_%s", FIREBASE_SEMVER, __DATE__];
  158. }
  159. + (FIRDatabase *)createDatabaseForTests:(FRepoInfo *)repoInfo config:(FIRDatabaseConfig *)config {
  160. FIRDatabase *db = [[FIRDatabase alloc] initWithApp:nil repoInfo:repoInfo config:config];
  161. [db ensureRepo];
  162. return db;
  163. }
  164. + (NSString *) sdkVersion {
  165. return [NSString stringWithUTF8String:FIREBASE_SEMVER];
  166. }
  167. + (void) setLoggingEnabled:(BOOL)enabled {
  168. [FUtilities setLoggingEnabled:enabled];
  169. FFLog(@"I-RDB024001", @"BUILD Version: %@", [FIRDatabase buildVersion]);
  170. }
  171. - (id)initWithApp:(FIRApp *)app repoInfo:(FRepoInfo *)info config:(FIRDatabaseConfig *)config {
  172. self = [super init];
  173. if (self != nil) {
  174. self->_repoInfo = info;
  175. self->_config = config;
  176. self->_app = app;
  177. }
  178. return self;
  179. }
  180. - (FIRDatabaseReference *)reference {
  181. [self ensureRepo];
  182. return [[FIRDatabaseReference alloc] initWithRepo:self.repo path:[FPath empty]];
  183. }
  184. - (FIRDatabaseReference *)referenceWithPath:(NSString *)path {
  185. [self ensureRepo];
  186. [FValidation validateFrom:@"referenceWithPath" validRootPathString:path];
  187. FPath *childPath = [[FPath alloc] initWith:path];
  188. return [[FIRDatabaseReference alloc] initWithRepo:self.repo path:childPath];
  189. }
  190. - (FIRDatabaseReference *)referenceFromURL:(NSString *)databaseUrl {
  191. [self ensureRepo];
  192. if (databaseUrl == nil) {
  193. [NSException raise:@"InvalidDatabaseURL" format:@"Invalid nil url passed to referenceFromURL:"];
  194. }
  195. FParsedUrl *parsedUrl = [FUtilities parseUrl:databaseUrl];
  196. [FValidation validateFrom:@"referenceFromURL:" validURL:parsedUrl];
  197. if (![parsedUrl.repoInfo.host isEqualToString:_repoInfo.host]) {
  198. [NSException raise:@"InvalidDatabaseURL" format:@"Invalid URL (%@) passed to getReference(). URL was expected "
  199. "to match configured Database URL: %@", databaseUrl, [self reference].URL];
  200. }
  201. return [[FIRDatabaseReference alloc] initWithRepo:self.repo path:parsedUrl.path];
  202. }
  203. - (void)purgeOutstandingWrites {
  204. [self ensureRepo];
  205. dispatch_async([FIRDatabaseQuery sharedQueue], ^{
  206. [self.repo purgeOutstandingWrites];
  207. });
  208. }
  209. - (void)goOnline {
  210. [self ensureRepo];
  211. dispatch_async([FIRDatabaseQuery sharedQueue], ^{
  212. [self.repo resume];
  213. });
  214. }
  215. - (void)goOffline {
  216. [self ensureRepo];
  217. dispatch_async([FIRDatabaseQuery sharedQueue], ^{
  218. [self.repo interrupt];
  219. });
  220. }
  221. - (void)setPersistenceEnabled:(BOOL)persistenceEnabled {
  222. [self assertUnfrozen:@"setPersistenceEnabled"];
  223. self->_config.persistenceEnabled = persistenceEnabled;
  224. }
  225. - (BOOL)persistenceEnabled {
  226. return self->_config.persistenceEnabled;
  227. }
  228. - (void)setPersistenceCacheSizeBytes:(NSUInteger)persistenceCacheSizeBytes {
  229. [self assertUnfrozen:@"setPersistenceCacheSizeBytes"];
  230. self->_config.persistenceCacheSizeBytes = persistenceCacheSizeBytes;
  231. }
  232. - (NSUInteger)persistenceCacheSizeBytes {
  233. return self->_config.persistenceCacheSizeBytes;
  234. }
  235. - (void)setCallbackQueue:(dispatch_queue_t)callbackQueue {
  236. [self assertUnfrozen:@"setCallbackQueue"];
  237. self->_config.callbackQueue = callbackQueue;
  238. }
  239. - (dispatch_queue_t)callbackQueue {
  240. return self->_config.callbackQueue;
  241. }
  242. - (void) assertUnfrozen:(NSString*)methodName {
  243. if (self.repo != nil) {
  244. [NSException raise:@"FIRDatabaseAlreadyInUse" format:@"Calls to %@ must be made before any other usage of "
  245. "FIRDatabase instance.", methodName];
  246. }
  247. }
  248. - (void) ensureRepo {
  249. if (self.repo == nil) {
  250. self.repo = [FRepoManager createRepo:self.repoInfo config:self.config database:self];
  251. }
  252. }
  253. @end