FIRStorage.m 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  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/FIRStorage.h>
  15. #import <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 <FirebaseAuthInterop/FIRAuthInterop.h>
  24. #import <FirebaseCore/FIRAppInternal.h>
  25. #import <FirebaseCore/FIRComponentContainer.h>
  26. #import <FirebaseCore/FIROptions.h>
  27. #import <GTMSessionFetcher/GTMSessionFetcher.h>
  28. #import <GTMSessionFetcher/GTMSessionFetcherLogging.h>
  29. static NSMutableDictionary<
  30. NSString * /* app name */,
  31. NSMutableDictionary<NSString * /* bucket */, GTMSessionFetcherService *> *> *_fetcherServiceMap;
  32. static GTMSessionFetcherRetryBlock _retryWhenOffline;
  33. @interface FIRStorage () {
  34. /// Stored Auth reference, if it exists. This needs to be stored for `copyWithZone:`.
  35. id<FIRAuthInterop> _Nullable _auth;
  36. }
  37. @end
  38. @implementation FIRStorage
  39. + (void)initialize {
  40. static dispatch_once_t onceToken;
  41. dispatch_once(&onceToken, ^{
  42. _retryWhenOffline = ^(BOOL suggestedWillRetry, NSError *GTM_NULLABLE_TYPE error,
  43. GTMSessionFetcherRetryResponse response) {
  44. bool shouldRetry = suggestedWillRetry;
  45. // GTMSessionFetcher does not consider being offline a retryable error, but we do, so we
  46. // special-case it here.
  47. if (!shouldRetry && error) {
  48. shouldRetry = error.code == NSURLErrorNotConnectedToInternet;
  49. }
  50. response(shouldRetry);
  51. };
  52. _fetcherServiceMap = [[NSMutableDictionary alloc] init];
  53. });
  54. }
  55. + (GTMSessionFetcherService *)fetcherServiceForApp:(FIRApp *)app
  56. bucket:(NSString *)bucket
  57. auth:(nullable id<FIRAuthInterop>)auth {
  58. @synchronized(_fetcherServiceMap) {
  59. NSMutableDictionary *bucketMap = _fetcherServiceMap[app.name];
  60. if (!bucketMap) {
  61. bucketMap = [[NSMutableDictionary alloc] init];
  62. _fetcherServiceMap[app.name] = bucketMap;
  63. }
  64. GTMSessionFetcherService *fetcherService = bucketMap[bucket];
  65. if (!fetcherService) {
  66. fetcherService = [[GTMSessionFetcherService alloc] init];
  67. [fetcherService setRetryEnabled:YES];
  68. [fetcherService setRetryBlock:_retryWhenOffline];
  69. FIRStorageTokenAuthorizer *authorizer =
  70. [[FIRStorageTokenAuthorizer alloc] initWithGoogleAppID:app.options.googleAppID
  71. fetcherService:fetcherService
  72. authProvider:auth];
  73. [fetcherService setAuthorizer:authorizer];
  74. bucketMap[bucket] = fetcherService;
  75. }
  76. return fetcherService;
  77. }
  78. }
  79. + (void)setGTMSessionFetcherLoggingEnabled:(BOOL)isLoggingEnabled {
  80. [GTMSessionFetcher setLoggingEnabled:isLoggingEnabled];
  81. }
  82. + (instancetype)storage {
  83. return [self storageForApp:[FIRApp defaultApp]];
  84. }
  85. + (instancetype)storageForApp:(FIRApp *)app {
  86. if (app.options.storageBucket) {
  87. NSString *url = [app.options.storageBucket isEqualToString:@""]
  88. ? @""
  89. : [@"gs://" stringByAppendingString:app.options.storageBucket];
  90. return [self storageForApp:app URL:url];
  91. } else {
  92. NSString *const kAppNotConfiguredMessage =
  93. @"No default Storage bucket found. Did you configure Firebase Storage properly?";
  94. [NSException raise:NSInvalidArgumentException format:kAppNotConfiguredMessage];
  95. return nil;
  96. }
  97. }
  98. + (instancetype)storageWithURL:(NSString *)url {
  99. return [self storageForApp:[FIRApp defaultApp] URL:url];
  100. }
  101. + (instancetype)storageForApp:(FIRApp *)app URL:(NSString *)url {
  102. NSString *bucket;
  103. if ([url isEqualToString:@""]) {
  104. bucket = @"";
  105. } else {
  106. FIRStoragePath *path;
  107. @try {
  108. path = [FIRStoragePath pathFromGSURI:url];
  109. } @catch (NSException *e) {
  110. [NSException raise:NSInternalInconsistencyException
  111. format:@"URI must be in the form of gs://<bucket>/"];
  112. }
  113. if (path.object != nil && ![path.object isEqualToString:@""]) {
  114. [NSException raise:NSInternalInconsistencyException
  115. format:@"Storage bucket cannot be initialized with a path"];
  116. }
  117. bucket = path.bucket;
  118. }
  119. // Retrieve the instance provider from the app's container to inject dependencies as needed.
  120. id<FIRStorageMultiBucketProvider> provider =
  121. FIR_COMPONENT(FIRStorageMultiBucketProvider, app.container);
  122. return [provider storageForBucket:bucket];
  123. }
  124. - (instancetype)initWithApp:(FIRApp *)app
  125. bucket:(NSString *)bucket
  126. auth:(nullable id<FIRAuthInterop>)auth {
  127. self = [super init];
  128. if (self) {
  129. _app = app;
  130. _auth = auth;
  131. _storageBucket = bucket;
  132. _dispatchQueue = dispatch_queue_create("com.google.firebase.storage", DISPATCH_QUEUE_SERIAL);
  133. _fetcherServiceForApp = [FIRStorage fetcherServiceForApp:_app bucket:bucket auth:auth];
  134. _maxDownloadRetryTime = 600.0;
  135. _maxOperationRetryTime = 120.0;
  136. _maxUploadRetryTime = 600.0;
  137. }
  138. return self;
  139. }
  140. - (instancetype)init {
  141. NSAssert(false, @"Storage cannot be directly instantiated, use "
  142. "Storage.storage() or Storage.storage(app:) instead");
  143. return nil;
  144. }
  145. #pragma mark - NSObject overrides
  146. - (instancetype)copyWithZone:(NSZone *)zone {
  147. FIRStorage *storage = [[[self class] allocWithZone:zone] initWithApp:_app
  148. bucket:_storageBucket
  149. auth:_auth];
  150. storage.callbackQueue = self.callbackQueue;
  151. return storage;
  152. }
  153. // Two FIRStorage objects are equal if they use the same app
  154. - (BOOL)isEqual:(id)object {
  155. if (self == object) {
  156. return YES;
  157. }
  158. if (![object isKindOfClass:[FIRStorage class]]) {
  159. return NO;
  160. }
  161. BOOL isEqualObject = [self isEqualToFIRStorage:(FIRStorage *)object];
  162. return isEqualObject;
  163. }
  164. - (BOOL)isEqualToFIRStorage:(FIRStorage *)storage {
  165. BOOL isEqual =
  166. [_app isEqual:storage.app] && [_storageBucket isEqualToString:storage.storageBucket];
  167. return isEqual;
  168. }
  169. - (NSUInteger)hash {
  170. NSUInteger hash = [_app hash] ^ [self.callbackQueue hash];
  171. return hash;
  172. }
  173. - (NSString *)description {
  174. return [NSString stringWithFormat:@"%@ %p: %@", [self class], self, _app];
  175. }
  176. #pragma mark - Public methods
  177. - (FIRStorageReference *)reference {
  178. FIRStoragePath *path = [[FIRStoragePath alloc] initWithBucket:_storageBucket object:nil];
  179. return [[FIRStorageReference alloc] initWithStorage:self path:path];
  180. }
  181. - (FIRStorageReference *)referenceForURL:(NSString *)string {
  182. FIRStoragePath *path = [FIRStoragePath pathFromString:string];
  183. // If no default bucket exists (empty string), accept anything.
  184. if ([_storageBucket isEqual:@""]) {
  185. FIRStorageReference *reference = [[FIRStorageReference alloc] initWithStorage:self path:path];
  186. return reference;
  187. }
  188. // If there exists a default bucket, throw if provided a different bucket.
  189. if (![path.bucket isEqual:_storageBucket]) {
  190. NSString *const kInvalidBucketFormat =
  191. @"Provided bucket: %@ does not match the Storage bucket of the current instance: %@";
  192. [NSException raise:NSInvalidArgumentException
  193. format:kInvalidBucketFormat, path.bucket, _storageBucket];
  194. }
  195. FIRStorageReference *reference = [[FIRStorageReference alloc] initWithStorage:self path:path];
  196. return reference;
  197. }
  198. - (FIRStorageReference *)referenceWithPath:(NSString *)string {
  199. FIRStorageReference *reference = [[self reference] child:string];
  200. return reference;
  201. }
  202. - (dispatch_queue_t)callbackQueue {
  203. return _fetcherServiceForApp.callbackQueue;
  204. }
  205. - (void)setCallbackQueue:(dispatch_queue_t)callbackQueue {
  206. _fetcherServiceForApp.callbackQueue = callbackQueue;
  207. }
  208. #pragma mark - Background tasks
  209. + (void)enableBackgroundTasks:(BOOL)isEnabled {
  210. [NSException raise:NSGenericException format:@"enableBackgroundTasks not implemented"];
  211. }
  212. - (NSArray<FIRStorageUploadTask *> *)uploadTasks {
  213. [NSException raise:NSGenericException format:@"getUploadTasks not implemented"];
  214. return nil;
  215. }
  216. - (NSArray<FIRStorageDownloadTask *> *)downloadTasks {
  217. [NSException raise:NSGenericException format:@"getDownloadTasks not implemented"];
  218. return nil;
  219. }
  220. @end