StorageObservableTask.swift 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  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. * An extended `StorageTask` providing observable semantics that can be used for responding to changes
  22. * in task state.
  23. *
  24. * Observers produce a `StorageHandle`, which is used to keep track of and remove specific
  25. * observers at a later date.
  26. */
  27. @objc(FIRStorageObservableTask) open class StorageObservableTask: StorageTask {
  28. /**
  29. * Observes changes in the upload status: Resume, Pause, Progress, Success, and Failure.
  30. * - Parameters:
  31. * - status: The `StorageTaskStatus` change to observe.
  32. * - handler: A callback that fires every time the status event occurs,
  33. * containing a `StorageTaskSnapshot` describing task state.
  34. * - Returns: A task handle that can be used to remove the observer at a later date.
  35. */
  36. @objc(observeStatus:handler:) @discardableResult
  37. open func observe(_ status: StorageTaskStatus,
  38. handler: @escaping (StorageTaskSnapshot) -> Void) -> String {
  39. let callback = handler
  40. // Note: self.snapshot is synchronized
  41. let snapshot = self.snapshot
  42. // TODO: use an increasing counter instead of a random UUID
  43. let uuidString = updateHandlerDictionary(for: status, with: callback)
  44. if let handlerDictionary = handlerDictionaries[status] {
  45. switch status {
  46. case .pause:
  47. if state == .pausing || state == .paused {
  48. fire(handlers: handlerDictionary, snapshot: snapshot)
  49. }
  50. case .resume:
  51. if state == .resuming || state == .running {
  52. fire(handlers: handlerDictionary, snapshot: snapshot)
  53. }
  54. case .progress:
  55. if state == .running || state == .progress {
  56. fire(handlers: handlerDictionary, snapshot: snapshot)
  57. }
  58. case .success:
  59. if state == .success {
  60. fire(handlers: handlerDictionary, snapshot: snapshot)
  61. }
  62. case .failure:
  63. if state == .failed || state == .failing {
  64. fire(handlers: handlerDictionary, snapshot: snapshot)
  65. }
  66. case .unknown: fatalError("Invalid observer status requested, use one " +
  67. "of: Pause, Resume, Progress, Complete, or Failure")
  68. }
  69. }
  70. objc_sync_enter(StorageObservableTask.self)
  71. handleToStatusMap[uuidString] = status
  72. objc_sync_exit(StorageObservableTask.self)
  73. return uuidString
  74. }
  75. /**
  76. * Removes the single observer with the provided handle.
  77. * - Parameter handle: The handle of the task to remove.
  78. */
  79. @objc(removeObserverWithHandle:) open func removeObserver(withHandle handle: String) {
  80. if let status = handleToStatusMap[handle] {
  81. objc_sync_enter(StorageObservableTask.self)
  82. handlerDictionaries[status]?.removeValue(forKey: handle)
  83. handleToStatusMap.removeValue(forKey: handle)
  84. objc_sync_exit(StorageObservableTask.self)
  85. }
  86. }
  87. /**
  88. * Removes all observers for a single status.
  89. * - Parameter status: A `StorageTaskStatus` to remove all listeners for.
  90. */
  91. @objc(removeAllObserversForStatus:)
  92. open func removeAllObservers(for status: StorageTaskStatus) {
  93. if let handlerDictionary = handlerDictionaries[status] {
  94. objc_sync_enter(StorageObservableTask.self)
  95. for (key, _) in handlerDictionary {
  96. handleToStatusMap.removeValue(forKey: key)
  97. }
  98. handlerDictionaries[status]?.removeAll()
  99. objc_sync_exit(StorageObservableTask.self)
  100. }
  101. }
  102. /**
  103. * Removes all observers.
  104. */
  105. @objc open func removeAllObservers() {
  106. objc_sync_enter(StorageObservableTask.self)
  107. for (status, _) in handlerDictionaries {
  108. handlerDictionaries[status]?.removeAll()
  109. }
  110. handleToStatusMap.removeAll()
  111. objc_sync_exit(StorageObservableTask.self)
  112. }
  113. // MARK: - Private Handler Dictionaries
  114. var handlerDictionaries: [StorageTaskStatus: [String: (StorageTaskSnapshot) -> Void]]
  115. var handleToStatusMap: [String: StorageTaskStatus]
  116. /**
  117. * The file to download to or upload from
  118. */
  119. let fileURL: URL?
  120. // MARK: - Internal Implementations
  121. init(reference: StorageReference,
  122. service: GTMSessionFetcherService,
  123. queue: DispatchQueue,
  124. file: URL?) {
  125. handlerDictionaries = [
  126. .resume: [String: (StorageTaskSnapshot) -> Void](),
  127. .pause: [String: (StorageTaskSnapshot) -> Void](),
  128. .progress: [String: (StorageTaskSnapshot) -> Void](),
  129. .success: [String: (StorageTaskSnapshot) -> Void](),
  130. .failure: [String: (StorageTaskSnapshot) -> Void](),
  131. ]
  132. handleToStatusMap = [:]
  133. fileURL = file
  134. super.init(reference: reference, service: service, queue: queue)
  135. }
  136. func updateHandlerDictionary(for status: StorageTaskStatus,
  137. with handler: @escaping ((StorageTaskSnapshot) -> Void))
  138. -> String {
  139. // TODO: use an increasing counter instead of a random UUID
  140. let uuidString = NSUUID().uuidString
  141. objc_sync_enter(StorageObservableTask.self)
  142. handlerDictionaries[status]?[uuidString] = handler
  143. objc_sync_exit(StorageObservableTask.self)
  144. return uuidString
  145. }
  146. func fire(for status: StorageTaskStatus, snapshot: StorageTaskSnapshot) {
  147. if let observerDictionary = handlerDictionaries[status] {
  148. fire(handlers: observerDictionary, snapshot: snapshot)
  149. }
  150. }
  151. func fire(handlers: [String: (StorageTaskSnapshot) -> Void],
  152. snapshot: StorageTaskSnapshot) {
  153. let callbackQueue = fetcherService.callbackQueue ?? DispatchQueue.main
  154. objc_sync_enter(StorageObservableTask.self)
  155. let enumeration = handlers.enumerated()
  156. objc_sync_exit(StorageObservableTask.self)
  157. for (_, handler) in enumeration {
  158. callbackQueue.async {
  159. handler.value(snapshot)
  160. }
  161. }
  162. }
  163. }