StorageObservableTask.swift 6.2 KB

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