diff --git a/Frameworks/Account/GoogleReaderCompatible/GoogleReaderCompatibleAPICaller.swift b/Frameworks/Account/GoogleReaderCompatible/GoogleReaderCompatibleAPICaller.swift index 6b914266d..5025f315d 100644 --- a/Frameworks/Account/GoogleReaderCompatible/GoogleReaderCompatibleAPICaller.swift +++ b/Frameworks/Account/GoogleReaderCompatible/GoogleReaderCompatibleAPICaller.swift @@ -49,10 +49,11 @@ final class GoogleReaderCompatibleAPICaller: NSObject { case editTag = "/reader/api/0/edit-tag" } - // private let GoogleReaderCompatibleBaseURL = URL(string: "https://api.GoogleReaderCompatible.com/v2/")! private var transport: Transport! var credentials: Credentials? + private var accessToken: String? + weak var accountMetadata: AccountMetadata? var server: String? { @@ -127,6 +128,13 @@ final class GoogleReaderCompatibleAPICaller: NSObject { } func requestAuthorizationToken(endpoint: URL, completion: @escaping (Result) -> Void) { + // If we have a token already, use it + if let accessToken = accessToken { + completion(.success(accessToken)) + return + } + + // Otherwise request one. guard let credentials = credentials else { completion(.failure(CredentialsError.incompleteCredentials)) return @@ -143,67 +151,19 @@ final class GoogleReaderCompatibleAPICaller: NSObject { } // Convert the return data to UTF8 and then parse out the Auth token - guard let rawData = String(data: resultData, encoding: .utf8) else { + guard let accessToken = String(data: resultData, encoding: .utf8) else { completion(.failure(TransportError.noData)) break } - - completion(.success(rawData)) + self.accessToken = accessToken + completion(.success(accessToken)) case .failure(let error): completion(.failure(error)) } } } - func importOPML(opmlData: Data, completion: @escaping (Result) -> Void) { - -// let callURL = GoogleReaderCompatibleBaseURL.appendingPathComponent("imports.json") -// var request = URLRequest(url: callURL, credentials: credentials) -// request.addValue("text/xml; charset=utf-8", forHTTPHeaderField: HTTPRequestHeader.contentType) -// -// 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(GoogleReaderCompatibleImportResult.self, from: resultData) -// completion(.success(result)) -// } catch { -// completion(.failure(error)) -// } -// -// case .failure(let error): -// completion(.failure(error)) -// } -// -// } - - } - - func retrieveOPMLImportResult(importID: Int, completion: @escaping (Result) -> Void) { - -// let callURL = GoogleReaderCompatibleBaseURL.appendingPathComponent("imports/\(importID).json") -// let request = URLRequest(url: callURL, credentials: credentials) -// -// transport.send(request: request, resultType: GoogleReaderCompatibleImportResult.self) { result in -// -// switch result { -// case .success(let (_, importResult)): -// completion(.success(importResult)) -// case .failure(let error): -// completion(.failure(error)) -// } -// -// } - - } func retrieveTags(completion: @escaping (Result<[GoogleReaderCompatibleTag]?, Error>) -> Void) { guard let baseURL = APIBaseURL else { diff --git a/Frameworks/Account/GoogleReaderCompatible/GoogleReaderCompatibleAccountDelegate.swift b/Frameworks/Account/GoogleReaderCompatible/GoogleReaderCompatibleAccountDelegate.swift index 4c83ba17c..3fa28faaf 100644 --- a/Frameworks/Account/GoogleReaderCompatible/GoogleReaderCompatibleAccountDelegate.swift +++ b/Frameworks/Account/GoogleReaderCompatible/GoogleReaderCompatibleAccountDelegate.swift @@ -210,31 +210,113 @@ final class GoogleReaderCompatibleAccountDelegate: AccountDelegate { return } - os_log(.debug, log: log, "Begin importing OPML...") - opmlImportInProgress = true + let parserData = ParserData(url: opmlFile.absoluteString, data: opmlData) + var opmlDocument: RSOPMLDocument? - caller.importOPML(opmlData: opmlData) { result in - switch result { - case .success(let importResult): - if importResult.complete { - os_log(.debug, log: self.log, "Import OPML done.") - self.opmlImportInProgress = false - DispatchQueue.main.async { - completion(.success(())) - } - } else { - self.checkImportResult(opmlImportResultID: importResult.importResultID, completion: completion) + 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) } - case .failure(let error): - os_log(.debug, log: self.log, "Import OPML failed.") - self.opmlImportInProgress = false - DispatchQueue.main.async { - let wrappedError = AccountError.wrappedError(error: error, account: account) - completion(.failure(wrappedError)) + 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) { @@ -498,41 +580,6 @@ private extension GoogleReaderCompatibleAccountDelegate { } - func checkImportResult(opmlImportResultID: Int, completion: @escaping (Result) -> Void) { - - DispatchQueue.main.async { - - Timer.scheduledTimer(withTimeInterval: 15, repeats: true) { timer in - - 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() - self.opmlImportInProgress = false - DispatchQueue.main.async { - completion(.success(())) - } - } - case .failure(let error): - os_log(.debug, log: self.log, "Import OPML check failed.") - timer.invalidate() - self.opmlImportInProgress = false - DispatchQueue.main.async { - completion(.failure(error)) - } - } - } - - } - - } - - } - func syncFolders(_ account: Account, _ tags: [GoogleReaderCompatibleTag]?) { guard let tags = tags else { return }