import Foundation import UniformTypeIdentifiers /// 走査で見つかった画像ファイル1件。中身はまだ読んでいない。 struct ScannedFile: Hashable, Sendable { let url: URL let fileSize: Int64 let modificationDate: Date let creationDate: Date? } enum PhotoScanner { /// フォルダ配下の画像ファイルを列挙する。 /// /// UTType が `.image` に conform するものだけを拾うので、JPEG / PNG / HEIC / TIFF に加えて /// 各社の RAW も自動的に対象になる。写真ライブラリなどのパッケージは中に入らない。 static func scan(root: URL, includesSubfolders: Bool) throws -> [ScannedFile] { let keys: [URLResourceKey] = [ .isRegularFileKey, .contentTypeKey, .fileSizeKey, .contentModificationDateKey, .creationDateKey, ] var options: FileManager.DirectoryEnumerationOptions = [ .skipsHiddenFiles, .skipsPackageDescendants, ] if !includesSubfolders { options.insert(.skipsSubdirectoryDescendants) } guard let enumerator = FileManager.default.enumerator( at: root, includingPropertiesForKeys: keys, options: options ) else { throw ScanError.cannotEnumerate(root) } var results: [ScannedFile] = [] for case let url as URL in enumerator { try Task.checkCancellation() guard let values = try? url.resourceValues(forKeys: Set(keys)), values.isRegularFile == true, let type = values.contentType, type.conforms(to: .image), let size = values.fileSize, size > 0 else { continue } results.append( ScannedFile( url: url, fileSize: Int64(size), modificationDate: values.contentModificationDate ?? .distantPast, creationDate: values.creationDate ) ) } return results } } enum ScanError: LocalizedError { case cannotEnumerate(URL) var errorDescription: String? { switch self { case .cannotEnumerate(let url): return "「\(url.lastPathComponent)」の中身を読み取れませんでした。" } } }