2017-11-26 05:12:53 +01:00
|
|
|
//
|
|
|
|
// ImageDownloader.swift
|
|
|
|
// Evergreen
|
|
|
|
//
|
|
|
|
// Created by Brent Simmons on 11/25/17.
|
|
|
|
// Copyright © 2017 Ranchero Software. All rights reserved.
|
|
|
|
//
|
|
|
|
|
|
|
|
import AppKit
|
|
|
|
import RSCore
|
|
|
|
import RSWeb
|
|
|
|
|
|
|
|
extension Notification.Name {
|
|
|
|
|
2018-01-05 22:22:16 +01:00
|
|
|
static let ImageDidBecomeAvailable = Notification.Name("ImageDidBecomeAvailableNotification") // UserInfoKey.url
|
2017-11-26 05:12:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
final class ImageDownloader {
|
|
|
|
|
|
|
|
private let folder: String
|
|
|
|
private var diskCache: BinaryDiskCache
|
|
|
|
private let queue: DispatchQueue
|
|
|
|
private var imageCache = [String: NSImage]() // url: image
|
2017-11-26 05:24:38 +01:00
|
|
|
private var urlsInProgress = Set<String>()
|
|
|
|
private var badURLs = Set<String>() // That return a 404 or whatever. Just skip them in the future.
|
|
|
|
|
2017-11-26 05:12:53 +01:00
|
|
|
init(folder: String) {
|
|
|
|
|
|
|
|
self.folder = folder
|
|
|
|
self.diskCache = BinaryDiskCache(folder: folder)
|
|
|
|
self.queue = DispatchQueue(label: "ImageDownloader serial queue - \(folder)")
|
|
|
|
}
|
|
|
|
|
2017-11-26 05:24:38 +01:00
|
|
|
@discardableResult
|
2017-11-26 05:12:53 +01:00
|
|
|
func image(for url: String) -> NSImage? {
|
|
|
|
|
|
|
|
if let image = imageCache[url] {
|
|
|
|
return image
|
|
|
|
}
|
|
|
|
|
|
|
|
findImage(url)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private extension ImageDownloader {
|
|
|
|
|
|
|
|
func cacheImage(_ url: String, _ image: NSImage) {
|
|
|
|
|
|
|
|
imageCache[url] = image
|
|
|
|
postImageDidBecomeAvailableNotification(url)
|
|
|
|
}
|
|
|
|
|
|
|
|
func findImage(_ url: String) {
|
|
|
|
|
2017-11-26 05:24:38 +01:00
|
|
|
guard !urlsInProgress.contains(url) && !badURLs.contains(url) else {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
urlsInProgress.insert(url)
|
|
|
|
|
2017-11-26 05:12:53 +01:00
|
|
|
readFromDisk(url) { (image) in
|
|
|
|
|
|
|
|
if let image = image {
|
|
|
|
self.cacheImage(url, image)
|
2017-11-26 05:24:38 +01:00
|
|
|
self.urlsInProgress.remove(url)
|
2017-11-26 05:12:53 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
self.downloadImage(url) { (image) in
|
|
|
|
|
|
|
|
if let image = image {
|
|
|
|
self.cacheImage(url, image)
|
|
|
|
}
|
2017-11-26 05:24:38 +01:00
|
|
|
self.urlsInProgress.remove(url)
|
2017-11-26 05:12:53 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func readFromDisk(_ url: String, _ callback: @escaping (NSImage?) -> Void) {
|
|
|
|
|
|
|
|
queue.async {
|
|
|
|
|
|
|
|
if let data = self.diskCache[self.diskKey(url)], !data.isEmpty {
|
|
|
|
NSImage.rs_image(with: data, imageResultBlock: callback)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
callback(nil)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func downloadImage(_ url: String, _ callback: @escaping (NSImage?) -> Void) {
|
|
|
|
|
2017-11-26 05:24:38 +01:00
|
|
|
guard let imageURL = URL(string: url) else {
|
2017-11-26 05:12:53 +01:00
|
|
|
callback(nil)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-11-26 05:24:38 +01:00
|
|
|
downloadUsingCache(imageURL) { (data, response, error) in
|
2017-11-26 05:12:53 +01:00
|
|
|
|
|
|
|
if let data = data, !data.isEmpty, let response = response, response.statusIsOK, error == nil {
|
2017-11-26 05:24:38 +01:00
|
|
|
self.saveToDisk(url, data)
|
2017-11-26 05:12:53 +01:00
|
|
|
NSImage.rs_image(with: data, imageResultBlock: callback)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-11-26 05:24:38 +01:00
|
|
|
if let response = response as? HTTPURLResponse, response.statusCode >= HTTPResponseCode.badRequest && response.statusCode <= HTTPResponseCode.notAcceptable {
|
|
|
|
self.badURLs.insert(url)
|
|
|
|
}
|
2017-11-26 05:12:53 +01:00
|
|
|
if let error = error {
|
|
|
|
appDelegate.logMessage("Error downloading image at \(url): \(error)", type: .warning)
|
|
|
|
}
|
|
|
|
|
|
|
|
callback(nil)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func saveToDisk(_ url: String, _ data: Data) {
|
|
|
|
|
|
|
|
queue.async {
|
|
|
|
self.diskCache[self.diskKey(url)] = data
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func diskKey(_ url: String) -> String {
|
|
|
|
|
|
|
|
return (url as NSString).rs_md5Hash()
|
|
|
|
}
|
|
|
|
|
|
|
|
func postImageDidBecomeAvailableNotification(_ url: String) {
|
|
|
|
|
2018-01-09 06:34:39 +01:00
|
|
|
DispatchQueue.main.async {
|
|
|
|
NotificationCenter.default.post(name: .ImageDidBecomeAvailable, object: self, userInfo: [UserInfoKey.url: url])
|
|
|
|
}
|
2017-11-26 05:12:53 +01:00
|
|
|
}
|
|
|
|
}
|