NetNewsWire/Frameworks/Account/FeedWrangler/FeedWranglerAPICaller.swift

276 lines
8.3 KiB
Swift
Raw Normal View History

2019-09-28 06:44:58 +02:00
//
// FeedWranglerAPICaller.swift
// Account
//
// Created by Jonathan Bennett on 2019-08-29.
// Copyright © 2019 Ranchero Software, LLC. All rights reserved.
//
import Foundation
import Foundation
2019-10-17 07:05:18 +02:00
import SyncDatabase
2019-09-28 06:44:58 +02:00
import RSWeb
2019-10-24 17:48:12 +02:00
enum FeedWranglerError : Error {
case general(message: String)
}
2019-09-28 06:44:58 +02:00
final class FeedWranglerAPICaller: NSObject {
private var transport: Transport!
var credentials: Credentials?
weak var accountMetadata: AccountMetadata?
init(transport: Transport) {
super.init()
self.transport = transport
}
2019-11-20 22:33:56 +01:00
func cancelAll() {
transport.cancelAll()
}
2019-09-28 06:44:58 +02:00
func validateCredentials(completion: @escaping (Result<Credentials?, Error>) -> Void) {
let url = FeedWranglerConfig.clientURL.appendingPathComponent("users/authorize")
2019-09-28 06:44:58 +02:00
let username = self.credentials?.username ?? ""
standardSend(url: url, resultType: FeedWranglerAuthorizationResult.self) { result in
2019-09-28 06:44:58 +02:00
switch result {
case .success(let (_, results)):
if let accessToken = results?.accessToken {
let authCredentials = Credentials(type: .feedWranglerToken, username: username, secret: accessToken)
completion(.success(authCredentials))
} else {
2019-09-28 06:44:58 +02:00
completion(.success(nil))
}
case .failure(let error):
completion(.failure(error))
}
}
}
func retrieveSubscriptions(completion: @escaping (Result<[FeedWranglerSubscription], Error>) -> Void) {
let url = FeedWranglerConfig.clientURL.appendingPathComponent("subscriptions/list")
standardSend(url: url, resultType: FeedWranglerSubscriptionsRequest.self) { result in
switch result {
case .success(let (_, results)):
completion(.success(results?.feeds ?? []))
case .failure(let error):
completion(.failure(error))
}
}
}
func addSubscription(url: String, completion: @escaping (Result<FeedWranglerSubscription, Error>) -> Void) {
let url = FeedWranglerConfig
2019-10-24 17:48:12 +02:00
.clientURL
.appendingPathComponent("subscriptions/add_feed_and_wait")
.appendingQueryItems([
URLQueryItem(name: "feed_url", value: url),
URLQueryItem(name: "choose_first", value: "true")
])
standardSend(url: url, resultType: FeedWranglerSubscriptionResult.self) { result in
2019-10-24 17:48:12 +02:00
switch result {
case .success(let (_, results)):
if let results = results {
if let error = results.error {
completion(.failure(FeedWranglerError.general(message: error)))
} else {
completion(.success(results.feed))
}
2019-10-24 17:48:12 +02:00
} else {
completion(.failure(FeedWranglerError.general(message: "No feed found")))
2019-10-24 17:48:12 +02:00
}
2019-10-24 17:48:12 +02:00
case .failure(let error):
completion(.failure(error))
}
}
}
2019-10-16 17:32:22 +02:00
func renameSubscription(feedID: String, newName: String, completion: @escaping (Result<Void, Error>) -> Void) {
2019-10-17 19:59:43 +02:00
let url = FeedWranglerConfig.clientURL
.appendingPathComponent("subscriptions/rename_feed")
.appendingQueryItems([
URLQueryItem(name: "feed_id", value: feedID),
URLQueryItem(name: "feed_name", value: newName),
])
standardSend(url: url, resultType: FeedWranglerSubscriptionsRequest.self) { result in
2019-10-16 17:32:22 +02:00
switch result {
case .success:
completion(.success(()))
case .failure(let error):
completion(.failure(error))
}
}
}
2019-10-16 17:43:49 +02:00
func removeSubscription(feedID: String, completion: @escaping (Result<Void, Error>) -> Void) {
2019-10-17 19:59:43 +02:00
let url = FeedWranglerConfig.clientURL
.appendingPathComponent("subscriptions/remove_feed")
.appendingQueryItem(URLQueryItem(name: "feed_id", value: feedID))
standardSend(url: url, resultType: FeedWranglerGenericResult.self) { result in
2019-10-16 17:43:49 +02:00
switch result {
case .success:
completion(.success(()))
case .failure(let error):
completion(.failure(error))
}
}
}
2019-10-17 07:05:18 +02:00
// MARK: FeedItems
2019-11-20 12:13:30 +01:00
func retrieveEntries(articleIDs: [String], completion: @escaping (Result<[FeedWranglerFeedItem], Error>) -> Void) {
let IDs = articleIDs.joined(separator: ",")
let url = FeedWranglerConfig.clientURL
.appendingPathComponent("feed_items/get")
.appendingQueryItem(URLQueryItem(name: "feed_item_ids", value: IDs))
standardSend(url: url, resultType: FeedWranglerFeedItemsRequest.self) { result in
2019-11-20 12:13:30 +01:00
switch result {
case .success(let (_, results)):
completion(.success(results?.feedItems ?? []))
case .failure(let error):
completion(.failure(error))
}
}
}
2019-10-17 07:05:18 +02:00
func retrieveFeedItems(page: Int = 0, completion: @escaping (Result<[FeedWranglerFeedItem], Error>) -> Void) {
// todo: handle initial sync better
2019-10-17 19:59:43 +02:00
let url = FeedWranglerConfig.clientURL
.appendingPathComponent("feed_items/list")
.appendingQueryItems([
URLQueryItem(name: "read", value: "false"),
URLQueryItem(name: "offset", value: String(page * FeedWranglerConfig.pageSize)),
])
2019-10-17 07:05:18 +02:00
standardSend(url: url, resultType: FeedWranglerFeedItemsRequest.self) { result in
2019-10-17 07:05:18 +02:00
switch result {
case .success(let (_, results)):
completion(.success(results?.feedItems ?? []))
case .failure(let error):
completion(.failure(error))
}
}
}
func retrieveUnreadFeedItems(page: Int = 0, completion: @escaping (Result<[FeedWranglerFeedItem], Error>) -> Void) {
2019-10-17 19:59:43 +02:00
let url = FeedWranglerConfig.clientURL
.appendingPathComponent("feed_items/list")
.appendingQueryItems([
URLQueryItem(name: "read", value: "false"),
URLQueryItem(name: "offset", value: String(page * FeedWranglerConfig.pageSize)),
])
standardSend(url: url, resultType: FeedWranglerFeedItemsRequest.self) { result in
2019-10-17 07:05:18 +02:00
switch result {
case .success(let (_, results)):
completion(.success(results?.feedItems ?? []))
case .failure(let error):
completion(.failure(error))
}
}
}
func retrieveAllUnreadFeedItems(foundItems: [FeedWranglerFeedItem] = [], page: Int = 0, completion: @escaping (Result<[FeedWranglerFeedItem], Error>) -> Void) {
retrieveUnreadFeedItems(page: page) { result in
switch result {
case .success(let newItems):
if newItems.count > 0 {
self.retrieveAllUnreadFeedItems(foundItems: foundItems + newItems, page: (page + 1), completion: completion)
} else {
completion(.success(foundItems + newItems))
}
case .failure(let error):
completion(.failure(error))
}
}
}
func retrieveStarredFeedItems(page: Int = 0, completion: @escaping (Result<[FeedWranglerFeedItem], Error>) -> Void) {
2019-10-17 19:59:43 +02:00
let url = FeedWranglerConfig.clientURL
.appendingPathComponent("feed_items/list")
.appendingQueryItems([
2019-10-17 07:05:18 +02:00
URLQueryItem(name: "starred", value: "true"),
URLQueryItem(name: "offset", value: String(page * FeedWranglerConfig.pageSize)),
2019-10-17 19:59:43 +02:00
])
standardSend(url: url, resultType: FeedWranglerFeedItemsRequest.self) { result in
2019-10-17 19:59:43 +02:00
switch result {
case .success(let (_, results)):
completion(.success(results?.feedItems ?? []))
2019-10-17 07:05:18 +02:00
2019-10-17 19:59:43 +02:00
case .failure(let error):
completion(.failure(error))
2019-10-17 07:05:18 +02:00
}
}
2019-10-17 19:59:43 +02:00
}
2019-10-17 07:05:18 +02:00
func retrieveAllStarredFeedItems(foundItems: [FeedWranglerFeedItem] = [], page: Int = 0, completion: @escaping (Result<[FeedWranglerFeedItem], Error>) -> Void) {
retrieveStarredFeedItems(page: page) { result in
switch result {
case .success(let newItems):
if newItems.count > 0 {
self.retrieveAllStarredFeedItems(foundItems: foundItems + newItems, page: (page + 1), completion: completion)
} else {
completion(.success(foundItems + newItems))
}
case .failure(let error):
completion(.failure(error))
}
}
}
func updateArticleStatus(_ articleID: String, _ statuses: [SyncStatus], completion: @escaping () -> Void) {
2019-10-17 19:59:43 +02:00
2019-10-17 07:05:18 +02:00
var queryItems = statuses.compactMap { status -> URLQueryItem? in
switch status.key {
case .read:
return URLQueryItem(name: "read", value: status.flag.description)
case .starred:
return URLQueryItem(name: "starred", value: status.flag.description)
case .userDeleted:
return nil
}
}
queryItems.append(URLQueryItem(name: "feed_item_id", value: articleID))
2019-10-17 19:59:43 +02:00
let url = FeedWranglerConfig.clientURL
.appendingPathComponent("feed_items/update")
.appendingQueryItems(queryItems)
2019-10-17 07:05:18 +02:00
standardSend(url: url, resultType: FeedWranglerGenericResult.self) { result in
2019-10-17 07:05:18 +02:00
completion()
}
}
private func standardSend<R: Decodable>(url: URL?, resultType: R.Type, completion: @escaping (Result<(HTTPURLResponse, R?), Error>) -> Void) {
guard let callURL = url else {
completion(.failure(TransportError.noURL))
2019-10-17 07:05:18 +02:00
return
}
2019-10-17 19:59:43 +02:00
let request = URLRequest(url: callURL, credentials: credentials)
2019-10-17 07:05:18 +02:00
transport.send(request: request, resultType: resultType, completion: completion)
2019-10-17 07:05:18 +02:00
}
2019-09-28 06:44:58 +02:00
}