From 4ba6e2568955c9d1a7f1cf434d358f2ee09b4f15 Mon Sep 17 00:00:00 2001 From: The one with the braid Date: Thu, 4 Apr 2024 11:34:32 +0200 Subject: [PATCH] fix: dart:io import in matrix_sdk_database - removes `Directory` field in high-level `MatrixSdkDatabase` - migrates `Directory fileStoragePath` to `Uri fileStorageLocation` - makes file operations in `MatrixSdkDatabase` conditional on `dart.libraries.js_util` - implements a tiny stub of the file operations used in `MatrixSdkDatabase` It seems like the Flutter tool can compile despite these imports. Sadly the Dart standalone dart2js compiler doesn't reach there. While refactorying the code, I decided it's likely cleaner to have a `Uri` as storage location provider than using some fake directory or String as relacement. The advantage of a `Uri` at this place is the explicit `Uri.directory` constructor available to ensure type and encoding safe directory locations supporting both Windows and *nix. Additionally, admitted, that's an edge-case, one could even easily extend the use of a `Uri` based descriptor to support future storage location accesses (e.g. IPFS or custom schemes for e.g. local web browser based file system APIs). Using a `Uri`, one would only need to override the three methods making use of the `fileStorageLocation` property to handle different Uri schemes too. Signed-off-by: The one with the braid --- .../database/filesystem/fake_filesystem.dart | 37 ++++++++++++++ .../database/filesystem/io_filesystem.dart | 1 + lib/src/database/matrix_sdk_database.dart | 50 +++++++++++++------ 3 files changed, 73 insertions(+), 15 deletions(-) create mode 100644 lib/src/database/filesystem/fake_filesystem.dart create mode 100644 lib/src/database/filesystem/io_filesystem.dart diff --git a/lib/src/database/filesystem/fake_filesystem.dart b/lib/src/database/filesystem/fake_filesystem.dart new file mode 100644 index 00000000..098b0ef2 --- /dev/null +++ b/lib/src/database/filesystem/fake_filesystem.dart @@ -0,0 +1,37 @@ +// This is a stub implementation of all filesystem related calls done +// by the matrix SDK database. This fake implementation ensures we can compile +// using dart2js. + +import 'dart:typed_data'; + +class File extends FileSystemEntry { + const File(super.path); + + Future readAsBytes() async => Uint8List(0); + + Future writeAsBytes(Uint8List data) async => Future.value(); +} + +class Directory extends FileSystemEntry { + const Directory(super.path); + + Stream list() async* { + return; + } +} + +abstract class FileSystemEntry { + final String path; + + const FileSystemEntry(this.path); + + Future delete() => Future.value(); + + Future stat() async => FileStat(); + + Future exists() async => false; +} + +class FileStat { + final modified = DateTime.fromMillisecondsSinceEpoch(0); +} diff --git a/lib/src/database/filesystem/io_filesystem.dart b/lib/src/database/filesystem/io_filesystem.dart new file mode 100644 index 00000000..047f132b --- /dev/null +++ b/lib/src/database/filesystem/io_filesystem.dart @@ -0,0 +1 @@ +export 'dart:io'; diff --git a/lib/src/database/matrix_sdk_database.dart b/lib/src/database/matrix_sdk_database.dart index 4a230769..83df4307 100644 --- a/lib/src/database/matrix_sdk_database.dart +++ b/lib/src/database/matrix_sdk_database.dart @@ -18,7 +18,6 @@ import 'dart:async'; import 'dart:convert'; -import 'dart:io'; import 'dart:math'; import 'dart:typed_data'; @@ -100,11 +99,28 @@ class MatrixSdkDatabase extends DatabaseApi { late Box _seenDeviceIdsBox; late Box _seenDeviceKeysBox; + @override - bool get supportsFileStoring => fileStoragePath != null; + bool get supportsFileStoring => fileStorageLocation != null; @override final int maxFileSize; - final Directory? fileStoragePath; + + // there was a field of type `dart:io:Directory` here. This one broke the + // dart js standalone compiler. Migration via URI as file system identifier. + @Deprecated( + 'Breaks support for web standalone. Use [fileStorageLocation] instead.') + Object? get fileStoragePath => fileStorageLocation?.toFilePath(); + + /// A [Uri] defining the file storage location. + /// + /// Unless you support custom Uri schemes, this should usually be a + /// [Uri.directory] identifier. + /// + /// Using a [Uri] as type here enables users to technically extend the API to + /// support file storage on non-io platforms as well - or to use non-File + /// based storage mechanisms on io. + final Uri? fileStorageLocation; + final Duration? deleteFilesAfterDuration; static const String _clientBoxName = 'box_client'; @@ -168,9 +184,11 @@ class MatrixSdkDatabase extends DatabaseApi { this.idbFactory, this.sqfliteFactory, this.maxFileSize = 0, - this.fileStoragePath, + // TODO : remove deprecated member migration on next major release + dynamic fileStoragePath, + Uri? fileStorageLocation, this.deleteFilesAfterDuration, - }); + }) : fileStorageLocation = fileStorageLocation ?? fileStoragePath?.path; Future open() async { _collection = await BoxCollection.open( @@ -311,13 +329,15 @@ class MatrixSdkDatabase extends DatabaseApi { @override Future deleteOldFiles(int savedAt) async { - final dir = fileStoragePath; + if (_kIsWeb) return; + final path = fileStorageLocation?.toFilePath(); final deleteFilesAfterDuration = this.deleteFilesAfterDuration; if (!supportsFileStoring || - dir == null || + path == null || deleteFilesAfterDuration == null) { return; } + final dir = Directory(path); final entities = await dir.list().toList(); for (final file in entities) { if (file is! File) continue; @@ -453,11 +473,11 @@ class MatrixSdkDatabase extends DatabaseApi { @override Future getFile(Uri mxcUri) async { - final fileStoragePath = this.fileStoragePath; - if (!supportsFileStoring || fileStoragePath == null) return null; + if (_kIsWeb) return null; + final path = fileStorageLocation?.toFilePath(); + if (!supportsFileStoring || path == null) return null; - final file = - File('${fileStoragePath.path}/${mxcUri.toString().split('/').last}'); + final file = File('$path/${mxcUri.toString().split('/').last}'); if (await file.exists()) return await file.readAsBytes(); return null; @@ -1138,11 +1158,11 @@ class MatrixSdkDatabase extends DatabaseApi { @override Future storeFile(Uri mxcUri, Uint8List bytes, int time) async { - final fileStoragePath = this.fileStoragePath; - if (!supportsFileStoring || fileStoragePath == null) return; + if (_kIsWeb) return; + final path = fileStorageLocation?.toFilePath(); + if (!supportsFileStoring || path == null) return; - final file = - File('${fileStoragePath.path}/${mxcUri.toString().split('/').last}'); + final file = File('$path/${mxcUri.toString().split('/').last}'); if (await file.exists()) return; await file.writeAsBytes(bytes);