I have impemented NSFilePresenter presentedSubitem:at oldURL:didMoveTo newURL protocol method in a Mac app (Catalina 10.15.3 and Xcode 11.4.1) on a local drive without sandboxing. The method gets called correctly when a file within the presented directory gets moved.
However, the two urls passed are formatted differently. OldURL "file:///System/Volumes/Data/...", while newURL is "file:///Users/...".
func presentedSubitem(at oldURL: URL, didMoveTo newURL: URL) {
print(oldURL) // file:///System/Volumes/Data/Users/...
print(newURL) // file:///Users/...
}
This creates the following issue: I keep a tree of nodes with file information and traverse the tree to search for the node of the oldURL. The URL's in the node is stored in a "file://Users/..." format and the url's aren't considered euqal.
I've tried to compare the File Resource Identifier. However, oldURL's file resource identifier is nil.
extension URL {
var identifier: NSObjectProtocol? {
return (try? resourceValues(forKeys: [.fileResourceIdentifierKey]))?.fileResourceIdentifier
}
static func &=(lhs: URL, rhs: URL) -> Bool {
guard let idl = lhs.identifier, let idr = rhs.identifier else {
assertionFailure("volume does not support resource identifiers") // oldURL.identifier = nil
return lhs == rhs
}
return idl.isEqual(idr)
}
}
How do I compare two URLs where one has the prefix "file:///File/Volumes/Data/" and one has not in a fool proof way?
I see what you mean: If you store the file resource identifier, you can use the identifier from newURL and simply ignore oldURL. You really don't need it. Thanks so much, that is indeed the solution.
If I understand you correctly, the solution would be something like this in code.
import Cocoa
final class Controller: NSObject, NSFilePresenter {
var presentedItemURL: URL?
var presentedItemOperationQueue: OperationQueue = OperationQueue()
var root: FileNode?
// ...
func presentedSubitem(at oldURL: URL, didMoveTo newURL: URL) {
// Ignore oldURL. Find node based on resource identifier of newURL
guard let node = root?.search(for: newURL) else { return }
// Move node to now location
}
}
final class FileNode: NSObject {
var url: URL
var identifier: NSObjectProtocol?
@objc var children = [FileNode]()
// ...
func search(for target: URL) throws -> FileNode? {
guard let identifier = self.url.identifier, let targetIdentifier = target.identifier else {
throw Error.NoIdentifier
}
if identifier.isEqual(targetIdentifier) {
return self
} else {
// Traverse tree
}
return nil
}
}
extension URL {
var identifier: NSObjectProtocol? {
return (try? resourceValues(forKeys: [.fileResourceIdentifierKey]))?.fileResourceIdentifier
}
}