From 7c478204557535b91c80448c79574b8e277231e8 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Sat, 14 Dec 2024 15:17:11 -0800 Subject: [PATCH] Simplify FeedIconDownloader. --- Shared/Images/FeedIconDownloader.swift | 166 +++---------------------- 1 file changed, 16 insertions(+), 150 deletions(-) diff --git a/Shared/Images/FeedIconDownloader.swift b/Shared/Images/FeedIconDownloader.swift index ee419df1f..b1cd52b06 100644 --- a/Shared/Images/FeedIconDownloader.swift +++ b/Shared/Images/FeedIconDownloader.swift @@ -7,7 +7,6 @@ // import Foundation -import Articles import Account import RSCore import RSWeb @@ -20,47 +19,16 @@ extension Notification.Name { public final class FeedIconDownloader { - private static let saveQueue = CoalescingQueue(name: "Cache Save Queue", interval: 1.0) + public static let shared = FeedIconDownloader() - private let imageDownloader: ImageDownloader - - private var feedURLToIconURLCache = [String: String]() - private var feedURLToIconURLCachePath: String - private var feedURLToIconURLCacheDirty = false { - didSet { - queueSaveFeedURLToIconURLCacheIfNeeded() - } - } - - private var homePageToIconURLCache = [String: String]() - private var homePageToIconURLCachePath: String - private var homePageToIconURLCacheDirty = false { - didSet { - queueSaveHomePageToIconURLCacheIfNeeded() - } - } - - private var homePagesWithNoIconURLCache = Set() - private var homePagesWithUglyIcons: Set = { - return Set(["https://www.macsparky.com/", "https://xkcd.com/"]) - }() - private var feedsWithNoIconURL = [String: WebFeed]() - - private var urlsInProgress = Set() + private let imageDownloader = ImageDownloader.shared + private var homePagesWithNoIconURL = Set() private var cache = [WebFeed: IconImage]() private var waitingForFeedURLs = [String: WebFeed]() - init(imageDownloader: ImageDownloader, folder: String) { - self.imageDownloader = imageDownloader - self.feedURLToIconURLCachePath = (folder as NSString).appendingPathComponent("FeedURLToIconURLCache.plist") - self.homePageToIconURLCachePath = (folder as NSString).appendingPathComponent("HomePageToIconURLCache.plist") - loadFeedURLToIconURLCache() - loadHomePageToIconURLCache() - NotificationCenter.default.addObserver(self, selector: #selector(imageDidBecomeAvailable(_:)), name: .ImageDidBecomeAvailable, object: imageDownloader) - } + init() { - func resetCache() { - cache = [WebFeed: IconImage]() + NotificationCenter.default.addObserver(self, selector: #selector(imageDidBecomeAvailable(_:)), name: .ImageDidBecomeAvailable, object: imageDownloader) } func icon(for feed: WebFeed) -> IconImage? { @@ -69,29 +37,21 @@ public final class FeedIconDownloader { return cachedImage } - if let hpURLString = feed.homePageURL, let hpURL = URL(string: hpURLString), (hpURL.host == "nnw.ranchero.com" || hpURL.host == "netnewswire.blog") { + if let homePageURLString = feed.homePageURL, let homePageURL = URL(string: homePageURLString), (homePageURL.host == "nnw.ranchero.com" || homePageURL.host == "netnewswire.blog") { return IconImage.appIcon } - if feedsWithNoIconURL[feed.url] != nil { - return nil - } - func checkHomePageURL() { guard let homePageURL = feed.homePageURL else { return } - if homePagesWithNoIconURLCache.contains(homePageURL) { + if homePagesWithNoIconURL.contains(homePageURL) { return } icon(forHomePageURL: homePageURL, feed: feed) { (image) in if let image { - self.postFeedIconDidBecomeAvailableNotification(feed) self.cache[feed] = IconImage(image) - } - else { - self.homePagesWithNoIconURLCache.insert(homePageURL) - self.feedsWithNoIconURL[feed.url] = feed + self.postFeedIconDidBecomeAvailableNotification(feed) } } } @@ -100,8 +60,8 @@ public final class FeedIconDownloader { if let iconURL = feed.iconURL { icon(forURL: iconURL, feed: feed) { (image) in if let image = image { - self.postFeedIconDidBecomeAvailableNotification(feed) self.cache[feed] = IconImage(image) + self.postFeedIconDidBecomeAvailableNotification(feed) } else { checkHomePageURL() } @@ -110,69 +70,44 @@ public final class FeedIconDownloader { checkHomePageURL() } } - - if let feedProviderURL = feedURLToIconURLCache[feed.url] { - self.icon(forURL: feedProviderURL, feed: feed) { (image) in - if let image = image { - self.postFeedIconDidBecomeAvailableNotification(feed) - self.cache[feed] = IconImage(image) - } - } - return nil - } - + checkFeedIconURL() return nil } @objc func imageDidBecomeAvailable(_ note: Notification) { - guard let url = note.userInfo?[UserInfoKey.url] as? String, let feed = waitingForFeedURLs[url] else { + guard let url = note.userInfo?[UserInfoKey.url] as? String, let feed = waitingForFeedURLs[url] else { return } waitingForFeedURLs[url] = nil _ = icon(for: feed) } - - @objc func saveFeedURLToIconURLCacheIfNeeded() { - if feedURLToIconURLCacheDirty { - saveFeedURLToIconURLCache() - } - } - - @objc func saveHomePageToIconURLCacheIfNeeded() { - if homePageToIconURLCacheDirty { - saveHomePageToIconURLCache() - } - } } private extension FeedIconDownloader { + static let homePagesWithUglyIcons: Set = Set(["https://www.macsparky.com/", "https://xkcd.com/"]) + func icon(forHomePageURL homePageURL: String, feed: WebFeed, _ imageResultBlock: @escaping (RSImage?) -> Void) { - if homePagesWithNoIconURLCache.contains(homePageURL) || homePagesWithUglyIcons.contains(homePageURL) { + if homePagesWithNoIconURL.contains(homePageURL) || Self.homePagesWithUglyIcons.contains(homePageURL) { imageResultBlock(nil) return } - if let iconURL = cachedIconURL(for: homePageURL) { - icon(forURL: iconURL, feed: feed, imageResultBlock) - return - } - guard let metadata = HTMLMetadataDownloader.shared.cachedMetadata(for: homePageURL) else { imageResultBlock(nil) return } if let url = metadata.bestWebsiteIconURL() { - cacheIconURL(for: homePageURL, url) + homePagesWithNoIconURL.remove(homePageURL) icon(forURL: url, feed: feed, imageResultBlock) return } - homePagesWithNoIconURLCache.insert(homePageURL) + homePagesWithNoIconURL.insert(homePageURL) } func icon(forURL url: String, feed: WebFeed, _ imageResultBlock: @escaping (RSImage?) -> Void) { @@ -191,73 +126,4 @@ private extension FeedIconDownloader { NotificationCenter.default.post(name: .feedIconDidBecomeAvailable, object: self, userInfo: userInfo) } } - - func cachedIconURL(for homePageURL: String) -> String? { - - return homePageToIconURLCache[homePageURL] - } - - func cacheIconURL(for homePageURL: String, _ iconURL: String) { - - homePagesWithNoIconURLCache.remove(homePageURL) - - if homePageToIconURLCache[homePageURL] != iconURL { - homePageToIconURLCache[homePageURL] = iconURL - homePageToIconURLCacheDirty = true - } - } - - func loadFeedURLToIconURLCache() { - let url = URL(fileURLWithPath: feedURLToIconURLCachePath) - guard let data = try? Data(contentsOf: url) else { - return - } - let decoder = PropertyListDecoder() - feedURLToIconURLCache = (try? decoder.decode([String: String].self, from: data)) ?? [String: String]() - } - - 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 queueSaveFeedURLToIconURLCacheIfNeeded() { - FeedIconDownloader.saveQueue.add(self, #selector(saveFeedURLToIconURLCacheIfNeeded)) - } - - func queueSaveHomePageToIconURLCacheIfNeeded() { - FeedIconDownloader.saveQueue.add(self, #selector(saveHomePageToIconURLCacheIfNeeded)) - } - - func saveFeedURLToIconURLCache() { - feedURLToIconURLCacheDirty = false - - let encoder = PropertyListEncoder() - encoder.outputFormat = .binary - let url = URL(fileURLWithPath: feedURLToIconURLCachePath) - do { - let data = try encoder.encode(feedURLToIconURLCache) - try data.write(to: url) - } catch { - assertionFailure(error.localizedDescription) - } - } - - 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) - } - } }