Add new images to image cache

This commit is contained in:
Christoph Hagen 2022-06-24 12:06:39 +02:00
parent 9b9ce63f7c
commit 68f82ce367
2 changed files with 94 additions and 59 deletions

View File

@ -49,8 +49,29 @@ final class Database: ObservableObject {
changedCapStorage = newValue.map { "\($0)" }.joined(separator: ",")
}
}
private lazy var imageUploads: [Int: Int] = loadImageUploadCounts()
@AppStorage("uploads")
private var pendingImageUploadStorage: String = ""
private(set) var imageUploads: [Int: [Int]] {
get {
pendingImageUploadStorage.components(separatedBy: ";").reduce(into: [:]) { dict, string in
let parts = string.components(separatedBy: "-")
guard parts.count == 2 else {
return
}
guard let cap = Int(parts[0]) else {
return
}
dict[cap] = parts[1].components(separatedBy: ":").compactMap(Int.init)
}
}
set {
pendingImageUploadStorage = newValue.map { cap, images in
"\(cap)-\(images.map { "\($0)" }.joined(separator: ":"))"
}.joined(separator: ";")
}
}
private var uploadTimer: Timer?
@ -128,7 +149,7 @@ final class Database: ObservableObject {
}
private var serverClassifierVersionUrl: URL {
serverUrl.appendingPathComponent("classifier.version")
serverUrl.appendingPathComponent("version")
}
private var gridStorageFolder: URL {
@ -255,7 +276,10 @@ final class Database: ObservableObject {
#warning("Merge changed caps with server updates")
} else {
oldCap.update(with: cap)
caps[cap.id] = oldCap
let save = oldCap
DispatchQueue.main.async {
self.caps[cap.id] = save
}
updates += 1
}
}
@ -289,10 +313,10 @@ final class Database: ObservableObject {
self.serverClassifierVersion = serverVersion
}
guard serverVersion > self.classifierVersion else {
print("No new classifier available")
print("No new classifier available (Local: \(classifierVersion) Server: \(serverVersion))")
return false
}
print("New classifier \(serverVersion) available")
print("New classifier available (Local: \(classifierVersion) Server: \(serverVersion))")
return true
}
@ -348,45 +372,29 @@ final class Database: ObservableObject {
@discardableResult
func save(_ image: UIImage, for capId: Int) -> Bool {
guard caps[capId] != nil else {
guard let cap = caps[capId] else {
log("Failed to save image for missing cap \(capId)")
return false
}
guard ensureFolderExistence(imageUploadFolderUrl) else {
guard images.save(image, for: CapImage(cap: cap.id, version: cap.imageCount)) else {
return false
}
guard let data = image.jpegData(compressionQuality: imageCompressionQuality) else {
log("Failed to compress image for cap: \(capId)")
return false
log("Saved image \(cap.imageCount) for cap \(capId)")
if imageUploads[capId] != nil {
DispatchQueue.main.async {
self.imageUploads[capId]!.append(cap.imageCount)
}
} else {
DispatchQueue.main.async {
self.imageUploads[capId] = [cap.imageCount]
}
}
let hash = Data(SHA256.hash(data: data)).hexEncoded.prefix(16)
let url = imageUploadFolderUrl.appendingPathComponent("\(capId)-\(hash).jpg")
do {
try data.write(to: url)
} catch {
log("Failed to save \(url.lastPathComponent): \(error)")
return false
DispatchQueue.main.async {
self.caps[capId]!.imageCount += 1
}
log("Saved \(url.lastPathComponent) for upload")
caps[capId]?.imageCount += 1
return true
}
private func loadImageUploadCounts() -> [Int : Int] {
var result = [Int : Int]()
pendingImageUploads.forEach { url in
guard let capId = capId(from: url) else {
return
}
if let old = result[capId] {
result[capId] = old + 1
} else {
result[capId] = 1
}
}
return result
}
// MARK: Uploads
func startRegularUploads() {
@ -437,12 +445,8 @@ final class Database: ObservableObject {
changedCaps.contains(cap) || imageUploads[cap] != nil
}
private var pendingImageUploads: [URL] {
(try? fm.contentsOfDirectory(at: imageUploadFolderUrl, includingPropertiesForKeys: nil)) ?? []
}
var pendingImageUploadCount: Int {
pendingImageUploads.count
imageUploads.values.reduce(0) { $0 + $1.count }
}
private func capId(from url: URL) -> Int? {
@ -454,24 +458,27 @@ final class Database: ObservableObject {
log("No server authentication to upload to server")
return
}
for url in pendingImageUploads {
guard let capId = capId(from: url) else {
log("Unexpected image \(url.lastPathComponent) in upload folder")
continue
}
guard fm.fileExists(atPath: url.path) else {
log("Missing image \(url.lastPathComponent) in upload folder")
continue
}
guard await upload(imageAt: url, for: capId) else {
log("Failed to upload image \(url.lastPathComponent)")
continue
}
log("Uploaded image \(url.lastPathComponent)")
do {
try fm.removeItem(at: url)
} catch {
log("Failed to remove uploaded image \(url.lastPathComponent): \(error)")
for (cap, images) in imageUploads {
for image in images {
guard let url = self.images.availableImageUrl(CapImage(cap: cap, version: image)) else {
log("Missing upload image \(image) for cap \(cap)")
continue
}
guard await upload(imageAt: url, for: cap) else {
log("Failed to upload image \(url.lastPathComponent)")
continue
}
log("Uploaded image \(image) for cap \(cap)")
let remaining = imageUploads[cap]?.filter { $0 != image }
if let r = remaining, !r.isEmpty {
DispatchQueue.main.async {
self.imageUploads[cap] = r
}
} else {
DispatchQueue.main.async {
self.imageUploads[cap] = nil
}
}
}
}
}

View File

@ -14,6 +14,8 @@ final class ImageCache {
private let session: URLSession = .shared
private let thumbnailQuality: CGFloat = 0.7
private let imageQuality: CGFloat = 0.3
init(folder: URL, server: URL, thumbnailSize: CGFloat) throws {
self.folder = folder
@ -26,12 +28,38 @@ final class ImageCache {
}
private func localImageUrl(_ image: CapImage) -> URL {
folder.appendingPathComponent(String(format: "%04d-%02d.jpg", image.cap, image.cap, image.version))
folder.appendingPathComponent(String(format: "%04d-%02d.jpg", image.cap, image.version))
}
private func remoteImageUrl(_ image: CapImage) -> URL {
server.appendingPathComponent(String(format: "images/%04d/%04d-%02d.jpg", image.cap, image.cap, image.version))
}
@discardableResult
func save(_ image: UIImage, for cap: CapImage) -> Bool {
guard let data = image.jpegData(compressionQuality: imageQuality) else {
return false
}
let localUrl = localImageUrl(cap)
guard removePossibleFile(localUrl) else {
return false
}
do {
try data.write(to: localUrl)
return true
} catch {
print("failed to save image \(localUrl.lastPathComponent): \(error)")
return false
}
}
func availableImageUrl(_ image: CapImage) -> URL? {
let url = localImageUrl(image)
guard exists(url) else {
return nil
}
return url
}
func image(_ image: CapImage, completion: @escaping (UIImage?) -> ()) {
Task {