StorageDownloadTask.swift 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  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. #if COCOAPODS
  16. import GTMSessionFetcher
  17. #else
  18. import GTMSessionFetcherCore
  19. #endif
  20. /**
  21. * `StorageDownloadTask` implements resumable downloads from an object in Firebase Storage.
  22. * Downloads can be returned on completion with a completion handler, and can be monitored
  23. * by attaching observers, or controlled by calling `pause()`, `resume()`,
  24. * or `cancel()`.
  25. * Downloads can currently be returned as `Data` in memory, or as a `URL` to a file on disk.
  26. * Downloads are performed on a background queue, and callbacks are raised on the developer
  27. * specified `callbackQueue` in Storage, or the main queue if left unspecified.
  28. */
  29. @objc(FIRStorageDownloadTask)
  30. open class StorageDownloadTask: StorageObservableTask, StorageTaskManagement {
  31. /**
  32. * Prepares a task and begins execution.
  33. */
  34. @objc open func enqueue() {
  35. enqueueImplementation()
  36. }
  37. /**
  38. * Pauses a task currently in progress. Calling this on a paused task has no effect.
  39. */
  40. @objc open func pause() {
  41. dispatchQueue.async { [weak self] in
  42. guard let self = self else { return }
  43. if self.state == .paused || self.state == .pausing {
  44. return
  45. }
  46. self.state = .pausing
  47. // Use the resume callback to confirm pause status since it always runs after the last
  48. // NSURLSession update.
  49. self.fetcher?.resumeDataBlock = { [weak self] (data: Data) in
  50. guard let self = self else { return }
  51. self.downloadData = data
  52. self.state = .paused
  53. self.fire(for: .pause, snapshot: self.snapshot)
  54. }
  55. self.fetcher?.stopFetching()
  56. }
  57. }
  58. /**
  59. * Cancels a task.
  60. */
  61. @objc open func cancel() {
  62. let error = StorageErrorCode.error(withCode: .cancelled)
  63. cancel(withError: error)
  64. }
  65. /**
  66. * Resumes a paused task. Calling this on a running task has no effect.
  67. */
  68. @objc open func resume() {
  69. dispatchQueue.async { [weak self] in
  70. guard let self = self else { return }
  71. self.state = .resuming
  72. self.fire(for: .resume, snapshot: self.snapshot)
  73. self.state = .running
  74. self.enqueueImplementation(resumeWith: self.downloadData)
  75. }
  76. }
  77. private var fetcher: GTMSessionFetcher?
  78. private var fetcherCompletion: ((Data?, NSError?) -> Void)?
  79. var downloadData: Data?
  80. // Hold completion in object to force it to be retained until completion block is called.
  81. var completionData: ((Data?, Error?) -> Void)?
  82. var completionURL: ((URL?, Error?) -> Void)?
  83. // MARK: - Internal Implementations
  84. override init(reference: StorageReference,
  85. service: GTMSessionFetcherService,
  86. queue: DispatchQueue,
  87. file: URL?) {
  88. super.init(reference: reference, service: service, queue: queue, file: file)
  89. }
  90. deinit {
  91. self.fetcher?.stopFetching()
  92. }
  93. func enqueueImplementation(resumeWith resumeData: Data? = nil) {
  94. dispatchQueue.async { [weak self] in
  95. guard let self = self else { return }
  96. self.state = .queueing
  97. var request = self.baseRequest
  98. request.httpMethod = "GET"
  99. request.timeoutInterval = self.reference.storage.maxDownloadRetryTime
  100. var components = URLComponents(url: request.url!, resolvingAgainstBaseURL: false)
  101. components?.query = "alt=media"
  102. request.url = components?.url
  103. var fetcher: GTMSessionFetcher
  104. if let resumeData = resumeData {
  105. fetcher = GTMSessionFetcher(downloadResumeData: resumeData)
  106. fetcher.comment = "Resuming DownloadTask"
  107. } else {
  108. fetcher = self.fetcherService.fetcher(with: request)
  109. fetcher.comment = "Starting DownloadTask"
  110. }
  111. fetcher.maxRetryInterval = self.reference.storage.maxDownloadRetryInterval
  112. if let fileURL = self.fileURL {
  113. // Handle file downloads
  114. fetcher.destinationFileURL = fileURL
  115. fetcher.downloadProgressBlock = { [weak self] (bytesWritten: Int64,
  116. totalBytesWritten: Int64,
  117. totalBytesExpectedToWrite: Int64) in
  118. guard let self = self else { return }
  119. self.state = .progress
  120. self.progress.completedUnitCount = totalBytesWritten
  121. self.progress.totalUnitCount = totalBytesExpectedToWrite
  122. self.fire(for: .progress, snapshot: self.snapshot)
  123. self.state = .running
  124. }
  125. } else {
  126. // Handle data downloads
  127. fetcher.receivedProgressBlock = { [weak self] (bytesWritten: Int64,
  128. totalBytesWritten: Int64) in
  129. guard let self = self else { return }
  130. self.state = .progress
  131. self.progress.completedUnitCount = totalBytesWritten
  132. if let totalLength = self.fetcher?.response?.expectedContentLength {
  133. self.progress.totalUnitCount = totalLength
  134. }
  135. self.fire(for: .progress, snapshot: self.snapshot)
  136. self.state = .running
  137. }
  138. }
  139. self.fetcher = fetcher
  140. // Capture self here to retain until completion.
  141. self.fetcherCompletion = { [self] (data: Data?, error: NSError?) in
  142. defer {
  143. self.removeAllObservers()
  144. self.fetcherCompletion = nil
  145. }
  146. self.fire(for: .progress, snapshot: self.snapshot)
  147. // Handle potential issues with download
  148. if let error = error {
  149. self.state = .failed
  150. self.error = StorageErrorCode.error(withServerError: error, ref: self.reference)
  151. self.fire(for: .failure, snapshot: self.snapshot)
  152. return
  153. }
  154. // Download completed successfully, fire completion callbacks
  155. self.state = .success
  156. if let data = data {
  157. self.downloadData = data
  158. }
  159. self.fire(for: .success, snapshot: self.snapshot)
  160. }
  161. self.state = .running
  162. self.fetcher?.beginFetch { [self] data, error in
  163. self.fetcherCompletion?(data, error as? NSError)
  164. }
  165. }
  166. }
  167. func cancel(withError error: NSError) {
  168. dispatchQueue.async { [weak self] in
  169. guard let self = self else { return }
  170. self.state = .cancelled
  171. self.fetcher?.stopFetching()
  172. self.error = error
  173. self.fire(for: .failure, snapshot: self.snapshot)
  174. }
  175. }
  176. }