Show classifier download progress
This commit is contained in:
@ -288,7 +288,7 @@ struct ContentView: View {
|
||||
}
|
||||
.alert(isPresented: $showNewClassifierAlert) {
|
||||
Alert(title: Text("New classifier available"),
|
||||
message: Text("Classifier \(database.serverClassifierVersion) is available. You have version \(database.classifierVersion). Do you want to download it now?"),
|
||||
message: Text("Classifier \(database.serverClassifierVersion) is available. You have version \(database.localClassifierVersion). Do you want to download it now?"),
|
||||
primaryButton: .default(Text("Download"), action: downloadClassifier),
|
||||
secondaryButton: .cancel())
|
||||
}
|
||||
@ -329,8 +329,8 @@ struct ContentView: View {
|
||||
private func refresh() {
|
||||
Task {
|
||||
await database.downloadCaps()
|
||||
let hasNewClassifier = await database.serverHasNewClassifier()
|
||||
guard hasNewClassifier else {
|
||||
await database.updateServerClassifierVersion()
|
||||
guard database.hasNewClassifier else {
|
||||
return
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
|
@ -8,10 +8,27 @@ final class Database: ObservableObject {
|
||||
private let imageCompressionQuality: CGFloat = 0.3
|
||||
|
||||
@AppStorage("classifier")
|
||||
private(set) var classifierVersion = 0
|
||||
private var storedLocalClassifierVersion = 0 {
|
||||
didSet { localClassifierVersion = storedLocalClassifierVersion }
|
||||
}
|
||||
|
||||
@AppStorage("serverClassifier")
|
||||
private var storedServerClassifierVersion = 0 {
|
||||
didSet { serverClassifierVersion = storedServerClassifierVersion }
|
||||
}
|
||||
|
||||
@Published
|
||||
private(set) var localClassifierVersion = 0
|
||||
|
||||
@Published
|
||||
private(set) var serverClassifierVersion = 0
|
||||
|
||||
@Published
|
||||
private(set) var classifierDownloadProgress: ClassifierProgress?
|
||||
|
||||
var hasNewClassifier: Bool {
|
||||
serverClassifierVersion > localClassifierVersion
|
||||
}
|
||||
|
||||
let images: ImageCache
|
||||
|
||||
@ -146,6 +163,9 @@ final class Database: ObservableObject {
|
||||
folder: imageFolder,
|
||||
server: server,
|
||||
thumbnailSize: CapsApp.thumbnailImageSize)
|
||||
|
||||
self.localClassifierVersion = storedLocalClassifierVersion
|
||||
self.serverClassifierVersion = storedServerClassifierVersion
|
||||
|
||||
ensureFolderExistence(gridStorageFolder)
|
||||
loadCaps()
|
||||
@ -329,7 +349,7 @@ final class Database: ObservableObject {
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func serverHasNewClassifier() async -> Bool {
|
||||
func updateServerClassifierVersion() async -> Bool {
|
||||
let data: Data
|
||||
let response: URLResponse
|
||||
do {
|
||||
@ -351,23 +371,29 @@ final class Database: ObservableObject {
|
||||
return false
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
self.serverClassifierVersion = serverVersion
|
||||
self.storedServerClassifierVersion = serverVersion
|
||||
}
|
||||
guard serverVersion > self.classifierVersion else {
|
||||
log("No new classifier available (Local: \(classifierVersion) Server: \(serverVersion))")
|
||||
return false
|
||||
}
|
||||
log("New classifier available (Local: \(classifierVersion) Server: \(serverVersion))")
|
||||
return true
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func downloadClassifier() async -> Bool {
|
||||
log("Downloading classifier")
|
||||
|
||||
let progress = ClassifierProgress()
|
||||
DispatchQueue.main.async {
|
||||
self.classifierDownloadProgress = progress
|
||||
}
|
||||
defer {
|
||||
DispatchQueue.main.async {
|
||||
self.classifierDownloadProgress = nil
|
||||
}
|
||||
}
|
||||
|
||||
let tempUrl: URL
|
||||
let response: URLResponse
|
||||
do {
|
||||
(tempUrl, response) = try await URLSession.shared.download(from: serverClassifierUrl)
|
||||
(tempUrl, response) = try await URLSession.shared.download(from: serverClassifierUrl, delegate: progress)
|
||||
} catch {
|
||||
log("Failed to download classifier version: \(error)")
|
||||
return false
|
||||
@ -386,10 +412,10 @@ final class Database: ObservableObject {
|
||||
return false
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
self.classifierVersion = self.serverClassifierVersion
|
||||
self.storedLocalClassifierVersion = self.serverClassifierVersion
|
||||
log("Downloaded classifier \(self.localClassifierVersion)")
|
||||
self.classifier = nil
|
||||
}
|
||||
log("Downloaded classifier \(classifierVersion)")
|
||||
return true
|
||||
}
|
||||
|
||||
@ -987,6 +1013,44 @@ final class Database: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
extension Database {
|
||||
|
||||
final class ClassifierProgress: NSObject, ObservableObject {
|
||||
|
||||
@Published
|
||||
var bytesLoaded: Double
|
||||
|
||||
@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 {
|
||||
|
23
Caps/Views/ClassifierDownloadView.swift
Normal file
23
Caps/Views/ClassifierDownloadView.swift
Normal file
@ -0,0 +1,23 @@
|
||||
import SwiftUI
|
||||
|
||||
struct ClassifierDownloadView: View {
|
||||
|
||||
@ObservedObject
|
||||
var progress: Database.ClassifierProgress
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
ProgressView("Downloading classifier...", value: progress.bytesLoaded, total: Double(progress.total))
|
||||
.progressViewStyle(.linear)
|
||||
HStack {
|
||||
Text(String(format: "%.0f %%", progress.percentage))
|
||||
Spacer()
|
||||
Text(String(format: "%.1f / %.1f MB", progress.bytesLoaded / 1_000_000, progress.total / 1_000_000 ))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
ClassifierDownloadView(progress: .init(bytesLoaded: 12_300_000, total: 24_500_000))
|
||||
}
|
@ -42,8 +42,28 @@ struct SettingsView: View {
|
||||
.foregroundColor(.secondary)
|
||||
.padding(.top)
|
||||
Group {
|
||||
SettingsStatisticRow(label: "Version", value: "\(database.classifierVersion)")
|
||||
SettingsStatisticRow(label: "Server Version", value: "\(database.serverClassifierVersion)")
|
||||
SettingsStatisticRow(label: "Local Version", value: "\(database.localClassifierVersion)")
|
||||
SettingsStatisticRow(label: "Recognized caps", value: "\(database.classifierClassCount)")
|
||||
HStack {
|
||||
Spacer()
|
||||
Button(action: updateClassifierVersion) {
|
||||
Label("Refresh", systemSymbol: .arrowCounterclockwise)
|
||||
}
|
||||
.disabled(database.classifierDownloadProgress != nil)
|
||||
.padding()
|
||||
if database.localClassifierVersion != database.serverClassifierVersion {
|
||||
Button(action: downloadNewClassifier) {
|
||||
Label("Download", systemSymbol: .squareAndArrowDown)
|
||||
}
|
||||
.disabled(database.classifierDownloadProgress != nil)
|
||||
.padding()
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
if let progress = database.classifierDownloadProgress {
|
||||
ClassifierDownloadView(progress: progress)
|
||||
}
|
||||
}.padding(.horizontal)
|
||||
Text("Storage")
|
||||
.font(.footnote)
|
||||
@ -56,7 +76,9 @@ struct SettingsView: View {
|
||||
SettingsStatisticRow(label: "Classifier", value: byteString(database.classifierSize))
|
||||
HStack {
|
||||
Spacer()
|
||||
Button("Clear image cache", action: clearImageCache)
|
||||
Button(action: clearImageCache) {
|
||||
Label("Clear image cache", systemSymbol: .trash)
|
||||
}
|
||||
.padding()
|
||||
Spacer()
|
||||
}
|
||||
@ -82,6 +104,20 @@ struct SettingsView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func downloadNewClassifier() {
|
||||
Task {
|
||||
// Ensure that correct version is saved
|
||||
await database.updateServerClassifierVersion()
|
||||
await database.downloadClassifier()
|
||||
}
|
||||
}
|
||||
|
||||
private func updateClassifierVersion() {
|
||||
Task {
|
||||
await database.updateServerClassifierVersion()
|
||||
}
|
||||
}
|
||||
|
||||
private func hide() {
|
||||
isPresented = false
|
||||
}
|
||||
|
Reference in New Issue
Block a user