mirror of
https://github.com/immich-app/immich.git
synced 2026-07-07 13:07:04 -07:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 74baf10649 | |||
| ea80e85d69 | |||
| fa7ecdd543 | |||
| a446f49bbf | |||
| daabab8c1c | |||
| a4f28dafb2 | |||
| 704b287bba | |||
| c7b4a798cb | |||
| f8e78792b8 | |||
| 0ebb723b39 | |||
| 99a8354145 | |||
| 7ee2f3644f | |||
| d2a46ade78 |
@@ -30,7 +30,7 @@ This environment includes the services below. Additional details are available i
|
||||
- Redis
|
||||
- PostgreSQL development database with exposed port `5432` so you can use any database client to access it
|
||||
|
||||
All the services are packaged to run as with single Docker Compose command.
|
||||
All the services are packaged to run with a single Docker Compose command.
|
||||
|
||||
:::tip mise
|
||||
[mise](https://mise.jdx.dev) is used throughout the project to manage tool versions and run tasks. [Install mise](https://mise.jdx.dev/installing-mise.html), then from the repo root run `mise trust` and `mise install` to get all required tools. Tasks for each service can be run from the repo root using `mise //namespace:task` (e.g. `mise //server:lint`). To list all available tasks, run `mise tasks ls --all`.
|
||||
@@ -41,7 +41,7 @@ All the services are packaged to run as with single Docker Compose command.
|
||||
1. Clone the project repo.
|
||||
2. Run `cp docker/example.env docker/.env`.
|
||||
3. Edit `docker/.env` to provide values for the required variable `UPLOAD_LOCATION`.
|
||||
4. Install dependencies - `pnpm i`
|
||||
4. Install dependencies - `mise x -- pnpm i`
|
||||
5. From the root directory, run:
|
||||
|
||||
```bash title="Start development server"
|
||||
@@ -52,7 +52,7 @@ mise dev
|
||||
|
||||
All the services will be started with hot-reloading enabled for a quick feedback loop.
|
||||
|
||||
You can access the web from `http://your-machine-ip:3000` or `http://localhost:3000` and access the server from the mobile app at `http://your-machine-ip:3000/api`
|
||||
You can access the web from `http://your-machine-ip:3000` or `http://localhost:3000` and access the server from the mobile app at `http://your-machine-ip:3000`
|
||||
|
||||
**Notes:**
|
||||
|
||||
|
||||
+3626
File diff suppressed because it is too large
Load Diff
@@ -1,19 +1,65 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
enum AspectRatioPreset {
|
||||
free(ratio: null, label: 'Free', icon: Icons.crop_free_rounded),
|
||||
square(ratio: 1.0, label: '1:1', icon: Icons.crop_square_rounded),
|
||||
ratio16x9(ratio: 16 / 9, label: '16:9', icon: Icons.crop_16_9_rounded),
|
||||
ratio3x2(ratio: 3 / 2, label: '3:2', icon: Icons.crop_3_2_rounded),
|
||||
ratio7x5(ratio: 7 / 5, label: '7:5', icon: Icons.crop_7_5_rounded),
|
||||
ratio9x16(ratio: 9 / 16, label: '9:16', icon: Icons.crop_16_9_rounded, iconRotated: true),
|
||||
ratio2x3(ratio: 2 / 3, label: '2:3', icon: Icons.crop_3_2_rounded, iconRotated: true),
|
||||
ratio5x7(ratio: 5 / 7, label: '5:7', icon: Icons.crop_7_5_rounded, iconRotated: true);
|
||||
class CropAspectRatio {
|
||||
final int? numerator;
|
||||
final int? denominator;
|
||||
|
||||
final double? ratio;
|
||||
final String label;
|
||||
final IconData icon;
|
||||
final bool iconRotated;
|
||||
final String? customLabel;
|
||||
final IconData? icon;
|
||||
|
||||
const AspectRatioPreset({required this.ratio, required this.label, required this.icon, this.iconRotated = false});
|
||||
const CropAspectRatio({this.numerator, this.denominator, this.customLabel, this.icon});
|
||||
|
||||
static const free = CropAspectRatio(customLabel: "Free", icon: Icons.crop_free);
|
||||
static const original = CropAspectRatio(customLabel: "Original", icon: Icons.crop_original);
|
||||
|
||||
String get label {
|
||||
return customLabel ?? (numerator != null && denominator != null ? '$numerator:$denominator' : 'Free');
|
||||
}
|
||||
|
||||
bool get hasFlippedVariant => numerator != denominator;
|
||||
double? get ratio => (numerator != null && denominator != null) ? numerator! / denominator! : null;
|
||||
|
||||
CropAspectRatio get flipped {
|
||||
return CropAspectRatio(numerator: denominator, denominator: numerator, customLabel: customLabel, icon: icon);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return other is CropAspectRatio &&
|
||||
other.numerator == numerator &&
|
||||
other.denominator == denominator &&
|
||||
other.customLabel == customLabel &&
|
||||
other.icon == icon;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return numerator.hashCode ^ denominator.hashCode ^ customLabel.hashCode ^ icon.hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
const aspectRatioFree = CropAspectRatio(customLabel: "Free", icon: Icons.crop_free);
|
||||
const aspectRatioOriginal = CropAspectRatio(customLabel: "Original", icon: Icons.crop_original);
|
||||
|
||||
final aspectRatioPresets = [
|
||||
CropAspectRatio.free,
|
||||
CropAspectRatio.original,
|
||||
|
||||
const CropAspectRatio(numerator: 1, denominator: 1),
|
||||
|
||||
// lanscape
|
||||
const CropAspectRatio(numerator: 16, denominator: 9),
|
||||
const CropAspectRatio(numerator: 3, denominator: 2),
|
||||
const CropAspectRatio(numerator: 7, denominator: 5),
|
||||
const CropAspectRatio(numerator: 4, denominator: 3),
|
||||
|
||||
// portrait
|
||||
const CropAspectRatio(numerator: 16, denominator: 9).flipped,
|
||||
const CropAspectRatio(numerator: 3, denominator: 2).flipped,
|
||||
const CropAspectRatio(numerator: 7, denominator: 5).flipped,
|
||||
const CropAspectRatio(numerator: 4, denominator: 3).flipped,
|
||||
];
|
||||
|
||||
@@ -21,6 +21,7 @@ WHERE (library_id IS NOT NULL);
|
||||
CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created
|
||||
ON remote_asset_entity (owner_id, visibility, deleted_at, created_at DESC)
|
||||
''')
|
||||
@TableIndex.sql('CREATE INDEX IF NOT EXISTS idx_remote_asset_uploaded ON remote_asset_entity (uploaded_at)')
|
||||
class RemoteAssetEntity extends Table with DriftDefaultsMixin, AssetEntityMixin {
|
||||
const RemoteAssetEntity();
|
||||
|
||||
|
||||
@@ -1779,3 +1779,7 @@ i0.Index get idxRemoteAssetOwnerVisibilityDeletedCreated => i0.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)',
|
||||
);
|
||||
i0.Index get idxRemoteAssetUploaded => i0.Index(
|
||||
'idx_remote_asset_uploaded',
|
||||
'CREATE INDEX IF NOT EXISTS idx_remote_asset_uploaded ON remote_asset_entity (uploaded_at)',
|
||||
);
|
||||
|
||||
@@ -120,7 +120,7 @@ class Drift extends $Drift {
|
||||
}
|
||||
|
||||
@override
|
||||
int get schemaVersion => 30;
|
||||
int get schemaVersion => 31;
|
||||
|
||||
@override
|
||||
MigrationStrategy get migration => MigrationStrategy(
|
||||
@@ -128,199 +128,209 @@ class Drift extends $Drift {
|
||||
// Run migration steps without foreign keys and re-enable them later
|
||||
await customStatement('PRAGMA foreign_keys = OFF');
|
||||
|
||||
await m.runMigrationSteps(
|
||||
from: from,
|
||||
to: to,
|
||||
steps: migrationSteps(
|
||||
from1To2: (m, v2) async {
|
||||
for (final entity in v2.entities) {
|
||||
await m.drop(entity);
|
||||
await m.create(entity);
|
||||
}
|
||||
},
|
||||
from2To3: (m, v3) async {
|
||||
// Removed foreign key constraint on stack.primaryAssetId
|
||||
await m.alterTable(TableMigration(v3.stackEntity));
|
||||
},
|
||||
from3To4: (m, v4) async {
|
||||
// Thumbnail path column got removed from person_entity
|
||||
await m.alterTable(TableMigration(v4.personEntity));
|
||||
// asset_face_entity is added
|
||||
await m.create(v4.assetFaceEntity);
|
||||
},
|
||||
from4To5: (m, v5) async {
|
||||
await m.alterTable(
|
||||
TableMigration(
|
||||
v5.userEntity,
|
||||
newColumns: [v5.userEntity.hasProfileImage, v5.userEntity.profileChangedAt],
|
||||
columnTransformer: {v5.userEntity.profileChangedAt: currentDateAndTime},
|
||||
),
|
||||
);
|
||||
},
|
||||
from5To6: (m, v6) async {
|
||||
// Drops the (checksum, ownerId) and adds it back as (ownerId, checksum)
|
||||
await customStatement('DROP INDEX IF EXISTS UQ_remote_asset_owner_checksum');
|
||||
await m.drop(v6.idxRemoteAssetOwnerChecksum);
|
||||
await m.create(v6.idxRemoteAssetOwnerChecksum);
|
||||
// Adds libraryId to remote_asset_entity
|
||||
await m.addColumn(v6.remoteAssetEntity, v6.remoteAssetEntity.libraryId);
|
||||
await m.drop(v6.uQRemoteAssetsOwnerChecksum);
|
||||
await m.create(v6.uQRemoteAssetsOwnerChecksum);
|
||||
await m.drop(v6.uQRemoteAssetsOwnerLibraryChecksum);
|
||||
await m.create(v6.uQRemoteAssetsOwnerLibraryChecksum);
|
||||
},
|
||||
from6To7: (m, v7) async {
|
||||
await m.createIndex(v7.idxLatLng);
|
||||
},
|
||||
from7To8: (m, v8) async {
|
||||
await m.create(v8.storeEntity);
|
||||
},
|
||||
from8To9: (m, v9) async {
|
||||
await m.addColumn(v9.localAlbumEntity, v9.localAlbumEntity.linkedRemoteAlbumId);
|
||||
},
|
||||
from9To10: (m, v10) async {
|
||||
await m.createTable(v10.authUserEntity);
|
||||
await m.addColumn(v10.userEntity, v10.userEntity.avatarColor);
|
||||
await m.alterTable(TableMigration(v10.userEntity));
|
||||
},
|
||||
from10To11: (m, v11) async {
|
||||
await m.addColumn(v11.localAlbumAssetEntity, v11.localAlbumAssetEntity.marker_);
|
||||
},
|
||||
from11To12: (m, v12) async {
|
||||
final localToUTCMapping = {
|
||||
v12.localAssetEntity: [v12.localAssetEntity.createdAt, v12.localAssetEntity.updatedAt],
|
||||
v12.localAlbumEntity: [v12.localAlbumEntity.updatedAt],
|
||||
};
|
||||
try {
|
||||
await transaction(
|
||||
() => m.runMigrationSteps(
|
||||
from: from,
|
||||
to: to,
|
||||
steps: migrationSteps(
|
||||
from1To2: (m, v2) async {
|
||||
for (final entity in v2.entities) {
|
||||
await m.drop(entity);
|
||||
await m.create(entity);
|
||||
}
|
||||
},
|
||||
from2To3: (m, v3) async {
|
||||
// Removed foreign key constraint on stack.primaryAssetId
|
||||
await m.alterTable(TableMigration(v3.stackEntity));
|
||||
},
|
||||
from3To4: (m, v4) async {
|
||||
// Thumbnail path column got removed from person_entity
|
||||
await m.alterTable(TableMigration(v4.personEntity));
|
||||
// asset_face_entity is added
|
||||
await m.create(v4.assetFaceEntity);
|
||||
},
|
||||
from4To5: (m, v5) async {
|
||||
await m.alterTable(
|
||||
TableMigration(
|
||||
v5.userEntity,
|
||||
newColumns: [v5.userEntity.hasProfileImage, v5.userEntity.profileChangedAt],
|
||||
columnTransformer: {v5.userEntity.profileChangedAt: currentDateAndTime},
|
||||
),
|
||||
);
|
||||
},
|
||||
from5To6: (m, v6) async {
|
||||
// Drops the (checksum, ownerId) and adds it back as (ownerId, checksum)
|
||||
await customStatement('DROP INDEX IF EXISTS UQ_remote_asset_owner_checksum');
|
||||
await m.drop(v6.idxRemoteAssetOwnerChecksum);
|
||||
await m.create(v6.idxRemoteAssetOwnerChecksum);
|
||||
// Adds libraryId to remote_asset_entity
|
||||
await m.addColumn(v6.remoteAssetEntity, v6.remoteAssetEntity.libraryId);
|
||||
await m.drop(v6.uQRemoteAssetsOwnerChecksum);
|
||||
await m.create(v6.uQRemoteAssetsOwnerChecksum);
|
||||
await m.drop(v6.uQRemoteAssetsOwnerLibraryChecksum);
|
||||
await m.create(v6.uQRemoteAssetsOwnerLibraryChecksum);
|
||||
},
|
||||
from6To7: (m, v7) async {
|
||||
await m.createIndex(v7.idxLatLng);
|
||||
},
|
||||
from7To8: (m, v8) async {
|
||||
await m.create(v8.storeEntity);
|
||||
},
|
||||
from8To9: (m, v9) async {
|
||||
await m.addColumn(v9.localAlbumEntity, v9.localAlbumEntity.linkedRemoteAlbumId);
|
||||
},
|
||||
from9To10: (m, v10) async {
|
||||
await m.createTable(v10.authUserEntity);
|
||||
await m.addColumn(v10.userEntity, v10.userEntity.avatarColor);
|
||||
await m.alterTable(TableMigration(v10.userEntity));
|
||||
},
|
||||
from10To11: (m, v11) async {
|
||||
await m.addColumn(v11.localAlbumAssetEntity, v11.localAlbumAssetEntity.marker_);
|
||||
},
|
||||
from11To12: (m, v12) async {
|
||||
final localToUTCMapping = {
|
||||
v12.localAssetEntity: [v12.localAssetEntity.createdAt, v12.localAssetEntity.updatedAt],
|
||||
v12.localAlbumEntity: [v12.localAlbumEntity.updatedAt],
|
||||
};
|
||||
|
||||
for (final entry in localToUTCMapping.entries) {
|
||||
final table = entry.key;
|
||||
await m.alterTable(
|
||||
TableMigration(
|
||||
table,
|
||||
columnTransformer: {
|
||||
for (final column in entry.value)
|
||||
column: column.modify(const DateTimeModifier.utc()).strftime('%Y-%m-%dT%H:%M:%fZ'),
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
from12To13: (m, v13) async {
|
||||
await m.create(v13.trashedLocalAssetEntity);
|
||||
await m.createIndex(v13.idxTrashedLocalAssetChecksum);
|
||||
await m.createIndex(v13.idxTrashedLocalAssetAlbum);
|
||||
},
|
||||
from13To14: (m, v14) async {
|
||||
await m.addColumn(v14.localAssetEntity, v14.localAssetEntity.adjustmentTime);
|
||||
await m.addColumn(v14.localAssetEntity, v14.localAssetEntity.latitude);
|
||||
await m.addColumn(v14.localAssetEntity, v14.localAssetEntity.longitude);
|
||||
},
|
||||
from14To15: (m, v15) async {
|
||||
await m.alterTable(
|
||||
TableMigration(
|
||||
v15.trashedLocalAssetEntity,
|
||||
columnTransformer: {v15.trashedLocalAssetEntity.source: Constant(TrashOrigin.localSync.index)},
|
||||
newColumns: [v15.trashedLocalAssetEntity.source],
|
||||
),
|
||||
);
|
||||
},
|
||||
from15To16: (m, v16) async {
|
||||
// Add i_cloud_id to local and remote asset tables
|
||||
await m.addColumn(v16.localAssetEntity, v16.localAssetEntity.iCloudId);
|
||||
await m.createIndex(v16.idxLocalAssetCloudId);
|
||||
await m.createTable(v16.remoteAssetCloudIdEntity);
|
||||
},
|
||||
from16To17: (m, v17) async {
|
||||
await m.addColumn(v17.remoteAssetEntity, v17.remoteAssetEntity.isEdited);
|
||||
},
|
||||
from17To18: (m, v18) async {
|
||||
await m.createIndex(v18.idxRemoteAssetCloudId);
|
||||
},
|
||||
from18To19: (m, v19) async {
|
||||
await m.createIndex(v19.idxAssetFacePersonId);
|
||||
await m.createIndex(v19.idxAssetFaceAssetId);
|
||||
await m.createIndex(v19.idxLocalAlbumAssetAlbumAsset);
|
||||
await m.createIndex(v19.idxPartnerSharedWithId);
|
||||
await m.createIndex(v19.idxPersonOwnerId);
|
||||
await m.createIndex(v19.idxRemoteAlbumOwnerId);
|
||||
await m.createIndex(v19.idxRemoteAlbumAssetAlbumAsset);
|
||||
await m.createIndex(v19.idxRemoteAssetStackId);
|
||||
await m.createIndex(v19.idxRemoteAssetLocalDateTimeDay);
|
||||
await m.createIndex(v19.idxRemoteAssetLocalDateTimeMonth);
|
||||
await m.createIndex(v19.idxStackPrimaryAssetId);
|
||||
},
|
||||
from19To20: (m, v20) async {
|
||||
await m.addColumn(v20.assetFaceEntity, v20.assetFaceEntity.isVisible);
|
||||
await m.addColumn(v20.assetFaceEntity, v20.assetFaceEntity.deletedAt);
|
||||
},
|
||||
from20To21: (m, v21) async {
|
||||
await m.addColumn(v21.localAssetEntity, v21.localAssetEntity.playbackStyle);
|
||||
await m.addColumn(v21.trashedLocalAssetEntity, v21.trashedLocalAssetEntity.playbackStyle);
|
||||
},
|
||||
from21To22: (m, v22) async {
|
||||
await m.createTable(v22.assetEditEntity);
|
||||
await m.createIndex(v22.idxAssetEditAssetId);
|
||||
},
|
||||
from22To23: (m, v23) async {
|
||||
await m.renameColumn(v23.localAssetEntity, 'duration_in_seconds', v23.localAssetEntity.durationMs);
|
||||
await m.renameColumn(v23.remoteAssetEntity, 'duration_in_seconds', v23.remoteAssetEntity.durationMs);
|
||||
await m.renameColumn(
|
||||
v23.trashedLocalAssetEntity,
|
||||
'duration_in_seconds',
|
||||
v23.trashedLocalAssetEntity.durationMs,
|
||||
);
|
||||
for (final entry in localToUTCMapping.entries) {
|
||||
final table = entry.key;
|
||||
await m.alterTable(
|
||||
TableMigration(
|
||||
table,
|
||||
columnTransformer: {
|
||||
for (final column in entry.value)
|
||||
column: column.modify(const DateTimeModifier.utc()).strftime('%Y-%m-%dT%H:%M:%fZ'),
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
from12To13: (m, v13) async {
|
||||
await m.create(v13.trashedLocalAssetEntity);
|
||||
await m.createIndex(v13.idxTrashedLocalAssetChecksum);
|
||||
await m.createIndex(v13.idxTrashedLocalAssetAlbum);
|
||||
},
|
||||
from13To14: (m, v14) async {
|
||||
await m.addColumn(v14.localAssetEntity, v14.localAssetEntity.adjustmentTime);
|
||||
await m.addColumn(v14.localAssetEntity, v14.localAssetEntity.latitude);
|
||||
await m.addColumn(v14.localAssetEntity, v14.localAssetEntity.longitude);
|
||||
},
|
||||
from14To15: (m, v15) async {
|
||||
await m.alterTable(
|
||||
TableMigration(
|
||||
v15.trashedLocalAssetEntity,
|
||||
columnTransformer: {v15.trashedLocalAssetEntity.source: Constant(TrashOrigin.localSync.index)},
|
||||
newColumns: [v15.trashedLocalAssetEntity.source],
|
||||
),
|
||||
);
|
||||
},
|
||||
from15To16: (m, v16) async {
|
||||
// Add i_cloud_id to local and remote asset tables
|
||||
await m.addColumn(v16.localAssetEntity, v16.localAssetEntity.iCloudId);
|
||||
await m.createIndex(v16.idxLocalAssetCloudId);
|
||||
await m.createTable(v16.remoteAssetCloudIdEntity);
|
||||
},
|
||||
from16To17: (m, v17) async {
|
||||
await m.addColumn(v17.remoteAssetEntity, v17.remoteAssetEntity.isEdited);
|
||||
},
|
||||
from17To18: (m, v18) async {
|
||||
await m.createIndex(v18.idxRemoteAssetCloudId);
|
||||
},
|
||||
from18To19: (m, v19) async {
|
||||
await m.createIndex(v19.idxAssetFacePersonId);
|
||||
await m.createIndex(v19.idxAssetFaceAssetId);
|
||||
await m.createIndex(v19.idxLocalAlbumAssetAlbumAsset);
|
||||
await m.createIndex(v19.idxPartnerSharedWithId);
|
||||
await m.createIndex(v19.idxPersonOwnerId);
|
||||
await m.createIndex(v19.idxRemoteAlbumOwnerId);
|
||||
await m.createIndex(v19.idxRemoteAlbumAssetAlbumAsset);
|
||||
await m.createIndex(v19.idxRemoteAssetStackId);
|
||||
await m.createIndex(v19.idxRemoteAssetLocalDateTimeDay);
|
||||
await m.createIndex(v19.idxRemoteAssetLocalDateTimeMonth);
|
||||
await m.createIndex(v19.idxStackPrimaryAssetId);
|
||||
},
|
||||
from19To20: (m, v20) async {
|
||||
await m.addColumn(v20.assetFaceEntity, v20.assetFaceEntity.isVisible);
|
||||
await m.addColumn(v20.assetFaceEntity, v20.assetFaceEntity.deletedAt);
|
||||
},
|
||||
from20To21: (m, v21) async {
|
||||
await m.addColumn(v21.localAssetEntity, v21.localAssetEntity.playbackStyle);
|
||||
await m.addColumn(v21.trashedLocalAssetEntity, v21.trashedLocalAssetEntity.playbackStyle);
|
||||
},
|
||||
from21To22: (m, v22) async {
|
||||
await m.createTable(v22.assetEditEntity);
|
||||
await m.createIndex(v22.idxAssetEditAssetId);
|
||||
},
|
||||
from22To23: (m, v23) async {
|
||||
await m.renameColumn(v23.localAssetEntity, 'duration_in_seconds', v23.localAssetEntity.durationMs);
|
||||
await m.renameColumn(v23.remoteAssetEntity, 'duration_in_seconds', v23.remoteAssetEntity.durationMs);
|
||||
await m.renameColumn(
|
||||
v23.trashedLocalAssetEntity,
|
||||
'duration_in_seconds',
|
||||
v23.trashedLocalAssetEntity.durationMs,
|
||||
);
|
||||
|
||||
await localAssetEntity.update().write(
|
||||
LocalAssetEntityCompanion.custom(durationMs: v23.localAssetEntity.durationMs * const Constant(1000)),
|
||||
);
|
||||
await remoteAssetEntity.update().write(
|
||||
RemoteAssetEntityCompanion.custom(durationMs: v23.remoteAssetEntity.durationMs * const Constant(1000)),
|
||||
);
|
||||
await trashedLocalAssetEntity.update().write(
|
||||
TrashedLocalAssetEntityCompanion.custom(
|
||||
durationMs: v23.trashedLocalAssetEntity.durationMs * const Constant(1000),
|
||||
),
|
||||
);
|
||||
},
|
||||
from23To24: (m, v24) async {
|
||||
await customStatement('DROP INDEX IF EXISTS idx_remote_album_owner_id');
|
||||
await m.alterTable(TableMigration(v24.remoteAlbumEntity));
|
||||
},
|
||||
from24To25: (m, v25) async {
|
||||
await m.createTable(v25.metadata);
|
||||
await customStatement('DROP INDEX IF EXISTS idx_remote_asset_owner_checksum');
|
||||
await customStatement('DROP INDEX IF EXISTS idx_remote_asset_local_date_time_day');
|
||||
await customStatement('DROP INDEX IF EXISTS idx_remote_asset_local_date_time_month');
|
||||
await m.createIndex(v25.idxRemoteAssetOwnerVisibilityDeletedCreated);
|
||||
await m.createIndex(v25.idxRemoteExifCity);
|
||||
await m.createIndex(v25.idxAssetFaceVisiblePerson);
|
||||
},
|
||||
from25To26: (m, v26) async {
|
||||
await m.addColumn(v26.remoteAssetEntity, v26.remoteAssetEntity.uploadedAt);
|
||||
},
|
||||
from26To27: (m, v27) async {
|
||||
await customStatement('ALTER TABLE metadata RENAME TO settings');
|
||||
},
|
||||
from27To28: (m, v28) async {
|
||||
await m.createIndex(v28.idxLocalAssetCreatedAt);
|
||||
},
|
||||
from28To29: (m, v29) async {
|
||||
await m.createTable(v29.assetOcrEntity);
|
||||
await m.createIndex(v29.idxAssetOcrAssetId);
|
||||
},
|
||||
from29To30: (m, v30) async {
|
||||
await m.alterTable(TableMigration(v30.settings));
|
||||
},
|
||||
),
|
||||
);
|
||||
await localAssetEntity.update().write(
|
||||
LocalAssetEntityCompanion.custom(durationMs: v23.localAssetEntity.durationMs * const Constant(1000)),
|
||||
);
|
||||
await remoteAssetEntity.update().write(
|
||||
RemoteAssetEntityCompanion.custom(
|
||||
durationMs: v23.remoteAssetEntity.durationMs * const Constant(1000),
|
||||
),
|
||||
);
|
||||
await trashedLocalAssetEntity.update().write(
|
||||
TrashedLocalAssetEntityCompanion.custom(
|
||||
durationMs: v23.trashedLocalAssetEntity.durationMs * const Constant(1000),
|
||||
),
|
||||
);
|
||||
},
|
||||
from23To24: (m, v24) async {
|
||||
await customStatement('DROP INDEX IF EXISTS idx_remote_album_owner_id');
|
||||
await m.alterTable(TableMigration(v24.remoteAlbumEntity));
|
||||
},
|
||||
from24To25: (m, v25) async {
|
||||
await m.createTable(v25.metadata);
|
||||
await customStatement('DROP INDEX IF EXISTS idx_remote_asset_owner_checksum');
|
||||
await customStatement('DROP INDEX IF EXISTS idx_remote_asset_local_date_time_day');
|
||||
await customStatement('DROP INDEX IF EXISTS idx_remote_asset_local_date_time_month');
|
||||
await m.createIndex(v25.idxRemoteAssetOwnerVisibilityDeletedCreated);
|
||||
await m.createIndex(v25.idxRemoteExifCity);
|
||||
await m.createIndex(v25.idxAssetFaceVisiblePerson);
|
||||
},
|
||||
from25To26: (m, v26) async {
|
||||
await m.addColumn(v26.remoteAssetEntity, v26.remoteAssetEntity.uploadedAt);
|
||||
},
|
||||
from26To27: (m, v27) async {
|
||||
await customStatement('ALTER TABLE metadata RENAME TO settings');
|
||||
},
|
||||
from27To28: (m, v28) async {
|
||||
await m.createIndex(v28.idxLocalAssetCreatedAt);
|
||||
},
|
||||
from28To29: (m, v29) async {
|
||||
await m.createTable(v29.assetOcrEntity);
|
||||
await m.createIndex(v29.idxAssetOcrAssetId);
|
||||
},
|
||||
from29To30: (m, v30) async {
|
||||
await m.alterTable(TableMigration(v30.settings));
|
||||
},
|
||||
from30To31: (m, v31) async {
|
||||
await m.createIndex(v31.idxRemoteAssetUploaded);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (kDebugMode) {
|
||||
// Fail if the migration broke foreign keys
|
||||
final wrongFKs = await customSelect('PRAGMA foreign_key_check').get();
|
||||
assert(wrongFKs.isEmpty, '${wrongFKs.map((e) => e.data)}');
|
||||
if (kDebugMode) {
|
||||
// Fail if the migration broke foreign keys
|
||||
final wrongFKs = await customSelect('PRAGMA foreign_key_check').get();
|
||||
assert(wrongFKs.isEmpty, '${wrongFKs.map((e) => e.data)}');
|
||||
}
|
||||
} finally {
|
||||
await customStatement('PRAGMA foreign_keys = ON;');
|
||||
}
|
||||
|
||||
await customStatement('PRAGMA foreign_keys = ON;');
|
||||
await optimize();
|
||||
},
|
||||
beforeOpen: (details) async {
|
||||
|
||||
@@ -124,6 +124,7 @@ abstract class $Drift extends i0.GeneratedDatabase {
|
||||
i2.idxRemoteAssetChecksum,
|
||||
i2.idxRemoteAssetStackId,
|
||||
i2.idxRemoteAssetOwnerVisibilityDeletedCreated,
|
||||
i2.idxRemoteAssetUploaded,
|
||||
authUserEntity,
|
||||
userMetadataEntity,
|
||||
partnerEntity,
|
||||
|
||||
@@ -15920,6 +15920,592 @@ 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,
|
||||
remoteAlbumEntity,
|
||||
localAlbumEntity,
|
||||
localAlbumAssetEntity,
|
||||
idxLocalAlbumAssetAlbumAsset,
|
||||
idxLocalAssetChecksum,
|
||||
idxLocalAssetCloudId,
|
||||
idxLocalAssetCreatedAt,
|
||||
idxStackPrimaryAssetId,
|
||||
uQRemoteAssetsOwnerChecksum,
|
||||
uQRemoteAssetsOwnerLibraryChecksum,
|
||||
idxRemoteAssetChecksum,
|
||||
idxRemoteAssetStackId,
|
||||
idxRemoteAssetOwnerVisibilityDeletedCreated,
|
||||
idxRemoteAssetUploaded,
|
||||
authUserEntity,
|
||||
userMetadataEntity,
|
||||
partnerEntity,
|
||||
remoteExifEntity,
|
||||
remoteAlbumAssetEntity,
|
||||
remoteAlbumUserEntity,
|
||||
remoteAssetCloudIdEntity,
|
||||
memoryEntity,
|
||||
memoryAssetEntity,
|
||||
personEntity,
|
||||
assetFaceEntity,
|
||||
storeEntity,
|
||||
trashedLocalAssetEntity,
|
||||
assetEditEntity,
|
||||
settings,
|
||||
assetOcrEntity,
|
||||
idxPartnerSharedWithId,
|
||||
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 Shape36 localAssetEntity = Shape36(
|
||||
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_132,
|
||||
_column_133,
|
||||
_column_134,
|
||||
_column_135,
|
||||
_column_136,
|
||||
_column_137,
|
||||
],
|
||||
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,
|
||||
);
|
||||
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 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 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)',
|
||||
);
|
||||
final i1.Index idxRemoteAssetUploaded = i1.Index(
|
||||
'idx_remote_asset_uploaded',
|
||||
'CREATE INDEX IF NOT EXISTS idx_remote_asset_uploaded ON remote_asset_entity (uploaded_at)',
|
||||
);
|
||||
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 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 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)',
|
||||
);
|
||||
}
|
||||
|
||||
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 +16536,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 +16685,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 +16726,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 +16758,6 @@ i1.OnUpgrade stepByStep({
|
||||
from27To28: from27To28,
|
||||
from28To29: from28To29,
|
||||
from29To30: from29To30,
|
||||
from30To31: from30To31,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -614,7 +614,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
|
||||
return (
|
||||
bucketSource: () => _watchRemoteBucket(filter: filter, groupBy: groupBy, sortBy: sortBy),
|
||||
assetSource: (offset, count) =>
|
||||
_getRemoteAssets(filter: filter, offset: offset, count: count, joinLocal: joinLocal),
|
||||
_getRemoteAssets(filter: filter, offset: offset, count: count, joinLocal: joinLocal, sortBy: sortBy),
|
||||
origin: origin,
|
||||
);
|
||||
}
|
||||
@@ -651,6 +651,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
|
||||
required int offset,
|
||||
required int count,
|
||||
bool joinLocal = false,
|
||||
SortAssetsBy sortBy = SortAssetsBy.taken,
|
||||
}) {
|
||||
if (joinLocal) {
|
||||
final query =
|
||||
@@ -663,7 +664,11 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
|
||||
])
|
||||
..addColumns([_db.localAssetEntity.id])
|
||||
..where(filter(_db.remoteAssetEntity))
|
||||
..orderBy([OrderingTerm.desc(_db.remoteAssetEntity.createdAt)])
|
||||
..orderBy([
|
||||
OrderingTerm.desc(
|
||||
sortBy == SortAssetsBy.uploaded ? _db.remoteAssetEntity.uploadedAt : _db.remoteAssetEntity.createdAt,
|
||||
),
|
||||
])
|
||||
..limit(count, offset: offset);
|
||||
|
||||
return query
|
||||
@@ -672,7 +677,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
|
||||
} else {
|
||||
final query = _db.remoteAssetEntity.select()
|
||||
..where(filter)
|
||||
..orderBy([(row) => OrderingTerm.desc(row.createdAt)])
|
||||
..orderBy([(row) => OrderingTerm.desc(sortBy == SortAssetsBy.uploaded ? row.uploadedAt : row.createdAt)])
|
||||
..limit(count, offset: offset);
|
||||
|
||||
return query.map((row) => row.toDto()).get();
|
||||
|
||||
@@ -154,7 +154,7 @@ class _DriftEditImagePageState extends ConsumerState<DriftEditImagePage> with Ti
|
||||
}
|
||||
|
||||
class _AspectRatioButton extends StatelessWidget {
|
||||
final AspectRatioPreset ratio;
|
||||
final CropAspectRatio ratio;
|
||||
final bool isSelected;
|
||||
final VoidCallback onPressed;
|
||||
|
||||
@@ -162,15 +162,16 @@ class _AspectRatioButton extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final color = isSelected ? context.primaryColor : context.themeData.iconTheme.color;
|
||||
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
IconButton(
|
||||
iconSize: 36,
|
||||
icon: Transform.rotate(
|
||||
angle: ratio.iconRotated ? pi / 2 : 0,
|
||||
child: Icon(ratio.icon, color: isSelected ? context.primaryColor : context.themeData.iconTheme.color),
|
||||
),
|
||||
icon: ratio.ratio != null
|
||||
? _AspectRatioRect(ratio: ratio.ratio!, color: color)
|
||||
: Icon(ratio.icon, color: color),
|
||||
onPressed: onPressed,
|
||||
),
|
||||
Text(ratio.label, style: context.textTheme.displayMedium),
|
||||
@@ -179,6 +180,32 @@ class _AspectRatioButton extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _AspectRatioRect extends StatelessWidget {
|
||||
final double ratio;
|
||||
final Color? color;
|
||||
|
||||
const _AspectRatioRect({required this.ratio, required this.color});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
width: 28,
|
||||
height: 28,
|
||||
child: Center(
|
||||
child: AspectRatio(
|
||||
aspectRatio: ratio,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: color ?? Colors.transparent, width: 3),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(4)),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AspectRatioSelector extends ConsumerWidget {
|
||||
const _AspectRatioSelector();
|
||||
|
||||
@@ -187,22 +214,16 @@ class _AspectRatioSelector extends ConsumerWidget {
|
||||
final editorState = ref.watch(editorStateProvider);
|
||||
final editorNotifier = ref.read(editorStateProvider.notifier);
|
||||
|
||||
// the whole crop view is rotated, so we need to swap the aspect ratio when the rotation is 90 or 270 degrees
|
||||
double? selectedAspectRatio = editorState.aspectRatio;
|
||||
if (editorState.rotationAngle % 180 != 0 && selectedAspectRatio != null) {
|
||||
selectedAspectRatio = 1 / selectedAspectRatio;
|
||||
}
|
||||
|
||||
return SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
children: AspectRatioPreset.values.map((entry) {
|
||||
children: aspectRatioPresets.map((entry) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: _AspectRatioButton(
|
||||
ratio: entry,
|
||||
isSelected: selectedAspectRatio == entry.ratio,
|
||||
onPressed: () => editorNotifier.setAspectRatio(entry.ratio),
|
||||
isSelected: editorState.aspectRatio == entry,
|
||||
onPressed: () => editorNotifier.setAspectRatio(entry),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
@@ -357,8 +378,22 @@ class _EditorPreviewState extends ConsumerState<_EditorPreview> with TickerProvi
|
||||
final editorState = ref.watch(editorStateProvider);
|
||||
final editorNotifier = ref.read(editorStateProvider.notifier);
|
||||
|
||||
ref.listen(editorStateProvider, (_, current) {
|
||||
cropController.aspectRatio = current.aspectRatio;
|
||||
ref.listen(editorStateProvider, (previous, current) {
|
||||
// Only re-apply the aspect ratio when it changes, otherwise the crop rect will shrink on every rotation
|
||||
if (previous?.aspectRatio != current.aspectRatio) {
|
||||
double? ratio;
|
||||
|
||||
ratio = switch (current.aspectRatio) {
|
||||
CropAspectRatio.original => current.originalWidth / current.originalHeight,
|
||||
_ => current.aspectRatio.ratio,
|
||||
};
|
||||
|
||||
if (current.rotationAngle % 180 != 0) {
|
||||
ratio = ratio != null ? 1 / ratio : null;
|
||||
}
|
||||
|
||||
cropController.aspectRatio = ratio;
|
||||
}
|
||||
|
||||
if (cropController.crop != current.crop) {
|
||||
cropController.crop = current.crop;
|
||||
@@ -386,7 +421,9 @@ class _EditorPreviewState extends ConsumerState<_EditorPreview> with TickerProvi
|
||||
1.0,
|
||||
1.0,
|
||||
),
|
||||
child: Container(
|
||||
child: AnimatedContainer(
|
||||
duration: editorState.animationDuration,
|
||||
curve: Curves.easeInOut,
|
||||
padding: const EdgeInsets.all(10),
|
||||
width: (editorState.rotationAngle % 180 == 0) ? baseWidth : baseHeight,
|
||||
height: (editorState.rotationAngle % 180 == 0) ? baseHeight : baseWidth,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/constants/aspect_ratios.dart';
|
||||
import 'package:immich_mobile/domain/models/asset_edit.model.dart';
|
||||
import 'package:immich_mobile/domain/models/exif.model.dart';
|
||||
import 'package:immich_mobile/utils/editor.utils.dart';
|
||||
@@ -60,13 +61,8 @@ class EditorProvider extends Notifier<EditorState> {
|
||||
state = state.copyWith(crop: crop, hasUnsavedEdits: true);
|
||||
}
|
||||
|
||||
void setAspectRatio(double? aspectRatio) {
|
||||
if (aspectRatio != null && state.rotationAngle % 180 != 0) {
|
||||
// When rotated 90 or 270 degrees, swap width and height for aspect ratio calculations
|
||||
aspectRatio = 1 / aspectRatio;
|
||||
}
|
||||
|
||||
state = state.copyWith(aspectRatio: aspectRatio);
|
||||
void setAspectRatio(CropAspectRatio preset) {
|
||||
state = state.copyWith(aspectRatio: preset, hasUnsavedEdits: true);
|
||||
}
|
||||
|
||||
void resetEdits() {
|
||||
@@ -76,19 +72,19 @@ class EditorProvider extends Notifier<EditorState> {
|
||||
flipHorizontal: false,
|
||||
flipVertical: false,
|
||||
crop: const Rect.fromLTRB(0, 0, 1, 1),
|
||||
aspectRatio: null,
|
||||
aspectRatio: CropAspectRatio.free,
|
||||
hasUnsavedEdits: true,
|
||||
);
|
||||
}
|
||||
|
||||
void rotateCCW() {
|
||||
_animateRotation(state.rotationAngle - 90);
|
||||
state = state.copyWith(hasUnsavedEdits: true);
|
||||
state = state.copyWith(aspectRatio: state.aspectRatio.flipped, hasUnsavedEdits: true);
|
||||
}
|
||||
|
||||
void rotateCW() {
|
||||
_animateRotation(state.rotationAngle + 90);
|
||||
state = state.copyWith(hasUnsavedEdits: true);
|
||||
state = state.copyWith(aspectRatio: state.aspectRatio.flipped, hasUnsavedEdits: true);
|
||||
}
|
||||
|
||||
void flipHorizontally() {
|
||||
@@ -117,7 +113,7 @@ class EditorState {
|
||||
final bool flipHorizontal;
|
||||
final bool flipVertical;
|
||||
final Rect crop;
|
||||
final double? aspectRatio;
|
||||
final CropAspectRatio aspectRatio;
|
||||
|
||||
final int originalWidth;
|
||||
final int originalHeight;
|
||||
@@ -132,7 +128,7 @@ class EditorState {
|
||||
bool? flipHorizontal,
|
||||
bool? flipVertical,
|
||||
Rect? crop,
|
||||
this.aspectRatio,
|
||||
CropAspectRatio? aspectRatio,
|
||||
int? originalWidth,
|
||||
int? originalHeight,
|
||||
Duration? animationDuration,
|
||||
@@ -145,6 +141,7 @@ class EditorState {
|
||||
originalWidth = originalWidth ?? 0,
|
||||
originalHeight = originalHeight ?? 0,
|
||||
crop = crop ?? const Rect.fromLTRB(0, 0, 1, 1),
|
||||
aspectRatio = aspectRatio ?? CropAspectRatio.free,
|
||||
hasUnsavedEdits = hasUnsavedEdits ?? false;
|
||||
|
||||
EditorState copyWith({
|
||||
@@ -152,7 +149,7 @@ class EditorState {
|
||||
int? rotationAngle,
|
||||
bool? flipHorizontal,
|
||||
bool? flipVertical,
|
||||
double? aspectRatio = double.infinity,
|
||||
CropAspectRatio? aspectRatio,
|
||||
int? originalWidth,
|
||||
int? originalHeight,
|
||||
Duration? animationDuration,
|
||||
@@ -164,7 +161,7 @@ class EditorState {
|
||||
rotationAngle: rotationAngle ?? this.rotationAngle,
|
||||
flipHorizontal: flipHorizontal ?? this.flipHorizontal,
|
||||
flipVertical: flipVertical ?? this.flipVertical,
|
||||
aspectRatio: aspectRatio == double.infinity ? this.aspectRatio : aspectRatio,
|
||||
aspectRatio: aspectRatio ?? this.aspectRatio,
|
||||
animationDuration: animationDuration ?? this.animationDuration,
|
||||
originalWidth: originalWidth ?? this.originalWidth,
|
||||
originalHeight: originalHeight ?? this.originalHeight,
|
||||
|
||||
@@ -135,7 +135,7 @@ class _SliverTimeline extends ConsumerStatefulWidget {
|
||||
ConsumerState createState() => _SliverTimelineState();
|
||||
}
|
||||
|
||||
class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
|
||||
class _SliverTimelineState extends ConsumerState<_SliverTimeline> with WidgetsBindingObserver {
|
||||
late final ScrollController _scrollController;
|
||||
StreamSubscription? _eventSubscription;
|
||||
|
||||
@@ -153,6 +153,7 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
_scrollController = ScrollController(onAttach: _restoreAssetPosition);
|
||||
_eventSubscription = EventStream.shared.listen(_onEvent);
|
||||
|
||||
@@ -178,17 +179,14 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
|
||||
}
|
||||
}
|
||||
|
||||
// Capture iOS status bar tap
|
||||
@override
|
||||
void handleStatusBarTap() => _scrollToTop();
|
||||
|
||||
void _onEvent(Event event) {
|
||||
switch (event) {
|
||||
case ScrollToTopEvent():
|
||||
{
|
||||
final timelineState = ref.read(timelineStateProvider.notifier);
|
||||
timelineState.setScrubbing(true);
|
||||
_scrollController
|
||||
.animateTo(0, duration: const Duration(milliseconds: 250), curve: Curves.easeInOut)
|
||||
.whenComplete(() => timelineState.setScrubbing(false));
|
||||
}
|
||||
|
||||
_scrollToTop();
|
||||
case ScrollToDateEvent scrollToDateEvent:
|
||||
_scrollToDate(scrollToDateEvent.date);
|
||||
case TimelineReloadEvent():
|
||||
@@ -246,11 +244,24 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
_scrollController.dispose();
|
||||
_eventSubscription?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _scrollToTop() {
|
||||
if (!_scrollController.hasClients) {
|
||||
return;
|
||||
}
|
||||
|
||||
final timelineState = ref.read(timelineStateProvider.notifier);
|
||||
timelineState.setScrubbing(true);
|
||||
_scrollController
|
||||
.animateTo(0, duration: const Duration(milliseconds: 250), curve: Curves.easeInOut)
|
||||
.whenComplete(() => timelineState.setScrubbing(false));
|
||||
}
|
||||
|
||||
void _scrollToDate(DateTime date) {
|
||||
final timelineState = ref.read(timelineStateProvider.notifier);
|
||||
final asyncSegments = ref.read(timelineSegmentProvider);
|
||||
@@ -381,6 +392,9 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
|
||||
child: PrimaryScrollController(
|
||||
controller: _scrollController,
|
||||
child: Scaffold(
|
||||
// This removes the built in Scaffold `handleStatusBarTap` implementation, preventing duplicate
|
||||
// events when we provide our own
|
||||
primary: false,
|
||||
resizeToAvoidBottomInset: false,
|
||||
floatingActionButton: const DownloadStatusFloatingButton(),
|
||||
body: asyncSegments.widgetWhen(
|
||||
|
||||
@@ -22,6 +22,7 @@ import 'package:immich_mobile/repositories/upload.repository.dart';
|
||||
import 'package:immich_mobile/services/api.service.dart';
|
||||
import 'package:immich_mobile/utils/debug_print.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:openapi/api.dart' as api;
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
final backgroundUploadServiceProvider = Provider((ref) {
|
||||
@@ -335,7 +336,8 @@ class BackgroundUploadService {
|
||||
return null;
|
||||
}
|
||||
|
||||
final fields = {'livePhotoVideoId': livePhotoVideoId};
|
||||
// Visibility hidden on upload to prevent the server from running regular jobs on the live photo asset
|
||||
final fields = {'livePhotoVideoId': livePhotoVideoId, 'visibility': api.AssetVisibility.hidden.value};
|
||||
|
||||
final requiresWiFi = _shouldRequireWiFi(asset);
|
||||
final originalFileName = await _assetMediaRepository.getOriginalFilename(asset.id) ?? asset.name;
|
||||
|
||||
@@ -4,7 +4,7 @@ import 'dart:io';
|
||||
|
||||
import 'package:hooks_riverpod/hooks_riverpod.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/domain/models/asset/base_asset.model.dart' hide AssetVisibility;
|
||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/extensions/network_capability_extensions.dart';
|
||||
@@ -19,6 +19,7 @@ import 'package:immich_mobile/providers/infrastructure/storage.provider.dart';
|
||||
import 'package:immich_mobile/repositories/asset_media.repository.dart';
|
||||
import 'package:immich_mobile/repositories/upload.repository.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:photo_manager/photo_manager.dart' show PMProgressHandler;
|
||||
|
||||
@@ -338,7 +339,8 @@ class ForegroundUploadService {
|
||||
final livePhotoResult = await _uploadRepository.uploadFile(
|
||||
file: livePhotoFile,
|
||||
originalFileName: livePhotoTitle,
|
||||
fields: fields,
|
||||
// Visibility hidden on upload to prevent the server from running regular jobs on the live photo asset
|
||||
fields: {...fields, 'visibility': AssetVisibility.hidden.value},
|
||||
cancelToken: cancelToken,
|
||||
onProgress: onProgress != null
|
||||
? (bytes, totalBytes) => onProgress(asset.localId!, livePhotoTitle, bytes, totalBytes)
|
||||
|
||||
+8
-7
@@ -309,8 +309,8 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "pkgs/cupertino_http"
|
||||
ref: a0a933358517c6d01cff37fc2a2752ee2d744a3c
|
||||
resolved-ref: a0a933358517c6d01cff37fc2a2752ee2d744a3c
|
||||
ref: "58b03c756b81d16b3975a8ae4b91d25360123bb5"
|
||||
resolved-ref: "58b03c756b81d16b3975a8ae4b91d25360123bb5"
|
||||
url: "https://github.com/mertalev/http"
|
||||
source: git
|
||||
version: "3.0.0-wip"
|
||||
@@ -1158,12 +1158,13 @@ packages:
|
||||
source: hosted
|
||||
version: "0.5.0"
|
||||
objective_c:
|
||||
dependency: transitive
|
||||
dependency: "direct overridden"
|
||||
description:
|
||||
name: objective_c
|
||||
sha256: "6cb691c686fa2838c6deb34980d426145c2a5d537491cb83d463c33cdbc726ed"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
path: "pkgs/objective_c"
|
||||
ref: "2915556701f4734a784222f290a82adb1b101682"
|
||||
resolved-ref: "2915556701f4734a784222f290a82adb1b101682"
|
||||
url: "https://github.com/mertalev/native"
|
||||
source: git
|
||||
version: "9.4.1"
|
||||
octo_image:
|
||||
dependency: "direct main"
|
||||
|
||||
+6
-1
@@ -84,7 +84,7 @@ dependencies:
|
||||
cupertino_http:
|
||||
git:
|
||||
url: https://github.com/mertalev/http
|
||||
ref: 'a0a933358517c6d01cff37fc2a2752ee2d744a3c' # https://github.com/dart-lang/http/pull/1876
|
||||
ref: '58b03c756b81d16b3975a8ae4b91d25360123bb5'
|
||||
path: pkgs/cupertino_http/
|
||||
ok_http:
|
||||
git:
|
||||
@@ -114,6 +114,11 @@ dev_dependencies:
|
||||
# Pin bonsoir to 5.x until cast releases a version compatible with bonsoir 6.x.
|
||||
dependency_overrides:
|
||||
bonsoir: ^5.1.11
|
||||
objective_c:
|
||||
git:
|
||||
url: https://github.com/mertalev/native
|
||||
ref: '2915556701f4734a784222f290a82adb1b101682' # https://github.com/dart-lang/native/pull/3458
|
||||
path: pkgs/objective_c/
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
|
||||
+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,
|
||||
];
|
||||
}
|
||||
|
||||
+10032
File diff suppressed because it is too large
Load Diff
@@ -140,6 +140,7 @@ void main() {
|
||||
expect(task, isNotNull);
|
||||
expect(task!.fields['filename'], equals('OriginalLivePhoto.HEIC'));
|
||||
expect(task.fields['livePhotoVideoId'], equals('video-id-123'));
|
||||
expect(task.fields['visibility'], equals('hidden'));
|
||||
verify(() => mockAssetMediaRepository.getOriginalFilename(asset.id)).called(1);
|
||||
});
|
||||
|
||||
@@ -333,6 +334,7 @@ void main() {
|
||||
expect(task, isNotNull);
|
||||
expect(task!.fields.containsKey('metadata'), isTrue);
|
||||
expect(task.fields['livePhotoVideoId'], equals('video-123'));
|
||||
expect(task.fields['visibility'], equals('hidden'));
|
||||
|
||||
final metadata = jsonDecode(task.fields['metadata']!) as List;
|
||||
expect(metadata, hasLength(1));
|
||||
|
||||
@@ -151,14 +151,14 @@ const methods = wrapper<Manifest>({
|
||||
}),
|
||||
|
||||
webhook: ({ config, data, functions, type, trigger }) => {
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
const headers: Record<string, string> = {};
|
||||
|
||||
if (config.headerName && config.headerValue) {
|
||||
headers[config.headerName] = config.headerValue;
|
||||
}
|
||||
|
||||
headers['Content-Type'] = 'application/json';
|
||||
|
||||
functions.httpRequest(config.url, {
|
||||
method: config.method ?? 'POST',
|
||||
body: JSON.stringify({
|
||||
|
||||
Generated
+148
-9
@@ -34,7 +34,7 @@ importers:
|
||||
dependencies:
|
||||
'@docusaurus/core':
|
||||
specifier: ~3.10.0
|
||||
version: 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)
|
||||
version: 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)
|
||||
'@docusaurus/preset-classic':
|
||||
specifier: ~3.10.0
|
||||
version: 3.10.1(@algolia/client-search@5.54.0)(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(search-insights@2.17.3)(typescript@6.0.3)
|
||||
@@ -58,7 +58,7 @@ importers:
|
||||
version: 10.5.0(postcss@8.5.15)
|
||||
docusaurus-lunr-search:
|
||||
specifier: ^3.3.2
|
||||
version: 3.6.0(@docusaurus/core@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
version: 3.6.0(@docusaurus/core@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
lunr:
|
||||
specifier: ^2.3.9
|
||||
version: 2.3.9
|
||||
@@ -786,8 +786,8 @@ importers:
|
||||
specifier: workspace:*
|
||||
version: link:../packages/sdk
|
||||
'@immich/ui':
|
||||
specifier: ^0.83.0
|
||||
version: 0.83.0(@sveltejs/kit@2.65.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.3(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.3(@typescript-eslint/types@8.61.0))(typescript@6.0.3)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.3(@typescript-eslint/types@8.61.0))
|
||||
specifier: ^0.81.1
|
||||
version: 0.81.1(@sveltejs/kit@2.65.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.3(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.3(@typescript-eslint/types@8.61.0))(typescript@6.0.3)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.3(@typescript-eslint/types@8.61.0))
|
||||
'@mapbox/mapbox-gl-rtl-text':
|
||||
specifier: 0.4.0
|
||||
version: 0.4.0
|
||||
@@ -3250,8 +3250,8 @@ packages:
|
||||
resolution: {integrity: sha512-O1SJ+BbeFVsUTF4af1MfagJZM+lPgLjI8lQ3SZNjpo8SGJReSbUl2ii03OKuGni/G0yp2GnRLpOTNSHYGtVrcg==}
|
||||
hasBin: true
|
||||
|
||||
'@immich/ui@0.83.0':
|
||||
resolution: {integrity: sha512-Xh3R3yhn8/Qyq8lGWIiVmturA+dHXtmW/h++AGb2UAwS/58qu7ssocO/HWIamaSpkgjPGotlWpPIpUJq1zh9FQ==}
|
||||
'@immich/ui@0.81.1':
|
||||
resolution: {integrity: sha512-7g173hArs7OS5CfHUG+ZJVQp1iCvFZzBmm+f2uH1WChlFdcwty5DLilYrDBszWuPIvu8wTX2AXTneS/KYBCUxw==}
|
||||
peerDependencies:
|
||||
'@sveltejs/kit': ^2.13.0
|
||||
svelte: ^5.0.0
|
||||
@@ -14402,6 +14402,49 @@ snapshots:
|
||||
- uglify-js
|
||||
- webpack-cli
|
||||
|
||||
'@docusaurus/bundler@3.10.1(esbuild@0.28.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)':
|
||||
dependencies:
|
||||
'@babel/core': 7.29.7
|
||||
'@docusaurus/babel': 3.10.1(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
'@docusaurus/cssnano-preset': 3.10.1
|
||||
'@docusaurus/logger': 3.10.1
|
||||
'@docusaurus/types': 3.10.1(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
'@docusaurus/utils': 3.10.1(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
babel-loader: 9.2.1(@babel/core@7.29.7)(webpack@5.107.2(postcss@8.5.15))
|
||||
clean-css: 5.3.3
|
||||
copy-webpack-plugin: 11.0.0(webpack@5.107.2(postcss@8.5.15))
|
||||
css-loader: 6.11.0(webpack@5.107.2(postcss@8.5.15))
|
||||
css-minimizer-webpack-plugin: 5.0.1(clean-css@5.3.3)(esbuild@0.28.1)(webpack@5.107.2(postcss@8.5.15))
|
||||
cssnano: 6.1.2(postcss@8.5.15)
|
||||
file-loader: 6.2.0(webpack@5.107.2(postcss@8.5.15))
|
||||
html-minifier-terser: 7.2.0
|
||||
mini-css-extract-plugin: 2.10.2(webpack@5.107.2(postcss@8.5.15))
|
||||
null-loader: 4.0.1(webpack@5.107.2(postcss@8.5.15))
|
||||
postcss: 8.5.15
|
||||
postcss-loader: 7.3.4(postcss@8.5.15)(typescript@6.0.3)(webpack@5.107.2(postcss@8.5.15))
|
||||
postcss-preset-env: 10.6.1(postcss@8.5.15)
|
||||
terser-webpack-plugin: 5.6.1(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.15)(webpack@5.107.2(postcss@8.5.15))
|
||||
tslib: 2.8.1
|
||||
url-loader: 4.1.1(file-loader@6.2.0(webpack@5.107.2(postcss@8.5.15)))(webpack@5.107.2(postcss@8.5.15))
|
||||
webpack: 5.107.2(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(html-minifier-terser@7.2.0)(postcss@8.5.15)
|
||||
webpackbar: 7.0.0(webpack@5.107.2(postcss@8.5.15))
|
||||
transitivePeerDependencies:
|
||||
- '@minify-html/node'
|
||||
- '@parcel/css'
|
||||
- '@rspack/core'
|
||||
- '@swc/core'
|
||||
- '@swc/css'
|
||||
- '@swc/html'
|
||||
- csso
|
||||
- esbuild
|
||||
- lightningcss
|
||||
- react
|
||||
- react-dom
|
||||
- supports-color
|
||||
- typescript
|
||||
- uglify-js
|
||||
- webpack-cli
|
||||
|
||||
'@docusaurus/bundler@3.10.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)':
|
||||
dependencies:
|
||||
'@babel/core': 7.29.7
|
||||
@@ -14445,6 +14488,75 @@ snapshots:
|
||||
- uglify-js
|
||||
- webpack-cli
|
||||
|
||||
'@docusaurus/core@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)':
|
||||
dependencies:
|
||||
'@docusaurus/babel': 3.10.1(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
'@docusaurus/bundler': 3.10.1(esbuild@0.28.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)
|
||||
'@docusaurus/logger': 3.10.1
|
||||
'@docusaurus/mdx-loader': 3.10.1(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
'@docusaurus/utils': 3.10.1(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
'@docusaurus/utils-common': 3.10.1(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
'@docusaurus/utils-validation': 3.10.1(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
'@mdx-js/react': 3.1.1(@types/react@19.2.17)(react@19.2.7)
|
||||
boxen: 6.2.1
|
||||
chalk: 4.1.2
|
||||
chokidar: 3.6.0
|
||||
cli-table3: 0.6.5
|
||||
combine-promises: 1.2.0
|
||||
commander: 5.1.0
|
||||
core-js: 3.49.0
|
||||
detect-port: 1.6.1
|
||||
escape-html: 1.0.3
|
||||
eta: 2.2.0
|
||||
eval: 0.1.8
|
||||
execa: 5.1.1
|
||||
fs-extra: 11.3.5
|
||||
html-tags: 3.3.1
|
||||
html-webpack-plugin: 5.6.7(webpack@5.107.2(postcss@8.5.15))
|
||||
leven: 3.1.0
|
||||
lodash: 4.18.1
|
||||
open: 8.4.2
|
||||
p-map: 4.0.0
|
||||
prompts: 2.4.2
|
||||
react: 19.2.7
|
||||
react-dom: 19.2.7(react@19.2.7)
|
||||
react-helmet-async: '@slorber/react-helmet-async@1.3.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)'
|
||||
react-loadable: '@docusaurus/react-loadable@6.0.0(react@19.2.7)'
|
||||
react-loadable-ssr-addon-v5-slorber: 1.0.3(@docusaurus/react-loadable@6.0.0(react@19.2.7))(webpack@5.107.2(postcss@8.5.15))
|
||||
react-router: 5.3.4(react@19.2.7)
|
||||
react-router-config: 5.1.1(react-router@5.3.4(react@19.2.7))(react@19.2.7)
|
||||
react-router-dom: 5.3.4(react@19.2.7)
|
||||
semver: 7.8.4
|
||||
serve-handler: 6.1.7
|
||||
tinypool: 1.1.1
|
||||
tslib: 2.8.1
|
||||
update-notifier: 6.0.2
|
||||
webpack: 5.107.2(postcss@8.5.15)
|
||||
webpack-bundle-analyzer: 4.10.2
|
||||
webpack-dev-server: 5.2.5(tslib@2.8.1)(webpack@5.107.2(postcss@8.5.15))
|
||||
webpack-merge: 6.0.1
|
||||
transitivePeerDependencies:
|
||||
- '@minify-html/node'
|
||||
- '@parcel/css'
|
||||
- '@rspack/core'
|
||||
- '@swc/core'
|
||||
- '@swc/css'
|
||||
- '@swc/html'
|
||||
- bufferutil
|
||||
- clean-css
|
||||
- cssnano
|
||||
- csso
|
||||
- debug
|
||||
- esbuild
|
||||
- html-minifier-terser
|
||||
- lightningcss
|
||||
- postcss
|
||||
- supports-color
|
||||
- typescript
|
||||
- uglify-js
|
||||
- utf-8-validate
|
||||
- webpack-cli
|
||||
|
||||
'@docusaurus/core@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)':
|
||||
dependencies:
|
||||
'@docusaurus/babel': 3.10.1(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
@@ -15999,7 +16111,7 @@ snapshots:
|
||||
pg-connection-string: 2.13.0
|
||||
postgres: 3.4.9
|
||||
|
||||
'@immich/ui@0.83.0(@sveltejs/kit@2.65.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.3(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.3(@typescript-eslint/types@8.61.0))(typescript@6.0.3)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.3(@typescript-eslint/types@8.61.0))':
|
||||
'@immich/ui@0.81.1(@sveltejs/kit@2.65.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.3(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.3(@typescript-eslint/types@8.61.0))(typescript@6.0.3)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.3(@typescript-eslint/types@8.61.0))':
|
||||
dependencies:
|
||||
'@internationalized/date': 3.12.2
|
||||
'@mdi/js': 7.4.47
|
||||
@@ -19865,6 +19977,19 @@ snapshots:
|
||||
optionalDependencies:
|
||||
webpack: 5.107.2(postcss@8.5.15)
|
||||
|
||||
css-minimizer-webpack-plugin@5.0.1(clean-css@5.3.3)(esbuild@0.28.1)(webpack@5.107.2(postcss@8.5.15)):
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.31
|
||||
cssnano: 6.1.2(postcss@8.5.15)
|
||||
jest-worker: 29.7.0
|
||||
postcss: 8.5.15
|
||||
schema-utils: 4.3.3
|
||||
serialize-javascript: 6.0.2
|
||||
webpack: 5.107.2(postcss@8.5.15)
|
||||
optionalDependencies:
|
||||
clean-css: 5.3.3
|
||||
esbuild: 0.28.1
|
||||
|
||||
css-minimizer-webpack-plugin@5.0.1(clean-css@5.3.3)(webpack@5.107.2(postcss@8.5.15)):
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.31
|
||||
@@ -20342,9 +20467,9 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
docusaurus-lunr-search@3.6.0(@docusaurus/core@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3))(react-dom@19.2.7(react@19.2.7))(react@19.2.7):
|
||||
docusaurus-lunr-search@3.6.0(@docusaurus/core@3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3))(react-dom@19.2.7(react@19.2.7))(react@19.2.7):
|
||||
dependencies:
|
||||
'@docusaurus/core': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)
|
||||
'@docusaurus/core': 3.10.1(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)
|
||||
autocomplete.js: 0.37.1
|
||||
clsx: 2.1.1
|
||||
gauge: 3.0.2
|
||||
@@ -25943,6 +26068,20 @@ snapshots:
|
||||
esbuild: 0.28.1
|
||||
lightningcss: 1.32.0
|
||||
|
||||
terser-webpack-plugin@5.6.1(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.15)(webpack@5.107.2(postcss@8.5.15)):
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.31
|
||||
jest-worker: 27.5.1
|
||||
schema-utils: 4.3.3
|
||||
terser: 5.48.0
|
||||
webpack: 5.107.2(postcss@8.5.15)
|
||||
optionalDependencies:
|
||||
clean-css: 5.3.3
|
||||
cssnano: 6.1.2(postcss@8.5.15)
|
||||
esbuild: 0.28.1
|
||||
html-minifier-terser: 7.2.0
|
||||
postcss: 8.5.15
|
||||
|
||||
terser-webpack-plugin@5.6.1(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(html-minifier-terser@7.2.0)(postcss@8.5.15)(webpack@5.107.2(postcss@8.5.15)):
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.31
|
||||
|
||||
@@ -130,10 +130,9 @@ from
|
||||
where
|
||||
"asset"."deletedAt" is null
|
||||
and "asset"."isExternal" = false
|
||||
and "integrity_report"."createdAt" >= $2
|
||||
and "integrity_report"."createdAt" <= $3
|
||||
and "asset"."createdAt" >= $2
|
||||
order by
|
||||
"integrity_report"."createdAt" asc
|
||||
"asset"."createdAt" asc
|
||||
|
||||
-- IntegrityRepository.streamIntegrityReports
|
||||
select
|
||||
|
||||
@@ -160,8 +160,8 @@ export class IntegrityRepository {
|
||||
>;
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.DATE, DummyValue.DATE], stream: true })
|
||||
streamAssetChecksums(startMarker?: Date, endMarker?: Date) {
|
||||
@GenerateSql({ params: [DummyValue.DATE], stream: true })
|
||||
streamAssetChecksums(startMarker?: Date) {
|
||||
return this.db
|
||||
.selectFrom('asset')
|
||||
.where('asset.deletedAt', 'is', null)
|
||||
@@ -178,9 +178,8 @@ export class IntegrityRepository {
|
||||
'integrity_report.id as reportId',
|
||||
])
|
||||
.where('asset.isExternal', '=', sql.lit(false))
|
||||
.$if(startMarker !== undefined, (qb) => qb.where('integrity_report.createdAt', '>=', startMarker!))
|
||||
.$if(endMarker !== undefined, (qb) => qb.where('integrity_report.createdAt', '<=', endMarker!))
|
||||
.orderBy('integrity_report.createdAt', 'asc')
|
||||
.$if(startMarker !== undefined, (qb) => qb.where('asset.createdAt', '>=', startMarker!))
|
||||
.orderBy('asset.createdAt', 'asc')
|
||||
.stream();
|
||||
}
|
||||
|
||||
|
||||
@@ -426,7 +426,7 @@ export class AuthService extends BaseService {
|
||||
throw new BadRequestException('This OAuth account has already been linked to another user.');
|
||||
}
|
||||
|
||||
if (auth.session) {
|
||||
if (auth.session && sid) {
|
||||
await this.sessionRepository.update(auth.session.id, { oauthSid: sid });
|
||||
}
|
||||
|
||||
|
||||
@@ -532,8 +532,7 @@ export class IntegrityService extends BaseService {
|
||||
const { count } = await this.integrityRepository.getAssetCount();
|
||||
const checkpoint = await this.systemMetadataRepository.get(SystemMetadataKey.IntegrityChecksumCheckpoint);
|
||||
|
||||
let startMarker: Date | undefined = checkpoint?.date ? new Date(checkpoint.date) : undefined;
|
||||
let endMarker: Date | undefined;
|
||||
const startMarker = checkpoint?.date ? new Date(checkpoint.date) : undefined;
|
||||
|
||||
const printStats = () => {
|
||||
const averageTime = ((Date.now() - startedAt) / processed).toFixed(2);
|
||||
@@ -546,31 +545,25 @@ export class IntegrityService extends BaseService {
|
||||
|
||||
let lastCreatedAt: Date | undefined;
|
||||
|
||||
finishEarly: do {
|
||||
this.logger.log(
|
||||
`Processing assets in range [${startMarker?.toISOString() ?? 'beginning'}, ${endMarker?.toISOString() ?? 'end'}]`,
|
||||
);
|
||||
this.logger.log(`Processing assets from ${startMarker?.toISOString() ?? 'beginning'}`);
|
||||
|
||||
const assets = this.integrityRepository.streamAssetChecksums(startMarker, endMarker);
|
||||
endMarker = startMarker;
|
||||
startMarker = undefined;
|
||||
const assets = this.integrityRepository.streamAssetChecksums(startMarker);
|
||||
|
||||
for await (const { originalPath, checksum, createdAt, assetId, reportId } of assets) {
|
||||
await this.checkAssetChecksum(originalPath, checksum, assetId, reportId);
|
||||
for await (const { originalPath, checksum, createdAt, assetId, reportId } of assets) {
|
||||
await this.checkAssetChecksum(originalPath, checksum, assetId, reportId);
|
||||
|
||||
processed++;
|
||||
processed++;
|
||||
|
||||
if (processed % 100 === 0) {
|
||||
printStats();
|
||||
}
|
||||
|
||||
if (Date.now() > startedAt + timeLimit || processed > count * percentageLimit) {
|
||||
this.logger.log('Reached stop criteria.');
|
||||
lastCreatedAt = createdAt;
|
||||
break finishEarly;
|
||||
}
|
||||
if (processed % 100 === 0) {
|
||||
printStats();
|
||||
}
|
||||
} while (endMarker);
|
||||
|
||||
if (Date.now() > startedAt + timeLimit || processed > count * percentageLimit) {
|
||||
this.logger.log('Reached stop criteria.');
|
||||
lastCreatedAt = createdAt;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
await this.systemMetadataRepository.set(SystemMetadataKey.IntegrityChecksumCheckpoint, {
|
||||
date: lastCreatedAt?.toISOString(),
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Kysely } from 'kysely';
|
||||
import { DateTime } from 'luxon';
|
||||
import { createHash, randomUUID } from 'node:crypto';
|
||||
import { Readable } from 'node:stream';
|
||||
import { text } from 'node:stream/consumers';
|
||||
import { StorageCore } from 'src/cores/storage.core';
|
||||
import { AssetFileType, IntegrityReport, JobName, JobStatus } from 'src/enum';
|
||||
import { AssetFileType, IntegrityReport, JobName, JobStatus, SystemMetadataKey } from 'src/enum';
|
||||
import { AssetRepository } from 'src/repositories/asset.repository';
|
||||
import { ConfigRepository } from 'src/repositories/config.repository';
|
||||
import { EventRepository } from 'src/repositories/event.repository';
|
||||
@@ -702,6 +703,30 @@ describe(IntegrityService.name, () => {
|
||||
ctx.get(IntegrityRepository).getIntegrityReport({ limit: 100 }, IntegrityReport.ChecksumFail),
|
||||
).resolves.toEqual({ items: [], nextCursor: undefined });
|
||||
});
|
||||
|
||||
it('should continue from checkpoint', async () => {
|
||||
const { sut, ctx } = setup();
|
||||
const job = ctx.getMock(JobRepository);
|
||||
job.queue.mockResolvedValue();
|
||||
|
||||
const { user } = await ctx.newUser();
|
||||
|
||||
await ctx.newAsset({
|
||||
ownerId: user.id,
|
||||
createdAt: DateTime.now().minus({ days: 1 }).toISO(),
|
||||
originalPath: '/foo/bar',
|
||||
});
|
||||
await ctx.newAsset({ ownerId: user.id, originalPath: '/foo/baz' });
|
||||
|
||||
await ctx
|
||||
.get(SystemMetadataRepository)
|
||||
.set(SystemMetadataKey.IntegrityChecksumCheckpoint, { date: DateTime.now().minus({ minutes: 5 }).toISO() });
|
||||
|
||||
await sut.handleChecksumFiles({ refreshOnly: false });
|
||||
|
||||
expect(ctx.getMock(StorageRepository).createPlainReadStream).not.toHaveBeenCalledWith('/foo/bar');
|
||||
expect(ctx.getMock(StorageRepository).createPlainReadStream).toHaveBeenCalledWith('/foo/baz');
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleChecksumRefresh', () => {
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@
|
||||
"@formatjs/icu-messageformat-parser": "^3.0.0",
|
||||
"@immich/justified-layout-wasm": "^0.4.3",
|
||||
"@immich/sdk": "workspace:*",
|
||||
"@immich/ui": "^0.83.0",
|
||||
"@immich/ui": "^0.81.1",
|
||||
"@mapbox/mapbox-gl-rtl-text": "0.4.0",
|
||||
"@mdi/js": "^7.4.47",
|
||||
"@noble/hashes": "^2.2.0",
|
||||
|
||||
@@ -15,12 +15,12 @@ import {
|
||||
fromTimelinePlainDate,
|
||||
fromTimelinePlainDateTime,
|
||||
fromTimelinePlainYearMonth,
|
||||
fromISODateTimeUTCToObject,
|
||||
getTimes,
|
||||
setDifference,
|
||||
type TimelineDateTime,
|
||||
type TimelineYearMonth,
|
||||
getOrderingDate,
|
||||
fromISODateTimeUTC,
|
||||
} from '$lib/utils/timeline-util';
|
||||
import { GroupInsertionCache } from './group-insertion-cache.svelte';
|
||||
import { TimelineDay } from './timeline-day.svelte';
|
||||
@@ -190,7 +190,7 @@ export class TimelineMonth {
|
||||
isVideo: !bucketAssets.isImage[i],
|
||||
livePhotoVideoId: bucketAssets.livePhotoVideoId[i],
|
||||
localDateTime,
|
||||
createdAt: fromISODateTimeUTCToObject(bucketAssets.createdAt[i]),
|
||||
createdAt: fromISODateTimeUTC(bucketAssets.createdAt[i]).toLocal().toObject(),
|
||||
fileCreatedAt,
|
||||
ownerId: bucketAssets.ownerId[i],
|
||||
projectionType: bucketAssets.projectionType[i],
|
||||
|
||||
Reference in New Issue
Block a user