FIRStorage.m 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. // Copyright 2017 Google
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #import "FirebaseStorage/Sources/Public/FirebaseStorage/FIRStorage.h"
  15. #import "FirebaseStorage/Sources/Public/FirebaseStorage/FIRStorageReference.h"
  16. #import "FirebaseStorage/Sources/FIRStorageComponent.h"
  17. #import "FirebaseStorage/Sources/FIRStorageConstants_Private.h"
  18. #import "FirebaseStorage/Sources/FIRStoragePath.h"
  19. #import "FirebaseStorage/Sources/FIRStorageReference_Private.h"
  20. #import "FirebaseStorage/Sources/FIRStorageTokenAuthorizer.h"
  21. #import "FirebaseStorage/Sources/FIRStorageUtils.h"
  22. #import "FirebaseStorage/Sources/FIRStorage_Private.h"
  23. #import "FirebaseAppCheck/Sources/Interop/FIRAppCheckInterop.h"
  24. #import "FirebaseCore/Sources/Private/FirebaseCoreInternal.h"
  25. #import "Interop/Auth/Public/FIRAuthInterop.h"
  26. #if SWIFT_PACKAGE
  27. @import GTMSessionFetcherCore;
  28. #else
  29. #import <GTMSessionFetcher/GTMSessionFetcher.h>
  30. #import <GTMSessionFetcher/GTMSessionFetcherLogging.h>
  31. #endif
  32. static NSMutableDictionary<
  33. NSString * /* app name */,
  34. NSMutableDictionary<NSString * /* bucket */, GTMSessionFetcherService *> *> *_fetcherServiceMap;
  35. static GTMSessionFetcherRetryBlock _retryWhenOffline;
  36. @interface FIRStorage () {
  37. /// Stored Auth reference, if it exists. This needs to be stored for `copyWithZone:`.
  38. id<FIRAuthInterop> _Nullable _auth;
  39. id<FIRAppCheckInterop> _Nullable _appCheck;
  40. BOOL _usesEmulator;
  41. NSTimeInterval _maxUploadRetryTime;
  42. NSTimeInterval _maxDownloadRetryTime;
  43. NSTimeInterval _maxOperationRetryTime;
  44. }
  45. @end
  46. @implementation FIRStorage
  47. + (void)initialize {
  48. static dispatch_once_t onceToken;
  49. dispatch_once(&onceToken, ^{
  50. _retryWhenOffline = ^(BOOL suggestedWillRetry, NSError *GTM_NULLABLE_TYPE error,
  51. GTMSessionFetcherRetryResponse response) {
  52. bool shouldRetry = suggestedWillRetry;
  53. // GTMSessionFetcher does not consider being offline a retryable error, but we do, so we
  54. // special-case it here.
  55. if (!shouldRetry && error) {
  56. shouldRetry = error.code == NSURLErrorNotConnectedToInternet;
  57. }
  58. response(shouldRetry);
  59. };
  60. _fetcherServiceMap = [[NSMutableDictionary alloc] init];
  61. });
  62. }
  63. + (GTMSessionFetcherService *)fetcherServiceForApp:(FIRApp *)app
  64. bucket:(NSString *)bucket
  65. auth:(nullable id<FIRAuthInterop>)auth
  66. appCheck:(nullable id<FIRAppCheckInterop>)appCheck {
  67. @synchronized(_fetcherServiceMap) {
  68. NSMutableDictionary *bucketMap = _fetcherServiceMap[app.name];
  69. if (!bucketMap) {
  70. bucketMap = [[NSMutableDictionary alloc] init];
  71. _fetcherServiceMap[app.name] = bucketMap;
  72. }
  73. GTMSessionFetcherService *fetcherService = bucketMap[bucket];
  74. if (!fetcherService) {
  75. fetcherService = [[GTMSessionFetcherService alloc] init];
  76. [fetcherService setRetryEnabled:YES];
  77. [fetcherService setRetryBlock:_retryWhenOffline];
  78. [fetcherService setAllowLocalhostRequest:YES];
  79. FIRStorageTokenAuthorizer *authorizer =
  80. [[FIRStorageTokenAuthorizer alloc] initWithGoogleAppID:app.options.googleAppID
  81. fetcherService:fetcherService
  82. authProvider:auth
  83. appCheck:appCheck];
  84. [fetcherService setAuthorizer:authorizer];
  85. bucketMap[bucket] = fetcherService;
  86. }
  87. return fetcherService;
  88. }
  89. }
  90. + (void)setGTMSessionFetcherLoggingEnabled:(BOOL)isLoggingEnabled {
  91. [GTMSessionFetcher setLoggingEnabled:isLoggingEnabled];
  92. }
  93. + (instancetype)storage {
  94. return [self storageForApp:[FIRApp defaultApp]];
  95. }
  96. + (instancetype)storageForApp:(FIRApp *)app {
  97. if (app.options.storageBucket) {
  98. NSString *url = [app.options.storageBucket isEqualToString:@""]
  99. ? @""
  100. : [@"gs://" stringByAppendingString:app.options.storageBucket];
  101. return [self storageForApp:app URL:url];
  102. } else {
  103. NSString *const kAppNotConfiguredMessage =
  104. @"No default Storage bucket found. Did you configure Firebase Storage properly?";
  105. [NSException raise:NSInvalidArgumentException format:kAppNotConfiguredMessage];
  106. return nil;
  107. }
  108. }
  109. + (instancetype)storageWithURL:(NSString *)url {
  110. return [self storageForApp:[FIRApp defaultApp] URL:url];
  111. }
  112. + (instancetype)storageForApp:(FIRApp *)app URL:(NSString *)url {
  113. NSString *bucket;
  114. if ([url isEqualToString:@""]) {
  115. bucket = @"";
  116. } else {
  117. FIRStoragePath *path;
  118. @try {
  119. path = [FIRStoragePath pathFromGSURI:url];
  120. } @catch (NSException *e) {
  121. [NSException raise:NSInternalInconsistencyException
  122. format:@"URI must be in the form of gs://<bucket>/"];
  123. }
  124. if (path.object != nil && ![path.object isEqualToString:@""]) {
  125. [NSException raise:NSInternalInconsistencyException
  126. format:@"Storage bucket cannot be initialized with a path"];
  127. }
  128. bucket = path.bucket;
  129. }
  130. // Retrieve the instance provider from the app's container to inject dependencies as needed.
  131. id<FIRStorageMultiBucketProvider> provider =
  132. FIR_COMPONENT(FIRStorageMultiBucketProvider, app.container);
  133. return [provider storageForBucket:bucket];
  134. }
  135. - (instancetype)initWithApp:(FIRApp *)app
  136. bucket:(NSString *)bucket
  137. auth:(nullable id<FIRAuthInterop>)auth
  138. appCheck:(nullable id<FIRAppCheckInterop>)appCheck {
  139. self = [super init];
  140. if (self) {
  141. _app = app;
  142. _auth = auth;
  143. _appCheck = appCheck;
  144. _storageBucket = bucket;
  145. _host = kFIRStorageHost;
  146. _scheme = kFIRStorageScheme;
  147. _port = @(kFIRStoragePort);
  148. _fetcherServiceForApp = nil; // Configured in `ensureConfigured()`
  149. // Must be a serial queue.
  150. _dispatchQueue = dispatch_queue_create("com.google.firebase.storage", DISPATCH_QUEUE_SERIAL);
  151. _maxDownloadRetryTime = 600.0;
  152. _maxDownloadRetryInterval =
  153. [FIRStorageUtils computeRetryIntervalFromRetryTime:_maxDownloadRetryTime];
  154. _maxOperationRetryTime = 120.0;
  155. _maxOperationRetryInterval =
  156. [FIRStorageUtils computeRetryIntervalFromRetryTime:_maxOperationRetryTime];
  157. _maxUploadRetryTime = 600.0;
  158. _maxUploadRetryInterval =
  159. [FIRStorageUtils computeRetryIntervalFromRetryTime:_maxUploadRetryTime];
  160. }
  161. return self;
  162. }
  163. - (instancetype)init {
  164. NSAssert(false, @"Storage cannot be directly instantiated, use "
  165. "Storage.storage() or Storage.storage(app:) instead");
  166. return nil;
  167. }
  168. #pragma mark - NSObject overrides
  169. - (instancetype)copyWithZone:(NSZone *)zone {
  170. FIRStorage *storage = [[[self class] allocWithZone:zone] initWithApp:_app
  171. bucket:_storageBucket
  172. auth:_auth
  173. appCheck:_appCheck];
  174. storage.callbackQueue = self.callbackQueue;
  175. return storage;
  176. }
  177. // Two FIRStorage objects are equal if they use the same app
  178. - (BOOL)isEqual:(id)object {
  179. if (self == object) {
  180. return YES;
  181. }
  182. if (![object isKindOfClass:[FIRStorage class]]) {
  183. return NO;
  184. }
  185. BOOL isEqualObject = [self isEqualToFIRStorage:(FIRStorage *)object];
  186. return isEqualObject;
  187. }
  188. - (BOOL)isEqualToFIRStorage:(FIRStorage *)storage {
  189. BOOL isEqual =
  190. [_app isEqual:storage.app] && [_storageBucket isEqualToString:storage.storageBucket];
  191. return isEqual;
  192. }
  193. - (NSUInteger)hash {
  194. NSUInteger hash = [_app hash] ^ [self.callbackQueue hash];
  195. return hash;
  196. }
  197. - (NSString *)description {
  198. return [NSString stringWithFormat:@"%@ %p: %@", [self class], self, _app];
  199. }
  200. #pragma mark - Retry time intervals
  201. - (void)setMaxUploadRetryTime:(NSTimeInterval)maxUploadRetryTime {
  202. @synchronized(self) {
  203. _maxUploadRetryTime = maxUploadRetryTime;
  204. _maxUploadRetryInterval =
  205. [FIRStorageUtils computeRetryIntervalFromRetryTime:maxUploadRetryTime];
  206. }
  207. }
  208. - (NSTimeInterval)maxDownloadRetryTime {
  209. @synchronized(self) {
  210. return _maxDownloadRetryTime;
  211. }
  212. }
  213. - (void)setMaxDownloadRetryTime:(NSTimeInterval)maxDownloadRetryTime {
  214. @synchronized(self) {
  215. _maxDownloadRetryTime = maxDownloadRetryTime;
  216. _maxDownloadRetryInterval =
  217. [FIRStorageUtils computeRetryIntervalFromRetryTime:maxDownloadRetryTime];
  218. }
  219. }
  220. - (NSTimeInterval)maxUploadRetryTime {
  221. @synchronized(self) {
  222. return _maxUploadRetryTime;
  223. }
  224. }
  225. - (void)setMaxOperationRetryTime:(NSTimeInterval)maxOperationRetryTime {
  226. @synchronized(self) {
  227. _maxOperationRetryTime = maxOperationRetryTime;
  228. _maxOperationRetryInterval =
  229. [FIRStorageUtils computeRetryIntervalFromRetryTime:maxOperationRetryTime];
  230. }
  231. }
  232. - (NSTimeInterval)maxOperationRetryTime {
  233. @synchronized(self) {
  234. return _maxOperationRetryTime;
  235. }
  236. }
  237. #pragma mark - Public methods
  238. - (FIRStorageReference *)reference {
  239. [self ensureConfigured];
  240. FIRStoragePath *path = [[FIRStoragePath alloc] initWithBucket:_storageBucket object:nil];
  241. return [[FIRStorageReference alloc] initWithStorage:self path:path];
  242. }
  243. - (FIRStorageReference *)referenceForURL:(NSString *)string {
  244. [self ensureConfigured];
  245. FIRStoragePath *path = [FIRStoragePath pathFromString:string];
  246. // If no default bucket exists (empty string), accept anything.
  247. if ([_storageBucket isEqual:@""]) {
  248. FIRStorageReference *reference = [[FIRStorageReference alloc] initWithStorage:self path:path];
  249. return reference;
  250. }
  251. // If there exists a default bucket, throw if provided a different bucket.
  252. if (![path.bucket isEqual:_storageBucket]) {
  253. NSString *const kInvalidBucketFormat =
  254. @"Provided bucket: %@ does not match the Storage bucket of the current instance: %@";
  255. [NSException raise:NSInvalidArgumentException
  256. format:kInvalidBucketFormat, path.bucket, _storageBucket];
  257. }
  258. FIRStorageReference *reference = [[FIRStorageReference alloc] initWithStorage:self path:path];
  259. return reference;
  260. }
  261. - (FIRStorageReference *)referenceWithPath:(NSString *)string {
  262. FIRStorageReference *reference = [[self reference] child:string];
  263. return reference;
  264. }
  265. - (dispatch_queue_t)callbackQueue {
  266. [self ensureConfigured];
  267. return _fetcherServiceForApp.callbackQueue;
  268. }
  269. - (void)setCallbackQueue:(dispatch_queue_t)callbackQueue {
  270. [self ensureConfigured];
  271. _fetcherServiceForApp.callbackQueue = callbackQueue;
  272. }
  273. - (void)useEmulatorWithHost:(NSString *)host port:(NSInteger)port {
  274. if (host.length == 0) {
  275. [NSException raise:NSInvalidArgumentException format:@"Cannot connect to nil or empty host."];
  276. }
  277. if (port < 0) {
  278. [NSException raise:NSInvalidArgumentException
  279. format:@"Port must be greater than or equal to zero."];
  280. }
  281. if (_fetcherServiceForApp != nil) {
  282. [NSException raise:NSInternalInconsistencyException
  283. format:@"Cannot connect to emulator after Storage SDK initialization. "
  284. @"Call useEmulator(host:port:) before creating a Storage "
  285. @"reference or trying to load data."];
  286. }
  287. _usesEmulator = YES;
  288. _scheme = @"http";
  289. _host = host;
  290. _port = @(port);
  291. }
  292. #pragma mark - Background tasks
  293. + (void)enableBackgroundTasks:(BOOL)isEnabled {
  294. [NSException raise:NSGenericException format:@"enableBackgroundTasks not implemented"];
  295. }
  296. - (NSArray<FIRStorageUploadTask *> *)uploadTasks {
  297. [NSException raise:NSGenericException format:@"getUploadTasks not implemented"];
  298. return nil;
  299. }
  300. - (NSArray<FIRStorageDownloadTask *> *)downloadTasks {
  301. [NSException raise:NSGenericException format:@"getDownloadTasks not implemented"];
  302. return nil;
  303. }
  304. - (void)ensureConfigured {
  305. if (!_fetcherServiceForApp) {
  306. _fetcherServiceForApp = [FIRStorage fetcherServiceForApp:_app
  307. bucket:_storageBucket
  308. auth:_auth
  309. appCheck:_appCheck];
  310. if (_usesEmulator) {
  311. _fetcherServiceForApp.allowLocalhostRequest = YES;
  312. _fetcherServiceForApp.allowedInsecureSchemes = @[ @"http" ];
  313. }
  314. }
  315. }
  316. @end