mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-01-23 17:26:18 +01:00
329 lines
9.6 KiB
Dart
329 lines
9.6 KiB
Dart
import 'package:bloc/bloc.dart';
|
|
import 'package:equatable/equatable.dart';
|
|
import 'package:logging/logging.dart';
|
|
import 'package:nc_photos/account.dart';
|
|
import 'package:nc_photos/ci_string.dart';
|
|
import 'package:nc_photos/debug_util.dart';
|
|
import 'package:nc_photos/entity/album.dart';
|
|
import 'package:nc_photos/entity/album/item.dart';
|
|
import 'package:nc_photos/entity/album/provider.dart';
|
|
import 'package:nc_photos/entity/file.dart';
|
|
import 'package:nc_photos/entity/share.dart';
|
|
import 'package:nc_photos/entity/sharee.dart';
|
|
import 'package:nc_photos/iterable_extension.dart';
|
|
import 'package:nc_photos/object_extension.dart';
|
|
import 'package:nc_photos/use_case/list_share.dart';
|
|
import 'package:nc_photos/use_case/list_sharee.dart';
|
|
|
|
class ListAlbumShareOutlierItem with EquatableMixin {
|
|
const ListAlbumShareOutlierItem(this.file, this.shareItems);
|
|
|
|
@override
|
|
toString() {
|
|
return "$runtimeType {"
|
|
"file: '${file.path}', "
|
|
"shareItems: ${shareItems.toReadableString()}, "
|
|
"}";
|
|
}
|
|
|
|
@override
|
|
get props => [
|
|
file,
|
|
shareItems,
|
|
];
|
|
|
|
final File file;
|
|
final List<ListAlbumShareOutlierShareItem> shareItems;
|
|
}
|
|
|
|
abstract class ListAlbumShareOutlierShareItem with EquatableMixin {
|
|
const ListAlbumShareOutlierShareItem();
|
|
}
|
|
|
|
class ListAlbumShareOutlierExtraShareItem
|
|
extends ListAlbumShareOutlierShareItem {
|
|
const ListAlbumShareOutlierExtraShareItem(this.share);
|
|
|
|
@override
|
|
toString() {
|
|
return "$runtimeType {"
|
|
"share: $share, "
|
|
"}";
|
|
}
|
|
|
|
@override
|
|
get props => [
|
|
share,
|
|
];
|
|
|
|
final Share share;
|
|
}
|
|
|
|
class ListAlbumShareOutlierMissingShareItem
|
|
extends ListAlbumShareOutlierShareItem {
|
|
const ListAlbumShareOutlierMissingShareItem(
|
|
this.shareWith, this.shareWithDisplayName);
|
|
|
|
@override
|
|
toString() {
|
|
return "$runtimeType {"
|
|
"shareWith: $shareWith, "
|
|
"shareWithDisplayName: $shareWithDisplayName, "
|
|
"}";
|
|
}
|
|
|
|
@override
|
|
get props => [
|
|
shareWith,
|
|
shareWithDisplayName,
|
|
];
|
|
|
|
final CiString shareWith;
|
|
final String? shareWithDisplayName;
|
|
}
|
|
|
|
abstract class ListAlbumShareOutlierBlocEvent {
|
|
const ListAlbumShareOutlierBlocEvent();
|
|
}
|
|
|
|
class ListAlbumShareOutlierBlocQuery extends ListAlbumShareOutlierBlocEvent {
|
|
const ListAlbumShareOutlierBlocQuery(this.account, this.album);
|
|
|
|
@override
|
|
toString() {
|
|
return "$runtimeType {"
|
|
"account: $account, "
|
|
"album: $album, "
|
|
"}";
|
|
}
|
|
|
|
final Account account;
|
|
final Album album;
|
|
}
|
|
|
|
abstract class ListAlbumShareOutlierBlocState with EquatableMixin {
|
|
const ListAlbumShareOutlierBlocState(this.account, this.items);
|
|
|
|
@override
|
|
toString() {
|
|
return "$runtimeType {"
|
|
"account: $account, "
|
|
"items: ${items.toReadableString()}, "
|
|
"}";
|
|
}
|
|
|
|
@override
|
|
get props => [
|
|
account,
|
|
items,
|
|
];
|
|
|
|
final Account? account;
|
|
final List<ListAlbumShareOutlierItem> items;
|
|
}
|
|
|
|
class ListAlbumShareOutlierBlocInit extends ListAlbumShareOutlierBlocState {
|
|
ListAlbumShareOutlierBlocInit() : super(null, const []);
|
|
}
|
|
|
|
class ListAlbumShareOutlierBlocLoading extends ListAlbumShareOutlierBlocState {
|
|
const ListAlbumShareOutlierBlocLoading(
|
|
Account? account, List<ListAlbumShareOutlierItem> items)
|
|
: super(account, items);
|
|
}
|
|
|
|
class ListAlbumShareOutlierBlocSuccess extends ListAlbumShareOutlierBlocState {
|
|
const ListAlbumShareOutlierBlocSuccess(
|
|
Account? account, List<ListAlbumShareOutlierItem> items)
|
|
: super(account, items);
|
|
}
|
|
|
|
class ListAlbumShareOutlierBlocFailure extends ListAlbumShareOutlierBlocState {
|
|
const ListAlbumShareOutlierBlocFailure(
|
|
Account? account, List<ListAlbumShareOutlierItem> items, this.exception)
|
|
: super(account, items);
|
|
|
|
@override
|
|
toString() {
|
|
return "$runtimeType {"
|
|
"super: ${super.toString()}, "
|
|
"exception: $exception, "
|
|
"}";
|
|
}
|
|
|
|
@override
|
|
get props => [
|
|
...super.props,
|
|
exception,
|
|
];
|
|
|
|
final dynamic exception;
|
|
}
|
|
|
|
/// List the outliers in a shared album
|
|
///
|
|
/// An outlier is a file where its shares are different to the album's that it
|
|
/// belongs, e.g., an unshared item in a shared album, or vice versa
|
|
class ListAlbumShareOutlierBloc extends Bloc<ListAlbumShareOutlierBlocEvent,
|
|
ListAlbumShareOutlierBlocState> {
|
|
ListAlbumShareOutlierBloc(this.shareRepo, this.shareeRepo)
|
|
: super(ListAlbumShareOutlierBlocInit());
|
|
|
|
@override
|
|
mapEventToState(ListAlbumShareOutlierBlocEvent event) async* {
|
|
_log.info("[mapEventToState] $event");
|
|
if (event is ListAlbumShareOutlierBlocQuery) {
|
|
yield* _onEventQuery(event);
|
|
}
|
|
}
|
|
|
|
Stream<ListAlbumShareOutlierBlocState> _onEventQuery(
|
|
ListAlbumShareOutlierBlocQuery ev) async* {
|
|
try {
|
|
assert(ev.album.provider is AlbumStaticProvider);
|
|
yield ListAlbumShareOutlierBlocLoading(ev.account, state.items);
|
|
|
|
final albumShares = await () async {
|
|
var temp = (ev.album.shares ?? [])
|
|
.where((s) => s.userId != ev.account.username)
|
|
.toList();
|
|
if (ev.album.albumFile!.ownerId != ev.account.username) {
|
|
// add owner if the album is not owned by this account
|
|
final ownerSharee = (await ListSharee(shareeRepo)(ev.account))
|
|
.firstWhere((s) => s.shareWith == ev.album.albumFile!.ownerId);
|
|
temp.add(AlbumShare(
|
|
userId: ownerSharee.shareWith,
|
|
displayName: ownerSharee.shareWithDisplayNameUnique,
|
|
));
|
|
}
|
|
return Map.fromEntries(temp.map((as) => MapEntry(as.userId, as)));
|
|
}();
|
|
final albumSharees = albumShares.values.map((s) => s.userId).toSet();
|
|
|
|
final products = <ListAlbumShareOutlierItem>[];
|
|
final errors = <Object>[];
|
|
products.addAll(await _processAlbumFile(
|
|
ev.account, ev.album, albumShares, albumSharees, errors));
|
|
products.addAll(await _processAlbumItems(
|
|
ev.account, ev.album, albumShares, albumSharees, errors));
|
|
|
|
if (errors.isEmpty) {
|
|
yield ListAlbumShareOutlierBlocSuccess(ev.account, products);
|
|
} else {
|
|
yield ListAlbumShareOutlierBlocFailure(
|
|
ev.account, products, errors.first);
|
|
}
|
|
} catch (e, stackTrace) {
|
|
_log.severe("[_onEventQuery] Exception while request", e, stackTrace);
|
|
yield ListAlbumShareOutlierBlocFailure(ev.account, state.items, e);
|
|
}
|
|
}
|
|
|
|
Future<List<ListAlbumShareOutlierItem>> _processAlbumFile(
|
|
Account account,
|
|
Album album,
|
|
Map<CiString, AlbumShare> albumShares,
|
|
Set<CiString> albumSharees,
|
|
List<Object> errors,
|
|
) async {
|
|
try {
|
|
final item = await _processSingleFile(
|
|
account, album.albumFile!, albumShares, albumSharees, errors);
|
|
return item == null ? [] : [item];
|
|
} catch (e, stackTrace) {
|
|
_log.severe(
|
|
"[_processAlbumFile] Failed while _processSingleFile: ${logFilename(album.albumFile?.path)}",
|
|
e,
|
|
stackTrace);
|
|
errors.add(e);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
Future<List<ListAlbumShareOutlierItem>> _processAlbumItems(
|
|
Account account,
|
|
Album album,
|
|
Map<CiString, AlbumShare> albumShares,
|
|
Set<CiString> albumSharees,
|
|
List<Object> errors,
|
|
) async {
|
|
final products = <ListAlbumShareOutlierItem>[];
|
|
final files = AlbumStaticProvider.of(album)
|
|
.items
|
|
.whereType<AlbumFileItem>()
|
|
.map((e) => e.file)
|
|
.toList();
|
|
for (final f in files) {
|
|
try {
|
|
(await _processSingleFile(
|
|
account, f, albumShares, albumSharees, errors))
|
|
?.apply((item) {
|
|
products.add(item);
|
|
});
|
|
} catch (e, stackTrace) {
|
|
_log.severe(
|
|
"[_processAlbumItems] Failed while _processSingleFile: ${logFilename(f.path)}",
|
|
e,
|
|
stackTrace);
|
|
errors.add(e);
|
|
}
|
|
}
|
|
return products;
|
|
}
|
|
|
|
Future<ListAlbumShareOutlierItem?> _processSingleFile(
|
|
Account account,
|
|
File file,
|
|
Map<CiString, AlbumShare> albumShares,
|
|
Set<CiString> albumSharees,
|
|
List<Object> errors,
|
|
) async {
|
|
final shareItems = <ListAlbumShareOutlierShareItem>[];
|
|
final shares = (await ListShare(shareRepo)(account, file))
|
|
.where((element) => element.shareType == ShareType.user)
|
|
.toList();
|
|
final sharees = shares.map((s) => s.shareWith!).toSet();
|
|
final missings = albumSharees.difference(sharees);
|
|
_log.info(
|
|
"Missing shares: ${missings.toReadableString()} for file: ${logFilename(file.path)}");
|
|
for (final m in missings) {
|
|
try {
|
|
final as = albumShares[m]!;
|
|
shareItems.add(
|
|
ListAlbumShareOutlierMissingShareItem(as.userId, as.displayName));
|
|
} catch (e, stackTrace) {
|
|
_log.severe(
|
|
"[_processSingleFile] Failed while processing missing share for file: ${logFilename(file.path)}",
|
|
e,
|
|
stackTrace);
|
|
errors.add(e);
|
|
}
|
|
}
|
|
final extras = sharees.difference(albumSharees);
|
|
_log.info(
|
|
"Extra shares: ${extras.toReadableString()} for file: ${logFilename(file.path)}");
|
|
for (final e in extras) {
|
|
try {
|
|
shareItems.add(ListAlbumShareOutlierExtraShareItem(
|
|
shares.firstWhere((s) => s.shareWith == e)));
|
|
} catch (e, stackTrace) {
|
|
_log.severe(
|
|
"[_processSingleFile] Failed while processing extra share for file: ${logFilename(file.path)}",
|
|
e,
|
|
stackTrace);
|
|
errors.add(e);
|
|
}
|
|
}
|
|
if (shareItems.isNotEmpty) {
|
|
return ListAlbumShareOutlierItem(file, shareItems);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
final ShareRepo shareRepo;
|
|
final ShareeRepo shareeRepo;
|
|
|
|
static final _log =
|
|
Logger("bloc.list_album_share_outlier.ListAlbumShareOutlierBloc");
|
|
}
|