Add a cache to HTMLMetadataDownloader; do all parsing off the main thread, for better UI performance. Also: make FaviconURLFinder use HTMLMetadataDownloader, as it should have in the first place.

This commit is contained in:
Brent Simmons 2017-12-18 10:20:28 -08:00
parent e091f1c609
commit 81d8532e2c
2 changed files with 30 additions and 49 deletions

View File

@ -8,64 +8,20 @@
import Foundation import Foundation
import RSParser import RSParser
import RSWeb
import RSCore
// The favicon URL may be specified in the head section of the home page. // The favicon URL may be specified in the head section of the home page.
struct FaviconURLFinder { struct FaviconURLFinder {
static var metadataCache = [String: RSHTMLMetadata]()
static let serialDispatchQueue = DispatchQueue(label: "FaviconURLFinder")
static func findFaviconURL(_ homePageURL: String, _ callback: @escaping (String?) -> Void) { static func findFaviconURL(_ homePageURL: String, _ callback: @escaping (String?) -> Void) {
guard let url = URL(string: homePageURL) else { guard let _ = URL(string: homePageURL) else {
callback(nil) callback(nil)
return return
} }
downloadUsingCache(url) { (data, response, error) in HTMLMetadataDownloader.downloadMetadata(for: homePageURL) { (htmlMetadata) in
callback(htmlMetadata?.faviconLink)
guard let data = data, let response = response, response.statusIsOK else {
callback(nil)
return
}
// Use the absoluteString of the responses URL instead of the homePageURL,
// since the homePageURL might actually have been redirected.
// Example: Dr. Drangs feed reports the homePageURL as http://www.leancrew.com/all-this
// but it gets redirected to http://www.leancrew.com/all-this/ which is correct.
// This way any relative link to a favicon in the pages metadata
// will be made absolute correctly.
let urlToUse = response.url?.absoluteString ?? homePageURL
faviconURL(urlToUse, data, callback)
}
}
static private func faviconURL(_ url: String, _ webPageData: Data, _ callback: @escaping (String?) -> Void) {
serialDispatchQueue.async {
let md5String = (webPageData as NSData).rs_md5HashString()
if let md5String = md5String, let cachedMetadata = metadataCache[md5String] {
let cachedURL = cachedMetadata.faviconLink
DispatchQueue.main.async {
callback(cachedURL)
}
return
}
let parserData = ParserData(url: url, data: webPageData)
let htmlMetadata = RSHTMLMetadataParser.htmlMetadata(with: parserData)
if let md5String = md5String {
metadataCache[md5String] = htmlMetadata
}
let url = htmlMetadata.faviconLink
DispatchQueue.main.async {
callback(url)
}
} }
} }
} }

View File

@ -12,6 +12,9 @@ import RSParser
struct HTMLMetadataDownloader { struct HTMLMetadataDownloader {
static var metadataCache = [String: RSHTMLMetadata]()
static let serialDispatchQueue = DispatchQueue(label: "HTMLMetadataDownloader")
static func downloadMetadata(for url: String, _ callback: @escaping (RSHTMLMetadata?) -> Void) { static func downloadMetadata(for url: String, _ callback: @escaping (RSHTMLMetadata?) -> Void) {
guard let actualURL = URL(string: url) else { guard let actualURL = URL(string: url) else {
@ -25,8 +28,7 @@ struct HTMLMetadataDownloader {
let urlToUse = response.url ?? actualURL let urlToUse = response.url ?? actualURL
let parserData = ParserData(url: urlToUse.absoluteString, data: data) let parserData = ParserData(url: urlToUse.absoluteString, data: data)
let metadata = RSHTMLMetadataParser.htmlMetadata(with: parserData) parseMetadata(with: parserData, callback)
callback(metadata)
return return
} }
@ -37,4 +39,27 @@ struct HTMLMetadataDownloader {
callback(nil) callback(nil)
} }
} }
private static func parseMetadata(with parserData: ParserData, _ callback: @escaping (RSHTMLMetadata?) -> Void) {
serialDispatchQueue.async {
let md5String = (parserData.data as NSData).rs_md5HashString()
if let md5String = md5String, let cachedMetadata = metadataCache[md5String] {
DispatchQueue.main.async {
callback(cachedMetadata)
}
return
}
let htmlMetadata = RSHTMLMetadataParser.htmlMetadata(with: parserData)
if let md5String = md5String {
metadataCache[md5String] = htmlMetadata
}
DispatchQueue.main.async {
callback(htmlMetadata)
}
}
}
} }