diff --git a/Frameworks/Account/Account.xcodeproj/project.pbxproj b/Frameworks/Account/Account.xcodeproj/project.pbxproj index e85e58d88..5c82a7f64 100644 --- a/Frameworks/Account/Account.xcodeproj/project.pbxproj +++ b/Frameworks/Account/Account.xcodeproj/project.pbxproj @@ -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 = ""; }; 9E1D154E233371DD00F4944C /* FeedlyGetCollectionsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyGetCollectionsOperation.swift; sourceTree = ""; }; 9E1D15502334282100F4944C /* FeedlyMirrorCollectionsAsFoldersOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyMirrorCollectionsAsFoldersOperation.swift; sourceTree = ""; }; - 9E1D15522334304B00F4944C /* FeedlyGetCollectionStreamOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyGetCollectionStreamOperation.swift; sourceTree = ""; }; + 9E1D15522334304B00F4944C /* FeedlyGetStreamOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyGetStreamOperation.swift; sourceTree = ""; }; 9E1D1554233431A600F4944C /* FeedlyOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyOperation.swift; sourceTree = ""; }; 9E1D15562334355900F4944C /* FeedlyRequestStreamsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyRequestStreamsOperation.swift; sourceTree = ""; }; - 9E1D155823343B2A00F4944C /* FeedlyGetStreamParsedItemsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyGetStreamParsedItemsOperation.swift; sourceTree = ""; }; 9E1D155A2334423300F4944C /* FeedlyOrganiseParsedItemsByFeedOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyOrganiseParsedItemsByFeedOperation.swift; sourceTree = ""; }; 9E1D155C233447F000F4944C /* FeedlyUpdateAccountFeedsWithItemsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyUpdateAccountFeedsWithItemsOperation.swift; sourceTree = ""; }; 9E713652233AD63E00765C84 /* FeedlyRefreshStreamEntriesStatusOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyRefreshStreamEntriesStatusOperation.swift; sourceTree = ""; }; @@ -289,6 +288,7 @@ 9EC688EB232C583300A8D0A2 /* FeedlyAccountDelegate+OAuth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FeedlyAccountDelegate+OAuth.swift"; sourceTree = ""; }; 9EC688ED232C58E800A8D0A2 /* OAuthAuthorizationCodeGranting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuthAuthorizationCodeGranting.swift; sourceTree = ""; }; 9ECC9A84234DC16E009B5144 /* FeedlyAccountDelegateError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyAccountDelegateError.swift; sourceTree = ""; }; + 9EF35F79234E830E003AE2AE /* FeedlyCompoundOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyCompoundOperation.swift; sourceTree = ""; }; D511EEB5202422BB00712EC3 /* Account_project_debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Account_project_debug.xcconfig; sourceTree = ""; }; D511EEB6202422BB00712EC3 /* Account_target.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Account_target.xcconfig; sourceTree = ""; }; D511EEB7202422BB00712EC3 /* Account_project_release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Account_project_release.xcconfig; sourceTree = ""; }; @@ -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 */, diff --git a/Frameworks/Account/Feedly/FeedlyArticleStatusCoordinator.swift b/Frameworks/Account/Feedly/FeedlyArticleStatusCoordinator.swift index f56e9372e..1450783c9 100644 --- a/Frameworks/Account/Feedly/FeedlyArticleStatusCoordinator.swift +++ b/Frameworks/Account/Feedly/FeedlyArticleStatusCoordinator.swift @@ -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() diff --git a/Frameworks/Account/Feedly/FeedlyCompoundOperation.swift b/Frameworks/Account/Feedly/FeedlyCompoundOperation.swift new file mode 100644 index 000000000..093c57ca7 --- /dev/null +++ b/Frameworks/Account/Feedly/FeedlyCompoundOperation.swift @@ -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) + } +} diff --git a/Frameworks/Account/Feedly/Models/FeedlyResourceId.swift b/Frameworks/Account/Feedly/Models/FeedlyResourceId.swift index af31c55fd..d59acc32d 100644 --- a/Frameworks/Account/Feedly/Models/FeedlyResourceId.swift +++ b/Frameworks/Account/Feedly/Models/FeedlyResourceId.swift @@ -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) + } +} diff --git a/Frameworks/Account/Feedly/Refresh/FeedlyCreateFeedsForCollectionFoldersOperation.swift b/Frameworks/Account/Feedly/Refresh/FeedlyCreateFeedsForCollectionFoldersOperation.swift index b44e249fe..69f4926fb 100644 --- a/Frameworks/Account/Feedly/Refresh/FeedlyCreateFeedsForCollectionFoldersOperation.swift +++ b/Frameworks/Account/Feedly/Refresh/FeedlyCreateFeedsForCollectionFoldersOperation.swift @@ -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 diff --git a/Frameworks/Account/Feedly/Refresh/FeedlyGetCollectionStreamOperation.swift b/Frameworks/Account/Feedly/Refresh/FeedlyGetCollectionStreamOperation.swift deleted file mode 100644 index 7f67cc759..000000000 --- a/Frameworks/Account/Feedly/Refresh/FeedlyGetCollectionStreamOperation.swift +++ /dev/null @@ -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) - } - } - } -} diff --git a/Frameworks/Account/Feedly/Refresh/FeedlyGetStreamOperation.swift b/Frameworks/Account/Feedly/Refresh/FeedlyGetStreamOperation.swift new file mode 100644 index 000000000..0703562ed --- /dev/null +++ b/Frameworks/Account/Feedly/Refresh/FeedlyGetStreamOperation.swift @@ -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 { 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 { + 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? + + + 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) + } + } + } +} diff --git a/Frameworks/Account/Feedly/Refresh/FeedlyGetStreamParsedItemsOperation.swift b/Frameworks/Account/Feedly/Refresh/FeedlyGetStreamParsedItemsOperation.swift deleted file mode 100644 index 9069a0637..000000000 --- a/Frameworks/Account/Feedly/Refresh/FeedlyGetStreamParsedItemsOperation.swift +++ /dev/null @@ -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) - } -} diff --git a/Frameworks/Account/Feedly/Refresh/FeedlyMirrorCollectionsAsFoldersOperation.swift b/Frameworks/Account/Feedly/Refresh/FeedlyMirrorCollectionsAsFoldersOperation.swift index 82aaf6ea0..7a530cac5 100644 --- a/Frameworks/Account/Feedly/Refresh/FeedlyMirrorCollectionsAsFoldersOperation.swift +++ b/Frameworks/Account/Feedly/Refresh/FeedlyMirrorCollectionsAsFoldersOperation.swift @@ -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) diff --git a/Frameworks/Account/Feedly/Refresh/FeedlyOrganiseParsedItemsByFeedOperation.swift b/Frameworks/Account/Feedly/Refresh/FeedlyOrganiseParsedItemsByFeedOperation.swift index a58b33abc..ed9ff2ad6 100644 --- a/Frameworks/Account/Feedly/Refresh/FeedlyOrganiseParsedItemsByFeedOperation.swift +++ b/Frameworks/Account/Feedly/Refresh/FeedlyOrganiseParsedItemsByFeedOperation.swift @@ -11,8 +11,6 @@ import RSParser import os.log protocol FeedlyParsedItemsByFeedProviding { - var collection: FeedlyCollection { get } - var stream: FeedlyStream { get } var allFeeds: Set { get } func parsedItems(for feed: Feed) -> Set? } @@ -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 { @@ -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]() - 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](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 } diff --git a/Frameworks/Account/Feedly/Refresh/FeedlyRefreshStreamEntriesStatusOperation.swift b/Frameworks/Account/Feedly/Refresh/FeedlyRefreshStreamEntriesStatusOperation.swift index 6789b86fb..a4419d7d1 100644 --- a/Frameworks/Account/Feedly/Refresh/FeedlyRefreshStreamEntriesStatusOperation.swift +++ b/Frameworks/Account/Feedly/Refresh/FeedlyRefreshStreamEntriesStatusOperation.swift @@ -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() } } diff --git a/Frameworks/Account/Feedly/Refresh/FeedlyRequestStreamsOperation.swift b/Frameworks/Account/Feedly/Refresh/FeedlyRequestStreamsOperation.swift index 59311bace..c5c6f0745 100644 --- a/Frameworks/Account/Feedly/Refresh/FeedlyRequestStreamsOperation.swift +++ b/Frameworks/Account/Feedly/Refresh/FeedlyRequestStreamsOperation.swift @@ -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) diff --git a/Frameworks/Account/Feedly/Refresh/FeedlySyncStrategy.swift b/Frameworks/Account/Feedly/Refresh/FeedlySyncStrategy.swift index ab62248c2..0ce386cbb 100644 --- a/Frameworks/Account/Feedly/Refresh/FeedlySyncStrategy.swift +++ b/Frameworks/Account/Feedly/Refresh/FeedlySyncStrategy.swift @@ -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) } diff --git a/Frameworks/Account/Feedly/Refresh/FeedlyUpdateAccountFeedsWithItemsOperation.swift b/Frameworks/Account/Feedly/Refresh/FeedlyUpdateAccountFeedsWithItemsOperation.swift index b7ab54c4a..f30322b36 100644 --- a/Frameworks/Account/Feedly/Refresh/FeedlyUpdateAccountFeedsWithItemsOperation.swift +++ b/Frameworks/Account/Feedly/Refresh/FeedlyUpdateAccountFeedsWithItemsOperation.swift @@ -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() } }