NetNewsWire/Shared/Commands/DeleteCommand.swift

308 lines
5.9 KiB
Swift
Raw Normal View History

//
// DeleteFromSidebarCommand.swift
2018-08-29 07:18:24 +02:00
// NetNewsWire
//
// Created by Brent Simmons on 11/4/17.
// Copyright © 2017 Ranchero Software. All rights reserved.
//
import Foundation
2024-03-22 01:21:50 +01:00
import Tree
import Account
import Articles
import Core
2024-03-22 01:21:50 +01:00
@MainActor final class DeleteCommand: UndoableCommand {
2020-03-02 01:32:31 +01:00
let treeController: TreeController?
2017-11-05 21:14:36 +01:00
let undoManager: UndoManager
let undoActionName: String
var redoActionName: String {
return undoActionName
}
let errorHandler: (Error) -> ()
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) -> ()) {
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
}
self.treeController = treeController
2017-11-05 21:14:36 +01:00
self.undoActionName = actionName
self.undoManager = undoManager
self.errorHandler = errorHandler
2017-11-05 21:14:36 +01: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
}
func perform() {
2024-03-28 00:21:57 +01:00
Task {
2024-05-22 07:18:26 +02:00
for itemSpecifier in itemSpecifiers {
await itemSpecifier.delete()
}
2024-03-28 00:21:57 +01:00
2024-05-22 07:18:26 +02:00
treeController?.rebuild()
registerUndo()
}
}
2024-03-28 00:21:57 +01:00
func undo() {
2024-04-08 02:06:39 +02:00
for itemSpecifier in itemSpecifiers {
itemSpecifier.restore()
}
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-02-26 06:41:18 +01:00
if let _ = node.representedObject as? Feed {
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.
2024-03-22 01:21:50 +01:00
@MainActor private struct SidebarItemSpecifier {
private weak var account: Account?
2017-11-05 21:14:36 +01:00
private let parentFolder: Folder?
private let folder: Folder?
private let feed: Feed?
private let path: ContainerPath
private let errorHandler: (Error) -> ()
2017-11-05 21:14:36 +01:00
private var container: Container? {
if let parentFolder = parentFolder {
return parentFolder
}
if let account = account {
return account
2017-11-05 21:14:36 +01:00
}
return nil
2017-11-05 21:14:36 +01:00
}
init?(node: Node, errorHandler: @escaping (Error) -> ()) {
var account: Account?
2017-11-05 21:14:36 +01:00
self.parentFolder = node.parentFolder()
if let feed = node.representedObject as? Feed {
self.feed = feed
2017-11-05 21:14:36 +01:00
self.folder = nil
account = feed.account
}
else if let folder = node.representedObject as? Folder {
self.feed = nil
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())
2024-03-28 00:21:57 +01:00
self.errorHandler = errorHandler
2024-03-28 00:21:57 +01:00
2017-11-05 21:14:36 +01:00
}
2024-05-22 07:18:26 +02:00
func delete() async {
if let feed {
guard let container = path.resolveContainer() else {
return
}
2024-05-22 07:18:26 +02:00
BatchUpdate.shared.start()
2024-03-28 17:28:16 +01:00
2024-05-22 07:18:26 +02:00
do {
try await account?.removeFeed(feed, from: container)
BatchUpdate.shared.end()
} catch {
BatchUpdate.shared.end()
errorHandler(error)
2019-05-09 14:25:45 +02:00
}
2024-03-28 17:28:16 +01:00
2024-05-22 07:18:26 +02:00
} else if let folder {
BatchUpdate.shared.start()
2024-05-22 07:18:26 +02:00
do {
try await account?.removeFolder(folder)
BatchUpdate.shared.end()
} catch {
BatchUpdate.shared.end()
errorHandler(error)
2019-05-07 00:34:41 +02:00
}
2017-11-05 21:14:36 +01:00
}
}
func restore() {
if let _ = feed {
restoreFeed()
2017-11-05 21:14:36 +01:00
}
else if let _ = folder {
2017-11-05 21:14:36 +01:00
restoreFolder()
}
}
private func restoreFeed() {
2017-11-05 21:14:36 +01:00
guard let account = account, let feed = feed, let container = path.resolveContainer() else {
2017-11-05 21:14:36 +01:00
return
}
2024-03-28 00:21:57 +01:00
BatchUpdate.shared.start()
2024-03-28 00:21:57 +01:00
Task {
2024-03-28 01:18:17 +01:00
do {
try await account.restoreFeed(feed, container: container)
BatchUpdate.shared.end()
} catch {
BatchUpdate.shared.end()
self.errorHandler(error)
}
}
2017-11-05 21:14:36 +01:00
}
private func restoreFolder() {
2024-03-28 00:21:57 +01:00
guard let account, let folder else {
2017-11-05 21:14:36 +01:00
return
}
2024-03-28 00:21:57 +01:00
Task {
2024-03-28 00:21:57 +01:00
BatchUpdate.shared.start()
do {
try await account.restoreFolder(folder)
BatchUpdate.shared.end()
} catch {
BatchUpdate.shared.end()
self.errorHandler(error)
}
2019-05-09 23:09:21 +02:00
}
2017-11-05 21:14:36 +01:00
}
2019-05-09 23:09:21 +02:00
private func checkResult(_ result: Result<Void, Error>) {
2024-03-28 00:21:57 +01:00
2019-05-09 23:09:21 +02:00
switch result {
case .success:
break
case .failure(let error):
errorHandler(error)
2019-05-09 23:09:21 +02:00
}
}
2024-03-28 00:21:57 +01:00
}
2017-11-05 03:29:43 +01:00
private extension Node {
2024-03-28 00:21:57 +01: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 {
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
}
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")
2024-03-22 01:21:50 +01:00
@MainActor static func name(for nodes: [Node]) -> String? {
2017-11-05 21:14:36 +01:00
var numberOfFeeds = 0
var numberOfFolders = 0
for node in nodes {
2024-02-26 06:41:18 +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
}
}