NetNewsWire/Shared/Favicons/SingleFaviconDownloader.swift

156 lines
3.0 KiB
Swift
Raw Normal View History

//
// SingleFaviconDownloader.swift
2018-08-29 07:18:24 +02:00
// NetNewsWire
//
// Created by Brent Simmons on 11/23/17.
// Copyright © 2017 Ranchero Software. All rights reserved.
//
import AppKit
import RSCore
import RSWeb
// The image may be on disk already. If not, download it.
// Post .DidLoadFavicon notification once its in memory.
extension Notification.Name {
static let DidLoadFavicon = Notification.Name("DidLoadFaviconNotification")
}
final class SingleFaviconDownloader {
enum DiskStatus {
case unknown, notOnDisk, onDisk
}
let faviconURL: String
var image: RSImage?
private var lastDownloadAttemptDate: Date
private var diskStatus = DiskStatus.unknown
private var diskCache: BinaryDiskCache
private let queue: DispatchQueue
private var diskKey: String {
return (faviconURL as NSString).rs_md5Hash()
}
init(faviconURL: String, diskCache: BinaryDiskCache, queue: DispatchQueue) {
self.faviconURL = faviconURL
self.diskCache = diskCache
self.queue = queue
self.lastDownloadAttemptDate = Date()
findFavicon()
}
func downloadFaviconIfNeeded() {
// If we dont have an image, and lastDownloadAttemptDate is a while ago, try again.
if let _ = image {
return
}
let retryInterval: TimeInterval = 30 * 60 // 30 minutes
if Date().timeIntervalSince(lastDownloadAttemptDate) < retryInterval {
return
}
lastDownloadAttemptDate = Date()
findFavicon()
}
}
private extension SingleFaviconDownloader {
func findFavicon() {
readFromDisk { (image) in
if let image = image {
self.diskStatus = .onDisk
self.image = image
self.postDidLoadFaviconNotification()
return
}
self.diskStatus = .notOnDisk
self.downloadFavicon { (image) in
if let image = image {
self.image = image
self.postDidLoadFaviconNotification()
}
}
}
}
func readFromDisk(_ callback: @escaping (RSImage?) -> Void) {
guard diskStatus != .notOnDisk else {
callback(nil)
return
}
queue.async {
if let data = self.diskCache[self.diskKey], !data.isEmpty {
RSImage.scaledForAvatar(data, imageResultBlock: callback)
return
}
DispatchQueue.main.async {
callback(nil)
}
}
}
func saveToDisk(_ data: Data) {
queue.async {
do {
try self.diskCache.setData(data, forKey: self.diskKey)
DispatchQueue.main.async {
self.diskStatus = .onDisk
}
}
catch {}
}
}
func downloadFavicon(_ callback: @escaping (RSImage?) -> Void) {
guard let url = URL(string: faviconURL) else {
callback(nil)
return
}
downloadUsingCache(url) { (data, response, error) in
if let data = data, !data.isEmpty, let response = response, response.statusIsOK, error == nil {
self.saveToDisk(data)
RSImage.scaledForAvatar(data, imageResultBlock: callback)
return
}
if let error = error {
appDelegate.logMessage("Error downloading favicon at \(url): \(error)", type: .warning)
}
callback(nil)
}
}
func postDidLoadFaviconNotification() {
assert(Thread.isMainThread)
NotificationCenter.default.post(name: .DidLoadFavicon, object: self)
}
}