diff --git a/Frameworks/Account/Account.swift b/Frameworks/Account/Account.swift index 853d1be0e..0c8a19279 100644 --- a/Frameworks/Account/Account.swift +++ b/Frameworks/Account/Account.swift @@ -270,19 +270,19 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, self.username = nil } - public static func validateCredentials(transport: Transport = URLSession.webserviceTransport(), type: AccountType, credentials: Credentials, completionHandler handler: @escaping (Result) -> Void) { + public static func validateCredentials(transport: Transport = URLSession.webserviceTransport(), type: AccountType, credentials: Credentials, completion: @escaping (Result) -> Void) { switch type { case .onMyMac: - LocalAccountDelegate.validateCredentials(transport: transport, credentials: credentials, completionHandler: handler) + LocalAccountDelegate.validateCredentials(transport: transport, credentials: credentials, completion: completion) case .feedbin: - FeedbinAccountDelegate.validateCredentials(transport: transport, credentials: credentials, completionHandler: handler) + FeedbinAccountDelegate.validateCredentials(transport: transport, credentials: credentials, completion: completion) default: break } } public func refreshAll(completionHandler completion: (() -> Void)? = nil) { - delegate.refreshAll(for: self, completionHandler: completion) + delegate.refreshAll(for: self, completion: completion) } public func update(_ feed: Feed, with parsedFeed: ParsedFeed, _ completion: @escaping (() -> Void)) { @@ -417,6 +417,10 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, structureDidChange() return true } + + public func renameFolder(_ folder: Folder, to name: String, completion: @escaping (Result) -> Void) { + delegate.renameFolder(folder, to: name, completion: completion) + } public func importOPML(_ opmlDocument: RSOPMLDocument) { diff --git a/Frameworks/Account/Account.xcodeproj/project.pbxproj b/Frameworks/Account/Account.xcodeproj/project.pbxproj index 402342af1..5e39f32d4 100644 --- a/Frameworks/Account/Account.xcodeproj/project.pbxproj +++ b/Frameworks/Account/Account.xcodeproj/project.pbxproj @@ -17,6 +17,7 @@ 51D5875B227F630B00900287 /* tags_add.json in Resources */ = {isa = PBXBuildFile; fileRef = 51D58758227F630B00900287 /* tags_add.json */; }; 51D5875C227F630B00900287 /* tags_initial.json in Resources */ = {isa = PBXBuildFile; fileRef = 51D58759227F630B00900287 /* tags_initial.json */; }; 51D5875E227F643C00900287 /* AccountFolderSyncTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51D5875D227F643C00900287 /* AccountFolderSyncTest.swift */; }; + 51D587642280594700900287 /* FeedbinRenameTag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51D5875F22804FD200900287 /* FeedbinRenameTag.swift */; }; 841973FE1F6DD1BC006346C4 /* RSCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 841973EF1F6DD19E006346C4 /* RSCore.framework */; }; 841973FF1F6DD1C5006346C4 /* RSParser.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 841973FA1F6DD1AC006346C4 /* RSParser.framework */; }; 841974011F6DD1EC006346C4 /* Folder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841974001F6DD1EC006346C4 /* Folder.swift */; }; @@ -102,6 +103,7 @@ 51D58758227F630B00900287 /* tags_add.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = tags_add.json; sourceTree = ""; }; 51D58759227F630B00900287 /* tags_initial.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = tags_initial.json; sourceTree = ""; }; 51D5875D227F643C00900287 /* AccountFolderSyncTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountFolderSyncTest.swift; sourceTree = ""; }; + 51D5875F22804FD200900287 /* FeedbinRenameTag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinRenameTag.swift; sourceTree = ""; }; 841973E81F6DD19E006346C4 /* RSCore.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RSCore.xcodeproj; path = ../RSCore/RSCore.xcodeproj; sourceTree = ""; }; 841973F41F6DD1AC006346C4 /* RSParser.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RSParser.xcodeproj; path = ../RSParser/RSParser.xcodeproj; sourceTree = ""; }; 841974001F6DD1EC006346C4 /* Folder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Folder.swift; sourceTree = ""; }; @@ -212,6 +214,7 @@ 84245C841FDDD8CB0074AFBB /* FeedbinFeed.swift */, 84D0962421741B8500D77525 /* FeedbinSavedSearch.swift */, 51D58754227F53BE00900287 /* FeedbinTag.swift */, + 51D5875F22804FD200900287 /* FeedbinRenameTag.swift */, 84D09622217418DC00D77525 /* FeedbinTagging.swift */, ); path = Feedbin; @@ -246,11 +249,11 @@ 84B2D4CE2238C13D00498ADA /* FeedMetadata.swift */, 841974001F6DD1EC006346C4 /* Folder.swift */, 844B297E210CE37E004020B3 /* UnreadCountProvider.swift */, - 848935031F62484F00CEBD24 /* AccountTests */, - 84245C7D1FDDD2580074AFBB /* Feedbin */, - 8469F80F1F6DC3C10084783E /* Frameworks */, 8419742B1F6DDE84006346C4 /* LocalAccount */, + 84245C7D1FDDD2580074AFBB /* Feedbin */, + 848935031F62484F00CEBD24 /* AccountTests */, 848934F71F62484F00CEBD24 /* Products */, + 8469F80F1F6DC3C10084783E /* Frameworks */, D511EEB4202422BB00712EC3 /* xcconfig */, 848934FA1F62484F00CEBD24 /* Info.plist */, ); @@ -454,6 +457,7 @@ files = ( 84C8B3F41F89DE430053CCA6 /* DataExtensions.swift in Sources */, 84C3654A1F899F3B001EC85C /* CombinedRefreshProgress.swift in Sources */, + 51D587642280594700900287 /* FeedbinRenameTag.swift in Sources */, 8469F81C1F6DD15E0084783E /* Account.swift in Sources */, 84D096212174169100D77525 /* FeedbinArticleIDArray.swift in Sources */, 5144EA4E227B829A00D19003 /* FeedbinAccountDelegate.swift in Sources */, diff --git a/Frameworks/Account/AccountDelegate.swift b/Frameworks/Account/AccountDelegate.swift index 505e2154f..a00c757b6 100644 --- a/Frameworks/Account/AccountDelegate.swift +++ b/Frameworks/Account/AccountDelegate.swift @@ -19,12 +19,13 @@ protocol AccountDelegate { var refreshProgress: DownloadProgress { get } - func refreshAll(for: Account, completionHandler completion: (() -> Void)?) + func refreshAll(for: Account, completion: (() -> Void)?) + func renameFolder(_ folder: Folder, to name: String, completion: @escaping (Result) -> Void) + // Called at the end of account’s init method. - func accountDidInitialize(_ account: Account) - static func validateCredentials(transport: Transport, credentials: Credentials, completionHandler handler: @escaping (Result) -> Void) + static func validateCredentials(transport: Transport, credentials: Credentials, completion: @escaping (Result) -> Void) } diff --git a/Frameworks/Account/AccountTests/AccountFolderSyncTest.swift b/Frameworks/Account/AccountTests/AccountFolderSyncTest.swift index c81c76324..e4d4c6cb8 100644 --- a/Frameworks/Account/AccountTests/AccountFolderSyncTest.swift +++ b/Frameworks/Account/AccountTests/AccountFolderSyncTest.swift @@ -17,7 +17,7 @@ class AccountFolderSyncTest: XCTestCase { override func tearDown() { } - func testFolderSync() { + func testDownloadSync() { let testTransport = TestTransport() testTransport.testFiles["https://api.feedbin.com/v2/tags.json"] = "tags_initial.json" diff --git a/Frameworks/Account/AccountTests/TestTransport.swift b/Frameworks/Account/AccountTests/TestTransport.swift index ebb9f3514..c84655916 100644 --- a/Frameworks/Account/AccountTests/TestTransport.swift +++ b/Frameworks/Account/AccountTests/TestTransport.swift @@ -17,7 +17,7 @@ final class TestTransport: Transport { var testFiles = [String: String]() - func send(request: URLRequest, completion: @escaping (Result<(HTTPHeaders, Data), Error>) -> Void) { + func send(request: URLRequest, completion: @escaping (Result<(HTTPHeaders, Data?), Error>) -> Void) { guard let urlString = request.url?.absoluteString else { completion(.failure(TestTransportError.invalidState)) @@ -31,4 +31,8 @@ final class TestTransport: Transport { } + func send(request: URLRequest, data: Data, completion: @escaping (Result<(HTTPHeaders, Data?), Error>) -> Void) { + + } + } diff --git a/Frameworks/Account/Feed.swift b/Frameworks/Account/Feed.swift index 436e7913a..dd1c963ca 100644 --- a/Frameworks/Account/Feed.swift +++ b/Frameworks/Account/Feed.swift @@ -132,8 +132,9 @@ public final class Feed: DisplayNameProvider, Renamable, UnreadCountProvider, Ha // MARK: - Renamable - public func rename(to newName: String) { + public func rename(to newName: String, completion: @escaping (Result) -> Void) { editedName = newName + completion(.success(())) } // MARK: - UnreadCountProvider diff --git a/Frameworks/Account/Feedbin/FeedbinAPICaller.swift b/Frameworks/Account/Feedbin/FeedbinAPICaller.swift index f4e8e8b52..838734166 100644 --- a/Frameworks/Account/Feedbin/FeedbinAPICaller.swift +++ b/Frameworks/Account/Feedbin/FeedbinAPICaller.swift @@ -47,13 +47,13 @@ final class FeedbinAPICaller: NSObject { } - func retrieveTags(completionHandler completion: @escaping (Result<[FeedbinTag]?, Error>) -> Void) { + func retrieveTags(completionHandler completion: @escaping (Result<[FeedbinTag]?, Error>) -> Void) { let callURL = feedbinBaseURL.appendingPathComponent("tags.json") let conditionalGet = accountMetadata?.conditionalGetInfo[AccountMetadata.ConditionalGetKeys.tags] let request = URLRequest(url: callURL, credentials: credentials, conditionalGet: conditionalGet) - transport.send(request: request, resultType: [FeedbinTag].self) { [weak self] result in + transport.getJSON(request: request, resultType: [FeedbinTag].self) { [weak self] result in switch result { case .success(let (headers, tags)): self?.storeConditionalGet(metadata: self?.accountMetadata, key: AccountMetadata.ConditionalGetKeys.tags, headers: headers) @@ -65,49 +65,12 @@ final class FeedbinAPICaller: NSObject { } } - - func retrieveTaggings(completionHandler completion: @escaping (Result<[FeedbinTagging], Error>) -> Void) { - - let callURL = feedbinBaseURL.appendingPathComponent("taggings.json") - let conditionalGet = accountMetadata?.conditionalGetInfo[AccountMetadata.ConditionalGetKeys.taggings] - let request = URLRequest(url: callURL, credentials: credentials, conditionalGet: conditionalGet) - - transport.send(request: request, resultType: [FeedbinTagging].self) { [weak self] result in - switch result { - case .success(let (headers, taggings)): - - self?.storeConditionalGet(metadata: self?.accountMetadata, key: AccountMetadata.ConditionalGetKeys.taggings, headers: headers) - - // TODO: Add paging code - - case .failure(let error): - completion(.failure(error)) - } - - } - - } - - func retrieveSubscriptions(completionHandler completion: @escaping (Result<[FeedbinFeed], Error>) -> Void) { - - let callURL = feedbinBaseURL.appendingPathComponent("subscriptions.json") - let conditionalGet = accountMetadata?.conditionalGetInfo[AccountMetadata.ConditionalGetKeys.subscriptions] - let request = URLRequest(url: callURL, credentials: credentials, conditionalGet: conditionalGet) - - transport.send(request: request, resultType: [FeedbinFeed].self) { [weak self] result in - switch result { - case .success(let (headers, feeds)): - - self?.storeConditionalGet(metadata: self?.accountMetadata, key: AccountMetadata.ConditionalGetKeys.subscriptions, headers: headers) - // TODO: Add paging code - - case .failure(let error): - completion(.failure(error)) - } - - } - + func renameTag(oldName: String, newName: String, completion: @escaping (Result) -> Void) { + let callURL = feedbinBaseURL.appendingPathComponent("tags.json") + let request = URLRequest(url: callURL, credentials: credentials) + let payload = FeedbinRenameTag(oldName: oldName, newName: newName) + transport.postJSON(request: request, payload: payload, completion: completion) } } diff --git a/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift b/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift index ecf17fcde..39ae79b30 100644 --- a/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift +++ b/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift @@ -38,7 +38,37 @@ final class FeedbinAccountDelegate: AccountDelegate { var refreshProgress = DownloadProgress(numberOfTasks: 0) - static func validateCredentials(transport: Transport, credentials: Credentials, completionHandler completion: @escaping (Result) -> Void) { + func refreshAll(for account: Account, completion: (() -> Void)? = nil) { + refreshAll(account) { [weak self] result in + switch result { + case .success(): + completion?() + case .failure(let error): + self?.handleError(error) + } + } + } + + func renameFolder(_ folder: Folder, to name: String, completion: @escaping (Result) -> Void) { + + caller.renameTag(oldName: folder.name ?? "", newName: name) { result in + switch result { + case .success: + folder.name = name + completion(.success(())) + case .failure(let error): + completion(.failure(error)) + } + } + + } + + func accountDidInitialize(_ account: Account) { + credentials = try? account.retrieveBasicCredentials() + accountMetadata = account.metadata + } + + static func validateCredentials(transport: Transport, credentials: Credentials, completion: @escaping (Result) -> Void) { let caller = FeedbinAPICaller(transport: transport) caller.credentials = credentials @@ -48,35 +78,23 @@ final class FeedbinAccountDelegate: AccountDelegate { } - func refreshAll(for account: Account, completionHandler completion: (() -> Void)? = nil) { - refreshAll(account) { result in - switch result { - case .success(): - completion?() - case .failure(let error): - // TODO: We should do a better job of error handling here. - // We need to prompt for credentials and provide user friendly - // errors. - #if os(macOS) - NSApplication.shared.presentError(error) - #else - UIApplication.shared.presentError(error) - #endif - } - } - } - - func accountDidInitialize(_ account: Account) { - credentials = try? account.retrieveBasicCredentials() - accountMetadata = account.metadata - } - } // MARK: Private private extension FeedbinAccountDelegate { + func handleError(_ error: Error) { + // TODO: We should do a better job of error handling here. + // We need to prompt for credentials and provide user friendly + // errors. + #if os(macOS) + NSApplication.shared.presentError(error) + #else + UIApplication.shared.presentError(error) + #endif + } + func refreshAll(_ account: Account, completion: @escaping (Result) -> Void) { caller.retrieveTags { [weak self] result in diff --git a/Frameworks/Account/Feedbin/FeedbinRenameTag.swift b/Frameworks/Account/Feedbin/FeedbinRenameTag.swift new file mode 100644 index 000000000..49768c096 --- /dev/null +++ b/Frameworks/Account/Feedbin/FeedbinRenameTag.swift @@ -0,0 +1,21 @@ +// +// FeedbinRenameTag.swift +// Account +// +// Created by Maurice Parker on 5/6/19. +// Copyright © 2019 Ranchero Software, LLC. All rights reserved. +// + +import Foundation + +struct FeedbinRenameTag: Codable { + + let oldName: String + let newName: String + + enum CodingKeys: String, CodingKey { + case oldName = "old_name" + case newName = "new_name" + } + +} diff --git a/Frameworks/Account/Folder.swift b/Frameworks/Account/Folder.swift index b2ac8157c..bcb912d51 100644 --- a/Frameworks/Account/Folder.swift +++ b/Frameworks/Account/Folder.swift @@ -44,8 +44,9 @@ public final class Folder: DisplayNameProvider, Renamable, Container, UnreadCoun // MARK: - Renamable - public func rename(to newName: String) { - name = newName + public func rename(to name: String, completion: @escaping (Result) -> Void) { + guard let account = account else { return } + account.renameFolder(self, to: name, completion: completion) } // MARK: - Init diff --git a/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift b/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift index f0368ead4..2f05729eb 100644 --- a/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift +++ b/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift @@ -22,17 +22,22 @@ final class LocalAccountDelegate: AccountDelegate { return refresher.progress } - static func validateCredentials(transport: Transport, credentials: Credentials, completionHandler handler: (Result) -> Void) { - return handler(.success(false)) - } - // LocalAccountDelegate doesn't wait for completion before calling the completion block - func refreshAll(for account: Account, completionHandler completion: (() -> Void)? = nil) { + func refreshAll(for account: Account, completion: (() -> Void)? = nil) { refresher.refreshFeeds(account.flattenedFeeds()) completion?() } + func renameFolder(_ folder: Folder, to name: String, completion: @escaping (Result) -> Void) { + folder.name = name + completion(.success(())) + } + func accountDidInitialize(_ account: Account) { } + static func validateCredentials(transport: Transport, credentials: Credentials, completion: (Result) -> Void) { + return completion(.success(false)) + } + } diff --git a/Mac/Inspector/FolderInspectorViewController.swift b/Mac/Inspector/FolderInspectorViewController.swift index 6f33773ee..45ca150f9 100644 --- a/Mac/Inspector/FolderInspectorViewController.swift +++ b/Mac/Inspector/FolderInspectorViewController.swift @@ -61,12 +61,19 @@ final class FolderInspectorViewController: NSViewController, Inspector { extension FolderInspectorViewController: NSTextFieldDelegate { - func controlTextDidChange(_ note: Notification) { + func controlTextDidEndEditing(_ obj: Notification) { guard let folder = folder, let nameTextField = nameTextField else { return } - folder.name = nameTextField.stringValue + folder.rename(to: nameTextField.stringValue) { result in + switch result { + case .success: + break + case .failure(let error): + NSApplication.shared.presentError(error) + } + } } } diff --git a/Mac/MainWindow/Sidebar/SidebarViewController+ContextualMenus.swift b/Mac/MainWindow/Sidebar/SidebarViewController+ContextualMenus.swift index c29f8d2cd..af201b0dd 100644 --- a/Mac/MainWindow/Sidebar/SidebarViewController+ContextualMenus.swift +++ b/Mac/MainWindow/Sidebar/SidebarViewController+ContextualMenus.swift @@ -103,7 +103,14 @@ extension SidebarViewController: RenameWindowControllerDelegate { feed.editedName = name } else if let folder = object as? Folder { - folder.name = name + folder.rename(to: name) { result in + switch result { + case .success: + break + case .failure(let error): + NSApplication.shared.presentError(error) + } + } } } } diff --git a/submodules/RSCore b/submodules/RSCore index 4ea1f13bb..1b0d13f7d 160000 --- a/submodules/RSCore +++ b/submodules/RSCore @@ -1 +1 @@ -Subproject commit 4ea1f13bbdea2c07d52a7b66909f1e6ae5ba13bf +Subproject commit 1b0d13f7dbb7bafe7d50027b146c10dc5bd8582c diff --git a/submodules/RSWeb b/submodules/RSWeb index 903f6dae0..72c45431f 160000 --- a/submodules/RSWeb +++ b/submodules/RSWeb @@ -1 +1 @@ -Subproject commit 903f6dae0a91f382e310a30aa690eb12569cfff4 +Subproject commit 72c45431fbba0bf1c17d11555c8f75ea963bcb5b