Cache the feed provider results to make it as fast as the other icon look up types.

This commit is contained in:
Maurice Parker 2020-04-16 02:17:38 -05:00
parent c44759fdb2
commit f8a664d035
1 changed files with 62 additions and 6 deletions

View File

@ -24,6 +24,14 @@ public final class WebFeedIconDownloader {
private let imageDownloader: ImageDownloader 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 homePageToIconURLCache = [String: String]()
private var homePageToIconURLCachePath: String private var homePageToIconURLCachePath: String
private var homePageToIconURLCacheDirty = false { private var homePageToIconURLCacheDirty = false {
@ -50,8 +58,10 @@ public final class WebFeedIconDownloader {
init(imageDownloader: ImageDownloader, folder: String) { init(imageDownloader: ImageDownloader, folder: String) {
self.imageDownloader = imageDownloader self.imageDownloader = imageDownloader
self.feedURLToIconURLCachePath = (folder as NSString).appendingPathComponent("FeedURLToIconURLCache.plist")
self.homePageToIconURLCachePath = (folder as NSString).appendingPathComponent("HomePageToIconURLCache.plist") self.homePageToIconURLCachePath = (folder as NSString).appendingPathComponent("HomePageToIconURLCache.plist")
self.homePagesWithNoIconURLCachePath = (folder as NSString).appendingPathComponent("HomePagesWithNoIconURLCache.plist") self.homePagesWithNoIconURLCachePath = (folder as NSString).appendingPathComponent("HomePagesWithNoIconURLCache.plist")
loadFeedURLToIconURLCache()
loadHomePageToIconURLCache() loadHomePageToIconURLCache()
loadHomePagesWithNoIconURLCache() loadHomePagesWithNoIconURLCache()
NotificationCenter.default.addObserver(self, selector: #selector(imageDidBecomeAvailable(_:)), name: .ImageDidBecomeAvailable, object: imageDownloader) NotificationCenter.default.addObserver(self, selector: #selector(imageDidBecomeAvailable(_:)), name: .ImageDidBecomeAvailable, object: imageDownloader)
@ -98,17 +108,30 @@ public final class WebFeedIconDownloader {
} }
} }
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
}
if let components = URLComponents(string: feed.url), let feedProvider = ExtensionPointManager.shared.bestFeedProvider(for: components, with: nil) { if let components = URLComponents(string: feed.url), let feedProvider = ExtensionPointManager.shared.bestFeedProvider(for: components, with: nil) {
feedProvider.iconURL(components) { result in feedProvider.iconURL(components) { result in
if case .success(let url) = result { switch result {
self.icon(forURL: url, feed: feed) { (image) in case .success(let feedProviderURL):
self.feedURLToIconURLCache[feed.url] = feedProviderURL
self.feedURLToIconURLCacheDirty = true
self.icon(forURL: feedProviderURL, feed: feed) { (image) in
if let image = image { if let image = image {
self.postFeedIconDidBecomeAvailableNotification(feed) self.postFeedIconDidBecomeAvailableNotification(feed)
self.cache[feed] = IconImage(image) self.cache[feed] = IconImage(image)
} else {
checkFeedIconURL()
} }
} }
case .failure:
checkFeedIconURL()
} }
} }
} else { } else {
@ -126,6 +149,12 @@ public final class WebFeedIconDownloader {
_ = icon(for: feed) _ = icon(for: feed)
} }
@objc func saveFeedURLToIconURLCacheIfNeeded() {
if feedURLToIconURLCacheDirty {
saveFeedURLToIconURLCache()
}
}
@objc func saveHomePageToIconURLCacheIfNeeded() { @objc func saveHomePageToIconURLCacheIfNeeded() {
if homePageToIconURLCacheDirty { if homePageToIconURLCacheDirty {
saveHomePageToIconURLCache() saveHomePageToIconURLCache()
@ -216,6 +245,15 @@ private extension WebFeedIconDownloader {
homePagesWithNoIconURLCacheDirty = true homePagesWithNoIconURLCacheDirty = 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() { func loadHomePageToIconURLCache() {
let url = URL(fileURLWithPath: homePageToIconURLCachePath) let url = URL(fileURLWithPath: homePageToIconURLCachePath)
guard let data = try? Data(contentsOf: url) else { guard let data = try? Data(contentsOf: url) else {
@ -235,6 +273,10 @@ private extension WebFeedIconDownloader {
homePagesWithNoIconURLCache = Set(decoded) homePagesWithNoIconURLCache = Set(decoded)
} }
func queueSaveFeedURLToIconURLCacheIfNeeded() {
WebFeedIconDownloader.saveQueue.add(self, #selector(saveFeedURLToIconURLCacheIfNeeded))
}
func queueSaveHomePageToIconURLCacheIfNeeded() { func queueSaveHomePageToIconURLCacheIfNeeded() {
WebFeedIconDownloader.saveQueue.add(self, #selector(saveHomePageToIconURLCacheIfNeeded)) WebFeedIconDownloader.saveQueue.add(self, #selector(saveHomePageToIconURLCacheIfNeeded))
} }
@ -243,6 +285,20 @@ private extension WebFeedIconDownloader {
WebFeedIconDownloader.saveQueue.add(self, #selector(saveHomePagesWithNoIconURLCacheIfNeeded)) WebFeedIconDownloader.saveQueue.add(self, #selector(saveHomePagesWithNoIconURLCacheIfNeeded))
} }
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() { func saveHomePageToIconURLCache() {
homePageToIconURLCacheDirty = false homePageToIconURLCacheDirty = false