147 lines
4.6 KiB
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")
|
|
}
|
|
}
|