75 lines
2.4 KiB
Swift
75 lines
2.4 KiB
Swift
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)」の中身を読み取れませんでした。"
|
|
}
|
|
}
|
|
}
|