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 {
|
||||
case .success:
|
||||
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) {
|
||||
completion(.success(()))
|
||||
}
|
||||
|
@ -17,6 +17,7 @@
|
||||
5133231122810EB200C30F19 /* FeedbinIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5133230F22810E5700C30F19 /* FeedbinIcon.swift */; };
|
||||
5144EA49227B497600D19003 /* FeedbinAPICaller.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA48227B497600D19003 /* FeedbinAPICaller.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 */; };
|
||||
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 */; };
|
||||
@ -116,6 +117,7 @@
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@ -255,6 +257,7 @@
|
||||
51E490352288C37100C791F0 /* FeedbinDate.swift */,
|
||||
84CAD7151FDF2E22000F0755 /* FeedbinEntry.swift */,
|
||||
5133230F22810E5700C30F19 /* FeedbinIcon.swift */,
|
||||
5154367A228EEB28005E1CDF /* FeedbinImportResult.swift */,
|
||||
51E5959A228C781500FCC42B /* FeedbinStarredEntry.swift */,
|
||||
84245C841FDDD8CB0074AFBB /* FeedbinSubscription.swift */,
|
||||
51D58754227F53BE00900287 /* FeedbinTag.swift */,
|
||||
@ -521,6 +524,7 @@
|
||||
51E490362288C37100C791F0 /* FeedbinDate.swift in Sources */,
|
||||
5165D72922835F7A00D9D53D /* FeedSpecifier.swift in Sources */,
|
||||
844B297D2106C7EC004020B3 /* Feed.swift in Sources */,
|
||||
5154367B228EEB28005E1CDF /* FeedbinImportResult.swift in Sources */,
|
||||
84B2D4D02238CD8A00498ADA /* FeedMetadata.swift in Sources */,
|
||||
5144EA49227B497600D19003 /* FeedbinAPICaller.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) {
|
||||
|
||||
let callURL = feedbinBaseURL.appendingPathComponent("tags.json")
|
||||
|
@ -185,7 +185,7 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
||||
}
|
||||
|
||||
func importOPML(for account:Account, opmlFile: URL, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
|
||||
|
||||
var fileData: Data?
|
||||
|
||||
do {
|
||||
@ -200,27 +200,66 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
||||
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)
|
||||
os_log(.debug, log: log, "Begin importing OPML...")
|
||||
|
||||
completion(.success(()))
|
||||
caller.importOPML(opmlData: opmlData) { [weak self] result in
|
||||
switch result {
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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(()))
|
||||
}
|
||||
}
|
||||
case .failure(let error):
|
||||
os_log(.debug, log: self.log, "Import OPML check failed.")
|
||||
timer.invalidate()
|
||||
DispatchQueue.main.async {
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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
|
||||
@ -768,109 +807,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) {
|
||||
|
||||
|
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