nc-photos/app/lib/bloc/list_album.dart

346 lines
10 KiB
Dart
Raw Normal View History

2021-04-10 06:28:12 +02:00
import 'package:bloc/bloc.dart';
import 'package:kiwi/kiwi.dart';
2021-04-10 06:28:12 +02:00
import 'package:logging/logging.dart';
import 'package:nc_photos/account.dart';
2022-01-27 11:02:45 +01:00
import 'package:nc_photos/bloc/bloc_util.dart' as bloc_util;
import 'package:nc_photos/di_container.dart';
2021-04-10 06:28:12 +02:00
import 'package:nc_photos/entity/album.dart';
2021-11-12 12:44:31 +01:00
import 'package:nc_photos/entity/file_util.dart' as file_util;
2021-08-13 12:45:26 +02:00
import 'package:nc_photos/entity/share.dart';
2021-04-10 06:28:12 +02:00
import 'package:nc_photos/event/event.dart';
2021-08-06 06:54:06 +02:00
import 'package:nc_photos/exception.dart';
import 'package:nc_photos/exception_event.dart';
import 'package:nc_photos/or_null.dart';
2021-08-13 12:45:26 +02:00
import 'package:nc_photos/remote_storage_util.dart' as remote_storage_util;
2021-07-31 21:49:28 +02:00
import 'package:nc_photos/throttler.dart';
2021-04-10 06:28:12 +02:00
import 'package:nc_photos/use_case/list_album.dart';
2021-08-13 12:38:46 +02:00
class ListAlbumBlocItem {
ListAlbumBlocItem(this.album);
2021-08-13 12:38:46 +02:00
final Album album;
}
2021-04-10 06:28:12 +02:00
abstract class ListAlbumBlocEvent {
const ListAlbumBlocEvent();
}
class ListAlbumBlocQuery extends ListAlbumBlocEvent {
const ListAlbumBlocQuery(this.account);
@override
toString() {
return "$runtimeType {"
"account: $account, "
"}";
}
final Account account;
}
/// An external event has happened and may affect the state of this bloc
class _ListAlbumBlocExternalEvent extends ListAlbumBlocEvent {
const _ListAlbumBlocExternalEvent();
@override
toString() {
return "$runtimeType {"
"}";
}
}
abstract class ListAlbumBlocState {
2021-08-13 12:38:46 +02:00
const ListAlbumBlocState(this.account, this.items);
2021-04-10 06:28:12 +02:00
@override
toString() {
return "$runtimeType {"
"account: $account, "
2021-08-13 12:38:46 +02:00
"items: List {length: ${items.length}}, "
2021-04-10 06:28:12 +02:00
"}";
}
2021-07-23 22:05:57 +02:00
final Account? account;
2021-08-13 12:38:46 +02:00
final List<ListAlbumBlocItem> items;
2021-04-10 06:28:12 +02:00
}
class ListAlbumBlocInit extends ListAlbumBlocState {
const ListAlbumBlocInit() : super(null, const []);
}
class ListAlbumBlocLoading extends ListAlbumBlocState {
2021-08-13 12:38:46 +02:00
const ListAlbumBlocLoading(Account? account, List<ListAlbumBlocItem> items)
: super(account, items);
2021-04-10 06:28:12 +02:00
}
class ListAlbumBlocSuccess extends ListAlbumBlocState {
2021-08-13 12:38:46 +02:00
const ListAlbumBlocSuccess(Account? account, List<ListAlbumBlocItem> items)
: super(account, items);
2021-04-10 06:28:12 +02:00
}
class ListAlbumBlocFailure extends ListAlbumBlocState {
const ListAlbumBlocFailure(
2021-08-13 12:38:46 +02:00
Account? account, List<ListAlbumBlocItem> items, this.exception)
: super(account, items);
2021-04-10 06:28:12 +02:00
@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 ListAlbumBlocInconsistent extends ListAlbumBlocState {
2021-08-13 12:38:46 +02:00
const ListAlbumBlocInconsistent(
Account? account, List<ListAlbumBlocItem> items)
: super(account, items);
2021-04-10 06:28:12 +02:00
}
class ListAlbumBloc extends Bloc<ListAlbumBlocEvent, ListAlbumBlocState> {
/// Constructor
///
/// If [offlineC] is not null, this [DiContainer] will be used when requesting
/// offline contents, otherwise [_c] will be used
ListAlbumBloc(
this._c, [
DiContainer? offlineC,
]) : _offlineC = offlineC ?? _c,
assert(require(_c)),
assert(offlineC == null || require(offlineC)),
assert(ListAlbum.require(_c)),
assert(offlineC == null || ListAlbum.require(offlineC)),
super(const ListAlbumBlocInit()) {
2021-04-10 06:28:12 +02:00
_albumUpdatedListener =
AppEventListener<AlbumUpdatedEvent>(_onAlbumUpdatedEvent);
_fileRemovedListener =
AppEventListener<FileRemovedEvent>(_onFileRemovedEvent);
_albumCreatedListener =
AppEventListener<AlbumCreatedEvent>(_onAlbumCreatedEvent);
_albumUpdatedListener.begin();
_fileRemovedListener.begin();
_albumCreatedListener.begin();
_fileMovedListener.begin();
_shareCreatedListener.begin();
_shareRemovedListener.begin();
2021-07-31 21:49:28 +02:00
_refreshThrottler = Throttler(
onTriggered: (_) {
2021-09-15 08:58:06 +02:00
add(const _ListAlbumBlocExternalEvent());
2021-07-31 21:49:28 +02:00
},
logTag: "ListAlbumBloc.refresh",
);
2022-07-09 07:59:09 +02:00
on<ListAlbumBlocEvent>(_onEvent);
2021-04-10 06:28:12 +02:00
}
static bool require(DiContainer c) => true;
static ListAlbumBloc of(Account account) {
2022-01-27 11:02:45 +01:00
final name = bloc_util.getInstNameForAccount("ListAlbumBloc", account);
try {
2022-01-27 11:02:45 +01:00
_log.fine("[of] Resolving bloc for '$name'");
return KiwiContainer().resolve<ListAlbumBloc>(name);
} catch (_) {
// no created instance for this account, make a new one
_log.info("[of] New bloc instance for account: $account");
final c = KiwiContainer().resolve<DiContainer>();
final offlineC = c.copyWith(
fileRepo: OrNull(c.fileRepoLocal),
albumRepo: OrNull(c.albumRepoLocal),
);
final bloc = ListAlbumBloc(c, offlineC);
2022-01-27 11:02:45 +01:00
KiwiContainer().registerInstance<ListAlbumBloc>(bloc, name: name);
return bloc;
}
}
2021-04-10 06:28:12 +02:00
@override
close() {
_albumUpdatedListener.end();
_fileRemovedListener.end();
_albumCreatedListener.end();
_fileMovedListener.end();
_shareCreatedListener.end();
_shareRemovedListener.end();
2021-07-31 21:49:28 +02:00
_refreshThrottler.clear();
2021-04-10 06:28:12 +02:00
return super.close();
}
2022-07-09 07:59:09 +02:00
Future<void> _onEvent(
ListAlbumBlocEvent event, Emitter<ListAlbumBlocState> emit) async {
_log.info("[_onEvent] $event");
if (event is ListAlbumBlocQuery) {
await _onEventQuery(event, emit);
} else if (event is _ListAlbumBlocExternalEvent) {
await _onExternalEvent(event, emit);
}
}
Future<void> _onEventQuery(
ListAlbumBlocQuery ev, Emitter<ListAlbumBlocState> emit) async {
emit(ListAlbumBlocLoading(ev.account, state.items));
2021-08-13 12:38:46 +02:00
bool hasContent = state.items.isNotEmpty;
2021-04-10 06:28:12 +02:00
if (!hasContent) {
// show something instantly on first load
final cacheState = await _queryOffline(ev);
2022-07-09 07:59:09 +02:00
emit(ListAlbumBlocLoading(ev.account, cacheState.items));
2021-08-13 12:38:46 +02:00
hasContent = cacheState.items.isNotEmpty;
2021-04-10 06:28:12 +02:00
}
final newState = await _queryOnline(ev);
if (newState is ListAlbumBlocFailure) {
2022-07-09 07:59:09 +02:00
emit(ListAlbumBlocFailure(
ev.account,
2021-08-13 12:38:46 +02:00
newState.items.isNotEmpty ? newState.items : state.items,
2022-07-09 07:59:09 +02:00
newState.exception));
2021-04-10 06:28:12 +02:00
} else {
2022-07-09 07:59:09 +02:00
emit(newState);
2021-04-10 06:28:12 +02:00
}
}
2022-07-09 07:59:09 +02:00
Future<void> _onExternalEvent(
_ListAlbumBlocExternalEvent ev, Emitter<ListAlbumBlocState> emit) async {
emit(ListAlbumBlocInconsistent(state.account, state.items));
2021-04-10 06:28:12 +02:00
}
void _onAlbumUpdatedEvent(AlbumUpdatedEvent ev) {
if (state is ListAlbumBlocInit) {
// no data in this bloc, ignore
return;
}
if (_isAccountOfInterest(ev.account)) {
_refreshThrottler.trigger(
maxResponceTime: const Duration(seconds: 3),
maxPendingCount: 10,
);
}
2021-04-10 06:28:12 +02:00
}
void _onFileRemovedEvent(FileRemovedEvent ev) {
if (state is ListAlbumBlocInit) {
// no data in this bloc, ignore
return;
}
if (_isAccountOfInterest(ev.account) &&
file_util.isAlbumFile(ev.account, ev.file)) {
2021-07-31 21:49:28 +02:00
_refreshThrottler.trigger(
maxResponceTime: const Duration(seconds: 3),
maxPendingCount: 10,
);
2021-04-10 06:28:12 +02:00
}
}
void _onFileMovedEvent(FileMovedEvent ev) {
if (state is ListAlbumBlocInit) {
// no data in this bloc, ignore
return;
}
if (_isAccountOfInterest(ev.account)) {
if (ev.destination
.startsWith(remote_storage_util.getRemoteAlbumsDir(ev.account)) ||
ev.file.path
.startsWith(remote_storage_util.getRemoteAlbumsDir(ev.account))) {
// moving from/to album dir
_refreshThrottler.trigger(
maxResponceTime: const Duration(seconds: 3),
maxPendingCount: 10,
);
}
}
}
2021-04-10 06:28:12 +02:00
void _onAlbumCreatedEvent(AlbumCreatedEvent ev) {
if (state is ListAlbumBlocInit) {
// no data in this bloc, ignore
return;
}
if (_isAccountOfInterest(ev.account)) {
add(const _ListAlbumBlocExternalEvent());
}
2021-04-10 06:28:12 +02:00
}
void _onShareCreatedEvent(ShareCreatedEvent ev) =>
_onShareChanged(ev.account, ev.share);
void _onShareRemovedEvent(ShareRemovedEvent ev) =>
_onShareChanged(ev.account, ev.share);
void _onShareChanged(Account account, Share share) {
if (_isAccountOfInterest(account)) {
final webdavPath = file_util.unstripPath(account, share.path);
if (webdavPath
.startsWith(remote_storage_util.getRemoteAlbumsDir(account))) {
_refreshThrottler.trigger(
maxResponceTime: const Duration(seconds: 3),
maxPendingCount: 10,
);
}
}
}
Future<ListAlbumBlocState> _queryOffline(ListAlbumBlocQuery ev) =>
_queryWithAlbumDataSource(_offlineC, ev);
2021-04-10 06:28:12 +02:00
Future<ListAlbumBlocState> _queryOnline(ListAlbumBlocQuery ev) =>
_queryWithAlbumDataSource(_c, ev);
2021-04-10 06:28:12 +02:00
Future<ListAlbumBlocState> _queryWithAlbumDataSource(
DiContainer c, ListAlbumBlocQuery ev) async {
2021-04-10 06:28:12 +02:00
try {
final albums = <Album>[];
final errors = <dynamic>[];
await for (final result in ListAlbum(c)(ev.account)) {
if (result is ExceptionEvent) {
if (result.error is CacheNotFoundException) {
2021-08-06 06:54:06 +02:00
_log.info(
"[_queryWithAlbumDataSource] Cache not found", result.error);
2021-08-06 06:54:06 +02:00
} else {
_log.shout("[_queryWithAlbumDataSource] Exception while ListAlbum",
result.error, result.stackTrace);
2021-08-06 06:54:06 +02:00
}
errors.add(result.error);
} else if (result is Album) {
albums.add(result);
}
}
2021-08-13 12:38:46 +02:00
final items = albums.map((e) => ListAlbumBlocItem(e)).toList();
if (errors.isEmpty) {
2021-08-13 12:38:46 +02:00
return ListAlbumBlocSuccess(ev.account, items);
} else {
2021-08-13 12:38:46 +02:00
return ListAlbumBlocFailure(ev.account, items, errors.first);
}
2021-04-26 18:15:37 +02:00
} catch (e, stacktrace) {
_log.severe("[_queryWithAlbumDataSource] Exception", e, stacktrace);
return ListAlbumBlocFailure(ev.account, [], e);
2021-04-10 06:28:12 +02:00
}
}
bool _isAccountOfInterest(Account account) =>
state.account == null || state.account!.compareServerIdentity(account);
final DiContainer _c;
final DiContainer _offlineC;
2021-07-23 22:05:57 +02:00
late AppEventListener<AlbumUpdatedEvent> _albumUpdatedListener;
late AppEventListener<FileRemovedEvent> _fileRemovedListener;
late AppEventListener<AlbumCreatedEvent> _albumCreatedListener;
late final _fileMovedListener =
AppEventListener<FileMovedEvent>(_onFileMovedEvent);
late final _shareCreatedListener =
AppEventListener<ShareCreatedEvent>(_onShareCreatedEvent);
late final _shareRemovedListener =
AppEventListener<ShareRemovedEvent>(_onShareRemovedEvent);
2021-04-10 06:28:12 +02:00
2021-07-31 21:49:28 +02:00
late Throttler _refreshThrottler;
2021-04-10 06:28:12 +02:00
static final _log = Logger("bloc.list_album.ListAlbumBloc");
}