nc-photos/lib/bloc/list_sharing.dart

430 lines
13 KiB
Dart
Raw Normal View History

2021-10-06 22:32:36 +02:00
import 'package:bloc/bloc.dart';
import 'package:kiwi/kiwi.dart';
import 'package:logging/logging.dart';
import 'package:nc_photos/account.dart';
2021-11-01 10:50:13 +01:00
import 'package:nc_photos/app_db.dart';
2021-10-12 12:15:58 +02:00
import 'package:nc_photos/entity/album.dart';
2021-10-06 22:32:36 +02:00
import 'package:nc_photos/entity/file.dart';
import 'package:nc_photos/entity/file/data_source.dart';
2021-10-06 22:32:36 +02:00
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';
2021-10-25 12:44:43 +02:00
import 'package:nc_photos/iterable_extension.dart';
2021-10-08 20:40:15 +02:00
import 'package:nc_photos/remote_storage_util.dart' as remote_storage_util;
2021-10-25 12:44:43 +02:00
import 'package:nc_photos/throttler.dart';
2021-10-06 22:32:36 +02:00
import 'package:nc_photos/use_case/find_file.dart';
import 'package:nc_photos/use_case/list_share_with_me.dart';
import 'package:nc_photos/use_case/ls.dart';
import 'package:nc_photos/use_case/ls_single_file.dart';
2021-10-12 12:15:58 +02:00
import 'package:path/path.dart' as path;
2021-10-06 22:32:36 +02:00
2021-10-12 12:15:58 +02:00
abstract class ListSharingItem {
const ListSharingItem(this.share);
2021-10-06 22:32:36 +02:00
final Share share;
2021-10-12 12:15:58 +02:00
}
class ListSharingFile extends ListSharingItem {
const ListSharingFile(Share share, this.file) : super(share);
2021-10-06 22:32:36 +02:00
final File file;
}
2021-10-12 12:15:58 +02:00
class ListSharingAlbum extends ListSharingItem {
const ListSharingAlbum(Share share, this.album) : super(share);
final Album album;
}
2021-10-06 22:32:36 +02:00
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 {
2021-10-25 12:44:43 +02:00
const _ListSharingBlocShareRemoved(this.shares);
2021-10-06 22:32:36 +02:00
@override
toString() {
return "$runtimeType {"
2021-10-25 12:44:43 +02:00
"shares: ${shares.toReadableString()}, "
2021-10-06 22:32:36 +02:00
"}";
}
2021-10-25 12:44:43 +02:00
final List<Share> shares;
2021-10-06 22:32:36 +02:00
}
class _ListSharingBlocPendingSharedAlbumMoved extends ListSharingBlocEvent {
const _ListSharingBlocPendingSharedAlbumMoved(
this.account, this.file, this.destination);
@override
toString() {
return "$runtimeType {"
"account: $account, "
"file: $file, "
"destination: $destination, "
"}";
}
final Account account;
final File file;
final String destination;
}
2021-10-06 22:32:36 +02:00
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<ListSharingItem> items;
}
class ListSharingBlocInit extends ListSharingBlocState {
ListSharingBlocInit() : super(null, const []);
}
class ListSharingBlocLoading extends ListSharingBlocState {
const ListSharingBlocLoading(Account? account, List<ListSharingItem> items)
: super(account, items);
}
class ListSharingBlocSuccess extends ListSharingBlocState {
const ListSharingBlocSuccess(Account? account, List<ListSharingItem> items)
: super(account, items);
ListSharingBlocSuccess copyWith({
Account? account,
List<ListSharingItem>? items,
}) =>
ListSharingBlocSuccess(
account ?? this.account,
items ?? this.items,
);
}
class ListSharingBlocFailure extends ListSharingBlocState {
const ListSharingBlocFailure(
Account? account, List<ListSharingItem> items, this.exception)
: super(account, items);
@override
toString() {
return "$runtimeType {"
"super: ${super.toString()}, "
"exception: $exception, "
"}";
}
ListSharingBlocFailure copyWith({
Account? account,
List<ListSharingItem>? items,
dynamic exception,
}) =>
ListSharingBlocFailure(
account ?? this.account,
items ?? this.items,
exception ?? this.exception,
);
final dynamic exception;
}
2021-10-17 11:46:23 +02:00
/// List shares to be shown in [SharingBrowser]
2021-10-06 22:32:36 +02:00
class ListSharingBloc extends Bloc<ListSharingBlocEvent, ListSharingBlocState> {
ListSharingBloc() : super(ListSharingBlocInit()) {
_shareRemovedListener.begin();
_fileMovedEventListener.begin();
2021-10-25 12:44:43 +02:00
_refreshThrottler = Throttler<Share>(
onTriggered: (shares) {
add(_ListSharingBlocShareRemoved(shares));
},
logTag: "ListSharingBloc.refresh",
);
2021-10-06 22:32:36 +02:00
}
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>("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<ListSharingBloc>(bloc,
name: "ListSharingBloc($id)");
return bloc;
}
}
2021-11-22 07:58:06 +01:00
@override
close() {
_shareRemovedListener.end();
_fileMovedEventListener.end();
2021-11-22 07:58:06 +01:00
_refreshThrottler.clear();
return super.close();
}
2021-10-06 22:32:36 +02:00
@override
mapEventToState(ListSharingBlocEvent event) async* {
_log.info("[mapEventToState] $event");
if (event is ListSharingBlocQuery) {
yield* _onEventQuery(event);
} else if (event is _ListSharingBlocShareRemoved) {
yield* _onEventShareRemoved(event);
} else if (event is _ListSharingBlocPendingSharedAlbumMoved) {
yield* _onEventPendingSharedAlbumMoved(event);
2021-10-06 22:32:36 +02:00
}
}
Stream<ListSharingBlocState> _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<ListSharingBlocState> _onEventShareRemoved(
_ListSharingBlocShareRemoved ev) async* {
if (state is! ListSharingBlocSuccess && state is! ListSharingBlocFailure) {
return;
}
2021-11-22 09:35:32 +01:00
final newItems = state.items
2021-10-25 12:44:43 +02:00
.where((element) => !ev.shares.containsIdentical(element.share))
2021-10-06 22:32:36 +02:00
.toList();
// i love hacks :)
yield (state as dynamic).copyWith(
items: newItems,
) as ListSharingBlocState;
}
Stream<ListSharingBlocState> _onEventPendingSharedAlbumMoved(
_ListSharingBlocPendingSharedAlbumMoved ev) async* {
if (state.items.isEmpty) {
return;
}
try {
yield ListSharingBlocLoading(ev.account, state.items);
final items = List.of(state.items);
items.removeWhere(
(i) => i is ListSharingAlbum && i.share.path == ev.file.strippedPath);
final fileRepo = FileRepo(FileCachedDataSource(AppDb()));
final albumRepo = AlbumRepo(AlbumCachedDataSource(AppDb()));
final shareRepo = ShareRepo(ShareRemoteDataSource());
final newShares = await ListShareWithMe(shareRepo)(
ev.account, File(path: ev.destination));
final newAlbumFile =
await LsSingleFile(fileRepo)(ev.account, ev.destination);
final newAlbum = await albumRepo.get(ev.account, newAlbumFile);
for (final s in newShares) {
items.add(ListSharingAlbum(s, newAlbum));
}
yield ListSharingBlocSuccess(ev.account, items);
} catch (e, stackTrace) {
_log.severe("[_onEventPendingSharedAlbumMoved] Exception while request",
e, stackTrace);
yield ListSharingBlocFailure(ev.account, state.items, e);
}
}
2021-10-06 22:32:36 +02:00
void _onShareRemovedEvent(ShareRemovedEvent ev) {
if (_isAccountOfInterest(ev.account)) {
_refreshThrottler.trigger(
maxResponceTime: const Duration(seconds: 3),
maxPendingCount: 10,
data: ev.share,
);
}
2021-10-06 22:32:36 +02:00
}
void _onFileMovedEvent(FileMovedEvent ev) {
if (state is ListSharingBlocInit) {
// no data in this bloc, ignore
return;
}
// from pending dir to album dir
if (_isAccountOfInterest(ev.account) &&
ev.file.path.startsWith(
remote_storage_util.getRemotePendingSharedAlbumsDir(ev.account)) &&
ev.destination
.startsWith(remote_storage_util.getRemoteAlbumsDir(ev.account))) {
add(_ListSharingBlocPendingSharedAlbumMoved(
ev.account, ev.file, ev.destination));
}
}
2021-10-06 22:32:36 +02:00
Future<List<ListSharingItem>> _query(ListSharingBlocQuery ev) async {
2021-11-01 10:50:13 +01:00
final fileRepo = FileRepo(FileCachedDataSource(AppDb()));
final sharedAlbumFiles = await Ls(fileRepo)(
ev.account,
File(
path: remote_storage_util.getRemoteAlbumsDir(ev.account),
));
2021-10-11 19:03:37 +02:00
return (await Future.wait([
2021-10-18 22:27:30 +02:00
_querySharesByMe(ev, sharedAlbumFiles),
_querySharesWithMe(ev, sharedAlbumFiles),
2021-10-11 19:03:37 +02:00
]))
.reduce((value, element) => value + element);
}
Future<List<ListSharingItem>> _querySharesByMe(
2021-10-18 22:27:30 +02:00
ListSharingBlocQuery ev, List<File> sharedAlbumFiles) async {
2021-10-06 22:32:36 +02:00
final shareRepo = ShareRepo(ShareRemoteDataSource());
final shares = await shareRepo.listAll(ev.account);
2021-10-18 22:27:30 +02:00
final futures = shares.map((s) async {
final webdavPath = file_util.unstripPath(ev.account, s.path);
2021-10-08 20:40:15 +02:00
// include link share dirs
2021-10-18 22:27:30 +02:00
if (s.itemType == ShareItemType.folder) {
2021-10-08 20:40:15 +02:00
if (webdavPath.startsWith(
remote_storage_util.getRemoteLinkSharesDir(ev.account))) {
2021-10-12 12:15:58 +02:00
return ListSharingFile(
2021-10-18 22:27:30 +02:00
s,
2021-10-08 20:40:15 +02:00
File(
path: webdavPath,
isCollection: true,
),
);
}
}
2021-10-18 22:27:30 +02:00
// include shared albums
if (path.dirname(webdavPath) ==
remote_storage_util.getRemoteAlbumsDir(ev.account)) {
try {
final file = sharedAlbumFiles
.firstWhere((element) => element.fileId == s.itemSource);
return await _querySharedAlbum(ev, s, file);
} catch (e, stackTrace) {
_log.severe(
"[_querySharesWithMe] Shared album not found: ${s.itemSource}",
e,
stackTrace);
return null;
}
}
2021-10-08 20:40:15 +02:00
2021-10-18 22:27:30 +02:00
if (!file_util.isSupportedMime(s.mimeType)) {
2021-10-06 22:32:36 +02:00
return null;
}
2021-10-18 22:16:26 +02:00
// show only link shares
if (s.url == null) {
return null;
}
2021-10-06 22:32:36 +02:00
if (ev.account.roots
2021-10-18 22:27:30 +02:00
.every((r) => r.isNotEmpty && !s.path.startsWith("/$r/"))) {
2021-10-06 22:32:36 +02:00
// ignore files not under root dirs
return null;
}
try {
2021-11-01 10:50:13 +01:00
final file = await FindFile(AppDb())(ev.account, s.itemSource);
2021-10-18 22:27:30 +02:00
return ListSharingFile(s, file);
2021-10-18 22:27:36 +02:00
} catch (e, stackTrace) {
_log.severe("[_querySharesByMe] File not found: ${s.itemSource}", e,
stackTrace);
2021-10-11 19:03:37 +02:00
return null;
}
});
return (await Future.wait(futures)).whereType<ListSharingItem>().toList();
}
Future<List<ListSharingItem>> _querySharesWithMe(
ListSharingBlocQuery ev, List<File> sharedAlbumFiles) async {
2021-11-01 10:50:13 +01:00
final fileRepo = FileRepo(FileCachedDataSource(AppDb()));
final pendingSharedAlbumFiles = await Ls(fileRepo)(
ev.account,
File(
path: remote_storage_util.getRemotePendingSharedAlbumsDir(ev.account),
));
2021-10-11 19:03:37 +02:00
final shareRepo = ShareRepo(ShareRemoteDataSource());
final shares = await shareRepo.reverseListAll(ev.account);
final futures = shares.map((s) async {
final webdavPath = file_util.unstripPath(ev.account, s.path);
// include pending shared albums
2021-10-12 12:15:58 +02:00
if (path.dirname(webdavPath) ==
remote_storage_util.getRemotePendingSharedAlbumsDir(ev.account)) {
try {
final file = pendingSharedAlbumFiles
.firstWhere((element) => element.fileId == s.itemSource);
return await _querySharedAlbum(ev, s, file);
} catch (e, stackTrace) {
_log.severe(
"[_querySharesWithMe] Pending shared album not found: ${s.itemSource}",
e,
stackTrace);
return null;
}
2021-10-12 12:15:58 +02:00
}
// include shared albums
2021-10-12 12:15:58 +02:00
if (path.dirname(webdavPath) ==
remote_storage_util.getRemoteAlbumsDir(ev.account)) {
try {
final file = sharedAlbumFiles
.firstWhere((element) => element.fileId == s.itemSource);
return await _querySharedAlbum(ev, s, file);
} catch (e, stackTrace) {
_log.severe(
"[_querySharesWithMe] Shared album not found: ${s.itemSource}",
e,
stackTrace);
return null;
}
2021-10-12 12:15:58 +02:00
}
2021-10-06 22:32:36 +02:00
});
return (await Future.wait(futures)).whereType<ListSharingItem>().toList();
}
2021-10-12 12:15:58 +02:00
Future<ListSharingItem?> _querySharedAlbum(
ListSharingBlocQuery ev, Share share, File albumFile) async {
2021-10-12 12:15:58 +02:00
try {
2021-11-01 10:50:13 +01:00
final albumRepo = AlbumRepo(AlbumCachedDataSource(AppDb()));
final album = await albumRepo.get(ev.account, albumFile);
return ListSharingAlbum(share, album);
2021-10-12 12:15:58 +02:00
} catch (e, stackTrace) {
_log.shout(
"[_querySharedAlbum] Failed while getting album", e, stackTrace);
return null;
}
}
bool _isAccountOfInterest(Account account) =>
state.account == null || state.account!.compareServerIdentity(account);
2021-10-06 22:32:36 +02:00
late final _shareRemovedListener =
AppEventListener<ShareRemovedEvent>(_onShareRemovedEvent);
late final _fileMovedEventListener =
AppEventListener<FileMovedEvent>(_onFileMovedEvent);
2021-10-06 22:32:36 +02:00
2021-10-25 12:44:43 +02:00
late Throttler _refreshThrottler;
2021-10-12 11:02:57 +02:00
static final _log = Logger("bloc.list_sharing.ListSharingBloc");
2021-10-06 22:32:36 +02:00
}