mirror of
https://github.com/Ranchero-Software/NetNewsWire
synced 2025-08-12 06:26:36 +00:00
Simplifying operations to improve their reusability and composability
This commit is contained in:
@@ -86,10 +86,9 @@
|
||||
9E1D154D233370D800F4944C /* FeedlySyncStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1D154C233370D800F4944C /* FeedlySyncStrategy.swift */; };
|
||||
9E1D154F233371DD00F4944C /* FeedlyGetCollectionsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1D154E233371DD00F4944C /* FeedlyGetCollectionsOperation.swift */; };
|
||||
9E1D15512334282100F4944C /* FeedlyMirrorCollectionsAsFoldersOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1D15502334282100F4944C /* FeedlyMirrorCollectionsAsFoldersOperation.swift */; };
|
||||
9E1D15532334304B00F4944C /* FeedlyGetCollectionStreamOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1D15522334304B00F4944C /* FeedlyGetCollectionStreamOperation.swift */; };
|
||||
9E1D15532334304B00F4944C /* FeedlyGetStreamOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1D15522334304B00F4944C /* FeedlyGetStreamOperation.swift */; };
|
||||
9E1D1555233431A600F4944C /* FeedlyOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1D1554233431A600F4944C /* FeedlyOperation.swift */; };
|
||||
9E1D15572334355900F4944C /* FeedlyRequestStreamsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1D15562334355900F4944C /* FeedlyRequestStreamsOperation.swift */; };
|
||||
9E1D155923343B2A00F4944C /* FeedlyGetStreamParsedItemsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1D155823343B2A00F4944C /* FeedlyGetStreamParsedItemsOperation.swift */; };
|
||||
9E1D155B2334423300F4944C /* FeedlyOrganiseParsedItemsByFeedOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1D155A2334423300F4944C /* FeedlyOrganiseParsedItemsByFeedOperation.swift */; };
|
||||
9E1D155D233447F000F4944C /* FeedlyUpdateAccountFeedsWithItemsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1D155C233447F000F4944C /* FeedlyUpdateAccountFeedsWithItemsOperation.swift */; };
|
||||
9E713653233AD63E00765C84 /* FeedlyRefreshStreamEntriesStatusOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E713652233AD63E00765C84 /* FeedlyRefreshStreamEntriesStatusOperation.swift */; };
|
||||
@@ -116,6 +115,7 @@
|
||||
9EC688EC232C583300A8D0A2 /* FeedlyAccountDelegate+OAuth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EC688EB232C583300A8D0A2 /* FeedlyAccountDelegate+OAuth.swift */; };
|
||||
9EC688EE232C58E800A8D0A2 /* OAuthAuthorizationCodeGranting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EC688ED232C58E800A8D0A2 /* OAuthAuthorizationCodeGranting.swift */; };
|
||||
9ECC9A85234DC16E009B5144 /* FeedlyAccountDelegateError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ECC9A84234DC16E009B5144 /* FeedlyAccountDelegateError.swift */; };
|
||||
9EF35F7A234E830E003AE2AE /* FeedlyCompoundOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF35F79234E830E003AE2AE /* FeedlyCompoundOperation.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -259,10 +259,9 @@
|
||||
9E1D154C233370D800F4944C /* FeedlySyncStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlySyncStrategy.swift; sourceTree = "<group>"; };
|
||||
9E1D154E233371DD00F4944C /* FeedlyGetCollectionsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyGetCollectionsOperation.swift; sourceTree = "<group>"; };
|
||||
9E1D15502334282100F4944C /* FeedlyMirrorCollectionsAsFoldersOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyMirrorCollectionsAsFoldersOperation.swift; sourceTree = "<group>"; };
|
||||
9E1D15522334304B00F4944C /* FeedlyGetCollectionStreamOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyGetCollectionStreamOperation.swift; sourceTree = "<group>"; };
|
||||
9E1D15522334304B00F4944C /* FeedlyGetStreamOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyGetStreamOperation.swift; sourceTree = "<group>"; };
|
||||
9E1D1554233431A600F4944C /* FeedlyOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyOperation.swift; sourceTree = "<group>"; };
|
||||
9E1D15562334355900F4944C /* FeedlyRequestStreamsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyRequestStreamsOperation.swift; sourceTree = "<group>"; };
|
||||
9E1D155823343B2A00F4944C /* FeedlyGetStreamParsedItemsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyGetStreamParsedItemsOperation.swift; sourceTree = "<group>"; };
|
||||
9E1D155A2334423300F4944C /* FeedlyOrganiseParsedItemsByFeedOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyOrganiseParsedItemsByFeedOperation.swift; sourceTree = "<group>"; };
|
||||
9E1D155C233447F000F4944C /* FeedlyUpdateAccountFeedsWithItemsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyUpdateAccountFeedsWithItemsOperation.swift; sourceTree = "<group>"; };
|
||||
9E713652233AD63E00765C84 /* FeedlyRefreshStreamEntriesStatusOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyRefreshStreamEntriesStatusOperation.swift; sourceTree = "<group>"; };
|
||||
@@ -289,6 +288,7 @@
|
||||
9EC688EB232C583300A8D0A2 /* FeedlyAccountDelegate+OAuth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FeedlyAccountDelegate+OAuth.swift"; sourceTree = "<group>"; };
|
||||
9EC688ED232C58E800A8D0A2 /* OAuthAuthorizationCodeGranting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuthAuthorizationCodeGranting.swift; sourceTree = "<group>"; };
|
||||
9ECC9A84234DC16E009B5144 /* FeedlyAccountDelegateError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyAccountDelegateError.swift; sourceTree = "<group>"; };
|
||||
9EF35F79234E830E003AE2AE /* FeedlyCompoundOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyCompoundOperation.swift; sourceTree = "<group>"; };
|
||||
D511EEB5202422BB00712EC3 /* Account_project_debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Account_project_debug.xcconfig; sourceTree = "<group>"; };
|
||||
D511EEB6202422BB00712EC3 /* Account_target.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Account_target.xcconfig; sourceTree = "<group>"; };
|
||||
D511EEB7202422BB00712EC3 /* Account_project_release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Account_project_release.xcconfig; sourceTree = "<group>"; };
|
||||
@@ -547,6 +547,7 @@
|
||||
9EC688ED232C58E800A8D0A2 /* OAuthAuthorizationCodeGranting.swift */,
|
||||
9EC688E9232B973C00A8D0A2 /* FeedlyAPICaller.swift */,
|
||||
9E1D1554233431A600F4944C /* FeedlyOperation.swift */,
|
||||
9EF35F79234E830E003AE2AE /* FeedlyCompoundOperation.swift */,
|
||||
9EBC31B6233987C1002A567B /* FeedlyArticleStatusCoordinator.swift */,
|
||||
9EBC31B32338AC2E002A567B /* Models */,
|
||||
9EBC31B22338AC0F002A567B /* Refresh */,
|
||||
@@ -562,8 +563,7 @@
|
||||
9E1D15502334282100F4944C /* FeedlyMirrorCollectionsAsFoldersOperation.swift */,
|
||||
9E12B01F2334696A00ADE5A0 /* FeedlyCreateFeedsForCollectionFoldersOperation.swift */,
|
||||
9E1D15562334355900F4944C /* FeedlyRequestStreamsOperation.swift */,
|
||||
9E1D15522334304B00F4944C /* FeedlyGetCollectionStreamOperation.swift */,
|
||||
9E1D155823343B2A00F4944C /* FeedlyGetStreamParsedItemsOperation.swift */,
|
||||
9E1D15522334304B00F4944C /* FeedlyGetStreamOperation.swift */,
|
||||
9E1D155A2334423300F4944C /* FeedlyOrganiseParsedItemsByFeedOperation.swift */,
|
||||
9E1D155C233447F000F4944C /* FeedlyUpdateAccountFeedsWithItemsOperation.swift */,
|
||||
9E713652233AD63E00765C84 /* FeedlyRefreshStreamEntriesStatusOperation.swift */,
|
||||
@@ -779,6 +779,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
84C8B3F41F89DE430053CCA6 /* DataExtensions.swift in Sources */,
|
||||
9EF35F7A234E830E003AE2AE /* FeedlyCompoundOperation.swift in Sources */,
|
||||
552032F9229D5D5A009559E0 /* ReaderAPISubscription.swift in Sources */,
|
||||
84C3654A1F899F3B001EC85C /* CombinedRefreshProgress.swift in Sources */,
|
||||
9EC688EE232C58E800A8D0A2 /* OAuthAuthorizationCodeGranting.swift in Sources */,
|
||||
@@ -789,9 +790,8 @@
|
||||
9ECC9A85234DC16E009B5144 /* FeedlyAccountDelegateError.swift in Sources */,
|
||||
9EA3133B231E368100268BA0 /* FeedlyAccountDelegate.swift in Sources */,
|
||||
51E5959B228C781500FCC42B /* FeedbinStarredEntry.swift in Sources */,
|
||||
9E1D155923343B2A00F4944C /* FeedlyGetStreamParsedItemsOperation.swift in Sources */,
|
||||
846E77451F6EF9B900A165E2 /* Container.swift in Sources */,
|
||||
9E1D15532334304B00F4944C /* FeedlyGetCollectionStreamOperation.swift in Sources */,
|
||||
9E1D15532334304B00F4944C /* FeedlyGetStreamOperation.swift in Sources */,
|
||||
9E12B0202334696A00ADE5A0 /* FeedlyCreateFeedsForCollectionFoldersOperation.swift in Sources */,
|
||||
552032FD229D5D5A009559E0 /* ReaderAPITagging.swift in Sources */,
|
||||
9EAEC62A23331EE70085D7C9 /* FeedlyOrigin.swift in Sources */,
|
||||
|
||||
@@ -41,13 +41,9 @@ final class FeedlyArticleStatusCoordinator {
|
||||
}
|
||||
|
||||
/// Ensures local articles have the same status as they do remotely.
|
||||
func refreshArticleStatus(for account: Account, stream: FeedlyStream, collection: FeedlyCollection, completion: @escaping (() -> Void)) {
|
||||
func refreshArticleStatus(for account: Account, entries: [FeedlyEntry], completion: @escaping (() -> Void)) {
|
||||
|
||||
let unreadArticleIds = Set(
|
||||
stream.items
|
||||
.filter { $0.unread }
|
||||
.map { $0.id }
|
||||
)
|
||||
let unreadArticleIds = Set(entries.filter { $0.unread }.map { $0.id })
|
||||
|
||||
// Mark articles as unread
|
||||
let currentUnreadArticleIDs = account.fetchUnreadArticleIDs()
|
||||
@@ -55,17 +51,13 @@ final class FeedlyArticleStatusCoordinator {
|
||||
let markUnreadArticles = account.fetchArticles(.articleIDs(deltaUnreadArticleIDs))
|
||||
account.update(markUnreadArticles, statusKey: .read, flag: false)
|
||||
|
||||
let readAritcleIds = Set(
|
||||
stream.items
|
||||
.filter { !$0.unread }
|
||||
.map { $0.id }
|
||||
)
|
||||
let readAritcleIds = Set(entries.filter { !$0.unread }.map { $0.id })
|
||||
|
||||
let deltaReadArticleIDs = currentUnreadArticleIDs.intersection(readAritcleIds)
|
||||
let markReadArticles = account.fetchArticles(.articleIDs(deltaReadArticleIDs))
|
||||
account.update(markReadArticles, statusKey: .read, flag: true)
|
||||
|
||||
os_log(.debug, log: log, "\"%@\" - updated %i UNREAD and %i read article(s).", collection.label, unreadArticleIds.count, markReadArticles.count)
|
||||
// os_log(.debug, log: log, "\"%@\" - updated %i UNREAD and %i read article(s).", collection.label, unreadArticleIds.count, markReadArticles.count)
|
||||
|
||||
completion()
|
||||
|
||||
|
||||
36
Frameworks/Account/Feedly/FeedlyCompoundOperation.swift
Normal file
36
Frameworks/Account/Feedly/FeedlyCompoundOperation.swift
Normal file
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// FeedlyCompoundOperation.swift
|
||||
// Account
|
||||
//
|
||||
// Created by Kiel Gillard on 10/10/19.
|
||||
// Copyright © 2019 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
final class FeedlyCompoundOperation: FeedlyOperation {
|
||||
private let operationQueue = OperationQueue()
|
||||
private let operations: [Operation]
|
||||
|
||||
init(operations: [Operation]) {
|
||||
assert(!operations.isEmpty)
|
||||
self.operations = operations
|
||||
}
|
||||
|
||||
convenience init(operationsBlock: () -> ([Operation])) {
|
||||
let operations = operationsBlock()
|
||||
self.init(operations: operations)
|
||||
}
|
||||
|
||||
override func main() {
|
||||
let finishOperation = BlockOperation { [weak self] in
|
||||
self?.didFinish()
|
||||
}
|
||||
|
||||
for operation in operations {
|
||||
finishOperation.addDependency(operation)
|
||||
}
|
||||
|
||||
operationQueue.addOperations(operations, waitUntilFinished: false)
|
||||
}
|
||||
}
|
||||
@@ -49,3 +49,13 @@ struct FeedlyCategoryResourceId: FeedlyResourceId {
|
||||
return FeedlyCategoryResourceId(id: id)
|
||||
}
|
||||
}
|
||||
|
||||
struct FeedlyTagResourceId: FeedlyResourceId {
|
||||
var id: String
|
||||
|
||||
static func saved(for userId: String) -> FeedlyTagResourceId {
|
||||
// https://developer.feedly.com/cloud/#global-resource-ids
|
||||
let id = "user/\(userId)/tag/global.saved"
|
||||
return FeedlyTagResourceId(id: id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,11 +13,11 @@ import os.log
|
||||
final class FeedlyCreateFeedsForCollectionFoldersOperation: FeedlyOperation {
|
||||
|
||||
let account: Account
|
||||
let collectionsAndFoldersProvider: FeedlyCollectionsAndFoldersProviding
|
||||
let feedsAndFoldersProvider: FeedlyFeedsAndFoldersProviding
|
||||
let log: OSLog
|
||||
|
||||
init(account: Account, collectionsAndFoldersProvider: FeedlyCollectionsAndFoldersProviding, log: OSLog) {
|
||||
self.collectionsAndFoldersProvider = collectionsAndFoldersProvider
|
||||
init(account: Account, feedsAndFoldersProvider: FeedlyFeedsAndFoldersProviding, log: OSLog) {
|
||||
self.feedsAndFoldersProvider = feedsAndFoldersProvider
|
||||
self.account = account
|
||||
self.log = log
|
||||
}
|
||||
@@ -29,23 +29,22 @@ final class FeedlyCreateFeedsForCollectionFoldersOperation: FeedlyOperation {
|
||||
|
||||
var localFeeds = account.flattenedFeeds()
|
||||
let feedsBefore = localFeeds
|
||||
let pairs = collectionsAndFoldersProvider.collectionsAndFolders
|
||||
let pairs = feedsAndFoldersProvider.feedsAndFolders
|
||||
|
||||
// Remove feeds in a folder which are not in the corresponding collection.
|
||||
for (collection, folder) in pairs {
|
||||
for (collectionFeeds, folder) in pairs {
|
||||
let feedsInFolder = folder.topLevelFeeds
|
||||
let feedsInCollection = Set(collection.feeds.map { $0.id })
|
||||
let feedsInCollection = Set(collectionFeeds.map { $0.id })
|
||||
let feedsToRemove = feedsInFolder.filter { !feedsInCollection.contains($0.feedID) }
|
||||
if !feedsToRemove.isEmpty {
|
||||
folder.removeFeeds(feedsToRemove)
|
||||
os_log(.debug, log: log, "\"%@\" - removed: %@", collection.label, feedsToRemove.map { $0.feedID }, feedsInCollection)
|
||||
// os_log(.debug, log: log, "\"%@\" - removed: %@", collection.label, feedsToRemove.map { $0.feedID }, feedsInCollection)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Pair each Feed with its Folder.
|
||||
let feedsAndFolders = pairs
|
||||
.compactMap { ($0.0.feeds, $0.1) }
|
||||
.map({ (collectionFeeds, folder) -> [(FeedlyFeed, Folder)] in
|
||||
return collectionFeeds.map { feed -> (FeedlyFeed, Folder) in
|
||||
return (feed, folder) // pairs a folder for every feed in parallel
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
//
|
||||
// FeedlyGetCollectionStreamOperation.swift
|
||||
// Account
|
||||
//
|
||||
// Created by Kiel Gillard on 20/9/19.
|
||||
// Copyright © 2019 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol FeedlyCollectionStreamProviding: class {
|
||||
var collection: FeedlyCollection { get }
|
||||
var stream: FeedlyStream { get }
|
||||
}
|
||||
|
||||
/// Single responsibility is to get the stream content of a Collection from Feedly.
|
||||
final class FeedlyGetCollectionStreamOperation: FeedlyOperation, FeedlyCollectionStreamProviding {
|
||||
|
||||
private(set) var collection: FeedlyCollection
|
||||
|
||||
var stream: FeedlyStream {
|
||||
guard let stream = storedStream else {
|
||||
// TODO: this is probably more error prone than it seems!
|
||||
fatalError("\(type(of: self)) has been told to finish too early or a dependency is ignoring cancellation.")
|
||||
}
|
||||
return stream
|
||||
}
|
||||
|
||||
private var storedStream: FeedlyStream?
|
||||
|
||||
let account: Account
|
||||
let caller: FeedlyAPICaller
|
||||
let unreadOnly: Bool?
|
||||
let newerThan: Date?
|
||||
|
||||
init(account: Account, collection: FeedlyCollection, caller: FeedlyAPICaller, newerThan: Date?, unreadOnly: Bool? = nil) {
|
||||
self.account = account
|
||||
self.collection = collection
|
||||
self.caller = caller
|
||||
self.unreadOnly = unreadOnly
|
||||
self.newerThan = newerThan
|
||||
}
|
||||
|
||||
override func main() {
|
||||
guard !isCancelled else {
|
||||
didFinish()
|
||||
return
|
||||
}
|
||||
|
||||
caller.getStream(for: collection, newerThan: newerThan, unreadOnly: unreadOnly) { result in
|
||||
switch result {
|
||||
case .success(let stream):
|
||||
self.storedStream = stream
|
||||
self.didFinish()
|
||||
case .failure(let error):
|
||||
self.didFinish(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
//
|
||||
// FeedlyGetStreamOperation.swift
|
||||
// Account
|
||||
//
|
||||
// Created by Kiel Gillard on 20/9/19.
|
||||
// Copyright © 2019 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import RSParser
|
||||
|
||||
protocol FeedlyEntryProviding: class {
|
||||
var resource: FeedlyResourceId { get }
|
||||
var entries: [FeedlyEntry] { get }
|
||||
var parsedEntries: Set<ParsedItem> { get }
|
||||
}
|
||||
|
||||
/// Single responsibility is to get the stream content of a Collection from Feedly.
|
||||
final class FeedlyGetStreamOperation: FeedlyOperation, FeedlyEntryProviding {
|
||||
|
||||
private(set) var resource: FeedlyResourceId
|
||||
|
||||
var entries: [FeedlyEntry] {
|
||||
guard let entries = storedStream?.items else {
|
||||
assertionFailure("Has a prior operation finished too early? Is the operation included in \(self.dependencies)?")
|
||||
return []
|
||||
}
|
||||
return entries
|
||||
}
|
||||
|
||||
var parsedEntries: Set<ParsedItem> {
|
||||
if let entries = storedParsedEntries {
|
||||
return entries
|
||||
}
|
||||
|
||||
let parsed = Set(entries.map { FeedlyEntryParser(entry: $0).parsedItemRepresentation })
|
||||
storedParsedEntries = parsed
|
||||
|
||||
return parsed
|
||||
}
|
||||
|
||||
private var storedStream: FeedlyStream? {
|
||||
didSet {
|
||||
storedParsedEntries = nil
|
||||
}
|
||||
}
|
||||
|
||||
private var storedParsedEntries: Set<ParsedItem>?
|
||||
|
||||
|
||||
let account: Account
|
||||
let caller: FeedlyAPICaller
|
||||
let unreadOnly: Bool?
|
||||
let newerThan: Date?
|
||||
|
||||
init(account: Account, resource: FeedlyResourceId, caller: FeedlyAPICaller, newerThan: Date?, unreadOnly: Bool? = nil) {
|
||||
self.account = account
|
||||
self.resource = resource
|
||||
self.caller = caller
|
||||
self.unreadOnly = unreadOnly
|
||||
self.newerThan = newerThan
|
||||
}
|
||||
|
||||
override func main() {
|
||||
guard !isCancelled else {
|
||||
didFinish()
|
||||
return
|
||||
}
|
||||
|
||||
caller.getStream(for: resource, newerThan: newerThan, unreadOnly: unreadOnly) { result in
|
||||
switch result {
|
||||
case .success(let stream):
|
||||
self.storedStream = stream
|
||||
self.didFinish()
|
||||
case .failure(let error):
|
||||
self.didFinish(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
//
|
||||
// FeedlyGetStreamParsedItemsOperation.swift
|
||||
// Account
|
||||
//
|
||||
// Created by Kiel Gillard on 20/9/19.
|
||||
// Copyright © 2019 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import RSParser
|
||||
import os.log
|
||||
|
||||
protocol FeedlyStreamParsedItemsProviding: class {
|
||||
var collection: FeedlyCollection { get }
|
||||
var stream: FeedlyStream { get }
|
||||
var parsedItems: [ParsedItem] { get }
|
||||
}
|
||||
|
||||
/// Single responsibility is to model articles as ParsedItems for entries in a Collection's stream from Feedly.
|
||||
final class FeedlyGetStreamParsedItemsOperation: FeedlyOperation, FeedlyStreamParsedItemsProviding {
|
||||
private let account: Account
|
||||
private let caller: FeedlyAPICaller
|
||||
private let collectionStreamProvider: FeedlyCollectionStreamProviding
|
||||
private let log: OSLog
|
||||
|
||||
var collection: FeedlyCollection {
|
||||
return collectionStreamProvider.collection
|
||||
}
|
||||
|
||||
var stream: FeedlyStream {
|
||||
return collectionStreamProvider.stream
|
||||
}
|
||||
|
||||
private(set) var parsedItems = [ParsedItem]()
|
||||
|
||||
init(account: Account, collectionStreamProvider: FeedlyCollectionStreamProviding, caller: FeedlyAPICaller, log: OSLog) {
|
||||
self.account = account
|
||||
self.caller = caller
|
||||
self.collectionStreamProvider = collectionStreamProvider
|
||||
self.log = log
|
||||
}
|
||||
|
||||
override func main() {
|
||||
defer { didFinish() }
|
||||
|
||||
guard !isCancelled else { return }
|
||||
|
||||
parsedItems = stream.items.map { FeedlyEntryParser(entry: $0).parsedItemRepresentation }
|
||||
|
||||
os_log(.debug, log: log, "Parsed %i items of %i entries for %@", parsedItems.count, stream.items.count, collection.label)
|
||||
}
|
||||
}
|
||||
@@ -13,8 +13,12 @@ protocol FeedlyCollectionsAndFoldersProviding: class {
|
||||
var collectionsAndFolders: [(FeedlyCollection, Folder)] { get }
|
||||
}
|
||||
|
||||
protocol FeedlyFeedsAndFoldersProviding {
|
||||
var feedsAndFolders: [([FeedlyFeed], Folder)] { get }
|
||||
}
|
||||
|
||||
/// Single responsibility is accurately reflect Collections from Feedly as Folders.
|
||||
final class FeedlyMirrorCollectionsAsFoldersOperation: FeedlyOperation, FeedlyCollectionsAndFoldersProviding {
|
||||
final class FeedlyMirrorCollectionsAsFoldersOperation: FeedlyOperation, FeedlyCollectionsAndFoldersProviding, FeedlyFeedsAndFoldersProviding {
|
||||
|
||||
let caller: FeedlyAPICaller
|
||||
let account: Account
|
||||
@@ -22,6 +26,7 @@ final class FeedlyMirrorCollectionsAsFoldersOperation: FeedlyOperation, FeedlyCo
|
||||
let log: OSLog
|
||||
|
||||
private(set) var collectionsAndFolders = [(FeedlyCollection, Folder)]()
|
||||
private(set) var feedsAndFolders = [([FeedlyFeed], Folder)]()
|
||||
|
||||
init(account: Account, collectionsProvider: FeedlyCollectionProviding, caller: FeedlyAPICaller, log: OSLog) {
|
||||
self.collectionsProvider = collectionsProvider
|
||||
@@ -50,6 +55,10 @@ final class FeedlyMirrorCollectionsAsFoldersOperation: FeedlyOperation, FeedlyCo
|
||||
collectionsAndFolders = pairs
|
||||
os_log(.debug, log: log, "Ensured %i folders for %i collections.", pairs.count, collections.count)
|
||||
|
||||
feedsAndFolders = pairs.map { (collection, folder) -> (([FeedlyFeed], Folder)) in
|
||||
return (collection.feeds, folder)
|
||||
}
|
||||
|
||||
// Remove folders without a corresponding collection
|
||||
let collectionFolders = Set(pairs.map { $0.1 })
|
||||
let foldersWithoutCollections = localFolders.subtracting(collectionFolders)
|
||||
|
||||
@@ -11,8 +11,6 @@ import RSParser
|
||||
import os.log
|
||||
|
||||
protocol FeedlyParsedItemsByFeedProviding {
|
||||
var collection: FeedlyCollection { get }
|
||||
var stream: FeedlyStream { get }
|
||||
var allFeeds: Set<Feed> { get }
|
||||
func parsedItems(for feed: Feed) -> Set<ParsedItem>?
|
||||
}
|
||||
@@ -20,7 +18,7 @@ protocol FeedlyParsedItemsByFeedProviding {
|
||||
/// Single responsibility is to group articles by their feeds.
|
||||
final class FeedlyOrganiseParsedItemsByFeedOperation: FeedlyOperation, FeedlyParsedItemsByFeedProviding {
|
||||
private let account: Account
|
||||
private let parsedItemsProvider: FeedlyStreamParsedItemsProviding
|
||||
private let entryProvider: FeedlyEntryProviding
|
||||
private let log: OSLog
|
||||
|
||||
var allFeeds: Set<Feed> {
|
||||
@@ -32,19 +30,11 @@ final class FeedlyOrganiseParsedItemsByFeedOperation: FeedlyOperation, FeedlyPar
|
||||
return itemsKeyedByFeedId[feed.feedID]
|
||||
}
|
||||
|
||||
var collection: FeedlyCollection {
|
||||
return parsedItemsProvider.collection
|
||||
}
|
||||
|
||||
var stream: FeedlyStream {
|
||||
return parsedItemsProvider.stream
|
||||
}
|
||||
|
||||
private var itemsKeyedByFeedId = [String: Set<ParsedItem>]()
|
||||
|
||||
init(account: Account, parsedItemsProvider: FeedlyStreamParsedItemsProviding, log: OSLog) {
|
||||
init(account: Account, entryProvider: FeedlyEntryProviding, log: OSLog) {
|
||||
self.account = account
|
||||
self.parsedItemsProvider = parsedItemsProvider
|
||||
self.entryProvider = entryProvider
|
||||
self.log = log
|
||||
}
|
||||
|
||||
@@ -53,7 +43,7 @@ final class FeedlyOrganiseParsedItemsByFeedOperation: FeedlyOperation, FeedlyPar
|
||||
|
||||
guard !isCancelled else { return }
|
||||
|
||||
let items = parsedItemsProvider.parsedItems
|
||||
let items = entryProvider.parsedEntries
|
||||
var dict = [String: Set<ParsedItem>](minimumCapacity: items.count)
|
||||
|
||||
for item in items {
|
||||
@@ -71,7 +61,7 @@ final class FeedlyOrganiseParsedItemsByFeedOperation: FeedlyOperation, FeedlyPar
|
||||
guard !isCancelled else { return }
|
||||
}
|
||||
|
||||
os_log(.debug, log: log, "Grouped %i items by %i feeds for %@", items.count, dict.count, parsedItemsProvider.collection.label)
|
||||
// os_log(.debug, log: log, "Grouped %i items by %i feeds for %@", items.count, dict.count, parsedItemsProvider.collection.label)
|
||||
|
||||
itemsKeyedByFeedId = dict
|
||||
}
|
||||
|
||||
@@ -12,14 +12,14 @@ import os.log
|
||||
/// Single responsibility is to update the read status of articles stored locally with the unread status of the entries in a Collection's stream from Feedly.
|
||||
final class FeedlyRefreshStreamEntriesStatusOperation: FeedlyOperation {
|
||||
private let account: Account
|
||||
private let collectionStreamProvider: FeedlyCollectionStreamProviding
|
||||
private let entryProvider: FeedlyEntryProviding
|
||||
private let log: OSLog
|
||||
let articleStatusCoordinator: FeedlyArticleStatusCoordinator
|
||||
|
||||
init(account: Account, collectionStreamProvider: FeedlyCollectionStreamProviding, articleStatusCoordinator: FeedlyArticleStatusCoordinator, log: OSLog) {
|
||||
init(account: Account, entryProvider: FeedlyEntryProviding, articleStatusCoordinator: FeedlyArticleStatusCoordinator, log: OSLog) {
|
||||
self.account = account
|
||||
self.articleStatusCoordinator = articleStatusCoordinator
|
||||
self.collectionStreamProvider = collectionStreamProvider
|
||||
self.entryProvider = entryProvider
|
||||
self.log = log
|
||||
}
|
||||
|
||||
@@ -29,9 +29,7 @@ final class FeedlyRefreshStreamEntriesStatusOperation: FeedlyOperation {
|
||||
return
|
||||
}
|
||||
|
||||
let collection = collectionStreamProvider.collection
|
||||
let stream = collectionStreamProvider.stream
|
||||
articleStatusCoordinator.refreshArticleStatus(for: account, stream: stream, collection: collection) {
|
||||
articleStatusCoordinator.refreshArticleStatus(for: account, entries: entryProvider.entries) {
|
||||
self.didFinish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import Foundation
|
||||
import os.log
|
||||
|
||||
protocol FeedlyRequestStreamsOperationDelegate: class {
|
||||
func feedlyRequestStreamsOperation(_ operation: FeedlyRequestStreamsOperation, enqueue collectionStreamOperation: FeedlyGetCollectionStreamOperation)
|
||||
func feedlyRequestStreamsOperation(_ operation: FeedlyRequestStreamsOperation, enqueue collectionStreamOperation: FeedlyGetStreamOperation)
|
||||
}
|
||||
|
||||
/// Single responsibility is to create one stream request operation for one Feedly collection.
|
||||
@@ -45,8 +45,9 @@ final class FeedlyRequestStreamsOperation: FeedlyOperation {
|
||||
// TODO: Prioritise the must read collection/category before others so the most important content for the user loads first.
|
||||
|
||||
for collection in collectionsProvider.collections {
|
||||
let operation = FeedlyGetCollectionStreamOperation(account: account,
|
||||
collection: collection,
|
||||
let resource = FeedlyCategoryResourceId(id: collection.id)
|
||||
let operation = FeedlyGetStreamOperation(account: account,
|
||||
resource: resource,
|
||||
caller: caller,
|
||||
newerThan: newerThan,
|
||||
unreadOnly: unreadOnly)
|
||||
|
||||
@@ -62,7 +62,7 @@ final class FeedlySyncStrategy {
|
||||
|
||||
// Ensure feeds are created and grouped by their folders.
|
||||
let createFeedsOperation = FeedlyCreateFeedsForCollectionFoldersOperation(account: account,
|
||||
collectionsAndFoldersProvider: mirrorCollectionsAsFolders,
|
||||
feedsAndFoldersProvider: mirrorCollectionsAsFolders,
|
||||
log: log)
|
||||
createFeedsOperation.delegate = self
|
||||
createFeedsOperation.addDependency(mirrorCollectionsAsFolders)
|
||||
@@ -79,6 +79,41 @@ final class FeedlySyncStrategy {
|
||||
getCollectionStreams.queueDelegate = self
|
||||
getCollectionStreams.addDependency(getCollections)
|
||||
|
||||
// if let user = caller.credentials?.username {
|
||||
//
|
||||
// let syncSaved = FeedlyCompoundOperation {
|
||||
//
|
||||
// let saved = FeedlyTagResourceId.saved(for: user)
|
||||
// let getSavedStream = FeedlyGetStreamOperation(account: account,
|
||||
// resource: saved,
|
||||
// caller: caller,
|
||||
// newerThan: newerThan)
|
||||
// getSavedStream.delegate = self
|
||||
//
|
||||
// getSavedStream.addDependency(getCollections)
|
||||
// getSavedStream.addDependency(mirrorCollectionsAsFolders)
|
||||
// getSavedStream.addDependency(createFeedsOperation)
|
||||
//
|
||||
// let organiseByFeed = FeedlyOrganiseParsedItemsByFeedOperation(account: account,
|
||||
// streamProvider: getSavedStream,
|
||||
// log: log)
|
||||
// organiseByFeed.delegate = self
|
||||
// organiseByFeed.addDependency(getSavedStream)
|
||||
//
|
||||
// let updateAccount = FeedlyUpdateAccountFeedsWithItemsOperation(account: account,
|
||||
// organisedItemsProvider: organiseByFeed,
|
||||
// log: log)
|
||||
// updateAccount.delegate = self
|
||||
// updateAccount.addDependency(organiseByFeed)
|
||||
//
|
||||
// // refresh stream entries status
|
||||
//
|
||||
// return [getSavedStream, organiseByFeed]
|
||||
// }
|
||||
//
|
||||
// operationQueue.addOperation(syncSaved)
|
||||
// }
|
||||
|
||||
// Last operation to perform, which should be dependent on any other operation added to the queue.
|
||||
let syncId = UUID().uuidString
|
||||
let lastArticleFetchDate = Date()
|
||||
@@ -117,26 +152,18 @@ final class FeedlySyncStrategy {
|
||||
|
||||
extension FeedlySyncStrategy: FeedlyRequestStreamsOperationDelegate {
|
||||
|
||||
func feedlyRequestStreamsOperation(_ operation: FeedlyRequestStreamsOperation, enqueue collectionStreamOperation: FeedlyGetCollectionStreamOperation) {
|
||||
func feedlyRequestStreamsOperation(_ operation: FeedlyRequestStreamsOperation, enqueue streamOperation: FeedlyGetStreamOperation) {
|
||||
|
||||
collectionStreamOperation.delegate = self
|
||||
streamOperation.delegate = self
|
||||
|
||||
os_log(.debug, log: log, "Requesting stream for collection \"%@\"", collectionStreamOperation.collection.label)
|
||||
|
||||
// Parse the contents of this collection's stream.
|
||||
let parseItemsOperation = FeedlyGetStreamParsedItemsOperation(account: account,
|
||||
collectionStreamProvider: collectionStreamOperation,
|
||||
caller: caller,
|
||||
log: log)
|
||||
parseItemsOperation.delegate = self
|
||||
parseItemsOperation.addDependency(collectionStreamOperation)
|
||||
// os_log(.debug, log: log, "Requesting stream for collection \"%@\"", streamOperation.collection.label)
|
||||
|
||||
// Group the stream's content by feed.
|
||||
let groupItemsByFeed = FeedlyOrganiseParsedItemsByFeedOperation(account: account,
|
||||
parsedItemsProvider: parseItemsOperation,
|
||||
entryProvider: streamOperation,
|
||||
log: log)
|
||||
groupItemsByFeed.delegate = self
|
||||
groupItemsByFeed.addDependency(parseItemsOperation)
|
||||
groupItemsByFeed.addDependency(streamOperation)
|
||||
|
||||
// Update the account with the articles for the feeds in the stream.
|
||||
let updateOperation = FeedlyUpdateAccountFeedsWithItemsOperation(account: account,
|
||||
@@ -147,7 +174,7 @@ extension FeedlySyncStrategy: FeedlyRequestStreamsOperationDelegate {
|
||||
|
||||
// Once the articles are in the account, ensure they have the correct status
|
||||
let ensureUnreadOperation = FeedlyRefreshStreamEntriesStatusOperation(account: account,
|
||||
collectionStreamProvider: collectionStreamOperation,
|
||||
entryProvider: streamOperation,
|
||||
articleStatusCoordinator: articleStatusCoordinator,
|
||||
log: log)
|
||||
|
||||
@@ -159,7 +186,7 @@ extension FeedlySyncStrategy: FeedlyRequestStreamsOperationDelegate {
|
||||
operation.addDependency(ensureUnreadOperation)
|
||||
}
|
||||
|
||||
let operations = [collectionStreamOperation, parseItemsOperation, groupItemsByFeed, updateOperation, ensureUnreadOperation]
|
||||
let operations = [streamOperation, groupItemsByFeed, updateOperation, ensureUnreadOperation]
|
||||
|
||||
operationQueue.addOperations(operations, waitUntilFinished: false)
|
||||
}
|
||||
|
||||
@@ -31,14 +31,14 @@ final class FeedlyUpdateAccountFeedsWithItemsOperation: FeedlyOperation {
|
||||
let group = DispatchGroup()
|
||||
let allFeeds = organisedItemsProvider.allFeeds
|
||||
|
||||
os_log(.debug, log: log, "Begin updating %i feeds in collection \"%@\"", allFeeds.count, organisedItemsProvider.collection.label)
|
||||
// os_log(.debug, log: log, "Begin updating %i feeds in collection \"%@\"", allFeeds.count, organisedItemsProvider.collection.label)
|
||||
|
||||
for feed in allFeeds {
|
||||
guard let items = organisedItemsProvider.parsedItems(for: feed) else {
|
||||
continue
|
||||
}
|
||||
group.enter()
|
||||
os_log(.debug, log: log, "Updating %i items for feed \"%@\" in collection \"%@\"", items.count, feed.nameForDisplay, organisedItemsProvider.collection.label)
|
||||
// os_log(.debug, log: log, "Updating %i items for feed \"%@\" in collection \"%@\"", items.count, feed.nameForDisplay, organisedItemsProvider.collection.label)
|
||||
|
||||
account.update(feed, parsedItems: items, defaultRead: true) {
|
||||
group.leave()
|
||||
@@ -46,7 +46,7 @@ final class FeedlyUpdateAccountFeedsWithItemsOperation: FeedlyOperation {
|
||||
}
|
||||
|
||||
group.notify(qos: .userInitiated, queue: .main) {
|
||||
os_log(.debug, log: self.log, "Finished updating feeds in collection \"%@\"", self.organisedItemsProvider.collection.label)
|
||||
// os_log(.debug, log: self.log, "Finished updating feeds in collection \"%@\"", self.organisedItemsProvider.collection.label)
|
||||
self.didFinish()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user