Swift InputStream.hasBytesAvailable() returns false when reading a file on the desktop in swift how do I fix this?

I'm creating a Swift finder sync extension that needs to read the file data into an array to send to an api but it won't read it into the array because InputStream.hasBytesAvailable() returns false so won't enter the for loop. my app requires the app sandbox which iv'e got "User Selected Files" and "Downloads" folder given read/ write access. I've also given the app full disk access in my system settings.

This is my block of code causing the error specifically at the start of the While loop.

let filePath = "/Users/<user>/Desktop/film.mp4"

  guard let inputStream = InputStream(fileAtPath: filePath) else {
            print("Failed to create input stream")
            return
        }
        // This dictates how many bytes are in each packet it must be a multiple of 327,680
        let packetSize = 3276800
        inputStream.open()
        
        var buffer = [UInt8](repeating: 0, count: packetSize)

        // Write data to bytesArray
        while inputStream.hasBytesAvailable{
            let bytesRead = inputStream.read(&buffer, maxLength: buffer.count)
            
            if bytesRead < 0 {
                print("Failed to read from input stream: \(inputStream.streamError?.localizedDescription ?? "unknown error")")
                break
            } else if bytesRead == 0 {
                print("End of input stream reached")
                break
            } else {
                // Process the bytes that were read
                let data = Data(bytes: buffer, count: bytesRead)
                
                bytesArray.append(data)
            }
        }
        
        inputStream.close()

these errors are printed in the console when i hit the button:

  • open flag(s) 0x01000000 are reserved for VFS use and do not affect behaviour when passed to sqlite3_open_v2
  • cannot open file at line 46922 of [554764a6e7]
  • os_unix.c:46922: (0) open(/private/var/db/DetachedSignatures) - Undefined error: 0

I have tried deleting the the app sandbox which can stop access to some files but when my app builds it doesn't run properly and my option doesn't appear in the context menu and no setup logs are printed to the console.

It works in my first app that doesn't have an app sandbox and isn't split over two targets.

Any help is much appreciated

Accepted Reply

Does anyone know if this is correct?

You are correct that:

  • Finder Sync extensions must be sandboxed.

  • Sandboxed apps (and appexes) can’t access resources outside of their sandbox.

  • The temporary exception entitlements allow you to bypass that restriction.

However, whether this is the right approach depends on the context. If you plan to ship your Finder Sync extension to the Mac App Store, it’s definitely not the right approach. My experience is that App Review takes a dim view of any use of these temporary exception entitlements.

As to what else you can do, it depends on the overall structure of your extension and its container app. The standard pattern for a Finder Sync extension is for the container app to ask the user for the directory in which they want operate. That yields a sandbox extension allowing the app to use that. The app then passes that privilege along to the appex.

For more background on this, see On File System Permissions.

ps Swift is moving away from InputStream for reading files. The better option these days is FileHandle. Having said that, whatever API you use will having this problem.

Share and Enjoy

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

Replies

I think I may need to add com.apple.security.temporary-exception.files.home-relative-path.read-only to my .extensions file to let my app be able to access my home folder. It is a finder sync extension so needs to be sandboxed and this looks like the way to get around it.

Does anyone know if this is correct?

Thanks

Does anyone know if this is correct?

You are correct that:

  • Finder Sync extensions must be sandboxed.

  • Sandboxed apps (and appexes) can’t access resources outside of their sandbox.

  • The temporary exception entitlements allow you to bypass that restriction.

However, whether this is the right approach depends on the context. If you plan to ship your Finder Sync extension to the Mac App Store, it’s definitely not the right approach. My experience is that App Review takes a dim view of any use of these temporary exception entitlements.

As to what else you can do, it depends on the overall structure of your extension and its container app. The standard pattern for a Finder Sync extension is for the container app to ask the user for the directory in which they want operate. That yields a sandbox extension allowing the app to use that. The app then passes that privilege along to the appex.

For more background on this, see On File System Permissions.

ps Swift is moving away from InputStream for reading files. The better option these days is FileHandle. Having said that, whatever API you use will having this problem.

Share and Enjoy

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

Thanks for the reply @eskimo!

So if I use NSOpenPanel() in my finder sync file and the user selects the home folder, will that give my app access to all sub folders and files and they won't have to give that permission again? Would you say this is a better approach than trying to use a temporary exception entitlements? I don't plan on shipping the app on the Mac App Store.

Also thanks for the notice about InputStream.

This is what i'm trying to use now but it just produces a very buggy and unusable window and I can't get the window to show when running it in override func beginObservingDirectory(at url: URL) {}

class FinderSync: FIFinderSync {
   override init() {
        super.init()
        
        // Check if there is a previously selected folder URL
           let defaults = UserDefaults.standard
           guard let selectedFolderURL = defaults.url(forKey: "selectedFolderURL") else {
               // Show the NSOpenPanel to select a folder
               let openPanel = NSOpenPanel()
               openPanel.canChooseDirectories = true
               openPanel.canChooseFiles = false
               openPanel.allowsMultipleSelection = false
               openPanel.prompt = "Select a folder"
               if openPanel.runModal() == .OK, let url = openPanel.urls.first {
                   // Store the selected folder URL
                   defaults.set(url, forKey: "selectedFolderURL")
               }
               return
           }
}

Thanks for your help.

So if I use NSOpenPanel in my finder sync

To be clear, your Finder Sync app extension can’t present an open panel. You’d have to do this in your container app. Most folks do that as part of an initial configuration step.

This is what i'm trying to use now but it just produces a very buggy and unusable window

Yep. That’s because you’re presenting it from your appex, which isn’t something we support.

the user selects the home folder, will that give my app access to all sub folders and files and they won't have to give that permission again?

Yes. And no (-: This design allows you to maintain a persistent sandbox extension. It won’t prevent the MAC checks, but those are relatively unobtrusive.

Would you say this is a better approach than trying to use a temporary exception entitlements? I don't plan on shipping the app on the Mac App Store.

If you’re not targeting the Mac App Store then there’s no real downside to using a temporary exception entitlement. You will, however, still encounter MAC checks (that’s the mandatory part of mandatory access control).

Share and Enjoy

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

How do i get this pop up to show up to show when i run the app for the first time?

“AAA” would like to access files in your Desktop folder.

[Don’t Allow] [OK]

In your thread it makes it seem like it should just appear when the application tries to access a file on the desktop but is there anything else i need to do to get this popup to appear?

Again thanks for the help :)

is there anything else i need to do to get this popup to appear?

No.

The main gotcha with the TCC MAC process is that the system has to be able to trace from the process doing the access to the responsible program. For a Finder Sync extension, where the process doing the access is an appex nested within a user-visible app, that should be pretty straightforward.

Share and Enjoy

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