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 {
app = self

View File

@ -1,9 +1,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"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17125"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17703"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.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] {
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,22 +419,15 @@ 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)
}
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) {
@ -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 {
@ -659,6 +662,24 @@ final class Database {
}
}
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 {
log("Not uploading pending data due to offline mode")
@ -673,23 +694,33 @@ final class Database {
log("\(uploads.count) cap uploads pending")
var remaining = uploads.count
DispatchQueue.global(qos: .background).async {
let group = DispatchGroup()
for cap in uploads {
upload.upload(name: cap.name, for: cap.id) { success in
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
if remaining == 0 {
}
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,18 +731,37 @@ final class Database {
}
log("\(uploads.count) image uploads pending")
for (cap, version) in uploads {
upload.uploadImage(for: cap, version: version) { count in
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 \(cap)")
self.log("Failed to upload version \(version) of cap \(id)")
return
}
self.log("Uploaded version \(version) of cap \(cap)")
self.removePendingUpload(of: cap, version: version)
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
}
}
}
}
@discardableResult
func removePendingUpload(of cap: Int, version: Int) -> Bool {
do {

View File

@ -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)"
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)
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

View File

@ -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
}
}

View File

@ -18,17 +18,6 @@
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<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>
<string>1</string>
<key>LSApplicationQueriesSchemes</key>

View File

@ -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 {

View File

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

View File

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

View File

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