import 'dart:async'; import 'package:aves/utils/android_file_utils.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:streams_channel/streams_channel.dart'; abstract class StorageService { Future> getStorageVolumes(); Future getFreeSpace(StorageVolume volume); Future> getGrantedDirectories(); Future revokeDirectoryAccess(String path); Future> getInaccessibleDirectories(Iterable dirPaths); Future> getRestrictedDirectories(); // returns whether user granted access to volume root at `volumePath` Future requestVolumeAccess(String volumePath); // returns number of deleted directories Future deleteEmptyDirectories(Iterable dirPaths); // returns media URI Future scanFile(String path, String mimeType); } class PlatformStorageService implements StorageService { static const platform = MethodChannel('deckers.thibault/aves/storage'); static final StreamsChannel storageAccessChannel = StreamsChannel('deckers.thibault/aves/storageaccessstream'); @override Future> getStorageVolumes() async { try { final result = await platform.invokeMethod('getStorageVolumes'); return (result as List).cast().map((map) => StorageVolume.fromMap(map)).toSet(); } on PlatformException catch (e) { debugPrint('getStorageVolumes failed with code=${e.code}, exception=${e.message}, details=${e.details}}'); } return {}; } @override Future getFreeSpace(StorageVolume volume) async { try { final result = await platform.invokeMethod('getFreeSpace', { 'path': volume.path, }); return result as int?; } on PlatformException catch (e) { debugPrint('getFreeSpace failed with code=${e.code}, exception=${e.message}, details=${e.details}}'); } return null; } @override Future> getGrantedDirectories() async { try { final result = await platform.invokeMethod('getGrantedDirectories'); return (result as List).cast(); } on PlatformException catch (e) { debugPrint('getGrantedDirectories failed with code=${e.code}, exception=${e.message}, details=${e.details}}'); } return []; } @override Future revokeDirectoryAccess(String path) async { try { await platform.invokeMethod('revokeDirectoryAccess', { 'path': path, }); } on PlatformException catch (e) { debugPrint('revokeDirectoryAccess failed with code=${e.code}, exception=${e.message}, details=${e.details}}'); } return; } @override Future> getInaccessibleDirectories(Iterable dirPaths) async { try { final result = await platform.invokeMethod('getInaccessibleDirectories', { 'dirPaths': dirPaths.toList(), }); if (result != null) { return (result as List).cast().map(VolumeRelativeDirectory.fromMap).toSet(); } } on PlatformException catch (e) { debugPrint('getInaccessibleDirectories failed with code=${e.code}, exception=${e.message}, details=${e.details}}'); } return {}; } @override Future> getRestrictedDirectories() async { try { final result = await platform.invokeMethod('getRestrictedDirectories'); if (result != null) { return (result as List).cast().map(VolumeRelativeDirectory.fromMap).toSet(); } } on PlatformException catch (e) { debugPrint('getRestrictedDirectories failed with code=${e.code}, exception=${e.message}, details=${e.details}}'); } return {}; } // returns whether user granted access to volume root at `volumePath` @override Future requestVolumeAccess(String volumePath) async { try { final completer = Completer(); storageAccessChannel.receiveBroadcastStream({ 'path': volumePath, }).listen( (data) => completer.complete(data as bool?), onError: completer.completeError, onDone: () { if (!completer.isCompleted) completer.complete(false); }, cancelOnError: true, ); return completer.future; } on PlatformException catch (e) { debugPrint('requestVolumeAccess failed with code=${e.code}, exception=${e.message}, details=${e.details}}'); } return false; } // returns number of deleted directories @override Future deleteEmptyDirectories(Iterable dirPaths) async { try { final result = await platform.invokeMethod('deleteEmptyDirectories', { 'dirPaths': dirPaths.toList(), }); if (result != null) return result as int; } on PlatformException catch (e) { debugPrint('deleteEmptyDirectories failed with code=${e.code}, exception=${e.message}, details=${e.details}}'); } return 0; } // returns media URI @override Future scanFile(String path, String mimeType) async { debugPrint('scanFile with path=$path, mimeType=$mimeType'); try { final result = await platform.invokeMethod('scanFile', { 'path': path, 'mimeType': mimeType, }); if (result != null) return Uri.tryParse(result); } on PlatformException catch (e) { debugPrint('scanFile failed with code=${e.code}, exception=${e.message}, details=${e.details}}'); } return null; } }