diff --git a/Mac/AppDelegate.swift b/Mac/AppDelegate.swift index 53a823fcb..00933d244 100644 --- a/Mac/AppDelegate.swift +++ b/Mac/AppDelegate.swift @@ -142,7 +142,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, imageDownloader = ImageDownloader(folder: imagesFolder) authorAvatarDownloader = AuthorAvatarDownloader(imageDownloader: imageDownloader) - feedIconDownloader = FeedIconDownloader(imageDownloader: imageDownloader) + feedIconDownloader = FeedIconDownloader(imageDownloader: imageDownloader, folder: tempDirectory) updateSortMenuItems() createAndShowMainWindow() diff --git a/Shared/Images/FeedIconDownloader.swift b/Shared/Images/FeedIconDownloader.swift index 001c884ad..2d924e584 100644 --- a/Shared/Images/FeedIconDownloader.swift +++ b/Shared/Images/FeedIconDownloader.swift @@ -20,15 +20,27 @@ extension Notification.Name { public final class FeedIconDownloader { + private static let saveQueue = CoalescingQueue(name: "Cache Save Queue", interval: 1.0) + private let imageDownloader: ImageDownloader + private var homePageToIconURLCache = [String: String]() + private var homePageToIconURLCachePath: String + private var homePageToIconURLCacheDirty = false { + didSet { + queueSaveHomePageToIconURLCacheIfNeeded() + } + } + private var homePagesWithNoIconURL = Set() private var urlsInProgress = Set() private var cache = [Feed: RSImage]() private var waitingForFeedURLs = [String: Feed]() - init(imageDownloader: ImageDownloader) { + init(imageDownloader: ImageDownloader, folder: String) { self.imageDownloader = imageDownloader + self.homePageToIconURLCachePath = (folder as NSString).appendingPathComponent("HomePageToIconURLCache.plist") + loadHomePageToIconURLCache() NotificationCenter.default.addObserver(self, selector: #selector(imageDidBecomeAvailable(_:)), name: .ImageDidBecomeAvailable, object: imageDownloader) } @@ -81,6 +93,12 @@ public final class FeedIconDownloader { _ = icon(for: feed) } + @objc func saveHomePageToIconURLCacheIfNeeded() { + if homePageToIconURLCacheDirty { + saveHomePageToIconURLCache() + } + } + } private extension FeedIconDownloader { @@ -123,9 +141,9 @@ private extension FeedIconDownloader { } func cacheIconURL(for homePageURL: String, _ iconURL: String) { - homePagesWithNoIconURL.remove(homePageURL) homePageToIconURLCache[homePageURL] = iconURL + homePageToIconURLCacheDirty = true } func findIconURLForHomePageURL(_ homePageURL: String, feed: Feed) { @@ -156,4 +174,32 @@ private extension FeedIconDownloader { homePagesWithNoIconURL.insert(homePageURL) } + + func loadHomePageToIconURLCache() { + let url = URL(fileURLWithPath: homePageToIconURLCachePath) + guard let data = try? Data(contentsOf: url) else { + return + } + let decoder = PropertyListDecoder() + homePageToIconURLCache = (try? decoder.decode([String: String].self, from: data)) ?? [String: String]() + } + + func queueSaveHomePageToIconURLCacheIfNeeded() { + FeedIconDownloader.saveQueue.add(self, #selector(saveHomePageToIconURLCacheIfNeeded)) + } + + func saveHomePageToIconURLCache() { + homePageToIconURLCacheDirty = false + + let encoder = PropertyListEncoder() + encoder.outputFormat = .binary + let url = URL(fileURLWithPath: homePageToIconURLCachePath) + do { + let data = try encoder.encode(homePageToIconURLCache) + try data.write(to: url) + } catch { + assertionFailure(error.localizedDescription) + } + } + }