Add undo for feeds

This commit is contained in:
Maurice Parker 2019-05-09 16:09:21 -05:00
parent d4bfab2da6
commit 23b43ecc07
7 changed files with 178 additions and 71 deletions

View File

@ -354,30 +354,16 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
return ensureFolder(with: folderName)
}
public func canAddFeed(_ feed: Feed, to folder: Folder?) -> Bool {
// If folder is nil, then it should go at the top level.
// The same feed in multiple folders is allowed.
// But the same feed cant appear twice in the same folder
// (or at the top level).
return true // TODO
}
public func removeFeed(_ feed: Feed, completion: @escaping (Result<Void, Error>) -> Void) {
delegate.removeFeed(for: self, from: self, with: feed, completion: completion)
}
public func addFeed(_ feed: Feed, completion: @escaping (Result<Void, Error>) -> Void) {
delegate.addFeed(for: self, to: self, with: feed, completion: completion)
}
func addFeed(container: Container, feed: Feed, completion: @escaping (Result<Void, Error>) -> Void) {
delegate.addFeed(for: self, to: container, with: feed, completion: completion)
}
public func createFeed(with name: String?, url: String, completion: @escaping (Result<AccountCreateFeedResult, Error>) -> Void) {
delegate.createFeed(for: self, with: name, url: url, completion: completion)
func removeFeed(_ feed: Feed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
delegate.removeFeed(for: self, from: container, with: feed, completion: completion)
}
public func createFeed(url: String, completion: @escaping (Result<AccountCreateFeedResult, Error>) -> Void) {
delegate.createFeed(for: self, url: url, completion: completion)
}
func createFeed(with name: String?, url: String, feedID: String, homePageURL: String?) -> Feed {
@ -391,28 +377,37 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
}
public func deleteFeed(_ feed: Feed, completion: @escaping (Result<Void, Error>) -> Void) {
feedMetadata[feed.url] = nil
delegate.deleteFeed(for: self, with: feed, completion: completion)
}
public func renameFeed(_ feed: Feed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
delegate.renameFeed(for: self, with: feed, to: name, completion: completion)
}
@discardableResult
public func addFolder(_ folder: Folder, to parentFolder: Folder?) -> Bool {
// TODO: support subfolders, maybe, some day, if one of the sync systems
// supports subfolders. But, for now, parentFolder is ignored.
if folders!.contains(folder) {
return true
public func restoreFeed(_ feed: Feed, folder: Folder?, completion: @escaping (Result<Void, Error>) -> Void) {
delegate.restoreFeed(for: self, feed: feed, folder: folder, completion: completion)
}
folders!.insert(folder)
postChildrenDidChangeNotification()
structureDidChange()
return true
public func deleteFolder(_ folder: Folder, completion: @escaping (Result<Void, Error>) -> Void) {
delegate.deleteFolder(for: self, with: folder, completion: completion)
}
public func renameFolder(_ folder: Folder, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
delegate.renameFolder(for: self, with: folder, to: name, completion: completion)
}
public func restoreFolder(_ folder: Folder, completion: @escaping (Result<Void, Error>) -> Void) {
delegate.restoreFolder(for: self, folder: folder, completion: completion)
}
func addFolder(_ folder: Folder) {
folders!.insert(folder)
postChildrenDidChangeNotification()
structureDidChange()
}
public func importOPML(_ opmlDocument: RSOPMLDocument) {
guard let children = opmlDocument.children else {
@ -582,12 +577,12 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
return _flattenedFeeds
}
public func deleteFeed(_ feed: Feed, completion: @escaping (Result<Void, Error>) -> Void) {
delegate.deleteFeed(for: self, with: feed, completion: completion)
public func removeFeed(_ feed: Feed, completion: @escaping (Result<Void, Error>) -> Void) {
delegate.removeFeed(for: self, from: self, with: feed, completion: completion)
}
func removeFeed(_ feed: Feed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
delegate.removeFeed(for: self, from: container, with: feed, completion: completion)
public func addFeed(_ feed: Feed, completion: @escaping (Result<Void, Error>) -> Void) {
delegate.addFeed(for: self, to: self, with: feed, completion: completion)
}
func removeFeed(_ feed: Feed) {
@ -602,10 +597,6 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
postChildrenDidChangeNotification()
}
public func deleteFolder(_ folder: Folder, completion: @escaping (Result<Void, Error>) -> Void) {
delegate.deleteFolder(for: self, with: folder, completion: completion)
}
func deleteFolder(_ folder: Folder) {
folders?.remove(folder)
structureDidChange()

View File

@ -24,13 +24,16 @@ protocol AccountDelegate {
func renameFolder(for account: Account, with folder: Folder, to name: String, completion: @escaping (Result<Void, Error>) -> Void)
func deleteFolder(for account: Account, with folder: Folder, completion: @escaping (Result<Void, Error>) -> Void)
func createFeed(for account: Account, with name: String?, url: String, completion: @escaping (Result<AccountCreateFeedResult, Error>) -> Void)
func createFeed(for account: Account, url: String, completion: @escaping (Result<AccountCreateFeedResult, Error>) -> Void)
func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result<Void, Error>) -> Void)
func deleteFeed(for account: Account, with feed: Feed, completion: @escaping (Result<Void, Error>) -> Void)
func addFeed(for account: Account, to container: Container, with: Feed, completion: @escaping (Result<Void, Error>) -> Void)
func removeFeed(for account: Account, from container: Container, with: Feed, completion: @escaping (Result<Void, Error>) -> Void)
func restoreFeed(for account: Account, feed: Feed, folder: Folder?, completion: @escaping (Result<Void, Error>) -> Void)
func restoreFolder(for account: Account, folder: Folder, completion: @escaping (Result<Void, Error>) -> Void)
// Called at the end of accounts init method.
func accountDidInitialize(_ account: Account)

View File

@ -18,6 +18,7 @@ import os.log
public enum FeedbinAccountDelegateError: String, Error {
case invalidParameter = "There was an invalid parameter passed."
case invalidResponse = "An invalid response was received from the service."
}
final class FeedbinAccountDelegate: AccountDelegate {
@ -114,7 +115,7 @@ final class FeedbinAccountDelegate: AccountDelegate {
}
func createFeed(for account: Account, with name: String?, url: String, completion: @escaping (Result<AccountCreateFeedResult, Error>) -> Void) {
func createFeed(for account: Account, url: String, completion: @escaping (Result<AccountCreateFeedResult, Error>) -> Void) {
caller.createSubscription(url: url) { result in
switch result {
@ -256,6 +257,99 @@ final class FeedbinAccountDelegate: AccountDelegate {
}
func restoreFeed(for account: Account, feed: Feed, folder: Folder?, completion: @escaping (Result<Void, Error>) -> Void) {
let editedName = feed.editedName
createFeed(for: account, url: feed.url) { [weak self] result in
switch result {
case .success(let createResult):
switch createResult {
case .created(let feed):
self?.processRestoredFeed(for: account, feed: feed, editedName: editedName, folder: folder, completion: completion)
default:
DispatchQueue.main.async {
completion(.failure(FeedbinAccountDelegateError.invalidResponse))
}
}
case .failure(let error):
DispatchQueue.main.async {
completion(.failure(error))
}
}
}
}
private func processRestoredFeed(for account: Account, feed: Feed, editedName: String?, folder: Folder?, completion: @escaping (Result<Void, Error>) -> Void) {
if let folder = folder {
addFeed(for: account, to: folder, with: feed) { [weak self] result in
switch result {
case .success:
if editedName != nil {
DispatchQueue.main.async {
folder.addFeed(feed)
}
self?.processRestoredFeedName(for: account, feed: feed, editedName: editedName!, completion: completion)
} else {
DispatchQueue.main.async {
folder.addFeed(feed)
completion(.success(()))
}
}
case .failure(let error):
DispatchQueue.main.async {
completion(.failure(error))
}
}
}
} else {
DispatchQueue.main.async {
account.addFeed(feed)
}
processRestoredFeedName(for: account, feed: feed, editedName: editedName!, completion: completion)
}
}
private func processRestoredFeedName(for account: Account, feed: Feed, editedName: String, completion: @escaping (Result<Void, Error>) -> Void) {
renameFeed(for: account, with: feed, to: editedName) { result in
switch result {
case .success:
DispatchQueue.main.async {
feed.editedName = editedName
completion(.success(()))
}
case .failure(let error):
DispatchQueue.main.async {
completion(.failure(error))
}
}
}
}
func restoreFolder(for account: Account, folder: Folder, completion: @escaping (Result<Void, Error>) -> Void) {
account.addFolder(folder)
completion(.success(()))
}
func accountDidInitialize(_ account: Account) {
credentials = try? account.retrieveBasicCredentials()
accountMetadata = account.metadata

View File

@ -46,7 +46,7 @@ final class LocalAccountDelegate: AccountDelegate {
completion(.success(()))
}
func createFeed(for account: Account, with name: String?, url urlString: String, completion: @escaping (Result<AccountCreateFeedResult, Error>) -> Void) {
func createFeed(for account: Account, url urlString: String, completion: @escaping (Result<AccountCreateFeedResult, Error>) -> Void) {
guard let url = URL(string: urlString) else {
completion(.failure(LocalAccountDelegateError.invalidParameter))
@ -107,6 +107,20 @@ final class LocalAccountDelegate: AccountDelegate {
completion(.success(()))
}
func restoreFeed(for account: Account, feed: Feed, folder: Folder?, completion: @escaping (Result<Void, Error>) -> Void) {
if let folder = folder {
folder.addFeed(feed)
} else {
account.addFeed(feed)
}
completion(.success(()))
}
func restoreFolder(for account: Account, folder: Folder, completion: @escaping (Result<Void, Error>) -> Void) {
account.addFolder(folder)
completion(.success(()))
}
func accountDidInitialize(_ account: Account) {
}

View File

@ -59,7 +59,7 @@ class AddFeedController: AddFeedWindowControllerDelegate {
return
}
account.createFeed(with: nil, url: url.absoluteString) { [weak self] result in
account.createFeed(url: url.absoluteString) { [weak self] result in
self?.endShowingProgress()

View File

@ -100,7 +100,7 @@ class ScriptableFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContaine
// suspendExecution(). When we get the callback, we can supply the event result and call resumeExecution()
command.suspendExecution()
account.createFeed(with: nil, url: url) { result in
account.createFeed(url: url) { result in
switch result {
case .success(let createFeedResult):

View File

@ -136,29 +136,11 @@ private struct SidebarItemSpecifier {
if let feed = feed {
account?.deleteFeed(feed) { result in
switch result {
case .success():
break
case .failure(let error):
#if os(macOS)
NSApplication.shared.presentError(error)
#else
UIApplication.shared.presentError(error)
#endif
}
self.checkResult(result)
}
} else if let folder = folder {
account?.deleteFolder(folder) { result in
switch result {
case .success():
break
case .failure(let error):
#if os(macOS)
NSApplication.shared.presentError(error)
#else
UIApplication.shared.presentError(error)
#endif
}
self.checkResult(result)
}
}
}
@ -178,7 +160,11 @@ private struct SidebarItemSpecifier {
guard let account = account, let feed = feed else {
return
}
// account.addFeed(feed, to: resolvedFolder())
account.restoreFeed(feed, folder: resolvedFolder()) { result in
self.checkResult(result)
}
}
private func restoreFolder() {
@ -186,13 +172,32 @@ private struct SidebarItemSpecifier {
guard let account = account, let folder = folder else {
return
}
account.addFolder(folder, to: nil)
account.restoreFolder(folder) { result in
self.checkResult(result)
}
}
private func resolvedFolder() -> Folder? {
return path.resolveContainer() as? Folder
}
private func checkResult(_ result: Result<Void, Error>) {
switch result {
case .success:
break
case .failure(let error):
#if os(macOS)
NSApplication.shared.presentError(error)
#else
UIApplication.shared.presentError(error)
#endif
}
}
}
private extension Node {