Skip to content
This repository has been archived by the owner on Sep 22, 2024. It is now read-only.

Commit

Permalink
Support working on background
Browse files Browse the repository at this point in the history
#7
Using extended IsolatedReceiver/IsolatedSender from Sender/Receiver
  • Loading branch information
iamalper committed Nov 30, 2023
1 parent 768a883 commit 2577ac5
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 93 deletions.
29 changes: 23 additions & 6 deletions lib/classes/receiver.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class Receiver {
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.
Expand Down Expand Up @@ -85,19 +86,35 @@ class Receiver {
int? code,
}) : _code = code ?? Random().nextInt(8888) + 1111;

///Get storage permission for Android and IOS
///
///For other platforms always returns [true]
Future<bool> checkPermission() async {
if (Platform.isAndroid || Platform.isIOS) {
//These platforms needs storage permissions (only tested on Android)
final perm = await Permission.storage.request();
return perm.isGranted;
} else {
return true;
}
}

///Starts listening for discovery and recieving file(s).
///Handles one connection at once. If another device tires to match,
///sends `400 Bad request` as response
///
///Returns the code generated for discovery. Other devices should select this code for
///connecting to this device
Future<int> listen() async {
if (!saveToTemp && (Platform.isAndroid || Platform.isIOS)) {
//These platforms needs storage permissions (only tested on Android)
final perm = await Permission.storage.request();
if (!perm.isGranted) throw NoStoragePermissionException();
_ms = MediaStore();
MediaStore.appFolder = Constants.saveFolder;
if (!saveToTemp) {
final permissionStatus = await checkPermission();
if (!permissionStatus) {
throw NoStoragePermissionException();
}
if (Platform.isAndroid) {
_ms = MediaStore();
MediaStore.appFolder = Constants.saveFolder;
}
}
_isBusy = false;

Expand Down
125 changes: 86 additions & 39 deletions lib/classes/worker_interface.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import 'dart:async';
import 'dart:io';

import 'package:file_picker/file_picker.dart';
import 'package:flutter/src/animation/animation_controller.dart';
import 'package:path/path.dart';
import 'package:weepy/classes/exceptions.dart';
import 'package:weepy/classes/sender.dart';
import 'package:weepy/models.dart';

import 'worker_messages.dart' as commands;
import 'worker_messages.dart' as messages;
import 'dart:isolate';
import 'dart:ui';
import 'package:workmanager/workmanager.dart';
Expand All @@ -32,7 +35,7 @@ void _callBack() {
onDownloadUpdatePercent: (percent) {
final sendPort =
IsolateNameServer.lookupPortByName(MyTasks.receive.name);
sendPort?.send(commands.UpdatePercent(percent));
sendPort?.send(messages.UpdatePercent(percent));
});
await receiver.listen();
return true;
Expand All @@ -46,52 +49,96 @@ void _callBack() {
final port = IsolateNameServer.lookupPortByName(MyTasks.send.name);
await Sender.send(device, platformFiles,
onUploadProgress: (percent) =>
port?.send(commands.UpdatePercent(percent)));
port?.send(messages.UpdatePercent(percent)));
return true;
}
});
}

Future<void> initalize() => _workManager.initialize(_callBack);
class IsolatedSender extends Sender {
///Run [Sender] from a worker.
///
///Call [initalize()] before.
Future<void> send(Device device, Iterable<PlatformFile> files,
{AnimationController? uploadAnimC,
bool useDb = true,
void Function(double percent)? onUploadProgress}) async {
final fileDirs = files.map((e) => e.path!);
final map = device.map..addAll({"files": fileDirs});
final port = ReceivePort();
IsolateNameServer.registerPortWithName(port.sendPort, MyTasks.send.name);
final streamSubscription = port.listen((message) {
switch (message as messages.SenderMessage) {
case final messages.UpdatePercent e:
onUploadProgress?.call(e.newPercent);
break;
case final messages.FiledropError e:
throw e.exception;
default:
throw Error();
}
});
_workManager.registerOneOffTask(MyTasks.send.name, MyTasks.send.name,
inputData: map);
await streamSubscription.asFuture();
}

Future<void> cancelSend() => _workManager.cancelByUniqueName(MyTasks.send.name);
static Future<void> cancelSend() =>
_workManager.cancelByUniqueName(MyTasks.send.name);
}

Future<void> cancelReceive() =>
_workManager.cancelByUniqueName(MyTasks.receive.name);
Future<void> initalize() => _workManager.initialize(_callBack);

ReceivePort runReceiver(
Receiver receiver, void Function(double percent)? onReceivePercent) {
final port = ReceivePort();
IsolateNameServer.registerPortWithName(port.sendPort, MyTasks.receive.name);
port.listen((message) {
switch (message) {
case final commands.UpdatePercent e:
onReceivePercent?.call(e.newPercent);
break;
default:
throw Error();
}
class IsolatedReceiver extends Receiver {
///Runs [Receiver] from a worker.
///
///Call [initalize()] before.
IsolatedReceiver({
super.onDownloadUpdatePercent,
super.useDb,
super.saveToTemp,
super.port,
super.onDownloadStart,
super.onFileDownloaded,
super.onAllFilesDownloaded,
super.onDownloadError,
super.code,
});
_workManager.registerOneOffTask(MyTasks.receive.name, MyTasks.receive.name,
inputData: receiver.map, existingWorkPolicy: ExistingWorkPolicy.keep);
return port;
}

ReceivePort runSender(Device device, List<String> filePaths,
void Function(double percent)? onSendPercent) {
final inputMap = device.map..addAll({"files": filePaths});
final port = ReceivePort();
IsolateNameServer.registerPortWithName(port.sendPort, MyTasks.send.name);
port.listen((message) {
switch (message) {
case final commands.UpdatePercent e:
onSendPercent?.call(e.newPercent);
break;
default:
throw Error();
///Starts worker and runs [Receiver.listen]
///
///If called twice, it has no effect.
@override
Future<int> listen() async {
final permissionStatus = await super.checkPermission();
if (!permissionStatus) {
throw NoStoragePermissionException();
}
});
_workManager.registerOneOffTask(MyTasks.send.name, MyTasks.send.name,
inputData: inputMap, existingWorkPolicy: ExistingWorkPolicy.keep);
return port;
final port = ReceivePort();
IsolateNameServer.registerPortWithName(port.sendPort, MyTasks.receive.name);
port.listen((message) {
switch (message as messages.ReceiverMessage) {
case final messages.UpdatePercent e:
super.onDownloadUpdatePercent?.call(e.newPercent);
break;
case final messages.FiledropError e:
super.onDownloadError?.call(e.exception);
break;
case final messages.FileDownloaded e:
super.onFileDownloaded?.call(e.file);
break;
case final messages.AllFilesDownloaded e:
super.onAllFilesDownloaded?.call(e.files);
default:
throw Error();
}
});
_workManager.registerOneOffTask(MyTasks.receive.name, MyTasks.receive.name,
inputData: super.map, existingWorkPolicy: ExistingWorkPolicy.keep);
return super.code;
}

///Stops [Receiver] worker.
static Future<void> cancel() =>
_workManager.cancelByUniqueName(MyTasks.receive.name);
}
19 changes: 17 additions & 2 deletions lib/classes/worker_messages.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import 'package:weepy/classes/exceptions.dart';
import 'package:weepy/models.dart';

class SenderMessage {}

class ReceiverMessage {}

///Sends when a progress updated.
///
///Read new percent from [newPercent]
class UpdatePercent {
class UpdatePercent implements SenderMessage, ReceiverMessage {
///The new percent of progress.
///
///It maybe same previous sent [newPercent] and it must between `0.00` and `1.00`
Expand All @@ -14,7 +19,17 @@ class UpdatePercent {
///Sends when an error caused from worker.
///
///Read error details from [exception.getErrorMessage(appLocalizations)]
class FiledropError {
class FiledropError implements SenderMessage, ReceiverMessage {
final FileDropException exception;
const FiledropError(this.exception);
}

class FileDownloaded implements ReceiverMessage {
final DbFile file;
const FileDownloaded(this.file);
}

class AllFilesDownloaded implements ReceiverMessage {
final List<DbFile> files;
const AllFilesDownloaded(this.files);
}
72 changes: 26 additions & 46 deletions lib/screens/receive_page.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import 'dart:math';

import 'package:weepy/files_riverpod.dart';
import 'package:weepy/models.dart';
import '../classes/worker_interface.dart';
import '../classes/worker_messages.dart' as commands;
import '../constants.dart';
import 'package:flutter/material.dart';
import '../classes/receiver.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

Expand Down Expand Up @@ -34,60 +30,44 @@ class ReceivePageInner extends ConsumerStatefulWidget {

class _ReceivePageInnerState extends ConsumerState<ReceivePageInner>
with TickerProviderStateMixin {
late AnimationController _downloadAnimC;
late Receiver _receiveClass;
final _code = Random().nextInt(8999) + 1000;
late final _downloadAnimC = AnimationController(vsync: this)
..addListener(() {
setState(() {});
});
late final _receiveClass = IsolatedReceiver(
onDownloadStart: () => uiStatus = _UiState.downloading,
onAllFilesDownloaded: (files) async {
await ref.read(filesProvider.notifier).addFiles(files);
_files = files;
uiStatus = _UiState.complete;
},
onDownloadError: (e) {
errorMessage = e.getErrorMessage(AppLocalizations.of(context)!);
uiStatus = _UiState.error;
},
onDownloadUpdatePercent: (percent) {
_downloadAnimC.value = percent;
});
late List<DbFile> _files;
late String errorMessage;
late int _code;

///Use [uiStatus] setter for updating state without [setState]
var _uiStatus = _UiState.loading;

@override
void initState() {
super.initState();
_receive();
}

///Setter for ui state.
///
///Don't need warp with [setState].
set uiStatus(_UiState uiStatus) => setState(() => _uiStatus = uiStatus);
@override
initState() {
_downloadAnimC = AnimationController(vsync: this)
..addListener(() {
setState(() {});
});
//TODO: Instead, create Receiver inside worker and pass callbacks
_receiveClass = Receiver(
/*downloadAnimC: _downloadAnimC,
onDownloadStart: () => uiStatus = _UiState.downloading,
onAllFilesDownloaded: (files) async {
await ref.read(filesProvider.notifier).addFiles(files);
_files = files;
uiStatus = _UiState.complete;
},
onDownloadError: (e) {
errorMessage = e.getErrorMessage(AppLocalizations.of(context)!);
uiStatus = _UiState.error;
},*/
code: _code);
_receive();
super.initState();
}

Future<void> _receive() async {
final port = runReceiver(_receiveClass, (percent) {});
port.listen((message) {
switch (message) {
case final commands.UpdatePercent percent:
_downloadAnimC.value = percent.newPercent;
break;
case final commands.FiledropError e:
if (context.mounted) {
errorMessage =
e.exception.getErrorMessage(AppLocalizations.of(context)!);
uiStatus = _UiState.error;
}
default:
throw Error();
}
});
_code = await _receiveClass.listen();
uiStatus = _UiState.listening;
}

Expand Down

0 comments on commit 2577ac5

Please sign in to comment.