Switch to using the new OPML import service
This commit is contained in:
parent
3d253ce6d9
commit
b89f088917
@ -303,6 +303,8 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
|||||||
switch result {
|
switch result {
|
||||||
case .success:
|
case .success:
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
|
// Reset the last fetch date to get the article history for the added feeds.
|
||||||
|
self.metadata.lastArticleFetch = nil
|
||||||
self.delegate.refreshAll(for: self) {
|
self.delegate.refreshAll(for: self) {
|
||||||
completion(.success(()))
|
completion(.success(()))
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
5133231122810EB200C30F19 /* FeedbinIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5133230F22810E5700C30F19 /* FeedbinIcon.swift */; };
|
5133231122810EB200C30F19 /* FeedbinIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5133230F22810E5700C30F19 /* FeedbinIcon.swift */; };
|
||||||
5144EA49227B497600D19003 /* FeedbinAPICaller.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA48227B497600D19003 /* FeedbinAPICaller.swift */; };
|
5144EA49227B497600D19003 /* FeedbinAPICaller.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA48227B497600D19003 /* FeedbinAPICaller.swift */; };
|
||||||
5144EA4E227B829A00D19003 /* FeedbinAccountDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA4D227B829A00D19003 /* FeedbinAccountDelegate.swift */; };
|
5144EA4E227B829A00D19003 /* FeedbinAccountDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA4D227B829A00D19003 /* FeedbinAccountDelegate.swift */; };
|
||||||
|
5154367B228EEB28005E1CDF /* FeedbinImportResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5154367A228EEB28005E1CDF /* FeedbinImportResult.swift */; };
|
||||||
5165D7122282080C00D9D53D /* AccountFolderContentsSyncTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5165D7112282080C00D9D53D /* AccountFolderContentsSyncTest.swift */; };
|
5165D7122282080C00D9D53D /* AccountFolderContentsSyncTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5165D7112282080C00D9D53D /* AccountFolderContentsSyncTest.swift */; };
|
||||||
5165D71622821C2400D9D53D /* taggings_delete.json in Resources */ = {isa = PBXBuildFile; fileRef = 5165D71322821C2400D9D53D /* taggings_delete.json */; };
|
5165D71622821C2400D9D53D /* taggings_delete.json in Resources */ = {isa = PBXBuildFile; fileRef = 5165D71322821C2400D9D53D /* taggings_delete.json */; };
|
||||||
5165D71722821C2400D9D53D /* taggings_add.json in Resources */ = {isa = PBXBuildFile; fileRef = 5165D71422821C2400D9D53D /* taggings_add.json */; };
|
5165D71722821C2400D9D53D /* taggings_add.json in Resources */ = {isa = PBXBuildFile; fileRef = 5165D71422821C2400D9D53D /* taggings_add.json */; };
|
||||||
@ -116,6 +117,7 @@
|
|||||||
5133230F22810E5700C30F19 /* FeedbinIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinIcon.swift; sourceTree = "<group>"; };
|
5133230F22810E5700C30F19 /* FeedbinIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinIcon.swift; sourceTree = "<group>"; };
|
||||||
5144EA48227B497600D19003 /* FeedbinAPICaller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinAPICaller.swift; sourceTree = "<group>"; };
|
5144EA48227B497600D19003 /* FeedbinAPICaller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinAPICaller.swift; sourceTree = "<group>"; };
|
||||||
5144EA4D227B829A00D19003 /* FeedbinAccountDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinAccountDelegate.swift; sourceTree = "<group>"; };
|
5144EA4D227B829A00D19003 /* FeedbinAccountDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinAccountDelegate.swift; sourceTree = "<group>"; };
|
||||||
|
5154367A228EEB28005E1CDF /* FeedbinImportResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinImportResult.swift; sourceTree = "<group>"; };
|
||||||
5165D7112282080C00D9D53D /* AccountFolderContentsSyncTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountFolderContentsSyncTest.swift; sourceTree = "<group>"; };
|
5165D7112282080C00D9D53D /* AccountFolderContentsSyncTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountFolderContentsSyncTest.swift; sourceTree = "<group>"; };
|
||||||
5165D71322821C2400D9D53D /* taggings_delete.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = taggings_delete.json; sourceTree = "<group>"; };
|
5165D71322821C2400D9D53D /* taggings_delete.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = taggings_delete.json; sourceTree = "<group>"; };
|
||||||
5165D71422821C2400D9D53D /* taggings_add.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = taggings_add.json; sourceTree = "<group>"; };
|
5165D71422821C2400D9D53D /* taggings_add.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = taggings_add.json; sourceTree = "<group>"; };
|
||||||
@ -255,6 +257,7 @@
|
|||||||
51E490352288C37100C791F0 /* FeedbinDate.swift */,
|
51E490352288C37100C791F0 /* FeedbinDate.swift */,
|
||||||
84CAD7151FDF2E22000F0755 /* FeedbinEntry.swift */,
|
84CAD7151FDF2E22000F0755 /* FeedbinEntry.swift */,
|
||||||
5133230F22810E5700C30F19 /* FeedbinIcon.swift */,
|
5133230F22810E5700C30F19 /* FeedbinIcon.swift */,
|
||||||
|
5154367A228EEB28005E1CDF /* FeedbinImportResult.swift */,
|
||||||
51E5959A228C781500FCC42B /* FeedbinStarredEntry.swift */,
|
51E5959A228C781500FCC42B /* FeedbinStarredEntry.swift */,
|
||||||
84245C841FDDD8CB0074AFBB /* FeedbinSubscription.swift */,
|
84245C841FDDD8CB0074AFBB /* FeedbinSubscription.swift */,
|
||||||
51D58754227F53BE00900287 /* FeedbinTag.swift */,
|
51D58754227F53BE00900287 /* FeedbinTag.swift */,
|
||||||
@ -521,6 +524,7 @@
|
|||||||
51E490362288C37100C791F0 /* FeedbinDate.swift in Sources */,
|
51E490362288C37100C791F0 /* FeedbinDate.swift in Sources */,
|
||||||
5165D72922835F7A00D9D53D /* FeedSpecifier.swift in Sources */,
|
5165D72922835F7A00D9D53D /* FeedSpecifier.swift in Sources */,
|
||||||
844B297D2106C7EC004020B3 /* Feed.swift in Sources */,
|
844B297D2106C7EC004020B3 /* Feed.swift in Sources */,
|
||||||
|
5154367B228EEB28005E1CDF /* FeedbinImportResult.swift in Sources */,
|
||||||
84B2D4D02238CD8A00498ADA /* FeedMetadata.swift in Sources */,
|
84B2D4D02238CD8A00498ADA /* FeedMetadata.swift in Sources */,
|
||||||
5144EA49227B497600D19003 /* FeedbinAPICaller.swift in Sources */,
|
5144EA49227B497600D19003 /* FeedbinAPICaller.swift in Sources */,
|
||||||
84B99C9F1FAE8D3200ECDEDB /* ContainerPath.swift in Sources */,
|
84B99C9F1FAE8D3200ECDEDB /* ContainerPath.swift in Sources */,
|
||||||
|
@ -67,6 +67,54 @@ final class FeedbinAPICaller: NSObject {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func importOPML(opmlData: Data, completion: @escaping (Result<FeedbinImportResult, Error>) -> Void) {
|
||||||
|
|
||||||
|
let callURL = feedbinBaseURL.appendingPathComponent("imports.json")
|
||||||
|
let request = URLRequest(url: callURL, credentials: credentials)
|
||||||
|
|
||||||
|
transport.send(request: request, method: HTTPMethod.post, payload: opmlData) { result in
|
||||||
|
|
||||||
|
switch result {
|
||||||
|
case .success(let (_, data)):
|
||||||
|
|
||||||
|
guard let resultData = data else {
|
||||||
|
completion(.failure(TransportError.noData))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
let result = try JSONDecoder().decode(FeedbinImportResult.self, from: resultData)
|
||||||
|
completion(.success(result))
|
||||||
|
} catch {
|
||||||
|
completion(.failure(error))
|
||||||
|
}
|
||||||
|
|
||||||
|
case .failure(let error):
|
||||||
|
completion(.failure(error))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func retrieveOPMLImportResult(importID: Int, completion: @escaping (Result<FeedbinImportResult?, Error>) -> Void) {
|
||||||
|
|
||||||
|
let callURL = feedbinBaseURL.appendingPathComponent("imports/\(importID).json")
|
||||||
|
let request = URLRequest(url: callURL, credentials: credentials)
|
||||||
|
|
||||||
|
transport.send(request: request, resultType: FeedbinImportResult.self) { result in
|
||||||
|
|
||||||
|
switch result {
|
||||||
|
case .success(let (_, importResult)):
|
||||||
|
completion(.success(importResult))
|
||||||
|
case .failure(let error):
|
||||||
|
completion(.failure(error))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func retrieveTags(completion: @escaping (Result<[FeedbinTag]?, Error>) -> Void) {
|
func retrieveTags(completion: @escaping (Result<[FeedbinTag]?, Error>) -> Void) {
|
||||||
|
|
||||||
let callURL = feedbinBaseURL.appendingPathComponent("tags.json")
|
let callURL = feedbinBaseURL.appendingPathComponent("tags.json")
|
||||||
|
@ -200,24 +200,63 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let parserData = ParserData(url: opmlFile.absoluteString, data: opmlData)
|
os_log(.debug, log: log, "Begin importing OPML...")
|
||||||
var opmlDocument: RSOPMLDocument?
|
|
||||||
|
|
||||||
do {
|
caller.importOPML(opmlData: opmlData) { [weak self] result in
|
||||||
opmlDocument = try RSOPMLParser.parseOPML(with: parserData)
|
switch result {
|
||||||
} catch {
|
case .success(let importResult):
|
||||||
|
if importResult.complete {
|
||||||
|
guard let self = self else { return }
|
||||||
|
os_log(.debug, log: self.log, "Import OPML done.")
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
completion(.success(()))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self?.checkImportResult(opmlImportResultID: importResult.importResultID, completion: completion)
|
||||||
|
}
|
||||||
|
case .failure(let error):
|
||||||
|
guard let self = self else { return }
|
||||||
|
os_log(.debug, log: self.log, "Import OPML failed.")
|
||||||
|
DispatchQueue.main.async {
|
||||||
completion(.failure(error))
|
completion(.failure(error))
|
||||||
return
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let loadDocument = opmlDocument, let children = loadDocument.children else {
|
|
||||||
completion(.success(()))
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
importOPMLItems(account, items: children, parentFolder: nil)
|
private func checkImportResult(opmlImportResultID: Int, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
|
||||||
|
Timer.scheduledTimer(withTimeInterval: 15, repeats: true) { [weak self] timer in
|
||||||
|
|
||||||
|
guard let self = self else { return }
|
||||||
|
|
||||||
|
os_log(.debug, log: self.log, "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.")
|
||||||
|
timer.invalidate()
|
||||||
|
DispatchQueue.main.async {
|
||||||
completion(.success(()))
|
completion(.success(()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case .failure(let error):
|
||||||
|
os_log(.debug, log: self.log, "Import OPML check failed.")
|
||||||
|
timer.invalidate()
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
completion(.failure(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -769,109 +808,6 @@ 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 doesn’t have a name, so it won’t 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 processRestoredFeed(for account: Account, feed: Feed, editedName: String?, folder: Folder?, completion: @escaping (Result<Void, Error>) -> Void) {
|
func processRestoredFeed(for account: Account, feed: Feed, editedName: String?, folder: Folder?, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||||
|
|
||||||
if let folder = folder {
|
if let folder = folder {
|
||||||
|
21
Frameworks/Account/Feedbin/FeedbinImportResult.swift
Normal file
21
Frameworks/Account/Feedbin/FeedbinImportResult.swift
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
//
|
||||||
|
// FeedbinImportResult.swift
|
||||||
|
// Account
|
||||||
|
//
|
||||||
|
// Created by Maurice Parker on 5/17/19.
|
||||||
|
// Copyright © 2019 Ranchero Software, LLC. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct FeedbinImportResult: Codable {
|
||||||
|
|
||||||
|
let importResultID: Int
|
||||||
|
let complete: Bool
|
||||||
|
|
||||||
|
enum CodingKeys: String, CodingKey {
|
||||||
|
case importResultID = "id"
|
||||||
|
case complete
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user