async loading of the remote emoji database

- load the local one first
 - async task to load the remote db and if it succeeds overwrite the local db and load the remote one

emoiji.localimageurl is now a computed propery
 - need to fix the fact that the file extension is not known 😭

fetchRemoteDB automatically stores the db lcoally after fetching
add storeDB(data: Data) to store the db from a data form
Emoji.uiID is generated automatically
fix hoarder.localEmojiDB path

imessage extension scheme??
This commit is contained in:
neon443
2025-10-29 16:26:57 +00:00
parent 20e4e8a2c7
commit 002fc21308
4 changed files with 31 additions and 21 deletions

View File

@@ -74,6 +74,7 @@
savedToolIdentifier = "" savedToolIdentifier = ""
useCustomWorkingDirectory = "NO" useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES" debugDocumentVersioning = "YES"
askForAppToLaunch = "Yes"
launchAutomaticallySubstyle = "2"> launchAutomaticallySubstyle = "2">
<RemoteRunnable <RemoteRunnable
runnableDebuggingMode = "1" runnableDebuggingMode = "1"

View File

@@ -11,9 +11,11 @@ import UniformTypeIdentifiers
struct Emoji: Codable, Identifiable, Hashable { struct Emoji: Codable, Identifiable, Hashable {
var id: UUID var id: UUID
var uiID: UUID var uiID: UUID = UUID()
var name: String var name: String
var localImageURL: URL var localImageURL: URL {
return EmojiHoarder.container.appendingPathComponent(id.uuidString, conformingTo: .image)
}
var remoteImageURL: URL var remoteImageURL: URL
var isLocal: Bool { var isLocal: Bool {
@@ -32,16 +34,13 @@ struct Emoji: Codable, Identifiable, Hashable {
enum CodingKeys: String, CodingKey { enum CodingKeys: String, CodingKey {
case id = "id" case id = "id"
case name = "name" case name = "name"
case localImageURL = "localImageURL" case remoteImageURL = "imageUrl"
case remoteImageURL = "remoteImageURL"
} }
init(from decoder: any Decoder) throws { init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self) let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(UUID.self, forKey: .id) self.id = try container.decode(UUID.self, forKey: .id)
self.uiID = UUID()
self.name = try container.decode(String.self, forKey: .name) self.name = try container.decode(String.self, forKey: .name)
self.localImageURL = try container.decode(URL.self, forKey: .localImageURL)
self.remoteImageURL = try container.decode(URL.self, forKey: .remoteImageURL) self.remoteImageURL = try container.decode(URL.self, forKey: .remoteImageURL)
} }
@@ -50,12 +49,11 @@ struct Emoji: Codable, Identifiable, Hashable {
id: UUID = UUID() id: UUID = UUID()
) { ) {
self.id = id self.id = id
self.uiID = id
self.name = apiEmoji.name self.name = apiEmoji.name
self.remoteImageURL = apiEmoji.url self.remoteImageURL = apiEmoji.url
let fileExtension = String(apiEmoji.urlString.split(separator: ".").last ?? "png") let fileExtension = String(apiEmoji.urlString.split(separator: ".").last ?? "png")
self.localImageURL = EmojiHoarder.container.appendingPathComponent(id.uuidString+"."+fileExtension, conformingTo: .image) // self.localImageURL = EmojiHoarder.container.appendingPathComponent(id.uuidString+"."+fileExtension, conformingTo: .image)
// Task { [weak self] in // Task { [weak self] in
// let (data, response) = try await URLSession.shared.data(from: apiEmoji.url) // let (data, response) = try await URLSession.shared.data(from: apiEmoji.url)

View File

@@ -12,7 +12,7 @@ import UniformTypeIdentifiers
class EmojiHoarder: ObservableObject { class EmojiHoarder: ObservableObject {
static let container = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.neon443.StickerSlack")!.appendingPathComponent("Library", conformingTo: .directory) static let container = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.neon443.StickerSlack")!.appendingPathComponent("Library", conformingTo: .directory)
static let localEmojiDB: URL = EmojiHoarder.container.appendingPathExtension("localEmojiDB.json") static let localEmojiDB: URL = EmojiHoarder.container.appendingPathComponent("_localEmojiDB.json", conformingTo: .fileURL)
private let endpoint: URL = URL(string: "https://cachet.dunkirk.sh/emojis")! private let endpoint: URL = URL(string: "https://cachet.dunkirk.sh/emojis")!
private let encoder = JSONEncoder() private let encoder = JSONEncoder()
private let decoder = JSONDecoder() private let decoder = JSONDecoder()
@@ -22,13 +22,15 @@ class EmojiHoarder: ObservableObject {
@Published var prefix: Int = 100 @Published var prefix: Int = 100
init() { init() {
guard let fetched = fetchRemoteDB() else { self.emojis = loadLocalDB()
self.emojis = loadLocalDB() self.filteredEmojis = self.emojis
return
}
self.emojis = fetched Task(priority: .high) {
self.filteredEmojis = fetched if let fetched = await self.fetchRemoteDB() {
self.emojis = fetched
self.filteredEmojis = fetched
}
}
} }
func storeStickers(_ toStore: [UUID]) { func storeStickers(_ toStore: [UUID]) {
@@ -41,6 +43,10 @@ class EmojiHoarder: ObservableObject {
try! encoder.encode(emojis).write(to: EmojiHoarder.localEmojiDB) try! encoder.encode(emojis).write(to: EmojiHoarder.localEmojiDB)
} }
func storeDB(data: Data) {
try! data.write(to: EmojiHoarder.localEmojiDB)
}
func loadLocalDB() -> [Emoji] { func loadLocalDB() -> [Emoji] {
if let localEmojiDB = try? Data(contentsOf: EmojiHoarder.localEmojiDB) { if let localEmojiDB = try? Data(contentsOf: EmojiHoarder.localEmojiDB) {
let decoded = try! decoder.decode([Emoji].self, from: localEmojiDB) let decoded = try! decoder.decode([Emoji].self, from: localEmojiDB)
@@ -49,11 +55,17 @@ class EmojiHoarder: ObservableObject {
return [] return []
} }
func fetchRemoteDB() -> [Emoji]? { func fetchRemoteDB() async -> [Emoji]? {
guard let data = try? Data(contentsOf: endpoint) else { return nil } do {
decoder.dateDecodingStrategy = .iso8601 let (data, _) = try await URLSession.shared.data(from: endpoint)
let decoded: [SlackResponse] = try! decoder.decode([SlackResponse].self, from: data) decoder.dateDecodingStrategy = .iso8601
return SlackResponse.toEmojis(from: decoded) let decoded: [SlackResponse] = try! decoder.decode([SlackResponse].self, from: data)
storeDB(data: data)
return SlackResponse.toEmojis(from: decoded)
} catch {
print(error.localizedDescription)
fatalError()
}
} }
func setPrefix(to: Int) { func setPrefix(to: Int) {

View File

@@ -35,7 +35,6 @@ struct EmojiPreview: View {
.resizable().scaledToFit() .resizable().scaledToFit()
} else if phase.error != nil { } else if phase.error != nil {
ZStack { ZStack {
Image(systemName: "xmark.app.fill") Image(systemName: "xmark.app.fill")
.resizable().scaledToFit() .resizable().scaledToFit()
.padding() .padding()