Enable adding accounts for the Reader API services.

This commit is contained in:
Maurice Parker
2020-10-24 20:42:34 -05:00
parent 1ab5323a43
commit ecbd7d2f55
6 changed files with 85 additions and 61 deletions

View File

@@ -369,12 +369,12 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
switch type {
case .feedbin:
FeedbinAccountDelegate.validateCredentials(transport: transport, credentials: credentials, completion: completion)
case .freshRSS:
ReaderAPIAccountDelegate.validateCredentials(transport: transport, credentials: credentials, endpoint: endpoint, completion: completion)
case .feedWrangler:
FeedWranglerAccountDelegate.validateCredentials(transport: transport, credentials: credentials, completion: completion)
case .newsBlur:
NewsBlurAccountDelegate.validateCredentials(transport: transport, credentials: credentials, completion: completion)
case .freshRSS, .inoreader, .bazQux, .theOldReader:
ReaderAPIAccountDelegate.validateCredentials(transport: transport, credentials: credentials, endpoint: endpoint, completion: completion)
default:
break
}

View File

@@ -21,11 +21,7 @@ public enum ReaderAPIAccountDelegateError: String, Error {
final class ReaderAPIAccountDelegate: AccountDelegate {
private var variant: ReaderAPIVariant {
didSet {
caller.variant = variant
}
}
private let variant: ReaderAPIVariant
private let database: SyncDatabase
@@ -61,11 +57,8 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
database = SyncDatabase(databaseFilePath: databaseFilePath)
if transport != nil {
caller = ReaderAPICaller(transport: transport!)
} else {
let sessionConfiguration = URLSessionConfiguration.default
sessionConfiguration.requestCachePolicy = .reloadIgnoringLocalCacheData
sessionConfiguration.timeoutIntervalForRequest = 60.0
@@ -80,9 +73,9 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
}
caller = ReaderAPICaller(transport: URLSession(configuration: sessionConfiguration))
}
caller.variant = variant
self.variant = variant
}
@@ -1022,7 +1015,7 @@ private extension ReaderAPIAccountDelegate {
}
func syncArticleReadState(account: Account, articleIDs: [Int]?) {
func syncArticleReadState(account: Account, articleIDs: [String]?) {
guard let articleIDs = articleIDs else {
return
}
@@ -1030,9 +1023,7 @@ private extension ReaderAPIAccountDelegate {
database.selectPendingReadStatusArticleIDs() { result in
func process(_ pendingArticleIDs: Set<String>) {
let readerUnreadArticleIDs = Set(articleIDs.map { String($0) } )
let updatableReaderUnreadArticleIDs = readerUnreadArticleIDs.subtracting(pendingArticleIDs)
let updatableReaderUnreadArticleIDs = Set(articleIDs).subtracting(pendingArticleIDs)
account.fetchUnreadArticleIDs { articleIDsResult in
guard let currentUnreadArticleIDs = try? articleIDsResult.get() else {
@@ -1047,7 +1038,6 @@ private extension ReaderAPIAccountDelegate {
let deltaReadArticleIDs = currentUnreadArticleIDs.subtracting(updatableReaderUnreadArticleIDs)
account.markAsRead(deltaReadArticleIDs)
}
}
switch result {
@@ -1061,7 +1051,7 @@ private extension ReaderAPIAccountDelegate {
}
func syncArticleStarredState(account: Account, articleIDs: [Int]?) {
func syncArticleStarredState(account: Account, articleIDs: [String]?) {
guard let articleIDs = articleIDs else {
return
}
@@ -1069,9 +1059,7 @@ private extension ReaderAPIAccountDelegate {
database.selectPendingStarredStatusArticleIDs() { result in
func process(_ pendingArticleIDs: Set<String>) {
let readerStarredArticleIDs = Set(articleIDs.map { String($0) } )
let updatableReaderUnreadArticleIDs = readerStarredArticleIDs.subtracting(pendingArticleIDs)
let updatableReaderUnreadArticleIDs = Set(articleIDs).subtracting(pendingArticleIDs)
account.fetchStarredArticleIDs { articleIDsResult in
guard let currentStarredArticleIDs = try? articleIDsResult.get() else {
@@ -1086,7 +1074,6 @@ private extension ReaderAPIAccountDelegate {
let deltaUnstarredArticleIDs = currentStarredArticleIDs.subtracting(updatableReaderUnreadArticleIDs)
account.markAsUnstarred(deltaUnstarredArticleIDs)
}
}
switch result {

View File

@@ -64,17 +64,13 @@ final class ReaderAPICaller: NSObject {
private var APIBaseURL: URL? {
get {
switch variant {
case .inoreader:
return URL(string: "https://www.inoreader.com")
case .bazQux:
return URL(string: "https://bazqux.com")
case .theOldReader:
return URL(string: "https://theoldreader.com")
case .generic:
guard let accountMetadata = accountMetadata else {
return nil
}
return accountMetadata.endpointURL
default:
return URL(string: variant.host)
}
}
}
@@ -685,9 +681,13 @@ final class ReaderAPICaller: NSObject {
// Get ids from above into hex representation of value
let idsToFetch = entries.itemRefs.map({ (reference) -> String in
let idValue = Int(reference.itemId)!
let idHexString = String(idValue, radix: 16, uppercase: false)
return "i=\(idHexString)"
if self.variant == .theOldReader {
return "i=\(reference.itemId)"
} else {
let idValue = Int(reference.itemId)!
let idHexString = String(idValue, radix: 16, uppercase: false)
return "i=\(idHexString)"
}
}).joined(separator:"&")
let postData = "T=\(token)&output=json&\(idsToFetch)".data(using: String.Encoding.utf8)
@@ -749,7 +749,7 @@ final class ReaderAPICaller: NSObject {
}
func retrieveUnreadEntries(completion: @escaping (Result<[Int]?, Error>) -> Void) {
func retrieveUnreadEntries(completion: @escaping (Result<[String]?, Error>) -> Void) {
guard let baseURL = APIBaseURL else {
completion(.failure(CredentialsError.incompleteCredentials))
@@ -783,7 +783,7 @@ final class ReaderAPICaller: NSObject {
return
}
let itemIds = itemRefs.map{ Int($0.itemId)! }
let itemIds = itemRefs.map { $0.itemId }
self.storeConditionalGet(key: ConditionalGetKeys.unreadEntries, headers: response.allHeaderFields)
completion(.success(itemIds))
@@ -853,7 +853,7 @@ final class ReaderAPICaller: NSObject {
updateStateToEntries(entries: entries, state: .starred, add: false, completion: completion)
}
func retrieveStarredEntries(completion: @escaping (Result<[Int]?, Error>) -> Void) {
func retrieveStarredEntries(completion: @escaping (Result<[String]?, Error>) -> Void) {
guard let baseURL = APIBaseURL else {
completion(.failure(CredentialsError.incompleteCredentials))
return
@@ -879,14 +879,12 @@ final class ReaderAPICaller: NSObject {
switch result {
case .success(let (response, unreadEntries)):
guard let itemRefs = unreadEntries?.itemRefs else {
completion(.success([]))
return
}
let itemIds = itemRefs.map{ Int($0.itemId)! }
let itemIds = itemRefs.map { $0.itemId }
self.storeConditionalGet(key: ConditionalGetKeys.starredEntries, headers: response.allHeaderFields)
completion(.success(itemIds))
case .failure(let error):

View File

@@ -7,9 +7,23 @@
import Foundation
enum ReaderAPIVariant {
public enum ReaderAPIVariant {
case generic
case inoreader
case bazQux
case theOldReader
public var host: String {
switch self {
case .inoreader:
return "https://www.inoreader.com"
case .bazQux:
return "https://bazqux.com"
case .theOldReader:
return "https://theoldreader.com"
default:
return ""
}
}
}

View File

@@ -1,15 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="17505" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="17505"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="AccountsReaderAPIWindowController" customModule="NetNewsWire" customModuleProvider="target">
<connections>
<outlet property="actionButton" destination="9mz-D9-krh" id="ozu-6Q-9Lb"/>
<outlet property="apiURLLabel" destination="H6f-t4-SMg" id="1ac-qe-jw2"/>
<outlet property="apiURLTextField" destination="d7d-ZV-CcZ" id="Af4-uM-Dgd"/>
<outlet property="errorMessageLabel" destination="byK-Sd-r7F" id="8zt-9d-dWQ"/>
<outlet property="gridView" destination="zBB-JH-huI" id="ghz-ki-b0V"/>
<outlet property="passwordTextField" destination="JSa-LY-zNQ" id="E9W-0F-69m"/>
<outlet property="progressIndicator" destination="B0W-bh-Evv" id="Tiq-gx-s3F"/>
<outlet property="titleImageView" destination="Ssh-Dh-xbg" id="8Iy-jf-EYR"/>
@@ -24,13 +27,13 @@
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="196" y="240" width="433" height="249"/>
<rect key="screenRect" x="0.0" y="0.0" width="1680" height="1027"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1415"/>
<view key="contentView" misplaced="YES" id="se5-gp-TjO">
<rect key="frame" x="0.0" y="0.0" width="433" height="249"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<stackView distribution="fill" orientation="horizontal" alignment="bottom" spacing="19" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="7Ht-Fn-0Ya">
<rect key="frame" x="113" y="224" width="207" height="39"/>
<rect key="frame" x="113" y="219" width="208" height="38"/>
<subviews>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="Ssh-Dh-xbg">
<rect key="frame" x="0.0" y="0.0" width="36" height="36"/>
@@ -41,7 +44,7 @@
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" id="y38-YL-woC"/>
</imageView>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="lti-yM-8LV">
<rect key="frame" x="53" y="0.0" width="156" height="39"/>
<rect key="frame" x="53" y="0.0" width="157" height="38"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="Reader API" id="ras-dj-nP8">
<font key="font" metaFont="system" size="32"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@@ -59,7 +62,7 @@
</customSpacing>
</stackView>
<gridView xPlacement="trailing" yPlacement="center" rowAlignment="none" rowSpacing="12" columnSpacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="zBB-JH-huI">
<rect key="frame" x="82" y="61" width="270" height="131"/>
<rect key="frame" x="82" y="60" width="270" height="127"/>
<constraints>
<constraint firstItem="byK-Sd-r7F" firstAttribute="width" secondItem="d7d-ZV-CcZ" secondAttribute="width" id="PDt-cC-RvD"/>
</constraints>
@@ -76,7 +79,7 @@
<gridCells>
<gridCell row="DRl-lC-vUc" column="fCQ-jY-Mts" id="4DI-01-jGD">
<textField key="contentView" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Zy6-9c-8TI">
<rect key="frame" x="23" y="112" width="41" height="17"/>
<rect key="frame" x="23" y="109" width="41" height="16"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="Login:" id="DqN-SV-v35">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@@ -86,7 +89,7 @@
</gridCell>
<gridCell row="DRl-lC-vUc" column="7CY-bX-6x4" id="Z0b-qS-MUJ">
<textField key="contentView" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="78p-Cf-f55">
<rect key="frame" x="70" y="109" width="200" height="22"/>
<rect key="frame" x="70" y="106" width="200" height="21"/>
<constraints>
<constraint firstAttribute="width" constant="200" id="Qin-jm-4zt"/>
</constraints>
@@ -99,7 +102,7 @@
</gridCell>
<gridCell row="eW8-uH-txq" column="fCQ-jY-Mts" id="Hqa-3w-cQv">
<textField key="contentView" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="wEx-TM-rPM">
<rect key="frame" x="-2" y="78" width="66" height="17"/>
<rect key="frame" x="-2" y="76" width="66" height="16"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="Password:" id="7g8-Kk-ISg">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@@ -109,7 +112,7 @@
</gridCell>
<gridCell row="eW8-uH-txq" column="7CY-bX-6x4" id="m16-3v-9pf">
<secureTextField key="contentView" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="JSa-LY-zNQ">
<rect key="frame" x="70" y="75" width="200" height="22"/>
<rect key="frame" x="70" y="73" width="200" height="21"/>
<constraints>
<constraint firstAttribute="width" constant="200" id="eal-wa-1nU"/>
</constraints>
@@ -125,7 +128,7 @@
</gridCell>
<gridCell row="ebD-On-mOK" column="fCQ-jY-Mts" id="xwR-xz-N6h">
<textField key="contentView" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="H6f-t4-SMg">
<rect key="frame" x="7" y="44" width="57" height="17"/>
<rect key="frame" x="7" y="43" width="57" height="16"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="API URL:" id="zBm-dZ-EF1">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@@ -135,7 +138,7 @@
</gridCell>
<gridCell row="ebD-On-mOK" column="7CY-bX-6x4" id="Wd5-Zp-t61">
<textField key="contentView" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="d7d-ZV-CcZ" userLabel="API URL Text Field">
<rect key="frame" x="70" y="41" width="200" height="22"/>
<rect key="frame" x="70" y="40" width="200" height="21"/>
<constraints>
<constraint firstAttribute="width" constant="200" id="Mki-bb-tDu"/>
</constraints>
@@ -149,7 +152,7 @@
<gridCell row="DbI-7g-Xme" column="fCQ-jY-Mts" xPlacement="leading" id="xX0-vn-AId"/>
<gridCell row="DbI-7g-Xme" column="7CY-bX-6x4" yPlacement="top" id="hk5-St-E4y">
<textField key="contentView" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="byK-Sd-r7F">
<rect key="frame" x="68" y="12" width="204" height="17"/>
<rect key="frame" x="68" y="12" width="204" height="16"/>
<textFieldCell key="cell" id="0yh-Ab-UTX">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@@ -160,7 +163,7 @@
</gridCells>
</gridView>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="9mz-D9-krh">
<rect key="frame" x="340" y="13" width="79" height="32"/>
<rect key="frame" x="347" y="13" width="73" height="32"/>
<buttonCell key="cell" type="push" title="Action" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="IMO-YT-k9Z">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
@@ -173,7 +176,7 @@ DQ
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="XAM-Hb-0Hw">
<rect key="frame" x="258" y="13" width="82" height="32"/>
<rect key="frame" x="273" y="13" width="76" height="32"/>
<buttonCell key="cell" type="push" title="Cancel" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="ufs-ar-BAY">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
@@ -186,7 +189,7 @@ Gw
</connections>
</button>
<progressIndicator hidden="YES" wantsLayer="YES" horizontalHuggingPriority="750" verticalHuggingPriority="750" maxValue="100" displayedWhenStopped="NO" bezeled="NO" indeterminate="YES" controlSize="small" style="spinning" translatesAutoresizingMaskIntoConstraints="NO" id="B0W-bh-Evv">
<rect key="frame" x="209" y="200" width="16" height="16"/>
<rect key="frame" x="209" y="195" width="16" height="16"/>
</progressIndicator>
</subviews>
<constraints>

View File

@@ -16,6 +16,7 @@ class AccountsReaderAPIWindowController: NSWindowController {
@IBOutlet weak var titleImageView: NSImageView!
@IBOutlet weak var titleLabel: NSTextField!
@IBOutlet weak var gridView: NSGridView!
@IBOutlet weak var progressIndicator: NSProgressIndicator!
@IBOutlet weak var usernameTextField: NSTextField!
@IBOutlet weak var apiURLTextField: NSTextField!
@@ -41,12 +42,15 @@ class AccountsReaderAPIWindowController: NSWindowController {
case .inoreader:
titleImageView.image = AppAssets.accountInoreader
titleLabel.stringValue = NSLocalizedString("InoReader", comment: "InoReader")
gridView.row(at: 2).isHidden = true
case .bazQux:
titleImageView.image = AppAssets.accountBazQux
titleLabel.stringValue = NSLocalizedString("BazQux", comment: "BazQux")
gridView.row(at: 2).isHidden = true
case .theOldReader:
titleImageView.image = AppAssets.accountTheOldReader
titleLabel.stringValue = NSLocalizedString("The Old Reader", comment: "The Old Reader")
gridView.row(at: 2).isHidden = true
default:
break
}
@@ -75,25 +79,43 @@ class AccountsReaderAPIWindowController: NSWindowController {
}
@IBAction func action(_ sender: Any) {
self.errorMessageLabel.stringValue = ""
guard !usernameTextField.stringValue.isEmpty && !passwordTextField.stringValue.isEmpty && !apiURLTextField.stringValue.isEmpty else {
guard !usernameTextField.stringValue.isEmpty && !passwordTextField.stringValue.isEmpty else {
self.errorMessageLabel.stringValue = NSLocalizedString("Username, password & API URL are required.", comment: "Credentials Error")
return
}
guard let accountType = accountType, !(accountType == .freshRSS && apiURLTextField.stringValue.isEmpty) else {
self.errorMessageLabel.stringValue = NSLocalizedString("Username, password & API URL are required.", comment: "Credentials Error")
return
}
let apiURL: URL
switch accountType {
case .freshRSS:
guard let inputURL = URL(string: apiURLTextField.stringValue) else {
self.errorMessageLabel.stringValue = NSLocalizedString("Invalid API URL.", comment: "Invalid API URL")
return
}
apiURL = inputURL
case .inoreader:
apiURL = URL(string: ReaderAPIVariant.inoreader.host)!
case .bazQux:
apiURL = URL(string: ReaderAPIVariant.bazQux.host)!
case .theOldReader:
apiURL = URL(string: ReaderAPIVariant.theOldReader.host)!
default:
self.errorMessageLabel.stringValue = NSLocalizedString("Unrecognized account type.", comment: "Bad account type")
return
}
actionButton.isEnabled = false
progressIndicator.isHidden = false
progressIndicator.startAnimation(self)
guard let apiURL = URL(string: apiURLTextField.stringValue) else {
self.errorMessageLabel.stringValue = NSLocalizedString("Invalid API URL.", comment: "Credentials Error")
return
}
let credentials = Credentials(type: .readerBasic, username: usernameTextField.stringValue, secret: passwordTextField.stringValue)
Account.validateCredentials(type: accountType!, credentials: credentials, endpoint: apiURL) { [weak self] result in
Account.validateCredentials(type: accountType, credentials: credentials, endpoint: apiURL) { [weak self] result in
guard let self = self else { return }