mirror of
https://github.com/immich-app/immich.git
synced 2026-07-28 14:47:30 -07:00
fix local date migration for trash and epoch rows
This commit is contained in:
@@ -174,12 +174,16 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin(), ActivityAwa
|
||||
MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO -> 2L
|
||||
else -> 0L
|
||||
}
|
||||
// Date taken is in ms; date added/modified in seconds. No-EXIF (date taken <= 0)
|
||||
// falls back to the earliest of modified/added to match the server + iOS.
|
||||
val createdAt = (c.getLong(dateTakenColumn).takeIf { it > 0 }?.div(1000))
|
||||
?: minOf(c.getLong(dateModifiedColumn), c.getLong(dateAddedColumn))
|
||||
// Date modified is seconds since epoch
|
||||
// Date taken is in ms; added/modified are in seconds. No-EXIF assets use the earliest
|
||||
// positive modified/added date, or added if neither is positive.
|
||||
val modifiedAt = c.getLong(dateModifiedColumn)
|
||||
val addedAt = c.getLong(dateAddedColumn)
|
||||
val createdAt = (c.getLong(dateTakenColumn).takeIf { it > 0 }?.div(1000))
|
||||
?: when {
|
||||
modifiedAt <= 0 -> addedAt
|
||||
addedAt <= 0 -> modifiedAt
|
||||
else -> minOf(modifiedAt, addedAt)
|
||||
}
|
||||
val width = c.getInt(widthColumn).toLong()
|
||||
val height = c.getInt(heightColumn).toLong()
|
||||
// Duration is milliseconds
|
||||
|
||||
@@ -54,7 +54,16 @@ Future<bool> _migrateTo27(Drift drift) async {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
await drift.customStatement('UPDATE local_asset_entity SET created_at = updated_at WHERE created_at > updated_at');
|
||||
await drift.customStatement(
|
||||
"UPDATE local_asset_entity SET created_at = updated_at "
|
||||
"WHERE julianday(updated_at) > julianday('1970-01-01T00:00:00Z') "
|
||||
"AND julianday(created_at) > julianday(updated_at)",
|
||||
);
|
||||
await drift.customStatement(
|
||||
"UPDATE trashed_local_asset_entity SET created_at = updated_at "
|
||||
"WHERE julianday(updated_at) > julianday('1970-01-01T00:00:00Z') "
|
||||
"AND julianday(created_at) > julianday(updated_at)",
|
||||
);
|
||||
return true;
|
||||
} catch (_) {
|
||||
return false;
|
||||
|
||||
@@ -7,6 +7,8 @@ import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:immich_mobile/domain/services/store.service.dart';
|
||||
import 'package:immich_mobile/entities/store.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/repositories/db.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
|
||||
import 'package:immich_mobile/utils/migration.dart';
|
||||
@@ -24,6 +26,13 @@ void main() {
|
||||
await StoreService.init(storeRepository: storeRepository, listenUpdates: false);
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
await Store.clear();
|
||||
await db.delete(db.localAssetEntity).go();
|
||||
await db.delete(db.trashedLocalAssetEntity).go();
|
||||
await db.customStatement('DROP TRIGGER IF EXISTS fail_migration');
|
||||
});
|
||||
|
||||
tearDownAll(() async {
|
||||
debugDefaultTargetPlatformOverride = null;
|
||||
await Store.clear();
|
||||
@@ -52,4 +61,56 @@ void main() {
|
||||
|
||||
expect(await storeRepository.tryGet(StoreKey.version), 26);
|
||||
});
|
||||
|
||||
test('fixes dates in active and trashed assets', () async {
|
||||
final createdAt = DateTime(2026);
|
||||
final updatedAt = DateTime(2025);
|
||||
final epoch = DateTime.fromMillisecondsSinceEpoch(0);
|
||||
await Store.put(StoreKey.version, 26);
|
||||
await db
|
||||
.into(db.localAssetEntity)
|
||||
.insert(
|
||||
LocalAssetEntityCompanion.insert(
|
||||
id: 'local',
|
||||
name: 'local.jpg',
|
||||
type: AssetType.image,
|
||||
createdAt: drift.Value(createdAt),
|
||||
updatedAt: drift.Value(updatedAt),
|
||||
),
|
||||
);
|
||||
await db
|
||||
.into(db.localAssetEntity)
|
||||
.insert(
|
||||
LocalAssetEntityCompanion.insert(
|
||||
id: 'epoch',
|
||||
name: 'epoch.jpg',
|
||||
type: AssetType.image,
|
||||
createdAt: drift.Value(createdAt),
|
||||
updatedAt: drift.Value(epoch),
|
||||
),
|
||||
);
|
||||
await db
|
||||
.into(db.trashedLocalAssetEntity)
|
||||
.insert(
|
||||
TrashedLocalAssetEntityCompanion.insert(
|
||||
id: 'trashed',
|
||||
albumId: 'album',
|
||||
name: 'trashed.jpg',
|
||||
type: AssetType.image,
|
||||
createdAt: drift.Value(createdAt),
|
||||
updatedAt: drift.Value(updatedAt),
|
||||
source: TrashOrigin.localSync,
|
||||
),
|
||||
);
|
||||
|
||||
await migrateDatabaseIfNeeded(db);
|
||||
|
||||
final local = await (db.select(db.localAssetEntity)..where((row) => row.id.equals('local'))).getSingle();
|
||||
final unchanged = await (db.select(db.localAssetEntity)..where((row) => row.id.equals('epoch'))).getSingle();
|
||||
final trashed = await db.select(db.trashedLocalAssetEntity).getSingle();
|
||||
expect(local.createdAt, updatedAt);
|
||||
expect(unchanged.createdAt, createdAt);
|
||||
expect(trashed.createdAt, updatedAt);
|
||||
expect(await storeRepository.tryGet(StoreKey.version), 27);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user