Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import WordPressShared
// This protocol is used to hide the `@available(iOS 26.0, *)` check.
protocol MediaUploadBackgroundTracker {

func track(progress: Progress, media: TaggedManagedObjectID<Media>) async
func track(progress: Progress, media: TaggedManagedObjectID<Media>)

}

Expand All @@ -19,8 +19,9 @@ func mediaUploadBackgroundTracker() -> MediaUploadBackgroundTracker? {
}

@available(iOS 26.0, *)
@MainActor
/// Utilize `BGContinuedProcessingTask` to show the uploading media activity.
private actor ConcreteMediaUploadBackgroundTracker: MediaUploadBackgroundTracker {
private final class ConcreteMediaUploadBackgroundTracker: MediaUploadBackgroundTracker {
struct Item {
// Please note: all media query needs to be done in the main context, due to the current upload media implementation.
var media: TaggedManagedObjectID<Media>
Expand Down Expand Up @@ -72,15 +73,13 @@ private actor ConcreteMediaUploadBackgroundTracker: MediaUploadBackgroundTracker
wpAssertionFailure("Unexpected task instance")
return
}

Task {
await self?.taskCreated(task)
Task { @MainActor in
self?.taskCreated(task)
}
}

}

func track(progress: Progress, media: TaggedManagedObjectID<Media>) async {
func track(progress: Progress, media: TaggedManagedObjectID<Media>) {
observeCoreDataChanges()

let item = Item(media: media, progress: progress)
Expand Down Expand Up @@ -125,18 +124,16 @@ private actor ConcreteMediaUploadBackgroundTracker: MediaUploadBackgroundTracker
}

if !mediaObjectIDs.isEmpty {
Task {
await self?.handleMediaObjectsUpdates(updated: mediaObjectIDs)
}
self?.handleMediaObjectsUpdates(updated: mediaObjectIDs)
}
}
}

private func taskCreated(_ task: BGContinuedProcessingTask) {
task.progress.totalUnitCount = 100
task.expirationHandler = { [weak self] in
Task {
await self?.handleExpiration()
Task { @MainActor in
self?.handleExpiration()
}
}

Expand All @@ -157,22 +154,16 @@ private actor ConcreteMediaUploadBackgroundTracker: MediaUploadBackgroundTracker
accepted.items.append(item)

let progress = item.progress.publisher(for: \.fractionCompleted).sink { [weak self] _ in
Task {
await self?.handleProgressUpdates()
}
self?.handleProgressUpdates()
}
accepted.observers.append(progress)

Task { @MainActor in
guard let media = try? ContextManager.shared.mainContext.existingObject(with: item.media) else { return }
guard let media = try? ContextManager.shared.mainContext.existingObject(with: item.media) else { return }

let completion = media.publisher(for: \.remoteStatusNumber).sink { [weak self] _ in
Task {
await self?.handleStatusUpdates()
}
}
await self.addObserver(completion)
let completion = media.publisher(for: \.remoteStatusNumber).sink { [weak self] _ in
self?.handleStatusUpdates()
}
self.addObserver(completion)
}

private func addObserver(_ cancellable: AnyCancellable) {
Expand All @@ -183,55 +174,49 @@ private actor ConcreteMediaUploadBackgroundTracker: MediaUploadBackgroundTracker

private func handleExpiration() {
if case let .accepted(accepted) = state {
Task { @MainActor in
let context = ContextManager.shared.mainContext
for item in accepted.items {
guard let media = try? context.existingObject(with: item.media) else { continue }
MediaCoordinator.shared.cancelUpload(of: media)
}
let context = ContextManager.shared.mainContext
for item in accepted.items {
guard let media = try? context.existingObject(with: item.media) else { continue }
MediaCoordinator.shared.cancelUpload(of: media)
}
}

setTaskCompleted(success: false)
}

private func handleProgressUpdates() async {
private func handleProgressUpdates() {
guard case let .accepted(accepted) = state else { return }

let progresses = await MainActor.run {
let context = ContextManager.shared.mainContext
return accepted.items
.filter { item in
(try? context.existingObject(with: item.media)) != nil
}
.map(\.progress)
}
let context = ContextManager.shared.mainContext
let progresses = accepted.items
.filter { item in
(try? context.existingObject(with: item.media)) != nil
}
.map(\.progress)

let fractionCompleted = progresses.map(\.fractionCompleted).reduce(0, +) / Double(progresses.count)
accepted.task.progress.completedUnitCount = Int64(fractionCompleted * Double(accepted.task.progress.totalUnitCount))
}

private func handleMediaObjectsUpdates(updated: Set<TaggedManagedObjectID<Media>>) async {
private func handleMediaObjectsUpdates(updated: Set<TaggedManagedObjectID<Media>>) {
guard case let .accepted(accepted) = state else { return }

let needsUpdate = accepted.items.contains(where: { updated.contains($0.media) })
if needsUpdate {
await handleStatusUpdates()
handleStatusUpdates()
}
}

private func handleStatusUpdates() async {
await updateMessaging()
await updateResult()
private func handleStatusUpdates() {
updateMessaging()
updateResult()
}

private func updateMessaging() async {
private func updateMessaging() {
guard case let .accepted(accepted) = self.state else { return }

let statuses = await MainActor.run {
let context = ContextManager.shared.mainContext
return accepted.items.compactMap { try? context.existingObject(with: $0.media).uploadStatus }
}
let context = ContextManager.shared.mainContext
let statuses = accepted.items.compactMap { try? context.existingObject(with: $0.media).uploadStatus }

let failed = statuses.count { $0 == .failure }
let success = statuses.count { $0 == .success}
Expand All @@ -251,13 +236,11 @@ private actor ConcreteMediaUploadBackgroundTracker: MediaUploadBackgroundTracker
accepted.task.updateTitle(Strings.uploadingMediaTitle, subtitle: ListFormatter.localizedString(byJoining: subtitle))
}

private func updateResult() async {
private func updateResult() {
guard case let .accepted(accepted) = self.state else { return }

let mediaStatuses = await MainActor.run {
let context = ContextManager.shared.mainContext
return accepted.items.compactMap { try? context.existingObject(with: $0.media).uploadStatus }
}
let context = ContextManager.shared.mainContext
let mediaStatuses = accepted.items.compactMap { try? context.existingObject(with: $0.media).uploadStatus }

let completed = mediaStatuses.allSatisfy { $0 == .success || $0 == .failure }
guard completed else {
Expand Down