Post

Replies

Boosts

Views

Activity

Reply to URL.checkResourceIsReachable() throws error if file is on FTP server and name contains special characters
FTP is fundamentally broken at the protocol level. You won’t be able to make non-ASCII file names work with FTP in the general case. Thanks for your comments. Regarding your link "On FTP", I was wondering about this: FTPS is FTP over TLS (aka SSL). While FTPS adds security to the protocol, which is very important, it still inherits many of FTP’s other problems. Personally I try to avoid this protocol. in particular the part "it still inherits many of FTP’s other problems". What are these other problems? At the beginning of the post you only mentioned privacy and security, but these are already fixed with FTPS according to you. Also do you have any clue why open(source.path, O_RDONLY) returns a valid file descriptor if source is a directory and if it's a regular file it returns -1 (see my previous post)?
Topic: App & System Services SubTopic: General Tags:
Apr ’24
Reply to How to link multiple text views to a single text storage in TextKit 2
Thanks! I have another question now: how can I link multiple NSTextContentStorage to the same NSTextStorage? The documentation for NSTextContentStorage reads: By default, the framework initializes the NSTextContentStorage with NSTextStorage as the backing store. But I don't see any way of accessing or setting the NSTextStorage. If I'm not supposed to do so, then how can I programmatically change the text?
Topic: App & System Services SubTopic: General Tags:
Apr ’24
Reply to NSMenu.popUp(positioning:at:in:) doesn't enable menu items when opened inside modal window
If you implement the method -(BOOL)worksWhenModal to return YES on the target object for your menu item, AppKit will allow the menu item to be validated, and enabled if the target implements the item's action. Still doesn't seem to work for the sample code I provided. I had to leave the menu item's target to nil to make it work, but then using a normal window was enough, without the need to implement worksWhenModal. This is in fact the workaround I just found. If I leave the menu item's target to nil and make sure that the view of the view controller or a subview is first responder, e.g. with override func viewDidAppear() { view.window?.makeFirstResponder(view) } then the menu item is enabled. In my case it doesn't work though, since the view controller is a child of another view controller and it's not guaranteed that the view of the view controller showing the menu (and implementing the menu item actions) is first responder (as the parent can have other views that can be first responders). As soon as I set the menu item's target to self, the menu items are disabled again.
Topic: UI Frameworks SubTopic: AppKit Tags:
Feb ’24
Reply to NSMenu.popUp(positioning:at:in:) doesn't enable menu items when opened inside modal window
However, if the app is in a modal state, AppKit skips over item validation and always disables the item. Thanks for your feedback. Indeed, it seems that all menu items targeting a window or view controller are automatically disabled, while menu items targeting the app delegate can still be enabled, as well as the standard copy, paste, etc. I think the correct behaviour should be to still enable menu items targeting the modal window itself. I think you may have reported this issue with Bug Reporter as FB9190141? Yes, I opened FB9190141 2.5 years ago but got no response, which is why I decided to ask here. It would be great if you could investigate it. Also to everyone else who might be tempted to use NSMenu.popUpContextMenu(_:with:for:): provide a made-up NSEvent and don't rely on NSApp.currentEvent being non-nil. When using VoiceOver for instance, it's nil.
Topic: UI Frameworks SubTopic: AppKit Tags:
Feb ’24
Reply to Get Application Scripts directory for app group
But NSUserScriptTask won’t find them there Then it's more like a limitation of how application scripts are designed to work, but I don't get it when you say that it doesn't make sense to have application scripts in an app group. App groups are meant to share data between apps, so why not application scripts as well? The fact that the Application Scripts directory is created automatically even for app groups adds to the confusion.
Topic: App & System Services SubTopic: General Tags:
Feb ’24
Reply to Making filecopy faster by changing block size
By the way, I just had to learn the hard way that this code var blockSize = UInt32(16_777_216) if copyfile_state_set(state, UInt32(COPYFILE_STATE_BSIZE), &blockSize) != 0 { throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno)) } always throws on macOS 11 or older with a POSIX error 22: Invalid argument regardless of the provided block size. There's no mention of this in the documentation and I assumed that it would just work, but got bug reports from users running older systems.
Topic: App & System Services SubTopic: General Tags:
Jan ’24
Reply to Making filecopy faster by changing block size
It’s important when the buffer size is less than the file size because it keeps all transfers aligned within the file, which facilitates uncached I/O. Good, that's what I was aiming at: use a block size equal to the file size up to a maximum power of 2. I don't think I was really trying to optimize the buffer allocation time; rather it didn't feel right to allocate an arbitrarily large buffer. After all, space is constrained and there is a reason why we can specify the block size, or it would automatically be set to infinity... right? it’s time to benchmark and use that to guide your optimisation Here are my results when copying a file with a given size using varying block sizes and 5 repetitions. (Explicitly writing random data to the file makes the file creation quite slow, but simply writing a buffer with uninitialized data seems to work as well.) Different file sizes seem to follow a similar trend. Using a multiple of 1000 or a multiple of the page size also doesn't seem to make a difference in the overall trend. The lowest block sizes, 1'024 and 2'048, seem to be special and cause a very fast copy. From 4'096 upwards, the time decreases... ...up to 65'536, where it suddenly increases again, but from then it definitely decreases. The bigger the file, the higher the block size needs to be to notice a difference. With a 100 MB file, increasing the block size from 1'048'576 to 2'097'152 makes the operation about twice as fast, with little improvements above that block size. With a 1 GB file, increasing the block size from 1'048'576 to 8'388'608 makes the operation about twice as fast, with little improvements above that block size. Without using F_NOCACHE, the operation gets slowly faster when increasing the block size from 1'048'576, and then gets slower again from 8'388'608 upwards. Not sure if that means anything. Here are the graphs for a 100 MB and a 1 GB file. Copying a 100 MB file: Copying a 1 GB file: Copying a 100 MB file created without F_NOCACHE: And here is the code: class AppDelegate: NSObject, NSApplicationDelegate { func applicationDidFinishLaunching(_ aNotification: Notification) { print("page size", getpagesize()) let openPanel = NSOpenPanel() openPanel.canChooseDirectories = true openPanel.runModal() test(url: openPanel.urls[0]) } func test(url: URL) { let source = url.appendingPathComponent("file source") let destination = url.appendingPathComponent("file destination") let fileSizes = [1_000, 1_000_000, 10_000_000, 100_000_000, 1_000_000_000, 10_000_000_000, Int(getpagesize()) * 10_000] let blockSizes: [Int32] = (10..<31).map({ 1 << $0 }) let repetitions = 5 var times = [[TimeInterval]](repeating: [TimeInterval](repeating: 0, count: repetitions), count: blockSizes.count) for fileSize in fileSizes { print("fileSize", fileSize) for (i, blockSize) in blockSizes.enumerated() { print("blockSize", blockSize) let date = Date() for j in 0..<repetitions { try? FileManager.default.removeItem(at: destination) var date = Date() print("create", terminator: " ") createFile(source: source, size: fileSize) print(-date.timeIntervalSinceNow) date = Date() print("copy", terminator: " ") do { try copy(source: source, destination: destination, blockSize: blockSize) } catch { preconditionFailure(error.localizedDescription) } let time = -date.timeIntervalSinceNow times[i][j] = time print(time) } let average = -date.timeIntervalSinceNow / Double(repetitions) print("average copy", average) print() } let header = blockSizes.map({ NumberFormatter.localizedString(from: $0 as NSNumber, number: .decimal) }).joined(separator: "\t") try! Data(([header] + (0..<repetitions).map { j in (["\(j)"] + (0..<blockSizes.count).map { i in return timeToString(times[i][j]) }).joined(separator: "\t") }).joined(separator: "\n").utf8).write(to: url.appendingPathComponent("results \(fileSize).tsv")) } } func timeToString(_ time: TimeInterval) -> String { return String(format: "%.6f", time) } func createFile(source: URL, size: Int) { var buffer = UnsafeMutableRawBufferPointer.allocate(byteCount: size, alignment: Int(getpagesize())) // for i in 0..<size { // buffer[i] = UInt8.random(in: 0...255) // } let fp = fopen(source.path, "w") let success = fcntl(fileno(fp), F_NOCACHE, 1) assert(success == 0) let bytes = fwrite(buffer.baseAddress!, 1, size, fp) assert(bytes == size) fclose(fp) } func copy(source: URL, destination: URL, blockSize: Int32) throws { try source.withUnsafeFileSystemRepresentation { sourcePath in try destination.withUnsafeFileSystemRepresentation { destinationPath in let state = copyfile_state_alloc() defer { copyfile_state_free(state) } var blockSize = blockSize if copyfile_state_set(state, UInt32(COPYFILE_STATE_BSIZE), &blockSize) != 0 || copyfile_state_set(state, UInt32(COPYFILE_STATE_STATUS_CB), unsafeBitCast(copyfileCallback, to: UnsafeRawPointer.self)) != 0 || copyfile_state_set(state, UInt32(COPYFILE_STATE_STATUS_CTX), unsafeBitCast(self, to: UnsafeRawPointer.self)) != 0 || copyfile(sourcePath, destinationPath, state, copyfile_flags_t(COPYFILE_ALL | COPYFILE_NOFOLLOW | COPYFILE_EXCL)) != 0 { throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno)) } } } } private let copyfileCallback: copyfile_callback_t = { what, stage, state, src, dst, ctx in if what == COPYFILE_COPY_DATA { if stage == COPYFILE_ERR { return COPYFILE_QUIT } } return COPYFILE_CONTINUE } }
Topic: App & System Services SubTopic: General Tags:
Jan ’24
Reply to URL.checkResourceIsReachable() throws error if file is on FTP server and name contains special characters
FTP is fundamentally broken at the protocol level. You won’t be able to make non-ASCII file names work with FTP in the general case. Thanks for your comments. Regarding your link "On FTP", I was wondering about this: FTPS is FTP over TLS (aka SSL). While FTPS adds security to the protocol, which is very important, it still inherits many of FTP’s other problems. Personally I try to avoid this protocol. in particular the part "it still inherits many of FTP’s other problems". What are these other problems? At the beginning of the post you only mentioned privacy and security, but these are already fixed with FTPS according to you. Also do you have any clue why open(source.path, O_RDONLY) returns a valid file descriptor if source is a directory and if it's a regular file it returns -1 (see my previous post)?
Topic: App & System Services SubTopic: General Tags:
Replies
Boosts
Views
Activity
Apr ’24
Reply to Select directory in SwiftUI file importer
Use .folder instead of .directory and your Open button will be there. Thanks! That works, but why? Even Apple‘s sample code in the official documentation uses ˋ.directoryˋ. Why does it only work on macOS by default?
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Replies
Boosts
Views
Activity
Apr ’24
Reply to iOS file tags API?
I've been wondering for a long time as well. Why is Apple not commenting on this?
Topic: App & System Services SubTopic: Core OS Tags:
Replies
Boosts
Views
Activity
Apr ’24
Reply to How to link multiple text views to a single text storage in TextKit 2
Thanks! I have another question now: how can I link multiple NSTextContentStorage to the same NSTextStorage? The documentation for NSTextContentStorage reads: By default, the framework initializes the NSTextContentStorage with NSTextStorage as the backing store. But I don't see any way of accessing or setting the NSTextStorage. If I'm not supposed to do so, then how can I programmatically change the text?
Topic: App & System Services SubTopic: General Tags:
Replies
Boosts
Views
Activity
Apr ’24
Reply to URL.checkResourceIsReachable() throws error if file is on FTP server and name contains special characters
After a few more tests, I noticed that, while try source.checkResourceIsReachable() throws an error and FileManager.default.fileExists(atPath: source.path) returns false, calling open(source.path, O_RDONLY) returns a valid file descriptor if source is a directory; if it's a regular file, it returns -1.
Topic: App & System Services SubTopic: General Tags:
Replies
Boosts
Views
Activity
Apr ’24
Reply to URL.checkResourceIsReachable() throws error if file is on FTP server and name contains special characters
Have you tried encoding the file name and creating url from it? No, since my app relies on the Finder to mount and connect to FTP volumes, so I can only use file URLs.
Topic: App & System Services SubTopic: General Tags:
Replies
Boosts
Views
Activity
Apr ’24
Reply to How to get upload progress when using "await urlSession.upload()"
What if I have multiple parallel upload operations and need to map the URLSession in the delegate method to a progress indicator? Would I need a separate actor to manage this mapping?
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Replies
Boosts
Views
Activity
Mar ’24
Reply to NSMenu.popUp(positioning:at:in:) doesn't enable menu items when opened inside modal window
If you implement the method -(BOOL)worksWhenModal to return YES on the target object for your menu item, AppKit will allow the menu item to be validated, and enabled if the target implements the item's action. Still doesn't seem to work for the sample code I provided. I had to leave the menu item's target to nil to make it work, but then using a normal window was enough, without the need to implement worksWhenModal. This is in fact the workaround I just found. If I leave the menu item's target to nil and make sure that the view of the view controller or a subview is first responder, e.g. with override func viewDidAppear() { view.window?.makeFirstResponder(view) } then the menu item is enabled. In my case it doesn't work though, since the view controller is a child of another view controller and it's not guaranteed that the view of the view controller showing the menu (and implementing the menu item actions) is first responder (as the parent can have other views that can be first responders). As soon as I set the menu item's target to self, the menu items are disabled again.
Topic: UI Frameworks SubTopic: AppKit Tags:
Replies
Boosts
Views
Activity
Feb ’24
Reply to NSMenu.popUp(positioning:at:in:) doesn't enable menu items when opened inside modal window
However, if the app is in a modal state, AppKit skips over item validation and always disables the item. Thanks for your feedback. Indeed, it seems that all menu items targeting a window or view controller are automatically disabled, while menu items targeting the app delegate can still be enabled, as well as the standard copy, paste, etc. I think the correct behaviour should be to still enable menu items targeting the modal window itself. I think you may have reported this issue with Bug Reporter as FB9190141? Yes, I opened FB9190141 2.5 years ago but got no response, which is why I decided to ask here. It would be great if you could investigate it. Also to everyone else who might be tempted to use NSMenu.popUpContextMenu(_:with:for:): provide a made-up NSEvent and don't rely on NSApp.currentEvent being non-nil. When using VoiceOver for instance, it's nil.
Topic: UI Frameworks SubTopic: AppKit Tags:
Replies
Boosts
Views
Activity
Feb ’24
Reply to Get Application Scripts directory for app group
But NSUserScriptTask won’t find them there Then it's more like a limitation of how application scripts are designed to work, but I don't get it when you say that it doesn't make sense to have application scripts in an app group. App groups are meant to share data between apps, so why not application scripts as well? The fact that the Application Scripts directory is created automatically even for app groups adds to the confusion.
Topic: App & System Services SubTopic: General Tags:
Replies
Boosts
Views
Activity
Feb ’24
Reply to Get Application Scripts directory for app group
Why do you need to get this directory? For sharing scripts between apps. Why does it make sense to share any other data, but not Application Scripts? I was hoping that I could reduce user confusion by putting all shared data, including scripts, in one place (the app group).
Topic: App & System Services SubTopic: General Tags:
Replies
Boosts
Views
Activity
Feb ’24
Reply to Warning when archiving product in Xcode with AppIntent extension
Occasionally this warning turns into a compiler error when trying to archive the product. Then only deleting the DerivedData folder, restarting Xcode and archiving again turns it into a warning again.
Replies
Boosts
Views
Activity
Feb ’24
Reply to Making filecopy faster by changing block size
By the way, I just had to learn the hard way that this code var blockSize = UInt32(16_777_216) if copyfile_state_set(state, UInt32(COPYFILE_STATE_BSIZE), &blockSize) != 0 { throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno)) } always throws on macOS 11 or older with a POSIX error 22: Invalid argument regardless of the provided block size. There's no mention of this in the documentation and I assumed that it would just work, but got bug reports from users running older systems.
Topic: App & System Services SubTopic: General Tags:
Replies
Boosts
Views
Activity
Jan ’24
Reply to Making filecopy faster by changing block size
And then I tested if copying a 1 KB file performs better with a 1'000 or 1'024 block size, and the first iteration of the whole test is always an outlier. Am I still doing something wrong?
Topic: App & System Services SubTopic: General Tags:
Replies
Boosts
Views
Activity
Jan ’24
Reply to Making filecopy faster by changing block size
It’s important when the buffer size is less than the file size because it keeps all transfers aligned within the file, which facilitates uncached I/O. Good, that's what I was aiming at: use a block size equal to the file size up to a maximum power of 2. I don't think I was really trying to optimize the buffer allocation time; rather it didn't feel right to allocate an arbitrarily large buffer. After all, space is constrained and there is a reason why we can specify the block size, or it would automatically be set to infinity... right? it’s time to benchmark and use that to guide your optimisation Here are my results when copying a file with a given size using varying block sizes and 5 repetitions. (Explicitly writing random data to the file makes the file creation quite slow, but simply writing a buffer with uninitialized data seems to work as well.) Different file sizes seem to follow a similar trend. Using a multiple of 1000 or a multiple of the page size also doesn't seem to make a difference in the overall trend. The lowest block sizes, 1'024 and 2'048, seem to be special and cause a very fast copy. From 4'096 upwards, the time decreases... ...up to 65'536, where it suddenly increases again, but from then it definitely decreases. The bigger the file, the higher the block size needs to be to notice a difference. With a 100 MB file, increasing the block size from 1'048'576 to 2'097'152 makes the operation about twice as fast, with little improvements above that block size. With a 1 GB file, increasing the block size from 1'048'576 to 8'388'608 makes the operation about twice as fast, with little improvements above that block size. Without using F_NOCACHE, the operation gets slowly faster when increasing the block size from 1'048'576, and then gets slower again from 8'388'608 upwards. Not sure if that means anything. Here are the graphs for a 100 MB and a 1 GB file. Copying a 100 MB file: Copying a 1 GB file: Copying a 100 MB file created without F_NOCACHE: And here is the code: class AppDelegate: NSObject, NSApplicationDelegate { func applicationDidFinishLaunching(_ aNotification: Notification) { print("page size", getpagesize()) let openPanel = NSOpenPanel() openPanel.canChooseDirectories = true openPanel.runModal() test(url: openPanel.urls[0]) } func test(url: URL) { let source = url.appendingPathComponent("file source") let destination = url.appendingPathComponent("file destination") let fileSizes = [1_000, 1_000_000, 10_000_000, 100_000_000, 1_000_000_000, 10_000_000_000, Int(getpagesize()) * 10_000] let blockSizes: [Int32] = (10..<31).map({ 1 << $0 }) let repetitions = 5 var times = [[TimeInterval]](repeating: [TimeInterval](repeating: 0, count: repetitions), count: blockSizes.count) for fileSize in fileSizes { print("fileSize", fileSize) for (i, blockSize) in blockSizes.enumerated() { print("blockSize", blockSize) let date = Date() for j in 0..<repetitions { try? FileManager.default.removeItem(at: destination) var date = Date() print("create", terminator: " ") createFile(source: source, size: fileSize) print(-date.timeIntervalSinceNow) date = Date() print("copy", terminator: " ") do { try copy(source: source, destination: destination, blockSize: blockSize) } catch { preconditionFailure(error.localizedDescription) } let time = -date.timeIntervalSinceNow times[i][j] = time print(time) } let average = -date.timeIntervalSinceNow / Double(repetitions) print("average copy", average) print() } let header = blockSizes.map({ NumberFormatter.localizedString(from: $0 as NSNumber, number: .decimal) }).joined(separator: "\t") try! Data(([header] + (0..<repetitions).map { j in (["\(j)"] + (0..<blockSizes.count).map { i in return timeToString(times[i][j]) }).joined(separator: "\t") }).joined(separator: "\n").utf8).write(to: url.appendingPathComponent("results \(fileSize).tsv")) } } func timeToString(_ time: TimeInterval) -> String { return String(format: "%.6f", time) } func createFile(source: URL, size: Int) { var buffer = UnsafeMutableRawBufferPointer.allocate(byteCount: size, alignment: Int(getpagesize())) // for i in 0..<size { // buffer[i] = UInt8.random(in: 0...255) // } let fp = fopen(source.path, "w") let success = fcntl(fileno(fp), F_NOCACHE, 1) assert(success == 0) let bytes = fwrite(buffer.baseAddress!, 1, size, fp) assert(bytes == size) fclose(fp) } func copy(source: URL, destination: URL, blockSize: Int32) throws { try source.withUnsafeFileSystemRepresentation { sourcePath in try destination.withUnsafeFileSystemRepresentation { destinationPath in let state = copyfile_state_alloc() defer { copyfile_state_free(state) } var blockSize = blockSize if copyfile_state_set(state, UInt32(COPYFILE_STATE_BSIZE), &blockSize) != 0 || copyfile_state_set(state, UInt32(COPYFILE_STATE_STATUS_CB), unsafeBitCast(copyfileCallback, to: UnsafeRawPointer.self)) != 0 || copyfile_state_set(state, UInt32(COPYFILE_STATE_STATUS_CTX), unsafeBitCast(self, to: UnsafeRawPointer.self)) != 0 || copyfile(sourcePath, destinationPath, state, copyfile_flags_t(COPYFILE_ALL | COPYFILE_NOFOLLOW | COPYFILE_EXCL)) != 0 { throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno)) } } } } private let copyfileCallback: copyfile_callback_t = { what, stage, state, src, dst, ctx in if what == COPYFILE_COPY_DATA { if stage == COPYFILE_ERR { return COPYFILE_QUIT } } return COPYFILE_CONTINUE } }
Topic: App & System Services SubTopic: General Tags:
Replies
Boosts
Views
Activity
Jan ’24