Files
2025-12-19 14:35:17 +08:00

112 lines
4.0 KiB
Swift

import FileProvider
class FileProviderEnumerator: NSObject, NSFileProviderEnumerator {
private let directoryURL: URL?
private let workingDirectory: URL
private let isWorkingSet: Bool
init(url: URL, workingDirectory: URL) {
directoryURL = url
self.workingDirectory = workingDirectory
isWorkingSet = false
super.init()
}
init(workingSet: Bool, workingDirectory: URL) {
directoryURL = nil
self.workingDirectory = workingDirectory
isWorkingSet = workingSet
super.init()
}
func invalidate() {}
func enumerateItems(for observer: NSFileProviderEnumerationObserver, startingAt _: NSFileProviderPage) {
guard !isWorkingSet else {
var allItems: [FileProviderItem] = []
enumerateRecursively(at: workingDirectory, into: &allItems)
observer.didEnumerate(allItems)
observer.finishEnumerating(upTo: nil)
return
}
guard let url = directoryURL else {
observer.finishEnumerating(upTo: nil)
return
}
let contents = (try? FileManager.default.contentsOfDirectory(
at: url,
includingPropertiesForKeys: [.isDirectoryKey, .fileSizeKey, .creationDateKey, .contentModificationDateKey],
options: [.skipsHiddenFiles]
)) ?? []
let items: [FileProviderItem] = contents.map { childURL in
let identifier = itemIdentifier(for: childURL)
return FileProviderItem(url: childURL, identifier: identifier, workingDirectory: workingDirectory)
}
observer.didEnumerate(items)
observer.finishEnumerating(upTo: nil)
}
func enumerateChanges(for observer: NSFileProviderChangeObserver, from _: NSFileProviderSyncAnchor) {
var allItems: [FileProviderItem] = []
if isWorkingSet {
enumerateRecursively(at: workingDirectory, into: &allItems)
} else if let url = directoryURL {
let contents = (try? FileManager.default.contentsOfDirectory(
at: url,
includingPropertiesForKeys: nil,
options: [.skipsHiddenFiles]
)) ?? []
allItems = contents.map { childURL in
let identifier = itemIdentifier(for: childURL)
return FileProviderItem(url: childURL, identifier: identifier, workingDirectory: workingDirectory)
}
}
observer.didUpdate(allItems)
observer.finishEnumeratingChanges(upTo: currentSyncAnchor(), moreComing: false)
}
func currentSyncAnchor(completionHandler: @escaping (NSFileProviderSyncAnchor?) -> Void) {
completionHandler(currentSyncAnchor())
}
// MARK: - Helpers
private func currentSyncAnchor() -> NSFileProviderSyncAnchor {
let timestamp = Date().timeIntervalSince1970
return NSFileProviderSyncAnchor(withUnsafeBytes(of: timestamp) { Data($0) })
}
private func itemIdentifier(for url: URL) -> NSFileProviderItemIdentifier {
let relativePath = url.path.replacingOccurrences(of: workingDirectory.path + "/", with: "")
if relativePath == url.path || relativePath.isEmpty {
return .rootContainer
}
return NSFileProviderItemIdentifier(relativePath)
}
private func enumerateRecursively(at url: URL, into items: inout [FileProviderItem]) {
guard let contents = try? FileManager.default.contentsOfDirectory(
at: url,
includingPropertiesForKeys: [.isDirectoryKey],
options: [.skipsHiddenFiles]
) else { return }
for childURL in contents {
let identifier = itemIdentifier(for: childURL)
items.append(FileProviderItem(url: childURL, identifier: identifier, workingDirectory: workingDirectory))
var isDir: ObjCBool = false
if FileManager.default.fileExists(atPath: childURL.path, isDirectory: &isDir), isDir.boolValue {
enumerateRecursively(at: childURL, into: &items)
}
}
}
}