From 7bddeed04a7de414ceb562c5e8e4310a4aeb7e53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 19 Dec 2025 14:33:58 +0800 Subject: [PATCH] Add file provider for iOS --- .../FileProviderEnumerator.swift | 111 +++++++++ .../FileProviderExtension.entitlements | 10 + .../FileProviderExtension.swift | 213 ++++++++++++++++++ FileProviderExtension/FileProviderItem.swift | 101 +++++++++ FileProviderExtension/Info.plist | 17 ++ SFI/ApplicationDelegate.swift | 15 ++ sing-box.xcodeproj/project.pbxproj | 158 ++++++++++++- 7 files changed, 623 insertions(+), 2 deletions(-) create mode 100644 FileProviderExtension/FileProviderEnumerator.swift create mode 100644 FileProviderExtension/FileProviderExtension.entitlements create mode 100644 FileProviderExtension/FileProviderExtension.swift create mode 100644 FileProviderExtension/FileProviderItem.swift create mode 100644 FileProviderExtension/Info.plist diff --git a/FileProviderExtension/FileProviderEnumerator.swift b/FileProviderExtension/FileProviderEnumerator.swift new file mode 100644 index 0000000..2d8fa50 --- /dev/null +++ b/FileProviderExtension/FileProviderEnumerator.swift @@ -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) + } + } + } +} diff --git a/FileProviderExtension/FileProviderExtension.entitlements b/FileProviderExtension/FileProviderExtension.entitlements new file mode 100644 index 0000000..395b1b9 --- /dev/null +++ b/FileProviderExtension/FileProviderExtension.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.io.nekohasekai.sfavt + + + diff --git a/FileProviderExtension/FileProviderExtension.swift b/FileProviderExtension/FileProviderExtension.swift new file mode 100644 index 0000000..dc8dd6c --- /dev/null +++ b/FileProviderExtension/FileProviderExtension.swift @@ -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) + } +} diff --git a/FileProviderExtension/FileProviderItem.swift b/FileProviderExtension/FileProviderItem.swift new file mode 100644 index 0000000..3ffaeb6 --- /dev/null +++ b/FileProviderExtension/FileProviderItem.swift @@ -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 + } +} diff --git a/FileProviderExtension/Info.plist b/FileProviderExtension/Info.plist new file mode 100644 index 0000000..6f67c56 --- /dev/null +++ b/FileProviderExtension/Info.plist @@ -0,0 +1,17 @@ + + + + + NSExtension + + NSExtensionFileProviderDocumentGroup + group.io.nekohasekai.sfavt + NSExtensionFileProviderSupportsEnumeration + + NSExtensionPointIdentifier + com.apple.fileprovider-nonui + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).FileProviderExtension + + + diff --git a/SFI/ApplicationDelegate.swift b/SFI/ApplicationDelegate.swift index a9db138..8af1fd7 100644 --- a/SFI/ApplicationDelegate.swift +++ b/SFI/ApplicationDelegate.swift @@ -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)") + } } } diff --git a/sing-box.xcodeproj/project.pbxproj b/sing-box.xcodeproj/project.pbxproj index 4f33d5d..7f5a711 100644 --- a/sing-box.xcodeproj/project.pbxproj +++ b/sing-box.xcodeproj/project.pbxproj @@ -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 = ""; }; 3ADDCEB42E8B723B009ACE1D /* SFI */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (3ADDCEBC2E8B723B009ACE1D /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = SFI; sourceTree = ""; }; 3ADDCEC02E8B7240009ACE1D /* SFM */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (3ADDCEC22E8B7240009ACE1D /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = SFM; sourceTree = ""; }; 3ADDCEC92E8B7243009ACE1D /* SFM.System */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (3ADDCECC2E8B7243009ACE1D /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = SFM.System; sourceTree = ""; }; @@ -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 = ""; @@ -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 = ""; @@ -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 = (