Refactor to differentiate between loading the Subscriptions.opml file and importing an external OPML file as they now need separate behaviors

This commit is contained in:
Maurice Parker 2019-05-11 12:26:23 -05:00
parent 5200e49175
commit 6f92cd1a73
8 changed files with 92 additions and 97 deletions

View File

@ -290,28 +290,10 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
delegate.refreshAll(for: self, completion: completion)
}
public func update(_ feed: Feed, with parsedFeed: ParsedFeed, _ completion: @escaping (() -> Void)) {
feed.takeSettings(from: parsedFeed)
database.update(feedID: feed.feedID, parsedFeed: parsedFeed) { (newArticles, updatedArticles) in
var userInfo = [String: Any]()
if let newArticles = newArticles, !newArticles.isEmpty {
self.updateUnreadCounts(for: Set([feed]))
userInfo[UserInfoKey.newArticles] = newArticles
}
if let updatedArticles = updatedArticles, !updatedArticles.isEmpty {
userInfo[UserInfoKey.updatedArticles] = updatedArticles
}
userInfo[UserInfoKey.feeds] = Set([feed])
completion()
NotificationCenter.default.post(name: .AccountDidDownloadArticles, object: self, userInfo: userInfo)
}
public func importOPML(_ opmlFile: URL, completion: @escaping (Result<Void, Error>) -> Void) {
delegate.importOPML(for: self, opmlFile: opmlFile, completion: completion)
}
public func markArticles(_ articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool) -> Set<Article>? {
// Returns set of Articles whose statuses did change.
@ -413,12 +395,12 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
structureDidChange()
}
public func importOPML(_ opmlDocument: RSOPMLDocument) {
func loadOPML(_ opmlDocument: RSOPMLDocument) {
guard let children = opmlDocument.children else {
return
}
importOPMLItems(children, parentFolder: nil)
loadOPMLItems(children, parentFolder: nil)
structureDidChange()
DispatchQueue.main.async {
@ -573,6 +555,28 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
feedDictionaryNeedsUpdate = true
}
func update(_ feed: Feed, with parsedFeed: ParsedFeed, _ completion: @escaping (() -> Void)) {
feed.takeSettings(from: parsedFeed)
database.update(feedID: feed.feedID, parsedFeed: parsedFeed) { (newArticles, updatedArticles) in
var userInfo = [String: Any]()
if let newArticles = newArticles, !newArticles.isEmpty {
self.updateUnreadCounts(for: Set([feed]))
userInfo[UserInfoKey.newArticles] = newArticles
}
if let updatedArticles = updatedArticles, !updatedArticles.isEmpty {
userInfo[UserInfoKey.updatedArticles] = updatedArticles
}
userInfo[UserInfoKey.feeds] = Set([feed])
completion()
NotificationCenter.default.post(name: .AccountDidDownloadArticles, object: self, userInfo: userInfo)
}
}
// MARK: - Container
public func flattenedFeeds() -> Set<Feed> {
@ -735,12 +739,12 @@ private extension Account {
}
func pullObjectsFromDisk() {
importAccountMetadata()
importFeedMetadata()
importOPMLFile(path: opmlFilePath)
loadAccountMetadata()
loadFeedMetadata()
loadOPMLFile(path: opmlFilePath)
}
func importAccountMetadata() {
func loadAccountMetadata() {
let url = URL(fileURLWithPath: metadataPath)
guard let data = try? Data(contentsOf: url) else {
metadata.delegate = self
@ -751,7 +755,7 @@ private extension Account {
metadata.delegate = self
}
func importFeedMetadata() {
func loadFeedMetadata() {
let url = URL(fileURLWithPath: feedMetadataPath)
guard let data = try? Data(contentsOf: url) else {
return
@ -761,7 +765,7 @@ private extension Account {
feedMetadata.values.forEach { $0.delegate = self }
}
func importOPMLFile(path: String) {
func loadOPMLFile(path: String) {
let opmlFileURL = URL(fileURLWithPath: path)
var fileData: Data?
do {
@ -794,7 +798,7 @@ private extension Account {
}
BatchUpdate.shared.perform {
importOPMLItems(children, parentFolder: nil)
loadOPMLItems(children, parentFolder: nil)
}
}
@ -901,7 +905,7 @@ private extension Account {
feedDictionaryNeedsUpdate = false
}
func createFeed(with opmlFeedSpecifier: RSOPMLFeedSpecifier) -> Feed {
func ensureFeed(with opmlFeedSpecifier: RSOPMLFeedSpecifier) -> Feed {
let feedURL = opmlFeedSpecifier.feedURL
let metadata = feedMetadata(feedURL: feedURL, feedID: feedURL)
let feed = Feed(account: self, url: opmlFeedSpecifier.feedURL, metadata: metadata)
@ -913,14 +917,14 @@ private extension Account {
return feed
}
func importOPMLItems(_ items: [RSOPMLItem], parentFolder: Folder?) {
func loadOPMLItems(_ items: [RSOPMLItem], parentFolder: Folder?) {
var feedsToAdd = Set<Feed>()
items.forEach { (item) in
if let feedSpecifier = item.feedSpecifier {
let feed = createFeed(with: feedSpecifier)
let feed = ensureFeed(with: feedSpecifier)
feedsToAdd.insert(feed)
return
}
@ -928,14 +932,14 @@ private extension Account {
guard let folderName = item.titleFromAttributes else {
// Folder doesnt have a name, so it wont be created, and its items will go one level up.
if let itemChildren = item.children {
importOPMLItems(itemChildren, parentFolder: parentFolder)
loadOPMLItems(itemChildren, parentFolder: parentFolder)
}
return
}
if let folder = ensureFolder(with: folderName) {
if let itemChildren = item.children {
importOPMLItems(itemChildren, parentFolder: folder)
loadOPMLItems(itemChildren, parentFolder: folder)
}
}
}

View File

@ -20,7 +20,8 @@ protocol AccountDelegate {
var refreshProgress: DownloadProgress { get }
func refreshAll(for account: Account, completion: (() -> Void)?)
func importOPML(for account:Account, opmlFile: URL, completion: @escaping (Result<Void, Error>) -> Void)
func renameFolder(for account: Account, with folder: Folder, to name: String, completion: @escaping (Result<Void, Error>) -> Void)
func deleteFolder(for account: Account, with folder: Folder, completion: @escaping (Result<Void, Error>) -> Void)

View File

@ -62,6 +62,10 @@ final class FeedbinAccountDelegate: AccountDelegate {
}
}
func importOPML(for account:Account, opmlFile: URL, completion: @escaping (Result<Void, Error>) -> Void) {
}
func renameFolder(for account: Account, with folder: Folder, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
caller.renameTag(oldName: folder.name ?? "", newName: name) { result in

View File

@ -7,6 +7,7 @@
//
import Foundation
import RSParser
import RSWeb
public enum LocalAccountDelegateError: String, Error {
@ -35,6 +36,44 @@ final class LocalAccountDelegate: AccountDelegate {
refresher.refreshFeeds(account.flattenedFeeds())
completion?()
}
func importOPML(for account:Account, opmlFile: URL, completion: @escaping (Result<Void, Error>) -> 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.
account.loadOPML(loadDocument)
completion(.success(()))
}
func renameFolder(for account: Account, with folder: Folder, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
folder.name = name

View File

@ -64,13 +64,13 @@ class ImportOPMLWindowController: NSWindowController {
panel.allowedFileTypes = ["opml", "xml"]
panel.allowsOtherFileTypes = false
panel.beginSheetModal(for: hostWindow!) { result in
if result == NSApplication.ModalResponse.OK, let url = panel.url {
DispatchQueue.main.async {
do {
try OPMLImporter.parseAndImport(fileURL: url, account: account)
}
catch let error as NSError {
panel.beginSheetModal(for: hostWindow!) { modalResult in
if modalResult == NSApplication.ModalResponse.OK, let url = panel.url {
account.importOPML(url) { result in
switch result {
case .success:
break
case .failure(let error):
NSApplication.shared.presentError(error)
}
}

View File

@ -89,7 +89,6 @@
51C45294226509C800C03939 /* SearchFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8477ACBD22238E9500DF7F37 /* SearchFeedDelegate.swift */; };
51C45296226509D300C03939 /* OPMLExporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8444C8F11FED81840051386C /* OPMLExporter.swift */; };
51C45297226509E300C03939 /* DefaultFeedsImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97591ED9EB0D007D329B /* DefaultFeedsImporter.swift */; };
51C45298226509E600C03939 /* OPMLImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DAEE2F1F86CAFE0058304B /* OPMLImporter.swift */; };
51C4529922650A0000C03939 /* ArticleStylesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97881ED9ECEF007D329B /* ArticleStylesManager.swift */; };
51C4529A22650A0400C03939 /* ArticleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97871ED9ECEF007D329B /* ArticleStyle.swift */; };
51C4529B22650A1000C03939 /* FaviconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848F6AE41FC29CFA002D422E /* FaviconDownloader.swift */; };
@ -259,7 +258,6 @@
84C9FCA42262A1B800D921D6 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 84C9FCA22262A1B800D921D6 /* LaunchScreen.storyboard */; };
84CC88181FE59CBF00644329 /* SmartFeedsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CC88171FE59CBF00644329 /* SmartFeedsController.swift */; };
84D52E951FE588BB00D14F5B /* DetailStatusBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D52E941FE588BB00D14F5B /* DetailStatusBarView.swift */; };
84DAEE301F86CAFE0058304B /* OPMLImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DAEE2F1F86CAFE0058304B /* OPMLImporter.swift */; };
84E185B3203B74E500F69BFA /* SingleLineTextFieldSizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E185B2203B74E500F69BFA /* SingleLineTextFieldSizer.swift */; };
84E185C3203BB12600F69BFA /* MultilineTextFieldSizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E185C2203BB12600F69BFA /* MultilineTextFieldSizer.swift */; };
84E46C7D1F75EF7B005ECFB3 /* AppDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E46C7C1F75EF7B005ECFB3 /* AppDefaults.swift */; };
@ -834,7 +832,6 @@
84CBDDAE1FD3674C005A61AA /* Technotes */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Technotes; sourceTree = "<group>"; };
84CC88171FE59CBF00644329 /* SmartFeedsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmartFeedsController.swift; sourceTree = "<group>"; };
84D52E941FE588BB00D14F5B /* DetailStatusBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailStatusBarView.swift; sourceTree = "<group>"; };
84DAEE2F1F86CAFE0058304B /* OPMLImporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OPMLImporter.swift; sourceTree = "<group>"; };
84E185B2203B74E500F69BFA /* SingleLineTextFieldSizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleLineTextFieldSizer.swift; sourceTree = "<group>"; };
84E185C2203BB12600F69BFA /* MultilineTextFieldSizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultilineTextFieldSizer.swift; sourceTree = "<group>"; };
84E46C7C1F75EF7B005ECFB3 /* AppDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDefaults.swift; sourceTree = "<group>"; };
@ -1654,7 +1651,6 @@
84DAEE201F86CAE00058304B /* Importers */ = {
isa = PBXGroup;
children = (
84DAEE2F1F86CAFE0058304B /* OPMLImporter.swift */,
849A97591ED9EB0D007D329B /* DefaultFeedsImporter.swift */,
84A3EE52223B667F00557320 /* DefaultFeeds.opml */,
);
@ -2319,7 +2315,6 @@
51C452782265091600C03939 /* MasterTimelineCellData.swift in Sources */,
51C45259226508D300C03939 /* AppDefaults.swift in Sources */,
51C45293226509C800C03939 /* StarredFeedDelegate.swift in Sources */,
51C45298226509E600C03939 /* OPMLImporter.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -2374,7 +2369,6 @@
84162A152038C12C00035290 /* MarkCommandValidationStatus.swift in Sources */,
84E95D241FB1087500552D99 /* ArticlePasteboardWriter.swift in Sources */,
849A975B1ED9EB0D007D329B /* ArticleUtilities.swift in Sources */,
84DAEE301F86CAFE0058304B /* OPMLImporter.swift in Sources */,
849A975C1ED9EB0D007D329B /* DefaultFeedsImporter.swift in Sources */,
84A37CB5201ECD610087C5AF /* RenameWindowController.swift in Sources */,
84A14FF320048CA70046AD9A /* SendToMicroBlogCommand.swift in Sources */,

View File

@ -19,7 +19,7 @@ struct DefaultFeedsImporter {
appDelegate.logDebugMessage("Importing default feeds.")
let defaultFeedsURL = Bundle.main.url(forResource: "DefaultFeeds", withExtension: "opml")!
try! OPMLImporter.parseAndImport(fileURL: defaultFeedsURL, account: AccountManager.shared.defaultAccount)
AccountManager.shared.defaultAccount.importOPML(defaultFeedsURL) { result in }
}
private static func shouldImportDefaultFeeds(_ isFirstRun: Bool) -> Bool {

View File

@ -1,47 +0,0 @@
//
// OPMLImporter.swift
// NetNewsWire
//
// Created by Brent Simmons on 10/5/17.
// Copyright © 2017 Ranchero Software. All rights reserved.
//
import Foundation
import RSParser
import Account
import RSCore
struct OPMLImporter {
static func parseAndImport(fileURL: URL, account: Account) throws {
var fileData: Data?
do {
fileData = try Data(contentsOf: fileURL)
} catch {
print("Error reading OPML file. \(error)")
throw error
}
guard let opmlData = fileData else {
return
}
let parserData = ParserData(url: fileURL.absoluteString, data: opmlData)
var opmlDocument: RSOPMLDocument?
do {
opmlDocument = try RSOPMLParser.parseOPML(with: parserData)
} catch {
print("Error parsing OPML file. \(error)")
throw error
}
if let opmlDocument = opmlDocument {
BatchUpdate.shared.perform {
account.importOPML(opmlDocument)
}
}
}
}