@@ -22,6 +22,7 @@ func mediaUploadBackgroundTracker() -> MediaUploadBackgroundTracker? {
2222/// Utilize `BGContinuedProcessingTask` to show the uploading media activity.
2323private actor ConcreteMediaUploadBackgroundTracker : MediaUploadBackgroundTracker {
2424 struct Item {
25+ // Please note: all media query needs to be done in the main context, due to the current upload media implementation.
2526 var media : TaggedManagedObjectID < Media >
2627 var progress : Progress
2728 }
@@ -55,6 +56,8 @@ private actor ConcreteMediaUploadBackgroundTracker: MediaUploadBackgroundTracker
5556 // State transtion: idle -> pending -> accepted -> [accepted...] -> idle.
5657 private var state : BGTaskState = . idle
5758
59+ private var coreDataChangesObserver : NSObjectProtocol ?
60+
5861 private init ( ) {
5962 let taskId = Bundle . main. bundleIdentifier! + " .mediaUpload "
6063
@@ -74,9 +77,12 @@ private actor ConcreteMediaUploadBackgroundTracker: MediaUploadBackgroundTracker
7477 await self ? . taskCreated ( task)
7578 }
7679 }
80+
7781 }
7882
7983 func track( progress: Progress , media: TaggedManagedObjectID < Media > ) async {
84+ observeCoreDataChanges ( )
85+
8086 let item = Item ( media: media, progress: progress)
8187 switch state {
8288 case . idle:
@@ -101,6 +107,31 @@ private actor ConcreteMediaUploadBackgroundTracker: MediaUploadBackgroundTracker
101107 }
102108 }
103109
110+ private func observeCoreDataChanges( ) {
111+ guard coreDataChangesObserver == nil else { return }
112+
113+ coreDataChangesObserver = NotificationCenter . default. addObserver (
114+ forName: . NSManagedObjectContextObjectsDidChange,
115+ object: ContextManager . shared. mainContext,
116+ queue: . main
117+ ) { [ weak self] notification in
118+ let deleted = notification. userInfo ? [ NSManagedObjectContext . NotificationKey. deletedObjects. rawValue] as? Set < NSManagedObject > ?? [ ]
119+
120+ var mediaObjectIDs = Set < TaggedManagedObjectID < Media > > ( )
121+ for object in deleted {
122+ if let media = object as? Media {
123+ mediaObjectIDs. insert ( TaggedManagedObjectID ( media) )
124+ }
125+ }
126+
127+ if !mediaObjectIDs. isEmpty {
128+ Task {
129+ await self ? . handleMediaObjectsUpdates ( updated: mediaObjectIDs)
130+ }
131+ }
132+ }
133+ }
134+
104135 private func taskCreated( _ task: BGContinuedProcessingTask ) {
105136 task. progress. totalUnitCount = 100
106137 task. expirationHandler = { [ weak self] in
@@ -164,32 +195,51 @@ private actor ConcreteMediaUploadBackgroundTracker: MediaUploadBackgroundTracker
164195 setTaskCompleted ( success: false )
165196 }
166197
167- private func handleProgressUpdates( ) {
198+ private func handleProgressUpdates( ) async {
168199 guard case let . accepted( accepted) = state else { return }
169200
170- let fractionCompleted = accepted. items. map ( \. progress. fractionCompleted) . reduce ( 0 , + ) / Double( accepted. items. count)
201+ let progresses = await MainActor . run {
202+ let context = ContextManager . shared. mainContext
203+ return accepted. items
204+ . filter { item in
205+ ( try ? context. existingObject ( with: item. media) ) != nil
206+ }
207+ . map ( \. progress)
208+ }
209+
210+ let fractionCompleted = progresses. map ( \. fractionCompleted) . reduce ( 0 , + ) / Double( progresses. count)
171211 accepted. task. progress. completedUnitCount = Int64 ( fractionCompleted * Double( accepted. task. progress. totalUnitCount) )
172212 }
173213
214+ private func handleMediaObjectsUpdates( updated: Set < TaggedManagedObjectID < Media > > ) async {
215+ guard case let . accepted( accepted) = state else { return }
216+
217+ let needsUpdate = accepted. items. contains ( where: { updated. contains ( $0. media) } )
218+ if needsUpdate {
219+ await handleStatusUpdates ( )
220+ }
221+ }
222+
174223 private func handleStatusUpdates( ) async {
175224 await updateMessaging ( )
176225 await updateResult ( )
177226 }
178227
179- @MainActor
180228 private func updateMessaging( ) async {
181- guard case let . accepted( accepted) = await self . state else { return }
229+ guard case let . accepted( accepted) = self . state else { return }
182230
183- let context = ContextManager . shared. mainContext
184- let mediaItems = accepted. items. compactMap { try ? context. existingObject ( with: $0. media) }
231+ let statuses = await MainActor . run {
232+ let context = ContextManager . shared. mainContext
233+ return accepted. items. compactMap { try ? context. existingObject ( with: $0. media) . uploadStatus }
234+ }
185235
186- let failed = mediaItems . count { $0. remoteStatus == . failed }
187- let success = mediaItems . count { $0. remoteStatus == . sync }
188- let total = mediaItems . count
236+ let failed = statuses . count { $0 == . failure }
237+ let success = statuses . count { $0 == . success }
238+ let uploading = statuses . count { $0 == . uploading }
189239
190240 var subtitle = [ String] ( )
191- if total - success - failed > 0 {
192- subtitle. append ( String . localizedStringWithFormat ( Strings . uploadingStatus, total - success - failed ) )
241+ if uploading > 0 {
242+ subtitle. append ( String . localizedStringWithFormat ( Strings . uploadingStatus, uploading ) )
193243 }
194244 if success > 0 {
195245 subtitle. append ( String . localizedStringWithFormat ( Strings . successStatus, success) )
@@ -201,20 +251,21 @@ private actor ConcreteMediaUploadBackgroundTracker: MediaUploadBackgroundTracker
201251 accepted. task. updateTitle ( Strings . uploadingMediaTitle, subtitle: ListFormatter . localizedString ( byJoining: subtitle) )
202252 }
203253
204- @MainActor
205254 private func updateResult( ) async {
206- guard case let . accepted( accepted) = await self . state else { return }
255+ guard case let . accepted( accepted) = self . state else { return }
207256
208- let context = ContextManager . shared. mainContext
209- let mediaItems = accepted. items. compactMap { try ? context. existingObject ( with: $0. media) }
257+ let mediaStatuses = await MainActor . run {
258+ let context = ContextManager . shared. mainContext
259+ return accepted. items. compactMap { try ? context. existingObject ( with: $0. media) . uploadStatus }
260+ }
210261
211- let completed = mediaItems . allSatisfy { $0. remoteStatus == . sync || $0. remoteStatus == . failed }
262+ let completed = mediaStatuses . allSatisfy { $0 == . success || $0 == . failure }
212263 guard completed else {
213264 return
214265 }
215266
216- let success = mediaItems . allSatisfy { $0. remoteStatus == . sync }
217- await setTaskCompleted ( success: success)
267+ let success = mediaStatuses . allSatisfy { $0 == . success }
268+ setTaskCompleted ( success: success)
218269 }
219270
220271 private func setTaskCompleted( success: Bool ) {
@@ -226,6 +277,32 @@ private actor ConcreteMediaUploadBackgroundTracker: MediaUploadBackgroundTracker
226277 }
227278}
228279
280+ private enum MediaUploadStatus : Hashable {
281+ case success
282+ case failure
283+ case uploading
284+ case unknown
285+ }
286+
287+ private extension Media {
288+ var uploadStatus : MediaUploadStatus {
289+ if let number = remoteStatusNumber, let status = MediaRemoteStatus ( rawValue: number. uintValue) {
290+ switch status {
291+ case . sync:
292+ return . success
293+ case . failed:
294+ return . failure
295+ case . pushing, . processing:
296+ return . uploading
297+ default :
298+ return . unknown
299+ }
300+ } else {
301+ return . unknown
302+ }
303+ }
304+ }
305+
229306private enum Strings {
230307 static let uploadingMediaTitle = NSLocalizedString (
231308 " BGTask.mediaUpload.title " ,
0 commit comments