Add OPML import for Feedbin.

This commit is contained in:
Maurice Parker 2019-05-11 16:07:27 -05:00
parent 6f92cd1a73
commit 51c2527da2
2 changed files with 154 additions and 15 deletions

View File

@ -341,6 +341,18 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
return ensureFolder(with: folderName)
}
func newFeed(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)
if let feedTitle = opmlFeedSpecifier.title {
if feed.name == nil {
feed.name = feedTitle
}
}
return feed
}
func addFeed(container: Container, feed: Feed, completion: @escaping (Result<Void, Error>) -> Void) {
delegate.addFeed(for: self, to: container, with: feed, completion: completion)
}
@ -905,18 +917,6 @@ private extension Account {
feedDictionaryNeedsUpdate = false
}
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)
if let feedTitle = opmlFeedSpecifier.title {
if feed.name == nil {
feed.name = feedTitle
}
}
return feed
}
func loadOPMLItems(_ items: [RSOPMLItem], parentFolder: Folder?) {
var feedsToAdd = Set<Feed>()
@ -924,7 +924,7 @@ private extension Account {
items.forEach { (item) in
if let feedSpecifier = item.feedSpecifier {
let feed = ensureFeed(with: feedSpecifier)
let feed = newFeed(with: feedSpecifier)
feedsToAdd.insert(feed)
return
}

View File

@ -13,6 +13,7 @@ import UIKit
import RSCore
#endif
import RSCore
import RSParser
import RSWeb
import os.log
@ -64,6 +65,39 @@ final class FeedbinAccountDelegate: AccountDelegate {
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, let children = loadDocument.children else {
completion(.success(()))
return
}
importOPMLItems(account, items: children, parentFolder: nil)
completion(.success(()))
}
func renameFolder(for account: Account, with folder: Folder, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
@ -342,6 +376,109 @@ private extension FeedbinAccountDelegate {
}
func importOPMLItems(_ account: Account, items: [RSOPMLItem], parentFolder: Folder?) {
items.forEach { (item) in
if let feedSpecifier = item.feedSpecifier {
importFeedSpecifier(account, feedSpecifier: feedSpecifier, parentFolder: parentFolder)
return
}
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(account, items: itemChildren, parentFolder: parentFolder)
}
return
}
if let folder = account.ensureFolder(with: folderName) {
if let itemChildren = item.children {
importOPMLItems(account, items: itemChildren, parentFolder: folder)
}
}
}
}
func importFeedSpecifier(_ account: Account, feedSpecifier: RSOPMLFeedSpecifier, parentFolder: Folder?) {
caller.createSubscription(url: feedSpecifier.feedURL) { [weak self] result in
switch result {
case .success(let subResult):
switch subResult {
case .created(let sub):
DispatchQueue.main.async {
let feed = account.createFeed(with: sub.name, url: sub.url, feedID: String(sub.feedID), homePageURL: sub.homePageURL)
feed.subscriptionID = String(sub.subscriptionID)
self?.importFeedSpecifierPostProcess(account: account, sub: sub, feedSpecifier: feedSpecifier, feed: feed, parentFolder: parentFolder)
}
default:
break
}
case .failure(let error):
guard let self = self else { return }
os_log(.error, log: self.log, "Create feed on OPML import failed: %@.", error.localizedDescription)
}
}
}
func importFeedSpecifierPostProcess(account: Account, sub: FeedbinSubscription, feedSpecifier: RSOPMLFeedSpecifier, feed: Feed, parentFolder: Folder?) {
// Rename the feed if its name in the OPML file doesn't match the found name
if sub.name != feedSpecifier.title, let newName = feedSpecifier.title {
self.caller.renameSubscription(subscriptionID: String(sub.subscriptionID), newName: newName) { [weak self] result in
switch result {
case .success:
DispatchQueue.main.async {
feed.editedName = newName
}
case .failure(let error):
guard let self = self else { return }
os_log(.error, log: self.log, "Rename feed on OPML import failed: %@.", error.localizedDescription)
}
}
}
// Move the new feed if it is in a folder
if let folder = parentFolder, let feedID = Int(feed.feedID) {
self.caller.createTagging(feedID: feedID, name: folder.name ?? "") { [weak self] result in
switch result {
case .success(let taggingID):
DispatchQueue.main.async {
self?.saveFolderRelationship(for: feed, withFolderName: folder.name ?? "", id: String(taggingID))
folder.addFeed(feed)
}
case .failure(let error):
guard let self = self else { return }
os_log(.error, log: self.log, "Move feed to folder on OPML import failed: %@.", error.localizedDescription)
}
}
} else {
DispatchQueue.main.async {
account.addFeed(feed)
}
}
}
func syncFolders(_ account: Account, _ tags: [FeedbinTag]?) {
guard let tags = tags else { return }
@ -606,7 +743,9 @@ private extension FeedbinAccountDelegate {
account.addFeed(feed)
}
processRestoredFeedName(for: account, feed: feed, editedName: editedName!, completion: completion)
if editedName != nil {
processRestoredFeedName(for: account, feed: feed, editedName: editedName!, completion: completion)
}
}