mirror of
https://github.com/immich-app/immich.git
synced 2026-07-24 05:44:37 +03:00
Compare commits
1 Commits
fix/androi
...
make-sure-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5136f16e6d |
@@ -174,16 +174,11 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin(), ActivityAwa
|
||||
MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO -> 2L
|
||||
else -> 0L
|
||||
}
|
||||
// Date taken is in ms; added/modified are in seconds, and modified can be 0 when unset.
|
||||
// No-EXIF assets use the earliest positive added/modified date, or raw added if neither is positive.
|
||||
val modifiedAt = c.getLong(dateModifiedColumn)
|
||||
val addedAt = c.getLong(dateAddedColumn)
|
||||
// Date taken is milliseconds since epoch, Date added is seconds since epoch
|
||||
val createdAt = (c.getLong(dateTakenColumn).takeIf { it > 0 }?.div(1000))
|
||||
?: when {
|
||||
modifiedAt <= 0 -> addedAt
|
||||
addedAt <= 0 -> modifiedAt
|
||||
else -> minOf(modifiedAt, addedAt)
|
||||
}
|
||||
?: c.getLong(dateAddedColumn)
|
||||
// Date modified is seconds since epoch
|
||||
val modifiedAt = c.getLong(dateModifiedColumn)
|
||||
val width = c.getInt(widthColumn).toLong()
|
||||
val height = c.getInt(heightColumn).toLong()
|
||||
// Duration is milliseconds
|
||||
|
||||
@@ -18,21 +18,15 @@ struct ImmichWidgetView: View {
|
||||
|
||||
var body: some View {
|
||||
if entry.image == nil {
|
||||
Image("LaunchImage")
|
||||
.tintedWidgetImageModifier()
|
||||
.overlay(alignment: .bottom) {
|
||||
if let error = entry.metadata.error?.errorDescription {
|
||||
Text(error)
|
||||
.minimumScaleFactor(0.25)
|
||||
.multilineTextAlignment(.center)
|
||||
.foregroundStyle(.secondary)
|
||||
.fixedSize()
|
||||
.alignmentGuide(.bottom) { dimensions in
|
||||
// Place the text below the bottom of the image
|
||||
dimensions[.top] - 8
|
||||
}
|
||||
}
|
||||
}
|
||||
VStack {
|
||||
Image("LaunchImage")
|
||||
.tintedWidgetImageModifier()
|
||||
Text(entry.metadata.error?.errorDescription ?? "")
|
||||
.minimumScaleFactor(0.25)
|
||||
.multilineTextAlignment(.center)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
.padding(16)
|
||||
} else {
|
||||
ZStack(alignment: .leading) {
|
||||
Color.clear.overlay(
|
||||
|
||||
@@ -83,7 +83,13 @@ struct ImmichMemoryProvider: TimelineProvider {
|
||||
return
|
||||
}
|
||||
|
||||
let memories = try await api.fetchMemory(for: Date.now)
|
||||
let memories: [MemoryResult]
|
||||
do {
|
||||
memories = try await api.fetchMemory(for: Date.now)
|
||||
} catch {
|
||||
completion(ImageEntry.handleError(for: cacheKey))
|
||||
return
|
||||
}
|
||||
|
||||
await withTaskGroup(of: ImageEntry?.self) { group in
|
||||
var totalAssets = 0
|
||||
|
||||
@@ -13,7 +13,6 @@ import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:immich_mobile/domain/models/timeline.model.dart';
|
||||
import 'package:immich_mobile/domain/services/feature_message.service.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/extensions/platform_extensions.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/network.repository.dart';
|
||||
@@ -21,7 +20,7 @@ import 'package:immich_mobile/infrastructure/repositories/settings.repository.da
|
||||
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 = 27;
|
||||
const int targetVersion = 26;
|
||||
|
||||
Future<void> migrateDatabaseIfNeeded(Drift drift) async {
|
||||
final int? storedVersion = Store.tryGet(StoreKey.version);
|
||||
@@ -35,11 +34,6 @@ Future<void> migrateDatabaseIfNeeded(Drift drift) async {
|
||||
await _migrateTo26(drift);
|
||||
}
|
||||
|
||||
if (version < 27 && !await _migrateTo27(drift)) {
|
||||
await Store.put(StoreKey.version, 26);
|
||||
return;
|
||||
}
|
||||
|
||||
if (storedVersion == null) {
|
||||
await FeatureMessageService(SettingsRepository.instance).markSeen();
|
||||
}
|
||||
@@ -48,28 +42,6 @@ Future<void> migrateDatabaseIfNeeded(Drift drift) async {
|
||||
return;
|
||||
}
|
||||
|
||||
Future<bool> _migrateTo27(Drift drift) async {
|
||||
// DATE_ADDED can be later than DATE_MODIFIED after a file is copied.
|
||||
if (!CurrentPlatform.isAndroid) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _migrateTo25() async {
|
||||
final accessToken = Store.tryGet(StoreKey.accessToken);
|
||||
if (accessToken == null || accessToken.isEmpty) {
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
import 'package:drift/drift.dart' as drift;
|
||||
import 'package:drift/native.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
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';
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
late Drift db;
|
||||
late DriftStoreRepository storeRepository;
|
||||
|
||||
setUpAll(() async {
|
||||
debugDefaultTargetPlatformOverride = TargetPlatform.android;
|
||||
db = Drift(drift.DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true));
|
||||
storeRepository = DriftStoreRepository(db);
|
||||
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();
|
||||
await db.close();
|
||||
});
|
||||
|
||||
test('stores version 26 when migration 27 fails', () async {
|
||||
await Store.put(StoreKey.version, 25);
|
||||
await db
|
||||
.into(db.localAssetEntity)
|
||||
.insert(
|
||||
LocalAssetEntityCompanion.insert(
|
||||
id: 'asset',
|
||||
name: 'asset.jpg',
|
||||
type: AssetType.image,
|
||||
createdAt: drift.Value(DateTime(2026)),
|
||||
updatedAt: drift.Value(DateTime(2025)),
|
||||
),
|
||||
);
|
||||
await db.customStatement(
|
||||
"CREATE TRIGGER fail_migration BEFORE UPDATE OF created_at ON local_asset_entity "
|
||||
"BEGIN SELECT RAISE(FAIL, 'migration failed'); END",
|
||||
);
|
||||
|
||||
await migrateDatabaseIfNeeded(db);
|
||||
|
||||
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 db
|
||||
.into(db.trashedLocalAssetEntity)
|
||||
.insert(
|
||||
TrashedLocalAssetEntityCompanion.insert(
|
||||
id: 'trashed-epoch',
|
||||
albumId: 'album',
|
||||
name: 'trashed-epoch.jpg',
|
||||
type: AssetType.image,
|
||||
createdAt: drift.Value(createdAt),
|
||||
updatedAt: drift.Value(epoch),
|
||||
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)..where((row) => row.id.equals('trashed'))).getSingle();
|
||||
final trashedUnchanged = await (db.select(
|
||||
db.trashedLocalAssetEntity,
|
||||
)..where((row) => row.id.equals('trashed-epoch'))).getSingle();
|
||||
expect(local.createdAt, updatedAt);
|
||||
expect(unchanged.createdAt, createdAt);
|
||||
expect(trashed.createdAt, updatedAt);
|
||||
expect(trashedUnchanged.createdAt, createdAt);
|
||||
expect(await storeRepository.tryGet(StoreKey.version), 27);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user