I have a C function cmark_node *cmark_parse_file(FILE *f, int options); to which I'd like to pass in a FILE handle. I currently have the following code
public func parse(file: URL, options: Options) throws -> Node? {
var cFileHandle: UnsafeMutablePointer<FILE>?
defer {
if let cFile = cFileHandle {
fclose(cFile)
}
}
cFileHandle = try file.withUnsafeFileSystemRepresentation({ cFileUrl in
if cFileUrl == nil {
throw Errors.invalidFileUrl
}
return fopen(cFileUrl, "r")
})
if cFileHandle == nil {
throw Errors.invalidFile
}
return cmark_parse_file(cFileHandle, options.rawValue)
.map { Node(owned: $0) }
}
Here, I'm trying to
always close the file handle if opened
Avoid passing a null pointer to fopen - if the URL cannot be represented by a file system representation
Is there a way to simplify this code? - primarily around this bit
cFileHandle = try file.withUnsafeFileSystemRepresentation({ cFileUrl in
if cFileUrl == nil {
throw Errors.invalidFileUrl
}
return fopen(cFileUrl, "r")
})
if cFileHandle == nil {
throw Errors.invalidFile
}
Thank you. I didn't realize Swift would automatically bridge a String to C, and, we could add the defer after the guard. This is a lot simpler :)
Does url.path give the file system representation of the URL? I assume it would return an empty string in the event the URL doesn't really map to a file on the system?
Does url.path give the file system representation of the URL?
Yes.
This assumes that this is a file system URL. If you want to check for that, add the following:
guard self.isFileURL else {
… something …
}
The only potential gotcha here is with platforms that don’t use UTF-8 as their file system representation. That’s not an issue on Apple platforms or Linux. I’m less confident about Windows.
I assume it would return an empty string in the event the URL doesn't
really map to a file on the system?
I've found out (the hard way) that fopen(url.path, "r") fails (errno is "No such file or directory") if the URL contains space (that is being converted into %20 in a path). Meanwhile url.withUnsafeFileSystemRepresentation works flawlessly.
Ha. That's a good catch. And that's my fault after all.
Most likely the compiler said path is deprecated and I've accepted the suggested change to the path() which passes true for its percentEncoded parameter. With such a path, the fopen fails if there is a space in the URL.
The fopen seems to be happy with the output of the now deprecated path or path(percentEncoded: false). Providing true for the path(percentEncoded:) (or simple path()) makes the fopen to fail.
As for deprecation - as the path(percentEncoded:) has been available since macOS 13, I guess that's the point when the path was marked as deprecated.
These changes to URL made me stop using it for file paths and instead switch to Swift System’s FilePath. That doesn't handle anything besides file paths (no HTTP, etc.), but it does give better access to the platform-specific representation, including file paths on Windows.
Correct, Windows requires UTF-16 for the file path strings. Additionally, .path will not give the FSR, which is important for Windows API calls. You should use withUnsafeFileSystemRepresentation and then re-code the string to UTF-16 for maximal safety on Windows.
These changes to URL made me stop using it for file paths and
instead switch to Swift System’s FilePath.
There is a downside to that though. On Apple platforms a URL can carry a security scope and a FilePath can’t. Consider this example:
You’re in a sandboxed app and you call NSOpenPanel.
It gives you back a security-scoped URL.
You convert it to a FilePath as a matter of routine.
Later on you then try to access it. That requires you to convert the path back to a URL because you have to call startAccessingSecurityScopedResource(). That conversion works.
But then startAccessingSecurityScopedResource() fails because you lost the security scope in step 3.
compnerd wrote:
You should use withUnsafeFileSystemRepresentation
This is like déjà vu all over again (-:
It’s before my time, but my understanding is that Foundation added the ‘file system representation’ stuff to support Windows. After Yellow Box for Windows died, we all went back to just passing in strings. But now we have to be careful again!
Thanks for pointing it out! It does not apply to my current use case, but it might be relevant in the future. I wrapped FilePath anyway as AbsoluteFilePath and RelativeFilePath, so I should be able to swap the underlying storage for a URL if the need arises.