From f573335cb8592ff0ae1eed5ebffc57493a761ca0 Mon Sep 17 00:00:00 2001 From: iamalper <29778028+iamalper@users.noreply.github.com> Date: Sat, 9 Dec 2023 13:18:11 +0300 Subject: [PATCH] Support working on background #7 complete implemention of isolatedreceiver & isolatedsender --- .github/workflows/build.yml | 4 +- integration_test/app_test.dart | 55 ++++---- integration_test/fake_path_provider.dart | 1 - integration_test/mobile_test.dart | 77 ++++++++++ lib/classes/database.dart | 47 ++++--- lib/classes/receiver.dart | 56 +++++--- lib/classes/workers/worker_interface.dart | 164 +++++++++++++--------- lib/classes/workers/worker_messages.dart | 99 ++++++++++++- lib/models.dart | 15 +- test/unit_test.dart | 23 +++ 10 files changed, 404 insertions(+), 137 deletions(-) create mode 100644 integration_test/mobile_test.dart create mode 100644 test/unit_test.dart diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f7ba90b..15ded1d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -121,7 +121,7 @@ jobs: - run: flutter config --enable-linux-desktop - name: Running flutter tests - run: xvfb-run flutter test integration_test + run: xvfb-run flutter test integration_test/app_test.dart - name: Start linux build run: flutter build linux @@ -166,7 +166,7 @@ jobs: - run: flutter config --enable-windows-desktop - name: Running flutter tests - run: flutter test integration_test + run: flutter test integration_test\app_test.dart - name: Build for windows run: flutter build windows diff --git a/integration_test/app_test.dart b/integration_test/app_test.dart index f4eef24..a1a02a2 100644 --- a/integration_test/app_test.dart +++ b/integration_test/app_test.dart @@ -17,9 +17,7 @@ void main() { var sendingFiles = []; var platformFiles = []; late Directory subdir; - late Sender sender; setUpAll(() async { - sender = Sender(); final tempDir = await getTemporaryDirectory(); subdir = tempDir.createTempSync("sending"); final testImageData = await rootBundle.load("assets/test_image.png"); @@ -47,28 +45,32 @@ void main() { }); group('IO tests', () { var downloadedFiles = []; - final recieve = Receiver( - saveToTemp: true, - useDb: false, - onAllFilesDownloaded: (files) => downloadedFiles = files); - - testWidgets('Discover, send and receive files', (_) async { - final code = await recieve.listen(); - var allDevices = []; - while (allDevices.isEmpty) { - allDevices = await Discover.discover(); - } - final devices = allDevices.where((device) => device.code == code); - expect(devices, isNotEmpty, reason: "Expected to discover itself"); - await sender.send(devices.first, platformFiles, useDb: false); - 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"); - } - }); + Receiver? receiver; + testWidgets( + 'Discover, send and receive files', + (_) async { + receiver = Receiver( + saveToTemp: true, + useDb: false, + onAllFilesDownloaded: (files) => downloadedFiles = files); + final code = await receiver!.listen(); + var allDevices = []; + while (allDevices.isEmpty) { + allDevices = await Discover.discover(); + } + final devices = allDevices.where((device) => device.code == code); + expect(devices, isNotEmpty, reason: "Expected to discover itself"); + await Sender().send(devices.first, platformFiles, useDb: false); + 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"); + } + }, + ); tearDown(() { for (var file in downloadedFiles) { @@ -77,7 +79,7 @@ void main() { downloadedFiles = []; }); tearDownAll(() async { - await recieve.stopListening(); + await receiver?.stopListening(); }); }); @@ -85,7 +87,7 @@ void main() { testWidgets("Handle no_receiver error", (_) async { final rand1 = Random().nextInt(30); final rand2 = Random().nextInt(30); - final sendFuture = sender.send( + final sendFuture = Sender().send( Device(adress: "192.168.$rand1.$rand2", code: 1000, port: 2326), platformFiles, useDb: false); @@ -93,6 +95,7 @@ void main() { }, retry: 2); testWidgets("Handle connection lost while reciving", (_) async { FileDropException? throwedError; + final sender = Sender(); final code = await Receiver( onDownloadError: (error) => throwedError = error, useDb: false, diff --git a/integration_test/fake_path_provider.dart b/integration_test/fake_path_provider.dart index 2a5f737..a0f2b4e 100644 --- a/integration_test/fake_path_provider.dart +++ b/integration_test/fake_path_provider.dart @@ -1,5 +1,4 @@ import 'dart:io'; - import 'package:path/path.dart' as p; import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; diff --git a/integration_test/mobile_test.dart b/integration_test/mobile_test.dart new file mode 100644 index 0000000..44e66b2 --- /dev/null +++ b/integration_test/mobile_test.dart @@ -0,0 +1,77 @@ +import 'dart:io'; +import 'package:flutter/services.dart'; +import 'package:path/path.dart' as path; +import 'package:file_picker/file_picker.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:weepy/classes/discover.dart'; +import 'package:weepy/classes/workers/worker_interface.dart'; +import 'package:weepy/models.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + var sendingFiles = []; + var platformFiles = []; + var downloadedFiles = []; + IsolatedReceiver? receiver; + late Directory subdir; + setUpAll(() async { + final tempDir = await getTemporaryDirectory(); + subdir = tempDir.createTempSync("sending"); + final testImageData = await rootBundle.load("assets/test_image.png"); + final testImageFile = File(path.join(subdir.path, "test_image.png")); + testImageFile.writeAsBytesSync(testImageData.buffer.asInt8List(), + mode: FileMode.writeOnly); + sendingFiles = [ + File(path.join(subdir.path, "deneme 1.txt")), + File(path.join(subdir.path, "deneme 2.txt")), + testImageFile + ]; + for (var gidenDosya in sendingFiles) { + if (path.extension(gidenDosya.path) == ".txt") { + gidenDosya.writeAsStringSync("deneme gövdesi", + mode: FileMode.writeOnly); + } + } + platformFiles = List.generate( + sendingFiles.length, + (index) => PlatformFile( + //readStream: sendingFiles[index].openRead(), + size: sendingFiles[index].lengthSync(), + name: path.basename(sendingFiles[index].path), + path: sendingFiles[index].path)); + }); + testWidgets("Test IsolatedReceiver & IsolatedSender", (_) async { + receiver = IsolatedReceiver( + saveToTemp: true, + useDb: false, + onAllFilesDownloaded: (files) => downloadedFiles = files, + onDownloadError: (error) => throw error, + progressNotification: false); + final code = await receiver!.listen(); + var allDevices = []; + while (allDevices.isEmpty) { + allDevices = await Discover.discover(); + } + 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); + 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"); + } + }); + tearDown(() { + for (var file in downloadedFiles) { + File(file.path).deleteSync(); + } + downloadedFiles = []; + }); + tearDownAll(() async { + await receiver?.stopListening(); + }); +} diff --git a/lib/classes/database.dart b/lib/classes/database.dart index d30d5e1..04d35eb 100644 --- a/lib/classes/database.dart +++ b/lib/classes/database.dart @@ -11,16 +11,21 @@ class DatabaseManager { databaseFactory = databaseFactoryFfiNoIsolate; } _initalised = true; - return openDatabase( - "files.db", - version: 1, - 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, time int not null)"); - await db.execute( - "create table uploaded (ID integer primary key autoincrement, name text not null, path text not null, type text, time int not null)"); - }, - ); + return 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( + "create table uploaded (ID integer primary key autoincrement, name text not null, path text not null, type text, timeEpoch int not null)"); + }, onUpgrade: (db, oldVersion, newVersion) async { + if (oldVersion == 1 && newVersion == 2) { + await db + .execute("alter table downloaded rename column time to timeEpoch"); + await db + .execute("alter table uploaded rename column time to timeEpoch"); + } else { + throw UnsupportedError("Unsupported db version"); + } + }); } ///Insert a uploaded or downloaded file information @@ -28,12 +33,18 @@ class DatabaseManager { final db = await _db; switch (file.fileStatus) { case DbFileStatus.upload: - await db.insert("uploaded", - {"name": file.name, "time": file.timeEpoch, "path": file.path}); + await db.insert("uploaded", { + "name": file.name, + "timeEpoch": file.timeEpoch, + "path": file.path + }); break; case DbFileStatus.download: - await db.insert("downloaded", - {"name": file.name, "time": file.timeEpoch, "path": file.path}); + await db.insert("downloaded", { + "name": file.name, + "timeEpoch": file.timeEpoch, + "path": file.path + }); break; } } @@ -58,12 +69,12 @@ class DatabaseManager { ///Get all downloaded/uploaded file information as list. Future> get files async { final db = await _db; - final uploadedMaps = - await db.query("uploaded", columns: ["name", "type", "time", "path"]); + final uploadedMaps = await db + .query("uploaded", columns: ["name", "type", "timeEpoch", "path"]); final uploadedFiles = uploadedMaps.map((e) => DbFile.uploadedFromMap(e)).toList(); - final downloadedMaps = - await db.query("downloaded", columns: ["name", "type", "time", "path"]); + final downloadedMaps = await db + .query("downloaded", columns: ["name", "type", "timeEpoch", "path"]); final downloadedFiles = downloadedMaps.map((e) => DbFile.downloadedFromMap(e)).toList(); List files = []; diff --git a/lib/classes/receiver.dart b/lib/classes/receiver.dart index dbc7462..9dfa300 100644 --- a/lib/classes/receiver.dart +++ b/lib/classes/receiver.dart @@ -24,10 +24,9 @@ class Receiver { final _files = []; late MediaStore _ms; final _tempDir = getTemporaryDirectory(); - final int _code; + final int code; HttpServer? _server; bool _isBusy = false; - int get code => _code; ///If [useDb] is `true`, file informations will be saved to sqflite database. ///Don't needed to open the database manually. @@ -63,7 +62,7 @@ class Receiver { ///[onDownloadError] will be called when error happened while saving file. /// ///When [onDownloadError] called, no other callback will be called, - ///no exception thrown and server will wait new connection. + ///no exception thrown and server will wait for new connection. /// ///See [FileDropException] final void Function(FileDropException error)? onDownloadError; @@ -84,16 +83,22 @@ class Receiver { this.onAllFilesDownloaded, this.onDownloadError, int? code, - }) : _code = code ?? Random().nextInt(8888) + 1111; + }) : code = code ?? Random().nextInt(8888) + 1111; ///Get storage permission for Android and IOS /// ///For other platforms always returns [true] Future checkPermission() async { + //These platforms needs storage permissions (only tested on Android) if (Platform.isAndroid || Platform.isIOS) { - //These platforms needs storage permissions (only tested on Android) - final perm = await Permission.storage.request(); - return perm.isGranted; + final newPermission = + await _ms.requestForAccess(initialRelativePath: null); + if (newPermission == null) { + final perm = await Permission.storage.request(); + return perm.isGranted; + } else { + return true; + } } else { return true; } @@ -104,9 +109,9 @@ class Receiver { ///sends `400 Bad request` as response /// ///Returns the code generated for discovery. Other devices should select this code for - ///connecting to this devices + ///connecting to [Receiver] Future listen() async { - if (!saveToTemp && false) { + if (!saveToTemp) { final permissionStatus = await checkPermission(); if (!permissionStatus) { throw NoStoragePermissionException(); @@ -133,10 +138,10 @@ class Receiver { } } if (_server != null) { - log("Listening for new file with port: ${_server!.port}, code: $_code", + log("Listening for new file with port: ${_server!.port}, code: $code", name: "Receive"); } - return _code; + return code; } Future _requestMethod(Request request) async { @@ -147,10 +152,10 @@ class Receiver { } if (request.method == "GET") { //Response to discovery requests - log("Discovery request recieved, returned code $_code", + log("Discovery request recieved, returned code $code", name: "Receive server"); return Response.ok( - jsonEncode({"message": Constants.meeting, "code": _code})); + jsonEncode({"message": Constants.meeting, "code": code})); } else if (request.method == "POST") { //Reciving file log("Reciving file...", name: "Receive server"); @@ -224,8 +229,8 @@ class Receiver { } log("Recived file(s) $_files", name: "Receive server"); return Response.ok(null); - } catch (_) { - log("Download error", name: "Receiver"); + } catch (e) { + log("Download error", name: "Receiver", error: e); onDownloadError?.call(ConnectionLostException()); return Response.badRequest(); } finally { @@ -262,14 +267,19 @@ class Receiver { Map get map => { "useDb": useDb, "saveToTemp": saveToTemp, - if (port != null) "port": port, - "code": _code, + if (port != null) "port": port!, + "code": code, }; - Receiver.fromMap(Map map) - : this( - useDb: map["useDb"], - saveToTemp: map["saveToTemp"], - port: map["port"], - code: map["code"]); + Receiver.fromMap(Map map, + {this.onAllFilesDownloaded, + this.downloadAnimC, + this.onDownloadError, + this.onDownloadStart, + this.onFileDownloaded, + this.onDownloadUpdatePercent}) + : useDb = map["useDb"], + saveToTemp = map["saveToTemp"], + port = map["port"], + code = map["code"]; } diff --git a/lib/classes/workers/worker_interface.dart b/lib/classes/workers/worker_interface.dart index 5b50ad8..a8d4921 100644 --- a/lib/classes/workers/worker_interface.dart +++ b/lib/classes/workers/worker_interface.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/animation.dart'; +import 'package:flutter/foundation.dart'; import 'package:path/path.dart'; import 'package:weepy/classes/exceptions.dart'; import 'package:weepy/classes/sender.dart'; @@ -23,35 +24,61 @@ final _workManager = Workmanager(); @pragma("vm:entry-point") void _callBack() { _workManager.executeTask((taskName, inputData) async { - final task = - MyTasks.values.singleWhere((element) => element.name == taskName); - switch (task) { - case MyTasks.receive: - final receiverMap = inputData!; - final receiver = Receiver( - useDb: receiverMap["useDb"], - port: receiverMap["port"], - saveToTemp: receiverMap["saveToTemp"], - code: receiverMap["code"], - onDownloadUpdatePercent: (percent) { - final sendPort = - IsolateNameServer.lookupPortByName(MyTasks.receive.name); - sendPort?.send(messages.UpdatePercent(percent)); - }); - await receiver.listen(); - return true; - case MyTasks.send: - final senderMap = inputData!; - final fileDirs = senderMap["files"] as List; - final files = fileDirs.map((e) => File(e)); - final platformFiles = files.map((e) => PlatformFile( - path: e.path, name: basename(e.path), size: e.lengthSync())); - final device = Device.fromMap(senderMap); - final port = IsolateNameServer.lookupPortByName(MyTasks.send.name); - await Sender().send(device, platformFiles, - onUploadProgress: (percent) => - port?.send(messages.UpdatePercent(percent))); - return true; + try { + final task = + MyTasks.values.singleWhere((element) => element.name == taskName); + switch (task) { + case MyTasks.receive: + SendPort? getReceiverPort() => + IsolateNameServer.lookupPortByName(MyTasks.receive.name); + final exitBlock = Completer(); + final receiverMap = inputData!; + final receiver = + Receiver.fromMap(receiverMap, onDownloadUpdatePercent: (percent) { + final sendPort = getReceiverPort(); + sendPort?.send(messages.UpdatePercent(percent).map); + }, onDownloadError: (error) { + final sendPort = getReceiverPort(); + if (sendPort != null) { + sendPort.send(messages.FiledropError(error).map); + } else { + exitBlock.completeError(error); + } + }, onDownloadStart: () { + final sendPort = getReceiverPort(); + sendPort?.send(const messages.DownloadStarted().map); + }, onAllFilesDownloaded: (files) { + final sendPort = getReceiverPort(); + sendPort?.send(messages.AllFilesDownloaded(files).map); + exitBlock.complete(true); + }, onFileDownloaded: (file) { + final sendPort = getReceiverPort(); + sendPort?.send(messages.FileDownloaded(file).map); + }); + await receiver.listen(); + return exitBlock.future; + case MyTasks.send: + final senderMap = inputData!; + final fileDirs = []; + for (var a = 0; senderMap["file$a"] != null; a++) { + fileDirs.add(senderMap["file$a"]); + } + assert(fileDirs.isNotEmpty); + final files = fileDirs.map((e) => File(e)); + final platformFiles = files.map((e) => PlatformFile( + path: e.path, name: basename(e.path), size: e.lengthSync())); + final device = Device.fromMap(senderMap); + SendPort? getSenderPort() => + IsolateNameServer.lookupPortByName(MyTasks.send.name); + await Sender().send(device, platformFiles, + useDb: senderMap["useDb"], + onUploadProgress: (percent) => + getSenderPort()?.send(messages.UpdatePercent(percent).map)); + getSenderPort()?.send(const messages.Completed().map); + return true; + } + } on Exception { + rethrow; } }); } @@ -77,35 +104,40 @@ class IsolatedSender extends Sender { progressNotification = await notifications.initalise(); } final fileDirs = files.map((e) => e.path!); - final map = device.map..addAll({"files": fileDirs}); + final map = device.map; + for (var i = 0; i < fileDirs.length; i++) { + map["file$i"] = fileDirs.elementAt(i); + } + map["useDb"] = useDb; final port = ReceivePort(); IsolateNameServer.registerPortWithName(port.sendPort, MyTasks.send.name); - final streamSubscription = port.listen((message) async { - switch (message as messages.SenderMessage) { - case final messages.UpdatePercent e: + + final exitBlock = Completer(); + port.listen((data) async { + switch (messages.MessageType.values[data["type"]]) { + case messages.MessageType.updatePercent: + final message = messages.UpdatePercent.fromMap(data); if (progressNotification) { - await notifications.showUpload((e.newPercent * 100).round()); + await notifications.showUpload((message.newPercent * 100).round()); } - onUploadProgress?.call(e.newPercent); + onUploadProgress?.call(message.newPercent); break; - case final messages.FiledropError e: - if (progressNotification) { - await cancel(); - } - throw e.exception; + case messages.MessageType.completed: + final _ = messages.Completed.fromMap(data); + exitBlock.complete(); default: throw Error(); } }); - _workManager.registerOneOffTask(MyTasks.send.name, MyTasks.send.name, + await _workManager.registerOneOffTask(MyTasks.send.name, MyTasks.send.name, inputData: map); if (progressNotification) { await notifications.showDownload(0); } - await streamSubscription.asFuture(); if (progressNotification) { await notifications.cancelDownload(); } + return exitBlock.future; } @override @@ -113,7 +145,7 @@ class IsolatedSender extends Sender { } Future initalize() async { - await _workManager.initialize(_callBack); + await _workManager.initialize(_callBack, isInDebugMode: kDebugMode); } class IsolatedReceiver extends Receiver { @@ -144,7 +176,7 @@ class IsolatedReceiver extends Receiver { if (progressNotification) { progressNotification = await notifications.initalise(); } - if (!saveToTemp && false) { + if (!saveToTemp) { final permissionStatus = await super.checkPermission(); if (!permissionStatus) { throw NoStoragePermissionException(); @@ -153,48 +185,52 @@ class IsolatedReceiver extends Receiver { final port = ReceivePort(); IsolateNameServer.registerPortWithName(port.sendPort, MyTasks.receive.name); port.listen(_portCallback); - _workManager.registerOneOffTask(MyTasks.receive.name, MyTasks.receive.name, + await _workManager.registerOneOffTask( + MyTasks.receive.name, MyTasks.receive.name, inputData: super.map, existingWorkPolicy: ExistingWorkPolicy.keep); return super.code; } @override Future stopListening() async { - await notifications.cancelDownload(); + if (progressNotification) { + await notifications.cancelDownload(); + } await _workManager.cancelByUniqueName(MyTasks.receive.name); } - Future _portCallback(message) async { - switch (message as messages.ReceiverMessage) { - case final messages.UpdatePercent e: + 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((e.newPercent * 100).round()); + await notifications.showDownload((message.newPercent * 100).round()); } - super.onDownloadUpdatePercent?.call(e.newPercent); + super.onDownloadUpdatePercent?.call(message.newPercent); break; - case final messages.FiledropError e: + case messages.MessageType.filedropError: + final message = messages.FiledropError.fromMap(data); if (progressNotification) { await notifications.cancelDownload(); } - super.onDownloadError?.call(e.exception); + super.onDownloadError?.call(message.exception); break; - case final messages.FileDownloaded e: - super.onFileDownloaded?.call(e.file); + case messages.MessageType.fileDownloaded: + final message = messages.FileDownloaded.fromMap(data); + super.onFileDownloaded?.call(message.file); break; - case final messages.AllFilesDownloaded e: + case messages.MessageType.allFilesDownloaded: + final message = messages.AllFilesDownloaded.fromMap(data); if (progressNotification) { await notifications.cancelDownload(); } - super.onAllFilesDownloaded?.call(e.files); - case final messages.DownloadStarted _: - if (progressNotification) { - await notifications.showDownload(0); - } + super.onAllFilesDownloaded?.call(message.files.toList()); + break; + case messages.MessageType.downloadStarted: + final _ = messages.DownloadStarted.fromMap(data); super.onDownloadStart?.call(); default: - if (progressNotification) { - await notifications.cancelDownload(); - } throw Error(); } } diff --git a/lib/classes/workers/worker_messages.dart b/lib/classes/workers/worker_messages.dart index c1bec9e..9870319 100644 --- a/lib/classes/workers/worker_messages.dart +++ b/lib/classes/workers/worker_messages.dart @@ -1,9 +1,34 @@ import 'package:weepy/classes/exceptions.dart'; import 'package:weepy/models.dart'; +import 'worker_interface.dart'; -class SenderMessage {} +enum MessageType { + updatePercent, + filedropError, + fileDownloaded, + allFilesDownloaded, + downloadStarted, + completed +} + +///Message which should send between [IsolatedSender] and main isolate. +abstract class SenderMessage { + final MessageType type; + + SenderMessage(this.type) { + assert(MessageType.values[map["type"]] == type); + } + Map get map; +} -class ReceiverMessage {} +///Message which should send between [IsolatedReceiver] and main isolate. +abstract class ReceiverMessage { + final MessageType type; + ReceiverMessage(this.type) { + assert(MessageType.values[map["type"]] == type); + } + Map get map; +} ///Sends when a progress updated. /// @@ -14,6 +39,18 @@ class UpdatePercent implements SenderMessage, ReceiverMessage { ///It maybe same previous sent [newPercent] and it must between `0.00` and `1.00` final double newPercent; const UpdatePercent(this.newPercent); + + @override + Map get map => + {"type": type.index, "newPercent": newPercent}; + + @override + final type = MessageType.updatePercent; + + UpdatePercent.fromMap(Map map) + : newPercent = map["newPercent"] { + assert(MessageType.values[map["type"]] == type); + } } ///Sends when an error caused from worker. @@ -22,18 +59,76 @@ class UpdatePercent implements SenderMessage, ReceiverMessage { class FiledropError implements SenderMessage, ReceiverMessage { final FileDropException exception; const FiledropError(this.exception); + + @override + Map get map => + {"type": type.index, "exceptionType": exception.runtimeType}; + + @override + final type = MessageType.filedropError; + + FiledropError.fromMap(Map map) + : exception = map["exceptionType"] { + assert(MessageType.values[map["type"]] == type); + } } class FileDownloaded implements ReceiverMessage { final DbFile file; const FileDownloaded(this.file); + + @override + Map get map => {"type": type.index, "file": file.map}; + + @override + final type = MessageType.fileDownloaded; + + FileDownloaded.fromMap(Map map) + : file = DbFile.fromMap(map["file"]) { + assert(MessageType.values[map["type"]] == type); + } } class AllFilesDownloaded implements ReceiverMessage { final List files; const AllFilesDownloaded(this.files); + @override + Map get map => + {"type": type.index, "files": files.map((e) => e.map).toList()}; + + @override + final type = MessageType.allFilesDownloaded; + + AllFilesDownloaded.fromMap(Map map) + : files = + (map["files"] as Iterable).map((e) => DbFile.fromMap(e)).toList() { + assert(MessageType.values[map["type"]] == type); + } } class DownloadStarted implements ReceiverMessage { const DownloadStarted(); + @override + Map get map => {"type": type.index}; + + @override + final type = MessageType.downloadStarted; + + DownloadStarted.fromMap(Map map) { + assert(MessageType.values[map["type"]] == type); + } +} + +///Sent when [IsolatedSender] completed uploading. +class Completed implements SenderMessage { + const Completed(); + @override + Map get map => {"type": type.index}; + + @override + MessageType get type => MessageType.completed; + + Completed.fromMap(Map map) { + assert(MessageType.values[map["type"]] == type); + } } diff --git a/lib/models.dart b/lib/models.dart index 198f3af..2fb07a8 100644 --- a/lib/models.dart +++ b/lib/models.dart @@ -44,7 +44,13 @@ class DbFile { DbFile.downloadedFromMap(Map map) : name = map["name"], fileStatus = DbFileStatus.download, - time = DateTime.fromMillisecondsSinceEpoch(map["time"]), + time = DateTime.fromMillisecondsSinceEpoch(map["timeEpoch"]), + path = map["path"]; + + DbFile.fromMap(Map map) + : name = map["name"], + fileStatus = DbFileStatus.values[map["fileStatus"]], + time = DateTime.fromMillisecondsSinceEpoch(map["timeEpoch"]), path = map["path"]; ///Icon for showing in UI. @@ -71,6 +77,13 @@ class DbFile { @override String toString() => "dbFile{name: $name, time: $time, fileStatus: ${fileStatus.name}}"; + + Map get map => { + "name": name, + "path": path, + "timeEpoch": timeEpoch, + "fileStatus": fileStatus.index + }; } class Device { diff --git a/test/unit_test.dart b/test/unit_test.dart new file mode 100644 index 0000000..088a8cf --- /dev/null +++ b/test/unit_test.dart @@ -0,0 +1,23 @@ +import 'dart:math'; + +import 'package:flutter_test/flutter_test.dart'; + +void main() { + test('Nesne test', () { + final sayi = Random().nextInt(1000); + expect(_ParentNesne(sayi).getSayi(), sayi); + }); +} + +class _ChildNesne { + final int code; + _ChildNesne([int? code]) : code = code ?? Random().nextInt(1000); +} + +class _ParentNesne extends _ChildNesne { + int getSayi() { + return super.code; + } + + _ParentNesne([super.randomSayi]); +}