import Foundation import UIKit class GIFFileManager { static let shared = GIFFileManager() private init() { createGIFsDirectoryIfNeeded() } // MARK: - File Storage private var documentsDirectory: URL { // First try to get the App Group container if let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.gifcollector") { return containerURL } // Fall back to the app's documents directory if App Group is not available let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) return paths[0] } private var gifsDirectory: URL { return documentsDirectory.appendingPathComponent("SavedGIFs", isDirectory: true) } private func createGIFsDirectoryIfNeeded() { let fileManager = FileManager.default if !fileManager.fileExists(atPath: gifsDirectory.path) { do { try fileManager.createDirectory(at: gifsDirectory, withIntermediateDirectories: true) } catch { print("Error creating GIFs directory: \(error)") } } } func storeGIF(data: Data, fromURL urlString: String) -> String? { // Check if this is a shared GIF from the share extension if let sharedGIFPath = checkForSharedGIF(withURL: urlString), FileManager.default.fileExists(atPath: sharedGIFPath) { return sharedGIFPath } // Create a unique filename based on the URL hash and timestamp let urlHash = urlString.hashValue let timestamp = Int(Date().timeIntervalSince1970) let filename = "gif_\(urlHash)_\(timestamp).gif" let fileURL = gifsDirectory.appendingPathComponent(filename) do { try data.write(to: fileURL) return fileURL.path } catch { print("Error saving GIF to disk: \(error)") return nil } } func loadGIFData(from localPath: String) -> Data? { let url = URL(fileURLWithPath: localPath) do { let data = try Data(contentsOf: url) return data } catch { print("Error loading GIF from disk: \(error)") return nil } } func deleteGIF(at localPath: String) -> Bool { let fileManager = FileManager.default do { try fileManager.removeItem(atPath: localPath) return true } catch { print("Error deleting GIF from disk: \(error)") return false } } func fileExists(at localPath: String) -> Bool { return FileManager.default.fileExists(atPath: localPath) } func getAllGIFsSize() -> Int64 { let fileManager = FileManager.default let enumerator = fileManager.enumerator(at: gifsDirectory, includingPropertiesForKeys: [.fileSizeKey]) var totalSize: Int64 = 0 while let fileURL = enumerator?.nextObject() as? URL { do { let attributes = try fileURL.resourceValues(forKeys: [.fileSizeKey]) if let fileSize = attributes.fileSize { totalSize += Int64(fileSize) } } catch { print("Error calculating file size: \(error)") } } return totalSize } // Clean up old GIFs if storage exceeds 100 MB func performStorageCleanupIfNeeded() { let maxStorageSize: Int64 = 100 * 1024 * 1024 // 100 MB if getAllGIFsSize() > maxStorageSize { cleanupOldGIFs() } } private func cleanupOldGIFs() { let fileManager = FileManager.default do { // Get all files and their creation dates let fileURLs = try fileManager.contentsOfDirectory(at: gifsDirectory, includingPropertiesForKeys: [.creationDateKey]) // Sort by creation date let sortedFiles = try fileURLs.sorted { let date1 = try $0.resourceValues(forKeys: [.creationDateKey]).creationDate ?? Date.distantPast let date2 = try $1.resourceValues(forKeys: [.creationDateKey]).creationDate ?? Date.distantPast return date1 < date2 } // Delete the oldest 30% of files let filesToDelete = Int(Double(sortedFiles.count) * 0.3) for i in 0.. String? { guard let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.gifcollector") else { return nil } let sharedGIFsFolder = containerURL.appendingPathComponent("GIFs", isDirectory: true) guard FileManager.default.fileExists(atPath: sharedGIFsFolder.path) else { return nil } do { let fileURLs = try FileManager.default.contentsOfDirectory(at: sharedGIFsFolder, includingPropertiesForKeys: nil) // Find any shared GIF that matches our URL (usually won't find any, but helps avoid duplicates) let userDefaults = UserDefaults(suiteName: "group.gifcollector") if let pendingGIFs = userDefaults?.array(forKey: "pendingGIFs") as? [[String: Any]] { for gifInfo in pendingGIFs { if let originURL = gifInfo["originalURL"] as? String, let path = gifInfo["localFilePath"] as? String, originURL == urlString { return path } } } return nil } catch { print("Error checking for shared GIFs: \(error)") return nil } } }