From 74f84dc000c785bb2ea4fa9cadf75d277ed57356 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Wed, 19 Jun 2019 17:50:32 -0500 Subject: [PATCH] Restrict OPML import for Account types that don't support it --- Frameworks/Account/Account.swift | 14 +- Frameworks/Account/AccountDelegate.swift | 7 +- .../Feedbin/FeedbinAccountDelegate.swift | 23 ++- .../LocalAccount/LocalAccountDelegate.swift | 7 +- .../ReaderAPI/ReaderAPIAccountDelegate.swift | 135 +----------------- .../OPML/ImportOPMLWindowController.swift | 4 + .../Sidebar/SidebarOutlineDataSource.swift | 2 +- iOS/Settings/SettingsView.swift | 9 ++ 8 files changed, 44 insertions(+), 157 deletions(-) diff --git a/Frameworks/Account/Account.swift b/Frameworks/Account/Account.swift index 87c061727..36066fdf1 100644 --- a/Frameworks/Account/Account.swift +++ b/Frameworks/Account/Account.swift @@ -184,8 +184,12 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, } } - public var usesTags: Bool { - return delegate.usesTags + public var isTagBasedSystem: Bool { + return delegate.isTagBasedSystem + } + + public var isOPMLImportSupported: Bool { + return delegate.isOPMLImportSupported } var refreshInProgress = false { @@ -206,8 +210,8 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, return delegate.refreshProgress } - var supportsSubFolders: Bool { - return delegate.supportsSubFolders + var isSubfoldersSupported: Bool { + return delegate.isSubfoldersSupported } init?(dataFolder: String, type: AccountType, accountID: String, transport: Transport? = nil) { @@ -348,7 +352,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, public func importOPML(_ opmlFile: URL, completion: @escaping (Result) -> Void) { - guard !delegate.opmlImportInProgress else { + guard !delegate.isOPMLImportInProgress else { completion(.failure(AccountError.opmlImportInProgress)) return } diff --git a/Frameworks/Account/AccountDelegate.swift b/Frameworks/Account/AccountDelegate.swift index 1445e1f1f..cc657f770 100644 --- a/Frameworks/Account/AccountDelegate.swift +++ b/Frameworks/Account/AccountDelegate.swift @@ -13,9 +13,10 @@ import RSWeb protocol AccountDelegate { // Local account does not; some synced accounts might. - var supportsSubFolders: Bool { get } - var usesTags: Bool { get } - var opmlImportInProgress: Bool { get } + var isSubfoldersSupported: Bool { get } + var isTagBasedSystem: Bool { get } + var isOPMLImportSupported: Bool { get } + var isOPMLImportInProgress: Bool { get } var server: String? { get } var credentials: Credentials? { get set } diff --git a/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift b/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift index 4ed6aa518..05d3c0d0c 100644 --- a/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift +++ b/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift @@ -6,12 +6,6 @@ // Copyright © 2019 Ranchero Software, LLC. All rights reserved. // -#if os(macOS) -import AppKit -#else -import UIKit -import RSCore -#endif import Articles import RSCore import RSParser @@ -30,10 +24,11 @@ final class FeedbinAccountDelegate: AccountDelegate { private let caller: FeedbinAPICaller private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "Feedbin") - let supportsSubFolders = false - let usesTags = true + let isSubfoldersSupported = false + let isTagBasedSystem = true + let isOPMLImportSupported = true let server: String? = "api.feedbin.com" - var opmlImportInProgress = false + var isOPMLImportInProgress = false var credentials: Credentials? { didSet { @@ -207,7 +202,7 @@ final class FeedbinAccountDelegate: AccountDelegate { } os_log(.debug, log: log, "Begin importing OPML...") - opmlImportInProgress = true + isOPMLImportInProgress = true refreshProgress.addToNumberOfTasksAndRemaining(1) caller.importOPML(opmlData: opmlData) { result in @@ -216,7 +211,7 @@ final class FeedbinAccountDelegate: AccountDelegate { if importResult.complete { os_log(.debug, log: self.log, "Import OPML done.") self.refreshProgress.completeTask() - self.opmlImportInProgress = false + self.isOPMLImportInProgress = false DispatchQueue.main.async { completion(.success(())) } @@ -226,7 +221,7 @@ final class FeedbinAccountDelegate: AccountDelegate { case .failure(let error): os_log(.debug, log: self.log, "Import OPML failed.") self.refreshProgress.completeTask() - self.opmlImportInProgress = false + self.isOPMLImportInProgress = false DispatchQueue.main.async { let wrappedError = AccountError.wrappedError(error: error, account: account) completion(.failure(wrappedError)) @@ -569,7 +564,7 @@ private extension FeedbinAccountDelegate { os_log(.debug, log: self.log, "Checking status of OPML import successfully completed.") timer.invalidate() self.refreshProgress.completeTask() - self.opmlImportInProgress = false + self.isOPMLImportInProgress = false DispatchQueue.main.async { completion(.success(())) } @@ -578,7 +573,7 @@ private extension FeedbinAccountDelegate { os_log(.debug, log: self.log, "Import OPML check failed.") timer.invalidate() self.refreshProgress.completeTask() - self.opmlImportInProgress = false + self.isOPMLImportInProgress = false DispatchQueue.main.async { completion(.failure(error)) } diff --git a/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift b/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift index 29d6e31a3..b051f1f9b 100644 --- a/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift +++ b/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift @@ -18,9 +18,10 @@ public enum LocalAccountDelegateError: String, Error { final class LocalAccountDelegate: AccountDelegate { - let supportsSubFolders = false - let usesTags = false - let opmlImportInProgress = false + let isSubfoldersSupported = false + let isTagBasedSystem = false + let isOPMLImportSupported = true + let isOPMLImportInProgress = false let server: String? = nil var credentials: Credentials? diff --git a/Frameworks/Account/ReaderAPI/ReaderAPIAccountDelegate.swift b/Frameworks/Account/ReaderAPI/ReaderAPIAccountDelegate.swift index b81c1cebc..6e80bd3db 100644 --- a/Frameworks/Account/ReaderAPI/ReaderAPIAccountDelegate.swift +++ b/Frameworks/Account/ReaderAPI/ReaderAPIAccountDelegate.swift @@ -6,12 +6,6 @@ // Copyright © 2019 Ranchero Software, LLC. All rights reserved. // -#if os(macOS) -import AppKit -#else -import UIKit -import RSCore -#endif import Articles import RSCore import RSParser @@ -31,8 +25,8 @@ final class ReaderAPIAccountDelegate: AccountDelegate { private let caller: ReaderAPICaller private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "ReaderAPI") - let supportsSubFolders = false - let usesTags = true + let isSubfoldersSupported = false + let isTagBasedSystem = true var server: String? { get { @@ -40,7 +34,8 @@ final class ReaderAPIAccountDelegate: AccountDelegate { } } - var opmlImportInProgress = false + let isOPMLImportSupported = false + var isOPMLImportInProgress = false var credentials: Credentials? { didSet { @@ -195,128 +190,6 @@ final class ReaderAPIAccountDelegate: AccountDelegate { } func importOPML(for account:Account, opmlFile: URL, completion: @escaping (Result) -> Void) { - - var fileData: Data? - - do { - fileData = try Data(contentsOf: opmlFile) - } catch { - completion(.failure(error)) - return - } - - guard let opmlData = fileData else { - completion(.success(())) - return - } - - let parserData = ParserData(url: opmlFile.absoluteString, data: opmlData) - var opmlDocument: RSOPMLDocument? - - do { - opmlDocument = try RSOPMLParser.parseOPML(with: parserData) - } catch { - completion(.failure(error)) - return - } - - guard let loadDocument = opmlDocument else { - completion(.success(())) - return - } - - // We use the same mechanism to load local accounts as we do to load the subscription - // OPML all accounts. - BatchUpdate.shared.perform { - loadOPML(account: account, opmlDocument: loadDocument) - } - completion(.success(())) - - } - - func loadOPML(account: Account, opmlDocument: RSOPMLDocument) { - - guard let children = opmlDocument.children else { - return - } - loadOPMLItems(account: account, items: children, parentFolder: nil) - } - - func loadOPMLItems(account: Account, items: [RSOPMLItem], parentFolder: Folder?) { - - var feedsToAdd = Set() - - items.forEach { (item) in - - if let feedSpecifier = item.feedSpecifier { - feedsToAdd.insert(feedSpecifier.feedURL) - return - } - - guard let folderName = item.titleFromAttributes else { - // Folder doesn’t have a name, so it won’t be created, and its items will go one level up. - if let itemChildren = item.children { - loadOPMLItems(account: account, items: itemChildren, parentFolder: parentFolder) - } - return - } - - if let itemChildren = item.children, let folder = account.ensureFolder(with: folderName) { - loadOPMLItems(account: account, items: itemChildren, parentFolder: folder) - } - } - - let group = DispatchGroup() - - if let parentFolder = parentFolder { - for url in feedsToAdd { - group.enter() - caller.createSubscription(url: url) { result in - group.leave() - switch result { - case .success(let subResult): - switch subResult { - case .created(let subscription): - let feed = account.createFeed(with: subscription.name, url: subscription.url, feedID: String(subscription.feedID), homePageURL: subscription.homePageURL) - feed.subscriptionID = String(subscription.feedID) - account.addFeed(feed, to: parentFolder) { _ in } - default: - break - } - case .failure(_): - break - } - - } - } - } else { - for url in feedsToAdd { - group.enter() - caller.createSubscription(url: url) { result in - group.leave() - switch result { - case .success(let subResult): - switch subResult { - case .created(let subscription): - let feed = account.createFeed(with: subscription.name, url: subscription.url, feedID: String(subscription.feedID), homePageURL: subscription.homePageURL) - feed.subscriptionID = String(subscription.feedID) - account.addFeed(feed) - default: - break - } - case .failure(_): - break - } - } - } - } - - group.notify(queue: DispatchQueue.main) { - - DispatchQueue.main.async { - self.refreshAll(for: account) { (_) in } - } - } } func addFolder(for account: Account, name: String, completion: @escaping (Result) -> Void) { diff --git a/Mac/MainWindow/OPML/ImportOPMLWindowController.swift b/Mac/MainWindow/OPML/ImportOPMLWindowController.swift index 76038c293..074419f02 100644 --- a/Mac/MainWindow/OPML/ImportOPMLWindowController.swift +++ b/Mac/MainWindow/OPML/ImportOPMLWindowController.swift @@ -26,6 +26,10 @@ class ImportOPMLWindowController: NSWindowController { for oneAccount in AccountManager.shared.sortedActiveAccounts { + if !oneAccount.isOPMLImportSupported { + continue + } + let oneMenuItem = NSMenuItem() oneMenuItem.title = oneAccount.nameForDisplay oneMenuItem.representedObject = oneAccount diff --git a/Mac/MainWindow/Sidebar/SidebarOutlineDataSource.swift b/Mac/MainWindow/Sidebar/SidebarOutlineDataSource.swift index a42a522b9..c993ac343 100644 --- a/Mac/MainWindow/Sidebar/SidebarOutlineDataSource.swift +++ b/Mac/MainWindow/Sidebar/SidebarOutlineDataSource.swift @@ -636,7 +636,7 @@ private extension SidebarOutlineDataSource { } func violatesTagSpecificBehavior(_ parentNode: Node, _ draggedFeeds: Set) -> Bool { - guard let parentAccount = nodeAccount(parentNode), parentAccount.usesTags else { + guard let parentAccount = nodeAccount(parentNode), parentAccount.isTagBasedSystem else { return false } diff --git a/iOS/Settings/SettingsView.swift b/iOS/Settings/SettingsView.swift index 5cc2a4863..bf7eacc1a 100644 --- a/iOS/Settings/SettingsView.swift +++ b/iOS/Settings/SettingsView.swift @@ -93,19 +93,27 @@ struct SettingsView : View { var createSubscriptionsImportAccounts: ActionSheet { var buttons = [ActionSheet.Button]() + for account in viewModel.activeAccounts { + if !account.isOPMLImportSupported { + continue + } + let button = ActionSheet.Button.default(Text(verbatim: account.nameForDisplay)) { self.subscriptionsImportAccounts = nil self.subscriptionsImportDocumentPicker = Modal(SettingsSubscriptionsImportDocumentPickerView(account: account)) } + buttons.append(button) } + buttons.append(.cancel { self.subscriptionsImportAccounts = nil }) return ActionSheet(title: Text("Import Subscriptions..."), message: Text("Select the account to import your OPML file into."), buttons: buttons) } var createSubscriptionsExportAccounts: ActionSheet { var buttons = [ActionSheet.Button]() + for account in viewModel.accounts { let button = ActionSheet.Button.default(Text(verbatim: account.nameForDisplay)) { self.subscriptionsExportAccounts = nil @@ -113,6 +121,7 @@ struct SettingsView : View { } buttons.append(button) } + buttons.append(.cancel { self.subscriptionsExportAccounts = nil }) return ActionSheet(title: Text("Export Subscriptions..."), message: Text("Select the account to export out of."), buttons: buttons) }