NetNewsWire/Shared/Favicons/FaviconDownloader.swift

179 lines
4.5 KiB
Swift
Raw Normal View History

2017-11-20 08:59:04 +01:00
//
// FaviconDownloader.swift
2018-08-29 07:18:24 +02:00
// NetNewsWire
2017-11-20 08:59:04 +01:00
//
// Created by Brent Simmons on 11/19/17.
// Copyright © 2017 Ranchero Software. All rights reserved.
//
import Foundation
import Articles
import Account
2017-11-20 08:59:04 +01:00
import RSCore
extension Notification.Name {
static let FaviconDidBecomeAvailable = Notification.Name("FaviconDidBecomeAvailableNotification") // userInfo key: FaviconDownloader.UserInfoKey.faviconURL
2017-11-20 08:59:04 +01:00
}
final class FaviconDownloader {
private let folder: String
private let diskCache: BinaryDiskCache
private var singleFaviconDownloaderCache = [String: SingleFaviconDownloader]() // faviconURL: SingleFaviconDownloader
private var homePageToFaviconURLCache = [String: String]() //homePageURL: faviconURL
private var homePageURLsWithNoFaviconURL = Set<String>()
2017-11-20 08:59:04 +01:00
private let queue: DispatchQueue
private var cache = [Feed: RSImage]() // faviconURL: RSImage
2017-11-20 08:59:04 +01:00
struct UserInfoKey {
2017-11-20 08:59:04 +01:00
static let faviconURL = "faviconURL"
}
init(folder: String) {
self.folder = folder
self.diskCache = BinaryDiskCache(folder: folder)
2017-11-23 23:15:28 +01:00
self.queue = DispatchQueue(label: "FaviconDownloader serial queue - \(folder)")
NotificationCenter.default.addObserver(self, selector: #selector(didLoadFavicon(_:)), name: .DidLoadFavicon, object: nil)
2017-11-20 08:59:04 +01:00
}
// MARK: - API
func resetCache() {
cache = [Feed: RSImage]()
}
func favicon(for feed: Feed) -> RSImage? {
2017-11-20 08:59:04 +01:00
assert(Thread.isMainThread)
2017-11-24 19:45:22 +01:00
if let faviconURL = feed.faviconURL {
return favicon(with: faviconURL)
2017-11-24 19:45:22 +01:00
}
var homePageURL = feed.homePageURL
if homePageURL == nil {
// Base homePageURL off feedURL if needed. Wont always be accurate, but is good enough.
if let feedURL = URL(string: feed.url), let scheme = feedURL.scheme, let host = feedURL.host {
homePageURL = scheme + "://" + host + "/"
}
}
if let homePageURL = homePageURL {
return favicon(withHomePageURL: homePageURL)
}
return nil
2017-11-24 19:45:22 +01:00
}
func faviconAsAvatar(for feed: Feed) -> RSImage? {
if let image = cache[feed] {
return image
}
if let image = favicon(for: feed), let imageData = image.dataRepresentation() {
if let scaledImage = RSImage.scaledForAvatar(imageData) {
cache[feed] = scaledImage
return scaledImage
}
}
return nil
}
2017-11-24 19:45:22 +01:00
func favicon(with faviconURL: String) -> RSImage? {
2017-11-24 19:45:22 +01:00
let downloader = faviconDownloader(withURL: faviconURL)
return downloader.image
2017-11-24 19:45:22 +01:00
}
func favicon(withHomePageURL homePageURL: String) -> RSImage? {
let url = homePageURL.rs_normalizedURL()
if homePageURLsWithNoFaviconURL.contains(url) {
return nil
2017-11-24 19:45:22 +01:00
}
if let faviconURL = homePageToFaviconURLCache[url] {
return favicon(with: faviconURL)
}
2017-11-24 19:45:22 +01:00
findFaviconURL(with: url) { (faviconURL) in
if let faviconURL = faviconURL {
self.homePageToFaviconURLCache[url] = faviconURL
let _ = self.favicon(with: faviconURL)
}
else {
self.homePageURLsWithNoFaviconURL.insert(url)
}
2017-11-23 23:15:28 +01:00
}
return nil
2017-11-23 23:15:28 +01:00
}
// MARK: - Notifications
@objc func didLoadFavicon(_ note: Notification) {
2017-11-23 23:15:28 +01:00
guard let singleFaviconDownloader = note.object as? SingleFaviconDownloader else {
return
}
guard let _ = singleFaviconDownloader.image else {
return
2017-11-20 08:59:04 +01:00
}
postFaviconDidBecomeAvailableNotification(singleFaviconDownloader.faviconURL)
2017-11-20 08:59:04 +01:00
}
}
private extension FaviconDownloader {
static let localeForLowercasing = Locale(identifier: "en_US")
func findFaviconURL(with homePageURL: String, _ completion: @escaping (String?) -> Void) {
guard let url = URL(string: homePageURL) else {
completion(nil)
return
}
FaviconURLFinder.findFaviconURL(homePageURL) { (faviconURL) in
if let faviconURL = faviconURL {
completion(faviconURL)
return
}
guard let scheme = url.scheme, let host = url.host else {
completion(nil)
return
}
let defaultFaviconURL = "\(scheme)://\(host)/favicon.ico".lowercased(with: FaviconDownloader.localeForLowercasing)
completion(defaultFaviconURL)
}
}
func faviconDownloader(withURL faviconURL: String) -> SingleFaviconDownloader {
2017-11-20 08:59:04 +01:00
if let downloader = singleFaviconDownloaderCache[faviconURL] {
downloader.downloadFaviconIfNeeded()
return downloader
2017-11-20 08:59:04 +01:00
}
let downloader = SingleFaviconDownloader(faviconURL: faviconURL, diskCache: diskCache, queue: queue)
singleFaviconDownloaderCache[faviconURL] = downloader
return downloader
2017-11-20 08:59:04 +01:00
}
func postFaviconDidBecomeAvailableNotification(_ faviconURL: String) {
2017-11-23 23:15:28 +01:00
DispatchQueue.main.async {
let userInfo: [AnyHashable: Any] = [UserInfoKey.faviconURL: faviconURL]
NotificationCenter.default.post(name: .FaviconDidBecomeAvailable, object: self, userInfo: userInfo)
}
2017-11-23 23:15:28 +01:00
}
2017-11-20 08:59:04 +01:00
}