Add file provider for iOS
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.io.nekohasekai.sfavt</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
import FileProvider
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
class FileProviderItem: NSObject, NSFileProviderItem {
|
||||
private let url: URL
|
||||
private let fileAttributes: [FileAttributeKey: Any]
|
||||
private let _identifier: NSFileProviderItemIdentifier
|
||||
private let _parentIdentifier: NSFileProviderItemIdentifier
|
||||
private let isRoot: Bool
|
||||
|
||||
init(rootAt url: URL) {
|
||||
self.url = url
|
||||
_identifier = .rootContainer
|
||||
_parentIdentifier = .rootContainer
|
||||
isRoot = true
|
||||
fileAttributes = (try? FileManager.default.attributesOfItem(atPath: url.path)) ?? [:]
|
||||
super.init()
|
||||
}
|
||||
|
||||
init(url: URL, identifier: NSFileProviderItemIdentifier, workingDirectory: URL) {
|
||||
self.url = url
|
||||
_identifier = identifier
|
||||
isRoot = false
|
||||
fileAttributes = (try? FileManager.default.attributesOfItem(atPath: url.path)) ?? [:]
|
||||
|
||||
let parentPath = url.deletingLastPathComponent().path
|
||||
if parentPath == workingDirectory.path {
|
||||
_parentIdentifier = .rootContainer
|
||||
} else {
|
||||
let relativePath = parentPath.replacingOccurrences(of: workingDirectory.path + "/", with: "")
|
||||
_parentIdentifier = NSFileProviderItemIdentifier(relativePath)
|
||||
}
|
||||
|
||||
super.init()
|
||||
}
|
||||
|
||||
var itemIdentifier: NSFileProviderItemIdentifier {
|
||||
_identifier
|
||||
}
|
||||
|
||||
var parentItemIdentifier: NSFileProviderItemIdentifier {
|
||||
_parentIdentifier
|
||||
}
|
||||
|
||||
var filename: String {
|
||||
if isRoot {
|
||||
return "sing-box"
|
||||
}
|
||||
return url.lastPathComponent
|
||||
}
|
||||
|
||||
var contentType: UTType {
|
||||
if isDirectory {
|
||||
return .folder
|
||||
}
|
||||
return UTType(filenameExtension: url.pathExtension) ?? .data
|
||||
}
|
||||
|
||||
var capabilities: NSFileProviderItemCapabilities {
|
||||
[
|
||||
.allowsReading,
|
||||
.allowsWriting,
|
||||
.allowsRenaming,
|
||||
.allowsReparenting,
|
||||
.allowsDeleting,
|
||||
.allowsAddingSubItems,
|
||||
.allowsContentEnumerating,
|
||||
]
|
||||
}
|
||||
|
||||
var itemVersion: NSFileProviderItemVersion {
|
||||
let modDate = fileAttributes[.modificationDate] as? Date ?? Date()
|
||||
let contentVersion = withUnsafeBytes(of: modDate.timeIntervalSince1970) { Data($0) }
|
||||
return NSFileProviderItemVersion(contentVersion: contentVersion, metadataVersion: contentVersion)
|
||||
}
|
||||
|
||||
var documentSize: NSNumber? {
|
||||
guard !isDirectory else { return nil }
|
||||
return fileAttributes[.size] as? NSNumber
|
||||
}
|
||||
|
||||
var creationDate: Date? {
|
||||
fileAttributes[.creationDate] as? Date
|
||||
}
|
||||
|
||||
var contentModificationDate: Date? {
|
||||
fileAttributes[.modificationDate] as? Date
|
||||
}
|
||||
|
||||
var childItemCount: NSNumber? {
|
||||
guard isDirectory else { return nil }
|
||||
let contents = try? FileManager.default.contentsOfDirectory(atPath: url.path)
|
||||
return NSNumber(value: contents?.count ?? 0)
|
||||
}
|
||||
|
||||
private var isDirectory: Bool {
|
||||
var isDir: ObjCBool = false
|
||||
FileManager.default.fileExists(atPath: url.path, isDirectory: &isDir)
|
||||
return isDir.boolValue
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionFileProviderDocumentGroup</key>
|
||||
<string>group.io.nekohasekai.sfavt</string>
|
||||
<key>NSExtensionFileProviderSupportsEnumeration</key>
|
||||
<true/>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.fileprovider-nonui</string>
|
||||
<key>NSExtensionPrincipalClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).FileProviderExtension</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,4 +1,5 @@
|
||||
import ApplicationLibrary
|
||||
import FileProvider
|
||||
import Foundation
|
||||
import Libbox
|
||||
import Library
|
||||
@@ -77,6 +78,20 @@ class ApplicationDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCe
|
||||
} catch {
|
||||
NSLog("setup profile server error: \(error.localizedDescription)")
|
||||
}
|
||||
registerFileProviderDomain()
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 16.0, *)
|
||||
private nonisolated func registerFileProviderDomain() {
|
||||
let domain = NSFileProviderDomain(
|
||||
identifier: NSFileProviderDomainIdentifier("io.nekohasekai.sfavt.workingdir"),
|
||||
displayName: "sing-box"
|
||||
)
|
||||
NSFileProviderManager.add(domain) { error in
|
||||
if let error {
|
||||
NSLog("Failed to add file provider domain: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,9 @@
|
||||
3A8655142A4FA26600B7181F /* IntentsExtension.appex in Embed ExtensionKit Extensions */ = {isa = PBXBuildFile; fileRef = 3A77016D2A4E6B34008F031F /* IntentsExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
3A9759202A4EB69C00E4404B /* Library.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AEC211D2A459B4700A63465 /* Library.framework */; };
|
||||
3A9759212A4EB69C00E4404B /* Library.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3AEC211D2A459B4700A63465 /* Library.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
3AAAFB222EF5218F004C69AD /* UniformTypeIdentifiers.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AAAFB212EF5218F004C69AD /* UniformTypeIdentifiers.framework */; };
|
||||
3AAAFB2E2EF5218F004C69AD /* FileProviderExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 3AAAFB202EF5218F004C69AD /* FileProviderExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
3AAAFE172EF52772004C69AD /* FileProvider.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AAAFE162EF52772004C69AD /* FileProvider.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
|
||||
3AC194492A50013F00BD8CB9 /* IntentsExtension.appex in Embed ExtensionKit Extensions */ = {isa = PBXBuildFile; fileRef = 3A77016D2A4E6B34008F031F /* IntentsExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
3ACE5E032EE1A91300644196 /* CodeEditSourceEditor in Frameworks */ = {isa = PBXBuildFile; productRef = 3ACE5E022EE1A91200644196 /* CodeEditSourceEditor */; };
|
||||
3AE1719A2A8128DD00393060 /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AF342B12A4AA520002B34AC /* NetworkExtension.framework */; };
|
||||
@@ -131,6 +134,13 @@
|
||||
remoteGlobalIDString = 3A77016C2A4E6B34008F031F;
|
||||
remoteInfo = IntentsExtension;
|
||||
};
|
||||
3AAAFB2C2EF5218F004C69AD /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 3AEC20BD2A45991900A63465 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 3AAAFB1F2EF5218F004C69AD;
|
||||
remoteInfo = FileProviderExtension;
|
||||
};
|
||||
3AC1944A2A50014000BD8CB9 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 3AEC20BD2A45991900A63465 /* Project object */;
|
||||
@@ -251,6 +261,7 @@
|
||||
files = (
|
||||
3AE396032C21A5CC00647718 /* WidgetExtension.appex in Embed Foundation Extensions */,
|
||||
3AEAEE992A4F16430059612D /* Extension.appex in Embed Foundation Extensions */,
|
||||
3AAAFB2E2EF5218F004C69AD /* FileProviderExtension.appex in Embed Foundation Extensions */,
|
||||
);
|
||||
name = "Embed Foundation Extensions";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -362,6 +373,9 @@
|
||||
3A3DEBE62A4FFA6000373BF4 /* AppIntents.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppIntents.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS17.0.sdk/System/Library/Frameworks/AppIntents.framework; sourceTree = DEVELOPER_DIR; };
|
||||
3A4EAD102A4FEAE6005435B3 /* ApplicationLibrary.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ApplicationLibrary.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3A77016D2A4E6B34008F031F /* IntentsExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.extensionkit-extension"; includeInIndex = 0; path = IntentsExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3AAAFB202EF5218F004C69AD /* FileProviderExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = FileProviderExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3AAAFB212EF5218F004C69AD /* UniformTypeIdentifiers.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UniformTypeIdentifiers.framework; path = System/Library/Frameworks/UniformTypeIdentifiers.framework; sourceTree = SDKROOT; };
|
||||
3AAAFE162EF52772004C69AD /* FileProvider.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FileProvider.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS26.2.sdk/System/Library/Frameworks/FileProvider.framework; sourceTree = DEVELOPER_DIR; };
|
||||
3ABA46D22A6A32A100D8366B /* Messages.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Messages.framework; path = Library/Frameworks/Messages.framework; sourceTree = DEVELOPER_DIR; };
|
||||
3AC03B962A72BF3300B7946F /* sing-box.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "sing-box.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3AC72A2E2EED94DD0039DEA4 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; };
|
||||
@@ -383,6 +397,13 @@
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||
3AAAFB322EF5218F004C69AD /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
|
||||
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||
membershipExceptions = (
|
||||
Info.plist,
|
||||
);
|
||||
target = 3AAAFB1F2EF5218F004C69AD /* FileProviderExtension */;
|
||||
};
|
||||
3ADDCEBC2E8B723B009ACE1D /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
|
||||
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||
membershipExceptions = (
|
||||
@@ -511,6 +532,7 @@
|
||||
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||
|
||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||
3AAAFB232EF5218F004C69AD /* FileProviderExtension */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (3AAAFB322EF5218F004C69AD /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = FileProviderExtension; sourceTree = "<group>"; };
|
||||
3ADDCEB42E8B723B009ACE1D /* SFI */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (3ADDCEBC2E8B723B009ACE1D /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = SFI; sourceTree = "<group>"; };
|
||||
3ADDCEC02E8B7240009ACE1D /* SFM */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (3ADDCEC22E8B7240009ACE1D /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = SFM; sourceTree = "<group>"; };
|
||||
3ADDCEC92E8B7243009ACE1D /* SFM.System */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (3ADDCECC2E8B7243009ACE1D /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = SFM.System; sourceTree = "<group>"; };
|
||||
@@ -554,6 +576,14 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
3AAAFB1D2EF5218F004C69AD /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
3AAAFB222EF5218F004C69AD /* UniformTypeIdentifiers.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
3AC03B932A72BF3300B7946F /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -586,6 +616,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
3AAAFE172EF52772004C69AD /* FileProvider.framework in Frameworks */,
|
||||
3A9759202A4EB69C00E4404B /* Library.framework in Frameworks */,
|
||||
3A4EAD372A4FEC20005435B3 /* ApplicationLibrary.framework in Frameworks */,
|
||||
3A2E87F22ED5A91100644195 /* Runestone in Frameworks */,
|
||||
@@ -608,7 +639,6 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
3A017F922A4AB2E4009149FA /* GRDB in Frameworks */,
|
||||
3AC72A312EED94F70039DEA4 /* SystemConfiguration.framework in Frameworks */,
|
||||
3A76504C2A4F08BA003945C5 /* Libbox.xcframework in Frameworks */,
|
||||
3A7E90382A46778E00D53052 /* BinaryCodable in Frameworks */,
|
||||
3AF3A3D22B2207F3001FD7C1 /* libresolv.tbd in Frameworks */,
|
||||
@@ -663,6 +693,7 @@
|
||||
3ADDCFC62E8B72B4009ACE1D /* IntentsExtension */,
|
||||
3ADDCFCD2E8B72B8009ACE1D /* TVExtension */,
|
||||
3ADDCFD52E8B72BC009ACE1D /* WidgetExtension */,
|
||||
3AAAFB232EF5218F004C69AD /* FileProviderExtension */,
|
||||
3AEC20C72A45991900A63465 /* Products */,
|
||||
3AEC21012A459AE300A63465 /* Frameworks */,
|
||||
);
|
||||
@@ -683,6 +714,7 @@
|
||||
3AC03B962A72BF3300B7946F /* sing-box.app */,
|
||||
3AE171992A8128DD00393060 /* TVExtension.appex */,
|
||||
3AE395F22C21A5CA00647718 /* WidgetExtension.appex */,
|
||||
3AAAFB202EF5218F004C69AD /* FileProviderExtension.appex */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@@ -690,6 +722,7 @@
|
||||
3AEC21012A459AE300A63465 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3AAAFE162EF52772004C69AD /* FileProvider.framework */,
|
||||
3AC72A2E2EED94DD0039DEA4 /* SystemConfiguration.framework */,
|
||||
3AC72A302EED94F60039DEA4 /* SystemConfiguration.framework */,
|
||||
3AF3A3D12B2207E1001FD7C1 /* libresolv.tbd */,
|
||||
@@ -699,6 +732,7 @@
|
||||
3ABA46D22A6A32A100D8366B /* Messages.framework */,
|
||||
3AE395F32C21A5CA00647718 /* WidgetKit.framework */,
|
||||
3AE395F52C21A5CA00647718 /* SwiftUI.framework */,
|
||||
3AAAFB212EF5218F004C69AD /* UniformTypeIdentifiers.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
@@ -790,6 +824,28 @@
|
||||
productReference = 3A77016D2A4E6B34008F031F /* IntentsExtension.appex */;
|
||||
productType = "com.apple.product-type.extensionkit-extension";
|
||||
};
|
||||
3AAAFB1F2EF5218F004C69AD /* FileProviderExtension */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 3AAAFB312EF5218F004C69AD /* Build configuration list for PBXNativeTarget "FileProviderExtension" */;
|
||||
buildPhases = (
|
||||
3AAAFB1C2EF5218F004C69AD /* Sources */,
|
||||
3AAAFB1D2EF5218F004C69AD /* Frameworks */,
|
||||
3AAAFB1E2EF5218F004C69AD /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
fileSystemSynchronizedGroups = (
|
||||
3AAAFB232EF5218F004C69AD /* FileProviderExtension */,
|
||||
);
|
||||
name = FileProviderExtension;
|
||||
packageProductDependencies = (
|
||||
);
|
||||
productName = FileProviderExtension;
|
||||
productReference = 3AAAFB202EF5218F004C69AD /* FileProviderExtension.appex */;
|
||||
productType = "com.apple.product-type.app-extension";
|
||||
};
|
||||
3AC03B952A72BF3300B7946F /* SFT */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 3AC03BA32A72BF3500B7946F /* Build configuration list for PBXNativeTarget "SFT" */;
|
||||
@@ -874,6 +930,7 @@
|
||||
3A8655162A4FA26600B7181F /* PBXTargetDependency */,
|
||||
3A4EAD3A2A4FEC20005435B3 /* PBXTargetDependency */,
|
||||
3AE396022C21A5CC00647718 /* PBXTargetDependency */,
|
||||
3AAAFB2D2EF5218F004C69AD /* PBXTargetDependency */,
|
||||
);
|
||||
fileSystemSynchronizedGroups = (
|
||||
3ADDCEB42E8B723B009ACE1D /* SFI */,
|
||||
@@ -1015,7 +1072,7 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = 1;
|
||||
LastSwiftUpdateCheck = 1600;
|
||||
LastSwiftUpdateCheck = 2620;
|
||||
LastUpgradeCheck = 1430;
|
||||
TargetAttributes = {
|
||||
3A096F852A4ED3DE00D4A2ED = {
|
||||
@@ -1029,6 +1086,9 @@
|
||||
3A77016C2A4E6B34008F031F = {
|
||||
CreatedOnToolsVersion = 15.0;
|
||||
};
|
||||
3AAAFB1F2EF5218F004C69AD = {
|
||||
CreatedOnToolsVersion = 26.2;
|
||||
};
|
||||
3AC03B952A72BF3300B7946F = {
|
||||
CreatedOnToolsVersion = 14.3.1;
|
||||
};
|
||||
@@ -1096,6 +1156,7 @@
|
||||
3AE171982A8128DD00393060 /* TVExtension */,
|
||||
3A77016C2A4E6B34008F031F /* IntentsExtension */,
|
||||
3AE395F12C21A5CA00647718 /* WidgetExtension */,
|
||||
3AAAFB1F2EF5218F004C69AD /* FileProviderExtension */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
@@ -1109,6 +1170,13 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
3AAAFB1E2EF5218F004C69AD /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
3AC03B942A72BF3300B7946F /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -1202,6 +1270,13 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
3AAAFB1C2EF5218F004C69AD /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
3AC03B922A72BF3300B7946F /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -1318,6 +1393,11 @@
|
||||
target = 3A77016C2A4E6B34008F031F /* IntentsExtension */;
|
||||
targetProxy = 3A8655152A4FA26600B7181F /* PBXContainerItemProxy */;
|
||||
};
|
||||
3AAAFB2D2EF5218F004C69AD /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 3AAAFB1F2EF5218F004C69AD /* FileProviderExtension */;
|
||||
targetProxy = 3AAAFB2C2EF5218F004C69AD /* PBXContainerItemProxy */;
|
||||
};
|
||||
3AC1944B2A50014000BD8CB9 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 3A77016C2A4E6B34008F031F /* IntentsExtension */;
|
||||
@@ -1632,6 +1712,71 @@
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
3AAAFB2F2EF5218F004C69AD /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_ENTITLEMENTS = FileProviderExtension/FileProviderExtension.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = 287TTNZF8L;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = FileProviderExtension/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = FileProviderExtension;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfavt.fileprovider;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
3AAAFB302EF5218F004C69AD /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_ENTITLEMENTS = FileProviderExtension/FileProviderExtension.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = 287TTNZF8L;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = FileProviderExtension/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = FileProviderExtension;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfavt.fileprovider;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
3AC03BA12A72BF3500B7946F /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
@@ -2482,6 +2627,15 @@
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
3AAAFB312EF5218F004C69AD /* Build configuration list for PBXNativeTarget "FileProviderExtension" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
3AAAFB2F2EF5218F004C69AD /* Debug */,
|
||||
3AAAFB302EF5218F004C69AD /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
3AC03BA32A72BF3500B7946F /* Build configuration list for PBXNativeTarget "SFT" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
|
||||
Reference in New Issue
Block a user