Add file provider for iOS
This commit is contained in:
@@ -0,0 +1,213 @@
|
||||
import FileProvider
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension {
|
||||
let domain: NSFileProviderDomain
|
||||
|
||||
private var workingDirectory: URL {
|
||||
let groupURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.io.nekohasekai.sfavt")!
|
||||
return groupURL
|
||||
.appendingPathComponent("Library", isDirectory: true)
|
||||
.appendingPathComponent("Caches", isDirectory: true)
|
||||
.appendingPathComponent("Working", isDirectory: true)
|
||||
}
|
||||
|
||||
required init(domain: NSFileProviderDomain) {
|
||||
self.domain = domain
|
||||
super.init()
|
||||
try? FileManager.default.createDirectory(at: workingDirectory, withIntermediateDirectories: true)
|
||||
}
|
||||
|
||||
func invalidate() {}
|
||||
|
||||
// MARK: - NSFileProviderReplicatedExtension
|
||||
|
||||
func item(for identifier: NSFileProviderItemIdentifier,
|
||||
request _: NSFileProviderRequest,
|
||||
completionHandler: @escaping (NSFileProviderItem?, Error?) -> Void) -> Progress
|
||||
{
|
||||
let progress = Progress(totalUnitCount: 1)
|
||||
|
||||
if identifier == .rootContainer {
|
||||
completionHandler(FileProviderItem(rootAt: workingDirectory), nil)
|
||||
} else {
|
||||
let url = fileURL(for: identifier)
|
||||
if FileManager.default.fileExists(atPath: url.path) {
|
||||
completionHandler(FileProviderItem(url: url, identifier: identifier, workingDirectory: workingDirectory), nil)
|
||||
} else {
|
||||
completionHandler(nil, NSFileProviderError(.noSuchItem))
|
||||
}
|
||||
}
|
||||
|
||||
progress.completedUnitCount = 1
|
||||
return progress
|
||||
}
|
||||
|
||||
func fetchContents(for itemIdentifier: NSFileProviderItemIdentifier,
|
||||
version _: NSFileProviderItemVersion?,
|
||||
request _: NSFileProviderRequest,
|
||||
completionHandler: @escaping (URL?, NSFileProviderItem?, Error?) -> Void) -> Progress
|
||||
{
|
||||
let progress = Progress(totalUnitCount: 100)
|
||||
|
||||
let url = fileURL(for: itemIdentifier)
|
||||
guard FileManager.default.fileExists(atPath: url.path) else {
|
||||
completionHandler(nil, nil, NSFileProviderError(.noSuchItem))
|
||||
return progress
|
||||
}
|
||||
|
||||
let item = FileProviderItem(url: url, identifier: itemIdentifier, workingDirectory: workingDirectory)
|
||||
completionHandler(url, item, nil)
|
||||
progress.completedUnitCount = 100
|
||||
|
||||
return progress
|
||||
}
|
||||
|
||||
func createItem(basedOn itemTemplate: NSFileProviderItem,
|
||||
fields _: NSFileProviderItemFields,
|
||||
contents url: URL?,
|
||||
options _: NSFileProviderCreateItemOptions,
|
||||
request _: NSFileProviderRequest,
|
||||
completionHandler: @escaping (NSFileProviderItem?, NSFileProviderItemFields, Bool, Error?) -> Void) -> Progress
|
||||
{
|
||||
let progress = Progress(totalUnitCount: 100)
|
||||
|
||||
let parentURL = fileURL(for: itemTemplate.parentItemIdentifier)
|
||||
let targetURL = parentURL.appendingPathComponent(itemTemplate.filename)
|
||||
|
||||
do {
|
||||
if let contentType = itemTemplate.contentType, contentType.conforms(to: .folder) {
|
||||
try FileManager.default.createDirectory(at: targetURL, withIntermediateDirectories: true)
|
||||
} else if let sourceURL = url {
|
||||
try FileManager.default.copyItem(at: sourceURL, to: targetURL)
|
||||
} else {
|
||||
FileManager.default.createFile(atPath: targetURL.path, contents: nil)
|
||||
}
|
||||
|
||||
let identifier = itemIdentifier(for: targetURL)
|
||||
let item = FileProviderItem(url: targetURL, identifier: identifier, workingDirectory: workingDirectory)
|
||||
completionHandler(item, [], false, nil)
|
||||
|
||||
} catch {
|
||||
completionHandler(nil, [], false, error)
|
||||
}
|
||||
|
||||
progress.completedUnitCount = 100
|
||||
return progress
|
||||
}
|
||||
|
||||
func modifyItem(_ item: NSFileProviderItem,
|
||||
baseVersion _: NSFileProviderItemVersion,
|
||||
changedFields: NSFileProviderItemFields,
|
||||
contents newContents: URL?,
|
||||
options _: NSFileProviderModifyItemOptions,
|
||||
request _: NSFileProviderRequest,
|
||||
completionHandler: @escaping (NSFileProviderItem?, NSFileProviderItemFields, Bool, Error?) -> Void) -> Progress
|
||||
{
|
||||
let progress = Progress(totalUnitCount: 100)
|
||||
|
||||
var currentURL = fileURL(for: item.itemIdentifier)
|
||||
var newIdentifier = item.itemIdentifier
|
||||
|
||||
do {
|
||||
if changedFields.contains(.filename) {
|
||||
let newURL = currentURL.deletingLastPathComponent().appendingPathComponent(item.filename)
|
||||
if currentURL != newURL {
|
||||
try FileManager.default.moveItem(at: currentURL, to: newURL)
|
||||
currentURL = newURL
|
||||
newIdentifier = itemIdentifier(for: newURL)
|
||||
}
|
||||
}
|
||||
|
||||
if changedFields.contains(.parentItemIdentifier) {
|
||||
let newParentURL = fileURL(for: item.parentItemIdentifier)
|
||||
let newURL = newParentURL.appendingPathComponent(currentURL.lastPathComponent)
|
||||
if currentURL != newURL {
|
||||
try FileManager.default.moveItem(at: currentURL, to: newURL)
|
||||
currentURL = newURL
|
||||
newIdentifier = itemIdentifier(for: newURL)
|
||||
}
|
||||
}
|
||||
|
||||
if changedFields.contains(.contents), let contentsURL = newContents {
|
||||
try FileManager.default.removeItem(at: currentURL)
|
||||
try FileManager.default.copyItem(at: contentsURL, to: currentURL)
|
||||
}
|
||||
|
||||
let resultItem = FileProviderItem(url: currentURL, identifier: newIdentifier, workingDirectory: workingDirectory)
|
||||
completionHandler(resultItem, [], false, nil)
|
||||
|
||||
} catch {
|
||||
completionHandler(nil, [], false, error)
|
||||
}
|
||||
|
||||
progress.completedUnitCount = 100
|
||||
return progress
|
||||
}
|
||||
|
||||
func deleteItem(identifier: NSFileProviderItemIdentifier,
|
||||
baseVersion _: NSFileProviderItemVersion,
|
||||
options _: NSFileProviderDeleteItemOptions,
|
||||
request _: NSFileProviderRequest,
|
||||
completionHandler: @escaping (Error?) -> Void) -> Progress
|
||||
{
|
||||
let progress = Progress(totalUnitCount: 1)
|
||||
|
||||
let url = fileURL(for: identifier)
|
||||
|
||||
do {
|
||||
if FileManager.default.fileExists(atPath: url.path) {
|
||||
try FileManager.default.removeItem(at: url)
|
||||
}
|
||||
completionHandler(nil)
|
||||
} catch {
|
||||
completionHandler(error)
|
||||
}
|
||||
|
||||
progress.completedUnitCount = 1
|
||||
return progress
|
||||
}
|
||||
|
||||
func enumerator(for containerItemIdentifier: NSFileProviderItemIdentifier,
|
||||
request _: NSFileProviderRequest) throws -> NSFileProviderEnumerator
|
||||
{
|
||||
if containerItemIdentifier == .workingSet {
|
||||
return FileProviderEnumerator(workingSet: true, workingDirectory: workingDirectory)
|
||||
}
|
||||
|
||||
if containerItemIdentifier == .trashContainer {
|
||||
throw NSFileProviderError(.noSuchItem)
|
||||
}
|
||||
|
||||
let url: URL
|
||||
if containerItemIdentifier == .rootContainer {
|
||||
url = workingDirectory
|
||||
} else {
|
||||
url = fileURL(for: containerItemIdentifier)
|
||||
}
|
||||
|
||||
guard FileManager.default.fileExists(atPath: url.path) else {
|
||||
throw NSFileProviderError(.noSuchItem)
|
||||
}
|
||||
|
||||
return FileProviderEnumerator(url: url, workingDirectory: workingDirectory)
|
||||
}
|
||||
|
||||
// MARK: - Helper Methods
|
||||
|
||||
private func fileURL(for identifier: NSFileProviderItemIdentifier) -> URL {
|
||||
if identifier == .rootContainer {
|
||||
return workingDirectory
|
||||
}
|
||||
let relativePath = identifier.rawValue
|
||||
return workingDirectory.appendingPathComponent(relativePath)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user