NetNewsWire/Frameworks/Account/Feedly/Operations/FeedlySyncAllOperation.swift

148 lines
6.2 KiB
Swift

//
// FeedlySyncAllOperation.swift
// Account
//
// Created by Kiel Gillard on 19/9/19.
// Copyright © 2019 Ranchero Software, LLC. All rights reserved.
//
import Foundation
import os.log
import SyncDatabase
import RSWeb
/// 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 log: OSLog
let syncUUID: UUID
var syncCompletionHandler: ((Result<Void, Error>) -> ())?
init(account: Account, credentials: Credentials, lastSuccessfulFetchStartDate: Date?, markArticlesService: FeedlyMarkArticlesService, getUnreadService: FeedlyGetStreamIdsService, getCollectionsService: FeedlyGetCollectionsService, getStreamContentsService: FeedlyGetStreamContentsService, getStarredArticlesService: FeedlyGetStreamContentsService, database: SyncDatabase, downloadProgress: DownloadProgress, log: OSLog) {
self.syncUUID = UUID()
self.log = log
self.operationQueue = OperationQueue()
self.operationQueue.isSuspended = true
super.init()
self.downloadProgress = downloadProgress
// Send any read/unread/starred article statuses to Feedly before anything else.
let sendArticleStatuses = FeedlySendArticleStatusesOperation(database: database, service: markArticlesService, log: log)
sendArticleStatuses.delegate = self
sendArticleStatuses.downloadProgress = downloadProgress
self.operationQueue.addOperation(sendArticleStatuses)
// Get all the Collections the user has.
let getCollections = FeedlyGetCollectionsOperation(service: getCollectionsService, log: log)
getCollections.delegate = self
getCollections.downloadProgress = downloadProgress
getCollections.addDependency(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.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.addOperation(createFeedsOperation)
// Get each page of unread article ids in the global.all stream for the last 31 days (nil = Feedly API default).
let getUnread = FeedlySyncUnreadStatusesOperation(account: account, credentials: credentials, service: getUnreadService, newerThan: nil, log: log)
getUnread.delegate = self
getUnread.addDependency(createFeedsOperation)
getUnread.downloadProgress = downloadProgress
self.operationQueue.addOperation(getUnread)
// Get each page of the global.all stream until we get either the content from the last sync or the last 31 days.
let getStreamContents = FeedlySyncStreamContentsOperation(account: account, credentials: credentials, service: getStreamContentsService, newerThan: lastSuccessfulFetchStartDate, log: log)
getStreamContents.delegate = self
getStreamContents.downloadProgress = downloadProgress
getStreamContents.addDependency(getUnread)
self.operationQueue.addOperation(getStreamContents)
// Get each and every starred article.
let syncStarred = FeedlySyncStarredArticlesOperation(account: account, credentials: credentials, service: getStarredArticlesService, log: log)
syncStarred.downloadProgress = downloadProgress
syncStarred.addDependency(createFeedsOperation)
self.operationQueue.addOperation(syncStarred)
// Once this operation's dependencies, their dependencies etc finish, we can finish.
let finishOperation = FeedlyCheckpointOperation()
finishOperation.checkpointDelegate = self
finishOperation.downloadProgress = downloadProgress
finishOperation.addDependency(getStreamContents)
finishOperation.addDependency(syncStarred)
self.operationQueue.addOperation(finishOperation)
}
convenience init(account: Account, credentials: Credentials, caller: FeedlyAPICaller, database: SyncDatabase, lastSuccessfulFetchStartDate: Date?, downloadProgress: DownloadProgress, log: OSLog) {
let newerThan: Date? = {
if let date = lastSuccessfulFetchStartDate {
return date
} else {
return Calendar.current.date(byAdding: .day, value: -31, to: Date())
}
}()
self.init(account: account, credentials: credentials, lastSuccessfulFetchStartDate: newerThan, markArticlesService: caller, getUnreadService: caller, getCollectionsService: caller, getStreamContentsService: caller, getStarredArticlesService: 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 main() {
guard !isCancelled else {
// override of cancel calls didFinish().
return
}
os_log(.debug, log: log, "Starting sync %{public}@", syncUUID.uuidString)
operationQueue.isSuspended = false
}
}
extension FeedlySyncAllOperation: FeedlyCheckpointOperationDelegate {
func feedlyCheckpointOperationDidReachCheckpoint(_ operation: FeedlyCheckpointOperation) {
assert(Thread.isMainThread)
os_log(.debug, log: self.log, "Sync completed: %{public}@", syncUUID.uuidString)
syncCompletionHandler?(.success(()))
syncCompletionHandler = nil
didFinish()
}
}
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)
syncCompletionHandler?(.failure(error))
syncCompletionHandler = nil
cancel()
}
}