Improve progress updating, fix warnings
This commit is contained in:
parent
3dc5674a3a
commit
92e1eacf57
@ -29,6 +29,7 @@
|
|||||||
E25AAC94283D88A4006E9E7F /* Cap.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25AAC93283D88A4006E9E7F /* Cap.swift */; };
|
E25AAC94283D88A4006E9E7F /* Cap.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25AAC93283D88A4006E9E7F /* Cap.swift */; };
|
||||||
E25AAC96283E14DF006E9E7F /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25AAC95283E14DF006E9E7F /* Database.swift */; };
|
E25AAC96283E14DF006E9E7F /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25AAC95283E14DF006E9E7F /* Database.swift */; };
|
||||||
E25AAC9B283E3395006E9E7F /* CapRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25AAC9A283E3395006E9E7F /* CapRowView.swift */; };
|
E25AAC9B283E3395006E9E7F /* CapRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25AAC9A283E3395006E9E7F /* CapRowView.swift */; };
|
||||||
|
E29E17C92D4D0ABF00E0EE54 /* Database+Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29E17C82D4D0ABF00E0EE54 /* Database+Mock.swift */; };
|
||||||
E2EA00C3283E672A00F7B269 /* SFSafeSymbols in Frameworks */ = {isa = PBXBuildFile; productRef = E2EA00C2283E672A00F7B269 /* SFSafeSymbols */; };
|
E2EA00C3283E672A00F7B269 /* SFSafeSymbols in Frameworks */ = {isa = PBXBuildFile; productRef = E2EA00C2283E672A00F7B269 /* SFSafeSymbols */; };
|
||||||
E2EA00C5283EA72000F7B269 /* SortCriteria.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2EA00C4283EA72000F7B269 /* SortCriteria.swift */; };
|
E2EA00C5283EA72000F7B269 /* SortCriteria.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2EA00C4283EA72000F7B269 /* SortCriteria.swift */; };
|
||||||
E2EA00C7283EAA0100F7B269 /* SortSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2EA00C6283EAA0100F7B269 /* SortSelectionView.swift */; };
|
E2EA00C7283EAA0100F7B269 /* SortSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2EA00C6283EAA0100F7B269 /* SortSelectionView.swift */; };
|
||||||
@ -78,6 +79,7 @@
|
|||||||
E25AAC95283E14DF006E9E7F /* Database.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Database.swift; sourceTree = "<group>"; };
|
E25AAC95283E14DF006E9E7F /* Database.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Database.swift; sourceTree = "<group>"; };
|
||||||
E25AAC9A283E3395006E9E7F /* CapRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CapRowView.swift; sourceTree = "<group>"; };
|
E25AAC9A283E3395006E9E7F /* CapRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CapRowView.swift; sourceTree = "<group>"; };
|
||||||
E268C72D29BF2D8400D813A0 /* ISSUES.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = ISSUES.md; sourceTree = "<group>"; };
|
E268C72D29BF2D8400D813A0 /* ISSUES.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = ISSUES.md; sourceTree = "<group>"; };
|
||||||
|
E29E17C82D4D0ABF00E0EE54 /* Database+Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Database+Mock.swift"; sourceTree = "<group>"; };
|
||||||
E2EA00C4283EA72000F7B269 /* SortCriteria.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SortCriteria.swift; sourceTree = "<group>"; };
|
E2EA00C4283EA72000F7B269 /* SortCriteria.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SortCriteria.swift; sourceTree = "<group>"; };
|
||||||
E2EA00C6283EAA0100F7B269 /* SortSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SortSelectionView.swift; sourceTree = "<group>"; };
|
E2EA00C6283EAA0100F7B269 /* SortSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SortSelectionView.swift; sourceTree = "<group>"; };
|
||||||
E2EA00CB283EB43E00F7B269 /* SortCaseRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SortCaseRowView.swift; sourceTree = "<group>"; };
|
E2EA00CB283EB43E00F7B269 /* SortCaseRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SortCaseRowView.swift; sourceTree = "<group>"; };
|
||||||
@ -100,6 +102,10 @@
|
|||||||
E2ED70992A73D86F00067808 /* String+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extensions.swift"; sourceTree = "<group>"; };
|
E2ED70992A73D86F00067808 /* String+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extensions.swift"; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
|
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||||
|
E29E17C32D4D0A9300E0EE54 /* Download */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = Download; sourceTree = "<group>"; };
|
||||||
|
/* End PBXFileSystemSynchronizedRootGroup section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
E25AAC75283D855D006E9E7F /* Frameworks */ = {
|
E25AAC75283D855D006E9E7F /* Frameworks */ = {
|
||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
@ -133,6 +139,7 @@
|
|||||||
E25AAC7A283D855D006E9E7F /* Caps */ = {
|
E25AAC7A283D855D006E9E7F /* Caps */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
E29E17C32D4D0A9300E0EE54 /* Download */,
|
||||||
E25AAC7B283D855D006E9E7F /* CapsApp.swift */,
|
E25AAC7B283D855D006E9E7F /* CapsApp.swift */,
|
||||||
E25AAC7D283D855D006E9E7F /* ContentView.swift */,
|
E25AAC7D283D855D006E9E7F /* ContentView.swift */,
|
||||||
E2EA00CF283EDD2C00F7B269 /* Camera */,
|
E2EA00CF283EDD2C00F7B269 /* Camera */,
|
||||||
@ -149,6 +156,7 @@
|
|||||||
E25AAC81283D855F006E9E7F /* Preview Content */ = {
|
E25AAC81283D855F006E9E7F /* Preview Content */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
E29E17C82D4D0ABF00E0EE54 /* Database+Mock.swift */,
|
||||||
E25AAC82283D855F006E9E7F /* Preview Assets.xcassets */,
|
E25AAC82283D855F006E9E7F /* Preview Assets.xcassets */,
|
||||||
);
|
);
|
||||||
path = "Preview Content";
|
path = "Preview Content";
|
||||||
@ -234,6 +242,9 @@
|
|||||||
);
|
);
|
||||||
dependencies = (
|
dependencies = (
|
||||||
);
|
);
|
||||||
|
fileSystemSynchronizedGroups = (
|
||||||
|
E29E17C32D4D0A9300E0EE54 /* Download */,
|
||||||
|
);
|
||||||
name = Caps;
|
name = Caps;
|
||||||
packageProductDependencies = (
|
packageProductDependencies = (
|
||||||
E2EA00C2283E672A00F7B269 /* SFSafeSymbols */,
|
E2EA00C2283E672A00F7B269 /* SFSafeSymbols */,
|
||||||
@ -332,6 +343,7 @@
|
|||||||
E25AAC8D283D86CF006E9E7F /* Logger.swift in Sources */,
|
E25AAC8D283D86CF006E9E7F /* Logger.swift in Sources */,
|
||||||
E2ED709A2A73D86F00067808 /* String+Extensions.swift in Sources */,
|
E2ED709A2A73D86F00067808 /* String+Extensions.swift in Sources */,
|
||||||
E20D105028574E190019BD91 /* CapImage.swift in Sources */,
|
E20D105028574E190019BD91 /* CapImage.swift in Sources */,
|
||||||
|
E29E17C92D4D0ABF00E0EE54 /* Database+Mock.swift in Sources */,
|
||||||
E2EA00ED2841170100F7B269 /* UIImage+Extensions.swift in Sources */,
|
E2EA00ED2841170100F7B269 /* UIImage+Extensions.swift in Sources */,
|
||||||
E20D105228589AAC0019BD91 /* FileManager+Extensions.swift in Sources */,
|
E20D105228589AAC0019BD91 /* FileManager+Extensions.swift in Sources */,
|
||||||
882C955E2AE7F0DE00657886 /* ClassifierDownloadView.swift in Sources */,
|
882C955E2AE7F0DE00657886 /* ClassifierDownloadView.swift in Sources */,
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
{
|
{
|
||||||
|
"originHash" : "c78617470808606280f24ff566c34cb1b032779babfb39f1ddbcda602b363bd4",
|
||||||
"pins" : [
|
"pins" : [
|
||||||
{
|
{
|
||||||
"identity" : "bottom-sheet",
|
"identity" : "bottom-sheet",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/weitieda/bottom-sheet",
|
"location" : "https://github.com/weitieda/bottom-sheet",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "4e074d49f3148577ac66cf47b85a99d016480d01",
|
"revision" : "6b21007153365235418f3943a960a1f9546592e0",
|
||||||
"version" : "1.0.10"
|
"version" : "1.0.12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -19,5 +20,5 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"version" : 2
|
"version" : 3
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
@ -3,6 +3,7 @@ import SwiftUI
|
|||||||
import Vision
|
import Vision
|
||||||
import CryptoKit
|
import CryptoKit
|
||||||
|
|
||||||
|
@MainActor
|
||||||
final class Database: ObservableObject {
|
final class Database: ObservableObject {
|
||||||
|
|
||||||
@AppStorage("classifier")
|
@AppStorage("classifier")
|
||||||
@ -33,6 +34,8 @@ final class Database: ObservableObject {
|
|||||||
private let encoder = JSONEncoder()
|
private let encoder = JSONEncoder()
|
||||||
private let decoder = JSONDecoder()
|
private let decoder = JSONDecoder()
|
||||||
|
|
||||||
|
private let progressObserver = ClassifierProgressObserver()
|
||||||
|
|
||||||
@AppStorage("serverUrl")
|
@AppStorage("serverUrl")
|
||||||
var serverPath: String = "" {
|
var serverPath: String = "" {
|
||||||
didSet {
|
didSet {
|
||||||
@ -150,9 +153,7 @@ final class Database: ObservableObject {
|
|||||||
}
|
}
|
||||||
set {
|
set {
|
||||||
_classifierClassesCache = newValue
|
_classifierClassesCache = newValue
|
||||||
DispatchQueue.main.async {
|
self._classifierClassesString = newValue.map { "\($0)" }.joined(separator: ",")
|
||||||
self._classifierClassesString = newValue.map { "\($0)" }.joined(separator: ",")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -187,6 +188,11 @@ final class Database: ObservableObject {
|
|||||||
updatePendingImageUploadCount(imageUploads: imageUploads)
|
updatePendingImageUploadCount(imageUploads: imageUploads)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
convenience init(caps: [Int : Cap]) {
|
||||||
|
self.init()
|
||||||
|
self.caps = caps
|
||||||
|
}
|
||||||
|
|
||||||
func mainImage(for cap: Int) -> Int {
|
func mainImage(for cap: Int) -> Int {
|
||||||
caps[cap]?.mainImage ?? 0
|
caps[cap]?.mainImage ?? 0
|
||||||
}
|
}
|
||||||
@ -358,9 +364,7 @@ final class Database: ObservableObject {
|
|||||||
} else {
|
} else {
|
||||||
oldCap.update(with: cap)
|
oldCap.update(with: cap)
|
||||||
let save = oldCap
|
let save = oldCap
|
||||||
DispatchQueue.main.async {
|
self.caps[cap.id] = save
|
||||||
self.caps[cap.id] = save
|
|
||||||
}
|
|
||||||
updates += 1
|
updates += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -394,9 +398,7 @@ final class Database: ObservableObject {
|
|||||||
log("Classifier version has an invalid value '\(string)'")
|
log("Classifier version has an invalid value '\(string)'")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
DispatchQueue.main.async {
|
self.storedServerClassifierVersion = serverVersion
|
||||||
self.storedServerClassifierVersion = serverVersion
|
|
||||||
}
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -409,19 +411,16 @@ final class Database: ObservableObject {
|
|||||||
log("Downloading classifier")
|
log("Downloading classifier")
|
||||||
|
|
||||||
let progress = ClassifierProgress()
|
let progress = ClassifierProgress()
|
||||||
DispatchQueue.main.async {
|
self.classifierDownloadProgress = progress
|
||||||
self.classifierDownloadProgress = progress
|
|
||||||
}
|
|
||||||
defer {
|
defer {
|
||||||
DispatchQueue.main.async {
|
self.classifierDownloadProgress = nil
|
||||||
self.classifierDownloadProgress = nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let tempUrl: URL
|
let tempUrl: URL
|
||||||
let response: URLResponse
|
let response: URLResponse
|
||||||
do {
|
do {
|
||||||
(tempUrl, response) = try await URLSession.shared.download(from: serverClassifierUrl, delegate: progress)
|
progressObserver.delegate = self
|
||||||
|
(tempUrl, response) = try await URLSession.shared.download(from: serverClassifierUrl, delegate: progressObserver)
|
||||||
} catch {
|
} catch {
|
||||||
log("Failed to download classifier version: \(error)")
|
log("Failed to download classifier version: \(error)")
|
||||||
return false
|
return false
|
||||||
@ -439,11 +438,11 @@ final class Database: ObservableObject {
|
|||||||
log("Failed to replace classifier: \(error)")
|
log("Failed to replace classifier: \(error)")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.storedLocalClassifierVersion = self.serverClassifierVersion
|
self.storedLocalClassifierVersion = self.serverClassifierVersion
|
||||||
log("Downloaded classifier \(self.localClassifierVersion)")
|
log("Downloaded classifier \(self.localClassifierVersion)")
|
||||||
self.classifier = nil
|
self.classifier = nil
|
||||||
}
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -509,9 +508,7 @@ final class Database: ObservableObject {
|
|||||||
func save(newCap name: String) -> Cap {
|
func save(newCap name: String) -> Cap {
|
||||||
let cap = Cap(id: nextCapId, name: name)
|
let cap = Cap(id: nextCapId, name: name)
|
||||||
caps[cap.id] = cap
|
caps[cap.id] = cap
|
||||||
DispatchQueue.main.async {
|
self.changedCaps.insert(cap.id)
|
||||||
self.changedCaps.insert(cap.id)
|
|
||||||
}
|
|
||||||
return cap
|
return cap
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -526,17 +523,11 @@ final class Database: ObservableObject {
|
|||||||
}
|
}
|
||||||
log("Saved image \(cap.imageCount) for cap \(capId)")
|
log("Saved image \(cap.imageCount) for cap \(capId)")
|
||||||
if imageUploads[capId] != nil {
|
if imageUploads[capId] != nil {
|
||||||
DispatchQueue.main.async {
|
self.imageUploads[capId]!.append(cap.imageCount)
|
||||||
self.imageUploads[capId]!.append(cap.imageCount)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
DispatchQueue.main.async {
|
self.imageUploads[capId] = [cap.imageCount]
|
||||||
self.imageUploads[capId] = [cap.imageCount]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.caps[capId]!.imageCount += 1
|
|
||||||
}
|
}
|
||||||
|
self.caps[capId]!.imageCount += 1
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -558,14 +549,10 @@ final class Database: ObservableObject {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
log("Starting upload timer")
|
log("Starting upload timer")
|
||||||
DispatchQueue.main.async {
|
self.uploadTimer = Timer.scheduledTimer(withTimeInterval: 5, repeats: true) { _ in
|
||||||
self.uploadTimer = Timer.scheduledTimer(withTimeInterval: 5, repeats: true, block: self.uploadTimerElapsed)
|
Task {
|
||||||
}
|
await self.uploadAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func uploadTimerElapsed(timer: Timer) {
|
|
||||||
Task {
|
|
||||||
await uploadAll()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -574,22 +561,16 @@ final class Database: ObservableObject {
|
|||||||
log("Already uploading")
|
log("Already uploading")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
DispatchQueue.main.async {
|
self.isUploading = true
|
||||||
self.isUploading = true
|
|
||||||
}
|
|
||||||
defer {
|
defer {
|
||||||
DispatchQueue.main.async {
|
self.isUploading = false
|
||||||
self.isUploading = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
guard !changedCaps.isEmpty || pendingImageUploadCount > 0 else {
|
guard !changedCaps.isEmpty || pendingImageUploadCount > 0 else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log("Starting uploads")
|
log("Starting uploads")
|
||||||
let uploaded = await uploadAllChangedCaps()
|
let uploaded = await uploadAllChangedCaps()
|
||||||
DispatchQueue.main.async {
|
self.changedCaps.subtract(uploaded)
|
||||||
self.changedCaps.subtract(uploaded)
|
|
||||||
}
|
|
||||||
await uploadAllImages()
|
await uploadAllImages()
|
||||||
log("Uploads finished")
|
log("Uploads finished")
|
||||||
}
|
}
|
||||||
@ -622,14 +603,10 @@ final class Database: ObservableObject {
|
|||||||
}
|
}
|
||||||
log("Uploaded image \(image) for cap \(cap)")
|
log("Uploaded image \(image) for cap \(cap)")
|
||||||
let remaining = imageUploads[cap]?.filter { $0 != image }
|
let remaining = imageUploads[cap]?.filter { $0 != image }
|
||||||
if let r = remaining, !r.isEmpty {
|
if let remaining, !remaining.isEmpty {
|
||||||
DispatchQueue.main.async {
|
self.imageUploads[cap] = remaining
|
||||||
self.imageUploads[cap] = r
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
DispatchQueue.main.async {
|
self.imageUploads[cap] = nil
|
||||||
self.imageUploads[cap] = nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -664,9 +641,7 @@ final class Database: ObservableObject {
|
|||||||
if httpResponse.statusCode == 410 {
|
if httpResponse.statusCode == 410 {
|
||||||
log("Missing cap for image \(url.lastPathComponent), reupload cap")
|
log("Missing cap for image \(url.lastPathComponent), reupload cap")
|
||||||
// Missing cap, force upload
|
// Missing cap, force upload
|
||||||
DispatchQueue.main.async {
|
self.changedCaps.insert(cap)
|
||||||
self.changedCaps.insert(cap)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
log("Failed to upload image \(url.lastPathComponent): Response \(httpResponse.statusCode)")
|
log("Failed to upload image \(url.lastPathComponent): Response \(httpResponse.statusCode)")
|
||||||
}
|
}
|
||||||
@ -736,9 +711,7 @@ final class Database: ObservableObject {
|
|||||||
log("Failed to upload cap \(cap.id): Response \(httpResponse.statusCode)")
|
log("Failed to upload cap \(cap.id): Response \(httpResponse.statusCode)")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
DispatchQueue.main.async {
|
self.changedCaps.remove(cap.id)
|
||||||
self.changedCaps.remove(cap.id)
|
|
||||||
}
|
|
||||||
return true
|
return true
|
||||||
} catch {
|
} catch {
|
||||||
log("Failed to upload cap \(cap.id): \(error)")
|
log("Failed to upload cap \(cap.id): \(error)")
|
||||||
@ -761,10 +734,8 @@ final class Database: ObservableObject {
|
|||||||
}
|
}
|
||||||
cap.mainImage = version
|
cap.mainImage = version
|
||||||
let finalCap = cap
|
let finalCap = cap
|
||||||
DispatchQueue.main.async {
|
self.caps[capId] = finalCap
|
||||||
self.caps[capId] = finalCap
|
log("Set main image \(version) for \(capId)")
|
||||||
log("Set main image \(version) for \(capId)")
|
|
||||||
}
|
|
||||||
return finalCap
|
return finalCap
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -852,9 +823,7 @@ final class Database: ObservableObject {
|
|||||||
// Delete cached images
|
// Delete cached images
|
||||||
images.removeCachedImages(for: cap)
|
images.removeCachedImages(for: cap)
|
||||||
// Delete cap
|
// Delete cap
|
||||||
DispatchQueue.main.async {
|
self.caps[cap] = nil
|
||||||
self.caps[cap] = nil
|
|
||||||
}
|
|
||||||
log("Deleted cap \(cap)")
|
log("Deleted cap \(cap)")
|
||||||
return true
|
return true
|
||||||
} catch {
|
} catch {
|
||||||
@ -888,19 +857,24 @@ final class Database: ObservableObject {
|
|||||||
log("Image removed")
|
log("Image removed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
DispatchQueue.global().async {
|
Task {
|
||||||
guard let classifier = self.getClassifier() else {
|
guard let classifier = self.getClassifier() else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log("Image classification started")
|
log("Image classification started")
|
||||||
classifier.recognize(image: image) { matches in
|
let matches = await withCheckedContinuation { continuation in
|
||||||
DispatchQueue.main.async {
|
classifier.recognize(image: image) { matches in
|
||||||
self.matches = matches ?? [:]
|
continuation.resume(returning: matches)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.update(matches: matches ?? [:])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func update(matches: [Int : Float]) {
|
||||||
|
self.matches = matches
|
||||||
|
}
|
||||||
|
|
||||||
func canClassify(cap id: Int) -> Bool {
|
func canClassify(cap id: Int) -> Bool {
|
||||||
classifierClasses.contains(id)
|
classifierClasses.contains(id)
|
||||||
}
|
}
|
||||||
@ -1027,68 +1001,13 @@ final class Database: ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Database {
|
extension Database: ClassifierProgressDelegate {
|
||||||
|
|
||||||
final class ClassifierProgress: NSObject, ObservableObject {
|
nonisolated func classifierProgress(_ progress: ClassifierProgress) {
|
||||||
|
Task {
|
||||||
@Published
|
await MainActor.run {
|
||||||
var bytesLoaded: Double
|
self.classifierDownloadProgress = progress
|
||||||
|
|
||||||
@Published
|
|
||||||
var total: Double
|
|
||||||
|
|
||||||
var percentage: Double {
|
|
||||||
guard total > 0 else {
|
|
||||||
return 0.0
|
|
||||||
}
|
}
|
||||||
return bytesLoaded * 100 / total
|
|
||||||
}
|
|
||||||
|
|
||||||
init(bytesLoaded: Double = 0, total: Double = 0) {
|
|
||||||
self.bytesLoaded = bytesLoaded
|
|
||||||
self.total = total
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Database.ClassifierProgress: URLSessionDownloadDelegate {
|
|
||||||
|
|
||||||
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.bytesLoaded = Double(totalBytesWritten)
|
|
||||||
self.total = Double(totalBytesExpectedToWrite)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Database {
|
|
||||||
|
|
||||||
static var mock: Database {
|
|
||||||
let db = Database()
|
|
||||||
db.serverPath = "https://caps.christophhagen.de"
|
|
||||||
db.caps = [
|
|
||||||
Cap(id: 123, name: "My new cap"),
|
|
||||||
Cap(id: 234, name: "My favorite cap"),
|
|
||||||
Cap(id: 345, name: "My oldest cap"),
|
|
||||||
Cap(id: 456, name: "My new cap"),
|
|
||||||
Cap(id: 567, name: "My favorite cap"),
|
|
||||||
Cap(id: 678, name: "My oldest cap"),
|
|
||||||
].reduce(into: [:]) { $0[$1.id] = $1 }
|
|
||||||
db.image = UIImage(systemSymbol: .photo)
|
|
||||||
return db
|
|
||||||
}
|
|
||||||
|
|
||||||
static var largeMock: Database {
|
|
||||||
let db = Database()
|
|
||||||
db.serverPath = "https://caps.christophhagen.de"
|
|
||||||
db.caps = (1..<500)
|
|
||||||
.map { Cap(id: $0, name: "Cap \($0)") }
|
|
||||||
.reduce(into: [:]) { $0[$1.id] = $1 }
|
|
||||||
db.image = UIImage(systemSymbol: .photo)
|
|
||||||
return db
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
19
Caps/Download/ClassifierProgress.swift
Normal file
19
Caps/Download/ClassifierProgress.swift
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
|
||||||
|
struct ClassifierProgress {
|
||||||
|
|
||||||
|
let bytesLoaded: Double
|
||||||
|
|
||||||
|
let total: Double
|
||||||
|
|
||||||
|
var percentage: Double {
|
||||||
|
guard total > 0 else {
|
||||||
|
return 0.0
|
||||||
|
}
|
||||||
|
return bytesLoaded * 100 / total
|
||||||
|
}
|
||||||
|
|
||||||
|
init(bytesLoaded: Double = 0, total: Double = 0) {
|
||||||
|
self.bytesLoaded = bytesLoaded
|
||||||
|
self.total = total
|
||||||
|
}
|
||||||
|
}
|
5
Caps/Download/ClassifierProgressDelegate.swift
Normal file
5
Caps/Download/ClassifierProgressDelegate.swift
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
protocol ClassifierProgressDelegate: AnyObject {
|
||||||
|
|
||||||
|
func classifierProgress(_ progress: ClassifierProgress)
|
||||||
|
}
|
20
Caps/Download/ClassifierProgressObserver.swift
Normal file
20
Caps/Download/ClassifierProgressObserver.swift
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
final class ClassifierProgressObserver: NSObject {
|
||||||
|
|
||||||
|
weak var delegate: ClassifierProgressDelegate?
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
extension ClassifierProgressObserver: URLSessionDownloadDelegate {
|
||||||
|
|
||||||
|
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
|
||||||
|
delegate?.classifierProgress(.init(
|
||||||
|
bytesLoaded: Double(totalBytesWritten),
|
||||||
|
total: Double(totalBytesExpectedToWrite)))
|
||||||
|
}
|
||||||
|
}
|
32
Caps/Preview Content/Database+Mock.swift
Normal file
32
Caps/Preview Content/Database+Mock.swift
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import UIKit
|
||||||
|
|
||||||
|
extension Database {
|
||||||
|
|
||||||
|
static var mock: Database {
|
||||||
|
let caps = [
|
||||||
|
Cap(id: 123, name: "My new cap"),
|
||||||
|
Cap(id: 234, name: "My favorite cap"),
|
||||||
|
Cap(id: 345, name: "My oldest cap"),
|
||||||
|
Cap(id: 456, name: "My new cap"),
|
||||||
|
Cap(id: 567, name: "My favorite cap"),
|
||||||
|
Cap(id: 678, name: "My oldest cap"),
|
||||||
|
].reduce(into: [:]) { $0[$1.id] = $1 }
|
||||||
|
|
||||||
|
let db = Database(caps: caps)
|
||||||
|
db.serverPath = "https://caps.christophhagen.de"
|
||||||
|
|
||||||
|
db.image = UIImage(systemSymbol: .photo)
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
static var largeMock: Database {
|
||||||
|
let caps = (1..<500)
|
||||||
|
.map { Cap(id: $0, name: "Cap \($0)") }
|
||||||
|
.reduce(into: [:]) { $0[$1.id] = $1 }
|
||||||
|
|
||||||
|
let db = Database(caps: caps)
|
||||||
|
db.serverPath = "https://caps.christophhagen.de"
|
||||||
|
db.image = UIImage(systemSymbol: .photo)
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
}
|
@ -2,8 +2,7 @@ import SwiftUI
|
|||||||
|
|
||||||
struct ClassifierDownloadView: View {
|
struct ClassifierDownloadView: View {
|
||||||
|
|
||||||
@ObservedObject
|
let progress: ClassifierProgress
|
||||||
var progress: Database.ClassifierProgress
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
VStack {
|
||||||
@ -19,5 +18,8 @@ struct ClassifierDownloadView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#Preview {
|
#Preview {
|
||||||
ClassifierDownloadView(progress: .init(bytesLoaded: 12_300_000, total: 24_500_000))
|
ClassifierDownloadView(progress: .init(
|
||||||
|
bytesLoaded: 12_300_000,
|
||||||
|
total: 24_500_000)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user