| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617 |
- // Copyright 2022 Google LLC
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- import Foundation
- /// `StorageReference` represents a reference to a Google Cloud Storage object. Developers can
- /// upload and download objects, as well as get/set object metadata, and delete an object at the
- /// path. See the [Cloud docs](https://cloud.google.com/storage/) for more details.
- @objc(FIRStorageReference) open class StorageReference: NSObject {
- // MARK: - Public APIs
- /// The `Storage` service object which created this reference.
- @objc public let storage: Storage
- /// The name of the Google Cloud Storage bucket associated with this reference.
- /// For example, in `gs://bucket/path/to/object.txt`, the bucket would be 'bucket'.
- @objc public var bucket: String {
- return path.bucket
- }
- /// The full path to this object, not including the Google Cloud Storage bucket.
- /// In `gs://bucket/path/to/object.txt`, the full path would be: `path/to/object.txt`.
- @objc public var fullPath: String {
- return path.object ?? ""
- }
- /// The short name of the object associated with this reference.
- ///
- /// In `gs://bucket/path/to/object.txt`, the name of the object would be `object.txt`.
- @objc public var name: String {
- return (path.object as? NSString)?.lastPathComponent ?? ""
- }
- /// Creates a new `StorageReference` pointing to the root object.
- /// - Returns: A new `StorageReference` pointing to the root object.
- @objc open func root() -> StorageReference {
- return StorageReference(storage: storage, path: path.root())
- }
- /// Creates a new `StorageReference` pointing to the parent of the current reference
- /// or `nil` if this instance references the root location.
- /// ```
- /// For example:
- /// path = foo/bar/baz parent = foo/bar
- /// path = foo parent = (root)
- /// path = (root) parent = nil
- /// ```
- /// - Returns: A new `StorageReference` pointing to the parent of the current reference.
- @objc open func parent() -> StorageReference? {
- guard let parentPath = path.parent() else {
- return nil
- }
- return StorageReference(storage: storage, path: parentPath)
- }
- /// Creates a new `StorageReference` pointing to a child object of the current reference.
- /// ```
- /// path = foo child = bar newPath = foo/bar
- /// path = foo/bar child = baz ntask.impl.snapshotwPath = foo/bar/baz
- /// All leading and trailing slashes will be removed, and consecutive slashes will be
- /// compressed to single slashes. For example:
- /// child = /foo/bar newPath = foo/bar
- /// child = foo/bar/ newPath = foo/bar
- /// child = foo///bar newPath = foo/bar
- /// ```
- ///
- /// - Parameter path: The path to append to the current path.
- /// - Returns: A new `StorageReference` pointing to a child location of the current reference.
- @objc(child:) open func child(_ path: String) -> StorageReference {
- return StorageReference(storage: storage, path: self.path.child(path))
- }
- // MARK: - Uploads
- /// Asynchronously uploads data to the currently specified `StorageReference`,
- /// without additional metadata.
- /// This is not recommended for large files, and one should instead upload a file from disk.
- /// - Parameters:
- /// - uploadData: The data to upload.
- /// - metadata: `StorageMetadata` containing additional information (MIME type, etc.)
- /// about the object being uploaded.
- /// - Returns: An instance of `StorageUploadTask`, which can be used to monitor or manage the
- /// upload.
- @objc(putData:metadata:)
- @discardableResult
- open func putData(_ uploadData: Data, metadata: StorageMetadata? = nil) -> StorageUploadTask {
- return putData(uploadData, metadata: metadata, completion: nil)
- }
- /// Asynchronously uploads data to the currently specified `StorageReference`.
- /// This is not recommended for large files, and one should instead upload a file from disk.
- /// - Parameter uploadData The data to upload.
- /// - Returns: An instance of `StorageUploadTask`, which can be used to monitor or manage the
- /// upload.
- @objc(putData:) @discardableResult open func __putData(_ uploadData: Data) -> StorageUploadTask {
- return putData(uploadData, metadata: nil, completion: nil)
- }
- /// Asynchronously uploads data to the currently specified `StorageReference`.
- /// This is not recommended for large files, and one should instead upload a file from disk.
- /// - Parameters:
- /// - uploadData: The data to upload.
- /// - metadata: `StorageMetadata` containing additional information (MIME type, etc.)
- /// about the object being uploaded.
- /// - completion: A closure that either returns the object metadata on success,
- /// or an error on failure.
- /// - Returns: An instance of `StorageUploadTask`, which can be used to monitor or manage the
- /// upload.
- @objc(putData:metadata:completion:) @discardableResult
- open func putData(_ uploadData: Data,
- metadata: StorageMetadata? = nil,
- completion: ((_: StorageMetadata?, _: Error?) -> Void)?) -> StorageUploadTask {
- let putMetadata = metadata ?? StorageMetadata()
- if let path = path.object {
- putMetadata.path = path
- putMetadata.name = (path as NSString).lastPathComponent as String
- }
- let task = StorageUploadTask(reference: self,
- service: storage.fetcherServiceForApp,
- queue: storage.dispatchQueue,
- data: uploadData,
- metadata: putMetadata)
- startAndObserveUploadTask(task: task, completion: completion)
- return task
- }
- /// Asynchronously uploads a file to the currently specified `StorageReference`.
- /// - Parameters:
- /// - fileURL: A URL representing the system file path of the object to be uploaded.
- /// - metadata: `StorageMetadata` containing additional information (MIME type, etc.)
- /// about the object being uploaded.
- /// - Returns: An instance of `StorageUploadTask`, which can be used to monitor or manage the
- /// upload.
- @objc(putFile:metadata:) @discardableResult
- open func putFile(from fileURL: URL, metadata: StorageMetadata? = nil) -> StorageUploadTask {
- return putFile(from: fileURL, metadata: metadata, completion: nil)
- }
- /// Asynchronously uploads a file to the currently specified `StorageReference`,
- /// without additional metadata.
- /// @param fileURL A URL representing the system file path of the object to be uploaded.
- /// @return An instance of StorageUploadTask, which can be used to monitor or manage the upload.
- @objc(putFile:) @discardableResult open func __putFile(from fileURL: URL) -> StorageUploadTask {
- return putFile(from: fileURL, metadata: nil, completion: nil)
- }
- /// Asynchronously uploads a file to the currently specified `StorageReference`.
- /// - Parameters:
- /// - fileURL: A URL representing the system file path of the object to be uploaded.
- /// - metadata: `StorageMetadata` containing additional information (MIME type, etc.)
- /// about the object being uploaded.
- /// - completion: A completion block that either returns the object metadata on success,
- /// or an error on failure.
- /// - Returns: An instance of `StorageUploadTask`, which can be used to monitor or manage the
- /// upload.
- @objc(putFile:metadata:completion:) @discardableResult
- open func putFile(from fileURL: URL,
- metadata: StorageMetadata? = nil,
- completion: ((_: StorageMetadata?, _: Error?) -> Void)?) -> StorageUploadTask {
- let putMetadata: StorageMetadata = metadata ?? StorageMetadata()
- if let path = path.object {
- putMetadata.path = path
- putMetadata.name = (path as NSString).lastPathComponent as String
- }
- let task = StorageUploadTask(reference: self,
- service: storage.fetcherServiceForApp,
- queue: storage.dispatchQueue,
- file: fileURL,
- metadata: putMetadata)
- startAndObserveUploadTask(task: task, completion: completion)
- return task
- }
- // MARK: - Downloads
- /// Asynchronously downloads the object at the `StorageReference` to a `Data` instance in memory.
- /// A `Data` buffer of the provided max size will be allocated, so ensure that the device has
- /// enough free
- /// memory to complete the download. For downloading large files, `write(toFile:)` may be a better
- /// option.
- /// - Parameters:
- /// - maxSize: The maximum size in bytes to download. If the download exceeds this size,
- /// the task will be cancelled and an error will be returned.
- /// - completion: A completion block that either returns the object data on success,
- /// or an error on failure.
- /// - Returns: A `StorageDownloadTask` that can be used to monitor or manage the download.
- @objc(dataWithMaxSize:completion:) @discardableResult
- open func getData(maxSize: Int64,
- completion: @escaping ((_: Data?, _: Error?) -> Void)) -> StorageDownloadTask {
- let fetcherService = storage.fetcherServiceForApp
- let task = StorageDownloadTask(reference: self,
- service: fetcherService,
- queue: storage.dispatchQueue,
- file: nil)
- task.completionData = completion
- let callbackQueue = fetcherService.callbackQueue ?? DispatchQueue.main
- task.observe(.success) { snapshot in
- let error = self.checkSizeOverflow(task: snapshot.task, maxSize: maxSize)
- callbackQueue.async {
- if let completion = task.completionData {
- let data = error == nil ? task.downloadData : nil
- completion(data, error)
- task.completionData = nil
- }
- }
- }
- task.observe(.failure) { snapshot in
- callbackQueue.async {
- task.completionData?(nil, snapshot.error)
- task.completionData = nil
- }
- }
- task.observe(.progress) { snapshot in
- if let error = self.checkSizeOverflow(task: snapshot.task, maxSize: maxSize) {
- task.cancel(withError: error)
- }
- }
- task.enqueue()
- return task
- }
- /// Asynchronously retrieves a long lived download URL with a revokable token.
- /// This can be used to share the file with others, but can be revoked by a developer
- /// in the Firebase Console.
- /// - Parameter completion: A completion block that either returns the URL on success,
- /// or an error on failure.
- @objc(downloadURLWithCompletion:)
- open func downloadURL(completion: @escaping ((_: URL?, _: Error?) -> Void)) {
- let fetcherService = storage.fetcherServiceForApp
- let task = StorageGetDownloadURLTask(reference: self,
- fetcherService: fetcherService,
- queue: storage.dispatchQueue,
- completion: completion)
- task.enqueue()
- }
- /// Asynchronously retrieves a long lived download URL with a revokable token.
- /// This can be used to share the file with others, but can be revoked by a developer
- /// in the Firebase Console.
- /// - Throws: An error if the download URL could not be retrieved.
- /// - Returns: The URL on success.
- @available(iOS 13, tvOS 13, macOS 10.15, watchOS 8, *)
- open func downloadURL() async throws -> URL {
- return try await withCheckedThrowingContinuation { continuation in
- self.downloadURL { result in
- continuation.resume(with: result)
- }
- }
- }
- /// Asynchronously downloads the object at the current path to a specified system filepath.
- /// - Parameter fileURL: A file system URL representing the path the object should be downloaded
- /// to.
- /// - Returns A `StorageDownloadTask` that can be used to monitor or manage the download.
- @objc(writeToFile:) @discardableResult
- open func write(toFile fileURL: URL) -> StorageDownloadTask {
- return write(toFile: fileURL, completion: nil)
- }
- /// Asynchronously downloads the object at the current path to a specified system filepath.
- /// - Parameters:
- /// - fileURL: A file system URL representing the path the object should be downloaded to.
- /// - completion: A closure that fires when the file download completes, passed either
- /// a URL pointing to the file path of the downloaded file on success,
- /// or an error on failure.
- /// - Returns: A `StorageDownloadTask` that can be used to monitor or manage the download.
- @objc(writeToFile:completion:) @discardableResult
- open func write(toFile fileURL: URL,
- completion: ((_: URL?, _: Error?) -> Void)?) -> StorageDownloadTask {
- let fetcherService = storage.fetcherServiceForApp
- let task = StorageDownloadTask(reference: self,
- service: fetcherService,
- queue: storage.dispatchQueue,
- file: fileURL)
- if let completion {
- task.completionURL = completion
- let callbackQueue = fetcherService.callbackQueue ?? DispatchQueue.main
- task.observe(.success) { snapshot in
- callbackQueue.async {
- task.completionURL?(fileURL, nil)
- task.completionURL = nil
- }
- }
- task.observe(.failure) { snapshot in
- callbackQueue.async {
- task.completionURL?(nil, snapshot.error)
- task.completionURL = nil
- }
- }
- }
- task.enqueue()
- return task
- }
- // MARK: - List Support
- /// Lists all items (files) and prefixes (folders) under this `StorageReference`.
- ///
- /// This is a helper method for calling `list()` repeatedly until there are no more results.
- ///
- /// Consistency of the result is not guaranteed if objects are inserted or removed while this
- /// operation is executing. All results are buffered in memory.
- ///
- /// `listAll(completion:)` is only available for projects using Firebase Rules Version 2.
- /// - Parameter completion: A completion handler that will be invoked with all items and prefixes
- /// under
- /// the current `StorageReference`.
- @objc(listAllWithCompletion:)
- open func listAll(completion: @escaping ((_: StorageListResult?, _: Error?) -> Void)) {
- let fetcherService = storage.fetcherServiceForApp
- var prefixes = [StorageReference]()
- var items = [StorageReference]()
- weak var weakSelf = self
- var paginatedCompletion: ((_: StorageListResult?, _: Error?) -> Void)?
- paginatedCompletion = { (_ listResult: StorageListResult?, _ error: Error?) in
- if let error {
- completion(nil, error)
- return
- }
- guard let strongSelf = weakSelf else { return }
- guard let listResult = listResult else {
- fatalError("internal error: both listResult and error are nil")
- }
- prefixes.append(contentsOf: listResult.prefixes)
- items.append(contentsOf: listResult.items)
- if let pageToken = listResult.pageToken {
- let nextPage = StorageListTask(reference: strongSelf,
- fetcherService: fetcherService,
- queue: strongSelf.storage.dispatchQueue,
- pageSize: nil,
- previousPageToken: pageToken,
- completion: paginatedCompletion)
- nextPage.enqueue()
- } else {
- let result = StorageListResult(withPrefixes: prefixes, items: items, pageToken: nil)
- // Break the retain cycle we set up indirectly by passing the callback to `nextPage`.
- paginatedCompletion = nil
- completion(result, nil)
- }
- }
- let task = StorageListTask(reference: self,
- fetcherService: fetcherService,
- queue: storage.dispatchQueue,
- pageSize: nil,
- previousPageToken: nil,
- completion: paginatedCompletion)
- task.enqueue()
- }
- /// Lists all items (files) and prefixes (folders) under this StorageReference.
- /// This is a helper method for calling list() repeatedly until there are no more results.
- /// Consistency of the result is not guaranteed if objects are inserted or removed while this
- /// operation is executing. All results are buffered in memory.
- /// `listAll()` is only available for projects using Firebase Rules Version 2.
- /// - Throws: An error if the list operation failed.
- /// - Returns: All items and prefixes under the current `StorageReference`.
- @available(iOS 13, tvOS 13, macOS 10.15, watchOS 8, *)
- open func listAll() async throws -> StorageListResult {
- return try await withCheckedThrowingContinuation { continuation in
- self.listAll { result in
- continuation.resume(with: result)
- }
- }
- }
- /// List up to `maxResults` items (files) and prefixes (folders) under this StorageReference.
- ///
- /// "/" is treated as a path delimiter. Firebase Storage does not support unsupported object
- /// paths that end with "/" or contain two consecutive "/"s. All invalid objects in GCS will be
- /// filtered.
- ///
- /// Only available for projects using Firebase Rules Version 2.
- /// - Parameters:
- /// - maxResults: The maximum number of results to return in a single page. Must be
- /// greater than 0 and at most 1000.
- /// - completion: A completion handler that will be invoked with up to `maxResults` items and
- /// prefixes under the current `StorageReference`.
- @objc(listWithMaxResults:completion:)
- open func list(maxResults: Int64,
- completion: @escaping ((_: StorageListResult?, _: Error?) -> Void)) {
- if maxResults <= 0 || maxResults > 1000 {
- completion(nil,
- NSError(domain: StorageErrorDomain,
- code: StorageErrorCode.invalidArgument.rawValue,
- userInfo: [NSLocalizedDescriptionKey:
- "Argument 'maxResults' must be between 1 and 1000 inclusive."]))
- } else {
- let fetcherService = storage.fetcherServiceForApp
- let task = StorageListTask(reference: self,
- fetcherService: fetcherService,
- queue: storage.dispatchQueue,
- pageSize: maxResults,
- previousPageToken: nil,
- completion: completion)
- task.enqueue()
- }
- }
- /// Resumes a previous call to `list(maxResults:completion:)`, starting after a pagination token.
- ///
- /// Returns the next set of items (files) and prefixes (folders) under this `StorageReference`.
- ///
- /// "/" is treated as a path delimiter. Storage does not support unsupported object
- /// paths that end with "/" or contain two consecutive "/"s. All invalid objects in GCS will be
- /// filtered.
- ///
- /// `list(maxResults:pageToken:completion:)`is only available for projects using Firebase Rules
- /// Version 2.
- /// - Parameters:
- /// - maxResults: The maximum number of results to return in a single page. Must be greater
- /// than 0 and at most 1000.
- /// - pageToken: A page token from a previous call to list.
- /// - completion: A completion handler that will be invoked with the next items and prefixes
- /// under the current StorageReference.
- @objc(listWithMaxResults:pageToken:completion:)
- open func list(maxResults: Int64,
- pageToken: String,
- completion: @escaping ((_: StorageListResult?, _: Error?) -> Void)) {
- if maxResults <= 0 || maxResults > 1000 {
- completion(nil,
- NSError(domain: StorageErrorDomain,
- code: StorageErrorCode.invalidArgument.rawValue,
- userInfo: [NSLocalizedDescriptionKey:
- "Argument 'maxResults' must be between 1 and 1000 inclusive."]))
- } else {
- let fetcherService = storage.fetcherServiceForApp
- let task = StorageListTask(reference: self,
- fetcherService: fetcherService,
- queue: storage.dispatchQueue,
- pageSize: maxResults,
- previousPageToken: pageToken,
- completion: completion)
- task.enqueue()
- }
- }
- // MARK: - Metadata Operations
- /// Retrieves metadata associated with an object at the current path.
- /// - Parameter completion: A completion block which returns the object metadata on success,
- /// or an error on failure.
- @objc(metadataWithCompletion:)
- open func getMetadata(completion: @escaping ((_: StorageMetadata?, _: Error?) -> Void)) {
- let fetcherService = storage.fetcherServiceForApp
- let task = StorageGetMetadataTask(reference: self,
- fetcherService: fetcherService,
- queue: storage.dispatchQueue,
- completion: completion)
- task.enqueue()
- }
- /// Retrieves metadata associated with an object at the current path.
- /// - Throws: An error if the object metadata could not be retrieved.
- /// - Returns: The object metadata on success.
- @available(iOS 13, tvOS 13, macOS 10.15, watchOS 8, *)
- open func getMetadata() async throws -> StorageMetadata {
- return try await withCheckedThrowingContinuation { continuation in
- self.getMetadata { result in
- continuation.resume(with: result)
- }
- }
- }
- /// Updates the metadata associated with an object at the current path.
- /// - Parameters:
- /// - metadata: A `StorageMetadata` object with the metadata to update.
- /// - completion: A completion block which returns the `StorageMetadata` on success,
- /// or an error on failure.
- @objc(updateMetadata:completion:)
- open func updateMetadata(_ metadata: StorageMetadata,
- completion: ((_: StorageMetadata?, _: Error?) -> Void)?) {
- let fetcherService = storage.fetcherServiceForApp
- let task = StorageUpdateMetadataTask(reference: self,
- fetcherService: fetcherService,
- queue: storage.dispatchQueue,
- metadata: metadata,
- completion: completion)
- task.enqueue()
- }
- /// Updates the metadata associated with an object at the current path.
- /// - Parameter metadata: A `StorageMetadata` object with the metadata to update.
- /// - Throws: An error if the metadata update operation failed.
- /// - Returns: The object metadata on success.
- @available(iOS 13, tvOS 13, macOS 10.15, watchOS 8, *)
- open func updateMetadata(_ metadata: StorageMetadata) async throws -> StorageMetadata {
- return try await withCheckedThrowingContinuation { continuation in
- self.updateMetadata(metadata) { result in
- continuation.resume(with: result)
- }
- }
- }
- // MARK: - Delete
- /// Deletes the object at the current path.
- /// - Parameter completion: A completion block which returns a nonnull error on failure.
- @objc(deleteWithCompletion:)
- open func delete(completion: ((_: Error?) -> Void)?) {
- let fetcherService = storage.fetcherServiceForApp
- let task = StorageDeleteTask(reference: self,
- fetcherService: fetcherService,
- queue: storage.dispatchQueue,
- completion: completion)
- task.enqueue()
- }
- /// Deletes the object at the current path.
- /// - Throws: An error if the delete operation failed.
- @available(iOS 13, tvOS 13, macOS 10.15, watchOS 8, *)
- open func delete() async throws {
- return try await withCheckedThrowingContinuation { continuation in
- self.delete { error in
- if let error {
- continuation.resume(throwing: error)
- } else {
- continuation.resume()
- }
- }
- }
- }
- // MARK: - NSObject overrides
- /// NSObject override
- @objc override open func copy() -> Any {
- return StorageReference(storage: storage, path: path)
- }
- /// NSObject override
- @objc override open func isEqual(_ object: Any?) -> Bool {
- guard let ref = object as? StorageReference else {
- return false
- }
- return storage == ref.storage && path == ref.path
- }
- /// NSObject override
- @objc override public var hash: Int {
- return storage.hash ^ path.bucket.hashValue
- }
- /// NSObject override
- @objc override public var description: String {
- return "gs://\(path.bucket)/\(path.object ?? "")"
- }
- // MARK: - Internal APIs
- /// The current path which points to an object in the Google Cloud Storage bucket.
- let path: StoragePath
- override init() {
- storage = Storage.storage()
- let storageBucket = storage.app.options.storageBucket!
- path = StoragePath(with: storageBucket)
- }
- init(storage: Storage, path: StoragePath) {
- self.storage = storage
- self.path = path
- }
- /// For maxSize API, return an error if the size is exceeded.
- private func checkSizeOverflow(task: StorageTask, maxSize: Int64) -> NSError? {
- if task.progress.totalUnitCount > maxSize || task.progress.completedUnitCount > maxSize {
- return StorageErrorCode.error(withCode: .downloadSizeExceeded,
- infoDictionary: [
- "totalSize": task.progress.totalUnitCount,
- "maxAllowedSize": maxSize,
- ])
- }
- return nil
- }
- private func startAndObserveUploadTask(task: StorageUploadTask,
- completion: ((_: StorageMetadata?, _: Error?) -> Void)?) {
- if let completion {
- task.completionMetadata = completion
- let callbackQueue = storage.fetcherServiceForApp.callbackQueue ?? DispatchQueue.main
- task.observe(.success) { snapshot in
- callbackQueue.async {
- task.completionMetadata?(snapshot.metadata, nil)
- task.completionMetadata = nil
- }
- }
- task.observe(.failure) { snapshot in
- callbackQueue.async {
- task.completionMetadata?(nil, snapshot.error)
- task.completionMetadata = nil
- }
- }
- }
- task.enqueue()
- }
- }
|