6

I'm having trouble resolving the alias link on mac. I'm checking if the file is an alias and then I would want to receive the original path. Instead I'm only getting a File-Id. Anly ideas?

func isFinderAlias(path:String) -> Bool? {

    var isAlias:Bool? = false // Initialize result var.

    // Create a CFURL instance for the given filesystem path.
    // This should never fail, because the existence isn't verified at this point.
    // Note: No need to call CFRelease(fUrl) later, because Swift auto-memory-manages CoreFoundation objects.
    print("path before \(path)");
    let fUrl = CFURLCreateWithFileSystemPath(nil, path, CFURLPathStyle.CFURLPOSIXPathStyle, false)
    print("path furl \(fUrl)");
    // Allocate void pointer - no need for initialization,
    // it will be assigned to by CFURLCopyResourcePropertyForKey() below.
    let ptrPropVal = UnsafeMutablePointer<Void>.alloc(1)

    // Call the CoreFoundation function that copies the desired information as
    // a CFBoolean to newly allocated memory that prt will point to on return.
    if CFURLCopyResourcePropertyForKey(fUrl, kCFURLIsAliasFileKey, ptrPropVal, nil) {

        // Extract the Bool value from the memory allocated.
        isAlias = UnsafePointer<CFBoolean>(ptrPropVal).memory as Bool


        // it will be assigned to by CFURLCopyResourcePropertyForKey() below.
        let ptrDarwin = UnsafeMutablePointer<DarwinBoolean>.alloc(1)

        if ((isAlias) == true){
            if let bookmark = CFURLCreateBookmarkDataFromFile(kCFAllocatorDefault, fUrl, nil){
                let url = CFURLCreateByResolvingBookmarkData(kCFAllocatorDefault, bookmark.takeRetainedValue(), CFURLBookmarkResolutionOptions.CFBookmarkResolutionWithoutMountingMask, nil, nil, ptrDarwin, nil)
                print("getting the path \(url)")
            }
        }

        // Since the CF*() call contains the word "Copy", WE are responsible
        // for destroying (freeing) the memory.
        ptrDarwin.destroy()
        ptrDarwin.dealloc(1)
        ptrPropVal.destroy()
    }

    // Deallocate the pointer
    ptrPropVal.dealloc(1)

    return isAlias
}

EDIT: Both Answers are correct! I would choose the answer of mklement0 due to the originally not stated requirement that the code run on 10.9 which makes it more flexible

Silve2611
  • 2,198
  • 2
  • 34
  • 55

4 Answers4

7

This is a solution using NSURL.

It expects an NSURL object as parameter and returns either the original path if the url is an alias or nil.

func resolveFinderAlias(url:NSURL) -> String? {

  var isAlias : AnyObject?
  do {
    try url.getResourceValue(&isAlias, forKey: NSURLIsAliasFileKey)
    if isAlias as! Bool {
      do {
        let original = try NSURL(byResolvingAliasFileAtURL: url, options: NSURLBookmarkResolutionOptions())
        return original.path!
      } catch let error as NSError {
        print(error)
      }
    }
  } catch _ {}

  return nil
}

Swift 3:

func resolveFinderAlias(at url: URL) -> String? {
    do {
        let resourceValues = try url.resourceValues(forKeys: [.isAliasFileKey])
        if resourceValues.isAliasFile! {
            let original = try URL(resolvingAliasFileAt: url)
            return original.path
        }
    } catch  {
        print(error)
    }
    return nil
}

Be aware to provide appropriate entitlements if the function is called in a sandboxed environment.

vadian
  • 274,689
  • 30
  • 353
  • 361
  • Interesting solution. But NSURLBookmarkResolutionOptions is not availabe on 10.10 or new (gettin an error message) Also byResolvingAliasFileAtUrl will not working with version before 10.10, is it? – Silve2611 Oct 26 '15 at 15:37
  • 1
    `NSURLBookmarkResolutionOptions` is available since 10.6, `NSURL(byResolvingAliasFileAtURL:options:)` is available since 10.10 – vadian Oct 26 '15 at 21:04
  • @Silve2611: Perhaps you simply forgot `import Foundation`? – mklement0 Oct 26 '15 at 21:35
  • Great solution; note that Xcode playgrounds are apparently also sandboxed, so you can't try the function there, but it does work from a script with shebang line `#!/usr/bin/env swift`. It may make sense to pass `[ NSURLBookmarkResolutionOptions.WithoutUI , NSURLBookmarkResolutionOptions.WithoutMounting]` instead of `NSURLBookmarkResolutionOptions()`; quibble: since the function returns type `String?`, there's no point in using `!` in `return original.path!` – mklement0 Oct 26 '15 at 22:00
  • Great solution. I marked it as correct. But it would still be nice if someone could show the way to do it for 10.9. Im close ill keep trying. – Silve2611 Oct 26 '15 at 22:18
  • How exactly do you call the second function with the URL parameter type? Here's how I'm calling the function: let path = "/file/path/to/alias/file", let url = URL(fileURLWithPath: path), resolveFinderAlias(at: url) But it keeps giving me an error saying: – user2635911 Jun 14 '18 at 16:30
  • @user2635911 `let aliasOriginalPath = resolveFinderAlias(at: URL(fileURLWithPath:"/path/to/aliasfile"))` – vadian Jun 14 '18 at 16:33
  • Error Domain=NSCocoaErrorDomain Code=260 "The file “Application” couldn’t be opened because there is no such file." UserInfo={NSURL=/file/path/to/alias/file -- file:///file/path/to/swift/script/location, NSFilePath=file/path/to/swift/script/location//file/path/to/alias/file, NSUnderlyingError=0x7f91f4170630 {Error Domain=NSPOSIXErrorDomain Code=2 "No such file or directory"}} – user2635911 Jun 14 '18 at 16:35
  • For some reason, for the key NSFilePath, it is appending the file location of swift script and the location of the alias file path. And then swift complains that it cannot find this location (as it does not exist). – user2635911 Jun 14 '18 at 16:36
  • @user2635911 Of course you have to replace `"/path/to/aliasfile"` with the (real) full(!) path to the file to check. – vadian Jun 14 '18 at 16:37
  • Oh man I finally resolved the issue. The path to my alias file was missing a forward slash which is why I kept getting the error. Once I put the correct path in, it worked. – user2635911 Jun 14 '18 at 17:57
5

vadian's answer works great on OS X 10.10+.

Here's an implementation that also works on OS X 10.9:

// OSX 10.9+
// Resolves a Finder alias to its full target path.
// If the given path is not a Finder alias, its *own* full path is returned.
// If the input path doesn't exist or any other error occurs, nil is returned.
func resolveFinderAlias(path: String) -> String? {
  let fUrl = NSURL(fileURLWithPath: path)
  var targetPath:String? = nil
  if (fUrl.fileReferenceURL() != nil) { // item exists
    do {
        // Get information about the file alias.
        // If the file is not an alias files, an exception is thrown
        // and execution continues in the catch clause.
        let data = try NSURL.bookmarkDataWithContentsOfURL(fUrl)
        // NSURLPathKey contains the target path.
        let rv = NSURL.resourceValuesForKeys([ NSURLPathKey ], fromBookmarkData: data) 
        targetPath = rv![NSURLPathKey] as! String?
    } catch {
        // We know that the input path exists, but treating it as an alias 
        // file failed, so we assume it's not an alias file and return its
        // *own* full path.
        targetPath = fUrl.path
    }
  }
  return targetPath
}

Note:

  • Unlike vadian's solution, this will return a value even for non-alias files, namely that file's own full path, and takes a path string rather than a NSURL instance as input.

  • vadian's solution requires appropriate entitlements in order to use the function in a sandboxed application/environment. It seems that this one at least doesn't need that to the same extent, as it will run in an Xcode Playground, unlike vadian's solution. If someone can shed light on this, please help.

    • Either solution, however, does run in a shell script with shebang line #!/usr/bin/env swift.
  • If you want to explicitly test whether a given path is a Finder alias, see this answer, which is derived from vadian's, but due to its narrower focus also runs on 10.9.

Community
  • 1
  • 1
mklement0
  • 382,024
  • 64
  • 607
  • 775
  • Thank you for the great solution! You helped me a lot! Unfortunately I already marked one answer as correct because it was a working solution. This solution is even better! – Silve2611 Oct 27 '15 at 17:41
  • 1
    @Silve2611 Glad to hear it; please at least _up-vote_ vadian's answer, and perhaps state as an update in your question that you chose this answer due to the originally not stated requirement that the code run on 10.9 as well. – mklement0 Oct 27 '15 at 17:48
1

Here's a Swift 3 implementation, based largely on vadian's approach. My idea is to return a file URL, so I effectively combine it with fileURLWithPath. It's an NSURL class extension because I need to be able to call into it from existing Objective-C code:

extension NSURL {
    class func fileURL(path:String, resolveAlias yn:Bool) -> URL {
        let url = URL(fileURLWithPath: path)
        if !yn {
            return url
        }
        do {
            let vals = try url.resourceValues(forKeys: [.isAliasFileKey])
            if let isAlias = vals.isAliasFile {
                if isAlias {
                    let original = try URL(resolvingAliasFileAt: url)
                    return original
                }
            }
        } catch {
            return url // give up
        }
        return url // really give up
    }
}
matt
  • 515,959
  • 87
  • 875
  • 1,141
0

URL variant I need to return nil (not an alias or error) else original - Swift4

func resolvedFinderAlias() -> URL? {
    if (self.fileReferenceURL() != nil) { // item exists
        do {
            // Get information about the file alias.
            // If the file is not an alias files, an exception is thrown
            // and execution continues in the catch clause.
            let data = try NSURL.bookmarkData(withContentsOf: self as URL)
            // NSURLPathKey contains the target path.
            let rv = NSURL.resourceValues(forKeys: [ URLResourceKey.pathKey ], fromBookmarkData: data)
            var urlString = rv![URLResourceKey.pathKey] as! String
            if !urlString.hasPrefix("file://") {
                urlString = "file://" + urlString
            }
            return URL.init(string: urlString)
        } catch {
            // We know that the input path exists, but treating it as an alias
            // file failed, so we assume it's not an alias file so return nil.
            return nil
        }
    }
    return nil
}
slashlos
  • 913
  • 9
  • 17