mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-01-23 01:06:21 +01:00
Refactoring: extract throttling logic
This commit is contained in:
parent
8cb56e095c
commit
36e5a1c17b
2 changed files with 110 additions and 25 deletions
|
@ -9,6 +9,7 @@ import 'package:nc_photos/entity/file.dart';
|
||||||
import 'package:nc_photos/entity/file/data_source.dart';
|
import 'package:nc_photos/entity/file/data_source.dart';
|
||||||
import 'package:nc_photos/event/event.dart';
|
import 'package:nc_photos/event/event.dart';
|
||||||
import 'package:nc_photos/iterable_extension.dart';
|
import 'package:nc_photos/iterable_extension.dart';
|
||||||
|
import 'package:nc_photos/throttler.dart';
|
||||||
import 'package:nc_photos/use_case/scan_dir.dart';
|
import 'package:nc_photos/use_case/scan_dir.dart';
|
||||||
|
|
||||||
abstract class ScanDirBlocEvent {
|
abstract class ScanDirBlocEvent {
|
||||||
|
@ -119,6 +120,13 @@ class ScanDirBloc extends Bloc<ScanDirBlocEvent, ScanDirBlocState> {
|
||||||
AppEventListener<FilePropertyUpdatedEvent>(_onFilePropertyUpdatedEvent);
|
AppEventListener<FilePropertyUpdatedEvent>(_onFilePropertyUpdatedEvent);
|
||||||
_fileRemovedEventListener.begin();
|
_fileRemovedEventListener.begin();
|
||||||
_filePropertyUpdatedEventListener.begin();
|
_filePropertyUpdatedEventListener.begin();
|
||||||
|
|
||||||
|
_refreshThrottler = Throttler(
|
||||||
|
onTriggered: (_) {
|
||||||
|
add(_ScanDirBlocExternalEvent());
|
||||||
|
},
|
||||||
|
logTag: "ScanDirBloc.refresh",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static ScanDirBloc of(Account account) {
|
static ScanDirBloc of(Account account) {
|
||||||
|
@ -163,7 +171,7 @@ class ScanDirBloc extends Bloc<ScanDirBlocEvent, ScanDirBlocState> {
|
||||||
close() {
|
close() {
|
||||||
_fileRemovedEventListener.end();
|
_fileRemovedEventListener.end();
|
||||||
_filePropertyUpdatedEventListener.end();
|
_filePropertyUpdatedEventListener.end();
|
||||||
_propertyUpdatedSubscription?.cancel();
|
_refreshThrottler.clear();
|
||||||
return super.close();
|
return super.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -209,7 +217,10 @@ class ScanDirBloc extends Bloc<ScanDirBlocEvent, ScanDirBlocState> {
|
||||||
// no data in this bloc, ignore
|
// no data in this bloc, ignore
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
add(_ScanDirBlocExternalEvent());
|
_refreshThrottler.trigger(
|
||||||
|
maxResponceTime: const Duration(seconds: 3),
|
||||||
|
maxPendingCount: 10,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onFilePropertyUpdatedEvent(FilePropertyUpdatedEvent ev) {
|
void _onFilePropertyUpdatedEvent(FilePropertyUpdatedEvent ev) {
|
||||||
|
@ -226,28 +237,19 @@ class ScanDirBloc extends Bloc<ScanDirBlocEvent, ScanDirBlocState> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_successivePropertyUpdatedCount += 1;
|
|
||||||
_propertyUpdatedSubscription?.cancel();
|
|
||||||
// only trigger the event on the 10th update or 10s after the last update
|
|
||||||
if (_successivePropertyUpdatedCount % 10 == 0) {
|
|
||||||
add(_ScanDirBlocExternalEvent());
|
|
||||||
} else {
|
|
||||||
if (ev.hasAnyProperties([
|
if (ev.hasAnyProperties([
|
||||||
FilePropertyUpdatedEvent.propIsArchived,
|
FilePropertyUpdatedEvent.propIsArchived,
|
||||||
FilePropertyUpdatedEvent.propOverrideDateTime,
|
FilePropertyUpdatedEvent.propOverrideDateTime,
|
||||||
])) {
|
])) {
|
||||||
_propertyUpdatedSubscription =
|
_refreshThrottler.trigger(
|
||||||
Future.delayed(const Duration(seconds: 2)).asStream().listen((_) {
|
maxResponceTime: const Duration(seconds: 3),
|
||||||
add(_ScanDirBlocExternalEvent());
|
maxPendingCount: 10,
|
||||||
_successivePropertyUpdatedCount = 0;
|
);
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
_propertyUpdatedSubscription =
|
_refreshThrottler.trigger(
|
||||||
Future.delayed(const Duration(seconds: 10)).asStream().listen((_) {
|
maxResponceTime: const Duration(seconds: 10),
|
||||||
add(_ScanDirBlocExternalEvent());
|
maxPendingCount: 10,
|
||||||
_successivePropertyUpdatedCount = 0;
|
);
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -286,8 +288,7 @@ class ScanDirBloc extends Bloc<ScanDirBlocEvent, ScanDirBlocState> {
|
||||||
late AppEventListener<FilePropertyUpdatedEvent>
|
late AppEventListener<FilePropertyUpdatedEvent>
|
||||||
_filePropertyUpdatedEventListener;
|
_filePropertyUpdatedEventListener;
|
||||||
|
|
||||||
int _successivePropertyUpdatedCount = 0;
|
late Throttler _refreshThrottler;
|
||||||
StreamSubscription<void>? _propertyUpdatedSubscription;
|
|
||||||
|
|
||||||
bool _shouldCheckCache = true;
|
bool _shouldCheckCache = true;
|
||||||
|
|
||||||
|
|
84
lib/throttler.dart
Normal file
84
lib/throttler.dart
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:nc_photos/int_util.dart';
|
||||||
|
|
||||||
|
/// Throttle how many times an event could be triggered
|
||||||
|
///
|
||||||
|
/// Events can be filtered by 2 ways:
|
||||||
|
/// 1. Time passed after the last event
|
||||||
|
/// 2. Number of events
|
||||||
|
class Throttler<T> {
|
||||||
|
Throttler({
|
||||||
|
required this.onTriggered,
|
||||||
|
this.logTag,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Post an event
|
||||||
|
///
|
||||||
|
/// [data] can be used to provide optional data. When the stream of events
|
||||||
|
/// eventually trigger, a list of all data received will be passed to the
|
||||||
|
/// callback. Nulls are ignored
|
||||||
|
void trigger({
|
||||||
|
Duration maxResponceTime = const Duration(seconds: 1),
|
||||||
|
int? maxPendingCount,
|
||||||
|
T? data,
|
||||||
|
}) {
|
||||||
|
_count += 1;
|
||||||
|
if (data != null) {
|
||||||
|
_data.add(data);
|
||||||
|
}
|
||||||
|
_subscription?.cancel();
|
||||||
|
_subscription = null;
|
||||||
|
if (maxPendingCount != null) {
|
||||||
|
_maxCount = math.min(maxPendingCount, _maxCount ?? int32Max);
|
||||||
|
}
|
||||||
|
if (_maxCount != null && _count >= _maxCount!) {
|
||||||
|
_log.info("[trigger]$_logTag Triggered after $_count events");
|
||||||
|
_doTrigger();
|
||||||
|
} else {
|
||||||
|
final responseTime = _minDuration(
|
||||||
|
maxResponceTime, _currentResponseTime ?? Duration(days: 1));
|
||||||
|
_subscription = Future.delayed(responseTime).asStream().listen((event) {
|
||||||
|
_log.info("[trigger]$_logTag Triggered after $responseTime");
|
||||||
|
_doTrigger();
|
||||||
|
});
|
||||||
|
_currentResponseTime = responseTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Drop all pending triggers, this may be useful in places like [dispose]
|
||||||
|
void clear() {
|
||||||
|
_subscription?.cancel();
|
||||||
|
_subscription = null;
|
||||||
|
_currentResponseTime = null;
|
||||||
|
_count = 0;
|
||||||
|
_maxCount = null;
|
||||||
|
_data = <T>[];
|
||||||
|
}
|
||||||
|
|
||||||
|
void _doTrigger() {
|
||||||
|
onTriggered?.call(_data);
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
String get _logTag => logTag == null ? "" : "[$logTag]";
|
||||||
|
|
||||||
|
final ValueChanged<List<T>>? onTriggered;
|
||||||
|
/// Extra tag printed with logs from this class
|
||||||
|
final String? logTag;
|
||||||
|
|
||||||
|
StreamSubscription<void>? _subscription;
|
||||||
|
Duration? _currentResponseTime;
|
||||||
|
int _count = 0;
|
||||||
|
int? _maxCount;
|
||||||
|
var _data = <T>[];
|
||||||
|
|
||||||
|
static final _log = Logger("throttler.Throttler");
|
||||||
|
}
|
||||||
|
|
||||||
|
Duration _minDuration(Duration a, Duration b) {
|
||||||
|
return a.compareTo(b) < 0 ? a : b;
|
||||||
|
}
|
Loading…
Reference in a new issue