From d7605cbf9d29e87b8d6ae58e488e4c97ccfcc6ba Mon Sep 17 00:00:00 2001 From: iamalper <29778028+iamalper@users.noreply.github.com> Date: Fri, 15 Dec 2023 22:11:31 +0300 Subject: [PATCH] Support working on background #7 general improvements --- integration_test/app_test.dart | 28 ++++++++ integration_test/mobile_test.dart | 14 ++-- lib/classes/database.dart | 11 ++-- lib/classes/notifications.dart | 19 ++++-- lib/classes/workers/worker_interface.dart | 80 +++++++++++++---------- lib/models.dart | 12 ++++ 6 files changed, 114 insertions(+), 50 deletions(-) diff --git a/integration_test/app_test.dart b/integration_test/app_test.dart index a1a02a2..60a668d 100644 --- a/integration_test/app_test.dart +++ b/integration_test/app_test.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'dart:math'; import 'package:file_picker/file_picker.dart'; +import 'package:weepy/classes/database.dart'; import 'package:weepy/classes/discover.dart'; import 'package:weepy/classes/exceptions.dart'; import 'package:weepy/classes/sender.dart'; @@ -17,6 +18,7 @@ void main() { var sendingFiles = []; var platformFiles = []; late Directory subdir; + //TODO: Refactor setUpAll(() async { final tempDir = await getTemporaryDirectory(); subdir = tempDir.createTempSync("sending"); @@ -43,6 +45,32 @@ void main() { name: path.basename(sendingFiles[index].path), path: sendingFiles[index].path)); }); + + group("Database tests", () { + final db = DatabaseManager(); + setUp(() => db.clear()); + testWidgets("Downloaded file insert", (_) async { + final file = DbFile( + name: "test1", + path: "/.../../", + time: DateTime.now(), + fileStatus: DbFileStatus.download); + await db.insert(file); + final savedFiles = await db.files; + expect(savedFiles, equals([file])); + }); + testWidgets("Uploaded file insert", (_) async { + final file = DbFile( + name: "test1", + path: "/.../../", + time: DateTime.now(), + fileStatus: DbFileStatus.upload); + await db.insert(file); + final savedFiles = await db.files; + expect(savedFiles, equals([file])); + }); + tearDown(() => db.close()); + }); group('IO tests', () { var downloadedFiles = []; Receiver? receiver; diff --git a/integration_test/mobile_test.dart b/integration_test/mobile_test.dart index 44e66b2..c7b5009 100644 --- a/integration_test/mobile_test.dart +++ b/integration_test/mobile_test.dart @@ -42,13 +42,16 @@ void main() { name: path.basename(sendingFiles[index].path), path: sendingFiles[index].path)); }); + + final dbStatusVariant = ValueVariant({true, false}); + testWidgets("Test IsolatedReceiver & IsolatedSender", (_) async { receiver = IsolatedReceiver( saveToTemp: true, - useDb: false, + useDb: dbStatusVariant.currentValue!, onAllFilesDownloaded: (files) => downloadedFiles = files, onDownloadError: (error) => throw error, - progressNotification: false); + progressNotification: true); final code = await receiver!.listen(); var allDevices = []; while (allDevices.isEmpty) { @@ -56,15 +59,16 @@ void main() { } final devices = allDevices.where((device) => device.code == code); expect(devices, isNotEmpty, reason: "Expected to discover itself"); - await IsolatedSender(progressNotification: false) - .send(devices.first, platformFiles, useDb: false); + await IsolatedSender(progressNotification: false).send( + devices.first, platformFiles, + useDb: dbStatusVariant.currentValue!); for (var i = 0; i < sendingFiles.length; i++) { final gidenDosya = sendingFiles[i]; final gelenDosya = File(downloadedFiles[i].path); expect(gidenDosya.readAsBytesSync(), equals(gelenDosya.readAsBytesSync()), reason: "All sent files expected to has same content as originals"); } - }); + }, variant: dbStatusVariant); tearDown(() { for (var file in downloadedFiles) { File(file.path).deleteSync(); diff --git a/lib/classes/database.dart b/lib/classes/database.dart index e711923..45f89bd 100644 --- a/lib/classes/database.dart +++ b/lib/classes/database.dart @@ -6,13 +6,13 @@ import '../models.dart'; class DatabaseManager { bool _initalised = false; - Future get _db { + Future get _db async { if (!_initalised && (Platform.isLinux || Platform.isWindows)) { sqfliteFfiInit(); databaseFactory = databaseFactoryFfiNoIsolate; } - _initalised = true; - return openDatabase("files.db", version: 2, onCreate: (db, version) async { + final db = await openDatabase("files.db", version: 2, + onCreate: (db, version) async { await db.execute( "create table downloaded (ID integer primary key autoincrement, name text not null, path text not null, type text, timeepoch int not null)"); await db.execute( @@ -26,7 +26,8 @@ class DatabaseManager { .execute("alter table uploaded rename column time to timeepoch"); } on Exception catch (e) { //Some old android devices does not support 'alert table' - //Workaround: Dropping table then recreating + // + //Workaround: Dropping table then recreating table //since database contains only file history that would not be a problem await FirebaseCrashlytics.instance.recordError(e, null, reason: @@ -42,6 +43,8 @@ class DatabaseManager { throw UnsupportedError("Unsupported db version"); } }); + _initalised = true; + return db; } ///Insert a uploaded or downloaded file information diff --git a/lib/classes/notifications.dart b/lib/classes/notifications.dart index 69535f2..a9a8c9d 100644 --- a/lib/classes/notifications.dart +++ b/lib/classes/notifications.dart @@ -27,7 +27,6 @@ AndroidNotificationDetails _androidDetails(int progress, Type type) { showProgress: true, maxProgress: 100, progress: progress, - channelAction: AndroidNotificationChannelAction.update, category: AndroidNotificationCategory.progress); } @@ -35,7 +34,7 @@ AndroidNotificationDetails _androidDetails(int progress, Type type) { /// ///Make sure [progress] within range 0 to 100 Future showDownload(int progress) => _localNotifications.show( - 0, + Type.download.index, null, null, NotificationDetails(android: _androidDetails(progress, Type.download))); @@ -43,13 +42,23 @@ Future showDownload(int progress) => _localNotifications.show( ///Show or update upload progress notification. /// ///Make sure [progress] within range 0 to 100 -Future showUpload(int progress) => _localNotifications.show(1, null, null, +Future showUpload(int progress) => _localNotifications.show( + Type.upload.index, + null, + null, NotificationDetails(android: _androidDetails(progress, Type.upload))); -Future cancelDownload() => _localNotifications.cancel(0); +Future cancelDownload() async { + //_localNotifications.cancel(Type.download.index); +} -Future cancelUpload() => _localNotifications.cancel(1); +Future cancelUpload() async { + //_localNotifications.cancel(Type.upload.index); +} +///Inıtalise local notifications. +/// +///Call before using any method. Future initalise() async { final status = await _localNotifications.initialize( const InitializationSettings( diff --git a/lib/classes/workers/worker_interface.dart b/lib/classes/workers/worker_interface.dart index a8d4921..15a573c 100644 --- a/lib/classes/workers/worker_interface.dart +++ b/lib/classes/workers/worker_interface.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:developer'; import 'dart:io'; import 'package:file_picker/file_picker.dart'; @@ -132,12 +133,12 @@ class IsolatedSender extends Sender { await _workManager.registerOneOffTask(MyTasks.send.name, MyTasks.send.name, inputData: map); if (progressNotification) { - await notifications.showDownload(0); + await notifications.showUpload(0); } + await exitBlock.future; if (progressNotification) { - await notifications.cancelDownload(); + await notifications.cancelUpload(); } - return exitBlock.future; } @override @@ -169,7 +170,8 @@ class IsolatedReceiver extends Receiver { ///Starts worker and runs [Receiver.listen] /// - ///If called twice, it has no effect. + ///If necessary requests permission. Throws [NoStoragePermissionException] + ///if permission rejected by user. @override Future listen() async { await initalize(); @@ -200,38 +202,44 @@ class IsolatedReceiver extends Receiver { } Future _portCallback(data) async { - final type = messages.MessageType.values[data["type"]]; - switch (type) { - case messages.MessageType.updatePercent: - final message = messages.UpdatePercent.fromMap(data); - if (progressNotification) { - await notifications.showDownload((message.newPercent * 100).round()); - } - super.onDownloadUpdatePercent?.call(message.newPercent); - break; - case messages.MessageType.filedropError: - final message = messages.FiledropError.fromMap(data); - if (progressNotification) { - await notifications.cancelDownload(); - } - super.onDownloadError?.call(message.exception); - break; - case messages.MessageType.fileDownloaded: - final message = messages.FileDownloaded.fromMap(data); - super.onFileDownloaded?.call(message.file); - break; - case messages.MessageType.allFilesDownloaded: - final message = messages.AllFilesDownloaded.fromMap(data); - if (progressNotification) { - await notifications.cancelDownload(); - } - super.onAllFilesDownloaded?.call(message.files.toList()); - break; - case messages.MessageType.downloadStarted: - final _ = messages.DownloadStarted.fromMap(data); - super.onDownloadStart?.call(); - default: - throw Error(); + try { + final type = messages.MessageType.values[data["type"]]; + switch (type) { + case messages.MessageType.updatePercent: + final message = messages.UpdatePercent.fromMap(data); + if (progressNotification) { + await notifications + .showDownload((message.newPercent * 100).round()); + } + super.onDownloadUpdatePercent?.call(message.newPercent); + break; + case messages.MessageType.filedropError: + final message = messages.FiledropError.fromMap(data); + if (progressNotification) { + await notifications.cancelDownload(); + } + super.onDownloadError?.call(message.exception); + break; + case messages.MessageType.fileDownloaded: + final message = messages.FileDownloaded.fromMap(data); + super.onFileDownloaded?.call(message.file); + break; + case messages.MessageType.allFilesDownloaded: + final message = messages.AllFilesDownloaded.fromMap(data); + if (progressNotification) { + await notifications.cancelDownload(); + } + super.onAllFilesDownloaded?.call(message.files.toList()); + break; + case messages.MessageType.downloadStarted: + final _ = messages.DownloadStarted.fromMap(data); + super.onDownloadStart?.call(); + default: + throw Error(); + } + } on Exception catch (e) { + log("Interface error", name: "IsolatedReceiver", error: e); + rethrow; } } } diff --git a/lib/models.dart b/lib/models.dart index fef2c86..8e65fdf 100644 --- a/lib/models.dart +++ b/lib/models.dart @@ -84,6 +84,18 @@ class DbFile { "timeepoch": timeEpoch, "fileStatus": fileStatus.index }; + @override + int get hashCode => + path.hashCode ^ timeEpoch.hashCode ^ fileStatus.index.hashCode; + + @override + bool operator ==(Object other) { + if (other is DbFile) { + return other.hashCode == hashCode; + } else { + return false; + } + } } class Device {