86 lines
3.4 KiB
Dart
86 lines
3.4 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:aves/model/entry/entry.dart';
|
|
import 'package:aves/model/entry/extensions/props.dart';
|
|
import 'package:aves/model/metadata/trash.dart';
|
|
import 'package:aves/model/source/collection_source.dart';
|
|
import 'package:aves/services/common/image_op_events.dart';
|
|
import 'package:aves/services/common/services.dart';
|
|
import 'package:aves/utils/android_file_utils.dart';
|
|
import 'package:collection/collection.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
|
|
mixin TrashMixin on SourceBase {
|
|
static const Duration binKeepDuration = Duration(days: 30);
|
|
|
|
Future<void> loadTrashDetails() async {
|
|
final saved = await metadataDb.loadAllTrashDetails();
|
|
final idMap = entryById;
|
|
saved.forEach((details) => idMap[details.id]?.trashDetails = details);
|
|
}
|
|
|
|
Future<Set<String>> deleteExpiredTrash() async {
|
|
final expiredEntries = trashedEntries.where((entry) => entry.isExpiredTrash).toSet();
|
|
if (expiredEntries.isEmpty) return {};
|
|
|
|
final processed = <ImageOpEvent>{};
|
|
final completer = Completer<Set<String>>();
|
|
mediaEditService.delete(entries: expiredEntries).listen(
|
|
processed.add,
|
|
onError: completer.completeError,
|
|
onDone: () async {
|
|
final successOps = processed.where((e) => e.success).toSet();
|
|
final deletedOps = successOps.where((e) => !e.skipped).toSet();
|
|
final deletedUris = deletedOps.map((event) => event.uri).toSet();
|
|
completer.complete(deletedUris);
|
|
},
|
|
);
|
|
return await completer.future;
|
|
}
|
|
|
|
Future<Set<AvesEntry>> recoverUntrackedTrashItems() async {
|
|
final newEntries = <AvesEntry>{};
|
|
|
|
final knownPaths = allEntries.map((v) => v.trashDetails?.path).whereNotNull().toSet();
|
|
final untrackedPaths = await storageService.getUntrackedTrashPaths(knownPaths);
|
|
if (untrackedPaths.isNotEmpty) {
|
|
debugPrint('Recovering ${untrackedPaths.length} untracked bin items');
|
|
final recoveryPath = pContext.join(androidFileUtils.picturesPath, AndroidFileUtils.recoveryDir);
|
|
await Future.forEach(untrackedPaths, (untrackedPath) async {
|
|
TrashDetails _buildTrashDetails(int id) => TrashDetails(
|
|
id: id,
|
|
path: untrackedPath,
|
|
dateMillis: DateTime.now().millisecondsSinceEpoch,
|
|
);
|
|
|
|
final uri = Uri.file(untrackedPath).toString();
|
|
final entry = allEntries.firstWhereOrNull((v) => v.uri == uri);
|
|
if (entry != null) {
|
|
// there is already a matching entry
|
|
// but missing trash details, and possibly not marked as trash
|
|
final id = entry.id;
|
|
entry.contentId = null;
|
|
entry.trashed = true;
|
|
entry.trashDetails = _buildTrashDetails(id);
|
|
// persist
|
|
await metadataDb.updateEntry(id, entry);
|
|
await metadataDb.updateTrash(id, entry.trashDetails);
|
|
} else {
|
|
// there is no matching entry
|
|
final sourceEntry = await mediaFetchService.getEntry(uri, null);
|
|
if (sourceEntry != null) {
|
|
final id = metadataDb.nextId;
|
|
sourceEntry.id = id;
|
|
sourceEntry.path = pContext.join(recoveryPath, pContext.basename(untrackedPath));
|
|
sourceEntry.trashed = true;
|
|
sourceEntry.trashDetails = _buildTrashDetails(id);
|
|
newEntries.add(sourceEntry);
|
|
} else {
|
|
await reportService.recordError('Failed to recover untracked bin item at uri=$uri', null);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
return newEntries;
|
|
}
|
|
}
|