After successfully implementing a scan with getattrlistbulk
(thanks to https://developer.apple.com/forums/thread/656787/), I'm now trying for each file to get whether the extension is shown in the Finder or not. I think this could be read from the Finder Info (i.e.ATTR_CMN_FNDRINFO
), although I'm not sure, since there doesn't seem do be any documentation about what this attribute actually contains. From the setattrlist documentation archive it seems that it is char[32]
, and I've tried different changes within readUnaligned()
so that it would work with an array, but I couldn't get it to work.
Below is my current implementation. The comment How to get Finder Info?
shows where the implementation currently doesn't work.
class AppDelegate: NSObject, NSApplicationDelegate {
private static var attributeKeys: attrlist = {
var attributeKeys = attrlist()
attributeKeys.bitmapcount = u_short(ATTR_BIT_MAP_COUNT)
attributeKeys.commonattr = attrgroup_t(ATTR_CMN_RETURNED_ATTRS) | attrgroup_t(bitPattern: ATTR_CMN_ERROR | ATTR_CMN_NAME | ATTR_CMN_OBJTYPE | ATTR_CMN_MODTIME | ATTR_CMN_FNDRINFO | ATTR_CMN_FILEID)
attributeKeys.fileattr = attrgroup_t(bitPattern: ATTR_FILE_DATALENGTH)
return attributeKeys
}()
private let bufferWithAlignment16 = UnsafeMutableRawBufferPointer.allocate(byteCount: 256, alignment: 16)
func applicationDidFinishLaunching(_ notification: Notification) {
let openPanel = NSOpenPanel()
openPanel.canChooseDirectories = true
openPanel.canChooseFiles = false
openPanel.runModal()
try! scan(directoryPath: openPanel.urls[0].path)
}
deinit {
bufferWithAlignment16.deallocate()
}
func scan(directoryPath: String) throws {
let fileDescriptor = open(directoryPath, O_RDONLY)
if fileDescriptor < 0 {
throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno))
}
defer {
let result = close(fileDescriptor)
assert(result == 0)
}
let attributeBuffer = UnsafeMutableRawBufferPointer.allocate(byteCount: 2048, alignment: 16)
defer {
attributeBuffer.deallocate()
}
while true {
let itemCount = Int(getattrlistbulk(fileDescriptor, &AppDelegate.attributeKeys, attributeBuffer.baseAddress!, attributeBuffer.count, 0))
if itemCount == 0 {
return
} else if itemCount > 0 {
var entryOffset = attributeBuffer.baseAddress!
for _ in 0..<itemCount {
let length = Int(entryOffset.load(as: UInt32.self))
try unpackResources(at: entryOffset + MemoryLayout<UInt32>.size, parentDirectory: directoryPath)
entryOffset += length
}
} else if errno != EINTR {
throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno))
}
}
}
private func unpackResources(at attributeOffset: UnsafeMutableRawPointer, parentDirectory: String) throws {
var attributeOffset = attributeOffset
let returned = attributeOffset.load(as: attribute_set_t.self)
attributeOffset += MemoryLayout<attribute_set_t>.size
var error: Error?
if (returned.commonattr & attrgroup_t(bitPattern: ATTR_CMN_ERROR)) != 0 {
error = NSError(domain: NSPOSIXErrorDomain, code: Int(attributeOffset.load(as: UInt32.self)))
attributeOffset += MemoryLayout<UInt32>.size
}
let name: String
if (returned.commonattr & attrgroup_t(bitPattern: ATTR_CMN_NAME)) != 0 {
let nameInfo = attributeOffset.load(as: attrreference_t.self)
name = String(cString: (attributeOffset + Int(nameInfo.attr_dataoffset)).assumingMemoryBound(to: CChar.self))
attributeOffset += MemoryLayout<attrreference_t>.size
} else {
name = ""
}
let fileType: fsobj_type_t
if (returned.commonattr & attrgroup_t(bitPattern: ATTR_CMN_OBJTYPE)) != 0 {
fileType = attributeOffset.load(as: fsobj_type_t.self)
attributeOffset += MemoryLayout<fsobj_type_t>.size
} else {
fileType = VNON.rawValue
}
var modificationDate: Date?
if (returned.commonattr & attrgroup_t(bitPattern: ATTR_CMN_MODTIME)) != 0 {
modificationDate = Date(timeIntervalSince1970: TimeInterval(readUnaligned(pointer: attributeOffset, as: timespec.self).tv_sec))
attributeOffset += MemoryLayout<timespec>.size
}
// How to get Finder Info?
let finderInfo: [Int8]
if (returned.commonattr & attrgroup_t(bitPattern: ATTR_CMN_FNDRINFO)) != 0 {
finderInfo = readUnaligned(pointer: attributeOffset, as: [Int8].self)
attributeOffset += MemoryLayout<Int8>.size
} else {
finderInfo = []
}
let fileId: UInt64
if (returned.commonattr & attrgroup_t(bitPattern: ATTR_CMN_FILEID)) != 0 {
fileId = readUnaligned(pointer: attributeOffset, as: UInt64.self)
attributeOffset += MemoryLayout<UInt64>.size
} else {
fileId = 0
}
let size: Int64
if (returned.fileattr & attrgroup_t(bitPattern: ATTR_FILE_DATALENGTH)) != 0 {
size = Int64(readUnaligned(pointer: attributeOffset, as: off_t.self))
attributeOffset += MemoryLayout<off_t>.size
} else {
size = 0
}
if let error = error {
throw error
}
let isDirectory = fileType == VDIR.rawValue
let isSymbolicLink = fileType == VLNK.rawValue
print(name, isDirectory, isSymbolicLink, modificationDate as Any, finderInfo, fileId, size)
}
private func readUnaligned<Result>(pointer: UnsafeRawPointer, as: Result.Type) -> Result {
bufferWithAlignment16.copyMemory(from: UnsafeRawBufferPointer(start: pointer, count: MemoryLayout<Result>.size))
return bufferWithAlignment16.baseAddress!.load(as: Result.self)
}
}