From f192e1c29d510001dc8279972ed61b0fc31e05b8 Mon Sep 17 00:00:00 2001 From: Christoph Hagen Date: Tue, 24 Oct 2023 15:44:13 +0200 Subject: [PATCH] Improve cache settings view --- Caps.xcodeproj/project.pbxproj | 4 + .../UserInterfaceState.xcuserstate | Bin 53979 -> 61013 bytes Caps/Data/Database.swift | 54 ++----------- Caps/Data/ImageCache.swift | 75 ++++++++++++++++-- Caps/Views/CachedImageCountView.swift | 15 ++++ Caps/Views/SettingsView.swift | 36 ++++----- 6 files changed, 110 insertions(+), 74 deletions(-) create mode 100644 Caps/Views/CachedImageCountView.swift diff --git a/Caps.xcodeproj/project.pbxproj b/Caps.xcodeproj/project.pbxproj index b4f58df..918fe13 100644 --- a/Caps.xcodeproj/project.pbxproj +++ b/Caps.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 882C955E2AE7F0DE00657886 /* ClassifierDownloadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 882C955D2AE7F0DE00657886 /* ClassifierDownloadView.swift */; }; + 882C95602AE7FA7100657886 /* CachedImageCountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 882C955F2AE7FA7100657886 /* CachedImageCountView.swift */; }; 88C1511C29A11ADF0080EF4F /* CapImagesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88C1511B29A11ADF0080EF4F /* CapImagesView.swift */; }; 88DBE72E285495B100D1573B /* FancyTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88DBE72D285495B100D1573B /* FancyTextField.swift */; }; E20D104A285612AF0019BD91 /* ImageGrid.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20D1049285612AF0019BD91 /* ImageGrid.swift */; }; @@ -54,6 +55,7 @@ /* Begin PBXFileReference section */ 882C955D2AE7F0DE00657886 /* ClassifierDownloadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClassifierDownloadView.swift; sourceTree = ""; }; + 882C955F2AE7FA7100657886 /* CachedImageCountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CachedImageCountView.swift; sourceTree = ""; }; 88C1511B29A11ADF0080EF4F /* CapImagesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CapImagesView.swift; sourceTree = ""; }; 88DBE72D285495B100D1573B /* FancyTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FancyTextField.swift; sourceTree = ""; }; E20D1049285612AF0019BD91 /* ImageGrid.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageGrid.swift; sourceTree = ""; }; @@ -195,6 +197,7 @@ E2EA00E4283F69DF00F7B269 /* SettingsStatisticRow.swift */, E2EA00E0283F658E00F7B269 /* SettingsView.swift */, 882C955D2AE7F0DE00657886 /* ClassifierDownloadView.swift */, + 882C955F2AE7FA7100657886 /* CachedImageCountView.swift */, E2EA00CB283EB43E00F7B269 /* SortCaseRowView.swift */, E2EA00C6283EAA0100F7B269 /* SortSelectionView.swift */, 88C1511B29A11ADF0080EF4F /* CapImagesView.swift */, @@ -306,6 +309,7 @@ E2EA00EB284109CC00F7B269 /* CGImage+Extensions.swift in Sources */, E20D104E28574C7C0019BD91 /* CachedCapImage.swift in Sources */, E2EA00DF283F5CA000F7B269 /* ErrorView.swift in Sources */, + 882C95602AE7FA7100657886 /* CachedImageCountView.swift in Sources */, E20D104A285612AF0019BD91 /* ImageGrid.swift in Sources */, E2EA00D5283EDFA200F7B269 /* FrameManager.swift in Sources */, E25AAC90283D871E006E9E7F /* CGImagePropertyOrientation+Extensions.swift in Sources */, diff --git a/Caps.xcodeproj/project.xcworkspace/xcuserdata/imac.xcuserdatad/UserInterfaceState.xcuserstate b/Caps.xcodeproj/project.xcworkspace/xcuserdata/imac.xcuserdatad/UserInterfaceState.xcuserstate index a938d06f55e6d716747e9e7b88493ae80008cbd4..d594b651d54a64aebe0f83c8b3dbc56b3501ffb5 100644 GIT binary patch delta 28620 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

_>= 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 diff --git a/Caps/Data/Database.swift b/Caps/Data/Database.swift index 90adcc6..6beac4b 100644 --- a/Caps/Data/Database.swift +++ b/Caps/Data/Database.swift @@ -91,8 +91,12 @@ final class Database: ObservableObject { pendingImageUploadStorage = newValue.map { cap, images in "\(cap)-\(images.map { "\($0)" }.joined(separator: ":"))" }.joined(separator: ";") + pendingImageUploadCount = newValue.values.reduce(0) { $0 + $1.count } } } + + @Published + private(set) var pendingImageUploadCount = 0 private var uploadTimer: Timer? @@ -569,28 +573,10 @@ final class Database: ObservableObject { changedCaps.contains(cap) || imageUploads[cap] != nil } - var pendingImageUploadCount: Int { - imageUploads.values.reduce(0) { $0 + $1.count } - } - private func capId(from url: URL) -> Int? { Int(url.lastPathComponent.components(separatedBy: "-").first!) } - private func imageId(from url: URL) -> CapImage? { - let parts = url.deletingPathExtension().lastPathComponent.components(separatedBy: "-") - guard parts.count == 2 else { - log("File \(url.lastPathComponent) is not a cap image") - return nil - } - guard let capId = Int(parts.first!), - let version = Int(parts.last!) else { - log("File \(url.lastPathComponent) is not a cap image") - return nil - } - return .init(cap: capId, version: version) - } - private func uploadAllImages() async { guard hasServerAuthentication else { log("No server authentication to upload to server") @@ -980,36 +966,10 @@ final class Database: ObservableObject { var classifierSize: Int { localClassifierUrl.fileSize } - - private var cachedImages: [URL] { - do { - return try fm.contentsOfDirectory(at: images.folder, includingPropertiesForKeys: nil) - } catch { - log("Failed to get cached images: \(error)") - return [] - } - } - + func clearImageCache() { - let allImages = cachedImages - let unnecessaryImages = allImages - .filter { - guard let id = imageId(from: $0) else { - return true - } - guard let cap = caps[id.cap] else { - return true - } - return cap.mainImage != id.version - } - log("Deleting \(unnecessaryImages.count) of \(allImages.count) cached images") - for cachedImage in unnecessaryImages { - do { - try fm.removeItem(at: cachedImage) - } catch { - log("Failed to delete cached image \(cachedImage.lastPathComponent): \(error)") - } - } + let imagesToKeep = caps.values.map { $0.image } + images.clearImageCache(keeping: Set(imagesToKeep)) } } diff --git a/Caps/Data/ImageCache.swift b/Caps/Data/ImageCache.swift index aa625e5..f68cdb1 100644 --- a/Caps/Data/ImageCache.swift +++ b/Caps/Data/ImageCache.swift @@ -1,7 +1,7 @@ import Foundation import UIKit -final class ImageCache { +final class ImageCache: ObservableObject { let folder: URL @@ -16,6 +16,9 @@ final class ImageCache { private let thumbnailQuality: CGFloat = 0.7 private let imageQuality: CGFloat = 0.3 + + @Published + private(set) var imageCount = 0 init(folder: URL, server: URL, thumbnailSize: CGFloat) throws { self.folder = folder @@ -25,6 +28,7 @@ final class ImageCache { if !fm.fileExists(atPath: folder.path) { try fm.createDirectory(at: folder, withIntermediateDirectories: true) } + self.imageCount = countLocalImages() } private func localImageUrl(_ image: CapImage) -> URL { @@ -92,12 +96,6 @@ final class ImageCache { return UIImage(at: localUrl) } - @discardableResult - func removeImage(_ image: CapImage) -> Bool { - let localUrl = localImageUrl(image) - return removePossibleFile(localUrl) - } - @discardableResult func refreshImage(_ image: CapImage) async -> Bool { guard let downloadedImageUrl = await loadRemoteImage(image) else { @@ -122,6 +120,9 @@ final class ImageCache { for url in files { do { try fm.removeItem(at: url) + DispatchQueue.main.async { + self.imageCount -= 1 + } } catch { isSuccessful = false log("Failed to remove image \(url.lastPathComponent) from cache: \(error)") @@ -157,14 +158,20 @@ final class ImageCache { private func saveImage(_ image: CapImage, at tempUrl: URL) -> Bool { let localUrl = localImageUrl(image) + let isOverwrite = exists(localUrl) guard removePossibleFile(localUrl) else { return false } do { try fm.moveItem(at: tempUrl, to: localUrl) + if !isOverwrite { + DispatchQueue.main.async { + self.imageCount += 1 + } + } return true } catch { - print("failed to save image \(localUrl.lastPathComponent): \(error)") + print("Failed to save image \(localUrl.lastPathComponent): \(error)") return false } } @@ -250,12 +257,64 @@ final class ImageCache { return false } let localUrl = localThumbnailUrl(cap: cap) + let isOverwrite = exists(localUrl) do { try data.write(to: localUrl) + if !isOverwrite { + DispatchQueue.main.async { + self.imageCount += 1 + } + } return true } catch { print("Failed to save thumbnail \(cap): \(error)") return false } } + + private func countLocalImages() -> Int { + cachedImages.count + } + + private var cachedImages: [URL] { + do { + return try fm.contentsOfDirectory(at: folder, includingPropertiesForKeys: nil) + } catch { + log("Failed to get cached images: \(error)") + return [] + } + } + + private func imageId(from url: URL) -> CapImage? { + let parts = url.deletingPathExtension().lastPathComponent.components(separatedBy: "-") + guard parts.count == 2 else { + log("File \(url.lastPathComponent) is not a cap image") + return nil + } + guard let capId = Int(parts.first!), + let version = Int(parts.last!) else { + log("File \(url.lastPathComponent) is not a cap image") + return nil + } + return .init(cap: capId, version: version) + } + + func clearImageCache(keeping: Set) { + let allImages = cachedImages + for url in allImages { + if let id = imageId(from: url), keeping.contains(id) { + continue // Skip image + } + do { + try fm.removeItem(at: url) + } catch { + log("Failed to delete cached image \(url.lastPathComponent): \(error)") + } + } + let newCount = countLocalImages() + log("Deleted \(allImages.count - newCount) of \(allImages.count) cached images, leaving \(newCount)") + DispatchQueue.main.async { + self.imageCount = newCount + } + } } diff --git a/Caps/Views/CachedImageCountView.swift b/Caps/Views/CachedImageCountView.swift new file mode 100644 index 0000000..4996576 --- /dev/null +++ b/Caps/Views/CachedImageCountView.swift @@ -0,0 +1,15 @@ +import SwiftUI + +struct CachedImageCountView: View { + + @ObservedObject + var cache: ImageCache + + var body: some View { + SettingsStatisticRow(label: "Downloaded images", value: "\(cache.imageCount)") + } +} + +#Preview { + CachedImageCountView(cache: Database.mock.images) +} diff --git a/Caps/Views/SettingsView.swift b/Caps/Views/SettingsView.swift index c8f5414..72652ba 100644 --- a/Caps/Views/SettingsView.swift +++ b/Caps/Views/SettingsView.swift @@ -33,8 +33,22 @@ struct SettingsView: View { .padding(.top) Group { SettingsStatisticRow(label: "Caps", value: "\(database.numberOfCaps)") - SettingsStatisticRow(label: "Total images", value: "\(database.numberOfImages)") SettingsStatisticRow(label: "Images per cap", value: String(format: "%.1f", database.averageImageCount)) + SettingsStatisticRow(label: "Total images", value: "\(database.numberOfImages)") + CachedImageCountView(cache: database.images) + SettingsStatisticRow(label: "Cache size", value: byteString(imageCacheSize)) + SettingsStatisticRow(label: "Database", value: byteString(database.databaseSize)) + if database.pendingImageUploadCount > 0 { + SettingsStatisticRow(label: "Images to upload", value: "\(database.pendingImageUploadCount)") + } + HStack { + Spacer() + Button(action: clearImageCache) { + Label("Clear image cache", systemSymbol: .trash) + } + .padding() + Spacer() + } }.padding(.horizontal) Text("Classifier") .font(.footnote) @@ -44,6 +58,7 @@ struct SettingsView: View { Group { SettingsStatisticRow(label: "Server Version", value: "\(database.serverClassifierVersion)") SettingsStatisticRow(label: "Local Version", value: "\(database.localClassifierVersion)") + SettingsStatisticRow(label: "Size", value: byteString(database.classifierSize)) SettingsStatisticRow(label: "Recognized caps", value: "\(database.classifierClassCount)") HStack { Spacer() @@ -65,24 +80,6 @@ struct SettingsView: View { ClassifierDownloadView(progress: progress) } }.padding(.horizontal) - Text("Storage") - .font(.footnote) - .textCase(.uppercase) - .foregroundColor(.secondary) - .padding(.top) - Group { - SettingsStatisticRow(label: "Image cache", value: byteString(imageCacheSize)) - SettingsStatisticRow(label: "Database", value: byteString(database.databaseSize)) - SettingsStatisticRow(label: "Classifier", value: byteString(database.classifierSize)) - HStack { - Spacer() - Button(action: clearImageCache) { - Label("Clear image cache", systemSymbol: .trash) - } - .padding() - Spacer() - } - }.padding(.horizontal) Spacer() } .padding(.horizontal) @@ -109,6 +106,7 @@ struct SettingsView: View { // Ensure that correct version is saved await database.updateServerClassifierVersion() await database.downloadClassifier() + await database.downloadClassifierClasses() } }