StorageObservableTask.swift 6.0 KB

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