785 lines
20 KiB
Dart
785 lines
20 KiB
Dart
// lib/model/source/media_store_source.dart
|
|
import 'dart:async';
|
|
|
|
import 'package:aves/model/covers.dart';
|
|
import 'package:aves/model/dynamic_albums.dart';
|
|
import 'package:aves/model/entry/entry.dart';
|
|
import 'package:aves/model/entry/origins.dart';
|
|
import 'package:aves/model/favourites.dart';
|
|
import 'package:aves/model/filters/covered/stored_album.dart';
|
|
import 'package:aves/model/grouping/common.dart';
|
|
import 'package:aves/model/settings/settings.dart';
|
|
import 'package:aves/model/source/analysis_controller.dart';
|
|
import 'package:aves/model/source/collection_source.dart';
|
|
import 'package:aves/model/vaults/vaults.dart';
|
|
import 'package:aves/services/common/services.dart';
|
|
import 'package:aves/theme/durations.dart';
|
|
import 'package:aves/utils/android_file_utils.dart';
|
|
import 'package:aves/utils/debouncer.dart';
|
|
import 'package:aves_model/aves_model.dart';
|
|
import 'package:collection/collection.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:aves/remote/remote_origin.dart';
|
|
|
|
// Temporaneo: origine remota come nel tuo corrente.
|
|
|
|
class MediaStoreSource extends CollectionSource {
|
|
final Debouncer _changeDebouncer = Debouncer(
|
|
delay: ADurations.mediaContentChangeDebounceDelay,
|
|
);
|
|
|
|
final Set<String> _changedUris = {};
|
|
|
|
int? _lastGeneration;
|
|
|
|
SourceScope _loadedScope, _targetScope;
|
|
|
|
bool _canAnalyze = true;
|
|
|
|
// Evita caricamenti paralleli di _loadEntries.
|
|
bool _loadingEntries = false;
|
|
|
|
@override
|
|
set canAnalyze(bool enabled) => _canAnalyze = enabled;
|
|
|
|
@override
|
|
SourceScope get loadedScope => _loadedScope;
|
|
|
|
@override
|
|
SourceScope get targetScope => _targetScope;
|
|
|
|
@override
|
|
Future<void> init({
|
|
required SourceScope scope,
|
|
AnalysisController? analysisController,
|
|
bool loadTopEntriesFirst = false,
|
|
}) async {
|
|
_targetScope = scope;
|
|
|
|
await reportService.log('$runtimeType init target scope=$scope');
|
|
|
|
await _loadEssentials();
|
|
|
|
addDirectories(
|
|
albums: settings.pinnedFilters
|
|
.whereType<StoredAlbumFilter>()
|
|
.map((v) => v.album)
|
|
.toSet(),
|
|
);
|
|
|
|
await updateGeneration();
|
|
|
|
unawaited(
|
|
_loadEntries(
|
|
analysisController: analysisController,
|
|
loadTopEntriesFirst: loadTopEntriesFirst,
|
|
),
|
|
);
|
|
}
|
|
|
|
Future<void> _loadEssentials() async {
|
|
final stopwatch = Stopwatch()..start();
|
|
|
|
state = SourceState.loading;
|
|
|
|
await localMediaDb.init();
|
|
|
|
debugPrint(
|
|
'[MediaStoreSource] localMediaDb instance hashCode after init = ${localMediaDb.hashCode}',
|
|
);
|
|
|
|
await vaults.init();
|
|
await favourites.init();
|
|
|
|
albumGrouping.init();
|
|
albumGrouping.setGroups(settings.albumGroups);
|
|
albumGrouping.registerSource(this);
|
|
|
|
tagGrouping.init();
|
|
tagGrouping.setGroups(settings.tagGroups);
|
|
tagGrouping.registerSource(this);
|
|
|
|
await covers.init();
|
|
await dynamicAlbums.init();
|
|
|
|
final deviceOffset = DateTime.now().timeZoneOffset.inMilliseconds;
|
|
final catalogOffset = settings.catalogTimeZoneOffsetMillis;
|
|
|
|
if (deviceOffset != catalogOffset) {
|
|
unawaited(
|
|
reportService.log(
|
|
'Time zone offset change: $catalogOffset -> $deviceOffset. '
|
|
'Clear catalog metadata to get correct date/times.',
|
|
),
|
|
);
|
|
|
|
await localMediaDb.clearDates();
|
|
await localMediaDb.clearCatalogMetadata();
|
|
|
|
settings.catalogTimeZoneOffsetMillis = deviceOffset;
|
|
}
|
|
|
|
debugPrint('[MediaStoreSource] calling loadDates()...');
|
|
await loadDates();
|
|
debugPrint(
|
|
'[MediaStoreSource] localMediaDb instance hashCode before loadDates = ${localMediaDb.hashCode}',
|
|
);
|
|
debugPrint(
|
|
'[MediaStoreSource] BEFORE loadDates: rawDb is null? ${localMediaDb.rawDb == null}',
|
|
);
|
|
debugPrint('[MediaStoreSource] loadDates() DONE');
|
|
|
|
debugPrint(
|
|
'$runtimeType load essentials complete in ${stopwatch.elapsed.inMilliseconds}ms',
|
|
);
|
|
}
|
|
|
|
Future<void> _loadEntries({
|
|
AnalysisController? analysisController,
|
|
required bool loadTopEntriesFirst,
|
|
}) async {
|
|
if (_loadingEntries) {
|
|
debugPrint('[MediaStoreSource] _loadEntries skipped: already running');
|
|
return;
|
|
}
|
|
|
|
_loadingEntries = true;
|
|
|
|
try {
|
|
unawaited(reportService.log('$runtimeType load (known) start'));
|
|
|
|
final stopwatch = Stopwatch()..start();
|
|
|
|
state = SourceState.loading;
|
|
|
|
// Come Aves originale: pulizia solo in memoria, NON nel DB.
|
|
clearEntries();
|
|
|
|
final scopeAlbumFilters = _targetScope?.whereType<StoredAlbumFilter>();
|
|
|
|
final scopeDirectory =
|
|
scopeAlbumFilters != null && scopeAlbumFilters.length == 1
|
|
? scopeAlbumFilters.first.album
|
|
: null;
|
|
|
|
final topEntries = <AvesEntry>{};
|
|
|
|
if (loadTopEntriesFirst) {
|
|
final topIds = settings.topEntryIds?.toSet();
|
|
|
|
if (topIds != null) {
|
|
debugPrint(
|
|
'$runtimeType load ${stopwatch.elapsed} load ${topIds.length} top entries',
|
|
);
|
|
|
|
topEntries.addAll(await localMediaDb.loadEntriesById(topIds));
|
|
|
|
|
|
|
|
await replaceEntriesInMemory(
|
|
topEntries,
|
|
reason: 'loadTopEntriesInMemory',
|
|
notify: true,
|
|
updateFolders: false,
|
|
);
|
|
|
|
|
|
}
|
|
}
|
|
|
|
debugPrint('$runtimeType load ${stopwatch.elapsed} fetch known entries');
|
|
|
|
final knownEntries = await localMediaDb.loadEntries(
|
|
origin: EntryOrigins.mediaStoreContent,
|
|
directory: scopeDirectory,
|
|
);
|
|
|
|
final knownLiveEntries =
|
|
knownEntries.where((entry) => !entry.trashed).toSet();
|
|
|
|
final isLargeCollection = knownEntries.length > 80000;
|
|
|
|
if (isLargeCollection && settings.isErrorReportingAllowed) {
|
|
settings.isErrorReportingAllowed = false;
|
|
}
|
|
|
|
unawaited(
|
|
reportService.setCustomKey('is_large_collection', isLargeCollection),
|
|
);
|
|
|
|
unawaited(
|
|
reportService.log('$runtimeType found ${knownEntries.length} known entries'),
|
|
);
|
|
|
|
debugPrint('$runtimeType load ${stopwatch.elapsed} check obsolete entries');
|
|
|
|
final knownDateByContentId = Map.fromEntries(
|
|
knownLiveEntries.map(
|
|
(entry) => MapEntry(
|
|
entry.contentId,
|
|
entry.dateModifiedMillis,
|
|
),
|
|
),
|
|
);
|
|
|
|
final knownContentIds = knownDateByContentId.keys.toList();
|
|
|
|
final removedContentIds =
|
|
(await mediaStoreService.checkObsoleteContentIds(knownContentIds))
|
|
.toSet();
|
|
|
|
debugPrint(
|
|
'[media_store_source] removedContentIds count=${removedContentIds.length} '
|
|
'sample=${removedContentIds.take(10)}',
|
|
);
|
|
|
|
if (topEntries.isNotEmpty) {
|
|
final removedTopEntries = topEntries.where(
|
|
(entry) => removedContentIds.contains(entry.contentId),
|
|
);
|
|
|
|
await removeEntries(
|
|
removedTopEntries.map((entry) => entry.uri).toSet(),
|
|
includeTrash: false,
|
|
);
|
|
}
|
|
|
|
final removedEntries = knownEntries
|
|
.where((entry) => removedContentIds.contains(entry.contentId))
|
|
.toSet();
|
|
|
|
knownEntries.removeAll(removedEntries);
|
|
|
|
// Rimuovi solo locali, non remote.
|
|
final localRemovedEntries = removedEntries
|
|
.where((entry) => entry.origin != RemoteOrigin.value)
|
|
.toSet();
|
|
|
|
debugPrint(
|
|
'[media_store_source] removedEntries total=${removedEntries.length} '
|
|
'localRemovedEntries=${localRemovedEntries.length}',
|
|
);
|
|
|
|
if (localRemovedEntries.isNotEmpty) {
|
|
await localMediaDb.removeIds(
|
|
localRemovedEntries.map((entry) => entry.id).toSet(),
|
|
);
|
|
}
|
|
|
|
// Show known entries.
|
|
debugPrint('$runtimeType load ${stopwatch.elapsed} load known entries in memory');
|
|
|
|
|
|
await replaceEntriesInMemory(
|
|
knownEntries,
|
|
reason: 'loadKnownEntriesInMemory',
|
|
notify: false,
|
|
updateFolders: true,
|
|
);
|
|
|
|
|
|
await _loadVaultEntries(scopeDirectory);
|
|
|
|
debugPrint('$runtimeType load ${stopwatch.elapsed} load metadata');
|
|
|
|
if (scopeDirectory != null) {
|
|
final ids = knownLiveEntries.map((entry) => entry.id).toSet();
|
|
|
|
await loadCatalogMetadata(ids: ids);
|
|
await loadAddresses(ids: ids);
|
|
} else {
|
|
await loadCatalogMetadata();
|
|
await loadAddresses();
|
|
|
|
await loadTrashDetails();
|
|
|
|
unawaited(
|
|
deleteExpiredTrash().then(
|
|
(deletedUris) {
|
|
if (deletedUris.isNotEmpty) {
|
|
debugPrint(
|
|
'evicted ${deletedUris.length} expired items from the trash',
|
|
);
|
|
|
|
removeEntries(
|
|
deletedUris,
|
|
includeTrash: true,
|
|
);
|
|
}
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
updateDerivedFilters();
|
|
|
|
_loadedScope = _targetScope;
|
|
|
|
if (_canAnalyze) {
|
|
await _loadNewEntries(
|
|
analysisController: analysisController,
|
|
directory: scopeDirectory,
|
|
knownLiveEntries: knownLiveEntries,
|
|
knownDateByContentId: knownDateByContentId,
|
|
);
|
|
} else {
|
|
state = SourceState.ready;
|
|
}
|
|
} finally {
|
|
_loadingEntries = false;
|
|
}
|
|
}
|
|
|
|
Future<void> _loadNewEntries({
|
|
required AnalysisController? analysisController,
|
|
required String? directory,
|
|
required Set<AvesEntry> knownLiveEntries,
|
|
required Map<int?, int?> knownDateByContentId,
|
|
}) async {
|
|
unawaited(reportService.log('$runtimeType load (new) start'));
|
|
|
|
final stopwatch = Stopwatch()..start();
|
|
|
|
final newEntries = <AvesEntry>{};
|
|
final folderSet = <String>{};
|
|
|
|
if (directory == null) {
|
|
newEntries.addAll(await recoverUntrackedTrashItems());
|
|
}
|
|
|
|
debugPrint('$runtimeType load ${stopwatch.elapsed} check obsolete paths');
|
|
|
|
// Escludi remote.
|
|
final knownPathByContentId = Map.fromEntries(
|
|
knownLiveEntries
|
|
.where((entry) => entry.origin != RemoteOrigin.value)
|
|
.map(
|
|
(entry) => MapEntry(
|
|
entry.contentId,
|
|
entry.path,
|
|
),
|
|
),
|
|
);
|
|
|
|
final movedContentIds =
|
|
(await mediaStoreService.checkObsoletePaths(knownPathByContentId))
|
|
.toSet();
|
|
|
|
for (final contentId in movedContentIds) {
|
|
knownDateByContentId[contentId] = 0;
|
|
}
|
|
|
|
debugPrint('$runtimeType load ${stopwatch.elapsed} fetch new entries');
|
|
|
|
final knownContentIds = knownDateByContentId.keys.toSet();
|
|
|
|
final completer = Completer<void>();
|
|
|
|
late final StreamSubscription<AvesEntry> subscription;
|
|
|
|
subscription = mediaStoreService
|
|
.getEntries(
|
|
knownDateByContentId,
|
|
directory: directory,
|
|
)
|
|
.listen(
|
|
(entry) {
|
|
final dir = entry.directory;
|
|
|
|
if (dir != null) {
|
|
folderSet.add(dir);
|
|
}
|
|
|
|
final contentId = entry.contentId;
|
|
|
|
final existingEntry = knownContentIds.contains(contentId)
|
|
? knownLiveEntries.firstWhereOrNull(
|
|
(entry) => entry.contentId == contentId,
|
|
)
|
|
: null;
|
|
|
|
entry.id = existingEntry?.id ?? localMediaDb.nextId;
|
|
|
|
newEntries.add(entry);
|
|
|
|
setProgress(
|
|
done: newEntries.length,
|
|
total: 0,
|
|
);
|
|
},
|
|
onError: (Object e, StackTrace st) async {
|
|
await subscription.cancel();
|
|
|
|
if (!completer.isCompleted) {
|
|
completer.completeError(e, st);
|
|
}
|
|
},
|
|
onDone: () async {
|
|
try {
|
|
if (newEntries.isNotEmpty) {
|
|
await localMediaDb.insertEntries(newEntries);
|
|
|
|
final duplicates = await localMediaDb.searchLiveDuplicates(
|
|
EntryOrigins.mediaStoreContent,
|
|
newEntries,
|
|
);
|
|
|
|
if (duplicates.isNotEmpty) {
|
|
await localMediaDb.removeIds(
|
|
duplicates.map((v) => v.id).toSet(),
|
|
);
|
|
|
|
for (final duplicate in duplicates) {
|
|
final duplicateId = duplicate.id;
|
|
newEntries.removeWhere((v) => v.id == duplicateId);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
await mergeEntriesInMemory(
|
|
newEntries,
|
|
reason: 'loadNewEntriesInMemory',
|
|
notify: true,
|
|
updateFolders: true,
|
|
);
|
|
|
|
|
|
|
|
invalidateAlbumFilterSummary();
|
|
}
|
|
|
|
Set<AvesEntry>? analysisEntries;
|
|
|
|
final analysisIds = analysisController?.entryIds;
|
|
|
|
if (analysisIds != null) {
|
|
analysisEntries = allEntries
|
|
.where((entry) => analysisIds.contains(entry.id))
|
|
.toSet();
|
|
}
|
|
|
|
await analyze(
|
|
analysisController,
|
|
entries: analysisEntries,
|
|
);
|
|
|
|
// Comportamento incrementale, non distruttivo.
|
|
if (folderSet.isEmpty) {
|
|
debugPrint('[folders] Nessuna nuova cartella → DB invariato');
|
|
state = SourceState.ready;
|
|
|
|
if (!completer.isCompleted) {
|
|
completer.complete();
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
final existing = await localMediaDb.getLocalFolderPaths();
|
|
final newOnes = folderSet.difference(existing.toSet());
|
|
|
|
if (newOnes.isEmpty) {
|
|
debugPrint('[folders] Nessuna cartella nuova → DB invariato');
|
|
state = SourceState.ready;
|
|
|
|
if (!completer.isCompleted) {
|
|
completer.complete();
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
debugPrint('[folders] Aggiungo ${newOnes.length} nuove cartelle');
|
|
|
|
await updateDirectories();
|
|
notifyAlbumsChanged();
|
|
|
|
state = SourceState.ready;
|
|
|
|
if (!completer.isCompleted) {
|
|
completer.complete();
|
|
}
|
|
} catch (e, st) {
|
|
if (!completer.isCompleted) {
|
|
completer.completeError(e, st);
|
|
}
|
|
}
|
|
},
|
|
cancelOnError: true,
|
|
);
|
|
|
|
await completer.future;
|
|
}
|
|
|
|
@override
|
|
Future<Set<String>> refreshUris(
|
|
Set<String> changedUris, {
|
|
AnalysisController? analysisController,
|
|
}) async {
|
|
if (!canRefresh || !isReady) return changedUris;
|
|
|
|
state = SourceState.loading;
|
|
|
|
final changedUriByContentId = Map.fromEntries(
|
|
changedUris.map((uri) {
|
|
final pathSegments = Uri.parse(uri).pathSegments;
|
|
|
|
if (pathSegments.isEmpty) return null;
|
|
|
|
final idString = pathSegments.last;
|
|
final contentId = int.tryParse(idString);
|
|
|
|
if (contentId == null) return null;
|
|
|
|
return MapEntry(contentId, uri);
|
|
}).nonNulls,
|
|
);
|
|
|
|
final obsoleteContentIds =
|
|
(await mediaStoreService.checkObsoleteContentIds(
|
|
changedUriByContentId.keys.toList(),
|
|
))
|
|
.toSet();
|
|
|
|
final obsoleteUris = obsoleteContentIds
|
|
.map((contentId) => changedUriByContentId[contentId])
|
|
.nonNulls
|
|
.toSet();
|
|
|
|
// Rimuovi solo locali.
|
|
final localObsoleteUris = <String>{};
|
|
|
|
for (final uri in obsoleteUris) {
|
|
final existingEntry = allEntries.firstWhereOrNull(
|
|
(entry) => entry.uri == uri,
|
|
);
|
|
|
|
if (existingEntry == null) {
|
|
final sourceEntry = await mediaFetchService.getEntry(
|
|
uri,
|
|
null,
|
|
allowUnsized: true,
|
|
);
|
|
|
|
if (sourceEntry != null && sourceEntry.origin != RemoteOrigin.value) {
|
|
localObsoleteUris.add(uri);
|
|
}
|
|
} else {
|
|
if (existingEntry.origin != RemoteOrigin.value) {
|
|
localObsoleteUris.add(uri);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (localObsoleteUris.isNotEmpty) {
|
|
await removeEntries(
|
|
localObsoleteUris,
|
|
includeTrash: false,
|
|
);
|
|
}
|
|
|
|
for (final contentId in obsoleteContentIds) {
|
|
changedUriByContentId.remove(contentId);
|
|
}
|
|
|
|
final tempUris = <String>{};
|
|
final newEntries = <AvesEntry>{};
|
|
final entriesToRefresh = <AvesEntry>{};
|
|
final existingDirectories = <String>{};
|
|
|
|
for (final kv in changedUriByContentId.entries) {
|
|
final contentId = kv.key;
|
|
final uri = kv.value;
|
|
|
|
final sourceEntry = await mediaFetchService.getEntry(
|
|
uri,
|
|
null,
|
|
);
|
|
|
|
if (sourceEntry != null) {
|
|
final existingEntry = allEntries.firstWhereOrNull(
|
|
(entry) => entry.contentId == contentId,
|
|
);
|
|
|
|
if (existingEntry == null ||
|
|
(sourceEntry.dateModifiedMillis ?? 0) >
|
|
(existingEntry.dateModifiedMillis ?? 0) ||
|
|
sourceEntry.path != existingEntry.path) {
|
|
final newPath = sourceEntry.path;
|
|
final volume =
|
|
newPath != null ? androidFileUtils.getStorageVolume(newPath) : null;
|
|
|
|
if (volume != null) {
|
|
if (existingEntry != null) {
|
|
entriesToRefresh.add(existingEntry);
|
|
} else if (_canAnalyze) {
|
|
sourceEntry.id = localMediaDb.nextId;
|
|
newEntries.add(sourceEntry);
|
|
}
|
|
|
|
final existingDirectory = existingEntry?.directory;
|
|
|
|
if (existingDirectory != null) {
|
|
existingDirectories.add(existingDirectory);
|
|
}
|
|
} else {
|
|
tempUris.add(uri);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
await _refreshVaultEntries(
|
|
changedUris: changedUris.where(vaults.isVaultEntryUri).toSet(),
|
|
newEntries: newEntries,
|
|
entriesToRefresh: entriesToRefresh,
|
|
existingDirectories: existingDirectories,
|
|
);
|
|
|
|
invalidateAlbumFilterSummary(
|
|
directories: existingDirectories,
|
|
);
|
|
|
|
if (newEntries.isNotEmpty) {
|
|
await localMediaDb.insertEntries(newEntries);
|
|
|
|
final duplicates = await localMediaDb.searchLiveDuplicates(
|
|
EntryOrigins.mediaStoreContent,
|
|
newEntries,
|
|
);
|
|
|
|
if (duplicates.isNotEmpty) {
|
|
await localMediaDb.removeIds(
|
|
duplicates.map((v) => v.id).toSet(),
|
|
);
|
|
|
|
for (final duplicate in duplicates) {
|
|
final duplicateId = duplicate.id;
|
|
newEntries.removeWhere((v) => v.id == duplicateId);
|
|
tempUris.add(duplicate.uri);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
await mergeEntriesInMemory(
|
|
newEntries,
|
|
reason: 'refreshUrisNewEntriesInMemory',
|
|
notify: true,
|
|
updateFolders: true,
|
|
);
|
|
|
|
|
|
await analyze(
|
|
analysisController,
|
|
entries: newEntries,
|
|
);
|
|
}
|
|
|
|
if (entriesToRefresh.isNotEmpty) {
|
|
await refreshEntries(
|
|
entriesToRefresh,
|
|
EntryDataType.values.toSet(),
|
|
);
|
|
}
|
|
|
|
state = SourceState.ready;
|
|
|
|
cleanEmptyAlbums();
|
|
|
|
await updateDirectories();
|
|
notifyAlbumsChanged();
|
|
|
|
return tempUris;
|
|
}
|
|
|
|
void onStoreChanged(String? uri) {
|
|
if (uri != null) _changedUris.add(uri);
|
|
|
|
if (_changedUris.isNotEmpty) {
|
|
_changeDebouncer(() async {
|
|
final todo = _changedUris.toSet();
|
|
|
|
_changedUris.clear();
|
|
|
|
final tempUris = await refreshUris(todo);
|
|
|
|
if (tempUris.isNotEmpty) {
|
|
_changedUris.addAll(tempUris);
|
|
onStoreChanged(null);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
Future<void> checkForChanges() async {
|
|
final sinceGeneration = _lastGeneration;
|
|
|
|
if (sinceGeneration != null) {
|
|
_changedUris.addAll(
|
|
await mediaStoreService.getChangedUris(sinceGeneration),
|
|
);
|
|
|
|
onStoreChanged(null);
|
|
}
|
|
|
|
await updateGeneration();
|
|
}
|
|
|
|
Future<void> updateGeneration() async {
|
|
_lastGeneration = await mediaStoreService.getGeneration();
|
|
}
|
|
|
|
Future<void> _loadVaultEntries(String? directory) async {
|
|
final entries = await localMediaDb.loadEntries(
|
|
origin: EntryOrigins.vault,
|
|
directory: directory,
|
|
);
|
|
|
|
await mergeEntriesInMemory(
|
|
entries,
|
|
reason: 'loadVaultEntriesInMemory',
|
|
notify: false,
|
|
updateFolders: true,
|
|
);
|
|
}
|
|
|
|
Future<void> _refreshVaultEntries({
|
|
required Set<String> changedUris,
|
|
required Set<AvesEntry> newEntries,
|
|
required Set<AvesEntry> entriesToRefresh,
|
|
required Set<String> existingDirectories,
|
|
}) async {
|
|
for (final uri in changedUris) {
|
|
final existingEntry = allEntries.firstWhereOrNull(
|
|
(entry) => entry.uri == uri,
|
|
);
|
|
|
|
if (existingEntry != null) {
|
|
entriesToRefresh.add(existingEntry);
|
|
|
|
final existingDirectory = existingEntry.directory;
|
|
|
|
if (existingDirectory != null) {
|
|
existingDirectories.add(existingDirectory);
|
|
}
|
|
} else {
|
|
final sourceEntry = await mediaFetchService.getEntry(
|
|
uri,
|
|
null,
|
|
allowUnsized: true,
|
|
);
|
|
|
|
if (sourceEntry != null) {
|
|
newEntries.add(
|
|
sourceEntry.copyWith(
|
|
id: localMediaDb.nextId,
|
|
origin: EntryOrigins.vault,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|