diff --git a/packages/path_provider/path_provider_foundation/CHANGELOG.md b/packages/path_provider/path_provider_foundation/CHANGELOG.md index 1c0ffee40bf..f8f1e75dcb3 100644 --- a/packages/path_provider/path_provider_foundation/CHANGELOG.md +++ b/packages/path_provider/path_provider_foundation/CHANGELOG.md @@ -1,5 +1,10 @@ +## 2.5.1 + +* Reverts to plugin-based implementation while FFI issues are investigated. + ## 2.5.0 +* **Retracted** due to production build issues. * Replaces Flutter-plugin-based implementation with direct FFI calls to Foundation. diff --git a/packages/path_provider/path_provider_foundation/CONTRIBUTING.md b/packages/path_provider/path_provider_foundation/CONTRIBUTING.md deleted file mode 100644 index e5c45fb48bf..00000000000 --- a/packages/path_provider/path_provider_foundation/CONTRIBUTING.md +++ /dev/null @@ -1,18 +0,0 @@ -# Contributing - -## `ffigen` - -This package uses [ffigen](https://pub.dev/packages/ffigen) to call Foundation -methods, rather than using the standard Flutter plugin structure. To add new -functionality to the FFI interface, update `tool/ffigen.dart`, then run: - -```bash -dart run tool/ffigen.dart -``` - -### Configuration philosophy - -This package intentionally uses very strict filtering rules to include only the -necessary methods and functions. This is partially to keep the package small, -but mostly to avoid unnecessarily generating anything that requires native code -helpers, which would require setting up a native compilation step. diff --git a/packages/path_provider/path_provider_foundation/darwin/RunnerTests/RunnerTests.swift b/packages/path_provider/path_provider_foundation/darwin/RunnerTests/RunnerTests.swift new file mode 100644 index 00000000000..608b7f05971 --- /dev/null +++ b/packages/path_provider/path_provider_foundation/darwin/RunnerTests/RunnerTests.swift @@ -0,0 +1,90 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import XCTest + +@testable import path_provider_foundation + +#if os(iOS) + import Flutter +#elseif os(macOS) + import FlutterMacOS +#endif + +class RunnerTests: XCTestCase { + func testGetTemporaryDirectory() throws { + let plugin = PathProviderPlugin() + let path = plugin.getDirectoryPath(type: .temp) + XCTAssertEqual( + path, + NSSearchPathForDirectoriesInDomains( + FileManager.SearchPathDirectory.cachesDirectory, + FileManager.SearchPathDomainMask.userDomainMask, + true + ).first) + } + + func testGetApplicationDocumentsDirectory() throws { + let plugin = PathProviderPlugin() + let path = plugin.getDirectoryPath(type: .applicationDocuments) + XCTAssertEqual( + path, + NSSearchPathForDirectoriesInDomains( + FileManager.SearchPathDirectory.documentDirectory, + FileManager.SearchPathDomainMask.userDomainMask, + true + ).first) + } + + func testGetApplicationSupportDirectory() throws { + let plugin = PathProviderPlugin() + let path = plugin.getDirectoryPath(type: .applicationSupport) + #if os(iOS) + // On iOS, the application support directory path should be just the system application + // support path. + XCTAssertEqual( + path, + NSSearchPathForDirectoriesInDomains( + FileManager.SearchPathDirectory.applicationSupportDirectory, + FileManager.SearchPathDomainMask.userDomainMask, + true + ).first) + #else + // On macOS, the application support directory path should be the system application + // support path with an added subdirectory based on the app name. + XCTAssert( + path!.hasPrefix( + NSSearchPathForDirectoriesInDomains( + FileManager.SearchPathDirectory.applicationSupportDirectory, + FileManager.SearchPathDomainMask.userDomainMask, + true + ).first!)) + XCTAssert(path!.hasSuffix("Example")) + #endif + } + + func testGetLibraryDirectory() throws { + let plugin = PathProviderPlugin() + let path = plugin.getDirectoryPath(type: .library) + XCTAssertEqual( + path, + NSSearchPathForDirectoriesInDomains( + FileManager.SearchPathDirectory.libraryDirectory, + FileManager.SearchPathDomainMask.userDomainMask, + true + ).first) + } + + func testGetDownloadsDirectory() throws { + let plugin = PathProviderPlugin() + let path = plugin.getDirectoryPath(type: .downloads) + XCTAssertEqual( + path, + NSSearchPathForDirectoriesInDomains( + FileManager.SearchPathDirectory.downloadsDirectory, + FileManager.SearchPathDomainMask.userDomainMask, + true + ).first) + } +} diff --git a/packages/path_provider/path_provider_foundation/darwin/path_provider_foundation.podspec b/packages/path_provider/path_provider_foundation/darwin/path_provider_foundation.podspec new file mode 100644 index 00000000000..5a20d27dfc6 --- /dev/null +++ b/packages/path_provider/path_provider_foundation/darwin/path_provider_foundation.podspec @@ -0,0 +1,26 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html +# +Pod::Spec.new do |s| + s.name = 'path_provider_foundation' + s.version = '0.0.1' + s.summary = 'An iOS and macOS implementation of the path_provider plugin.' + s.description = <<-DESC + An iOS and macOS implementation of the Flutter plugin for getting commonly used locations on the filesystem. + DESC + s.homepage = 'https://github.com/flutter/packages/tree/main/packages/path_provider/path_provider_foundation' + s.license = { :type => 'BSD', :file => '../LICENSE' } + s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } + s.source = { :http => 'https://github.com/flutter/packages/tree/main/packages/path_provider/path_provider_foundation' } + s.source_files = 'path_provider_foundation/Sources/path_provider_foundation/**/*.swift' + s.ios.dependency 'Flutter' + s.osx.dependency 'FlutterMacOS' + s.ios.deployment_target = '13.0' + s.osx.deployment_target = '10.15' + s.ios.xcconfig = { + 'LIBRARY_SEARCH_PATHS' => '$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)/ $(SDKROOT)/usr/lib/swift', + 'LD_RUNPATH_SEARCH_PATHS' => '/usr/lib/swift', + } + s.swift_version = '5.0' + s.resource_bundles = {'path_provider_foundation_privacy' => ['path_provider_foundation/Sources/path_provider_foundation/Resources/PrivacyInfo.xcprivacy']} +end diff --git a/packages/path_provider/path_provider_foundation/darwin/path_provider_foundation/Package.swift b/packages/path_provider/path_provider_foundation/darwin/path_provider_foundation/Package.swift new file mode 100644 index 00000000000..88e470e06a2 --- /dev/null +++ b/packages/path_provider/path_provider_foundation/darwin/path_provider_foundation/Package.swift @@ -0,0 +1,28 @@ +// swift-tools-version: 5.9 + +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import PackageDescription + +let package = Package( + name: "path_provider_foundation", + platforms: [ + .iOS("13.0"), + .macOS("10.15"), + ], + products: [ + .library(name: "path-provider-foundation", targets: ["path_provider_foundation"]) + ], + dependencies: [], + targets: [ + .target( + name: "path_provider_foundation", + dependencies: [], + resources: [ + .process("Resources") + ] + ) + ] +) diff --git a/packages/path_provider/path_provider_foundation/darwin/path_provider_foundation/Sources/path_provider_foundation/PathProviderPlugin.swift b/packages/path_provider/path_provider_foundation/darwin/path_provider_foundation/Sources/path_provider_foundation/PathProviderPlugin.swift new file mode 100644 index 00000000000..0dc61bc8d9a --- /dev/null +++ b/packages/path_provider/path_provider_foundation/darwin/path_provider_foundation/Sources/path_provider_foundation/PathProviderPlugin.swift @@ -0,0 +1,75 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import Foundation + +#if os(iOS) + import Flutter +#elseif os(macOS) + import FlutterMacOS +#endif + +public class PathProviderPlugin: NSObject, FlutterPlugin, PathProviderApi { + public static func register(with registrar: FlutterPluginRegistrar) { + let instance = PathProviderPlugin() + // Workaround for https://github.com/flutter/flutter/issues/118103. + #if os(iOS) + let messenger = registrar.messenger() + #else + let messenger = registrar.messenger + #endif + PathProviderApiSetup.setUp(binaryMessenger: messenger, api: instance) + } + + func getDirectoryPath(type: DirectoryType) -> String? { + var path = getDirectory(ofType: fileManagerDirectoryForType(type)) + #if os(macOS) + // In a non-sandboxed app, these are shared directories where applications are + // expected to use its bundle ID as a subdirectory. (For non-sandboxed apps, + // adding the extra path is harmless). + // This is not done for iOS, for compatibility with older versions of the + // plugin. + if type == .applicationSupport || type == .applicationCache { + if let basePath = path { + let basePathURL = URL.init(fileURLWithPath: basePath) + path = basePathURL.appendingPathComponent(Bundle.main.bundleIdentifier!).path + } + } + #endif + return path + } + + // Returns the path for the container of the specified app group. + func getContainerPath(appGroupIdentifier: String) -> String? { + return FileManager.default.containerURL( + forSecurityApplicationGroupIdentifier: appGroupIdentifier)?.path + } +} + +/// Returns the FileManager constant corresponding to the given type. +private func fileManagerDirectoryForType(_ type: DirectoryType) -> FileManager.SearchPathDirectory { + switch type { + case .applicationCache: + return FileManager.SearchPathDirectory.cachesDirectory + case .applicationDocuments: + return FileManager.SearchPathDirectory.documentDirectory + case .applicationSupport: + return FileManager.SearchPathDirectory.applicationSupportDirectory + case .downloads: + return FileManager.SearchPathDirectory.downloadsDirectory + case .library: + return FileManager.SearchPathDirectory.libraryDirectory + case .temp: + return FileManager.SearchPathDirectory.cachesDirectory + } +} + +/// Returns the user-domain directory of the given type. +private func getDirectory(ofType directory: FileManager.SearchPathDirectory) -> String? { + let paths = NSSearchPathForDirectoriesInDomains( + directory, + FileManager.SearchPathDomainMask.userDomainMask, + true) + return paths.first +} diff --git a/packages/path_provider/path_provider_foundation/darwin/path_provider_foundation/Sources/path_provider_foundation/Resources/PrivacyInfo.xcprivacy b/packages/path_provider/path_provider_foundation/darwin/path_provider_foundation/Sources/path_provider_foundation/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 00000000000..a34b7e2e60c --- /dev/null +++ b/packages/path_provider/path_provider_foundation/darwin/path_provider_foundation/Sources/path_provider_foundation/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,14 @@ + + + + + NSPrivacyTrackingDomains + + NSPrivacyAccessedAPITypes + + NSPrivacyCollectedDataTypes + + NSPrivacyTracking + + + diff --git a/packages/path_provider/path_provider_foundation/darwin/path_provider_foundation/Sources/path_provider_foundation/messages.g.swift b/packages/path_provider/path_provider_foundation/darwin/path_provider_foundation/Sources/path_provider_foundation/messages.g.swift new file mode 100644 index 00000000000..3c045880dc6 --- /dev/null +++ b/packages/path_provider/path_provider_foundation/darwin/path_provider_foundation/Sources/path_provider_foundation/messages.g.swift @@ -0,0 +1,171 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// Autogenerated from Pigeon (v26.1.0), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +import Foundation + +#if os(iOS) + import Flutter +#elseif os(macOS) + import FlutterMacOS +#else + #error("Unsupported platform.") +#endif + +/// Error class for passing custom error details to Dart side. +final class PigeonError: Error { + let code: String + let message: String? + let details: Sendable? + + init(code: String, message: String?, details: Sendable?) { + self.code = code + self.message = message + self.details = details + } + + var localizedDescription: String { + return + "PigeonError(code: \(code), message: \(message ?? ""), details: \(details ?? "")" + } +} + +private func wrapResult(_ result: Any?) -> [Any?] { + return [result] +} + +private func wrapError(_ error: Any) -> [Any?] { + if let pigeonError = error as? PigeonError { + return [ + pigeonError.code, + pigeonError.message, + pigeonError.details, + ] + } + if let flutterError = error as? FlutterError { + return [ + flutterError.code, + flutterError.message, + flutterError.details, + ] + } + return [ + "\(error)", + "\(type(of: error))", + "Stacktrace: \(Thread.callStackSymbols)", + ] +} + +private func isNullish(_ value: Any?) -> Bool { + return value is NSNull || value == nil +} + +private func nilOrValue(_ value: Any?) -> T? { + if value is NSNull { return nil } + return value as! T? +} + +enum DirectoryType: Int { + case applicationDocuments = 0 + case applicationSupport = 1 + case downloads = 2 + case library = 3 + case temp = 4 + case applicationCache = 5 +} + +private class MessagesPigeonCodecReader: FlutterStandardReader { + override func readValue(ofType type: UInt8) -> Any? { + switch type { + case 129: + let enumResultAsInt: Int? = nilOrValue(self.readValue() as! Int?) + if let enumResultAsInt = enumResultAsInt { + return DirectoryType(rawValue: enumResultAsInt) + } + return nil + default: + return super.readValue(ofType: type) + } + } +} + +private class MessagesPigeonCodecWriter: FlutterStandardWriter { + override func writeValue(_ value: Any) { + if let value = value as? DirectoryType { + super.writeByte(129) + super.writeValue(value.rawValue) + } else { + super.writeValue(value) + } + } +} + +private class MessagesPigeonCodecReaderWriter: FlutterStandardReaderWriter { + override func reader(with data: Data) -> FlutterStandardReader { + return MessagesPigeonCodecReader(data: data) + } + + override func writer(with data: NSMutableData) -> FlutterStandardWriter { + return MessagesPigeonCodecWriter(data: data) + } +} + +class MessagesPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable { + static let shared = MessagesPigeonCodec(readerWriter: MessagesPigeonCodecReaderWriter()) +} + +/// Generated protocol from Pigeon that represents a handler of messages from Flutter. +protocol PathProviderApi { + func getDirectoryPath(type: DirectoryType) throws -> String? + func getContainerPath(appGroupIdentifier: String) throws -> String? +} + +/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. +class PathProviderApiSetup { + static var codec: FlutterStandardMessageCodec { MessagesPigeonCodec.shared } + /// Sets up an instance of `PathProviderApi` to handle messages through the `binaryMessenger`. + static func setUp( + binaryMessenger: FlutterBinaryMessenger, api: PathProviderApi?, + messageChannelSuffix: String = "" + ) { + let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : "" + let getDirectoryPathChannel = FlutterBasicMessageChannel( + name: + "dev.flutter.pigeon.path_provider_foundation.PathProviderApi.getDirectoryPath\(channelSuffix)", + binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + getDirectoryPathChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let typeArg = args[0] as! DirectoryType + do { + let result = try api.getDirectoryPath(type: typeArg) + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + getDirectoryPathChannel.setMessageHandler(nil) + } + let getContainerPathChannel = FlutterBasicMessageChannel( + name: + "dev.flutter.pigeon.path_provider_foundation.PathProviderApi.getContainerPath\(channelSuffix)", + binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + getContainerPathChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let appGroupIdentifierArg = args[0] as! String + do { + let result = try api.getContainerPath(appGroupIdentifier: appGroupIdentifierArg) + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + getContainerPathChannel.setMessageHandler(nil) + } + } +} diff --git a/packages/path_provider/path_provider_foundation/example/build.yaml b/packages/path_provider/path_provider_foundation/example/build.yaml deleted file mode 100644 index ef6f032afd8..00000000000 --- a/packages/path_provider/path_provider_foundation/example/build.yaml +++ /dev/null @@ -1,13 +0,0 @@ -targets: - $default: - sources: - - $package$ - - lib/$lib$ - - lib/**.dart - - test/**.dart - - integration_test/**.dart - builders: - mockito|mockBuilder: - generate_for: - - test/**.dart - - integration_test/**.dart diff --git a/packages/path_provider/path_provider_foundation/example/integration_test/path_provider_test.dart b/packages/path_provider/path_provider_foundation/example/integration_test/path_provider_test.dart index 9ad8b748b97..6e63e0478f9 100644 --- a/packages/path_provider/path_provider_foundation/example/integration_test/path_provider_test.dart +++ b/packages/path_provider/path_provider_foundation/example/integration_test/path_provider_test.dart @@ -5,419 +5,69 @@ import 'dart:io'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; -import 'package:mockito/annotations.dart'; -import 'package:mockito/mockito.dart'; -import 'package:objective_c/objective_c.dart'; -import 'package:path/path.dart' as p; -import 'package:path_provider_foundation/src/ffi_bindings.g.dart'; -import 'package:path_provider_foundation/src/path_provider_foundation_real.dart'; +import 'package:path_provider_foundation/path_provider_foundation.dart'; import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; -import 'path_provider_test.mocks.dart'; - -@GenerateNiceMocks(>[ - MockSpec(), - MockSpec(), - MockSpec(), -]) void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - // This group contains standard integration tests that do end-to-end testing - // of the calls into the platform. - group('end-to-end', () { - testWidgets('getTemporaryDirectory', (WidgetTester tester) async { - final PathProviderPlatform provider = PathProviderPlatform.instance; - final String? result = await provider.getTemporaryPath(); - _verifySampleFile(result, 'temporaryDirectory'); - }); - - testWidgets('getApplicationDocumentsDirectory', ( - WidgetTester tester, - ) async { - final PathProviderPlatform provider = PathProviderPlatform.instance; - final String? result = await provider.getApplicationDocumentsPath(); - if (Platform.isMacOS) { - // _verifySampleFile causes hangs in driver when sandboxing is disabled - // because the path changes from an app specific directory to - // ~/Documents, which requires additional permissions to access on macOS. - // Instead, validate that a non-empty path was returned. - expect(result, isNotEmpty); - } else { - _verifySampleFile(result, 'applicationDocuments'); - } - }); - - testWidgets('getApplicationSupportDirectory', (WidgetTester tester) async { - final PathProviderPlatform provider = PathProviderPlatform.instance; - final String? result = await provider.getApplicationSupportPath(); - _verifySampleFile(result, 'applicationSupport'); - }); - - testWidgets('getApplicationCacheDirectory', (WidgetTester tester) async { - final PathProviderPlatform provider = PathProviderPlatform.instance; - final String? result = await provider.getApplicationCachePath(); - _verifySampleFile(result, 'applicationCache'); - }); - - testWidgets('getLibraryDirectory', (WidgetTester tester) async { - final PathProviderPlatform provider = PathProviderPlatform.instance; - final String? result = await provider.getLibraryPath(); - _verifySampleFile(result, 'library'); - }); + testWidgets('getTemporaryDirectory', (WidgetTester tester) async { + final PathProviderPlatform provider = PathProviderPlatform.instance; + final String? result = await provider.getTemporaryPath(); + _verifySampleFile(result, 'temporaryDirectory'); + }); - testWidgets('getDownloadsDirectory', (WidgetTester tester) async { - final PathProviderPlatform provider = PathProviderPlatform.instance; - final String? result = await provider.getDownloadsPath(); - // _verifySampleFile causes hangs in driver for some reason, so just - // validate that a non-empty path was returned. + testWidgets('getApplicationDocumentsDirectory', (WidgetTester tester) async { + final PathProviderPlatform provider = PathProviderPlatform.instance; + final String? result = await provider.getApplicationDocumentsPath(); + if (Platform.isMacOS) { + // _verifySampleFile causes hangs in driver when sandboxing is disabled + // because the path changes from an app specific directory to + // ~/Documents, which requires additional permissions to access on macOS. + // Instead, validate that a non-empty path was returned. expect(result, isNotEmpty); - }); - - testWidgets('getContainerDirectory', (WidgetTester tester) async { - if (Platform.isIOS) { - final provider = PathProviderFoundation(); - final String? result = await provider.getContainerPath( - appGroupIdentifier: 'group.flutter.appGroupTest', - ); - _verifySampleFile(result, 'appGroup'); - } - }); + } else { + _verifySampleFile(result, 'applicationDocuments'); + } }); - // This group contains tests that would normally be Dart unit tests in the - // test/ directory, but can't be because they use Objective-C types (NSURL, - // NSString, etc.) that aren't available in an actual unit test. For these - // tests, the platform is stubbed out. - group('unit', () { - final platformVariants = ValueVariant( - { - FakePlatformProvider(isIOS: true), - FakePlatformProvider(isMacOS: true), - }, - ); - - // These tests use the actual filesystem, since an injectable filesystem - // would add a runtime dependency to the package, so everything is contained - // to a temporary directory. - late Directory testRoot; - - setUp(() async { - testRoot = Directory.systemTemp.createTempSync(); - }); - - tearDown(() { - testRoot.deleteSync(recursive: true); - }); - - testWidgets('getTemporaryPath iOS', (_) async { - final mockFfiLib = MockFoundationFFI(); - final pathProvider = PathProviderFoundation( - ffiLib: mockFfiLib, - platform: FakePlatformProvider(isIOS: true), - ); - - final String temporaryPath = p.join(testRoot.path, 'temporary', 'path'); - when( - mockFfiLib.NSSearchPathForDirectoriesInDomains( - NSSearchPathDirectory.NSCachesDirectory, - NSSearchPathDomainMask.NSUserDomainMask, - true, - ), - ).thenReturn(_arrayWithString(temporaryPath)); - - final String? path = await pathProvider.getTemporaryPath(); - - expect(path, temporaryPath); - }); - - testWidgets('getTemporaryPath macOS', (_) async { - final mockFfiLib = MockFoundationFFI(); - final pathProvider = PathProviderFoundation( - ffiLib: mockFfiLib, - platform: FakePlatformProvider(isMacOS: true), - ); - - final String temporaryPath = p.join(testRoot.path, 'temporary', 'path'); - when( - mockFfiLib.NSSearchPathForDirectoriesInDomains( - NSSearchPathDirectory.NSCachesDirectory, - NSSearchPathDomainMask.NSUserDomainMask, - true, - ), - ).thenReturn(_arrayWithString(temporaryPath)); - - final String? path = await pathProvider.getTemporaryPath(); - - // On macOS, the bundle ID should be appended to the path. - expect(path, '$temporaryPath/dev.flutter.plugins.pathProviderExample'); - }); - - testWidgets('getApplicationSupportPath iOS', (_) async { - final mockFfiLib = MockFoundationFFI(); - final pathProvider = PathProviderFoundation( - ffiLib: mockFfiLib, - platform: FakePlatformProvider(isIOS: true), - ); - - final String applicationSupportPath = p.join( - testRoot.path, - 'application', - 'support', - 'path', - ); - when( - mockFfiLib.NSSearchPathForDirectoriesInDomains( - NSSearchPathDirectory.NSApplicationSupportDirectory, - NSSearchPathDomainMask.NSUserDomainMask, - true, - ), - ).thenReturn(_arrayWithString(applicationSupportPath)); - - final String? path = await pathProvider.getApplicationSupportPath(); - - expect(path, applicationSupportPath); - }); - - testWidgets('getApplicationSupportPath macOS', (_) async { - final mockFfiLib = MockFoundationFFI(); - final pathProvider = PathProviderFoundation( - ffiLib: mockFfiLib, - platform: FakePlatformProvider(isMacOS: true), - ); - - final String applicationSupportPath = p.join( - testRoot.path, - 'application', - 'support', - 'path', - ); - when( - mockFfiLib.NSSearchPathForDirectoriesInDomains( - NSSearchPathDirectory.NSApplicationSupportDirectory, - NSSearchPathDomainMask.NSUserDomainMask, - true, - ), - ).thenReturn(_arrayWithString(applicationSupportPath)); - - final String? path = await pathProvider.getApplicationSupportPath(); - - // On macOS, the bundle ID should be appended to the path. - expect( - path, - '$applicationSupportPath/dev.flutter.plugins.pathProviderExample', - ); - }); - - testWidgets( - 'getApplicationSupportPath creates the directory if necessary', - (_) async { - final mockFfiLib = MockFoundationFFI(); - final pathProvider = PathProviderFoundation( - ffiLib: mockFfiLib, - platform: platformVariants.currentValue, - ); - - final String applicationSupportPath = p.join( - testRoot.path, - 'application', - 'support', - 'path', - ); - when( - mockFfiLib.NSSearchPathForDirectoriesInDomains( - NSSearchPathDirectory.NSApplicationSupportDirectory, - NSSearchPathDomainMask.NSUserDomainMask, - true, - ), - ).thenReturn(_arrayWithString(applicationSupportPath)); - - final String? path = await pathProvider.getApplicationSupportPath(); - - expect(Directory(path!).existsSync(), isTrue); - }, - variant: platformVariants, - ); - - testWidgets('getLibraryPath', (_) async { - final mockFfiLib = MockFoundationFFI(); - final pathProvider = PathProviderFoundation( - ffiLib: mockFfiLib, - platform: platformVariants.currentValue, - ); - - final String libraryPath = p.join(testRoot.path, 'library', 'path'); - when( - mockFfiLib.NSSearchPathForDirectoriesInDomains( - NSSearchPathDirectory.NSLibraryDirectory, - NSSearchPathDomainMask.NSUserDomainMask, - true, - ), - ).thenReturn(_arrayWithString(libraryPath)); - - final String? path = await pathProvider.getLibraryPath(); - - expect(path, libraryPath); - }, variant: platformVariants); - - testWidgets('getApplicationDocumentsPath', (_) async { - final mockFfiLib = MockFoundationFFI(); - final pathProvider = PathProviderFoundation( - ffiLib: mockFfiLib, - platform: platformVariants.currentValue, - ); - - final String applicationDocumentsPath = p.join( - testRoot.path, - 'application', - 'documents', - 'path', - ); - when( - mockFfiLib.NSSearchPathForDirectoriesInDomains( - NSSearchPathDirectory.NSDocumentDirectory, - NSSearchPathDomainMask.NSUserDomainMask, - true, - ), - ).thenReturn(_arrayWithString(applicationDocumentsPath)); - - final String? path = await pathProvider.getApplicationDocumentsPath(); - - expect(path, applicationDocumentsPath); - }, variant: platformVariants); - - testWidgets('getApplicationCachePath iOS', (_) async { - final mockFfiLib = MockFoundationFFI(); - final pathProvider = PathProviderFoundation( - ffiLib: mockFfiLib, - platform: FakePlatformProvider(isIOS: true), - ); - - final String applicationCachePath = p.join( - testRoot.path, - 'application', - 'cache', - 'path', - ); - when( - mockFfiLib.NSSearchPathForDirectoriesInDomains( - NSSearchPathDirectory.NSCachesDirectory, - NSSearchPathDomainMask.NSUserDomainMask, - true, - ), - ).thenReturn(_arrayWithString(applicationCachePath)); - - final String? path = await pathProvider.getApplicationCachePath(); - - expect(path, applicationCachePath); - }); - - testWidgets('getApplicationCachePath macOS', (_) async { - final mockFfiLib = MockFoundationFFI(); - final pathProvider = PathProviderFoundation( - ffiLib: mockFfiLib, - platform: FakePlatformProvider(isMacOS: true), - ); - - final String applicationCachePath = p.join( - testRoot.path, - 'application', - 'cache', - 'path', - ); - when( - mockFfiLib.NSSearchPathForDirectoriesInDomains( - NSSearchPathDirectory.NSCachesDirectory, - NSSearchPathDomainMask.NSUserDomainMask, - true, - ), - ).thenReturn(_arrayWithString(applicationCachePath)); - - final String? path = await pathProvider.getApplicationCachePath(); - - // On macOS, the bundle ID should be appended to the path. - expect( - path, - '$applicationCachePath/dev.flutter.plugins.pathProviderExample', - ); - }); - - testWidgets( - 'getApplicationCachePath creates the directory if necessary', - (_) async { - final mockFfiLib = MockFoundationFFI(); - final pathProvider = PathProviderFoundation( - ffiLib: mockFfiLib, - platform: platformVariants.currentValue, - ); - - final String applicationCachePath = p.join( - testRoot.path, - 'application', - 'cache', - 'path', - ); - when( - mockFfiLib.NSSearchPathForDirectoriesInDomains( - NSSearchPathDirectory.NSCachesDirectory, - NSSearchPathDomainMask.NSUserDomainMask, - true, - ), - ).thenReturn(_arrayWithString(applicationCachePath)); - - final String? path = await pathProvider.getApplicationCachePath(); - - expect(Directory(path!).existsSync(), isTrue); - }, - variant: platformVariants, - ); - - testWidgets('getDownloadsPath', (_) async { - final mockFfiLib = MockFoundationFFI(); - final pathProvider = PathProviderFoundation( - ffiLib: mockFfiLib, - platform: platformVariants.currentValue, - ); - - final String downloadsPath = p.join(testRoot.path, 'downloads', 'path'); - when( - mockFfiLib.NSSearchPathForDirectoriesInDomains( - NSSearchPathDirectory.NSDownloadsDirectory, - NSSearchPathDomainMask.NSUserDomainMask, - true, - ), - ).thenReturn(_arrayWithString(downloadsPath)); - - final String? result = await pathProvider.getDownloadsPath(); + testWidgets('getApplicationSupportDirectory', (WidgetTester tester) async { + final PathProviderPlatform provider = PathProviderPlatform.instance; + final String? result = await provider.getApplicationSupportPath(); + _verifySampleFile(result, 'applicationSupport'); + }); - expect(result, downloadsPath); - }, variant: platformVariants); + testWidgets('getApplicationCacheDirectory', (WidgetTester tester) async { + final PathProviderPlatform provider = PathProviderPlatform.instance; + final String? result = await provider.getApplicationCachePath(); + _verifySampleFile(result, 'applicationCache'); + }); - testWidgets('getContainerPath', (_) async { - final String containerPath = p.join(testRoot.path, 'container', 'path'); - final NSURL containerUrl = NSURL.fileURLWithPath(NSString(containerPath)); + testWidgets('getLibraryDirectory', (WidgetTester tester) async { + final PathProviderPlatform provider = PathProviderPlatform.instance; + final String? result = await provider.getLibraryPath(); + _verifySampleFile(result, 'library'); + }); - final mockFfiLib = MockFoundationFFI(); - final pathProvider = PathProviderFoundation( - ffiLib: mockFfiLib, - containerURLForSecurityApplicationGroupIdentifier: (_) => containerUrl, - platform: FakePlatformProvider(isIOS: true), - ); + testWidgets('getDownloadsDirectory', (WidgetTester tester) async { + final PathProviderPlatform provider = PathProviderPlatform.instance; + final String? result = await provider.getDownloadsPath(); + // _verifySampleFile causes hangs in driver for some reason, so just + // validate that a non-empty path was returned. + expect(result, isNotEmpty); + }); - const appGroupIdentifier = 'group.example.test'; - final String? result = await pathProvider.getContainerPath( - appGroupIdentifier: appGroupIdentifier, + testWidgets('getContainerDirectory', (WidgetTester tester) async { + if (Platform.isIOS) { + final provider = PathProviderFoundation(); + final String? result = await provider.getContainerPath( + appGroupIdentifier: 'group.flutter.appGroupTest', ); - - expect(result, containerPath); - }); + _verifySampleFile(result, 'appGroup'); + } }); } -NSArray _arrayWithString(String s) { - return NSArray.arrayWithObject(NSString(s)); -} - /// Verify a file called [name] in [directoryPath] by recreating it with test /// contents when necessary. /// @@ -440,17 +90,3 @@ void _verifySampleFile(String? directoryPath, String name) { expect(directory.listSync(), isNotEmpty); file.deleteSync(); } - -/// Fake implementation of PathProviderPlatformProvider. -class FakePlatformProvider implements PathProviderPlatformProvider { - FakePlatformProvider({this.isIOS = false, this.isMacOS = false}) - : assert(isIOS != isMacOS); - @override - bool isIOS; - - @override - bool isMacOS; - - @override - String toString() => isIOS ? 'iOS' : 'macOS'; -} diff --git a/packages/path_provider/path_provider_foundation/example/integration_test/path_provider_test.mocks.dart b/packages/path_provider/path_provider_foundation/example/integration_test/path_provider_test.mocks.dart deleted file mode 100644 index 24980d636be..00000000000 --- a/packages/path_provider/path_provider_foundation/example/integration_test/path_provider_test.mocks.dart +++ /dev/null @@ -1,111 +0,0 @@ -// Mocks generated by Mockito 5.4.6 from annotations -// in path_provider_example/integration_test/path_provider_test.dart. -// Do not manually edit this file. - -// ignore_for_file: no_leading_underscores_for_library_prefixes -import 'package:mockito/mockito.dart' as _i1; -import 'package:mockito/src/dummies.dart' as _i4; -import 'package:objective_c/objective_c.dart' as _i2; -import 'package:path_provider_foundation/src/ffi_bindings.g.dart' as _i3; -import 'package:path_provider_foundation/src/path_provider_foundation_real.dart' - as _i5; - -// ignore_for_file: type=lint -// ignore_for_file: avoid_redundant_argument_values -// ignore_for_file: avoid_setters_without_getters -// ignore_for_file: comment_references -// ignore_for_file: deprecated_member_use -// ignore_for_file: deprecated_member_use_from_same_package -// ignore_for_file: implementation_imports -// ignore_for_file: invalid_use_of_visible_for_testing_member -// ignore_for_file: must_be_immutable -// ignore_for_file: prefer_const_constructors -// ignore_for_file: unnecessary_parenthesis -// ignore_for_file: camel_case_types -// ignore_for_file: subtype_of_sealed_class -// ignore_for_file: invalid_use_of_internal_member - -class _FakeObjCObject_0 extends _i1.SmartFake implements _i2.ObjCObject { - _FakeObjCObject_0(Object parent, Invocation parentInvocation) - : super(parent, parentInvocation); -} - -/// A class which mocks [FoundationFFI]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockFoundationFFI extends _i1.Mock implements _i3.FoundationFFI { - @override - _i2.NSArray NSSearchPathForDirectoriesInDomains( - _i3.NSSearchPathDirectory? directory, - int? domainMask, - bool? expandTilde, - ) => - (super.noSuchMethod( - Invocation.method(#NSSearchPathForDirectoriesInDomains, [ - directory, - domainMask, - expandTilde, - ]), - returnValue: _FakeObjCObject_0( - this, - Invocation.method(#NSSearchPathForDirectoriesInDomains, [ - directory, - domainMask, - expandTilde, - ]), - ), - returnValueForMissingStub: _FakeObjCObject_0( - this, - Invocation.method(#NSSearchPathForDirectoriesInDomains, [ - directory, - domainMask, - expandTilde, - ]), - ), - ) - as _i2.NSArray); -} - -/// A class which mocks [ObjCObject]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockObjCObject extends _i1.Mock implements _i2.ObjCObject { - @override - _i2.ObjCObjectRef get ref => - (super.noSuchMethod( - Invocation.getter(#ref), - returnValue: _i4.dummyValue<_i2.ObjCObjectRef>( - this, - Invocation.getter(#ref), - ), - returnValueForMissingStub: _i4.dummyValue<_i2.ObjCObjectRef>( - this, - Invocation.getter(#ref), - ), - ) - as _i2.ObjCObjectRef); -} - -/// A class which mocks [PathProviderPlatformProvider]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockPathProviderPlatformProvider extends _i1.Mock - implements _i5.PathProviderPlatformProvider { - @override - bool get isIOS => - (super.noSuchMethod( - Invocation.getter(#isIOS), - returnValue: false, - returnValueForMissingStub: false, - ) - as bool); - - @override - bool get isMacOS => - (super.noSuchMethod( - Invocation.getter(#isMacOS), - returnValue: false, - returnValueForMissingStub: false, - ) - as bool); -} diff --git a/packages/path_provider/path_provider_foundation/example/ios/Runner.xcodeproj/project.pbxproj b/packages/path_provider/path_provider_foundation/example/ios/Runner.xcodeproj/project.pbxproj index ea6743595e0..16c8997bc44 100644 --- a/packages/path_provider/path_provider_foundation/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/path_provider/path_provider_foundation/example/ios/Runner.xcodeproj/project.pbxproj @@ -53,7 +53,6 @@ 5DB8EF5A2759054360D79B8D /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 86F7986E9DC17432CC8AE464 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; 91DA83C3D33EB641BAEA3087 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; @@ -102,7 +101,6 @@ 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( - 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, @@ -203,7 +201,6 @@ 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - D4AF0CAAE697EF439AFEC08C /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -356,23 +353,6 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; - D4AF0CAAE697EF439AFEC08C /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -487,7 +467,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.pathProviderExample; + PRODUCT_BUNDLE_IDENTIFIER = com.example.pathProviderFoundationExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -661,7 +641,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.pathProviderExample; + PRODUCT_BUNDLE_IDENTIFIER = com.example.pathProviderFoundationExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -684,7 +664,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.pathProviderExample; + PRODUCT_BUNDLE_IDENTIFIER = com.example.pathProviderFoundationExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; diff --git a/packages/path_provider/path_provider_foundation/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/path_provider/path_provider_foundation/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a02dac9162d..3ea14b59077 100644 --- a/packages/path_provider/path_provider_foundation/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/path_provider/path_provider_foundation/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -44,7 +44,6 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit" shouldUseLaunchSchemeArgsEnv = "YES"> pigeonChannelCodec = _PigeonCodec(); + + final String pigeonVar_messageChannelSuffix; + + Future getDirectoryPath(DirectoryType type) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.path_provider_foundation.PathProviderApi.getDirectoryPath$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [type], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return (pigeonVar_replyList[0] as String?); + } + } + + Future getContainerPath(String appGroupIdentifier) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.path_provider_foundation.PathProviderApi.getContainerPath$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [appGroupIdentifier], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return (pigeonVar_replyList[0] as String?); + } + } +} diff --git a/packages/path_provider/path_provider_foundation/lib/path_provider_foundation.dart b/packages/path_provider/path_provider_foundation/lib/path_provider_foundation.dart index 91b60fca355..64a92c5cf59 100644 --- a/packages/path_provider/path_provider_foundation/lib/path_provider_foundation.dart +++ b/packages/path_provider/path_provider_foundation/lib/path_provider_foundation.dart @@ -2,8 +2,114 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// path_provider_foundation is implemented using FFI; export a stub for -// platforms that don't support FFI (e.g., web) to avoid having transitive -// dependencies break web compilation. -export 'src/path_provider_foundation_stub.dart' - if (dart.library.ffi) 'src/path_provider_foundation_real.dart'; +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; + +import 'messages.g.dart'; + +/// The iOS and macOS implementation of [PathProviderPlatform]. +class PathProviderFoundation extends PathProviderPlatform { + /// Constructor that accepts a testable PathProviderPlatformProvider. + PathProviderFoundation({ + @visibleForTesting PathProviderPlatformProvider? platform, + @visibleForTesting PathProviderApi? pathProviderApi, + }) : _platformProvider = platform ?? PathProviderPlatformProvider(), + _pathProvider = pathProviderApi ?? PathProviderApi(); + + final PathProviderPlatformProvider _platformProvider; + final PathProviderApi _pathProvider; + + /// Registers this class as the default instance of [PathProviderPlatform] + static void registerWith() { + PathProviderPlatform.instance = PathProviderFoundation(); + } + + @override + Future getTemporaryPath() { + return _pathProvider.getDirectoryPath(DirectoryType.temp); + } + + @override + Future getApplicationSupportPath() async { + final String? path = await _pathProvider.getDirectoryPath( + DirectoryType.applicationSupport, + ); + if (path != null) { + // Ensure the directory exists before returning it, for consistency with + // other platforms. + await Directory(path).create(recursive: true); + } + return path; + } + + @override + Future getLibraryPath() { + return _pathProvider.getDirectoryPath(DirectoryType.library); + } + + @override + Future getApplicationDocumentsPath() { + return _pathProvider.getDirectoryPath(DirectoryType.applicationDocuments); + } + + @override + Future getApplicationCachePath() async { + final String? path = await _pathProvider.getDirectoryPath( + DirectoryType.applicationCache, + ); + if (path != null) { + // Ensure the directory exists before returning it, for consistency with + // other platforms. + await Directory(path).create(recursive: true); + } + return path; + } + + @override + Future getExternalStoragePath() async { + throw UnsupportedError( + 'getExternalStoragePath is not supported on this platform', + ); + } + + @override + Future?> getExternalCachePaths() async { + throw UnsupportedError( + 'getExternalCachePaths is not supported on this platform', + ); + } + + @override + Future?> getExternalStoragePaths({ + StorageDirectory? type, + }) async { + throw UnsupportedError( + 'getExternalStoragePaths is not supported on this platform', + ); + } + + @override + Future getDownloadsPath() { + return _pathProvider.getDirectoryPath(DirectoryType.downloads); + } + + /// Returns the path to the container of the specified App Group. + /// This is only supported for iOS. + Future getContainerPath({required String appGroupIdentifier}) async { + if (!_platformProvider.isIOS) { + throw UnsupportedError( + 'getContainerPath is not supported on this platform', + ); + } + return _pathProvider.getContainerPath(appGroupIdentifier); + } +} + +/// Helper class for returning information about the current platform. +@visibleForTesting +class PathProviderPlatformProvider { + /// Specifies whether the current platform is iOS. + bool get isIOS => Platform.isIOS; +} diff --git a/packages/path_provider/path_provider_foundation/lib/src/ffi_bindings.g.dart b/packages/path_provider/path_provider_foundation/lib/src/ffi_bindings.g.dart deleted file mode 100644 index e0e01ffa9f0..00000000000 --- a/packages/path_provider/path_provider_foundation/lib/src/ffi_bindings.g.dart +++ /dev/null @@ -1,509 +0,0 @@ -// AUTO GENERATED FILE, DO NOT EDIT. -// -// Generated by `package:ffigen`. -// ignore_for_file: type=lint, unused_import -import 'dart:ffi' as ffi; -import 'package:objective_c/objective_c.dart' as objc; -import 'package:ffi/ffi.dart' as pkg_ffi; - -/// Bindings for NSFileManager. -class FoundationFFI { - /// Holds the symbol lookup function. - final ffi.Pointer Function(String symbolName) - _lookup; - - /// The symbols are looked up in [dynamicLibrary]. - FoundationFFI(ffi.DynamicLibrary dynamicLibrary) - : _lookup = dynamicLibrary.lookup; - - /// The symbols are looked up with [lookup]. - FoundationFFI.fromLookup( - ffi.Pointer Function(String symbolName) lookup, - ) : _lookup = lookup; - - objc.NSArray NSSearchPathForDirectoriesInDomains( - NSSearchPathDirectory directory, - int domainMask, - bool expandTilde, - ) { - return objc.NSArray.fromPointer( - _NSSearchPathForDirectoriesInDomains( - directory.value, - domainMask, - expandTilde, - ), - retain: true, - release: true, - ); - } - - late final _NSSearchPathForDirectoriesInDomainsPtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer Function( - ffi.UnsignedLong, - ffi.UnsignedLong, - ffi.Bool, - ) - > - >('NSSearchPathForDirectoriesInDomains'); - late final _NSSearchPathForDirectoriesInDomains = - _NSSearchPathForDirectoriesInDomainsPtr.asFunction< - ffi.Pointer Function(int, int, bool) - >(); -} - -enum NSSearchPathDirectory { - NSApplicationDirectory(1), - NSDemoApplicationDirectory(2), - NSDeveloperApplicationDirectory(3), - NSAdminApplicationDirectory(4), - NSLibraryDirectory(5), - NSDeveloperDirectory(6), - NSUserDirectory(7), - NSDocumentationDirectory(8), - NSDocumentDirectory(9), - NSCoreServiceDirectory(10), - NSAutosavedInformationDirectory(11), - NSDesktopDirectory(12), - NSCachesDirectory(13), - NSApplicationSupportDirectory(14), - NSDownloadsDirectory(15), - NSInputMethodsDirectory(16), - NSMoviesDirectory(17), - NSMusicDirectory(18), - NSPicturesDirectory(19), - NSPrinterDescriptionDirectory(20), - NSSharedPublicDirectory(21), - NSPreferencePanesDirectory(22), - NSApplicationScriptsDirectory(23), - NSItemReplacementDirectory(99), - NSAllApplicationsDirectory(100), - NSAllLibrariesDirectory(101), - NSTrashDirectory(102); - - final int value; - const NSSearchPathDirectory(this.value); - - static NSSearchPathDirectory fromValue(int value) => switch (value) { - 1 => NSApplicationDirectory, - 2 => NSDemoApplicationDirectory, - 3 => NSDeveloperApplicationDirectory, - 4 => NSAdminApplicationDirectory, - 5 => NSLibraryDirectory, - 6 => NSDeveloperDirectory, - 7 => NSUserDirectory, - 8 => NSDocumentationDirectory, - 9 => NSDocumentDirectory, - 10 => NSCoreServiceDirectory, - 11 => NSAutosavedInformationDirectory, - 12 => NSDesktopDirectory, - 13 => NSCachesDirectory, - 14 => NSApplicationSupportDirectory, - 15 => NSDownloadsDirectory, - 16 => NSInputMethodsDirectory, - 17 => NSMoviesDirectory, - 18 => NSMusicDirectory, - 19 => NSPicturesDirectory, - 20 => NSPrinterDescriptionDirectory, - 21 => NSSharedPublicDirectory, - 22 => NSPreferencePanesDirectory, - 23 => NSApplicationScriptsDirectory, - 99 => NSItemReplacementDirectory, - 100 => NSAllApplicationsDirectory, - 101 => NSAllLibrariesDirectory, - 102 => NSTrashDirectory, - _ => throw ArgumentError('Unknown value for NSSearchPathDirectory: $value'), - }; -} - -sealed class NSSearchPathDomainMask { - static const NSUserDomainMask = 1; - static const NSLocalDomainMask = 2; - static const NSNetworkDomainMask = 4; - static const NSSystemDomainMask = 8; - static const NSAllDomainsMask = 65535; -} - -late final _class_NSURL = objc.getClass("NSURL"); -late final _sel_fileURLWithPathComponents_ = objc.registerName( - "fileURLWithPathComponents:", -); -final _objc_msgSend_1sotr3r = objc.msgSendPointer - .cast< - ffi.NativeFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ) - > - >() - .asFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ) - >(); -late final _sel_pathComponents = objc.registerName("pathComponents"); -final _objc_msgSend_151sglz = objc.msgSendPointer - .cast< - ffi.NativeFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ) - > - >() - .asFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ) - >(); -late final _sel_lastPathComponent = objc.registerName("lastPathComponent"); -late final _sel_pathExtension = objc.registerName("pathExtension"); -late final _sel_URLByAppendingPathComponent_ = objc.registerName( - "URLByAppendingPathComponent:", -); -late final _sel_URLByAppendingPathComponent_isDirectory_ = objc.registerName( - "URLByAppendingPathComponent:isDirectory:", -); -final _objc_msgSend_17amj0z = objc.msgSendPointer - .cast< - ffi.NativeFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Bool, - ) - > - >() - .asFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - bool, - ) - >(); -late final _sel_URLByDeletingLastPathComponent = objc.registerName( - "URLByDeletingLastPathComponent", -); -late final _sel_URLByAppendingPathExtension_ = objc.registerName( - "URLByAppendingPathExtension:", -); -late final _sel_URLByDeletingPathExtension = objc.registerName( - "URLByDeletingPathExtension", -); -late final _sel_checkResourceIsReachableAndReturnError_ = objc.registerName( - "checkResourceIsReachableAndReturnError:", -); -final _objc_msgSend_1dom33q = objc.msgSendPointer - .cast< - ffi.NativeFunction< - ffi.Bool Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer>, - ) - > - >() - .asFunction< - bool Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer>, - ) - >(); -late final _sel_URLByStandardizingPath = objc.registerName( - "URLByStandardizingPath", -); -late final _sel_URLByResolvingSymlinksInPath = objc.registerName( - "URLByResolvingSymlinksInPath", -); - -/// NSURLPathUtilities -extension NSURLPathUtilities on objc.NSURL { - /// URLByAppendingPathComponent: - objc.NSURL? URLByAppendingPathComponent(objc.NSString pathComponent) { - objc.checkOsVersionInternal( - 'NSURL.URLByAppendingPathComponent:', - iOS: (false, (4, 0, 0)), - macOS: (false, (10, 6, 0)), - ); - final $ret = _objc_msgSend_1sotr3r( - object$.ref.pointer, - _sel_URLByAppendingPathComponent_, - pathComponent.ref.pointer, - ); - return $ret.address == 0 - ? null - : objc.NSURL.fromPointer($ret, retain: true, release: true); - } - - /// URLByAppendingPathComponent:isDirectory: - objc.NSURL? URLByAppendingPathComponent$1( - objc.NSString pathComponent, { - required bool isDirectory, - }) { - objc.checkOsVersionInternal( - 'NSURL.URLByAppendingPathComponent:isDirectory:', - iOS: (false, (5, 0, 0)), - macOS: (false, (10, 7, 0)), - ); - final $ret = _objc_msgSend_17amj0z( - object$.ref.pointer, - _sel_URLByAppendingPathComponent_isDirectory_, - pathComponent.ref.pointer, - isDirectory, - ); - return $ret.address == 0 - ? null - : objc.NSURL.fromPointer($ret, retain: true, release: true); - } - - /// URLByAppendingPathExtension: - objc.NSURL? URLByAppendingPathExtension(objc.NSString pathExtension) { - objc.checkOsVersionInternal( - 'NSURL.URLByAppendingPathExtension:', - iOS: (false, (4, 0, 0)), - macOS: (false, (10, 6, 0)), - ); - final $ret = _objc_msgSend_1sotr3r( - object$.ref.pointer, - _sel_URLByAppendingPathExtension_, - pathExtension.ref.pointer, - ); - return $ret.address == 0 - ? null - : objc.NSURL.fromPointer($ret, retain: true, release: true); - } - - /// URLByDeletingLastPathComponent - objc.NSURL? get URLByDeletingLastPathComponent { - objc.checkOsVersionInternal( - 'NSURL.URLByDeletingLastPathComponent', - iOS: (false, (4, 0, 0)), - macOS: (false, (10, 6, 0)), - ); - final $ret = _objc_msgSend_151sglz( - object$.ref.pointer, - _sel_URLByDeletingLastPathComponent, - ); - return $ret.address == 0 - ? null - : objc.NSURL.fromPointer($ret, retain: true, release: true); - } - - /// URLByDeletingPathExtension - objc.NSURL? get URLByDeletingPathExtension { - objc.checkOsVersionInternal( - 'NSURL.URLByDeletingPathExtension', - iOS: (false, (4, 0, 0)), - macOS: (false, (10, 6, 0)), - ); - final $ret = _objc_msgSend_151sglz( - object$.ref.pointer, - _sel_URLByDeletingPathExtension, - ); - return $ret.address == 0 - ? null - : objc.NSURL.fromPointer($ret, retain: true, release: true); - } - - /// URLByResolvingSymlinksInPath - objc.NSURL? get URLByResolvingSymlinksInPath { - objc.checkOsVersionInternal( - 'NSURL.URLByResolvingSymlinksInPath', - iOS: (false, (4, 0, 0)), - macOS: (false, (10, 6, 0)), - ); - final $ret = _objc_msgSend_151sglz( - object$.ref.pointer, - _sel_URLByResolvingSymlinksInPath, - ); - return $ret.address == 0 - ? null - : objc.NSURL.fromPointer($ret, retain: true, release: true); - } - - /// URLByStandardizingPath - objc.NSURL? get URLByStandardizingPath { - objc.checkOsVersionInternal( - 'NSURL.URLByStandardizingPath', - iOS: (false, (4, 0, 0)), - macOS: (false, (10, 6, 0)), - ); - final $ret = _objc_msgSend_151sglz( - object$.ref.pointer, - _sel_URLByStandardizingPath, - ); - return $ret.address == 0 - ? null - : objc.NSURL.fromPointer($ret, retain: true, release: true); - } - - /// checkResourceIsReachableAndReturnError: - bool checkResourceIsReachableAndReturnError() { - objc.checkOsVersionInternal( - 'NSURL.checkResourceIsReachableAndReturnError:', - iOS: (false, (4, 0, 0)), - macOS: (false, (10, 6, 0)), - ); - final $err = pkg_ffi.calloc>(); - try { - final $ret = _objc_msgSend_1dom33q( - object$.ref.pointer, - _sel_checkResourceIsReachableAndReturnError_, - $err, - ); - objc.NSErrorException.checkErrorPointer($err.value); - return $ret; - } finally { - pkg_ffi.calloc.free($err); - } - } - - /// lastPathComponent - objc.NSString? get lastPathComponent { - objc.checkOsVersionInternal( - 'NSURL.lastPathComponent', - iOS: (false, (4, 0, 0)), - macOS: (false, (10, 6, 0)), - ); - final $ret = _objc_msgSend_151sglz( - object$.ref.pointer, - _sel_lastPathComponent, - ); - return $ret.address == 0 - ? null - : objc.NSString.fromPointer($ret, retain: true, release: true); - } - - /// pathComponents - objc.NSArray? get pathComponents { - objc.checkOsVersionInternal( - 'NSURL.pathComponents', - iOS: (false, (4, 0, 0)), - macOS: (false, (10, 6, 0)), - ); - final $ret = _objc_msgSend_151sglz( - object$.ref.pointer, - _sel_pathComponents, - ); - return $ret.address == 0 - ? null - : objc.NSArray.fromPointer($ret, retain: true, release: true); - } - - /// pathExtension - objc.NSString? get pathExtension { - objc.checkOsVersionInternal( - 'NSURL.pathExtension', - iOS: (false, (4, 0, 0)), - macOS: (false, (10, 6, 0)), - ); - final $ret = _objc_msgSend_151sglz(object$.ref.pointer, _sel_pathExtension); - return $ret.address == 0 - ? null - : objc.NSString.fromPointer($ret, retain: true, release: true); - } - - /// fileURLWithPathComponents: - static objc.NSURL? fileURLWithPathComponents(objc.NSArray components) { - objc.checkOsVersionInternal( - 'NSURL.fileURLWithPathComponents:', - iOS: (false, (4, 0, 0)), - macOS: (false, (10, 6, 0)), - ); - final $ret = _objc_msgSend_1sotr3r( - _class_NSURL, - _sel_fileURLWithPathComponents_, - components.ref.pointer, - ); - return $ret.address == 0 - ? null - : objc.NSURL.fromPointer($ret, retain: true, release: true); - } -} - -late final _class_NSFileManager = objc.getClass("NSFileManager"); -late final _sel_isKindOfClass_ = objc.registerName("isKindOfClass:"); -final _objc_msgSend_19nvye5 = objc.msgSendPointer - .cast< - ffi.NativeFunction< - ffi.Bool Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ) - > - >() - .asFunction< - bool Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ) - >(); -late final _sel_defaultManager = objc.registerName("defaultManager"); -late final _sel_containerURLForSecurityApplicationGroupIdentifier_ = objc - .registerName("containerURLForSecurityApplicationGroupIdentifier:"); - -/// NSFileManager -extension type NSFileManager._(objc.ObjCObject object$) - implements objc.ObjCObject, objc.NSObject { - /// Constructs a [NSFileManager] that points to the same underlying object as [other]. - NSFileManager.as(objc.ObjCObject other) : object$ = other { - assert(isA(object$)); - } - - /// Constructs a [NSFileManager] that wraps the given raw object pointer. - NSFileManager.fromPointer( - ffi.Pointer other, { - bool retain = false, - bool release = false, - }) : object$ = objc.ObjCObject(other, retain: retain, release: release) { - assert(isA(object$)); - } - - /// Returns whether [obj] is an instance of [NSFileManager]. - static bool isA(objc.ObjCObject obj) => _objc_msgSend_19nvye5( - obj.ref.pointer, - _sel_isKindOfClass_, - _class_NSFileManager, - ); - - /// defaultManager - static NSFileManager getDefaultManager() { - final $ret = _objc_msgSend_151sglz( - _class_NSFileManager, - _sel_defaultManager, - ); - return NSFileManager.fromPointer($ret, retain: true, release: true); - } -} - -extension NSFileManager$Methods on NSFileManager { - /// containerURLForSecurityApplicationGroupIdentifier: - objc.NSURL? containerURLForSecurityApplicationGroupIdentifier( - objc.NSString groupIdentifier, - ) { - objc.checkOsVersionInternal( - 'NSFileManager.containerURLForSecurityApplicationGroupIdentifier:', - iOS: (false, (7, 0, 0)), - macOS: (false, (10, 8, 0)), - ); - final $ret = _objc_msgSend_1sotr3r( - object$.ref.pointer, - _sel_containerURLForSecurityApplicationGroupIdentifier_, - groupIdentifier.ref.pointer, - ); - return $ret.address == 0 - ? null - : objc.NSURL.fromPointer($ret, retain: true, release: true); - } -} diff --git a/packages/path_provider/path_provider_foundation/lib/src/path_provider_foundation_real.dart b/packages/path_provider/path_provider_foundation/lib/src/path_provider_foundation_real.dart deleted file mode 100644 index 8be02bac2f7..00000000000 --- a/packages/path_provider/path_provider_foundation/lib/src/path_provider_foundation_real.dart +++ /dev/null @@ -1,180 +0,0 @@ -// Copyright 2013 The Flutter Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:ffi' as ffi; -import 'dart:io'; - -import 'package:flutter/foundation.dart'; -import 'package:objective_c/objective_c.dart'; -import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; - -import 'ffi_bindings.g.dart'; - -/// The iOS and macOS implementation of [PathProviderPlatform]. -class PathProviderFoundation extends PathProviderPlatform { - /// Constructor that accepts a testable PathProviderPlatformProvider. - PathProviderFoundation({ - @visibleForTesting PathProviderPlatformProvider? platform, - @visibleForTesting FoundationFFI? ffiLib, - @visibleForTesting - NSURL? Function(NSString)? - containerURLForSecurityApplicationGroupIdentifier, - }) : _platformProvider = platform ?? PathProviderPlatformProvider(), - _ffiLib = ffiLib ?? _lib, - _containerURLForSecurityApplicationGroupIdentifier = - containerURLForSecurityApplicationGroupIdentifier ?? - _sharedNSFileManagerContainerURLForSecurityApplicationGroupIdentifier; - - final PathProviderPlatformProvider _platformProvider; - final FoundationFFI _ffiLib; - final NSURL? Function(NSString) - _containerURLForSecurityApplicationGroupIdentifier; - - /// Registers this class as the default instance of [PathProviderPlatform]. - static void registerWith() { - PathProviderPlatform.instance = PathProviderFoundation(); - } - - @override - Future getTemporaryPath() async { - return _getDirectoryPath(NSSearchPathDirectory.NSCachesDirectory); - } - - @override - Future getApplicationSupportPath() async { - final String? path = _getDirectoryPath( - NSSearchPathDirectory.NSApplicationSupportDirectory, - ); - if (path != null) { - // Ensure the directory exists before returning it, for consistency with - // other platforms. - await Directory(path).create(recursive: true); - } - return path; - } - - @override - Future getLibraryPath() async { - return _getDirectoryPath(NSSearchPathDirectory.NSLibraryDirectory); - } - - @override - Future getApplicationDocumentsPath() async { - return _getDirectoryPath(NSSearchPathDirectory.NSDocumentDirectory); - } - - @override - Future getApplicationCachePath() async { - final String? path = _getDirectoryPath( - NSSearchPathDirectory.NSCachesDirectory, - ); - if (path != null) { - // Ensure the directory exists before returning it, for consistency with - // other platforms. - await Directory(path).create(recursive: true); - } - return path; - } - - @override - Future getExternalStoragePath() async { - throw UnsupportedError( - 'getExternalStoragePath is not supported on this platform', - ); - } - - @override - Future?> getExternalCachePaths() async { - throw UnsupportedError( - 'getExternalCachePaths is not supported on this platform', - ); - } - - @override - Future?> getExternalStoragePaths({ - StorageDirectory? type, - }) async { - throw UnsupportedError( - 'getExternalStoragePaths is not supported on this platform', - ); - } - - @override - Future getDownloadsPath() async { - return _getDirectoryPath(NSSearchPathDirectory.NSDownloadsDirectory); - } - - /// Returns the path to the container of the specified App Group. - /// This is only supported for iOS. - Future getContainerPath({required String appGroupIdentifier}) async { - if (!_platformProvider.isIOS) { - throw UnsupportedError( - 'getContainerPath is not supported on this platform', - ); - } - return _containerURLForSecurityApplicationGroupIdentifier( - NSString(appGroupIdentifier), - )?.path?.toDartString(); - } - - String? _getDirectoryPath(NSSearchPathDirectory directory) { - NSString? path = _getUserDirectory(directory); - if (path != null && _platformProvider.isMacOS) { - // In a non-sandboxed app, these are shared directories where applications - // are expected to use their bundle ID as a subdirectory. For - // non-sandboxed apps, adding the extra path is harmless. - // This is not done for iOS, for compatibility with older versions of the - // plugin. - if (directory == NSSearchPathDirectory.NSApplicationSupportDirectory || - directory == NSSearchPathDirectory.NSCachesDirectory) { - final NSString? bundleIdentifier = - NSBundle.getMainBundle().bundleIdentifier; - if (bundleIdentifier != null) { - final NSURL basePathURL = NSURL.fileURLWithPath(path); - path = basePathURL.URLByAppendingPathComponent( - bundleIdentifier, - )?.path; - } - } - } - return path?.toDartString(); - } - - /// Returns the user-domain directory of the given type. - NSString? _getUserDirectory(NSSearchPathDirectory directory) { - final NSArray paths = _ffiLib.NSSearchPathForDirectoriesInDomains( - directory, - NSSearchPathDomainMask.NSUserDomainMask, - true, - ); - final ObjCObject? first = paths.firstObject; - return first == null ? null : NSString.as(first); - } -} - -/// Helper class for returning information about the current platform. -@visibleForTesting -class PathProviderPlatformProvider { - /// Specifies whether the current platform is iOS. - bool get isIOS => Platform.isIOS; - - /// Specifies whether the current platform is macOS. - bool get isMacOS => Platform.isMacOS; -} - -NSURL? _sharedNSFileManagerContainerURLForSecurityApplicationGroupIdentifier( - NSString groupIdentifier, -) => NSFileManager.getDefaultManager() - .containerURLForSecurityApplicationGroupIdentifier(groupIdentifier); - -final ffi.DynamicLibrary _dylib = () { - return ffi.DynamicLibrary.open( - '/System/Library/Frameworks/Foundation.framework/Foundation', - ); -}(); - -/// The bindings to the native functions in [_dylib]. -final FoundationFFI _lib = () { - return FoundationFFI(_dylib); -}(); diff --git a/packages/path_provider/path_provider_foundation/lib/src/path_provider_foundation_stub.dart b/packages/path_provider/path_provider_foundation/lib/src/path_provider_foundation_stub.dart deleted file mode 100644 index d478136566d..00000000000 --- a/packages/path_provider/path_provider_foundation/lib/src/path_provider_foundation_stub.dart +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2013 The Flutter Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; - -// This file is a stub to satisfy the analyzer by matching the public API -// surface of the real implementation. This code should never actually be -// called. - -/// The iOS and macOS implementation of [PathProviderPlatform]. -class PathProviderFoundation extends PathProviderPlatform { - /// Registers this class as the default instance of [PathProviderPlatform]. - static void registerWith() {} - - /// Returns the path to the container of the specified App Group. - /// This is only supported for iOS. - Future getContainerPath({required String appGroupIdentifier}) async { - return null; - } -} diff --git a/packages/path_provider/path_provider_foundation/pigeons/copyright.txt b/packages/path_provider/path_provider_foundation/pigeons/copyright.txt new file mode 100644 index 00000000000..07e5f8598a8 --- /dev/null +++ b/packages/path_provider/path_provider_foundation/pigeons/copyright.txt @@ -0,0 +1,3 @@ +Copyright 2013 The Flutter Authors +Use of this source code is governed by a BSD-style license that can be +found in the LICENSE file. diff --git a/packages/path_provider/path_provider_foundation/pigeons/messages.dart b/packages/path_provider/path_provider_foundation/pigeons/messages.dart new file mode 100644 index 00000000000..7ca732c475c --- /dev/null +++ b/packages/path_provider/path_provider_foundation/pigeons/messages.dart @@ -0,0 +1,28 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +import 'package:pigeon/pigeon.dart'; + +@ConfigurePigeon( + PigeonOptions( + input: 'pigeons/messages.dart', + swiftOut: + 'darwin/path_provider_foundation/Sources/path_provider_foundation/messages.g.swift', + dartOut: 'lib/messages.g.dart', + copyrightHeader: 'pigeons/copyright.txt', + ), +) +enum DirectoryType { + applicationDocuments, + applicationSupport, + downloads, + library, + temp, + applicationCache, +} + +@HostApi() +abstract class PathProviderApi { + String? getDirectoryPath(DirectoryType type); + String? getContainerPath(String appGroupIdentifier); +} diff --git a/packages/path_provider/path_provider_foundation/pubspec.yaml b/packages/path_provider/path_provider_foundation/pubspec.yaml index a7daee5e898..0c7ac5431fd 100644 --- a/packages/path_provider/path_provider_foundation/pubspec.yaml +++ b/packages/path_provider/path_provider_foundation/pubspec.yaml @@ -2,7 +2,7 @@ name: path_provider_foundation description: iOS and macOS implementation of the path_provider plugin repository: https://github.com/flutter/packages/tree/main/packages/path_provider/path_provider_foundation issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+path_provider%22 -version: 2.5.0 +version: 2.5.1 environment: sdk: ^3.9.0 @@ -13,21 +13,24 @@ flutter: implements: path_provider platforms: ios: + pluginClass: PathProviderPlugin dartPluginClass: PathProviderFoundation + sharedDarwinSource: true macos: + pluginClass: PathProviderPlugin dartPluginClass: PathProviderFoundation + sharedDarwinSource: true dependencies: - ffi: ^2.1.4 flutter: sdk: flutter - objective_c: ^9.1.0 path_provider_platform_interface: ^2.1.0 dev_dependencies: - ffigen: ^20.0.0 flutter_test: sdk: flutter + path: ^1.8.0 + pigeon: ^26.1.0 topics: - files diff --git a/packages/path_provider/path_provider_foundation/test/path_provider_foundation_test.dart b/packages/path_provider/path_provider_foundation/test/path_provider_foundation_test.dart index 506e135768d..f6f3a730f83 100644 --- a/packages/path_provider/path_provider_foundation/test/path_provider_foundation_test.dart +++ b/packages/path_provider/path_provider_foundation/test/path_provider_foundation_test.dart @@ -4,18 +4,15 @@ import 'dart:io'; +import 'package:flutter/src/services/binary_messenger.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:objective_c/src/objective_c_bindings_generated.dart'; -import 'package:path_provider_foundation/src/ffi_bindings.g.dart'; -import 'package:path_provider_foundation/src/path_provider_foundation_real.dart'; - -// Most tests are in integration_test rather than here, because anything that -// needs to create Objective-C objects has to run in the real runtime. +import 'package:path/path.dart' as p; +import 'package:path_provider_foundation/messages.g.dart'; +import 'package:path_provider_foundation/path_provider_foundation.dart'; void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - group('PathProviderFoundation', () { + late FakePathProviderApi api; // These unit tests use the actual filesystem, since an injectable // filesystem would add a runtime dependency to the package, so everything // is contained to a temporary directory. @@ -23,19 +20,137 @@ void main() { setUp(() async { testRoot = Directory.systemTemp.createTempSync(); + api = FakePathProviderApi(); }); tearDown(() { testRoot.deleteSync(recursive: true); }); + test('getTemporaryPath', () async { + final pathProvider = PathProviderFoundation(pathProviderApi: api); + final String temporaryPath = p.join(testRoot.path, 'temporary', 'path'); + api.directoryResult = temporaryPath; + + final String? path = await pathProvider.getTemporaryPath(); + + expect(api.passedDirectoryType, DirectoryType.temp); + expect(path, temporaryPath); + }); + + test('getApplicationSupportPath', () async { + final pathProvider = PathProviderFoundation(pathProviderApi: api); + final String applicationSupportPath = p.join( + testRoot.path, + 'application', + 'support', + 'path', + ); + api.directoryResult = applicationSupportPath; + + final String? path = await pathProvider.getApplicationSupportPath(); + + expect(api.passedDirectoryType, DirectoryType.applicationSupport); + expect(path, applicationSupportPath); + }); + + test( + 'getApplicationSupportPath creates the directory if necessary', + () async { + final pathProvider = PathProviderFoundation(pathProviderApi: api); + final String applicationSupportPath = p.join( + testRoot.path, + 'application', + 'support', + 'path', + ); + api.directoryResult = applicationSupportPath; + + final String? path = await pathProvider.getApplicationSupportPath(); + + expect(Directory(path!).existsSync(), isTrue); + }, + ); + + test('getLibraryPath', () async { + final pathProvider = PathProviderFoundation(pathProviderApi: api); + final String libraryPath = p.join(testRoot.path, 'library', 'path'); + api.directoryResult = libraryPath; + + final String? path = await pathProvider.getLibraryPath(); + + expect(api.passedDirectoryType, DirectoryType.library); + expect(path, libraryPath); + }); + + test('getApplicationDocumentsPath', () async { + final pathProvider = PathProviderFoundation(pathProviderApi: api); + final String applicationDocumentsPath = p.join( + testRoot.path, + 'application', + 'documents', + 'path', + ); + api.directoryResult = applicationDocumentsPath; + + final String? path = await pathProvider.getApplicationDocumentsPath(); + + expect(api.passedDirectoryType, DirectoryType.applicationDocuments); + expect(path, applicationDocumentsPath); + }); + + test('getApplicationCachePath', () async { + final pathProvider = PathProviderFoundation(pathProviderApi: api); + final String applicationCachePath = p.join( + testRoot.path, + 'application', + 'cache', + 'path', + ); + api.directoryResult = applicationCachePath; + + final String? path = await pathProvider.getApplicationCachePath(); + + expect(api.passedDirectoryType, DirectoryType.applicationCache); + expect(path, applicationCachePath); + }); + + test( + 'getApplicationCachePath creates the directory if necessary', + () async { + final pathProvider = PathProviderFoundation(pathProviderApi: api); + final String applicationCachePath = p.join( + testRoot.path, + 'application', + 'cache', + 'path', + ); + api.directoryResult = applicationCachePath; + + final String? path = await pathProvider.getApplicationCachePath(); + + expect(Directory(path!).existsSync(), isTrue); + }, + ); + + test('getDownloadsPath', () async { + final pathProvider = PathProviderFoundation(pathProviderApi: api); + final String downloadsPath = p.join(testRoot.path, 'downloads', 'path'); + api.directoryResult = downloadsPath; + + final String? result = await pathProvider.getDownloadsPath(); + + expect(api.passedDirectoryType, DirectoryType.downloads); + expect(result, downloadsPath); + }); + test('getExternalCachePaths throws', () async { - final pathProvider = PathProviderFoundation(ffiLib: FakeFoundationFFI()); + final pathProvider = PathProviderFoundation(pathProviderApi: api); expect(pathProvider.getExternalCachePaths(), throwsA(isUnsupportedError)); }); test('getExternalStoragePath throws', () async { - final pathProvider = PathProviderFoundation(ffiLib: FakeFoundationFFI()); + final pathProvider = PathProviderFoundation(pathProviderApi: api); expect( pathProvider.getExternalStoragePath(), throwsA(isUnsupportedError), @@ -43,17 +158,35 @@ void main() { }); test('getExternalStoragePaths throws', () async { - final pathProvider = PathProviderFoundation(ffiLib: FakeFoundationFFI()); + final pathProvider = PathProviderFoundation(pathProviderApi: api); expect( pathProvider.getExternalStoragePaths(), throwsA(isUnsupportedError), ); }); + test('getContainerPath', () async { + final pathProvider = PathProviderFoundation( + pathProviderApi: api, + platform: FakePlatformProvider(isIOS: true), + ); + const appGroupIdentifier = 'group.example.test'; + + final String containerPath = p.join(testRoot.path, 'container', 'path'); + api.containerResult = containerPath; + + final String? result = await pathProvider.getContainerPath( + appGroupIdentifier: appGroupIdentifier, + ); + + expect(api.passedAppGroupIdentifier, appGroupIdentifier); + expect(result, containerPath); + }); + test('getContainerPath throws on macOS', () async { final pathProvider = PathProviderFoundation( - platform: FakePlatformProvider(isMacOS: true), - ffiLib: FakeFoundationFFI(), + pathProviderApi: api, + platform: FakePlatformProvider(isIOS: false), ); expect( pathProvider.getContainerPath(appGroupIdentifier: 'group.example.test'), @@ -63,24 +196,37 @@ void main() { }); } -/// Fake implementation of PathProviderPlatformProvider. +/// Fake implementation of PathProviderPlatformProvider that returns iOS is true class FakePlatformProvider implements PathProviderPlatformProvider { - FakePlatformProvider({this.isIOS = false, this.isMacOS = false}); + FakePlatformProvider({required this.isIOS}); @override bool isIOS; +} + +class FakePathProviderApi implements PathProviderApi { + String? directoryResult; + String? containerResult; + + DirectoryType? passedDirectoryType; + String? passedAppGroupIdentifier; @override - bool isMacOS; -} + Future getDirectoryPath(DirectoryType type) async { + passedDirectoryType = type; + return directoryResult; + } -class FakeFoundationFFI implements FoundationFFI { @override - // ignore: non_constant_identifier_names - NSArray NSSearchPathForDirectoriesInDomains( - NSSearchPathDirectory directory, - int domainMask, - bool expandTilde, - ) { - throw UnimplementedError(); + Future getContainerPath(String appGroupIdentifier) async { + passedAppGroupIdentifier = appGroupIdentifier; + return containerResult; } + + @override + // ignore: non_constant_identifier_names + BinaryMessenger? get pigeonVar_binaryMessenger => null; + + @override + // ignore: non_constant_identifier_names + String get pigeonVar_messageChannelSuffix => ''; } diff --git a/packages/path_provider/path_provider_foundation/tool/ffigen.dart b/packages/path_provider/path_provider_foundation/tool/ffigen.dart deleted file mode 100644 index e07f848581e..00000000000 --- a/packages/path_provider/path_provider_foundation/tool/ffigen.dart +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2013 The Flutter Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:io'; - -import 'package:ffigen/ffigen.dart'; - -void main() { - final Uri packageRoot = Platform.script.resolve('../'); - FfiGenerator( - output: Output( - dartFile: packageRoot.resolve('lib/src/ffi_bindings.g.dart'), - style: const DynamicLibraryBindings( - wrapperName: 'FoundationFFI', - wrapperDocComment: 'Bindings for NSFileManager.', - ), - ), - headers: Headers( - entryPoints: [ - Uri.file( - '$macSdkPath/System/Library/Frameworks/AVFAudio.framework/Headers/AVAudioPlayer.h', - ), - ], - ), - objectiveC: ObjectiveC( - interfaces: Interfaces( - include: (Declaration declaration) { - return { - 'NSFileManager', - 'NSURL', - }.contains(declaration.originalName); - }, - includeMember: (Declaration declaration, String member) { - final String interfaceName = declaration.originalName; - final signature = member; - return switch (interfaceName) { - 'NSFileManager' => { - 'containerURLForSecurityApplicationGroupIdentifier:', - 'defaultManager', - }.contains(signature), - 'NSURL' => { - 'fileURLWithPath:', - 'URLByAppendingPathComponent:', - }.contains(signature), - _ => false, - }; - }, - ), - categories: Categories( - include: (Declaration declaration) => { - // For URLByAppendingPathComponent: - 'NSURLPathUtilities', - }.contains(declaration.originalName), - includeTransitive: false, - ), - ), - functions: Functions.includeSet({ - 'NSSearchPathForDirectoriesInDomains', - }), - ).generate(); -}