ImageCacheMigrations.swift (3694B)
1 // 2 // ImageCacheMigrations.swift 3 // damus 4 // 5 // Created by Daniel D’Aquino on 2025-04-26. 6 // 7 8 import Foundation 9 import Kingfisher 10 11 struct ImageCacheMigrations { 12 static func migrateKingfisherCacheIfNeeded() { 13 let fileManager = FileManager.default 14 let defaults = UserDefaults.standard 15 let migration1Key = "KingfisherCacheMigrated" // Never ever changes 16 let migration2Key = "KingfisherCacheMigratedV2" // Never ever changes 17 18 let migration1Done = defaults.bool(forKey: migration1Key) 19 let migration2Done = defaults.bool(forKey: migration2Key) 20 21 guard !migration1Done || !migration2Done else { 22 // All migrations are already done. Skip. 23 return 24 } 25 26 let oldCachePath = migration1Done ? migration1KingfisherCachePath() : migration0KingfisherCachePath() 27 28 // New shared cache location 29 let newCachePath = kingfisherCachePath().path 30 31 if fileManager.fileExists(atPath: oldCachePath) { 32 do { 33 // Move the old cache to the new location 34 try fileManager.moveItem(atPath: oldCachePath, toPath: newCachePath) 35 Log.info("Successfully migrated Kingfisher cache to %s", for: .storage, newCachePath) 36 } catch { 37 do { 38 // Cache data is not essential, fallback to deleting the cache and starting all over 39 // It's better than leaving significant garbage data stuck indefinitely on the user's phone 40 try fileManager.removeItem(atPath: newCachePath) 41 try fileManager.removeItem(atPath: oldCachePath) 42 } 43 catch { 44 Log.error("Failed to migrate cache: %s", for: .storage, error.localizedDescription) 45 return // Do not mark them as complete, we can try again next time the user reloads the app 46 } 47 } 48 } 49 50 // Mark migrations as complete 51 defaults.set(true, forKey: migration1Key) 52 defaults.set(true, forKey: migration2Key) 53 } 54 55 static private func migration0KingfisherCachePath() -> String { 56 // Implementation note: These are old, so they should not be changed 57 let defaultCache = ImageCache.default 58 return defaultCache.diskStorage.directoryURL.path 59 } 60 61 static private func migration1KingfisherCachePath() -> String { 62 // Implementation note: These are old, so they are hard-coded on purpose, because we can't change these values from the past. 63 let groupURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.damus")! 64 return groupURL.appendingPathComponent("ImageCache").path 65 } 66 67 /// The latest path for kingfisher to store cached images on. 68 /// 69 /// Documentation references: 70 /// - https://developer.apple.com/documentation/foundation/filemanager/containerurl(forsecurityapplicationgroupidentifier:)#:~:text=The%20system%20creates%20only%20the%20Library/Caches%20subdirectory%20automatically 71 /// - https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html#:~:text=Put%20data%20cache,files%20as%20needed. 72 static func kingfisherCachePath() -> URL { 73 let groupURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: Constants.DAMUS_APP_GROUP_IDENTIFIER)! 74 return groupURL 75 .appendingPathComponent("Library") 76 .appendingPathComponent("Caches") 77 .appendingPathComponent(Constants.IMAGE_CACHE_DIRNAME) 78 } 79 }