diff --git a/CapCollector.xcodeproj/project.xcworkspace/xcuserdata/imac.xcuserdatad/UserInterfaceState.xcuserstate b/CapCollector.xcodeproj/project.xcworkspace/xcuserdata/imac.xcuserdatad/UserInterfaceState.xcuserstate index b15d0c8..0577e13 100644 Binary files a/CapCollector.xcodeproj/project.xcworkspace/xcuserdata/imac.xcuserdatad/UserInterfaceState.xcuserstate and b/CapCollector.xcodeproj/project.xcworkspace/xcuserdata/imac.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/CapCollector/AppDelegate.swift b/CapCollector/AppDelegate.swift index 3750df9..64f38f4 100644 --- a/CapCollector/AppDelegate.swift +++ b/CapCollector/AppDelegate.swift @@ -60,7 +60,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } } - let serverUrl = URL(string: "https://christophhagen.de")! + let serverUrl = URL(string: "https://christophhagen.de:6000")! func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { app = self diff --git a/CapCollector/Base.lproj/Main.storyboard b/CapCollector/Base.lproj/Main.storyboard index 5f90789..ba932a3 100644 --- a/CapCollector/Base.lproj/Main.storyboard +++ b/CapCollector/Base.lproj/Main.storyboard @@ -1,9 +1,8 @@ - + - - + diff --git a/CapCollector/Data/Database.swift b/CapCollector/Data/Database.swift index 6c6c7ab..a49b4c9 100644 --- a/CapCollector/Data/Database.swift +++ b/CapCollector/Data/Database.swift @@ -128,13 +128,23 @@ final class Database { var pendingCapUploads: [Cap] { do { - return try db.prepare(Cap.table.filter(Cap.columnUploaded == false)).map(Cap.init) + return try db.prepare(Cap.table.filter(Cap.columnUploaded == false).order(Cap.columnId.asc)).map(Cap.init) } catch { log("Failed to get pending cap uploads") return [] } } + var hasPendingCapUploads: Bool { + do { + let query = Cap.table.filter(Cap.columnUploaded == false).count + return try db.scalar(query) > 0 + } catch { + log("Failed to get pending cap upload count") + return false + } + } + var classifierVersion: Int { set { UserDefaults.standard.set(newValue, forKey: Classifier.userDefaultsKey) @@ -409,24 +419,17 @@ final class Database { // MARK: Downloads @discardableResult - func downloadMainImage(for cap: Int, completion: @escaping (_ success: Bool) -> Void) -> Bool { - return download.mainImage(for: cap) { success in - guard success else { - completion(false) - return + func downloadImage(for cap: Int, version: Int = 0, completion: @escaping (_ image: UIImage?) -> Void) -> Bool { + return download.image(for: cap, version: version) { image in + if version == 0 && image != nil { + DispatchQueue.main.async { + self.delegate?.database(didLoadImageForCap: cap) + } } - DispatchQueue.main.async { - self.delegate?.database(didLoadImageForCap: cap) - } - completion(true) + completion(image) } } - @discardableResult - func downloadImage(for cap: Int, version: Int, completion: @escaping (_ success: Bool) -> Void) -> Bool { - return download.image(for: cap, version: version, completion: completion) - } - func downloadCapNames(completion: @escaping (_ success: Bool) -> Void) { log("Downloading cap names") download.names { names in @@ -496,7 +499,7 @@ final class Database { DispatchQueue.global(qos: .userInitiated).async { for part in caps.split(intoPartsOf: split) { for id in part { - let downloading = self.downloadMainImage(for: id) { _ in + let downloading = self.downloadImage(for: id) { _ in group.leave() } if downloading { @@ -658,6 +661,24 @@ final class Database { self.update(count: count, for: cap) } } + + private func uploadNextItem() { + let capUploads = self.pendingCapUploads + if let id = capUploads.first { + + return + } + let imageUploads = pendingImageUploads + guard imageUploads.count > 0 else { + log("No pending image uploads") + return + } + uploadRemainingImages() + } + + private func upload(cap: Int) { + + } func uploadRemainingData() { guard !isInOfflineMode else { @@ -673,23 +694,33 @@ final class Database { log("\(uploads.count) cap uploads pending") var remaining = uploads.count - for cap in uploads { - upload.upload(name: cap.name, for: cap.id) { success in - if success { - self.log("Uploaded cap \(cap.id)") - self.update(uploaded: true, for: cap.id) - } else { - self.log("Failed to upload cap \(cap.id)") - } - - remaining -= 1 - if remaining == 0 { - DispatchQueue.main.async { - self.uploadRemainingImages() + DispatchQueue.global(qos: .background).async { + let group = DispatchGroup() + for cap in uploads { + group.enter() + self.upload.upload(name: cap.name, for: cap.id) { success in + group.leave() + if success { + self.log("Uploaded cap \(cap.id)") + self.update(uploaded: true, for: cap.id) + } else { + self.log("Failed to upload cap \(cap.id)") + return } + + remaining -= 1 + + } + guard group.wait(timeout: .now() + .seconds(60)) == .success else { + self.log("Timed out uploading cap \(cap.id)") + return } } + DispatchQueue.main.async { + self.uploadRemainingImages() + } } + } private func uploadRemainingImages() { @@ -700,16 +731,35 @@ final class Database { } log("\(uploads.count) image uploads pending") - for (cap, version) in uploads { - upload.uploadImage(for: cap, version: version) { count in - guard let _ = count else { - self.log("Failed to upload version \(version) of cap \(cap)") + DispatchQueue.global(qos: .background).async { + let group = DispatchGroup() + for (id, version) in uploads { + guard let cap = self.cap(for: id) else { + self.log("No cap \(id) to upload image \(version)") + self.removePendingUpload(of: id, version: version) + continue + } + guard cap.uploaded else { + self.log("Cap \(id) not uploaded, skipping image upload") + continue + } + group.enter() + self.upload.uploadImage(for: id, version: version) { count in + group.leave() + guard let _ = count else { + self.log("Failed to upload version \(version) of cap \(id)") + return + } + self.log("Uploaded version \(version) of cap \(id)") + self.removePendingUpload(of: id, version: version) + } + guard group.wait(timeout: .now() + .seconds(60)) == .success else { + self.log("Timed out uploading version \(version) of cap \(id)") return } - self.log("Uploaded version \(version) of cap \(cap)") - self.removePendingUpload(of: cap, version: version) } } + } @discardableResult diff --git a/CapCollector/Data/Download.swift b/CapCollector/Data/Download.swift index b5f74d0..7cd10d5 100644 --- a/CapCollector/Data/Download.swift +++ b/CapCollector/Data/Download.swift @@ -116,59 +116,35 @@ final class Download { - Parameter cap: The id of the cap. - Parameter version: The image version to download. - Parameter completion: A closure with the resulting image - - Note: The closure will be called from the main queue. - Returns: `true`, of the file download was started, `false`, if the image is already downloading. */ @discardableResult - func mainImage(for cap: Int, completion: @escaping (_ success: Bool) -> Void) -> Bool { - let url = serverImageUrl(for: cap) - let query = "Main image of cap \(cap)" - guard !downloadingMainImages.contains(cap) else { - return false + func image(for cap: Int, version: Int = 0, completion: @escaping (_ image: UIImage?) -> Void) -> Bool { + // Check if main image, and already being downloaded + if version == 0 { + guard !downloadingMainImages.contains(cap) else { + return false + } + downloadingMainImages.insert(cap) } - downloadingMainImages.insert(cap) - - let task = session.downloadTask(with: url) { fileUrl, response, error in - DispatchQueue.main.async { - self.downloadingMainImages.remove(cap) - } - guard let fileUrl = self.convertResponse(to: query, fileUrl, response, error) else { - completion(false) - return - } - guard app.storage.saveImage(at: fileUrl, for: cap) else { - self.log("Request '\(query)' could not move downloaded file") - completion(false) - return - } - completion(true) - } - task.resume() - return true - } - - /** - Download an image for a cap. - - Parameter cap: The id of the cap. - - Parameter version: The image version to download. - - Parameter completion: A closure with the resulting image - - Returns: `true`, of the file download was started, `false`, if the image is already downloading. - */ - @discardableResult - func image(for cap: Int, version: Int, completion: @escaping (_ success: Bool) -> Void) -> Bool { let url = serverImageUrl(for: cap, version: version) let query = "Image of cap \(cap) version \(version)" let task = session.downloadTask(with: url) { fileUrl, response, error in + if version == 0 { + DispatchQueue.main.async { + self.downloadingMainImages.remove(cap) + } + } guard let fileUrl = self.convertResponse(to: query, fileUrl, response, error) else { - completion(false) + completion(nil) return } - guard app.storage.saveImage(at: fileUrl, for: cap, version: version) else { + guard let image = app.storage.saveImage(at: fileUrl, for: cap, version: version) else { self.log("Request '\(query)' could not move downloaded file") - completion(false) + completion(nil) return } - completion(true) + completion(image) } task.resume() return true diff --git a/CapCollector/Data/Storage.swift b/CapCollector/Data/Storage.swift index 2d7feb6..a98ed6f 100644 --- a/CapCollector/Data/Storage.swift +++ b/CapCollector/Data/Storage.swift @@ -56,17 +56,17 @@ final class Storage { - parameter version: The version of the image to get - returns: True, if the image was saved */ - func saveImage(at url: URL, for cap: Int, version: Int = 0) -> Bool { + func saveImage(at url: URL, for cap: Int, version: Int = 0) -> UIImage? { let targetUrl = localImageUrl(for: cap, version: version) do { if fm.fileExists(atPath: targetUrl.path) { try fm.removeItem(at: targetUrl) } try fm.moveItem(at: url, to: targetUrl) - return true + return UIImage(contentsOfFile: targetUrl.path) } catch { log("Failed to delete or move image \(version) for cap \(cap)") - return false + return nil } } diff --git a/CapCollector/Info.plist b/CapCollector/Info.plist index 36be64f..a6c6308 100644 --- a/CapCollector/Info.plist +++ b/CapCollector/Info.plist @@ -18,17 +18,6 @@ APPL CFBundleShortVersionString $(MARKETING_VERSION) - CFBundleURLTypes - - - CFBundleURLName - - CFBundleURLSchemes - - db-n81tx2g638wuffl - - - CFBundleVersion 1 LSApplicationQueriesSchemes diff --git a/CapCollector/Presentation/GridViewController.swift b/CapCollector/Presentation/GridViewController.swift index fe5247c..973f876 100644 --- a/CapCollector/Presentation/GridViewController.swift +++ b/CapCollector/Presentation/GridViewController.swift @@ -230,8 +230,8 @@ class GridViewController: UIViewController { } private func downloadImage(cap id: Int, tile: Int) { - app.database.downloadMainImage(for: id) { success in - guard success else { + app.database.downloadImage(for: id) { img in + guard img != nil else { return } guard let view = self.installedTiles[tile] else { diff --git a/CapCollector/Presentation/ImageSelector.swift b/CapCollector/Presentation/ImageSelector.swift index 43d5ba9..560c060 100644 --- a/CapCollector/Presentation/ImageSelector.swift +++ b/CapCollector/Presentation/ImageSelector.swift @@ -96,33 +96,28 @@ class ImageSelector: UIViewController { private func downloadImages() { images = [UIImage?](repeating: nil, count: cap.count) log("\(cap.count) images for cap \(cap.id)") - if let image = app.storage.image(for: cap.id) { - self.images[0] = image - self.collection.reloadItems(at: [IndexPath(row: 0, section: 0)]) - } else { - app.database.downloadMainImage(for: cap.id) { success in - guard success, let image = app.storage.image(for: self.cap.id) else { - return - } - self.images[0] = image - DispatchQueue.main.async { - self.collection.reloadItems(at: [IndexPath(row: 0, section: 0)]) + for version in 0.. 0 else { - return - } - for number in 1..