mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
fix(mobile): avoid duplicate assets in album view (#28152)
fix(mobile): avoid duplicate assets in remote album timeline Co-authored-by: Stefan Friedli <stefan@stefanfriedli.ch>
This commit is contained in:
@@ -244,17 +244,21 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
|
|||||||
|
|
||||||
final isAscending = albumData.order == AlbumAssetOrder.asc;
|
final isAscending = albumData.order == AlbumAssetOrder.asc;
|
||||||
|
|
||||||
final query = _db.remoteAssetEntity.select().addColumns([_db.localAssetEntity.id]).join([
|
// Correlated subquery picks the first matching local asset by checksum,
|
||||||
|
// avoiding fan-out when the same photo exists in multiple device albums (#23273).
|
||||||
|
final localId = subqueryExpression<String>(
|
||||||
|
_db.localAssetEntity.selectOnly()
|
||||||
|
..addColumns([_db.localAssetEntity.id])
|
||||||
|
..where(_db.localAssetEntity.checksum.equalsExp(_db.remoteAssetEntity.checksum))
|
||||||
|
..limit(1),
|
||||||
|
);
|
||||||
|
|
||||||
|
final query = _db.remoteAssetEntity.select().addColumns([localId]).join([
|
||||||
innerJoin(
|
innerJoin(
|
||||||
_db.remoteAlbumAssetEntity,
|
_db.remoteAlbumAssetEntity,
|
||||||
_db.remoteAlbumAssetEntity.assetId.equalsExp(_db.remoteAssetEntity.id),
|
_db.remoteAlbumAssetEntity.assetId.equalsExp(_db.remoteAssetEntity.id),
|
||||||
useColumns: false,
|
useColumns: false,
|
||||||
),
|
),
|
||||||
leftOuterJoin(
|
|
||||||
_db.localAssetEntity,
|
|
||||||
_db.remoteAssetEntity.checksum.equalsExp(_db.localAssetEntity.checksum),
|
|
||||||
useColumns: false,
|
|
||||||
),
|
|
||||||
])..where(_db.remoteAssetEntity.deletedAt.isNull() & _db.remoteAlbumAssetEntity.albumId.equals(albumId));
|
])..where(_db.remoteAssetEntity.deletedAt.isNull() & _db.remoteAlbumAssetEntity.albumId.equals(albumId));
|
||||||
|
|
||||||
if (isAscending) {
|
if (isAscending) {
|
||||||
@@ -265,9 +269,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
|
|||||||
|
|
||||||
query.limit(count, offset: offset);
|
query.limit(count, offset: offset);
|
||||||
|
|
||||||
return query
|
return query.map((row) => row.readTable(_db.remoteAssetEntity).toDto(localId: row.read(localId))).get();
|
||||||
.map((row) => row.readTable(_db.remoteAssetEntity).toDto(localId: row.read(_db.localAssetEntity.id)))
|
|
||||||
.get();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TimelineQuery fromAssets(List<BaseAsset> assets, TimelineOrigin origin) => (
|
TimelineQuery fromAssets(List<BaseAsset> assets, TimelineOrigin origin) => (
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/timeline.model.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/timeline.repository.dart';
|
||||||
|
|
||||||
|
import '../repository_context.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
late MediumRepositoryContext ctx;
|
||||||
|
late DriftTimelineRepository sut;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
ctx = MediumRepositoryContext();
|
||||||
|
sut = DriftTimelineRepository(ctx.db);
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDown(() async {
|
||||||
|
await ctx.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
group('remoteAlbum assets', () {
|
||||||
|
test('no duplicate assets when identical checksum appears in multiple local asset rows', () async {
|
||||||
|
// Regression check for #23273: a LEFT OUTER JOIN on checksum would fan out and create duplicates
|
||||||
|
// happens when same photo exists in multiple albums on device
|
||||||
|
final user = await ctx.newUser();
|
||||||
|
final checksum = 'yolo';
|
||||||
|
final album = await ctx.newRemoteAlbum(ownerId: user.id);
|
||||||
|
final remoteAsset = await ctx.newRemoteAsset(ownerId: user.id, checksum: checksum);
|
||||||
|
await ctx.insertRemoteAlbumAsset(albumId: album.id, assetId: remoteAsset.id);
|
||||||
|
|
||||||
|
final localAsset1 = await ctx.newLocalAsset(checksum: checksum);
|
||||||
|
final localAsset2 = await ctx.newLocalAsset(checksum: checksum);
|
||||||
|
|
||||||
|
final assets = await sut.remoteAlbum(album.id, GroupAssetsBy.day).assetSource(0, 10);
|
||||||
|
|
||||||
|
expect(assets, hasLength(1));
|
||||||
|
expect((assets.first as RemoteAsset).id, remoteAsset.id);
|
||||||
|
expect([localAsset1.id, localAsset2.id], contains((assets.first as RemoteAsset).localId));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user