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.

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