Merge branch 'ios-candidate' of https://github.com/Ranchero-Software/NetNewsWire into ios-candidate

This commit is contained in:
Maurice Parker 2020-01-19 15:36:31 -07:00
commit fd1d84dff8
27 changed files with 166 additions and 215 deletions

View File

@ -399,7 +399,6 @@
9EF1B10623590D61000A486A /* FeedlyGetStreamIdsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyGetStreamIdsOperation.swift; sourceTree = "<group>"; }; 9EF1B10623590D61000A486A /* FeedlyGetStreamIdsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyGetStreamIdsOperation.swift; sourceTree = "<group>"; };
9EF1B10823590E93000A486A /* FeedlyStreamIds.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyStreamIds.swift; sourceTree = "<group>"; }; 9EF1B10823590E93000A486A /* FeedlyStreamIds.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyStreamIds.swift; sourceTree = "<group>"; };
9EF2602B23C91FFE006D160C /* FeedlyGetUpdatedArticleIdsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyGetUpdatedArticleIdsOperation.swift; sourceTree = "<group>"; }; 9EF2602B23C91FFE006D160C /* FeedlyGetUpdatedArticleIdsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyGetUpdatedArticleIdsOperation.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>"; }; 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>"; }; 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>"; }; D511EEB7202422BB00712EC3 /* Account_project_release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Account_project_release.xcconfig; sourceTree = "<group>"; };
@ -700,7 +699,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
9E1D1554233431A600F4944C /* FeedlyOperation.swift */, 9E1D1554233431A600F4944C /* FeedlyOperation.swift */,
9EF35F79234E830E003AE2AE /* FeedlyCompoundOperation.swift */, 9E1D154C233370D800F4944C /* FeedlySyncAllOperation.swift */,
9EA643D2239305680018A28C /* FeedlySearchOperation.swift */, 9EA643D2239305680018A28C /* FeedlySearchOperation.swift */,
9E7299D623505E9600DAEFB7 /* FeedlyAddFeedToCollectionOperation.swift */, 9E7299D623505E9600DAEFB7 /* FeedlyAddFeedToCollectionOperation.swift */,
9EB1D575238E6A3900A753D7 /* FeedlyAddNewFeedOperation.swift */, 9EB1D575238E6A3900A753D7 /* FeedlyAddNewFeedOperation.swift */,
@ -721,7 +720,6 @@
9EEEF7202355277F009E9D80 /* FeedlyIngestStarredArticleIdsOperation.swift */, 9EEEF7202355277F009E9D80 /* FeedlyIngestStarredArticleIdsOperation.swift */,
9E84DC462359A23200D6E809 /* FeedlyIngestUnreadArticleIdsOperation.swift */, 9E84DC462359A23200D6E809 /* FeedlyIngestUnreadArticleIdsOperation.swift */,
9EF2602B23C91FFE006D160C /* FeedlyGetUpdatedArticleIdsOperation.swift */, 9EF2602B23C91FFE006D160C /* FeedlyGetUpdatedArticleIdsOperation.swift */,
9E1D154C233370D800F4944C /* FeedlySyncAllOperation.swift */,
9E672393236F7CA0000BE141 /* FeedlyRefreshAccessTokenOperation.swift */, 9E672393236F7CA0000BE141 /* FeedlyRefreshAccessTokenOperation.swift */,
9E784EBD237E890600099B1B /* FeedlyLogoutOperation.swift */, 9E784EBD237E890600099B1B /* FeedlyLogoutOperation.swift */,
9E5DE60D23C3F4B70064DA30 /* FeedlyFetchIdsForMissingArticlesOperation.swift */, 9E5DE60D23C3F4B70064DA30 /* FeedlyFetchIdsForMissingArticlesOperation.swift */,

View File

@ -12,18 +12,17 @@ import RSWeb
import RSCore import RSCore
class FeedlyAddExistingFeedOperation: FeedlyOperation, FeedlyOperationDelegate, FeedlyCheckpointOperationDelegate { class FeedlyAddExistingFeedOperation: FeedlyOperation, FeedlyOperationDelegate, FeedlyCheckpointOperationDelegate {
private let operationQueue: MainThreadOperationQueue
private let operationQueue = MainThreadOperationQueue()
var addCompletionHandler: ((Result<Void, Error>) -> ())? var addCompletionHandler: ((Result<Void, Error>) -> ())?
init(account: Account, credentials: Credentials, resource: FeedlyFeedResourceId, service: FeedlyAddFeedToCollectionService, container: Container, progress: DownloadProgress, log: OSLog) throws { init(account: Account, credentials: Credentials, resource: FeedlyFeedResourceId, service: FeedlyAddFeedToCollectionService, container: Container, progress: DownloadProgress, log: OSLog) throws {
let validator = FeedlyFeedContainerValidator(container: container) let validator = FeedlyFeedContainerValidator(container: container)
let (folder, collectionId) = try validator.getValidContainer() let (folder, collectionId) = try validator.getValidContainer()
self.operationQueue = MainThreadOperationQueue()
self.operationQueue.suspend() self.operationQueue.suspend()
super.init() super.init()
self.downloadProgress = progress self.downloadProgress = progress
@ -35,26 +34,24 @@ class FeedlyAddExistingFeedOperation: FeedlyOperation, FeedlyOperationDelegate,
let createFeeds = FeedlyCreateFeedsForCollectionFoldersOperation(account: account, feedsAndFoldersProvider: addRequest, log: log) let createFeeds = FeedlyCreateFeedsForCollectionFoldersOperation(account: account, feedsAndFoldersProvider: addRequest, log: log)
createFeeds.downloadProgress = progress createFeeds.downloadProgress = progress
self.operationQueue.make(createFeeds, dependOn: addRequest) createFeeds.addDependency(addRequest)
self.operationQueue.addOperation(createFeeds) self.operationQueue.addOperation(createFeeds)
let finishOperation = FeedlyCheckpointOperation() let finishOperation = FeedlyCheckpointOperation()
finishOperation.checkpointDelegate = self finishOperation.checkpointDelegate = self
finishOperation.downloadProgress = progress finishOperation.downloadProgress = progress
self.operationQueue.make(finishOperation, dependOn: createFeeds) finishOperation.addDependency(createFeeds)
self.operationQueue.addOperation(finishOperation) self.operationQueue.addOperation(finishOperation)
} }
override func cancel() {
operationQueue.cancelAllOperations()
super.cancel()
didFinish()
}
override func run() { override func run() {
super.run()
operationQueue.resume() operationQueue.resume()
} }
override func didCancel() {
operationQueue.cancelAllOperations()
addCompletionHandler = nil
}
func feedlyOperation(_ operation: FeedlyOperation, didFailWith error: Error) { func feedlyOperation(_ operation: FeedlyOperation, didFailWith error: Error) {
addCompletionHandler?(.failure(error)) addCompletionHandler?(.failure(error))

View File

@ -13,13 +13,14 @@ protocol FeedlyAddFeedToCollectionService {
} }
final class FeedlyAddFeedToCollectionOperation: FeedlyOperation, FeedlyFeedsAndFoldersProviding, FeedlyResourceProviding { final class FeedlyAddFeedToCollectionOperation: FeedlyOperation, FeedlyFeedsAndFoldersProviding, FeedlyResourceProviding {
let feedName: String? let feedName: String?
let collectionId: String let collectionId: String
let service: FeedlyAddFeedToCollectionService let service: FeedlyAddFeedToCollectionService
let account: Account let account: Account
let folder: Folder let folder: Folder
let feedResource: FeedlyFeedResourceId let feedResource: FeedlyFeedResourceId
init(account: Account, folder: Folder, feedResource: FeedlyFeedResourceId, feedName: String? = nil, collectionId: String, service: FeedlyAddFeedToCollectionService) { init(account: Account, folder: Folder, feedResource: FeedlyFeedResourceId, feedName: String? = nil, collectionId: String, service: FeedlyAddFeedToCollectionService) {
self.account = account self.account = account
self.folder = folder self.folder = folder
@ -36,8 +37,6 @@ final class FeedlyAddFeedToCollectionOperation: FeedlyOperation, FeedlyFeedsAndF
} }
override func run() { override func run() {
super.run()
service.addFeed(with: feedResource, title: feedName, toCollectionWith: collectionId) { [weak self] result in service.addFeed(with: feedResource, title: feedName, toCollectionWith: collectionId) { [weak self] result in
guard let self = self else { guard let self = self else {
return return
@ -49,8 +48,11 @@ final class FeedlyAddFeedToCollectionOperation: FeedlyOperation, FeedlyFeedsAndF
self.didCompleteRequest(result) self.didCompleteRequest(result)
} }
} }
}
private func didCompleteRequest(_ result: Result<[FeedlyFeed], Error>) {
private extension FeedlyAddFeedToCollectionOperation {
func didCompleteRequest(_ result: Result<[FeedlyFeed], Error>) {
switch result { switch result {
case .success(let feedlyFeeds): case .success(let feedlyFeeds):
feedsAndFolders = [(feedlyFeeds, folder)] feedsAndFolders = [(feedlyFeeds, folder)]
@ -58,13 +60,13 @@ final class FeedlyAddFeedToCollectionOperation: FeedlyOperation, FeedlyFeedsAndF
let feedsWithCreatedFeedId = feedlyFeeds.filter { $0.id == resource.id } let feedsWithCreatedFeedId = feedlyFeeds.filter { $0.id == resource.id }
if feedsWithCreatedFeedId.isEmpty { if feedsWithCreatedFeedId.isEmpty {
didFinish(AccountError.createErrorNotFound) didFinish(with: AccountError.createErrorNotFound)
} else { } else {
didFinish() didFinish()
} }
case .failure(let error): case .failure(let error):
didFinish(error) didFinish(with: error)
} }
} }
} }

View File

@ -13,7 +13,8 @@ import RSWeb
import RSCore import RSCore
class FeedlyAddNewFeedOperation: FeedlyOperation, FeedlyOperationDelegate, FeedlySearchOperationDelegate, FeedlyCheckpointOperationDelegate { class FeedlyAddNewFeedOperation: FeedlyOperation, FeedlyOperationDelegate, FeedlySearchOperationDelegate, FeedlyCheckpointOperationDelegate {
private let operationQueue: MainThreadOperationQueue
private let operationQueue = MainThreadOperationQueue()
private let folder: Folder private let folder: Folder
private let collectionId: String private let collectionId: String
private let url: String private let url: String
@ -25,16 +26,16 @@ class FeedlyAddNewFeedOperation: FeedlyOperation, FeedlyOperationDelegate, Feedl
private let syncUnreadIdsService: FeedlyGetStreamIdsService private let syncUnreadIdsService: FeedlyGetStreamIdsService
private let getStreamContentsService: FeedlyGetStreamContentsService private let getStreamContentsService: FeedlyGetStreamContentsService
private let log: OSLog private let log: OSLog
private var feedResourceId: FeedlyFeedResourceId?
var addCompletionHandler: ((Result<WebFeed, Error>) -> ())? var addCompletionHandler: ((Result<WebFeed, Error>) -> ())?
init(account: Account, credentials: Credentials, url: String, feedName: String?, searchService: FeedlySearchService, addToCollectionService: FeedlyAddFeedToCollectionService, syncUnreadIdsService: FeedlyGetStreamIdsService, getStreamContentsService: FeedlyGetStreamContentsService, database: SyncDatabase, container: Container, progress: DownloadProgress, log: OSLog) throws { init(account: Account, credentials: Credentials, url: String, feedName: String?, searchService: FeedlySearchService, addToCollectionService: FeedlyAddFeedToCollectionService, syncUnreadIdsService: FeedlyGetStreamIdsService, getStreamContentsService: FeedlyGetStreamContentsService, database: SyncDatabase, container: Container, progress: DownloadProgress, log: OSLog) throws {
let validator = FeedlyFeedContainerValidator(container: container) let validator = FeedlyFeedContainerValidator(container: container)
(self.folder, self.collectionId) = try validator.getValidContainer() (self.folder, self.collectionId) = try validator.getValidContainer()
self.url = url self.url = url
self.operationQueue = MainThreadOperationQueue()
self.operationQueue.suspend() self.operationQueue.suspend()
self.account = account self.account = account
self.credentials = credentials self.credentials = credentials
@ -44,9 +45,9 @@ class FeedlyAddNewFeedOperation: FeedlyOperation, FeedlyOperationDelegate, Feedl
self.syncUnreadIdsService = syncUnreadIdsService self.syncUnreadIdsService = syncUnreadIdsService
self.getStreamContentsService = getStreamContentsService self.getStreamContentsService = getStreamContentsService
self.log = log self.log = log
super.init() super.init()
self.downloadProgress = progress self.downloadProgress = progress
let search = FeedlySearchOperation(query: url, locale: .current, service: searchService) let search = FeedlySearchOperation(query: url, locale: .current, service: searchService)
@ -57,24 +58,20 @@ class FeedlyAddNewFeedOperation: FeedlyOperation, FeedlyOperationDelegate, Feedl
} }
override func run() { override func run() {
super.run()
operationQueue.resume() operationQueue.resume()
} }
override func cancel() { override func didCancel() {
super.cancel()
operationQueue.cancelAllOperations() operationQueue.cancelAllOperations()
addCompletionHandler = nil addCompletionHandler = nil
} }
private var feedResourceId: FeedlyFeedResourceId?
func feedlySearchOperation(_ operation: FeedlySearchOperation, didGet response: FeedlyFeedsSearchResponse) { func feedlySearchOperation(_ operation: FeedlySearchOperation, didGet response: FeedlyFeedsSearchResponse) {
guard !isCanceled else { guard !isCanceled else {
return return
} }
guard let first = response.results.first else { guard let first = response.results.first else {
return didFinish(AccountError.createErrorNotFound) return didFinish(with: AccountError.createErrorNotFound)
} }
let feedResourceId = FeedlyFeedResourceId(id: first.feedId) let feedResourceId = FeedlyFeedResourceId(id: first.feedId)
@ -86,24 +83,24 @@ class FeedlyAddNewFeedOperation: FeedlyOperation, FeedlyOperationDelegate, Feedl
operationQueue.addOperation(addRequest) operationQueue.addOperation(addRequest)
let createFeeds = FeedlyCreateFeedsForCollectionFoldersOperation(account: account, feedsAndFoldersProvider: addRequest, log: log) let createFeeds = FeedlyCreateFeedsForCollectionFoldersOperation(account: account, feedsAndFoldersProvider: addRequest, log: log)
operationQueue.make(createFeeds, dependOn: addRequest) createFeeds.addDependency(addRequest)
createFeeds.downloadProgress = downloadProgress createFeeds.downloadProgress = downloadProgress
operationQueue.addOperation(createFeeds) operationQueue.addOperation(createFeeds)
let syncUnread = FeedlyIngestUnreadArticleIdsOperation(account: account, credentials: credentials, service: syncUnreadIdsService, database: database, newerThan: nil, log: log) let syncUnread = FeedlyIngestUnreadArticleIdsOperation(account: account, credentials: credentials, service: syncUnreadIdsService, database: database, newerThan: nil, log: log)
operationQueue.make(syncUnread, dependOn: createFeeds) syncUnread.addDependency(createFeeds)
syncUnread.downloadProgress = downloadProgress syncUnread.downloadProgress = downloadProgress
operationQueue.addOperation(syncUnread) operationQueue.addOperation(syncUnread)
let syncFeed = FeedlySyncStreamContentsOperation(account: account, resource: feedResourceId, service: getStreamContentsService, isPagingEnabled: false, newerThan: nil, log: log) let syncFeed = FeedlySyncStreamContentsOperation(account: account, resource: feedResourceId, service: getStreamContentsService, isPagingEnabled: false, newerThan: nil, log: log)
operationQueue.make(syncFeed, dependOn: syncUnread) syncFeed.addDependency(syncUnread)
syncFeed.downloadProgress = downloadProgress syncFeed.downloadProgress = downloadProgress
operationQueue.addOperation(syncFeed) operationQueue.addOperation(syncFeed)
let finishOperation = FeedlyCheckpointOperation() let finishOperation = FeedlyCheckpointOperation()
finishOperation.checkpointDelegate = self finishOperation.checkpointDelegate = self
finishOperation.downloadProgress = downloadProgress finishOperation.downloadProgress = downloadProgress
operationQueue.make(finishOperation, dependOn: syncFeed) finishOperation.addDependency(syncFeed)
operationQueue.addOperation(finishOperation) operationQueue.addOperation(finishOperation)
} }
@ -118,7 +115,6 @@ class FeedlyAddNewFeedOperation: FeedlyOperation, FeedlyOperationDelegate, Feedl
guard !isCanceled else { guard !isCanceled else {
return return
} }
defer { defer {
didFinish() didFinish()
} }
@ -126,14 +122,12 @@ class FeedlyAddNewFeedOperation: FeedlyOperation, FeedlyOperationDelegate, Feedl
guard let handler = addCompletionHandler else { guard let handler = addCompletionHandler else {
return return
} }
if let feedResource = feedResourceId, let feed = folder.existingWebFeed(withWebFeedID: feedResource.id) { if let feedResource = feedResourceId, let feed = folder.existingWebFeed(withWebFeedID: feedResource.id) {
handler(.success(feed)) handler(.success(feed))
}
} else { else {
handler(.failure(AccountError.createErrorNotFound)) handler(.failure(AccountError.createErrorNotFound))
} }
addCompletionHandler = nil addCompletionHandler = nil
} }
} }

View File

@ -12,13 +12,12 @@ protocol FeedlyCheckpointOperationDelegate: class {
func feedlyCheckpointOperationDidReachCheckpoint(_ operation: FeedlyCheckpointOperation) func feedlyCheckpointOperationDidReachCheckpoint(_ operation: FeedlyCheckpointOperation)
} }
/// Single responsibility is to let the delegate know an instance is executing. The semantics are up to the delegate. /// Let the delegate know an instance is executing. The semantics are up to the delegate.
final class FeedlyCheckpointOperation: FeedlyOperation { final class FeedlyCheckpointOperation: FeedlyOperation {
weak var checkpointDelegate: FeedlyCheckpointOperationDelegate? weak var checkpointDelegate: FeedlyCheckpointOperationDelegate?
override func run() { override func run() {
super.run()
defer { defer {
didFinish() didFinish()
} }

View File

@ -15,7 +15,7 @@ final class FeedlyCreateFeedsForCollectionFoldersOperation: FeedlyOperation {
let account: Account let account: Account
let feedsAndFoldersProvider: FeedlyFeedsAndFoldersProviding let feedsAndFoldersProvider: FeedlyFeedsAndFoldersProviding
let log: OSLog let log: OSLog
init(account: Account, feedsAndFoldersProvider: FeedlyFeedsAndFoldersProviding, log: OSLog) { init(account: Account, feedsAndFoldersProvider: FeedlyFeedsAndFoldersProviding, log: OSLog) {
self.feedsAndFoldersProvider = feedsAndFoldersProvider self.feedsAndFoldersProvider = feedsAndFoldersProvider
self.account = account self.account = account
@ -23,7 +23,6 @@ final class FeedlyCreateFeedsForCollectionFoldersOperation: FeedlyOperation {
} }
override func run() { override func run() {
super.run()
defer { defer {
didFinish() didFinish()
} }

View File

@ -9,8 +9,10 @@
import Foundation import Foundation
import os.log import os.log
import RSCore import RSCore
import RSWeb
class FeedlyDownloadArticlesOperation: FeedlyOperation { class FeedlyDownloadArticlesOperation: FeedlyOperation {
private let account: Account private let account: Account
private let log: OSLog private let log: OSLog
private let missingArticleEntryIdProvider: FeedlyEntryIdentifierProviding private let missingArticleEntryIdProvider: FeedlyEntryIdentifierProviding
@ -27,24 +29,12 @@ class FeedlyDownloadArticlesOperation: FeedlyOperation {
self.getEntriesService = getEntriesService self.getEntriesService = getEntriesService
self.finishOperation = FeedlyCheckpointOperation() self.finishOperation = FeedlyCheckpointOperation()
self.log = log self.log = log
super.init() super.init()
self.finishOperation.checkpointDelegate = self self.finishOperation.checkpointDelegate = self
self.operationQueue.addOperation(self.finishOperation) self.operationQueue.addOperation(self.finishOperation)
} }
override func cancel() {
// TODO: fix error on below line: "Expression type '()' is ambiguous without more context"
//os_log(.debug, log: log, "Cancelling %{public}@.", self)
operationQueue.cancelAllOperations()
super.cancel()
didFinish()
}
override func run() { override func run() {
super.run()
var articleIds = missingArticleEntryIdProvider.entryIds var articleIds = missingArticleEntryIdProvider.entryIds
articleIds.formUnion(updatedArticleEntryIdProvider.entryIds) articleIds.formUnion(updatedArticleEntryIdProvider.entryIds)
@ -62,7 +52,7 @@ class FeedlyDownloadArticlesOperation: FeedlyOperation {
parsedItemProvider: getEntries, parsedItemProvider: getEntries,
log: log) log: log)
organiseByFeed.delegate = self organiseByFeed.delegate = self
self.operationQueue.make(organiseByFeed, dependOn: getEntries) organiseByFeed.addDependency(getEntries)
self.operationQueue.addOperation(organiseByFeed) self.operationQueue.addOperation(organiseByFeed)
let updateAccount = FeedlyUpdateAccountFeedsWithItemsOperation(account: account, let updateAccount = FeedlyUpdateAccountFeedsWithItemsOperation(account: account,
@ -70,14 +60,21 @@ class FeedlyDownloadArticlesOperation: FeedlyOperation {
log: log) log: log)
updateAccount.delegate = self updateAccount.delegate = self
self.operationQueue.make(updateAccount, dependOn: organiseByFeed) updateAccount.addDependency(organiseByFeed)
self.operationQueue.addOperation(updateAccount) self.operationQueue.addOperation(updateAccount)
self.operationQueue.make(finishOperation, dependOn: updateAccount) finishOperation.addDependency(updateAccount)
} }
operationQueue.resume() operationQueue.resume()
} }
override func didCancel() {
// TODO: fix error on below line: "Expression type '()' is ambiguous without more context"
//os_log(.debug, log: log, "Cancelling %{public}@.", self)
operationQueue.cancelAllOperations()
didFinish()
}
} }
extension FeedlyDownloadArticlesOperation: FeedlyCheckpointOperationDelegate { extension FeedlyDownloadArticlesOperation: FeedlyCheckpointOperationDelegate {

View File

@ -10,6 +10,7 @@ import Foundation
import os.log import os.log
final class FeedlyFetchIdsForMissingArticlesOperation: FeedlyOperation, FeedlyEntryIdentifierProviding { final class FeedlyFetchIdsForMissingArticlesOperation: FeedlyOperation, FeedlyEntryIdentifierProviding {
private let account: Account private let account: Account
private let log: OSLog private let log: OSLog
@ -21,7 +22,6 @@ final class FeedlyFetchIdsForMissingArticlesOperation: FeedlyOperation, FeedlyEn
} }
override func run() { override func run() {
super.run()
account.fetchArticleIDsForStatusesWithoutArticlesNewerThanCutoffDate { result in account.fetchArticleIDsForStatusesWithoutArticlesNewerThanCutoffDate { result in
switch result { switch result {
case .success(let articleIds): case .success(let articleIds):
@ -29,7 +29,7 @@ final class FeedlyFetchIdsForMissingArticlesOperation: FeedlyOperation, FeedlyEn
self.didFinish() self.didFinish()
case .failure(let error): case .failure(let error):
self.didFinish(error) self.didFinish(with: error)
} }
} }
} }

View File

@ -13,22 +13,20 @@ protocol FeedlyCollectionProviding: class {
var collections: [FeedlyCollection] { get } var collections: [FeedlyCollection] { get }
} }
/// Single responsibility is to get Collections from Feedly. /// Get Collections from Feedly.
final class FeedlyGetCollectionsOperation: FeedlyOperation, FeedlyCollectionProviding { final class FeedlyGetCollectionsOperation: FeedlyOperation, FeedlyCollectionProviding {
let service: FeedlyGetCollectionsService let service: FeedlyGetCollectionsService
let log: OSLog let log: OSLog
private(set) var collections = [FeedlyCollection]() private(set) var collections = [FeedlyCollection]()
init(service: FeedlyGetCollectionsService, log: OSLog) { init(service: FeedlyGetCollectionsService, log: OSLog) {
self.service = service self.service = service
self.log = log self.log = log
} }
override func run() { override func run() {
super.run()
os_log(.debug, log: log, "Requesting collections.") os_log(.debug, log: log, "Requesting collections.")
service.getCollections { result in service.getCollections { result in
@ -40,7 +38,7 @@ final class FeedlyGetCollectionsOperation: FeedlyOperation, FeedlyCollectionProv
case .failure(let error): case .failure(let error):
os_log(.debug, log: self.log, "Unable to request collections: %{public}@.", error as NSError) os_log(.debug, log: self.log, "Unable to request collections: %{public}@.", error as NSError)
self.didFinish(error) self.didFinish(with: error)
} }
} }
} }

View File

@ -10,13 +10,14 @@ import Foundation
import os.log import os.log
import RSParser import RSParser
/// Single responsibility is to get full entries for the entry identifiers. /// Get full entries for the entry identifiers.
final class FeedlyGetEntriesOperation: FeedlyOperation, FeedlyEntryProviding, FeedlyParsedItemProviding { final class FeedlyGetEntriesOperation: FeedlyOperation, FeedlyEntryProviding, FeedlyParsedItemProviding {
let account: Account let account: Account
let service: FeedlyGetEntriesService let service: FeedlyGetEntriesService
let provider: FeedlyEntryIdentifierProviding let provider: FeedlyEntryIdentifierProviding
let log: OSLog let log: OSLog
init(account: Account, service: FeedlyGetEntriesService, provider: FeedlyEntryIdentifierProviding, log: OSLog) { init(account: Account, service: FeedlyGetEntriesService, provider: FeedlyEntryIdentifierProviding, log: OSLog) {
self.account = account self.account = account
self.service = service self.service = service
@ -55,8 +56,6 @@ final class FeedlyGetEntriesOperation: FeedlyOperation, FeedlyEntryProviding, Fe
} }
override func run() { override func run() {
super.run()
service.getEntries(for: provider.entryIds) { result in service.getEntries(for: provider.entryIds) { result in
switch result { switch result {
case .success(let entries): case .success(let entries):
@ -65,7 +64,7 @@ final class FeedlyGetEntriesOperation: FeedlyOperation, FeedlyEntryProviding, Fe
case .failure(let error): case .failure(let error):
os_log(.debug, log: self.log, "Unable to get entries: %{public}@.", error as NSError) os_log(.debug, log: self.log, "Unable to get entries: %{public}@.", error as NSError)
self.didFinish(error) self.didFinish(with: error)
} }
} }
} }

View File

@ -23,7 +23,7 @@ protocol FeedlyGetStreamContentsOperationDelegate: class {
func feedlyGetStreamContentsOperation(_ operation: FeedlyGetStreamContentsOperation, didGetContentsOf stream: FeedlyStream) func feedlyGetStreamContentsOperation(_ operation: FeedlyGetStreamContentsOperation, didGetContentsOf stream: FeedlyStream)
} }
/// Single responsibility is to get the stream content of a Collection from Feedly. /// Get the stream content of a Collection from Feedly.
final class FeedlyGetStreamContentsOperation: FeedlyOperation, FeedlyEntryProviding, FeedlyParsedItemProviding { final class FeedlyGetStreamContentsOperation: FeedlyOperation, FeedlyEntryProviding, FeedlyParsedItemProviding {
struct ResourceProvider: FeedlyResourceProviding { struct ResourceProvider: FeedlyResourceProviding {
@ -38,7 +38,7 @@ final class FeedlyGetStreamContentsOperation: FeedlyOperation, FeedlyEntryProvid
var entries: [FeedlyEntry] { var entries: [FeedlyEntry] {
guard let entries = stream?.items else { guard let entries = stream?.items else {
assert(isFinished, "This should only be called when the operation finishes without error.") // assert(isFinished, "This should only be called when the operation finishes without error.")
assertionFailure("Has this operation been addeded as a dependency on the caller?") assertionFailure("Has this operation been addeded as a dependency on the caller?")
return [] return []
} }
@ -82,7 +82,7 @@ final class FeedlyGetStreamContentsOperation: FeedlyOperation, FeedlyEntryProvid
let log: OSLog let log: OSLog
weak var streamDelegate: FeedlyGetStreamContentsOperationDelegate? weak var streamDelegate: FeedlyGetStreamContentsOperationDelegate?
init(account: Account, resource: FeedlyResourceId, service: FeedlyGetStreamContentsService, continuation: String? = nil, newerThan: Date?, unreadOnly: Bool? = nil, log: OSLog) { init(account: Account, resource: FeedlyResourceId, service: FeedlyGetStreamContentsService, continuation: String? = nil, newerThan: Date?, unreadOnly: Bool? = nil, log: OSLog) {
self.account = account self.account = account
self.resourceProvider = ResourceProvider(resource: resource) self.resourceProvider = ResourceProvider(resource: resource)
@ -98,8 +98,6 @@ final class FeedlyGetStreamContentsOperation: FeedlyOperation, FeedlyEntryProvid
} }
override func run() { override func run() {
super.run()
service.getStreamContents(for: resourceProvider.resource, continuation: continuation, newerThan: newerThan, unreadOnly: unreadOnly) { result in service.getStreamContents(for: resourceProvider.resource, continuation: continuation, newerThan: newerThan, unreadOnly: unreadOnly) { result in
switch result { switch result {
case .success(let stream): case .success(let stream):
@ -111,7 +109,7 @@ final class FeedlyGetStreamContentsOperation: FeedlyOperation, FeedlyEntryProvid
case .failure(let error): case .failure(let error):
os_log(.debug, log: self.log, "Unable to get stream contents: %{public}@.", error as NSError) os_log(.debug, log: self.log, "Unable to get stream contents: %{public}@.", error as NSError)
self.didFinish(error) self.didFinish(with: error)
} }
} }
} }

View File

@ -18,7 +18,6 @@ final class FeedlyGetStreamIdsOperation: FeedlyOperation, FeedlyEntryIdentifierP
var entryIds: Set<String> { var entryIds: Set<String> {
guard let ids = streamIds?.ids else { guard let ids = streamIds?.ids else {
assert(isFinished, "This should only be called when the operation finishes without error.")
assertionFailure("Has this operation been addeded as a dependency on the caller?") assertionFailure("Has this operation been addeded as a dependency on the caller?")
return [] return []
} }
@ -34,7 +33,7 @@ final class FeedlyGetStreamIdsOperation: FeedlyOperation, FeedlyEntryIdentifierP
let unreadOnly: Bool? let unreadOnly: Bool?
let newerThan: Date? let newerThan: Date?
let log: OSLog let log: OSLog
init(account: Account, resource: FeedlyResourceId, service: FeedlyGetStreamIdsService, continuation: String? = nil, newerThan: Date? = nil, unreadOnly: Bool?, log: OSLog) { init(account: Account, resource: FeedlyResourceId, service: FeedlyGetStreamIdsService, continuation: String? = nil, newerThan: Date? = nil, unreadOnly: Bool?, log: OSLog) {
self.account = account self.account = account
self.resource = resource self.resource = resource
@ -48,8 +47,6 @@ final class FeedlyGetStreamIdsOperation: FeedlyOperation, FeedlyEntryIdentifierP
weak var streamIdsDelegate: FeedlyGetStreamIdsOperationDelegate? weak var streamIdsDelegate: FeedlyGetStreamIdsOperationDelegate?
override func run() { override func run() {
super.run()
service.getStreamIds(for: resource, continuation: continuation, newerThan: newerThan, unreadOnly: unreadOnly) { result in service.getStreamIds(for: resource, continuation: continuation, newerThan: newerThan, unreadOnly: unreadOnly) { result in
switch result { switch result {
case .success(let stream): case .success(let stream):
@ -61,7 +58,7 @@ final class FeedlyGetStreamIdsOperation: FeedlyOperation, FeedlyEntryIdentifierP
case .failure(let error): case .failure(let error):
os_log(.debug, log: self.log, "Unable to get stream ids: %{public}@.", error as NSError) os_log(.debug, log: self.log, "Unable to get stream ids: %{public}@.", error as NSError)
self.didFinish(error) self.didFinish(with: error)
} }
} }
} }

View File

@ -14,6 +14,7 @@ import os.log
/// Typically, it pages through the article ids of the global.all stream. /// Typically, it pages through the article ids of the global.all stream.
/// When all the article ids are collected, it is the responsibility of another operation to download them when appropriate. /// When all the article ids are collected, it is the responsibility of another operation to download them when appropriate.
class FeedlyGetUpdatedArticleIdsOperation: FeedlyOperation, FeedlyEntryIdentifierProviding { class FeedlyGetUpdatedArticleIdsOperation: FeedlyOperation, FeedlyEntryIdentifierProviding {
private let account: Account private let account: Account
private let resource: FeedlyResourceId private let resource: FeedlyResourceId
private let service: FeedlyGetStreamIdsService private let service: FeedlyGetStreamIdsService
@ -40,7 +41,6 @@ class FeedlyGetUpdatedArticleIdsOperation: FeedlyOperation, FeedlyEntryIdentifie
private var storedUpdatedArticleIds = Set<String>() private var storedUpdatedArticleIds = Set<String>()
override func run() { override func run() {
super.run()
getStreamIds(nil) getStreamIds(nil)
} }
@ -73,7 +73,7 @@ class FeedlyGetUpdatedArticleIdsOperation: FeedlyOperation, FeedlyEntryIdentifie
getStreamIds(continuation) getStreamIds(continuation)
case .failure(let error): case .failure(let error):
didFinish(error) didFinish(with: error)
} }
} }
} }

View File

@ -10,13 +10,14 @@ import Foundation
import os.log import os.log
import SyncDatabase import SyncDatabase
/// Single responsibility is to clone locally the remote starred article state. /// Clone locally the remote starred article state.
/// ///
/// Typically, it pages through the article ids of the global.saved stream. /// Typically, it pages through the article ids of the global.saved stream.
/// When all the article ids are collected, a status is created for each. /// When all the article ids are collected, a status is created for each.
/// The article ids previously marked as starred but not collected become unstarred. /// The article ids previously marked as starred but not collected become unstarred.
/// So this operation has side effects *for the entire account* it operates on. /// So this operation has side effects *for the entire account* it operates on.
final class FeedlyIngestStarredArticleIdsOperation: FeedlyOperation { final class FeedlyIngestStarredArticleIdsOperation: FeedlyOperation {
private let account: Account private let account: Account
private let resource: FeedlyResourceId private let resource: FeedlyResourceId
private let service: FeedlyGetStreamIdsService private let service: FeedlyGetStreamIdsService
@ -38,7 +39,6 @@ final class FeedlyIngestStarredArticleIdsOperation: FeedlyOperation {
} }
override func run() { override func run() {
super.run()
getStreamIds(nil) getStreamIds(nil)
} }
@ -65,7 +65,7 @@ final class FeedlyIngestStarredArticleIdsOperation: FeedlyOperation {
getStreamIds(continuation) getStreamIds(continuation)
case .failure(let error): case .failure(let error):
didFinish(error) didFinish(with: error)
} }
} }
@ -84,7 +84,7 @@ final class FeedlyIngestStarredArticleIdsOperation: FeedlyOperation {
self.updateStarredStatuses() self.updateStarredStatuses()
case .failure(let error): case .failure(let error):
self.didFinish(error) self.didFinish(with: error)
} }
} }
} }
@ -101,7 +101,7 @@ final class FeedlyIngestStarredArticleIdsOperation: FeedlyOperation {
self.processStarredArticleIDs(localStarredArticleIDs) self.processStarredArticleIDs(localStarredArticleIDs)
case .failure(let error): case .failure(let error):
self.didFinish(error) self.didFinish(with: error)
} }
} }
} }
@ -142,7 +142,7 @@ final class FeedlyIngestStarredArticleIdsOperation: FeedlyOperation {
self.didFinish() self.didFinish()
return return
} }
self.didFinish(error) self.didFinish(with: error)
} }
} }
} }

View File

@ -9,12 +9,13 @@
import Foundation import Foundation
import os.log import os.log
/// Single responsibility is to ensure a status exists for every article id the user might be interested in. /// Ensure a status exists for every article id the user might be interested in.
/// ///
/// Typically, it pages through the article ids of the global.all stream. /// Typically, it pages through the article ids of the global.all stream.
/// As the article ids are collected, a default read status is created for each. /// As the article ids are collected, a default read status is created for each.
/// So this operation has side effects *for the entire account* it operates on. /// So this operation has side effects *for the entire account* it operates on.
class FeedlyIngestStreamArticleIdsOperation: FeedlyOperation { class FeedlyIngestStreamArticleIdsOperation: FeedlyOperation {
private let account: Account private let account: Account
private let resource: FeedlyResourceId private let resource: FeedlyResourceId
private let service: FeedlyGetStreamIdsService private let service: FeedlyGetStreamIdsService
@ -33,7 +34,6 @@ class FeedlyIngestStreamArticleIdsOperation: FeedlyOperation {
} }
override func run() { override func run() {
super.run()
getStreamIds(nil) getStreamIds(nil)
} }
@ -52,7 +52,7 @@ class FeedlyIngestStreamArticleIdsOperation: FeedlyOperation {
account.createStatusesIfNeeded(articleIDs: Set(streamIds.ids)) { databaseError in account.createStatusesIfNeeded(articleIDs: Set(streamIds.ids)) { databaseError in
if let error = databaseError { if let error = databaseError {
self.didFinish(error) self.didFinish(with: error)
return return
} }
@ -65,7 +65,7 @@ class FeedlyIngestStreamArticleIdsOperation: FeedlyOperation {
self.getStreamIds(continuation) self.getStreamIds(continuation)
} }
case .failure(let error): case .failure(let error):
didFinish(error) didFinish(with: error)
} }
} }
} }

View File

@ -11,13 +11,14 @@ import os.log
import RSParser import RSParser
import SyncDatabase import SyncDatabase
/// Single responsibility is to clone locally the remote unread article state. /// Clone locally the remote unread article state.
/// ///
/// Typically, it pages through the unread article ids of the global.all stream. /// Typically, it pages through the unread article ids of the global.all stream.
/// When all the unread article ids are collected, a status is created for each. /// When all the unread article ids are collected, a status is created for each.
/// The article ids previously marked as unread but not collected become read. /// The article ids previously marked as unread but not collected become read.
/// So this operation has side effects *for the entire account* it operates on. /// So this operation has side effects *for the entire account* it operates on.
final class FeedlyIngestUnreadArticleIdsOperation: FeedlyOperation { final class FeedlyIngestUnreadArticleIdsOperation: FeedlyOperation {
private let account: Account private let account: Account
private let resource: FeedlyResourceId private let resource: FeedlyResourceId
private let service: FeedlyGetStreamIdsService private let service: FeedlyGetStreamIdsService
@ -39,7 +40,6 @@ final class FeedlyIngestUnreadArticleIdsOperation: FeedlyOperation {
} }
override func run() { override func run() {
super.run()
getStreamIds(nil) getStreamIds(nil)
} }
@ -66,7 +66,7 @@ final class FeedlyIngestUnreadArticleIdsOperation: FeedlyOperation {
getStreamIds(continuation) getStreamIds(continuation)
case .failure(let error): case .failure(let error):
didFinish(error) didFinish(with: error)
} }
} }
@ -85,7 +85,7 @@ final class FeedlyIngestUnreadArticleIdsOperation: FeedlyOperation {
self.updateUnreadStatuses() self.updateUnreadStatuses()
case .failure(let error): case .failure(let error):
self.didFinish(error) self.didFinish(with: error)
} }
} }
} }
@ -102,7 +102,7 @@ final class FeedlyIngestUnreadArticleIdsOperation: FeedlyOperation {
self.processUnreadArticleIDs(localUnreadArticleIDs) self.processUnreadArticleIDs(localUnreadArticleIDs)
case .failure(let error): case .failure(let error):
self.didFinish(error) self.didFinish(with: error)
} }
} }
} }
@ -142,7 +142,7 @@ final class FeedlyIngestUnreadArticleIdsOperation: FeedlyOperation {
self.didFinish() self.didFinish()
return return
} }
self.didFinish(error) self.didFinish(with: error)
} }
} }
} }

View File

@ -14,6 +14,7 @@ protocol FeedlyLogoutService {
} }
final class FeedlyLogoutOperation: FeedlyOperation { final class FeedlyLogoutOperation: FeedlyOperation {
let service: FeedlyLogoutService let service: FeedlyLogoutService
let account: Account let account: Account
let log: OSLog let log: OSLog
@ -25,7 +26,6 @@ final class FeedlyLogoutOperation: FeedlyOperation {
} }
override func run() { override func run() {
super.run()
os_log("Requesting logout of %{public}@ account.", "\(account.type)") os_log("Requesting logout of %{public}@ account.", "\(account.type)")
service.logout(completion: didCompleteLogout(_:)) service.logout(completion: didCompleteLogout(_:))
} }
@ -45,7 +45,7 @@ final class FeedlyLogoutOperation: FeedlyOperation {
case .failure(let error): case .failure(let error):
os_log("Logout failed because %{public}@.", error as NSError) os_log("Logout failed because %{public}@.", error as NSError)
didFinish(error) didFinish(with: error)
} }
} }
} }

View File

@ -17,7 +17,7 @@ protocol FeedlyFeedsAndFoldersProviding {
var feedsAndFolders: [([FeedlyFeed], Folder)] { get } var feedsAndFolders: [([FeedlyFeed], Folder)] { get }
} }
/// Single responsibility is accurately reflect Collections from Feedly as Folders. /// Reflect Collections from Feedly as Folders.
final class FeedlyMirrorCollectionsAsFoldersOperation: FeedlyOperation, FeedlyCollectionsAndFoldersProviding, FeedlyFeedsAndFoldersProviding { final class FeedlyMirrorCollectionsAsFoldersOperation: FeedlyOperation, FeedlyCollectionsAndFoldersProviding, FeedlyFeedsAndFoldersProviding {
let account: Account let account: Account
@ -26,7 +26,7 @@ final class FeedlyMirrorCollectionsAsFoldersOperation: FeedlyOperation, FeedlyCo
private(set) var collectionsAndFolders = [(FeedlyCollection, Folder)]() private(set) var collectionsAndFolders = [(FeedlyCollection, Folder)]()
private(set) var feedsAndFolders = [([FeedlyFeed], Folder)]() private(set) var feedsAndFolders = [([FeedlyFeed], Folder)]()
init(account: Account, collectionsProvider: FeedlyCollectionProviding, log: OSLog) { init(account: Account, collectionsProvider: FeedlyCollectionProviding, log: OSLog) {
self.collectionsProvider = collectionsProvider self.collectionsProvider = collectionsProvider
self.account = account self.account = account
@ -34,7 +34,6 @@ final class FeedlyMirrorCollectionsAsFoldersOperation: FeedlyOperation, FeedlyCo
} }
override func run() { override func run() {
super.run()
defer { defer {
didFinish() didFinish()
} }

View File

@ -14,60 +14,47 @@ protocol FeedlyOperationDelegate: class {
func feedlyOperation(_ operation: FeedlyOperation, didFailWith error: Error) func feedlyOperation(_ operation: FeedlyOperation, didFailWith error: Error)
} }
/// Abstract class common to all the tasks required to ingest content from Feedly into NetNewsWire. /// Abstract base class for Feedly sync operations.
/// Each task should try to have a single responsibility so they can be easily composed with others. ///
/// Normally we dont do inheritance but in this case
/// its the best option.
class FeedlyOperation: MainThreadOperation { class FeedlyOperation: MainThreadOperation {
weak var delegate: FeedlyOperationDelegate? weak var delegate: FeedlyOperationDelegate?
// MainThreadOperationDelegate
var isCanceled = false {
didSet {
if isCanceled {
cancel()
}
}
}
var id: Int?
weak var operationDelegate: MainThreadOperationDelegate?
var completionBlock: FeedlyOperation.MainThreadOperationCompletionBlock?
var name: String?
var isExecuting = false
var isFinished = false
var downloadProgress: DownloadProgress? { var downloadProgress: DownloadProgress? {
didSet { didSet {
guard downloadProgress == nil || !isExecuting else {
fatalError("\(\FeedlyOperation.downloadProgress) was set to late. Set before operation starts executing.")
}
oldValue?.completeTask() oldValue?.completeTask()
downloadProgress?.addToNumberOfTasksAndRemaining(1) downloadProgress?.addToNumberOfTasksAndRemaining(1)
} }
} }
// Override this. Call super.run() first in the overridden method. // MainThreadOperation
func run() { var isCanceled = false {
isExecuting = true didSet {
if isCanceled {
didCancel()
}
}
} }
var id: Int?
weak var operationDelegate: MainThreadOperationDelegate?
var name: String?
var completionBlock: MainThreadOperation.MainThreadOperationCompletionBlock?
// Called when isCanceled is set to true. Useful to override. func run() {
func cancel() {
didFinish()
} }
func didFinish() { func didFinish() {
precondition(Thread.isMainThread)
isExecuting = false
isFinished = true
downloadProgress = nil
if !isCanceled { if !isCanceled {
operationDelegate?.operationDidComplete(self) operationDelegate?.operationDidComplete(self)
} }
} }
func didFinish(_ error: Error) { func didFinish(with error: Error) {
delegate?.feedlyOperation(self, didFailWith: error) delegate?.feedlyOperation(self, didFailWith: error)
didFinish() didFinish()
} }
func didCancel() {
}
} }

View File

@ -15,8 +15,9 @@ protocol FeedlyParsedItemsByFeedProviding {
var parsedItemsKeyedByFeedId: [String: Set<ParsedItem>] { get } var parsedItemsKeyedByFeedId: [String: Set<ParsedItem>] { get }
} }
/// Single responsibility is to group articles by their feeds. /// Group articles by their feeds.
final class FeedlyOrganiseParsedItemsByFeedOperation: FeedlyOperation, FeedlyParsedItemsByFeedProviding { final class FeedlyOrganiseParsedItemsByFeedOperation: FeedlyOperation, FeedlyParsedItemsByFeedProviding {
private let account: Account private let account: Account
private let parsedItemProvider: FeedlyParsedItemProviding private let parsedItemProvider: FeedlyParsedItemProviding
private let log: OSLog private let log: OSLog
@ -42,8 +43,7 @@ final class FeedlyOrganiseParsedItemsByFeedOperation: FeedlyOperation, FeedlyPar
defer { defer {
didFinish() didFinish()
} }
super.run()
let items = parsedItemProvider.parsedEntries let items = parsedItemProvider.parsedEntries
var dict = [String: Set<ParsedItem>](minimumCapacity: items.count) var dict = [String: Set<ParsedItem>](minimumCapacity: items.count)

View File

@ -11,6 +11,7 @@ import os.log
import RSWeb import RSWeb
final class FeedlyRefreshAccessTokenOperation: FeedlyOperation { final class FeedlyRefreshAccessTokenOperation: FeedlyOperation {
let service: OAuthAccessTokenRefreshing let service: OAuthAccessTokenRefreshing
let oauthClient: OAuthAuthorizationClient let oauthClient: OAuthAuthorizationClient
let account: Account let account: Account
@ -24,7 +25,6 @@ final class FeedlyRefreshAccessTokenOperation: FeedlyOperation {
} }
override func run() { override func run() {
super.run()
let refreshToken: Credentials let refreshToken: Credentials
do { do {
@ -36,7 +36,7 @@ final class FeedlyRefreshAccessTokenOperation: FeedlyOperation {
refreshToken = credentials refreshToken = credentials
} catch { } catch {
didFinish(error) didFinish(with: error)
return return
} }
@ -66,11 +66,11 @@ final class FeedlyRefreshAccessTokenOperation: FeedlyOperation {
didFinish() didFinish()
} catch { } catch {
didFinish(error) didFinish(with: error)
} }
case .failure(let error): case .failure(let error):
didFinish(error) didFinish(with: error)
} }
} }
} }

View File

@ -13,7 +13,7 @@ protocol FeedlyRequestStreamsOperationDelegate: class {
func feedlyRequestStreamsOperation(_ operation: FeedlyRequestStreamsOperation, enqueue collectionStreamOperation: FeedlyGetStreamContentsOperation) func feedlyRequestStreamsOperation(_ operation: FeedlyRequestStreamsOperation, enqueue collectionStreamOperation: FeedlyGetStreamContentsOperation)
} }
/// Single responsibility is to create one stream request operation for one Feedly collection. /// Create one stream request operation for one Feedly collection.
/// This is the start of the process of refreshing the entire contents of a Folder. /// This is the start of the process of refreshing the entire contents of a Folder.
final class FeedlyRequestStreamsOperation: FeedlyOperation { final class FeedlyRequestStreamsOperation: FeedlyOperation {
@ -25,7 +25,7 @@ final class FeedlyRequestStreamsOperation: FeedlyOperation {
let log: OSLog let log: OSLog
let newerThan: Date? let newerThan: Date?
let unreadOnly: Bool? let unreadOnly: Bool?
init(account: Account, collectionsProvider: FeedlyCollectionProviding, newerThan: Date?, unreadOnly: Bool?, service: FeedlyGetStreamContentsService, log: OSLog) { init(account: Account, collectionsProvider: FeedlyCollectionProviding, newerThan: Date?, unreadOnly: Bool?, service: FeedlyGetStreamContentsService, log: OSLog) {
self.account = account self.account = account
self.service = service self.service = service
@ -36,7 +36,6 @@ final class FeedlyRequestStreamsOperation: FeedlyOperation {
} }
override func run() { override func run() {
super.run()
defer { defer {
didFinish() didFinish()
} }

View File

@ -16,15 +16,15 @@ protocol FeedlySearchOperationDelegate: class {
func feedlySearchOperation(_ operation: FeedlySearchOperation, didGet response: FeedlyFeedsSearchResponse) func feedlySearchOperation(_ operation: FeedlySearchOperation, didGet response: FeedlyFeedsSearchResponse)
} }
/// Single responsibility is to find one and only one feed for a given query (usually, a URL). /// Find one and only one feed for a given query (usually, a URL).
/// What happens when a feed is found for the URL is delegated to the `searchDelegate`. /// What happens when a feed is found for the URL is delegated to the `searchDelegate`.
class FeedlySearchOperation: FeedlyOperation { class FeedlySearchOperation: FeedlyOperation {
let query: String let query: String
let locale: Locale let locale: Locale
let searchService: FeedlySearchService let searchService: FeedlySearchService
weak var searchDelegate: FeedlySearchOperationDelegate? weak var searchDelegate: FeedlySearchOperationDelegate?
init(query: String, locale: Locale = .current, service: FeedlySearchService) { init(query: String, locale: Locale = .current, service: FeedlySearchService) {
self.query = query self.query = query
self.locale = locale self.locale = locale
@ -32,8 +32,6 @@ class FeedlySearchOperation: FeedlyOperation {
} }
override func run() { override func run() {
super.run()
searchService.getFeeds(for: query, count: 1, locale: locale.identifier) { result in searchService.getFeeds(for: query, count: 1, locale: locale.identifier) { result in
switch result { switch result {
case .success(let response): case .success(let response):
@ -42,7 +40,7 @@ class FeedlySearchOperation: FeedlyOperation {
self.didFinish() self.didFinish()
case .failure(let error): case .failure(let error):
self.didFinish(error) self.didFinish(with: error)
} }
} }
} }

View File

@ -11,12 +11,14 @@ import Articles
import SyncDatabase import SyncDatabase
import os.log import os.log
/// Single responsibility is to take changes to statuses of articles locally and apply them to the corresponding the articles remotely.
/// Take changes to statuses of articles locally and apply them to the corresponding the articles remotely.
final class FeedlySendArticleStatusesOperation: FeedlyOperation { final class FeedlySendArticleStatusesOperation: FeedlyOperation {
private let database: SyncDatabase private let database: SyncDatabase
private let log: OSLog private let log: OSLog
private let service: FeedlyMarkArticlesService private let service: FeedlyMarkArticlesService
init(database: SyncDatabase, service: FeedlyMarkArticlesService, log: OSLog) { init(database: SyncDatabase, service: FeedlyMarkArticlesService, log: OSLog) {
self.database = database self.database = database
self.service = service self.service = service
@ -24,7 +26,6 @@ final class FeedlySendArticleStatusesOperation: FeedlyOperation {
} }
override func run() { override func run() {
super.run()
os_log(.debug, log: log, "Sending article statuses...") os_log(.debug, log: log, "Sending article statuses...")
database.selectForProcessing { result in database.selectForProcessing { result in

View File

@ -12,8 +12,9 @@ import SyncDatabase
import RSWeb import RSWeb
import RSCore import RSCore
/// Single responsibility is to compose the operations necessary to get the entire set of articles, feeds and folders with the statuses the user expects between now and a certain date in the past. /// Compose the operations necessary to get the entire set of articles, feeds and folders with the statuses the user expects between now and a certain date in the past.
final class FeedlySyncAllOperation: FeedlyOperation { final class FeedlySyncAllOperation: FeedlyOperation {
private let operationQueue = MainThreadOperationQueue() private let operationQueue = MainThreadOperationQueue()
private let log: OSLog private let log: OSLog
let syncUUID: UUID let syncUUID: UUID
@ -51,31 +52,31 @@ final class FeedlySyncAllOperation: FeedlyOperation {
let getCollections = FeedlyGetCollectionsOperation(service: getCollectionsService, log: log) let getCollections = FeedlyGetCollectionsOperation(service: getCollectionsService, log: log)
getCollections.delegate = self getCollections.delegate = self
getCollections.downloadProgress = downloadProgress getCollections.downloadProgress = downloadProgress
self.operationQueue.make(getCollections, dependOn: sendArticleStatuses) getCollections.addDependency(sendArticleStatuses)
self.operationQueue.addOperation(getCollections) self.operationQueue.addOperation(getCollections)
// Ensure a folder exists for each Collection, removing Folders without a corresponding Collection. // Ensure a folder exists for each Collection, removing Folders without a corresponding Collection.
let mirrorCollectionsAsFolders = FeedlyMirrorCollectionsAsFoldersOperation(account: account, collectionsProvider: getCollections, log: log) let mirrorCollectionsAsFolders = FeedlyMirrorCollectionsAsFoldersOperation(account: account, collectionsProvider: getCollections, log: log)
mirrorCollectionsAsFolders.delegate = self mirrorCollectionsAsFolders.delegate = self
self.operationQueue.make(mirrorCollectionsAsFolders, dependOn: getCollections) mirrorCollectionsAsFolders.addDependency(getCollections)
self.operationQueue.addOperation(mirrorCollectionsAsFolders) self.operationQueue.addOperation(mirrorCollectionsAsFolders)
// Ensure feeds are created and grouped by their folders. // Ensure feeds are created and grouped by their folders.
let createFeedsOperation = FeedlyCreateFeedsForCollectionFoldersOperation(account: account, feedsAndFoldersProvider: mirrorCollectionsAsFolders, log: log) let createFeedsOperation = FeedlyCreateFeedsForCollectionFoldersOperation(account: account, feedsAndFoldersProvider: mirrorCollectionsAsFolders, log: log)
createFeedsOperation.delegate = self createFeedsOperation.delegate = self
self.operationQueue.make(createFeedsOperation, dependOn: mirrorCollectionsAsFolders) createFeedsOperation.addDependency(mirrorCollectionsAsFolders)
self.operationQueue.addOperation(createFeedsOperation) self.operationQueue.addOperation(createFeedsOperation)
let getAllArticleIds = FeedlyIngestStreamArticleIdsOperation(account: account, credentials: credentials, service: getStreamIdsService, log: log) let getAllArticleIds = FeedlyIngestStreamArticleIdsOperation(account: account, credentials: credentials, service: getStreamIdsService, log: log)
getAllArticleIds.delegate = self getAllArticleIds.delegate = self
getAllArticleIds.downloadProgress = downloadProgress getAllArticleIds.downloadProgress = downloadProgress
self.operationQueue.make(getAllArticleIds, dependOn: createFeedsOperation) getAllArticleIds.addDependency(createFeedsOperation)
self.operationQueue.addOperation(getAllArticleIds) self.operationQueue.addOperation(getAllArticleIds)
// Get each page of unread article ids in the global.all stream for the last 31 days (nil = Feedly API default). // Get each page of unread article ids in the global.all stream for the last 31 days (nil = Feedly API default).
let getUnread = FeedlyIngestUnreadArticleIdsOperation(account: account, credentials: credentials, service: getUnreadService, database: database, newerThan: nil, log: log) let getUnread = FeedlyIngestUnreadArticleIdsOperation(account: account, credentials: credentials, service: getUnreadService, database: database, newerThan: nil, log: log)
getUnread.delegate = self getUnread.delegate = self
self.operationQueue.make(getUnread, dependOn: getAllArticleIds) getUnread.addDependency(getAllArticleIds)
getUnread.downloadProgress = downloadProgress getUnread.downloadProgress = downloadProgress
self.operationQueue.addOperation(getUnread) self.operationQueue.addOperation(getUnread)
@ -84,24 +85,24 @@ final class FeedlySyncAllOperation: FeedlyOperation {
let getUpdated = FeedlyGetUpdatedArticleIdsOperation(account: account, credentials: credentials, service: getStreamIdsService, newerThan: lastSuccessfulFetchStartDate, log: log) let getUpdated = FeedlyGetUpdatedArticleIdsOperation(account: account, credentials: credentials, service: getStreamIdsService, newerThan: lastSuccessfulFetchStartDate, log: log)
getUpdated.delegate = self getUpdated.delegate = self
getUpdated.downloadProgress = downloadProgress getUpdated.downloadProgress = downloadProgress
self.operationQueue.make(getUpdated, dependOn: createFeedsOperation) getUpdated.addDependency(createFeedsOperation)
self.operationQueue.addOperation(getUpdated) self.operationQueue.addOperation(getUpdated)
// Get each page of the article ids for starred articles. // Get each page of the article ids for starred articles.
let getStarred = FeedlyIngestStarredArticleIdsOperation(account: account, credentials: credentials, service: getStarredService, database: database, newerThan: nil, log: log) let getStarred = FeedlyIngestStarredArticleIdsOperation(account: account, credentials: credentials, service: getStarredService, database: database, newerThan: nil, log: log)
getStarred.delegate = self getStarred.delegate = self
getStarred.downloadProgress = downloadProgress getStarred.downloadProgress = downloadProgress
self.operationQueue.make(getStarred, dependOn: createFeedsOperation) getStarred.addDependency(createFeedsOperation)
self.operationQueue.addOperation(getStarred) self.operationQueue.addOperation(getStarred)
// Now all the possible article ids we need have a status, fetch the article ids for missing articles. // Now all the possible article ids we need have a status, fetch the article ids for missing articles.
let getMissingIds = FeedlyFetchIdsForMissingArticlesOperation(account: account, log: log) let getMissingIds = FeedlyFetchIdsForMissingArticlesOperation(account: account, log: log)
getMissingIds.delegate = self getMissingIds.delegate = self
getMissingIds.downloadProgress = downloadProgress getMissingIds.downloadProgress = downloadProgress
self.operationQueue.make(getMissingIds, dependOn: getAllArticleIds) getMissingIds.addDependency(getAllArticleIds)
self.operationQueue.make(getMissingIds, dependOn: getUnread) getMissingIds.addDependency(getUnread)
self.operationQueue.make(getMissingIds, dependOn: getStarred) getMissingIds.addDependency(getStarred)
self.operationQueue.make(getMissingIds, dependOn: getUpdated) getMissingIds.addDependency(getUpdated)
self.operationQueue.addOperation(getMissingIds) self.operationQueue.addOperation(getMissingIds)
// Download all the missing and updated articles // Download all the missing and updated articles
@ -112,15 +113,15 @@ final class FeedlySyncAllOperation: FeedlyOperation {
log: log) log: log)
downloadMissingArticles.delegate = self downloadMissingArticles.delegate = self
downloadMissingArticles.downloadProgress = downloadProgress downloadMissingArticles.downloadProgress = downloadProgress
self.operationQueue.make(downloadMissingArticles, dependOn: getMissingIds) downloadMissingArticles.addDependency(getMissingIds)
self.operationQueue.make(downloadMissingArticles, dependOn: getUpdated) downloadMissingArticles.addDependency(getUpdated)
self.operationQueue.addOperation(downloadMissingArticles) self.operationQueue.addOperation(downloadMissingArticles)
// Once this operation's dependencies, their dependencies etc finish, we can finish. // Once this operation's dependencies, their dependencies etc finish, we can finish.
let finishOperation = FeedlyCheckpointOperation() let finishOperation = FeedlyCheckpointOperation()
finishOperation.checkpointDelegate = self finishOperation.checkpointDelegate = self
finishOperation.downloadProgress = downloadProgress finishOperation.downloadProgress = downloadProgress
self.operationQueue.make(finishOperation, dependOn: downloadMissingArticles) finishOperation.addDependency(downloadMissingArticles)
self.operationQueue.addOperation(finishOperation) self.operationQueue.addOperation(finishOperation)
} }
@ -128,23 +129,16 @@ final class FeedlySyncAllOperation: FeedlyOperation {
self.init(account: account, credentials: credentials, lastSuccessfulFetchStartDate: lastSuccessfulFetchStartDate, markArticlesService: caller, getUnreadService: caller, getCollectionsService: caller, getStreamContentsService: caller, getStarredService: caller, getStreamIdsService: caller, getEntriesService: caller, database: database, downloadProgress: downloadProgress, log: log) self.init(account: account, credentials: credentials, lastSuccessfulFetchStartDate: lastSuccessfulFetchStartDate, markArticlesService: caller, getUnreadService: caller, getCollectionsService: caller, getStreamContentsService: caller, getStarredService: caller, getStreamIdsService: caller, getEntriesService: caller, database: database, downloadProgress: downloadProgress, log: log)
} }
override func cancel() {
os_log(.debug, log: log, "Cancelling sync %{public}@", syncUUID.uuidString)
self.operationQueue.cancelAllOperations()
super.cancel()
didFinish()
// Operation should silently cancel.
syncCompletionHandler = nil
}
override func run() { override func run() {
super.run()
os_log(.debug, log: log, "Starting sync %{public}@", syncUUID.uuidString) os_log(.debug, log: log, "Starting sync %{public}@", syncUUID.uuidString)
operationQueue.resume() operationQueue.resume()
} }
override func didCancel() {
os_log(.debug, log: log, "Cancelling sync %{public}@", syncUUID.uuidString)
self.operationQueue.cancelAllOperations()
syncCompletionHandler = nil
}
} }
extension FeedlySyncAllOperation: FeedlyCheckpointOperationDelegate { extension FeedlySyncAllOperation: FeedlyCheckpointOperationDelegate {

View File

@ -10,8 +10,10 @@ import Foundation
import os.log import os.log
import RSParser import RSParser
import RSCore import RSCore
import RSWeb
final class FeedlySyncStreamContentsOperation: FeedlyOperation, FeedlyOperationDelegate, FeedlyGetStreamContentsOperationDelegate, FeedlyCheckpointOperationDelegate { final class FeedlySyncStreamContentsOperation: FeedlyOperation, FeedlyOperationDelegate, FeedlyGetStreamContentsOperationDelegate, FeedlyCheckpointOperationDelegate {
private let account: Account private let account: Account
private let resource: FeedlyResourceId private let resource: FeedlyResourceId
private let operationQueue = MainThreadOperationQueue() private let operationQueue = MainThreadOperationQueue()
@ -43,18 +45,15 @@ final class FeedlySyncStreamContentsOperation: FeedlyOperation, FeedlyOperationD
self.init(account: account, resource: all, service: service, isPagingEnabled: true, newerThan: newerThan, log: log) self.init(account: account, resource: all, service: service, isPagingEnabled: true, newerThan: newerThan, log: log)
} }
override func cancel() {
os_log(.debug, log: log, "Canceling sync stream contents")
operationQueue.cancelAllOperations()
super.cancel()
didFinish()
}
override func run() { override func run() {
super.run()
operationQueue.resume() operationQueue.resume()
} }
override func didCancel() {
os_log(.debug, log: log, "Canceling sync stream contents")
operationQueue.cancelAllOperations()
}
func enqueueOperations(for continuation: String?) { func enqueueOperations(for continuation: String?) {
os_log(.debug, log: log, "Requesting page for %@", resource.id) os_log(.debug, log: log, "Requesting page for %@", resource.id)
let operations = pageOperations(for: continuation) let operations = pageOperations(for: continuation)
@ -70,24 +69,20 @@ final class FeedlySyncStreamContentsOperation: FeedlyOperation, FeedlyOperationD
log: log) log: log)
let organiseByFeed = FeedlyOrganiseParsedItemsByFeedOperation(account: account, let organiseByFeed = FeedlyOrganiseParsedItemsByFeedOperation(account: account, parsedItemProvider: getPage, log: log)
parsedItemProvider: getPage,
log: log)
let updateAccount = FeedlyUpdateAccountFeedsWithItemsOperation(account: account, let updateAccount = FeedlyUpdateAccountFeedsWithItemsOperation(account: account, organisedItemsProvider: organiseByFeed, log: log)
organisedItemsProvider: organiseByFeed,
log: log)
getPage.delegate = self getPage.delegate = self
getPage.streamDelegate = self getPage.streamDelegate = self
operationQueue.make(organiseByFeed, dependOn: getPage) organiseByFeed.addDependency(getPage)
organiseByFeed.delegate = self organiseByFeed.delegate = self
operationQueue.make(updateAccount, dependOn: organiseByFeed) updateAccount.addDependency(organiseByFeed)
updateAccount.delegate = self updateAccount.delegate = self
operationQueue.make(finishOperation, dependOn: updateAccount) finishOperation.addDependency(updateAccount)
return [getPage, organiseByFeed, updateAccount] return [getPage, organiseByFeed, updateAccount]
} }
@ -115,6 +110,6 @@ final class FeedlySyncStreamContentsOperation: FeedlyOperation, FeedlyOperationD
func feedlyOperation(_ operation: FeedlyOperation, didFailWith error: Error) { func feedlyOperation(_ operation: FeedlyOperation, didFailWith error: Error) {
operationQueue.cancelAllOperations() operationQueue.cancelAllOperations()
didFinish(error) didFinish(with: error)
} }
} }

View File

@ -10,12 +10,13 @@ import Foundation
import RSParser import RSParser
import os.log import os.log
/// Single responsibility is to combine the articles with their feeds for a specific account. /// Combine the articles with their feeds for a specific account.
final class FeedlyUpdateAccountFeedsWithItemsOperation: FeedlyOperation { final class FeedlyUpdateAccountFeedsWithItemsOperation: FeedlyOperation {
private let account: Account private let account: Account
private let organisedItemsProvider: FeedlyParsedItemsByFeedProviding private let organisedItemsProvider: FeedlyParsedItemsByFeedProviding
private let log: OSLog private let log: OSLog
init(account: Account, organisedItemsProvider: FeedlyParsedItemsByFeedProviding, log: OSLog) { init(account: Account, organisedItemsProvider: FeedlyParsedItemsByFeedProviding, log: OSLog) {
self.account = account self.account = account
self.organisedItemsProvider = organisedItemsProvider self.organisedItemsProvider = organisedItemsProvider
@ -23,12 +24,11 @@ final class FeedlyUpdateAccountFeedsWithItemsOperation: FeedlyOperation {
} }
override func run() { override func run() {
super.run()
let webFeedIDsAndItems = organisedItemsProvider.parsedItemsKeyedByFeedId let webFeedIDsAndItems = organisedItemsProvider.parsedItemsKeyedByFeedId
account.update(webFeedIDsAndItems: webFeedIDsAndItems, defaultRead: true) { databaseError in account.update(webFeedIDsAndItems: webFeedIDsAndItems, defaultRead: true) { databaseError in
if let error = databaseError { if let error = databaseError {
self.didFinish(error) self.didFinish(with: error)
return return
} }