155 lines
4.1 KiB
Swift
155 lines
4.1 KiB
Swift
//
|
|
// FaviconDownloader.swift
|
|
// Evergreen
|
|
//
|
|
// Created by Brent Simmons on 11/19/17.
|
|
// Copyright © 2017 Ranchero Software. All rights reserved.
|
|
//
|
|
|
|
import AppKit
|
|
import Data
|
|
import RSCore
|
|
|
|
extension Notification.Name {
|
|
|
|
static let FaviconDidBecomeAvailable = Notification.Name("FaviconDidBecomeAvailableNotification") // userInfo key: FaviconDownloader.UserInfoKey.faviconURL
|
|
}
|
|
|
|
final class FaviconDownloader {
|
|
|
|
private var seekingFaviconCache = [String: SeekingFavicon]() // homePageURL: SeekingFavicon
|
|
private var singleFaviconDownloaderCache = [String: SingleFaviconDownloader]() // faviconURL: SingleFaviconDownloader
|
|
private let folder: String
|
|
private let diskCache: BinaryDiskCache
|
|
private let queue: DispatchQueue
|
|
|
|
struct UserInfoKey {
|
|
static let faviconURL = "faviconURL"
|
|
}
|
|
|
|
init(folder: String) {
|
|
|
|
self.folder = folder
|
|
self.diskCache = BinaryDiskCache(folder: folder)
|
|
self.queue = DispatchQueue(label: "FaviconDownloader serial queue - \(folder)")
|
|
|
|
NotificationCenter.default.addObserver(self, selector: #selector(seekingFaviconDidSeek(_:)), name: .SeekingFaviconSeekDidComplete, object: nil)
|
|
NotificationCenter.default.addObserver(self, selector: #selector(didLoadFavicon(_:)), name: .DidLoadFavicon, object: nil)
|
|
}
|
|
|
|
// MARK: - API
|
|
|
|
func favicon(for feed: Feed) -> NSImage? {
|
|
|
|
assert(Thread.isMainThread)
|
|
|
|
if let faviconURL = feed.faviconURL {
|
|
return favicon(with: faviconURL)
|
|
}
|
|
|
|
guard let homePageURL = feed.homePageURL else {
|
|
return nil
|
|
}
|
|
return favicon(withHomePageURL: homePageURL)
|
|
}
|
|
|
|
func favicon(with faviconURL: String) -> NSImage? {
|
|
|
|
let downloader = faviconDownloader(withURL: faviconURL)
|
|
return downloader.image
|
|
}
|
|
|
|
func favicon(withHomePageURL homePageURL: String) -> NSImage? {
|
|
|
|
guard let seekingFavicon = seekingFavicon(with: normalizedHomePageURL(homePageURL)) else {
|
|
return nil
|
|
}
|
|
return favicon(withSeekingFavicon: seekingFavicon)
|
|
}
|
|
|
|
// MARK: - Notifications
|
|
|
|
@objc func seekingFaviconDidSeek(_ note: Notification) {
|
|
|
|
guard let seekingFavicon = note.object as? SeekingFavicon else {
|
|
return
|
|
}
|
|
favicon(withSeekingFavicon: seekingFavicon)
|
|
}
|
|
|
|
@objc func didLoadFavicon(_ note: Notification) {
|
|
|
|
guard let singleFaviconDownloader = note.object as? SingleFaviconDownloader else {
|
|
return
|
|
}
|
|
guard let _ = singleFaviconDownloader.image else {
|
|
return
|
|
}
|
|
|
|
postFaviconDidBecomeAvailableNotification(singleFaviconDownloader.faviconURL)
|
|
}
|
|
}
|
|
|
|
private extension FaviconDownloader {
|
|
|
|
private static let localeForLowercasing = Locale(identifier: "en_US")
|
|
|
|
func normalizedHomePageURL(_ url: String) -> String {
|
|
|
|
// Many times the homePageURL is missing a trailing /.
|
|
// We add one when needed.
|
|
|
|
guard !url.hasSuffix("/") else {
|
|
return url
|
|
}
|
|
let lowercasedURL = url.lowercased(with: FaviconDownloader.localeForLowercasing)
|
|
guard lowercasedURL.hasPrefix("http://") || lowercasedURL.hasPrefix("https://") else {
|
|
return url
|
|
}
|
|
guard url.components(separatedBy: "/").count < 4 else {
|
|
return url
|
|
}
|
|
return url + "/"
|
|
}
|
|
|
|
@discardableResult
|
|
func favicon(withSeekingFavicon seekingFavicon: SeekingFavicon) -> NSImage? {
|
|
|
|
guard let faviconURL = seekingFavicon.faviconURL else {
|
|
return nil
|
|
}
|
|
return favicon(with: faviconURL)
|
|
}
|
|
|
|
func faviconDownloader(withURL faviconURL: String) -> SingleFaviconDownloader {
|
|
|
|
if let downloader = singleFaviconDownloaderCache[faviconURL] {
|
|
downloader.downloadFaviconIfNeeded()
|
|
return downloader
|
|
}
|
|
|
|
let downloader = SingleFaviconDownloader(faviconURL: faviconURL, diskCache: diskCache, queue: queue)
|
|
singleFaviconDownloaderCache[faviconURL] = downloader
|
|
return downloader
|
|
}
|
|
|
|
func seekingFavicon(with homePageURL: String) -> SeekingFavicon? {
|
|
|
|
if let seekingFavicon = seekingFaviconCache[homePageURL] {
|
|
return seekingFavicon
|
|
}
|
|
|
|
guard let seekingFavicon = SeekingFavicon(homePageURL: homePageURL) else {
|
|
return nil
|
|
}
|
|
seekingFaviconCache[homePageURL] = seekingFavicon
|
|
return seekingFavicon
|
|
}
|
|
|
|
func postFaviconDidBecomeAvailableNotification(_ faviconURL: String) {
|
|
|
|
let userInfo: [AnyHashable: Any] = [UserInfoKey.faviconURL: faviconURL]
|
|
NotificationCenter.default.post(name: .FaviconDidBecomeAvailable, object: self, userInfo: userInfo)
|
|
}
|
|
}
|