Files
stickercloner/StickerCloner/Shared/StickerStore.swift

147 lines
4.6 KiB
Swift

import Foundation
import UIKit
final class StickerStore {
static let shared = StickerStore()
private let packsKey = "savedPacks"
private let suiteName = "group.de.felixfoertsch.StickerCloner"
private let fileManager = FileManager.default
private let thumbnailCache = NSCache<NSString, UIImage>()
private lazy var defaults: UserDefaults = {
guard let defaults = UserDefaults(suiteName: suiteName) else {
fatalError("App Group '\(suiteName)' is not configured")
}
return defaults
}()
private init() {
migrateIfNeeded()
}
// MARK: - Pack persistence
var savedPacks: [SavedPack] {
get {
guard let data = defaults.data(forKey: packsKey) else { return [] }
return (try? JSONDecoder().decode([SavedPack].self, from: data)) ?? []
}
set {
let data = try? JSONEncoder().encode(newValue)
defaults.set(data, forKey: packsKey)
}
}
// MARK: - File management
private var stickersDirectory: URL {
let container = fileManager.containerURL(forSecurityApplicationGroupIdentifier: suiteName)!
return container.appendingPathComponent("stickers", isDirectory: true)
}
private func packDirectory(name: String) -> URL {
stickersDirectory.appendingPathComponent(name, isDirectory: true)
}
// MARK: - Add / remove packs
func addPack(response: StickerSetResponse) async throws {
let pack = SavedPack(name: response.name, title: response.title, stickerCount: response.stickerCount)
let packDir = packDirectory(name: response.name)
try fileManager.createDirectory(at: packDir, withIntermediateDirectories: true)
for sticker in response.stickers {
let localURL = packDir.appendingPathComponent("\(sticker.id).png")
if fileManager.fileExists(atPath: localURL.path) { continue }
try await StickerClonerAPI.downloadSticker(from: sticker.pngUrl, to: localURL)
}
var packs = savedPacks
packs.removeAll { $0.name == pack.name }
packs.append(pack)
savedPacks = packs
}
func removePack(name: String) {
var packs = savedPacks
packs.removeAll { $0.name == name }
savedPacks = packs
thumbnailCache.removeObject(forKey: name as NSString)
let packDir = packDirectory(name: name)
try? fileManager.removeItem(at: packDir)
}
// MARK: - Load sticker file URLs
func stickerFileURLs() -> [URL] {
var urls: [URL] = []
for pack in savedPacks {
let packDir = packDirectory(name: pack.name)
guard let files = try? fileManager.contentsOfDirectory(at: packDir, includingPropertiesForKeys: nil) else { continue }
for file in files.sorted(by: { $0.lastPathComponent < $1.lastPathComponent }) {
guard file.pathExtension == "png" else { continue }
urls.append(file)
}
}
return urls
}
func stickerFileURLs(forPack name: String) -> [URL] {
let packDir = packDirectory(name: name)
guard let files = try? fileManager.contentsOfDirectory(at: packDir, includingPropertiesForKeys: nil) else { return [] }
return files
.filter { $0.pathExtension == "png" }
.sorted { $0.lastPathComponent < $1.lastPathComponent }
}
func firstStickerURL(forPack name: String) -> URL? {
let packDir = packDirectory(name: name)
guard let files = try? fileManager.contentsOfDirectory(at: packDir, includingPropertiesForKeys: nil) else { return nil }
return files
.filter { $0.pathExtension == "png" }
.sorted { $0.lastPathComponent < $1.lastPathComponent }
.first
}
func thumbnail(forPack name: String) -> UIImage? {
let key = name as NSString
if let cached = thumbnailCache.object(forKey: key) { return cached }
guard let url = firstStickerURL(forPack: name),
let data = try? Data(contentsOf: url),
let image = UIImage(data: data) else { return nil }
thumbnailCache.setObject(image, forKey: key)
return image
}
// MARK: - Migration from old sandbox
private func migrateIfNeeded() {
let migrated = defaults.bool(forKey: "didMigrateToAppGroup")
guard !migrated else { return }
let oldDefaults = UserDefaults.standard
if let data = oldDefaults.data(forKey: packsKey) {
defaults.set(data, forKey: packsKey)
oldDefaults.removeObject(forKey: packsKey)
}
let oldCaches = fileManager.urls(for: .cachesDirectory, in: .userDomainMask).first!
let oldStickersDir = oldCaches.appendingPathComponent("stickers", isDirectory: true)
if fileManager.fileExists(atPath: oldStickersDir.path) {
let newStickersDir = stickersDirectory
try? fileManager.createDirectory(at: newStickersDir.deletingLastPathComponent(), withIntermediateDirectories: true)
if !fileManager.fileExists(atPath: newStickersDir.path) {
try? fileManager.moveItem(at: oldStickersDir, to: newStickersDir)
}
}
defaults.set(true, forKey: "didMigrateToAppGroup")
}
}