mirror of
https://github.com/immich-app/immich.git
synced 2026-06-14 19:09:15 -07:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e6db274aa9 |
+3640
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@ import 'package:immich_mobile/constants/constants.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/asset_metadata.model.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/mapper.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart';
|
||||
import 'package:immich_mobile/platform/native_sync_api.g.dart';
|
||||
@@ -211,7 +211,7 @@ Future<List<_CloudIdMapping>> _fetchCloudIdMappings(Drift drift, String userId,
|
||||
return query.map((row) {
|
||||
return (
|
||||
remoteAssetId: row.read(drift.remoteAssetEntity.id)!,
|
||||
localAsset: row.readTable(drift.localAssetEntity).toDto(),
|
||||
localAsset: mapToLocalAsset(row.readTable(drift.localAssetEntity)),
|
||||
);
|
||||
}).get();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/local_album.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/remote_album.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
|
||||
|
||||
@@ -23,17 +22,3 @@ class LocalAlbumEntity extends Table with DriftDefaultsMixin {
|
||||
@override
|
||||
Set<Column> get primaryKey => {id};
|
||||
}
|
||||
|
||||
extension LocalAlbumEntityDataHelper on LocalAlbumEntityData {
|
||||
LocalAlbum toDto({int assetCount = 0}) {
|
||||
return LocalAlbum(
|
||||
id: id,
|
||||
name: name,
|
||||
updatedAt: updatedAt,
|
||||
assetCount: assetCount,
|
||||
backupSelection: backupSelection,
|
||||
linkedRemoteAlbumId: linkedRemoteAlbumId,
|
||||
isIosSharedAlbum: isIosSharedAlbum,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/utils/asset.mixin.dart';
|
||||
import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
|
||||
|
||||
@TableIndex.sql('CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)')
|
||||
@TableIndex.sql('CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)')
|
||||
@TableIndex.sql('CREATE INDEX IF NOT EXISTS idx_local_asset_created_at ON local_asset_entity (created_at)')
|
||||
@TableIndex.sql(
|
||||
'CREATE INDEX IF NOT EXISTS idx_local_asset_backup_candidate ON local_asset_entity (is_backup_candidate)',
|
||||
)
|
||||
class LocalAssetEntity extends Table with DriftDefaultsMixin, AssetEntityMixin {
|
||||
const LocalAssetEntity();
|
||||
|
||||
@@ -16,6 +18,8 @@ class LocalAssetEntity extends Table with DriftDefaultsMixin, AssetEntityMixin {
|
||||
// Only used during backup to mirror the favorite status of the asset in the server
|
||||
BoolColumn get isFavorite => boolean().withDefault(const Constant(false))();
|
||||
|
||||
BoolColumn get isBackupCandidate => boolean().withDefault(const Constant(false))();
|
||||
|
||||
IntColumn get orientation => integer().withDefault(const Constant(0))();
|
||||
|
||||
TextColumn get iCloudId => text().nullable()();
|
||||
@@ -31,26 +35,3 @@ class LocalAssetEntity extends Table with DriftDefaultsMixin, AssetEntityMixin {
|
||||
@override
|
||||
Set<Column> get primaryKey => {id};
|
||||
}
|
||||
|
||||
extension LocalAssetEntityDataDomainExtension on LocalAssetEntityData {
|
||||
LocalAsset toDto({String? remoteId}) => LocalAsset(
|
||||
id: id,
|
||||
name: name,
|
||||
checksum: checksum,
|
||||
type: type,
|
||||
createdAt: createdAt,
|
||||
updatedAt: updatedAt,
|
||||
durationMs: durationMs,
|
||||
isFavorite: isFavorite,
|
||||
height: height,
|
||||
width: width,
|
||||
remoteId: remoteId,
|
||||
orientation: orientation,
|
||||
playbackStyle: playbackStyle,
|
||||
adjustmentTime: adjustmentTime,
|
||||
latitude: latitude,
|
||||
longitude: longitude,
|
||||
cloudId: iCloudId,
|
||||
isEdited: false,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ typedef $$LocalAssetEntityTableCreateCompanionBuilder =
|
||||
required String id,
|
||||
i0.Value<String?> checksum,
|
||||
i0.Value<bool> isFavorite,
|
||||
i0.Value<bool> isBackupCandidate,
|
||||
i0.Value<int> orientation,
|
||||
i0.Value<String?> iCloudId,
|
||||
i0.Value<DateTime?> adjustmentTime,
|
||||
@@ -39,6 +40,7 @@ typedef $$LocalAssetEntityTableUpdateCompanionBuilder =
|
||||
i0.Value<String> id,
|
||||
i0.Value<String?> checksum,
|
||||
i0.Value<bool> isFavorite,
|
||||
i0.Value<bool> isBackupCandidate,
|
||||
i0.Value<int> orientation,
|
||||
i0.Value<String?> iCloudId,
|
||||
i0.Value<DateTime?> adjustmentTime,
|
||||
@@ -107,6 +109,11 @@ class $$LocalAssetEntityTableFilterComposer
|
||||
builder: (column) => i0.ColumnFilters(column),
|
||||
);
|
||||
|
||||
i0.ColumnFilters<bool> get isBackupCandidate => $composableBuilder(
|
||||
column: $table.isBackupCandidate,
|
||||
builder: (column) => i0.ColumnFilters(column),
|
||||
);
|
||||
|
||||
i0.ColumnFilters<int> get orientation => $composableBuilder(
|
||||
column: $table.orientation,
|
||||
builder: (column) => i0.ColumnFilters(column),
|
||||
@@ -202,6 +209,11 @@ class $$LocalAssetEntityTableOrderingComposer
|
||||
builder: (column) => i0.ColumnOrderings(column),
|
||||
);
|
||||
|
||||
i0.ColumnOrderings<bool> get isBackupCandidate => $composableBuilder(
|
||||
column: $table.isBackupCandidate,
|
||||
builder: (column) => i0.ColumnOrderings(column),
|
||||
);
|
||||
|
||||
i0.ColumnOrderings<int> get orientation => $composableBuilder(
|
||||
column: $table.orientation,
|
||||
builder: (column) => i0.ColumnOrderings(column),
|
||||
@@ -276,6 +288,11 @@ class $$LocalAssetEntityTableAnnotationComposer
|
||||
builder: (column) => column,
|
||||
);
|
||||
|
||||
i0.GeneratedColumn<bool> get isBackupCandidate => $composableBuilder(
|
||||
column: $table.isBackupCandidate,
|
||||
builder: (column) => column,
|
||||
);
|
||||
|
||||
i0.GeneratedColumn<int> get orientation => $composableBuilder(
|
||||
column: $table.orientation,
|
||||
builder: (column) => column,
|
||||
@@ -352,6 +369,7 @@ class $$LocalAssetEntityTableTableManager
|
||||
i0.Value<String> id = const i0.Value.absent(),
|
||||
i0.Value<String?> checksum = const i0.Value.absent(),
|
||||
i0.Value<bool> isFavorite = const i0.Value.absent(),
|
||||
i0.Value<bool> isBackupCandidate = const i0.Value.absent(),
|
||||
i0.Value<int> orientation = const i0.Value.absent(),
|
||||
i0.Value<String?> iCloudId = const i0.Value.absent(),
|
||||
i0.Value<DateTime?> adjustmentTime = const i0.Value.absent(),
|
||||
@@ -370,6 +388,7 @@ class $$LocalAssetEntityTableTableManager
|
||||
id: id,
|
||||
checksum: checksum,
|
||||
isFavorite: isFavorite,
|
||||
isBackupCandidate: isBackupCandidate,
|
||||
orientation: orientation,
|
||||
iCloudId: iCloudId,
|
||||
adjustmentTime: adjustmentTime,
|
||||
@@ -389,6 +408,7 @@ class $$LocalAssetEntityTableTableManager
|
||||
required String id,
|
||||
i0.Value<String?> checksum = const i0.Value.absent(),
|
||||
i0.Value<bool> isFavorite = const i0.Value.absent(),
|
||||
i0.Value<bool> isBackupCandidate = const i0.Value.absent(),
|
||||
i0.Value<int> orientation = const i0.Value.absent(),
|
||||
i0.Value<String?> iCloudId = const i0.Value.absent(),
|
||||
i0.Value<DateTime?> adjustmentTime = const i0.Value.absent(),
|
||||
@@ -407,6 +427,7 @@ class $$LocalAssetEntityTableTableManager
|
||||
id: id,
|
||||
checksum: checksum,
|
||||
isFavorite: isFavorite,
|
||||
isBackupCandidate: isBackupCandidate,
|
||||
orientation: orientation,
|
||||
iCloudId: iCloudId,
|
||||
adjustmentTime: adjustmentTime,
|
||||
@@ -568,6 +589,21 @@ class $LocalAssetEntityTable extends i3.LocalAssetEntity
|
||||
),
|
||||
defaultValue: const i4.Constant(false),
|
||||
);
|
||||
static const i0.VerificationMeta _isBackupCandidateMeta =
|
||||
const i0.VerificationMeta('isBackupCandidate');
|
||||
@override
|
||||
late final i0.GeneratedColumn<bool> isBackupCandidate =
|
||||
i0.GeneratedColumn<bool>(
|
||||
'is_backup_candidate',
|
||||
aliasedName,
|
||||
false,
|
||||
type: i0.DriftSqlType.bool,
|
||||
requiredDuringInsert: false,
|
||||
defaultConstraints: i0.GeneratedColumn.constraintIsAlways(
|
||||
'CHECK ("is_backup_candidate" IN (0, 1))',
|
||||
),
|
||||
defaultValue: const i4.Constant(false),
|
||||
);
|
||||
static const i0.VerificationMeta _orientationMeta = const i0.VerificationMeta(
|
||||
'orientation',
|
||||
);
|
||||
@@ -649,6 +685,7 @@ class $LocalAssetEntityTable extends i3.LocalAssetEntity
|
||||
id,
|
||||
checksum,
|
||||
isFavorite,
|
||||
isBackupCandidate,
|
||||
orientation,
|
||||
iCloudId,
|
||||
adjustmentTime,
|
||||
@@ -723,6 +760,15 @@ class $LocalAssetEntityTable extends i3.LocalAssetEntity
|
||||
isFavorite.isAcceptableOrUnknown(data['is_favorite']!, _isFavoriteMeta),
|
||||
);
|
||||
}
|
||||
if (data.containsKey('is_backup_candidate')) {
|
||||
context.handle(
|
||||
_isBackupCandidateMeta,
|
||||
isBackupCandidate.isAcceptableOrUnknown(
|
||||
data['is_backup_candidate']!,
|
||||
_isBackupCandidateMeta,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (data.containsKey('orientation')) {
|
||||
context.handle(
|
||||
_orientationMeta,
|
||||
@@ -813,6 +859,10 @@ class $LocalAssetEntityTable extends i3.LocalAssetEntity
|
||||
i0.DriftSqlType.bool,
|
||||
data['${effectivePrefix}is_favorite'],
|
||||
)!,
|
||||
isBackupCandidate: attachedDatabase.typeMapping.read(
|
||||
i0.DriftSqlType.bool,
|
||||
data['${effectivePrefix}is_backup_candidate'],
|
||||
)!,
|
||||
orientation: attachedDatabase.typeMapping.read(
|
||||
i0.DriftSqlType.int,
|
||||
data['${effectivePrefix}orientation'],
|
||||
@@ -871,6 +921,7 @@ class LocalAssetEntityData extends i0.DataClass
|
||||
final String id;
|
||||
final String? checksum;
|
||||
final bool isFavorite;
|
||||
final bool isBackupCandidate;
|
||||
final int orientation;
|
||||
final String? iCloudId;
|
||||
final DateTime? adjustmentTime;
|
||||
@@ -888,6 +939,7 @@ class LocalAssetEntityData extends i0.DataClass
|
||||
required this.id,
|
||||
this.checksum,
|
||||
required this.isFavorite,
|
||||
required this.isBackupCandidate,
|
||||
required this.orientation,
|
||||
this.iCloudId,
|
||||
this.adjustmentTime,
|
||||
@@ -920,6 +972,7 @@ class LocalAssetEntityData extends i0.DataClass
|
||||
map['checksum'] = i0.Variable<String>(checksum);
|
||||
}
|
||||
map['is_favorite'] = i0.Variable<bool>(isFavorite);
|
||||
map['is_backup_candidate'] = i0.Variable<bool>(isBackupCandidate);
|
||||
map['orientation'] = i0.Variable<int>(orientation);
|
||||
if (!nullToAbsent || iCloudId != null) {
|
||||
map['i_cloud_id'] = i0.Variable<String>(iCloudId);
|
||||
@@ -959,6 +1012,7 @@ class LocalAssetEntityData extends i0.DataClass
|
||||
id: serializer.fromJson<String>(json['id']),
|
||||
checksum: serializer.fromJson<String?>(json['checksum']),
|
||||
isFavorite: serializer.fromJson<bool>(json['isFavorite']),
|
||||
isBackupCandidate: serializer.fromJson<bool>(json['isBackupCandidate']),
|
||||
orientation: serializer.fromJson<int>(json['orientation']),
|
||||
iCloudId: serializer.fromJson<String?>(json['iCloudId']),
|
||||
adjustmentTime: serializer.fromJson<DateTime?>(json['adjustmentTime']),
|
||||
@@ -985,6 +1039,7 @@ class LocalAssetEntityData extends i0.DataClass
|
||||
'id': serializer.toJson<String>(id),
|
||||
'checksum': serializer.toJson<String?>(checksum),
|
||||
'isFavorite': serializer.toJson<bool>(isFavorite),
|
||||
'isBackupCandidate': serializer.toJson<bool>(isBackupCandidate),
|
||||
'orientation': serializer.toJson<int>(orientation),
|
||||
'iCloudId': serializer.toJson<String?>(iCloudId),
|
||||
'adjustmentTime': serializer.toJson<DateTime?>(adjustmentTime),
|
||||
@@ -1007,6 +1062,7 @@ class LocalAssetEntityData extends i0.DataClass
|
||||
String? id,
|
||||
i0.Value<String?> checksum = const i0.Value.absent(),
|
||||
bool? isFavorite,
|
||||
bool? isBackupCandidate,
|
||||
int? orientation,
|
||||
i0.Value<String?> iCloudId = const i0.Value.absent(),
|
||||
i0.Value<DateTime?> adjustmentTime = const i0.Value.absent(),
|
||||
@@ -1024,6 +1080,7 @@ class LocalAssetEntityData extends i0.DataClass
|
||||
id: id ?? this.id,
|
||||
checksum: checksum.present ? checksum.value : this.checksum,
|
||||
isFavorite: isFavorite ?? this.isFavorite,
|
||||
isBackupCandidate: isBackupCandidate ?? this.isBackupCandidate,
|
||||
orientation: orientation ?? this.orientation,
|
||||
iCloudId: iCloudId.present ? iCloudId.value : this.iCloudId,
|
||||
adjustmentTime: adjustmentTime.present
|
||||
@@ -1049,6 +1106,9 @@ class LocalAssetEntityData extends i0.DataClass
|
||||
isFavorite: data.isFavorite.present
|
||||
? data.isFavorite.value
|
||||
: this.isFavorite,
|
||||
isBackupCandidate: data.isBackupCandidate.present
|
||||
? data.isBackupCandidate.value
|
||||
: this.isBackupCandidate,
|
||||
orientation: data.orientation.present
|
||||
? data.orientation.value
|
||||
: this.orientation,
|
||||
@@ -1077,6 +1137,7 @@ class LocalAssetEntityData extends i0.DataClass
|
||||
..write('id: $id, ')
|
||||
..write('checksum: $checksum, ')
|
||||
..write('isFavorite: $isFavorite, ')
|
||||
..write('isBackupCandidate: $isBackupCandidate, ')
|
||||
..write('orientation: $orientation, ')
|
||||
..write('iCloudId: $iCloudId, ')
|
||||
..write('adjustmentTime: $adjustmentTime, ')
|
||||
@@ -1099,6 +1160,7 @@ class LocalAssetEntityData extends i0.DataClass
|
||||
id,
|
||||
checksum,
|
||||
isFavorite,
|
||||
isBackupCandidate,
|
||||
orientation,
|
||||
iCloudId,
|
||||
adjustmentTime,
|
||||
@@ -1120,6 +1182,7 @@ class LocalAssetEntityData extends i0.DataClass
|
||||
other.id == this.id &&
|
||||
other.checksum == this.checksum &&
|
||||
other.isFavorite == this.isFavorite &&
|
||||
other.isBackupCandidate == this.isBackupCandidate &&
|
||||
other.orientation == this.orientation &&
|
||||
other.iCloudId == this.iCloudId &&
|
||||
other.adjustmentTime == this.adjustmentTime &&
|
||||
@@ -1140,6 +1203,7 @@ class LocalAssetEntityCompanion
|
||||
final i0.Value<String> id;
|
||||
final i0.Value<String?> checksum;
|
||||
final i0.Value<bool> isFavorite;
|
||||
final i0.Value<bool> isBackupCandidate;
|
||||
final i0.Value<int> orientation;
|
||||
final i0.Value<String?> iCloudId;
|
||||
final i0.Value<DateTime?> adjustmentTime;
|
||||
@@ -1157,6 +1221,7 @@ class LocalAssetEntityCompanion
|
||||
this.id = const i0.Value.absent(),
|
||||
this.checksum = const i0.Value.absent(),
|
||||
this.isFavorite = const i0.Value.absent(),
|
||||
this.isBackupCandidate = const i0.Value.absent(),
|
||||
this.orientation = const i0.Value.absent(),
|
||||
this.iCloudId = const i0.Value.absent(),
|
||||
this.adjustmentTime = const i0.Value.absent(),
|
||||
@@ -1175,6 +1240,7 @@ class LocalAssetEntityCompanion
|
||||
required String id,
|
||||
this.checksum = const i0.Value.absent(),
|
||||
this.isFavorite = const i0.Value.absent(),
|
||||
this.isBackupCandidate = const i0.Value.absent(),
|
||||
this.orientation = const i0.Value.absent(),
|
||||
this.iCloudId = const i0.Value.absent(),
|
||||
this.adjustmentTime = const i0.Value.absent(),
|
||||
@@ -1195,6 +1261,7 @@ class LocalAssetEntityCompanion
|
||||
i0.Expression<String>? id,
|
||||
i0.Expression<String>? checksum,
|
||||
i0.Expression<bool>? isFavorite,
|
||||
i0.Expression<bool>? isBackupCandidate,
|
||||
i0.Expression<int>? orientation,
|
||||
i0.Expression<String>? iCloudId,
|
||||
i0.Expression<DateTime>? adjustmentTime,
|
||||
@@ -1213,6 +1280,7 @@ class LocalAssetEntityCompanion
|
||||
if (id != null) 'id': id,
|
||||
if (checksum != null) 'checksum': checksum,
|
||||
if (isFavorite != null) 'is_favorite': isFavorite,
|
||||
if (isBackupCandidate != null) 'is_backup_candidate': isBackupCandidate,
|
||||
if (orientation != null) 'orientation': orientation,
|
||||
if (iCloudId != null) 'i_cloud_id': iCloudId,
|
||||
if (adjustmentTime != null) 'adjustment_time': adjustmentTime,
|
||||
@@ -1233,6 +1301,7 @@ class LocalAssetEntityCompanion
|
||||
i0.Value<String>? id,
|
||||
i0.Value<String?>? checksum,
|
||||
i0.Value<bool>? isFavorite,
|
||||
i0.Value<bool>? isBackupCandidate,
|
||||
i0.Value<int>? orientation,
|
||||
i0.Value<String?>? iCloudId,
|
||||
i0.Value<DateTime?>? adjustmentTime,
|
||||
@@ -1251,6 +1320,7 @@ class LocalAssetEntityCompanion
|
||||
id: id ?? this.id,
|
||||
checksum: checksum ?? this.checksum,
|
||||
isFavorite: isFavorite ?? this.isFavorite,
|
||||
isBackupCandidate: isBackupCandidate ?? this.isBackupCandidate,
|
||||
orientation: orientation ?? this.orientation,
|
||||
iCloudId: iCloudId ?? this.iCloudId,
|
||||
adjustmentTime: adjustmentTime ?? this.adjustmentTime,
|
||||
@@ -1295,6 +1365,9 @@ class LocalAssetEntityCompanion
|
||||
if (isFavorite.present) {
|
||||
map['is_favorite'] = i0.Variable<bool>(isFavorite.value);
|
||||
}
|
||||
if (isBackupCandidate.present) {
|
||||
map['is_backup_candidate'] = i0.Variable<bool>(isBackupCandidate.value);
|
||||
}
|
||||
if (orientation.present) {
|
||||
map['orientation'] = i0.Variable<int>(orientation.value);
|
||||
}
|
||||
@@ -1333,6 +1406,7 @@ class LocalAssetEntityCompanion
|
||||
..write('id: $id, ')
|
||||
..write('checksum: $checksum, ')
|
||||
..write('isFavorite: $isFavorite, ')
|
||||
..write('isBackupCandidate: $isBackupCandidate, ')
|
||||
..write('orientation: $orientation, ')
|
||||
..write('iCloudId: $iCloudId, ')
|
||||
..write('adjustmentTime: $adjustmentTime, ')
|
||||
@@ -1352,3 +1426,7 @@ i0.Index get idxLocalAssetCreatedAt => i0.Index(
|
||||
'idx_local_asset_created_at',
|
||||
'CREATE INDEX IF NOT EXISTS idx_local_asset_created_at ON local_asset_entity (created_at)',
|
||||
);
|
||||
i0.Index get idxLocalAssetBackupCandidate => i0.Index(
|
||||
'idx_local_asset_backup_candidate',
|
||||
'CREATE INDEX IF NOT EXISTS idx_local_asset_backup_candidate ON local_asset_entity (is_backup_candidate)',
|
||||
);
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import 'remote_asset.entity.dart';
|
||||
import 'stack.entity.dart';
|
||||
import 'local_asset.entity.dart';
|
||||
import 'local_album.entity.dart';
|
||||
import 'local_album_asset.entity.dart';
|
||||
|
||||
mergedAsset:
|
||||
SELECT
|
||||
@@ -73,16 +71,7 @@ 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 lae.is_backup_candidate
|
||||
ORDER BY created_at DESC
|
||||
LIMIT $limit;
|
||||
|
||||
@@ -126,16 +115,7 @@ 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 lae.is_backup_candidate
|
||||
)
|
||||
GROUP BY bucket_date
|
||||
ORDER BY bucket_date DESC;
|
||||
|
||||
+3
-22
@@ -9,10 +9,6 @@ import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.
|
||||
as i4;
|
||||
import 'package:immich_mobile/infrastructure/entities/stack.entity.drift.dart'
|
||||
as i5;
|
||||
import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.drift.dart'
|
||||
as i6;
|
||||
import 'package:immich_mobile/infrastructure/entities/local_album.entity.drift.dart'
|
||||
as i7;
|
||||
|
||||
class MergedAssetDrift extends i1.ModularAccessor {
|
||||
MergedAssetDrift(i0.GeneratedDatabase db) : super(db);
|
||||
@@ -29,7 +25,7 @@ class MergedAssetDrift extends i1.ModularAccessor {
|
||||
);
|
||||
$arrayStartIndex += generatedlimit.amountOfVariables;
|
||||
return customSelect(
|
||||
'SELECT rae.id AS remote_id, (SELECT lae.id FROM local_asset_entity AS lae WHERE lae.checksum = rae.checksum LIMIT 1) AS local_id, rae.name, rae.type, rae.created_at AS created_at, rae.updated_at, rae.width, rae.height, rae.duration_ms, rae.is_favorite, rae.thumb_hash, rae.checksum, rae.owner_id, rae.live_photo_video_id, 0 AS orientation, rae.stack_id, NULL AS i_cloud_id, NULL AS latitude, NULL AS longitude, NULL AS adjustmentTime, rae.is_edited, 0 AS playback_style, rae.uploaded_at FROM remote_asset_entity AS rae LEFT JOIN stack_entity AS se ON rae.stack_id = se.id WHERE rae.deleted_at IS NULL AND rae.visibility = 0 AND rae.owner_id IN ($expandeduserIds) AND(rae.stack_id IS NULL OR rae.id = se.primary_asset_id)UNION ALL SELECT NULL AS remote_id, lae.id AS local_id, lae.name, lae.type, lae.created_at AS created_at, lae.updated_at, lae.width, lae.height, lae.duration_ms, lae.is_favorite, NULL AS thumb_hash, lae.checksum, NULL AS owner_id, NULL AS live_photo_video_id, lae.orientation, NULL AS stack_id, lae.i_cloud_id, lae.latitude, lae.longitude, lae.adjustment_time, 0 AS is_edited, lae.playback_style, NULL AS uploaded_at FROM local_asset_entity AS lae WHERE NOT EXISTS (SELECT 1 FROM remote_asset_entity AS rae WHERE rae.checksum = lae.checksum AND rae.owner_id IN ($expandeduserIds)) AND EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 0) AND NOT EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 2) ORDER BY created_at DESC ${generatedlimit.sql}',
|
||||
'SELECT rae.id AS remote_id, (SELECT lae.id FROM local_asset_entity AS lae WHERE lae.checksum = rae.checksum LIMIT 1) AS local_id, rae.name, rae.type, rae.created_at AS created_at, rae.updated_at, rae.width, rae.height, rae.duration_ms, rae.is_favorite, rae.thumb_hash, rae.checksum, rae.owner_id, rae.live_photo_video_id, 0 AS orientation, rae.stack_id, NULL AS i_cloud_id, NULL AS latitude, NULL AS longitude, NULL AS adjustmentTime, rae.is_edited, 0 AS playback_style, rae.uploaded_at FROM remote_asset_entity AS rae LEFT JOIN stack_entity AS se ON rae.stack_id = se.id WHERE rae.deleted_at IS NULL AND rae.visibility = 0 AND rae.owner_id IN ($expandeduserIds) AND(rae.stack_id IS NULL OR rae.id = se.primary_asset_id)UNION ALL SELECT NULL AS remote_id, lae.id AS local_id, lae.name, lae.type, lae.created_at AS created_at, lae.updated_at, lae.width, lae.height, lae.duration_ms, lae.is_favorite, NULL AS thumb_hash, lae.checksum, NULL AS owner_id, NULL AS live_photo_video_id, lae.orientation, NULL AS stack_id, lae.i_cloud_id, lae.latitude, lae.longitude, lae.adjustment_time, 0 AS is_edited, lae.playback_style, NULL AS uploaded_at FROM local_asset_entity AS lae WHERE NOT EXISTS (SELECT 1 FROM remote_asset_entity AS rae WHERE rae.checksum = lae.checksum AND rae.owner_id IN ($expandeduserIds)) AND lae.is_backup_candidate ORDER BY created_at DESC ${generatedlimit.sql}',
|
||||
variables: [
|
||||
for (var $ in userIds) i0.Variable<String>($),
|
||||
...generatedlimit.introducedVariables,
|
||||
@@ -38,8 +34,6 @@ class MergedAssetDrift extends i1.ModularAccessor {
|
||||
remoteAssetEntity,
|
||||
localAssetEntity,
|
||||
stackEntity,
|
||||
localAlbumAssetEntity,
|
||||
localAlbumEntity,
|
||||
...generatedlimit.watchedTables,
|
||||
},
|
||||
).map(
|
||||
@@ -81,18 +75,12 @@ class MergedAssetDrift extends i1.ModularAccessor {
|
||||
final expandeduserIds = $expandVar($arrayStartIndex, userIds.length);
|
||||
$arrayStartIndex += userIds.length;
|
||||
return customSelect(
|
||||
'SELECT COUNT(*) AS asset_count, bucket_date FROM (SELECT CASE WHEN ?1 = 0 THEN COALESCE(STRFTIME(\'%Y-%m-%d\', rae.local_date_time), STRFTIME(\'%Y-%m-%d\', rae.created_at, \'localtime\')) WHEN ?1 = 1 THEN COALESCE(STRFTIME(\'%Y-%m\', rae.local_date_time), STRFTIME(\'%Y-%m\', rae.created_at, \'localtime\')) END AS bucket_date FROM remote_asset_entity AS rae LEFT JOIN stack_entity AS se ON rae.stack_id = se.id WHERE rae.deleted_at IS NULL AND rae.visibility = 0 AND rae.owner_id IN ($expandeduserIds) AND(rae.stack_id IS NULL OR rae.id = se.primary_asset_id)UNION ALL SELECT CASE WHEN ?1 = 0 THEN STRFTIME(\'%Y-%m-%d\', lae.created_at, \'localtime\') WHEN ?1 = 1 THEN STRFTIME(\'%Y-%m\', lae.created_at, \'localtime\') END AS bucket_date FROM local_asset_entity AS lae WHERE NOT EXISTS (SELECT 1 FROM remote_asset_entity AS rae WHERE rae.checksum = lae.checksum AND rae.owner_id IN ($expandeduserIds)) AND EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 0) AND NOT EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 2)) GROUP BY bucket_date ORDER BY bucket_date DESC',
|
||||
'SELECT COUNT(*) AS asset_count, bucket_date FROM (SELECT CASE WHEN ?1 = 0 THEN COALESCE(STRFTIME(\'%Y-%m-%d\', rae.local_date_time), STRFTIME(\'%Y-%m-%d\', rae.created_at, \'localtime\')) WHEN ?1 = 1 THEN COALESCE(STRFTIME(\'%Y-%m\', rae.local_date_time), STRFTIME(\'%Y-%m\', rae.created_at, \'localtime\')) END AS bucket_date FROM remote_asset_entity AS rae LEFT JOIN stack_entity AS se ON rae.stack_id = se.id WHERE rae.deleted_at IS NULL AND rae.visibility = 0 AND rae.owner_id IN ($expandeduserIds) AND(rae.stack_id IS NULL OR rae.id = se.primary_asset_id)UNION ALL SELECT CASE WHEN ?1 = 0 THEN STRFTIME(\'%Y-%m-%d\', lae.created_at, \'localtime\') WHEN ?1 = 1 THEN STRFTIME(\'%Y-%m\', lae.created_at, \'localtime\') END AS bucket_date FROM local_asset_entity AS lae WHERE NOT EXISTS (SELECT 1 FROM remote_asset_entity AS rae WHERE rae.checksum = lae.checksum AND rae.owner_id IN ($expandeduserIds)) AND lae.is_backup_candidate) GROUP BY bucket_date ORDER BY bucket_date DESC',
|
||||
variables: [
|
||||
i0.Variable<int>(groupBy),
|
||||
for (var $ in userIds) i0.Variable<String>($),
|
||||
],
|
||||
readsFrom: {
|
||||
remoteAssetEntity,
|
||||
stackEntity,
|
||||
localAssetEntity,
|
||||
localAlbumAssetEntity,
|
||||
localAlbumEntity,
|
||||
},
|
||||
readsFrom: {remoteAssetEntity, stackEntity, localAssetEntity},
|
||||
).map(
|
||||
(i0.QueryRow row) => MergedBucketResult(
|
||||
assetCount: row.read<int>('asset_count'),
|
||||
@@ -110,13 +98,6 @@ class MergedAssetDrift extends i1.ModularAccessor {
|
||||
i3.$LocalAssetEntityTable get localAssetEntity => i1.ReadDatabaseContainer(
|
||||
attachedDatabase,
|
||||
).resultSet<i3.$LocalAssetEntityTable>('local_asset_entity');
|
||||
i6.$LocalAlbumAssetEntityTable get localAlbumAssetEntity =>
|
||||
i1.ReadDatabaseContainer(
|
||||
attachedDatabase,
|
||||
).resultSet<i6.$LocalAlbumAssetEntityTable>('local_album_asset_entity');
|
||||
i7.$LocalAlbumEntityTable get localAlbumEntity => i1.ReadDatabaseContainer(
|
||||
attachedDatabase,
|
||||
).resultSet<i7.$LocalAlbumEntityTable>('local_album_entity');
|
||||
}
|
||||
|
||||
class MergedAssetResult {
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/local_album.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/partner.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/user.entity.drift.dart';
|
||||
|
||||
@@ -13,3 +17,34 @@ User mapToUser(UserEntityData data) => User(
|
||||
|
||||
Partner mapToPartner(UserEntityData user, PartnerEntityData partner) =>
|
||||
Partner.fromUser(mapToUser(user), inTimeline: partner.inTimeline);
|
||||
|
||||
LocalAlbum mapToLocalAlbum(LocalAlbumEntityData data, {int assetCount = 0}) => LocalAlbum(
|
||||
id: data.id,
|
||||
name: data.name,
|
||||
updatedAt: data.updatedAt,
|
||||
assetCount: assetCount,
|
||||
backupSelection: data.backupSelection,
|
||||
linkedRemoteAlbumId: data.linkedRemoteAlbumId,
|
||||
isIosSharedAlbum: data.isIosSharedAlbum,
|
||||
);
|
||||
|
||||
LocalAsset mapToLocalAsset(LocalAssetEntityData data, {String? remoteId}) => LocalAsset(
|
||||
id: data.id,
|
||||
name: data.name,
|
||||
checksum: data.checksum,
|
||||
type: data.type,
|
||||
createdAt: data.createdAt,
|
||||
updatedAt: data.updatedAt,
|
||||
durationMs: data.durationMs,
|
||||
isFavorite: data.isFavorite,
|
||||
height: data.height,
|
||||
width: data.width,
|
||||
remoteId: remoteId,
|
||||
orientation: data.orientation,
|
||||
playbackStyle: data.playbackStyle,
|
||||
adjustmentTime: data.adjustmentTime,
|
||||
latitude: data.latitude,
|
||||
longitude: data.longitude,
|
||||
cloudId: data.iCloudId,
|
||||
isEdited: false,
|
||||
);
|
||||
|
||||
@@ -2,9 +2,8 @@ import 'dart:async';
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/mapper.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||
|
||||
@@ -16,19 +15,6 @@ class DriftBackupRepository extends DriftDatabaseRepository {
|
||||
final Drift _db;
|
||||
const DriftBackupRepository(this._db) : super(_db);
|
||||
|
||||
_getExcludedSubquery() {
|
||||
return _db.localAlbumAssetEntity.selectOnly()
|
||||
..addColumns([_db.localAlbumAssetEntity.assetId])
|
||||
..join([
|
||||
innerJoin(
|
||||
_db.localAlbumEntity,
|
||||
_db.localAlbumAssetEntity.albumId.equalsExp(_db.localAlbumEntity.id),
|
||||
useColumns: false,
|
||||
),
|
||||
])
|
||||
..where(_db.localAlbumEntity.backupSelection.equalsValue(BackupSelection.excluded));
|
||||
}
|
||||
|
||||
/// Returns all backup-related counts in a single query.
|
||||
///
|
||||
/// - total: number of distinct assets in selected albums, excluding those that are also in excluded albums
|
||||
@@ -45,31 +31,14 @@ class DriftBackupRepository extends DriftDatabaseRepository {
|
||||
FROM local_asset_entity lae
|
||||
LEFT JOIN main.remote_asset_entity rae
|
||||
ON lae.checksum = rae.checksum AND rae.owner_id = ?1
|
||||
WHERE EXISTS (
|
||||
SELECT 1
|
||||
FROM local_album_asset_entity laa
|
||||
INNER JOIN main.local_album_entity la on laa.album_id = la.id
|
||||
WHERE laa.asset_id = lae.id
|
||||
AND la.backup_selection = ?2
|
||||
)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM local_album_asset_entity laa
|
||||
INNER JOIN main.local_album_entity la on laa.album_id = la.id
|
||||
WHERE laa.asset_id = lae.id
|
||||
AND la.backup_selection = ?3
|
||||
);
|
||||
WHERE lae.is_backup_candidate;
|
||||
''';
|
||||
|
||||
final row = await _db
|
||||
.customSelect(
|
||||
sql,
|
||||
variables: [
|
||||
Variable.withString(userId),
|
||||
Variable.withInt(BackupSelection.selected.index),
|
||||
Variable.withInt(BackupSelection.excluded.index),
|
||||
],
|
||||
readsFrom: {_db.localAlbumAssetEntity, _db.localAlbumEntity, _db.localAssetEntity, _db.remoteAssetEntity},
|
||||
variables: [Variable.withString(userId)],
|
||||
readsFrom: {_db.localAssetEntity, _db.remoteAssetEntity},
|
||||
)
|
||||
.getSingle();
|
||||
|
||||
@@ -82,29 +51,17 @@ class DriftBackupRepository extends DriftDatabaseRepository {
|
||||
}
|
||||
|
||||
Future<List<LocalAsset>> getCandidates(String userId, {bool onlyHashed = true}) async {
|
||||
final selectedAlbumIds = _db.localAlbumEntity.selectOnly(distinct: true)
|
||||
..addColumns([_db.localAlbumEntity.id])
|
||||
..where(_db.localAlbumEntity.backupSelection.equalsValue(BackupSelection.selected));
|
||||
|
||||
final query = _db.localAssetEntity.select()
|
||||
..where(
|
||||
(lae) =>
|
||||
existsQuery(
|
||||
_db.localAlbumAssetEntity.selectOnly()
|
||||
..addColumns([_db.localAlbumAssetEntity.assetId])
|
||||
..where(
|
||||
_db.localAlbumAssetEntity.albumId.isInQuery(selectedAlbumIds) &
|
||||
_db.localAlbumAssetEntity.assetId.equalsExp(lae.id),
|
||||
),
|
||||
) &
|
||||
lae.isBackupCandidate.equals(true) &
|
||||
notExistsQuery(
|
||||
_db.remoteAssetEntity.selectOnly()
|
||||
..addColumns([_db.remoteAssetEntity.checksum])
|
||||
..where(
|
||||
_db.remoteAssetEntity.checksum.equalsExp(lae.checksum) & _db.remoteAssetEntity.ownerId.equals(userId),
|
||||
),
|
||||
) &
|
||||
lae.id.isNotInQuery(_getExcludedSubquery()),
|
||||
),
|
||||
)
|
||||
..orderBy([(localAsset) => OrderingTerm.desc(localAsset.createdAt)]);
|
||||
|
||||
@@ -112,6 +69,6 @@ class DriftBackupRepository extends DriftDatabaseRepository {
|
||||
query.where((lae) => lae.checksum.isNotNull());
|
||||
}
|
||||
|
||||
return query.map((localAsset) => localAsset.toDto()).get();
|
||||
return query.map(mapToLocalAsset).get();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ class Drift extends $Drift {
|
||||
}
|
||||
|
||||
@override
|
||||
int get schemaVersion => 30;
|
||||
int get schemaVersion => 31;
|
||||
|
||||
@override
|
||||
MigrationStrategy get migration => MigrationStrategy(
|
||||
@@ -311,6 +311,10 @@ class Drift extends $Drift {
|
||||
from29To30: (m, v30) async {
|
||||
await m.alterTable(TableMigration(v30.settings));
|
||||
},
|
||||
from30To31: (m, v31) async {
|
||||
await m.addColumn(v31.localAssetEntity, v31.localAssetEntity.isBackupCandidate);
|
||||
await m.createIndex(v31.idxLocalAssetBackupCandidate);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
+57
-56
@@ -9,17 +9,17 @@ import 'package:immich_mobile/infrastructure/entities/stack.entity.drift.dart'
|
||||
as i3;
|
||||
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart'
|
||||
as i4;
|
||||
import 'package:immich_mobile/infrastructure/entities/remote_album.entity.drift.dart'
|
||||
as i5;
|
||||
import 'package:immich_mobile/infrastructure/entities/local_album.entity.drift.dart'
|
||||
as i6;
|
||||
import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.drift.dart'
|
||||
as i7;
|
||||
import 'package:immich_mobile/infrastructure/entities/auth_user.entity.drift.dart'
|
||||
as i8;
|
||||
as i5;
|
||||
import 'package:immich_mobile/infrastructure/entities/user_metadata.entity.drift.dart'
|
||||
as i9;
|
||||
as i6;
|
||||
import 'package:immich_mobile/infrastructure/entities/partner.entity.drift.dart'
|
||||
as i7;
|
||||
import 'package:immich_mobile/infrastructure/entities/remote_album.entity.drift.dart'
|
||||
as i8;
|
||||
import 'package:immich_mobile/infrastructure/entities/local_album.entity.drift.dart'
|
||||
as i9;
|
||||
import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.drift.dart'
|
||||
as i10;
|
||||
import 'package:immich_mobile/infrastructure/entities/exif.entity.drift.dart'
|
||||
as i11;
|
||||
@@ -60,20 +60,20 @@ abstract class $Drift extends i0.GeneratedDatabase {
|
||||
late final i3.$StackEntityTable stackEntity = i3.$StackEntityTable(this);
|
||||
late final i4.$LocalAssetEntityTable localAssetEntity = i4
|
||||
.$LocalAssetEntityTable(this);
|
||||
late final i5.$RemoteAlbumEntityTable remoteAlbumEntity = i5
|
||||
.$RemoteAlbumEntityTable(this);
|
||||
late final i6.$LocalAlbumEntityTable localAlbumEntity = i6
|
||||
.$LocalAlbumEntityTable(this);
|
||||
late final i7.$LocalAlbumAssetEntityTable localAlbumAssetEntity = i7
|
||||
.$LocalAlbumAssetEntityTable(this);
|
||||
late final i8.$AuthUserEntityTable authUserEntity = i8.$AuthUserEntityTable(
|
||||
late final i5.$AuthUserEntityTable authUserEntity = i5.$AuthUserEntityTable(
|
||||
this,
|
||||
);
|
||||
late final i9.$UserMetadataEntityTable userMetadataEntity = i9
|
||||
late final i6.$UserMetadataEntityTable userMetadataEntity = i6
|
||||
.$UserMetadataEntityTable(this);
|
||||
late final i10.$PartnerEntityTable partnerEntity = i10.$PartnerEntityTable(
|
||||
late final i7.$PartnerEntityTable partnerEntity = i7.$PartnerEntityTable(
|
||||
this,
|
||||
);
|
||||
late final i8.$RemoteAlbumEntityTable remoteAlbumEntity = i8
|
||||
.$RemoteAlbumEntityTable(this);
|
||||
late final i9.$LocalAlbumEntityTable localAlbumEntity = i9
|
||||
.$LocalAlbumEntityTable(this);
|
||||
late final i10.$LocalAlbumAssetEntityTable localAlbumAssetEntity = i10
|
||||
.$LocalAlbumAssetEntityTable(this);
|
||||
late final i11.$RemoteExifEntityTable remoteExifEntity = i11
|
||||
.$RemoteExifEntityTable(this);
|
||||
late final i12.$RemoteAlbumAssetEntityTable remoteAlbumAssetEntity = i12
|
||||
@@ -111,13 +111,10 @@ abstract class $Drift extends i0.GeneratedDatabase {
|
||||
remoteAssetEntity,
|
||||
stackEntity,
|
||||
localAssetEntity,
|
||||
remoteAlbumEntity,
|
||||
localAlbumEntity,
|
||||
localAlbumAssetEntity,
|
||||
i7.idxLocalAlbumAssetAlbumAsset,
|
||||
i4.idxLocalAssetChecksum,
|
||||
i4.idxLocalAssetCloudId,
|
||||
i4.idxLocalAssetCreatedAt,
|
||||
i4.idxLocalAssetBackupCandidate,
|
||||
i3.idxStackPrimaryAssetId,
|
||||
i2.uQRemoteAssetsOwnerChecksum,
|
||||
i2.uQRemoteAssetsOwnerLibraryChecksum,
|
||||
@@ -127,6 +124,9 @@ abstract class $Drift extends i0.GeneratedDatabase {
|
||||
authUserEntity,
|
||||
userMetadataEntity,
|
||||
partnerEntity,
|
||||
remoteAlbumEntity,
|
||||
localAlbumEntity,
|
||||
localAlbumAssetEntity,
|
||||
remoteExifEntity,
|
||||
remoteAlbumAssetEntity,
|
||||
remoteAlbumUserEntity,
|
||||
@@ -140,7 +140,8 @@ abstract class $Drift extends i0.GeneratedDatabase {
|
||||
assetEditEntity,
|
||||
settingsEntity,
|
||||
assetOcrEntity,
|
||||
i10.idxPartnerSharedWithId,
|
||||
i7.idxPartnerSharedWithId,
|
||||
i10.idxLocalAlbumAssetAlbumAsset,
|
||||
i11.idxLatLng,
|
||||
i11.idxRemoteExifCity,
|
||||
i12.idxRemoteAlbumAssetAlbumAsset,
|
||||
@@ -173,6 +174,29 @@ abstract class $Drift extends i0.GeneratedDatabase {
|
||||
),
|
||||
result: [i0.TableUpdate('stack_entity', kind: i0.UpdateKind.delete)],
|
||||
),
|
||||
i0.WritePropagation(
|
||||
on: i0.TableUpdateQuery.onTableName(
|
||||
'user_entity',
|
||||
limitUpdateKind: i0.UpdateKind.delete,
|
||||
),
|
||||
result: [
|
||||
i0.TableUpdate('user_metadata_entity', kind: i0.UpdateKind.delete),
|
||||
],
|
||||
),
|
||||
i0.WritePropagation(
|
||||
on: i0.TableUpdateQuery.onTableName(
|
||||
'user_entity',
|
||||
limitUpdateKind: i0.UpdateKind.delete,
|
||||
),
|
||||
result: [i0.TableUpdate('partner_entity', kind: i0.UpdateKind.delete)],
|
||||
),
|
||||
i0.WritePropagation(
|
||||
on: i0.TableUpdateQuery.onTableName(
|
||||
'user_entity',
|
||||
limitUpdateKind: i0.UpdateKind.delete,
|
||||
),
|
||||
result: [i0.TableUpdate('partner_entity', kind: i0.UpdateKind.delete)],
|
||||
),
|
||||
i0.WritePropagation(
|
||||
on: i0.TableUpdateQuery.onTableName(
|
||||
'remote_asset_entity',
|
||||
@@ -209,29 +233,6 @@ abstract class $Drift extends i0.GeneratedDatabase {
|
||||
i0.TableUpdate('local_album_asset_entity', kind: i0.UpdateKind.delete),
|
||||
],
|
||||
),
|
||||
i0.WritePropagation(
|
||||
on: i0.TableUpdateQuery.onTableName(
|
||||
'user_entity',
|
||||
limitUpdateKind: i0.UpdateKind.delete,
|
||||
),
|
||||
result: [
|
||||
i0.TableUpdate('user_metadata_entity', kind: i0.UpdateKind.delete),
|
||||
],
|
||||
),
|
||||
i0.WritePropagation(
|
||||
on: i0.TableUpdateQuery.onTableName(
|
||||
'user_entity',
|
||||
limitUpdateKind: i0.UpdateKind.delete,
|
||||
),
|
||||
result: [i0.TableUpdate('partner_entity', kind: i0.UpdateKind.delete)],
|
||||
),
|
||||
i0.WritePropagation(
|
||||
on: i0.TableUpdateQuery.onTableName(
|
||||
'user_entity',
|
||||
limitUpdateKind: i0.UpdateKind.delete,
|
||||
),
|
||||
result: [i0.TableUpdate('partner_entity', kind: i0.UpdateKind.delete)],
|
||||
),
|
||||
i0.WritePropagation(
|
||||
on: i0.TableUpdateQuery.onTableName(
|
||||
'remote_asset_entity',
|
||||
@@ -366,18 +367,18 @@ class $DriftManager {
|
||||
i3.$$StackEntityTableTableManager(_db, _db.stackEntity);
|
||||
i4.$$LocalAssetEntityTableTableManager get localAssetEntity =>
|
||||
i4.$$LocalAssetEntityTableTableManager(_db, _db.localAssetEntity);
|
||||
i5.$$RemoteAlbumEntityTableTableManager get remoteAlbumEntity =>
|
||||
i5.$$RemoteAlbumEntityTableTableManager(_db, _db.remoteAlbumEntity);
|
||||
i6.$$LocalAlbumEntityTableTableManager get localAlbumEntity =>
|
||||
i6.$$LocalAlbumEntityTableTableManager(_db, _db.localAlbumEntity);
|
||||
i7.$$LocalAlbumAssetEntityTableTableManager get localAlbumAssetEntity => i7
|
||||
i5.$$AuthUserEntityTableTableManager get authUserEntity =>
|
||||
i5.$$AuthUserEntityTableTableManager(_db, _db.authUserEntity);
|
||||
i6.$$UserMetadataEntityTableTableManager get userMetadataEntity =>
|
||||
i6.$$UserMetadataEntityTableTableManager(_db, _db.userMetadataEntity);
|
||||
i7.$$PartnerEntityTableTableManager get partnerEntity =>
|
||||
i7.$$PartnerEntityTableTableManager(_db, _db.partnerEntity);
|
||||
i8.$$RemoteAlbumEntityTableTableManager get remoteAlbumEntity =>
|
||||
i8.$$RemoteAlbumEntityTableTableManager(_db, _db.remoteAlbumEntity);
|
||||
i9.$$LocalAlbumEntityTableTableManager get localAlbumEntity =>
|
||||
i9.$$LocalAlbumEntityTableTableManager(_db, _db.localAlbumEntity);
|
||||
i10.$$LocalAlbumAssetEntityTableTableManager get localAlbumAssetEntity => i10
|
||||
.$$LocalAlbumAssetEntityTableTableManager(_db, _db.localAlbumAssetEntity);
|
||||
i8.$$AuthUserEntityTableTableManager get authUserEntity =>
|
||||
i8.$$AuthUserEntityTableTableManager(_db, _db.authUserEntity);
|
||||
i9.$$UserMetadataEntityTableTableManager get userMetadataEntity =>
|
||||
i9.$$UserMetadataEntityTableTableManager(_db, _db.userMetadataEntity);
|
||||
i10.$$PartnerEntityTableTableManager get partnerEntity =>
|
||||
i10.$$PartnerEntityTableTableManager(_db, _db.partnerEntity);
|
||||
i11.$$RemoteExifEntityTableTableManager get remoteExifEntity =>
|
||||
i11.$$RemoteExifEntityTableTableManager(_db, _db.remoteExifEntity);
|
||||
i12.$$RemoteAlbumAssetEntityTableTableManager get remoteAlbumAssetEntity =>
|
||||
|
||||
@@ -15920,6 +15920,641 @@ i1.GeneratedColumn<String> _column_224(String aliasedName) =>
|
||||
type: i1.DriftSqlType.string,
|
||||
$customConstraints: 'NULL',
|
||||
);
|
||||
|
||||
final class Schema31 extends i0.VersionedSchema {
|
||||
Schema31({required super.database}) : super(version: 31);
|
||||
@override
|
||||
late final List<i1.DatabaseSchemaEntity> entities = [
|
||||
userEntity,
|
||||
remoteAssetEntity,
|
||||
stackEntity,
|
||||
localAssetEntity,
|
||||
idxLocalAssetChecksum,
|
||||
idxLocalAssetCloudId,
|
||||
idxLocalAssetCreatedAt,
|
||||
idxLocalAssetBackupCandidate,
|
||||
idxStackPrimaryAssetId,
|
||||
uQRemoteAssetsOwnerChecksum,
|
||||
uQRemoteAssetsOwnerLibraryChecksum,
|
||||
idxRemoteAssetChecksum,
|
||||
idxRemoteAssetStackId,
|
||||
idxRemoteAssetOwnerVisibilityDeletedCreated,
|
||||
authUserEntity,
|
||||
userMetadataEntity,
|
||||
partnerEntity,
|
||||
remoteAlbumEntity,
|
||||
localAlbumEntity,
|
||||
localAlbumAssetEntity,
|
||||
remoteExifEntity,
|
||||
remoteAlbumAssetEntity,
|
||||
remoteAlbumUserEntity,
|
||||
remoteAssetCloudIdEntity,
|
||||
memoryEntity,
|
||||
memoryAssetEntity,
|
||||
personEntity,
|
||||
assetFaceEntity,
|
||||
storeEntity,
|
||||
trashedLocalAssetEntity,
|
||||
assetEditEntity,
|
||||
settings,
|
||||
assetOcrEntity,
|
||||
idxPartnerSharedWithId,
|
||||
idxLocalAlbumAssetAlbumAsset,
|
||||
idxLatLng,
|
||||
idxRemoteExifCity,
|
||||
idxRemoteAlbumAssetAlbumAsset,
|
||||
idxRemoteAssetCloudId,
|
||||
idxPersonOwnerId,
|
||||
idxAssetFacePersonId,
|
||||
idxAssetFaceAssetId,
|
||||
idxAssetFaceVisiblePerson,
|
||||
idxTrashedLocalAssetChecksum,
|
||||
idxTrashedLocalAssetAlbum,
|
||||
idxAssetEditAssetId,
|
||||
idxAssetOcrAssetId,
|
||||
];
|
||||
late final Shape33 userEntity = Shape33(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'user_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(id)'],
|
||||
columns: [
|
||||
_column_107,
|
||||
_column_108,
|
||||
_column_109,
|
||||
_column_110,
|
||||
_column_111,
|
||||
_column_112,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape50 remoteAssetEntity = Shape50(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'remote_asset_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(id)'],
|
||||
columns: [
|
||||
_column_108,
|
||||
_column_113,
|
||||
_column_114,
|
||||
_column_115,
|
||||
_column_116,
|
||||
_column_117,
|
||||
_column_118,
|
||||
_column_107,
|
||||
_column_119,
|
||||
_column_120,
|
||||
_column_121,
|
||||
_column_122,
|
||||
_column_123,
|
||||
_column_124,
|
||||
_column_212,
|
||||
_column_125,
|
||||
_column_126,
|
||||
_column_127,
|
||||
_column_128,
|
||||
_column_129,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape35 stackEntity = Shape35(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'stack_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(id)'],
|
||||
columns: [
|
||||
_column_107,
|
||||
_column_114,
|
||||
_column_115,
|
||||
_column_121,
|
||||
_column_130,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape52 localAssetEntity = Shape52(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'local_asset_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(id)'],
|
||||
columns: [
|
||||
_column_108,
|
||||
_column_113,
|
||||
_column_114,
|
||||
_column_115,
|
||||
_column_116,
|
||||
_column_117,
|
||||
_column_118,
|
||||
_column_107,
|
||||
_column_131,
|
||||
_column_120,
|
||||
_column_225,
|
||||
_column_132,
|
||||
_column_133,
|
||||
_column_134,
|
||||
_column_135,
|
||||
_column_136,
|
||||
_column_137,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
final i1.Index idxLocalAssetChecksum = i1.Index(
|
||||
'idx_local_asset_checksum',
|
||||
'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)',
|
||||
);
|
||||
final i1.Index idxLocalAssetCloudId = i1.Index(
|
||||
'idx_local_asset_cloud_id',
|
||||
'CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)',
|
||||
);
|
||||
final i1.Index idxLocalAssetCreatedAt = i1.Index(
|
||||
'idx_local_asset_created_at',
|
||||
'CREATE INDEX IF NOT EXISTS idx_local_asset_created_at ON local_asset_entity (created_at)',
|
||||
);
|
||||
final i1.Index idxLocalAssetBackupCandidate = i1.Index(
|
||||
'idx_local_asset_backup_candidate',
|
||||
'CREATE INDEX IF NOT EXISTS idx_local_asset_backup_candidate ON local_asset_entity (is_backup_candidate)',
|
||||
);
|
||||
final i1.Index idxStackPrimaryAssetId = i1.Index(
|
||||
'idx_stack_primary_asset_id',
|
||||
'CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)',
|
||||
);
|
||||
final i1.Index uQRemoteAssetsOwnerChecksum = i1.Index(
|
||||
'UQ_remote_assets_owner_checksum',
|
||||
'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)',
|
||||
);
|
||||
final i1.Index uQRemoteAssetsOwnerLibraryChecksum = i1.Index(
|
||||
'UQ_remote_assets_owner_library_checksum',
|
||||
'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)',
|
||||
);
|
||||
final i1.Index idxRemoteAssetChecksum = i1.Index(
|
||||
'idx_remote_asset_checksum',
|
||||
'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)',
|
||||
);
|
||||
final i1.Index idxRemoteAssetStackId = i1.Index(
|
||||
'idx_remote_asset_stack_id',
|
||||
'CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)',
|
||||
);
|
||||
final i1.Index idxRemoteAssetOwnerVisibilityDeletedCreated = i1.Index(
|
||||
'idx_remote_asset_owner_visibility_deleted_created',
|
||||
'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created ON remote_asset_entity (owner_id, visibility, deleted_at, created_at DESC)',
|
||||
);
|
||||
late final Shape40 authUserEntity = Shape40(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'auth_user_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(id)'],
|
||||
columns: [
|
||||
_column_107,
|
||||
_column_108,
|
||||
_column_109,
|
||||
_column_148,
|
||||
_column_110,
|
||||
_column_111,
|
||||
_column_149,
|
||||
_column_150,
|
||||
_column_151,
|
||||
_column_152,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape4 userMetadataEntity = Shape4(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'user_metadata_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(user_id, "key")'],
|
||||
columns: [_column_153, _column_154, _column_155],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape41 partnerEntity = Shape41(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'partner_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(shared_by_id, shared_with_id)'],
|
||||
columns: [_column_156, _column_157, _column_158],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape48 remoteAlbumEntity = Shape48(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'remote_album_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(id)'],
|
||||
columns: [
|
||||
_column_107,
|
||||
_column_108,
|
||||
_column_138,
|
||||
_column_114,
|
||||
_column_115,
|
||||
_column_139,
|
||||
_column_140,
|
||||
_column_141,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape38 localAlbumEntity = Shape38(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'local_album_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(id)'],
|
||||
columns: [
|
||||
_column_107,
|
||||
_column_108,
|
||||
_column_115,
|
||||
_column_142,
|
||||
_column_143,
|
||||
_column_144,
|
||||
_column_145,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape39 localAlbumAssetEntity = Shape39(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'local_album_asset_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(asset_id, album_id)'],
|
||||
columns: [_column_146, _column_147, _column_145],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape42 remoteExifEntity = Shape42(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'remote_exif_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(asset_id)'],
|
||||
columns: [
|
||||
_column_159,
|
||||
_column_160,
|
||||
_column_161,
|
||||
_column_162,
|
||||
_column_163,
|
||||
_column_164,
|
||||
_column_117,
|
||||
_column_116,
|
||||
_column_165,
|
||||
_column_166,
|
||||
_column_167,
|
||||
_column_168,
|
||||
_column_135,
|
||||
_column_136,
|
||||
_column_169,
|
||||
_column_170,
|
||||
_column_171,
|
||||
_column_172,
|
||||
_column_173,
|
||||
_column_174,
|
||||
_column_175,
|
||||
_column_176,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape7 remoteAlbumAssetEntity = Shape7(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'remote_album_asset_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(asset_id, album_id)'],
|
||||
columns: [_column_159, _column_177],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape10 remoteAlbumUserEntity = Shape10(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'remote_album_user_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(album_id, user_id)'],
|
||||
columns: [_column_177, _column_153, _column_178],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape43 remoteAssetCloudIdEntity = Shape43(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'remote_asset_cloud_id_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(asset_id)'],
|
||||
columns: [
|
||||
_column_159,
|
||||
_column_179,
|
||||
_column_180,
|
||||
_column_134,
|
||||
_column_135,
|
||||
_column_136,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape44 memoryEntity = Shape44(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'memory_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(id)'],
|
||||
columns: [
|
||||
_column_107,
|
||||
_column_114,
|
||||
_column_115,
|
||||
_column_124,
|
||||
_column_121,
|
||||
_column_113,
|
||||
_column_181,
|
||||
_column_182,
|
||||
_column_183,
|
||||
_column_184,
|
||||
_column_185,
|
||||
_column_186,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape12 memoryAssetEntity = Shape12(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'memory_asset_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(asset_id, memory_id)'],
|
||||
columns: [_column_159, _column_187],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape45 personEntity = Shape45(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'person_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(id)'],
|
||||
columns: [
|
||||
_column_107,
|
||||
_column_114,
|
||||
_column_115,
|
||||
_column_121,
|
||||
_column_108,
|
||||
_column_188,
|
||||
_column_189,
|
||||
_column_190,
|
||||
_column_191,
|
||||
_column_192,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape46 assetFaceEntity = Shape46(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'asset_face_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(id)'],
|
||||
columns: [
|
||||
_column_107,
|
||||
_column_159,
|
||||
_column_193,
|
||||
_column_194,
|
||||
_column_195,
|
||||
_column_196,
|
||||
_column_197,
|
||||
_column_198,
|
||||
_column_199,
|
||||
_column_200,
|
||||
_column_201,
|
||||
_column_124,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape18 storeEntity = Shape18(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'store_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(id)'],
|
||||
columns: [_column_202, _column_203, _column_204],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape47 trashedLocalAssetEntity = Shape47(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'trashed_local_asset_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(id, album_id)'],
|
||||
columns: [
|
||||
_column_108,
|
||||
_column_113,
|
||||
_column_114,
|
||||
_column_115,
|
||||
_column_116,
|
||||
_column_117,
|
||||
_column_118,
|
||||
_column_107,
|
||||
_column_205,
|
||||
_column_131,
|
||||
_column_120,
|
||||
_column_132,
|
||||
_column_206,
|
||||
_column_137,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape32 assetEditEntity = Shape32(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'asset_edit_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(id)'],
|
||||
columns: [
|
||||
_column_107,
|
||||
_column_159,
|
||||
_column_207,
|
||||
_column_208,
|
||||
_column_209,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape49 settings = Shape49(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'settings',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY("key")'],
|
||||
columns: [_column_210, _column_224, _column_115],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape51 assetOcrEntity = Shape51(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'asset_ocr_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(id)'],
|
||||
columns: [
|
||||
_column_107,
|
||||
_column_159,
|
||||
_column_213,
|
||||
_column_214,
|
||||
_column_215,
|
||||
_column_216,
|
||||
_column_217,
|
||||
_column_218,
|
||||
_column_219,
|
||||
_column_220,
|
||||
_column_221,
|
||||
_column_222,
|
||||
_column_223,
|
||||
_column_201,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
final i1.Index idxPartnerSharedWithId = i1.Index(
|
||||
'idx_partner_shared_with_id',
|
||||
'CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)',
|
||||
);
|
||||
final i1.Index idxLocalAlbumAssetAlbumAsset = i1.Index(
|
||||
'idx_local_album_asset_album_asset',
|
||||
'CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)',
|
||||
);
|
||||
final i1.Index idxLatLng = i1.Index(
|
||||
'idx_lat_lng',
|
||||
'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)',
|
||||
);
|
||||
final i1.Index idxRemoteExifCity = i1.Index(
|
||||
'idx_remote_exif_city',
|
||||
'CREATE INDEX IF NOT EXISTS idx_remote_exif_city ON remote_exif_entity (city) WHERE city IS NOT NULL',
|
||||
);
|
||||
final i1.Index idxRemoteAlbumAssetAlbumAsset = i1.Index(
|
||||
'idx_remote_album_asset_album_asset',
|
||||
'CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)',
|
||||
);
|
||||
final i1.Index idxRemoteAssetCloudId = i1.Index(
|
||||
'idx_remote_asset_cloud_id',
|
||||
'CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)',
|
||||
);
|
||||
final i1.Index idxPersonOwnerId = i1.Index(
|
||||
'idx_person_owner_id',
|
||||
'CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)',
|
||||
);
|
||||
final i1.Index idxAssetFacePersonId = i1.Index(
|
||||
'idx_asset_face_person_id',
|
||||
'CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)',
|
||||
);
|
||||
final i1.Index idxAssetFaceAssetId = i1.Index(
|
||||
'idx_asset_face_asset_id',
|
||||
'CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)',
|
||||
);
|
||||
final i1.Index idxAssetFaceVisiblePerson = i1.Index(
|
||||
'idx_asset_face_visible_person',
|
||||
'CREATE INDEX IF NOT EXISTS idx_asset_face_visible_person ON asset_face_entity (person_id, asset_id) WHERE is_visible = 1 AND deleted_at IS NULL',
|
||||
);
|
||||
final i1.Index idxTrashedLocalAssetChecksum = i1.Index(
|
||||
'idx_trashed_local_asset_checksum',
|
||||
'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)',
|
||||
);
|
||||
final i1.Index idxTrashedLocalAssetAlbum = i1.Index(
|
||||
'idx_trashed_local_asset_album',
|
||||
'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)',
|
||||
);
|
||||
final i1.Index idxAssetEditAssetId = i1.Index(
|
||||
'idx_asset_edit_asset_id',
|
||||
'CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)',
|
||||
);
|
||||
final i1.Index idxAssetOcrAssetId = i1.Index(
|
||||
'idx_asset_ocr_asset_id',
|
||||
'CREATE INDEX IF NOT EXISTS idx_asset_ocr_asset_id ON asset_ocr_entity (asset_id)',
|
||||
);
|
||||
}
|
||||
|
||||
class Shape52 extends i0.VersionedTable {
|
||||
Shape52({required super.source, required super.alias}) : super.aliased();
|
||||
i1.GeneratedColumn<String> get name =>
|
||||
columnsByName['name']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<int> get type =>
|
||||
columnsByName['type']! as i1.GeneratedColumn<int>;
|
||||
i1.GeneratedColumn<String> get createdAt =>
|
||||
columnsByName['created_at']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<String> get updatedAt =>
|
||||
columnsByName['updated_at']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<int> get width =>
|
||||
columnsByName['width']! as i1.GeneratedColumn<int>;
|
||||
i1.GeneratedColumn<int> get height =>
|
||||
columnsByName['height']! as i1.GeneratedColumn<int>;
|
||||
i1.GeneratedColumn<int> get durationMs =>
|
||||
columnsByName['duration_ms']! as i1.GeneratedColumn<int>;
|
||||
i1.GeneratedColumn<String> get id =>
|
||||
columnsByName['id']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<String> get checksum =>
|
||||
columnsByName['checksum']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<int> get isFavorite =>
|
||||
columnsByName['is_favorite']! as i1.GeneratedColumn<int>;
|
||||
i1.GeneratedColumn<int> get isBackupCandidate =>
|
||||
columnsByName['is_backup_candidate']! as i1.GeneratedColumn<int>;
|
||||
i1.GeneratedColumn<int> get orientation =>
|
||||
columnsByName['orientation']! as i1.GeneratedColumn<int>;
|
||||
i1.GeneratedColumn<String> get iCloudId =>
|
||||
columnsByName['i_cloud_id']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<String> get adjustmentTime =>
|
||||
columnsByName['adjustment_time']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<double> get latitude =>
|
||||
columnsByName['latitude']! as i1.GeneratedColumn<double>;
|
||||
i1.GeneratedColumn<double> get longitude =>
|
||||
columnsByName['longitude']! as i1.GeneratedColumn<double>;
|
||||
i1.GeneratedColumn<int> get playbackStyle =>
|
||||
columnsByName['playback_style']! as i1.GeneratedColumn<int>;
|
||||
}
|
||||
|
||||
i1.GeneratedColumn<int> _column_225(String aliasedName) =>
|
||||
i1.GeneratedColumn<int>(
|
||||
'is_backup_candidate',
|
||||
aliasedName,
|
||||
false,
|
||||
type: i1.DriftSqlType.int,
|
||||
$customConstraints:
|
||||
'NOT NULL DEFAULT 0 CHECK (is_backup_candidate IN (0, 1))',
|
||||
defaultValue: const i1.CustomExpression('0'),
|
||||
);
|
||||
i0.MigrationStepWithVersion migrationSteps({
|
||||
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
|
||||
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
|
||||
@@ -15950,6 +16585,7 @@ i0.MigrationStepWithVersion migrationSteps({
|
||||
required Future<void> Function(i1.Migrator m, Schema28 schema) from27To28,
|
||||
required Future<void> Function(i1.Migrator m, Schema29 schema) from28To29,
|
||||
required Future<void> Function(i1.Migrator m, Schema30 schema) from29To30,
|
||||
required Future<void> Function(i1.Migrator m, Schema31 schema) from30To31,
|
||||
}) {
|
||||
return (currentVersion, database) async {
|
||||
switch (currentVersion) {
|
||||
@@ -16098,6 +16734,11 @@ i0.MigrationStepWithVersion migrationSteps({
|
||||
final migrator = i1.Migrator(database, schema);
|
||||
await from29To30(migrator, schema);
|
||||
return 30;
|
||||
case 30:
|
||||
final schema = Schema31(database: database);
|
||||
final migrator = i1.Migrator(database, schema);
|
||||
await from30To31(migrator, schema);
|
||||
return 31;
|
||||
default:
|
||||
throw ArgumentError.value('Unknown migration from $currentVersion');
|
||||
}
|
||||
@@ -16134,6 +16775,7 @@ i1.OnUpgrade stepByStep({
|
||||
required Future<void> Function(i1.Migrator m, Schema28 schema) from27To28,
|
||||
required Future<void> Function(i1.Migrator m, Schema29 schema) from28To29,
|
||||
required Future<void> Function(i1.Migrator m, Schema30 schema) from29To30,
|
||||
required Future<void> Function(i1.Migrator m, Schema31 schema) from30To31,
|
||||
}) => i0.VersionedSchema.stepByStepHelper(
|
||||
step: migrationSteps(
|
||||
from1To2: from1To2,
|
||||
@@ -16165,5 +16807,6 @@ i1.OnUpgrade stepByStep({
|
||||
from27To28: from27To28,
|
||||
from28To29: from28To29,
|
||||
from29To30: from29To30,
|
||||
from30To31: from30To31,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:immich_mobile/constants/constants.dart';
|
||||
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/local_album.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/local_album.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/mapper.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
|
||||
enum SortLocalAlbumsBy { id, backupSelection, isIosSharedAlbum, name, assetCount, newestAsset }
|
||||
@@ -47,26 +48,32 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
|
||||
query.orderBy(orderings);
|
||||
}
|
||||
|
||||
return query.map((row) => row.readTable(_db.localAlbumEntity).toDto(assetCount: row.read(assetCount) ?? 0)).get();
|
||||
return query
|
||||
.map((row) => mapToLocalAlbum(row.readTable(_db.localAlbumEntity), assetCount: row.read(assetCount) ?? 0))
|
||||
.get();
|
||||
}
|
||||
|
||||
Future<List<LocalAlbum>> getBackupAlbums() async {
|
||||
final query = _db.localAlbumEntity.select()
|
||||
..where((row) => row.backupSelection.equalsValue(BackupSelection.selected));
|
||||
|
||||
return query.map((row) => row.toDto()).get();
|
||||
return query.map(mapToLocalAlbum).get();
|
||||
}
|
||||
|
||||
Future<void> delete(String albumId) => transaction(() async {
|
||||
// Remove all assets that are only in this particular album
|
||||
// We cannot remove all assets in the album because they might be in other albums in iOS
|
||||
// That is not the case on Android since asset <-> album has one:one mapping
|
||||
final assetsToDelete = CurrentPlatform.isIOS ? await _getUniqueAssetsInAlbum(albumId) : await getAssetIds(albumId);
|
||||
await _deleteAssets(assetsToDelete);
|
||||
|
||||
await _db.managers.localAlbumEntity
|
||||
.filter((a) => a.id.equals(albumId) & a.backupSelection.equals(BackupSelection.none))
|
||||
.delete();
|
||||
final affectedAssetIds = await getAssetIds(albumId);
|
||||
final assetsToDelete = CurrentPlatform.isIOS ? await _getUniqueAssetsInAlbum(albumId) : affectedAssetIds;
|
||||
await _db.transaction(() async {
|
||||
await _deleteAssets(assetsToDelete);
|
||||
await _db.localAlbumAssetEntity.deleteWhere((f) => f.albumId.equals(albumId));
|
||||
await _db.managers.localAlbumEntity
|
||||
.filter((a) => a.id.equals(albumId) & a.backupSelection.equals(BackupSelection.none))
|
||||
.delete();
|
||||
await recomputeBackupCandidatesForAssets(affectedAssetIds);
|
||||
});
|
||||
});
|
||||
|
||||
Future<void> syncDeletes(String albumId, Iterable<String> assetIdsToKeep) async {
|
||||
@@ -126,64 +133,93 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
|
||||
);
|
||||
}
|
||||
await _removeAssets(localAlbum.id, toDelete);
|
||||
await recomputeBackupCandidatesForAlbum(localAlbum.id);
|
||||
await recomputeBackupCandidatesForAssets(toDelete);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> updateAll(Iterable<LocalAlbum> albums) {
|
||||
return _db.transaction(() async {
|
||||
await _db.localAlbumEntity.update().write(const LocalAlbumEntityCompanion(marker_: Value(true)));
|
||||
|
||||
await _db.batch((batch) {
|
||||
for (final album in albums) {
|
||||
final companion = LocalAlbumEntityCompanion.insert(
|
||||
id: album.id,
|
||||
name: album.name,
|
||||
updatedAt: Value(album.updatedAt),
|
||||
backupSelection: album.backupSelection,
|
||||
isIosSharedAlbum: Value(album.isIosSharedAlbum),
|
||||
marker_: const Value(null),
|
||||
);
|
||||
|
||||
batch.insert(
|
||||
_db.localAlbumEntity,
|
||||
companion,
|
||||
onConflict: DoUpdate(
|
||||
(old) => LocalAlbumEntityCompanion(
|
||||
id: companion.id,
|
||||
name: companion.name,
|
||||
updatedAt: companion.updatedAt,
|
||||
isIosSharedAlbum: companion.isIosSharedAlbum,
|
||||
marker_: companion.marker_,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
await _markAllAlbums();
|
||||
await _upsertAndUnmarkIncoming(albums);
|
||||
if (CurrentPlatform.isAndroid) {
|
||||
// On Android, an asset can only be in one album
|
||||
// So, get the albums that are marked for deletion
|
||||
// On Android, an asset can only be in one album. So, get the albums that are marked for deletion
|
||||
// and delete all the assets that are in those albums
|
||||
final deleteSmt = _db.localAssetEntity.delete();
|
||||
deleteSmt.where((localAsset) {
|
||||
final subQuery = _db.localAlbumAssetEntity.selectOnly()
|
||||
..addColumns([_db.localAlbumAssetEntity.assetId])
|
||||
..join([
|
||||
innerJoin(_db.localAlbumEntity, _db.localAlbumAssetEntity.albumId.equalsExp(_db.localAlbumEntity.id)),
|
||||
]);
|
||||
subQuery.where(_db.localAlbumEntity.marker_.isNotNull());
|
||||
return localAsset.id.isInQuery(subQuery);
|
||||
});
|
||||
await deleteSmt.go();
|
||||
await _hardDeleteAssetsInMarkedAlbums();
|
||||
}
|
||||
|
||||
// Only remove albums that are not explicitly selected or excluded from backups
|
||||
await _db.localAlbumEntity.deleteWhere(
|
||||
(f) => f.marker_.isNotNull() & f.backupSelection.equalsValue(BackupSelection.none),
|
||||
);
|
||||
final affectedAssetIds = await _removeAssetsFromMarkedAlbums();
|
||||
await _deleteMarkedNoneAlbums();
|
||||
await recomputeBackupCandidatesForAssets(affectedAssetIds);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _markAllAlbums() async {
|
||||
await _db.localAlbumEntity.update().write(const LocalAlbumEntityCompanion(marker_: Value(true)));
|
||||
}
|
||||
|
||||
Future<void> _upsertAndUnmarkIncoming(Iterable<LocalAlbum> albums) async {
|
||||
await _db.batch((batch) {
|
||||
for (final album in albums) {
|
||||
final companion = LocalAlbumEntityCompanion.insert(
|
||||
id: album.id,
|
||||
name: album.name,
|
||||
updatedAt: Value(album.updatedAt),
|
||||
backupSelection: album.backupSelection,
|
||||
isIosSharedAlbum: Value(album.isIosSharedAlbum),
|
||||
marker_: const Value(null),
|
||||
);
|
||||
batch.insert(
|
||||
_db.localAlbumEntity,
|
||||
companion,
|
||||
onConflict: DoUpdate(
|
||||
(old) => LocalAlbumEntityCompanion(
|
||||
id: companion.id,
|
||||
name: companion.name,
|
||||
updatedAt: companion.updatedAt,
|
||||
isIosSharedAlbum: companion.isIosSharedAlbum,
|
||||
marker_: companion.marker_,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _hardDeleteAssetsInMarkedAlbums() async {
|
||||
final deleteSmt = _db.localAssetEntity.delete();
|
||||
deleteSmt.where((localAsset) {
|
||||
final subQuery = _db.localAlbumAssetEntity.selectOnly()
|
||||
..addColumns([_db.localAlbumAssetEntity.assetId])
|
||||
..join([innerJoin(_db.localAlbumEntity, _db.localAlbumAssetEntity.albumId.equalsExp(_db.localAlbumEntity.id))]);
|
||||
subQuery.where(_db.localAlbumEntity.marker_.isNotNull());
|
||||
return localAsset.id.isInQuery(subQuery);
|
||||
});
|
||||
await deleteSmt.go();
|
||||
}
|
||||
|
||||
Future<List<String>> _removeAssetsFromMarkedAlbums() async {
|
||||
final orphanedAlbumIds = _db.localAlbumEntity.selectOnly()
|
||||
..addColumns([_db.localAlbumEntity.id])
|
||||
..where(_db.localAlbumEntity.marker_.isNotNull());
|
||||
|
||||
final affectedAssetIds =
|
||||
await (_db.localAlbumAssetEntity.selectOnly(distinct: true)
|
||||
..addColumns([_db.localAlbumAssetEntity.assetId])
|
||||
..where(_db.localAlbumAssetEntity.albumId.isInQuery(orphanedAlbumIds)))
|
||||
.map((row) => row.read(_db.localAlbumAssetEntity.assetId)!)
|
||||
.get();
|
||||
|
||||
await (_db.localAlbumAssetEntity.delete()..where((f) => f.albumId.isInQuery(orphanedAlbumIds))).go();
|
||||
|
||||
return affectedAssetIds;
|
||||
}
|
||||
|
||||
Future<void> _deleteMarkedNoneAlbums() async {
|
||||
await _db.localAlbumEntity.deleteWhere(
|
||||
(f) => f.marker_.isNotNull() & f.backupSelection.equalsValue(BackupSelection.none),
|
||||
);
|
||||
}
|
||||
|
||||
Future<List<LocalAsset>> getAssets(String albumId) {
|
||||
final query =
|
||||
_db.localAlbumAssetEntity.select().join([
|
||||
@@ -191,7 +227,7 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
|
||||
])
|
||||
..where(_db.localAlbumAssetEntity.albumId.equals(albumId))
|
||||
..orderBy([OrderingTerm.asc(_db.localAssetEntity.id)]);
|
||||
return query.map((row) => row.readTable(_db.localAssetEntity).toDto()).get();
|
||||
return query.map((row) => mapToLocalAsset(row.readTable(_db.localAssetEntity))).get();
|
||||
}
|
||||
|
||||
Future<List<String>> getAssetIds(String albumId) {
|
||||
@@ -232,6 +268,8 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
await recomputeBackupCandidatesForAssets(assetAlbums.keys);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -240,10 +278,65 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
|
||||
_db.localAlbumAssetEntity.select().join([
|
||||
innerJoin(_db.localAssetEntity, _db.localAlbumAssetEntity.assetId.equalsExp(_db.localAssetEntity.id)),
|
||||
])
|
||||
..where(_db.localAlbumAssetEntity.albumId.equals(albumId) & _db.localAssetEntity.checksum.isNull())
|
||||
..where(
|
||||
_db.localAlbumAssetEntity.albumId.equals(albumId) &
|
||||
_db.localAssetEntity.checksum.isNull() &
|
||||
_db.localAssetEntity.isBackupCandidate.equals(true),
|
||||
)
|
||||
..orderBy([OrderingTerm.desc(_db.localAssetEntity.createdAt)]);
|
||||
|
||||
return query.map((row) => row.readTable(_db.localAssetEntity).toDto()).get();
|
||||
return query.map((row) => mapToLocalAsset(row.readTable(_db.localAssetEntity))).get();
|
||||
}
|
||||
|
||||
Future<void> recomputeBackupCandidatesForAlbum(String albumId) => _recomputeBackupCandidates(
|
||||
whereClause: 'WHERE id IN (SELECT asset_id FROM local_album_asset_entity WHERE album_id = ?)',
|
||||
extraVariables: [Variable.withString(albumId)],
|
||||
);
|
||||
|
||||
Future<void> recomputeBackupCandidatesForAssets(Iterable<String> assetIds) async {
|
||||
final ids = assetIds.toList(growable: false);
|
||||
if (ids.isEmpty) {
|
||||
return;
|
||||
}
|
||||
for (final slice in ids.slices(kDriftMaxChunk)) {
|
||||
await _recomputeBackupCandidates(
|
||||
whereClause: 'WHERE id IN (${List.filled(slice.length, '?').join(',')})',
|
||||
extraVariables: slice.map(Variable.withString).toList(growable: false),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> recomputeAllBackupCandidates() => _recomputeBackupCandidates(whereClause: '', extraVariables: const []);
|
||||
|
||||
Future<void> _recomputeBackupCandidates({
|
||||
required String whereClause,
|
||||
required List<Variable<Object>> extraVariables,
|
||||
}) async {
|
||||
await _db.customUpdate(
|
||||
'''
|
||||
UPDATE local_asset_entity
|
||||
SET is_backup_candidate = (
|
||||
EXISTS (
|
||||
SELECT 1 FROM local_album_asset_entity laa
|
||||
INNER JOIN local_album_entity la ON la.id = laa.album_id
|
||||
WHERE laa.asset_id = local_asset_entity.id AND la.backup_selection = ?
|
||||
)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM local_album_asset_entity laa
|
||||
INNER JOIN local_album_entity la ON la.id = laa.album_id
|
||||
WHERE laa.asset_id = local_asset_entity.id AND la.backup_selection = ?
|
||||
)
|
||||
)
|
||||
$whereClause
|
||||
''',
|
||||
variables: [
|
||||
Variable.withInt(BackupSelection.selected.index),
|
||||
Variable.withInt(BackupSelection.excluded.index),
|
||||
...extraVariables,
|
||||
],
|
||||
updates: {_db.localAssetEntity},
|
||||
updateKind: UpdateKind.update,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> updateCloudMapping(Map<String, String> cloudMapping) {
|
||||
@@ -424,7 +517,7 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
|
||||
..orderBy([OrderingTerm.desc(_db.localAssetEntity.createdAt)])
|
||||
..limit(1);
|
||||
|
||||
final results = await query.map((row) => row.readTable(_db.localAssetEntity).toDto()).get();
|
||||
final results = await query.map((row) => mapToLocalAsset(row.readTable(_db.localAssetEntity))).get();
|
||||
|
||||
return results.isNotEmpty ? results.first : null;
|
||||
}
|
||||
|
||||
@@ -6,9 +6,8 @@ import 'package:immich_mobile/constants/constants.dart';
|
||||
import 'package:immich_mobile/constants/enums.dart';
|
||||
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/local_album.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/mapper.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
|
||||
class RemovalCandidatesResult {
|
||||
@@ -33,7 +32,7 @@ class DriftLocalAssetRepository extends DriftDatabaseRepository {
|
||||
])..where(_db.localAssetEntity.id.equals(id));
|
||||
|
||||
return query.map((row) {
|
||||
final asset = row.readTable(_db.localAssetEntity).toDto();
|
||||
final asset = mapToLocalAsset(row.readTable(_db.localAssetEntity));
|
||||
return asset.copyWith(remoteId: row.read(_db.remoteAssetEntity.id));
|
||||
});
|
||||
}
|
||||
@@ -43,7 +42,7 @@ class DriftLocalAssetRepository extends DriftDatabaseRepository {
|
||||
Future<List<LocalAsset?>> getByChecksum(String checksum) {
|
||||
final query = _db.localAssetEntity.select()..where((lae) => lae.checksum.equals(checksum));
|
||||
|
||||
return query.map((row) => row.toDto()).get();
|
||||
return query.map(mapToLocalAsset).get();
|
||||
}
|
||||
|
||||
Stream<LocalAsset?> watch(String id) => _assetSelectable(id).watchSingleOrNull();
|
||||
@@ -79,7 +78,7 @@ class DriftLocalAssetRepository extends DriftDatabaseRepository {
|
||||
Future<LocalAsset?> getById(String id) {
|
||||
final query = _db.localAssetEntity.select()..where((lae) => lae.id.equals(id));
|
||||
|
||||
return query.map((row) => row.toDto()).getSingleOrNull();
|
||||
return query.map(mapToLocalAsset).getSingleOrNull();
|
||||
}
|
||||
|
||||
Future<int> getCount() {
|
||||
@@ -106,7 +105,7 @@ class DriftLocalAssetRepository extends DriftDatabaseRepository {
|
||||
if (backupSelection != null) {
|
||||
query.where((lae) => lae.backupSelection.equalsValue(backupSelection));
|
||||
}
|
||||
return query.map((localAlbum) => localAlbum.toDto()).get();
|
||||
return query.map(mapToLocalAlbum).get();
|
||||
}
|
||||
|
||||
Future<Map<String, List<LocalAsset>>> getAssetsFromBackupAlbums(Iterable<String> remoteIds) async {
|
||||
@@ -138,7 +137,7 @@ class DriftLocalAssetRepository extends DriftDatabaseRepository {
|
||||
|
||||
for (final row in rows) {
|
||||
final albumId = row.readTable(_db.localAlbumAssetEntity).albumId;
|
||||
final asset = row.readTable(_db.localAssetEntity).toDto();
|
||||
final asset = mapToLocalAsset(row.readTable(_db.localAssetEntity));
|
||||
(result[albumId] ??= <LocalAsset>[]).add(asset);
|
||||
}
|
||||
}
|
||||
@@ -200,7 +199,7 @@ class DriftLocalAssetRepository extends DriftDatabaseRepository {
|
||||
query.where(whereClause);
|
||||
|
||||
final rows = await query.get();
|
||||
final assets = rows.map((row) => row.readTable(_db.localAssetEntity).toDto()).toList();
|
||||
final assets = rows.map((row) => mapToLocalAsset(row.readTable(_db.localAssetEntity))).toList();
|
||||
final totalBytes = rows.fold<int>(0, (sum, row) {
|
||||
final fileSize = row.readTableOrNull(_db.remoteExifEntity)?.fileSize;
|
||||
return sum + (fileSize ?? 0);
|
||||
@@ -211,7 +210,7 @@ class DriftLocalAssetRepository extends DriftDatabaseRepository {
|
||||
|
||||
Future<List<LocalAsset>> getEmptyCloudIdAssets() {
|
||||
final query = _db.localAssetEntity.select()..where((row) => row.iCloudId.isNull());
|
||||
return query.map((row) => row.toDto()).get();
|
||||
return query.map(mapToLocalAsset).get();
|
||||
}
|
||||
|
||||
Future<void> reconcileHashesFromCloudId() async {
|
||||
|
||||
@@ -7,9 +7,9 @@ import 'package:immich_mobile/domain/models/album/album.model.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/domain/services/timeline.service.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/mapper.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/map.repository.dart';
|
||||
import 'package:maplibre_gl/maplibre_gl.dart';
|
||||
@@ -175,7 +175,9 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
|
||||
..limit(count, offset: offset);
|
||||
|
||||
return query
|
||||
.map((row) => row.readTable(_db.localAssetEntity).toDto(remoteId: row.read(_db.remoteAssetEntity.id)))
|
||||
.map(
|
||||
(row) => mapToLocalAsset(row.readTable(_db.localAssetEntity), remoteId: row.read(_db.remoteAssetEntity.id)),
|
||||
)
|
||||
.get();
|
||||
}
|
||||
|
||||
|
||||
@@ -3,10 +3,10 @@ import 'package:drift/drift.dart';
|
||||
import 'package:immich_mobile/constants/constants.dart';
|
||||
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/mapper.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
|
||||
typedef TrashedAsset = ({String albumId, LocalAsset asset});
|
||||
@@ -198,6 +198,9 @@ class DriftTrashedLocalAssetRepository extends DriftDatabaseRepository {
|
||||
isFavorite: Value(e.isFavorite),
|
||||
orientation: Value(e.orientation),
|
||||
playbackStyle: Value(e.playbackStyle),
|
||||
// getToRestore only restores assets whose album is selected
|
||||
// TODO: Refactor getToRestore to not assume that and remove the backup candidate flag from here
|
||||
isBackupCandidate: const Value(true),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -283,7 +286,7 @@ class DriftTrashedLocalAssetRepository extends DriftDatabaseRepository {
|
||||
|
||||
for (final row in rows) {
|
||||
final albumId = row.readTable(_db.localAlbumAssetEntity).albumId;
|
||||
final asset = row.readTable(_db.localAssetEntity).toDto();
|
||||
final asset = mapToLocalAsset(row.readTable(_db.localAssetEntity));
|
||||
(result[albumId] ??= <LocalAsset>[]).add(asset);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,11 +14,12 @@ import 'package:immich_mobile/domain/models/timeline.model.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/settings.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/network.repository.dart';
|
||||
import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart';
|
||||
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
|
||||
|
||||
const int targetVersion = 26;
|
||||
const int targetVersion = 27;
|
||||
|
||||
Future<void> migrateDatabaseIfNeeded(Drift drift) async {
|
||||
final int version = Store.get(StoreKey.version, targetVersion);
|
||||
@@ -31,6 +32,10 @@ Future<void> migrateDatabaseIfNeeded(Drift drift) async {
|
||||
await _migrateTo26(drift);
|
||||
}
|
||||
|
||||
if (version < 27) {
|
||||
await DriftLocalAlbumRepository(drift).recomputeAllBackupCandidates();
|
||||
}
|
||||
|
||||
await Store.put(StoreKey.version, targetVersion);
|
||||
return;
|
||||
}
|
||||
|
||||
+4
@@ -34,6 +34,7 @@ import 'schema_v27.dart' as v27;
|
||||
import 'schema_v28.dart' as v28;
|
||||
import 'schema_v29.dart' as v29;
|
||||
import 'schema_v30.dart' as v30;
|
||||
import 'schema_v31.dart' as v31;
|
||||
|
||||
class GeneratedHelper implements SchemaInstantiationHelper {
|
||||
@override
|
||||
@@ -99,6 +100,8 @@ class GeneratedHelper implements SchemaInstantiationHelper {
|
||||
return v29.DatabaseAtV29(db);
|
||||
case 30:
|
||||
return v30.DatabaseAtV30(db);
|
||||
case 31:
|
||||
return v31.DatabaseAtV31(db);
|
||||
default:
|
||||
throw MissingSchemaException(version, versions);
|
||||
}
|
||||
@@ -135,5 +138,6 @@ class GeneratedHelper implements SchemaInstantiationHelper {
|
||||
28,
|
||||
29,
|
||||
30,
|
||||
31,
|
||||
];
|
||||
}
|
||||
|
||||
+10071
File diff suppressed because it is too large
Load Diff
@@ -14,10 +14,6 @@ void main() {
|
||||
sut = DriftBackupRepository(ctx.db);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await ctx.dispose();
|
||||
});
|
||||
|
||||
group('getAllCounts', () {
|
||||
late String userId;
|
||||
|
||||
|
||||
@@ -0,0 +1,366 @@
|
||||
import 'package:drift/drift.dart' show TableStatements, Value;
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/local_album.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/mapper.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart';
|
||||
|
||||
import '../repository_context.dart';
|
||||
|
||||
void main() {
|
||||
late MediumRepositoryContext ctx;
|
||||
late DriftLocalAlbumRepository sut;
|
||||
|
||||
setUp(() {
|
||||
ctx = MediumRepositoryContext();
|
||||
sut = DriftLocalAlbumRepository(ctx.db);
|
||||
});
|
||||
|
||||
group('recomputeAllBackupCandidates', () {
|
||||
test('sets flag true when asset is only in a selected album', () async {
|
||||
final selected = await ctx.newLocalAlbum(backupSelection: BackupSelection.selected);
|
||||
final asset = await ctx.newLocalAsset();
|
||||
await ctx.newLocalAlbumAsset(albumId: selected.id, assetId: asset.id, recomputeBackupCandidates: false);
|
||||
|
||||
expect(await ctx.isAssetBackupCandidate(asset.id), isFalse);
|
||||
await sut.recomputeAllBackupCandidates();
|
||||
expect(await ctx.isAssetBackupCandidate(asset.id), isTrue);
|
||||
});
|
||||
|
||||
test('keeps flag false when asset is only in a none / excluded album', () async {
|
||||
final none = await ctx.newLocalAlbum(backupSelection: BackupSelection.none);
|
||||
final excluded = await ctx.newLocalAlbum(backupSelection: BackupSelection.excluded);
|
||||
final inNone = await ctx.newLocalAsset();
|
||||
final inExcluded = await ctx.newLocalAsset();
|
||||
await ctx.newLocalAlbumAsset(albumId: none.id, assetId: inNone.id, recomputeBackupCandidates: false);
|
||||
await ctx.newLocalAlbumAsset(albumId: excluded.id, assetId: inExcluded.id, recomputeBackupCandidates: false);
|
||||
|
||||
await sut.recomputeAllBackupCandidates();
|
||||
expect(await ctx.isAssetBackupCandidate(inNone.id), isFalse);
|
||||
expect(await ctx.isAssetBackupCandidate(inExcluded.id), isFalse);
|
||||
});
|
||||
|
||||
test('keeps flag false when asset is in both a selected and an excluded album', () async {
|
||||
final selected = await ctx.newLocalAlbum(backupSelection: BackupSelection.selected);
|
||||
final excluded = await ctx.newLocalAlbum(backupSelection: BackupSelection.excluded);
|
||||
final asset = await ctx.newLocalAsset();
|
||||
await ctx.newLocalAlbumAsset(albumId: selected.id, assetId: asset.id, recomputeBackupCandidates: false);
|
||||
await ctx.newLocalAlbumAsset(albumId: excluded.id, assetId: asset.id, recomputeBackupCandidates: false);
|
||||
|
||||
await sut.recomputeAllBackupCandidates();
|
||||
expect(await ctx.isAssetBackupCandidate(asset.id), isFalse);
|
||||
});
|
||||
|
||||
test('flipping selection to excluded flips a candidate back to false', () async {
|
||||
final album = await ctx.newLocalAlbum(backupSelection: BackupSelection.selected);
|
||||
final asset = await ctx.newLocalAsset();
|
||||
await ctx.newLocalAlbumAsset(albumId: album.id, assetId: asset.id, recomputeBackupCandidates: false);
|
||||
await sut.recomputeAllBackupCandidates();
|
||||
expect(await ctx.isAssetBackupCandidate(asset.id), isTrue);
|
||||
|
||||
await (ctx.db.localAlbumEntity.update()..where((row) => row.id.equals(album.id))).write(
|
||||
const LocalAlbumEntityCompanion(backupSelection: Value(BackupSelection.excluded)),
|
||||
);
|
||||
await sut.recomputeAllBackupCandidates();
|
||||
expect(await ctx.isAssetBackupCandidate(asset.id), isFalse);
|
||||
});
|
||||
});
|
||||
|
||||
group('recomputeBackupCandidatesForAlbum', () {
|
||||
test('only touches assets in the given album', () async {
|
||||
final albumA = await ctx.newLocalAlbum(backupSelection: BackupSelection.selected);
|
||||
final albumB = await ctx.newLocalAlbum(backupSelection: BackupSelection.selected);
|
||||
final inAlbumA = await ctx.newLocalAsset();
|
||||
final inAlbumB = await ctx.newLocalAsset();
|
||||
await ctx.newLocalAlbumAsset(albumId: albumA.id, assetId: inAlbumA.id, recomputeBackupCandidates: false);
|
||||
await ctx.newLocalAlbumAsset(albumId: albumB.id, assetId: inAlbumB.id, recomputeBackupCandidates: false);
|
||||
await sut.recomputeBackupCandidatesForAlbum(albumA.id);
|
||||
|
||||
expect(await ctx.isAssetBackupCandidate(inAlbumA.id), isTrue);
|
||||
expect(
|
||||
await ctx.isAssetBackupCandidate(inAlbumB.id),
|
||||
isFalse,
|
||||
reason: 'asset in album B should be untouched by an album A-scoped recompute',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('recomputeBackupCandidatesForAssets', () {
|
||||
test('only touches the listed assets', () async {
|
||||
final selected = await ctx.newLocalAlbum(backupSelection: BackupSelection.selected);
|
||||
final touched = await ctx.newLocalAsset();
|
||||
final untouched = await ctx.newLocalAsset();
|
||||
await ctx.newLocalAlbumAsset(albumId: selected.id, assetId: touched.id, recomputeBackupCandidates: false);
|
||||
await ctx.newLocalAlbumAsset(albumId: selected.id, assetId: untouched.id, recomputeBackupCandidates: false);
|
||||
await sut.recomputeBackupCandidatesForAssets([touched.id]);
|
||||
|
||||
expect(await ctx.isAssetBackupCandidate(touched.id), isTrue);
|
||||
expect(await ctx.isAssetBackupCandidate(untouched.id), isFalse);
|
||||
});
|
||||
});
|
||||
|
||||
group('upsert', () {
|
||||
test('flipping selection via upsert recomputes the album', () async {
|
||||
final asset = await ctx.newLocalAsset();
|
||||
final album = mapToLocalAlbum(await ctx.newLocalAlbum(backupSelection: BackupSelection.none), assetCount: 1);
|
||||
await ctx.newLocalAlbumAsset(albumId: album.id, assetId: asset.id, recomputeBackupCandidates: false);
|
||||
await sut.recomputeAllBackupCandidates();
|
||||
expect(await ctx.isAssetBackupCandidate(asset.id), isFalse);
|
||||
|
||||
await sut.upsert(album.copyWith(backupSelection: BackupSelection.selected, updatedAt: DateTime.now()));
|
||||
expect(await ctx.isAssetBackupCandidate(asset.id), isTrue);
|
||||
});
|
||||
});
|
||||
|
||||
group('delete (iOS)', () {
|
||||
setUp(() {
|
||||
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
|
||||
});
|
||||
tearDown(() {
|
||||
debugDefaultTargetPlatformOverride = null;
|
||||
});
|
||||
|
||||
test('flips candidate flag when the only selected album is deleted', () async {
|
||||
final selected = await ctx.newLocalAlbum(backupSelection: BackupSelection.selected);
|
||||
final other = await ctx.newLocalAlbum(backupSelection: BackupSelection.none);
|
||||
final asset = await ctx.newLocalAsset();
|
||||
await ctx.newLocalAlbumAsset(albumId: selected.id, assetId: asset.id, recomputeBackupCandidates: false);
|
||||
await ctx.newLocalAlbumAsset(albumId: other.id, assetId: asset.id, recomputeBackupCandidates: false);
|
||||
await sut.recomputeAllBackupCandidates();
|
||||
expect(await ctx.isAssetBackupCandidate(asset.id), isTrue);
|
||||
|
||||
await sut.delete(selected.id);
|
||||
expect(await ctx.isAssetBackupCandidate(asset.id), isFalse, reason: 'survivor lost its only selected membership');
|
||||
expect(await ctx.albumAssetCount(selected.id), 0, reason: 'memberships must reflect the now-empty album');
|
||||
expect(await ctx.hasLocalAlbum(selected.id), isTrue, reason: 'selected album row is preserved as a bookmark');
|
||||
});
|
||||
|
||||
test('keeps candidate flag when another selected album remains', () async {
|
||||
final albumA = await ctx.newLocalAlbum(backupSelection: BackupSelection.selected);
|
||||
final albumB = await ctx.newLocalAlbum(backupSelection: BackupSelection.selected);
|
||||
final asset = await ctx.newLocalAsset();
|
||||
await ctx.newLocalAlbumAsset(albumId: albumA.id, assetId: asset.id, recomputeBackupCandidates: false);
|
||||
await ctx.newLocalAlbumAsset(albumId: albumB.id, assetId: asset.id, recomputeBackupCandidates: false);
|
||||
await sut.recomputeAllBackupCandidates();
|
||||
expect(await ctx.isAssetBackupCandidate(asset.id), isTrue);
|
||||
|
||||
await sut.delete(albumA.id);
|
||||
expect(await ctx.isAssetBackupCandidate(asset.id), isTrue, reason: 'albumB still keeps the asset a candidate');
|
||||
expect(await ctx.albumAssetCount(albumA.id), 0);
|
||||
});
|
||||
|
||||
test('deleting an excluded album can flip a flag from false to true', () async {
|
||||
final selected = await ctx.newLocalAlbum(backupSelection: BackupSelection.selected);
|
||||
final excluded = await ctx.newLocalAlbum(backupSelection: BackupSelection.excluded);
|
||||
final asset = await ctx.newLocalAsset();
|
||||
await ctx.newLocalAlbumAsset(albumId: selected.id, assetId: asset.id, recomputeBackupCandidates: false);
|
||||
await ctx.newLocalAlbumAsset(albumId: excluded.id, assetId: asset.id, recomputeBackupCandidates: false);
|
||||
await sut.recomputeAllBackupCandidates();
|
||||
expect(
|
||||
await ctx.isAssetBackupCandidate(asset.id),
|
||||
isFalse,
|
||||
reason: 'excluded suppresses an otherwise-candidate asset',
|
||||
);
|
||||
|
||||
await sut.delete(excluded.id);
|
||||
expect(
|
||||
await ctx.isAssetBackupCandidate(asset.id),
|
||||
isTrue,
|
||||
reason: 'with excluded gone, the selected membership wins',
|
||||
);
|
||||
expect(await ctx.albumAssetCount(excluded.id), 0);
|
||||
expect(await ctx.hasLocalAlbum(excluded.id), isTrue, reason: 'excluded album row is preserved as a bookmark');
|
||||
});
|
||||
|
||||
test('deleting a none album does not flip flags, and the row is removed', () async {
|
||||
final selected = await ctx.newLocalAlbum(backupSelection: BackupSelection.selected);
|
||||
final none = await ctx.newLocalAlbum(backupSelection: BackupSelection.none);
|
||||
final asset = await ctx.newLocalAsset();
|
||||
await ctx.newLocalAlbumAsset(albumId: selected.id, assetId: asset.id, recomputeBackupCandidates: false);
|
||||
await ctx.newLocalAlbumAsset(albumId: none.id, assetId: asset.id, recomputeBackupCandidates: false);
|
||||
await sut.recomputeAllBackupCandidates();
|
||||
expect(await ctx.isAssetBackupCandidate(asset.id), isTrue);
|
||||
|
||||
await sut.delete(none.id);
|
||||
expect(
|
||||
await ctx.isAssetBackupCandidate(asset.id),
|
||||
isTrue,
|
||||
reason: 'none membership never affected the predicate',
|
||||
);
|
||||
expect(await ctx.albumAssetCount(none.id), 0);
|
||||
expect(await ctx.hasLocalAlbum(none.id), isFalse, reason: 'none album row is hard-deleted');
|
||||
});
|
||||
});
|
||||
|
||||
group('updateAll (iOS)', () {
|
||||
setUp(() {
|
||||
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
|
||||
});
|
||||
tearDown(() {
|
||||
debugDefaultTargetPlatformOverride = null;
|
||||
});
|
||||
|
||||
test('drops selected memberships when album removed from device', () async {
|
||||
final selected = mapToLocalAlbum(await ctx.newLocalAlbum(backupSelection: BackupSelection.selected));
|
||||
final kept = mapToLocalAlbum(await ctx.newLocalAlbum(backupSelection: BackupSelection.none));
|
||||
final asset = await ctx.newLocalAsset();
|
||||
await ctx.newLocalAlbumAsset(albumId: selected.id, assetId: asset.id, recomputeBackupCandidates: false);
|
||||
await ctx.newLocalAlbumAsset(albumId: kept.id, assetId: asset.id, recomputeBackupCandidates: false);
|
||||
await sut.recomputeAllBackupCandidates();
|
||||
expect(await ctx.isAssetBackupCandidate(asset.id), isTrue);
|
||||
|
||||
await sut.updateAll([kept]);
|
||||
expect(await ctx.isAssetBackupCandidate(asset.id), isFalse, reason: 'survivor lost its only selected membership');
|
||||
expect(await ctx.albumAssetCount(selected.id), 0, reason: 'selected album must be empty');
|
||||
expect(await ctx.hasLocalAlbum(selected.id), isTrue, reason: 'selected album row preserved as bookmark');
|
||||
});
|
||||
|
||||
test('drops excluded memberships when album removed from device', () async {
|
||||
final excluded = mapToLocalAlbum(await ctx.newLocalAlbum(backupSelection: BackupSelection.excluded));
|
||||
final selected = mapToLocalAlbum(await ctx.newLocalAlbum(backupSelection: BackupSelection.selected));
|
||||
final asset = await ctx.newLocalAsset();
|
||||
await ctx.newLocalAlbumAsset(albumId: excluded.id, assetId: asset.id, recomputeBackupCandidates: false);
|
||||
await ctx.newLocalAlbumAsset(albumId: selected.id, assetId: asset.id, recomputeBackupCandidates: false);
|
||||
await sut.recomputeAllBackupCandidates();
|
||||
expect(
|
||||
await ctx.isAssetBackupCandidate(asset.id),
|
||||
isFalse,
|
||||
reason: 'excluded suppresses an otherwise-candidate asset',
|
||||
);
|
||||
|
||||
await sut.updateAll([selected]);
|
||||
expect(await ctx.isAssetBackupCandidate(asset.id), isTrue, reason: 'with excluded gone, selected wins');
|
||||
expect(await ctx.albumAssetCount(excluded.id), 0);
|
||||
expect(await ctx.hasLocalAlbum(excluded.id), isTrue, reason: 'excluded album row preserved as a bookmark');
|
||||
});
|
||||
|
||||
test('removes none rows entirely and leaves untouched albums alone', () async {
|
||||
final selected = mapToLocalAlbum(await ctx.newLocalAlbum(backupSelection: BackupSelection.selected));
|
||||
final none = mapToLocalAlbum(await ctx.newLocalAlbum(backupSelection: BackupSelection.none));
|
||||
final asset = await ctx.newLocalAsset();
|
||||
await ctx.newLocalAlbumAsset(albumId: selected.id, assetId: asset.id, recomputeBackupCandidates: false);
|
||||
await ctx.newLocalAlbumAsset(albumId: none.id, assetId: asset.id, recomputeBackupCandidates: false);
|
||||
await sut.recomputeAllBackupCandidates();
|
||||
expect(await ctx.isAssetBackupCandidate(asset.id), isTrue);
|
||||
|
||||
await sut.updateAll([selected]);
|
||||
|
||||
expect(
|
||||
await ctx.isAssetBackupCandidate(asset.id),
|
||||
isTrue,
|
||||
reason: 'none membership never affected the predicate',
|
||||
);
|
||||
expect(await ctx.hasLocalAlbum(none.id), isFalse, reason: 'none removed rows are hard-deleted');
|
||||
expect(await ctx.hasLocalAlbum(selected.id), isTrue);
|
||||
expect(await ctx.albumAssetCount(selected.id), 1, reason: 'kept album retains its membership');
|
||||
});
|
||||
});
|
||||
|
||||
group('delete (Android)', () {
|
||||
setUp(() {
|
||||
debugDefaultTargetPlatformOverride = TargetPlatform.android;
|
||||
});
|
||||
tearDown(() {
|
||||
debugDefaultTargetPlatformOverride = null;
|
||||
});
|
||||
|
||||
test('hard-deletes all member assets and preserves a selected album as a bookmark', () async {
|
||||
final selected = await ctx.newLocalAlbum(backupSelection: BackupSelection.selected);
|
||||
final other = await ctx.newLocalAlbum(backupSelection: BackupSelection.selected);
|
||||
final assetA = await ctx.newLocalAsset();
|
||||
final assetB = await ctx.newLocalAsset();
|
||||
final assetC = await ctx.newLocalAsset();
|
||||
await ctx.newLocalAlbumAsset(albumId: selected.id, assetId: assetA.id, recomputeBackupCandidates: false);
|
||||
await ctx.newLocalAlbumAsset(albumId: selected.id, assetId: assetB.id, recomputeBackupCandidates: false);
|
||||
await ctx.newLocalAlbumAsset(albumId: other.id, assetId: assetC.id, recomputeBackupCandidates: false);
|
||||
await sut.recomputeAllBackupCandidates();
|
||||
expect(await ctx.isAssetBackupCandidate(assetA.id), isTrue);
|
||||
|
||||
await sut.delete(selected.id);
|
||||
expect(await ctx.hasLocalAsset(assetA.id), isFalse, reason: 'Android removes every member asset of the album');
|
||||
expect(await ctx.hasLocalAsset(assetB.id), isFalse, reason: 'Android removes every member asset of the album');
|
||||
expect(await ctx.albumAssetCount(selected.id), 0, reason: 'cascade clears memberships of deleted assets');
|
||||
expect(await ctx.hasLocalAlbum(selected.id), isTrue, reason: 'selected album row is preserved as a bookmark');
|
||||
expect(await ctx.hasLocalAsset(assetC.id), isTrue, reason: 'a different album keeps its own distinct assets');
|
||||
});
|
||||
|
||||
test('hard-deletes member assets and removes a none album row', () async {
|
||||
final none = await ctx.newLocalAlbum(backupSelection: BackupSelection.none);
|
||||
final asset = await ctx.newLocalAsset();
|
||||
await ctx.newLocalAlbumAsset(albumId: none.id, assetId: asset.id, recomputeBackupCandidates: false);
|
||||
|
||||
await sut.delete(none.id);
|
||||
expect(await ctx.hasLocalAsset(asset.id), isFalse);
|
||||
expect(await ctx.albumAssetCount(none.id), 0);
|
||||
expect(await ctx.hasLocalAlbum(none.id), isFalse, reason: 'none album row is hard-deleted');
|
||||
});
|
||||
});
|
||||
|
||||
group('updateAll (Android)', () {
|
||||
setUp(() {
|
||||
debugDefaultTargetPlatformOverride = TargetPlatform.android;
|
||||
});
|
||||
tearDown(() {
|
||||
debugDefaultTargetPlatformOverride = null;
|
||||
});
|
||||
|
||||
test('hard-deletes assets of a removed selected album, preserving its row as a bookmark', () async {
|
||||
final removed = mapToLocalAlbum(await ctx.newLocalAlbum(backupSelection: BackupSelection.selected));
|
||||
final kept = mapToLocalAlbum(await ctx.newLocalAlbum(backupSelection: BackupSelection.selected));
|
||||
final removedAsset = await ctx.newLocalAsset();
|
||||
final keptAsset = await ctx.newLocalAsset();
|
||||
await ctx.newLocalAlbumAsset(albumId: removed.id, assetId: removedAsset.id, recomputeBackupCandidates: false);
|
||||
await ctx.newLocalAlbumAsset(albumId: kept.id, assetId: keptAsset.id, recomputeBackupCandidates: false);
|
||||
await sut.recomputeAllBackupCandidates();
|
||||
await sut.updateAll([kept]);
|
||||
|
||||
expect(
|
||||
await ctx.hasLocalAsset(removedAsset.id),
|
||||
isFalse,
|
||||
reason: 'removed album assets are gone from the device',
|
||||
);
|
||||
expect(await ctx.albumAssetCount(removed.id), 0, reason: 'cascade clears the removed album memberships');
|
||||
expect(await ctx.hasLocalAlbum(removed.id), isTrue, reason: 'selected removed row preserved as bookmark');
|
||||
expect(await ctx.isAssetBackupCandidate(keptAsset.id), isTrue, reason: 'surviving album assets are untouched');
|
||||
expect(await ctx.albumAssetCount(kept.id), 1);
|
||||
});
|
||||
|
||||
test('hard-deletes assets of a removed none album and removes the row', () async {
|
||||
final removed = mapToLocalAlbum(await ctx.newLocalAlbum(backupSelection: BackupSelection.none));
|
||||
final kept = mapToLocalAlbum(await ctx.newLocalAlbum(backupSelection: BackupSelection.selected));
|
||||
final removedAsset = await ctx.newLocalAsset();
|
||||
await ctx.newLocalAlbumAsset(albumId: removed.id, assetId: removedAsset.id, recomputeBackupCandidates: false);
|
||||
|
||||
await sut.updateAll([kept]);
|
||||
|
||||
expect(await ctx.hasLocalAsset(removedAsset.id), isFalse);
|
||||
expect(await ctx.hasLocalAlbum(removed.id), isFalse, reason: 'none removed rows are hard-deleted');
|
||||
expect(await ctx.hasLocalAlbum(kept.id), isTrue);
|
||||
});
|
||||
});
|
||||
|
||||
group('upsert selection flips', () {
|
||||
test('flipping selected to excluded flips a candidate back to false', () async {
|
||||
final asset = await ctx.newLocalAsset();
|
||||
final album = mapToLocalAlbum(await ctx.newLocalAlbum(backupSelection: BackupSelection.selected), assetCount: 1);
|
||||
await ctx.newLocalAlbumAsset(albumId: album.id, assetId: asset.id, recomputeBackupCandidates: false);
|
||||
await sut.recomputeAllBackupCandidates();
|
||||
expect(await ctx.isAssetBackupCandidate(asset.id), isTrue);
|
||||
|
||||
await sut.upsert(album.copyWith(backupSelection: BackupSelection.excluded, updatedAt: DateTime.now()));
|
||||
expect(await ctx.isAssetBackupCandidate(asset.id), isFalse);
|
||||
});
|
||||
|
||||
test('flipping selected to none flips a candidate back to false', () async {
|
||||
final asset = await ctx.newLocalAsset();
|
||||
final album = mapToLocalAlbum(await ctx.newLocalAlbum(backupSelection: BackupSelection.selected), assetCount: 1);
|
||||
await ctx.newLocalAlbumAsset(albumId: album.id, assetId: asset.id, recomputeBackupCandidates: false);
|
||||
await sut.recomputeAllBackupCandidates();
|
||||
expect(await ctx.isAssetBackupCandidate(asset.id), isTrue);
|
||||
|
||||
await sut.upsert(album.copyWith(backupSelection: BackupSelection.none, updatedAt: DateTime.now()));
|
||||
expect(await ctx.isAssetBackupCandidate(asset.id), isFalse);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -15,10 +15,6 @@ void main() {
|
||||
sut = DriftLocalAssetRepository(ctx.db);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await ctx.dispose();
|
||||
});
|
||||
|
||||
group('getRemovalCandidates', () {
|
||||
final cutoffDate = DateTime(2024, 1, 1);
|
||||
final beforeCutoff = DateTime(2023, 12, 31);
|
||||
|
||||
@@ -12,10 +12,6 @@ void main() {
|
||||
sut = PartnerRepository(ctx.db);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await ctx.dispose();
|
||||
});
|
||||
|
||||
group('search', () {
|
||||
test('sharedBy returns users the current user shares their library to', () async {
|
||||
final me = await ctx.newUser();
|
||||
|
||||
@@ -12,10 +12,6 @@ void main() {
|
||||
sut = DriftPeopleRepository(ctx.db);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await ctx.dispose();
|
||||
});
|
||||
|
||||
group('getAssetPeople', () {
|
||||
test('does not duplicate a person with multiple face records on the same asset', () async {
|
||||
// Regression check for #20585: a join on asset_face_entity returned one row
|
||||
|
||||
@@ -13,10 +13,6 @@ void main() {
|
||||
sut = DriftRemoteAlbumRepository(ctx.db);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await ctx.dispose();
|
||||
});
|
||||
|
||||
group('addAssets', () {
|
||||
test('sets the first added asset as thumbnail when the album has no thumbnail', () async {
|
||||
final user = await ctx.newUser();
|
||||
|
||||
@@ -16,10 +16,6 @@ void main() {
|
||||
sut = await SettingsRepository.ensureInitialized(ctx.db);
|
||||
});
|
||||
|
||||
tearDownAll(() async {
|
||||
await ctx.dispose();
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
await ctx.db.delete(ctx.db.settingsEntity).go();
|
||||
await SettingsRepository.instance.refresh();
|
||||
|
||||
@@ -18,10 +18,6 @@ void main() {
|
||||
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
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
import 'package:drift/drift.dart' show TableOrViewStatements;
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
||||
import 'package:immich_mobile/infrastructure/mapper.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart';
|
||||
|
||||
import '../repository_context.dart';
|
||||
|
||||
void main() {
|
||||
late MediumRepositoryContext ctx;
|
||||
late DriftTrashedLocalAssetRepository sut;
|
||||
|
||||
setUp(() {
|
||||
ctx = MediumRepositoryContext();
|
||||
sut = DriftTrashedLocalAssetRepository(ctx.db);
|
||||
});
|
||||
|
||||
Future<int> trashedCount(String assetId) async =>
|
||||
await (ctx.db.trashedLocalAssetEntity.count(where: (row) => row.id.equals(assetId))).getSingle();
|
||||
|
||||
group('trash and restore lifecycle', () {
|
||||
test('trashing a candidate removes the local asset and cascades its album membership', () async {
|
||||
final album = await ctx.newLocalAlbum(backupSelection: BackupSelection.selected);
|
||||
final asset = await ctx.newLocalAsset();
|
||||
await ctx.newLocalAlbumAsset(albumId: album.id, assetId: asset.id);
|
||||
|
||||
expect(
|
||||
await ctx.isAssetBackupCandidate(asset.id),
|
||||
isTrue,
|
||||
reason: 'asset in a selected album starts as a candidate',
|
||||
);
|
||||
expect(await ctx.albumAssetCount(album.id), 1);
|
||||
|
||||
await sut.trashLocalAsset({
|
||||
album.id: [mapToLocalAsset(asset)],
|
||||
});
|
||||
|
||||
expect(await ctx.hasLocalAsset(asset.id), isFalse, reason: 'row moves out of local_asset_entity');
|
||||
expect(await ctx.albumAssetCount(album.id), 0, reason: 'FK cascade removes the album membership');
|
||||
expect(await trashedCount(asset.id), 1, reason: 'asset now lives in the trashed table');
|
||||
});
|
||||
|
||||
test('restoring re-inserts the asset and marks it a backup candidate', () async {
|
||||
final album = await ctx.newLocalAlbum(backupSelection: BackupSelection.selected);
|
||||
final asset = await ctx.newLocalAsset();
|
||||
await ctx.newLocalAlbumAsset(albumId: album.id, assetId: asset.id);
|
||||
await sut.trashLocalAsset({
|
||||
album.id: [mapToLocalAsset(asset)],
|
||||
});
|
||||
|
||||
await sut.applyRestoredAssets([asset.id]);
|
||||
|
||||
expect(await ctx.hasLocalAsset(asset.id), isTrue, reason: 'row returns to local_asset_entity');
|
||||
expect(await trashedCount(asset.id), 0, reason: 'trashed row is consumed');
|
||||
expect(await ctx.isAssetBackupCandidate(asset.id), isTrue, reason: 'restored asset is a candidate again');
|
||||
expect(await ctx.albumAssetCount(album.id), 0, reason: 'restore does not re-link membership');
|
||||
});
|
||||
});
|
||||
|
||||
group('getToRestore', () {
|
||||
test('returns trashed assets whose album is selected and whose remote copy is live again', () async {
|
||||
const checksum = 'shared-checksum';
|
||||
final owner = await ctx.newUser();
|
||||
final selected = await ctx.newLocalAlbum(backupSelection: BackupSelection.selected);
|
||||
final asset = await ctx.newLocalAsset(checksum: checksum);
|
||||
await ctx.newLocalAlbumAsset(albumId: selected.id, assetId: asset.id);
|
||||
await ctx.newRemoteAsset(checksum: checksum, ownerId: owner.id);
|
||||
await sut.trashLocalAsset({
|
||||
selected.id: [mapToLocalAsset(asset)],
|
||||
});
|
||||
|
||||
final toRestore = await sut.getToRestore();
|
||||
|
||||
expect(toRestore.map((a) => a.id), contains(asset.id));
|
||||
});
|
||||
|
||||
test('ignores trashed assets whose album is not selected', () async {
|
||||
const checksum = 'shared-checksum';
|
||||
final owner = await ctx.newUser();
|
||||
final none = await ctx.newLocalAlbum(backupSelection: BackupSelection.none);
|
||||
final asset = await ctx.newLocalAsset(checksum: checksum);
|
||||
await ctx.newLocalAlbumAsset(albumId: none.id, assetId: asset.id);
|
||||
await ctx.newRemoteAsset(checksum: checksum, ownerId: owner.id);
|
||||
await sut.trashLocalAsset({
|
||||
none.id: [mapToLocalAsset(asset)],
|
||||
});
|
||||
|
||||
final toRestore = await sut.getToRestore();
|
||||
|
||||
expect(toRestore, isEmpty);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:drift/native.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:immich_mobile/domain/models/album/album.model.dart';
|
||||
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
@@ -17,6 +18,7 @@ import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.
|
||||
import 'package:immich_mobile/infrastructure/entities/remote_asset_cloud_id.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/user.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart';
|
||||
import 'package:immich_mobile/utils/option.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
@@ -25,12 +27,29 @@ import '../utils.dart';
|
||||
class MediumRepositoryContext {
|
||||
final Drift db;
|
||||
|
||||
MediumRepositoryContext() : db = Drift(DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true));
|
||||
MediumRepositoryContext() : db = Drift(DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true)) {
|
||||
addTearDown(dispose);
|
||||
}
|
||||
|
||||
Future<void> dispose() async {
|
||||
await db.close();
|
||||
}
|
||||
|
||||
Future<bool> isAssetBackupCandidate(String assetId) async =>
|
||||
await (db.localAssetEntity.select()..where((row) => row.id.equals(assetId)))
|
||||
.map((row) => row.isBackupCandidate)
|
||||
.getSingleOrNull() ==
|
||||
true;
|
||||
|
||||
Future<bool> hasLocalAsset(String assetId) async =>
|
||||
await (db.localAssetEntity.count(where: (row) => row.id.equals(assetId))).getSingle() == 1;
|
||||
|
||||
Future<bool> hasLocalAlbum(String albumId) async =>
|
||||
await (db.localAlbumEntity.count(where: (row) => row.id.equals(albumId))).getSingle() == 1;
|
||||
|
||||
Future<int> albumAssetCount(String albumId) async =>
|
||||
await (db.localAlbumAssetEntity.count(where: (row) => row.albumId.equals(albumId))).getSingle();
|
||||
|
||||
static Value<T> _resolveUndefined<T>(T? plain, Option<T>? option, T fallback) {
|
||||
if (plain != null) {
|
||||
return .new(plain);
|
||||
@@ -214,8 +233,8 @@ class MediumRepositoryContext {
|
||||
}
|
||||
|
||||
Future<AssetFaceEntityData> newFace({String? assetId, String? personId, int? imageWidth, int? imageHeight}) {
|
||||
imageWidth ??= TestUtils.randInt(999) + 1;
|
||||
imageHeight ??= TestUtils.randInt(999) + 1;
|
||||
imageWidth ??= TestUtils.randInt(998) + 2;
|
||||
imageHeight ??= TestUtils.randInt(998) + 2;
|
||||
|
||||
final x1 = TestUtils.randInt(imageWidth - 1);
|
||||
final y1 = TestUtils.randInt(imageHeight - 1);
|
||||
@@ -306,7 +325,16 @@ class MediumRepositoryContext {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> newLocalAlbumAsset({required String albumId, required String assetId}) => db
|
||||
.into(db.localAlbumAssetEntity)
|
||||
.insert(LocalAlbumAssetEntityCompanion(albumId: .new(albumId), assetId: .new(assetId)));
|
||||
Future<void> newLocalAlbumAsset({
|
||||
required String albumId,
|
||||
required String assetId,
|
||||
bool recomputeBackupCandidates = true,
|
||||
}) async {
|
||||
await db
|
||||
.into(db.localAlbumAssetEntity)
|
||||
.insert(LocalAlbumAssetEntityCompanion(albumId: .new(albumId), assetId: .new(assetId)));
|
||||
if (recomputeBackupCandidates) {
|
||||
await DriftLocalAlbumRepository(db).recomputeBackupCandidatesForAssets([assetId]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,10 +13,6 @@ void main() {
|
||||
sut = PartnerService(ctx.userRepository, ctx.partnerRepository, ctx.partnerApi);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await ctx.dispose();
|
||||
});
|
||||
|
||||
group('getCandidates', () {
|
||||
test('returns the other users and excludes the current user', () async {
|
||||
final me = await ctx.newUser();
|
||||
|
||||
Reference in New Issue
Block a user