mirror of
https://github.com/immich-app/immich
synced 2026-08-15 13:03:57 +00:00
fix(mobile): resolve view intents from remote checksum matches
Use cached remote assets when the local Drift record is unavailable, preferring active and most recently uploaded matches.
This commit is contained in:
parent
f90359655b
commit
595a3a7f28
2 changed files with 52 additions and 7 deletions
|
|
@ -55,7 +55,11 @@ class ViewIntentAssetResolver {
|
|||
({LocalAsset? asset, String? checksum}) resolvedLocal = (asset: null, checksum: null);
|
||||
if (localAssetId != null) {
|
||||
resolvedLocal = await _resolveLocalAsset(localAssetId);
|
||||
final remoteAsset = await _resolveRemoteAsset(localAssetId, resolvedLocal.asset?.remoteId);
|
||||
final remoteAsset = await _resolveRemoteAsset(
|
||||
localAssetId,
|
||||
remoteAssetId: resolvedLocal.asset?.remoteId,
|
||||
checksum: resolvedLocal.checksum,
|
||||
);
|
||||
if (remoteAsset != null) {
|
||||
return ViewIntentResolvedAsset(asset: remoteAsset, timelineService: _timelineFor(remoteAsset));
|
||||
}
|
||||
|
|
@ -111,21 +115,45 @@ class ViewIntentAssetResolver {
|
|||
}
|
||||
}
|
||||
|
||||
Future<RemoteAsset?> _resolveRemoteAsset(String localAssetId, String? remoteAssetId) async {
|
||||
if (remoteAssetId == null) {
|
||||
return null;
|
||||
Future<RemoteAsset?> _resolveRemoteAsset(
|
||||
String localAssetId, {
|
||||
required String? remoteAssetId,
|
||||
required String? checksum,
|
||||
}) async {
|
||||
RemoteAsset? remoteAsset;
|
||||
if (remoteAssetId != null) {
|
||||
remoteAsset = await _assetService.getRemoteAsset(remoteAssetId);
|
||||
if (remoteAsset != null) {
|
||||
_logger.fine('resolve matched remote asset by id: $remoteAssetId, asset=$remoteAsset');
|
||||
}
|
||||
}
|
||||
|
||||
if (remoteAsset == null && checksum != null) {
|
||||
final candidates = await _assetService.getAllRemoteAssetDebugByChecksum(checksum);
|
||||
if (candidates.isNotEmpty) {
|
||||
remoteAsset = ([...candidates]..sort(_compareRemoteAssetCandidates)).first;
|
||||
_logger.fine('resolve matched remote asset by checksum: $checksum, asset=$remoteAsset');
|
||||
}
|
||||
}
|
||||
|
||||
final remoteAsset = await _assetService.getRemoteAsset(remoteAssetId);
|
||||
if (remoteAsset == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final asset = remoteAsset.copyWith(localId: localAssetId);
|
||||
_logger.fine('resolve matched remote asset by id: $remoteAssetId, asset=$asset');
|
||||
return asset;
|
||||
}
|
||||
|
||||
static int _compareRemoteAssetCandidates(RemoteAsset first, RemoteAsset second) {
|
||||
if (first.isTrashed != second.isTrashed) {
|
||||
return first.isTrashed ? 1 : -1;
|
||||
}
|
||||
|
||||
final firstDate = first.uploadedAt ?? first.createdAt;
|
||||
final secondDate = second.uploadedAt ?? second.createdAt;
|
||||
final date = secondDate.compareTo(firstDate);
|
||||
return date != 0 ? date : first.id.compareTo(second.id);
|
||||
}
|
||||
|
||||
LocalAsset _toTransientAsset(ViewIntentPayload attachment, String? checksum) {
|
||||
final now = DateTime.now();
|
||||
// A FileBackedAsset could model the path more explicitly, but would require broader changes to the asset hierarchy.
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ void main() {
|
|||
|
||||
when(() => mockLocalAssetRepository.get(any())).thenAnswer((_) async => null);
|
||||
when(() => assetService.getRemoteAsset(any())).thenAnswer((_) async => null);
|
||||
when(() => assetService.getAllRemoteAssetDebugByChecksum(any())).thenAnswer((_) async => const []);
|
||||
when(() => nativeSyncApi.hashAssets(any())).thenAnswer((_) async => const []);
|
||||
when(() => mockLocalAssetRepository.updateHashes(any())).thenAnswer((_) async {});
|
||||
|
||||
|
|
@ -142,6 +143,22 @@ void main() {
|
|||
expect(result.viewIntentFilePath, '/tmp/incoming.jpg');
|
||||
});
|
||||
|
||||
test('returns cached remote asset when local Drift row is absent but checksum matches', () async {
|
||||
final remoteAsset = _remoteAsset(id: 'remote-1', checksum: 'checksum-1');
|
||||
when(
|
||||
() => nativeSyncApi.hashAssets(['local-1']),
|
||||
).thenAnswer((_) async => [HashResult(assetId: 'local-1', hash: 'checksum-1')]);
|
||||
when(() => assetService.getAllRemoteAssetDebugByChecksum('checksum-1')).thenAnswer((_) async => [remoteAsset]);
|
||||
|
||||
final result = await _resolve(container, _payload(localAssetId: 'local-1'));
|
||||
|
||||
expect(result.asset, isA<RemoteAsset>());
|
||||
expect((result.asset as RemoteAsset).id, 'remote-1');
|
||||
expect((result.asset as RemoteAsset).localId, 'local-1');
|
||||
expect(result.timelineService.origin, TimelineOrigin.deepLink);
|
||||
verify(() => assetService.getAllRemoteAssetDebugByChecksum('checksum-1')).called(1);
|
||||
});
|
||||
|
||||
test('returns transient asset for path-only attachment', () async {
|
||||
final result = await _resolve(
|
||||
container,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue