feat: all local assets in album asset selection

This commit is contained in:
shenlong-tanwen 2026-07-31 17:08:17 +05:30
parent 36cb86c886
commit 241231c73e
6 changed files with 108 additions and 36 deletions

View file

@ -49,7 +49,8 @@ class TimelineFactory {
return group == GroupAssetsBy.auto ? GroupAssetsBy.day : group;
}
TimelineService main(List<String> timelineUsers) => TimelineService(_timelineRepository.main(timelineUsers, groupBy));
TimelineService main(List<String> timelineUsers, {bool ignoreBackupSelection = false}) =>
TimelineService(_timelineRepository.main(timelineUsers, groupBy, ignoreBackupSelection: ignoreBackupSelection));
TimelineService localAlbum({required String albumId}) =>
TimelineService(_timelineRepository.localAlbum(albumId, groupBy));

View file

@ -4,7 +4,7 @@ import 'local_asset.entity.dart';
import 'local_album.entity.dart';
import 'local_album_asset.entity.dart';
mergedAsset:
mergedAsset(:ignore_backup_selection AS BOOLEAN):
SELECT
rae.id as remote_id,
(SELECT lae.id FROM local_asset_entity lae WHERE lae.checksum = rae.checksum LIMIT 1) as local_id,
@ -73,20 +73,25 @@ FROM
WHERE NOT EXISTS (
SELECT 1 FROM remote_asset_entity rae WHERE rae.checksum = lae.checksum AND rae.owner_id IN :user_ids
)
AND EXISTS (
SELECT 1 FROM local_album_asset_entity laa
INNER JOIN local_album_entity la on laa.album_id = la.id
WHERE laa.asset_id = lae.id AND la.backup_selection = 0 -- selected
)
AND NOT EXISTS (
SELECT 1 FROM local_album_asset_entity laa
INNER JOIN local_album_entity la on laa.album_id = la.id
WHERE laa.asset_id = lae.id AND la.backup_selection = 2 -- excluded
AND (
:ignore_backup_selection
OR (
EXISTS (
SELECT 1 FROM local_album_asset_entity laa
INNER JOIN local_album_entity la on laa.album_id = la.id
WHERE laa.asset_id = lae.id AND la.backup_selection = 0 -- selected
)
AND NOT EXISTS (
SELECT 1 FROM local_album_asset_entity laa
INNER JOIN local_album_entity la on laa.album_id = la.id
WHERE laa.asset_id = lae.id AND la.backup_selection = 2 -- excluded
)
)
)
ORDER BY created_at DESC
LIMIT $limit;
mergedBucket(:group_by AS INTEGER):
mergedBucket(:group_by AS INTEGER, :ignore_backup_selection AS BOOLEAN):
SELECT
COUNT(*) as asset_count,
bucket_date
@ -106,7 +111,7 @@ FROM
FROM
remote_asset_entity rae
LEFT JOIN
stack_entity se ON rae.stack_id = se.id
stack_entity se ON rae.stack_id = se.id
WHERE
rae.deleted_at IS NULL
AND rae.visibility = 0 -- timeline visibility
@ -126,15 +131,20 @@ FROM
WHERE NOT EXISTS (
SELECT 1 FROM remote_asset_entity rae WHERE rae.checksum = lae.checksum AND rae.owner_id IN :user_ids
)
AND EXISTS (
SELECT 1 FROM local_album_asset_entity laa
INNER JOIN local_album_entity la on laa.album_id = la.id
WHERE laa.asset_id = lae.id AND la.backup_selection = 0 -- selected
)
AND NOT EXISTS (
SELECT 1 FROM local_album_asset_entity laa
INNER JOIN local_album_entity la on laa.album_id = la.id
WHERE laa.asset_id = lae.id AND la.backup_selection = 2 -- excluded
AND (
:ignore_backup_selection
OR (
EXISTS (
SELECT 1 FROM local_album_asset_entity laa
INNER JOIN local_album_entity la on laa.album_id = la.id
WHERE laa.asset_id = lae.id AND la.backup_selection = 0 -- selected
)
AND NOT EXISTS (
SELECT 1 FROM local_album_asset_entity laa
INNER JOIN local_album_entity la on laa.album_id = la.id
WHERE laa.asset_id = lae.id AND la.backup_selection = 2 -- excluded
)
)
)
)
GROUP BY bucket_date

View file

@ -50,26 +50,39 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
.map((users) => users..add(userId));
}
TimelineQuery main(List<String> userIds, GroupAssetsBy groupBy) => (
bucketSource: () => _watchMainBucket(userIds, groupBy: groupBy),
assetSource: (offset, count) => _getMainBucketAssets(userIds, offset: offset, count: count),
origin: TimelineOrigin.main,
TimelineQuery main(List<String> userIds, GroupAssetsBy groupBy, {bool ignoreBackupSelection = false}) => (
bucketSource: () => _watchMainBucket(userIds, groupBy: groupBy, ignoreBackupSelection: ignoreBackupSelection),
assetSource: (offset, count) =>
_getMainBucketAssets(userIds, offset: offset, count: count, ignoreBackupSelection: ignoreBackupSelection),
origin: .main,
);
Stream<List<Bucket>> _watchMainBucket(List<String> userIds, {GroupAssetsBy groupBy = GroupAssetsBy.day}) {
if (groupBy == GroupAssetsBy.none) {
Stream<List<Bucket>> _watchMainBucket(
List<String> userIds, {
GroupAssetsBy groupBy = .day,
bool ignoreBackupSelection = false,
}) {
if (groupBy == .none) {
throw UnsupportedError("GroupAssetsBy.none is not supported for watchMainBucket");
}
return _db.mergedAssetDrift.mergedBucket(userIds: userIds, groupBy: groupBy.index).map((row) {
final date = row.bucketDate.truncateDate(groupBy);
return TimeBucket(date: date, assetCount: row.assetCount);
}).watch();
return _db.mergedAssetDrift
.mergedBucket(userIds: userIds, groupBy: groupBy.index, ignoreBackupSelection: ignoreBackupSelection)
.map((row) {
final date = row.bucketDate.truncateDate(groupBy);
return TimeBucket(date: date, assetCount: row.assetCount);
})
.watch();
}
Future<List<BaseAsset>> _getMainBucketAssets(List<String> userIds, {required int offset, required int count}) {
Future<List<BaseAsset>> _getMainBucketAssets(
List<String> userIds, {
required int offset,
required int count,
bool ignoreBackupSelection = false,
}) {
return _db.mergedAssetDrift
.mergedAsset(userIds: userIds, limit: (_) => Limit(count, offset))
.mergedAsset(userIds: userIds, limit: (_) => Limit(count, offset), ignoreBackupSelection: ignoreBackupSelection)
.map(
(row) => row.remoteId != null && row.ownerId != null
? RemoteAsset(

View file

@ -22,7 +22,7 @@ class DriftAssetSelectionTimelinePage extends ConsumerWidget {
),
timelineServiceProvider.overrideWith((ref) {
final timelineUsers = ref.watch(timelineUsersProvider).valueOrNull ?? [];
final timelineService = ref.watch(timelineFactoryProvider).main(timelineUsers);
final timelineService = ref.watch(timelineFactoryProvider).main(timelineUsers, ignoreBackupSelection: true);
ref.onDispose(timelineService.dispose);
return timelineService;
}),

View file

@ -43,7 +43,9 @@ void main() {
),
);
final buckets = await db.mergedAssetDrift.mergedBucket(groupBy: GroupAssetsBy.day.index, userIds: [userId]).get();
final buckets = await db.mergedAssetDrift
.mergedBucket(groupBy: GroupAssetsBy.day.index, userIds: [userId], ignoreBackupSelection: false)
.get();
expect(buckets, hasLength(1));
expect(buckets.single.assetCount, 1);

View file

@ -102,4 +102,50 @@ void main() {
expect(remote.localId, local.id);
});
});
group('main backup selection', () {
test('excludes local assets whose album is not selected for backup', () async {
final user = await ctx.newUser();
final album = await ctx.newLocalAlbum(backupSelection: .none);
final asset = await ctx.newLocalAsset();
await ctx.newLocalAlbumAsset(albumId: album.id, assetId: asset.id);
final query = sut.main([user.id], .day);
expect(await query.bucketSource().first, isEmpty);
expect(await query.assetSource(0, 10), isEmpty);
});
test('excludes local assets in an excluded album even when also in a selected album', () async {
final user = await ctx.newUser();
final selected = await ctx.newLocalAlbum(backupSelection: .selected);
final excluded = await ctx.newLocalAlbum(backupSelection: .excluded);
final asset = await ctx.newLocalAsset();
await ctx.newLocalAlbumAsset(albumId: selected.id, assetId: asset.id);
await ctx.newLocalAlbumAsset(albumId: excluded.id, assetId: asset.id);
final query = sut.main([user.id], .day);
expect(await query.bucketSource().first, isEmpty);
expect(await query.assetSource(0, 10), isEmpty);
});
test('ignoreBackupSelection includes local assets regardless of album selection', () async {
final user = await ctx.newUser();
final none = await ctx.newLocalAlbum(backupSelection: .none);
final excluded = await ctx.newLocalAlbum(backupSelection: .excluded);
final unselectedAsset = await ctx.newLocalAsset();
final excludedAsset = await ctx.newLocalAsset();
await ctx.newLocalAlbumAsset(albumId: none.id, assetId: unselectedAsset.id);
await ctx.newLocalAlbumAsset(albumId: excluded.id, assetId: excludedAsset.id);
final query = sut.main([user.id], .day, ignoreBackupSelection: true);
final buckets = await query.bucketSource().first;
expect(buckets.fold<int>(0, (sum, bucket) => sum + bucket.assetCount), 2);
final assets = await query.assetSource(0, 10);
expect(assets.map((asset) => (asset as LocalAsset).id), containsAll([unselectedAsset.id, excludedAsset.id]));
});
});
}