vinceglb / FileKit

Pick and save Files, Medias and Folder for Kotlin Multiplatform / KMP and Compose Multiplatform / CMP
https://vinceglb.github.io/FileKit/
MIT License
493 stars 15 forks source link

macOS - return unresolved symlinks #96

Closed StefanLobbenmeier closed 3 weeks ago

StefanLobbenmeier commented 4 weeks ago

I just tried to use this library to pick an executable from /opt/homebrew/bin

To my surprise, the library did not return /opt/homebrew/bin/ffmpeg, but /opt/homebrew/Cellar/ffmpeg/7.0.2/bin/ffmpeg

I briefly checked the libraries implementation and apple developers docs and from what I could see I would need to set this property "resolveAliases" to false: https://developer.apple.com/documentation/appkit/nsopenpanel/1533625-resolvesaliases. But this wrapper does not expose this.

Would you be open to override the apple default and pass false here for everyone? Or where could we add an extra property to expose this to the outside. I am not sure if similar properties exist for windows and linux.

StefanLobbenmeier commented 4 weeks ago

I could confirm via the debugger that this changes the return value to the unresolved symlink:

Foundation.invoke(openPanel, "setResolvesAliases:", false)

However, I would also like this behaviour for the initial directory, and that seems to still resolve the symlink if the initial directory is set to a file that is a symlink. Let me check if there is another option for that

StefanLobbenmeier commented 4 weeks ago

Spent some time trying to convince macOS to not resolve the directory I passed, but no avail. As a workaround, when the file is a symlink we can instead open at the parent directory.

For reference, this is the swiftUI code I used to play around with the settings:

import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundStyle(.tint)
            Button("Show details") {
                openDialog()
            }

        }
        .padding()
    }
}

func openDialog() {
    let openPanel = NSOpenPanel()

    // The path to the symlink
    let symlinkPath = "/opt/homebrew/bin/yt-dlp"

    var symlinkURL = URL(filePath: symlinkPath)

    // Set the directoryURL to the folder instead of the symlink
    openPanel.directoryURL = symlinkURL.deletingLastPathComponent()

    // Set the directoryURL to the symlink resolves the symlink, even if resolve aliases is false
    // openPanel.directoryURL = symlinkURL

    // Configure the open panel as needed
    openPanel.canChooseFiles = true
    openPanel.canChooseDirectories = false
    openPanel.allowsMultipleSelection = false
    openPanel.resolvesAliases = false

    // Present the open panel
    if openPanel.runModal() == .OK {
        if let selectedURL = openPanel.url {
            print("Selected file: \(selectedURL.path)")
        }
    }
}

#Preview {
    ContentView()
}
vinceglb commented 3 weeks ago

Thanks a lot for reporting this!

I reproduce your issue. To fix that, I will expose an extra property. I'm working on it!

StefanLobbenmeier commented 3 weeks ago

Sounds good :) as for the initial directory when set to a symlink, I also asked stackoverflow but didn’t get any actual response yet: https://stackoverflow.com/questions/78912708/macos-nsopenpanel-use-symlink-for-directoryurl

But I guess I will just manually check if it’s a symlink and pass the parent directory on macOS if that is the case

vinceglb commented 3 weeks ago

Now, FileKit exposes resolvesAliases property through FileKitPlatformSettings on JVM.

On JVM, you can now use the following code:

val platformSettings = FileKitPlatformSettings(
    macOS = FileKitMacOSSettings(
        resolvesAliases = false
    ),
)

FileKit.pickFile(
     ...
     platformSettings = platformSettings,
)

rememberFilePickerLauncher(
    ...
    platformSettings = platformSettings,
)

If your app is multiplatform, you can do the same. Create a expect val platformSettings: FileKitPlatformSettings from common code. On JVM, you can use the code above, and on the other platforms you can create an empty instance.

I'm going to publish a new version with the fix soon! Does this solve your first issue?

About your second one, let me know if you found a solution! If it's implementable, I'll try to add it to FileKit 👌

StefanLobbenmeier commented 3 weeks ago

Looking good, thanks 🙏 I will give this a try later

yeah I will let you know :)

StefanLobbenmeier commented 3 weeks ago

Just tried it out, works as expected 😄 many thanks ❤️