mirror of
https://github.com/Ranchero-Software/NetNewsWire
synced 2025-08-12 06:26:36 +00:00
Merge pull request #3643 from stuartbreckenridge/os-log-audit
Logging changes
This commit is contained in:
@@ -17,7 +17,6 @@ import RSParser
|
||||
import RSDatabase
|
||||
import ArticlesDatabase
|
||||
import RSWeb
|
||||
import os.log
|
||||
import Secrets
|
||||
|
||||
// Main thread only.
|
||||
@@ -91,8 +90,6 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
|
||||
return defaultName
|
||||
}()
|
||||
|
||||
var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "account")
|
||||
|
||||
public var isDeleted = false
|
||||
|
||||
|
||||
@@ -7,12 +7,9 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
import RSCore
|
||||
|
||||
final class AccountMetadataFile {
|
||||
|
||||
private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "accountMetadataFile")
|
||||
final class AccountMetadataFile: Logging {
|
||||
|
||||
private let fileURL: URL
|
||||
private let account: Account
|
||||
@@ -50,8 +47,8 @@ final class AccountMetadataFile {
|
||||
do {
|
||||
let data = try encoder.encode(account.metadata)
|
||||
try data.write(to: fileURL)
|
||||
} catch let error as NSError {
|
||||
os_log(.error, log: log, "Save to disk failed: %@.", error.localizedDescription)
|
||||
} catch let error {
|
||||
logger.error("Save to disk failed: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,22 +7,21 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
import RSCore
|
||||
|
||||
public extension Notification.Name {
|
||||
static let AccountsDidFailToSyncWithErrors = Notification.Name("AccountsDidFailToSyncWithErrors")
|
||||
}
|
||||
|
||||
public struct AccountSyncError {
|
||||
public struct AccountSyncError: Logging {
|
||||
|
||||
private static let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "Application")
|
||||
public let account: Account
|
||||
public let error: Error
|
||||
|
||||
init(account: Account, error: Error) {
|
||||
self.account = account
|
||||
self.error = error
|
||||
os_log(.error, log: AccountSyncError.log, "%@", error.localizedDescription)
|
||||
AccountSyncError.logger.error("Account Sync Error: \(error.localizedDescription)")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
import Foundation
|
||||
import CloudKit
|
||||
import SystemConfiguration
|
||||
import os.log
|
||||
import SyncDatabase
|
||||
import RSCore
|
||||
import RSParser
|
||||
@@ -27,9 +26,7 @@ enum CloudKitAccountDelegateError: LocalizedError {
|
||||
}
|
||||
}
|
||||
|
||||
final class CloudKitAccountDelegate: AccountDelegate {
|
||||
|
||||
private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "CloudKit")
|
||||
final class CloudKitAccountDelegate: AccountDelegate, Logging {
|
||||
|
||||
private let database: SyncDatabase
|
||||
|
||||
@@ -318,10 +315,10 @@ final class CloudKitAccountDelegate: AccountDelegate {
|
||||
|
||||
for webFeed in webFeeds {
|
||||
group.enter()
|
||||
self.removeWebFeedFromCloud(for: account, with: webFeed, from: folder) { result in
|
||||
self.removeWebFeedFromCloud(for: account, with: webFeed, from: folder) { [weak self] result in
|
||||
group.leave()
|
||||
if case .failure(let error) = result {
|
||||
os_log(.error, log: self.log, "Remove folder, remove webfeed error: %@.", error.localizedDescription)
|
||||
self?.logger.error("Remove folder, remove webfeed error: \(error.localizedDescription, privacy: .public)")
|
||||
errorOccurred = true
|
||||
}
|
||||
}
|
||||
@@ -380,14 +377,14 @@ final class CloudKitAccountDelegate: AccountDelegate {
|
||||
folder.topLevelWebFeeds.remove(feed)
|
||||
|
||||
group.enter()
|
||||
self.restoreWebFeed(for: account, feed: feed, container: folder) { result in
|
||||
self.refreshProgress.completeTask()
|
||||
self.restoreWebFeed(for: account, feed: feed, container: folder) { [weak self] result in
|
||||
self?.refreshProgress.completeTask()
|
||||
group.leave()
|
||||
switch result {
|
||||
case .success:
|
||||
break
|
||||
case .failure(let error):
|
||||
os_log(.error, log: self.log, "Restore folder feed error: %@.", error.localizedDescription)
|
||||
self?.logger.error("Restore folder feed error: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -437,13 +434,13 @@ final class CloudKitAccountDelegate: AccountDelegate {
|
||||
|
||||
// Check to see if this is a new account and initialize anything we need
|
||||
if account.externalID == nil {
|
||||
accountZone.findOrCreateAccount() { result in
|
||||
accountZone.findOrCreateAccount() { [weak self] result in
|
||||
switch result {
|
||||
case .success(let externalID):
|
||||
account.externalID = externalID
|
||||
self.initialRefreshAll(for: account) { _ in }
|
||||
self?.initialRefreshAll(for: account) { _ in }
|
||||
case .failure(let error):
|
||||
os_log(.error, log: self.log, "Error adding account container: %@", error.localizedDescription)
|
||||
self?.logger.error("Error adding account container: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
accountZone.subscribeToZoneChanges()
|
||||
@@ -589,7 +586,7 @@ private extension CloudKitAccountDelegate {
|
||||
group.leave()
|
||||
}
|
||||
case .failure(let error):
|
||||
os_log(.error, log: self.log, "CloudKit Feed refresh update error: %@.", error.localizedDescription)
|
||||
self.logger.error("CloudKit Feed refresh update error: \(error.localizedDescription, privacy: .public)")
|
||||
self.refreshProgress.completeTask()
|
||||
group.leave()
|
||||
}
|
||||
@@ -597,7 +594,7 @@ private extension CloudKitAccountDelegate {
|
||||
}
|
||||
|
||||
case .failure(let error):
|
||||
os_log(.error, log: self.log, "CloudKit Feed refresh error: %@.", error.localizedDescription)
|
||||
self.logger.error("CloudKit Feed refresh error: \(error.localizedDescription, privacy: .public)")
|
||||
feedProviderError = error
|
||||
self.refreshProgress.completeTask()
|
||||
group.leave()
|
||||
@@ -804,12 +801,12 @@ private extension CloudKitAccountDelegate {
|
||||
case .success:
|
||||
self.articlesZone.fetchChangesInZone() { _ in }
|
||||
case .failure(let error):
|
||||
os_log(.error, log: self.log, "CloudKit Feed send articles error: %@.", error.localizedDescription)
|
||||
self.logger.error("CloudKit Feed send articles error: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
}
|
||||
case .failure(let error):
|
||||
os_log(.error, log: self.log, "CloudKit Feed send articles error: %@.", error.localizedDescription)
|
||||
self.logger.error("CloudKit Feed send articles error: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
import RSCore
|
||||
import RSParser
|
||||
import RSWeb
|
||||
@@ -16,10 +15,8 @@ import SyncDatabase
|
||||
import Articles
|
||||
import ArticlesDatabase
|
||||
|
||||
class CloudKitArticlesZoneDelegate: CloudKitZoneDelegate {
|
||||
class CloudKitArticlesZoneDelegate: CloudKitZoneDelegate, Logging {
|
||||
|
||||
private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "CloudKit")
|
||||
|
||||
weak var account: Account?
|
||||
var database: SyncDatabase
|
||||
weak var articlesZone: CloudKitArticlesZone?
|
||||
@@ -49,12 +46,12 @@ class CloudKitArticlesZoneDelegate: CloudKitZoneDelegate {
|
||||
}
|
||||
|
||||
case .failure(let error):
|
||||
os_log(.error, log: self.log, "Error occurred getting pending starred records: %@", error.localizedDescription)
|
||||
self.logger.error("Error occurred getting pending starred records: \(error.localizedDescription)")
|
||||
completion(.failure(CloudKitZoneError.unknown))
|
||||
}
|
||||
}
|
||||
case .failure(let error):
|
||||
os_log(.error, log: self.log, "Error occurred getting pending read status records: %@", error.localizedDescription)
|
||||
self.logger.error("Error occurred getting pending read status records: \(error.localizedDescription)")
|
||||
completion(.failure(CloudKitZoneError.unknown))
|
||||
}
|
||||
|
||||
@@ -102,7 +99,7 @@ private extension CloudKitArticlesZoneDelegate {
|
||||
account?.markAsUnread(updateableUnreadArticleIDs) { result in
|
||||
if case .failure(let databaseError) = result {
|
||||
errorOccurred = true
|
||||
os_log(.error, log: self.log, "Error occurred while storing unread statuses: %@", databaseError.localizedDescription)
|
||||
self.logger.error("Error occurred while storing unread statuses: \(databaseError.localizedDescription)")
|
||||
}
|
||||
group.leave()
|
||||
}
|
||||
@@ -111,7 +108,7 @@ private extension CloudKitArticlesZoneDelegate {
|
||||
account?.markAsRead(updateableReadArticleIDs) { result in
|
||||
if case .failure(let databaseError) = result {
|
||||
errorOccurred = true
|
||||
os_log(.error, log: self.log, "Error occurred while storing read statuses: %@", databaseError.localizedDescription)
|
||||
self.logger.error("Error occurred while storing read statuses: \(databaseError.localizedDescription)")
|
||||
}
|
||||
group.leave()
|
||||
}
|
||||
@@ -120,7 +117,7 @@ private extension CloudKitArticlesZoneDelegate {
|
||||
account?.markAsUnstarred(updateableUnstarredArticleIDs) { result in
|
||||
if case .failure(let databaseError) = result {
|
||||
errorOccurred = true
|
||||
os_log(.error, log: self.log, "Error occurred while storing unstarred statuses: %@", databaseError.localizedDescription)
|
||||
self.logger.error("Error occurred while storing unstarred statuses: \(databaseError.localizedDescription)")
|
||||
}
|
||||
group.leave()
|
||||
}
|
||||
@@ -129,7 +126,7 @@ private extension CloudKitArticlesZoneDelegate {
|
||||
account?.markAsStarred(updateableStarredArticleIDs) { result in
|
||||
if case .failure(let databaseError) = result {
|
||||
errorOccurred = true
|
||||
os_log(.error, log: self.log, "Error occurred while storing starred statuses: %@", databaseError.localizedDescription)
|
||||
self.logger.error("Error occurred while stroing starred records: \(databaseError.localizedDescription)")
|
||||
}
|
||||
group.leave()
|
||||
}
|
||||
@@ -155,7 +152,7 @@ private extension CloudKitArticlesZoneDelegate {
|
||||
}
|
||||
case .failure(let databaseError):
|
||||
errorOccurred = true
|
||||
os_log(.error, log: self.log, "Error occurred while storing articles: %@", databaseError.localizedDescription)
|
||||
self.logger.error("Error occurred while storing articles: \(databaseError.localizedDescription)")
|
||||
group.leave()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,13 +7,10 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
import RSCore
|
||||
|
||||
class CloudKitReceiveStatusOperation: MainThreadOperation {
|
||||
class CloudKitReceiveStatusOperation: MainThreadOperation, Logging {
|
||||
|
||||
private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "CloudKit")
|
||||
|
||||
// MainThreadOperation
|
||||
public var isCanceled = false
|
||||
public var id: Int?
|
||||
@@ -33,15 +30,15 @@ class CloudKitReceiveStatusOperation: MainThreadOperation {
|
||||
return
|
||||
}
|
||||
|
||||
os_log(.debug, log: log, "Refreshing article statuses...")
|
||||
logger.debug("Refreshing article statuses...")
|
||||
|
||||
articlesZone.refreshArticles() { result in
|
||||
os_log(.debug, log: self.log, "Done refreshing article statuses.")
|
||||
self.logger.debug("Done refreshing article statuses.")
|
||||
switch result {
|
||||
case .success:
|
||||
self.operationDelegate?.operationDidComplete(self)
|
||||
case .failure(let error):
|
||||
os_log(.error, log: self.log, "Receive status error: %@.", error.localizedDescription)
|
||||
self.logger.debug("Receive status error: \(error.localizedDescription)")
|
||||
self.operationDelegate?.cancelOperation(self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,14 +7,10 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
import os.log
|
||||
import RSCore
|
||||
|
||||
class CloudKitRemoteNotificationOperation: MainThreadOperation {
|
||||
class CloudKitRemoteNotificationOperation: MainThreadOperation, Logging {
|
||||
|
||||
private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "CloudKit")
|
||||
|
||||
// MainThreadOperation
|
||||
public var isCanceled = false
|
||||
public var id: Int?
|
||||
@@ -37,12 +33,12 @@ class CloudKitRemoteNotificationOperation: MainThreadOperation {
|
||||
self.operationDelegate?.operationDidComplete(self)
|
||||
return
|
||||
}
|
||||
|
||||
os_log(.debug, log: log, "Processing remote notification...")
|
||||
|
||||
logger.debug("Processing remote notification...")
|
||||
|
||||
accountZone.receiveRemoteNotification(userInfo: userInfo) {
|
||||
articlesZone.receiveRemoteNotification(userInfo: self.userInfo) {
|
||||
os_log(.debug, log: self.log, "Done processing remote notification.")
|
||||
self.logger.debug("Done processing remote notification.")
|
||||
self.operationDelegate?.operationDidComplete(self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,14 +8,12 @@
|
||||
|
||||
import Foundation
|
||||
import Articles
|
||||
import os.log
|
||||
import RSCore
|
||||
import RSWeb
|
||||
import SyncDatabase
|
||||
|
||||
class CloudKitSendStatusOperation: MainThreadOperation {
|
||||
class CloudKitSendStatusOperation: MainThreadOperation, Logging {
|
||||
|
||||
private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "CloudKit")
|
||||
private let blockSize = 150
|
||||
|
||||
// MainThreadOperation
|
||||
@@ -40,7 +38,7 @@ class CloudKitSendStatusOperation: MainThreadOperation {
|
||||
}
|
||||
|
||||
func run() {
|
||||
os_log(.debug, log: log, "Sending article statuses...")
|
||||
logger.debug("Sending article statuses...")
|
||||
|
||||
if showProgress {
|
||||
|
||||
@@ -51,7 +49,7 @@ class CloudKitSendStatusOperation: MainThreadOperation {
|
||||
self.refreshProgress?.addToNumberOfTasksAndRemaining(ticks)
|
||||
self.selectForProcessing()
|
||||
case .failure(let databaseError):
|
||||
os_log(.error, log: self.log, "Send status count pending error: %@.", databaseError.localizedDescription)
|
||||
self.logger.error("Send status count pending error: \(databaseError.localizedDescription)")
|
||||
self.operationDelegate?.cancelOperation(self)
|
||||
}
|
||||
}
|
||||
@@ -77,7 +75,7 @@ private extension CloudKitSendStatusOperation {
|
||||
if self.showProgress {
|
||||
self.refreshProgress?.completeTask()
|
||||
}
|
||||
os_log(.debug, log: self.log, "Done sending article statuses.")
|
||||
self.logger.debug("Done sending article statuses.")
|
||||
self.operationDelegate?.operationDidComplete(self)
|
||||
}
|
||||
|
||||
@@ -95,7 +93,7 @@ private extension CloudKitSendStatusOperation {
|
||||
}
|
||||
|
||||
case .failure(let databaseError):
|
||||
os_log(.error, log: self.log, "Send status error: %@.", databaseError.localizedDescription)
|
||||
self.logger.error("Send status error: \(databaseError.localizedDescription)")
|
||||
self.operationDelegate?.cancelOperation(self)
|
||||
}
|
||||
}
|
||||
@@ -125,7 +123,7 @@ private extension CloudKitSendStatusOperation {
|
||||
if self.showProgress && self.refreshProgress?.numberRemaining ?? 0 > 1 {
|
||||
self.refreshProgress?.completeTask()
|
||||
}
|
||||
os_log(.debug, log: self.log, "Done sending article status block...")
|
||||
self.logger.debug("Done sending article status block...")
|
||||
completion(stop)
|
||||
}
|
||||
|
||||
@@ -147,7 +145,7 @@ private extension CloudKitSendStatusOperation {
|
||||
case .failure(let error):
|
||||
self.database.resetSelectedForProcessing(syncStatuses.map({ $0.articleID })) { _ in
|
||||
self.processAccountError(account, error)
|
||||
os_log(.error, log: self.log, "Send article status modify articles error: %@.", error.localizedDescription)
|
||||
self.logger.error("Send article status modify articles error: \(error.localizedDescription)")
|
||||
completion(true)
|
||||
}
|
||||
}
|
||||
@@ -161,7 +159,7 @@ private extension CloudKitSendStatusOperation {
|
||||
processWithArticles(articles)
|
||||
case .failure(let databaseError):
|
||||
self.database.resetSelectedForProcessing(syncStatuses.map({ $0.articleID })) { _ in
|
||||
os_log(.error, log: self.log, "Send article status fetch articles error: %@.", databaseError.localizedDescription)
|
||||
self.logger.error("Send article status fetch articles error: \(databaseError.localizedDescription)")
|
||||
completion(true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
import OAuthSwift
|
||||
import Secrets
|
||||
import RSCore
|
||||
@@ -40,8 +39,6 @@ public enum RedditFeedType: Int {
|
||||
|
||||
public final class RedditFeedProvider: FeedProvider, RedditFeedProviderTokenRefreshOperationDelegate {
|
||||
|
||||
var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "RedditFeedProvider")
|
||||
|
||||
private static let homeURL = "https://www.reddit.com"
|
||||
private static let server = "www.reddit.com"
|
||||
private static let apiBase = "https://oauth.reddit.com"
|
||||
|
||||
@@ -19,9 +19,7 @@ protocol RedditFeedProviderTokenRefreshOperationDelegate: AnyObject {
|
||||
var oauthSwift: OAuth2Swift? { get }
|
||||
}
|
||||
|
||||
class RedditFeedProviderTokenRefreshOperation: MainThreadOperation {
|
||||
|
||||
var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "RedditFeedProvider")
|
||||
class RedditFeedProviderTokenRefreshOperation: MainThreadOperation, Logging {
|
||||
|
||||
public var isCanceled = false
|
||||
public var id: Int?
|
||||
@@ -49,7 +47,7 @@ class RedditFeedProviderTokenRefreshOperation: MainThreadOperation {
|
||||
return
|
||||
}
|
||||
|
||||
os_log(.debug, log: self.log, "Access token expired, attempting to renew...")
|
||||
logger.debug("Access token expired, attempting to renew...")
|
||||
|
||||
delegate.oauthSwift?.renewAccessToken(withRefreshToken: delegate.oauthRefreshToken) { [weak self] result in
|
||||
guard let self = self else { return }
|
||||
@@ -61,7 +59,7 @@ class RedditFeedProviderTokenRefreshOperation: MainThreadOperation {
|
||||
do {
|
||||
try RedditFeedProvider.storeCredentials(username: username, oauthToken: delegate.oauthToken, oauthRefreshToken: delegate.oauthRefreshToken)
|
||||
delegate.oauthTokenLastRefresh = Date()
|
||||
os_log(.debug, log: self.log, "Access token renewed.")
|
||||
self.logger.debug("Access token renewed.")
|
||||
} catch {
|
||||
self.error = error
|
||||
self.operationDelegate?.operationDidComplete(self)
|
||||
|
||||
@@ -12,7 +12,6 @@ import RSDatabase
|
||||
import RSParser
|
||||
import RSWeb
|
||||
import SyncDatabase
|
||||
import os.log
|
||||
import Secrets
|
||||
|
||||
public enum FeedbinAccountDelegateError: String, Error {
|
||||
@@ -20,12 +19,11 @@ public enum FeedbinAccountDelegateError: String, Error {
|
||||
case unknown = "An unknown error occurred."
|
||||
}
|
||||
|
||||
final class FeedbinAccountDelegate: AccountDelegate {
|
||||
final class FeedbinAccountDelegate: AccountDelegate, Logging {
|
||||
|
||||
private let database: SyncDatabase
|
||||
|
||||
private let caller: FeedbinAPICaller
|
||||
private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "Feedbin")
|
||||
|
||||
let behaviors: AccountBehaviors = [.disallowFeedCopyInRootFolder]
|
||||
let server: String? = "api.feedbin.com"
|
||||
@@ -132,7 +130,7 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
||||
|
||||
func sendArticleStatus(for account: Account, completion: @escaping ((Result<Void, Error>) -> Void)) {
|
||||
|
||||
os_log(.debug, log: log, "Sending article statuses...")
|
||||
logger.debug("Sending article statuses")
|
||||
|
||||
database.selectForProcessing { result in
|
||||
|
||||
@@ -178,7 +176,7 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
||||
}
|
||||
|
||||
group.notify(queue: DispatchQueue.main) {
|
||||
os_log(.debug, log: self.log, "Done sending article statuses.")
|
||||
self.logger.debug("Done sending article statuses.")
|
||||
if errorOccurred {
|
||||
completion(.failure(FeedbinAccountDelegateError.unknown))
|
||||
} else {
|
||||
@@ -198,7 +196,7 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
||||
|
||||
func refreshArticleStatus(for account: Account, completion: @escaping ((Result<Void, Error>) -> Void)) {
|
||||
|
||||
os_log(.debug, log: log, "Refreshing article statuses...")
|
||||
logger.debug("Refreshing article statuses...")
|
||||
|
||||
let group = DispatchGroup()
|
||||
var errorOccurred = false
|
||||
@@ -212,7 +210,7 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
||||
}
|
||||
case .failure(let error):
|
||||
errorOccurred = true
|
||||
os_log(.info, log: self.log, "Retrieving unread entries failed: %@.", error.localizedDescription)
|
||||
self.logger.error("Retrieving unread entries failed: \(error.localizedDescription, privacy: .public)")
|
||||
group.leave()
|
||||
}
|
||||
|
||||
@@ -227,14 +225,14 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
||||
}
|
||||
case .failure(let error):
|
||||
errorOccurred = true
|
||||
os_log(.info, log: self.log, "Retrieving starred entries failed: %@.", error.localizedDescription)
|
||||
self.logger.error("Retrieving starred entries failed: \(error.localizedDescription, privacy: .public)")
|
||||
group.leave()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
group.notify(queue: DispatchQueue.main) {
|
||||
os_log(.debug, log: self.log, "Done refreshing article statuses.")
|
||||
self.logger.debug("Done refreshing article statuses.")
|
||||
if errorOccurred {
|
||||
completion(.failure(FeedbinAccountDelegateError.unknown))
|
||||
} else {
|
||||
@@ -260,7 +258,7 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
||||
return
|
||||
}
|
||||
|
||||
os_log(.debug, log: log, "Begin importing OPML...")
|
||||
logger.debug("Begin importing OPML...")
|
||||
isOPMLImportInProgress = true
|
||||
refreshProgress.addToNumberOfTasksAndRemaining(1)
|
||||
|
||||
@@ -268,7 +266,7 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
||||
switch result {
|
||||
case .success(let importResult):
|
||||
if importResult.complete {
|
||||
os_log(.debug, log: self.log, "Import OPML done.")
|
||||
self.logger.debug("Import OPML done.")
|
||||
self.refreshProgress.completeTask()
|
||||
self.isOPMLImportInProgress = false
|
||||
DispatchQueue.main.async {
|
||||
@@ -278,7 +276,7 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
||||
self.checkImportResult(opmlImportResultID: importResult.importResultID, completion: completion)
|
||||
}
|
||||
case .failure(let error):
|
||||
os_log(.debug, log: self.log, "Import OPML failed.")
|
||||
self.logger.error("Import OPML failed: \(error.localizedDescription, privacy: .public)")
|
||||
self.refreshProgress.completeTask()
|
||||
self.isOPMLImportInProgress = false
|
||||
DispatchQueue.main.async {
|
||||
@@ -352,7 +350,7 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
||||
self.clearFolderRelationship(for: feed, withFolderName: folder.name ?? "")
|
||||
}
|
||||
case .failure(let error):
|
||||
os_log(.error, log: self.log, "Remove feed error: %@.", error.localizedDescription)
|
||||
self.logger.error("Remove feed error: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -371,7 +369,7 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
||||
account.clearWebFeedMetadata(feed)
|
||||
}
|
||||
case .failure(let error):
|
||||
os_log(.error, log: self.log, "Remove feed error: %@.", error.localizedDescription)
|
||||
self.logger.error("Remove feed error: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -541,7 +539,7 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
||||
case .success:
|
||||
break
|
||||
case .failure(let error):
|
||||
os_log(.error, log: self.log, "Restore folder feed error: %@.", error.localizedDescription)
|
||||
self.logger.error("Restore folder feed error: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -624,13 +622,13 @@ private extension FeedbinAccountDelegate {
|
||||
|
||||
Timer.scheduledTimer(withTimeInterval: 15, repeats: true) { timer in
|
||||
|
||||
os_log(.debug, log: self.log, "Checking status of OPML import...")
|
||||
self.logger.debug("Checking status of OPML import...")
|
||||
|
||||
self.caller.retrieveOPMLImportResult(importID: opmlImportResultID) { result in
|
||||
switch result {
|
||||
case .success(let importResult):
|
||||
if let result = importResult, result.complete {
|
||||
os_log(.debug, log: self.log, "Checking status of OPML import successfully completed.")
|
||||
self.logger.debug("Checking status of OPML import successfully completed.")
|
||||
timer.invalidate()
|
||||
self.refreshProgress.completeTask()
|
||||
self.isOPMLImportInProgress = false
|
||||
@@ -639,7 +637,7 @@ private extension FeedbinAccountDelegate {
|
||||
}
|
||||
}
|
||||
case .failure(let error):
|
||||
os_log(.debug, log: self.log, "Import OPML check failed.")
|
||||
self.logger.debug("Import OPML check failed.")
|
||||
timer.invalidate()
|
||||
self.refreshProgress.completeTask()
|
||||
self.isOPMLImportInProgress = false
|
||||
@@ -772,7 +770,7 @@ private extension FeedbinAccountDelegate {
|
||||
guard let tags = tags else { return }
|
||||
assert(Thread.isMainThread)
|
||||
|
||||
os_log(.debug, log: log, "Syncing folders with %ld tags.", tags.count)
|
||||
logger.debug("Syncing folders with \(tags.count, privacy: .public) tags.")
|
||||
|
||||
let tagNames = tags.map { $0.name }
|
||||
|
||||
@@ -811,7 +809,7 @@ private extension FeedbinAccountDelegate {
|
||||
guard let subscriptions = subscriptions else { return }
|
||||
assert(Thread.isMainThread)
|
||||
|
||||
os_log(.debug, log: log, "Syncing feeds with %ld subscriptions.", subscriptions.count)
|
||||
logger.debug("Syncing feeds with \(subscriptions.count, privacy: .public) subscriptions.")
|
||||
|
||||
let subFeedIds = subscriptions.map { String($0.feedID) }
|
||||
|
||||
@@ -865,7 +863,7 @@ private extension FeedbinAccountDelegate {
|
||||
guard let taggings = taggings else { return }
|
||||
assert(Thread.isMainThread)
|
||||
|
||||
os_log(.debug, log: log, "Syncing taggings with %ld taggings.", taggings.count)
|
||||
logger.debug("Syncing taggings with \(taggings.count) taggings.")
|
||||
|
||||
// Set up some structures to make syncing easier
|
||||
let folderDict = nameToFolderDictionary(with: account.folders)
|
||||
@@ -961,7 +959,7 @@ private extension FeedbinAccountDelegate {
|
||||
group.leave()
|
||||
case .failure(let error):
|
||||
errorOccurred = true
|
||||
os_log(.error, log: self.log, "Article status sync call failed: %@.", error.localizedDescription)
|
||||
self.logger.error("Article status sync call failed: \(error.localizedDescription, privacy: .public)")
|
||||
self.database.resetSelectedForProcessing(articleIDGroup.map { String($0) } )
|
||||
group.leave()
|
||||
}
|
||||
@@ -1122,7 +1120,7 @@ private extension FeedbinAccountDelegate {
|
||||
|
||||
func refreshArticles(_ account: Account, completion: @escaping VoidResultCompletionBlock) {
|
||||
|
||||
os_log(.debug, log: log, "Refreshing articles...")
|
||||
logger.debug("Refreshing articles...")
|
||||
|
||||
caller.retrieveEntries() { result in
|
||||
|
||||
@@ -1143,7 +1141,7 @@ private extension FeedbinAccountDelegate {
|
||||
}
|
||||
|
||||
self.refreshArticles(account, page: page, updateFetchDate: updateFetchDate) { result in
|
||||
os_log(.debug, log: self.log, "Done refreshing articles.")
|
||||
self.logger.debug("Done refreshing articles.")
|
||||
switch result {
|
||||
case .success:
|
||||
completion(.success(()))
|
||||
@@ -1160,7 +1158,7 @@ private extension FeedbinAccountDelegate {
|
||||
}
|
||||
|
||||
func refreshMissingArticles(_ account: Account, completion: @escaping ((Result<Void, Error>) -> Void)) {
|
||||
os_log(.debug, log: log, "Refreshing missing articles...")
|
||||
logger.debug("Refreshing missing articles...")
|
||||
|
||||
account.fetchArticleIDsForStatusesWithoutArticlesNewerThanCutoffDate { result in
|
||||
|
||||
@@ -1187,7 +1185,7 @@ private extension FeedbinAccountDelegate {
|
||||
|
||||
case .failure(let error):
|
||||
errorOccurred = true
|
||||
os_log(.error, log: self.log, "Refresh missing articles failed: %@.", error.localizedDescription)
|
||||
self.logger.error("Refreshing missing articles failed: \(error.localizedDescription, privacy: .public)")
|
||||
group.leave()
|
||||
}
|
||||
}
|
||||
@@ -1195,7 +1193,7 @@ private extension FeedbinAccountDelegate {
|
||||
|
||||
group.notify(queue: DispatchQueue.main) {
|
||||
self.refreshProgress.completeTask()
|
||||
os_log(.debug, log: self.log, "Done refreshing missing articles.")
|
||||
self.logger.debug("Done refreshing missing articles.")
|
||||
if errorOccurred {
|
||||
completion(.failure(FeedbinAccountDelegateError.unknown))
|
||||
} else {
|
||||
@@ -1312,7 +1310,7 @@ private extension FeedbinAccountDelegate {
|
||||
case .success(let pendingArticleIDs):
|
||||
process(pendingArticleIDs)
|
||||
case .failure(let error):
|
||||
os_log(.error, log: self.log, "Sync Article Read Status failed: %@.", error.localizedDescription)
|
||||
self.logger.error("Sync Articles Read Status failed: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1364,7 +1362,7 @@ private extension FeedbinAccountDelegate {
|
||||
case .success(let pendingArticleIDs):
|
||||
process(pendingArticleIDs)
|
||||
case .failure(let error):
|
||||
os_log(.error, log: self.log, "Sync Article Starred Status failed: %@.", error.localizedDescription)
|
||||
self.logger.error("Sync Article Starred Status failed: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,10 +11,9 @@ import RSCore
|
||||
import RSParser
|
||||
import RSWeb
|
||||
import SyncDatabase
|
||||
import os.log
|
||||
import Secrets
|
||||
|
||||
final class FeedlyAccountDelegate: AccountDelegate {
|
||||
final class FeedlyAccountDelegate: AccountDelegate, Logging {
|
||||
|
||||
/// Feedly has a sandbox API and a production API.
|
||||
/// This property is referred to when clients need to know which environment it should be pointing to.
|
||||
@@ -61,7 +60,6 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
||||
|
||||
internal let caller: FeedlyAPICaller
|
||||
|
||||
private let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "Feedly")
|
||||
private let database: SyncDatabase
|
||||
|
||||
private weak var currentSyncAllOperation: MainThreadOperation?
|
||||
@@ -112,20 +110,18 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
||||
assert(Thread.isMainThread)
|
||||
|
||||
guard currentSyncAllOperation == nil else {
|
||||
os_log(.debug, log: log, "Ignoring refreshAll: Feedly sync already in progress.")
|
||||
self.logger.debug("Ignoring refreshAll: Feedly sync already in progress.")
|
||||
completion(.success(()))
|
||||
return
|
||||
}
|
||||
|
||||
guard let credentials = credentials else {
|
||||
os_log(.debug, log: log, "Ignoring refreshAll: Feedly account has no credentials.")
|
||||
self.logger.debug("Ignoring refreshAll: Feedly account has no credentials.")
|
||||
completion(.failure(FeedlyAccountDelegateError.notLoggedIn))
|
||||
return
|
||||
}
|
||||
|
||||
let log = self.log
|
||||
|
||||
let syncAllOperation = FeedlySyncAllOperation(account: account, feedlyUserId: credentials.username, caller: caller, database: database, lastSuccessfulFetchStartDate: accountMetadata?.lastArticleFetchStartTime, downloadProgress: refreshProgress, log: log)
|
||||
let syncAllOperation = FeedlySyncAllOperation(account: account, feedlyUserId: credentials.username, caller: caller, database: database, lastSuccessfulFetchStartDate: accountMetadata?.lastArticleFetchStartTime, downloadProgress: refreshProgress)
|
||||
|
||||
syncAllOperation.downloadProgress = refreshProgress
|
||||
|
||||
@@ -136,7 +132,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
||||
self?.accountMetadata?.lastArticleFetchEndTime = Date()
|
||||
}
|
||||
|
||||
os_log(.debug, log: log, "Sync took %{public}.3f seconds", -date.timeIntervalSinceNow)
|
||||
self?.logger.debug("Sync took \(-date.timeIntervalSinceNow) seconds.")
|
||||
completion(result)
|
||||
}
|
||||
|
||||
@@ -154,10 +150,12 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
||||
case .success:
|
||||
completion?(.success(()))
|
||||
case .failure(let error):
|
||||
self.logger.error("Failed to refresh article status for account \(String(describing: account.type)): \(error.localizedDescription)")
|
||||
completion?(.failure(error))
|
||||
}
|
||||
}
|
||||
case .failure(let error):
|
||||
self.logger.error("Failed to send article status for account \(String(describing: account.type)): \(error.localizedDescription)")
|
||||
completion?(.failure(error))
|
||||
}
|
||||
}
|
||||
@@ -165,7 +163,7 @@ 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)
|
||||
let send = FeedlySendArticleStatusesOperation(database: database, service: caller)
|
||||
send.completionBlock = { operation in
|
||||
// TODO: not call with success if operation was canceled? Not sure.
|
||||
DispatchQueue.main.async {
|
||||
@@ -188,7 +186,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
||||
|
||||
let group = DispatchGroup()
|
||||
|
||||
let ingestUnread = FeedlyIngestUnreadArticleIdsOperation(account: account, userId: credentials.username, service: caller, database: database, newerThan: nil, log: log)
|
||||
let ingestUnread = FeedlyIngestUnreadArticleIdsOperation(account: account, userId: credentials.username, service: caller, database: database, newerThan: nil)
|
||||
|
||||
group.enter()
|
||||
ingestUnread.completionBlock = { _ in
|
||||
@@ -196,7 +194,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
||||
|
||||
}
|
||||
|
||||
let ingestStarred = FeedlyIngestStarredArticleIdsOperation(account: account, userId: credentials.username, service: caller, database: database, newerThan: nil, log: log)
|
||||
let ingestStarred = FeedlyIngestStarredArticleIdsOperation(account: account, userId: credentials.username, service: caller, database: database, newerThan: nil)
|
||||
|
||||
group.enter()
|
||||
ingestStarred.completionBlock = { _ in
|
||||
@@ -220,21 +218,21 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
||||
return
|
||||
}
|
||||
|
||||
os_log(.debug, log: log, "Begin importing OPML...")
|
||||
logger.debug("Begin importing OPML...")
|
||||
isOPMLImportInProgress = true
|
||||
refreshProgress.addToNumberOfTasksAndRemaining(1)
|
||||
|
||||
caller.importOpml(data) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
os_log(.debug, log: self.log, "Import OPML done.")
|
||||
self.logger.debug("Import OPML done.")
|
||||
self.refreshProgress.completeTask()
|
||||
self.isOPMLImportInProgress = false
|
||||
DispatchQueue.main.async {
|
||||
completion(.success(()))
|
||||
}
|
||||
case .failure(let error):
|
||||
os_log(.debug, log: self.log, "Import OPML failed.")
|
||||
self.logger.error("Import OPML failed: \(error.localizedDescription)")
|
||||
self.refreshProgress.completeTask()
|
||||
self.isOPMLImportInProgress = false
|
||||
DispatchQueue.main.async {
|
||||
@@ -331,8 +329,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
||||
getStreamContentsService: caller,
|
||||
database: database,
|
||||
container: container,
|
||||
progress: refreshProgress,
|
||||
log: log)
|
||||
progress: refreshProgress)
|
||||
|
||||
addNewFeed.addCompletionHandler = { result in
|
||||
completion(result)
|
||||
@@ -388,7 +385,6 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
||||
service: caller,
|
||||
container: container,
|
||||
progress: refreshProgress,
|
||||
log: log,
|
||||
customFeedName: feed.editedName)
|
||||
|
||||
|
||||
@@ -494,7 +490,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
||||
case .success:
|
||||
break
|
||||
case .failure(let error):
|
||||
os_log(.error, log: self.log, "Restore folder feed error: %@.", error.localizedDescription)
|
||||
self.logger.error("Restore folder feed error: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -534,7 +530,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
||||
}
|
||||
|
||||
func accountWillBeDeleted(_ account: Account) {
|
||||
let logout = FeedlyLogoutOperation(account: account, service: caller, log: log)
|
||||
let logout = FeedlyLogoutOperation(account: account, service: caller)
|
||||
// Dispatch on the shared queue because the lifetime of the account delegate is uncertain.
|
||||
MainThreadOperationQueue.shared.add(logout)
|
||||
}
|
||||
@@ -582,7 +578,7 @@ extension FeedlyAccountDelegate: FeedlyAPICallerDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
let refreshAccessToken = FeedlyRefreshAccessTokenOperation(account: account, service: self, oauthClient: oauthAuthorizationClient, log: log)
|
||||
let refreshAccessToken = FeedlyRefreshAccessTokenOperation(account: account, service: self, oauthClient: oauthAuthorizationClient)
|
||||
refreshAccessToken.downloadProgress = refreshProgress
|
||||
|
||||
/// This must be strongly referenced by the completionBlock of the `FeedlyRefreshAccessTokenOperation`.
|
||||
|
||||
@@ -7,17 +7,16 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
import RSWeb
|
||||
import RSCore
|
||||
import Secrets
|
||||
|
||||
class FeedlyAddExistingFeedOperation: FeedlyOperation, FeedlyOperationDelegate, FeedlyCheckpointOperationDelegate {
|
||||
class FeedlyAddExistingFeedOperation: FeedlyOperation, FeedlyOperationDelegate, FeedlyCheckpointOperationDelegate, Logging {
|
||||
|
||||
private let operationQueue = MainThreadOperationQueue()
|
||||
var addCompletionHandler: ((Result<Void, Error>) -> ())?
|
||||
|
||||
init(account: Account, credentials: Credentials, resource: FeedlyFeedResourceId, service: FeedlyAddFeedToCollectionService, container: Container, progress: DownloadProgress, log: OSLog, customFeedName: String? = nil) throws {
|
||||
init(account: Account, credentials: Credentials, resource: FeedlyFeedResourceId, service: FeedlyAddFeedToCollectionService, container: Container, progress: DownloadProgress, customFeedName: String? = nil) throws {
|
||||
|
||||
let validator = FeedlyFeedContainerValidator(container: container)
|
||||
let (folder, collectionId) = try validator.getValidContainer()
|
||||
@@ -33,7 +32,7 @@ class FeedlyAddExistingFeedOperation: FeedlyOperation, FeedlyOperationDelegate,
|
||||
addRequest.downloadProgress = progress
|
||||
self.operationQueue.add(addRequest)
|
||||
|
||||
let createFeeds = FeedlyCreateFeedsForCollectionFoldersOperation(account: account, feedsAndFoldersProvider: addRequest, log: log)
|
||||
let createFeeds = FeedlyCreateFeedsForCollectionFoldersOperation(account: account, feedsAndFoldersProvider: addRequest)
|
||||
createFeeds.downloadProgress = progress
|
||||
createFeeds.addDependency(addRequest)
|
||||
self.operationQueue.add(createFeeds)
|
||||
|
||||
@@ -7,13 +7,12 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
import SyncDatabase
|
||||
import RSWeb
|
||||
import RSCore
|
||||
import Secrets
|
||||
|
||||
class FeedlyAddNewFeedOperation: FeedlyOperation, FeedlyOperationDelegate, FeedlySearchOperationDelegate, FeedlyCheckpointOperationDelegate {
|
||||
class FeedlyAddNewFeedOperation: FeedlyOperation, FeedlyOperationDelegate, FeedlySearchOperationDelegate, FeedlyCheckpointOperationDelegate, Logging {
|
||||
|
||||
private let operationQueue = MainThreadOperationQueue()
|
||||
private let folder: Folder
|
||||
@@ -26,11 +25,10 @@ class FeedlyAddNewFeedOperation: FeedlyOperation, FeedlyOperationDelegate, Feedl
|
||||
private let addToCollectionService: FeedlyAddFeedToCollectionService
|
||||
private let syncUnreadIdsService: FeedlyGetStreamIdsService
|
||||
private let getStreamContentsService: FeedlyGetStreamContentsService
|
||||
private let log: OSLog
|
||||
private var feedResourceId: FeedlyFeedResourceId?
|
||||
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) throws {
|
||||
|
||||
|
||||
let validator = FeedlyFeedContainerValidator(container: container)
|
||||
@@ -45,7 +43,6 @@ class FeedlyAddNewFeedOperation: FeedlyOperation, FeedlyOperationDelegate, Feedl
|
||||
self.addToCollectionService = addToCollectionService
|
||||
self.syncUnreadIdsService = syncUnreadIdsService
|
||||
self.getStreamContentsService = getStreamContentsService
|
||||
self.log = log
|
||||
|
||||
super.init()
|
||||
|
||||
@@ -91,19 +88,19 @@ class FeedlyAddNewFeedOperation: FeedlyOperation, FeedlyOperationDelegate, Feedl
|
||||
addRequest.downloadProgress = downloadProgress
|
||||
operationQueue.add(addRequest)
|
||||
|
||||
let createFeeds = FeedlyCreateFeedsForCollectionFoldersOperation(account: account, feedsAndFoldersProvider: addRequest, log: log)
|
||||
let createFeeds = FeedlyCreateFeedsForCollectionFoldersOperation(account: account, feedsAndFoldersProvider: addRequest)
|
||||
createFeeds.delegate = self
|
||||
createFeeds.addDependency(addRequest)
|
||||
createFeeds.downloadProgress = downloadProgress
|
||||
operationQueue.add(createFeeds)
|
||||
|
||||
let syncUnread = FeedlyIngestUnreadArticleIdsOperation(account: account, userId: credentials.username, service: syncUnreadIdsService, database: database, newerThan: nil, log: log)
|
||||
let syncUnread = FeedlyIngestUnreadArticleIdsOperation(account: account, userId: credentials.username, service: syncUnreadIdsService, database: database, newerThan: nil)
|
||||
syncUnread.addDependency(createFeeds)
|
||||
syncUnread.downloadProgress = downloadProgress
|
||||
syncUnread.delegate = self
|
||||
operationQueue.add(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)
|
||||
syncFeed.addDependency(syncUnread)
|
||||
syncFeed.downloadProgress = downloadProgress
|
||||
syncFeed.delegate = self
|
||||
@@ -121,7 +118,7 @@ class FeedlyAddNewFeedOperation: FeedlyOperation, FeedlyOperationDelegate, Feedl
|
||||
addCompletionHandler?(.failure(error))
|
||||
addCompletionHandler = nil
|
||||
|
||||
os_log(.debug, log: log, "Unable to add new feed: %{public}@.", error as NSError)
|
||||
logger.debug("Unale to add new feed: \(error.localizedDescription, privacy: .public)")
|
||||
|
||||
cancel()
|
||||
}
|
||||
|
||||
@@ -7,19 +7,17 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
import RSCore
|
||||
|
||||
/// Single responsibility is to accurately reflect Collections and their Feeds as Folders and their Feeds.
|
||||
final class FeedlyCreateFeedsForCollectionFoldersOperation: FeedlyOperation {
|
||||
final class FeedlyCreateFeedsForCollectionFoldersOperation: FeedlyOperation, Logging {
|
||||
|
||||
let account: Account
|
||||
let feedsAndFoldersProvider: FeedlyFeedsAndFoldersProviding
|
||||
let log: OSLog
|
||||
|
||||
init(account: Account, feedsAndFoldersProvider: FeedlyFeedsAndFoldersProviding, log: OSLog) {
|
||||
init(account: Account, feedsAndFoldersProvider: FeedlyFeedsAndFoldersProviding) {
|
||||
self.feedsAndFoldersProvider = feedsAndFoldersProvider
|
||||
self.account = account
|
||||
self.log = log
|
||||
}
|
||||
|
||||
override func run() {
|
||||
@@ -40,7 +38,6 @@ final class FeedlyCreateFeedsForCollectionFoldersOperation: FeedlyOperation {
|
||||
let feedsToRemove = feedsInFolder.filter { !feedsInCollection.contains($0.webFeedID) }
|
||||
if !feedsToRemove.isEmpty {
|
||||
folder.removeFeeds(feedsToRemove)
|
||||
// os_log(.debug, log: log, "\"%@\" - removed: %@", collection.label, feedsToRemove.map { $0.feedID }, feedsInCollection)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -94,7 +91,7 @@ final class FeedlyCreateFeedsForCollectionFoldersOperation: FeedlyOperation {
|
||||
return (feed, folder)
|
||||
}
|
||||
|
||||
os_log(.debug, log: log, "Processing %i feeds.", feedsAndFolders.count)
|
||||
logger.debug("Processing \(feedsAndFolders.count) feeds.")
|
||||
feedsAndFolders.forEach { (feed, folder) in
|
||||
if !folder.has(feed) {
|
||||
folder.addWebFeed(feed)
|
||||
@@ -107,7 +104,7 @@ final class FeedlyCreateFeedsForCollectionFoldersOperation: FeedlyOperation {
|
||||
account.removeFeeds(feedsWithoutCollections)
|
||||
|
||||
if !feedsWithoutCollections.isEmpty {
|
||||
os_log(.debug, log: log, "Removed %i feeds", feedsWithoutCollections.count)
|
||||
logger.debug("Removed \(feedsWithoutCollections.count) feeds.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,28 +7,25 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
import RSCore
|
||||
import RSWeb
|
||||
|
||||
class FeedlyDownloadArticlesOperation: FeedlyOperation {
|
||||
class FeedlyDownloadArticlesOperation: FeedlyOperation, Logging {
|
||||
|
||||
private let account: Account
|
||||
private let log: OSLog
|
||||
private let missingArticleEntryIdProvider: FeedlyEntryIdentifierProviding
|
||||
private let updatedArticleEntryIdProvider: FeedlyEntryIdentifierProviding
|
||||
private let getEntriesService: FeedlyGetEntriesService
|
||||
private let operationQueue = MainThreadOperationQueue()
|
||||
private let finishOperation: FeedlyCheckpointOperation
|
||||
|
||||
init(account: Account, missingArticleEntryIdProvider: FeedlyEntryIdentifierProviding, updatedArticleEntryIdProvider: FeedlyEntryIdentifierProviding, getEntriesService: FeedlyGetEntriesService, log: OSLog) {
|
||||
init(account: Account, missingArticleEntryIdProvider: FeedlyEntryIdentifierProviding, updatedArticleEntryIdProvider: FeedlyEntryIdentifierProviding, getEntriesService: FeedlyGetEntriesService) {
|
||||
self.account = account
|
||||
self.operationQueue.suspend()
|
||||
self.missingArticleEntryIdProvider = missingArticleEntryIdProvider
|
||||
self.updatedArticleEntryIdProvider = updatedArticleEntryIdProvider
|
||||
self.getEntriesService = getEntriesService
|
||||
self.finishOperation = FeedlyCheckpointOperation()
|
||||
self.log = log
|
||||
super.init()
|
||||
self.finishOperation.checkpointDelegate = self
|
||||
self.operationQueue.add(self.finishOperation)
|
||||
@@ -38,26 +35,24 @@ class FeedlyDownloadArticlesOperation: FeedlyOperation {
|
||||
var articleIds = missingArticleEntryIdProvider.entryIds
|
||||
articleIds.formUnion(updatedArticleEntryIdProvider.entryIds)
|
||||
|
||||
os_log(.debug, log: log, "Requesting %{public}i articles.", articleIds.count)
|
||||
self.logger.debug("Requesting \(articleIds.count) articles.")
|
||||
|
||||
let feedlyAPILimitBatchSize = 1000
|
||||
for articleIds in Array(articleIds).chunked(into: feedlyAPILimitBatchSize) {
|
||||
|
||||
let provider = FeedlyEntryIdentifierProvider(entryIds: Set(articleIds))
|
||||
let getEntries = FeedlyGetEntriesOperation(account: account, service: getEntriesService, provider: provider, log: log)
|
||||
let getEntries = FeedlyGetEntriesOperation(account: account, service: getEntriesService, provider: provider)
|
||||
getEntries.delegate = self
|
||||
self.operationQueue.add(getEntries)
|
||||
|
||||
let organiseByFeed = FeedlyOrganiseParsedItemsByFeedOperation(account: account,
|
||||
parsedItemProvider: getEntries,
|
||||
log: log)
|
||||
parsedItemProvider: getEntries)
|
||||
organiseByFeed.delegate = self
|
||||
organiseByFeed.addDependency(getEntries)
|
||||
self.operationQueue.add(organiseByFeed)
|
||||
|
||||
let updateAccount = FeedlyUpdateAccountFeedsWithItemsOperation(account: account,
|
||||
organisedItemsProvider: organiseByFeed,
|
||||
log: log)
|
||||
organisedItemsProvider: organiseByFeed)
|
||||
|
||||
updateAccount.delegate = self
|
||||
updateAccount.addDependency(organiseByFeed)
|
||||
@@ -70,8 +65,7 @@ class FeedlyDownloadArticlesOperation: FeedlyOperation {
|
||||
}
|
||||
|
||||
override func didCancel() {
|
||||
// TODO: fix error on below line: "Expression type '()' is ambiguous without more context"
|
||||
//os_log(.debug, log: log, "Cancelling %{public}@.", self)
|
||||
logger.debug("Cancelling \(String(describing: self)).")
|
||||
operationQueue.cancelAllOperations()
|
||||
super.didCancel()
|
||||
}
|
||||
@@ -90,7 +84,7 @@ extension FeedlyDownloadArticlesOperation: FeedlyOperationDelegate {
|
||||
assert(Thread.isMainThread)
|
||||
|
||||
// Having this log is useful for debugging missing required JSON keys in the response from Feedly, for example.
|
||||
os_log(.debug, log: log, "%{public}@ failed with error: %{public}@.", String(describing: operation), error as NSError)
|
||||
self.logger.debug("\(String(describing: operation)) failed with error: \(error.localizedDescription)")
|
||||
|
||||
cancel()
|
||||
}
|
||||
|
||||
@@ -7,18 +7,16 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
import RSCore
|
||||
|
||||
final class FeedlyFetchIdsForMissingArticlesOperation: FeedlyOperation, FeedlyEntryIdentifierProviding {
|
||||
final class FeedlyFetchIdsForMissingArticlesOperation: FeedlyOperation, FeedlyEntryIdentifierProviding, Logging {
|
||||
|
||||
private let account: Account
|
||||
private let log: OSLog
|
||||
|
||||
private(set) var entryIds = Set<String>()
|
||||
|
||||
init(account: Account, log: OSLog) {
|
||||
init(account: Account) {
|
||||
self.account = account
|
||||
self.log = log
|
||||
}
|
||||
|
||||
override func run() {
|
||||
@@ -29,6 +27,7 @@ final class FeedlyFetchIdsForMissingArticlesOperation: FeedlyOperation, FeedlyEn
|
||||
self.didFinish()
|
||||
|
||||
case .failure(let error):
|
||||
self.logger.error("Failed to fetch articleIDs: \(error.localizedDescription).")
|
||||
self.didFinish(with: error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,37 +7,35 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
import RSCore
|
||||
|
||||
protocol FeedlyCollectionProviding: AnyObject {
|
||||
var collections: [FeedlyCollection] { get }
|
||||
}
|
||||
|
||||
/// Get Collections from Feedly.
|
||||
final class FeedlyGetCollectionsOperation: FeedlyOperation, FeedlyCollectionProviding {
|
||||
final class FeedlyGetCollectionsOperation: FeedlyOperation, FeedlyCollectionProviding, Logging {
|
||||
|
||||
let service: FeedlyGetCollectionsService
|
||||
let log: OSLog
|
||||
|
||||
private(set) var collections = [FeedlyCollection]()
|
||||
|
||||
init(service: FeedlyGetCollectionsService, log: OSLog) {
|
||||
init(service: FeedlyGetCollectionsService) {
|
||||
self.service = service
|
||||
self.log = log
|
||||
}
|
||||
|
||||
override func run() {
|
||||
os_log(.debug, log: log, "Requesting collections.")
|
||||
logger.debug("Requesting collections.")
|
||||
|
||||
service.getCollections { result in
|
||||
switch result {
|
||||
case .success(let collections):
|
||||
os_log(.debug, log: self.log, "Received collections: %{public}@", collections.map { $0.id })
|
||||
self.logger.debug("Receving collections: \(collections.map({ $0.id }))")
|
||||
self.collections = collections
|
||||
self.didFinish()
|
||||
|
||||
case .failure(let error):
|
||||
os_log(.debug, log: self.log, "Unable to request collections: %{public}@.", error as NSError)
|
||||
self.logger.error("Unable to request collections: \(error.localizedDescription).")
|
||||
self.didFinish(with: error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,22 +7,20 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
import RSCore
|
||||
import RSParser
|
||||
|
||||
/// Get full entries for the entry identifiers.
|
||||
final class FeedlyGetEntriesOperation: FeedlyOperation, FeedlyEntryProviding, FeedlyParsedItemProviding {
|
||||
final class FeedlyGetEntriesOperation: FeedlyOperation, FeedlyEntryProviding, FeedlyParsedItemProviding, Logging {
|
||||
|
||||
let account: Account
|
||||
let service: FeedlyGetEntriesService
|
||||
let provider: FeedlyEntryIdentifierProviding
|
||||
let log: OSLog
|
||||
|
||||
init(account: Account, service: FeedlyGetEntriesService, provider: FeedlyEntryIdentifierProviding, log: OSLog) {
|
||||
init(account: Account, service: FeedlyGetEntriesService, provider: FeedlyEntryIdentifierProviding) {
|
||||
self.account = account
|
||||
self.service = service
|
||||
self.provider = provider
|
||||
self.log = log
|
||||
}
|
||||
|
||||
private (set) var entries = [FeedlyEntry]()
|
||||
@@ -38,13 +36,12 @@ final class FeedlyGetEntriesOperation: FeedlyOperation, FeedlyEntryProviding, Fe
|
||||
FeedlyEntryParser(entry: $0).parsedItemRepresentation
|
||||
})
|
||||
|
||||
// TODO: Fix the below. There’s 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)
|
||||
// }
|
||||
if parsed.count != entries.count {
|
||||
let entryIds = Set(entries.map { $0.id })
|
||||
let parsedIds = Set(parsed.map { $0.uniqueID })
|
||||
let difference = entryIds.subtracting(parsedIds)
|
||||
self.logger.debug("\(String(describing: self)) dropping articles with ids: \(difference)).")
|
||||
}
|
||||
|
||||
storedParsedEntries = parsed
|
||||
|
||||
@@ -63,7 +60,7 @@ final class FeedlyGetEntriesOperation: FeedlyOperation, FeedlyEntryProviding, Fe
|
||||
self.didFinish()
|
||||
|
||||
case .failure(let error):
|
||||
os_log(.debug, log: self.log, "Unable to get entries: %{public}@.", error as NSError)
|
||||
self.logger.error("Unable to get entries: \(error.localizedDescription)")
|
||||
self.didFinish(with: error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
import RSParser
|
||||
import os.log
|
||||
import RSCore
|
||||
|
||||
protocol FeedlyEntryProviding {
|
||||
var entries: [FeedlyEntry] { get }
|
||||
@@ -24,7 +24,7 @@ protocol FeedlyGetStreamContentsOperationDelegate: AnyObject {
|
||||
}
|
||||
|
||||
/// Get the stream content of a Collection from Feedly.
|
||||
final class FeedlyGetStreamContentsOperation: FeedlyOperation, FeedlyEntryProviding, FeedlyParsedItemProviding {
|
||||
final class FeedlyGetStreamContentsOperation: FeedlyOperation, FeedlyEntryProviding, FeedlyParsedItemProviding, Logging {
|
||||
|
||||
struct ResourceProvider: FeedlyResourceProviding {
|
||||
var resource: FeedlyResourceId
|
||||
@@ -58,7 +58,7 @@ final class FeedlyGetStreamContentsOperation: FeedlyOperation, FeedlyEntryProvid
|
||||
let entryIds = Set(entries.map { $0.id })
|
||||
let parsedIds = Set(parsed.map { $0.uniqueID })
|
||||
let difference = entryIds.subtracting(parsedIds)
|
||||
os_log(.debug, log: log, "Dropping articles with ids: %{public}@.", difference)
|
||||
logger.debug("Dropping articles with ids: \(difference)")
|
||||
}
|
||||
|
||||
storedParsedEntries = parsed
|
||||
@@ -79,22 +79,20 @@ final class FeedlyGetStreamContentsOperation: FeedlyOperation, FeedlyEntryProvid
|
||||
let unreadOnly: Bool?
|
||||
let newerThan: Date?
|
||||
let continuation: String?
|
||||
let log: OSLog
|
||||
|
||||
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) {
|
||||
self.account = account
|
||||
self.resourceProvider = ResourceProvider(resource: resource)
|
||||
self.service = service
|
||||
self.continuation = continuation
|
||||
self.unreadOnly = unreadOnly
|
||||
self.newerThan = newerThan
|
||||
self.log = log
|
||||
}
|
||||
|
||||
convenience init(account: Account, resourceProvider: FeedlyResourceProviding, service: FeedlyGetStreamContentsService, newerThan: Date?, unreadOnly: Bool? = nil, log: OSLog) {
|
||||
self.init(account: account, resource: resourceProvider.resource, service: service, newerThan: newerThan, unreadOnly: unreadOnly, log: log)
|
||||
convenience init(account: Account, resourceProvider: FeedlyResourceProviding, service: FeedlyGetStreamContentsService, newerThan: Date?, unreadOnly: Bool? = nil) {
|
||||
self.init(account: account, resource: resourceProvider.resource, service: service, newerThan: newerThan, unreadOnly: unreadOnly)
|
||||
}
|
||||
|
||||
override func run() {
|
||||
@@ -108,7 +106,7 @@ final class FeedlyGetStreamContentsOperation: FeedlyOperation, FeedlyEntryProvid
|
||||
self.didFinish()
|
||||
|
||||
case .failure(let error):
|
||||
os_log(.debug, log: self.log, "Unable to get stream contents: %{public}@.", error as NSError)
|
||||
self.logger.error("Unable to get stream contents: \(error.localizedDescription)")
|
||||
self.didFinish(with: error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,14 +7,14 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
import RSCore
|
||||
|
||||
protocol FeedlyGetStreamIdsOperationDelegate: AnyObject {
|
||||
func feedlyGetStreamIdsOperation(_ operation: FeedlyGetStreamIdsOperation, didGet streamIds: FeedlyStreamIds)
|
||||
}
|
||||
|
||||
/// Single responsibility is to get the stream ids from Feedly.
|
||||
final class FeedlyGetStreamIdsOperation: FeedlyOperation, FeedlyEntryIdentifierProviding {
|
||||
final class FeedlyGetStreamIdsOperation: FeedlyOperation, FeedlyEntryIdentifierProviding, Logging {
|
||||
|
||||
var entryIds: Set<String> {
|
||||
guard let ids = streamIds?.ids else {
|
||||
@@ -32,16 +32,14 @@ final class FeedlyGetStreamIdsOperation: FeedlyOperation, FeedlyEntryIdentifierP
|
||||
let resource: FeedlyResourceId
|
||||
let unreadOnly: Bool?
|
||||
let newerThan: Date?
|
||||
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?) {
|
||||
self.account = account
|
||||
self.resource = resource
|
||||
self.service = service
|
||||
self.continuation = continuation
|
||||
self.newerThan = newerThan
|
||||
self.unreadOnly = unreadOnly
|
||||
self.log = log
|
||||
}
|
||||
|
||||
weak var streamIdsDelegate: FeedlyGetStreamIdsOperationDelegate?
|
||||
@@ -57,7 +55,7 @@ final class FeedlyGetStreamIdsOperation: FeedlyOperation, FeedlyEntryIdentifierP
|
||||
self.didFinish()
|
||||
|
||||
case .failure(let error):
|
||||
os_log(.debug, log: self.log, "Unable to get stream ids: %{public}@.", error as NSError)
|
||||
self.logger.error("Unable to get stream ids: \(error.localizedDescription)")
|
||||
self.didFinish(with: error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,32 +7,30 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
import RSCore
|
||||
import Secrets
|
||||
|
||||
/// Single responsibility is to identify articles that have changed since a particular date.
|
||||
///
|
||||
/// 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.
|
||||
class FeedlyGetUpdatedArticleIdsOperation: FeedlyOperation, FeedlyEntryIdentifierProviding {
|
||||
class FeedlyGetUpdatedArticleIdsOperation: FeedlyOperation, FeedlyEntryIdentifierProviding, Logging {
|
||||
|
||||
private let account: Account
|
||||
private let resource: FeedlyResourceId
|
||||
private let service: FeedlyGetStreamIdsService
|
||||
private let newerThan: Date?
|
||||
private let log: OSLog
|
||||
|
||||
init(account: Account, resource: FeedlyResourceId, service: FeedlyGetStreamIdsService, newerThan: Date?, log: OSLog) {
|
||||
init(account: Account, resource: FeedlyResourceId, service: FeedlyGetStreamIdsService, newerThan: Date?) {
|
||||
self.account = account
|
||||
self.resource = resource
|
||||
self.service = service
|
||||
self.newerThan = newerThan
|
||||
self.log = log
|
||||
}
|
||||
|
||||
convenience init(account: Account, userId: String, service: FeedlyGetStreamIdsService, newerThan: Date?, log: OSLog) {
|
||||
convenience init(account: Account, userId: String, service: FeedlyGetStreamIdsService, newerThan: Date?) {
|
||||
let all = FeedlyCategoryResourceId.Global.all(for: userId)
|
||||
self.init(account: account, resource: all, service: service, newerThan: newerThan, log: log)
|
||||
self.init(account: account, resource: all, service: service, newerThan: newerThan)
|
||||
}
|
||||
|
||||
var entryIds: Set<String> {
|
||||
@@ -47,7 +45,7 @@ class FeedlyGetUpdatedArticleIdsOperation: FeedlyOperation, FeedlyEntryIdentifie
|
||||
|
||||
private func getStreamIds(_ continuation: String?) {
|
||||
guard let date = newerThan else {
|
||||
os_log(.debug, log: log, "No date provided so everything must be new (nothing is updated).")
|
||||
logger.debug("No date provided so everything must be new (nothing is updated).")
|
||||
didFinish()
|
||||
return
|
||||
}
|
||||
@@ -66,7 +64,7 @@ class FeedlyGetUpdatedArticleIdsOperation: FeedlyOperation, FeedlyEntryIdentifie
|
||||
storedUpdatedArticleIds.formUnion(streamIds.ids)
|
||||
|
||||
guard let continuation = streamIds.continuation else {
|
||||
os_log(.debug, log: log, "%{public}i articles updated since last successful sync start date.", storedUpdatedArticleIds.count)
|
||||
self.logger.debug("\(self.storedUpdatedArticleIds.count) articles updated since last successful sync start date.")
|
||||
didFinish()
|
||||
return
|
||||
}
|
||||
@@ -74,6 +72,7 @@ class FeedlyGetUpdatedArticleIdsOperation: FeedlyOperation, FeedlyEntryIdentifie
|
||||
getStreamIds(continuation)
|
||||
|
||||
case .failure(let error):
|
||||
self.logger.error("Error getting FeedlyStreamIds: \(error.localizedDescription).")
|
||||
didFinish(with: error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
import RSCore
|
||||
import SyncDatabase
|
||||
import Secrets
|
||||
|
||||
@@ -17,26 +17,24 @@ import Secrets
|
||||
/// 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.
|
||||
/// So this operation has side effects *for the entire account* it operates on.
|
||||
final class FeedlyIngestStarredArticleIdsOperation: FeedlyOperation {
|
||||
final class FeedlyIngestStarredArticleIdsOperation: FeedlyOperation, Logging {
|
||||
|
||||
private let account: Account
|
||||
private let resource: FeedlyResourceId
|
||||
private let service: FeedlyGetStreamIdsService
|
||||
private let database: SyncDatabase
|
||||
private var remoteEntryIds = Set<String>()
|
||||
private let log: OSLog
|
||||
|
||||
convenience init(account: Account, userId: String, service: FeedlyGetStreamIdsService, database: SyncDatabase, newerThan: Date?, log: OSLog) {
|
||||
convenience init(account: Account, userId: String, service: FeedlyGetStreamIdsService, database: SyncDatabase, newerThan: Date?) {
|
||||
let resource = FeedlyTagResourceId.Global.saved(for: userId)
|
||||
self.init(account: account, resource: resource, service: service, database: database, newerThan: newerThan, log: log)
|
||||
self.init(account: account, resource: resource, service: service, database: database, newerThan: newerThan)
|
||||
}
|
||||
|
||||
init(account: Account, resource: FeedlyResourceId, service: FeedlyGetStreamIdsService, database: SyncDatabase, newerThan: Date?, log: OSLog) {
|
||||
init(account: Account, resource: FeedlyResourceId, service: FeedlyGetStreamIdsService, database: SyncDatabase, newerThan: Date?) {
|
||||
self.account = account
|
||||
self.resource = resource
|
||||
self.service = service
|
||||
self.database = database
|
||||
self.log = log
|
||||
}
|
||||
|
||||
override func run() {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
import RSCore
|
||||
import Secrets
|
||||
|
||||
/// Ensure a status exists for every article id the user might be interested in.
|
||||
@@ -15,23 +15,21 @@ import Secrets
|
||||
/// 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.
|
||||
/// So this operation has side effects *for the entire account* it operates on.
|
||||
class FeedlyIngestStreamArticleIdsOperation: FeedlyOperation {
|
||||
class FeedlyIngestStreamArticleIdsOperation: FeedlyOperation, Logging {
|
||||
|
||||
private let account: Account
|
||||
private let resource: FeedlyResourceId
|
||||
private let service: FeedlyGetStreamIdsService
|
||||
private let log: OSLog
|
||||
|
||||
init(account: Account, resource: FeedlyResourceId, service: FeedlyGetStreamIdsService, log: OSLog) {
|
||||
init(account: Account, resource: FeedlyResourceId, service: FeedlyGetStreamIdsService) {
|
||||
self.account = account
|
||||
self.resource = resource
|
||||
self.service = service
|
||||
self.log = log
|
||||
}
|
||||
|
||||
convenience init(account: Account, userId: String, service: FeedlyGetStreamIdsService, log: OSLog) {
|
||||
convenience init(account: Account, userId: String, service: FeedlyGetStreamIdsService) {
|
||||
let all = FeedlyCategoryResourceId.Global.all(for: userId)
|
||||
self.init(account: account, resource: all, service: service, log: log)
|
||||
self.init(account: account, resource: all, service: service)
|
||||
}
|
||||
|
||||
override func run() {
|
||||
@@ -58,7 +56,7 @@ class FeedlyIngestStreamArticleIdsOperation: FeedlyOperation {
|
||||
}
|
||||
|
||||
guard let continuation = streamIds.continuation else {
|
||||
os_log(.debug, log: self.log, "Reached end of stream for %@", self.resource.id)
|
||||
self.logger.debug("Reached end of stream: \(self.resource.id).")
|
||||
self.didFinish()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
import RSCore
|
||||
import RSParser
|
||||
import SyncDatabase
|
||||
import Secrets
|
||||
@@ -18,26 +18,24 @@ import Secrets
|
||||
/// 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.
|
||||
/// So this operation has side effects *for the entire account* it operates on.
|
||||
final class FeedlyIngestUnreadArticleIdsOperation: FeedlyOperation {
|
||||
final class FeedlyIngestUnreadArticleIdsOperation: FeedlyOperation, Logging {
|
||||
|
||||
private let account: Account
|
||||
private let resource: FeedlyResourceId
|
||||
private let service: FeedlyGetStreamIdsService
|
||||
private let database: SyncDatabase
|
||||
private var remoteEntryIds = Set<String>()
|
||||
private let log: OSLog
|
||||
|
||||
convenience init(account: Account, userId: String, service: FeedlyGetStreamIdsService, database: SyncDatabase, newerThan: Date?, log: OSLog) {
|
||||
convenience init(account: Account, userId: String, service: FeedlyGetStreamIdsService, database: SyncDatabase, newerThan: Date?) {
|
||||
let resource = FeedlyCategoryResourceId.Global.all(for: userId)
|
||||
self.init(account: account, resource: resource, service: service, database: database, newerThan: newerThan, log: log)
|
||||
self.init(account: account, resource: resource, service: service, database: database, newerThan: newerThan)
|
||||
}
|
||||
|
||||
init(account: Account, resource: FeedlyResourceId, service: FeedlyGetStreamIdsService, database: SyncDatabase, newerThan: Date?, log: OSLog) {
|
||||
init(account: Account, resource: FeedlyResourceId, service: FeedlyGetStreamIdsService, database: SyncDatabase, newerThan: Date?) {
|
||||
self.account = account
|
||||
self.resource = resource
|
||||
self.service = service
|
||||
self.database = database
|
||||
self.log = log
|
||||
}
|
||||
|
||||
override func run() {
|
||||
|
||||
@@ -7,26 +7,24 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
import RSCore
|
||||
|
||||
protocol FeedlyLogoutService {
|
||||
func logout(completion: @escaping (Result<Void, Error>) -> ())
|
||||
}
|
||||
|
||||
final class FeedlyLogoutOperation: FeedlyOperation {
|
||||
final class FeedlyLogoutOperation: FeedlyOperation, Logging {
|
||||
|
||||
let service: FeedlyLogoutService
|
||||
let account: Account
|
||||
let log: OSLog
|
||||
|
||||
init(account: Account, service: FeedlyLogoutService, log: OSLog) {
|
||||
init(account: Account, service: FeedlyLogoutService) {
|
||||
self.service = service
|
||||
self.account = account
|
||||
self.log = log
|
||||
}
|
||||
|
||||
override func run() {
|
||||
os_log("Requesting logout of %{public}@ account.", "\(account.type)")
|
||||
self.logger.debug("Requesting logout of \(String(describing: self.account.type)).")
|
||||
service.logout(completion: didCompleteLogout(_:))
|
||||
}
|
||||
|
||||
@@ -34,7 +32,7 @@ final class FeedlyLogoutOperation: FeedlyOperation {
|
||||
assert(Thread.isMainThread)
|
||||
switch result {
|
||||
case .success:
|
||||
os_log("Logged out of %{public}@ account.", log: log, "\(account.type)")
|
||||
self.logger.debug("Logged out of \(String(describing: self.account.type)).")
|
||||
do {
|
||||
try account.removeCredentials(type: .oauthAccessToken)
|
||||
try account.removeCredentials(type: .oauthRefreshToken)
|
||||
@@ -44,7 +42,7 @@ final class FeedlyLogoutOperation: FeedlyOperation {
|
||||
didFinish()
|
||||
|
||||
case .failure(let error):
|
||||
os_log("Logout failed because %{public}@.", log: log, error as NSError)
|
||||
self.logger.error("Logout failed because: \(error.localizedDescription)")
|
||||
didFinish(with: error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,25 +7,23 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
import RSCore
|
||||
|
||||
protocol FeedlyFeedsAndFoldersProviding {
|
||||
var feedsAndFolders: [([FeedlyFeed], Folder)] { get }
|
||||
}
|
||||
|
||||
/// Reflect Collections from Feedly as Folders.
|
||||
final class FeedlyMirrorCollectionsAsFoldersOperation: FeedlyOperation, FeedlyFeedsAndFoldersProviding {
|
||||
final class FeedlyMirrorCollectionsAsFoldersOperation: FeedlyOperation, FeedlyFeedsAndFoldersProviding, Logging {
|
||||
|
||||
let account: Account
|
||||
let collectionsProvider: FeedlyCollectionProviding
|
||||
let log: OSLog
|
||||
|
||||
private(set) var feedsAndFolders = [([FeedlyFeed], Folder)]()
|
||||
|
||||
init(account: Account, collectionsProvider: FeedlyCollectionProviding, log: OSLog) {
|
||||
init(account: Account, collectionsProvider: FeedlyCollectionProviding) {
|
||||
self.collectionsProvider = collectionsProvider
|
||||
self.account = account
|
||||
self.log = log
|
||||
}
|
||||
|
||||
override func run() {
|
||||
@@ -46,8 +44,8 @@ final class FeedlyMirrorCollectionsAsFoldersOperation: FeedlyOperation, FeedlyFe
|
||||
return (collection.feeds, folder)
|
||||
}
|
||||
|
||||
os_log(.debug, log: log, "Ensured %i folders for %i collections.", feedsAndFolders.count, collections.count)
|
||||
|
||||
self.logger.debug("Ensured \(self.feedsAndFolders.count) folders for \(collections.count) collections.")
|
||||
|
||||
// Remove folders without a corresponding collection
|
||||
let collectionFolders = Set(feedsAndFolders.map { $0.1 })
|
||||
let foldersWithoutCollections = localFolders.subtracting(collectionFolders)
|
||||
@@ -57,7 +55,7 @@ final class FeedlyMirrorCollectionsAsFoldersOperation: FeedlyOperation, FeedlyFe
|
||||
account.removeFolder(unmatched)
|
||||
}
|
||||
|
||||
os_log(.debug, log: log, "Removed %i folders: %@", foldersWithoutCollections.count, foldersWithoutCollections.map { $0.externalID ?? $0.nameForDisplay })
|
||||
self.logger.debug("Removed \(foldersWithoutCollections.count) folders: \(foldersWithoutCollections.map({ $0.externalID ?? $0.nameForDisplay }))")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
import RSParser
|
||||
import os.log
|
||||
import RSCore
|
||||
|
||||
protocol FeedlyParsedItemsByFeedProviding {
|
||||
var parsedItemsByFeedProviderName: String { get }
|
||||
@@ -16,11 +16,10 @@ protocol FeedlyParsedItemsByFeedProviding {
|
||||
}
|
||||
|
||||
/// Group articles by their feeds.
|
||||
final class FeedlyOrganiseParsedItemsByFeedOperation: FeedlyOperation, FeedlyParsedItemsByFeedProviding {
|
||||
final class FeedlyOrganiseParsedItemsByFeedOperation: FeedlyOperation, FeedlyParsedItemsByFeedProviding, Logging {
|
||||
|
||||
private let account: Account
|
||||
private let parsedItemProvider: FeedlyParsedItemProviding
|
||||
private let log: OSLog
|
||||
|
||||
var parsedItemsByFeedProviderName: String {
|
||||
return name ?? String(describing: Self.self)
|
||||
@@ -33,10 +32,9 @@ final class FeedlyOrganiseParsedItemsByFeedOperation: FeedlyOperation, FeedlyPar
|
||||
|
||||
private var itemsKeyedByFeedId = [String: Set<ParsedItem>]()
|
||||
|
||||
init(account: Account, parsedItemProvider: FeedlyParsedItemProviding, log: OSLog) {
|
||||
init(account: Account, parsedItemProvider: FeedlyParsedItemProviding) {
|
||||
self.account = account
|
||||
self.parsedItemProvider = parsedItemProvider
|
||||
self.log = log
|
||||
}
|
||||
|
||||
override func run() {
|
||||
@@ -60,7 +58,7 @@ final class FeedlyOrganiseParsedItemsByFeedOperation: FeedlyOperation, FeedlyPar
|
||||
dict[key] = value
|
||||
}
|
||||
|
||||
os_log(.debug, log: log, "Grouped %i items by %i feeds for %@", items.count, dict.count, parsedItemProvider.parsedItemProviderName)
|
||||
self.logger.debug("Grouped \(items.count) items by \(dict.count) feeds for \(self.parsedItemProvider.parsedItemProviderName).")
|
||||
|
||||
itemsKeyedByFeedId = dict
|
||||
}
|
||||
|
||||
@@ -7,22 +7,20 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
import RSCore
|
||||
import RSWeb
|
||||
import Secrets
|
||||
|
||||
final class FeedlyRefreshAccessTokenOperation: FeedlyOperation {
|
||||
final class FeedlyRefreshAccessTokenOperation: FeedlyOperation, Logging {
|
||||
|
||||
let service: OAuthAccessTokenRefreshing
|
||||
let oauthClient: OAuthAuthorizationClient
|
||||
let account: Account
|
||||
let log: OSLog
|
||||
|
||||
init(account: Account, service: OAuthAccessTokenRefreshing, oauthClient: OAuthAuthorizationClient, log: OSLog) {
|
||||
init(account: Account, service: OAuthAccessTokenRefreshing, oauthClient: OAuthAuthorizationClient) {
|
||||
self.oauthClient = oauthClient
|
||||
self.service = service
|
||||
self.account = account
|
||||
self.log = log
|
||||
}
|
||||
|
||||
override func run() {
|
||||
@@ -30,7 +28,7 @@ final class FeedlyRefreshAccessTokenOperation: FeedlyOperation {
|
||||
|
||||
do {
|
||||
guard let credentials = try account.retrieveCredentials(type: .oauthRefreshToken) else {
|
||||
os_log(.debug, log: log, "Could not find a refresh token in the keychain. Check the refresh token is added to the Keychain, remove the account and add it again.")
|
||||
self.logger.debug("Could not find a refresh token in the keychain. Check the refresh token is added to the Keychain, remove the account and add it again.")
|
||||
throw TransportError.httpError(status: 403)
|
||||
}
|
||||
|
||||
@@ -41,7 +39,7 @@ final class FeedlyRefreshAccessTokenOperation: FeedlyOperation {
|
||||
return
|
||||
}
|
||||
|
||||
os_log(.debug, log: log, "Refreshing access token.")
|
||||
self.logger.debug("Refreshing access token.")
|
||||
|
||||
// Ignore cancellation after the request is resumed otherwise we may continue storing a potentially invalid token!
|
||||
service.refreshAccessToken(with: refreshToken.secret, client: oauthClient) { result in
|
||||
@@ -55,13 +53,13 @@ final class FeedlyRefreshAccessTokenOperation: FeedlyOperation {
|
||||
switch result {
|
||||
case .success(let grant):
|
||||
do {
|
||||
os_log(.debug, log: log, "Storing refresh token.")
|
||||
self.logger.debug("Storing refresh token.")
|
||||
// Store the refresh token first because it sends this token to the account delegate.
|
||||
if let token = grant.refreshToken {
|
||||
try account.storeCredentials(token)
|
||||
}
|
||||
|
||||
os_log(.debug, log: log, "Storing access token.")
|
||||
self.logger.debug("Storing access token.")
|
||||
// Now store the access token because we want the account delegate to use it.
|
||||
try account.storeCredentials(grant.accessToken)
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
import RSCore
|
||||
|
||||
protocol FeedlyRequestStreamsOperationDelegate: AnyObject {
|
||||
func feedlyRequestStreamsOperation(_ operation: FeedlyRequestStreamsOperation, enqueue collectionStreamOperation: FeedlyGetStreamContentsOperation)
|
||||
@@ -15,24 +15,22 @@ protocol FeedlyRequestStreamsOperationDelegate: AnyObject {
|
||||
|
||||
/// Create one stream request operation for one Feedly collection.
|
||||
/// This is the start of the process of refreshing the entire contents of a Folder.
|
||||
final class FeedlyRequestStreamsOperation: FeedlyOperation {
|
||||
final class FeedlyRequestStreamsOperation: FeedlyOperation, Logging {
|
||||
|
||||
weak var queueDelegate: FeedlyRequestStreamsOperationDelegate?
|
||||
|
||||
let collectionsProvider: FeedlyCollectionProviding
|
||||
let service: FeedlyGetStreamContentsService
|
||||
let account: Account
|
||||
let log: OSLog
|
||||
let newerThan: Date?
|
||||
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) {
|
||||
self.account = account
|
||||
self.service = service
|
||||
self.collectionsProvider = collectionsProvider
|
||||
self.newerThan = newerThan
|
||||
self.unreadOnly = unreadOnly
|
||||
self.log = log
|
||||
}
|
||||
|
||||
override func run() {
|
||||
@@ -50,11 +48,10 @@ final class FeedlyRequestStreamsOperation: FeedlyOperation {
|
||||
resource: resource,
|
||||
service: service,
|
||||
newerThan: newerThan,
|
||||
unreadOnly: unreadOnly,
|
||||
log: log)
|
||||
unreadOnly: unreadOnly)
|
||||
queueDelegate?.feedlyRequestStreamsOperation(self, enqueue: operation)
|
||||
}
|
||||
|
||||
os_log(.debug, log: log, "Requested %i collection streams", collectionsProvider.collections.count)
|
||||
self.logger.debug("Requested \(self.collectionsProvider.collections.count) collections streams.")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,24 +9,22 @@
|
||||
import Foundation
|
||||
import Articles
|
||||
import SyncDatabase
|
||||
import os.log
|
||||
import RSCore
|
||||
|
||||
|
||||
/// Take changes to statuses of articles locally and apply them to the corresponding the articles remotely.
|
||||
final class FeedlySendArticleStatusesOperation: FeedlyOperation {
|
||||
final class FeedlySendArticleStatusesOperation: FeedlyOperation, Logging {
|
||||
|
||||
private let database: SyncDatabase
|
||||
private let log: OSLog
|
||||
private let service: FeedlyMarkArticlesService
|
||||
|
||||
init(database: SyncDatabase, service: FeedlyMarkArticlesService, log: OSLog) {
|
||||
init(database: SyncDatabase, service: FeedlyMarkArticlesService) {
|
||||
self.database = database
|
||||
self.service = service
|
||||
self.log = log
|
||||
}
|
||||
|
||||
override func run() {
|
||||
os_log(.debug, log: log, "Sending article statuses...")
|
||||
logger.debug("Sending article statuses...")
|
||||
|
||||
database.selectForProcessing { result in
|
||||
if self.isCanceled {
|
||||
@@ -81,7 +79,7 @@ private extension FeedlySendArticleStatusesOperation {
|
||||
}
|
||||
|
||||
group.notify(queue: DispatchQueue.main) {
|
||||
os_log(.debug, log: self.log, "Done sending article statuses.")
|
||||
self.logger.debug("Done sending article statuses.")
|
||||
self.didFinish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,17 +7,15 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
import SyncDatabase
|
||||
import RSWeb
|
||||
import RSCore
|
||||
import Secrets
|
||||
|
||||
/// 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, Logging {
|
||||
|
||||
private let operationQueue = MainThreadOperationQueue()
|
||||
private let log: OSLog
|
||||
let syncUUID: UUID
|
||||
|
||||
var syncCompletionHandler: ((Result<Void, Error>) -> ())?
|
||||
@@ -34,9 +32,8 @@ final class FeedlySyncAllOperation: FeedlyOperation {
|
||||
///
|
||||
/// Download articles for statuses at the union of those statuses without its corresponding article and those included in 3 (changed since last successful sync).
|
||||
///
|
||||
init(account: Account, feedlyUserId: String, lastSuccessfulFetchStartDate: Date?, markArticlesService: FeedlyMarkArticlesService, getUnreadService: FeedlyGetStreamIdsService, getCollectionsService: FeedlyGetCollectionsService, getStreamContentsService: FeedlyGetStreamContentsService, getStarredService: FeedlyGetStreamIdsService, getStreamIdsService: FeedlyGetStreamIdsService, getEntriesService: FeedlyGetEntriesService, database: SyncDatabase, downloadProgress: DownloadProgress, log: OSLog) {
|
||||
init(account: Account, feedlyUserId: String, lastSuccessfulFetchStartDate: Date?, markArticlesService: FeedlyMarkArticlesService, getUnreadService: FeedlyGetStreamIdsService, getCollectionsService: FeedlyGetCollectionsService, getStreamContentsService: FeedlyGetStreamContentsService, getStarredService: FeedlyGetStreamIdsService, getStreamIdsService: FeedlyGetStreamIdsService, getEntriesService: FeedlyGetEntriesService, database: SyncDatabase, downloadProgress: DownloadProgress) {
|
||||
self.syncUUID = UUID()
|
||||
self.log = log
|
||||
self.operationQueue.suspend()
|
||||
|
||||
super.init()
|
||||
@@ -44,38 +41,38 @@ final class FeedlySyncAllOperation: FeedlyOperation {
|
||||
self.downloadProgress = downloadProgress
|
||||
|
||||
// Send any read/unread/starred article statuses to Feedly before anything else.
|
||||
let sendArticleStatuses = FeedlySendArticleStatusesOperation(database: database, service: markArticlesService, log: log)
|
||||
let sendArticleStatuses = FeedlySendArticleStatusesOperation(database: database, service: markArticlesService)
|
||||
sendArticleStatuses.delegate = self
|
||||
sendArticleStatuses.downloadProgress = downloadProgress
|
||||
self.operationQueue.add(sendArticleStatuses)
|
||||
|
||||
// Get all the Collections the user has.
|
||||
let getCollections = FeedlyGetCollectionsOperation(service: getCollectionsService, log: log)
|
||||
let getCollections = FeedlyGetCollectionsOperation(service: getCollectionsService)
|
||||
getCollections.delegate = self
|
||||
getCollections.downloadProgress = downloadProgress
|
||||
getCollections.addDependency(sendArticleStatuses)
|
||||
self.operationQueue.add(getCollections)
|
||||
|
||||
// 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)
|
||||
mirrorCollectionsAsFolders.delegate = self
|
||||
mirrorCollectionsAsFolders.addDependency(getCollections)
|
||||
self.operationQueue.add(mirrorCollectionsAsFolders)
|
||||
|
||||
// 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)
|
||||
createFeedsOperation.delegate = self
|
||||
createFeedsOperation.addDependency(mirrorCollectionsAsFolders)
|
||||
self.operationQueue.add(createFeedsOperation)
|
||||
|
||||
let getAllArticleIds = FeedlyIngestStreamArticleIdsOperation(account: account, userId: feedlyUserId, service: getStreamIdsService, log: log)
|
||||
let getAllArticleIds = FeedlyIngestStreamArticleIdsOperation(account: account, userId: feedlyUserId, service: getStreamIdsService)
|
||||
getAllArticleIds.delegate = self
|
||||
getAllArticleIds.downloadProgress = downloadProgress
|
||||
getAllArticleIds.addDependency(createFeedsOperation)
|
||||
self.operationQueue.add(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, userId: feedlyUserId, service: getUnreadService, database: database, newerThan: nil, log: log)
|
||||
let getUnread = FeedlyIngestUnreadArticleIdsOperation(account: account, userId: feedlyUserId, service: getUnreadService, database: database, newerThan: nil)
|
||||
getUnread.delegate = self
|
||||
getUnread.addDependency(getAllArticleIds)
|
||||
getUnread.downloadProgress = downloadProgress
|
||||
@@ -83,21 +80,21 @@ final class FeedlySyncAllOperation: FeedlyOperation {
|
||||
|
||||
// Get each page of the article ids which have been update since the last successful fetch start date.
|
||||
// If the date is nil, this operation provides an empty set (everything is new, nothing is updated).
|
||||
let getUpdated = FeedlyGetUpdatedArticleIdsOperation(account: account, userId: feedlyUserId, service: getStreamIdsService, newerThan: lastSuccessfulFetchStartDate, log: log)
|
||||
let getUpdated = FeedlyGetUpdatedArticleIdsOperation(account: account, userId: feedlyUserId, service: getStreamIdsService, newerThan: lastSuccessfulFetchStartDate)
|
||||
getUpdated.delegate = self
|
||||
getUpdated.downloadProgress = downloadProgress
|
||||
getUpdated.addDependency(createFeedsOperation)
|
||||
self.operationQueue.add(getUpdated)
|
||||
|
||||
// Get each page of the article ids for starred articles.
|
||||
let getStarred = FeedlyIngestStarredArticleIdsOperation(account: account, userId: feedlyUserId, service: getStarredService, database: database, newerThan: nil, log: log)
|
||||
let getStarred = FeedlyIngestStarredArticleIdsOperation(account: account, userId: feedlyUserId, service: getStarredService, database: database, newerThan: nil)
|
||||
getStarred.delegate = self
|
||||
getStarred.downloadProgress = downloadProgress
|
||||
getStarred.addDependency(createFeedsOperation)
|
||||
self.operationQueue.add(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)
|
||||
let getMissingIds = FeedlyFetchIdsForMissingArticlesOperation(account: account)
|
||||
getMissingIds.delegate = self
|
||||
getMissingIds.downloadProgress = downloadProgress
|
||||
getMissingIds.addDependency(getAllArticleIds)
|
||||
@@ -110,8 +107,7 @@ final class FeedlySyncAllOperation: FeedlyOperation {
|
||||
let downloadMissingArticles = FeedlyDownloadArticlesOperation(account: account,
|
||||
missingArticleEntryIdProvider: getMissingIds,
|
||||
updatedArticleEntryIdProvider: getUpdated,
|
||||
getEntriesService: getEntriesService,
|
||||
log: log)
|
||||
getEntriesService: getEntriesService)
|
||||
downloadMissingArticles.delegate = self
|
||||
downloadMissingArticles.downloadProgress = downloadProgress
|
||||
downloadMissingArticles.addDependency(getMissingIds)
|
||||
@@ -126,17 +122,17 @@ final class FeedlySyncAllOperation: FeedlyOperation {
|
||||
self.operationQueue.add(finishOperation)
|
||||
}
|
||||
|
||||
convenience init(account: Account, feedlyUserId: String, caller: FeedlyAPICaller, database: SyncDatabase, lastSuccessfulFetchStartDate: Date?, downloadProgress: DownloadProgress, log: OSLog) {
|
||||
self.init(account: account, feedlyUserId: feedlyUserId, lastSuccessfulFetchStartDate: lastSuccessfulFetchStartDate, markArticlesService: caller, getUnreadService: caller, getCollectionsService: caller, getStreamContentsService: caller, getStarredService: caller, getStreamIdsService: caller, getEntriesService: caller, database: database, downloadProgress: downloadProgress, log: log)
|
||||
convenience init(account: Account, feedlyUserId: String, caller: FeedlyAPICaller, database: SyncDatabase, lastSuccessfulFetchStartDate: Date?, downloadProgress: DownloadProgress) {
|
||||
self.init(account: account, feedlyUserId: feedlyUserId, lastSuccessfulFetchStartDate: lastSuccessfulFetchStartDate, markArticlesService: caller, getUnreadService: caller, getCollectionsService: caller, getStreamContentsService: caller, getStarredService: caller, getStreamIdsService: caller, getEntriesService: caller, database: database, downloadProgress: downloadProgress)
|
||||
}
|
||||
|
||||
override func run() {
|
||||
os_log(.debug, log: log, "Starting sync %{public}@", syncUUID.uuidString)
|
||||
logger.debug("Starting sync \(self.syncUUID.uuidString).")
|
||||
operationQueue.resume()
|
||||
}
|
||||
|
||||
override func didCancel() {
|
||||
os_log(.debug, log: log, "Cancelling sync %{public}@", syncUUID.uuidString)
|
||||
logger.debug("Cancelling sync \(self.syncUUID.uuidString).")
|
||||
self.operationQueue.cancelAllOperations()
|
||||
syncCompletionHandler = nil
|
||||
super.didCancel()
|
||||
@@ -147,7 +143,7 @@ extension FeedlySyncAllOperation: FeedlyCheckpointOperationDelegate {
|
||||
|
||||
func feedlyCheckpointOperationDidReachCheckpoint(_ operation: FeedlyCheckpointOperation) {
|
||||
assert(Thread.isMainThread)
|
||||
os_log(.debug, log: self.log, "Sync completed: %{public}@", syncUUID.uuidString)
|
||||
logger.debug("Sync completed: \(self.syncUUID.uuidString).")
|
||||
|
||||
syncCompletionHandler?(.success(()))
|
||||
syncCompletionHandler = nil
|
||||
@@ -162,7 +158,7 @@ extension FeedlySyncAllOperation: FeedlyOperationDelegate {
|
||||
assert(Thread.isMainThread)
|
||||
|
||||
// Having this log is useful for debugging missing required JSON keys in the response from Feedly, for example.
|
||||
os_log(.debug, log: log, "%{public}@ failed with error: %{public}@.", String(describing: operation), error as NSError)
|
||||
logger.debug("\(String(describing: operation)) failed with error: \(error.localizedDescription).")
|
||||
|
||||
syncCompletionHandler?(.failure(error))
|
||||
syncCompletionHandler = nil
|
||||
|
||||
@@ -7,13 +7,12 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
import RSParser
|
||||
import RSCore
|
||||
import RSWeb
|
||||
import Secrets
|
||||
|
||||
final class FeedlySyncStreamContentsOperation: FeedlyOperation, FeedlyOperationDelegate, FeedlyGetStreamContentsOperationDelegate, FeedlyCheckpointOperationDelegate {
|
||||
final class FeedlySyncStreamContentsOperation: FeedlyOperation, FeedlyOperationDelegate, FeedlyGetStreamContentsOperationDelegate, FeedlyCheckpointOperationDelegate, Logging {
|
||||
|
||||
private let account: Account
|
||||
private let resource: FeedlyResourceId
|
||||
@@ -21,17 +20,15 @@ final class FeedlySyncStreamContentsOperation: FeedlyOperation, FeedlyOperationD
|
||||
private let service: FeedlyGetStreamContentsService
|
||||
private let newerThan: Date?
|
||||
private let isPagingEnabled: Bool
|
||||
private let log: OSLog
|
||||
private let finishOperation: FeedlyCheckpointOperation
|
||||
|
||||
init(account: Account, resource: FeedlyResourceId, service: FeedlyGetStreamContentsService, isPagingEnabled: Bool, newerThan: Date?, log: OSLog) {
|
||||
init(account: Account, resource: FeedlyResourceId, service: FeedlyGetStreamContentsService, isPagingEnabled: Bool, newerThan: Date?) {
|
||||
self.account = account
|
||||
self.resource = resource
|
||||
self.service = service
|
||||
self.isPagingEnabled = isPagingEnabled
|
||||
self.operationQueue.suspend()
|
||||
self.newerThan = newerThan
|
||||
self.log = log
|
||||
self.finishOperation = FeedlyCheckpointOperation()
|
||||
|
||||
super.init()
|
||||
@@ -41,9 +38,9 @@ final class FeedlySyncStreamContentsOperation: FeedlyOperation, FeedlyOperationD
|
||||
enqueueOperations(for: nil)
|
||||
}
|
||||
|
||||
convenience init(account: Account, credentials: Credentials, service: FeedlyGetStreamContentsService, newerThan: Date?, log: OSLog) {
|
||||
convenience init(account: Account, credentials: Credentials, service: FeedlyGetStreamContentsService, newerThan: Date?) {
|
||||
let all = FeedlyCategoryResourceId.Global.all(for: credentials.username)
|
||||
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)
|
||||
}
|
||||
|
||||
override func run() {
|
||||
@@ -51,13 +48,13 @@ final class FeedlySyncStreamContentsOperation: FeedlyOperation, FeedlyOperationD
|
||||
}
|
||||
|
||||
override func didCancel() {
|
||||
os_log(.debug, log: log, "Canceling sync stream contents for %{public}@", resource.id)
|
||||
self.logger.debug("Cancelling sync stream contents for \(self.resource.id).")
|
||||
operationQueue.cancelAllOperations()
|
||||
super.didCancel()
|
||||
}
|
||||
|
||||
func enqueueOperations(for continuation: String?) {
|
||||
os_log(.debug, log: log, "Requesting page for %{public}@", resource.id)
|
||||
self.logger.debug("Requesting page for \(self.resource.id).")
|
||||
let operations = pageOperations(for: continuation)
|
||||
operationQueue.addOperations(operations)
|
||||
}
|
||||
@@ -67,13 +64,12 @@ final class FeedlySyncStreamContentsOperation: FeedlyOperation, FeedlyOperationD
|
||||
resource: resource,
|
||||
service: service,
|
||||
continuation: continuation,
|
||||
newerThan: newerThan,
|
||||
log: log)
|
||||
newerThan: newerThan)
|
||||
|
||||
|
||||
let organiseByFeed = FeedlyOrganiseParsedItemsByFeedOperation(account: account, parsedItemProvider: getPage, log: log)
|
||||
let organiseByFeed = FeedlyOrganiseParsedItemsByFeedOperation(account: account, parsedItemProvider: getPage)
|
||||
|
||||
let updateAccount = FeedlyUpdateAccountFeedsWithItemsOperation(account: account, organisedItemsProvider: organiseByFeed, log: log)
|
||||
let updateAccount = FeedlyUpdateAccountFeedsWithItemsOperation(account: account, organisedItemsProvider: organiseByFeed)
|
||||
|
||||
getPage.delegate = self
|
||||
getPage.streamDelegate = self
|
||||
@@ -91,14 +87,14 @@ final class FeedlySyncStreamContentsOperation: FeedlyOperation, FeedlyOperationD
|
||||
|
||||
func feedlyGetStreamContentsOperation(_ operation: FeedlyGetStreamContentsOperation, didGetContentsOf stream: FeedlyStream) {
|
||||
guard !isCanceled else {
|
||||
os_log(.debug, log: log, "Cancelled requesting page for %{public}@", resource.id)
|
||||
self.logger.debug("Cancelled requesting page for \(self.resource.id).")
|
||||
return
|
||||
}
|
||||
|
||||
os_log(.debug, log: log, "Ingesting %i items from %{public}@", stream.items.count, stream.id)
|
||||
self.logger.debug("Ingesting \(stream.items.count) from \(stream.id).")
|
||||
|
||||
guard isPagingEnabled, let continuation = stream.continuation else {
|
||||
os_log(.debug, log: log, "Reached end of stream for %{public}@", stream.id)
|
||||
self.logger.debug("Reached end of stream for \(stream.id).")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -106,7 +102,7 @@ final class FeedlySyncStreamContentsOperation: FeedlyOperation, FeedlyOperationD
|
||||
}
|
||||
|
||||
func feedlyCheckpointOperationDidReachCheckpoint(_ operation: FeedlyCheckpointOperation) {
|
||||
os_log(.debug, log: log, "Completed ingesting items from %{public}@", resource.id)
|
||||
self.logger.debug("Completed ingesting items from \(self.resource.id).")
|
||||
didFinish()
|
||||
}
|
||||
|
||||
|
||||
@@ -8,19 +8,17 @@
|
||||
|
||||
import Foundation
|
||||
import RSParser
|
||||
import os.log
|
||||
import RSCore
|
||||
|
||||
/// Combine the articles with their feeds for a specific account.
|
||||
final class FeedlyUpdateAccountFeedsWithItemsOperation: FeedlyOperation {
|
||||
final class FeedlyUpdateAccountFeedsWithItemsOperation: FeedlyOperation, Logging {
|
||||
|
||||
private let account: Account
|
||||
private let organisedItemsProvider: FeedlyParsedItemsByFeedProviding
|
||||
private let log: OSLog
|
||||
|
||||
init(account: Account, organisedItemsProvider: FeedlyParsedItemsByFeedProviding, log: OSLog) {
|
||||
init(account: Account, organisedItemsProvider: FeedlyParsedItemsByFeedProviding) {
|
||||
self.account = account
|
||||
self.organisedItemsProvider = organisedItemsProvider
|
||||
self.log = log
|
||||
}
|
||||
|
||||
override func run() {
|
||||
@@ -32,7 +30,7 @@ final class FeedlyUpdateAccountFeedsWithItemsOperation: FeedlyOperation {
|
||||
return
|
||||
}
|
||||
|
||||
os_log(.debug, log: self.log, "Updated %i feeds for \"%@\"", webFeedIDsAndItems.count, self.organisedItemsProvider.parsedItemsByFeedProviderName)
|
||||
self.logger.debug("Updated \(webFeedIDsAndItems.count) feeds for \(self.organisedItemsProvider.parsedItemsByFeedProviderName).")
|
||||
self.didFinish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
import RSCore
|
||||
import RSParser
|
||||
import Articles
|
||||
@@ -19,9 +18,7 @@ public enum LocalAccountDelegateError: String, Error {
|
||||
case invalidParameter = "An invalid parameter was used."
|
||||
}
|
||||
|
||||
final class LocalAccountDelegate: AccountDelegate {
|
||||
|
||||
private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "LocalAccount")
|
||||
final class LocalAccountDelegate: AccountDelegate, Logging {
|
||||
|
||||
weak var account: Account?
|
||||
|
||||
@@ -68,7 +65,7 @@ final class LocalAccountDelegate: AccountDelegate {
|
||||
group.leave()
|
||||
}
|
||||
case .failure(let error):
|
||||
os_log(.error, log: self.log, "Feed Provider refresh error: %@.", error.localizedDescription)
|
||||
self.logger.error("Feed Provided refresh error: \(error.localizedDescription)")
|
||||
feedProviderError = error
|
||||
self.refreshProgress.completeTask()
|
||||
group.leave()
|
||||
|
||||
@@ -13,12 +13,12 @@ import RSDatabase
|
||||
import RSParser
|
||||
import RSWeb
|
||||
import SyncDatabase
|
||||
import os.log
|
||||
|
||||
extension NewsBlurAccountDelegate {
|
||||
|
||||
func refreshFeeds(for account: Account, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
os_log(.debug, log: log, "Refreshing feeds...")
|
||||
logger.debug("Refreshing feeds...")
|
||||
|
||||
|
||||
caller.retrieveFeeds { result in
|
||||
switch result {
|
||||
@@ -39,7 +39,7 @@ extension NewsBlurAccountDelegate {
|
||||
guard let folders = folders else { return }
|
||||
assert(Thread.isMainThread)
|
||||
|
||||
os_log(.debug, log: log, "Syncing folders with %ld folders.", folders.count)
|
||||
logger.debug("Syncing folders with \(folders.count) folders.")
|
||||
|
||||
let folderNames = folders.map { $0.name }
|
||||
|
||||
@@ -77,7 +77,7 @@ extension NewsBlurAccountDelegate {
|
||||
guard let feeds = feeds else { return }
|
||||
assert(Thread.isMainThread)
|
||||
|
||||
os_log(.debug, log: log, "Syncing feeds with %ld feeds.", feeds.count)
|
||||
logger.debug("Syncing feeds with \(feeds.count) feeds.")
|
||||
|
||||
let newsBlurFeedIds = feeds.map { String($0.feedID) }
|
||||
|
||||
@@ -128,7 +128,7 @@ extension NewsBlurAccountDelegate {
|
||||
guard let folders = folders else { return }
|
||||
assert(Thread.isMainThread)
|
||||
|
||||
os_log(.debug, log: log, "Syncing folders with %ld folders.", folders.count)
|
||||
logger.debug("Syncing folders with \(folders.count) folders.")
|
||||
|
||||
// Set up some structures to make syncing easier
|
||||
let relationships = folders.map({ $0.asRelationships }).flatMap { $0 }
|
||||
@@ -251,7 +251,7 @@ extension NewsBlurAccountDelegate {
|
||||
}
|
||||
|
||||
self.refreshUnreadStories(for: account, hashes: Array(hashes[numberOfStories...]), updateFetchDate: date) { result in
|
||||
os_log(.debug, log: self.log, "Done refreshing stories.")
|
||||
self.logger.debug("Done refreshing stories.")
|
||||
switch result {
|
||||
case .success:
|
||||
completion(.success(()))
|
||||
@@ -300,7 +300,7 @@ extension NewsBlurAccountDelegate {
|
||||
group.leave()
|
||||
case .failure(let error):
|
||||
errorOccurred = true
|
||||
os_log(.error, log: self.log, "Story status sync call failed: %@.", error.localizedDescription)
|
||||
self.logger.error("Story status sync call failed: \(error.localizedDescription)")
|
||||
self.database.resetSelectedForProcessing(storyHashGroup.map { String($0) } )
|
||||
group.leave()
|
||||
}
|
||||
@@ -359,7 +359,7 @@ extension NewsBlurAccountDelegate {
|
||||
case .success(let pendingArticleIDs):
|
||||
process(pendingArticleIDs)
|
||||
case .failure(let error):
|
||||
os_log(.error, log: self.log, "Sync Story Read Status failed: %@.", error.localizedDescription)
|
||||
self.logger.error("Sync story read status failed: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -407,7 +407,7 @@ extension NewsBlurAccountDelegate {
|
||||
case .success(let pendingArticleIDs):
|
||||
process(pendingArticleIDs)
|
||||
case .failure(let error):
|
||||
os_log(.error, log: self.log, "Sync Story Starred Status failed: %@.", error.localizedDescription)
|
||||
self.logger.error("Sync story starred status failed: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,10 +12,9 @@ import RSDatabase
|
||||
import RSParser
|
||||
import RSWeb
|
||||
import SyncDatabase
|
||||
import os.log
|
||||
import Secrets
|
||||
|
||||
final class NewsBlurAccountDelegate: AccountDelegate {
|
||||
final class NewsBlurAccountDelegate: AccountDelegate, Logging {
|
||||
|
||||
var behaviors: AccountBehaviors = []
|
||||
|
||||
@@ -31,7 +30,6 @@ final class NewsBlurAccountDelegate: AccountDelegate {
|
||||
var refreshProgress = DownloadProgress(numberOfTasks: 0)
|
||||
|
||||
let caller: NewsBlurAPICaller
|
||||
let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "NewsBlur")
|
||||
let database: SyncDatabase
|
||||
|
||||
init(dataFolder: String, transport: Transport?) {
|
||||
@@ -133,8 +131,7 @@ final class NewsBlurAccountDelegate: AccountDelegate {
|
||||
}
|
||||
|
||||
func sendArticleStatus(for account: Account, completion: @escaping (Result<Void, Error>) -> ()) {
|
||||
os_log(.debug, log: log, "Sending story statuses...")
|
||||
|
||||
logger.debug("Sending story statuses...")
|
||||
database.selectForProcessing { result in
|
||||
|
||||
func processStatuses(_ syncStatuses: [SyncStatus]) {
|
||||
@@ -187,7 +184,7 @@ final class NewsBlurAccountDelegate: AccountDelegate {
|
||||
}
|
||||
|
||||
group.notify(queue: DispatchQueue.main) {
|
||||
os_log(.debug, log: self.log, "Done sending article statuses.")
|
||||
self.logger.debug("Done sending article statuses.")
|
||||
if errorOccurred {
|
||||
completion(.failure(NewsBlurError.unknown))
|
||||
} else {
|
||||
@@ -206,7 +203,7 @@ final class NewsBlurAccountDelegate: AccountDelegate {
|
||||
}
|
||||
|
||||
func refreshArticleStatus(for account: Account, completion: @escaping (Result<Void, Error>) -> ()) {
|
||||
os_log(.debug, log: log, "Refreshing story statuses...")
|
||||
logger.debug("Refreshing story statuses...")
|
||||
|
||||
let group = DispatchGroup()
|
||||
var errorOccurred = false
|
||||
@@ -220,7 +217,7 @@ final class NewsBlurAccountDelegate: AccountDelegate {
|
||||
}
|
||||
case .failure(let error):
|
||||
errorOccurred = true
|
||||
os_log(.info, log: self.log, "Retrieving unread stories failed: %@.", error.localizedDescription)
|
||||
self.logger.error("Retrieving unread stories failed: \(error.localizedDescription)")
|
||||
group.leave()
|
||||
}
|
||||
}
|
||||
@@ -234,13 +231,13 @@ final class NewsBlurAccountDelegate: AccountDelegate {
|
||||
}
|
||||
case .failure(let error):
|
||||
errorOccurred = true
|
||||
os_log(.info, log: self.log, "Retrieving starred stories failed: %@.", error.localizedDescription)
|
||||
self.logger.error("Retrieving starred stories failed: \(error.localizedDescription)")
|
||||
group.leave()
|
||||
}
|
||||
}
|
||||
|
||||
group.notify(queue: DispatchQueue.main) {
|
||||
os_log(.debug, log: self.log, "Done refreshing article statuses.")
|
||||
self.logger.debug("Done refreshing article statuses.")
|
||||
if errorOccurred {
|
||||
completion(.failure(NewsBlurError.unknown))
|
||||
} else {
|
||||
@@ -250,9 +247,9 @@ final class NewsBlurAccountDelegate: AccountDelegate {
|
||||
}
|
||||
|
||||
func refreshStories(for account: Account, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
os_log(.debug, log: log, "Refreshing stories...")
|
||||
os_log(.debug, log: log, "Refreshing unread stories...")
|
||||
|
||||
self.logger.debug("Refreshing stories...")
|
||||
self.logger.debug("Refreshing unread stories...")
|
||||
|
||||
caller.retrieveUnreadStoryHashes { result in
|
||||
switch result {
|
||||
case .success(let storyHashes):
|
||||
@@ -269,7 +266,7 @@ final class NewsBlurAccountDelegate: AccountDelegate {
|
||||
}
|
||||
|
||||
func refreshMissingStories(for account: Account, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
os_log(.debug, log: log, "Refreshing missing stories...")
|
||||
self.logger.debug("Refreshing missing stories...")
|
||||
|
||||
account.fetchArticleIDsForStatusesWithoutArticlesNewerThanCutoffDate { result in
|
||||
|
||||
@@ -296,7 +293,7 @@ final class NewsBlurAccountDelegate: AccountDelegate {
|
||||
}
|
||||
case .failure(let error):
|
||||
errorOccurred = true
|
||||
os_log(.error, log: self.log, "Refresh missing stories failed: %@.", error.localizedDescription)
|
||||
self.logger.error("Refreshing missing stories failed: \(error.localizedDescription)")
|
||||
group.leave()
|
||||
}
|
||||
}
|
||||
@@ -304,7 +301,7 @@ final class NewsBlurAccountDelegate: AccountDelegate {
|
||||
|
||||
group.notify(queue: DispatchQueue.main) {
|
||||
self.refreshProgress.completeTask()
|
||||
os_log(.debug, log: self.log, "Done refreshing missing stories.")
|
||||
self.logger.debug("Done refreshing stories.")
|
||||
if errorOccurred {
|
||||
completion(.failure(NewsBlurError.unknown))
|
||||
} else {
|
||||
@@ -568,12 +565,12 @@ final class NewsBlurAccountDelegate: AccountDelegate {
|
||||
case .success:
|
||||
break
|
||||
case .failure(let error):
|
||||
os_log(.error, log: self.log, "Restore folder feed error: %@.", error.localizedDescription)
|
||||
self.logger.error("Restore folder feed error: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
case .failure(let error):
|
||||
os_log(.error, log: self.log, "Restore folder feed error: %@.", error.localizedDescription)
|
||||
self.logger.error("Restore folder feed error: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,13 +7,10 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
import RSCore
|
||||
import RSParser
|
||||
|
||||
final class OPMLFile {
|
||||
|
||||
private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "opmlFile")
|
||||
final class OPMLFile: Logging {
|
||||
|
||||
private let fileURL: URL
|
||||
private let account: Account
|
||||
@@ -50,8 +47,8 @@ final class OPMLFile {
|
||||
|
||||
do {
|
||||
try opmlDocumentString.write(to: fileURL, atomically: true, encoding: .utf8)
|
||||
} catch let error as NSError {
|
||||
os_log(.error, log: log, "OPML save to disk failed: %@.", error.localizedDescription)
|
||||
} catch let error {
|
||||
logger.error("OPML save to disk failed: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +73,7 @@ private extension OPMLFile {
|
||||
do {
|
||||
fileData = try Data(contentsOf: fileURL)
|
||||
} catch {
|
||||
os_log(.error, log: log, "OPML read from disk failed: %@.", error.localizedDescription)
|
||||
logger.error("OPML read from disk failed: \(error.localizedDescription)")
|
||||
}
|
||||
|
||||
return fileData
|
||||
@@ -89,7 +86,7 @@ private extension OPMLFile {
|
||||
do {
|
||||
opmlDocument = try RSOPMLParser.parseOPML(with: parserData)
|
||||
} catch {
|
||||
os_log(.error, log: log, "OPML Import failed: %@.", error.localizedDescription)
|
||||
logger.error("OPML Import failed: \(error.localizedDescription)")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ import RSCore
|
||||
import RSParser
|
||||
import RSWeb
|
||||
import SyncDatabase
|
||||
import os.log
|
||||
import Secrets
|
||||
|
||||
public enum ReaderAPIAccountDelegateError: LocalizedError {
|
||||
@@ -34,14 +33,13 @@ public enum ReaderAPIAccountDelegateError: LocalizedError {
|
||||
}
|
||||
}
|
||||
|
||||
final class ReaderAPIAccountDelegate: AccountDelegate {
|
||||
final class ReaderAPIAccountDelegate: AccountDelegate, Logging {
|
||||
|
||||
private let variant: ReaderAPIVariant
|
||||
|
||||
private let database: SyncDatabase
|
||||
|
||||
private let caller: ReaderAPICaller
|
||||
private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "ReaderAPI")
|
||||
|
||||
var behaviors: AccountBehaviors {
|
||||
var behaviors: AccountBehaviors = [.disallowOPMLImports, .disallowFeedInMultipleFolders]
|
||||
@@ -196,7 +194,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
|
||||
}
|
||||
|
||||
func sendArticleStatus(for account: Account, completion: @escaping ((Result<Void, Error>) -> Void)) {
|
||||
os_log(.debug, log: log, "Sending article statuses...")
|
||||
logger.debug("Sending article statuses...")
|
||||
|
||||
database.selectForProcessing { result in
|
||||
|
||||
@@ -229,7 +227,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
|
||||
}
|
||||
|
||||
group.notify(queue: DispatchQueue.main) {
|
||||
os_log(.debug, log: self.log, "Done sending article statuses.")
|
||||
self.logger.debug("Done sending article statuses.")
|
||||
completion(.success(()))
|
||||
}
|
||||
}
|
||||
@@ -244,7 +242,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
|
||||
}
|
||||
|
||||
func refreshArticleStatus(for account: Account, completion: @escaping ((Result<Void, Error>) -> Void)) {
|
||||
os_log(.debug, log: log, "Refreshing article statuses...")
|
||||
logger.debug("Refreshing article statuses...")
|
||||
|
||||
let group = DispatchGroup()
|
||||
var errorOccurred = false
|
||||
@@ -258,7 +256,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
|
||||
}
|
||||
case .failure(let error):
|
||||
errorOccurred = true
|
||||
os_log(.info, log: self.log, "Retrieving unread entries failed: %@.", error.localizedDescription)
|
||||
self.logger.info("Retrieving unread entries failed: \(error.localizedDescription)")
|
||||
group.leave()
|
||||
}
|
||||
|
||||
@@ -273,14 +271,14 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
|
||||
}
|
||||
case .failure(let error):
|
||||
errorOccurred = true
|
||||
os_log(.info, log: self.log, "Retrieving starred entries failed: %@.", error.localizedDescription)
|
||||
self.logger.info("Retrieving starred entries failed: \(error.localizedDescription)")
|
||||
group.leave()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
group.notify(queue: DispatchQueue.main) {
|
||||
os_log(.debug, log: self.log, "Done refreshing article statuses.")
|
||||
self.logger.debug("Done refreshing article statuses.")
|
||||
if errorOccurred {
|
||||
completion(.failure(ReaderAPIAccountDelegateError.unknown))
|
||||
} else {
|
||||
@@ -342,7 +340,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
|
||||
self.clearFolderRelationship(for: feed, folderExternalID: folder.externalID)
|
||||
}
|
||||
case .failure(let error):
|
||||
os_log(.error, log: self.log, "Remove feed error: %@.", error.localizedDescription)
|
||||
self.logger.error("Remove feed error: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -361,7 +359,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
|
||||
account.clearWebFeedMetadata(feed)
|
||||
}
|
||||
case .failure(let error):
|
||||
os_log(.error, log: self.log, "Remove feed error: %@.", error.localizedDescription)
|
||||
self.logger.error("Remove feed error: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -593,7 +591,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
|
||||
case .success:
|
||||
break
|
||||
case .failure(let error):
|
||||
os_log(.error, log: self.log, "Restore folder feed error: %@.", error.localizedDescription)
|
||||
self.logger.error("Restore folder feed error: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -711,7 +709,7 @@ private extension ReaderAPIAccountDelegate {
|
||||
|
||||
guard !folderTags.isEmpty else { return }
|
||||
|
||||
os_log(.debug, log: log, "Syncing folders with %ld tags.", folderTags.count)
|
||||
logger.debug("Syncing folders with \(folderTags.count) tags.")
|
||||
|
||||
let readerFolderExternalIDs = folderTags.compactMap { $0.tagID }
|
||||
|
||||
@@ -751,7 +749,7 @@ private extension ReaderAPIAccountDelegate {
|
||||
guard let subscriptions = subscriptions else { return }
|
||||
assert(Thread.isMainThread)
|
||||
|
||||
os_log(.debug, log: log, "Syncing feeds with %ld subscriptions.", subscriptions.count)
|
||||
logger.debug("Syncing feeds with \(subscriptions.count) subscriptions")
|
||||
|
||||
let subFeedIds = subscriptions.map { $0.feedID }
|
||||
|
||||
@@ -793,7 +791,7 @@ private extension ReaderAPIAccountDelegate {
|
||||
func syncFeedFolderRelationship(_ account: Account, _ subscriptions: [ReaderAPISubscription]?) {
|
||||
guard let subscriptions = subscriptions else { return }
|
||||
assert(Thread.isMainThread)
|
||||
os_log(.debug, log: log, "Syncing taggings with %ld subscriptions.", subscriptions.count)
|
||||
logger.debug("Syncing taggins with \(subscriptions.count) subscriptions.")
|
||||
|
||||
// Set up some structures to make syncing easier
|
||||
let folderDict = externalIDToFolderDictionary(with: account.folders)
|
||||
@@ -887,7 +885,7 @@ private extension ReaderAPIAccountDelegate {
|
||||
self.database.deleteSelectedForProcessing(articleIDGroup.map { $0 } )
|
||||
group.leave()
|
||||
case .failure(let error):
|
||||
os_log(.error, log: self.log, "Article status sync call failed: %@.", error.localizedDescription)
|
||||
self.logger.error("Article status sync call failed: \(error.localizedDescription)")
|
||||
self.database.resetSelectedForProcessing(articleIDGroup.map { $0 } )
|
||||
group.leave()
|
||||
}
|
||||
@@ -987,7 +985,7 @@ private extension ReaderAPIAccountDelegate {
|
||||
return
|
||||
}
|
||||
|
||||
os_log(.debug, log: self.log, "Refreshing missing articles...")
|
||||
self.logger.debug("Refreshing missing articles...")
|
||||
let group = DispatchGroup()
|
||||
|
||||
let articleIDs = Array(fetchedArticleIDs)
|
||||
@@ -1007,7 +1005,7 @@ private extension ReaderAPIAccountDelegate {
|
||||
}
|
||||
|
||||
case .failure(let error):
|
||||
os_log(.error, log: self.log, "Refresh missing articles failed: %@.", error.localizedDescription)
|
||||
self.logger.error("Refresh missing articles failed: \(error.localizedDescription)")
|
||||
group.leave()
|
||||
}
|
||||
}
|
||||
@@ -1015,7 +1013,7 @@ private extension ReaderAPIAccountDelegate {
|
||||
|
||||
group.notify(queue: DispatchQueue.main) {
|
||||
self.refreshProgress.completeTask()
|
||||
os_log(.debug, log: self.log, "Done refreshing missing articles.")
|
||||
self.logger.debug("Done refreshing missing articles.")
|
||||
completion()
|
||||
}
|
||||
}
|
||||
@@ -1121,7 +1119,7 @@ private extension ReaderAPIAccountDelegate {
|
||||
case .success(let pendingArticleIDs):
|
||||
process(pendingArticleIDs)
|
||||
case .failure(let error):
|
||||
os_log(.error, log: self.log, "Sync Article Read Status failed: %@.", error.localizedDescription)
|
||||
self.logger.error("Sync Article Read Status failed: \(error.localizedDescription)")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1170,7 +1168,7 @@ private extension ReaderAPIAccountDelegate {
|
||||
case .success(let pendingArticleIDs):
|
||||
process(pendingArticleIDs)
|
||||
case .failure(let error):
|
||||
os_log(.error, log: self.log, "Sync Article Starred Status failed: %@.", error.localizedDescription)
|
||||
self.logger.error("Sync Article Starred Status failed: \(error.localizedDescription)")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,12 +7,9 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
import RSCore
|
||||
|
||||
final class WebFeedMetadataFile {
|
||||
|
||||
private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "webFeedMetadataFile")
|
||||
final class WebFeedMetadataFile: Logging {
|
||||
|
||||
private let fileURL: URL
|
||||
private let account: Account
|
||||
@@ -52,8 +49,8 @@ final class WebFeedMetadataFile {
|
||||
do {
|
||||
let data = try encoder.encode(feedMetadata)
|
||||
try data.write(to: fileURL)
|
||||
} catch let error as NSError {
|
||||
os_log(.error, log: log, "Save to disk failed: %@.", error.localizedDescription)
|
||||
} catch let error {
|
||||
logger.error("Save to disk failed: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ import Account
|
||||
import RSCore
|
||||
import RSCoreResources
|
||||
import Secrets
|
||||
import OSLog
|
||||
import CrashReporter
|
||||
|
||||
// If we're not going to import Sparkle, provide dummy protocols to make it easy
|
||||
@@ -30,15 +29,13 @@ import Sparkle
|
||||
var appDelegate: AppDelegate!
|
||||
|
||||
@NSApplicationMain
|
||||
class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, UNUserNotificationCenterDelegate, UnreadCountProvider, SPUStandardUserDriverDelegate, SPUUpdaterDelegate
|
||||
class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, UNUserNotificationCenterDelegate, UnreadCountProvider, SPUStandardUserDriverDelegate, SPUUpdaterDelegate, Logging
|
||||
{
|
||||
|
||||
private struct WindowRestorationIdentifiers {
|
||||
static let mainWindow = "mainWindow"
|
||||
}
|
||||
|
||||
var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "Application")
|
||||
|
||||
var userNotificationManager: UserNotificationManager!
|
||||
var faviconDownloader: FaviconDownloader!
|
||||
var imageDownloader: ImageDownloader!
|
||||
@@ -194,14 +191,14 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
|
||||
try self.softwareUpdater.start()
|
||||
}
|
||||
catch {
|
||||
NSLog("Failed to start software updater with error: \(error)")
|
||||
logger.error("Failed to start software updater with error: \(error, privacy: .public)")
|
||||
}
|
||||
#endif
|
||||
|
||||
AppDefaults.shared.registerDefaults()
|
||||
let isFirstRun = AppDefaults.shared.isFirstRun
|
||||
if isFirstRun {
|
||||
os_log(.debug, log: log, "Is first run.")
|
||||
logger.debug("Is first run")
|
||||
}
|
||||
let localAccount = AccountManager.shared.defaultAccount
|
||||
|
||||
@@ -856,6 +853,7 @@ internal extension AppDelegate {
|
||||
confirmImportSuccess(themeName: theme.name)
|
||||
} catch {
|
||||
NSApplication.shared.presentError(error)
|
||||
logger.error("Error importing theme: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -884,6 +882,7 @@ internal extension AppDelegate {
|
||||
}
|
||||
} catch {
|
||||
NotificationCenter.default.post(name: .didFailToImportThemeWithError, object: nil, userInfo: ["error" : error, "path": filename])
|
||||
logger.error("Error importing theme: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1012,12 +1011,12 @@ private extension AppDelegate {
|
||||
|
||||
let account = AccountManager.shared.existingAccount(with: accountID)
|
||||
guard account != nil else {
|
||||
os_log(.debug, log: log, "No account found from notification.")
|
||||
logger.debug("No account found from notification.")
|
||||
return
|
||||
}
|
||||
let article = try? account!.fetchArticles(.articleIDs([articleID]))
|
||||
guard article != nil else {
|
||||
os_log(.debug, log: log, "No article found from search using %@", articleID)
|
||||
logger.debug("No article found from search using: \(articleID, privacy: .public)")
|
||||
return
|
||||
}
|
||||
account!.markArticles(article!, statusKey: .read, flag: true) { _ in }
|
||||
@@ -1031,12 +1030,12 @@ private extension AppDelegate {
|
||||
}
|
||||
let account = AccountManager.shared.existingAccount(with: accountID)
|
||||
guard account != nil else {
|
||||
os_log(.debug, log: log, "No account found from notification.")
|
||||
logger.debug("No account found from notification.")
|
||||
return
|
||||
}
|
||||
let article = try? account!.fetchArticles(.articleIDs([articleID]))
|
||||
guard article != nil else {
|
||||
os_log(.debug, log: log, "No article found from search using %@", articleID)
|
||||
logger.debug("No article found from search using: \(articleID, privacy: .public)")
|
||||
return
|
||||
}
|
||||
account!.markArticles(article!, statusKey: .starred, flag: true) { _ in }
|
||||
|
||||
@@ -8,18 +8,18 @@
|
||||
|
||||
import AppKit
|
||||
import Account
|
||||
import os.log
|
||||
import RSCore
|
||||
|
||||
struct ErrorHandler {
|
||||
struct ErrorHandler: Logging {
|
||||
|
||||
private static var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "Account")
|
||||
|
||||
|
||||
public static func present(_ error: Error) {
|
||||
NSApplication.shared.presentError(error)
|
||||
}
|
||||
|
||||
public static func log(_ error: Error) {
|
||||
os_log(.error, log: self.log, "%@", error.localizedDescription)
|
||||
ErrorHandler.logger.error("\(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import Foundation
|
||||
import RSCore
|
||||
import Articles
|
||||
import Account
|
||||
import os.log
|
||||
|
||||
protocol TimelineDelegate: AnyObject {
|
||||
func timelineSelectionDidChange(_: TimelineViewController, selectedArticles: [Article]?)
|
||||
|
||||
@@ -8,10 +8,11 @@
|
||||
|
||||
import AppKit
|
||||
import Account
|
||||
import RSCore
|
||||
import RSWeb
|
||||
import Secrets
|
||||
|
||||
class AccountsFeedbinWindowController: NSWindowController {
|
||||
class AccountsFeedbinWindowController: NSWindowController, Logging {
|
||||
|
||||
@IBOutlet weak var signInTextField: NSTextField!
|
||||
@IBOutlet weak var noAccountTextField: NSTextField!
|
||||
@@ -115,6 +116,7 @@ class AccountsFeedbinWindowController: NSWindowController {
|
||||
self.hostWindow?.endSheet(self.window!, returnCode: NSApplication.ModalResponse.OK)
|
||||
} catch {
|
||||
self.errorMessageLabel.stringValue = NSLocalizedString("Keychain error while storing credentials.", comment: "Credentials Error")
|
||||
self.logger.error("Keychain error while storing credentials: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
|
||||
case .failure:
|
||||
|
||||
@@ -9,9 +9,10 @@
|
||||
import AppKit
|
||||
import Account
|
||||
import RSWeb
|
||||
import RSCore
|
||||
import Secrets
|
||||
|
||||
class AccountsNewsBlurWindowController: NSWindowController {
|
||||
class AccountsNewsBlurWindowController: NSWindowController, Logging {
|
||||
|
||||
@IBOutlet weak var signInTextField: NSTextField!
|
||||
@IBOutlet weak var noAccountTextField: NSTextField!
|
||||
@@ -113,6 +114,7 @@ class AccountsNewsBlurWindowController: NSWindowController {
|
||||
self.hostWindow?.endSheet(self.window!, returnCode: NSApplication.ModalResponse.OK)
|
||||
} catch {
|
||||
self.errorMessageLabel.stringValue = NSLocalizedString("Keychain error while storing credentials.", comment: "Credentials Error")
|
||||
self.logger.error("Keychain error while storing credentials: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
|
||||
case .failure:
|
||||
|
||||
@@ -9,9 +9,10 @@
|
||||
import AppKit
|
||||
import Account
|
||||
import RSWeb
|
||||
import RSCore
|
||||
import Secrets
|
||||
|
||||
class AccountsReaderAPIWindowController: NSWindowController {
|
||||
class AccountsReaderAPIWindowController: NSWindowController, Logging {
|
||||
|
||||
@IBOutlet weak var titleImageView: NSImageView!
|
||||
@IBOutlet weak var titleLabel: NSTextField!
|
||||
@@ -170,6 +171,7 @@ class AccountsReaderAPIWindowController: NSWindowController {
|
||||
self.hostWindow?.endSheet(self.window!, returnCode: NSApplication.ModalResponse.OK)
|
||||
} catch {
|
||||
self.errorMessageLabel.stringValue = NSLocalizedString("Keychain error while storing credentials.", comment: "Credentials Error")
|
||||
self.logger.error("Keychain error while storing credentials: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
|
||||
case .failure:
|
||||
|
||||
@@ -55,7 +55,7 @@ extension AppDelegate : AppDelegateAppleEvents {
|
||||
|
||||
if let themeURL = URL(string: themeURLString) {
|
||||
let request = URLRequest(url: themeURL)
|
||||
let task = URLSession.shared.downloadTask(with: request) { location, response, error in
|
||||
let task = URLSession.shared.downloadTask(with: request) { [weak self] location, response, error in
|
||||
guard let location = location else {
|
||||
return
|
||||
}
|
||||
@@ -64,6 +64,7 @@ extension AppDelegate : AppDelegateAppleEvents {
|
||||
try ArticleThemeDownloader.shared.handleFile(at: location)
|
||||
} catch {
|
||||
NotificationCenter.default.post(name: .didFailToImportThemeWithError, object: nil, userInfo: ["error": error])
|
||||
self?.logger.error("Failed to import theme: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
task.resume()
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
import os.log
|
||||
|
||||
class ShareViewController: NSViewController {
|
||||
|
||||
@@ -16,7 +15,6 @@ class ShareViewController: NSViewController {
|
||||
|
||||
private var url: URL?
|
||||
private var extensionContainers: ExtensionContainers?
|
||||
private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "ShareViewController")
|
||||
|
||||
override var nibName: NSNib.Name? {
|
||||
return NSNib.Name("ShareViewController")
|
||||
|
||||
@@ -930,6 +930,7 @@
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
DF2A8F34289BFBDA002455AD /* RSCore in Embed Frameworks */,
|
||||
17EF6A2225C4E5B4002C9F81 /* RSWeb in Embed Frameworks */,
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
@@ -1599,6 +1600,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
DF2A8F33289BFBD9002455AD /* RSCore in Frameworks */,
|
||||
17EF6A2125C4E5B4002C9F81 /* RSWeb in Frameworks */,
|
||||
176813F72564BB2C00D98635 /* SwiftUI.framework in Frameworks */,
|
||||
176813F52564BB2C00D98635 /* WidgetKit.framework in Frameworks */,
|
||||
@@ -2583,6 +2585,7 @@
|
||||
849A97861ED9ECEF007D329B /* Article Styles */,
|
||||
84DAEE201F86CAE00058304B /* Importers */,
|
||||
8444C9011FED81880051386C /* Exporters */,
|
||||
DF2A8F28289A3EA8002455AD /* Logging */,
|
||||
51FE0FF9234552490056195D /* UserNotifications */,
|
||||
84F2D5341FC22FCB00998D64 /* SmartFeeds */,
|
||||
51B5C85A23F22A7A00032075 /* ShareExtension */,
|
||||
@@ -2862,6 +2865,13 @@
|
||||
path = Scriptability;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DF2A8F28289A3EA8002455AD /* Logging */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
);
|
||||
path = Logging;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
@@ -2882,6 +2892,7 @@
|
||||
name = "NetNewsWire iOS Widget Extension";
|
||||
packageProductDependencies = (
|
||||
17EF6A2025C4E5B4002C9F81 /* RSWeb */,
|
||||
DF2A8F32289BFBD9002455AD /* RSCore */,
|
||||
);
|
||||
productName = "NetNewsWire WidgetExtension";
|
||||
productReference = 176813F32564BB2C00D98635 /* NetNewsWire iOS Widget Extension.appex */;
|
||||
@@ -5183,6 +5194,11 @@
|
||||
package = 653813412680E2DA007A082C /* XCRemoteSwiftPackageReference "RSCore" */;
|
||||
productName = RSCore;
|
||||
};
|
||||
DF2A8F32289BFBD9002455AD /* RSCore */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 5102AE4324D17E820050839C /* XCRemoteSwiftPackageReference "RSCore" */;
|
||||
productName = RSCore;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
rootObject = 849C64581ED37A5D003D8FC0 /* Project object */;
|
||||
|
||||
@@ -3,7 +3,7 @@ import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "Secrets",
|
||||
platforms: [.macOS(SupportedPlatform.MacOSVersion.v10_15), .iOS(SupportedPlatform.IOSVersion.v13)],
|
||||
platforms: [.macOS(SupportedPlatform.MacOSVersion.v11), .iOS(SupportedPlatform.IOSVersion.v14)],
|
||||
products: [
|
||||
.library(
|
||||
name: "Secrets",
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
import Foundation
|
||||
import Account
|
||||
import Secrets
|
||||
import RSCore
|
||||
|
||||
public enum ArticleExtractorState {
|
||||
case ready
|
||||
@@ -23,7 +24,7 @@ protocol ArticleExtractorDelegate {
|
||||
func articleExtractionDidComplete(extractedArticle: ExtractedArticle)
|
||||
}
|
||||
|
||||
class ArticleExtractor {
|
||||
class ArticleExtractor: Logging {
|
||||
|
||||
private var dataTask: URLSessionDataTask? = nil
|
||||
|
||||
@@ -91,6 +92,7 @@ class ArticleExtractor {
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
self.logger.error("Failed to extract article: \(error.localizedDescription, privacy: .public)")
|
||||
self.state = .failedToParse
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.articleExtractionDidFail(with: error)
|
||||
|
||||
@@ -8,8 +8,9 @@
|
||||
|
||||
import Foundation
|
||||
import Zip
|
||||
import RSCore
|
||||
|
||||
public class ArticleThemeDownloader {
|
||||
public class ArticleThemeDownloader: Logging {
|
||||
|
||||
public enum ArticleThemeDownloaderError: LocalizedError {
|
||||
case noThemeFile
|
||||
@@ -63,6 +64,7 @@ public class ArticleThemeDownloader {
|
||||
}
|
||||
return URL(fileURLWithPath: unzipDirectory.appendingPathComponent(themeFilePath!).path)
|
||||
} catch {
|
||||
logger.error("Failed to unzip theme: \(error.localizedDescription, privacy: .public)")
|
||||
try? FileManager.default.removeItem(at: location)
|
||||
throw error
|
||||
}
|
||||
@@ -101,7 +103,7 @@ public class ArticleThemeDownloader {
|
||||
try FileManager.default.removeItem(atPath: downloadDirectory().appendingPathComponent(path).path)
|
||||
}
|
||||
} catch {
|
||||
print(error)
|
||||
logger.error("Failed to clean up theme download: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ public extension Notification.Name {
|
||||
static let CurrentArticleThemeDidChangeNotification = Notification.Name("CurrentArticleThemeDidChangeNotification")
|
||||
}
|
||||
|
||||
final class ArticleThemesManager: NSObject, NSFilePresenter {
|
||||
final class ArticleThemesManager: NSObject, NSFilePresenter, Logging {
|
||||
|
||||
static var shared: ArticleThemesManager!
|
||||
public let folderPath: String
|
||||
@@ -58,6 +58,7 @@ final class ArticleThemesManager: NSObject, NSFilePresenter {
|
||||
do {
|
||||
try FileManager.default.createDirectory(atPath: folderPath, withIntermediateDirectories: true, attributes: nil)
|
||||
} catch {
|
||||
logger.error("Could not create folder for themes: \(error.localizedDescription, privacy: .public)")
|
||||
assertionFailure("Could not create folder for Themes.")
|
||||
abort()
|
||||
}
|
||||
@@ -113,6 +114,7 @@ final class ArticleThemesManager: NSObject, NSFilePresenter {
|
||||
return try ArticleTheme(path: path, isAppTheme: isAppTheme)
|
||||
} catch {
|
||||
NotificationCenter.default.post(name: .didFailToImportThemeWithError, object: nil, userInfo: ["error": error])
|
||||
logger.error("Failed to import theme: \(error.localizedDescription, privacy: .public)")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -7,12 +7,10 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
import RSWeb
|
||||
import RSCore
|
||||
|
||||
struct CacheCleaner {
|
||||
|
||||
static let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "CacheCleaner")
|
||||
struct CacheCleaner: Logging {
|
||||
|
||||
static func purgeIfNecessary() {
|
||||
|
||||
@@ -35,10 +33,10 @@ struct CacheCleaner {
|
||||
|
||||
for tempItem in [faviconsFolderURL, imagesFolderURL, feedURLToIconURL, homePageToIconURL, homePagesWithNoIconURL] {
|
||||
do {
|
||||
os_log(.info, log: self.log, "Removing cache file: %@", tempItem.absoluteString)
|
||||
CacheCleaner.logger.info("Removing cache file: \(tempItem.absoluteString, privacy: .public)")
|
||||
try FileManager.default.removeItem(at: tempItem)
|
||||
} catch {
|
||||
os_log(.error, log: self.log, "Could not delete cache file: %@", error.localizedDescription)
|
||||
CacheCleaner.logger.error("Could not delete cache file: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ extension Notification.Name {
|
||||
static let FaviconDidBecomeAvailable = Notification.Name("FaviconDidBecomeAvailableNotification") // userInfo key: FaviconDownloader.UserInfoKey.faviconURL
|
||||
}
|
||||
|
||||
final class FaviconDownloader {
|
||||
final class FaviconDownloader: Logging {
|
||||
|
||||
private static let saveQueue = CoalescingQueue(name: "Cache Save Queue", interval: 1.0)
|
||||
|
||||
@@ -297,6 +297,7 @@ private extension FaviconDownloader {
|
||||
let data = try encoder.encode(homePageToFaviconURLCache)
|
||||
try data.write(to: url)
|
||||
} catch {
|
||||
logger.error("Failed to Save Home Page To Favicon URL Cache: \(error.localizedDescription, privacy: .public)")
|
||||
assertionFailure(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
@@ -311,6 +312,7 @@ private extension FaviconDownloader {
|
||||
let data = try encoder.encode(Array(homePageURLsWithNoFaviconURLCache))
|
||||
try data.write(to: url)
|
||||
} catch {
|
||||
logger.error("Failed to Save URLs With No Favicon URL Cache: \(error.localizedDescription, privacy: .public)")
|
||||
assertionFailure(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
import RSCore
|
||||
import RSWeb
|
||||
|
||||
@@ -19,7 +18,7 @@ extension Notification.Name {
|
||||
static let DidLoadFavicon = Notification.Name("DidLoadFaviconNotification")
|
||||
}
|
||||
|
||||
final class SingleFaviconDownloader {
|
||||
final class SingleFaviconDownloader: Logging {
|
||||
|
||||
enum DiskStatus {
|
||||
case unknown, notOnDisk, onDisk
|
||||
@@ -29,8 +28,6 @@ final class SingleFaviconDownloader {
|
||||
var iconImage: IconImage?
|
||||
let homePageURL: String?
|
||||
|
||||
private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "SingleFaviconDownloader")
|
||||
|
||||
private var lastDownloadAttemptDate: Date
|
||||
private var diskStatus = DiskStatus.unknown
|
||||
private var diskCache: BinaryDiskCache
|
||||
@@ -128,7 +125,9 @@ private extension SingleFaviconDownloader {
|
||||
self.diskStatus = .onDisk
|
||||
}
|
||||
}
|
||||
catch {}
|
||||
catch {
|
||||
self.logger.error("Unable to save to disk: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,16 +138,16 @@ private extension SingleFaviconDownloader {
|
||||
return
|
||||
}
|
||||
|
||||
downloadUsingCache(url) { (data, response, error) in
|
||||
downloadUsingCache(url) { [weak self] (data, response, error) in
|
||||
|
||||
if let data = data, !data.isEmpty, let response = response, response.statusIsOK, error == nil {
|
||||
self.saveToDisk(data)
|
||||
self?.saveToDisk(data)
|
||||
RSImage.image(with: data, imageResultBlock: completion)
|
||||
return
|
||||
}
|
||||
|
||||
if let error = error {
|
||||
os_log(.info, log: self.log, "Error downloading image at %@: %@.", url.absoluteString, error.localizedDescription)
|
||||
self?.logger.error("Error downloading image at: \(url.absoluteString, privacy: .sensitive): \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
|
||||
completion(nil)
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
import RSCore
|
||||
import RSWeb
|
||||
|
||||
@@ -16,9 +15,7 @@ extension Notification.Name {
|
||||
static let ImageDidBecomeAvailable = Notification.Name("ImageDidBecomeAvailableNotification") // UserInfoKey.url
|
||||
}
|
||||
|
||||
final class ImageDownloader {
|
||||
|
||||
private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "ImageDownloader")
|
||||
final class ImageDownloader: Logging {
|
||||
|
||||
private let folder: String
|
||||
private var diskCache: BinaryDiskCache
|
||||
@@ -103,19 +100,19 @@ private extension ImageDownloader {
|
||||
return
|
||||
}
|
||||
|
||||
downloadUsingCache(imageURL) { (data, response, error) in
|
||||
downloadUsingCache(imageURL) { [weak self] (data, response, error) in
|
||||
|
||||
if let data = data, !data.isEmpty, let response = response, response.statusIsOK, error == nil {
|
||||
self.saveToDisk(url, data)
|
||||
self?.saveToDisk(url, data)
|
||||
completion(data)
|
||||
return
|
||||
}
|
||||
|
||||
if let response = response as? HTTPURLResponse, response.statusCode >= HTTPResponseCode.badRequest && response.statusCode <= HTTPResponseCode.notAcceptable {
|
||||
self.badURLs.insert(url)
|
||||
self?.badURLs.insert(url)
|
||||
}
|
||||
if let error = error {
|
||||
os_log(.info, log: self.log, "Error downloading image at %@: %@.", url, error.localizedDescription)
|
||||
self?.logger.error("Error downloading image at: \(url, privacy: .sensitive): \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
|
||||
completion(nil)
|
||||
|
||||
@@ -7,15 +7,12 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
import RSCore
|
||||
import RSParser
|
||||
import Account
|
||||
|
||||
final class ExtensionContainersFile {
|
||||
final class ExtensionContainersFile: Logging {
|
||||
|
||||
private static var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "extensionContainersFile")
|
||||
|
||||
private static var filePath: String = {
|
||||
let appGroup = Bundle.main.object(forInfoDictionaryKey: "AppGroup") as! String
|
||||
let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroup)
|
||||
@@ -55,7 +52,7 @@ final class ExtensionContainersFile {
|
||||
})
|
||||
|
||||
if let error = errorPointer?.pointee {
|
||||
os_log(.error, log: log, "Read from disk coordination failed: %@.", error.localizedDescription)
|
||||
logger.error("Read from coordination failed: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
|
||||
return extensionContainers
|
||||
@@ -88,19 +85,19 @@ private extension ExtensionContainersFile {
|
||||
let fileCoordinator = NSFileCoordinator()
|
||||
let fileURL = URL(fileURLWithPath: ExtensionContainersFile.filePath)
|
||||
|
||||
fileCoordinator.coordinate(writingItemAt: fileURL, options: [], error: errorPointer, byAccessor: { writeURL in
|
||||
fileCoordinator.coordinate(writingItemAt: fileURL, options: [], error: errorPointer, byAccessor: { [weak self] writeURL in
|
||||
do {
|
||||
let extensionAccounts = AccountManager.shared.sortedActiveAccounts.map { ExtensionAccount(account: $0) }
|
||||
let extensionContainers = ExtensionContainers(accounts: extensionAccounts)
|
||||
let data = try encoder.encode(extensionContainers)
|
||||
try data.write(to: writeURL)
|
||||
} catch let error as NSError {
|
||||
os_log(.error, log: Self.log, "Save to disk failed: %@.", error.localizedDescription)
|
||||
self?.logger.error("Save to disk failed: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
})
|
||||
|
||||
if let error = errorPointer?.pointee {
|
||||
os_log(.error, log: Self.log, "Save to disk coordination failed: %@.", error.localizedDescription)
|
||||
logger.error("Save to disk coordination failed: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,13 +7,11 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
import Account
|
||||
import RSCore
|
||||
|
||||
final class ExtensionFeedAddRequestFile: NSObject, NSFilePresenter {
|
||||
final class ExtensionFeedAddRequestFile: NSObject, NSFilePresenter, Logging {
|
||||
|
||||
private static var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "extensionFeedAddRequestFile")
|
||||
|
||||
private static var filePath: String = {
|
||||
let appGroup = Bundle.main.object(forInfoDictionaryKey: "AppGroup") as! String
|
||||
let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroup)
|
||||
@@ -82,12 +80,12 @@ final class ExtensionFeedAddRequestFile: NSObject, NSFilePresenter {
|
||||
try data.write(to: url)
|
||||
|
||||
} catch let error as NSError {
|
||||
os_log(.error, log: Self.log, "Save to disk failed: %@.", error.localizedDescription)
|
||||
logger.error("Save to disk failed: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
})
|
||||
|
||||
if let error = errorPointer?.pointee {
|
||||
os_log(.error, log: Self.log, "Save to disk coordination failed: %@.", error.localizedDescription)
|
||||
logger.error("Save to disk coordination failed: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,7 +105,7 @@ private extension ExtensionFeedAddRequestFile {
|
||||
|
||||
var requests: [ExtensionFeedAddRequest]? = nil
|
||||
|
||||
fileCoordinator.coordinate(writingItemAt: fileURL, options: [.forMerging], error: errorPointer, byAccessor: { url in
|
||||
fileCoordinator.coordinate(writingItemAt: fileURL, options: [.forMerging], error: errorPointer, byAccessor: { [weak self] url in
|
||||
do {
|
||||
|
||||
if let fileData = try? Data(contentsOf: url),
|
||||
@@ -119,12 +117,12 @@ private extension ExtensionFeedAddRequestFile {
|
||||
try data.write(to: url)
|
||||
|
||||
} catch let error as NSError {
|
||||
os_log(.error, log: Self.log, "Save to disk failed: %@.", error.localizedDescription)
|
||||
self?.logger.error("Save to disk failed: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
})
|
||||
|
||||
if let error = errorPointer?.pointee {
|
||||
os_log(.error, log: Self.log, "Save to disk coordination failed: %@.", error.localizedDescription)
|
||||
logger.error("Save to disk coordination failed: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
|
||||
requests?.forEach { processRequest($0) }
|
||||
|
||||
@@ -7,10 +7,11 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import RSCore
|
||||
|
||||
struct WidgetDataDecoder {
|
||||
struct WidgetDataDecoder: Logging {
|
||||
|
||||
static func decodeWidgetData() throws -> WidgetData {
|
||||
func decodeWidgetData() throws -> WidgetData {
|
||||
let appGroup = Bundle.main.object(forInfoDictionaryKey: "AppGroup") as! String
|
||||
let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroup)
|
||||
let dataURL = containerURL?.appendingPathComponent("widget-data.json")
|
||||
@@ -22,13 +23,14 @@ struct WidgetDataDecoder {
|
||||
}
|
||||
}
|
||||
|
||||
static func sampleData() -> WidgetData {
|
||||
func sampleData() -> WidgetData {
|
||||
let pathToSample = Bundle.main.url(forResource: "widget-sample", withExtension: "json")
|
||||
do {
|
||||
let data = try Data(contentsOf: pathToSample!)
|
||||
let decoded = try JSONDecoder().decode(WidgetData.self, from: data)
|
||||
return decoded
|
||||
} catch {
|
||||
logger.error("Error accessing sample widget data: \(error.localizedDescription, privacy: .public)")
|
||||
return WidgetData(currentUnreadCount: 0, currentTodayCount: 0, currentStarredCount: 0, unreadArticles: [], starredArticles: [], todayArticles: [], lastUpdateTime: Date())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,16 +8,15 @@
|
||||
|
||||
import Foundation
|
||||
import WidgetKit
|
||||
import os.log
|
||||
import UIKit
|
||||
import RSCore
|
||||
import Articles
|
||||
import Account
|
||||
|
||||
|
||||
public final class WidgetDataEncoder {
|
||||
public final class WidgetDataEncoder: Logging {
|
||||
|
||||
|
||||
private let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "Application")
|
||||
private let fetchLimit = 7
|
||||
|
||||
private var backgroundTaskID: UIBackgroundTaskIdentifier!
|
||||
@@ -36,7 +35,7 @@ public final class WidgetDataEncoder {
|
||||
@available(iOS 14, *)
|
||||
func encodeWidgetData() throws {
|
||||
flushSharedContainer()
|
||||
os_log(.debug, log: log, "Starting encoding widget data.")
|
||||
logger.debug("Started encoding widget data.")
|
||||
|
||||
do {
|
||||
let unreadArticles = Array(try AccountManager.shared.fetchArticles(.unread(fetchLimit))).sortedByDate(.orderedDescending)
|
||||
@@ -95,14 +94,14 @@ public final class WidgetDataEncoder {
|
||||
}
|
||||
let encodedData = try? JSONEncoder().encode(latestData)
|
||||
|
||||
os_log(.debug, log: self.log, "Finished encoding widget data.")
|
||||
self.logger.debug("Finished encoding widget data.")
|
||||
|
||||
if self.fileExists() {
|
||||
try? FileManager.default.removeItem(at: self.dataURL!)
|
||||
os_log(.debug, log: self.log, "Removed widget data from container.")
|
||||
self.logger.debug("Removed widget data from container.")
|
||||
}
|
||||
if FileManager.default.createFile(atPath: self.dataURL!.path, contents: encodedData, attributes: nil) {
|
||||
os_log(.debug, log: self.log, "Wrote widget data to container.")
|
||||
self.logger.debug("Wrote widget data to container.")
|
||||
WidgetCenter.shared.reloadAllTimelines()
|
||||
UIApplication.shared.endBackgroundTask(self.backgroundTaskID!)
|
||||
self.backgroundTaskID = .invalid
|
||||
|
||||
@@ -18,7 +18,7 @@ dependencies.append(contentsOf: [
|
||||
|
||||
let package = Package(
|
||||
name: "SyncDatabase",
|
||||
platforms: [.macOS(SupportedPlatform.MacOSVersion.v11), .iOS(SupportedPlatform.IOSVersion.v14)],
|
||||
platforms: [.macOS(.v11), .iOS(.v14)],
|
||||
products: [
|
||||
.library(
|
||||
name: "SyncDatabase",
|
||||
|
||||
18
Technotes/Logs.md
Normal file
18
Technotes/Logs.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# Logs
|
||||
|
||||
`RSCore` contains a protocol called `Logging`. Classes and Structs that conform to `Logging` have a `logger` variable that the Class or Struct can use instead of importing `os.log` and creating a `var log = Logger(..)` variable.
|
||||
|
||||
Example:
|
||||
|
||||
```swift
|
||||
|
||||
import Foundation
|
||||
import RSCore
|
||||
|
||||
class Sample: Logging {
|
||||
|
||||
init() {
|
||||
logger.debug("Init")
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -8,35 +8,41 @@
|
||||
|
||||
import WidgetKit
|
||||
import SwiftUI
|
||||
import RSCore
|
||||
|
||||
struct Provider: TimelineProvider {
|
||||
struct Provider: TimelineProvider, Logging {
|
||||
|
||||
let decoder = WidgetDataDecoder()
|
||||
|
||||
func placeholder(in context: Context) -> WidgetTimelineEntry {
|
||||
do {
|
||||
let data = try WidgetDataDecoder.decodeWidgetData()
|
||||
let data = try decoder.decodeWidgetData()
|
||||
return WidgetTimelineEntry(date: Date(), widgetData: data)
|
||||
} catch {
|
||||
return WidgetTimelineEntry(date: Date(), widgetData: WidgetDataDecoder.sampleData())
|
||||
logger.error("Failed to decode widget data: \(error.localizedDescription, privacy: .public)")
|
||||
return WidgetTimelineEntry(date: Date(), widgetData: decoder.sampleData())
|
||||
}
|
||||
}
|
||||
|
||||
func getSnapshot(in context: Context, completion: @escaping (WidgetTimelineEntry) -> Void) {
|
||||
if context.isPreview {
|
||||
do {
|
||||
let data = try WidgetDataDecoder.decodeWidgetData()
|
||||
let data = try decoder.decodeWidgetData()
|
||||
completion(WidgetTimelineEntry(date: Date(), widgetData: data))
|
||||
} catch {
|
||||
logger.error("Failed to decode widget data: \(error.localizedDescription, privacy: .public)")
|
||||
completion(WidgetTimelineEntry(date: Date(),
|
||||
widgetData: WidgetDataDecoder.sampleData()))
|
||||
widgetData: decoder.sampleData()))
|
||||
}
|
||||
} else {
|
||||
do {
|
||||
let widgetData = try WidgetDataDecoder.decodeWidgetData()
|
||||
let widgetData = try decoder.decodeWidgetData()
|
||||
let entry = WidgetTimelineEntry(date: Date(), widgetData: widgetData)
|
||||
completion(entry)
|
||||
} catch {
|
||||
logger.error("Failed to decode widget data: \(error.localizedDescription, privacy: .public)")
|
||||
let entry = WidgetTimelineEntry(date: Date(),
|
||||
widgetData: WidgetDataDecoder.sampleData())
|
||||
widgetData: decoder.sampleData())
|
||||
completion(entry)
|
||||
}
|
||||
}
|
||||
@@ -48,9 +54,10 @@ struct Provider: TimelineProvider {
|
||||
var entry: WidgetTimelineEntry
|
||||
|
||||
do {
|
||||
let widgetData = try WidgetDataDecoder.decodeWidgetData()
|
||||
let widgetData = try decoder.decodeWidgetData()
|
||||
entry = WidgetTimelineEntry(date: date, widgetData: widgetData)
|
||||
} catch {
|
||||
logger.error("Failed to decode widget data: \(error.localizedDescription, privacy: .public)")
|
||||
entry = WidgetTimelineEntry(date: date, widgetData: WidgetData(currentUnreadCount: 0, currentTodayCount: 0, currentStarredCount: 0, unreadArticles: [], starredArticles: [], todayArticles: [], lastUpdateTime: Date()))
|
||||
}
|
||||
|
||||
|
||||
@@ -91,5 +91,6 @@ struct SmartFeedSummaryWidgetView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
SmartFeedSummaryWidgetView(entry: Provider.Entry.init(date: Date(), widgetData: WidgetDataDecoder.sampleData()))
|
||||
.previewContext(WidgetPreviewContext(family: .accessoryRectangular))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,9 @@ import Account
|
||||
import Secrets
|
||||
import RSWeb
|
||||
import SafariServices
|
||||
import RSCore
|
||||
|
||||
class FeedbinAccountViewController: UITableViewController {
|
||||
class FeedbinAccountViewController: UITableViewController, Logging {
|
||||
|
||||
@IBOutlet weak var activityIndicator: UIActivityIndicatorView!
|
||||
@IBOutlet weak var cancelBarButtonItem: UIBarButtonItem!
|
||||
@@ -116,7 +117,9 @@ class FeedbinAccountViewController: UITableViewController {
|
||||
|
||||
do {
|
||||
try self.account?.removeCredentials(type: .basic)
|
||||
} catch {}
|
||||
} catch {
|
||||
self.logger.error("Error removing credentials: \(error.localizedDescription, privacy: .public).")
|
||||
}
|
||||
try self.account?.storeCredentials(credentials)
|
||||
|
||||
self.account?.refreshAll() { result in
|
||||
@@ -132,6 +135,7 @@ class FeedbinAccountViewController: UITableViewController {
|
||||
self.delegate?.dismiss()
|
||||
} catch {
|
||||
self.showError(NSLocalizedString("Keychain error while storing credentials.", comment: "Credentials Error"))
|
||||
self.logger.error("Keychain error while storing credentials: \(error.localizedDescription, privacy: .public).")
|
||||
}
|
||||
} else {
|
||||
self.showError(NSLocalizedString("Invalid email/password combination.", comment: "Credentials Error"))
|
||||
|
||||
@@ -10,9 +10,10 @@ import UIKit
|
||||
import Account
|
||||
import Secrets
|
||||
import RSWeb
|
||||
import RSCore
|
||||
import SafariServices
|
||||
|
||||
class NewsBlurAccountViewController: UITableViewController {
|
||||
class NewsBlurAccountViewController: UITableViewController, Logging {
|
||||
|
||||
@IBOutlet weak var activityIndicator: UIActivityIndicatorView!
|
||||
@IBOutlet weak var cancelBarButtonItem: UIBarButtonItem!
|
||||
@@ -118,7 +119,9 @@ class NewsBlurAccountViewController: UITableViewController {
|
||||
do {
|
||||
try self.account?.removeCredentials(type: .newsBlurBasic)
|
||||
try self.account?.removeCredentials(type: .newsBlurSessionId)
|
||||
} catch {}
|
||||
} catch {
|
||||
self.logger.error("Error removing credentials: \(error.localizedDescription, privacy: .public).")
|
||||
}
|
||||
try self.account?.storeCredentials(basicCredentials)
|
||||
try self.account?.storeCredentials(sessionCredentials)
|
||||
|
||||
@@ -135,6 +138,7 @@ class NewsBlurAccountViewController: UITableViewController {
|
||||
self.delegate?.dismiss()
|
||||
} catch {
|
||||
self.showError(NSLocalizedString("Keychain error while storing credentials.", comment: "Credentials Error"))
|
||||
self.logger.error("Keychain error while storing credentials: \(error.localizedDescription, privacy: .public).")
|
||||
}
|
||||
} else {
|
||||
self.showError(NSLocalizedString("Invalid username/password combination.", comment: "Credentials Error"))
|
||||
|
||||
@@ -11,8 +11,9 @@ import Account
|
||||
import Secrets
|
||||
import RSWeb
|
||||
import SafariServices
|
||||
import RSCore
|
||||
|
||||
class ReaderAPIAccountViewController: UITableViewController {
|
||||
class ReaderAPIAccountViewController: UITableViewController, Logging {
|
||||
|
||||
@IBOutlet weak var activityIndicator: UIActivityIndicatorView!
|
||||
@IBOutlet weak var cancelBarButtonItem: UIBarButtonItem!
|
||||
@@ -187,6 +188,7 @@ class ReaderAPIAccountViewController: UITableViewController {
|
||||
self.delegate?.dismiss()
|
||||
} catch {
|
||||
self.showError(NSLocalizedString("Keychain error while storing credentials.", comment: "Credentials Error"))
|
||||
self.logger.error("Keychain error while storing credentials: \(error.localizedDescription, privacy: .public).")
|
||||
}
|
||||
} else {
|
||||
self.showError(NSLocalizedString("Invalid username/password combination.", comment: "Credentials Error"))
|
||||
|
||||
@@ -11,14 +11,13 @@ import RSCore
|
||||
import RSWeb
|
||||
import Account
|
||||
import BackgroundTasks
|
||||
import os.log
|
||||
import Secrets
|
||||
import WidgetKit
|
||||
|
||||
var appDelegate: AppDelegate!
|
||||
|
||||
@UIApplicationMain
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate, UnreadCountProvider {
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate, UnreadCountProvider, Logging {
|
||||
|
||||
private var bgTaskDispatchQueue = DispatchQueue.init(label: "BGTaskScheduler")
|
||||
|
||||
@@ -36,8 +35,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
||||
}
|
||||
}
|
||||
|
||||
var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "Application")
|
||||
|
||||
var userNotificationManager: UserNotificationManager!
|
||||
var faviconDownloader: FaviconDownloader!
|
||||
var imageDownloader: ImageDownloader!
|
||||
@@ -83,7 +80,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
||||
|
||||
let isFirstRun = AppDefaults.shared.isFirstRun
|
||||
if isFirstRun {
|
||||
os_log("Is first run.", log: log, type: .info)
|
||||
logger.info("Is first run.")
|
||||
}
|
||||
|
||||
if isFirstRun && !AccountManager.shared.anyAccountHasAtLeastOneFeed() {
|
||||
@@ -166,7 +163,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
||||
func resumeDatabaseProcessingIfNecessary() {
|
||||
if AccountManager.shared.isSuspended {
|
||||
AccountManager.shared.resumeAll()
|
||||
os_log("Application processing resumed.", log: self.log, type: .info)
|
||||
logger.info("Application processing resumed.")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,7 +273,7 @@ private extension AppDelegate {
|
||||
self.waitBackgroundUpdateTask = UIApplication.shared.beginBackgroundTask { [weak self] in
|
||||
guard let self = self else { return }
|
||||
self.completeProcessing(true)
|
||||
os_log("Accounts wait for progress terminated for running too long.", log: self.log, type: .info)
|
||||
self.logger.info("Accounts wait for progress terminated for running too long.")
|
||||
}
|
||||
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
@@ -288,18 +285,18 @@ private extension AppDelegate {
|
||||
|
||||
func waitToComplete(completion: @escaping (Bool) -> Void) {
|
||||
guard UIApplication.shared.applicationState == .background else {
|
||||
os_log("App came back to foreground, no longer waiting.", log: self.log, type: .info)
|
||||
logger.info("App came back to foreground, no longer waiting.")
|
||||
completion(false)
|
||||
return
|
||||
}
|
||||
|
||||
if AccountManager.shared.refreshInProgress || isSyncArticleStatusRunning {
|
||||
os_log("Waiting for sync to finish...", log: self.log, type: .info)
|
||||
logger.info("Waiting for sync to finish...")
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self] in
|
||||
self?.waitToComplete(completion: completion)
|
||||
}
|
||||
} else {
|
||||
os_log("Refresh progress complete.", log: self.log, type: .info)
|
||||
logger.info("Refresh progress complete.")
|
||||
completion(true)
|
||||
}
|
||||
}
|
||||
@@ -324,9 +321,9 @@ private extension AppDelegate {
|
||||
self.syncBackgroundUpdateTask = UIBackgroundTaskIdentifier.invalid
|
||||
}
|
||||
|
||||
self.syncBackgroundUpdateTask = UIApplication.shared.beginBackgroundTask {
|
||||
self.syncBackgroundUpdateTask = UIApplication.shared.beginBackgroundTask { [weak self] in
|
||||
completeProcessing()
|
||||
os_log("Accounts sync processing terminated for running too long.", log: self.log, type: .info)
|
||||
self?.logger.info("Accounts sync processing terminated for running too long.")
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
@@ -350,7 +347,7 @@ private extension AppDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
os_log("Application processing suspended.", log: self.log, type: .info)
|
||||
logger.info("Application processing suspended.")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -374,11 +371,11 @@ private extension AppDelegate {
|
||||
|
||||
// We send this to a dedicated serial queue because as of 11/05/19 on iOS 13.2 the call to the
|
||||
// task scheduler can hang indefinitely.
|
||||
bgTaskDispatchQueue.async {
|
||||
bgTaskDispatchQueue.async { [weak self] in
|
||||
do {
|
||||
try BGTaskScheduler.shared.submit(request)
|
||||
} catch {
|
||||
os_log(.error, log: self.log, "Could not schedule app refresh: %@", error.localizedDescription)
|
||||
self?.logger.error("Could not schedule app refresh: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -390,7 +387,7 @@ private extension AppDelegate {
|
||||
|
||||
scheduleBackgroundFeedRefresh() // schedule next refresh
|
||||
|
||||
os_log("Woken to perform account refresh.", log: self.log, type: .info)
|
||||
logger.info("Woken to perform account refresh.")
|
||||
|
||||
DispatchQueue.main.async {
|
||||
if AccountManager.shared.isSuspended {
|
||||
@@ -400,7 +397,7 @@ private extension AppDelegate {
|
||||
if !AccountManager.shared.isSuspended {
|
||||
try? WidgetDataEncoder.shared.encodeWidgetData()
|
||||
self.suspendApplication()
|
||||
os_log("Account refresh operation completed.", log: self.log, type: .info)
|
||||
self.logger.info("Account refresh operation completed.")
|
||||
task.setTaskCompleted(success: true)
|
||||
}
|
||||
}
|
||||
@@ -408,7 +405,7 @@ private extension AppDelegate {
|
||||
|
||||
// set expiration handler
|
||||
task.expirationHandler = { [weak task] in
|
||||
os_log("Accounts refresh processing terminated for running too long.", log: self.log, type: .info)
|
||||
self.logger.info("Accounts refresh processing terminated for running too long.")
|
||||
DispatchQueue.main.async {
|
||||
self.suspendApplication()
|
||||
task?.setTaskCompleted(success: false)
|
||||
@@ -431,12 +428,12 @@ private extension AppDelegate {
|
||||
resumeDatabaseProcessingIfNecessary()
|
||||
let account = AccountManager.shared.existingAccount(with: accountID)
|
||||
guard account != nil else {
|
||||
os_log(.debug, log: self.log, "No account found from notification.")
|
||||
logger.debug("No account found from notification.")
|
||||
return
|
||||
}
|
||||
let article = try? account!.fetchArticles(.articleIDs([articleID]))
|
||||
guard article != nil else {
|
||||
os_log(.debug, log: self.log, "No article found from search using %@", articleID)
|
||||
logger.debug("No account found from search using \(articleID, privacy: .public)")
|
||||
return
|
||||
}
|
||||
account!.markArticles(article!, statusKey: .read, flag: true) { _ in }
|
||||
@@ -459,12 +456,12 @@ private extension AppDelegate {
|
||||
resumeDatabaseProcessingIfNecessary()
|
||||
let account = AccountManager.shared.existingAccount(with: accountID)
|
||||
guard account != nil else {
|
||||
os_log(.debug, log: self.log, "No account found from notification.")
|
||||
logger.debug("No account found from notification.")
|
||||
return
|
||||
}
|
||||
let article = try? account!.fetchArticles(.articleIDs([articleID]))
|
||||
guard article != nil else {
|
||||
os_log(.debug, log: self.log, "No article found from search using %@", articleID)
|
||||
logger.debug("No article found from search using \(articleID, privacy: .public)")
|
||||
return
|
||||
}
|
||||
account!.markArticles(article!, statusKey: .starred, flag: true) { _ in }
|
||||
|
||||
@@ -8,24 +8,21 @@
|
||||
|
||||
import UIKit
|
||||
import RSCore
|
||||
import os.log
|
||||
|
||||
struct ErrorHandler {
|
||||
|
||||
private static var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "Application")
|
||||
struct ErrorHandler: Logging {
|
||||
|
||||
public static func present(_ viewController: UIViewController) -> (Error) -> () {
|
||||
return { [weak viewController] error in
|
||||
if UIApplication.shared.applicationState == .active {
|
||||
viewController?.presentError(error)
|
||||
} else {
|
||||
ErrorHandler.log(error)
|
||||
log(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static func log(_ error: Error) {
|
||||
os_log(.error, log: self.log, "%@", error.localizedDescription)
|
||||
ErrorHandler.logger.error("\(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ struct FeedNode: Hashable {
|
||||
}
|
||||
}
|
||||
|
||||
class SceneCoordinator: NSObject, UndoableCommandRunner {
|
||||
class SceneCoordinator: NSObject, UndoableCommandRunner, Logging {
|
||||
|
||||
var undoableCommands = [UndoableCommand]()
|
||||
var undoManager: UndoManager? {
|
||||
@@ -1272,6 +1272,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner {
|
||||
try ArticleThemeImporter.importTheme(controller: rootSplitViewController, filename: filename)
|
||||
} catch {
|
||||
NotificationCenter.default.post(name: .didFailToImportThemeWithError, object: nil, userInfo: ["error" : error])
|
||||
logger.error("Failed to import theme with error: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,8 +10,9 @@ import UIKit
|
||||
import UserNotifications
|
||||
import Account
|
||||
import Zip
|
||||
import RSCore
|
||||
|
||||
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||
class SceneDelegate: UIResponder, UIWindowSceneDelegate, Logging {
|
||||
|
||||
var window: UIWindow?
|
||||
var coordinator: SceneCoordinator!
|
||||
@@ -184,7 +185,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||
DispatchQueue.main.async {
|
||||
NotificationCenter.default.post(name: .didBeginDownloadingTheme, object: nil)
|
||||
}
|
||||
let task = URLSession.shared.downloadTask(with: request) { location, response, error in
|
||||
let task = URLSession.shared.downloadTask(with: request) { [weak self] location, response, error in
|
||||
guard
|
||||
let location = location else { return }
|
||||
|
||||
@@ -192,6 +193,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||
try ArticleThemeDownloader.shared.handleFile(at: location)
|
||||
} catch {
|
||||
NotificationCenter.default.post(name: .didFailToImportThemeWithError, object: nil, userInfo: ["error": error])
|
||||
self?.logger.error("Failed to import theme with error: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
task.resume()
|
||||
|
||||
@@ -7,8 +7,9 @@
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import RSCore
|
||||
|
||||
struct ArticleThemeImporter {
|
||||
struct ArticleThemeImporter: Logging {
|
||||
|
||||
static func importTheme(controller: UIViewController, filename: String) throws {
|
||||
let theme = try ArticleTheme(path: filename, isAppTheme: false)
|
||||
@@ -39,6 +40,7 @@ struct ArticleThemeImporter {
|
||||
confirmImportSuccess(controller: controller, themeName: theme.name)
|
||||
} catch {
|
||||
controller.presentError(error)
|
||||
ArticleThemeImporter.logger.error("Error importing theme: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
|
||||
import Foundation
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
import RSCore
|
||||
import UIKit
|
||||
|
||||
class ArticleThemesTableViewController: UITableViewController {
|
||||
class ArticleThemesTableViewController: UITableViewController, Logging {
|
||||
|
||||
override func viewDidLoad() {
|
||||
let importBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(importTheme(_:)));
|
||||
@@ -118,6 +118,7 @@ extension ArticleThemesTableViewController: UIDocumentPickerDelegate {
|
||||
try ArticleThemeImporter.importTheme(controller: self, filename: url.standardizedFileURL.path)
|
||||
} catch {
|
||||
NotificationCenter.default.post(name: .didFailToImportThemeWithError, object: nil, userInfo: ["error": error])
|
||||
logger.error("Did fail to import theme: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,8 +13,9 @@ import SafariServices
|
||||
import SwiftUI
|
||||
import UniformTypeIdentifiers
|
||||
import UserNotifications
|
||||
import RSCore
|
||||
|
||||
class SettingsViewController: UITableViewController {
|
||||
class SettingsViewController: UITableViewController, Logging {
|
||||
|
||||
private weak var opmlAccount: Account?
|
||||
|
||||
@@ -509,6 +510,7 @@ private extension SettingsViewController {
|
||||
try opmlString.write(to: tempFile, atomically: true, encoding: String.Encoding.utf8)
|
||||
} catch {
|
||||
self.presentError(title: "OPML Export Error", message: error.localizedDescription)
|
||||
logger.error("OPML Export Error: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
|
||||
let docPicker = UIDocumentPickerViewController(forExporting: [tempFile], asCopy: true)
|
||||
|
||||
Reference in New Issue
Block a user