NetNewsWire/Frameworks/Account/LocalAccount/LocalAccountRefresher.swift

192 lines
5.6 KiB
Swift
Raw Normal View History

//
// LocalAccountRefresher.swift
// NetNewsWire
//
// Created by Brent Simmons on 9/6/16.
// Copyright © 2016 Ranchero Software, LLC. All rights reserved.
//
import Foundation
import RSCore
import RSParser
import RSWeb
import Articles
import ArticlesDatabase
protocol LocalAccountRefresherDelegate {
func localAccountRefresher(_ refresher: LocalAccountRefresher, requestCompletedFor: WebFeed)
func localAccountRefresherDidFinish(_ refresher: LocalAccountRefresher)
}
final class LocalAccountRefresher {
2020-04-26 03:20:56 +02:00
var newArticles = Set<Article>()
var updatedArticles = Set<Article>()
2020-04-26 03:20:56 +02:00
var deletedArticles = Set<Article>()
private var completion: ((Set<Article>, Set<Article>, Set<Article>) -> Void)?
private var isSuspended = false
var delegate: LocalAccountRefresherDelegate?
private lazy var downloadSession: DownloadSession = {
return DownloadSession(delegate: self)
}()
public func refreshFeeds(_ feeds: Set<WebFeed>, completion: ((Set<Article>, Set<Article>, Set<Article>) -> Void)? = nil) {
guard !feeds.isEmpty else {
completion?(Set<Article>(), Set<Article>(), Set<Article>())
return
}
2020-04-26 03:20:56 +02:00
self.completion = completion
downloadSession.downloadObjects(feeds as NSSet)
}
public func suspend() {
downloadSession.cancelAll()
isSuspended = true
}
public func resume() {
isSuspended = false
}
}
// MARK: - DownloadSessionDelegate
extension LocalAccountRefresher: DownloadSessionDelegate {
func downloadSession(_ downloadSession: DownloadSession, requestForRepresentedObject representedObject: AnyObject) -> URLRequest? {
guard let feed = representedObject as? WebFeed else {
return nil
}
guard let url = URL(string: feed.url) else {
return nil
}
2020-01-28 05:58:59 +01:00
var request = URLRequest(url: url)
2017-09-27 06:43:40 +02:00
if let conditionalGetInfo = feed.conditionalGetInfo {
2020-01-28 05:58:59 +01:00
conditionalGetInfo.addRequestHeadersToURLRequest(&request)
}
2020-01-28 05:58:59 +01:00
return request
}
func downloadSession(_ downloadSession: DownloadSession, downloadDidCompleteForRepresentedObject representedObject: AnyObject, response: URLResponse?, data: Data, error: NSError?, completion: @escaping () -> Void) {
let feed = representedObject as! WebFeed
guard !data.isEmpty, !isSuspended else {
completion()
delegate?.localAccountRefresher(self, requestCompletedFor: feed)
return
}
if let error = error {
print("Error downloading \(feed.url) - \(error)")
completion()
delegate?.localAccountRefresher(self, requestCompletedFor: feed)
return
}
2020-01-18 08:00:56 +01:00
let dataHash = data.md5String
if dataHash == feed.contentHash {
completion()
delegate?.localAccountRefresher(self, requestCompletedFor: feed)
return
}
let parserData = ParserData(url: feed.url, data: data)
FeedParser.parse(parserData) { (parsedFeed, error) in
2017-09-17 21:08:50 +02:00
guard let account = feed.account, let parsedFeed = parsedFeed, error == nil else {
completion()
self.delegate?.localAccountRefresher(self, requestCompletedFor: feed)
return
}
account.update(feed, with: parsedFeed) { result in
if case .success(let articleChanges) = result {
2020-04-26 03:20:56 +02:00
self.newArticles.formUnion(articleChanges.newArticles ?? Set<Article>())
self.updatedArticles.formUnion(articleChanges.updatedArticles ?? Set<Article>())
2020-04-26 03:20:56 +02:00
self.deletedArticles.formUnion(articleChanges.deletedArticles ?? Set<Article>())
if let httpResponse = response as? HTTPURLResponse {
feed.conditionalGetInfo = HTTPConditionalGetInfo(urlResponse: httpResponse)
}
2020-04-26 03:20:56 +02:00
feed.contentHash = dataHash
completion()
self.delegate?.localAccountRefresher(self, requestCompletedFor: feed)
} else {
completion()
self.delegate?.localAccountRefresher(self, requestCompletedFor: feed)
}
}
}
}
func downloadSession(_ downloadSession: DownloadSession, shouldContinueAfterReceivingData data: Data, representedObject: AnyObject) -> Bool {
let feed = representedObject as! WebFeed
guard !isSuspended else {
delegate?.localAccountRefresher(self, requestCompletedFor: feed)
return false
}
if data.isEmpty {
return true
}
if data.isDefinitelyNotFeed() {
delegate?.localAccountRefresher(self, requestCompletedFor: feed)
return false
}
if data.count > 4096 {
let parserData = ParserData(url: feed.url, data: data)
2020-04-02 19:25:23 +02:00
if FeedParser.mightBeAbleToParseBasedOnPartialData(parserData) {
return true
} else {
delegate?.localAccountRefresher(self, requestCompletedFor: feed)
2020-04-02 19:25:23 +02:00
return false
}
}
return true
}
func downloadSession(_ downloadSession: DownloadSession, didReceiveUnexpectedResponse response: URLResponse, representedObject: AnyObject) {
2020-04-02 19:25:23 +02:00
let feed = representedObject as! WebFeed
delegate?.localAccountRefresher(self, requestCompletedFor: feed)
2019-04-25 14:13:14 +02:00
}
2019-04-25 14:13:14 +02:00
func downloadSession(_ downloadSession: DownloadSession, didReceiveNotModifiedResponse: URLResponse, representedObject: AnyObject) {
let feed = representedObject as! WebFeed
delegate?.localAccountRefresher(self, requestCompletedFor: feed)
2019-04-25 14:13:14 +02:00
}
func downloadSession(_ downloadSession: DownloadSession, didDiscardDuplicateRepresentedObject representedObject: AnyObject) {
let feed = representedObject as! WebFeed
delegate?.localAccountRefresher(self, requestCompletedFor: feed)
}
func downloadSessionDidCompleteDownloadObjects(_ downloadSession: DownloadSession) {
completion?(newArticles, updatedArticles, deletedArticles)
2020-04-26 03:20:56 +02:00
completion = nil
newArticles = Set<Article>()
deletedArticles = Set<Article>()
delegate?.localAccountRefresherDidFinish(self)
}
}
// MARK: - Utility
private extension Data {
func isDefinitelyNotFeed() -> Bool {
// We only detect a few image types for now. This should get fleshed-out at some later date.
2020-01-18 08:00:56 +01:00
return self.isImage
}
}