StorageReference.swift 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617
  1. // Copyright 2022 Google LLC
  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 Foundation
  15. /// `StorageReference` represents a reference to a Google Cloud Storage object. Developers can
  16. /// upload and download objects, as well as get/set object metadata, and delete an object at the
  17. /// path. See the [Cloud docs](https://cloud.google.com/storage/) for more details.
  18. @objc(FIRStorageReference) open class StorageReference: NSObject {
  19. // MARK: - Public APIs
  20. /// The `Storage` service object which created this reference.
  21. @objc public let storage: Storage
  22. /// The name of the Google Cloud Storage bucket associated with this reference.
  23. /// For example, in `gs://bucket/path/to/object.txt`, the bucket would be 'bucket'.
  24. @objc public var bucket: String {
  25. return path.bucket
  26. }
  27. /// The full path to this object, not including the Google Cloud Storage bucket.
  28. /// In `gs://bucket/path/to/object.txt`, the full path would be: `path/to/object.txt`.
  29. @objc public var fullPath: String {
  30. return path.object ?? ""
  31. }
  32. /// The short name of the object associated with this reference.
  33. ///
  34. /// In `gs://bucket/path/to/object.txt`, the name of the object would be `object.txt`.
  35. @objc public var name: String {
  36. return (path.object as? NSString)?.lastPathComponent ?? ""
  37. }
  38. /// Creates a new `StorageReference` pointing to the root object.
  39. /// - Returns: A new `StorageReference` pointing to the root object.
  40. @objc open func root() -> StorageReference {
  41. return StorageReference(storage: storage, path: path.root())
  42. }
  43. /// Creates a new `StorageReference` pointing to the parent of the current reference
  44. /// or `nil` if this instance references the root location.
  45. /// ```
  46. /// For example:
  47. /// path = foo/bar/baz parent = foo/bar
  48. /// path = foo parent = (root)
  49. /// path = (root) parent = nil
  50. /// ```
  51. /// - Returns: A new `StorageReference` pointing to the parent of the current reference.
  52. @objc open func parent() -> StorageReference? {
  53. guard let parentPath = path.parent() else {
  54. return nil
  55. }
  56. return StorageReference(storage: storage, path: parentPath)
  57. }
  58. /// Creates a new `StorageReference` pointing to a child object of the current reference.
  59. /// ```
  60. /// path = foo child = bar newPath = foo/bar
  61. /// path = foo/bar child = baz ntask.impl.snapshotwPath = foo/bar/baz
  62. /// All leading and trailing slashes will be removed, and consecutive slashes will be
  63. /// compressed to single slashes. For example:
  64. /// child = /foo/bar newPath = foo/bar
  65. /// child = foo/bar/ newPath = foo/bar
  66. /// child = foo///bar newPath = foo/bar
  67. /// ```
  68. ///
  69. /// - Parameter path: The path to append to the current path.
  70. /// - Returns: A new `StorageReference` pointing to a child location of the current reference.
  71. @objc(child:) open func child(_ path: String) -> StorageReference {
  72. return StorageReference(storage: storage, path: self.path.child(path))
  73. }
  74. // MARK: - Uploads
  75. /// Asynchronously uploads data to the currently specified `StorageReference`,
  76. /// without additional metadata.
  77. /// This is not recommended for large files, and one should instead upload a file from disk.
  78. /// - Parameters:
  79. /// - uploadData: The data to upload.
  80. /// - metadata: `StorageMetadata` containing additional information (MIME type, etc.)
  81. /// about the object being uploaded.
  82. /// - Returns: An instance of `StorageUploadTask`, which can be used to monitor or manage the
  83. /// upload.
  84. @objc(putData:metadata:)
  85. @discardableResult
  86. open func putData(_ uploadData: Data, metadata: StorageMetadata? = nil) -> StorageUploadTask {
  87. return putData(uploadData, metadata: metadata, completion: nil)
  88. }
  89. /// Asynchronously uploads data to the currently specified `StorageReference`.
  90. /// This is not recommended for large files, and one should instead upload a file from disk.
  91. /// - Parameter uploadData The data to upload.
  92. /// - Returns: An instance of `StorageUploadTask`, which can be used to monitor or manage the
  93. /// upload.
  94. @objc(putData:) @discardableResult open func __putData(_ uploadData: Data) -> StorageUploadTask {
  95. return putData(uploadData, metadata: nil, completion: nil)
  96. }
  97. /// Asynchronously uploads data to the currently specified `StorageReference`.
  98. /// This is not recommended for large files, and one should instead upload a file from disk.
  99. /// - Parameters:
  100. /// - uploadData: The data to upload.
  101. /// - metadata: `StorageMetadata` containing additional information (MIME type, etc.)
  102. /// about the object being uploaded.
  103. /// - completion: A closure that either returns the object metadata on success,
  104. /// or an error on failure.
  105. /// - Returns: An instance of `StorageUploadTask`, which can be used to monitor or manage the
  106. /// upload.
  107. @objc(putData:metadata:completion:) @discardableResult
  108. open func putData(_ uploadData: Data,
  109. metadata: StorageMetadata? = nil,
  110. completion: ((_: StorageMetadata?, _: Error?) -> Void)?) -> StorageUploadTask {
  111. let putMetadata = metadata ?? StorageMetadata()
  112. if let path = path.object {
  113. putMetadata.path = path
  114. putMetadata.name = (path as NSString).lastPathComponent as String
  115. }
  116. let task = StorageUploadTask(reference: self,
  117. service: storage.fetcherServiceForApp,
  118. queue: storage.dispatchQueue,
  119. data: uploadData,
  120. metadata: putMetadata)
  121. startAndObserveUploadTask(task: task, completion: completion)
  122. return task
  123. }
  124. /// Asynchronously uploads a file to the currently specified `StorageReference`.
  125. /// - Parameters:
  126. /// - fileURL: A URL representing the system file path of the object to be uploaded.
  127. /// - metadata: `StorageMetadata` containing additional information (MIME type, etc.)
  128. /// about the object being uploaded.
  129. /// - Returns: An instance of `StorageUploadTask`, which can be used to monitor or manage the
  130. /// upload.
  131. @objc(putFile:metadata:) @discardableResult
  132. open func putFile(from fileURL: URL, metadata: StorageMetadata? = nil) -> StorageUploadTask {
  133. return putFile(from: fileURL, metadata: metadata, completion: nil)
  134. }
  135. /// Asynchronously uploads a file to the currently specified `StorageReference`,
  136. /// without additional metadata.
  137. /// @param fileURL A URL representing the system file path of the object to be uploaded.
  138. /// @return An instance of StorageUploadTask, which can be used to monitor or manage the upload.
  139. @objc(putFile:) @discardableResult open func __putFile(from fileURL: URL) -> StorageUploadTask {
  140. return putFile(from: fileURL, metadata: nil, completion: nil)
  141. }
  142. /// Asynchronously uploads a file to the currently specified `StorageReference`.
  143. /// - Parameters:
  144. /// - fileURL: A URL representing the system file path of the object to be uploaded.
  145. /// - metadata: `StorageMetadata` containing additional information (MIME type, etc.)
  146. /// about the object being uploaded.
  147. /// - completion: A completion block that either returns the object metadata on success,
  148. /// or an error on failure.
  149. /// - Returns: An instance of `StorageUploadTask`, which can be used to monitor or manage the
  150. /// upload.
  151. @objc(putFile:metadata:completion:) @discardableResult
  152. open func putFile(from fileURL: URL,
  153. metadata: StorageMetadata? = nil,
  154. completion: ((_: StorageMetadata?, _: Error?) -> Void)?) -> StorageUploadTask {
  155. let putMetadata: StorageMetadata = metadata ?? StorageMetadata()
  156. if let path = path.object {
  157. putMetadata.path = path
  158. putMetadata.name = (path as NSString).lastPathComponent as String
  159. }
  160. let task = StorageUploadTask(reference: self,
  161. service: storage.fetcherServiceForApp,
  162. queue: storage.dispatchQueue,
  163. file: fileURL,
  164. metadata: putMetadata)
  165. startAndObserveUploadTask(task: task, completion: completion)
  166. return task
  167. }
  168. // MARK: - Downloads
  169. /// Asynchronously downloads the object at the `StorageReference` to a `Data` instance in memory.
  170. /// A `Data` buffer of the provided max size will be allocated, so ensure that the device has
  171. /// enough free
  172. /// memory to complete the download. For downloading large files, `write(toFile:)` may be a better
  173. /// option.
  174. /// - Parameters:
  175. /// - maxSize: The maximum size in bytes to download. If the download exceeds this size,
  176. /// the task will be cancelled and an error will be returned.
  177. /// - completion: A completion block that either returns the object data on success,
  178. /// or an error on failure.
  179. /// - Returns: A `StorageDownloadTask` that can be used to monitor or manage the download.
  180. @objc(dataWithMaxSize:completion:) @discardableResult
  181. open func getData(maxSize: Int64,
  182. completion: @escaping ((_: Data?, _: Error?) -> Void)) -> StorageDownloadTask {
  183. let fetcherService = storage.fetcherServiceForApp
  184. let task = StorageDownloadTask(reference: self,
  185. service: fetcherService,
  186. queue: storage.dispatchQueue,
  187. file: nil)
  188. task.completionData = completion
  189. let callbackQueue = fetcherService.callbackQueue ?? DispatchQueue.main
  190. task.observe(.success) { snapshot in
  191. let error = self.checkSizeOverflow(task: snapshot.task, maxSize: maxSize)
  192. callbackQueue.async {
  193. if let completion = task.completionData {
  194. let data = error == nil ? task.downloadData : nil
  195. completion(data, error)
  196. task.completionData = nil
  197. }
  198. }
  199. }
  200. task.observe(.failure) { snapshot in
  201. callbackQueue.async {
  202. task.completionData?(nil, snapshot.error)
  203. task.completionData = nil
  204. }
  205. }
  206. task.observe(.progress) { snapshot in
  207. if let error = self.checkSizeOverflow(task: snapshot.task, maxSize: maxSize) {
  208. task.cancel(withError: error)
  209. }
  210. }
  211. task.enqueue()
  212. return task
  213. }
  214. /// Asynchronously retrieves a long lived download URL with a revokable token.
  215. /// This can be used to share the file with others, but can be revoked by a developer
  216. /// in the Firebase Console.
  217. /// - Parameter completion: A completion block that either returns the URL on success,
  218. /// or an error on failure.
  219. @objc(downloadURLWithCompletion:)
  220. open func downloadURL(completion: @escaping ((_: URL?, _: Error?) -> Void)) {
  221. let fetcherService = storage.fetcherServiceForApp
  222. let task = StorageGetDownloadURLTask(reference: self,
  223. fetcherService: fetcherService,
  224. queue: storage.dispatchQueue,
  225. completion: completion)
  226. task.enqueue()
  227. }
  228. /// Asynchronously retrieves a long lived download URL with a revokable token.
  229. /// This can be used to share the file with others, but can be revoked by a developer
  230. /// in the Firebase Console.
  231. /// - Throws: An error if the download URL could not be retrieved.
  232. /// - Returns: The URL on success.
  233. @available(iOS 13, tvOS 13, macOS 10.15, watchOS 8, *)
  234. open func downloadURL() async throws -> URL {
  235. return try await withCheckedThrowingContinuation { continuation in
  236. self.downloadURL { result in
  237. continuation.resume(with: result)
  238. }
  239. }
  240. }
  241. /// Asynchronously downloads the object at the current path to a specified system filepath.
  242. /// - Parameter fileURL: A file system URL representing the path the object should be downloaded
  243. /// to.
  244. /// - Returns A `StorageDownloadTask` that can be used to monitor or manage the download.
  245. @objc(writeToFile:) @discardableResult
  246. open func write(toFile fileURL: URL) -> StorageDownloadTask {
  247. return write(toFile: fileURL, completion: nil)
  248. }
  249. /// Asynchronously downloads the object at the current path to a specified system filepath.
  250. /// - Parameters:
  251. /// - fileURL: A file system URL representing the path the object should be downloaded to.
  252. /// - completion: A closure that fires when the file download completes, passed either
  253. /// a URL pointing to the file path of the downloaded file on success,
  254. /// or an error on failure.
  255. /// - Returns: A `StorageDownloadTask` that can be used to monitor or manage the download.
  256. @objc(writeToFile:completion:) @discardableResult
  257. open func write(toFile fileURL: URL,
  258. completion: ((_: URL?, _: Error?) -> Void)?) -> StorageDownloadTask {
  259. let fetcherService = storage.fetcherServiceForApp
  260. let task = StorageDownloadTask(reference: self,
  261. service: fetcherService,
  262. queue: storage.dispatchQueue,
  263. file: fileURL)
  264. if let completion {
  265. task.completionURL = completion
  266. let callbackQueue = fetcherService.callbackQueue ?? DispatchQueue.main
  267. task.observe(.success) { snapshot in
  268. callbackQueue.async {
  269. task.completionURL?(fileURL, nil)
  270. task.completionURL = nil
  271. }
  272. }
  273. task.observe(.failure) { snapshot in
  274. callbackQueue.async {
  275. task.completionURL?(nil, snapshot.error)
  276. task.completionURL = nil
  277. }
  278. }
  279. }
  280. task.enqueue()
  281. return task
  282. }
  283. // MARK: - List Support
  284. /// Lists all items (files) and prefixes (folders) under this `StorageReference`.
  285. ///
  286. /// This is a helper method for calling `list()` repeatedly until there are no more results.
  287. ///
  288. /// Consistency of the result is not guaranteed if objects are inserted or removed while this
  289. /// operation is executing. All results are buffered in memory.
  290. ///
  291. /// `listAll(completion:)` is only available for projects using Firebase Rules Version 2.
  292. /// - Parameter completion: A completion handler that will be invoked with all items and prefixes
  293. /// under
  294. /// the current `StorageReference`.
  295. @objc(listAllWithCompletion:)
  296. open func listAll(completion: @escaping ((_: StorageListResult?, _: Error?) -> Void)) {
  297. let fetcherService = storage.fetcherServiceForApp
  298. var prefixes = [StorageReference]()
  299. var items = [StorageReference]()
  300. weak var weakSelf = self
  301. var paginatedCompletion: ((_: StorageListResult?, _: Error?) -> Void)?
  302. paginatedCompletion = { (_ listResult: StorageListResult?, _ error: Error?) in
  303. if let error {
  304. completion(nil, error)
  305. return
  306. }
  307. guard let strongSelf = weakSelf else { return }
  308. guard let listResult = listResult else {
  309. fatalError("internal error: both listResult and error are nil")
  310. }
  311. prefixes.append(contentsOf: listResult.prefixes)
  312. items.append(contentsOf: listResult.items)
  313. if let pageToken = listResult.pageToken {
  314. let nextPage = StorageListTask(reference: strongSelf,
  315. fetcherService: fetcherService,
  316. queue: strongSelf.storage.dispatchQueue,
  317. pageSize: nil,
  318. previousPageToken: pageToken,
  319. completion: paginatedCompletion)
  320. nextPage.enqueue()
  321. } else {
  322. let result = StorageListResult(withPrefixes: prefixes, items: items, pageToken: nil)
  323. // Break the retain cycle we set up indirectly by passing the callback to `nextPage`.
  324. paginatedCompletion = nil
  325. completion(result, nil)
  326. }
  327. }
  328. let task = StorageListTask(reference: self,
  329. fetcherService: fetcherService,
  330. queue: storage.dispatchQueue,
  331. pageSize: nil,
  332. previousPageToken: nil,
  333. completion: paginatedCompletion)
  334. task.enqueue()
  335. }
  336. /// Lists all items (files) and prefixes (folders) under this StorageReference.
  337. /// This is a helper method for calling list() repeatedly until there are no more results.
  338. /// Consistency of the result is not guaranteed if objects are inserted or removed while this
  339. /// operation is executing. All results are buffered in memory.
  340. /// `listAll()` is only available for projects using Firebase Rules Version 2.
  341. /// - Throws: An error if the list operation failed.
  342. /// - Returns: All items and prefixes under the current `StorageReference`.
  343. @available(iOS 13, tvOS 13, macOS 10.15, watchOS 8, *)
  344. open func listAll() async throws -> StorageListResult {
  345. return try await withCheckedThrowingContinuation { continuation in
  346. self.listAll { result in
  347. continuation.resume(with: result)
  348. }
  349. }
  350. }
  351. /// List up to `maxResults` items (files) and prefixes (folders) under this StorageReference.
  352. ///
  353. /// "/" is treated as a path delimiter. Firebase Storage does not support unsupported object
  354. /// paths that end with "/" or contain two consecutive "/"s. All invalid objects in GCS will be
  355. /// filtered.
  356. ///
  357. /// Only available for projects using Firebase Rules Version 2.
  358. /// - Parameters:
  359. /// - maxResults: The maximum number of results to return in a single page. Must be
  360. /// greater than 0 and at most 1000.
  361. /// - completion: A completion handler that will be invoked with up to `maxResults` items and
  362. /// prefixes under the current `StorageReference`.
  363. @objc(listWithMaxResults:completion:)
  364. open func list(maxResults: Int64,
  365. completion: @escaping ((_: StorageListResult?, _: Error?) -> Void)) {
  366. if maxResults <= 0 || maxResults > 1000 {
  367. completion(nil,
  368. NSError(domain: StorageErrorDomain,
  369. code: StorageErrorCode.invalidArgument.rawValue,
  370. userInfo: [NSLocalizedDescriptionKey:
  371. "Argument 'maxResults' must be between 1 and 1000 inclusive."]))
  372. } else {
  373. let fetcherService = storage.fetcherServiceForApp
  374. let task = StorageListTask(reference: self,
  375. fetcherService: fetcherService,
  376. queue: storage.dispatchQueue,
  377. pageSize: maxResults,
  378. previousPageToken: nil,
  379. completion: completion)
  380. task.enqueue()
  381. }
  382. }
  383. /// Resumes a previous call to `list(maxResults:completion:)`, starting after a pagination token.
  384. ///
  385. /// Returns the next set of items (files) and prefixes (folders) under this `StorageReference`.
  386. ///
  387. /// "/" is treated as a path delimiter. Storage does not support unsupported object
  388. /// paths that end with "/" or contain two consecutive "/"s. All invalid objects in GCS will be
  389. /// filtered.
  390. ///
  391. /// `list(maxResults:pageToken:completion:)`is only available for projects using Firebase Rules
  392. /// Version 2.
  393. /// - Parameters:
  394. /// - maxResults: The maximum number of results to return in a single page. Must be greater
  395. /// than 0 and at most 1000.
  396. /// - pageToken: A page token from a previous call to list.
  397. /// - completion: A completion handler that will be invoked with the next items and prefixes
  398. /// under the current StorageReference.
  399. @objc(listWithMaxResults:pageToken:completion:)
  400. open func list(maxResults: Int64,
  401. pageToken: String,
  402. completion: @escaping ((_: StorageListResult?, _: Error?) -> Void)) {
  403. if maxResults <= 0 || maxResults > 1000 {
  404. completion(nil,
  405. NSError(domain: StorageErrorDomain,
  406. code: StorageErrorCode.invalidArgument.rawValue,
  407. userInfo: [NSLocalizedDescriptionKey:
  408. "Argument 'maxResults' must be between 1 and 1000 inclusive."]))
  409. } else {
  410. let fetcherService = storage.fetcherServiceForApp
  411. let task = StorageListTask(reference: self,
  412. fetcherService: fetcherService,
  413. queue: storage.dispatchQueue,
  414. pageSize: maxResults,
  415. previousPageToken: pageToken,
  416. completion: completion)
  417. task.enqueue()
  418. }
  419. }
  420. // MARK: - Metadata Operations
  421. /// Retrieves metadata associated with an object at the current path.
  422. /// - Parameter completion: A completion block which returns the object metadata on success,
  423. /// or an error on failure.
  424. @objc(metadataWithCompletion:)
  425. open func getMetadata(completion: @escaping ((_: StorageMetadata?, _: Error?) -> Void)) {
  426. let fetcherService = storage.fetcherServiceForApp
  427. let task = StorageGetMetadataTask(reference: self,
  428. fetcherService: fetcherService,
  429. queue: storage.dispatchQueue,
  430. completion: completion)
  431. task.enqueue()
  432. }
  433. /// Retrieves metadata associated with an object at the current path.
  434. /// - Throws: An error if the object metadata could not be retrieved.
  435. /// - Returns: The object metadata on success.
  436. @available(iOS 13, tvOS 13, macOS 10.15, watchOS 8, *)
  437. open func getMetadata() async throws -> StorageMetadata {
  438. return try await withCheckedThrowingContinuation { continuation in
  439. self.getMetadata { result in
  440. continuation.resume(with: result)
  441. }
  442. }
  443. }
  444. /// Updates the metadata associated with an object at the current path.
  445. /// - Parameters:
  446. /// - metadata: A `StorageMetadata` object with the metadata to update.
  447. /// - completion: A completion block which returns the `StorageMetadata` on success,
  448. /// or an error on failure.
  449. @objc(updateMetadata:completion:)
  450. open func updateMetadata(_ metadata: StorageMetadata,
  451. completion: ((_: StorageMetadata?, _: Error?) -> Void)?) {
  452. let fetcherService = storage.fetcherServiceForApp
  453. let task = StorageUpdateMetadataTask(reference: self,
  454. fetcherService: fetcherService,
  455. queue: storage.dispatchQueue,
  456. metadata: metadata,
  457. completion: completion)
  458. task.enqueue()
  459. }
  460. /// Updates the metadata associated with an object at the current path.
  461. /// - Parameter metadata: A `StorageMetadata` object with the metadata to update.
  462. /// - Throws: An error if the metadata update operation failed.
  463. /// - Returns: The object metadata on success.
  464. @available(iOS 13, tvOS 13, macOS 10.15, watchOS 8, *)
  465. open func updateMetadata(_ metadata: StorageMetadata) async throws -> StorageMetadata {
  466. return try await withCheckedThrowingContinuation { continuation in
  467. self.updateMetadata(metadata) { result in
  468. continuation.resume(with: result)
  469. }
  470. }
  471. }
  472. // MARK: - Delete
  473. /// Deletes the object at the current path.
  474. /// - Parameter completion: A completion block which returns a nonnull error on failure.
  475. @objc(deleteWithCompletion:)
  476. open func delete(completion: ((_: Error?) -> Void)?) {
  477. let fetcherService = storage.fetcherServiceForApp
  478. let task = StorageDeleteTask(reference: self,
  479. fetcherService: fetcherService,
  480. queue: storage.dispatchQueue,
  481. completion: completion)
  482. task.enqueue()
  483. }
  484. /// Deletes the object at the current path.
  485. /// - Throws: An error if the delete operation failed.
  486. @available(iOS 13, tvOS 13, macOS 10.15, watchOS 8, *)
  487. open func delete() async throws {
  488. return try await withCheckedThrowingContinuation { continuation in
  489. self.delete { error in
  490. if let error {
  491. continuation.resume(throwing: error)
  492. } else {
  493. continuation.resume()
  494. }
  495. }
  496. }
  497. }
  498. // MARK: - NSObject overrides
  499. /// NSObject override
  500. @objc override open func copy() -> Any {
  501. return StorageReference(storage: storage, path: path)
  502. }
  503. /// NSObject override
  504. @objc override open func isEqual(_ object: Any?) -> Bool {
  505. guard let ref = object as? StorageReference else {
  506. return false
  507. }
  508. return storage == ref.storage && path == ref.path
  509. }
  510. /// NSObject override
  511. @objc override public var hash: Int {
  512. return storage.hash ^ path.bucket.hashValue
  513. }
  514. /// NSObject override
  515. @objc override public var description: String {
  516. return "gs://\(path.bucket)/\(path.object ?? "")"
  517. }
  518. // MARK: - Internal APIs
  519. /// The current path which points to an object in the Google Cloud Storage bucket.
  520. let path: StoragePath
  521. override init() {
  522. storage = Storage.storage()
  523. let storageBucket = storage.app.options.storageBucket!
  524. path = StoragePath(with: storageBucket)
  525. }
  526. init(storage: Storage, path: StoragePath) {
  527. self.storage = storage
  528. self.path = path
  529. }
  530. /// For maxSize API, return an error if the size is exceeded.
  531. private func checkSizeOverflow(task: StorageTask, maxSize: Int64) -> NSError? {
  532. if task.progress.totalUnitCount > maxSize || task.progress.completedUnitCount > maxSize {
  533. return StorageErrorCode.error(withCode: .downloadSizeExceeded,
  534. infoDictionary: [
  535. "totalSize": task.progress.totalUnitCount,
  536. "maxAllowedSize": maxSize,
  537. ])
  538. }
  539. return nil
  540. }
  541. private func startAndObserveUploadTask(task: StorageUploadTask,
  542. completion: ((_: StorageMetadata?, _: Error?) -> Void)?) {
  543. if let completion {
  544. task.completionMetadata = completion
  545. let callbackQueue = storage.fetcherServiceForApp.callbackQueue ?? DispatchQueue.main
  546. task.observe(.success) { snapshot in
  547. callbackQueue.async {
  548. task.completionMetadata?(snapshot.metadata, nil)
  549. task.completionMetadata = nil
  550. }
  551. }
  552. task.observe(.failure) { snapshot in
  553. callbackQueue.async {
  554. task.completionMetadata?(nil, snapshot.error)
  555. task.completionMetadata = nil
  556. }
  557. }
  558. }
  559. task.enqueue()
  560. }
  561. }