diff --git a/lib/src/database/database_file_storage_io.dart b/lib/src/database/database_file_storage_io.dart new file mode 100644 index 00000000..6a80efd7 --- /dev/null +++ b/lib/src/database/database_file_storage_io.dart @@ -0,0 +1,55 @@ +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:matrix/matrix.dart'; + +mixin DatabaseFileStorage { + bool get supportsFileStoring => fileStorageLocation != null; + + late final Uri? fileStorageLocation; + late final Duration? deleteFilesAfterDuration; + + Future storeFile(Uri mxcUri, Uint8List bytes, int time) async { + final fileStorageLocation = this.fileStorageLocation; + if (!supportsFileStoring || fileStorageLocation == null) return; + + final dir = Directory.fromUri(fileStorageLocation); + + final file = File('${dir.path}/${mxcUri.toString().split('/').last}'); + + if (await file.exists()) return; + await file.writeAsBytes(bytes); + } + + Future getFile(Uri mxcUri) async { + final fileStorageLocation = this.fileStorageLocation; + if (!supportsFileStoring || fileStorageLocation == null) return null; + + final dir = Directory.fromUri(fileStorageLocation); + + final file = File('${dir.path}/${mxcUri.toString().split('/').last}'); + + if (await file.exists()) return await file.readAsBytes(); + return null; + } + + Future deleteOldFiles(int savedAt) async { + final dirUri = fileStorageLocation; + final deleteFilesAfterDuration = this.deleteFilesAfterDuration; + if (!supportsFileStoring || + dirUri == null || + deleteFilesAfterDuration == null) { + return; + } + final dir = Directory.fromUri(dirUri); + final entities = await dir.list().toList(); + for (final file in entities) { + if (file is! File) continue; + final stat = await file.stat(); + if (DateTime.now().difference(stat.modified) > deleteFilesAfterDuration) { + Logs().v('Delete old file', file.path); + await file.delete(); + } + } + } +} diff --git a/lib/src/database/database_file_storage_stub.dart b/lib/src/database/database_file_storage_stub.dart new file mode 100644 index 00000000..7addfe1a --- /dev/null +++ b/lib/src/database/database_file_storage_stub.dart @@ -0,0 +1,20 @@ +import 'dart:typed_data'; + +mixin DatabaseFileStorage { + bool get supportsFileStoring => false; + + late final Uri? fileStorageLocation; + late final Duration? deleteFilesAfterDuration; + + Future storeFile(Uri mxcUri, Uint8List bytes, int time) async { + return; + } + + Future getFile(Uri mxcUri) async { + return null; + } + + Future deleteOldFiles(int savedAt) async { + return; + } +} diff --git a/lib/src/database/filesystem/fake_filesystem.dart b/lib/src/database/filesystem/fake_filesystem.dart deleted file mode 100644 index 098b0ef2..00000000 --- a/lib/src/database/filesystem/fake_filesystem.dart +++ /dev/null @@ -1,37 +0,0 @@ -// 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 deleted file mode 100644 index 047f132b..00000000 --- a/lib/src/database/filesystem/io_filesystem.dart +++ /dev/null @@ -1 +0,0 @@ -export 'dart:io'; diff --git a/lib/src/database/matrix_sdk_database.dart b/lib/src/database/matrix_sdk_database.dart index 83df4307..96b6e28e 100644 --- a/lib/src/database/matrix_sdk_database.dart +++ b/lib/src/database/matrix_sdk_database.dart @@ -19,7 +19,6 @@ import 'dart:async'; import 'dart:convert'; import 'dart:math'; -import 'dart:typed_data'; import 'package:sqflite_common/sqflite.dart'; @@ -35,6 +34,9 @@ import 'package:matrix/src/utils/run_benchmarked.dart'; import 'package:matrix/src/database/indexeddb_box.dart' if (dart.library.io) 'package:matrix/src/database/sqflite_box.dart'; +import 'package:matrix/src/database/database_file_storage_stub.dart' + if (dart.library.io) 'package:matrix/src/database/database_file_storage_io.dart'; + /// Database based on SQlite3 on native and IndexedDB on web. For native you /// have to pass a `Database` object, which can be created with the sqflite /// package like this: @@ -49,7 +51,7 @@ import 'package:matrix/src/database/indexeddb_box.dart' /// [sqflite_common_ffi](https://pub.dev/packages/sqflite_common_ffi). /// Learn more at: /// https://github.com/famedly/matrix-dart-sdk/issues/1642#issuecomment-1865827227 -class MatrixSdkDatabase extends DatabaseApi { +class MatrixSdkDatabase extends DatabaseApi with DatabaseFileStorage { static const int version = 7; final String name; late BoxCollection _collection; @@ -100,8 +102,6 @@ class MatrixSdkDatabase extends DatabaseApi { late Box _seenDeviceKeysBox; - @override - bool get supportsFileStoring => fileStorageLocation != null; @override final int maxFileSize; @@ -111,18 +111,6 @@ class MatrixSdkDatabase extends DatabaseApi { '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'; static const String _accountDataBoxName = 'box_account_data'; @@ -185,10 +173,17 @@ class MatrixSdkDatabase extends DatabaseApi { this.sqfliteFactory, this.maxFileSize = 0, // TODO : remove deprecated member migration on next major release + @Deprecated( + 'Breaks support for web standalone. Use [fileStorageLocation] instead.') dynamic fileStoragePath, Uri? fileStorageLocation, - this.deleteFilesAfterDuration, - }) : fileStorageLocation = fileStorageLocation ?? fileStoragePath?.path; + Duration? deleteFilesAfterDuration, + }) { + final legacyPath = fileStoragePath?.path; + this.fileStorageLocation = fileStorageLocation ?? + (legacyPath is String ? Uri.tryParse(legacyPath) : null); + this.deleteFilesAfterDuration = deleteFilesAfterDuration; + } Future open() async { _collection = await BoxCollection.open( @@ -327,28 +322,6 @@ class MatrixSdkDatabase extends DatabaseApi { return; } - @override - Future deleteOldFiles(int savedAt) async { - if (_kIsWeb) return; - final path = fileStorageLocation?.toFilePath(); - final deleteFilesAfterDuration = this.deleteFilesAfterDuration; - if (!supportsFileStoring || - 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; - final stat = await file.stat(); - if (DateTime.now().difference(stat.modified) > deleteFilesAfterDuration) { - Logs().v('Delete old file', file.path); - await file.delete(); - } - } - } - @override Future forgetRoom(String roomId) async { await _timelineFragmentsBox.delete(TupleKey(roomId, '').toString()); @@ -471,18 +444,6 @@ class MatrixSdkDatabase extends DatabaseApi { return await _getEventsByIds(eventIds.cast(), room); }); - @override - Future getFile(Uri mxcUri) async { - if (_kIsWeb) return null; - final path = fileStorageLocation?.toFilePath(); - if (!supportsFileStoring || path == null) return null; - - final file = File('$path/${mxcUri.toString().split('/').last}'); - - if (await file.exists()) return await file.readAsBytes(); - return null; - } - @override Future getInboundGroupSession( String roomId, @@ -1156,18 +1117,6 @@ class MatrixSdkDatabase extends DatabaseApi { } } - @override - Future storeFile(Uri mxcUri, Uint8List bytes, int time) async { - if (_kIsWeb) return; - final path = fileStorageLocation?.toFilePath(); - if (!supportsFileStoring || path == null) return; - - final file = File('$path/${mxcUri.toString().split('/').last}'); - - if (await file.exists()) return; - await file.writeAsBytes(bytes); - } - @override Future storeInboundGroupSession( String roomId,