FIRStorage.m 11 KB

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