nc-photos/lib/bloc/scan_dir.dart

334 lines
9.7 KiB
Dart
Raw Normal View History

import 'dart:async';
2021-04-10 06:28:12 +02:00
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
2021-04-11 17:38:01 +02:00
import 'package:kiwi/kiwi.dart';
2021-04-10 06:28:12 +02:00
import 'package:logging/logging.dart';
import 'package:nc_photos/account.dart';
import 'package:nc_photos/entity/file.dart';
2021-05-24 09:09:25 +02:00
import 'package:nc_photos/entity/file/data_source.dart';
2021-08-01 22:46:16 +02:00
import 'package:nc_photos/entity/file_util.dart' as file_util;
2021-04-10 06:28:12 +02:00
import 'package:nc_photos/event/event.dart';
import 'package:nc_photos/iterable_extension.dart';
2021-07-31 19:28:08 +02:00
import 'package:nc_photos/throttler.dart';
2021-04-10 06:28:12 +02:00
import 'package:nc_photos/use_case/scan_dir.dart';
abstract class ScanDirBlocEvent {
const ScanDirBlocEvent();
}
2021-06-14 18:05:28 +02:00
class ScanDirBlocQueryBase extends ScanDirBlocEvent with EquatableMixin {
const ScanDirBlocQueryBase(this.account, this.roots);
2021-04-10 06:28:12 +02:00
@override
toString() {
return "$runtimeType {"
"account: $account, "
"roots: ${roots.map((e) => e.path).toReadableString()}, "
"}";
}
2021-06-14 18:05:28 +02:00
@override
get props => [
account,
roots,
];
2021-04-10 06:28:12 +02:00
final Account account;
final List<File> roots;
}
2021-06-14 18:05:28 +02:00
class ScanDirBlocQuery extends ScanDirBlocQueryBase {
const ScanDirBlocQuery(Account account, List<File> roots)
: super(account, roots);
}
class ScanDirBlocRefresh extends ScanDirBlocQueryBase {
const ScanDirBlocRefresh(Account account, List<File> roots)
: super(account, roots);
}
2021-04-10 06:28:12 +02:00
/// An external event has happened and may affect the state of this bloc
class _ScanDirBlocExternalEvent extends ScanDirBlocEvent {
const _ScanDirBlocExternalEvent();
@override
toString() {
return "$runtimeType {"
"}";
}
}
abstract class ScanDirBlocState {
2021-07-23 22:05:57 +02:00
const ScanDirBlocState(this.account, this.files);
2021-04-10 06:28:12 +02:00
@override
toString() {
return "$runtimeType {"
"account: $account, "
"files: List {length: ${files.length}}, "
"}";
}
2021-07-23 22:05:57 +02:00
final Account? account;
final List<File> files;
2021-04-10 06:28:12 +02:00
}
class ScanDirBlocInit extends ScanDirBlocState {
const ScanDirBlocInit() : super(null, const []);
}
class ScanDirBlocLoading extends ScanDirBlocState {
2021-07-23 22:05:57 +02:00
const ScanDirBlocLoading(Account? account, List<File> files)
2021-04-10 06:28:12 +02:00
: super(account, files);
}
class ScanDirBlocSuccess extends ScanDirBlocState {
2021-07-23 22:05:57 +02:00
const ScanDirBlocSuccess(Account? account, List<File> files)
2021-04-10 06:28:12 +02:00
: super(account, files);
}
class ScanDirBlocFailure extends ScanDirBlocState {
2021-07-23 22:05:57 +02:00
const ScanDirBlocFailure(Account? account, List<File> files, this.exception)
2021-04-10 06:28:12 +02:00
: super(account, files);
@override
toString() {
return "$runtimeType {"
"super: ${super.toString()}, "
"exception: $exception, "
"}";
}
final dynamic exception;
}
/// The state of this bloc is inconsistent. This typically means that the data
/// may have been changed externally
class ScanDirBlocInconsistent extends ScanDirBlocState {
2021-07-23 22:05:57 +02:00
const ScanDirBlocInconsistent(Account? account, List<File> files)
2021-04-10 06:28:12 +02:00
: super(account, files);
}
/// A bloc that return all files under a dir recursively
///
/// See [ScanDir]
class ScanDirBloc extends Bloc<ScanDirBlocEvent, ScanDirBlocState> {
ScanDirBloc() : super(ScanDirBlocInit()) {
_fileRemovedEventListener =
AppEventListener<FileRemovedEvent>(_onFileRemovedEvent);
2021-05-28 19:15:09 +02:00
_filePropertyUpdatedEventListener =
AppEventListener<FilePropertyUpdatedEvent>(_onFilePropertyUpdatedEvent);
2021-08-01 22:46:16 +02:00
_fileTrashbinRestoredEventListener =
AppEventListener<FileTrashbinRestoredEvent>(
_onFileTrashbinRestoredEvent);
2021-04-10 06:28:12 +02:00
_fileRemovedEventListener.begin();
2021-05-28 19:15:09 +02:00
_filePropertyUpdatedEventListener.begin();
2021-08-01 22:46:16 +02:00
_fileTrashbinRestoredEventListener.begin();
2021-08-26 19:31:17 +02:00
_fileMovedEventListener.begin();
2021-07-31 19:28:08 +02:00
_refreshThrottler = Throttler(
onTriggered: (_) {
add(_ScanDirBlocExternalEvent());
},
logTag: "ScanDirBloc.refresh",
);
2021-04-10 06:28:12 +02:00
}
2021-04-11 17:38:01 +02:00
static ScanDirBloc of(Account account) {
final id =
"${account.scheme}://${account.username}@${account.address}?${account.roots.join('&')}";
try {
_log.fine("[of] Resolving bloc for '$id'");
return KiwiContainer().resolve<ScanDirBloc>("ScanDirBloc($id)");
} catch (_) {
// no created instance for this account, make a new one
_log.info("[of] New bloc instance for account: $account");
final bloc = ScanDirBloc();
KiwiContainer()
.registerInstance<ScanDirBloc>(bloc, name: "ScanDirBloc($id)");
return bloc;
}
}
@override
transformEvents(Stream<ScanDirBlocEvent> events, transitionFn) {
return super.transformEvents(events.distinct((a, b) {
// only handle ScanDirBlocQuery
final r = a is ScanDirBlocQuery && b is ScanDirBlocQuery && a == b;
if (r) {
_log.fine("[transformEvents] Skip identical ScanDirBlocQuery event");
}
return r;
}), transitionFn);
}
2021-04-10 06:28:12 +02:00
@override
mapEventToState(ScanDirBlocEvent event) async* {
_log.info("[mapEventToState] $event");
if (event is ScanDirBlocQueryBase) {
2021-04-10 06:28:12 +02:00
yield* _onEventQuery(event);
} else if (event is _ScanDirBlocExternalEvent) {
yield* _onExternalEvent(event);
}
}
@override
close() {
_fileRemovedEventListener.end();
2021-05-28 19:15:09 +02:00
_filePropertyUpdatedEventListener.end();
2021-08-01 22:46:16 +02:00
_fileTrashbinRestoredEventListener.end();
2021-07-31 19:28:08 +02:00
_refreshThrottler.clear();
2021-04-10 06:28:12 +02:00
return super.close();
}
Stream<ScanDirBlocState> _onEventQuery(ScanDirBlocQueryBase ev) async* {
2021-04-10 06:28:12 +02:00
yield ScanDirBlocLoading(ev.account, state.files);
bool hasContent = state.files.isNotEmpty;
2021-04-10 06:28:12 +02:00
if (!hasContent) {
// show something instantly on first load
ScanDirBlocState cacheState = ScanDirBlocInit();
await for (final s in _queryOffline(ev, () => cacheState)) {
cacheState = s;
}
yield ScanDirBlocLoading(ev.account, cacheState.files);
hasContent = cacheState.files.isNotEmpty;
2021-04-10 06:28:12 +02:00
}
ScanDirBlocState newState = ScanDirBlocInit();
if (!hasContent) {
2021-04-10 06:28:12 +02:00
await for (final s in _queryOnline(ev, () => newState)) {
newState = s;
yield s;
}
} else {
await for (final s in _queryOnline(ev, () => newState)) {
newState = s;
}
if (newState is ScanDirBlocSuccess) {
yield newState;
} else if (newState is ScanDirBlocFailure) {
yield ScanDirBlocFailure(ev.account, state.files, newState.exception);
2021-04-10 06:28:12 +02:00
}
}
}
Stream<ScanDirBlocState> _onExternalEvent(
_ScanDirBlocExternalEvent ev) async* {
yield ScanDirBlocInconsistent(state.account, state.files);
}
void _onFileRemovedEvent(FileRemovedEvent ev) {
if (state is ScanDirBlocInit) {
// no data in this bloc, ignore
return;
}
2021-08-01 22:46:16 +02:00
if (!file_util.isTrash(ev.account, ev.file)) {
_refreshThrottler.trigger(
maxResponceTime: const Duration(seconds: 3),
maxPendingCount: 10,
);
}
2021-04-10 06:28:12 +02:00
}
2021-05-28 19:15:09 +02:00
void _onFilePropertyUpdatedEvent(FilePropertyUpdatedEvent ev) {
if (!ev.hasAnyProperties([
FilePropertyUpdatedEvent.propMetadata,
2021-05-28 20:45:00 +02:00
FilePropertyUpdatedEvent.propIsArchived,
2021-06-21 12:39:17 +02:00
FilePropertyUpdatedEvent.propOverrideDateTime,
2021-05-28 19:15:09 +02:00
])) {
// not interested
return;
}
2021-04-10 06:28:12 +02:00
if (state is ScanDirBlocInit) {
// no data in this bloc, ignore
return;
}
2021-07-31 19:28:08 +02:00
if (ev.hasAnyProperties([
FilePropertyUpdatedEvent.propIsArchived,
FilePropertyUpdatedEvent.propOverrideDateTime,
])) {
_refreshThrottler.trigger(
maxResponceTime: const Duration(seconds: 3),
maxPendingCount: 10,
);
} else {
2021-07-31 19:28:08 +02:00
_refreshThrottler.trigger(
maxResponceTime: const Duration(seconds: 10),
maxPendingCount: 10,
);
}
2021-04-10 06:28:12 +02:00
}
2021-08-01 22:46:16 +02:00
void _onFileTrashbinRestoredEvent(FileTrashbinRestoredEvent ev) {
if (state is ScanDirBlocInit) {
// no data in this bloc, ignore
return;
}
_refreshThrottler.trigger(
maxResponceTime: const Duration(seconds: 3),
maxPendingCount: 10,
);
}
2021-08-26 19:31:17 +02:00
void _onFileMovedEvent(FileMovedEvent ev) {
if (state is ScanDirBlocInit) {
// no data in this bloc, ignore
return;
}
if (file_util.isSupportedFormat(ev.file)) {
_refreshThrottler.trigger(
maxResponceTime: const Duration(seconds: 3),
maxPendingCount: 10,
);
}
}
2021-04-10 06:28:12 +02:00
Stream<ScanDirBlocState> _queryOffline(
ScanDirBlocQueryBase ev, ScanDirBlocState Function() getState) =>
2021-04-10 06:28:12 +02:00
_queryWithFileDataSource(ev, getState, FileAppDbDataSource());
Stream<ScanDirBlocState> _queryOnline(
ScanDirBlocQueryBase ev, ScanDirBlocState Function() getState) {
final stream = _queryWithFileDataSource(ev, getState,
FileCachedDataSource(shouldCheckCache: _shouldCheckCache));
_shouldCheckCache = false;
return stream;
}
2021-04-10 06:28:12 +02:00
Stream<ScanDirBlocState> _queryWithFileDataSource(ScanDirBlocQueryBase ev,
2021-04-10 06:28:12 +02:00
ScanDirBlocState Function() getState, FileDataSource dataSrc) async* {
try {
for (final r in ev.roots) {
final dataStream = ScanDir(FileRepo(dataSrc))(ev.account, r);
await for (final d in dataStream) {
if (d is Exception || d is Error) {
throw d;
}
yield ScanDirBlocLoading(ev.account, getState().files + d);
}
}
yield ScanDirBlocSuccess(ev.account, getState().files);
} catch (e) {
_log.severe("[_queryWithFileDataSource] Exception while request", e);
yield ScanDirBlocFailure(ev.account, getState().files, e);
}
}
2021-07-23 22:05:57 +02:00
late AppEventListener<FileRemovedEvent> _fileRemovedEventListener;
late AppEventListener<FilePropertyUpdatedEvent>
_filePropertyUpdatedEventListener;
2021-08-01 22:46:16 +02:00
late final AppEventListener<FileTrashbinRestoredEvent>
_fileTrashbinRestoredEventListener;
2021-08-26 19:31:17 +02:00
late final _fileMovedEventListener =
AppEventListener<FileMovedEvent>(_onFileMovedEvent);
2021-04-10 06:28:12 +02:00
2021-07-31 19:28:08 +02:00
late Throttler _refreshThrottler;
bool _shouldCheckCache = true;
2021-04-10 06:28:12 +02:00
static final _log = Logger("bloc.scan_dir.ScanDirBloc");
}