mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-02-08 18:28:53 +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");
|
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 {
|
class FileRepo {
|
||||||
const FileRepo(this.dataSrc);
|
const FileRepo(this.dataSrc);
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ class TouchTokenManager {
|
||||||
"[setRemoteToken] Set remote token for file '${file.path}': $token");
|
"[setRemoteToken] Set remote token for file '${file.path}': $token");
|
||||||
final path = _getRemotePath(account, file);
|
final path = _getRemotePath(account, file);
|
||||||
if (token == null) {
|
if (token == null) {
|
||||||
return Remove(fileRepo, null)(account, file);
|
return Remove(fileRepo, null, null, null, null)(account, [file]);
|
||||||
} else {
|
} else {
|
||||||
return PutFileBinary(fileRepo)(
|
return PutFileBinary(fileRepo)(
|
||||||
account, path, const Utf8Encoder().convert(token),
|
account, path, const Utf8Encoder().convert(token),
|
||||||
|
|
|
@ -2,116 +2,127 @@ import 'package:event_bus/event_bus.dart';
|
||||||
import 'package:kiwi/kiwi.dart';
|
import 'package:kiwi/kiwi.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/account.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.dart';
|
||||||
import 'package:nc_photos/entity/album/item.dart';
|
import 'package:nc_photos/entity/album/item.dart';
|
||||||
import 'package:nc_photos/entity/album/provider.dart';
|
import 'package:nc_photos/entity/album/provider.dart';
|
||||||
import 'package:nc_photos/entity/file.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/event/event.dart';
|
||||||
import 'package:nc_photos/iterable_extension.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/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 {
|
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
|
/// Remove files
|
||||||
Future<void> call(Account account, File file) async {
|
Future<void> call(
|
||||||
await fileRepo.remove(account, file);
|
Account account,
|
||||||
if (albumRepo != null) {
|
List<File> files, {
|
||||||
_log.info("[call] Skip albums cleanup as albumRepo == null");
|
void Function(File file, Object error, StackTrace stackTrace)?
|
||||||
_CleanUpAlbums()(_CleanUpAlbumsData(fileRepo, albumRepo!, account, file));
|
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));
|
for (final f in files) {
|
||||||
}
|
try {
|
||||||
|
await fileRepo.remove(account, f);
|
||||||
final FileRepo fileRepo;
|
KiwiContainer().resolve<EventBus>().fire(FileRemovedEvent(account, f));
|
||||||
final AlbumRepo? albumRepo;
|
} catch (e, stackTrace) {
|
||||||
|
_log.severe("[call] Failed while remove: ${logFilename(f.path)}", e,
|
||||||
static final _log = Logger("use_case.remove.Remove");
|
stackTrace);
|
||||||
}
|
onRemoveFileFailed?.call(f, e, stackTrace);
|
||||||
|
}
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clean up for a single account
|
Future<void> _cleanUpAlbums(Account account, List<File> removes) async {
|
||||||
Future<void> _cleanUp(FileRepo fileRepo, AlbumRepo albumRepo, Account account,
|
final albums = await ListAlbum(fileRepo, albumRepo!)(account)
|
||||||
List<File> removes) async {
|
.where((event) => event is Album)
|
||||||
final albums = (await ListAlbum(fileRepo, albumRepo)(account)
|
.cast<Album>()
|
||||||
.where((event) => event is Album)
|
.toList();
|
||||||
.toList())
|
// figure out which files need to be unshared with whom
|
||||||
.cast<Album>();
|
final unshares = <FileServerIdentityComparator, Set<CiString>>{};
|
||||||
// clean up only make sense for static albums
|
// clean up only make sense for static albums
|
||||||
for (final a
|
for (final a in albums.where((a) => a.provider is AlbumStaticProvider)) {
|
||||||
in albums.where((element) => element.provider is AlbumStaticProvider)) {
|
|
||||||
try {
|
try {
|
||||||
final provider = AlbumStaticProvider.of(a);
|
final provider = AlbumStaticProvider.of(a);
|
||||||
if (provider.items.whereType<AlbumFileItem>().any((element) =>
|
final itemsToRemove = provider.items
|
||||||
removes.containsIf(element.file, (a, b) => a.path == b.path))) {
|
.whereType<AlbumFileItem>()
|
||||||
final newItems = provider.items.where((element) {
|
.where((i) =>
|
||||||
if (element is AlbumFileItem) {
|
(i.file.isOwned(account.username) ||
|
||||||
return !removes.containsIf(
|
i.addedBy == account.username) &&
|
||||||
element.file, (a, b) => a.path == b.path);
|
removes.any((r) => r.compareServerIdentity(i.file)))
|
||||||
} else {
|
.toList();
|
||||||
return true;
|
if (itemsToRemove.isEmpty) {
|
||||||
}
|
continue;
|
||||||
}).toList();
|
|
||||||
await UpdateAlbum(albumRepo)(
|
|
||||||
account,
|
|
||||||
a.copyWith(
|
|
||||||
provider: AlbumStaticProvider.of(a).copyWith(
|
|
||||||
items: newItems,
|
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
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) {
|
} catch (e, stacktrace) {
|
||||||
_log.shout(
|
_log.shout(
|
||||||
"[_cleanUpAlbums] Failed while updating album", e, stacktrace);
|
"[_cleanUpAlbums] Failed while updating album", e, stacktrace);
|
||||||
// continue to next album
|
// 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 final _log = Logger("use_case.remove.Remove");
|
||||||
|
|
||||||
static _CleanUpAlbums? _inst;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ class RemoveAlbum {
|
||||||
}
|
}
|
||||||
// you can't add an album to another album, so passing null here can save
|
// you can't add an album to another album, so passing null here can save
|
||||||
// a few queries
|
// 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 {
|
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';
|
import 'package:nc_photos/use_case/update_album_with_actual_items.dart';
|
||||||
|
|
||||||
class RemoveFromAlbum {
|
class RemoveFromAlbum {
|
||||||
|
/// Constructor
|
||||||
|
///
|
||||||
|
/// If [shareRepo] and [fileRepo] are null, files will not be unshared after
|
||||||
|
/// removing from the album
|
||||||
const RemoveFromAlbum(
|
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]
|
/// Remove a list of AlbumItems from [album]
|
||||||
///
|
///
|
||||||
|
@ -37,11 +42,15 @@ class RemoveFromAlbum {
|
||||||
newAlbum = await _fixAlbumPostRemove(account, newAlbum, items);
|
newAlbum = await _fixAlbumPostRemove(account, newAlbum, items);
|
||||||
await UpdateAlbum(albumRepo)(account, newAlbum);
|
await UpdateAlbum(albumRepo)(account, newAlbum);
|
||||||
|
|
||||||
if (album.shares?.isNotEmpty == true) {
|
if (shareRepo == null) {
|
||||||
final removeFiles =
|
_log.info("[call] Skip unsharing files as shareRepo == null");
|
||||||
items.whereType<AlbumFileItem>().map((e) => e.file).toList();
|
} else {
|
||||||
if (removeFiles.isNotEmpty) {
|
if (album.shares?.isNotEmpty == true) {
|
||||||
await _unshareFiles(account, newAlbum, removeFiles);
|
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)
|
.where((element) => element != account.username)
|
||||||
.toList();
|
.toList();
|
||||||
if (albumShares.isNotEmpty) {
|
if (albumShares.isNotEmpty) {
|
||||||
await UnshareFileFromAlbum(shareRepo, fileRepo, albumRepo)(
|
await UnshareFileFromAlbum(shareRepo!, fileRepo!, albumRepo)(
|
||||||
account, album, files, albumShares);
|
account, album, files, albumShares);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final AlbumRepo albumRepo;
|
final AlbumRepo albumRepo;
|
||||||
final ShareRepo shareRepo;
|
final ShareRepo? shareRepo;
|
||||||
final FileRepo fileRepo;
|
final FileRepo? fileRepo;
|
||||||
final AppDb appDb;
|
final AppDb appDb;
|
||||||
|
|
||||||
static final _log = Logger("use_case.remove_from_album.RemoveFromAlbum");
|
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.dart';
|
||||||
import 'package:nc_photos/entity/file/data_source.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/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/event/event.dart';
|
||||||
import 'package:nc_photos/exception_util.dart' as exception_util;
|
import 'package:nc_photos/exception_util.dart' as exception_util;
|
||||||
import 'package:nc_photos/iterable_extension.dart';
|
import 'package:nc_photos/iterable_extension.dart';
|
||||||
|
@ -419,31 +421,30 @@ class _DynamicAlbumBrowserState extends State<DynamicAlbumBrowser>
|
||||||
|
|
||||||
final fileRepo = FileRepo(FileCachedDataSource(AppDb()));
|
final fileRepo = FileRepo(FileCachedDataSource(AppDb()));
|
||||||
final albumRepo = AlbumRepo(AlbumCachedDataSource(AppDb()));
|
final albumRepo = AlbumRepo(AlbumCachedDataSource(AppDb()));
|
||||||
|
final shareRepo = ShareRepo(ShareRemoteDataSource());
|
||||||
final successes = <_FileListItem>[];
|
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(
|
SnackBarManager().showSnackBar(SnackBar(
|
||||||
content: Text(L10n.global().deleteSelectedSuccessNotification),
|
content: Text(L10n.global().deleteSelectedSuccessNotification),
|
||||||
duration: k.snackBarDurationNormal,
|
duration: k.snackBarDurationNormal,
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
SnackBarManager().showSnackBar(SnackBar(
|
SnackBarManager().showSnackBar(SnackBar(
|
||||||
content: Text(
|
content: Text(L10n.global().deleteSelectedFailureNotification(
|
||||||
L10n.global().deleteSelectedFailureNotification(failures.length)),
|
selected.length - successes.length)),
|
||||||
duration: k.snackBarDurationNormal,
|
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/bloc/scan_account_dir.dart';
|
||||||
import 'package:nc_photos/debug_util.dart';
|
import 'package:nc_photos/debug_util.dart';
|
||||||
import 'package:nc_photos/download_handler.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.dart';
|
||||||
import 'package:nc_photos/entity/file/data_source.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/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/share_handler.dart';
|
||||||
import 'package:nc_photos/snack_bar_manager.dart';
|
import 'package:nc_photos/snack_bar_manager.dart';
|
||||||
import 'package:nc_photos/theme.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/use_case/update_property.dart';
|
||||||
import 'package:nc_photos/widget/handler/add_selection_to_album_handler.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/home_app_bar.dart';
|
||||||
import 'package:nc_photos/widget/measure.dart';
|
import 'package:nc_photos/widget/measure.dart';
|
||||||
import 'package:nc_photos/widget/page_visibility_mixin.dart';
|
import 'package:nc_photos/widget/page_visibility_mixin.dart';
|
||||||
|
@ -466,26 +465,10 @@ class _HomePhotosState extends State<HomePhotos>
|
||||||
setState(() {
|
setState(() {
|
||||||
clearSelectedItems();
|
clearSelectedItems();
|
||||||
});
|
});
|
||||||
final fileRepo = FileRepo(FileCachedDataSource(AppDb()));
|
await RemoveSelectionHandler()(
|
||||||
final albumRepo = AlbumRepo(AlbumCachedDataSource(AppDb()));
|
account: widget.account,
|
||||||
await NotifiedListAction<File>(
|
selectedFiles: selectedFiles,
|
||||||
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);
|
|
||||||
},
|
|
||||||
)();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onMetadataTaskStateChanged(MetadataTaskStateChangedEvent ev) {
|
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/cache_manager_util.dart';
|
||||||
import 'package:nc_photos/debug_util.dart';
|
import 'package:nc_photos/debug_util.dart';
|
||||||
import 'package:nc_photos/download_handler.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/face.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
import 'package:nc_photos/entity/file/data_source.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/theme.dart';
|
||||||
import 'package:nc_photos/throttler.dart';
|
import 'package:nc_photos/throttler.dart';
|
||||||
import 'package:nc_photos/use_case/populate_person.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/use_case/update_property.dart';
|
||||||
import 'package:nc_photos/widget/handler/add_selection_to_album_handler.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/photo_list_item.dart';
|
||||||
import 'package:nc_photos/widget/selectable_item_stream_list_mixin.dart';
|
import 'package:nc_photos/widget/selectable_item_stream_list_mixin.dart';
|
||||||
import 'package:nc_photos/widget/selection_app_bar.dart';
|
import 'package:nc_photos/widget/selection_app_bar.dart';
|
||||||
|
@ -405,26 +404,10 @@ class _PersonBrowserState extends State<PersonBrowser>
|
||||||
setState(() {
|
setState(() {
|
||||||
clearSelectedItems();
|
clearSelectedItems();
|
||||||
});
|
});
|
||||||
final fileRepo = FileRepo(FileCachedDataSource(AppDb()));
|
await RemoveSelectionHandler()(
|
||||||
final albumRepo = AlbumRepo(AlbumCachedDataSource(AppDb()));
|
account: widget.account,
|
||||||
await NotifiedListAction<File>(
|
selectedFiles: selectedFiles,
|
||||||
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);
|
|
||||||
},
|
|
||||||
)();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onFilePropertyUpdated(FilePropertyUpdatedEvent ev) {
|
void _onFilePropertyUpdated(FilePropertyUpdatedEvent ev) {
|
||||||
|
|
|
@ -278,11 +278,9 @@ class _SharedFileViewerState extends State<SharedFileViewer> {
|
||||||
}
|
}
|
||||||
|
|
||||||
final fileRepo = FileRepo(FileCachedDataSource(AppDb()));
|
final fileRepo = FileRepo(FileCachedDataSource(AppDb()));
|
||||||
return Remove(fileRepo, null)(
|
return Remove(fileRepo, null, null, null, null)(
|
||||||
widget.account,
|
widget.account,
|
||||||
widget.file.copyWith(
|
[widget.file.copyWith(path: dirPath)],
|
||||||
path: dirPath,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/api/api_util.dart' as api_util;
|
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/app_localizations.dart';
|
||||||
import 'package:nc_photos/bloc/ls_trashbin.dart';
|
import 'package:nc_photos/bloc/ls_trashbin.dart';
|
||||||
import 'package:nc_photos/debug_util.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/pref.dart';
|
||||||
import 'package:nc_photos/snack_bar_manager.dart';
|
import 'package:nc_photos/snack_bar_manager.dart';
|
||||||
import 'package:nc_photos/theme.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/use_case/restore_trashbin.dart';
|
||||||
import 'package:nc_photos/widget/empty_list_indicator.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/photo_list_item.dart';
|
||||||
import 'package:nc_photos/widget/selectable_item_stream_list_mixin.dart';
|
import 'package:nc_photos/widget/selectable_item_stream_list_mixin.dart';
|
||||||
import 'package:nc_photos/widget/selection_app_bar.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 {
|
Future<void> _deleteFiles(List<File> files) async {
|
||||||
SnackBarManager().showSnackBar(SnackBar(
|
await RemoveSelectionHandler()(
|
||||||
content: Text(
|
account: widget.account,
|
||||||
L10n.global().deleteSelectedProcessingNotification(files.length)),
|
selectedFiles: files,
|
||||||
duration: k.snackBarDurationShort,
|
shouldCleanupAlbum: false,
|
||||||
));
|
);
|
||||||
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,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _reqQuery() {
|
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/k.dart' as k;
|
||||||
import 'package:nc_photos/snack_bar_manager.dart';
|
import 'package:nc_photos/snack_bar_manager.dart';
|
||||||
import 'package:nc_photos/theme.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/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/horizontal_page_viewer.dart';
|
||||||
import 'package:nc_photos/widget/image_viewer.dart';
|
import 'package:nc_photos/widget/image_viewer.dart';
|
||||||
import 'package:nc_photos/widget/video_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 {
|
Future<void> _delete(BuildContext context) async {
|
||||||
final file = widget.streamFiles[_viewerController.currentPage];
|
final file = widget.streamFiles[_viewerController.currentPage];
|
||||||
_log.info("[_delete] Removing file: ${file.path}");
|
_log.info("[_delete] Removing file: ${file.path}");
|
||||||
var controller = SnackBarManager().showSnackBar(SnackBar(
|
final count = await RemoveSelectionHandler()(
|
||||||
content: Text(L10n.global().deleteProcessingNotification),
|
account: widget.account,
|
||||||
duration: k.snackBarDurationShort,
|
selectedFiles: [file],
|
||||||
));
|
shouldCleanupAlbum: false,
|
||||||
controller?.closed.whenComplete(() {
|
isRemoveOpened: true,
|
||||||
controller = null;
|
);
|
||||||
});
|
if (count > 0) {
|
||||||
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,
|
|
||||||
));
|
|
||||||
Navigator.of(context).pop();
|
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:flutter/services.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/account.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/app_localizations.dart';
|
||||||
import 'package:nc_photos/debug_util.dart';
|
import 'package:nc_photos/debug_util.dart';
|
||||||
import 'package:nc_photos/download_handler.dart';
|
import 'package:nc_photos/download_handler.dart';
|
||||||
import 'package:nc_photos/entity/album.dart';
|
import 'package:nc_photos/entity/album.dart';
|
||||||
import 'package:nc_photos/entity/file.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/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/k.dart' as k;
|
||||||
import 'package:nc_photos/pref.dart';
|
import 'package:nc_photos/pref.dart';
|
||||||
import 'package:nc_photos/share_handler.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/theme.dart';
|
||||||
import 'package:nc_photos/use_case/remove.dart';
|
|
||||||
import 'package:nc_photos/widget/animated_visibility.dart';
|
import 'package:nc_photos/widget/animated_visibility.dart';
|
||||||
import 'package:nc_photos/widget/disposable.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/horizontal_page_viewer.dart';
|
||||||
import 'package:nc_photos/widget/image_viewer.dart';
|
import 'package:nc_photos/widget/image_viewer.dart';
|
||||||
import 'package:nc_photos/widget/slideshow_dialog.dart';
|
import 'package:nc_photos/widget/slideshow_dialog.dart';
|
||||||
|
@ -460,34 +456,13 @@ class _ViewerState extends State<Viewer>
|
||||||
void _onDeletePressed(BuildContext context) async {
|
void _onDeletePressed(BuildContext context) async {
|
||||||
final file = widget.streamFiles[_viewerController.currentPage];
|
final file = widget.streamFiles[_viewerController.currentPage];
|
||||||
_log.info("[_onDeletePressed] Removing file: ${file.path}");
|
_log.info("[_onDeletePressed] Removing file: ${file.path}");
|
||||||
var controller = SnackBarManager().showSnackBar(SnackBar(
|
final count = await RemoveSelectionHandler()(
|
||||||
content: Text(L10n.global().deleteProcessingNotification),
|
account: widget.account,
|
||||||
duration: k.snackBarDurationShort,
|
selectedFiles: [file],
|
||||||
));
|
isRemoveOpened: true,
|
||||||
controller?.closed.whenComplete(() {
|
);
|
||||||
controller = null;
|
if (count > 0) {
|
||||||
});
|
|
||||||
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,
|
|
||||||
));
|
|
||||||
Navigator.of(context).pop();
|
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