2017-11-05 03:03:47 +01:00
|
|
|
//
|
|
|
|
// DeleteFromSidebarCommand.swift
|
2018-08-29 07:18:24 +02:00
|
|
|
// NetNewsWire
|
2017-11-05 03:03:47 +01:00
|
|
|
//
|
|
|
|
// Created by Brent Simmons on 11/4/17.
|
|
|
|
// Copyright © 2017 Ranchero Software. All rights reserved.
|
|
|
|
//
|
|
|
|
|
|
|
|
import Foundation
|
|
|
|
import RSCore
|
|
|
|
import RSTree
|
|
|
|
import Account
|
2018-07-24 03:29:08 +02:00
|
|
|
import Articles
|
2017-11-05 03:03:47 +01:00
|
|
|
|
2019-04-18 15:54:48 +02:00
|
|
|
final class DeleteCommand: UndoableCommand {
|
2017-11-05 03:03:47 +01:00
|
|
|
|
2020-03-02 01:32:31 +01:00
|
|
|
let treeController: TreeController?
|
2017-11-05 21:14:36 +01:00
|
|
|
let undoManager: UndoManager
|
2017-11-05 03:03:47 +01:00
|
|
|
let undoActionName: String
|
|
|
|
var redoActionName: String {
|
2018-02-14 22:14:25 +01:00
|
|
|
return undoActionName
|
2017-11-05 03:03:47 +01:00
|
|
|
}
|
2019-06-27 21:21:07 +02:00
|
|
|
let errorHandler: (Error) -> ()
|
2017-11-05 03:03:47 +01:00
|
|
|
|
2017-11-05 21:14:36 +01:00
|
|
|
private let itemSpecifiers: [SidebarItemSpecifier]
|
|
|
|
|
2020-03-02 01:32:31 +01:00
|
|
|
init?(nodesToDelete: [Node], treeController: TreeController? = nil, undoManager: UndoManager, errorHandler: @escaping (Error) -> ()) {
|
2017-11-05 03:03:47 +01:00
|
|
|
|
2019-04-18 15:54:48 +02:00
|
|
|
guard DeleteCommand.canDelete(nodesToDelete) else {
|
2017-11-05 21:14:36 +01:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
guard let actionName = DeleteActionName.name(for: nodesToDelete) else {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-02-26 07:02:55 +01:00
|
|
|
self.treeController = treeController
|
2017-11-05 21:14:36 +01:00
|
|
|
self.undoActionName = actionName
|
2017-11-05 03:03:47 +01:00
|
|
|
self.undoManager = undoManager
|
2019-06-27 21:21:07 +02:00
|
|
|
self.errorHandler = errorHandler
|
2017-11-05 21:14:36 +01:00
|
|
|
|
2019-06-27 21:21:07 +02:00
|
|
|
let itemSpecifiers = nodesToDelete.compactMap{ SidebarItemSpecifier(node: $0, errorHandler: errorHandler) }
|
2017-11-05 21:14:36 +01:00
|
|
|
guard !itemSpecifiers.isEmpty else {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
self.itemSpecifiers = itemSpecifiers
|
2017-11-05 03:03:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func perform() {
|
2019-06-06 00:42:35 +02:00
|
|
|
|
|
|
|
let group = DispatchGroup()
|
|
|
|
itemSpecifiers.forEach {
|
2020-02-27 02:28:52 +01:00
|
|
|
group.enter()
|
2019-06-06 00:42:35 +02:00
|
|
|
$0.delete() {
|
|
|
|
group.leave()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
group.notify(queue: DispatchQueue.main) {
|
2020-03-02 01:32:31 +01:00
|
|
|
self.treeController?.rebuild()
|
2019-06-06 00:42:35 +02:00
|
|
|
self.registerUndo()
|
|
|
|
}
|
|
|
|
|
2017-11-05 03:03:47 +01:00
|
|
|
}
|
2019-06-06 00:42:35 +02:00
|
|
|
|
2017-11-05 03:03:47 +01:00
|
|
|
func undo() {
|
2019-09-01 17:57:08 +02:00
|
|
|
itemSpecifiers.forEach { $0.restore() }
|
2017-11-05 03:03:47 +01:00
|
|
|
registerRedo()
|
|
|
|
}
|
|
|
|
|
|
|
|
static func canDelete(_ nodes: [Node]) -> Bool {
|
|
|
|
|
|
|
|
// Return true if all nodes are feeds and folders.
|
|
|
|
// Any other type: return false.
|
|
|
|
|
|
|
|
if nodes.isEmpty {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
for node in nodes {
|
2024-11-02 05:34:08 +01:00
|
|
|
if let _ = node.representedObject as? Feed {
|
2017-11-05 03:03:47 +01:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
if let _ = node.representedObject as? Folder {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remember as much as we can now about the items being deleted,
|
|
|
|
// so they can be restored to the correct place.
|
|
|
|
|
|
|
|
private struct SidebarItemSpecifier {
|
|
|
|
|
2017-11-05 06:51:14 +01:00
|
|
|
private weak var account: Account?
|
2017-11-05 21:14:36 +01:00
|
|
|
private let parentFolder: Folder?
|
2017-11-05 06:51:14 +01:00
|
|
|
private let folder: Folder?
|
2024-11-02 19:08:58 +01:00
|
|
|
private let feed: Feed?
|
2017-11-05 06:51:14 +01:00
|
|
|
private let path: ContainerPath
|
2019-06-27 21:21:07 +02:00
|
|
|
private let errorHandler: (Error) -> ()
|
2017-11-05 21:14:36 +01:00
|
|
|
|
|
|
|
private var container: Container? {
|
2018-02-14 22:14:25 +01:00
|
|
|
if let parentFolder = parentFolder {
|
|
|
|
return parentFolder
|
|
|
|
}
|
|
|
|
if let account = account {
|
|
|
|
return account
|
2017-11-05 21:14:36 +01:00
|
|
|
}
|
2018-02-14 22:14:25 +01:00
|
|
|
return nil
|
2017-11-05 21:14:36 +01:00
|
|
|
}
|
|
|
|
|
2019-06-27 21:21:07 +02:00
|
|
|
init?(node: Node, errorHandler: @escaping (Error) -> ()) {
|
2017-11-05 03:03:47 +01:00
|
|
|
|
|
|
|
var account: Account?
|
2017-11-05 06:51:14 +01:00
|
|
|
|
2017-11-05 21:14:36 +01:00
|
|
|
self.parentFolder = node.parentFolder()
|
|
|
|
|
2024-11-02 19:08:58 +01:00
|
|
|
if let feed = node.representedObject as? Feed {
|
|
|
|
self.feed = feed
|
2017-11-05 21:14:36 +01:00
|
|
|
self.folder = nil
|
2024-11-02 19:08:58 +01:00
|
|
|
account = feed.account
|
2017-11-05 03:03:47 +01:00
|
|
|
}
|
|
|
|
else if let folder = node.representedObject as? Folder {
|
2024-11-02 19:08:58 +01:00
|
|
|
self.feed = nil
|
2017-11-05 03:03:47 +01:00
|
|
|
self.folder = folder
|
|
|
|
account = folder.account
|
|
|
|
}
|
2017-11-05 21:14:36 +01:00
|
|
|
else {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if account == nil {
|
|
|
|
return nil
|
|
|
|
}
|
2017-11-05 03:29:43 +01:00
|
|
|
|
|
|
|
self.account = account!
|
2017-11-05 21:14:36 +01:00
|
|
|
self.path = ContainerPath(account: account!, folders: node.containingFolders())
|
2019-06-27 21:21:07 +02:00
|
|
|
|
|
|
|
self.errorHandler = errorHandler
|
|
|
|
|
2017-11-05 21:14:36 +01:00
|
|
|
}
|
|
|
|
|
2019-06-06 00:42:35 +02:00
|
|
|
func delete(completion: @escaping () -> Void) {
|
2017-11-05 21:14:36 +01:00
|
|
|
|
2024-11-02 19:08:58 +01:00
|
|
|
if let feed = feed {
|
2019-07-17 22:41:21 +02:00
|
|
|
|
|
|
|
guard let container = path.resolveContainer() else {
|
|
|
|
completion()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-05-21 01:05:28 +02:00
|
|
|
BatchUpdate.shared.start()
|
2024-11-02 19:08:58 +01:00
|
|
|
account?.removeFeed(feed, from: container) { result in
|
2019-05-21 01:05:28 +02:00
|
|
|
BatchUpdate.shared.end()
|
2019-06-06 00:42:35 +02:00
|
|
|
completion()
|
2019-05-09 23:09:21 +02:00
|
|
|
self.checkResult(result)
|
2019-05-09 14:25:45 +02:00
|
|
|
}
|
2019-07-17 22:41:21 +02:00
|
|
|
|
2019-05-09 14:25:45 +02:00
|
|
|
} else if let folder = folder {
|
2019-07-17 22:41:21 +02:00
|
|
|
|
2019-05-21 01:05:28 +02:00
|
|
|
BatchUpdate.shared.start()
|
2019-05-30 03:53:00 +02:00
|
|
|
account?.removeFolder(folder) { result in
|
2019-05-21 01:05:28 +02:00
|
|
|
BatchUpdate.shared.end()
|
2019-06-06 00:42:35 +02:00
|
|
|
completion()
|
2019-05-09 23:09:21 +02:00
|
|
|
self.checkResult(result)
|
2019-05-07 00:34:41 +02:00
|
|
|
}
|
2019-07-17 22:41:21 +02:00
|
|
|
|
2017-11-05 21:14:36 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func restore() {
|
|
|
|
|
2024-11-02 19:08:58 +01:00
|
|
|
if let _ = feed {
|
|
|
|
restoreFeed()
|
2017-11-05 21:14:36 +01:00
|
|
|
}
|
2017-11-06 05:31:50 +01:00
|
|
|
else if let _ = folder {
|
2017-11-05 21:14:36 +01:00
|
|
|
restoreFolder()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-02 19:08:58 +01:00
|
|
|
private func restoreFeed() {
|
2017-11-05 21:14:36 +01:00
|
|
|
|
2024-11-02 19:08:58 +01:00
|
|
|
guard let account = account, let feed = feed, let container = path.resolveContainer() else {
|
2017-11-05 21:14:36 +01:00
|
|
|
return
|
|
|
|
}
|
2019-05-09 23:09:21 +02:00
|
|
|
|
2019-05-21 01:05:28 +02:00
|
|
|
BatchUpdate.shared.start()
|
2024-11-02 19:08:58 +01:00
|
|
|
account.restoreFeed(feed, container: container) { result in
|
2019-05-21 01:05:28 +02:00
|
|
|
BatchUpdate.shared.end()
|
2019-05-09 23:09:21 +02:00
|
|
|
self.checkResult(result)
|
|
|
|
}
|
|
|
|
|
2017-11-05 21:14:36 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
private func restoreFolder() {
|
|
|
|
|
|
|
|
guard let account = account, let folder = folder else {
|
|
|
|
return
|
|
|
|
}
|
2019-05-09 23:09:21 +02:00
|
|
|
|
2019-05-21 01:05:28 +02:00
|
|
|
BatchUpdate.shared.start()
|
2019-05-09 23:09:21 +02:00
|
|
|
account.restoreFolder(folder) { result in
|
2019-05-21 01:05:28 +02:00
|
|
|
BatchUpdate.shared.end()
|
2019-05-09 23:09:21 +02:00
|
|
|
self.checkResult(result)
|
|
|
|
}
|
|
|
|
|
2017-11-05 21:14:36 +01:00
|
|
|
}
|
|
|
|
|
2019-05-09 23:09:21 +02:00
|
|
|
private func checkResult(_ result: Result<Void, Error>) {
|
|
|
|
|
|
|
|
switch result {
|
|
|
|
case .success:
|
|
|
|
break
|
|
|
|
case .failure(let error):
|
2019-06-27 21:21:07 +02:00
|
|
|
errorHandler(error)
|
2019-05-09 23:09:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2017-11-05 03:03:47 +01:00
|
|
|
}
|
|
|
|
|
2017-11-05 03:29:43 +01:00
|
|
|
private extension Node {
|
2019-05-09 23:09:21 +02:00
|
|
|
|
2017-11-05 21:14:36 +01:00
|
|
|
func parentFolder() -> Folder? {
|
|
|
|
|
|
|
|
guard let parentNode = self.parent else {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if parentNode.isRoot {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if let folder = parentNode.representedObject as? Folder {
|
2017-11-05 06:51:14 +01:00
|
|
|
return folder
|
2017-11-05 21:14:36 +01:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func containingFolders() -> [Folder] {
|
|
|
|
|
|
|
|
var nomad = self.parent
|
|
|
|
var folders = [Folder]()
|
|
|
|
|
|
|
|
while nomad != nil {
|
|
|
|
if let folder = nomad!.representedObject as? Folder {
|
|
|
|
folders += [folder]
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
nomad = nomad!.parent
|
|
|
|
}
|
|
|
|
|
|
|
|
return folders.reversed()
|
|
|
|
}
|
|
|
|
|
2017-11-05 03:29:43 +01:00
|
|
|
}
|
|
|
|
|
2017-11-05 06:51:14 +01:00
|
|
|
private struct DeleteActionName {
|
2017-11-05 21:14:36 +01:00
|
|
|
|
|
|
|
private static let deleteFeed = NSLocalizedString("Delete Feed", comment: "command")
|
|
|
|
private static let deleteFeeds = NSLocalizedString("Delete Feeds", comment: "command")
|
|
|
|
private static let deleteFolder = NSLocalizedString("Delete Folder", comment: "command")
|
|
|
|
private static let deleteFolders = NSLocalizedString("Delete Folders", comment: "command")
|
|
|
|
private static let deleteFeedsAndFolders = NSLocalizedString("Delete Feeds and Folders", comment: "command")
|
|
|
|
|
|
|
|
static func name(for nodes: [Node]) -> String? {
|
|
|
|
|
|
|
|
var numberOfFeeds = 0
|
|
|
|
var numberOfFolders = 0
|
|
|
|
|
|
|
|
for node in nodes {
|
2024-11-02 05:34:08 +01:00
|
|
|
if let _ = node.representedObject as? Feed {
|
2017-11-05 21:14:36 +01:00
|
|
|
numberOfFeeds += 1
|
|
|
|
}
|
|
|
|
else if let _ = node.representedObject as? Folder {
|
|
|
|
numberOfFolders += 1
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return nil // Delete only Feeds and Folders.
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if numberOfFolders < 1 {
|
|
|
|
return numberOfFeeds == 1 ? deleteFeed : deleteFeeds
|
|
|
|
}
|
|
|
|
if numberOfFeeds < 1 {
|
|
|
|
return numberOfFolders == 1 ? deleteFolder : deleteFolders
|
|
|
|
}
|
|
|
|
|
|
|
|
return deleteFeedsAndFolders
|
|
|
|
}
|
2017-11-05 06:51:14 +01:00
|
|
|
}
|