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 <info@braid.business>
This commit is contained in:
The one with the braid 2024-04-04 11:34:32 +02:00 committed by Krille
parent b7bc6c3b79
commit 4ba6e25689
No known key found for this signature in database
GPG Key ID: E067ECD60F1A0652
3 changed files with 73 additions and 15 deletions

View File

@ -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<Uint8List> readAsBytes() async => Uint8List(0);
Future<void> writeAsBytes(Uint8List data) async => Future.value();
}
class Directory extends FileSystemEntry {
const Directory(super.path);
Stream<FileSystemEntry> list() async* {
return;
}
}
abstract class FileSystemEntry {
final String path;
const FileSystemEntry(this.path);
Future<void> delete() => Future.value();
Future<FileStat> stat() async => FileStat();
Future<bool> exists() async => false;
}
class FileStat {
final modified = DateTime.fromMillisecondsSinceEpoch(0);
}

View File

@ -0,0 +1 @@
export 'dart:io';

View File

@ -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<String> _seenDeviceIdsBox;
late Box<String> _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<void> open() async {
_collection = await BoxCollection.open(
@ -311,13 +329,15 @@ class MatrixSdkDatabase extends DatabaseApi {
@override
Future<void> 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<Uint8List?> 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<void> 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);