mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-03-22 06:59:21 +01:00
Treat metadata as property
This commit is contained in:
parent
ed9d5de275
commit
a846a51332
8 changed files with 153 additions and 57 deletions
|
@ -89,15 +89,15 @@ class ListAlbumBlocInconsistent extends ListAlbumBlocState {
|
||||||
|
|
||||||
class ListAlbumBloc extends Bloc<ListAlbumBlocEvent, ListAlbumBlocState> {
|
class ListAlbumBloc extends Bloc<ListAlbumBlocEvent, ListAlbumBlocState> {
|
||||||
ListAlbumBloc() : super(ListAlbumBlocInit()) {
|
ListAlbumBloc() : super(ListAlbumBlocInit()) {
|
||||||
_fileMetadataUpdatedListener =
|
_filePropertyUpdatedListener =
|
||||||
AppEventListener<FileMetadataUpdatedEvent>(_onFileMetadataUpdatedEvent);
|
AppEventListener<FilePropertyUpdatedEvent>(_onFilePropertyUpdatedEvent);
|
||||||
_albumUpdatedListener =
|
_albumUpdatedListener =
|
||||||
AppEventListener<AlbumUpdatedEvent>(_onAlbumUpdatedEvent);
|
AppEventListener<AlbumUpdatedEvent>(_onAlbumUpdatedEvent);
|
||||||
_fileRemovedListener =
|
_fileRemovedListener =
|
||||||
AppEventListener<FileRemovedEvent>(_onFileRemovedEvent);
|
AppEventListener<FileRemovedEvent>(_onFileRemovedEvent);
|
||||||
_albumCreatedListener =
|
_albumCreatedListener =
|
||||||
AppEventListener<AlbumCreatedEvent>(_onAlbumCreatedEvent);
|
AppEventListener<AlbumCreatedEvent>(_onAlbumCreatedEvent);
|
||||||
_fileMetadataUpdatedListener.begin();
|
_filePropertyUpdatedListener.begin();
|
||||||
_albumUpdatedListener.begin();
|
_albumUpdatedListener.begin();
|
||||||
_fileRemovedListener.begin();
|
_fileRemovedListener.begin();
|
||||||
_albumCreatedListener.begin();
|
_albumCreatedListener.begin();
|
||||||
|
@ -115,7 +115,7 @@ class ListAlbumBloc extends Bloc<ListAlbumBlocEvent, ListAlbumBlocState> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
close() {
|
close() {
|
||||||
_fileMetadataUpdatedListener.end();
|
_filePropertyUpdatedListener.end();
|
||||||
_albumUpdatedListener.end();
|
_albumUpdatedListener.end();
|
||||||
_fileRemovedListener.end();
|
_fileRemovedListener.end();
|
||||||
_albumCreatedListener.end();
|
_albumCreatedListener.end();
|
||||||
|
@ -160,7 +160,11 @@ class ListAlbumBloc extends Bloc<ListAlbumBlocEvent, ListAlbumBlocState> {
|
||||||
yield ListAlbumBlocInconsistent(state.account, state.albums);
|
yield ListAlbumBlocInconsistent(state.account, state.albums);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onFileMetadataUpdatedEvent(FileMetadataUpdatedEvent ev) {
|
void _onFilePropertyUpdatedEvent(FilePropertyUpdatedEvent ev) {
|
||||||
|
if (!ev.hasAnyProperties([FilePropertyUpdatedEvent.propMetadata])) {
|
||||||
|
// not interested
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (state is ListAlbumBlocInit) {
|
if (state is ListAlbumBlocInit) {
|
||||||
// no data in this bloc, ignore
|
// no data in this bloc, ignore
|
||||||
return;
|
return;
|
||||||
|
@ -220,7 +224,7 @@ class ListAlbumBloc extends Bloc<ListAlbumBlocEvent, ListAlbumBlocState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AppEventListener<FileMetadataUpdatedEvent> _fileMetadataUpdatedListener;
|
AppEventListener<FilePropertyUpdatedEvent> _filePropertyUpdatedListener;
|
||||||
AppEventListener<AlbumUpdatedEvent> _albumUpdatedListener;
|
AppEventListener<AlbumUpdatedEvent> _albumUpdatedListener;
|
||||||
AppEventListener<FileRemovedEvent> _fileRemovedListener;
|
AppEventListener<FileRemovedEvent> _fileRemovedListener;
|
||||||
AppEventListener<AlbumCreatedEvent> _albumCreatedListener;
|
AppEventListener<AlbumCreatedEvent> _albumCreatedListener;
|
||||||
|
|
|
@ -118,10 +118,10 @@ class ScanDirBloc extends Bloc<ScanDirBlocEvent, ScanDirBlocState> {
|
||||||
ScanDirBloc() : super(ScanDirBlocInit()) {
|
ScanDirBloc() : super(ScanDirBlocInit()) {
|
||||||
_fileRemovedEventListener =
|
_fileRemovedEventListener =
|
||||||
AppEventListener<FileRemovedEvent>(_onFileRemovedEvent);
|
AppEventListener<FileRemovedEvent>(_onFileRemovedEvent);
|
||||||
_fileMetadataUpdatedEventListener =
|
_filePropertyUpdatedEventListener =
|
||||||
AppEventListener<FileMetadataUpdatedEvent>(_onFileMetadataUpdatedEvent);
|
AppEventListener<FilePropertyUpdatedEvent>(_onFilePropertyUpdatedEvent);
|
||||||
_fileRemovedEventListener.begin();
|
_fileRemovedEventListener.begin();
|
||||||
_fileMetadataUpdatedEventListener.begin();
|
_filePropertyUpdatedEventListener.begin();
|
||||||
}
|
}
|
||||||
|
|
||||||
static ScanDirBloc of(Account account) {
|
static ScanDirBloc of(Account account) {
|
||||||
|
@ -165,8 +165,8 @@ class ScanDirBloc extends Bloc<ScanDirBlocEvent, ScanDirBlocState> {
|
||||||
@override
|
@override
|
||||||
close() {
|
close() {
|
||||||
_fileRemovedEventListener.end();
|
_fileRemovedEventListener.end();
|
||||||
_fileMetadataUpdatedEventListener.end();
|
_filePropertyUpdatedEventListener.end();
|
||||||
_metadataUpdatedSubscription?.cancel();
|
_propertyUpdatedSubscription?.cancel();
|
||||||
return super.close();
|
return super.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,22 +215,28 @@ class ScanDirBloc extends Bloc<ScanDirBlocEvent, ScanDirBlocState> {
|
||||||
add(_ScanDirBlocExternalEvent());
|
add(_ScanDirBlocExternalEvent());
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onFileMetadataUpdatedEvent(FileMetadataUpdatedEvent ev) {
|
void _onFilePropertyUpdatedEvent(FilePropertyUpdatedEvent ev) {
|
||||||
|
if (!ev.hasAnyProperties([
|
||||||
|
FilePropertyUpdatedEvent.propMetadata,
|
||||||
|
])) {
|
||||||
|
// not interested
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (state is ScanDirBlocInit) {
|
if (state is ScanDirBlocInit) {
|
||||||
// no data in this bloc, ignore
|
// no data in this bloc, ignore
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_successiveMetadataUpdatedCount += 1;
|
_successivePropertyUpdatedCount += 1;
|
||||||
_metadataUpdatedSubscription?.cancel();
|
_propertyUpdatedSubscription?.cancel();
|
||||||
// only trigger the event on the 10th update or 10s after the last update
|
// only trigger the event on the 10th update or 10s after the last update
|
||||||
if (_successiveMetadataUpdatedCount % 10 == 0) {
|
if (_successivePropertyUpdatedCount % 10 == 0) {
|
||||||
add(_ScanDirBlocExternalEvent());
|
add(_ScanDirBlocExternalEvent());
|
||||||
} else {
|
} else {
|
||||||
_metadataUpdatedSubscription =
|
_propertyUpdatedSubscription =
|
||||||
Future.delayed(const Duration(seconds: 10)).asStream().listen((_) {
|
Future.delayed(const Duration(seconds: 10)).asStream().listen((_) {
|
||||||
add(_ScanDirBlocExternalEvent());
|
add(_ScanDirBlocExternalEvent());
|
||||||
_successiveMetadataUpdatedCount = 0;
|
_successivePropertyUpdatedCount = 0;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -267,10 +273,10 @@ class ScanDirBloc extends Bloc<ScanDirBlocEvent, ScanDirBlocState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
AppEventListener<FileRemovedEvent> _fileRemovedEventListener;
|
AppEventListener<FileRemovedEvent> _fileRemovedEventListener;
|
||||||
AppEventListener<FileMetadataUpdatedEvent> _fileMetadataUpdatedEventListener;
|
AppEventListener<FilePropertyUpdatedEvent> _filePropertyUpdatedEventListener;
|
||||||
|
|
||||||
int _successiveMetadataUpdatedCount = 0;
|
int _successivePropertyUpdatedCount = 0;
|
||||||
StreamSubscription<void> _metadataUpdatedSubscription;
|
StreamSubscription<void> _propertyUpdatedSubscription;
|
||||||
|
|
||||||
bool _shouldCheckCache = true;
|
bool _shouldCheckCache = true;
|
||||||
|
|
||||||
|
|
|
@ -366,8 +366,16 @@ class FileRepo {
|
||||||
this.dataSrc.putBinary(account, path, content);
|
this.dataSrc.putBinary(account, path, content);
|
||||||
|
|
||||||
/// See [FileDataSource.updateMetadata]
|
/// See [FileDataSource.updateMetadata]
|
||||||
Future<void> updateMetadata(Account account, File file, Metadata metadata) =>
|
Future<void> updateProperty(
|
||||||
this.dataSrc.updateMetadata(account, file, metadata);
|
Account account,
|
||||||
|
File file, {
|
||||||
|
OrNull<Metadata> metadata,
|
||||||
|
}) =>
|
||||||
|
this.dataSrc.updateProperty(
|
||||||
|
account,
|
||||||
|
file,
|
||||||
|
metadata: metadata,
|
||||||
|
);
|
||||||
|
|
||||||
/// See [FileDataSource.copy]
|
/// See [FileDataSource.copy]
|
||||||
Future<void> copy(
|
Future<void> copy(
|
||||||
|
@ -417,11 +425,12 @@ abstract class FileDataSource {
|
||||||
/// Upload content to [path]
|
/// Upload content to [path]
|
||||||
Future<void> putBinary(Account account, String path, Uint8List content);
|
Future<void> putBinary(Account account, String path, Uint8List content);
|
||||||
|
|
||||||
/// Update metadata for a file
|
/// Update one or more properties of a file
|
||||||
///
|
Future<void> updateProperty(
|
||||||
/// This will completely replace the metadata of the file [f]. Partial update
|
Account account,
|
||||||
/// is not supported
|
File f, {
|
||||||
Future<void> updateMetadata(Account account, File f, Metadata metadata);
|
OrNull<Metadata> metadata,
|
||||||
|
});
|
||||||
|
|
||||||
/// Copy [f] to [destination]
|
/// Copy [f] to [destination]
|
||||||
///
|
///
|
||||||
|
|
|
@ -105,17 +105,22 @@ class FileWebdavDataSource implements FileDataSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
updateMetadata(Account account, File f, Metadata metadata) async {
|
updateProperty(
|
||||||
_log.info("[updateMetadata] ${f.path}");
|
Account account,
|
||||||
if (metadata != null && metadata.fileEtag != f.etag) {
|
File f, {
|
||||||
|
OrNull<Metadata> metadata,
|
||||||
|
}) async {
|
||||||
|
_log.info("[updateProperty] ${f.path}");
|
||||||
|
if (metadata?.obj != null && metadata.obj.fileEtag != f.etag) {
|
||||||
_log.warning(
|
_log.warning(
|
||||||
"[updateMetadata] etag mismatch (metadata: ${metadata.fileEtag}, file: ${f.etag})");
|
"[updateProperty] Metadata etag mismatch (metadata: ${metadata.obj.fileEtag}, file: ${f.etag})");
|
||||||
}
|
}
|
||||||
final setProps = {
|
final setProps = {
|
||||||
if (metadata != null) "app:metadata": jsonEncode(metadata.toJson()),
|
if (metadata?.obj != null)
|
||||||
|
"app:metadata": jsonEncode(metadata.obj.toJson()),
|
||||||
};
|
};
|
||||||
final removeProps = [
|
final removeProps = [
|
||||||
if (metadata == null) "app:metadata",
|
if (OrNull.isNull(metadata)) "app:metadata",
|
||||||
];
|
];
|
||||||
final response = await Api(account).files().proppatch(
|
final response = await Api(account).files().proppatch(
|
||||||
path: f.path,
|
path: f.path,
|
||||||
|
@ -126,7 +131,7 @@ class FileWebdavDataSource implements FileDataSource {
|
||||||
remove: removeProps.isNotEmpty ? removeProps : null,
|
remove: removeProps.isNotEmpty ? removeProps : null,
|
||||||
);
|
);
|
||||||
if (!response.isGood) {
|
if (!response.isGood) {
|
||||||
_log.severe("[updateMetadata] Failed requesting server: $response");
|
_log.severe("[updateProperty] Failed requesting server: $response");
|
||||||
throw ApiException(
|
throw ApiException(
|
||||||
response: response,
|
response: response,
|
||||||
message: "Failed communicating with server: ${response.statusCode}");
|
message: "Failed communicating with server: ${response.statusCode}");
|
||||||
|
@ -236,8 +241,12 @@ class FileAppDbDataSource implements FileDataSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
updateMetadata(Account account, File f, Metadata metadata) {
|
updateProperty(
|
||||||
_log.info("[updateMetadata] ${f.path}");
|
Account account,
|
||||||
|
File f, {
|
||||||
|
OrNull<Metadata> metadata,
|
||||||
|
}) {
|
||||||
|
_log.info("[updateProperty] ${f.path}");
|
||||||
return AppDb.use((db) async {
|
return AppDb.use((db) async {
|
||||||
final transaction = db.transaction(AppDb.fileStoreName, idbModeReadWrite);
|
final transaction = db.transaction(AppDb.fileStoreName, idbModeReadWrite);
|
||||||
final store = transaction.objectStore(AppDb.fileStoreName);
|
final store = transaction.objectStore(AppDb.fileStoreName);
|
||||||
|
@ -245,7 +254,9 @@ class FileAppDbDataSource implements FileDataSource {
|
||||||
final parentList = await _doList(store, account, parentDir);
|
final parentList = await _doList(store, account, parentDir);
|
||||||
final jsonList = parentList.map((e) {
|
final jsonList = parentList.map((e) {
|
||||||
if (e.path == f.path) {
|
if (e.path == f.path) {
|
||||||
return e.copyWith(metadata: OrNull(metadata));
|
return e.copyWith(
|
||||||
|
metadata: metadata,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
@ -369,10 +380,22 @@ class FileCachedDataSource implements FileDataSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
updateMetadata(Account account, File f, Metadata metadata) async {
|
updateProperty(
|
||||||
|
Account account,
|
||||||
|
File f, {
|
||||||
|
OrNull<Metadata> metadata,
|
||||||
|
}) async {
|
||||||
await _remoteSrc
|
await _remoteSrc
|
||||||
.updateMetadata(account, f, metadata)
|
.updateProperty(
|
||||||
.then((_) => _appDbSrc.updateMetadata(account, f, metadata));
|
account,
|
||||||
|
f,
|
||||||
|
metadata: metadata,
|
||||||
|
)
|
||||||
|
.then((_) => _appDbSrc.updateProperty(
|
||||||
|
account,
|
||||||
|
f,
|
||||||
|
metadata: metadata,
|
||||||
|
));
|
||||||
|
|
||||||
// generate a new random token
|
// generate a new random token
|
||||||
final token = Uuid().v4().replaceAll("-", "");
|
final token = Uuid().v4().replaceAll("-", "");
|
||||||
|
|
|
@ -48,11 +48,15 @@ class AlbumUpdatedEvent {
|
||||||
final Album album;
|
final Album album;
|
||||||
}
|
}
|
||||||
|
|
||||||
class FileMetadataUpdatedEvent {
|
class FilePropertyUpdatedEvent {
|
||||||
FileMetadataUpdatedEvent(this.account, this.file);
|
FilePropertyUpdatedEvent(this.account, this.file, this.properties);
|
||||||
|
|
||||||
final Account account;
|
final Account account;
|
||||||
final File file;
|
final File file;
|
||||||
|
final int properties;
|
||||||
|
|
||||||
|
// Bit masks for properties field
|
||||||
|
static const propMetadata = 0x01;
|
||||||
}
|
}
|
||||||
|
|
||||||
class FileRemovedEvent {
|
class FileRemovedEvent {
|
||||||
|
@ -63,3 +67,8 @@ class FileRemovedEvent {
|
||||||
}
|
}
|
||||||
|
|
||||||
class ThemeChangedEvent {}
|
class ThemeChangedEvent {}
|
||||||
|
|
||||||
|
extension FilePropertyUpdatedEventExtension on FilePropertyUpdatedEvent {
|
||||||
|
bool hasAnyProperties(List<int> properties) =>
|
||||||
|
properties.any((p) => this.properties & p != 0);
|
||||||
|
}
|
||||||
|
|
|
@ -2,5 +2,7 @@
|
||||||
class OrNull<T> {
|
class OrNull<T> {
|
||||||
OrNull(this.obj);
|
OrNull(this.obj);
|
||||||
|
|
||||||
|
static bool isNull(OrNull x) => x != null && x.obj == null;
|
||||||
|
|
||||||
final T obj;
|
final T obj;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,8 +7,9 @@ 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/mobile/platform.dart'
|
import 'package:nc_photos/mobile/platform.dart'
|
||||||
if (dart.library.html) 'package:nc_photos/web/platform.dart' as platform;
|
if (dart.library.html) 'package:nc_photos/web/platform.dart' as platform;
|
||||||
|
import 'package:nc_photos/or_null.dart';
|
||||||
import 'package:nc_photos/use_case/scan_missing_metadata.dart';
|
import 'package:nc_photos/use_case/scan_missing_metadata.dart';
|
||||||
import 'package:nc_photos/use_case/update_metadata.dart';
|
import 'package:nc_photos/use_case/update_property.dart';
|
||||||
|
|
||||||
class UpdateMissingMetadata {
|
class UpdateMissingMetadata {
|
||||||
UpdateMissingMetadata(this.fileRepo);
|
UpdateMissingMetadata(this.fileRepo);
|
||||||
|
@ -51,8 +52,13 @@ class UpdateMissingMetadata {
|
||||||
exif: exif,
|
exif: exif,
|
||||||
);
|
);
|
||||||
|
|
||||||
await UpdateMetadata(FileRepo(FileCachedDataSource()),
|
final updateOp = UpdateProperty(FileRepo(FileCachedDataSource()),
|
||||||
AlbumRepo(AlbumCachedDataSource()))(account, file, metadataObj);
|
AlbumRepo(AlbumCachedDataSource()));
|
||||||
|
await updateOp(
|
||||||
|
account,
|
||||||
|
file,
|
||||||
|
metadata: OrNull(metadataObj),
|
||||||
|
);
|
||||||
yield file;
|
yield file;
|
||||||
} catch (e, stacktrace) {
|
} catch (e, stacktrace) {
|
||||||
_log.shout(
|
_log.shout(
|
||||||
|
|
|
@ -9,23 +9,50 @@ import 'package:nc_photos/or_null.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/update_album.dart';
|
||||||
|
|
||||||
class UpdateMetadata {
|
class UpdateProperty {
|
||||||
UpdateMetadata(this.fileRepo, this.albumRepo);
|
UpdateProperty(this.fileRepo, this.albumRepo);
|
||||||
|
|
||||||
Future<void> call(Account account, File file, Metadata metadata) async {
|
Future<void> call(
|
||||||
if (metadata != null && metadata.fileEtag != file.etag) {
|
Account account,
|
||||||
_log.warning(
|
File file, {
|
||||||
"[call] Metadata fileEtag mismatch with actual file's (metadata: ${metadata.fileEtag}, file: ${file.etag})");
|
OrNull<Metadata> metadata,
|
||||||
|
}) async {
|
||||||
|
if (metadata == null) {
|
||||||
|
// ?
|
||||||
|
_log.warning("[call] Nothing to update");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
await fileRepo.updateMetadata(account, file, metadata);
|
|
||||||
await _cleanUpAlbums(account, file, metadata);
|
if (metadata?.obj != null && metadata.obj.fileEtag != file.etag) {
|
||||||
|
_log.warning(
|
||||||
|
"[call] Metadata fileEtag mismatch with actual file's (metadata: ${metadata.obj.fileEtag}, file: ${file.etag})");
|
||||||
|
}
|
||||||
|
await fileRepo.updateProperty(
|
||||||
|
account,
|
||||||
|
file,
|
||||||
|
metadata: metadata,
|
||||||
|
);
|
||||||
|
await _cleanUpAlbums(
|
||||||
|
account,
|
||||||
|
file,
|
||||||
|
metadata: metadata,
|
||||||
|
);
|
||||||
|
|
||||||
|
int properties = 0;
|
||||||
|
if (metadata != null) {
|
||||||
|
properties |= FilePropertyUpdatedEvent.propMetadata;
|
||||||
|
}
|
||||||
|
assert(properties != 0);
|
||||||
KiwiContainer()
|
KiwiContainer()
|
||||||
.resolve<EventBus>()
|
.resolve<EventBus>()
|
||||||
.fire(FileMetadataUpdatedEvent(account, file));
|
.fire(FilePropertyUpdatedEvent(account, file, properties));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _cleanUpAlbums(
|
Future<void> _cleanUpAlbums(
|
||||||
Account account, File file, Metadata metadata) async {
|
Account account,
|
||||||
|
File file, {
|
||||||
|
OrNull<Metadata> metadata,
|
||||||
|
}) async {
|
||||||
final albums = await ListAlbum(fileRepo, albumRepo)(account);
|
final albums = await ListAlbum(fileRepo, albumRepo)(account);
|
||||||
for (final a in albums) {
|
for (final a in albums) {
|
||||||
try {
|
try {
|
||||||
|
@ -34,7 +61,9 @@ class UpdateMetadata {
|
||||||
final newItems = a.items.map((e) {
|
final newItems = a.items.map((e) {
|
||||||
if (e is AlbumFileItem && e.file.path == file.path) {
|
if (e is AlbumFileItem && e.file.path == file.path) {
|
||||||
return AlbumFileItem(
|
return AlbumFileItem(
|
||||||
file: e.file.copyWith(metadata: OrNull(metadata)),
|
file: e.file.copyWith(
|
||||||
|
metadata: metadata,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return e;
|
return e;
|
||||||
|
@ -53,5 +82,13 @@ class UpdateMetadata {
|
||||||
final FileRepo fileRepo;
|
final FileRepo fileRepo;
|
||||||
final AlbumRepo albumRepo;
|
final AlbumRepo albumRepo;
|
||||||
|
|
||||||
static final _log = Logger("use_case.update_metadata.UpdateMetadata");
|
static final _log = Logger("use_case.update_property.UpdateProperty");
|
||||||
|
}
|
||||||
|
|
||||||
|
extension UpdatePropertyExtension on UpdateProperty {
|
||||||
|
/// Convenience function to only update metadata
|
||||||
|
///
|
||||||
|
/// See [UpdateProperty.call]
|
||||||
|
Future<void> updateMetadata(Account account, File file, Metadata metadata) =>
|
||||||
|
call(account, file, metadata: OrNull(metadata));
|
||||||
}
|
}
|
Loading…
Reference in a new issue