Post

Replies

Boosts

Views

Activity

Reply to FileManager.enumerator and URL problem
Unfortunately, contentsOfDirectory(at:) suffers the same problem as the enumerator. But anyway, I devised a workaround that leads to a little more memory footprint. var fileList = [URL]() for case let fileURL as URL in it { fileList.append(fileURL) } // Sort by length so that the shortest comes first fileList.sort { $0.relativePath.count < $1.relativePath.count } // The shortest path without last component is the root path. let packageRoot = fileList[0].url.deletingLastPathComponent().path
Jan ’24
Reply to Reading multi parts of a file concurrently
It seems my conclusion about a single channel cannot handle multiple reading operations is not correct. Here is a much improved sample to demonstrate the final solution: import Cocoa let OneMebi = 1024 * 1024 class IoJobState { let name: String let signature: UInt8 let label: NSTextField! var total: UInt64 = 0 var eof = false var offset: off_t = 0 var length = 0 var buffer = Data() var done = false var dispatchDone = false var dispatchData: DispatchData? var dispatchError: Int32 = 0 init(_ name: String, label: NSTextField, signature: UInt8) { self.name = name self.label = label self.signature = signature } } class DispatchIoVC: NSViewController { @IBOutlet weak var label: NSTextField! @IBOutlet weak var label1: NSTextField! @IBOutlet weak var label2: NSTextField! override func viewDidLoad() { super.viewDidLoad() } let opQ = OperationQueue() /// Should not be used! let globalDispatchQ = DispatchQueue.global() /// Note: the .concurrent attribute is a must, otherwise the created queue is in serial mode. let dispatchQ = DispatchQueue(label: "test", qos: .utility, attributes: .concurrent) @IBAction func test_click(_ sender: Any) { let filePath = ("~/tmp/bigfile" as NSString).expandingTildeInPath label.stringValue = filePath var fh: Int32 = -1 let signatures: [UInt8] = [0x11, 0xef] repeat { fh = open(filePath, O_RDONLY) if fh >= 0 { break } fh = open(filePath, O_WRONLY | O_CREAT) if (fh < 0) { perror("Opening") label1.stringValue = "Unable to create file" return } let a = [UInt8](repeating: signatures[0], count: OneMebi) a.withUnsafeBytes { for _ in 0..<100 { write(fh, $0.baseAddress, a.count) } } let a2 = [UInt8](repeating: signatures[1], count: OneMebi) a2.withUnsafeBytes { for _ in 0..<90 { write(fh, $0.baseAddress, a.count) } write(fh, $0.baseAddress, 13) } close(fh) filePath.utf8CString.withUnsafeBytes { if 0 != chmod($0.baseAddress, 0o644) { perror("chmod") } } } while true let fmt = ByteCountFormatter() fmt.countStyle = .binary let size = lseek(fh, 0, SEEK_END) print("size:", fmt.string(fromByteCount: size)) lseek(fh, 0, SEEK_SET) let jobs = [IoJobState("job1", label: label1, signature: signatures[0]), IoJobState("job2", label: label2, signature: signatures[1])] jobs[0].offset = 0 jobs[0].length = Int(size) / 2 jobs[1].offset = off_t(jobs[0].length) jobs[1].length = .max let channel = DispatchIO(type: .random, fileDescriptor: fh, queue: dispatchQ) { error in if error == 0 { print("Good cleanup") } else { print("Bad cleanup \(error)") } close(fh) if unlink(filePath) == 0 { print("File successfully deleted") } else { perror("unlink") } } jobs.forEach { job in channel.read(offset: job.offset, length: job.length, queue: self.dispatchQ) { done, data, error in job.dispatchDone = done job.dispatchData = data job.dispatchError = error job.eof = job.dispatchDone && job.dispatchError == 0 && (job.dispatchData == nil || job.dispatchData!.isEmpty) if let data = job.dispatchData { job.total += UInt64(data.count) } OperationQueue.main.addOperation { upateUI(with: job) } if let data { job.buffer.reserveCapacity(data.count) job.buffer.withUnsafeMutableBytes { _ = data.copyBytes(to: $0) } Thread.sleep(forTimeInterval: TimeInterval.random(in: 0.01..<0.05)) if !job.buffer.allSatisfy({ $0 == job.signature }) { print("\(job.name): bad reading \(job.total)") } } } } func upateUI(with job: IoJobState) { if job.dispatchDone && !job.done { job.done = true print("\(job.name) done") } if let data = job.dispatchData { let byteString = fmt.string(fromByteCount: Int64(data.count)) let totalString = fmt.string(fromByteCount: Int64(job.total)) job.label.stringValue = "\(job.name): got \(byteString) bytes, total \(totalString) \(job.total)" } if job.dispatchError != 0 { print("\(job.name): got error code:", job.dispatchError) perror("") } } } }
Jan ’24
Reply to Reading multi parts of a file concurrently
Here is my experiment with DispatchIO, hope it may be helpful for those who are looking for a quick start. Note that - The code still uses one channel (DispatchIO object) for reading. The result is that most of the cases 2nd part finishes and reaches EOF and thus causes the first part not having chance to execute cleanup handler. import Cocoa class IoJobState { let name: String! let label: NSTextField! var total: UInt64 = 0 var eof = false var offset: off_t = 0 var length = 0 var buffer = Data() var done = false var data: DispatchData? var error: Int32 = 0 init(_ name: String, label: NSTextField) { self.name = name self.label = label } } class DispatchIoVC: NSViewController { @IBOutlet weak var label1: NSTextField! @IBOutlet weak var label2: NSTextField! override func viewDidLoad() { super.viewDidLoad() } let opQ = OperationQueue() /// Should not be used! let globalDispatchQ = DispatchQueue.global() /// Note: the .concurrent attribute is a must, otherwise the created queue is in serial mode. let dispatchQ = DispatchQueue(label: "test", qos: .utility, attributes: .concurrent) @IBAction func test_click(_ sender: Any) { let filePath = ("~/tmp/bigfile" as NSString).expandingTildeInPath print(filePath) let fh = open(filePath, O_RDONLY) if fh < 0 { let proc = Process() proc.executableURL = URL(fileURLWithPath: "/usr/bin/truncate") proc.arguments = ["-s", "5G", filePath] do { try proc.run() label1.stringValue = "Created \(filePath) of size 5GB, try again" } catch { print(error) } return } let fmt = ByteCountFormatter() fmt.countStyle = .binary let size = lseek(fh, 0, SEEK_END) print("size:", fmt.string(fromByteCount: size)) lseek(fh, 0, SEEK_SET) let jobs = [IoJobState("job1", label: label1), IoJobState("job2", label: label2)] // TODO: A single channel cannot be used to issue multiple read operation? let d = DispatchIO(type: .random, fileDescriptor: fh, queue: dispatchQ) { error in if error == 0 { print("All good") } else { print("Got error code:", error) } if jobs[0].eof && jobs[1].eof { print("Closing file") close(fh) try? FileManager.default.removeItem(atPath: filePath) } } opQ.addOperation { jobs[0].offset = 0 jobs[0].length = Int(size) / 2 jobs[1].offset = off_t(jobs[0].length) jobs[1].length = .max jobs.forEach { job in d.read(offset: job.offset, length: job.length, queue: self.dispatchQ) { done, data, error in job.done = done job.data = data job.error = error job.eof = job.done && job.error == 0 && (job.data == nil || job.data!.isEmpty) if let data = job.data { job.total += UInt64(data.count) } OperationQueue.main.addOperation { upateUI(job: job) } if !job.eof, let data { job.buffer.reserveCapacity(data.count) job.buffer.withUnsafeMutableBytes { _ = data.copyBytes(to: $0) } } } } } func upateUI(job: IoJobState ) { if job.eof { print("\(job.name!) done") } if let data = job.data { let byteString = fmt.string(fromByteCount: Int64(data.count)) let totalString = fmt.string(fromByteCount: Int64(job.total)) job.label.stringValue = "\(job.name!): got \(byteString) bytes, total \(totalString)" } if job.error != 0 { print("\(job.name!): got error code:", job.error) perror("") } } } }
Jan ’24
Reply to How display shortened path in a label?
That can be done quite easily. But what I want is a more 'strict' way. I'd like to know if Cocoa/Appket provide some low-level mechnisms to measure if a long string fit in a constrained rectangle when it's displayed in a graphics context (here, an NSTextField). When it's not fit entirely, I can abbreviate some path components to make the path string shorter.
Dec ’23
Reply to GridView addRow height problem
In IB designer, I am not even able to resize an added image view in first cell. The width and height is fixed at 32; if I try to modify the value, it automatically reverts back to 32. It seems GridView is performing some kind of invisible magic, which I don't have any knowledge of.
Dec ’23
Reply to GridView addRow height problem
I found out why, but it's not expected: imageView.frame = NSMakeRect(0, 0, 80, 80) let row = gridView.addRow(with: [imageView, label]) print(gridView.column(at: 0).width, row.height) // output // 80.0 1.1754943508222875e-38 Why the initial height is 1.175 for a newly added row (note I already resized image view to 80x80)? Even later on I explicitly set row.height = 80, but it does not work; the image view is squeezed.
Dec ’23
Reply to Is there any way to get static var's in a type using Mirror?
Well, it's partially true. Playground: print("class:") let m1 = Mirror(reflecting: Some.self) for child in m1.children { print(child.label ?? "", child.value) } print("instance:") let m2 = Mirror(reflecting: Some()) for child in m2.children { print(child.label ?? "", child.value) } print("\n-=|E.O.F|=-") class Some { static let inst = Some() private var n = 1234 var f = 1.234 } Output: class: instance: n 1234 f 1.234 Mirror can't mirror the static variable.
Dec ’23