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/api/api_util.dart' as api_util; import 'package:nc_photos/entity/file.dart'; import 'package:nc_photos/entity/file_util.dart' as file_util; import 'package:nc_photos/entity/share.dart'; import 'package:nc_photos/entity/share/data_source.dart'; import 'package:nc_photos/event/event.dart'; import 'package:nc_photos/remote_storage_util.dart' as remote_storage_util; import 'package:nc_photos/use_case/find_file.dart'; class ListSharingItem { ListSharingItem(this.share, this.file); final Share share; final File file; } abstract class ListSharingBlocEvent { const ListSharingBlocEvent(); } class ListSharingBlocQuery extends ListSharingBlocEvent { const ListSharingBlocQuery(this.account); @override toString() { return "$runtimeType {" "account: $account, " "}"; } final Account account; } class _ListSharingBlocShareRemoved extends ListSharingBlocEvent { const _ListSharingBlocShareRemoved(this.share); @override toString() { return "$runtimeType {" "share: $share, " "}"; } final Share share; } abstract class ListSharingBlocState { const ListSharingBlocState(this.account, this.items); @override toString() { return "$runtimeType {" "account: $account, " "items: List {length: ${items.length}}, " "}"; } final Account? account; final List items; } class ListSharingBlocInit extends ListSharingBlocState { ListSharingBlocInit() : super(null, const []); } class ListSharingBlocLoading extends ListSharingBlocState { const ListSharingBlocLoading(Account? account, List items) : super(account, items); } class ListSharingBlocSuccess extends ListSharingBlocState { const ListSharingBlocSuccess(Account? account, List items) : super(account, items); ListSharingBlocSuccess copyWith({ Account? account, List? items, }) => ListSharingBlocSuccess( account ?? this.account, items ?? this.items, ); } class ListSharingBlocFailure extends ListSharingBlocState { const ListSharingBlocFailure( Account? account, List items, this.exception) : super(account, items); @override toString() { return "$runtimeType {" "super: ${super.toString()}, " "exception: $exception, " "}"; } ListSharingBlocFailure copyWith({ Account? account, List? items, dynamic exception, }) => ListSharingBlocFailure( account ?? this.account, items ?? this.items, exception ?? this.exception, ); final dynamic exception; } /// List all shares from a given file class ListSharingBloc extends Bloc { ListSharingBloc() : super(ListSharingBlocInit()) { _shareRemovedListener.begin(); } static ListSharingBloc 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("ListSharingBloc($id)"); } catch (_) { // no created instance for this account, make a new one _log.info("[of] New bloc instance for account: $account"); final bloc = ListSharingBloc(); KiwiContainer().registerInstance(bloc, name: "ListSharingBloc($id)"); return bloc; } } @override mapEventToState(ListSharingBlocEvent event) async* { _log.info("[mapEventToState] $event"); if (event is ListSharingBlocQuery) { yield* _onEventQuery(event); } else if (event is _ListSharingBlocShareRemoved) { yield* _onEventShareRemoved(event); } } Stream _onEventQuery(ListSharingBlocQuery ev) async* { try { yield ListSharingBlocLoading(ev.account, state.items); yield ListSharingBlocSuccess(ev.account, await _query(ev)); } catch (e, stackTrace) { _log.severe("[_onEventQuery] Exception while request", e, stackTrace); yield ListSharingBlocFailure(ev.account, state.items, e); } } Stream _onEventShareRemoved( _ListSharingBlocShareRemoved ev) async* { if (state is! ListSharingBlocSuccess && state is! ListSharingBlocFailure) { return; } final newItems = List.of(state.items) .where((element) => !identical(element.share, ev.share)) .toList(); // i love hacks :) yield (state as dynamic).copyWith( items: newItems, ) as ListSharingBlocState; } void _onShareRemovedEvent(ShareRemovedEvent ev) { add(_ListSharingBlocShareRemoved(ev.share)); } Future> _query(ListSharingBlocQuery ev) async { final shareRepo = ShareRepo(ShareRemoteDataSource()); final shares = await shareRepo.listAll(ev.account); final futures = shares.map((e) async { // include link share dirs if (e.itemType == ShareItemType.folder) { final webdavPath = "${api_util.getWebdavRootUrlRelative(ev.account)}/${e.path}"; if (webdavPath.startsWith( remote_storage_util.getRemoteLinkSharesDir(ev.account))) { return ListSharingItem( e, File( path: webdavPath, isCollection: true, ), ); } } if (!file_util.isSupportedMime(e.mimeType)) { return null; } if (ev.account.roots .every((r) => r.isNotEmpty && !e.path.startsWith("/$r/"))) { // ignore files not under root dirs return null; } try { final file = await FindFile()(ev.account, e.itemSource); return ListSharingItem(e, file); } catch (_) { _log.warning("[_query] File not found: ${e.itemSource}"); return null; } }); return (await Future.wait(futures)).whereType().toList(); } late final _shareRemovedListener = AppEventListener(_onShareRemovedEvent); static final _log = Logger("bloc.list_share.ListSharingBloc"); }