Strange behavior from FileManager.getRelationship(_:of:in:to)

I am attempting to create what should be a rather simple function, isInTrash, to return whether a file path passed to it points to a location in any trash. This thread https://developer.apple.com/forums/thread/110864 got me partway there, but the results I am getting don't match with what I expect.

Function:

    func isInTrash(file: String) -> Bool {
        guard let fileURL = URL(string: file) else {
            return false
        }
        var relationship: FileManager.URLRelationship = .same
        let defaultManager: FileManager = FileManager.default
        do {
            try defaultManager.getRelationship(&relationship,
                               of: FileManager.SearchPathDirectory.trashDirectory,
                               in: FileManager.SearchPathDomainMask(rawValue: 0),
                               toItemAt: fileURL)
        } catch {
            NSLog("Unable to get relationship of path to trash.:\n")
        }
        return relationship == .contains
    }

Unit Test:

    func testInTrashPaths() {
        XCTAssertTrue(handler.isInTrash(file: "/Users/username/Trash/foo")) //fails
        XCTAssertTrue(handler.isInTrash(file: "/Users/username/.Trash/foo")) //fails
        XCTAssertTrue(handler.isInTrash(file: "/Users/username/.Trash/bar")) //fails
        XCTAssertTrue(handler.isInTrash(file: "/Users/username/.Trash/blah")) //fails
        XCTAssertTrue(handler.isInTrash(file: "/Users/username/.Trash/test/bang")) //fails
        XCTAssertFalse(handler.isInTrash(file: "/Users/username/Desktop/test/bang")) //succeeds
    }

The tests that should return true all fail. The negative test succeeds, but that's not really a surprise. If I switch to testing for .same instead of .contains, then the first Assert succeeds ("/Users/username/Trash/foo").

Two questions about that behavior.

  1. it appears that while the actual path, if you open the trash in a terminal and use pwd, is /.Trash, the api treats it as /Trash. Why?
  2. /Trash/foo returns .same instead of .contains. Why is it saying that a file inside the trash has a relationship of .same to the Trash? It's very clearly a contains relationship. What's going on?

If I sanitize the inputs by substituting "/" for "/." using replacingOccurrences(of:with:), then everything except the negative test fails. When I step through in the debugger, I find that now a path to a file on the desktop is returning .same for the Trash! WHAT!?!?!? WHY!?!?

I would dearly love to know what I'm doing wrong here.

Answered by DTS Engineer in 698644022

I’m not sure if anything else is going on here but your isInTrash(…) function has a serious bug. Specifically, this code is incorrect:

guard let fileURL = URL(string: file) else {

You should be using init(fileURLWithPath:). What you’re constructing here is as scheme-less URL that just contains a path. While you want is a URL with the file scheme. Consider:

import Foundation

let u1 = URL(fileURLWithPath: "/Users/quinn/Test")
let u2 = URL(string: "/Users/quinn/Test")!
print(u1)   // file:///Users/quinn/Test/
print(u2)   // /Users/quinn/Test

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

When you're testing, do your test items like /Users/username/.Trash/foo actually exist? Maybe you're assuming that this test should be entirely path-based so it shouldn't matter, but in fact this getRelationship method returns an error if the URL does not point to an actual file/folder. Not sure why you didn't get the "Unable to get relationship..." log message; I'm an old-fashioned objective-C programmer.

Accepted Answer

I’m not sure if anything else is going on here but your isInTrash(…) function has a serious bug. Specifically, this code is incorrect:

guard let fileURL = URL(string: file) else {

You should be using init(fileURLWithPath:). What you’re constructing here is as scheme-less URL that just contains a path. While you want is a URL with the file scheme. Consider:

import Foundation

let u1 = URL(fileURLWithPath: "/Users/quinn/Test")
let u2 = URL(string: "/Users/quinn/Test")!
print(u1)   // file:///Users/quinn/Test/
print(u2)   // /Users/quinn/Test

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Strange behavior from FileManager.getRelationship(_:of:in:to)
 
 
Q