mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-02-25 02:48:54 +01:00
Scan share_folder even if it's not under account roots
This commit is contained in:
parent
679aaf36dd
commit
4027058809
4 changed files with 413 additions and 383 deletions
381
lib/bloc/scan_account_dir.dart
Normal file
381
lib/bloc/scan_account_dir.dart
Normal file
|
@ -0,0 +1,381 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:kiwi/kiwi.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:nc_photos/account.dart';
|
||||
import 'package:nc_photos/app_db.dart';
|
||||
import 'package:nc_photos/entity/file.dart';
|
||||
import 'package:nc_photos/entity/file/data_source.dart';
|
||||
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||
import 'package:nc_photos/event/event.dart';
|
||||
import 'package:nc_photos/exception_event.dart';
|
||||
import 'package:nc_photos/pref.dart';
|
||||
import 'package:nc_photos/throttler.dart';
|
||||
import 'package:nc_photos/use_case/ls.dart';
|
||||
import 'package:nc_photos/use_case/scan_dir.dart';
|
||||
|
||||
abstract class ScanAccountDirBlocEvent {
|
||||
const ScanAccountDirBlocEvent();
|
||||
}
|
||||
|
||||
class ScanAccountDirBlocQueryBase extends ScanAccountDirBlocEvent {
|
||||
const ScanAccountDirBlocQueryBase();
|
||||
|
||||
@override
|
||||
toString() {
|
||||
return "$runtimeType {"
|
||||
"}";
|
||||
}
|
||||
}
|
||||
|
||||
class ScanAccountDirBlocQuery extends ScanAccountDirBlocQueryBase {
|
||||
const ScanAccountDirBlocQuery();
|
||||
}
|
||||
|
||||
class ScanAccountDirBlocRefresh extends ScanAccountDirBlocQueryBase {
|
||||
const ScanAccountDirBlocRefresh();
|
||||
}
|
||||
|
||||
/// An external event has happened and may affect the state of this bloc
|
||||
class _ScanAccountDirBlocExternalEvent extends ScanAccountDirBlocEvent {
|
||||
const _ScanAccountDirBlocExternalEvent();
|
||||
|
||||
@override
|
||||
toString() {
|
||||
return "$runtimeType {"
|
||||
"}";
|
||||
}
|
||||
}
|
||||
|
||||
abstract class ScanAccountDirBlocState {
|
||||
const ScanAccountDirBlocState(this.files);
|
||||
|
||||
@override
|
||||
toString() {
|
||||
return "$runtimeType {"
|
||||
"files: List {length: ${files.length}}, "
|
||||
"}";
|
||||
}
|
||||
|
||||
final List<File> files;
|
||||
}
|
||||
|
||||
class ScanAccountDirBlocInit extends ScanAccountDirBlocState {
|
||||
const ScanAccountDirBlocInit() : super(const []);
|
||||
}
|
||||
|
||||
class ScanAccountDirBlocLoading extends ScanAccountDirBlocState {
|
||||
const ScanAccountDirBlocLoading(List<File> files) : super(files);
|
||||
}
|
||||
|
||||
class ScanAccountDirBlocSuccess extends ScanAccountDirBlocState {
|
||||
const ScanAccountDirBlocSuccess(List<File> files) : super(files);
|
||||
}
|
||||
|
||||
class ScanAccountDirBlocFailure extends ScanAccountDirBlocState {
|
||||
const ScanAccountDirBlocFailure(List<File> files, this.exception)
|
||||
: super(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 ScanAccountDirBlocInconsistent extends ScanAccountDirBlocState {
|
||||
const ScanAccountDirBlocInconsistent(List<File> files) : super(files);
|
||||
}
|
||||
|
||||
/// A bloc that return all files under a dir recursively
|
||||
///
|
||||
/// See [ScanDir]
|
||||
class ScanAccountDirBloc
|
||||
extends Bloc<ScanAccountDirBlocEvent, ScanAccountDirBlocState> {
|
||||
ScanAccountDirBloc._(this.account) : super(const ScanAccountDirBlocInit()) {
|
||||
_fileRemovedEventListener.begin();
|
||||
_filePropertyUpdatedEventListener.begin();
|
||||
_fileTrashbinRestoredEventListener.begin();
|
||||
_fileMovedEventListener.begin();
|
||||
_prefUpdatedEventListener.begin();
|
||||
}
|
||||
|
||||
static ScanAccountDirBloc 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<ScanAccountDirBloc>("ScanAccountDirBloc($id)");
|
||||
} catch (_) {
|
||||
// no created instance for this account, make a new one
|
||||
_log.info("[of] New bloc instance for account: $account");
|
||||
final bloc = ScanAccountDirBloc._(account);
|
||||
KiwiContainer().registerInstance<ScanAccountDirBloc>(bloc,
|
||||
name: "ScanAccountDirBloc($id)");
|
||||
return bloc;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
transformEvents(Stream<ScanAccountDirBlocEvent> events, transitionFn) {
|
||||
return super.transformEvents(events.distinct((a, b) {
|
||||
// only handle ScanAccountDirBlocQuery
|
||||
final r = a is ScanAccountDirBlocQuery &&
|
||||
b is ScanAccountDirBlocQuery &&
|
||||
a == b;
|
||||
if (r) {
|
||||
_log.fine(
|
||||
"[transformEvents] Skip identical ScanAccountDirBlocQuery event");
|
||||
}
|
||||
return r;
|
||||
}), transitionFn);
|
||||
}
|
||||
|
||||
@override
|
||||
mapEventToState(ScanAccountDirBlocEvent event) async* {
|
||||
_log.info("[mapEventToState] $event");
|
||||
if (event is ScanAccountDirBlocQueryBase) {
|
||||
yield* _onEventQuery(event);
|
||||
} else if (event is _ScanAccountDirBlocExternalEvent) {
|
||||
yield* _onExternalEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
close() {
|
||||
_fileRemovedEventListener.end();
|
||||
_filePropertyUpdatedEventListener.end();
|
||||
_fileTrashbinRestoredEventListener.end();
|
||||
_fileMovedEventListener.end();
|
||||
_prefUpdatedEventListener.end();
|
||||
|
||||
_refreshThrottler.clear();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
Stream<ScanAccountDirBlocState> _onEventQuery(
|
||||
ScanAccountDirBlocQueryBase ev) async* {
|
||||
yield ScanAccountDirBlocLoading(state.files);
|
||||
bool hasContent = state.files.isNotEmpty;
|
||||
|
||||
if (!hasContent) {
|
||||
// show something instantly on first load
|
||||
ScanAccountDirBlocState cacheState = const ScanAccountDirBlocInit();
|
||||
await for (final s in _queryOffline(ev, () => cacheState)) {
|
||||
cacheState = s;
|
||||
}
|
||||
yield ScanAccountDirBlocLoading(cacheState.files);
|
||||
hasContent = cacheState.files.isNotEmpty;
|
||||
}
|
||||
|
||||
ScanAccountDirBlocState newState = const ScanAccountDirBlocInit();
|
||||
if (!hasContent) {
|
||||
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 ScanAccountDirBlocSuccess) {
|
||||
yield newState;
|
||||
} else if (newState is ScanAccountDirBlocFailure) {
|
||||
yield ScanAccountDirBlocFailure(state.files, newState.exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Stream<ScanAccountDirBlocState> _onExternalEvent(
|
||||
_ScanAccountDirBlocExternalEvent ev) async* {
|
||||
yield ScanAccountDirBlocInconsistent(state.files);
|
||||
}
|
||||
|
||||
void _onFileRemovedEvent(FileRemovedEvent ev) {
|
||||
if (state is ScanAccountDirBlocInit) {
|
||||
// no data in this bloc, ignore
|
||||
return;
|
||||
}
|
||||
if (_isFileOfInterest(ev.file)) {
|
||||
_refreshThrottler.trigger(
|
||||
maxResponceTime: const Duration(seconds: 3),
|
||||
maxPendingCount: 10,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _onFilePropertyUpdatedEvent(FilePropertyUpdatedEvent ev) {
|
||||
if (!ev.hasAnyProperties([
|
||||
FilePropertyUpdatedEvent.propMetadata,
|
||||
FilePropertyUpdatedEvent.propIsArchived,
|
||||
FilePropertyUpdatedEvent.propOverrideDateTime,
|
||||
])) {
|
||||
// not interested
|
||||
return;
|
||||
}
|
||||
if (state is ScanAccountDirBlocInit) {
|
||||
// no data in this bloc, ignore
|
||||
return;
|
||||
}
|
||||
if (!_isFileOfInterest(ev.file)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ev.hasAnyProperties([
|
||||
FilePropertyUpdatedEvent.propIsArchived,
|
||||
FilePropertyUpdatedEvent.propOverrideDateTime,
|
||||
])) {
|
||||
_refreshThrottler.trigger(
|
||||
maxResponceTime: const Duration(seconds: 3),
|
||||
maxPendingCount: 10,
|
||||
);
|
||||
} else {
|
||||
_refreshThrottler.trigger(
|
||||
maxResponceTime: const Duration(seconds: 10),
|
||||
maxPendingCount: 10,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _onFileTrashbinRestoredEvent(FileTrashbinRestoredEvent ev) {
|
||||
if (state is ScanAccountDirBlocInit) {
|
||||
// no data in this bloc, ignore
|
||||
return;
|
||||
}
|
||||
_refreshThrottler.trigger(
|
||||
maxResponceTime: const Duration(seconds: 3),
|
||||
maxPendingCount: 10,
|
||||
);
|
||||
}
|
||||
|
||||
void _onFileMovedEvent(FileMovedEvent ev) {
|
||||
if (state is ScanAccountDirBlocInit) {
|
||||
// no data in this bloc, ignore
|
||||
return;
|
||||
}
|
||||
if (_isFileOfInterest(ev.file)) {
|
||||
_refreshThrottler.trigger(
|
||||
maxResponceTime: const Duration(seconds: 3),
|
||||
maxPendingCount: 10,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _onPrefUpdatedEvent(PrefUpdatedEvent ev) {
|
||||
if (state is ScanAccountDirBlocInit) {
|
||||
// no data in this bloc, ignore
|
||||
return;
|
||||
}
|
||||
if (ev.key == PrefKey.accounts2) {
|
||||
_refreshThrottler.trigger(
|
||||
maxResponceTime: const Duration(seconds: 3),
|
||||
maxPendingCount: 10,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Stream<ScanAccountDirBlocState> _queryOffline(ScanAccountDirBlocQueryBase ev,
|
||||
ScanAccountDirBlocState Function() getState) =>
|
||||
_queryWithFileDataSource(ev, getState, FileAppDbDataSource(AppDb()));
|
||||
|
||||
Stream<ScanAccountDirBlocState> _queryOnline(ScanAccountDirBlocQueryBase ev,
|
||||
ScanAccountDirBlocState Function() getState) {
|
||||
final stream = _queryWithFileDataSource(ev, getState,
|
||||
FileCachedDataSource(AppDb(), shouldCheckCache: _shouldCheckCache));
|
||||
_shouldCheckCache = false;
|
||||
return stream;
|
||||
}
|
||||
|
||||
Stream<ScanAccountDirBlocState> _queryWithFileDataSource(
|
||||
ScanAccountDirBlocQueryBase ev,
|
||||
ScanAccountDirBlocState Function() getState,
|
||||
FileDataSource dataSrc) async* {
|
||||
try {
|
||||
final fileRepo = FileRepo(dataSrc);
|
||||
// include files shared with this account
|
||||
final settings = Pref().getAccountSettings(account);
|
||||
final shareDir =
|
||||
File(path: file_util.unstripPath(account, settings.shareFolder));
|
||||
bool isShareDirIncluded = false;
|
||||
|
||||
for (final r in account.roots) {
|
||||
final dir = File(path: file_util.unstripPath(account, r));
|
||||
final dataStream = ScanDir(fileRepo)(account, dir);
|
||||
await for (final d in dataStream) {
|
||||
if (d is ExceptionEvent) {
|
||||
throw d.error;
|
||||
}
|
||||
yield ScanAccountDirBlocLoading(getState().files + d);
|
||||
}
|
||||
|
||||
isShareDirIncluded |= file_util.isOrUnderDir(shareDir, dir);
|
||||
}
|
||||
|
||||
if (!isShareDirIncluded) {
|
||||
final files = await Ls(fileRepo)(account,
|
||||
File(path: file_util.unstripPath(account, settings.shareFolder)));
|
||||
final sharedFiles =
|
||||
files.where((f) => !f.isOwned(account.username)).toList();
|
||||
yield ScanAccountDirBlocSuccess(getState().files + sharedFiles);
|
||||
} else {
|
||||
yield ScanAccountDirBlocSuccess(getState().files);
|
||||
}
|
||||
} catch (e) {
|
||||
_log.severe("[_queryWithFileDataSource] Exception while request", e);
|
||||
yield ScanAccountDirBlocFailure(getState().files, e);
|
||||
}
|
||||
}
|
||||
|
||||
bool _isFileOfInterest(File file) {
|
||||
if (!file_util.isSupportedFormat(file)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (final r in account.roots) {
|
||||
final dir = File(path: file_util.unstripPath(account, r));
|
||||
if (file_util.isUnderDir(file, dir)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
final settings = Pref().getAccountSettings(account);
|
||||
final shareDir =
|
||||
File(path: file_util.unstripPath(account, settings.shareFolder));
|
||||
if (file_util.isUnderDir(file, shareDir)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
final Account account;
|
||||
|
||||
late final _fileRemovedEventListener =
|
||||
AppEventListener<FileRemovedEvent>(_onFileRemovedEvent);
|
||||
late final _filePropertyUpdatedEventListener =
|
||||
AppEventListener<FilePropertyUpdatedEvent>(_onFilePropertyUpdatedEvent);
|
||||
late final _fileTrashbinRestoredEventListener =
|
||||
AppEventListener<FileTrashbinRestoredEvent>(_onFileTrashbinRestoredEvent);
|
||||
late final _fileMovedEventListener =
|
||||
AppEventListener<FileMovedEvent>(_onFileMovedEvent);
|
||||
late final _prefUpdatedEventListener =
|
||||
AppEventListener<PrefUpdatedEvent>(_onPrefUpdatedEvent);
|
||||
|
||||
late final _refreshThrottler = Throttler(
|
||||
onTriggered: (_) {
|
||||
add(const _ScanAccountDirBlocExternalEvent());
|
||||
},
|
||||
logTag: "ScanAccountDirBloc.refresh",
|
||||
);
|
||||
|
||||
bool _shouldCheckCache = true;
|
||||
|
||||
static final _log = Logger("bloc.scan_dir.ScanAccountDirBloc");
|
||||
}
|
|
@ -1,335 +0,0 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:kiwi/kiwi.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:nc_photos/account.dart';
|
||||
import 'package:nc_photos/app_db.dart';
|
||||
import 'package:nc_photos/entity/file.dart';
|
||||
import 'package:nc_photos/entity/file/data_source.dart';
|
||||
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||
import 'package:nc_photos/event/event.dart';
|
||||
import 'package:nc_photos/exception_event.dart';
|
||||
import 'package:nc_photos/iterable_extension.dart';
|
||||
import 'package:nc_photos/throttler.dart';
|
||||
import 'package:nc_photos/use_case/scan_dir.dart';
|
||||
|
||||
abstract class ScanDirBlocEvent {
|
||||
const ScanDirBlocEvent();
|
||||
}
|
||||
|
||||
class ScanDirBlocQueryBase extends ScanDirBlocEvent with EquatableMixin {
|
||||
const ScanDirBlocQueryBase(this.account, this.roots);
|
||||
|
||||
@override
|
||||
toString() {
|
||||
return "$runtimeType {"
|
||||
"account: $account, "
|
||||
"roots: ${roots.map((e) => e.path).toReadableString()}, "
|
||||
"}";
|
||||
}
|
||||
|
||||
@override
|
||||
get props => [
|
||||
account,
|
||||
roots,
|
||||
];
|
||||
|
||||
final Account account;
|
||||
final List<File> roots;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
const ScanDirBlocState(this.account, this.files);
|
||||
|
||||
@override
|
||||
toString() {
|
||||
return "$runtimeType {"
|
||||
"account: $account, "
|
||||
"files: List {length: ${files.length}}, "
|
||||
"}";
|
||||
}
|
||||
|
||||
final Account? account;
|
||||
final List<File> files;
|
||||
}
|
||||
|
||||
class ScanDirBlocInit extends ScanDirBlocState {
|
||||
const ScanDirBlocInit() : super(null, const []);
|
||||
}
|
||||
|
||||
class ScanDirBlocLoading extends ScanDirBlocState {
|
||||
const ScanDirBlocLoading(Account? account, List<File> files)
|
||||
: super(account, files);
|
||||
}
|
||||
|
||||
class ScanDirBlocSuccess extends ScanDirBlocState {
|
||||
const ScanDirBlocSuccess(Account? account, List<File> files)
|
||||
: super(account, files);
|
||||
}
|
||||
|
||||
class ScanDirBlocFailure extends ScanDirBlocState {
|
||||
const ScanDirBlocFailure(Account? account, List<File> files, this.exception)
|
||||
: 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 {
|
||||
const ScanDirBlocInconsistent(Account? account, List<File> files)
|
||||
: super(account, files);
|
||||
}
|
||||
|
||||
/// A bloc that return all files under a dir recursively
|
||||
///
|
||||
/// See [ScanDir]
|
||||
class ScanDirBloc extends Bloc<ScanDirBlocEvent, ScanDirBlocState> {
|
||||
ScanDirBloc() : super(const ScanDirBlocInit()) {
|
||||
_fileRemovedEventListener =
|
||||
AppEventListener<FileRemovedEvent>(_onFileRemovedEvent);
|
||||
_filePropertyUpdatedEventListener =
|
||||
AppEventListener<FilePropertyUpdatedEvent>(_onFilePropertyUpdatedEvent);
|
||||
_fileTrashbinRestoredEventListener =
|
||||
AppEventListener<FileTrashbinRestoredEvent>(
|
||||
_onFileTrashbinRestoredEvent);
|
||||
_fileRemovedEventListener.begin();
|
||||
_filePropertyUpdatedEventListener.begin();
|
||||
_fileTrashbinRestoredEventListener.begin();
|
||||
_fileMovedEventListener.begin();
|
||||
|
||||
_refreshThrottler = Throttler(
|
||||
onTriggered: (_) {
|
||||
add(const _ScanDirBlocExternalEvent());
|
||||
},
|
||||
logTag: "ScanDirBloc.refresh",
|
||||
);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@override
|
||||
mapEventToState(ScanDirBlocEvent event) async* {
|
||||
_log.info("[mapEventToState] $event");
|
||||
if (event is ScanDirBlocQueryBase) {
|
||||
yield* _onEventQuery(event);
|
||||
} else if (event is _ScanDirBlocExternalEvent) {
|
||||
yield* _onExternalEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
close() {
|
||||
_fileRemovedEventListener.end();
|
||||
_filePropertyUpdatedEventListener.end();
|
||||
_fileTrashbinRestoredEventListener.end();
|
||||
_refreshThrottler.clear();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
Stream<ScanDirBlocState> _onEventQuery(ScanDirBlocQueryBase ev) async* {
|
||||
yield ScanDirBlocLoading(ev.account, state.files);
|
||||
bool hasContent = state.files.isNotEmpty;
|
||||
|
||||
if (!hasContent) {
|
||||
// show something instantly on first load
|
||||
ScanDirBlocState cacheState = const ScanDirBlocInit();
|
||||
await for (final s in _queryOffline(ev, () => cacheState)) {
|
||||
cacheState = s;
|
||||
}
|
||||
yield ScanDirBlocLoading(ev.account, cacheState.files);
|
||||
hasContent = cacheState.files.isNotEmpty;
|
||||
}
|
||||
|
||||
ScanDirBlocState newState = const ScanDirBlocInit();
|
||||
if (!hasContent) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
if (!file_util.isTrash(ev.account, ev.file)) {
|
||||
_refreshThrottler.trigger(
|
||||
maxResponceTime: const Duration(seconds: 3),
|
||||
maxPendingCount: 10,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _onFilePropertyUpdatedEvent(FilePropertyUpdatedEvent ev) {
|
||||
if (!ev.hasAnyProperties([
|
||||
FilePropertyUpdatedEvent.propMetadata,
|
||||
FilePropertyUpdatedEvent.propIsArchived,
|
||||
FilePropertyUpdatedEvent.propOverrideDateTime,
|
||||
])) {
|
||||
// not interested
|
||||
return;
|
||||
}
|
||||
if (state is ScanDirBlocInit) {
|
||||
// no data in this bloc, ignore
|
||||
return;
|
||||
}
|
||||
|
||||
if (ev.hasAnyProperties([
|
||||
FilePropertyUpdatedEvent.propIsArchived,
|
||||
FilePropertyUpdatedEvent.propOverrideDateTime,
|
||||
])) {
|
||||
_refreshThrottler.trigger(
|
||||
maxResponceTime: const Duration(seconds: 3),
|
||||
maxPendingCount: 10,
|
||||
);
|
||||
} else {
|
||||
_refreshThrottler.trigger(
|
||||
maxResponceTime: const Duration(seconds: 10),
|
||||
maxPendingCount: 10,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _onFileTrashbinRestoredEvent(FileTrashbinRestoredEvent ev) {
|
||||
if (state is ScanDirBlocInit) {
|
||||
// no data in this bloc, ignore
|
||||
return;
|
||||
}
|
||||
_refreshThrottler.trigger(
|
||||
maxResponceTime: const Duration(seconds: 3),
|
||||
maxPendingCount: 10,
|
||||
);
|
||||
}
|
||||
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Stream<ScanDirBlocState> _queryOffline(
|
||||
ScanDirBlocQueryBase ev, ScanDirBlocState Function() getState) =>
|
||||
_queryWithFileDataSource(ev, getState, FileAppDbDataSource(AppDb()));
|
||||
|
||||
Stream<ScanDirBlocState> _queryOnline(
|
||||
ScanDirBlocQueryBase ev, ScanDirBlocState Function() getState) {
|
||||
final stream = _queryWithFileDataSource(ev, getState,
|
||||
FileCachedDataSource(AppDb(), shouldCheckCache: _shouldCheckCache));
|
||||
_shouldCheckCache = false;
|
||||
return stream;
|
||||
}
|
||||
|
||||
Stream<ScanDirBlocState> _queryWithFileDataSource(ScanDirBlocQueryBase ev,
|
||||
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 ExceptionEvent) {
|
||||
throw d.error;
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
late AppEventListener<FileRemovedEvent> _fileRemovedEventListener;
|
||||
late AppEventListener<FilePropertyUpdatedEvent>
|
||||
_filePropertyUpdatedEventListener;
|
||||
late final AppEventListener<FileTrashbinRestoredEvent>
|
||||
_fileTrashbinRestoredEventListener;
|
||||
late final _fileMovedEventListener =
|
||||
AppEventListener<FileMovedEvent>(_onFileMovedEvent);
|
||||
|
||||
late Throttler _refreshThrottler;
|
||||
|
||||
bool _shouldCheckCache = true;
|
||||
|
||||
static final _log = Logger("bloc.scan_dir.ScanDirBloc");
|
||||
}
|
|
@ -8,7 +8,7 @@ import 'package:nc_photos/account.dart';
|
|||
import 'package:nc_photos/api/api_util.dart' as api_util;
|
||||
import 'package:nc_photos/app_db.dart';
|
||||
import 'package:nc_photos/app_localizations.dart';
|
||||
import 'package:nc_photos/bloc/scan_dir.dart';
|
||||
import 'package:nc_photos/bloc/scan_account_dir.dart';
|
||||
import 'package:nc_photos/debug_util.dart';
|
||||
import 'package:nc_photos/entity/file.dart';
|
||||
import 'package:nc_photos/entity/file/data_source.dart';
|
||||
|
@ -70,10 +70,10 @@ class _ArchiveBrowserState extends State<ArchiveBrowser>
|
|||
build(BuildContext context) {
|
||||
return AppTheme(
|
||||
child: Scaffold(
|
||||
body: BlocListener<ScanDirBloc, ScanDirBlocState>(
|
||||
body: BlocListener<ScanAccountDirBloc, ScanAccountDirBlocState>(
|
||||
bloc: _bloc,
|
||||
listener: (context, state) => _onStateChange(context, state),
|
||||
child: BlocBuilder<ScanDirBloc, ScanDirBlocState>(
|
||||
child: BlocBuilder<ScanAccountDirBloc, ScanAccountDirBlocState>(
|
||||
bloc: _bloc,
|
||||
builder: (context, state) => _buildContent(context, state),
|
||||
),
|
||||
|
@ -83,8 +83,7 @@ class _ArchiveBrowserState extends State<ArchiveBrowser>
|
|||
}
|
||||
|
||||
void _initBloc() {
|
||||
_bloc = ScanDirBloc.of(widget.account);
|
||||
if (_bloc.state is ScanDirBlocInit) {
|
||||
if (_bloc.state is ScanAccountDirBlocInit) {
|
||||
_log.info("[_initBloc] Initialize bloc");
|
||||
_reqQuery();
|
||||
} else {
|
||||
|
@ -97,8 +96,8 @@ class _ArchiveBrowserState extends State<ArchiveBrowser>
|
|||
}
|
||||
}
|
||||
|
||||
Widget _buildContent(BuildContext context, ScanDirBlocState state) {
|
||||
if (state is ScanDirBlocSuccess && itemStreamListItems.isEmpty) {
|
||||
Widget _buildContent(BuildContext context, ScanAccountDirBlocState state) {
|
||||
if (state is ScanAccountDirBlocSuccess && itemStreamListItems.isEmpty) {
|
||||
return Column(
|
||||
children: [
|
||||
AppBar(
|
||||
|
@ -137,7 +136,7 @@ class _ArchiveBrowserState extends State<ArchiveBrowser>
|
|||
),
|
||||
),
|
||||
),
|
||||
if (state is ScanDirBlocLoading)
|
||||
if (state is ScanAccountDirBlocLoading)
|
||||
const Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: LinearProgressIndicator(),
|
||||
|
@ -195,18 +194,19 @@ class _ArchiveBrowserState extends State<ArchiveBrowser>
|
|||
);
|
||||
}
|
||||
|
||||
void _onStateChange(BuildContext context, ScanDirBlocState state) {
|
||||
if (state is ScanDirBlocInit) {
|
||||
void _onStateChange(BuildContext context, ScanAccountDirBlocState state) {
|
||||
if (state is ScanAccountDirBlocInit) {
|
||||
itemStreamListItems = [];
|
||||
} else if (state is ScanDirBlocSuccess || state is ScanDirBlocLoading) {
|
||||
} else if (state is ScanAccountDirBlocSuccess ||
|
||||
state is ScanAccountDirBlocLoading) {
|
||||
_transformItems(state.files);
|
||||
} else if (state is ScanDirBlocFailure) {
|
||||
} else if (state is ScanAccountDirBlocFailure) {
|
||||
_transformItems(state.files);
|
||||
SnackBarManager().showSnackBar(SnackBar(
|
||||
content: Text(exception_util.toUserString(state.exception)),
|
||||
duration: k.snackBarDurationNormal,
|
||||
));
|
||||
} else if (state is ScanDirBlocInconsistent) {
|
||||
} else if (state is ScanAccountDirBlocInconsistent) {
|
||||
_reqQuery();
|
||||
}
|
||||
}
|
||||
|
@ -294,11 +294,7 @@ class _ArchiveBrowserState extends State<ArchiveBrowser>
|
|||
}
|
||||
|
||||
void _reqQuery() {
|
||||
_bloc.add(ScanDirBlocQuery(
|
||||
widget.account,
|
||||
widget.account.roots
|
||||
.map((e) => File(path: file_util.unstripPath(widget.account, e)))
|
||||
.toList()));
|
||||
_bloc.add(const ScanAccountDirBlocQuery());
|
||||
}
|
||||
|
||||
int get _thumbSize {
|
||||
|
@ -315,7 +311,7 @@ class _ArchiveBrowserState extends State<ArchiveBrowser>
|
|||
}
|
||||
}
|
||||
|
||||
late ScanDirBloc _bloc;
|
||||
late final _bloc = ScanAccountDirBloc.of(widget.account);
|
||||
|
||||
var _backingFiles = <File>[];
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ import 'package:nc_photos/account.dart';
|
|||
import 'package:nc_photos/api/api_util.dart' as api_util;
|
||||
import 'package:nc_photos/app_db.dart';
|
||||
import 'package:nc_photos/app_localizations.dart';
|
||||
import 'package:nc_photos/bloc/scan_dir.dart';
|
||||
import 'package:nc_photos/bloc/scan_account_dir.dart';
|
||||
import 'package:nc_photos/debug_util.dart';
|
||||
import 'package:nc_photos/download_handler.dart';
|
||||
import 'package:nc_photos/entity/album.dart';
|
||||
|
@ -88,10 +88,10 @@ class _HomePhotosState extends State<HomePhotos>
|
|||
|
||||
@override
|
||||
build(BuildContext context) {
|
||||
return BlocListener<ScanDirBloc, ScanDirBlocState>(
|
||||
return BlocListener<ScanAccountDirBloc, ScanAccountDirBlocState>(
|
||||
bloc: _bloc,
|
||||
listener: (context, state) => _onStateChange(context, state),
|
||||
child: BlocBuilder<ScanDirBloc, ScanDirBlocState>(
|
||||
child: BlocBuilder<ScanAccountDirBloc, ScanAccountDirBlocState>(
|
||||
bloc: _bloc,
|
||||
builder: (context, state) => _buildContent(context, state),
|
||||
),
|
||||
|
@ -99,8 +99,7 @@ class _HomePhotosState extends State<HomePhotos>
|
|||
}
|
||||
|
||||
void _initBloc() {
|
||||
_bloc = ScanDirBloc.of(widget.account);
|
||||
if (_bloc.state is ScanDirBlocInit) {
|
||||
if (_bloc.state is ScanAccountDirBlocInit) {
|
||||
_log.info("[_initBloc] Initialize bloc");
|
||||
_reqQuery();
|
||||
} else {
|
||||
|
@ -113,7 +112,7 @@ class _HomePhotosState extends State<HomePhotos>
|
|||
}
|
||||
}
|
||||
|
||||
Widget _buildContent(BuildContext context, ScanDirBlocState state) {
|
||||
Widget _buildContent(BuildContext context, ScanAccountDirBlocState state) {
|
||||
return LayoutBuilder(builder: (context, constraints) {
|
||||
final scrollExtent = _getScrollViewExtent(constraints);
|
||||
return Stack(
|
||||
|
@ -161,7 +160,7 @@ class _HomePhotosState extends State<HomePhotos>
|
|||
),
|
||||
),
|
||||
),
|
||||
if (state is ScanDirBlocLoading)
|
||||
if (state is ScanAccountDirBlocLoading)
|
||||
const Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: LinearProgressIndicator(),
|
||||
|
@ -338,15 +337,16 @@ class _HomePhotosState extends State<HomePhotos>
|
|||
);
|
||||
}
|
||||
|
||||
void _onStateChange(BuildContext context, ScanDirBlocState state) {
|
||||
if (state is ScanDirBlocInit) {
|
||||
void _onStateChange(BuildContext context, ScanAccountDirBlocState state) {
|
||||
if (state is ScanAccountDirBlocInit) {
|
||||
itemStreamListItems = [];
|
||||
} else if (state is ScanDirBlocSuccess || state is ScanDirBlocLoading) {
|
||||
} else if (state is ScanAccountDirBlocSuccess ||
|
||||
state is ScanAccountDirBlocLoading) {
|
||||
_transformItems(state.files);
|
||||
if (state is ScanDirBlocSuccess) {
|
||||
if (state is ScanAccountDirBlocSuccess) {
|
||||
_tryStartMetadataTask();
|
||||
}
|
||||
} else if (state is ScanDirBlocFailure) {
|
||||
} else if (state is ScanAccountDirBlocFailure) {
|
||||
_transformItems(state.files);
|
||||
if (isPageVisible()) {
|
||||
SnackBarManager().showSnackBar(SnackBar(
|
||||
|
@ -354,7 +354,7 @@ class _HomePhotosState extends State<HomePhotos>
|
|||
duration: k.snackBarDurationNormal,
|
||||
));
|
||||
}
|
||||
} else if (state is ScanDirBlocInconsistent) {
|
||||
} else if (state is ScanAccountDirBlocInconsistent) {
|
||||
_reqQuery();
|
||||
}
|
||||
}
|
||||
|
@ -546,7 +546,7 @@ class _HomePhotosState extends State<HomePhotos>
|
|||
void _tryStartMetadataTask({
|
||||
bool ignoreFired = false,
|
||||
}) {
|
||||
if (_bloc.state is ScanDirBlocSuccess &&
|
||||
if (_bloc.state is ScanAccountDirBlocSuccess &&
|
||||
Pref().isEnableExifOr() &&
|
||||
(!_hasFiredMetadataTask.value || ignoreFired)) {
|
||||
MetadataTaskManager().addTask(MetadataTask(widget.account));
|
||||
|
@ -603,23 +603,11 @@ class _HomePhotosState extends State<HomePhotos>
|
|||
}
|
||||
|
||||
void _reqQuery() {
|
||||
_bloc.add(ScanDirBlocQuery(
|
||||
widget.account,
|
||||
widget.account.roots
|
||||
.map((e) => File(
|
||||
path:
|
||||
"${api_util.getWebdavRootUrlRelative(widget.account)}/$e"))
|
||||
.toList()));
|
||||
_bloc.add(const ScanAccountDirBlocQuery());
|
||||
}
|
||||
|
||||
void _reqRefresh() {
|
||||
_bloc.add(ScanDirBlocRefresh(
|
||||
widget.account,
|
||||
widget.account.roots
|
||||
.map((e) => File(
|
||||
path:
|
||||
"${api_util.getWebdavRootUrlRelative(widget.account)}/$e"))
|
||||
.toList()));
|
||||
_bloc.add(const ScanAccountDirBlocRefresh());
|
||||
}
|
||||
|
||||
void _setThumbZoomLevel(int level) {
|
||||
|
@ -696,7 +684,7 @@ class _HomePhotosState extends State<HomePhotos>
|
|||
}
|
||||
}
|
||||
|
||||
late ScanDirBloc _bloc;
|
||||
late final _bloc = ScanAccountDirBloc.of(widget.account);
|
||||
|
||||
var _backingFiles = <File>[];
|
||||
|
||||
|
|
Loading…
Reference in a new issue