Add a process to wipe out a zone and reload it from a local database
This commit is contained in:
parent
97d139d5ae
commit
c8612d59d7
|
@ -419,6 +419,15 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
|||
delegate.receiveRemoteNotification(for: self, userInfo: userInfo, completion: completion)
|
||||
}
|
||||
|
||||
public func wipeCloudKitArticlesZoneAndReload(completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
guard let cloudKitAccountDelegate = delegate as? CloudKitAccountDelegate else {
|
||||
completion(.success(()))
|
||||
return
|
||||
}
|
||||
|
||||
cloudKitAccountDelegate.wipeArticlesZoneAndReload(for: self, completion: completion)
|
||||
}
|
||||
|
||||
public func refreshAll(completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
delegate.refreshAll(for: self, completion: completion)
|
||||
}
|
||||
|
|
|
@ -245,6 +245,22 @@ public final class AccountManager: UnreadCountProvider {
|
|||
}
|
||||
}
|
||||
|
||||
public func wipeCloudKitArticlesZoneAndReload(errorHandler: @escaping (Error) -> Void, completion: (() -> Void)? = nil) {
|
||||
guard let cloudKitAccount = activeAccounts.first(where: { $0.type == .cloudKit }) else {
|
||||
completion?()
|
||||
return
|
||||
}
|
||||
|
||||
cloudKitAccount.wipeCloudKitArticlesZoneAndReload { result in
|
||||
switch result {
|
||||
case .success:
|
||||
completion?()
|
||||
case .failure(let error):
|
||||
errorHandler(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func refreshAll(errorHandler: @escaping (Error) -> Void, completion: (() -> Void)? = nil) {
|
||||
guard let reachability = try? Reachability(hostname: "apple.com"), reachability.connection != .unavailable else { return }
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ final class CloudKitAccountDelegate: AccountDelegate, Logging {
|
|||
|
||||
private let mainThreadOperationQueue = MainThreadOperationQueue()
|
||||
|
||||
private lazy var refresher: LocalAccountRefresher = {
|
||||
private lazy var standardRefresher: LocalAccountRefresher = {
|
||||
let refresher = LocalAccountRefresher()
|
||||
refresher.delegate = self
|
||||
return refresher
|
||||
|
@ -73,6 +73,29 @@ final class CloudKitAccountDelegate: AccountDelegate, Logging {
|
|||
mainThreadOperationQueue.add(op)
|
||||
}
|
||||
|
||||
func wipeArticlesZoneAndReload(for account: Account, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
guard refreshProgress.isComplete else {
|
||||
completion(.success(()))
|
||||
return
|
||||
}
|
||||
|
||||
articlesZone.deleteZoneRecord { result in
|
||||
switch result {
|
||||
case .success:
|
||||
self.articlesZone.createZoneRecord { result in
|
||||
switch result {
|
||||
case .success:
|
||||
self.reloadArticlesZone(for: account, completion: completion)
|
||||
case .failure(let error):
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
case .failure(let error):
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func refreshAll(for account: Account, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
guard refreshProgress.isComplete else {
|
||||
completion(.success(()))
|
||||
|
@ -462,7 +485,7 @@ final class CloudKitAccountDelegate: AccountDelegate, Logging {
|
|||
// MARK: Suspend and Resume (for iOS)
|
||||
|
||||
func suspendNetwork() {
|
||||
refresher.suspend()
|
||||
standardRefresher.suspend()
|
||||
}
|
||||
|
||||
func suspendDatabase() {
|
||||
|
@ -470,13 +493,65 @@ final class CloudKitAccountDelegate: AccountDelegate, Logging {
|
|||
}
|
||||
|
||||
func resume() {
|
||||
refresher.resume()
|
||||
standardRefresher.resume()
|
||||
database.resume()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private extension CloudKitAccountDelegate {
|
||||
|
||||
func reloadArticlesZone(for account: Account, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
account.fetchArticlesAsync(.starred()) { result in
|
||||
switch result {
|
||||
case .success(let articles):
|
||||
self.upload(articles: articles) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
account.fetchArticlesAsync(.unread()) { result in
|
||||
switch result {
|
||||
case .success(let articles):
|
||||
self.upload(articles: articles) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
let allCloudKitFeeds = account.flattenedWebFeeds()
|
||||
allCloudKitFeeds.forEach{ $0.dropConditionalGetInfo() }
|
||||
|
||||
let reloadRefresher = LocalAccountRefresher()
|
||||
reloadRefresher.delegate = ReloadAccountRefresherDelegate(self)
|
||||
|
||||
self.combinedRefresh(account, allCloudKitFeeds, reloadRefresher, completion: completion)
|
||||
case .failure(let error):
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
case .failure(let error):
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
case .failure(let error):
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
case .failure(let error):
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func upload(articles: Set<Article>, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
let op = CloudKitUploadArticlesOperation(articlesZone: articlesZone, articles: articles)
|
||||
op.completionBlock = { mainThreadOperaion in
|
||||
if let error = op.error {
|
||||
completion(.failure(error))
|
||||
} else {
|
||||
completion(.success(()))
|
||||
}
|
||||
}
|
||||
mainThreadOperationQueue.add(op)
|
||||
}
|
||||
|
||||
func initialRefreshAll(for account: Account, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
|
||||
func fail(_ error: Error) {
|
||||
|
@ -501,7 +576,7 @@ private extension CloudKitAccountDelegate {
|
|||
switch result {
|
||||
case .success:
|
||||
|
||||
self.combinedRefresh(account, webFeeds) { result in
|
||||
self.combinedRefresh(account, webFeeds, self.standardRefresher) { result in
|
||||
self.refreshProgress.clear()
|
||||
switch result {
|
||||
case .success:
|
||||
|
@ -547,7 +622,7 @@ private extension CloudKitAccountDelegate {
|
|||
case .success:
|
||||
self.refreshProgress.completeTask()
|
||||
self.refreshProgress.isIndeterminate = false
|
||||
self.combinedRefresh(account, webFeeds) { result in
|
||||
self.combinedRefresh(account, webFeeds, self.standardRefresher) { result in
|
||||
self.sendArticleStatus(for: account, showProgress: true) { _ in
|
||||
self.refreshProgress.clear()
|
||||
if case .failure(let error) = result {
|
||||
|
@ -570,7 +645,7 @@ private extension CloudKitAccountDelegate {
|
|||
|
||||
}
|
||||
|
||||
func combinedRefresh(_ account: Account, _ webFeeds: Set<WebFeed>, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func combinedRefresh(_ account: Account, _ webFeeds: Set<WebFeed>, _ refresher: LocalAccountRefresher, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
|
||||
var refresherWebFeeds = Set<WebFeed>()
|
||||
let group = DispatchGroup()
|
||||
|
@ -923,3 +998,70 @@ extension CloudKitAccountDelegate: LocalAccountRefresherDelegate {
|
|||
|
||||
}
|
||||
|
||||
class ReloadAccountRefresherDelegate: LocalAccountRefresherDelegate, Logging {
|
||||
|
||||
weak var cloudKitAccountDelegate: CloudKitAccountDelegate?
|
||||
|
||||
init(_ cloudKitAccountDelegate: CloudKitAccountDelegate) {
|
||||
self.cloudKitAccountDelegate = cloudKitAccountDelegate
|
||||
}
|
||||
|
||||
func localAccountRefresher(_ refresher: LocalAccountRefresher, requestCompletedFor: WebFeed) {
|
||||
}
|
||||
|
||||
func localAccountRefresher(_ refresher: LocalAccountRefresher, articleChanges: ArticleChanges, completion: @escaping () -> Void) {
|
||||
let newArticleCount = articleChanges.newArticles?.count ?? 0
|
||||
let updatedArticleCount = articleChanges.updatedArticles?.count ?? 0
|
||||
let unchangedArticleCount = articleChanges.unchangedArticles?.count ?? 0
|
||||
let incomingArticleCount = articleChanges.incomingArticles?.count ?? 0
|
||||
let deletedArticleCount = articleChanges.deletedArticles?.count ?? 0
|
||||
|
||||
cloudKitAccountDelegate?.logger.debug(
|
||||
"""
|
||||
Uploading \(newArticleCount, privacy: .public) new articles, \(updatedArticleCount, privacy: .public) updated articles, \
|
||||
and \(unchangedArticleCount, privacy: .public) unchanged articles out of \(incomingArticleCount, privacy: .public) total articles \
|
||||
(\(deletedArticleCount, privacy: .public) were deleted and not uploaded.)
|
||||
"""
|
||||
)
|
||||
|
||||
let group = DispatchGroup()
|
||||
|
||||
if let newArticles = articleChanges.newArticles {
|
||||
group.enter()
|
||||
cloudKitAccountDelegate?.upload(articles: newArticles) { result in
|
||||
if case .failure(let error) = result {
|
||||
self.logger.error("An error occurred uploading new articles: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
group.leave()
|
||||
}
|
||||
}
|
||||
|
||||
if let updatedArticles = articleChanges.updatedArticles {
|
||||
group.enter()
|
||||
cloudKitAccountDelegate?.upload(articles: updatedArticles) { result in
|
||||
if case .failure(let error) = result {
|
||||
self.logger.error("An error occurred uploading updated articles: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
group.leave()
|
||||
}
|
||||
}
|
||||
|
||||
if let unchangedArticles = articleChanges.unchangedArticles {
|
||||
// If the article is unchanged and unread, then we've already uploaded it
|
||||
let unchangedReadArticles = unchangedArticles.filter { $0.status.read }
|
||||
|
||||
group.enter()
|
||||
cloudKitAccountDelegate?.upload(articles: unchangedReadArticles) { result in
|
||||
if case .failure(let error) = result {
|
||||
self.logger.error("An error occurred uploading already read articles: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
group.leave()
|
||||
}
|
||||
}
|
||||
|
||||
group.notify(queue: .main) {
|
||||
completion()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
//
|
||||
// CloudKitUploadArticlesOperation.swift
|
||||
//
|
||||
//
|
||||
// Created by Maurice Parker on 11/13/22.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import RSCore
|
||||
import Articles
|
||||
import SyncDatabase
|
||||
|
||||
class CloudKitUploadArticlesOperation: MainThreadOperation, Logging {
|
||||
|
||||
// MainThreadOperation
|
||||
public var isCanceled = false
|
||||
public var id: Int?
|
||||
public weak var operationDelegate: MainThreadOperationDelegate?
|
||||
public var name: String? = "CloudKitUploadArticlesOperation"
|
||||
public var completionBlock: MainThreadOperation.MainThreadOperationCompletionBlock?
|
||||
|
||||
private weak var articlesZone: CloudKitArticlesZone?
|
||||
private let articles: Set<Article>
|
||||
|
||||
public var error: Error?
|
||||
|
||||
init(articlesZone: CloudKitArticlesZone, articles: Set<Article>) {
|
||||
self.articlesZone = articlesZone
|
||||
self.articles = articles
|
||||
}
|
||||
|
||||
func run() {
|
||||
guard let articlesZone = articlesZone else {
|
||||
self.operationDelegate?.operationDidComplete(self)
|
||||
return
|
||||
}
|
||||
|
||||
logger.debug("Uploading \(self.articles.count, privacy: .public) articles...")
|
||||
|
||||
let statusUpdates = articles.compactMap { article in
|
||||
return CloudKitArticleStatusUpdate(articleID: article.articleID, statuses: [SyncStatus(article: article)], article: article)
|
||||
}
|
||||
|
||||
articlesZone.modifyArticles(statusUpdates) { result in
|
||||
self.logger.debug("Done uploading articles.")
|
||||
switch result {
|
||||
case .success:
|
||||
self.operationDelegate?.operationDidComplete(self)
|
||||
case .failure(let error):
|
||||
self.error = error
|
||||
self.operationDelegate?.cancelOperation(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension SyncStatus {
|
||||
|
||||
init(article: Article) {
|
||||
switch true {
|
||||
case article.status.starred:
|
||||
self.init(articleID: article.articleID, key: .starred, flag: true)
|
||||
case article.status.read:
|
||||
self.init(articleID: article.articleID, key: .read, flag: true)
|
||||
default:
|
||||
self.init(articleID: article.articleID, key: .read, flag: false)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -25,17 +25,28 @@ public typealias SingleUnreadCountResult = Result<Int, DatabaseError>
|
|||
public typealias SingleUnreadCountCompletionBlock = (SingleUnreadCountResult) -> Void
|
||||
|
||||
public struct ArticleChanges {
|
||||
public let incomingArticles: Set<Article>?
|
||||
public let newArticles: Set<Article>?
|
||||
public let updatedArticles: Set<Article>?
|
||||
public let deletedArticles: Set<Article>?
|
||||
|
||||
public var unchangedArticles: Set<Article>? {
|
||||
guard let incomingArticles = incomingArticles else { return nil }
|
||||
return incomingArticles
|
||||
.subtracting(newArticles ?? Set<Article>())
|
||||
.subtracting(updatedArticles ?? Set<Article>())
|
||||
.subtracting(deletedArticles ?? Set<Article>())
|
||||
}
|
||||
|
||||
public init() {
|
||||
self.incomingArticles = Set<Article>()
|
||||
self.newArticles = Set<Article>()
|
||||
self.updatedArticles = Set<Article>()
|
||||
self.deletedArticles = Set<Article>()
|
||||
}
|
||||
|
||||
public init(newArticles: Set<Article>?, updatedArticles: Set<Article>?, deletedArticles: Set<Article>?) {
|
||||
public init(incomingArticles: Set<Article>?, newArticles: Set<Article>?, updatedArticles: Set<Article>?, deletedArticles: Set<Article>?) {
|
||||
self.incomingArticles = incomingArticles
|
||||
self.newArticles = newArticles
|
||||
self.updatedArticles = updatedArticles
|
||||
self.deletedArticles = deletedArticles
|
||||
|
|
|
@ -198,7 +198,7 @@ final class ArticlesTable: DatabaseTable {
|
|||
func update(_ parsedItems: Set<ParsedItem>, _ webFeedID: String, _ deleteOlder: Bool, _ completion: @escaping UpdateArticlesCompletionBlock) {
|
||||
precondition(retentionStyle == .feedBased)
|
||||
if parsedItems.isEmpty {
|
||||
callUpdateArticlesCompletionBlock(nil, nil, nil, completion)
|
||||
callUpdateArticlesCompletionBlock(nil, nil, nil, nil, completion)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -222,7 +222,7 @@ final class ArticlesTable: DatabaseTable {
|
|||
|
||||
let incomingArticles = Article.articlesWithParsedItems(parsedItems, webFeedID, self.accountID, statusesDictionary) //2
|
||||
if incomingArticles.isEmpty {
|
||||
self.callUpdateArticlesCompletionBlock(nil, nil, nil, completion)
|
||||
self.callUpdateArticlesCompletionBlock(nil, nil, nil, nil, completion)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -243,7 +243,7 @@ final class ArticlesTable: DatabaseTable {
|
|||
articlesToDelete = Set<Article>()
|
||||
}
|
||||
|
||||
self.callUpdateArticlesCompletionBlock(newArticles, updatedArticles, articlesToDelete, completion) //7
|
||||
self.callUpdateArticlesCompletionBlock(incomingArticles, newArticles, updatedArticles, articlesToDelete, completion) //7
|
||||
|
||||
self.addArticlesToCache(newArticles)
|
||||
self.addArticlesToCache(updatedArticles)
|
||||
|
@ -278,7 +278,7 @@ final class ArticlesTable: DatabaseTable {
|
|||
func update(_ webFeedIDsAndItems: [String: Set<ParsedItem>], _ read: Bool, _ completion: @escaping UpdateArticlesCompletionBlock) {
|
||||
precondition(retentionStyle == .syncSystem)
|
||||
if webFeedIDsAndItems.isEmpty {
|
||||
callUpdateArticlesCompletionBlock(nil, nil, nil, completion)
|
||||
callUpdateArticlesCompletionBlock(nil, nil, nil, nil, completion)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -304,13 +304,13 @@ final class ArticlesTable: DatabaseTable {
|
|||
|
||||
let allIncomingArticles = Article.articlesWithWebFeedIDsAndItems(webFeedIDsAndItems, self.accountID, statusesDictionary) //2
|
||||
if allIncomingArticles.isEmpty {
|
||||
self.callUpdateArticlesCompletionBlock(nil, nil, nil, completion)
|
||||
self.callUpdateArticlesCompletionBlock(nil, nil, nil, nil, completion)
|
||||
return
|
||||
}
|
||||
|
||||
let incomingArticles = self.filterIncomingArticles(allIncomingArticles) //3
|
||||
if incomingArticles.isEmpty {
|
||||
self.callUpdateArticlesCompletionBlock(nil, nil, nil, completion)
|
||||
self.callUpdateArticlesCompletionBlock(nil, nil, nil, nil, completion)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -321,7 +321,7 @@ final class ArticlesTable: DatabaseTable {
|
|||
let newArticles = self.findAndSaveNewArticles(incomingArticles, fetchedArticlesDictionary, database) //5
|
||||
let updatedArticles = self.findAndSaveUpdatedArticles(incomingArticles, fetchedArticlesDictionary, database) //6
|
||||
|
||||
self.callUpdateArticlesCompletionBlock(newArticles, updatedArticles, nil, completion) //7
|
||||
self.callUpdateArticlesCompletionBlock(incomingArticles, newArticles, updatedArticles, nil, completion) //7
|
||||
|
||||
self.addArticlesToCache(newArticles)
|
||||
self.addArticlesToCache(updatedArticles)
|
||||
|
@ -914,8 +914,11 @@ private extension ArticlesTable {
|
|||
|
||||
// MARK: - Saving Parsed Items
|
||||
|
||||
func callUpdateArticlesCompletionBlock(_ newArticles: Set<Article>?, _ updatedArticles: Set<Article>?, _ deletedArticles: Set<Article>?, _ completion: @escaping UpdateArticlesCompletionBlock) {
|
||||
let articleChanges = ArticleChanges(newArticles: newArticles, updatedArticles: updatedArticles, deletedArticles: deletedArticles)
|
||||
func callUpdateArticlesCompletionBlock(_ incomingArticles: Set<Article>?,
|
||||
_ newArticles: Set<Article>?,
|
||||
_ updatedArticles: Set<Article>?,
|
||||
_ deletedArticles: Set<Article>?, _ completion: @escaping UpdateArticlesCompletionBlock) {
|
||||
let articleChanges = ArticleChanges(incomingArticles: incomingArticles, newArticles: newArticles, updatedArticles: updatedArticles, deletedArticles: deletedArticles)
|
||||
DispatchQueue.main.async {
|
||||
completion(.success(articleChanges))
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="21225" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="21507" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21225"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21507"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
|
@ -15,6 +15,7 @@
|
|||
<outlet property="nameTextField" destination="TT0-Kf-YTC" id="oMG-jn-Qn0"/>
|
||||
<outlet property="typeLabel" destination="XYX-iz-hnq" id="SKM-et-3h3"/>
|
||||
<outlet property="view" destination="3ki-rg-6yb" id="ttM-4E-OLN"/>
|
||||
<outlet property="wipeCloudKitArticlesAndReloadButton" destination="56k-80-63v" id="bc2-1h-o2d"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
|
@ -130,9 +131,22 @@
|
|||
</connections>
|
||||
</buttonCell>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="56k-80-63v">
|
||||
<rect key="frame" x="32" y="38" width="266" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="Wipe iCloud Articles and Reload Them" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="yOR-Lx-8cZ">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="wipeCloudKitArticlesAndReload:" target="-2" id="luN-qw-nZg"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="56k-80-63v" firstAttribute="centerX" secondItem="ft2-Mb-5LD" secondAttribute="centerX" id="GpX-Fs-EEb"/>
|
||||
<constraint firstItem="56k-80-63v" firstAttribute="top" secondItem="nVy-H3-bFO" secondAttribute="bottom" constant="16" id="JGg-ll-aeC"/>
|
||||
<constraint firstItem="nVy-H3-bFO" firstAttribute="leading" secondItem="ft2-Mb-5LD" secondAttribute="leading" constant="20" symbolic="YES" id="SQe-pg-1hl"/>
|
||||
<constraint firstItem="gLh-gl-ZGQ" firstAttribute="centerX" secondItem="ft2-Mb-5LD" secondAttribute="centerX" id="VwD-aL-g9a"/>
|
||||
<constraint firstAttribute="trailing" secondItem="nVy-H3-bFO" secondAttribute="trailing" constant="20" symbolic="YES" id="Wsq-ar-poP"/>
|
||||
<constraint firstItem="gLh-gl-ZGQ" firstAttribute="top" secondItem="nVy-H3-bFO" secondAttribute="bottom" constant="8" id="a0S-2S-3dR"/>
|
||||
<constraint firstItem="gLh-gl-ZGQ" firstAttribute="centerX" secondItem="ft2-Mb-5LD" secondAttribute="centerX" id="cW8-YT-BEn"/>
|
||||
|
|
|
@ -17,6 +17,7 @@ final class AccountsDetailViewController: NSViewController, NSTextFieldDelegate
|
|||
@IBOutlet weak var limitationsAndSolutionsRow: NSGridRow!
|
||||
@IBOutlet weak var limitationsAndSolutionsTextField: NSTextField!
|
||||
@IBOutlet weak var credentialsButton: NSButton!
|
||||
@IBOutlet weak var wipeCloudKitArticlesAndReloadButton: NSButton!
|
||||
|
||||
private var accountsWindowController: NSWindowController?
|
||||
private var account: Account?
|
||||
|
@ -55,6 +56,7 @@ final class AccountsDetailViewController: NSViewController, NSTextFieldDelegate
|
|||
limitationsAndSolutionsTextField.attributedStringValue = attrString
|
||||
} else {
|
||||
limitationsAndSolutionsRow.isHidden = true
|
||||
wipeCloudKitArticlesAndReloadButton.isHidden = true
|
||||
}
|
||||
|
||||
credentialsButton.isHidden = hidesCredentialsButton
|
||||
|
@ -100,4 +102,28 @@ final class AccountsDetailViewController: NSViewController, NSTextFieldDelegate
|
|||
|
||||
}
|
||||
|
||||
@IBAction func wipeCloudKitArticlesAndReload(_ sender: Any) {
|
||||
let alert = NSAlert()
|
||||
alert.alertStyle = .warning
|
||||
alert.messageText = NSLocalizedString("Wipe And Reload Articles?", comment: "Wipe And Reload Articles")
|
||||
alert.informativeText = NSLocalizedString("Are you sure you want to wipe and reload the iCloud Articles? Only articles in RSS feeds and Starred articles will be reloaded.",
|
||||
comment: "Wipe And Reload Articles")
|
||||
|
||||
alert.addButton(withTitle: NSLocalizedString("Wipe And Reload", comment: "Wipe And Reload"))
|
||||
alert.addButton(withTitle: NSLocalizedString("Cancel", comment: "Cancel Delete Account"))
|
||||
|
||||
alert.beginSheetModal(for: view.window!) { [weak self] result in
|
||||
if result == NSApplication.ModalResponse.alertFirstButtonReturn {
|
||||
guard let self = self else { return }
|
||||
|
||||
self.wipeCloudKitArticlesAndReloadButton.isEnabled = false
|
||||
AccountManager.shared.wipeCloudKitArticlesZoneAndReload(errorHandler: ErrorHandler.present) {
|
||||
self.wipeCloudKitArticlesAndReloadButton.isEnabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -60,8 +60,8 @@
|
|||
"repositoryURL": "https://github.com/Ranchero-Software/RSCore.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "fd64fb77de2c4b6a87a971d353e7eea75100f694",
|
||||
"version": "1.1.3"
|
||||
"revision": "917610ce4af5a22d1f1647ace571cc1b359839ba",
|
||||
"version": "1.1.4"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -96,8 +96,8 @@
|
|||
"repositoryURL": "https://github.com/Ranchero-Software/RSWeb.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "c8d6212b08ae86142105e828fda391a6503a2ea7",
|
||||
"version": "1.0.6"
|
||||
"revision": "aca2db763e3404757b273821f058bed2bbe02fcf",
|
||||
"version": "1.0.7"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue