Add ImageDownloader.

This commit is contained in:
Brent Simmons 2017-11-25 20:12:53 -08:00
parent e026e159d1
commit 33fef5ea1c
3 changed files with 147 additions and 0 deletions

View File

@ -13,6 +13,7 @@
842E45E51ED8C6B7000A8B52 /* MainWindowSplitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842E45E41ED8C6B7000A8B52 /* MainWindowSplitView.swift */; };
842E45E71ED8C747000A8B52 /* DB5.plist in Resources */ = {isa = PBXBuildFile; fileRef = 842E45E61ED8C747000A8B52 /* DB5.plist */; };
84513F901FAA63950023A1A9 /* FeedListControlsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84513F8F1FAA63950023A1A9 /* FeedListControlsView.swift */; };
845213231FCA5B11003B6E93 /* ImageDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845213221FCA5B10003B6E93 /* ImageDownloader.swift */; };
845A29091FC74B8E007B49E3 /* SingleFaviconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845A29081FC74B8E007B49E3 /* SingleFaviconDownloader.swift */; };
845A291B1FC75AA6007B49E3 /* SeekingFavicon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845A291A1FC75AA6007B49E3 /* SeekingFavicon.swift */; };
845A29221FC9251E007B49E3 /* SidebarCellLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845A29211FC9251E007B49E3 /* SidebarCellLayout.swift */; };
@ -407,6 +408,7 @@
842E45E41ED8C6B7000A8B52 /* MainWindowSplitView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainWindowSplitView.swift; sourceTree = "<group>"; };
842E45E61ED8C747000A8B52 /* DB5.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = DB5.plist; path = Evergreen/Resources/DB5.plist; sourceTree = "<group>"; };
84513F8F1FAA63950023A1A9 /* FeedListControlsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedListControlsView.swift; sourceTree = "<group>"; };
845213221FCA5B10003B6E93 /* ImageDownloader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageDownloader.swift; sourceTree = "<group>"; };
845A29081FC74B8E007B49E3 /* SingleFaviconDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleFaviconDownloader.swift; sourceTree = "<group>"; };
845A291A1FC75AA6007B49E3 /* SeekingFavicon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeekingFavicon.swift; sourceTree = "<group>"; };
845A29211FC9251E007B49E3 /* SidebarCellLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarCellLayout.swift; sourceTree = "<group>"; };
@ -558,6 +560,15 @@
path = Evergreen/MainWindow;
sourceTree = "<group>";
};
845213211FCA5B10003B6E93 /* Images */ = {
isa = PBXGroup;
children = (
845213221FCA5B10003B6E93 /* ImageDownloader.swift */,
);
name = Images;
path = Evergreen/Images;
sourceTree = "<group>";
};
845A29251FC928C7007B49E3 /* Cell */ = {
isa = PBXGroup;
children = (
@ -785,6 +796,7 @@
84F2D5341FC22FCB00998D64 /* PseudoFeeds */,
849A97561ED9EB0D007D329B /* Data */,
848F6AE31FC29CFA002D422E /* Favicons */,
845213211FCA5B10003B6E93 /* Images */,
849A97961ED9EFAA007D329B /* Extensions */,
849A97991ED9EFB6007D329B /* Resources */,
84FB9A2C1EDCD6A4003D53B9 /* Frameworks */,
@ -1351,6 +1363,7 @@
849A97531ED9EAC0007D329B /* AddFeedController.swift in Sources */,
849A97831ED9EC63007D329B /* StatusBarView.swift in Sources */,
84F2D5381FC22FCC00998D64 /* TodayFeedDelegate.swift in Sources */,
845213231FCA5B11003B6E93 /* ImageDownloader.swift in Sources */,
849A97431ED9EAA9007D329B /* AddFolderWindowController.swift in Sources */,
849A97921ED9EF65007D329B /* IndeterminateProgressWindowController.swift in Sources */,
849A97801ED9EC42007D329B /* DetailViewController.swift in Sources */,

View File

@ -22,6 +22,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
var currentTheme: VSTheme!
var faviconDownloader: FaviconDownloader!
var imageDownloader: ImageDownloader!
var appName: String!
var pseudoFeeds = [PseudoFeed]()
@ -130,11 +131,17 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
let tempDirectory = NSTemporaryDirectory()
let cacheFolder = (tempDirectory as NSString).appendingPathComponent("com.ranchero.evergreen")
let faviconsFolder = (cacheFolder as NSString).appendingPathComponent("Favicons")
let faviconsFolderURL = URL(fileURLWithPath: faviconsFolder)
try! FileManager.default.createDirectory(at: faviconsFolderURL, withIntermediateDirectories: true, attributes: nil)
faviconDownloader = FaviconDownloader(folder: faviconsFolder)
let imagesFolder = (cacheFolder as NSString).appendingPathComponent("Images")
let imagesFolderURL = URL(fileURLWithPath: imagesFolder)
try! FileManager.default.createDirectory(at: imagesFolderURL, withIntermediateDirectories: true, attributes: nil)
imageDownloader = ImageDownloader(folder: imagesFolder)
let todayFeed = SmartFeed(delegate: TodayFeedDelegate())
let unreadFeed = UnreadFeed()
let starredFeed = SmartFeed(delegate: StarredFeedDelegate())

View File

@ -0,0 +1,127 @@
//
// 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 {
static let ImageDidBecomeAvailable = Notification.Name("ImageDidBecomeAvailableNotification") // ImageDownloader.UserInfoKey.imageURL
}
final class ImageDownloader {
private let folder: String
private var diskCache: BinaryDiskCache
private let queue: DispatchQueue
private var imageCache = [String: NSImage]() // url: image
struct UserInfoKey {
static let imageURL = "imageURL"
}
init(folder: String) {
self.folder = folder
self.diskCache = BinaryDiskCache(folder: folder)
self.queue = DispatchQueue(label: "ImageDownloader serial queue - \(folder)")
}
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) {
readFromDisk(url) { (image) in
if let image = image {
self.cacheImage(url, image)
return
}
self.downloadImage(url) { (image) in
if let image = image {
self.cacheImage(url, image)
}
}
}
}
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) {
guard let url = URL(string: url) 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(url.absoluteString, data)
NSImage.rs_image(with: data, imageResultBlock: callback)
return
}
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) {
NotificationCenter.default.post(name: .ImageDidBecomeAvailable, object: self, userInfo: [UserInfoKey.imageURL: url])
}
}