diff --git a/README.md b/README.md index a482599..807aa13 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ The plugins for elinux are basically designed to be API compatible with the offi | ------------------ | ---------------- | | [video_player_elinux](packages/video_player) | [video_player](https://github.com/flutter/plugins/tree/master/packages/video_player) | | [camera_elinux](packages/camera) | [camera](https://github.com/flutter/plugins/tree/master/packages/camera) | -| [path_provider_elinux](packages/path_provider) | [path_provider](https://github.com/flutter/plugins/tree/master/packages/path_provider) | +| [path_provider_elinux](packages/path_provider) | [path_provider](https://github.com/flutter/packages/tree/main/packages/path_provider) | | [shared_preferences_elinux](packages/shared_preferences) | [shared_preferences](https://github.com/flutter/plugins/tree/master/packages/shared_preferences) | | [joystick](packages/joystick) | - | diff --git a/packages/path_provider/CHANGELOG.md b/packages/path_provider/CHANGELOG.md index 2129338..755dc90 100644 --- a/packages/path_provider/CHANGELOG.md +++ b/packages/path_provider/CHANGELOG.md @@ -1,3 +1,6 @@ +## 2.2.0 +* Update for path_provider 2.2.0 + ## 1.0.2 * Update for flutter 3.3.0 release diff --git a/packages/path_provider/example/integration_test/path_provider_test.dart b/packages/path_provider/example/integration_test/path_provider_test.dart index df9babe..8506498 100644 --- a/packages/path_provider/example/integration_test/path_provider_test.dart +++ b/packages/path_provider/example/integration_test/path_provider_test.dart @@ -36,6 +36,12 @@ void main() { final String? result = await provider.getApplicationSupportPath(); _verifySampleFile(result, 'applicationSupport'); }); + + testWidgets('getApplicationCacheDirectory', (WidgetTester tester) async { + final PathProviderELinux provider = PathProviderELinux(); + final String? result = await provider.getApplicationCachePath(); + _verifySampleFile(result, 'applicationCache'); + }); } /// Verify a file called [name] in [directoryPath] by recreating it with test diff --git a/packages/path_provider/example/lib/main.dart b/packages/path_provider/example/lib/main.dart index 0ce5f81..b98329f 100644 --- a/packages/path_provider/example/lib/main.dart +++ b/packages/path_provider/example/lib/main.dart @@ -1,4 +1,4 @@ -// Copyright 2021 Sony Group Corporation. All rights reserved. +// Copyright 2023 Sony Group Corporation. All rights reserved. // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -13,14 +13,18 @@ void main() { /// Sample app class MyApp extends StatefulWidget { + /// Default Constructor + const MyApp({super.key}); + @override - _MyAppState createState() => _MyAppState(); + State createState() => _MyAppState(); } class _MyAppState extends State { String? _tempDirectory = 'Unknown'; String? _downloadsDirectory = 'Unknown'; String? _appSupportDirectory = 'Unknown'; + String? _appCacheDirectory = 'Unknown'; String? _documentsDirectory = 'Unknown'; final PathProviderELinux _provider = PathProviderELinux(); @@ -35,33 +39,36 @@ class _MyAppState extends State { String? tempDirectory; String? downloadsDirectory; String? appSupportDirectory; + String? appCacheDirectory; String? documentsDirectory; // Platform messages may fail, so we use a try/catch PlatformException. try { tempDirectory = await _provider.getTemporaryPath(); - } on PlatformException catch (e, stackTrace) { + } on PlatformException { tempDirectory = 'Failed to get temp directory.'; - print('$tempDirectory $e $stackTrace'); } try { downloadsDirectory = await _provider.getDownloadsPath(); - } on PlatformException catch (e, stackTrace) { + } on PlatformException { downloadsDirectory = 'Failed to get downloads directory.'; - print('$downloadsDirectory $e $stackTrace'); } try { documentsDirectory = await _provider.getApplicationDocumentsPath(); - } on PlatformException catch (e, stackTrace) { + } on PlatformException { documentsDirectory = 'Failed to get documents directory.'; - print('$documentsDirectory $e $stackTrace'); } try { appSupportDirectory = await _provider.getApplicationSupportPath(); - } on PlatformException catch (e, stackTrace) { + } on PlatformException { appSupportDirectory = 'Failed to get documents directory.'; - print('$appSupportDirectory $e $stackTrace'); + } + + try { + appCacheDirectory = await _provider.getApplicationCachePath(); + } on PlatformException { + appCacheDirectory = 'Failed to get cache directory.'; } // If the widget was removed from the tree while the asynchronous platform // message was in flight, we want to discard the reply rather than calling @@ -74,6 +81,7 @@ class _MyAppState extends State { _tempDirectory = tempDirectory; _downloadsDirectory = downloadsDirectory; _appSupportDirectory = appSupportDirectory; + _appCacheDirectory = appCacheDirectory; _documentsDirectory = documentsDirectory; }); } @@ -83,7 +91,7 @@ class _MyAppState extends State { return MaterialApp( home: Scaffold( appBar: AppBar( - title: const Text('Path Provider eLinux example app'), + title: const Text('Path Provider Linux example app'), ), body: Center( child: Column( @@ -92,6 +100,7 @@ class _MyAppState extends State { Text('Documents Directory: $_documentsDirectory\n'), Text('Downloads Directory: $_downloadsDirectory\n'), Text('Application Support Directory: $_appSupportDirectory\n'), + Text('Application Cache Directory: $_appCacheDirectory\n'), ], ), ), diff --git a/packages/path_provider/example/pubspec.yaml b/packages/path_provider/example/pubspec.yaml index b4e89d7..9428043 100644 --- a/packages/path_provider/example/pubspec.yaml +++ b/packages/path_provider/example/pubspec.yaml @@ -3,8 +3,8 @@ description: Demonstrates how to use the path_provider_elinux plugin. publish_to: "none" environment: - sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + sdk: ">=2.18.0 <4.0.0" + flutter: ">=3.3.0" dependencies: flutter: @@ -14,8 +14,6 @@ dependencies: path: ../ dev_dependencies: - flutter_driver: - sdk: flutter flutter_test: sdk: flutter integration_test: diff --git a/packages/path_provider/lib/path_provider_elinux.dart b/packages/path_provider/lib/path_provider_elinux.dart index be19876..8dcd2cd 100644 --- a/packages/path_provider/lib/path_provider_elinux.dart +++ b/packages/path_provider/lib/path_provider_elinux.dart @@ -1,48 +1,6 @@ -// Copyright 2021 Sony Group Corporation. All rights reserved. +// Copyright 2023 Sony Group Corporation. All rights reserved. // Copyright 2013 The Flutter Authors. All rights reserved. // 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:path/path.dart' as path; -import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; -import 'package:xdg_directories/xdg_directories.dart' as xdg; - -/// The elinux implementation of [PathProviderPlatform] -/// -/// This class implements the `package:path_provider` functionality for eLinux -class PathProviderELinux extends PathProviderPlatform { - /// Registers this class as the default instance of [PathProviderPlatform] - static void registerWith() { - PathProviderPlatform.instance = PathProviderELinux(); - } - - @override - Future getTemporaryPath() { - return Future.value('/tmp'); - } - - @override - Future getApplicationSupportPath() async { - final String processName = path.basenameWithoutExtension( - await File('/proc/self/exe').resolveSymbolicLinks()); - final Directory directory = - Directory(path.join(xdg.dataHome.path, processName)); - // Creating the directory if it doesn't exist, because mobile implementations assume the directory exists - if (!directory.existsSync()) { - await directory.create(recursive: true); - } - return directory.path; - } - - @override - Future getApplicationDocumentsPath() { - return Future.value(xdg.getUserDirectory('DOCUMENTS')?.path); - } - - @override - Future getDownloadsPath() { - return Future.value(xdg.getUserDirectory('DOWNLOAD')?.path); - } -} +export 'src/path_provider_elinux.dart'; diff --git a/packages/path_provider/lib/src/get_application_id.dart b/packages/path_provider/lib/src/get_application_id.dart new file mode 100644 index 0000000..e169c02 --- /dev/null +++ b/packages/path_provider/lib/src/get_application_id.dart @@ -0,0 +1,9 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// getApplicationId() 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 'get_application_id_stub.dart' + if (dart.library.ffi) 'get_application_id_real.dart'; diff --git a/packages/path_provider/lib/src/get_application_id_real.dart b/packages/path_provider/lib/src/get_application_id_real.dart new file mode 100644 index 0000000..5e16df0 --- /dev/null +++ b/packages/path_provider/lib/src/get_application_id_real.dart @@ -0,0 +1,78 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; +import 'package:flutter/foundation.dart' show visibleForTesting; + +// GApplication* g_application_get_default(); +typedef _GApplicationGetDefaultC = IntPtr Function(); +typedef _GApplicationGetDefaultDart = int Function(); + +// const gchar* g_application_get_application_id(GApplication* application); +typedef _GApplicationGetApplicationIdC = Pointer Function(IntPtr); +typedef _GApplicationGetApplicationIdDart = Pointer Function(int); + +/// Interface for interacting with libgio. +@visibleForTesting +class GioUtils { + /// Creates a default instance that uses the real libgio. + GioUtils() { + try { + _gio = DynamicLibrary.open('libgio-2.0.so'); + } on ArgumentError { + _gio = null; + } + } + + DynamicLibrary? _gio; + + /// True if libgio was opened successfully. + bool get libraryIsPresent => _gio != null; + + /// Wraps `g_application_get_default`. + int gApplicationGetDefault() { + if (_gio == null) { + return 0; + } + final _GApplicationGetDefaultDart getDefault = _gio! + .lookupFunction<_GApplicationGetDefaultC, _GApplicationGetDefaultDart>( + 'g_application_get_default'); + return getDefault(); + } + + /// Wraps g_application_get_application_id. + Pointer gApplicationGetApplicationId(int app) { + if (_gio == null) { + return nullptr; + } + final _GApplicationGetApplicationIdDart gApplicationGetApplicationId = _gio! + .lookupFunction<_GApplicationGetApplicationIdC, + _GApplicationGetApplicationIdDart>( + 'g_application_get_application_id'); + return gApplicationGetApplicationId(app); + } +} + +/// Allows overriding the default GioUtils instance with a fake for testing. +@visibleForTesting +GioUtils? gioUtilsOverride; + +/// Gets the application ID for this app. +String? getApplicationId() { + final GioUtils gio = gioUtilsOverride ?? GioUtils(); + if (!gio.libraryIsPresent) { + return null; + } + + final int app = gio.gApplicationGetDefault(); + if (app == 0) { + return null; + } + final Pointer appId = gio.gApplicationGetApplicationId(app); + if (appId == nullptr) { + return null; + } + return appId.toDartString(); +} diff --git a/packages/path_provider/lib/src/get_application_id_stub.dart b/packages/path_provider/lib/src/get_application_id_stub.dart new file mode 100644 index 0000000..9099976 --- /dev/null +++ b/packages/path_provider/lib/src/get_application_id_stub.dart @@ -0,0 +1,6 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/// Gets the application ID for this app. +String? getApplicationId() => null; diff --git a/packages/path_provider/lib/src/path_provider_elinux.dart b/packages/path_provider/lib/src/path_provider_elinux.dart new file mode 100644 index 0000000..bd3b19e --- /dev/null +++ b/packages/path_provider/lib/src/path_provider_elinux.dart @@ -0,0 +1,103 @@ +// Copyright 2023 Sony Group Corporation. All rights reserved. +// Copyright 2013 The Flutter Authors. All rights reserved. +// 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:flutter/foundation.dart'; +import 'package:path/path.dart' as path; +import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; +import 'package:xdg_directories/xdg_directories.dart' as xdg; + +import 'get_application_id.dart'; + +/// The elinux implementation of [PathProviderPlatform] +/// +/// This class implements the `package:path_provider` functionality for eLinux +class PathProviderELinux extends PathProviderPlatform { + /// Constructs an instance of [PathProviderELinux] + PathProviderELinux() : _environment = Platform.environment; + + /// Constructs an instance of [PathProviderELinux] with the given [environment] + @visibleForTesting + PathProviderELinux.private( + {Map environment = const {}, + String? executableName, + String? applicationId}) + : _environment = environment, + _executableName = executableName, + _applicationId = applicationId; + + final Map _environment; + String? _executableName; + String? _applicationId; + + /// Registers this class as the default instance of [PathProviderPlatform] + static void registerWith() { + PathProviderPlatform.instance = PathProviderELinux(); + } + + @override + Future getTemporaryPath() { + final String environmentTmpDir = _environment['TMPDIR'] ?? ''; + return Future.value( + environmentTmpDir.isEmpty ? '/tmp' : environmentTmpDir, + ); + } + + @override + Future getApplicationSupportPath() async { + final Directory directory = + Directory(path.join(xdg.dataHome.path, await _getId())); + if (directory.existsSync()) { + return directory.path; + } + + // This plugin originally used the executable name as a directory. + // Use that if it exists for backwards compatibility. + final Directory legacyDirectory = + Directory(path.join(xdg.dataHome.path, await _getExecutableName())); + if (legacyDirectory.existsSync()) { + return legacyDirectory.path; + } + + // Create the directory, because mobile implementations assume the directory exists. + await directory.create(recursive: true); + return directory.path; + } + + @override + Future getApplicationDocumentsPath() { + return Future.value(xdg.getUserDirectory('DOCUMENTS')?.path); + } + + @override + Future getApplicationCachePath() async { + final Directory directory = + Directory(path.join(xdg.cacheHome.path, await _getId())); + if (!directory.existsSync()) { + await directory.create(recursive: true); + } + return directory.path; + } + + @override + Future getDownloadsPath() { + return Future.value(xdg.getUserDirectory('DOWNLOAD')?.path); + } + + // Gets the name of this executable. + Future _getExecutableName() async { + _executableName ??= path.basenameWithoutExtension( + await File('/proc/self/exe').resolveSymbolicLinks()); + return _executableName!; + } + + // Gets the unique ID for this application. + Future _getId() async { + _applicationId ??= getApplicationId(); + // If no application ID then fall back to using the executable name. + return _applicationId ?? await _getExecutableName(); + } +} diff --git a/packages/path_provider/pubspec.yaml b/packages/path_provider/pubspec.yaml index 3232bdd..213d4da 100644 --- a/packages/path_provider/pubspec.yaml +++ b/packages/path_provider/pubspec.yaml @@ -2,11 +2,11 @@ name: path_provider_elinux description: eLinux implementation of the path_provider plugin homepage: https://github.com/sony/flutter-elinux-plugins repository: https://github.com/sony/flutter-elinux-plugins/tree/main/packages/path_provider -version: 1.0.2 +version: 2.2.0 environment: - sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + sdk: ">=2.18.0 <4.0.0" + flutter: ">=3.3.0" flutter: plugin: @@ -17,11 +17,12 @@ flutter: pluginClass: none dependencies: + ffi: ">=1.1.2 <3.0.0" flutter: sdk: flutter path: ^1.8.0 - path_provider_platform_interface: ^2.0.0 - xdg_directories: ^0.2.0 + path_provider_platform_interface: ^2.1.0 + xdg_directories: ">=0.2.0 <2.0.0" dev_dependencies: flutter_test: diff --git a/packages/path_provider/test/path_provider_elinux_test.dart b/packages/path_provider/test/path_provider_elinux_test.dart index 8e7c639..4a1850b 100644 --- a/packages/path_provider/test/path_provider_elinux_test.dart +++ b/packages/path_provider/test/path_provider_elinux_test.dart @@ -1,9 +1,11 @@ +// Copyright 2023 Sony Group Corporation. All rights reserved. // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter_test/flutter_test.dart'; import 'package:path_provider_elinux/path_provider_elinux.dart'; import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; +import 'package:xdg_directories/xdg_directories.dart' as xdg; void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -13,14 +15,42 @@ void main() { expect(PathProviderPlatform.instance, isA()); }); - test('getTemporaryPath', () async { - final PathProviderPlatform plugin = PathProviderPlatform.instance; + test('getTemporaryPath defaults to TMPDIR', () async { + final PathProviderPlatform plugin = PathProviderELinux.private( + environment: {'TMPDIR': '/run/user/0/tmp'}, + ); + expect(await plugin.getTemporaryPath(), '/run/user/0/tmp'); + }); + + test('getTemporaryPath uses fallback if TMPDIR is empty', () async { + final PathProviderPlatform plugin = PathProviderELinux.private( + environment: {'TMPDIR': ''}, + ); + expect(await plugin.getTemporaryPath(), '/tmp'); + }); + + test('getTemporaryPath uses fallback if TMPDIR is unset', () async { + final PathProviderPlatform plugin = PathProviderELinux.private( + environment: {}, + ); expect(await plugin.getTemporaryPath(), '/tmp'); }); test('getApplicationSupportPath', () async { - final PathProviderPlatform plugin = PathProviderPlatform.instance; - expect(await plugin.getApplicationSupportPath(), startsWith('/')); + final PathProviderPlatform plugin = PathProviderELinux.private( + executableName: 'path_provider_elinux_test_binary', + applicationId: 'com.example.Test'); + // Note this will fail if ${xdg.dataHome.path}/path_provider_linux_test_binary exists on the local filesystem. + expect(await plugin.getApplicationSupportPath(), + '${xdg.dataHome.path}/com.example.Test'); + }); + + test('getApplicationSupportPath uses executable name if no application Id', + () async { + final PathProviderPlatform plugin = PathProviderELinux.private( + executableName: 'path_provider_elinux_test_binary'); + expect(await plugin.getApplicationSupportPath(), + '${xdg.dataHome.path}/path_provider_elinux_test_binary'); }); test('getApplicationDocumentsPath', () async { @@ -28,6 +58,13 @@ void main() { expect(await plugin.getApplicationDocumentsPath(), startsWith('/')); }); + test('getApplicationCachePath', () async { + final PathProviderPlatform plugin = PathProviderELinux.private( + executableName: 'path_provider_elinux_test_binary'); + expect(await plugin.getApplicationCachePath(), + '${xdg.cacheHome.path}/path_provider_elinux_test_binary'); + }); + test('getDownloadsPath', () async { final PathProviderPlatform plugin = PathProviderPlatform.instance; expect(await plugin.getDownloadsPath(), startsWith('/'));