Update LocalAccountDelegate to use new FeedDownloader package. Temporarily comment-out feed refreshing in CloudKitAccountDelegate.

This commit is contained in:
Brent Simmons 2024-05-25 22:48:50 -07:00
parent ea3a78b841
commit 33215ba9e3
4 changed files with 129 additions and 58 deletions

View File

@ -27,7 +27,8 @@ let package = Package(
.package(path: "../LocalAccount"),
.package(path: "../FeedFinder"),
.package(path: "../Feedly"),
.package(path: "../CommonErrors")
.package(path: "../CommonErrors"),
.package(path: "../FeedDownloader")
],
targets: [
.target(
@ -49,7 +50,8 @@ let package = Package(
"LocalAccount",
"FeedFinder",
"CommonErrors",
"Feedly"
"Feedly",
"FeedDownloader"
],
swiftSettings: [
.enableExperimentalFeature("StrictConcurrency")

View File

@ -180,6 +180,14 @@ public enum FetchType {
return _externalIDToFeedDictionary
}
private var _feedURLToFeedDictionary = [String: Feed]()
var feedURLToFeedDictionary: [String: Feed] {
if feedDictionariesNeedUpdate {
rebuildFeedDictionaries()
}
return _feedURLToFeedDictionary
}
var flattenedFeedURLs: Set<String> {
return Set(flattenedFeeds().map({ $0.url }))
}
@ -1148,9 +1156,11 @@ private extension Account {
func rebuildFeedDictionaries() {
var idDictionary = [String: Feed]()
var externalIDDictionary = [String: Feed]()
var urlDictionary = [String: Feed]()
for feed in flattenedFeeds() {
idDictionary[feed.feedID] = feed
urlDictionary[feed.url] = feed
if let externalID = feed.externalID {
externalIDDictionary[externalID] = feed
}
@ -1158,6 +1168,7 @@ private extension Account {
_idToFeedDictionary = idDictionary
_externalIDToFeedDictionary = externalIDDictionary
_feedURLToFeedDictionary = urlDictionary
feedDictionariesNeedUpdate = false
}
@ -1319,6 +1330,10 @@ extension Account {
public func existingFeed(withExternalID externalID: String) -> Feed? {
return externalIDToFeedDictionary[externalID]
}
public func existingFeed(urlString: String) -> Feed? {
return feedURLToFeedDictionary[urlString]
}
}
// MARK: - OPMLRepresentable

View File

@ -48,11 +48,11 @@ enum CloudKitAccountDelegateError: LocalizedError {
private let mainThreadOperationQueue = MainThreadOperationQueue()
private lazy var refresher: LocalAccountRefresher = {
let refresher = LocalAccountRefresher()
refresher.delegate = self
return refresher
}()
// private lazy var refresher: LocalAccountRefresher = {
// let refresher = LocalAccountRefresher()
// refresher.delegate = self
// return refresher
// }()
weak var account: Account?
@ -459,7 +459,7 @@ enum CloudKitAccountDelegateError: LocalizedError {
func suspendNetwork() {
refresher.suspend()
// refresher.suspend()
}
func suspendDatabase() {
@ -471,7 +471,7 @@ enum CloudKitAccountDelegateError: LocalizedError {
func resume() {
refresher.resume()
// refresher.resume()
Task {
await database.resume()
@ -536,7 +536,7 @@ private extension CloudKitAccountDelegate {
func combinedRefresh(_ account: Account, _ feeds: Set<Feed>) async throws {
await refresher.refreshFeeds(feeds)
// await refresher.refreshFeeds(feeds)
}
func createRSSFeed(for account: Account, url: URL, editedName: String?, container: Container, validateFeed: Bool) async throws -> Feed {
@ -733,22 +733,22 @@ private extension CloudKitAccountDelegate {
}
}
extension CloudKitAccountDelegate: LocalAccountRefresherDelegate {
func localAccountRefresher(_ refresher: LocalAccountRefresher, requestCompletedFor: Feed) {
refreshProgress.completeTask()
}
func localAccountRefresher(_ refresher: LocalAccountRefresher, articleChanges: ArticleChanges, completion: @escaping () -> Void) {
Task { @MainActor in
await storeArticleChanges(new: articleChanges.newArticles,
updated: articleChanges.updatedArticles,
deleted: articleChanges.deletedArticles)
}
}
}
//extension CloudKitAccountDelegate: LocalAccountRefresherDelegate {
//
// func localAccountRefresher(_ refresher: LocalAccountRefresher, requestCompletedFor: Feed) {
//
// refreshProgress.completeTask()
// }
//
// func localAccountRefresher(_ refresher: LocalAccountRefresher, articleChanges: ArticleChanges, completion: @escaping () -> Void) {
//
// Task { @MainActor in
// await storeArticleChanges(new: articleChanges.newArticles,
// updated: articleChanges.updatedArticles,
// deleted: articleChanges.deletedArticles)
// }
// }
//}
extension CloudKitAccountDelegate: CloudKitFeedInfoDelegate {

View File

@ -18,6 +18,7 @@ import Core
import CommonErrors
import FeedFinder
import LocalAccount
import FeedDownloader
public enum LocalAccountDelegateError: String, Error {
case invalidParameter = "An invalid parameter was used."
@ -25,16 +26,10 @@ public enum LocalAccountDelegateError: String, Error {
final class LocalAccountDelegate: AccountDelegate {
private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "LocalAccount")
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "LocalAccount")
weak var account: Account?
private lazy var refresher: LocalAccountRefresher = {
let refresher = LocalAccountRefresher()
refresher.delegate = self
return refresher
}()
let behaviors: AccountBehaviors = []
let isOPMLImportInProgress = false
@ -42,8 +37,17 @@ final class LocalAccountDelegate: AccountDelegate {
var credentials: Credentials?
var accountMetadata: AccountMetadata?
let refreshProgress = DownloadProgress(numberOfTasks: 0)
var refreshProgress: DownloadProgress {
feedDownloader.downloadProgress
}
let feedDownloader: FeedDownloader
init() {
self.feedDownloader = FeedDownloader()
feedDownloader.delegate = self
}
func receiveRemoteNotification(for account: Account, userInfo: [AnyHashable : Any]) async {
}
@ -54,12 +58,10 @@ final class LocalAccountDelegate: AccountDelegate {
}
let feeds = account.flattenedFeeds()
refreshProgress.addToNumberOfTasksAndRemaining(feeds.count)
let feedURLStrings = feeds.map { $0.url }
let feedURLs = Set(feedURLStrings.compactMap { URL(string: $0) })
await refresher.refreshFeeds(feeds)
self.refreshProgress.clear()
account.metadata.lastArticleFetchEndTime = Date()
feedDownloader.downloadFeeds(feedURLs)
}
func syncArticleStatus(for account: Account) async throws {
@ -164,7 +166,9 @@ final class LocalAccountDelegate: AccountDelegate {
// MARK: Suspend and Resume (for iOS)
func suspendNetwork() {
refresher.suspend()
Task { @MainActor in
await feedDownloader.suspend()
}
}
func suspendDatabase() {
@ -172,23 +176,10 @@ final class LocalAccountDelegate: AccountDelegate {
}
func resume() {
refresher.resume()
feedDownloader.resume()
}
}
extension LocalAccountDelegate: LocalAccountRefresherDelegate {
func localAccountRefresher(_ refresher: LocalAccountRefresher, requestCompletedFor: Feed) {
refreshProgress.completeTask()
}
func localAccountRefresher(_ refresher: LocalAccountRefresher, articleChanges: ArticleChanges, completion: @escaping () -> Void) {
completion()
}
}
private extension LocalAccountDelegate {
func createRSSFeed(for account: Account, url: URL, editedName: String?, container: Container) async throws -> Feed {
@ -197,9 +188,7 @@ private extension LocalAccountDelegate {
// container before the name has been downloaded. This will put it in the sidebar
// with an Untitled name if we don't delay it being added to the sidebar.
BatchUpdate.shared.start()
refreshProgress.addToNumberOfTasksAndRemaining(1)
defer {
refreshProgress.completeTask()
BatchUpdate.shared.end()
}
@ -227,3 +216,68 @@ private extension LocalAccountDelegate {
return feed
}
}
extension LocalAccountDelegate: FeedDownloaderDelegate {
func feedDownloader(_: FeedDownloader, requestCompletedForFeedURL feedURL: URL, response: URLResponse?, data: Data?, error: Error?) {
guard let account, let feed = account.existingFeed(urlString: feedURL.absoluteString) else {
return
}
if let error {
logger.debug("Error downloading \(feed.url) - \(error)")
return
}
guard let response, let data, !data.isEmpty else {
return
}
parseAndUpdateFeed(feed, response: response, data: data)
}
func feedDownloader(_: FeedDownloader, requestCanceledForFeedURL feedURL: URL, response: URLResponse?, data: Data?, error: Error?, reason: FeedDownloader.CancellationReason) {
// nothing to do
}
func feedDownloaderSessionDidComplete(_: FeedDownloader) {
account?.metadata.lastArticleFetchEndTime = Date()
}
func feedDownloader(_: FeedDownloader, conditionalGetInfoFor feedURL: URL) -> HTTPConditionalGetInfo? {
guard let feed = account?.existingFeed(urlString: feedURL.absoluteString) else {
return nil
}
return feed.conditionalGetInfo
}
}
private extension LocalAccountDelegate {
func parseAndUpdateFeed(_ feed: Feed, response: URLResponse, data: Data) {
Task { @MainActor in
let dataHash = data.md5String
if dataHash == feed.contentHash {
return
}
let parserData = ParserData(url: feed.url, data: data)
guard let parsedFeed = try? await FeedParser.parse(parserData) else {
return
}
try await self.account?.update(feed: feed, with: parsedFeed)
if let httpResponse = response as? HTTPURLResponse {
feed.conditionalGetInfo = HTTPConditionalGetInfo(urlResponse: httpResponse)
}
feed.contentHash = dataHash
}
}
}