Add FeaturedImageDownloader.

This commit is contained in:
Brent Simmons 2017-11-26 19:57:45 -08:00
parent edba636121
commit 4e50529b16
6 changed files with 181 additions and 33 deletions

View File

@ -9,6 +9,8 @@
/* Begin PBXBuildFile section */
8426118A1FCB67AA0086A189 /* FeedIconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611891FCB67AA0086A189 /* FeedIconDownloader.swift */; };
8426119E1FCB6ED40086A189 /* HTMLMetadataDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8426119D1FCB6ED40086A189 /* HTMLMetadataDownloader.swift */; };
842611A01FCB72600086A189 /* FeaturedImageDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8426119F1FCB72600086A189 /* FeaturedImageDownloader.swift */; };
842611A21FCB769D0086A189 /* RSHTMLData+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611A11FCB769D0086A189 /* RSHTMLData+Extension.swift */; };
842E45CE1ED8C308000A8B52 /* AppNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842E45CD1ED8C308000A8B52 /* AppNotifications.swift */; };
842E45DD1ED8C54B000A8B52 /* Browser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842E45DC1ED8C54B000A8B52 /* Browser.swift */; };
842E45E31ED8C681000A8B52 /* KeyboardDelegateProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842E45E21ED8C681000A8B52 /* KeyboardDelegateProtocol.swift */; };
@ -407,6 +409,8 @@
/* Begin PBXFileReference section */
842611891FCB67AA0086A189 /* FeedIconDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedIconDownloader.swift; sourceTree = "<group>"; };
8426119D1FCB6ED40086A189 /* HTMLMetadataDownloader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTMLMetadataDownloader.swift; sourceTree = "<group>"; };
8426119F1FCB72600086A189 /* FeaturedImageDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeaturedImageDownloader.swift; sourceTree = "<group>"; };
842611A11FCB769D0086A189 /* RSHTMLData+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RSHTMLData+Extension.swift"; sourceTree = "<group>"; };
842E45CD1ED8C308000A8B52 /* AppNotifications.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AppNotifications.swift; path = Evergreen/AppNotifications.swift; sourceTree = "<group>"; };
842E45DC1ED8C54B000A8B52 /* Browser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Browser.swift; path = Evergreen/Browser.swift; sourceTree = "<group>"; };
842E45E21ED8C681000A8B52 /* KeyboardDelegateProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardDelegateProtocol.swift; sourceTree = "<group>"; };
@ -581,6 +585,8 @@
845213221FCA5B10003B6E93 /* ImageDownloader.swift */,
84E850851FCB60CE0072EA88 /* AuthorAvatarDownloader.swift */,
842611891FCB67AA0086A189 /* FeedIconDownloader.swift */,
8426119F1FCB72600086A189 /* FeaturedImageDownloader.swift */,
842611A11FCB769D0086A189 /* RSHTMLData+Extension.swift */,
);
name = Images;
path = Evergreen/Images;
@ -1372,6 +1378,7 @@
849A975C1ED9EB0D007D329B /* DefaultFeedsImporter.swift in Sources */,
849A97891ED9ECEF007D329B /* ArticleStyle.swift in Sources */,
84FF69B11FC3793300DC198E /* FaviconURLFinder.swift in Sources */,
842611A21FCB769D0086A189 /* RSHTMLData+Extension.swift in Sources */,
849A978A1ED9ECEF007D329B /* ArticleStylesManager.swift in Sources */,
849A97791ED9EC04007D329B /* TimelineStringUtilities.swift in Sources */,
84F204CE1FAACB660076E152 /* FeedListViewController.swift in Sources */,
@ -1391,6 +1398,7 @@
849A978D1ED9EE4D007D329B /* FeedListWindowController.swift in Sources */,
849A97771ED9EC04007D329B /* TimelineCellData.swift in Sources */,
84B99C6B1FAE370B00ECDEDB /* FeedListFeed.swift in Sources */,
842611A01FCB72600086A189 /* FeaturedImageDownloader.swift in Sources */,
849A97781ED9EC04007D329B /* TimelineCellLayout.swift in Sources */,
849A976C1ED9EBC8007D329B /* TimelineTableRowView.swift in Sources */,
849A977B1ED9EC04007D329B /* UnreadIndicatorView.swift in Sources */,

View File

@ -24,6 +24,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
var faviconDownloader: FaviconDownloader!
var imageDownloader: ImageDownloader!
var authorAvatarDownloader: AuthorAvatarDownloader!
var feedIconDownloader: FeedIconDownloader!
var appName: String!
var pseudoFeeds = [PseudoFeed]()
@ -144,7 +145,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
imageDownloader = ImageDownloader(folder: imagesFolder)
authorAvatarDownloader = AuthorAvatarDownloader(imageDownloader: imageDownloader)
feedIconDownloader = FeedIconDownloader(imageDownloader: imageDownloader)
let todayFeed = SmartFeed(delegate: TodayFeedDelegate())
let unreadFeed = UnreadFeed()
let starredFeed = SmartFeed(delegate: StarredFeedDelegate())

View File

@ -0,0 +1,88 @@
//
// FeaturedImageDownloader.swift
// Evergreen
//
// Created by Brent Simmons on 11/26/17.
// Copyright © 2017 Ranchero Software. All rights reserved.
//
import Cocoa
import Data
import RSParser
final class FeaturedImageDownloader {
private let imageDownloader: ImageDownloader
private var articleURLToFeaturedImageURLCache = [String: String]()
private var articleURLsWithNoFeaturedImage = Set<String>()
init(imageDownloader: ImageDownloader) {
self.imageDownloader = imageDownloader
}
func image(for article: Article) -> NSImage? {
if let url = article.imageURL {
return image(forFeaturedImageURL: url)
}
if let articleURL = article.url {
return image(forArticleURL: articleURL)
}
return nil
}
func image(forArticleURL articleURL: String) -> NSImage? {
if articleURLsWithNoFeaturedImage.contains(articleURL) {
return nil
}
if let featuredImageURL = cachedURL(for: articleURL) {
return image(forFeaturedImageURL: featuredImageURL)
}
findFeaturedImageURL(for: articleURL)
return nil
}
func image(forFeaturedImageURL featuredImageURL: String) -> NSImage? {
return imageDownloader.image(for: featuredImageURL)
}
}
private extension FeaturedImageDownloader {
func cachedURL(for articleURL: String) -> String? {
return articleURLToFeaturedImageURLCache[articleURL]
}
func cacheURL(for articleURL: String, _ featuredImageURL: String) {
articleURLsWithNoFeaturedImage.remove(articleURL)
articleURLToFeaturedImageURLCache[articleURL] = featuredImageURL
}
func findFeaturedImageURL(for articleURL: String) {
HTMLMetadataDownloader.downloadMetadata(for: articleURL) { (metadata) in
guard let metadata = metadata else {
return
}
self.pullFeaturedImageURL(from: metadata, articleURL: articleURL)
}
}
func pullFeaturedImageURL(from metadata: RSHTMLMetadata, articleURL: String) {
if let url = metadata.bestFeaturedImageURL() {
cacheURL(for: articleURL, url)
let _ = image(forFeaturedImageURL: url)
return
}
articleURLsWithNoFeaturedImage.insert(articleURL)
}
}

View File

@ -15,6 +15,8 @@ public final class FeedIconDownloader {
private let imageDownloader: ImageDownloader
private var homePageToIconURLCache = [String: String]()
private var homePagesWithNoIconURL = Set<String>()
private var homePageDownloadsInProgress = Set<String>()
init(imageDownloader: ImageDownloader) {
@ -36,6 +38,10 @@ public final class FeedIconDownloader {
func icon(forHomePageURL homePageURL: String) -> NSImage? {
if homePagesWithNoIconURL.contains(homePageURL) {
return nil
}
if let iconURL = cachedIconURL(for: homePageURL) {
return icon(forURL: iconURL)
}
@ -59,14 +65,20 @@ private extension FeedIconDownloader {
func cacheIconURL(for homePageURL: String, _ iconURL: String) {
homePagesWithNoIconURL.remove(homePageURL)
homePageToIconURLCache[homePageURL] = iconURL
let _ = icon(forURL: iconURL)
}
func findIconURLForHomePageURL(_ homePageURL: String) {
guard !homePageDownloadsInProgress.contains(homePageURL) else {
return
}
homePageDownloadsInProgress.insert(homePageURL)
HTMLMetadataDownloader.downloadMetadata(for: homePageURL) { (metadata) in
self.homePageDownloadsInProgress.remove(homePageURL)
guard let metadata = metadata else {
return
}
@ -76,34 +88,12 @@ private extension FeedIconDownloader {
func pullIconURL(from metadata: RSHTMLMetadata, homePageURL: String) {
if let openGraphImageURL = largestOpenGraphImageURL(from: metadata) {
cacheIconURL(for: homePageURL, openGraphImageURL)
if let url = metadata.bestWebsiteIconURL() {
cacheIconURL(for: homePageURL, url)
let _ = icon(forURL: url)
return
}
if let twitterImageURL = metadata.twitterProperties.imageURL {
cacheIconURL(for: homePageURL, twitterImageURL)
}
}
func largestOpenGraphImageURL(from metadata: RSHTMLMetadata) -> String? {
guard let openGraphImages = metadata.openGraphProperties?.images else {
return nil
}
var bestImage: RSHTMLOpenGraphImage? = nil
for image in openGraphImages {
if bestImage == nil {
bestImage = image
continue
}
if image.height > bestImage!.height && image.width > bestImage!.width {
bestImage = image
}
}
return bestImage?.secureURL ?? bestImage?.url
homePagesWithNoIconURL.insert(homePageURL)
}
}

View File

@ -0,0 +1,64 @@
//
// RSHTMLData+Extension.swift
// Evergreen
//
// Created by Brent Simmons on 11/26/17.
// Copyright © 2017 Ranchero Software. All rights reserved.
//
import Foundation
import RSParser
extension RSHTMLMetadata {
func largestOpenGraphImageURL() -> String? {
guard let openGraphImages = openGraphProperties?.images, !openGraphImages.isEmpty else {
return nil
}
var bestImage: RSHTMLOpenGraphImage? = nil
for image in openGraphImages {
if bestImage == nil {
bestImage = image
continue
}
if image.height > bestImage!.height && image.width > bestImage!.width {
bestImage = image
}
}
guard let url = bestImage?.secureURL ?? bestImage?.url else {
return nil
}
// Bad ones we should ignore.
let badURLs = Set(["https://s0.wp.com/i/blank.jpg"])
guard !badURLs.contains(url) else {
return nil
}
return url
}
func bestWebsiteIconURL() -> String? {
// TODO: metadata icons sometimes theyre large enough to use here.
if let openGraphImageURL = largestOpenGraphImageURL() {
return openGraphImageURL
}
return twitterProperties.imageURL
}
func bestFeaturedImageURL() -> String? {
if let openGraphImageURL = largestOpenGraphImageURL() {
return openGraphImageURL
}
return twitterProperties.imageURL
}
}

View File

@ -443,11 +443,7 @@ extension TimelineViewController: NSTableViewDelegate {
// TODO: make Feed know about its authors.
// https://github.com/brentsimmons/Evergreen/issues/212
if let iconURL = feed.iconURL {
return appDelegate.imageDownloader.image(for: iconURL)
}
return nil
return appDelegate.feedIconDownloader.icon(for: feed)
}
private func avatarForAuthor(_ author: Author) -> NSImage? {