Skip to content

Commit

Permalink
Handle download errors
Browse files Browse the repository at this point in the history
  • Loading branch information
GianniCarlo committed Mar 5, 2024
1 parent 9890d54 commit 2f08560
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 26 deletions.
8 changes: 8 additions & 0 deletions BookPlayer/Coordinators/LibraryListCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ class LibraryListCoordinator: ItemListCoordinator, UINavigationControllerDelegat
loadLastBookIfNeeded()
syncList()
bindImportObserverIfNeeded()
bindDownloadErrorObserver()

if let appDelegate = AppDelegate.shared {
for action in appDelegate.pendingURLActions {
Expand Down Expand Up @@ -136,6 +137,13 @@ class LibraryListCoordinator: ItemListCoordinator, UINavigationControllerDelegat
notifyPendingFiles()
}

func bindDownloadErrorObserver() {
syncService.downloadErrorPublisher.sink { (relativePath, error) in
self.showAlert("network_error_title".localized, message: "\(relativePath)\n\(error.localizedDescription)")
}
.store(in: &disposeBag)
}

@MainActor
func notifyPendingFiles() {
// Get reference of all the files located inside the Documents, Shared and Inbox folders
Expand Down
12 changes: 6 additions & 6 deletions BookPlayer/Library/ItemList Screen/ItemListViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -211,12 +211,12 @@ class ItemListViewModel: ViewModelProtocol {
))
}

singleDownloadProgressDelegateInterface.didFinishDownloadingTask = { [weak self] (task, fileURL) in
self?.handleSingleDownloadTaskFinished(task, fileURL: fileURL)
}

singleDownloadProgressDelegateInterface.didFinishTaskWithError = { [weak self] (task, error) in
self?.handleSingleDownloadTaskFinishedWithError(task, error: error)
singleDownloadProgressDelegateInterface.didFinishDownloadingTask = { [weak self] (task, fileURL, error) in
if let error {
self?.handleSingleDownloadTaskFinishedWithError(task, error: error)
} else if let fileURL {
self?.handleSingleDownloadTaskFinished(task, fileURL: fileURL)
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion Shared/Network/BPDownloadURLSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public class BPDownloadURLSession {
/// - didFinishDownloadingTask: Callback triggered when the download task is finished
public init(
downloadProgressUpdated: @escaping ((URLSessionDownloadTask, Double) -> Void),
didFinishDownloadingTask: @escaping ((URLSessionDownloadTask, URL) -> Void)
didFinishDownloadingTask: @escaping ((URLSessionTask, URL?, Error?) -> Void)
) {
let bundleIdentifier: String = Bundle.main.configurationValue(for: .bundleIdentifier)

Expand Down
32 changes: 25 additions & 7 deletions Shared/Network/BPTaskDownloadDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,7 @@ import Foundation

public class BPTaskDownloadDelegate: NSObject, URLSessionDownloadDelegate {
/// Callback triggered when the download task is finished
public var didFinishDownloadingTask: ((URLSessionDownloadTask, URL) -> Void)?
/// Callback triggered when the download task fails
/// - Note: the Error parameter represents client side errors
public var didFinishTaskWithError: ((URLSessionTask, Error?) -> Void)?
public var didFinishDownloadingTask: ((URLSessionTask, URL?, Error?) -> Void)?
/// Callback triggered when there's an update on the download progress
public var downloadProgressUpdated: ((URLSessionDownloadTask, Double) -> Void)?
/// Delegate callback when download finishes
Expand All @@ -22,12 +19,21 @@ public class BPTaskDownloadDelegate: NSObject, URLSessionDownloadDelegate {
downloadTask: URLSessionDownloadTask,
didFinishDownloadingTo location: URL
) {
didFinishDownloadingTask?(downloadTask, location)
didFinishDownloadingTask?(
downloadTask,
location,
parseErrorFromTask(downloadTask)
)
}

/// Note: this gets called even if there's no error
public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
didFinishTaskWithError?(task, error)
public func urlSession(
_ session: URLSession,
task: URLSessionTask,
didCompleteWithError error: Error?
) {
let error = error ?? parseErrorFromTask(task)
didFinishDownloadingTask?(task, nil, error)
}

/// Delegate callback when there's a progress update for the ongoing download
Expand All @@ -42,4 +48,16 @@ public class BPTaskDownloadDelegate: NSObject, URLSessionDownloadDelegate {

downloadProgressUpdated?(downloadTask, Double(calculatedProgress))
}

private func parseErrorFromTask(_ task: URLSessionTask) -> Error? {
guard
let response = task.response as? HTTPURLResponse,
response.statusCode >= 400
else {
return nil
}

let errorCode = URLError.Code(rawValue: response.statusCode)
return URLError(errorCode)
}
}
42 changes: 30 additions & 12 deletions Shared/Services/Sync/SyncService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ public protocol SyncServiceProtocol {
var downloadCompletedPublisher: PassthroughSubject<(String, String, String?), Never> { get }
/// Progress publisher for ongoing-download tasks
var downloadProgressPublisher: PassthroughSubject<(String, String, String?, Double), Never> { get }
/// Error publisher for ongoing-download tasks
var downloadErrorPublisher: PassthroughSubject<(String, Error), Never> { get }

/// Count of the currently queued sync jobs
func queuedJobsCount() async -> Int
Expand Down Expand Up @@ -105,17 +107,20 @@ public final class SyncService: SyncServiceProtocol, BPLogger {
public var downloadCompletedPublisher = PassthroughSubject<(String, String, String?), Never>()
/// Progress publisher for ongoing-download tasks
public var downloadProgressPublisher = PassthroughSubject<(String, String, String?, Double), Never>()
/// Error publisher for ongoing-download tasks
public var downloadErrorPublisher = PassthroughSubject<(String, Error), Never>()
/// Background URL session to handle downloading synced items
private lazy var downloadURLSession: BPDownloadURLSession = {
BPDownloadURLSession { task, progress in
self.handleDownloadProgressUpdated(
task: task,
individualProgress: progress
)
} didFinishDownloadingTask: { task, location in
} didFinishDownloadingTask: { task, location, error in
self.handleFinishedDownload(
task: task,
location: location
location: location,
error: error
)
}
}()
Expand Down Expand Up @@ -547,26 +552,39 @@ extension SyncService {
}

extension SyncService {
private func handleFinishedDownload(task: URLSessionTask, location: URL) {
private func handleFinishedDownload(
task: URLSessionTask,
location: URL?,
error: Error?
) {
guard let relativePath = task.taskDescription else { return }

do {
let fileURL = DataManager.getProcessedFolderURL().appendingPathComponent(relativePath)
if error == nil,
let location {
let fileURL = DataManager.getProcessedFolderURL().appendingPathComponent(relativePath)

/// If there's already something there, replace with new finished download
if FileManager.default.fileExists(atPath: fileURL.path) {
try FileManager.default.removeItem(at: fileURL)
}
try DataManager.createContainingFolderIfNeeded(for: fileURL)
try FileManager.default.moveItem(at: location, to: fileURL)
/// If there's already something there, replace with new finished download
if FileManager.default.fileExists(atPath: fileURL.path) {
try FileManager.default.removeItem(at: fileURL)
}
try DataManager.createContainingFolderIfNeeded(for: fileURL)
try FileManager.default.moveItem(at: location, to: fileURL)

Task {
await self.libraryService.loadChaptersIfNeeded(relativePath: relativePath)
Task {
await self.libraryService.loadChaptersIfNeeded(relativePath: relativePath)
}
}
} catch {
Self.logger.trace("Error moving downloaded file to the destination: \(error.localizedDescription)")
}

if let error {
DispatchQueue.main.async {
self.downloadErrorPublisher.send((relativePath, error))
}
}

guard let startingItemPath = ongoingTasksParentReference[relativePath] else {
initiatingFolderReference[relativePath] = nil
return
Expand Down

0 comments on commit 2f08560

Please sign in to comment.