Skip to content

Instantly share code, notes, and snippets.

@pommdau
Created October 3, 2023 11:14
Show Gist options
  • Save pommdau/e30413284f9c37daa0b572fd76c29205 to your computer and use it in GitHub Desktop.
Save pommdau/e30413284f9c37daa0b572fd76c29205 to your computer and use it in GitHub Desktop.

struct to get information about a file using FileAttributeKey

import Foundation

struct FileAttributesManager {
    
    // MARK: - Error Define
    
    enum FileAttributesError: Error {
        case typeCastingError
    }
    
    // MARK: - General
    
    static private var fileManager: FileManager = .default
    
    static private func loadAttributes(url: URL) throws -> [FileAttributeKey : Any] {
        try fileManager.attributesOfItem(atPath: url.path)
    }
}

// MARK: - Parameters
// [FileAttributeKey](https://developer.apple.com/documentation/foundation/fileattributekey)
// GitHub Copiliotで自動出力したメソッドもあるので、動かない物がある場合はご指摘ください

extension FileAttributesManager {
        
    // ファイルが読み取り専用かどうかを示す値
    // The key in a file attribute dictionary whose value indicates whether the file is read-only.
    static func appendOnly(url: URL) throws -> Bool {
        let attributes = try loadAttributes(url: url)
        guard let appendOnly = attributes[.appendOnly] as? NSNumber else {
            throw FileAttributesError.typeCastingError
        }
        return appendOnly.boolValue
    }
    
    // ファイルが使用中かどうかを示す値
    // The key in a file attribute dictionary whose value indicates whether the file is busy.
    static func busy(url: URL) throws -> Bool {
        let attributes = try loadAttributes(url: url)
        guard let busy = attributes[.busy] as? NSNumber else {
            throw FileAttributesError.typeCastingError
        }
        return busy.boolValue
    }
    
    // ファイルの作成日時
    // The key in a file attribute dictionary whose value indicates the file's creation date.
    static func creationDate(url: URL) throws -> Date {
        let attributes = try loadAttributes(url: url)
        guard let creationDate = attributes[.creationDate] as? Date else {
            throw FileAttributesError.typeCastingError
        }
        return creationDate
    }
    
    // ファイルが存在するデバイスの識別子
    // The key in a file attribute dictionary whose value indicates the identifier for the device on which the file resides.
    static func deviceIdentifier(url: URL) throws -> UInt {
        let attributes = try loadAttributes(url: url)
        guard let deviceIdentifier = attributes[.deviceIdentifier] as? NSNumber else {
            throw FileAttributesError.typeCastingError
        }
        return deviceIdentifier.uintValue
    }
    
    // ファイルの拡張子が非表示かどうかを示す値
    // The key in a file attribute dictionary whose value indicates whether the file’s extension is hidden.
    static func extensionHidden(url: URL) throws -> Bool {
        let attributes = try loadAttributes(url: url)
        guard let extensionHidden = attributes[.extensionHidden] as? NSNumber else {
            throw FileAttributesError.typeCastingError
        }
        return extensionHidden.boolValue
    }
    
    // ファイルのグループID
    // The key in a file attribute dictionary whose value indicates the file’s group ID.
    static func groupOwnerAccountID(url: URL) throws -> UInt {
        let attributes = try loadAttributes(url: url)
        guard let groupOwnerAccountID = attributes[.groupOwnerAccountID] as? NSNumber else {
            throw FileAttributesError.typeCastingError
        }
        return groupOwnerAccountID.uintValue
    }
    
    // ファイルの所有者のグループ名
    // The key in a file attribute dictionary whose value indicates the group name of the file’s owner.
    static func groupOwnerAccountName(url: URL) throws -> String {
        let attributes = try loadAttributes(url: url)
        guard let groupOwnerAccountName = attributes[.groupOwnerAccountName] as? String else {
            throw FileAttributesError.typeCastingError
        }
        return groupOwnerAccountName
    }
    
    // ファイルのHFSクリエイターコード
    // The key in a file attribute dictionary whose value indicates the file’s HFS creator code.
    static func hfsCreatorCode(url: URL) throws -> OSType {
        let attributes = try loadAttributes(url: url)
        guard let hfsCreatorCode = attributes[.hfsCreatorCode] as? NSNumber else {
            throw FileAttributesError.typeCastingError
        }
        return hfsCreatorCode.uint32Value
    }
    
    // ファイルのHFSタイプコード
    // The key in a file attribute dictionary whose value indicates the file’s HFS type code.
    static func hfsTypeCode(url: URL) throws -> OSType {
        let attributes = try loadAttributes(url: url)
        guard let hfsTypeCode = attributes[.hfsTypeCode] as? NSNumber else {
            throw FileAttributesError.typeCastingError
        }
        return hfsTypeCode.uint32Value
    }
    
    // ファイルが変更不可かどうかを示す値
    // The key in a file attribute dictionary whose value indicates whether the file is mutable.
    static func immutable(url: URL) throws -> Bool {
        let attributes = try loadAttributes(url: url)
        guard let immutable = attributes[.immutable] as? NSNumber else {
            throw FileAttributesError.typeCastingError
        }
        return immutable.boolValue
    }
    
    // ファイルの最終更新日時
    // The key in a file attribute dictionary whose value indicates the file’s last modified date.
    static func modificationDate(url: URL) throws -> Date {
        let attributes = try loadAttributes(url: url)
        guard let modificationDate = attributes[.modificationDate] as? Date else {
            throw FileAttributesError.typeCastingError
        }
        return modificationDate
    }
    
    // ファイルの所有者のアカウントID
    // The key in a file attribute dictionary whose value indicates the file’s owner's account ID.
    static func ownerAccountID(url: URL) throws -> UInt {
        let attributes = try loadAttributes(url: url)
        guard let ownerAccountID = attributes[.ownerAccountID] as? NSNumber else {
            throw FileAttributesError.typeCastingError
        }
        return ownerAccountID.uintValue
    }
    
    // ファイルの所有者の名前
    // The key in a file attribute dictionary whose value indicates the name of the file’s owner.
    static func ownerAccountName(url: URL) throws -> String {
        let attributes = try loadAttributes(url: url)
        guard let ownerAccountName = attributes[.ownerAccountName] as? String else {
            throw FileAttributesError.typeCastingError
        }
        return ownerAccountName
    }
    
    // ファイルのPosixパーミッション
    // The key in a file attribute dictionary whose value indicates the file’s Posix permissions.
    static func posixPermissions(url: URL) throws -> Int16 {
        let attributes = try loadAttributes(url: url)
        guard let posixPermissions = attributes[.posixPermissions] as? NSNumber else {
            throw FileAttributesError.typeCastingError
        }
        return posixPermissions.int16Value
    }
    
    // ファイルの保護レベルを識別する値
    // The key in a file attribute dictionary whose value identifies the protection level for this file.
    static func protectionKey(url: URL) throws -> String {
        let attributes = try loadAttributes(url: url)
        guard let protectionKey = attributes[.protectionKey] as? String else {
            throw FileAttributesError.typeCastingError
        }
        return protectionKey
    }
    
    // ファイルのリンク数
    // The key in a file attribute dictionary whose value indicates the file’s reference count.
    static func referenceCount(url: URL) throws -> UInt {
        let attributes = try loadAttributes(url: url)
        guard let referenceCount = attributes[.referenceCount] as? NSNumber else {
            throw FileAttributesError.typeCastingError
        }
        return referenceCount.uintValue
    }
    
    // ファイルサイズのバイト数
    // The key in a file attribute dictionary whose value indicates the file’s size in bytes.
    static func size(url: URL) throws -> UInt64 {
        let attributes = try loadAttributes(url: url)
        guard let size = attributes[.size] as? NSNumber else {
            throw FileAttributesError.typeCastingError
        }
        return size.uint64Value
    }
    
    // ファイルのファイルシステムファイル番号(inode番号)
    // The key in a file attribute dictionary whose value indicates the file’s filesystem file number.
    static func systemFileNumber(url: URL) throws -> UInt {
        let attributes = try loadAttributes(url: url)
        guard let systemFileNumber = attributes[.systemFileNumber] as? NSNumber else {
            throw FileAttributesError.typeCastingError
        }
        return systemFileNumber.uintValue
    }
    
    // ファイルシステムの空きノード数
    // The key in a file system attribute dictionary whose value indicates the number of free nodes in the file system.
    static func systemFreeNodes(url: URL) throws -> UInt64 {
        let attributes = try loadAttributes(url: url)
        guard let systemFreeNodes = attributes[.systemFreeNodes] as? NSNumber else {
            throw FileAttributesError.typeCastingError
        }
        return systemFreeNodes.uint64Value
    }
    
    // ファイルシステムの空き容量
    // The key in a file system attribute dictionary whose value indicates the amount of free space on the file system.
    static func systemFreeSize(url: URL) throws -> UInt64 {
        let attributes = try loadAttributes(url: url)
        guard let systemFreeSize = attributes[.systemFreeSize] as? NSNumber else {
            throw FileAttributesError.typeCastingError
        }
        return systemFreeSize.uint64Value
    }
    
    // ファイルシステムのノード数
    // The key in a file system attribute dictionary whose value indicates the number of nodes in the file system.
    static func systemNodes(url: URL) throws -> UInt64 {
        let attributes = try loadAttributes(url: url)
        guard let systemNodes = attributes[.systemNodes] as? NSNumber else {
            throw FileAttributesError.typeCastingError
        }
        return systemNodes.uint64Value
    }
    
    // ファイルシステムの番号(ファイルがあるデバイスのID)
    // The key in a file system attribute dictionary whose value indicates the filesystem number of the file system.
    static func systemNumber(url: URL) throws -> Int32 {
        let attributes = try loadAttributes(url: url)
        guard let systemNumber = attributes[.systemNumber] as? NSNumber else {
            throw FileAttributesError.typeCastingError
        }
        return systemNumber.int32Value
    }
    
    // ファイルシステムのサイズ
    // The key in a file system attribute dictionary whose value indicates the size of the file system.
    static func systemSize(url: URL) throws -> UInt64 {
        let attributes = try loadAttributes(url: url)
        guard let systemSize = attributes[.systemSize] as? NSNumber else {
            throw FileAttributesError.typeCastingError
        }
        return systemSize.uint64Value
    }
    
    // ファイルの種類
    // The key in a file attribute dictionary whose value indicates the file’s type.
    static func type(url: URL) throws -> String {
        let attributes = try loadAttributes(url: url)
        guard let type = attributes[.type] as? String else {
            throw FileAttributesError.typeCastingError
        }
        return type
    }
}
@pommdau
Copy link
Author

pommdau commented Oct 3, 2023

Example usage of FileAttributesManager

image

(Finder)
image

import SwiftUI

// MARK: - Models

struct FileData {
    let url: URL
    let size: UInt64
    let creationDate: Date
    let modificationDate: Date

    var dateFormatter: DateFormatter {
        let dateFormatter = DateFormatter()
        dateFormatter.dateStyle = .full
        dateFormatter.timeStyle = .short
        dateFormatter.locale = Locale(identifier: "ja_JP")
        return dateFormatter
    }
    
    var fileName: String {
        url.lastPathComponent
    }
    
    var createdAt: String {
        dateFormatter.string(from: creationDate)
    }
    
    var modifiedAt: String {
        dateFormatter.string(from: modificationDate)
    }
        
    init?(url: URL) {
        guard let size = try? FileAttributesManager.size(url: url),
              let creationDate =  try? FileAttributesManager.creationDate(url: url),
              let modificationDate = try? FileAttributesManager.modificationDate(url: url) else {
            return nil
        }
        self.url = url
        self.size = size
        self.creationDate = creationDate
        self.modificationDate = modificationDate
    }
}

// MARK: - Views

struct ContentView: View {
    
    @State private var fileData: FileData?
    
    let url = URL(fileURLWithPath: "/Users/ikeh/Downloads/InodeDemo/README.md")  // path-to-your-file
    
    var body: some View {
        
        VStack {
            if let fileData {
                FileDetailsView(fileData: fileData)
            } else {
                Text("Select a file")
            }
            selectButton()
        }
        .padding()
        .onAppear {
            if let fileData = FileData(url: url) {
                self.fileData = fileData
            }
        }
    }
    
    @ViewBuilder
    private func selectButton() -> some View {
        Button("Select...") {
            /// [SwiftUI macOSアプリでファイルを開く](https://genjiapp.com/blog/2021/09/06/swiftui-open-file.html)
            let nsOpenPanel = NSOpenPanel()
            nsOpenPanel.allowsMultipleSelection = false
            nsOpenPanel.canChooseDirectories = true
            nsOpenPanel.canChooseFiles = true
            if nsOpenPanel.runModal() == .OK,
               let url = nsOpenPanel.url,
               let fileData = FileData(url: url) {
                self.fileData = fileData
            }
        }
        .frame(maxWidth: .infinity, alignment: .trailing)
    }
}

struct FileDetailsView: View {
    
    let fileData: FileData
    
    var body: some View {
        VStack(alignment: .leading) {
            headerSection()
            Divider()
            generalInfoSection()
        }
    }
    
   
    @ViewBuilder
    private func headerSection() -> some View {
        VStack(alignment: .leading) {
            Text(fileData.fileName)
                .font(.headline)
                .frame(maxWidth: .infinity, alignment: .leading)
            Text("変更日: \(fileData.modifiedAt)")
                .foregroundStyle(.secondary)
        }
    }
    
    @ViewBuilder
    private func generalInfoSection() -> some View {
        Text("一般情報:")
            .padding(.bottom, 4)
        Group {
            Grid {
                GridRow {
                    Text("サイズ:")
                        .gridColumnAlignment(.trailing)
                    Text("\(fileData.size) バイト")
                        .gridColumnAlignment(.leading)
                }
                GridRow {
                    Text("場所:")
                        .gridColumnAlignment(.trailing)
                    Text("\(fileData.url.path)")
                        .gridColumnAlignment(.leading)
                }
                GridRow {
                    Text("作成日:")
                    Text(fileData.createdAt)
                }
                GridRow {
                    Text("更新日:")
                    Text(fileData.modifiedAt)
                }
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
            .frame(width: 600, height: 300)
    }
}

@pommdau
Copy link
Author

pommdau commented Oct 3, 2023

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment