From 6725c9544b73d63d3508c131dd3c13e16494367f Mon Sep 17 00:00:00 2001 From: Kingtous Date: Wed, 15 Mar 2023 10:43:27 +0800 Subject: [PATCH] opt: add event loop def --- flutter/lib/models/file_model.dart | 77 ++++++++++++++++++++++++++--- flutter/lib/utils/event_loop.dart | 79 ++++++++++++++++++++++++++++++ 2 files changed, 148 insertions(+), 8 deletions(-) create mode 100644 flutter/lib/utils/event_loop.dart diff --git a/flutter/lib/models/file_model.dart b/flutter/lib/models/file_model.dart index 4170a5461..953625c96 100644 --- a/flutter/lib/models/file_model.dart +++ b/flutter/lib/models/file_model.dart @@ -4,7 +4,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_hbb/common.dart'; -import 'package:flutter_hbb/consts.dart'; +import 'package:flutter_hbb/utils/event_loop.dart'; import 'package:get/get.dart'; import 'package:path/path.dart' as path; @@ -45,6 +45,7 @@ class FileModel { late final GetSessionID getSessionID; String get sessionID => getSessionID(); + late final _FileDialogEventLoop evtLoop; FileModel(this.parent) { getSessionID = () => parent.target?.id ?? ""; @@ -64,14 +65,17 @@ class FileModel { jobController: jobController, fileFetcher: fileFetcher, getOtherSideDirectoryData: () => localController.directoryData()); + evtLoop = _FileDialogEventLoop(); } Future onReady() async { + await evtLoop.onReady(); await localController.onReady(); await remoteController.onReady(); } Future close() async { + await evtLoop.close(); parent.target?.dialogManager.dismissAll(); await localController.close(); await remoteController.close(); @@ -90,14 +94,16 @@ class FileModel { fileFetcher.tryCompleteTask(evt['value'], evt['is_local']); } - void overrideFileConfirm(Map evt) async { - final resp = await showFileConfirmDialog( - translate("Overwrite"), "${evt['read_path']}", true); + Future overrideFileConfirm(Map evt, + {bool? overrideConfirm}) async { + final resp = overrideConfirm ?? + await showFileConfirmDialog( + translate("Overwrite"), "${evt['read_path']}", true); final id = int.tryParse(evt['id']) ?? 0; if (false == resp) { final jobIndex = jobController.getJob(id); if (jobIndex != -1) { - jobController.cancelJob(id); + await jobController.cancelJob(id); final job = jobController.jobTable[jobIndex]; job.state = JobState.done; jobController.jobTable.refresh(); @@ -111,7 +117,7 @@ class FileModel { // overwrite need_override = true; } - bind.sessionSetConfirmOverrideFile( + await bind.sessionSetConfirmOverrideFile( id: sessionID, actId: id, fileNum: int.parse(evt['file_num']), @@ -677,8 +683,8 @@ class JobController { debugPrint("jobError $evt"); } - void cancelJob(int id) async { - bind.sessionCancelJob(id: sessionID, actId: id); + Future cancelJob(int id) async { + await bind.sessionCancelJob(id: sessionID, actId: id); } void loadLastJob(Map evt) { @@ -1167,3 +1173,58 @@ List _sortList(List list, SortBy sortType, bool ascending) { } return []; } + +/// Define a general queue which can accepts different dialog type. +/// +/// [Visibility] +/// The `_FileDialogType` and `_DialogEvent` are invisible for other models. +enum _FileDialogType { overwrite, unknown } + +class _FileDialogEvent + extends BaseEvent<_FileDialogType, Map> { + WeakReference fileModel; + bool? _overrideConfirm; + + _FileDialogEvent(this.fileModel, super.type, super.data); + + void setOverrideConfirm(bool? confirm) { + _overrideConfirm = confirm; + } + + @override + EventCallback>? findCallback(_FileDialogType type) { + final model = fileModel.target; + if (model == null) { + return null; + } + switch (type) { + case _FileDialogType.overwrite: + return (data) async { + return await model.overrideFileConfirm(data, overrideConfirm: _overrideConfirm); + }; + default: + return null; + } + } +} + +class _FileDialogEventLoop extends BaseEventLoop<_FileDialogType, Map> { + + bool? overrideConfirm; + + @override + Future onPreConsume(BaseEvent<_FileDialogType, Map> evt) async { + var event = evt as _FileDialogEvent; + event.setOverrideConfirm(overrideConfirm); + } + + @override + Future onEventsClear() { + overrideConfirm = null; + return super.onEventsClear(); + } + + void setOverrideConfirm(bool confirm) { + overrideConfirm = confirm; + } +} \ No newline at end of file diff --git a/flutter/lib/utils/event_loop.dart b/flutter/lib/utils/event_loop.dart new file mode 100644 index 000000000..dc3671533 --- /dev/null +++ b/flutter/lib/utils/event_loop.dart @@ -0,0 +1,79 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; + +typedef EventCallback = Future Function(Data data); + +abstract class BaseEvent { + EventType type; + Data data; + + /// Constructor + BaseEvent(this.type, this.data); + + /// Consume this event + @visibleForTesting + Future consume() async { + final cb = findCallback(type); + if (cb == null) { + return null; + } else { + return cb(data); + } + } + + EventCallback? findCallback(EventType type); +} + +abstract class BaseEventLoop { + final List> _evts = []; + Timer? _timer; + + List> get evts => _evts; + + Future onReady() async { + // Poll every 100ms. + _timer = Timer.periodic(Duration(milliseconds: 100), _handleTimer); + } + + /// An Event is about to be consumed. + Future onPreConsume(BaseEvent evt) async {} + /// An Event was consumed. + Future onPostConsume(BaseEvent evt) async {} + /// Events are all handled and cleared. + Future onEventsClear() async {} + /// Events start to consume. + Future onEventsStartConsuming() async {} + + Future _handleTimer(Timer timer) async { + if (_evts.isEmpty) { + return; + } + timer.cancel(); + _timer = null; + // handle logic + await onEventsStartConsuming(); + while (_evts.isNotEmpty) { + final evt = _evts.first; + _evts.remove(evt); + await onPreConsume(evt); + await evt.consume(); + await onPostConsume(evt); + } + await onEventsClear(); + // Now events are all processed + _timer = Timer.periodic(Duration(milliseconds: 100), _handleTimer); + } + + Future close() async { + _timer?.cancel(); + } + + void push_event(BaseEvent evt) { + _evts.add(evt); + } + + void clear() { + _evts.clear(); + } +}