Fix port, begin upload UI

This commit is contained in:
Christoph Hagen 2021-01-10 16:11:31 +01:00
parent 75ebdf59ae
commit 746b69defc
11 changed files with 142 additions and 121 deletions

View File

@ -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 { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
app = self app = self

View File

@ -1,9 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17156" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="qlf-I7-aOI"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="qlf-I7-aOI">
<device id="retina4_7" orientation="portrait" appearance="light"/> <device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies> <dependencies>
<deployment identifier="iOS"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17703"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17125"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/> <capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/> <capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>

View File

@ -128,13 +128,23 @@ final class Database {
var pendingCapUploads: [Cap] { var pendingCapUploads: [Cap] {
do { 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 { } catch {
log("Failed to get pending cap uploads") log("Failed to get pending cap uploads")
return [] 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 { var classifierVersion: Int {
set { set {
UserDefaults.standard.set(newValue, forKey: Classifier.userDefaultsKey) UserDefaults.standard.set(newValue, forKey: Classifier.userDefaultsKey)
@ -409,24 +419,17 @@ final class Database {
// MARK: Downloads // MARK: Downloads
@discardableResult @discardableResult
func downloadMainImage(for cap: Int, completion: @escaping (_ success: Bool) -> Void) -> Bool { func downloadImage(for cap: Int, version: Int = 0, completion: @escaping (_ image: UIImage?) -> Void) -> Bool {
return download.mainImage(for: cap) { success in return download.image(for: cap, version: version) { image in
guard success else { if version == 0 && image != nil {
completion(false) DispatchQueue.main.async {
return self.delegate?.database(didLoadImageForCap: cap)
}
} }
DispatchQueue.main.async { completion(image)
self.delegate?.database(didLoadImageForCap: cap)
}
completion(true)
} }
} }
@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) { func downloadCapNames(completion: @escaping (_ success: Bool) -> Void) {
log("Downloading cap names") log("Downloading cap names")
download.names { names in download.names { names in
@ -496,7 +499,7 @@ final class Database {
DispatchQueue.global(qos: .userInitiated).async { DispatchQueue.global(qos: .userInitiated).async {
for part in caps.split(intoPartsOf: split) { for part in caps.split(intoPartsOf: split) {
for id in part { for id in part {
let downloading = self.downloadMainImage(for: id) { _ in let downloading = self.downloadImage(for: id) { _ in
group.leave() group.leave()
} }
if downloading { if downloading {
@ -658,6 +661,24 @@ final class Database {
self.update(count: count, for: cap) 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() { func uploadRemainingData() {
guard !isInOfflineMode else { guard !isInOfflineMode else {
@ -673,23 +694,33 @@ final class Database {
log("\(uploads.count) cap uploads pending") log("\(uploads.count) cap uploads pending")
var remaining = uploads.count var remaining = uploads.count
for cap in uploads { DispatchQueue.global(qos: .background).async {
upload.upload(name: cap.name, for: cap.id) { success in let group = DispatchGroup()
if success { for cap in uploads {
self.log("Uploaded cap \(cap.id)") group.enter()
self.update(uploaded: true, for: cap.id) self.upload.upload(name: cap.name, for: cap.id) { success in
} else { group.leave()
self.log("Failed to upload cap \(cap.id)") if success {
} self.log("Uploaded cap \(cap.id)")
self.update(uploaded: true, for: cap.id)
remaining -= 1 } else {
if remaining == 0 { self.log("Failed to upload cap \(cap.id)")
DispatchQueue.main.async { return
self.uploadRemainingImages()
} }
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() { private func uploadRemainingImages() {
@ -700,16 +731,35 @@ final class Database {
} }
log("\(uploads.count) image uploads pending") log("\(uploads.count) image uploads pending")
for (cap, version) in uploads { DispatchQueue.global(qos: .background).async {
upload.uploadImage(for: cap, version: version) { count in let group = DispatchGroup()
guard let _ = count else { for (id, version) in uploads {
self.log("Failed to upload version \(version) of cap \(cap)") 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 return
} }
self.log("Uploaded version \(version) of cap \(cap)")
self.removePendingUpload(of: cap, version: version)
} }
} }
} }
@discardableResult @discardableResult

View File

@ -116,59 +116,35 @@ final class Download {
- Parameter cap: The id of the cap. - Parameter cap: The id of the cap.
- Parameter version: The image version to download. - Parameter version: The image version to download.
- Parameter completion: A closure with the resulting image - 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. - Returns: `true`, of the file download was started, `false`, if the image is already downloading.
*/ */
@discardableResult @discardableResult
func mainImage(for cap: Int, completion: @escaping (_ success: Bool) -> Void) -> Bool { func image(for cap: Int, version: Int = 0, completion: @escaping (_ image: UIImage?) -> Void) -> Bool {
let url = serverImageUrl(for: cap) // Check if main image, and already being downloaded
let query = "Main image of cap \(cap)" if version == 0 {
guard !downloadingMainImages.contains(cap) else { guard !downloadingMainImages.contains(cap) else {
return false 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 url = serverImageUrl(for: cap, version: version)
let query = "Image of cap \(cap) version \(version)" let query = "Image of cap \(cap) version \(version)"
let task = session.downloadTask(with: url) { fileUrl, response, error in 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 { guard let fileUrl = self.convertResponse(to: query, fileUrl, response, error) else {
completion(false) completion(nil)
return 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") self.log("Request '\(query)' could not move downloaded file")
completion(false) completion(nil)
return return
} }
completion(true) completion(image)
} }
task.resume() task.resume()
return true return true

View File

@ -56,17 +56,17 @@ final class Storage {
- parameter version: The version of the image to get - parameter version: The version of the image to get
- returns: True, if the image was saved - 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) let targetUrl = localImageUrl(for: cap, version: version)
do { do {
if fm.fileExists(atPath: targetUrl.path) { if fm.fileExists(atPath: targetUrl.path) {
try fm.removeItem(at: targetUrl) try fm.removeItem(at: targetUrl)
} }
try fm.moveItem(at: url, to: targetUrl) try fm.moveItem(at: url, to: targetUrl)
return true return UIImage(contentsOfFile: targetUrl.path)
} catch { } catch {
log("Failed to delete or move image \(version) for cap \(cap)") log("Failed to delete or move image \(version) for cap \(cap)")
return false return nil
} }
} }

View File

@ -18,17 +18,6 @@
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string> <string>$(MARKETING_VERSION)</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string></string>
<key>CFBundleURLSchemes</key>
<array>
<string>db-n81tx2g638wuffl</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1</string> <string>1</string>
<key>LSApplicationQueriesSchemes</key> <key>LSApplicationQueriesSchemes</key>

View File

@ -230,8 +230,8 @@ class GridViewController: UIViewController {
} }
private func downloadImage(cap id: Int, tile: Int) { private func downloadImage(cap id: Int, tile: Int) {
app.database.downloadMainImage(for: id) { success in app.database.downloadImage(for: id) { img in
guard success else { guard img != nil else {
return return
} }
guard let view = self.installedTiles[tile] else { guard let view = self.installedTiles[tile] else {

View File

@ -96,33 +96,28 @@ class ImageSelector: UIViewController {
private func downloadImages() { private func downloadImages() {
images = [UIImage?](repeating: nil, count: cap.count) images = [UIImage?](repeating: nil, count: cap.count)
log("\(cap.count) images for cap \(cap.id)") log("\(cap.count) images for cap \(cap.id)")
if let image = app.storage.image(for: cap.id) { for version in 0..<cap.count {
self.images[0] = image if let image = app.storage.image(for: cap.id, version: version) {
self.collection.reloadItems(at: [IndexPath(row: 0, section: 0)]) log("Image \(version) already downloaded")
} else { set(image, for: version)
app.database.downloadMainImage(for: cap.id) { success in } else {
guard success, let image = app.storage.image(for: self.cap.id) else { log("Downloading image \(version)")
return app.database.downloadImage(for: cap.id, version: version) { image in
} self.set(image, for: version)
self.images[0] = image if image != nil {
DispatchQueue.main.async { self.log("Downloaded version \(version)")
self.collection.reloadItems(at: [IndexPath(row: 0, section: 0)]) } else {
self.log("Failed to download image \(version)")
}
} }
} }
} }
guard cap.count > 0 else { }
return
} private func set(_ image: UIImage?, for version: Int) {
for number in 1..<cap.count { self.images[version] = image
app.database.downloadImage(for: cap.id, version: number) { success in DispatchQueue.main.async {
guard success, let image = app.storage.image(for: self.cap.id, version: number) else { self.collection.reloadItems(at: [IndexPath(row: version, section: 0)])
return
}
self.images[number] = image
DispatchQueue.main.async {
self.collection.reloadItems(at: [IndexPath(row: number, section: 0)])
}
}
} }
} }

View File

@ -41,6 +41,8 @@ class TableView: UITableViewController {
private var processingScreenHud: JGProgressHUD? private var processingScreenHud: JGProgressHUD?
private var titleViewUpdateTimer: Timer?
// MARK: Computed properties // MARK: Computed properties
private var titleText: String { private var titleText: String {
@ -855,7 +857,7 @@ extension TableView {
cell.set(image: image) cell.set(image: image)
} else { } else {
cell.set(image: nil) cell.set(image: nil)
app.database.downloadMainImage(for: cap.id) { _ in app.database.downloadImage(for: cap.id) { _ in
// Delegate call will update image // Delegate call will update image
} }
} }
@ -1072,4 +1074,13 @@ extension TableView: CapAccessoryDelegate {
func capAccessoryCameraButtonPressed() { func capAccessoryCameraButtonPressed() {
showCameraView() showCameraView()
} }
}
extension TableView {
private func switchBetweenNavigationTitleViewsIfNeeded() {
}
} }

View File

@ -8,7 +8,8 @@
import UIKit import UIKit
@IBDesignable class CropView: UIView { //@IBDesignable
class CropView: UIView {
@IBInspectable var lineColor: UIColor = UIColor.black @IBInspectable var lineColor: UIColor = UIColor.black