From cf5c2c4386ffb69de65785508cf3d80edb9a301c Mon Sep 17 00:00:00 2001 From: Hans Allis Date: Sat, 21 Aug 2021 23:27:57 +0200 Subject: [PATCH 1/4] Concurrent uploads --- .../Screens/Upload/UploadViewModel.swift | 67 ++++++++++--------- 1 file changed, 37 insertions(+), 30 deletions(-) diff --git a/Tree Tracker/Screens/Upload/UploadViewModel.swift b/Tree Tracker/Screens/Upload/UploadViewModel.swift index 6a0e506..a889e79 100644 --- a/Tree Tracker/Screens/Upload/UploadViewModel.swift +++ b/Tree Tracker/Screens/Upload/UploadViewModel.swift @@ -22,6 +22,7 @@ final class UploadViewModel: CollectionViewModel { var rightNavigationButtonsPublisher: Published<[NavigationBarButtonModel]>.Publisher { $rightNavigationButtons } var dataPublisher: Published<[ListSection]>.Publisher { $data } + let maxConcurrentUploads = 3 private var api: Api private var database: Database private var screenLockManager: ScreenLockManaging @@ -29,7 +30,7 @@ final class UploadViewModel: CollectionViewModel { private var sites: [Site] = [] private var species: [Species] = [] private var supervisors: [Supervisor] = [] - private var currentUpload: Cancellable? + private var currentUploads: [String: Cancellable?] = [:] private weak var navigation: UploadNavigating? init(api: Api = CurrentEnvironment.api, database: Database = CurrentEnvironment.database, screenLockManager: ScreenLockManaging = CurrentEnvironment.screenLockManager, logger: Logging = CurrentEnvironment.logger, navigation: UploadNavigating) { @@ -125,7 +126,9 @@ final class UploadViewModel: CollectionViewModel { private func cancelUploading() { logger.log(.upload, "Uploading cancelled.") - currentUpload?.cancel() + for ((_, upload)) in currentUploads { + upload?.cancel(); + } stopUploading() } @@ -133,36 +136,40 @@ final class UploadViewModel: CollectionViewModel { logger.log(.upload, "Uploading images...") database.fetchLocalTrees { [weak self] trees in self?.logger.log(.upload, "Trees to upload: \(trees.count)") - - guard let tree = trees.sorted(by: \.createDate, order: .descending).first else { - self?.logger.log(.upload, "No more items to upload - bailing.") - self?.stopUploading() - return - } - - self?.logger.log(.upload, "Now uploading tree: \(tree)") - self?.currentUpload = self?.api.upload( - tree: tree, - progress: { progress in - self?.logger.log(.upload, "Progress: \(progress)") - self?.update(uploadProgress: progress, for: tree) - }, - completion: { result in - switch result { - case let .success(airtableTree): - self?.logger.log(.upload, "Successfully uploaded tree.") - self?.database.save([airtableTree], sentFromThisDevice: true) - self?.database.remove(tree: tree) { - self?.presentTreesFromDatabase() - self?.uploadLocalTreesRecursively() + let sortedTrees = trees.sorted(by: \.createDate, order: .descending) + while (self != nil && self!.currentUploads.count < self!.maxConcurrentUploads && self!.currentUploads.count < trees.count) { + + guard let tree = sortedTrees.filter({ treeEntry in self?.currentUploads[treeEntry.phImageId] == nil}).first else { + self?.logger.log(.upload, "No more items to upload - bailing.") + self?.stopUploading() + return + } + + self?.logger.log(.upload, "Now uploading tree: \(tree)") + self?.currentUploads[tree.phImageId] = (self?.api.upload( + tree: tree, + progress: { progress in + self?.logger.log(.upload, "Progress: \(progress)") + self?.update(uploadProgress: progress, for: tree) + }, + completion: { result in + switch result { + case let .success(airtableTree): + self?.logger.log(.upload, "Successfully uploaded tree.") + self?.database.save([airtableTree], sentFromThisDevice: true) + self?.database.remove(tree: tree) { + self?.currentUploads[tree.phImageId] = nil; + self?.presentTreesFromDatabase() + self?.uploadLocalTreesRecursively() + } + case let .failure(error): + self?.update(uploadProgress: 0.0, for: tree) + self?.presentUploadButton(isUploading: false) + self?.logger.log(.upload, "Error when uploading a local tree: \(error)") } - case let .failure(error): - self?.update(uploadProgress: 0.0, for: tree) - self?.presentUploadButton(isUploading: false) - self?.logger.log(.upload, "Error when uploading a local tree: \(error)") } - } - ) + )) + } } } From 0aaedc2b41b30c6947ed185dcf6abbe51425df42 Mon Sep 17 00:00:00 2001 From: Hans Allis Date: Sat, 21 Aug 2021 23:45:57 +0200 Subject: [PATCH 2/4] Improved updating of the Upload button --- Tree Tracker/Screens/Upload/UploadViewModel.swift | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Tree Tracker/Screens/Upload/UploadViewModel.swift b/Tree Tracker/Screens/Upload/UploadViewModel.swift index a889e79..07bda6f 100644 --- a/Tree Tracker/Screens/Upload/UploadViewModel.swift +++ b/Tree Tracker/Screens/Upload/UploadViewModel.swift @@ -159,12 +159,18 @@ final class UploadViewModel: CollectionViewModel { self?.database.save([airtableTree], sentFromThisDevice: true) self?.database.remove(tree: tree) { self?.currentUploads[tree.phImageId] = nil; + if (self != nil && self!.currentUploads.count == 0) { + self?.presentUploadButton(isUploading: false) + } self?.presentTreesFromDatabase() self?.uploadLocalTreesRecursively() } case let .failure(error): + self?.currentUploads[tree.phImageId] = nil; + if (self != nil && self!.currentUploads.count == 0) { + self?.presentUploadButton(isUploading: false) + } self?.update(uploadProgress: 0.0, for: tree) - self?.presentUploadButton(isUploading: false) self?.logger.log(.upload, "Error when uploading a local tree: \(error)") } } From 018fb8892a084743b0cc793930102996db238f68 Mon Sep 17 00:00:00 2001 From: Hans Allis Date: Sun, 29 Aug 2021 14:18:09 +0200 Subject: [PATCH 3/4] Uploads are retried 2 times. No distinction between whether upload or AirTable registration failed. --- Tree Tracker/Screens/Upload/UploadViewModel.swift | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Tree Tracker/Screens/Upload/UploadViewModel.swift b/Tree Tracker/Screens/Upload/UploadViewModel.swift index 07bda6f..f426836 100644 --- a/Tree Tracker/Screens/Upload/UploadViewModel.swift +++ b/Tree Tracker/Screens/Upload/UploadViewModel.swift @@ -23,6 +23,7 @@ final class UploadViewModel: CollectionViewModel { var dataPublisher: Published<[ListSection]>.Publisher { $data } let maxConcurrentUploads = 3 + let maxUploadAttempts = 3 private var api: Api private var database: Database private var screenLockManager: ScreenLockManaging @@ -31,6 +32,7 @@ final class UploadViewModel: CollectionViewModel { private var species: [Species] = [] private var supervisors: [Supervisor] = [] private var currentUploads: [String: Cancellable?] = [:] + private var failedUploadCount: [String: Int] = [:] private weak var navigation: UploadNavigating? init(api: Api = CurrentEnvironment.api, database: Database = CurrentEnvironment.database, screenLockManager: ScreenLockManaging = CurrentEnvironment.screenLockManager, logger: Logging = CurrentEnvironment.logger, navigation: UploadNavigating) { @@ -108,6 +110,8 @@ final class UploadViewModel: CollectionViewModel { presentUploadButton(isUploading: true) presentNavigationButtons(isUploading: true) screenLockManager.disableLocking() + self.currentUploads = [:] + self.failedUploadCount = [:] uploadLocalTreesRecursively() } @@ -139,7 +143,7 @@ final class UploadViewModel: CollectionViewModel { let sortedTrees = trees.sorted(by: \.createDate, order: .descending) while (self != nil && self!.currentUploads.count < self!.maxConcurrentUploads && self!.currentUploads.count < trees.count) { - guard let tree = sortedTrees.filter({ treeEntry in self?.currentUploads[treeEntry.phImageId] == nil}).first else { + guard let tree = sortedTrees.filter({ treeEntry in self?.currentUploads[treeEntry.phImageId] == nil && (self?.failedUploadCount[treeEntry.phImageId] ?? 0) < self!.maxUploadAttempts}).first else { self?.logger.log(.upload, "No more items to upload - bailing.") self?.stopUploading() return @@ -166,12 +170,14 @@ final class UploadViewModel: CollectionViewModel { self?.uploadLocalTreesRecursively() } case let .failure(error): + self?.failedUploadCount[tree.phImageId] = (self?.failedUploadCount[tree.phImageId] ?? 0) + 1 self?.currentUploads[tree.phImageId] = nil; + self?.update(uploadProgress: 0.0, for: tree) + self?.logger.log(.upload, "Error when uploading a local tree: \(error)") + self?.uploadLocalTreesRecursively() if (self != nil && self!.currentUploads.count == 0) { self?.presentUploadButton(isUploading: false) } - self?.update(uploadProgress: 0.0, for: tree) - self?.logger.log(.upload, "Error when uploading a local tree: \(error)") } } )) From 7b7dc83c9b7e1ffbb019c5f559cf6ff9b502c193 Mon Sep 17 00:00:00 2001 From: Hans Allis Date: Tue, 21 Sep 2021 17:14:38 +0200 Subject: [PATCH 4/4] Changed .filter(..).first to .first(where:) --- Tree Tracker/Screens/Upload/UploadViewModel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tree Tracker/Screens/Upload/UploadViewModel.swift b/Tree Tracker/Screens/Upload/UploadViewModel.swift index f426836..298a62d 100644 --- a/Tree Tracker/Screens/Upload/UploadViewModel.swift +++ b/Tree Tracker/Screens/Upload/UploadViewModel.swift @@ -143,7 +143,7 @@ final class UploadViewModel: CollectionViewModel { let sortedTrees = trees.sorted(by: \.createDate, order: .descending) while (self != nil && self!.currentUploads.count < self!.maxConcurrentUploads && self!.currentUploads.count < trees.count) { - guard let tree = sortedTrees.filter({ treeEntry in self?.currentUploads[treeEntry.phImageId] == nil && (self?.failedUploadCount[treeEntry.phImageId] ?? 0) < self!.maxUploadAttempts}).first else { + guard let tree = sortedTrees.first(where: { treeEntry in self?.currentUploads[treeEntry.phImageId] == nil && (self?.failedUploadCount[treeEntry.phImageId] ?? 0) < self!.maxUploadAttempts}) else { self?.logger.log(.upload, "No more items to upload - bailing.") self?.stopUploading() return