Use MainThreadOperationQueue with Feedly syncing.

This commit is contained in:
Brent Simmons 2020-01-15 21:30:37 -08:00
parent cbc24e3a2e
commit 39db00c022
29 changed files with 197 additions and 310 deletions

View File

@ -177,7 +177,6 @@
9EF1B10723590D61000A486A /* FeedlyGetStreamIdsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF1B10623590D61000A486A /* FeedlyGetStreamIdsOperation.swift */; };
9EF1B10923590E93000A486A /* FeedlyStreamIds.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF1B10823590E93000A486A /* FeedlyStreamIds.swift */; };
9EF2602C23C91FFE006D160C /* FeedlyGetUpdatedArticleIdsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF2602B23C91FFE006D160C /* FeedlyGetUpdatedArticleIdsOperation.swift */; };
9EF35F7A234E830E003AE2AE /* FeedlyCompoundOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF35F79234E830E003AE2AE /* FeedlyCompoundOperation.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -839,11 +838,11 @@
848934F51F62484F00CEBD24 = {
CreatedOnToolsVersion = 9.0;
LastSwiftMigration = 0900;
ProvisioningStyle = Automatic;
ProvisioningStyle = Manual;
};
848934FE1F62484F00CEBD24 = {
CreatedOnToolsVersion = 9.0;
ProvisioningStyle = Automatic;
ProvisioningStyle = Manual;
};
};
};
@ -975,7 +974,6 @@
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 */,

View File

@ -57,16 +57,14 @@ final class FeedlyAccountDelegate: AccountDelegate {
private let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "Feedly")
private let database: SyncDatabase
private weak var currentSyncAllOperation: FeedlySyncAllOperation?
private let operationQueue: OperationQueue
private weak var currentSyncAllOperation: MainThreadOperation?
private let operationQueue = MainThreadOperationQueue()
init(dataFolder: String, transport: Transport?, api: FeedlyAPICaller.API) {
self.operationQueue = OperationQueue()
// Many operations have their own operation queues, such as the sync all operation.
// Making this a serial queue at this higher level of abstraction means we can ensure,
// for example, a `FeedlyRefreshAccessTokenOperation` occurs before a `FeedlySyncAllOperation`,
// improving our ability to debug, reason about and predict the behaviour of the code.
self.operationQueue.maxConcurrentOperationCount = 1
if let transport = transport {
self.caller = FeedlyAPICaller(transport: transport, api: api)
@ -135,7 +133,8 @@ final class FeedlyAccountDelegate: AccountDelegate {
func sendArticleStatus(for account: Account, completion: @escaping ((Result<Void, Error>) -> Void)) {
// Ensure remote articles have the same status as they do locally.
let send = FeedlySendArticleStatusesOperation(database: database, service: caller, log: log)
send.completionBlock = {
send.completionBlock = { operation in
// TODO: not call with success if operation was canceled? Not sure.
DispatchQueue.main.async {
completion(.success(()))
}
@ -159,7 +158,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
let ingestUnread = FeedlyIngestUnreadArticleIdsOperation(account: account, credentials: credentials, service: caller, database: database, newerThan: nil, log: log)
group.enter()
ingestUnread.completionBlock = {
ingestUnread.completionBlock = { _ in
group.leave()
}
@ -167,7 +166,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
let ingestStarred = FeedlyIngestStarredArticleIdsOperation(account: account, credentials: credentials, service: caller, database: database, newerThan: nil, log: log)
group.enter()
ingestStarred.completionBlock = {
ingestStarred.completionBlock = { _ in
group.leave()
}
@ -175,7 +174,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
completion(.success(()))
}
operationQueue.addOperations([ingestUnread, ingestStarred], waitUntilFinished: false)
operationQueue.addOperations([ingestUnread, ingestStarred])
}
func importOPML(for account: Account, opmlFile: URL, completion: @escaping (Result<Void, Error>) -> Void) {
@ -500,8 +499,8 @@ final class FeedlyAccountDelegate: AccountDelegate {
func accountWillBeDeleted(_ account: Account) {
let logout = FeedlyLogoutOperation(account: account, service: caller, log: log)
// Dispatch on the main queue because the lifetime of the account delegate is uncertain.
OperationQueue.main.addOperation(logout)
// Dispatch on the shared queue because the lifetime of the account delegate is uncertain.
MainThreadOperationQueue.shared.addOperation(logout)
}
static func validateCredentials(transport: Transport, credentials: Credentials, endpoint: URL?, completion: @escaping (Result<Credentials?, Error>) -> Void) {

View File

@ -9,9 +9,10 @@
import Foundation
import os.log
import RSWeb
import RSCore
class FeedlyAddExistingFeedOperation: FeedlyOperation, FeedlyOperationDelegate, FeedlyCheckpointOperationDelegate {
private let operationQueue: OperationQueue
private let operationQueue: MainThreadOperationQueue
var addCompletionHandler: ((Result<Void, Error>) -> ())?
@ -20,8 +21,8 @@ class FeedlyAddExistingFeedOperation: FeedlyOperation, FeedlyOperationDelegate,
let validator = FeedlyFeedContainerValidator(container: container, userId: credentials.username)
let (folder, collectionId) = try validator.getValidContainer()
self.operationQueue = OperationQueue()
self.operationQueue.isSuspended = true
self.operationQueue = MainThreadOperationQueue()
self.operationQueue.suspend()
super.init()
@ -34,13 +35,13 @@ class FeedlyAddExistingFeedOperation: FeedlyOperation, FeedlyOperationDelegate,
let createFeeds = FeedlyCreateFeedsForCollectionFoldersOperation(account: account, feedsAndFoldersProvider: addRequest, log: log)
createFeeds.downloadProgress = progress
createFeeds.addDependency(addRequest)
self.operationQueue.make(createFeeds, dependOn: addRequest)
self.operationQueue.addOperation(createFeeds)
let finishOperation = FeedlyCheckpointOperation()
finishOperation.checkpointDelegate = self
finishOperation.downloadProgress = progress
finishOperation.addDependency(createFeeds)
self.operationQueue.make(finishOperation, dependOn: createFeeds)
self.operationQueue.addOperation(finishOperation)
}
@ -50,11 +51,9 @@ class FeedlyAddExistingFeedOperation: FeedlyOperation, FeedlyOperationDelegate,
didFinish()
}
override func main() {
guard !isCancelled else {
return
}
operationQueue.isSuspended = false
override func run() {
super.run()
operationQueue.resume()
}
func feedlyOperation(_ operation: FeedlyOperation, didFailWith error: Error) {
@ -65,7 +64,7 @@ class FeedlyAddExistingFeedOperation: FeedlyOperation, FeedlyOperationDelegate,
}
func feedlyCheckpointOperationDidReachCheckpoint(_ operation: FeedlyCheckpointOperation) {
guard !isCancelled else {
guard !isCanceled else {
return
}

View File

@ -35,17 +35,16 @@ final class FeedlyAddFeedToCollectionOperation: FeedlyOperation, FeedlyFeedsAndF
return feedResource
}
override func main() {
guard !isCancelled else {
return didFinish()
}
override func run() {
super.run()
service.addFeed(with: feedResource, title: feedName, toCollectionWith: collectionId) { [weak self] result in
guard let self = self else {
return
}
guard !self.isCancelled else {
return self.didFinish()
if self.isCanceled {
self.didFinish()
return
}
self.didCompleteRequest(result)
}

View File

@ -10,9 +10,10 @@ import Foundation
import os.log
import SyncDatabase
import RSWeb
import RSCore
class FeedlyAddNewFeedOperation: FeedlyOperation, FeedlyOperationDelegate, FeedlySearchOperationDelegate, FeedlyCheckpointOperationDelegate {
private let operationQueue: OperationQueue
private let operationQueue: MainThreadOperationQueue
private let folder: Folder
private let collectionId: String
private let url: String
@ -26,15 +27,15 @@ class FeedlyAddNewFeedOperation: FeedlyOperation, FeedlyOperationDelegate, Feedl
private let log: OSLog
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 {
let validator = FeedlyFeedContainerValidator(container: container, userId: credentials.username)
(self.folder, self.collectionId) = try validator.getValidContainer()
self.url = url
self.operationQueue = OperationQueue()
self.operationQueue.isSuspended = true
self.operationQueue = MainThreadOperationQueue()
self.operationQueue.suspend()
self.account = account
self.credentials = credentials
self.database = database
@ -55,27 +56,21 @@ class FeedlyAddNewFeedOperation: FeedlyOperation, FeedlyOperationDelegate, Feedl
self.operationQueue.addOperation(search)
}
override func run() {
super.run()
operationQueue.resume()
}
override func cancel() {
operationQueue.cancelAllOperations()
super.cancel()
didFinish()
// Operation should silently cancel.
operationQueue.cancelAllOperations()
addCompletionHandler = nil
}
override func main() {
guard !isCancelled else {
return
}
operationQueue.isSuspended = false
}
private var feedResourceId: FeedlyFeedResourceId?
func feedlySearchOperation(_ operation: FeedlySearchOperation, didGet response: FeedlyFeedsSearchResponse) {
guard !isCancelled else {
guard !isCanceled else {
return
}
guard let first = response.results.first else {
@ -91,24 +86,24 @@ class FeedlyAddNewFeedOperation: FeedlyOperation, FeedlyOperationDelegate, Feedl
self.operationQueue.addOperation(addRequest)
let createFeeds = FeedlyCreateFeedsForCollectionFoldersOperation(account: account, feedsAndFoldersProvider: addRequest, log: log)
createFeeds.addDependency(addRequest)
operationQueue.make(createFeeds, dependOn: addRequest)
createFeeds.downloadProgress = downloadProgress
self.operationQueue.addOperation(createFeeds)
let syncUnread = FeedlyIngestUnreadArticleIdsOperation(account: account, credentials: credentials, service: syncUnreadIdsService, database: database, newerThan: nil, log: log)
syncUnread.addDependency(createFeeds)
operationQueue.make(syncUnread, dependOn: createFeeds)
syncUnread.downloadProgress = downloadProgress
self.operationQueue.addOperation(syncUnread)
let syncFeed = FeedlySyncStreamContentsOperation(account: account, resource: feedResourceId, service: getStreamContentsService, newerThan: nil, log: log)
syncFeed.addDependency(syncUnread)
operationQueue.make(syncFeed, dependOn: syncUnread)
syncFeed.downloadProgress = downloadProgress
self.operationQueue.addOperation(syncFeed)
let finishOperation = FeedlyCheckpointOperation()
finishOperation.checkpointDelegate = self
finishOperation.downloadProgress = downloadProgress
finishOperation.addDependency(syncFeed)
operationQueue.make(finishOperation, dependOn: syncFeed)
self.operationQueue.addOperation(finishOperation)
}
@ -120,7 +115,7 @@ class FeedlyAddNewFeedOperation: FeedlyOperation, FeedlyOperationDelegate, Feedl
}
func feedlyCheckpointOperationDidReachCheckpoint(_ operation: FeedlyCheckpointOperation) {
guard !isCancelled else {
guard !isCanceled else {
return
}

View File

@ -17,10 +17,9 @@ final class FeedlyCheckpointOperation: FeedlyOperation {
weak var checkpointDelegate: FeedlyCheckpointOperationDelegate?
override func main() {
defer { didFinish() }
guard !isCancelled else {
return
override func run() {
defer {
didFinish()
}
checkpointDelegate?.feedlyCheckpointOperationDidReachCheckpoint(self)
}

View File

@ -22,10 +22,10 @@ final class FeedlyCreateFeedsForCollectionFoldersOperation: FeedlyOperation {
self.log = log
}
override func main() {
defer { didFinish() }
guard !isCancelled else { return }
override func run() {
defer {
didFinish()
}
let pairs = feedsAndFoldersProvider.feedsAndFolders

View File

@ -8,6 +8,7 @@
import Foundation
import os.log
import RSCore
class FeedlyDownloadArticlesOperation: FeedlyOperation {
private let account: Account
@ -15,13 +16,12 @@ class FeedlyDownloadArticlesOperation: FeedlyOperation {
private let missingArticleEntryIdProvider: FeedlyEntryIdentifierProviding
private let updatedArticleEntryIdProvider: FeedlyEntryIdentifierProviding
private let getEntriesService: FeedlyGetEntriesService
private let operationQueue: OperationQueue
private let operationQueue = MainThreadOperationQueue()
private let finishOperation: FeedlyCheckpointOperation
init(account: Account, missingArticleEntryIdProvider: FeedlyEntryIdentifierProviding, updatedArticleEntryIdProvider: FeedlyEntryIdentifierProviding, getEntriesService: FeedlyGetEntriesService, log: OSLog) {
self.account = account
self.operationQueue = OperationQueue()
self.operationQueue.isSuspended = true
self.operationQueue.suspend()
self.missingArticleEntryIdProvider = missingArticleEntryIdProvider
self.updatedArticleEntryIdProvider = updatedArticleEntryIdProvider
self.getEntriesService = getEntriesService
@ -35,18 +35,16 @@ class FeedlyDownloadArticlesOperation: FeedlyOperation {
}
override func cancel() {
os_log(.debug, log: log, "Cancelling %{public}@.", self)
// 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 main() {
guard !isCancelled else {
// override of cancel calls didFinish().
return
}
override func run() {
super.run()
var articleIds = missingArticleEntryIdProvider.entryIds
articleIds.formUnion(updatedArticleEntryIdProvider.entryIds)
@ -64,7 +62,7 @@ class FeedlyDownloadArticlesOperation: FeedlyOperation {
parsedItemProvider: getEntries,
log: log)
organiseByFeed.delegate = self
organiseByFeed.addDependency(getEntries)
self.operationQueue.make(organiseByFeed, dependOn: getEntries)
self.operationQueue.addOperation(organiseByFeed)
let updateAccount = FeedlyUpdateAccountFeedsWithItemsOperation(account: account,
@ -72,13 +70,13 @@ class FeedlyDownloadArticlesOperation: FeedlyOperation {
log: log)
updateAccount.delegate = self
updateAccount.addDependency(organiseByFeed)
self.operationQueue.make(updateAccount, dependOn: organiseByFeed)
self.operationQueue.addOperation(updateAccount)
finishOperation.addDependency(updateAccount)
self.operationQueue.make(finishOperation, dependOn: updateAccount)
}
operationQueue.isSuspended = false
operationQueue.resume()
}
}
@ -93,7 +91,8 @@ extension FeedlyDownloadArticlesOperation: FeedlyOperationDelegate {
func feedlyOperation(_ operation: FeedlyOperation, didFailWith error: Error) {
assert(Thread.isMainThread)
os_log(.debug, log: log, "%{public}@ failed with error: %{public}@.", operation, error as NSError)
// TODO: fix error for below line "Error is not convertible to NSError"
//os_log(.debug, log: log, "%{public}@ failed with error: %{public}@.", operation, error as NSError)
cancel()
}
}

View File

@ -20,12 +20,7 @@ final class FeedlyFetchIdsForMissingArticlesOperation: FeedlyOperation, FeedlyEn
self.log = log
}
override func main() {
guard !isCancelled else {
didFinish()
return
}
override func run() {
account.fetchArticleIDsForStatusesWithoutArticlesNewerThanCutoffDate { result in
switch result {
case .success(let articleIds):

View File

@ -26,11 +26,8 @@ final class FeedlyGetCollectionsOperation: FeedlyOperation, FeedlyCollectionProv
self.log = log
}
override func main() {
guard !isCancelled else {
didFinish()
return
}
override func run() {
super.run()
os_log(.debug, log: log, "Requesting collections.")

View File

@ -37,12 +37,13 @@ final class FeedlyGetEntriesOperation: FeedlyOperation, FeedlyEntryProviding, Fe
FeedlyEntryParser(entry: $0).parsedItemRepresentation
})
if parsed.count != entries.count {
let entryIds = Set(entries.map { $0.id })
let parsedIds = Set(parsed.map { $0.uniqueID })
let difference = entryIds.subtracting(parsedIds)
os_log(.debug, log: log, "%{public}@ dropping articles with ids: %{public}@.", self, difference)
}
// TODO: Fix the below. Theres an error on the os.log line: "Expression type '()' is ambiguous without more context"
// if parsed.count != entries.count {
// let entryIds = Set(entries.map { $0.id })
// let parsedIds = Set(parsed.map { $0.uniqueID })
// let difference = entryIds.subtracting(parsedIds)
// os_log(.debug, log: log, "%{public}@ dropping articles with ids: %{public}@.", self, difference)
// }
storedParsedEntries = parsed
@ -53,11 +54,7 @@ final class FeedlyGetEntriesOperation: FeedlyOperation, FeedlyEntryProviding, Fe
return name ?? String(describing: Self.self)
}
override func main() {
guard !isCancelled else {
didFinish()
return
}
override func run() {
service.getEntries(for: provider.entryIds) { result in
switch result {

View File

@ -97,11 +97,8 @@ final class FeedlyGetStreamContentsOperation: FeedlyOperation, FeedlyEntryProvid
self.init(account: account, resource: resourceProvider.resource, service: service, newerThan: newerThan, unreadOnly: unreadOnly, log: log)
}
override func main() {
guard !isCancelled else {
didFinish()
return
}
override func run() {
super.run()
service.getStreamContents(for: resourceProvider.resource, continuation: continuation, newerThan: newerThan, unreadOnly: unreadOnly) { result in
switch result {

View File

@ -47,11 +47,8 @@ final class FeedlyGetStreamIdsOperation: FeedlyOperation, FeedlyEntryIdentifierP
weak var streamIdsDelegate: FeedlyGetStreamIdsOperationDelegate?
override func main() {
guard !isCancelled else {
didFinish()
return
}
override func run() {
super.run()
service.getStreamIds(for: resource, continuation: continuation, newerThan: newerThan, unreadOnly: unreadOnly) { result in
switch result {

View File

@ -39,12 +39,8 @@ class FeedlyGetUpdatedArticleIdsOperation: FeedlyOperation, FeedlyEntryIdentifie
private var storedUpdatedArticleIds = Set<String>()
override func main() {
guard !isCancelled else {
didFinish()
return
}
override func run() {
super.run()
getStreamIds(nil)
}
@ -59,7 +55,7 @@ class FeedlyGetUpdatedArticleIdsOperation: FeedlyOperation, FeedlyEntryIdentifie
}
private func didGetStreamIds(_ result: Result<FeedlyStreamIds, Error>) {
guard !isCancelled else {
guard !isCanceled else {
didFinish()
return
}

View File

@ -37,12 +37,7 @@ final class FeedlyIngestStarredArticleIdsOperation: FeedlyOperation {
self.log = log
}
override func main() {
guard !isCancelled else {
didFinish()
return
}
override func run() {
getStreamIds(nil)
}
@ -51,7 +46,7 @@ final class FeedlyIngestStarredArticleIdsOperation: FeedlyOperation {
}
private func didGetStreamIds(_ result: Result<FeedlyStreamIds, Error>) {
guard !isCancelled else {
guard !isCanceled else {
didFinish()
return
}
@ -75,7 +70,7 @@ final class FeedlyIngestStarredArticleIdsOperation: FeedlyOperation {
/// Do not override pending statuses with the remote statuses of the same articles, otherwise an article will temporarily re-acquire the remote status before the pending status is pushed and subseqently pulled.
private func removeEntryIdsWithPendingStatus() {
guard !isCancelled else {
guard !isCanceled else {
didFinish()
return
}
@ -94,7 +89,7 @@ final class FeedlyIngestStarredArticleIdsOperation: FeedlyOperation {
}
private func updateStarredStatuses() {
guard !isCancelled else {
guard !isCanceled else {
didFinish()
return
}
@ -111,7 +106,7 @@ final class FeedlyIngestStarredArticleIdsOperation: FeedlyOperation {
}
func processStarredArticleIDs(_ localStarredArticleIDs: Set<String>) {
guard !isCancelled else {
guard !isCanceled else {
didFinish()
return
}

View File

@ -32,12 +32,7 @@ class FeedlyIngestStreamArticleIdsOperation: FeedlyOperation {
self.init(account: account, resource: all, service: service, log: log)
}
override func main() {
guard !isCancelled else {
didFinish()
return
}
override func run() {
getStreamIds(nil)
}
@ -46,7 +41,7 @@ class FeedlyIngestStreamArticleIdsOperation: FeedlyOperation {
}
private func didGetStreamIds(_ result: Result<FeedlyStreamIds, Error>) {
guard !isCancelled else {
guard !isCanceled else {
didFinish()
return
}

View File

@ -38,12 +38,7 @@ final class FeedlyIngestUnreadArticleIdsOperation: FeedlyOperation {
self.log = log
}
override func main() {
guard !isCancelled else {
didFinish()
return
}
override func run() {
getStreamIds(nil)
}
@ -52,7 +47,7 @@ final class FeedlyIngestUnreadArticleIdsOperation: FeedlyOperation {
}
private func didGetStreamIds(_ result: Result<FeedlyStreamIds, Error>) {
guard !isCancelled else {
guard !isCanceled else {
didFinish()
return
}
@ -76,7 +71,7 @@ final class FeedlyIngestUnreadArticleIdsOperation: FeedlyOperation {
/// Do not override pending statuses with the remote statuses of the same articles, otherwise an article will temporarily re-acquire the remote status before the pending status is pushed and subseqently pulled.
private func removeEntryIdsWithPendingStatus() {
guard !isCancelled else {
guard !isCanceled else {
didFinish()
return
}
@ -95,7 +90,7 @@ final class FeedlyIngestUnreadArticleIdsOperation: FeedlyOperation {
}
private func updateUnreadStatuses() {
guard !isCancelled else {
guard !isCanceled else {
didFinish()
return
}
@ -112,7 +107,7 @@ final class FeedlyIngestUnreadArticleIdsOperation: FeedlyOperation {
}
private func processUnreadArticleIDs(_ localUnreadArticleIDs: Set<String>) {
guard !isCancelled else {
guard !isCanceled else {
didFinish()
return
}

View File

@ -24,11 +24,7 @@ final class FeedlyLogoutOperation: FeedlyOperation {
self.log = log
}
override func main() {
guard !isCancelled else {
didFinish()
return
}
override func run() {
os_log("Requesting logout of %{public}@ account.", "\(account.type)")
service.logout(completion: didCompleteLogout(_:))
}

View File

@ -33,10 +33,10 @@ final class FeedlyMirrorCollectionsAsFoldersOperation: FeedlyOperation, FeedlyCo
self.log = log
}
override func main() {
defer { didFinish() }
guard !isCancelled else { return }
override func run() {
defer {
didFinish()
}
let localFolders = account.folders ?? Set()
let collections = collectionsProvider.collections

View File

@ -8,6 +8,7 @@
import Foundation
import RSWeb
import RSCore
protocol FeedlyOperationDelegate: class {
func feedlyOperation(_ operation: FeedlyOperation, didFailWith error: Error)
@ -15,10 +16,26 @@ protocol FeedlyOperationDelegate: class {
/// Abstract class common to all the tasks required to ingest content from Feedly into NetNewsWire.
/// Each task should try to have a single responsibility so they can be easily composed with others.
class FeedlyOperation: Operation {
class FeedlyOperation: MainThreadOperation {
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? {
didSet {
guard downloadProgress == nil || !isExecuting else {
@ -28,85 +45,29 @@ class FeedlyOperation: Operation {
downloadProgress?.addToNumberOfTasksAndRemaining(1)
}
}
override var isAsynchronous: Bool {
return true
// Override this. Call super.run() first in the overridden method.
func run() {
isExecuting = true
}
// Called when isCanceled is set to true. Useful to override.
func cancel() {
didFinish()
}
func didFinish() {
assert(Thread.isMainThread)
assert(!isFinished, "Finished operation is attempting to finish again.")
precondition(Thread.isMainThread)
isExecuting = false
isFinished = true
downloadProgress = nil
updateExecutingAndFinished(false, true)
if !isCanceled {
operationDelegate?.operationDidComplete(self)
}
}
func didFinish(_ error: Error) {
assert(Thread.isMainThread)
assert(!isFinished, "Finished operation is attempting to finish again.")
delegate?.feedlyOperation(self, didFailWith: error)
didFinish()
}
override func cancel() {
// If the operation never started, disown the download progress.
if !isExecuting && !isFinished, downloadProgress != nil {
DispatchQueue.main.async {
self.downloadProgress = nil
}
}
super.cancel()
}
override func start() {
guard !isCancelled else {
updateExecutingAndFinished(false, true)
if downloadProgress != nil {
DispatchQueue.main.async {
self.downloadProgress = nil
}
}
return
}
updateExecutingAndFinished(true, false)
DispatchQueue.main.async {
self.main()
}
}
override var isExecuting: Bool {
return isExecutingOperation
}
override var isFinished: Bool {
return isFinishedOperation
}
private var isExecutingOperation = false
private var isFinishedOperation = false
private func updateExecutingAndFinished(_ executing: Bool, _ finished: Bool) {
let isExecutingDidChange = executing != isExecutingOperation
let isFinishedDidChange = finished != isFinishedOperation
if isFinishedDidChange {
willChangeValue(forKey: #keyPath(isFinished))
}
if isExecutingDidChange {
willChangeValue(forKey: #keyPath(isExecuting))
}
isExecutingOperation = executing
isFinishedOperation = finished
if isExecutingDidChange {
didChangeValue(forKey: #keyPath(isExecuting))
}
if isFinishedDidChange {
didChangeValue(forKey: #keyPath(isFinished))
}
}
}

View File

@ -26,7 +26,7 @@ final class FeedlyOrganiseParsedItemsByFeedOperation: FeedlyOperation, FeedlyPar
}
var parsedItemsKeyedByFeedId: [String : Set<ParsedItem>] {
assert(Thread.isMainThread) // Needs to be on main thread because Feed is a main-thread-only model type.
precondition(Thread.isMainThread) // Needs to be on main thread because Feed is a main-thread-only model type.
return itemsKeyedByFeedId
}
@ -38,10 +38,11 @@ final class FeedlyOrganiseParsedItemsByFeedOperation: FeedlyOperation, FeedlyPar
self.log = log
}
override func main() {
defer { didFinish() }
guard !isCancelled else { return }
override func run() {
defer {
didFinish()
}
super.run()
let items = parsedItemProvider.parsedEntries
var dict = [String: Set<ParsedItem>](minimumCapacity: items.count)
@ -57,8 +58,6 @@ final class FeedlyOrganiseParsedItemsByFeedOperation: FeedlyOperation, FeedlyPar
}
}()
dict[key] = value
guard !isCancelled else { return }
}
os_log(.debug, log: log, "Grouped %i items by %i feeds for %@", items.count, dict.count, parsedItemProvider.parsedItemProviderName)

View File

@ -23,12 +23,7 @@ final class FeedlyRefreshAccessTokenOperation: FeedlyOperation {
self.log = log
}
override func main() {
guard !isCancelled else {
didFinish()
return
}
override func run() {
let refreshToken: Credentials
do {

View File

@ -35,10 +35,11 @@ final class FeedlyRequestStreamsOperation: FeedlyOperation {
self.log = log
}
override func main() {
defer { didFinish() }
guard !isCancelled else { return }
override func run() {
super.run()
defer {
didFinish()
}
assert(queueDelegate != nil, "This is not particularly useful unless the `queueDelegate` is non-nil.")

View File

@ -31,12 +31,9 @@ class FeedlySearchOperation: FeedlyOperation {
self.searchService = service
}
override func main() {
guard !isCancelled else {
didFinish()
return
}
override func run() {
super.run()
searchService.getFeeds(for: query, count: 1, locale: locale.identifier) { result in
switch result {
case .success(let response):

View File

@ -23,15 +23,15 @@ final class FeedlySendArticleStatusesOperation: FeedlyOperation {
self.log = log
}
override func main() {
guard !isCancelled else {
didFinish()
return
}
override func run() {
os_log(.debug, log: log, "Sending article statuses...")
database.selectForProcessing { result in
if self.isCanceled {
self.didFinish()
return
}
switch result {
case .success(let syncStatuses):
self.processStatuses(syncStatuses)

View File

@ -10,10 +10,11 @@ import Foundation
import os.log
import SyncDatabase
import RSWeb
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.
final class FeedlySyncAllOperation: FeedlyOperation {
private let operationQueue: OperationQueue
private let operationQueue = MainThreadOperationQueue()
private let log: OSLog
let syncUUID: UUID
@ -34,8 +35,7 @@ final class FeedlySyncAllOperation: FeedlyOperation {
init(account: Account, credentials: Credentials, lastSuccessfulFetchStartDate: Date?, markArticlesService: FeedlyMarkArticlesService, getUnreadService: FeedlyGetStreamIdsService, getCollectionsService: FeedlyGetCollectionsService, getStreamContentsService: FeedlyGetStreamContentsService, getStarredService: FeedlyGetStreamIdsService, getStreamIdsService: FeedlyGetStreamIdsService, getEntriesService: FeedlyGetEntriesService, database: SyncDatabase, downloadProgress: DownloadProgress, log: OSLog) {
self.syncUUID = UUID()
self.log = log
self.operationQueue = OperationQueue()
self.operationQueue.isSuspended = true
self.operationQueue.suspend()
super.init()
@ -51,31 +51,31 @@ final class FeedlySyncAllOperation: FeedlyOperation {
let getCollections = FeedlyGetCollectionsOperation(service: getCollectionsService, log: log)
getCollections.delegate = self
getCollections.downloadProgress = downloadProgress
getCollections.addDependency(sendArticleStatuses)
self.operationQueue.make(getCollections, dependOn: sendArticleStatuses)
self.operationQueue.addOperation(getCollections)
// Ensure a folder exists for each Collection, removing Folders without a corresponding Collection.
let mirrorCollectionsAsFolders = FeedlyMirrorCollectionsAsFoldersOperation(account: account, collectionsProvider: getCollections, log: log)
mirrorCollectionsAsFolders.delegate = self
mirrorCollectionsAsFolders.addDependency(getCollections)
self.operationQueue.make(mirrorCollectionsAsFolders, dependOn: getCollections)
self.operationQueue.addOperation(mirrorCollectionsAsFolders)
// Ensure feeds are created and grouped by their folders.
let createFeedsOperation = FeedlyCreateFeedsForCollectionFoldersOperation(account: account, feedsAndFoldersProvider: mirrorCollectionsAsFolders, log: log)
createFeedsOperation.delegate = self
createFeedsOperation.addDependency(mirrorCollectionsAsFolders)
self.operationQueue.make(createFeedsOperation, dependOn: mirrorCollectionsAsFolders)
self.operationQueue.addOperation(createFeedsOperation)
let getAllArticleIds = FeedlyIngestStreamArticleIdsOperation(account: account, credentials: credentials, service: getStreamIdsService, log: log)
getAllArticleIds.delegate = self
getAllArticleIds.downloadProgress = downloadProgress
getAllArticleIds.addDependency(createFeedsOperation)
self.operationQueue.make(getAllArticleIds, dependOn: createFeedsOperation)
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).
let getUnread = FeedlyIngestUnreadArticleIdsOperation(account: account, credentials: credentials, service: getUnreadService, database: database, newerThan: nil, log: log)
getUnread.delegate = self
getUnread.addDependency(getAllArticleIds)
self.operationQueue.make(getUnread, dependOn: getAllArticleIds)
getUnread.downloadProgress = downloadProgress
self.operationQueue.addOperation(getUnread)
@ -84,24 +84,24 @@ final class FeedlySyncAllOperation: FeedlyOperation {
let getUpdated = FeedlyGetUpdatedArticleIdsOperation(account: account, credentials: credentials, service: getStreamIdsService, newerThan: lastSuccessfulFetchStartDate, log: log)
getUpdated.delegate = self
getUpdated.downloadProgress = downloadProgress
getUpdated.addDependency(createFeedsOperation)
self.operationQueue.make(getUpdated, dependOn: createFeedsOperation)
self.operationQueue.addOperation(getUpdated)
// 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)
getStarred.delegate = self
getStarred.downloadProgress = downloadProgress
getStarred.addDependency(createFeedsOperation)
self.operationQueue.make(getStarred, dependOn: createFeedsOperation)
self.operationQueue.addOperation(getStarred)
// 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)
getMissingIds.delegate = self
getMissingIds.downloadProgress = downloadProgress
getMissingIds.addDependency(getAllArticleIds)
getMissingIds.addDependency(getUnread)
getMissingIds.addDependency(getStarred)
getMissingIds.addDependency(getUpdated)
self.operationQueue.make(getMissingIds, dependOn: getAllArticleIds)
self.operationQueue.make(getMissingIds, dependOn: getUnread)
self.operationQueue.make(getMissingIds, dependOn: getStarred)
self.operationQueue.make(getMissingIds, dependOn: getUpdated)
self.operationQueue.addOperation(getMissingIds)
// Download all the missing and updated articles
@ -112,15 +112,15 @@ final class FeedlySyncAllOperation: FeedlyOperation {
log: log)
downloadMissingArticles.delegate = self
downloadMissingArticles.downloadProgress = downloadProgress
downloadMissingArticles.addDependency(getMissingIds)
downloadMissingArticles.addDependency(getUpdated)
self.operationQueue.make(downloadMissingArticles, dependOn: getMissingIds)
self.operationQueue.make(downloadMissingArticles, dependOn: getUpdated)
self.operationQueue.addOperation(downloadMissingArticles)
// Once this operation's dependencies, their dependencies etc finish, we can finish.
let finishOperation = FeedlyCheckpointOperation()
finishOperation.checkpointDelegate = self
finishOperation.downloadProgress = downloadProgress
finishOperation.addDependency(downloadMissingArticles)
self.operationQueue.make(finishOperation, dependOn: downloadMissingArticles)
self.operationQueue.addOperation(finishOperation)
}
@ -140,14 +140,10 @@ final class FeedlySyncAllOperation: FeedlyOperation {
syncCompletionHandler = nil
}
override func main() {
guard !isCancelled else {
// override of cancel calls didFinish().
return
}
override func run() {
super.run()
os_log(.debug, log: log, "Starting sync %{public}@", syncUUID.uuidString)
operationQueue.isSuspended = false
operationQueue.resume()
}
}
@ -168,7 +164,8 @@ extension FeedlySyncAllOperation: FeedlyOperationDelegate {
func feedlyOperation(_ operation: FeedlyOperation, didFailWith error: Error) {
assert(Thread.isMainThread)
os_log(.debug, log: log, "%{public}@ failed with error: %{public}@.", operation, error as NSError)
// TODO: fix error for below line "Error is not convertible to NSError"
//os_log(.debug, log: log, "%{public}@ failed with error: %{public}@.", operation, error as NSError)
syncCompletionHandler?(.failure(error))
syncCompletionHandler = nil

View File

@ -9,11 +9,12 @@
import Foundation
import os.log
import RSParser
import RSCore
final class FeedlySyncStreamContentsOperation: FeedlyOperation, FeedlyOperationDelegate, FeedlyGetStreamContentsOperationDelegate, FeedlyCheckpointOperationDelegate {
private let account: Account
private let resource: FeedlyResourceId
private let operationQueue: OperationQueue
private let operationQueue = MainThreadOperationQueue()
private let service: FeedlyGetStreamContentsService
private let newerThan: Date?
private let log: OSLog
@ -23,8 +24,7 @@ final class FeedlySyncStreamContentsOperation: FeedlyOperation, FeedlyOperationD
self.account = account
self.resource = resource
self.service = service
self.operationQueue = OperationQueue()
self.operationQueue.isSuspended = true
self.operationQueue.suspend()
self.newerThan = newerThan
self.log = log
self.finishOperation = FeedlyCheckpointOperation()
@ -48,22 +48,17 @@ final class FeedlySyncStreamContentsOperation: FeedlyOperation, FeedlyOperationD
didFinish()
}
override func main() {
guard !isCancelled else {
// override of cancel calls didFinish().
return
}
operationQueue.isSuspended = false
override func run() {
operationQueue.resume()
}
func enqueueOperations(for continuation: String?) {
os_log(.debug, log: log, "Requesting page for %@", resource.id)
let operations = pageOperations(for: continuation)
operationQueue.addOperations(operations, waitUntilFinished: false)
operationQueue.addOperations(operations)
}
func pageOperations(for continuation: String?) -> [Operation] {
func pageOperations(for continuation: String?) -> [MainThreadOperation] {
let getPage = FeedlyGetStreamContentsOperation(account: account,
resource: resource,
service: service,
@ -82,20 +77,20 @@ final class FeedlySyncStreamContentsOperation: FeedlyOperation, FeedlyOperationD
getPage.delegate = self
getPage.streamDelegate = self
organiseByFeed.addDependency(getPage)
operationQueue.make(organiseByFeed, dependOn: getPage)
organiseByFeed.delegate = self
updateAccount.addDependency(organiseByFeed)
operationQueue.make(updateAccount, dependOn: organiseByFeed)
updateAccount.delegate = self
finishOperation.addDependency(updateAccount)
operationQueue.make(finishOperation, dependOn: updateAccount)
return [getPage, organiseByFeed, updateAccount]
}
func feedlyGetStreamContentsOperation(_ operation: FeedlyGetStreamContentsOperation, didGetContentsOf stream: FeedlyStream) {
guard !isCancelled else {
guard !isCanceled else {
os_log(.debug, log: log, "Cancelled requesting page for %@", resource.id)
return
}

View File

@ -22,13 +22,7 @@ final class FeedlyUpdateAccountFeedsWithItemsOperation: FeedlyOperation {
self.log = log
}
override func main() {
precondition(Thread.isMainThread) // Needs to be on main thread because Feed is a main-thread-only model type.
guard !isCancelled else {
didFinish()
return
}
override func run() {
let webFeedIDsAndItems = organisedItemsProvider.parsedItemsKeyedByFeedId
account.update(webFeedIDsAndItems: webFeedIDsAndItems, defaultRead: true) { databaseError in

@ -1 +1 @@
Subproject commit c6638e4d4282f41e1a3eddced6c2ae822fb34613
Subproject commit 3a2d030e8237bdb6ebbd6c389358aaae34d2d3b0