mirror of
https://github.com/Ranchero-Software/NetNewsWire.git
synced 2025-02-02 20:16:54 +01:00
Add FeaturedImageDownloader.
This commit is contained in:
parent
edba636121
commit
4e50529b16
@ -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 */,
|
||||
|
@ -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())
|
||||
|
88
Evergreen/Images/FeaturedImageDownloader.swift
Normal file
88
Evergreen/Images/FeaturedImageDownloader.swift
Normal 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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
64
Evergreen/Images/RSHTMLData+Extension.swift
Normal file
64
Evergreen/Images/RSHTMLData+Extension.swift
Normal 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 they’re 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
|
||||
}
|
||||
}
|
@ -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? {
|
||||
|
Loading…
x
Reference in New Issue
Block a user