mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-02-02 06:46:22 +01:00
Overhaul Remove to handle shared album properly
This commit is contained in:
parent
7ff21fa66e
commit
962e7d1e17
14 changed files with 588 additions and 254 deletions
|
@ -500,6 +500,26 @@ extension FileExtension on File {
|
|||
static final _log = Logger("entity.file.FileExtension");
|
||||
}
|
||||
|
||||
class FileServerIdentityComparator {
|
||||
const FileServerIdentityComparator(this.file);
|
||||
|
||||
@override
|
||||
operator ==(Object other) {
|
||||
if (other is FileServerIdentityComparator) {
|
||||
return file.compareServerIdentity(other.file);
|
||||
} else if (other is File) {
|
||||
return file.compareServerIdentity(other);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
get hashCode => file.fileId?.hashCode ?? file.path.hashCode;
|
||||
|
||||
final File file;
|
||||
}
|
||||
|
||||
class FileRepo {
|
||||
const FileRepo(this.dataSrc);
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ class TouchTokenManager {
|
|||
"[setRemoteToken] Set remote token for file '${file.path}': $token");
|
||||
final path = _getRemotePath(account, file);
|
||||
if (token == null) {
|
||||
return Remove(fileRepo, null)(account, file);
|
||||
return Remove(fileRepo, null, null, null, null)(account, [file]);
|
||||
} else {
|
||||
return PutFileBinary(fileRepo)(
|
||||
account, path, const Utf8Encoder().convert(token),
|
||||
|
|
|
@ -2,116 +2,127 @@ import 'package:event_bus/event_bus.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/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/file_util.dart' as file_util;
|
||||
import 'package:nc_photos/entity/share.dart';
|
||||
import 'package:nc_photos/event/event.dart';
|
||||
import 'package:nc_photos/iterable_extension.dart';
|
||||
import 'package:nc_photos/throttler.dart';
|
||||
import 'package:nc_photos/pref.dart';
|
||||
import 'package:nc_photos/use_case/find_file.dart';
|
||||
import 'package:nc_photos/use_case/list_album.dart';
|
||||
import 'package:nc_photos/use_case/update_album.dart';
|
||||
import 'package:nc_photos/use_case/list_share.dart';
|
||||
import 'package:nc_photos/use_case/remove_from_album.dart';
|
||||
import 'package:nc_photos/use_case/remove_share.dart';
|
||||
|
||||
class Remove {
|
||||
Remove(this.fileRepo, this.albumRepo);
|
||||
const Remove(
|
||||
this.fileRepo, this.albumRepo, this.shareRepo, this.appDb, this.pref)
|
||||
: assert(albumRepo == null ||
|
||||
(shareRepo != null && appDb != null && pref != null));
|
||||
|
||||
/// Remove a file
|
||||
Future<void> call(Account account, File file) async {
|
||||
await fileRepo.remove(account, file);
|
||||
if (albumRepo != null) {
|
||||
_log.info("[call] Skip albums cleanup as albumRepo == null");
|
||||
_CleanUpAlbums()(_CleanUpAlbumsData(fileRepo, albumRepo!, account, file));
|
||||
/// Remove files
|
||||
Future<void> call(
|
||||
Account account,
|
||||
List<File> files, {
|
||||
void Function(File file, Object error, StackTrace stackTrace)?
|
||||
onRemoveFileFailed,
|
||||
}) async {
|
||||
// need to cleanup first, otherwise we can't unshare the files
|
||||
if (albumRepo == null) {
|
||||
_log.info("[call] Skip album cleanup as albumRepo == null");
|
||||
} else {
|
||||
await _cleanUpAlbums(account, files);
|
||||
}
|
||||
KiwiContainer().resolve<EventBus>().fire(FileRemovedEvent(account, file));
|
||||
}
|
||||
|
||||
final FileRepo fileRepo;
|
||||
final AlbumRepo? albumRepo;
|
||||
|
||||
static final _log = Logger("use_case.remove.Remove");
|
||||
}
|
||||
|
||||
class _CleanUpAlbumsData {
|
||||
_CleanUpAlbumsData(this.fileRepo, this.albumRepo, this.account, this.file);
|
||||
|
||||
final FileRepo fileRepo;
|
||||
final AlbumRepo albumRepo;
|
||||
final Account account;
|
||||
final File file;
|
||||
}
|
||||
|
||||
class _CleanUpAlbums {
|
||||
factory _CleanUpAlbums() {
|
||||
_inst ??= _CleanUpAlbums._();
|
||||
return _inst!;
|
||||
}
|
||||
|
||||
_CleanUpAlbums._() {
|
||||
_throttler = Throttler<_CleanUpAlbumsData>(
|
||||
onTriggered: (data) {
|
||||
_onTriggered(data);
|
||||
},
|
||||
logTag: "remove._CleanUpAlbums",
|
||||
);
|
||||
}
|
||||
|
||||
void call(_CleanUpAlbumsData data) {
|
||||
_throttler.trigger(
|
||||
maxResponceTime: const Duration(seconds: 3),
|
||||
maxPendingCount: 10,
|
||||
data: data,
|
||||
);
|
||||
}
|
||||
|
||||
void _onTriggered(List<_CleanUpAlbumsData> data) async {
|
||||
for (final pair in data.groupBy(key: (e) => e.account)) {
|
||||
final list = pair.item2;
|
||||
await _cleanUp(list.first.fileRepo, list.first.albumRepo,
|
||||
list.first.account, list.map((e) => e.file).toList());
|
||||
for (final f in files) {
|
||||
try {
|
||||
await fileRepo.remove(account, f);
|
||||
KiwiContainer().resolve<EventBus>().fire(FileRemovedEvent(account, f));
|
||||
} catch (e, stackTrace) {
|
||||
_log.severe("[call] Failed while remove: ${logFilename(f.path)}", e,
|
||||
stackTrace);
|
||||
onRemoveFileFailed?.call(f, e, stackTrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Clean up for a single account
|
||||
Future<void> _cleanUp(FileRepo fileRepo, AlbumRepo albumRepo, Account account,
|
||||
List<File> removes) async {
|
||||
final albums = (await ListAlbum(fileRepo, albumRepo)(account)
|
||||
.where((event) => event is Album)
|
||||
.toList())
|
||||
.cast<Album>();
|
||||
Future<void> _cleanUpAlbums(Account account, List<File> removes) async {
|
||||
final albums = await ListAlbum(fileRepo, albumRepo!)(account)
|
||||
.where((event) => event is Album)
|
||||
.cast<Album>()
|
||||
.toList();
|
||||
// figure out which files need to be unshared with whom
|
||||
final unshares = <FileServerIdentityComparator, Set<CiString>>{};
|
||||
// clean up only make sense for static albums
|
||||
for (final a
|
||||
in albums.where((element) => element.provider is AlbumStaticProvider)) {
|
||||
for (final a in albums.where((a) => a.provider is AlbumStaticProvider)) {
|
||||
try {
|
||||
final provider = AlbumStaticProvider.of(a);
|
||||
if (provider.items.whereType<AlbumFileItem>().any((element) =>
|
||||
removes.containsIf(element.file, (a, b) => a.path == b.path))) {
|
||||
final newItems = provider.items.where((element) {
|
||||
if (element is AlbumFileItem) {
|
||||
return !removes.containsIf(
|
||||
element.file, (a, b) => a.path == b.path);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}).toList();
|
||||
await UpdateAlbum(albumRepo)(
|
||||
account,
|
||||
a.copyWith(
|
||||
provider: AlbumStaticProvider.of(a).copyWith(
|
||||
items: newItems,
|
||||
),
|
||||
));
|
||||
final itemsToRemove = provider.items
|
||||
.whereType<AlbumFileItem>()
|
||||
.where((i) =>
|
||||
(i.file.isOwned(account.username) ||
|
||||
i.addedBy == account.username) &&
|
||||
removes.any((r) => r.compareServerIdentity(i.file)))
|
||||
.toList();
|
||||
if (itemsToRemove.isEmpty) {
|
||||
continue;
|
||||
}
|
||||
for (final i in itemsToRemove) {
|
||||
final key = FileServerIdentityComparator(i.file);
|
||||
final value = (a.shares?.map((s) => s.userId).toList() ?? [])
|
||||
..add(a.albumFile!.ownerId!)
|
||||
..remove(account.username);
|
||||
(unshares[key] ??= <CiString>{}).addAll(value);
|
||||
}
|
||||
_log.fine(
|
||||
"[_cleanUpAlbums] Removing from album '${a.name}': ${itemsToRemove.map((e) => e.file.path).toReadableString()}");
|
||||
// skip unsharing as we'll handle it ourselves
|
||||
await RemoveFromAlbum(albumRepo!, null, null, appDb!)(
|
||||
account, a, itemsToRemove);
|
||||
} catch (e, stacktrace) {
|
||||
_log.shout(
|
||||
"[_cleanUpAlbums] Failed while updating album", e, stacktrace);
|
||||
// continue to next album
|
||||
}
|
||||
}
|
||||
|
||||
for (final e in unshares.entries) {
|
||||
try {
|
||||
var file = e.key.file;
|
||||
if (file_util.getUserDirName(file) != account.username) {
|
||||
try {
|
||||
file = await FindFile(appDb!)(account, file.fileId!);
|
||||
} catch (_) {
|
||||
// file not found
|
||||
_log.warning(
|
||||
"[_cleanUpAlbums] File not found in db: ${logFilename(file.path)}");
|
||||
}
|
||||
}
|
||||
final shares = await ListShare(shareRepo!)(account, file);
|
||||
for (final s in shares.where((s) => e.value.contains(s.shareWith))) {
|
||||
try {
|
||||
await RemoveShare(shareRepo!)(account, s);
|
||||
} catch (e, stackTrace) {
|
||||
_log.severe(
|
||||
"[_cleanUpAlbums] Failed while RemoveShare: $s", e, stackTrace);
|
||||
}
|
||||
}
|
||||
} catch (e, stackTrace) {
|
||||
_log.shout("[_cleanUpAlbums] Failed", e, stackTrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
late final Throttler<_CleanUpAlbumsData> _throttler;
|
||||
final FileRepo fileRepo;
|
||||
final AlbumRepo? albumRepo;
|
||||
final ShareRepo? shareRepo;
|
||||
final AppDb? appDb;
|
||||
final Pref? pref;
|
||||
|
||||
static final _log = Logger("use_case.remove");
|
||||
|
||||
static _CleanUpAlbums? _inst;
|
||||
static final _log = Logger("use_case.remove.Remove");
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ class RemoveAlbum {
|
|||
}
|
||||
// you can't add an album to another album, so passing null here can save
|
||||
// a few queries
|
||||
await Remove(fileRepo, null)(account, album.albumFile!);
|
||||
await Remove(fileRepo, null, null, null, null)(account, [album.albumFile!]);
|
||||
}
|
||||
|
||||
Future<void> _unshareFiles(Account account, Album album) async {
|
||||
|
|
|
@ -14,8 +14,13 @@ import 'package:nc_photos/use_case/update_album.dart';
|
|||
import 'package:nc_photos/use_case/update_album_with_actual_items.dart';
|
||||
|
||||
class RemoveFromAlbum {
|
||||
/// Constructor
|
||||
///
|
||||
/// If [shareRepo] and [fileRepo] are null, files will not be unshared after
|
||||
/// removing from the album
|
||||
const RemoveFromAlbum(
|
||||
this.albumRepo, this.shareRepo, this.fileRepo, this.appDb);
|
||||
this.albumRepo, this.shareRepo, this.fileRepo, this.appDb)
|
||||
: assert(shareRepo == null || fileRepo != null);
|
||||
|
||||
/// Remove a list of AlbumItems from [album]
|
||||
///
|
||||
|
@ -37,11 +42,15 @@ class RemoveFromAlbum {
|
|||
newAlbum = await _fixAlbumPostRemove(account, newAlbum, items);
|
||||
await UpdateAlbum(albumRepo)(account, newAlbum);
|
||||
|
||||
if (album.shares?.isNotEmpty == true) {
|
||||
final removeFiles =
|
||||
items.whereType<AlbumFileItem>().map((e) => e.file).toList();
|
||||
if (removeFiles.isNotEmpty) {
|
||||
await _unshareFiles(account, newAlbum, removeFiles);
|
||||
if (shareRepo == null) {
|
||||
_log.info("[call] Skip unsharing files as shareRepo == null");
|
||||
} else {
|
||||
if (album.shares?.isNotEmpty == true) {
|
||||
final removeFiles =
|
||||
items.whereType<AlbumFileItem>().map((e) => e.file).toList();
|
||||
if (removeFiles.isNotEmpty) {
|
||||
await _unshareFiles(account, newAlbum, removeFiles);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -93,14 +102,14 @@ class RemoveFromAlbum {
|
|||
.where((element) => element != account.username)
|
||||
.toList();
|
||||
if (albumShares.isNotEmpty) {
|
||||
await UnshareFileFromAlbum(shareRepo, fileRepo, albumRepo)(
|
||||
await UnshareFileFromAlbum(shareRepo!, fileRepo!, albumRepo)(
|
||||
account, album, files, albumShares);
|
||||
}
|
||||
}
|
||||
|
||||
final AlbumRepo albumRepo;
|
||||
final ShareRepo shareRepo;
|
||||
final FileRepo fileRepo;
|
||||
final ShareRepo? shareRepo;
|
||||
final FileRepo? fileRepo;
|
||||
final AppDb appDb;
|
||||
|
||||
static final _log = Logger("use_case.remove_from_album.RemoveFromAlbum");
|
||||
|
|
|
@ -17,6 +17,8 @@ import 'package:nc_photos/entity/album/sort_provider.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/entity/share.dart';
|
||||
import 'package:nc_photos/entity/share/data_source.dart';
|
||||
import 'package:nc_photos/event/event.dart';
|
||||
import 'package:nc_photos/exception_util.dart' as exception_util;
|
||||
import 'package:nc_photos/iterable_extension.dart';
|
||||
|
@ -419,31 +421,30 @@ class _DynamicAlbumBrowserState extends State<DynamicAlbumBrowser>
|
|||
|
||||
final fileRepo = FileRepo(FileCachedDataSource(AppDb()));
|
||||
final albumRepo = AlbumRepo(AlbumCachedDataSource(AppDb()));
|
||||
final shareRepo = ShareRepo(ShareRemoteDataSource());
|
||||
final successes = <_FileListItem>[];
|
||||
final failures = <_FileListItem>[];
|
||||
for (final item in selected) {
|
||||
try {
|
||||
await Remove(fileRepo, albumRepo)(widget.account, item.file);
|
||||
successes.add(item);
|
||||
} catch (e, stacktrace) {
|
||||
_log.shout(
|
||||
"[_onSelectionDeletePressed] Failed while removing file" +
|
||||
(shouldLogFileName ? ": ${item.file.path}" : ""),
|
||||
e,
|
||||
stacktrace);
|
||||
failures.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
if (failures.isEmpty) {
|
||||
await Remove(fileRepo, albumRepo, shareRepo, AppDb(), Pref())(
|
||||
widget.account,
|
||||
selected.map((e) => e.file).toList(),
|
||||
onRemoveFileFailed: (file, e, stackTrace) {
|
||||
_log.shout(
|
||||
"[_onSelectionDeletePressed] Failed while removing file: ${logFilename(file.path)}",
|
||||
e,
|
||||
stackTrace);
|
||||
successes.removeWhere((item) => item.file.compareServerIdentity(file));
|
||||
},
|
||||
);
|
||||
|
||||
if (successes.length == selected.length) {
|
||||
SnackBarManager().showSnackBar(SnackBar(
|
||||
content: Text(L10n.global().deleteSelectedSuccessNotification),
|
||||
duration: k.snackBarDurationNormal,
|
||||
));
|
||||
} else {
|
||||
SnackBarManager().showSnackBar(SnackBar(
|
||||
content: Text(
|
||||
L10n.global().deleteSelectedFailureNotification(failures.length)),
|
||||
content: Text(L10n.global().deleteSelectedFailureNotification(
|
||||
selected.length - successes.length)),
|
||||
duration: k.snackBarDurationNormal,
|
||||
));
|
||||
}
|
||||
|
|
80
lib/widget/handler/remove_selection_handler.dart
Normal file
80
lib/widget/handler/remove_selection_handler.dart
Normal file
|
@ -0,0 +1,80 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:nc_photos/account.dart';
|
||||
import 'package:nc_photos/app_db.dart';
|
||||
import 'package:nc_photos/app_localizations.dart';
|
||||
import 'package:nc_photos/debug_util.dart';
|
||||
import 'package:nc_photos/entity/album.dart';
|
||||
import 'package:nc_photos/entity/file.dart';
|
||||
import 'package:nc_photos/entity/file/data_source.dart';
|
||||
import 'package:nc_photos/entity/share.dart';
|
||||
import 'package:nc_photos/entity/share/data_source.dart';
|
||||
import 'package:nc_photos/k.dart' as k;
|
||||
import 'package:nc_photos/pref.dart';
|
||||
import 'package:nc_photos/snack_bar_manager.dart';
|
||||
import 'package:nc_photos/use_case/remove.dart';
|
||||
|
||||
class RemoveSelectionHandler {
|
||||
/// Remove [selectedFiles] and return the removed count
|
||||
Future<int> call({
|
||||
required Account account,
|
||||
required List<File> selectedFiles,
|
||||
bool shouldCleanupAlbum = true,
|
||||
bool isRemoveOpened = false,
|
||||
}) async {
|
||||
final String processingText, successText;
|
||||
final String Function(int) failureText;
|
||||
if (isRemoveOpened) {
|
||||
processingText = L10n.global().deleteProcessingNotification;
|
||||
successText = L10n.global().deleteSuccessNotification;
|
||||
failureText = (_) => L10n.global().deleteFailureNotification;
|
||||
} else {
|
||||
processingText = L10n.global()
|
||||
.deleteSelectedProcessingNotification(selectedFiles.length);
|
||||
successText = L10n.global().deleteSelectedSuccessNotification;
|
||||
failureText =
|
||||
(count) => L10n.global().deleteSelectedFailureNotification(count);
|
||||
}
|
||||
SnackBarManager().showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(processingText),
|
||||
duration: k.snackBarDurationShort,
|
||||
),
|
||||
canBeReplaced: true,
|
||||
);
|
||||
|
||||
final fileRepo = FileRepo(FileCachedDataSource(AppDb()));
|
||||
final albumRepo =
|
||||
shouldCleanupAlbum ? AlbumRepo(AlbumCachedDataSource(AppDb())) : null;
|
||||
final shareRepo =
|
||||
shouldCleanupAlbum ? ShareRepo(ShareRemoteDataSource()) : null;
|
||||
var failureCount = 0;
|
||||
await Remove(fileRepo, albumRepo, shareRepo, AppDb(), Pref())(
|
||||
account,
|
||||
selectedFiles,
|
||||
onRemoveFileFailed: (file, e, stackTrace) {
|
||||
_log.shout(
|
||||
"[call] Failed while removing file: ${logFilename(file.path)}",
|
||||
e,
|
||||
stackTrace);
|
||||
++failureCount;
|
||||
},
|
||||
);
|
||||
if (failureCount == 0) {
|
||||
SnackBarManager().showSnackBar(SnackBar(
|
||||
content: Text(successText),
|
||||
duration: k.snackBarDurationNormal,
|
||||
));
|
||||
} else {
|
||||
SnackBarManager().showSnackBar(SnackBar(
|
||||
content: Text(failureText(failureCount)),
|
||||
duration: k.snackBarDurationNormal,
|
||||
));
|
||||
}
|
||||
return selectedFiles.length - failureCount;
|
||||
}
|
||||
|
||||
static final _log =
|
||||
Logger("widget.handler.remove_selection_handler.RemoveSelectionHandler");
|
||||
}
|
|
@ -15,7 +15,6 @@ import 'package:nc_photos/app_localizations.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';
|
||||
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;
|
||||
|
@ -30,9 +29,9 @@ import 'package:nc_photos/primitive.dart';
|
|||
import 'package:nc_photos/share_handler.dart';
|
||||
import 'package:nc_photos/snack_bar_manager.dart';
|
||||
import 'package:nc_photos/theme.dart';
|
||||
import 'package:nc_photos/use_case/remove.dart';
|
||||
import 'package:nc_photos/use_case/update_property.dart';
|
||||
import 'package:nc_photos/widget/handler/add_selection_to_album_handler.dart';
|
||||
import 'package:nc_photos/widget/handler/remove_selection_handler.dart';
|
||||
import 'package:nc_photos/widget/home_app_bar.dart';
|
||||
import 'package:nc_photos/widget/measure.dart';
|
||||
import 'package:nc_photos/widget/page_visibility_mixin.dart';
|
||||
|
@ -466,26 +465,10 @@ class _HomePhotosState extends State<HomePhotos>
|
|||
setState(() {
|
||||
clearSelectedItems();
|
||||
});
|
||||
final fileRepo = FileRepo(FileCachedDataSource(AppDb()));
|
||||
final albumRepo = AlbumRepo(AlbumCachedDataSource(AppDb()));
|
||||
await NotifiedListAction<File>(
|
||||
list: selectedFiles,
|
||||
action: (file) async {
|
||||
await Remove(fileRepo, albumRepo)(widget.account, file);
|
||||
},
|
||||
processingText: L10n.global()
|
||||
.deleteSelectedProcessingNotification(selectedFiles.length),
|
||||
successText: L10n.global().deleteSelectedSuccessNotification,
|
||||
getFailureText: (failures) =>
|
||||
L10n.global().deleteSelectedFailureNotification(failures.length),
|
||||
onActionError: (file, e, stackTrace) {
|
||||
_log.shout(
|
||||
"[_onSelectionDeletePressed] Failed while removing file" +
|
||||
(shouldLogFileName ? ": ${file.path}" : ""),
|
||||
e,
|
||||
stackTrace);
|
||||
},
|
||||
)();
|
||||
await RemoveSelectionHandler()(
|
||||
account: widget.account,
|
||||
selectedFiles: selectedFiles,
|
||||
);
|
||||
}
|
||||
|
||||
void _onMetadataTaskStateChanged(MetadataTaskStateChangedEvent ev) {
|
||||
|
|
|
@ -13,7 +13,6 @@ import 'package:nc_photos/bloc/list_face.dart';
|
|||
import 'package:nc_photos/cache_manager_util.dart';
|
||||
import 'package:nc_photos/debug_util.dart';
|
||||
import 'package:nc_photos/download_handler.dart';
|
||||
import 'package:nc_photos/entity/album.dart';
|
||||
import 'package:nc_photos/entity/face.dart';
|
||||
import 'package:nc_photos/entity/file.dart';
|
||||
import 'package:nc_photos/entity/file/data_source.dart';
|
||||
|
@ -30,9 +29,9 @@ import 'package:nc_photos/snack_bar_manager.dart';
|
|||
import 'package:nc_photos/theme.dart';
|
||||
import 'package:nc_photos/throttler.dart';
|
||||
import 'package:nc_photos/use_case/populate_person.dart';
|
||||
import 'package:nc_photos/use_case/remove.dart';
|
||||
import 'package:nc_photos/use_case/update_property.dart';
|
||||
import 'package:nc_photos/widget/handler/add_selection_to_album_handler.dart';
|
||||
import 'package:nc_photos/widget/handler/remove_selection_handler.dart';
|
||||
import 'package:nc_photos/widget/photo_list_item.dart';
|
||||
import 'package:nc_photos/widget/selectable_item_stream_list_mixin.dart';
|
||||
import 'package:nc_photos/widget/selection_app_bar.dart';
|
||||
|
@ -405,26 +404,10 @@ class _PersonBrowserState extends State<PersonBrowser>
|
|||
setState(() {
|
||||
clearSelectedItems();
|
||||
});
|
||||
final fileRepo = FileRepo(FileCachedDataSource(AppDb()));
|
||||
final albumRepo = AlbumRepo(AlbumCachedDataSource(AppDb()));
|
||||
await NotifiedListAction<File>(
|
||||
list: selectedFiles,
|
||||
action: (file) async {
|
||||
await Remove(fileRepo, albumRepo)(widget.account, file);
|
||||
},
|
||||
processingText: L10n.global()
|
||||
.deleteSelectedProcessingNotification(selectedFiles.length),
|
||||
successText: L10n.global().deleteSelectedSuccessNotification,
|
||||
getFailureText: (failures) =>
|
||||
L10n.global().deleteSelectedFailureNotification(failures.length),
|
||||
onActionError: (file, e, stackTrace) {
|
||||
_log.shout(
|
||||
"[_onSelectionDeletePressed] Failed while removing file" +
|
||||
(shouldLogFileName ? ": ${file.path}" : ""),
|
||||
e,
|
||||
stackTrace);
|
||||
},
|
||||
)();
|
||||
await RemoveSelectionHandler()(
|
||||
account: widget.account,
|
||||
selectedFiles: selectedFiles,
|
||||
);
|
||||
}
|
||||
|
||||
void _onFilePropertyUpdated(FilePropertyUpdatedEvent ev) {
|
||||
|
|
|
@ -278,11 +278,9 @@ class _SharedFileViewerState extends State<SharedFileViewer> {
|
|||
}
|
||||
|
||||
final fileRepo = FileRepo(FileCachedDataSource(AppDb()));
|
||||
return Remove(fileRepo, null)(
|
||||
return Remove(fileRepo, null, null, null, null)(
|
||||
widget.account,
|
||||
widget.file.copyWith(
|
||||
path: dirPath,
|
||||
),
|
||||
[widget.file.copyWith(path: dirPath)],
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@ import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.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/app_db.dart';
|
||||
import 'package:nc_photos/app_localizations.dart';
|
||||
import 'package:nc_photos/bloc/ls_trashbin.dart';
|
||||
import 'package:nc_photos/debug_util.dart';
|
||||
|
@ -19,9 +18,9 @@ import 'package:nc_photos/k.dart' as k;
|
|||
import 'package:nc_photos/pref.dart';
|
||||
import 'package:nc_photos/snack_bar_manager.dart';
|
||||
import 'package:nc_photos/theme.dart';
|
||||
import 'package:nc_photos/use_case/remove.dart';
|
||||
import 'package:nc_photos/use_case/restore_trashbin.dart';
|
||||
import 'package:nc_photos/widget/empty_list_indicator.dart';
|
||||
import 'package:nc_photos/widget/handler/remove_selection_handler.dart';
|
||||
import 'package:nc_photos/widget/photo_list_item.dart';
|
||||
import 'package:nc_photos/widget/selectable_item_stream_list_mixin.dart';
|
||||
import 'package:nc_photos/widget/selection_app_bar.dart';
|
||||
|
@ -395,37 +394,11 @@ class _TrashbinBrowserState extends State<TrashbinBrowser>
|
|||
}
|
||||
|
||||
Future<void> _deleteFiles(List<File> files) async {
|
||||
SnackBarManager().showSnackBar(SnackBar(
|
||||
content: Text(
|
||||
L10n.global().deleteSelectedProcessingNotification(files.length)),
|
||||
duration: k.snackBarDurationShort,
|
||||
));
|
||||
final fileRepo = FileRepo(FileCachedDataSource(AppDb()));
|
||||
final failures = <File>[];
|
||||
for (final f in files) {
|
||||
try {
|
||||
await Remove(fileRepo, null)(widget.account, f);
|
||||
} catch (e, stacktrace) {
|
||||
_log.shout(
|
||||
"[_deleteFiles] Failed while removing file" +
|
||||
(shouldLogFileName ? ": ${f.path}" : ""),
|
||||
e,
|
||||
stacktrace);
|
||||
failures.add(f);
|
||||
}
|
||||
}
|
||||
if (failures.isEmpty) {
|
||||
SnackBarManager().showSnackBar(SnackBar(
|
||||
content: Text(L10n.global().deleteSelectedSuccessNotification),
|
||||
duration: k.snackBarDurationNormal,
|
||||
));
|
||||
} else {
|
||||
SnackBarManager().showSnackBar(SnackBar(
|
||||
content: Text(
|
||||
L10n.global().deleteSelectedFailureNotification(failures.length)),
|
||||
duration: k.snackBarDurationNormal,
|
||||
));
|
||||
}
|
||||
await RemoveSelectionHandler()(
|
||||
account: widget.account,
|
||||
selectedFiles: files,
|
||||
shouldCleanupAlbum: false,
|
||||
);
|
||||
}
|
||||
|
||||
void _reqQuery() {
|
||||
|
|
|
@ -13,8 +13,8 @@ import 'package:nc_photos/exception_util.dart' as exception_util;
|
|||
import 'package:nc_photos/k.dart' as k;
|
||||
import 'package:nc_photos/snack_bar_manager.dart';
|
||||
import 'package:nc_photos/theme.dart';
|
||||
import 'package:nc_photos/use_case/remove.dart';
|
||||
import 'package:nc_photos/use_case/restore_trashbin.dart';
|
||||
import 'package:nc_photos/widget/handler/remove_selection_handler.dart';
|
||||
import 'package:nc_photos/widget/horizontal_page_viewer.dart';
|
||||
import 'package:nc_photos/widget/image_viewer.dart';
|
||||
import 'package:nc_photos/widget/video_viewer.dart';
|
||||
|
@ -310,34 +310,14 @@ class _TrashbinViewerState extends State<TrashbinViewer> {
|
|||
Future<void> _delete(BuildContext context) async {
|
||||
final file = widget.streamFiles[_viewerController.currentPage];
|
||||
_log.info("[_delete] Removing file: ${file.path}");
|
||||
var controller = SnackBarManager().showSnackBar(SnackBar(
|
||||
content: Text(L10n.global().deleteProcessingNotification),
|
||||
duration: k.snackBarDurationShort,
|
||||
));
|
||||
controller?.closed.whenComplete(() {
|
||||
controller = null;
|
||||
});
|
||||
try {
|
||||
final fileRepo = FileRepo(FileCachedDataSource(AppDb()));
|
||||
await Remove(fileRepo, null)(widget.account, file);
|
||||
controller?.close();
|
||||
SnackBarManager().showSnackBar(SnackBar(
|
||||
content: Text(L10n.global().deleteSuccessNotification),
|
||||
duration: k.snackBarDurationNormal,
|
||||
));
|
||||
final count = await RemoveSelectionHandler()(
|
||||
account: widget.account,
|
||||
selectedFiles: [file],
|
||||
shouldCleanupAlbum: false,
|
||||
isRemoveOpened: true,
|
||||
);
|
||||
if (count > 0) {
|
||||
Navigator.of(context).pop();
|
||||
} catch (e, stacktrace) {
|
||||
_log.shout(
|
||||
"[_delete] Failed while remove" +
|
||||
(shouldLogFileName ? ": ${file.path}" : ""),
|
||||
e,
|
||||
stacktrace);
|
||||
controller?.close();
|
||||
SnackBarManager().showSnackBar(SnackBar(
|
||||
content: Text("${L10n.global().deleteFailureNotification}: "
|
||||
"${exception_util.toUserString(e)}"),
|
||||
duration: k.snackBarDurationNormal,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,23 +7,19 @@ import 'package:flutter/rendering.dart';
|
|||
import 'package:flutter/services.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:nc_photos/account.dart';
|
||||
import 'package:nc_photos/app_db.dart';
|
||||
import 'package:nc_photos/app_localizations.dart';
|
||||
import 'package:nc_photos/debug_util.dart';
|
||||
import 'package:nc_photos/download_handler.dart';
|
||||
import 'package:nc_photos/entity/album.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/exception_util.dart' as exception_util;
|
||||
import 'package:nc_photos/k.dart' as k;
|
||||
import 'package:nc_photos/pref.dart';
|
||||
import 'package:nc_photos/share_handler.dart';
|
||||
import 'package:nc_photos/snack_bar_manager.dart';
|
||||
import 'package:nc_photos/theme.dart';
|
||||
import 'package:nc_photos/use_case/remove.dart';
|
||||
import 'package:nc_photos/widget/animated_visibility.dart';
|
||||
import 'package:nc_photos/widget/disposable.dart';
|
||||
import 'package:nc_photos/widget/handler/remove_selection_handler.dart';
|
||||
import 'package:nc_photos/widget/horizontal_page_viewer.dart';
|
||||
import 'package:nc_photos/widget/image_viewer.dart';
|
||||
import 'package:nc_photos/widget/slideshow_dialog.dart';
|
||||
|
@ -460,34 +456,13 @@ class _ViewerState extends State<Viewer>
|
|||
void _onDeletePressed(BuildContext context) async {
|
||||
final file = widget.streamFiles[_viewerController.currentPage];
|
||||
_log.info("[_onDeletePressed] Removing file: ${file.path}");
|
||||
var controller = SnackBarManager().showSnackBar(SnackBar(
|
||||
content: Text(L10n.global().deleteProcessingNotification),
|
||||
duration: k.snackBarDurationShort,
|
||||
));
|
||||
controller?.closed.whenComplete(() {
|
||||
controller = null;
|
||||
});
|
||||
try {
|
||||
await Remove(FileRepo(FileCachedDataSource(AppDb())),
|
||||
AlbumRepo(AlbumCachedDataSource(AppDb())))(widget.account, file);
|
||||
controller?.close();
|
||||
SnackBarManager().showSnackBar(SnackBar(
|
||||
content: Text(L10n.global().deleteSuccessNotification),
|
||||
duration: k.snackBarDurationNormal,
|
||||
));
|
||||
final count = await RemoveSelectionHandler()(
|
||||
account: widget.account,
|
||||
selectedFiles: [file],
|
||||
isRemoveOpened: true,
|
||||
);
|
||||
if (count > 0) {
|
||||
Navigator.of(context).pop();
|
||||
} catch (e, stacktrace) {
|
||||
_log.shout(
|
||||
"[_onDeletePressed] Failed while remove" +
|
||||
(shouldLogFileName ? ": ${file.path}" : ""),
|
||||
e,
|
||||
stacktrace);
|
||||
controller?.close();
|
||||
SnackBarManager().showSnackBar(SnackBar(
|
||||
content: Text("${L10n.global().deleteFailureNotification}: "
|
||||
"${exception_util.toUserString(e)}"),
|
||||
duration: k.snackBarDurationNormal,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
321
test/use_case/remove_test.dart
Normal file
321
test/use_case/remove_test.dart
Normal file
|
@ -0,0 +1,321 @@
|
|||
import 'package:event_bus/event_bus.dart';
|
||||
import 'package:kiwi/kiwi.dart';
|
||||
import 'package:nc_photos/entity/album.dart';
|
||||
import 'package:nc_photos/entity/album/cover_provider.dart';
|
||||
import 'package:nc_photos/entity/album/provider.dart';
|
||||
import 'package:nc_photos/entity/album/sort_provider.dart';
|
||||
import 'package:nc_photos/or_null.dart';
|
||||
import 'package:nc_photos/pref.dart';
|
||||
import 'package:nc_photos/use_case/remove.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import '../mock_type.dart';
|
||||
import '../test_util.dart' as util;
|
||||
|
||||
void main() {
|
||||
KiwiContainer().registerInstance<EventBus>(MockEventBus());
|
||||
|
||||
group("Remove", () {
|
||||
test("file", _removeFile);
|
||||
test("file no clean up", _removeFileNoCleanUp);
|
||||
group("album", () {
|
||||
test("file", _removeAlbumFile);
|
||||
test("file no clean up", _removeAlbumFileNoCleanUp);
|
||||
});
|
||||
group("shared album", () {
|
||||
test("file", _removeSharedAlbumFile);
|
||||
test("shared file", _removeSharedAlbumSharedFile);
|
||||
test("file resynced by others", _removeSharedAlbumResyncedFile);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// Remove a file
|
||||
///
|
||||
/// Expect: file deleted
|
||||
Future<void> _removeFile() async {
|
||||
final account = util.buildAccount();
|
||||
final pref = Pref.scoped(PrefMemoryProvider());
|
||||
final files = (util.FilesBuilder()
|
||||
..addJpeg("admin/test1.jpg")
|
||||
..addJpeg("admin/test2.jpg"))
|
||||
.build();
|
||||
final appDb = MockAppDb();
|
||||
await util.fillAppDb(appDb, account, files);
|
||||
final fileRepo = MockFileMemoryRepo(files);
|
||||
final albumRepo = MockAlbumMemoryRepo();
|
||||
final shareRepo = MockShareMemoryRepo();
|
||||
|
||||
await Remove(fileRepo, albumRepo, shareRepo, appDb, pref)(
|
||||
account, [files[0]]);
|
||||
expect(fileRepo.files, [files[1]]);
|
||||
}
|
||||
|
||||
/// Remove a file, skip clean up
|
||||
///
|
||||
/// Expect: file deleted
|
||||
Future<void> _removeFileNoCleanUp() async {
|
||||
final account = util.buildAccount();
|
||||
final files = (util.FilesBuilder()
|
||||
..addJpeg("admin/test1.jpg")
|
||||
..addJpeg("admin/test2.jpg"))
|
||||
.build();
|
||||
final appDb = MockAppDb();
|
||||
await util.fillAppDb(appDb, account, files);
|
||||
final fileRepo = MockFileMemoryRepo(files);
|
||||
|
||||
await Remove(fileRepo, null, null, null, null)(account, [files[0]]);
|
||||
expect(fileRepo.files, [files[1]]);
|
||||
}
|
||||
|
||||
/// Remove a file included in an album
|
||||
///
|
||||
/// Expect: file removed from album
|
||||
Future<void> _removeAlbumFile() async {
|
||||
final account = util.buildAccount();
|
||||
final pref = Pref.scoped(PrefMemoryProvider());
|
||||
final files =
|
||||
(util.FilesBuilder(initialFileId: 1)..addJpeg("admin/test1.jpg")).build();
|
||||
final album = (util.AlbumBuilder()..addFileItem(files[0])).build();
|
||||
final albumFile = album.albumFile!;
|
||||
final appDb = MockAppDb();
|
||||
await util.fillAppDb(appDb, account, files);
|
||||
final fileRepo = MockFileMemoryRepo([albumFile, ...files]);
|
||||
final albumRepo = MockAlbumMemoryRepo([album]);
|
||||
final shareRepo = MockShareMemoryRepo();
|
||||
|
||||
await Remove(fileRepo, albumRepo, shareRepo, appDb, pref)(
|
||||
account, [files[0]]);
|
||||
expect(
|
||||
albumRepo.albums
|
||||
.map((e) => e.copyWith(
|
||||
// we need to set a known value to lastUpdated
|
||||
lastUpdated: OrNull(DateTime.utc(2020, 1, 2, 3, 4, 5)),
|
||||
))
|
||||
.toList(),
|
||||
[
|
||||
Album(
|
||||
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5),
|
||||
name: "test",
|
||||
provider: AlbumStaticProvider(items: []),
|
||||
coverProvider: AlbumAutoCoverProvider(),
|
||||
sortProvider: const AlbumNullSortProvider(),
|
||||
albumFile: albumFile,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// Remove a file included in an album
|
||||
///
|
||||
/// Expect: file not removed from album
|
||||
Future<void> _removeAlbumFileNoCleanUp() async {
|
||||
final account = util.buildAccount();
|
||||
final files =
|
||||
(util.FilesBuilder(initialFileId: 1)..addJpeg("admin/test1.jpg")).build();
|
||||
final album = (util.AlbumBuilder()..addFileItem(files[0])).build();
|
||||
final fileItems = util.AlbumBuilder.fileItemsOf(album);
|
||||
final albumFile = album.albumFile!;
|
||||
final appDb = MockAppDb();
|
||||
await util.fillAppDb(appDb, account, files);
|
||||
final fileRepo = MockFileMemoryRepo([albumFile, ...files]);
|
||||
final albumRepo = MockAlbumMemoryRepo([album]);
|
||||
|
||||
await Remove(fileRepo, null, null, null, null)(account, [files[0]]);
|
||||
expect(
|
||||
albumRepo.albums
|
||||
.map((e) => e.copyWith(
|
||||
// we need to set a known value to lastUpdated
|
||||
lastUpdated: OrNull(DateTime.utc(2020, 1, 2, 3, 4, 5)),
|
||||
))
|
||||
.toList(),
|
||||
[
|
||||
Album(
|
||||
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5),
|
||||
name: "test",
|
||||
provider: AlbumStaticProvider(
|
||||
items: fileItems,
|
||||
latestItemTime: files[0].lastModified,
|
||||
),
|
||||
coverProvider: AlbumAutoCoverProvider(coverFile: files[0]),
|
||||
sortProvider: const AlbumNullSortProvider(),
|
||||
albumFile: albumFile,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// Remove a file included in a shared album (admin -> user1)
|
||||
///
|
||||
/// Expect: file removed from album;
|
||||
/// file share (admin -> user1) deleted
|
||||
Future<void> _removeSharedAlbumFile() async {
|
||||
final account = util.buildAccount();
|
||||
final pref = Pref.scoped(PrefMemoryProvider());
|
||||
final files =
|
||||
(util.FilesBuilder(initialFileId: 1)..addJpeg("admin/test1.jpg")).build();
|
||||
final album = (util.AlbumBuilder()
|
||||
..addFileItem(files[0])
|
||||
..addShare("user1"))
|
||||
.build();
|
||||
final albumFile = album.albumFile!;
|
||||
final appDb = MockAppDb();
|
||||
await util.fillAppDb(appDb, account, files);
|
||||
final fileRepo = MockFileMemoryRepo([albumFile, ...files]);
|
||||
final albumRepo = MockAlbumMemoryRepo([album]);
|
||||
final shareRepo = MockShareMemoryRepo([
|
||||
util.buildShare(id: "0", file: albumFile, shareWith: "user1"),
|
||||
util.buildShare(id: "1", file: files[0], shareWith: "user1"),
|
||||
]);
|
||||
|
||||
await Remove(fileRepo, albumRepo, shareRepo, appDb, pref)(
|
||||
account, [files[0]]);
|
||||
expect(
|
||||
albumRepo.albums
|
||||
.map((e) => e.copyWith(
|
||||
// we need to set a known value to lastUpdated
|
||||
lastUpdated: OrNull(DateTime.utc(2020, 1, 2, 3, 4, 5)),
|
||||
))
|
||||
.toList(),
|
||||
[
|
||||
Album(
|
||||
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5),
|
||||
name: "test",
|
||||
provider: AlbumStaticProvider(items: []),
|
||||
coverProvider: AlbumAutoCoverProvider(),
|
||||
sortProvider: const AlbumNullSortProvider(),
|
||||
albumFile: albumFile,
|
||||
shares: [
|
||||
util.buildAlbumShare(userId: "user1"),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
expect(
|
||||
shareRepo.shares,
|
||||
[util.buildShare(id: "0", file: albumFile, shareWith: "user1")],
|
||||
);
|
||||
}
|
||||
|
||||
/// Remove a file shared with you (user1 -> admin), added by you to a shared
|
||||
/// album (admin -> user1, user2)
|
||||
///
|
||||
/// Expect: file removed from album;
|
||||
/// file share (admin -> user2) deleted
|
||||
Future<void> _removeSharedAlbumSharedFile() async {
|
||||
final account = util.buildAccount();
|
||||
final user1Account = util.buildAccount(username: "user1");
|
||||
final pref = Pref.scoped(PrefMemoryProvider());
|
||||
final files = (util.FilesBuilder(initialFileId: 1)
|
||||
..addJpeg("admin/test1.jpg", ownerId: "user1"))
|
||||
.build();
|
||||
final user1Files = [
|
||||
files[0].copyWith(path: "remote.php/dav/files/user1/test1.jpg")
|
||||
];
|
||||
final album = (util.AlbumBuilder()
|
||||
..addFileItem(files[0])
|
||||
..addShare("user1")
|
||||
..addShare("user2"))
|
||||
.build();
|
||||
final albumFile = album.albumFile!;
|
||||
final appDb = MockAppDb();
|
||||
await util.fillAppDb(appDb, account, files);
|
||||
await util.fillAppDb(appDb, user1Account, user1Files);
|
||||
final fileRepo = MockFileMemoryRepo([albumFile, ...files, ...user1Files]);
|
||||
final albumRepo = MockAlbumMemoryRepo([album]);
|
||||
final shareRepo = MockShareMemoryRepo([
|
||||
util.buildShare(id: "0", file: albumFile, shareWith: "user1"),
|
||||
util.buildShare(id: "1", file: albumFile, shareWith: "user2"),
|
||||
util.buildShare(
|
||||
id: "2", file: user1Files[0], uidOwner: "user1", shareWith: "admin"),
|
||||
util.buildShare(id: "3", file: files[0], shareWith: "user2"),
|
||||
]);
|
||||
|
||||
await Remove(fileRepo, albumRepo, shareRepo, appDb, pref)(
|
||||
account, [files[0]]);
|
||||
expect(
|
||||
albumRepo.albums
|
||||
.map((e) => e.copyWith(
|
||||
// we need to set a known value to lastUpdated
|
||||
lastUpdated: OrNull(DateTime.utc(2020, 1, 2, 3, 4, 5)),
|
||||
))
|
||||
.toList(),
|
||||
[
|
||||
Album(
|
||||
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5),
|
||||
name: "test",
|
||||
provider: AlbumStaticProvider(items: []),
|
||||
coverProvider: AlbumAutoCoverProvider(),
|
||||
sortProvider: const AlbumNullSortProvider(),
|
||||
albumFile: albumFile,
|
||||
shares: [
|
||||
util.buildAlbumShare(userId: "user1"),
|
||||
util.buildAlbumShare(userId: "user2"),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
expect(
|
||||
shareRepo.shares,
|
||||
[
|
||||
util.buildShare(id: "0", file: albumFile, shareWith: "user1"),
|
||||
util.buildShare(id: "1", file: albumFile, shareWith: "user2"),
|
||||
util.buildShare(
|
||||
id: "2", file: user1Files[0], uidOwner: "user1", shareWith: "admin"),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// Remove a file included in a shared album (admin -> user1), with the album
|
||||
/// json updated by user1
|
||||
///
|
||||
/// Expect: file removed from album;
|
||||
/// file share (admin -> user1) deleted
|
||||
Future<void> _removeSharedAlbumResyncedFile() async {
|
||||
final account = util.buildAccount();
|
||||
final pref = Pref.scoped(PrefMemoryProvider());
|
||||
final files =
|
||||
(util.FilesBuilder(initialFileId: 1)..addJpeg("admin/test1.jpg")).build();
|
||||
final album = (util.AlbumBuilder()
|
||||
..addFileItem(files[0]
|
||||
.copyWith(path: "remote.php/dav/files/user1/share/test1.jpg"))
|
||||
..addShare("user1"))
|
||||
.build();
|
||||
final albumFile = album.albumFile!;
|
||||
final appDb = MockAppDb();
|
||||
await util.fillAppDb(appDb, account, files);
|
||||
final fileRepo = MockFileMemoryRepo([albumFile, ...files]);
|
||||
final albumRepo = MockAlbumMemoryRepo([album]);
|
||||
final shareRepo = MockShareMemoryRepo([
|
||||
util.buildShare(id: "0", file: albumFile, shareWith: "user1"),
|
||||
util.buildShare(id: "1", file: files[0], shareWith: "user1"),
|
||||
]);
|
||||
|
||||
await Remove(fileRepo, albumRepo, shareRepo, appDb, pref)(
|
||||
account, [files[0]]);
|
||||
expect(
|
||||
albumRepo.albums
|
||||
.map((e) => e.copyWith(
|
||||
// we need to set a known value to lastUpdated
|
||||
lastUpdated: OrNull(DateTime.utc(2020, 1, 2, 3, 4, 5)),
|
||||
))
|
||||
.toList(),
|
||||
[
|
||||
Album(
|
||||
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5),
|
||||
name: "test",
|
||||
provider: AlbumStaticProvider(items: []),
|
||||
coverProvider: AlbumAutoCoverProvider(),
|
||||
sortProvider: const AlbumNullSortProvider(),
|
||||
albumFile: albumFile,
|
||||
shares: [
|
||||
util.buildAlbumShare(userId: "user1"),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
expect(
|
||||
shareRepo.shares,
|
||||
[util.buildShare(id: "0", file: albumFile, shareWith: "user1")],
|
||||
);
|
||||
}
|
Loading…
Reference in a new issue