From 493cde9d5535a79d314f791b474d9a93603e8f19 Mon Sep 17 00:00:00 2001
From: Peter Ombodi
Date: Mon, 10 Nov 2025 18:20:51 +0200
Subject: [PATCH] feat: opt-in sync of deletes and restores from web to Android
(beta timeline) (#20473)
* feature(mobile, beta, Android): handle remote asset trash/restore events and rescan media
- Handle move to trash and restore from trash for remote assets on Android
- Trigger MediaScannerConnection to rescan affected media files
* feature(mobile, beta, Android): fix rescan
* fix imports
* fix checking conditions
* refactor naming
* fix line breaks
* refactor code
rollback changes in BackgroundServicePlugin
* refactor code (use separate TrashService)
* refactor code
* parallelize restoreFromTrash calls with Future.wait
format trash.provider.dart
* try to re-format trash.provider.dart
* re-format trash.provider.dart
* rename TrashService to TrashSyncService to avoid duplicated names
revert changes in original trash.provider.dart
* refactor code (minor nitpicks)
* process restoreFromTrash sequentially instead of Future.wait
* group local assets by checksum before moving to trash
delete LocalAssetEntity records when moved to trash
refactor code
* fix format
* use checksum for asset restoration
refactro code
* fix format
* sync trash only for backup-selected assets
* feat(db): add local_trashed_asset table and integrate with restoration flow
- Add new `local_trashed_asset` table to store metadata of trashed assets
- Save trashed asset info into `local_trashed_asset` before deletion
- Use `local_trashed_asset` as source for asset restoration
- Implement file restoration by `mediaId`
* resolve merge conflicts
* fix index creating on migration
* rework trashed assets handling
- add new table trashed_local_asset
- mirror trashed assets data in trashed_local_asset.
- compute checksums for assets trashed out-of-app.
- restore assets present in trashed_local_asset and non-trashed in remote_asset.
- simplify moving-to-trash logic based on remote_asset events.
* resolve merge conflicts
use updated approach for calculating checksums
* use CurrentPlatform instead _platform
fix mocks
* revert redundant changes
* Include trashed items in getMediaChanges
Process trashed items delta during incremental sync
* fix merge conflicts
* fix format
* trashed_local_asset table mirror of local_asset table structure
trashed_local_asset<->local_asset transfer data on move to trash or restore
refactor code
* refactor and format code
* refactor TrashedAsset model
fix missed data transfering
* refactor code
remove unused model
* fix label
* fix merge conflicts
* optimize, refactor code
remove redundant code and checking
getTrashedAssetsForAlbum for iOS
tests for hash trashed assets
* format code
* fix migration
fix tests
* fix generated file
* reuse exist checksums on trash data update
handle restoration errors
fix import
* format code
* sync_stream.service depend on repos
refactor assets restoration
update dependencies in tests
* remove trashed asset model
remove trash_sync.service
refactor DriftTrashedLocalAssetRepository, LocalSyncService
* rework fetching trashed assets data on native side
optimize handling trashed assets in local sync service
refactor code
* update NativeSyncApi on iOS side
remove unused code
* optimize sync trashed assets call in full sync mode
refactor code
* fix format
* remove albumIds from getTrashedAssets params
fix upsert in trashed local asset repo
refactor code
* fix getTrashedAssets params
* fix(trash-sync): clean up NativeSyncApiImplBase and correct applyDelta
* refactor(trash-sync): optimize performance and fix minor issues
* refactor(trash-sync): add missed index
* feat(trash-sync): remove sinceLastCheckpoint param from getTrashedAssets
* fix(trash-sync): fix target table
* fix(trash-sync): remove unused extension
* fix(trash-sync): remove unused code
* fix(trash-sync): refactor code
* fix(trash-sync): reformat file
* fix(trash_sync): refactor code
* fix(trash_sync): improve moving to trash
* refactor(trash_sync): integrate MANAGE_MEDIA permission request into login flow and advanced settings
* refactor(trash_sync): add additional checking for experimental trash sync flag and MANAGE_MEDIA permission.
* refactor(trash_sync): resolve merge conflicts
* refactor(trash_sync): fix format
* resolve merge conflicts
add await for alert dialog
add missed request
* refactor(trash_sync): rework MANAGE_MEDIA info widget
show rationale text in permission request alert dialog
refactor setting getter
* fix(trash_sync): restore missing text values
* fix(trash_sync): format file
* fix(trash_sync): check backup enabled and remove remote asset existence check
* fix(trash_sync): remove checking backup enabled
test(trash_sync): cover sync-stream trash/restore paths and dedupe mocks
* test(trash_sync): cover trash/restore flows for local_sync_service
* chore(e2e): restore test-assets submodule pointer
---------
Co-authored-by: Peter Ombodi
Co-authored-by: Alex
---
i18n/en.json | 6 +
.../immich/BackgroundServicePlugin.kt | 128 +-
.../app/alextran/immich/sync/Messages.g.kt | 16 +
.../alextran/immich/sync/MessagesImpl26.kt | 5 +
.../alextran/immich/sync/MessagesImpl30.kt | 27 +
.../alextran/immich/sync/MessagesImplBase.kt | 12 +
.../drift_schemas/main/drift_schema_v13.json | 1 +
mobile/ios/Runner/Sync/Messages.g.swift | 16 +
mobile/ios/Runner/Sync/MessagesImpl.swift | 132 +-
mobile/lib/constants/constants.dart | 3 +
mobile/lib/domain/services/hash.service.dart | 28 +-
.../domain/services/local_sync.service.dart | 109 +-
.../domain/services/sync_stream.service.dart | 64 +-
.../entities/trashed_local_asset.entity.dart | 40 +
.../trashed_local_asset.entity.drift.dart | 1080 +++
.../repositories/db.repository.dart | 9 +-
.../repositories/db.repository.drift.dart | 20 +-
.../repositories/db.repository.steps.dart | 456 +
.../repositories/local_asset.repository.dart | 31 +
.../repositories/remote_asset.repository.dart | 1 +
.../trashed_local_asset.repository.dart | 252 +
mobile/lib/platform/native_sync_api.g.dart | 28 +
.../infrastructure/asset.provider.dart | 5 +
.../infrastructure/sync.provider.dart | 10 +
.../infrastructure/trash_sync.provider.dart | 12 +
mobile/lib/providers/trash.provider.dart | 2 +-
.../local_files_manager.repository.dart | 27 +-
.../services/local_files_manager.service.dart | 28 +
.../lib/widgets/forms/login/login_form.dart | 56 +
.../widgets/settings/advanced_settings.dart | 49 +-
.../sync_status_and_actions.dart | 43 +
.../settings/settings_action_tile.dart | 73 +
mobile/pigeon/native_sync_api.dart | 7 +
mobile/test/domain/service.mock.dart | 1 +
.../domain/services/hash_service_test.dart | 5 +
.../services/local_sync_service_test.dart | 190 +
.../services/sync_stream_service_test.dart | 194 +-
mobile/test/drift/main/generated/schema.dart | 5 +-
.../test/drift/main/generated/schema_v13.dart | 7765 +++++++++++++++++
mobile/test/fixtures/sync_stream.stub.dart | 63 +
.../test/infrastructure/repository.mock.dart | 3 +
mobile/test/mocks/asset_entity.mock.dart | 4 +
mobile/test/services/hash_service_test.dart | 4 +-
mobile/test/services/upload.service_test.dart | 4 +-
44 files changed, 10866 insertions(+), 148 deletions(-)
create mode 100644 mobile/drift_schemas/main/drift_schema_v13.json
create mode 100644 mobile/lib/infrastructure/entities/trashed_local_asset.entity.dart
create mode 100644 mobile/lib/infrastructure/entities/trashed_local_asset.entity.drift.dart
create mode 100644 mobile/lib/infrastructure/repositories/trashed_local_asset.repository.dart
create mode 100644 mobile/lib/providers/infrastructure/trash_sync.provider.dart
create mode 100644 mobile/lib/widgets/settings/settings_action_tile.dart
create mode 100644 mobile/test/domain/services/local_sync_service_test.dart
create mode 100644 mobile/test/drift/main/generated/schema_v13.dart
create mode 100644 mobile/test/mocks/asset_entity.mock.dart
diff --git a/i18n/en.json b/i18n/en.json
index a83cb07d62..644b74e715 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -475,6 +475,7 @@
"allow_edits": "Allow edits",
"allow_public_user_to_download": "Allow public user to download",
"allow_public_user_to_upload": "Allow public user to upload",
+ "allowed": "Allowed",
"alt_text_qr_code": "QR code image",
"anti_clockwise": "Anti-clockwise",
"api_key": "API Key",
@@ -1314,6 +1315,10 @@
"main_menu": "Main menu",
"make": "Make",
"manage_geolocation": "Manage location",
+ "manage_media_access_rationale": "This permission is required for proper handling of moving assets to the trash and restoring them from it.",
+ "manage_media_access_settings": "Open settings",
+ "manage_media_access_subtitle": "Allow the Immich app to manage and move media files.",
+ "manage_media_access_title": "Media Management Access",
"manage_shared_links": "Manage shared links",
"manage_sharing_with_partners": "Manage sharing with partners",
"manage_the_app_settings": "Manage the app settings",
@@ -1438,6 +1443,7 @@
"no_results_description": "Try a synonym or more general keyword",
"no_shared_albums_message": "Create an album to share photos and videos with people in your network",
"no_uploads_in_progress": "No uploads in progress",
+ "not_allowed": "Not allowed",
"not_available": "N/A",
"not_in_any_album": "Not in any album",
"not_selected": "Not selected",
diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/BackgroundServicePlugin.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/BackgroundServicePlugin.kt
index ae2ec22a71..f62f25558d 100644
--- a/mobile/android/app/src/main/kotlin/app/alextran/immich/BackgroundServicePlugin.kt
+++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/BackgroundServicePlugin.kt
@@ -143,7 +143,7 @@ class BackgroundServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
val mediaUrls = call.argument>("mediaUrls")
if (mediaUrls != null) {
if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) && hasManageMediaPermission()) {
- moveToTrash(mediaUrls, result)
+ moveToTrash(mediaUrls, result)
} else {
result.error("PERMISSION_DENIED", "Media permission required", null)
}
@@ -155,15 +155,23 @@ class BackgroundServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
"restoreFromTrash" -> {
val fileName = call.argument("fileName")
val type = call.argument("type")
+ val mediaId = call.argument("mediaId")
if (fileName != null && type != null) {
if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) && hasManageMediaPermission()) {
restoreFromTrash(fileName, type, result)
} else {
result.error("PERMISSION_DENIED", "Media permission required", null)
}
- } else {
- result.error("INVALID_NAME", "The file name is not specified.", null)
- }
+ } else
+ if (mediaId != null && type != null) {
+ if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) && hasManageMediaPermission()) {
+ restoreFromTrashById(mediaId, type, result)
+ } else {
+ result.error("PERMISSION_DENIED", "Media permission required", null)
+ }
+ } else {
+ result.error("INVALID_PARAMS", "Required params are not specified.", null)
+ }
}
"requestManageMediaPermission" -> {
@@ -175,6 +183,17 @@ class BackgroundServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
}
}
+ "hasManageMediaPermission" -> {
+ if (hasManageMediaPermission()) {
+ Log.i("Manage storage permission", "Permission already granted")
+ result.success(true)
+ } else {
+ result.success(false)
+ }
+ }
+
+ "manageMediaPermission" -> requestManageMediaPermission(result)
+
else -> result.notImplemented()
}
}
@@ -224,25 +243,47 @@ class BackgroundServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
}
@RequiresApi(Build.VERSION_CODES.R)
- private fun toggleTrash(contentUris: List, isTrashed: Boolean, result: Result) {
- val activity = activityBinding?.activity
- val contentResolver = context?.contentResolver
- if (activity == null || contentResolver == null) {
- result.error("TrashError", "Activity or ContentResolver not available", null)
- return
- }
+ private fun restoreFromTrashById(mediaId: String, type: Int, result: Result) {
+ val id = mediaId.toLongOrNull()
+ if (id == null) {
+ result.error("INVALID_ID", "The file id is not a valid number: $mediaId", null)
+ return
+ }
+ if (!isInTrash(id)) {
+ result.error("TrashNotFound", "Item with id=$id not found in trash", null)
+ return
+ }
- try {
- val pendingIntent = MediaStore.createTrashRequest(contentResolver, contentUris, isTrashed)
- pendingResult = result // Store for onActivityResult
- activity.startIntentSenderForResult(
- pendingIntent.intentSender,
- trashRequestCode,
- null, 0, 0, 0
- )
- } catch (e: Exception) {
- Log.e("TrashError", "Error creating or starting trash request", e)
- result.error("TrashError", "Error creating or starting trash request", null)
+ val uri = ContentUris.withAppendedId(contentUriForType(type), id)
+
+ try {
+ Log.i(TAG, "restoreFromTrashById: uri=$uri (type=$type,id=$id)")
+ restoreUris(listOf(uri), result)
+ } catch (e: Exception) {
+ Log.w(TAG, "restoreFromTrashById failed", e)
+ }
+ }
+
+ @RequiresApi(Build.VERSION_CODES.R)
+ private fun toggleTrash(contentUris: List, isTrashed: Boolean, result: Result) {
+ val activity = activityBinding?.activity
+ val contentResolver = context?.contentResolver
+ if (activity == null || contentResolver == null) {
+ result.error("TrashError", "Activity or ContentResolver not available", null)
+ return
+ }
+
+ try {
+ val pendingIntent = MediaStore.createTrashRequest(contentResolver, contentUris, isTrashed)
+ pendingResult = result // Store for onActivityResult
+ activity.startIntentSenderForResult(
+ pendingIntent.intentSender,
+ trashRequestCode,
+ null, 0, 0, 0
+ )
+ } catch (e: Exception) {
+ Log.e("TrashError", "Error creating or starting trash request", e)
+ result.error("TrashError", "Error creating or starting trash request", null)
}
}
@@ -264,14 +305,7 @@ class BackgroundServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
contentResolver.query(queryUri, projection, queryArgs, null)?.use { cursor ->
if (cursor.moveToFirst()) {
val id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID))
- // same order as AssetType from dart
- val contentUri = when (type) {
- 1 -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI
- 2 -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI
- 3 -> MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
- else -> queryUri
- }
- return ContentUris.withAppendedId(contentUri, id)
+ return ContentUris.withAppendedId(contentUriForType(type), id)
}
}
return null
@@ -315,6 +349,40 @@ class BackgroundServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
}
return false
}
+
+ @RequiresApi(Build.VERSION_CODES.R)
+ private fun isInTrash(id: Long): Boolean {
+ val contentResolver = context?.contentResolver ?: return false
+ val filesUri = MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL)
+ val args = Bundle().apply {
+ putString(ContentResolver.QUERY_ARG_SQL_SELECTION, "${MediaStore.Files.FileColumns._ID}=?")
+ putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, arrayOf(id.toString()))
+ putInt(MediaStore.QUERY_ARG_MATCH_TRASHED, MediaStore.MATCH_ONLY)
+ putInt(ContentResolver.QUERY_ARG_LIMIT, 1)
+ }
+ return contentResolver.query(filesUri, arrayOf(MediaStore.Files.FileColumns._ID), args, null)
+ ?.use { it.moveToFirst() } == true
+ }
+
+ @RequiresApi(Build.VERSION_CODES.R)
+ private fun restoreUris(uris: List, result: Result) {
+ if (uris.isEmpty()) {
+ result.error("TrashError", "No URIs to restore", null)
+ return
+ }
+ Log.i(TAG, "restoreUris: count=${uris.size}, first=${uris.first()}")
+ toggleTrash(uris, false, result)
+ }
+
+ @RequiresApi(Build.VERSION_CODES.Q)
+ private fun contentUriForType(type: Int): Uri =
+ when (type) {
+ // same order as AssetType from dart
+ 1 -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI
+ 2 -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI
+ 3 -> MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
+ else -> MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL)
+ }
}
private const val TAG = "BackgroundServicePlugin"
diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/Messages.g.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/Messages.g.kt
index 08ff0e821a..e6cf92f573 100644
--- a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/Messages.g.kt
+++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/Messages.g.kt
@@ -305,6 +305,7 @@ interface NativeSyncApi {
fun getAssetsForAlbum(albumId: String, updatedTimeCond: Long?): List
fun hashAssets(assetIds: List, allowNetworkAccess: Boolean, callback: (Result>) -> Unit)
fun cancelHashing()
+ fun getTrashedAssets(): Map>
companion object {
/** The codec used by NativeSyncApi. */
@@ -483,6 +484,21 @@ interface NativeSyncApi {
channel.setMessageHandler(null)
}
}
+ run {
+ val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getTrashedAssets$separatedMessageChannelSuffix", codec, taskQueue)
+ if (api != null) {
+ channel.setMessageHandler { _, reply ->
+ val wrapped: List = try {
+ listOf(api.getTrashedAssets())
+ } catch (exception: Throwable) {
+ MessagesPigeonUtils.wrapError(exception)
+ }
+ reply.reply(wrapped)
+ }
+ } else {
+ channel.setMessageHandler(null)
+ }
+ }
}
}
}
diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImpl26.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImpl26.kt
index 5deacc30db..6d2c35d78f 100644
--- a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImpl26.kt
+++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImpl26.kt
@@ -21,4 +21,9 @@ class NativeSyncApiImpl26(context: Context) : NativeSyncApiImplBase(context), Na
override fun getMediaChanges(): SyncDelta {
throw IllegalStateException("Method not supported on this Android version.")
}
+
+ override fun getTrashedAssets(): Map> {
+ //Method not supported on this Android version.
+ return emptyMap()
+ }
}
diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImpl30.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImpl30.kt
index 052032e143..ca54c9f823 100644
--- a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImpl30.kt
+++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImpl30.kt
@@ -1,7 +1,9 @@
package app.alextran.immich.sync
+import android.content.ContentResolver
import android.content.Context
import android.os.Build
+import android.os.Bundle
import android.provider.MediaStore
import androidx.annotation.RequiresApi
import androidx.annotation.RequiresExtension
@@ -86,4 +88,29 @@ class NativeSyncApiImpl30(context: Context) : NativeSyncApiImplBase(context), Na
// Unmounted volumes are handled in dart when the album is removed
return SyncDelta(hasChanges, changed, deleted, assetAlbums)
}
+
+ override fun getTrashedAssets(): Map> {
+
+ val result = LinkedHashMap>()
+ val volumes = MediaStore.getExternalVolumeNames(ctx)
+
+ for (volume in volumes) {
+
+ val queryArgs = Bundle().apply {
+ putString(ContentResolver.QUERY_ARG_SQL_SELECTION, MEDIA_SELECTION)
+ putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, MEDIA_SELECTION_ARGS)
+ putInt(MediaStore.QUERY_ARG_MATCH_TRASHED, MediaStore.MATCH_ONLY)
+ }
+
+ getCursor(volume, queryArgs).use { cursor ->
+ getAssets(cursor).forEach { res ->
+ if (res is AssetResult.ValidAsset) {
+ result.getOrPut(res.albumId) { mutableListOf() }.add(res.asset)
+ }
+ }
+ }
+ }
+
+ return result.mapValues { it.value.toList() }
+ }
}
diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImplBase.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImplBase.kt
index ca2781f7b4..b1e9dd7d44 100644
--- a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImplBase.kt
+++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImplBase.kt
@@ -4,6 +4,8 @@ import android.annotation.SuppressLint
import android.content.ContentUris
import android.content.Context
import android.database.Cursor
+import android.net.Uri
+import android.os.Bundle
import android.provider.MediaStore
import android.util.Base64
import androidx.core.database.getStringOrNull
@@ -81,6 +83,16 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin() {
sortOrder,
)
+ protected fun getCursor(
+ volume: String,
+ queryArgs: Bundle
+ ): Cursor? = ctx.contentResolver.query(
+ MediaStore.Files.getContentUri(volume),
+ ASSET_PROJECTION,
+ queryArgs,
+ null
+ )
+
protected fun getAssets(cursor: Cursor?): Sequence {
return sequence {
cursor?.use { c ->
diff --git a/mobile/drift_schemas/main/drift_schema_v13.json b/mobile/drift_schemas/main/drift_schema_v13.json
new file mode 100644
index 0000000000..e527e8d78a
--- /dev/null
+++ b/mobile/drift_schemas/main/drift_schema_v13.json
@@ -0,0 +1 @@
+{"_meta":{"description":"This file contains a serialized version of schema entities for drift.","version":"1.2.0"},"options":{"store_date_time_values_as_text":true},"entities":[{"id":0,"references":[],"type":"table","data":{"name":"user_entity","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"email","getter_name":"email","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"has_profile_image","getter_name":"hasProfileImage","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"has_profile_image\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"has_profile_image\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"profile_changed_at","getter_name":"profileChangedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"avatar_color","getter_name":"avatarColor","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(AvatarColor.values)","dart_type_name":"AvatarColor"}}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":1,"references":[0],"type":"table","data":{"name":"remote_asset_entity","was_declared_in_moor":false,"columns":[{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"type","getter_name":"type","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(AssetType.values)","dart_type_name":"AssetType"}},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"updated_at","getter_name":"updatedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"width","getter_name":"width","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"height","getter_name":"height","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"duration_in_seconds","getter_name":"durationInSeconds","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"checksum","getter_name":"checksum","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"is_favorite","getter_name":"isFavorite","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_favorite\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_favorite\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"owner_id","getter_name":"ownerId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"local_date_time","getter_name":"localDateTime","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"thumb_hash","getter_name":"thumbHash","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"deleted_at","getter_name":"deletedAt","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"live_photo_video_id","getter_name":"livePhotoVideoId","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"visibility","getter_name":"visibility","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(AssetVisibility.values)","dart_type_name":"AssetVisibility"}},{"name":"stack_id","getter_name":"stackId","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"library_id","getter_name":"libraryId","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":2,"references":[0],"type":"table","data":{"name":"stack_entity","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"updated_at","getter_name":"updatedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"owner_id","getter_name":"ownerId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"primary_asset_id","getter_name":"primaryAssetId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":3,"references":[],"type":"table","data":{"name":"local_asset_entity","was_declared_in_moor":false,"columns":[{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"type","getter_name":"type","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(AssetType.values)","dart_type_name":"AssetType"}},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"updated_at","getter_name":"updatedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"width","getter_name":"width","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"height","getter_name":"height","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"duration_in_seconds","getter_name":"durationInSeconds","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"checksum","getter_name":"checksum","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"is_favorite","getter_name":"isFavorite","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_favorite\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_favorite\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"orientation","getter_name":"orientation","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":4,"references":[0,1],"type":"table","data":{"name":"remote_album_entity","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"description","getter_name":"description","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('\\'\\'')","default_client_dart":null,"dsl_features":[]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"updated_at","getter_name":"updatedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"owner_id","getter_name":"ownerId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"thumbnail_asset_id","getter_name":"thumbnailAssetId","moor_type":"string","nullable":true,"customConstraints":null,"defaultConstraints":"REFERENCES remote_asset_entity (id) ON DELETE SET NULL","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES remote_asset_entity (id) ON DELETE SET NULL"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"is_activity_enabled","getter_name":"isActivityEnabled","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_activity_enabled\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_activity_enabled\" IN (0, 1))"},"default_dart":"const CustomExpression('1')","default_client_dart":null,"dsl_features":[]},{"name":"order","getter_name":"order","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(AlbumAssetOrder.values)","dart_type_name":"AlbumAssetOrder"}}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":5,"references":[4],"type":"table","data":{"name":"local_album_entity","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"updated_at","getter_name":"updatedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"backup_selection","getter_name":"backupSelection","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(BackupSelection.values)","dart_type_name":"BackupSelection"}},{"name":"is_ios_shared_album","getter_name":"isIosSharedAlbum","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_ios_shared_album\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_ios_shared_album\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"linked_remote_album_id","getter_name":"linkedRemoteAlbumId","moor_type":"string","nullable":true,"customConstraints":null,"defaultConstraints":"REFERENCES remote_album_entity (id) ON DELETE SET NULL","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES remote_album_entity (id) ON DELETE SET NULL"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"marker","getter_name":"marker_","moor_type":"bool","nullable":true,"customConstraints":null,"defaultConstraints":"CHECK (\"marker\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"marker\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":6,"references":[3,5],"type":"table","data":{"name":"local_album_asset_entity","was_declared_in_moor":false,"columns":[{"name":"asset_id","getter_name":"assetId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES local_asset_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES local_asset_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"album_id","getter_name":"albumId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES local_album_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES local_album_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"marker","getter_name":"marker_","moor_type":"bool","nullable":true,"customConstraints":null,"defaultConstraints":"CHECK (\"marker\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"marker\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["asset_id","album_id"]}},{"id":7,"references":[3],"type":"index","data":{"on":3,"name":"idx_local_asset_checksum","sql":"CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)","unique":false,"columns":[]}},{"id":8,"references":[1],"type":"index","data":{"on":1,"name":"idx_remote_asset_owner_checksum","sql":"CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)","unique":false,"columns":[]}},{"id":9,"references":[1],"type":"index","data":{"on":1,"name":"UQ_remote_assets_owner_checksum","sql":"CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum\nON remote_asset_entity (owner_id, checksum)\nWHERE (library_id IS NULL);\n","unique":true,"columns":[]}},{"id":10,"references":[1],"type":"index","data":{"on":1,"name":"UQ_remote_assets_owner_library_checksum","sql":"CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum\nON remote_asset_entity (owner_id, library_id, checksum)\nWHERE (library_id IS NOT NULL);\n","unique":true,"columns":[]}},{"id":11,"references":[1],"type":"index","data":{"on":1,"name":"idx_remote_asset_checksum","sql":"CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)","unique":false,"columns":[]}},{"id":12,"references":[],"type":"table","data":{"name":"auth_user_entity","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"email","getter_name":"email","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"is_admin","getter_name":"isAdmin","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_admin\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_admin\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"has_profile_image","getter_name":"hasProfileImage","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"has_profile_image\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"has_profile_image\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"profile_changed_at","getter_name":"profileChangedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"avatar_color","getter_name":"avatarColor","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(AvatarColor.values)","dart_type_name":"AvatarColor"}},{"name":"quota_size_in_bytes","getter_name":"quotaSizeInBytes","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"quota_usage_in_bytes","getter_name":"quotaUsageInBytes","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"pin_code","getter_name":"pinCode","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":13,"references":[0],"type":"table","data":{"name":"user_metadata_entity","was_declared_in_moor":false,"columns":[{"name":"user_id","getter_name":"userId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"key","getter_name":"key","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(UserMetadataKey.values)","dart_type_name":"UserMetadataKey"}},{"name":"value","getter_name":"value","moor_type":"blob","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"userMetadataConverter","dart_type_name":"Map"}}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["user_id","key"]}},{"id":14,"references":[0],"type":"table","data":{"name":"partner_entity","was_declared_in_moor":false,"columns":[{"name":"shared_by_id","getter_name":"sharedById","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"shared_with_id","getter_name":"sharedWithId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"in_timeline","getter_name":"inTimeline","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"in_timeline\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"in_timeline\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["shared_by_id","shared_with_id"]}},{"id":15,"references":[1],"type":"table","data":{"name":"remote_exif_entity","was_declared_in_moor":false,"columns":[{"name":"asset_id","getter_name":"assetId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES remote_asset_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES remote_asset_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"city","getter_name":"city","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"state","getter_name":"state","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"country","getter_name":"country","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"date_time_original","getter_name":"dateTimeOriginal","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"description","getter_name":"description","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"height","getter_name":"height","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"width","getter_name":"width","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"exposure_time","getter_name":"exposureTime","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"f_number","getter_name":"fNumber","moor_type":"double","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"file_size","getter_name":"fileSize","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"focal_length","getter_name":"focalLength","moor_type":"double","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"latitude","getter_name":"latitude","moor_type":"double","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"longitude","getter_name":"longitude","moor_type":"double","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"iso","getter_name":"iso","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"make","getter_name":"make","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"model","getter_name":"model","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"lens","getter_name":"lens","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"orientation","getter_name":"orientation","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"time_zone","getter_name":"timeZone","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"rating","getter_name":"rating","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"projection_type","getter_name":"projectionType","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["asset_id"]}},{"id":16,"references":[1,4],"type":"table","data":{"name":"remote_album_asset_entity","was_declared_in_moor":false,"columns":[{"name":"asset_id","getter_name":"assetId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES remote_asset_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES remote_asset_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"album_id","getter_name":"albumId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES remote_album_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES remote_album_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["asset_id","album_id"]}},{"id":17,"references":[4,0],"type":"table","data":{"name":"remote_album_user_entity","was_declared_in_moor":false,"columns":[{"name":"album_id","getter_name":"albumId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES remote_album_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES remote_album_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"user_id","getter_name":"userId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"role","getter_name":"role","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(AlbumUserRole.values)","dart_type_name":"AlbumUserRole"}}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["album_id","user_id"]}},{"id":18,"references":[0],"type":"table","data":{"name":"memory_entity","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"updated_at","getter_name":"updatedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"deleted_at","getter_name":"deletedAt","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"owner_id","getter_name":"ownerId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"type","getter_name":"type","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(MemoryTypeEnum.values)","dart_type_name":"MemoryTypeEnum"}},{"name":"data","getter_name":"data","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"is_saved","getter_name":"isSaved","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_saved\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_saved\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"memory_at","getter_name":"memoryAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"seen_at","getter_name":"seenAt","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"show_at","getter_name":"showAt","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"hide_at","getter_name":"hideAt","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":19,"references":[1,18],"type":"table","data":{"name":"memory_asset_entity","was_declared_in_moor":false,"columns":[{"name":"asset_id","getter_name":"assetId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES remote_asset_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES remote_asset_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"memory_id","getter_name":"memoryId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES memory_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES memory_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["asset_id","memory_id"]}},{"id":20,"references":[0],"type":"table","data":{"name":"person_entity","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"updated_at","getter_name":"updatedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"owner_id","getter_name":"ownerId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"face_asset_id","getter_name":"faceAssetId","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"is_favorite","getter_name":"isFavorite","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_favorite\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_favorite\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"is_hidden","getter_name":"isHidden","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_hidden\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_hidden\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"color","getter_name":"color","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"birth_date","getter_name":"birthDate","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":21,"references":[1,20],"type":"table","data":{"name":"asset_face_entity","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"asset_id","getter_name":"assetId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES remote_asset_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES remote_asset_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"person_id","getter_name":"personId","moor_type":"string","nullable":true,"customConstraints":null,"defaultConstraints":"REFERENCES person_entity (id) ON DELETE SET NULL","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES person_entity (id) ON DELETE SET NULL"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"image_width","getter_name":"imageWidth","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"image_height","getter_name":"imageHeight","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"bounding_box_x1","getter_name":"boundingBoxX1","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"bounding_box_y1","getter_name":"boundingBoxY1","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"bounding_box_x2","getter_name":"boundingBoxX2","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"bounding_box_y2","getter_name":"boundingBoxY2","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"source_type","getter_name":"sourceType","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":22,"references":[],"type":"table","data":{"name":"store_entity","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"string_value","getter_name":"stringValue","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"int_value","getter_name":"intValue","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":23,"references":[],"type":"table","data":{"name":"trashed_local_asset_entity","was_declared_in_moor":false,"columns":[{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"type","getter_name":"type","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(AssetType.values)","dart_type_name":"AssetType"}},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"updated_at","getter_name":"updatedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"width","getter_name":"width","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"height","getter_name":"height","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"duration_in_seconds","getter_name":"durationInSeconds","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"album_id","getter_name":"albumId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"checksum","getter_name":"checksum","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"is_favorite","getter_name":"isFavorite","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_favorite\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_favorite\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"orientation","getter_name":"orientation","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id","album_id"]}},{"id":24,"references":[15],"type":"index","data":{"on":15,"name":"idx_lat_lng","sql":"CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)","unique":false,"columns":[]}},{"id":25,"references":[23],"type":"index","data":{"on":23,"name":"idx_trashed_local_asset_checksum","sql":"CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)","unique":false,"columns":[]}},{"id":26,"references":[23],"type":"index","data":{"on":23,"name":"idx_trashed_local_asset_album","sql":"CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)","unique":false,"columns":[]}}]}
\ No newline at end of file
diff --git a/mobile/ios/Runner/Sync/Messages.g.swift b/mobile/ios/Runner/Sync/Messages.g.swift
index 6bcafb9215..bbe18e7375 100644
--- a/mobile/ios/Runner/Sync/Messages.g.swift
+++ b/mobile/ios/Runner/Sync/Messages.g.swift
@@ -364,6 +364,7 @@ protocol NativeSyncApi {
func getAssetsForAlbum(albumId: String, updatedTimeCond: Int64?) throws -> [PlatformAsset]
func hashAssets(assetIds: [String], allowNetworkAccess: Bool, completion: @escaping (Result<[HashResult], Error>) -> Void)
func cancelHashing() throws
+ func getTrashedAssets() throws -> [String: [PlatformAsset]]
}
/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
@@ -532,5 +533,20 @@ class NativeSyncApiSetup {
} else {
cancelHashingChannel.setMessageHandler(nil)
}
+ let getTrashedAssetsChannel = taskQueue == nil
+ ? FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getTrashedAssets\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
+ : FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getTrashedAssets\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue)
+ if let api = api {
+ getTrashedAssetsChannel.setMessageHandler { _, reply in
+ do {
+ let result = try api.getTrashedAssets()
+ reply(wrapResult(result))
+ } catch {
+ reply(wrapError(error))
+ }
+ }
+ } else {
+ getTrashedAssetsChannel.setMessageHandler(nil)
+ }
}
}
diff --git a/mobile/ios/Runner/Sync/MessagesImpl.swift b/mobile/ios/Runner/Sync/MessagesImpl.swift
index 75981fb7ea..03493f57ca 100644
--- a/mobile/ios/Runner/Sync/MessagesImpl.swift
+++ b/mobile/ios/Runner/Sync/MessagesImpl.swift
@@ -3,15 +3,15 @@ import CryptoKit
struct AssetWrapper: Hashable, Equatable {
let asset: PlatformAsset
-
+
init(with asset: PlatformAsset) {
self.asset = asset
}
-
+
func hash(into hasher: inout Hasher) {
hasher.combine(self.asset.id)
}
-
+
static func == (lhs: AssetWrapper, rhs: AssetWrapper) -> Bool {
return lhs.asset.id == rhs.asset.id
}
@@ -19,31 +19,31 @@ struct AssetWrapper: Hashable, Equatable {
class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin {
static let name = "NativeSyncApi"
-
+
static func register(with registrar: any FlutterPluginRegistrar) {
let instance = NativeSyncApiImpl()
NativeSyncApiSetup.setUp(binaryMessenger: registrar.messenger(), api: instance)
registrar.publish(instance)
}
-
+
func detachFromEngine(for registrar: any FlutterPluginRegistrar) {
super.detachFromEngine()
}
-
+
private let defaults: UserDefaults
private let changeTokenKey = "immich:changeToken"
private let albumTypes: [PHAssetCollectionType] = [.album, .smartAlbum]
private let recoveredAlbumSubType = 1000000219
-
+
private var hashTask: Task?
private static let hashCancelledCode = "HASH_CANCELLED"
private static let hashCancelled = Result<[HashResult], Error>.failure(PigeonError(code: hashCancelledCode, message: "Hashing cancelled", details: nil))
-
-
+
+
init(with defaults: UserDefaults = .standard) {
self.defaults = defaults
}
-
+
@available(iOS 16, *)
private func getChangeToken() -> PHPersistentChangeToken? {
guard let data = defaults.data(forKey: changeTokenKey) else {
@@ -51,7 +51,7 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin {
}
return try? NSKeyedUnarchiver.unarchivedObject(ofClass: PHPersistentChangeToken.self, from: data)
}
-
+
@available(iOS 16, *)
private func saveChangeToken(token: PHPersistentChangeToken) -> Void {
guard let data = try? NSKeyedArchiver.archivedData(withRootObject: token, requiringSecureCoding: true) else {
@@ -59,18 +59,18 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin {
}
defaults.set(data, forKey: changeTokenKey)
}
-
+
func clearSyncCheckpoint() -> Void {
defaults.removeObject(forKey: changeTokenKey)
}
-
+
func checkpointSync() {
guard #available(iOS 16, *) else {
return
}
saveChangeToken(token: PHPhotoLibrary.shared().currentChangeToken)
}
-
+
func shouldFullSync() -> Bool {
guard #available(iOS 16, *),
PHPhotoLibrary.authorizationStatus(for: .readWrite) == .authorized,
@@ -78,36 +78,36 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin {
// When we do not have access to photo library, older iOS version or No token available, fallback to full sync
return true
}
-
+
guard let _ = try? PHPhotoLibrary.shared().fetchPersistentChanges(since: storedToken) else {
// Cannot fetch persistent changes
return true
}
-
+
return false
}
-
+
func getAlbums() throws -> [PlatformAlbum] {
var albums: [PlatformAlbum] = []
-
+
albumTypes.forEach { type in
let collections = PHAssetCollection.fetchAssetCollections(with: type, subtype: .any, options: nil)
for i in 0.. SyncDelta {
guard #available(iOS 16, *) else {
throw PigeonError(code: "UNSUPPORTED_OS", message: "This feature requires iOS 16 or later.", details: nil)
}
-
+
guard PHPhotoLibrary.authorizationStatus(for: .readWrite) == .authorized else {
throw PigeonError(code: "NO_AUTH", message: "No photo library access", details: nil)
}
-
+
guard let storedToken = getChangeToken() else {
// No token exists, definitely need a full sync
print("MediaManager::getMediaChanges: No token found")
throw PigeonError(code: "NO_TOKEN", message: "No stored change token", details: nil)
}
-
+
let currentToken = PHPhotoLibrary.shared().currentChangeToken
if storedToken == currentToken {
return SyncDelta(hasChanges: false, updates: [], deletes: [], assetAlbums: [:])
}
-
+
do {
let changes = try PHPhotoLibrary.shared().fetchPersistentChanges(since: storedToken)
-
+
var updatedAssets: Set = []
var deletedAssets: Set = []
-
+
for change in changes {
guard let details = try? change.changeDetails(for: PHObjectType.asset) else { continue }
-
+
let updated = details.updatedLocalIdentifiers.union(details.insertedLocalIdentifiers)
deletedAssets.formUnion(details.deletedLocalIdentifiers)
-
+
if (updated.isEmpty) { continue }
-
+
let options = PHFetchOptions()
options.includeHiddenAssets = false
let result = PHAsset.fetchAssets(withLocalIdentifiers: Array(updated), options: options)
for i in 0..) -> [String: [String]] {
guard !assets.isEmpty else {
return [:]
}
-
+
var albumAssets: [String: [String]] = [:]
-
+
for type in albumTypes {
let collections = PHAssetCollection.fetchAssetCollections(with: type, subtype: .any, options: nil)
collections.enumerateObjects { (album, _, _) in
@@ -211,13 +211,13 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin {
}
return albumAssets
}
-
+
func getAssetIdsForAlbum(albumId: String) throws -> [String] {
let collections = PHAssetCollection.fetchAssetCollections(withLocalIdentifiers: [albumId], options: nil)
guard let album = collections.firstObject else {
return []
}
-
+
var ids: [String] = []
let options = PHFetchOptions()
options.includeHiddenAssets = false
@@ -227,13 +227,13 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin {
}
return ids
}
-
+
func getAssetsCountSince(albumId: String, timestamp: Int64) throws -> Int64 {
let collections = PHAssetCollection.fetchAssetCollections(withLocalIdentifiers: [albumId], options: nil)
guard let album = collections.firstObject else {
return 0
}
-
+
let date = NSDate(timeIntervalSince1970: TimeInterval(timestamp))
let options = PHFetchOptions()
options.predicate = NSPredicate(format: "creationDate > %@ OR modificationDate > %@", date, date)
@@ -241,32 +241,32 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin {
let assets = getAssetsFromAlbum(in: album, options: options)
return Int64(assets.count)
}
-
+
func getAssetsForAlbum(albumId: String, updatedTimeCond: Int64?) throws -> [PlatformAsset] {
let collections = PHAssetCollection.fetchAssetCollections(withLocalIdentifiers: [albumId], options: nil)
guard let album = collections.firstObject else {
return []
}
-
+
let options = PHFetchOptions()
options.includeHiddenAssets = false
if(updatedTimeCond != nil) {
let date = NSDate(timeIntervalSince1970: TimeInterval(updatedTimeCond!))
options.predicate = NSPredicate(format: "creationDate > %@ OR modificationDate > %@", date, date)
}
-
+
let result = getAssetsFromAlbum(in: album, options: options)
if(result.count == 0) {
return []
}
-
+
var assets: [PlatformAsset] = []
result.enumerateObjects { (asset, _, _) in
assets.append(asset.toPlatformAsset())
}
return assets
}
-
+
func hashAssets(assetIds: [String], allowNetworkAccess: Bool, completion: @escaping (Result<[HashResult], Error>) -> Void) {
if let prevTask = hashTask {
prevTask.cancel()
@@ -284,11 +284,11 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin {
missingAssetIds.remove(asset.localIdentifier)
assets.append(asset)
}
-
+
if Task.isCancelled {
return self?.completeWhenActive(for: completion, with: Self.hashCancelled)
}
-
+
await withTaskGroup(of: HashResult?.self) { taskGroup in
var results = [HashResult]()
results.reserveCapacity(assets.count)
@@ -301,28 +301,28 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin {
return await self.hashAsset(asset, allowNetworkAccess: allowNetworkAccess)
}
}
-
+
for await result in taskGroup {
guard let result = result else {
return self?.completeWhenActive(for: completion, with: Self.hashCancelled)
}
results.append(result)
}
-
+
for missing in missingAssetIds {
results.append(HashResult(assetId: missing, error: "Asset not found in library", hash: nil))
}
-
+
return self?.completeWhenActive(for: completion, with: .success(results))
}
}
}
-
+
func cancelHashing() {
hashTask?.cancel()
hashTask = nil
}
-
+
private func hashAsset(_ asset: PHAsset, allowNetworkAccess: Bool) async -> HashResult? {
class RequestRef {
var id: PHAssetResourceDataRequestID?
@@ -332,21 +332,21 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin {
if Task.isCancelled {
return nil
}
-
+
guard let resource = asset.getResource() else {
return HashResult(assetId: asset.localIdentifier, error: "Cannot get asset resource", hash: nil)
}
-
+
if Task.isCancelled {
return nil
}
-
+
let options = PHAssetResourceRequestOptions()
options.isNetworkAccessAllowed = allowNetworkAccess
-
+
return await withCheckedContinuation { continuation in
var hasher = Insecure.SHA1()
-
+
requestRef.id = PHAssetResourceManager.default().requestData(
for: resource,
options: options,
@@ -377,7 +377,11 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin {
PHAssetResourceManager.default().cancelDataRequest(requestId)
})
}
-
+
+ func getTrashedAssets() throws -> [String: [PlatformAsset]] {
+ throw PigeonError(code: "UNSUPPORTED_OS", message: "This feature not supported on iOS.", details: nil)
+ }
+
private func getAssetsFromAlbum(in album: PHAssetCollection, options: PHFetchOptions) -> PHFetchResult {
// Ensure to actually getting all assets for the Recents album
if (album.assetCollectionSubtype == .smartAlbumUserLibrary) {
diff --git a/mobile/lib/constants/constants.dart b/mobile/lib/constants/constants.dart
index 03acd6a548..8d4636bbe1 100644
--- a/mobile/lib/constants/constants.dart
+++ b/mobile/lib/constants/constants.dart
@@ -58,3 +58,6 @@ const int kPhotoTabIndex = 0;
const int kSearchTabIndex = 1;
const int kAlbumTabIndex = 2;
const int kLibraryTabIndex = 3;
+
+// Workaround for SQLite's variable limit (SQLITE_MAX_VARIABLE_NUMBER = 32766)
+const int kDriftMaxChunk = 32000;
diff --git a/mobile/lib/domain/services/hash.service.dart b/mobile/lib/domain/services/hash.service.dart
index 90f29b8bc1..5e81643fc5 100644
--- a/mobile/lib/domain/services/hash.service.dart
+++ b/mobile/lib/domain/services/hash.service.dart
@@ -2,8 +2,10 @@ import 'package:flutter/services.dart';
import 'package:immich_mobile/constants/constants.dart';
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
+import 'package:immich_mobile/extensions/platform_extensions.dart';
import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
+import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart';
import 'package:immich_mobile/platform/native_sync_api.g.dart';
import 'package:logging/logging.dart';
@@ -13,6 +15,7 @@ class HashService {
final int _batchSize;
final DriftLocalAlbumRepository _localAlbumRepository;
final DriftLocalAssetRepository _localAssetRepository;
+ final DriftTrashedLocalAssetRepository _trashedLocalAssetRepository;
final NativeSyncApi _nativeSyncApi;
final bool Function()? _cancelChecker;
final _log = Logger('HashService');
@@ -20,11 +23,13 @@ class HashService {
HashService({
required DriftLocalAlbumRepository localAlbumRepository,
required DriftLocalAssetRepository localAssetRepository,
+ required DriftTrashedLocalAssetRepository trashedLocalAssetRepository,
required NativeSyncApi nativeSyncApi,
bool Function()? cancelChecker,
int? batchSize,
}) : _localAlbumRepository = localAlbumRepository,
_localAssetRepository = localAssetRepository,
+ _trashedLocalAssetRepository = trashedLocalAssetRepository,
_cancelChecker = cancelChecker,
_nativeSyncApi = nativeSyncApi,
_batchSize = batchSize ?? kBatchHashFileLimit;
@@ -49,6 +54,14 @@ class HashService {
await _hashAssets(album, assetsToHash);
}
}
+ if (CurrentPlatform.isAndroid && localAlbums.isNotEmpty) {
+ final backupAlbumIds = localAlbums.map((e) => e.id);
+ final trashedToHash = await _trashedLocalAssetRepository.getAssetsToHash(backupAlbumIds);
+ if (trashedToHash.isNotEmpty) {
+ final pseudoAlbum = LocalAlbum(id: '-pseudoAlbum', name: 'Trash', updatedAt: DateTime.now());
+ await _hashAssets(pseudoAlbum, trashedToHash, isTrashed: true);
+ }
+ }
} on PlatformException catch (e) {
if (e.code == _kHashCancelledCode) {
_log.warning("Hashing cancelled by platform");
@@ -65,7 +78,7 @@ class HashService {
/// Processes a list of [LocalAsset]s, storing their hash and updating the assets in the DB
/// with hash for those that were successfully hashed. Hashes are looked up in a table
/// [LocalAssetHashEntity] by local id. Only missing entries are newly hashed and added to the DB.
- Future _hashAssets(LocalAlbum album, List assetsToHash) async {
+ Future _hashAssets(LocalAlbum album, List assetsToHash, {bool isTrashed = false}) async {
final toHash = {};
for (final asset in assetsToHash) {
@@ -76,16 +89,16 @@ class HashService {
toHash[asset.id] = asset;
if (toHash.length == _batchSize) {
- await _processBatch(album, toHash);
+ await _processBatch(album, toHash, isTrashed);
toHash.clear();
}
}
- await _processBatch(album, toHash);
+ await _processBatch(album, toHash, isTrashed);
}
/// Processes a batch of assets.
- Future _processBatch(LocalAlbum album, Map toHash) async {
+ Future _processBatch(LocalAlbum album, Map toHash, bool isTrashed) async {
if (toHash.isEmpty) {
return;
}
@@ -120,7 +133,10 @@ class HashService {
}
_log.fine("Hashed ${hashed.length}/${toHash.length} assets");
-
- await _localAssetRepository.updateHashes(hashed);
+ if (isTrashed) {
+ await _trashedLocalAssetRepository.updateHashes(hashed);
+ } else {
+ await _localAssetRepository.updateHashes(hashed);
+ }
}
}
diff --git a/mobile/lib/domain/services/local_sync.service.dart b/mobile/lib/domain/services/local_sync.service.dart
index 94a8a19e73..5cbae9c5a1 100644
--- a/mobile/lib/domain/services/local_sync.service.dart
+++ b/mobile/lib/domain/services/local_sync.service.dart
@@ -4,9 +4,14 @@ import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
+import 'package:immich_mobile/domain/models/store.model.dart';
+import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/extensions/platform_extensions.dart';
import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart';
+import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart';
+import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart';
import 'package:immich_mobile/platform/native_sync_api.g.dart';
+import 'package:immich_mobile/repositories/local_files_manager.repository.dart';
import 'package:immich_mobile/utils/datetime_helpers.dart';
import 'package:immich_mobile/utils/diff.dart';
import 'package:logging/logging.dart';
@@ -14,15 +19,34 @@ import 'package:logging/logging.dart';
class LocalSyncService {
final DriftLocalAlbumRepository _localAlbumRepository;
final NativeSyncApi _nativeSyncApi;
+ final DriftTrashedLocalAssetRepository _trashedLocalAssetRepository;
+ final LocalFilesManagerRepository _localFilesManager;
+ final StorageRepository _storageRepository;
final Logger _log = Logger("DeviceSyncService");
- LocalSyncService({required DriftLocalAlbumRepository localAlbumRepository, required NativeSyncApi nativeSyncApi})
- : _localAlbumRepository = localAlbumRepository,
- _nativeSyncApi = nativeSyncApi;
+ LocalSyncService({
+ required DriftLocalAlbumRepository localAlbumRepository,
+ required DriftTrashedLocalAssetRepository trashedLocalAssetRepository,
+ required LocalFilesManagerRepository localFilesManager,
+ required StorageRepository storageRepository,
+ required NativeSyncApi nativeSyncApi,
+ }) : _localAlbumRepository = localAlbumRepository,
+ _trashedLocalAssetRepository = trashedLocalAssetRepository,
+ _localFilesManager = localFilesManager,
+ _storageRepository = storageRepository,
+ _nativeSyncApi = nativeSyncApi;
Future sync({bool full = false}) async {
final Stopwatch stopwatch = Stopwatch()..start();
try {
+ if (CurrentPlatform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false)) {
+ final hasPermission = await _localFilesManager.hasManageMediaPermission();
+ if (hasPermission) {
+ await _syncTrashedAssets();
+ } else {
+ _log.warning("syncTrashedAssets cannot proceed because MANAGE_MEDIA permission is missing");
+ }
+ }
if (full || await _nativeSyncApi.shouldFullSync()) {
_log.fine("Full sync request from ${full ? "user" : "native"}");
return await fullSync();
@@ -69,7 +93,6 @@ class LocalSyncService {
await updateAlbum(dbAlbum, album);
}
}
-
await _nativeSyncApi.checkpointSync();
} catch (e, s) {
_log.severe("Error performing device sync", e, s);
@@ -273,6 +296,48 @@ class LocalSyncService {
bool _albumsEqual(LocalAlbum a, LocalAlbum b) {
return a.name == b.name && a.assetCount == b.assetCount && a.updatedAt.isAtSameMomentAs(b.updatedAt);
}
+
+ Future _syncTrashedAssets() async {
+ final trashedAssetMap = await _nativeSyncApi.getTrashedAssets();
+ await processTrashedAssets(trashedAssetMap);
+ }
+
+ @visibleForTesting
+ Future processTrashedAssets(Map> trashedAssetMap) async {
+ if (trashedAssetMap.isEmpty) {
+ _log.info("syncTrashedAssets, No trashed assets found");
+ }
+ final trashedAssets = trashedAssetMap.cast>().entries.expand(
+ (entry) => entry.value.cast().toTrashedAssets(entry.key),
+ );
+
+ _log.fine("syncTrashedAssets, trashedAssets: ${trashedAssets.map((e) => e.asset.id)}");
+ await _trashedLocalAssetRepository.processTrashSnapshot(trashedAssets);
+
+ final assetsToRestore = await _trashedLocalAssetRepository.getToRestore();
+ if (assetsToRestore.isNotEmpty) {
+ final restoredIds = await _localFilesManager.restoreAssetsFromTrash(assetsToRestore);
+ await _trashedLocalAssetRepository.applyRestoredAssets(restoredIds);
+ } else {
+ _log.info("syncTrashedAssets, No remote assets found for restoration");
+ }
+
+ final localAssetsToTrash = await _trashedLocalAssetRepository.getToTrash();
+ if (localAssetsToTrash.isNotEmpty) {
+ final mediaUrls = await Future.wait(
+ localAssetsToTrash.values
+ .expand((e) => e)
+ .map((localAsset) => _storageRepository.getAssetEntityForAsset(localAsset).then((e) => e?.getMediaUrl())),
+ );
+ _log.info("Moving to trash ${mediaUrls.join(", ")} assets");
+ final result = await _localFilesManager.moveToTrash(mediaUrls.nonNulls.toList());
+ if (result) {
+ await _trashedLocalAssetRepository.trashLocalAsset(localAssetsToTrash);
+ }
+ } else {
+ _log.info("syncTrashedAssets, No assets found in backup-enabled albums for move to trash");
+ }
+ }
}
extension on Iterable {
@@ -290,20 +355,26 @@ extension on Iterable {
extension on Iterable {
List toLocalAssets() {
- return map(
- (e) => LocalAsset(
- id: e.id,
- name: e.name,
- checksum: null,
- type: AssetType.values.elementAtOrNull(e.type) ?? AssetType.other,
- createdAt: tryFromSecondsSinceEpoch(e.createdAt, isUtc: true) ?? DateTime.timestamp(),
- updatedAt: tryFromSecondsSinceEpoch(e.updatedAt, isUtc: true) ?? DateTime.timestamp(),
- width: e.width,
- height: e.height,
- durationInSeconds: e.durationInSeconds,
- orientation: e.orientation,
- isFavorite: e.isFavorite,
- ),
- ).toList();
+ return map((e) => e.toLocalAsset()).toList();
+ }
+
+ Iterable toTrashedAssets(String albumId) {
+ return map((e) => (albumId: albumId, asset: e.toLocalAsset()));
}
}
+
+extension on PlatformAsset {
+ LocalAsset toLocalAsset() => LocalAsset(
+ id: id,
+ name: name,
+ checksum: null,
+ type: AssetType.values.elementAtOrNull(type) ?? AssetType.other,
+ createdAt: tryFromSecondsSinceEpoch(createdAt, isUtc: true) ?? DateTime.timestamp(),
+ updatedAt: tryFromSecondsSinceEpoch(createdAt, isUtc: true) ?? DateTime.timestamp(),
+ width: width,
+ height: height,
+ durationInSeconds: durationInSeconds,
+ isFavorite: isFavorite,
+ orientation: orientation,
+ );
+}
diff --git a/mobile/lib/domain/services/sync_stream.service.dart b/mobile/lib/domain/services/sync_stream.service.dart
index 9af541e3a4..2ff0f18fcf 100644
--- a/mobile/lib/domain/services/sync_stream.service.dart
+++ b/mobile/lib/domain/services/sync_stream.service.dart
@@ -1,8 +1,15 @@
import 'dart:async';
+import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/domain/models/sync_event.model.dart';
+import 'package:immich_mobile/entities/store.entity.dart';
+import 'package:immich_mobile/extensions/platform_extensions.dart';
+import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
+import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/sync_api.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart';
+import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart';
+import 'package:immich_mobile/repositories/local_files_manager.repository.dart';
import 'package:logging/logging.dart';
import 'package:openapi/api.dart';
@@ -11,14 +18,26 @@ class SyncStreamService {
final SyncApiRepository _syncApiRepository;
final SyncStreamRepository _syncStreamRepository;
+ final DriftLocalAssetRepository _localAssetRepository;
+ final DriftTrashedLocalAssetRepository _trashedLocalAssetRepository;
+ final LocalFilesManagerRepository _localFilesManager;
+ final StorageRepository _storageRepository;
final bool Function()? _cancelChecker;
SyncStreamService({
required SyncApiRepository syncApiRepository,
required SyncStreamRepository syncStreamRepository,
+ required DriftLocalAssetRepository localAssetRepository,
+ required DriftTrashedLocalAssetRepository trashedLocalAssetRepository,
+ required LocalFilesManagerRepository localFilesManager,
+ required StorageRepository storageRepository,
bool Function()? cancelChecker,
}) : _syncApiRepository = syncApiRepository,
_syncStreamRepository = syncStreamRepository,
+ _localAssetRepository = localAssetRepository,
+ _trashedLocalAssetRepository = trashedLocalAssetRepository,
+ _localFilesManager = localFilesManager,
+ _storageRepository = storageRepository,
_cancelChecker = cancelChecker;
bool get isCancelled => _cancelChecker?.call() ?? false;
@@ -83,7 +102,18 @@ class SyncStreamService {
case SyncEntityType.partnerDeleteV1:
return _syncStreamRepository.deletePartnerV1(data.cast());
case SyncEntityType.assetV1:
- return _syncStreamRepository.updateAssetsV1(data.cast());
+ final remoteSyncAssets = data.cast();
+ await _syncStreamRepository.updateAssetsV1(remoteSyncAssets);
+ if (CurrentPlatform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false)) {
+ final hasPermission = await _localFilesManager.hasManageMediaPermission();
+ if (hasPermission) {
+ await _handleRemoteTrashed(remoteSyncAssets.where((e) => e.deletedAt != null).map((e) => e.checksum));
+ await _applyRemoteRestoreToLocal();
+ } else {
+ _logger.warning("sync Trashed Assets cannot proceed because MANAGE_MEDIA permission is missing");
+ }
+ }
+ return;
case SyncEntityType.assetDeleteV1:
return _syncStreamRepository.deleteAssetsV1(data.cast());
case SyncEntityType.assetExifV1:
@@ -212,4 +242,36 @@ class SyncStreamService {
_logger.severe("Error processing AssetUploadReadyV1 websocket batch events", error, stackTrace);
}
}
+
+ Future _handleRemoteTrashed(Iterable checksums) async {
+ if (checksums.isEmpty) {
+ return Future.value();
+ } else {
+ final localAssetsToTrash = await _localAssetRepository.getAssetsFromBackupAlbums(checksums);
+ if (localAssetsToTrash.isNotEmpty) {
+ final mediaUrls = await Future.wait(
+ localAssetsToTrash.values
+ .expand((e) => e)
+ .map((localAsset) => _storageRepository.getAssetEntityForAsset(localAsset).then((e) => e?.getMediaUrl())),
+ );
+ _logger.info("Moving to trash ${mediaUrls.join(", ")} assets");
+ final result = await _localFilesManager.moveToTrash(mediaUrls.nonNulls.toList());
+ if (result) {
+ await _trashedLocalAssetRepository.trashLocalAsset(localAssetsToTrash);
+ }
+ } else {
+ _logger.info("No assets found in backup-enabled albums for assets: $checksums");
+ }
+ }
+ }
+
+ Future _applyRemoteRestoreToLocal() async {
+ final assetsToRestore = await _trashedLocalAssetRepository.getToRestore();
+ if (assetsToRestore.isNotEmpty) {
+ final restoredIds = await _localFilesManager.restoreAssetsFromTrash(assetsToRestore);
+ await _trashedLocalAssetRepository.applyRestoredAssets(restoredIds);
+ } else {
+ _logger.info("No remote assets found for restoration");
+ }
+ }
}
diff --git a/mobile/lib/infrastructure/entities/trashed_local_asset.entity.dart b/mobile/lib/infrastructure/entities/trashed_local_asset.entity.dart
new file mode 100644
index 0000000000..308130b9ea
--- /dev/null
+++ b/mobile/lib/infrastructure/entities/trashed_local_asset.entity.dart
@@ -0,0 +1,40 @@
+import 'package:drift/drift.dart';
+import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
+import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.drift.dart';
+import 'package:immich_mobile/infrastructure/utils/asset.mixin.dart';
+import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
+
+@TableIndex.sql('CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)')
+@TableIndex.sql('CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)')
+class TrashedLocalAssetEntity extends Table with DriftDefaultsMixin, AssetEntityMixin {
+ const TrashedLocalAssetEntity();
+
+ TextColumn get id => text()();
+
+ TextColumn get albumId => text()();
+
+ TextColumn get checksum => text().nullable()();
+
+ BoolColumn get isFavorite => boolean().withDefault(const Constant(false))();
+
+ IntColumn get orientation => integer().withDefault(const Constant(0))();
+
+ @override
+ Set get primaryKey => {id, albumId};
+}
+
+extension TrashedLocalAssetEntityDataDomainExtension on TrashedLocalAssetEntityData {
+ LocalAsset toLocalAsset() => LocalAsset(
+ id: id,
+ name: name,
+ checksum: checksum,
+ type: type,
+ createdAt: createdAt,
+ updatedAt: updatedAt,
+ durationInSeconds: durationInSeconds,
+ isFavorite: isFavorite,
+ height: height,
+ width: width,
+ orientation: orientation,
+ );
+}
diff --git a/mobile/lib/infrastructure/entities/trashed_local_asset.entity.drift.dart b/mobile/lib/infrastructure/entities/trashed_local_asset.entity.drift.dart
new file mode 100644
index 0000000000..aab226c3a2
--- /dev/null
+++ b/mobile/lib/infrastructure/entities/trashed_local_asset.entity.drift.dart
@@ -0,0 +1,1080 @@
+// dart format width=80
+// ignore_for_file: type=lint
+import 'package:drift/drift.dart' as i0;
+import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.drift.dart'
+ as i1;
+import 'package:immich_mobile/domain/models/asset/base_asset.model.dart' as i2;
+import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.dart'
+ as i3;
+import 'package:drift/src/runtime/query_builder/query_builder.dart' as i4;
+
+typedef $$TrashedLocalAssetEntityTableCreateCompanionBuilder =
+ i1.TrashedLocalAssetEntityCompanion Function({
+ required String name,
+ required i2.AssetType type,
+ i0.Value createdAt,
+ i0.Value updatedAt,
+ i0.Value width,
+ i0.Value height,
+ i0.Value durationInSeconds,
+ required String id,
+ required String albumId,
+ i0.Value checksum,
+ i0.Value isFavorite,
+ i0.Value orientation,
+ });
+typedef $$TrashedLocalAssetEntityTableUpdateCompanionBuilder =
+ i1.TrashedLocalAssetEntityCompanion Function({
+ i0.Value name,
+ i0.Value type,
+ i0.Value createdAt,
+ i0.Value updatedAt,
+ i0.Value width,
+ i0.Value height,
+ i0.Value durationInSeconds,
+ i0.Value id,
+ i0.Value albumId,
+ i0.Value checksum,
+ i0.Value isFavorite,
+ i0.Value orientation,
+ });
+
+class $$TrashedLocalAssetEntityTableFilterComposer
+ extends
+ i0.Composer {
+ $$TrashedLocalAssetEntityTableFilterComposer({
+ required super.$db,
+ required super.$table,
+ super.joinBuilder,
+ super.$addJoinBuilderToRootComposer,
+ super.$removeJoinBuilderFromRootComposer,
+ });
+ i0.ColumnFilters get name => $composableBuilder(
+ column: $table.name,
+ builder: (column) => i0.ColumnFilters(column),
+ );
+
+ i0.ColumnWithTypeConverterFilters get type =>
+ $composableBuilder(
+ column: $table.type,
+ builder: (column) => i0.ColumnWithTypeConverterFilters(column),
+ );
+
+ i0.ColumnFilters get createdAt => $composableBuilder(
+ column: $table.createdAt,
+ builder: (column) => i0.ColumnFilters(column),
+ );
+
+ i0.ColumnFilters get updatedAt => $composableBuilder(
+ column: $table.updatedAt,
+ builder: (column) => i0.ColumnFilters(column),
+ );
+
+ i0.ColumnFilters get width => $composableBuilder(
+ column: $table.width,
+ builder: (column) => i0.ColumnFilters(column),
+ );
+
+ i0.ColumnFilters get height => $composableBuilder(
+ column: $table.height,
+ builder: (column) => i0.ColumnFilters(column),
+ );
+
+ i0.ColumnFilters get durationInSeconds => $composableBuilder(
+ column: $table.durationInSeconds,
+ builder: (column) => i0.ColumnFilters(column),
+ );
+
+ i0.ColumnFilters get id => $composableBuilder(
+ column: $table.id,
+ builder: (column) => i0.ColumnFilters(column),
+ );
+
+ i0.ColumnFilters get albumId => $composableBuilder(
+ column: $table.albumId,
+ builder: (column) => i0.ColumnFilters(column),
+ );
+
+ i0.ColumnFilters get checksum => $composableBuilder(
+ column: $table.checksum,
+ builder: (column) => i0.ColumnFilters(column),
+ );
+
+ i0.ColumnFilters get isFavorite => $composableBuilder(
+ column: $table.isFavorite,
+ builder: (column) => i0.ColumnFilters(column),
+ );
+
+ i0.ColumnFilters get orientation => $composableBuilder(
+ column: $table.orientation,
+ builder: (column) => i0.ColumnFilters(column),
+ );
+}
+
+class $$TrashedLocalAssetEntityTableOrderingComposer
+ extends
+ i0.Composer {
+ $$TrashedLocalAssetEntityTableOrderingComposer({
+ required super.$db,
+ required super.$table,
+ super.joinBuilder,
+ super.$addJoinBuilderToRootComposer,
+ super.$removeJoinBuilderFromRootComposer,
+ });
+ i0.ColumnOrderings get name => $composableBuilder(
+ column: $table.name,
+ builder: (column) => i0.ColumnOrderings(column),
+ );
+
+ i0.ColumnOrderings get type => $composableBuilder(
+ column: $table.type,
+ builder: (column) => i0.ColumnOrderings(column),
+ );
+
+ i0.ColumnOrderings get createdAt => $composableBuilder(
+ column: $table.createdAt,
+ builder: (column) => i0.ColumnOrderings(column),
+ );
+
+ i0.ColumnOrderings get updatedAt => $composableBuilder(
+ column: $table.updatedAt,
+ builder: (column) => i0.ColumnOrderings(column),
+ );
+
+ i0.ColumnOrderings get width => $composableBuilder(
+ column: $table.width,
+ builder: (column) => i0.ColumnOrderings(column),
+ );
+
+ i0.ColumnOrderings get height => $composableBuilder(
+ column: $table.height,
+ builder: (column) => i0.ColumnOrderings(column),
+ );
+
+ i0.ColumnOrderings get durationInSeconds => $composableBuilder(
+ column: $table.durationInSeconds,
+ builder: (column) => i0.ColumnOrderings(column),
+ );
+
+ i0.ColumnOrderings get id => $composableBuilder(
+ column: $table.id,
+ builder: (column) => i0.ColumnOrderings(column),
+ );
+
+ i0.ColumnOrderings get albumId => $composableBuilder(
+ column: $table.albumId,
+ builder: (column) => i0.ColumnOrderings(column),
+ );
+
+ i0.ColumnOrderings get checksum => $composableBuilder(
+ column: $table.checksum,
+ builder: (column) => i0.ColumnOrderings(column),
+ );
+
+ i0.ColumnOrderings get isFavorite => $composableBuilder(
+ column: $table.isFavorite,
+ builder: (column) => i0.ColumnOrderings(column),
+ );
+
+ i0.ColumnOrderings get orientation => $composableBuilder(
+ column: $table.orientation,
+ builder: (column) => i0.ColumnOrderings(column),
+ );
+}
+
+class $$TrashedLocalAssetEntityTableAnnotationComposer
+ extends
+ i0.Composer {
+ $$TrashedLocalAssetEntityTableAnnotationComposer({
+ required super.$db,
+ required super.$table,
+ super.joinBuilder,
+ super.$addJoinBuilderToRootComposer,
+ super.$removeJoinBuilderFromRootComposer,
+ });
+ i0.GeneratedColumn get name =>
+ $composableBuilder(column: $table.name, builder: (column) => column);
+
+ i0.GeneratedColumnWithTypeConverter get type =>
+ $composableBuilder(column: $table.type, builder: (column) => column);
+
+ i0.GeneratedColumn get createdAt =>
+ $composableBuilder(column: $table.createdAt, builder: (column) => column);
+
+ i0.GeneratedColumn get updatedAt =>
+ $composableBuilder(column: $table.updatedAt, builder: (column) => column);
+
+ i0.GeneratedColumn get width =>
+ $composableBuilder(column: $table.width, builder: (column) => column);
+
+ i0.GeneratedColumn get height =>
+ $composableBuilder(column: $table.height, builder: (column) => column);
+
+ i0.GeneratedColumn get durationInSeconds => $composableBuilder(
+ column: $table.durationInSeconds,
+ builder: (column) => column,
+ );
+
+ i0.GeneratedColumn get id =>
+ $composableBuilder(column: $table.id, builder: (column) => column);
+
+ i0.GeneratedColumn get albumId =>
+ $composableBuilder(column: $table.albumId, builder: (column) => column);
+
+ i0.GeneratedColumn get checksum =>
+ $composableBuilder(column: $table.checksum, builder: (column) => column);
+
+ i0.GeneratedColumn get isFavorite => $composableBuilder(
+ column: $table.isFavorite,
+ builder: (column) => column,
+ );
+
+ i0.GeneratedColumn get orientation => $composableBuilder(
+ column: $table.orientation,
+ builder: (column) => column,
+ );
+}
+
+class $$TrashedLocalAssetEntityTableTableManager
+ extends
+ i0.RootTableManager<
+ i0.GeneratedDatabase,
+ i1.$TrashedLocalAssetEntityTable,
+ i1.TrashedLocalAssetEntityData,
+ i1.$$TrashedLocalAssetEntityTableFilterComposer,
+ i1.$$TrashedLocalAssetEntityTableOrderingComposer,
+ i1.$$TrashedLocalAssetEntityTableAnnotationComposer,
+ $$TrashedLocalAssetEntityTableCreateCompanionBuilder,
+ $$TrashedLocalAssetEntityTableUpdateCompanionBuilder,
+ (
+ i1.TrashedLocalAssetEntityData,
+ i0.BaseReferences<
+ i0.GeneratedDatabase,
+ i1.$TrashedLocalAssetEntityTable,
+ i1.TrashedLocalAssetEntityData
+ >,
+ ),
+ i1.TrashedLocalAssetEntityData,
+ i0.PrefetchHooks Function()
+ > {
+ $$TrashedLocalAssetEntityTableTableManager(
+ i0.GeneratedDatabase db,
+ i1.$TrashedLocalAssetEntityTable table,
+ ) : super(
+ i0.TableManagerState(
+ db: db,
+ table: table,
+ createFilteringComposer: () =>
+ i1.$$TrashedLocalAssetEntityTableFilterComposer(
+ $db: db,
+ $table: table,
+ ),
+ createOrderingComposer: () =>
+ i1.$$TrashedLocalAssetEntityTableOrderingComposer(
+ $db: db,
+ $table: table,
+ ),
+ createComputedFieldComposer: () =>
+ i1.$$TrashedLocalAssetEntityTableAnnotationComposer(
+ $db: db,
+ $table: table,
+ ),
+ updateCompanionCallback:
+ ({
+ i0.Value name = const i0.Value.absent(),
+ i0.Value type = const i0.Value.absent(),
+ i0.Value createdAt = const i0.Value.absent(),
+ i0.Value updatedAt = const i0.Value.absent(),
+ i0.Value width = const i0.Value.absent(),
+ i0.Value height = const i0.Value.absent(),
+ i0.Value durationInSeconds = const i0.Value.absent(),
+ i0.Value id = const i0.Value.absent(),
+ i0.Value albumId = const i0.Value.absent(),
+ i0.Value checksum = const i0.Value.absent(),
+ i0.Value isFavorite = const i0.Value.absent(),
+ i0.Value orientation = const i0.Value.absent(),
+ }) => i1.TrashedLocalAssetEntityCompanion(
+ name: name,
+ type: type,
+ createdAt: createdAt,
+ updatedAt: updatedAt,
+ width: width,
+ height: height,
+ durationInSeconds: durationInSeconds,
+ id: id,
+ albumId: albumId,
+ checksum: checksum,
+ isFavorite: isFavorite,
+ orientation: orientation,
+ ),
+ createCompanionCallback:
+ ({
+ required String name,
+ required i2.AssetType type,
+ i0.Value createdAt = const i0.Value.absent(),
+ i0.Value updatedAt = const i0.Value.absent(),
+ i0.Value width = const i0.Value.absent(),
+ i0.Value height = const i0.Value.absent(),
+ i0.Value durationInSeconds = const i0.Value.absent(),
+ required String id,
+ required String albumId,
+ i0.Value checksum = const i0.Value.absent(),
+ i0.Value isFavorite = const i0.Value.absent(),
+ i0.Value orientation = const i0.Value.absent(),
+ }) => i1.TrashedLocalAssetEntityCompanion.insert(
+ name: name,
+ type: type,
+ createdAt: createdAt,
+ updatedAt: updatedAt,
+ width: width,
+ height: height,
+ durationInSeconds: durationInSeconds,
+ id: id,
+ albumId: albumId,
+ checksum: checksum,
+ isFavorite: isFavorite,
+ orientation: orientation,
+ ),
+ withReferenceMapper: (p0) => p0
+ .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e)))
+ .toList(),
+ prefetchHooksCallback: null,
+ ),
+ );
+}
+
+typedef $$TrashedLocalAssetEntityTableProcessedTableManager =
+ i0.ProcessedTableManager<
+ i0.GeneratedDatabase,
+ i1.$TrashedLocalAssetEntityTable,
+ i1.TrashedLocalAssetEntityData,
+ i1.$$TrashedLocalAssetEntityTableFilterComposer,
+ i1.$$TrashedLocalAssetEntityTableOrderingComposer,
+ i1.$$TrashedLocalAssetEntityTableAnnotationComposer,
+ $$TrashedLocalAssetEntityTableCreateCompanionBuilder,
+ $$TrashedLocalAssetEntityTableUpdateCompanionBuilder,
+ (
+ i1.TrashedLocalAssetEntityData,
+ i0.BaseReferences<
+ i0.GeneratedDatabase,
+ i1.$TrashedLocalAssetEntityTable,
+ i1.TrashedLocalAssetEntityData
+ >,
+ ),
+ i1.TrashedLocalAssetEntityData,
+ i0.PrefetchHooks Function()
+ >;
+i0.Index get idxTrashedLocalAssetChecksum => i0.Index(
+ 'idx_trashed_local_asset_checksum',
+ 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)',
+);
+
+class $TrashedLocalAssetEntityTable extends i3.TrashedLocalAssetEntity
+ with
+ i0.TableInfo<
+ $TrashedLocalAssetEntityTable,
+ i1.TrashedLocalAssetEntityData
+ > {
+ @override
+ final i0.GeneratedDatabase attachedDatabase;
+ final String? _alias;
+ $TrashedLocalAssetEntityTable(this.attachedDatabase, [this._alias]);
+ static const i0.VerificationMeta _nameMeta = const i0.VerificationMeta(
+ 'name',
+ );
+ @override
+ late final i0.GeneratedColumn name = i0.GeneratedColumn(
+ 'name',
+ aliasedName,
+ false,
+ type: i0.DriftSqlType.string,
+ requiredDuringInsert: true,
+ );
+ @override
+ late final i0.GeneratedColumnWithTypeConverter type =
+ i0.GeneratedColumn(
+ 'type',
+ aliasedName,
+ false,
+ type: i0.DriftSqlType.int,
+ requiredDuringInsert: true,
+ ).withConverter(
+ i1.$TrashedLocalAssetEntityTable.$convertertype,
+ );
+ static const i0.VerificationMeta _createdAtMeta = const i0.VerificationMeta(
+ 'createdAt',
+ );
+ @override
+ late final i0.GeneratedColumn createdAt =
+ i0.GeneratedColumn(
+ 'created_at',
+ aliasedName,
+ false,
+ type: i0.DriftSqlType.dateTime,
+ requiredDuringInsert: false,
+ defaultValue: i4.currentDateAndTime,
+ );
+ static const i0.VerificationMeta _updatedAtMeta = const i0.VerificationMeta(
+ 'updatedAt',
+ );
+ @override
+ late final i0.GeneratedColumn updatedAt =
+ i0.GeneratedColumn(
+ 'updated_at',
+ aliasedName,
+ false,
+ type: i0.DriftSqlType.dateTime,
+ requiredDuringInsert: false,
+ defaultValue: i4.currentDateAndTime,
+ );
+ static const i0.VerificationMeta _widthMeta = const i0.VerificationMeta(
+ 'width',
+ );
+ @override
+ late final i0.GeneratedColumn width = i0.GeneratedColumn(
+ 'width',
+ aliasedName,
+ true,
+ type: i0.DriftSqlType.int,
+ requiredDuringInsert: false,
+ );
+ static const i0.VerificationMeta _heightMeta = const i0.VerificationMeta(
+ 'height',
+ );
+ @override
+ late final i0.GeneratedColumn height = i0.GeneratedColumn(
+ 'height',
+ aliasedName,
+ true,
+ type: i0.DriftSqlType.int,
+ requiredDuringInsert: false,
+ );
+ static const i0.VerificationMeta _durationInSecondsMeta =
+ const i0.VerificationMeta('durationInSeconds');
+ @override
+ late final i0.GeneratedColumn durationInSeconds =
+ i0.GeneratedColumn(
+ 'duration_in_seconds',
+ aliasedName,
+ true,
+ type: i0.DriftSqlType.int,
+ requiredDuringInsert: false,
+ );
+ static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id');
+ @override
+ late final i0.GeneratedColumn id = i0.GeneratedColumn(
+ 'id',
+ aliasedName,
+ false,
+ type: i0.DriftSqlType.string,
+ requiredDuringInsert: true,
+ );
+ static const i0.VerificationMeta _albumIdMeta = const i0.VerificationMeta(
+ 'albumId',
+ );
+ @override
+ late final i0.GeneratedColumn albumId = i0.GeneratedColumn(
+ 'album_id',
+ aliasedName,
+ false,
+ type: i0.DriftSqlType.string,
+ requiredDuringInsert: true,
+ );
+ static const i0.VerificationMeta _checksumMeta = const i0.VerificationMeta(
+ 'checksum',
+ );
+ @override
+ late final i0.GeneratedColumn checksum = i0.GeneratedColumn(
+ 'checksum',
+ aliasedName,
+ true,
+ type: i0.DriftSqlType.string,
+ requiredDuringInsert: false,
+ );
+ static const i0.VerificationMeta _isFavoriteMeta = const i0.VerificationMeta(
+ 'isFavorite',
+ );
+ @override
+ late final i0.GeneratedColumn isFavorite = i0.GeneratedColumn(
+ 'is_favorite',
+ aliasedName,
+ false,
+ type: i0.DriftSqlType.bool,
+ requiredDuringInsert: false,
+ defaultConstraints: i0.GeneratedColumn.constraintIsAlways(
+ 'CHECK ("is_favorite" IN (0, 1))',
+ ),
+ defaultValue: const i4.Constant(false),
+ );
+ static const i0.VerificationMeta _orientationMeta = const i0.VerificationMeta(
+ 'orientation',
+ );
+ @override
+ late final i0.GeneratedColumn orientation = i0.GeneratedColumn(
+ 'orientation',
+ aliasedName,
+ false,
+ type: i0.DriftSqlType.int,
+ requiredDuringInsert: false,
+ defaultValue: const i4.Constant(0),
+ );
+ @override
+ List get $columns => [
+ name,
+ type,
+ createdAt,
+ updatedAt,
+ width,
+ height,
+ durationInSeconds,
+ id,
+ albumId,
+ checksum,
+ isFavorite,
+ orientation,
+ ];
+ @override
+ String get aliasedName => _alias ?? actualTableName;
+ @override
+ String get actualTableName => $name;
+ static const String $name = 'trashed_local_asset_entity';
+ @override
+ i0.VerificationContext validateIntegrity(
+ i0.Insertable instance, {
+ bool isInserting = false,
+ }) {
+ final context = i0.VerificationContext();
+ final data = instance.toColumns(true);
+ if (data.containsKey('name')) {
+ context.handle(
+ _nameMeta,
+ name.isAcceptableOrUnknown(data['name']!, _nameMeta),
+ );
+ } else if (isInserting) {
+ context.missing(_nameMeta);
+ }
+ if (data.containsKey('created_at')) {
+ context.handle(
+ _createdAtMeta,
+ createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta),
+ );
+ }
+ if (data.containsKey('updated_at')) {
+ context.handle(
+ _updatedAtMeta,
+ updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta),
+ );
+ }
+ if (data.containsKey('width')) {
+ context.handle(
+ _widthMeta,
+ width.isAcceptableOrUnknown(data['width']!, _widthMeta),
+ );
+ }
+ if (data.containsKey('height')) {
+ context.handle(
+ _heightMeta,
+ height.isAcceptableOrUnknown(data['height']!, _heightMeta),
+ );
+ }
+ if (data.containsKey('duration_in_seconds')) {
+ context.handle(
+ _durationInSecondsMeta,
+ durationInSeconds.isAcceptableOrUnknown(
+ data['duration_in_seconds']!,
+ _durationInSecondsMeta,
+ ),
+ );
+ }
+ if (data.containsKey('id')) {
+ context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta));
+ } else if (isInserting) {
+ context.missing(_idMeta);
+ }
+ if (data.containsKey('album_id')) {
+ context.handle(
+ _albumIdMeta,
+ albumId.isAcceptableOrUnknown(data['album_id']!, _albumIdMeta),
+ );
+ } else if (isInserting) {
+ context.missing(_albumIdMeta);
+ }
+ if (data.containsKey('checksum')) {
+ context.handle(
+ _checksumMeta,
+ checksum.isAcceptableOrUnknown(data['checksum']!, _checksumMeta),
+ );
+ }
+ if (data.containsKey('is_favorite')) {
+ context.handle(
+ _isFavoriteMeta,
+ isFavorite.isAcceptableOrUnknown(data['is_favorite']!, _isFavoriteMeta),
+ );
+ }
+ if (data.containsKey('orientation')) {
+ context.handle(
+ _orientationMeta,
+ orientation.isAcceptableOrUnknown(
+ data['orientation']!,
+ _orientationMeta,
+ ),
+ );
+ }
+ return context;
+ }
+
+ @override
+ Set get $primaryKey => {id, albumId};
+ @override
+ i1.TrashedLocalAssetEntityData map(
+ Map data, {
+ String? tablePrefix,
+ }) {
+ final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
+ return i1.TrashedLocalAssetEntityData(
+ name: attachedDatabase.typeMapping.read(
+ i0.DriftSqlType.string,
+ data['${effectivePrefix}name'],
+ )!,
+ type: i1.$TrashedLocalAssetEntityTable.$convertertype.fromSql(
+ attachedDatabase.typeMapping.read(
+ i0.DriftSqlType.int,
+ data['${effectivePrefix}type'],
+ )!,
+ ),
+ createdAt: attachedDatabase.typeMapping.read(
+ i0.DriftSqlType.dateTime,
+ data['${effectivePrefix}created_at'],
+ )!,
+ updatedAt: attachedDatabase.typeMapping.read(
+ i0.DriftSqlType.dateTime,
+ data['${effectivePrefix}updated_at'],
+ )!,
+ width: attachedDatabase.typeMapping.read(
+ i0.DriftSqlType.int,
+ data['${effectivePrefix}width'],
+ ),
+ height: attachedDatabase.typeMapping.read(
+ i0.DriftSqlType.int,
+ data['${effectivePrefix}height'],
+ ),
+ durationInSeconds: attachedDatabase.typeMapping.read(
+ i0.DriftSqlType.int,
+ data['${effectivePrefix}duration_in_seconds'],
+ ),
+ id: attachedDatabase.typeMapping.read(
+ i0.DriftSqlType.string,
+ data['${effectivePrefix}id'],
+ )!,
+ albumId: attachedDatabase.typeMapping.read(
+ i0.DriftSqlType.string,
+ data['${effectivePrefix}album_id'],
+ )!,
+ checksum: attachedDatabase.typeMapping.read(
+ i0.DriftSqlType.string,
+ data['${effectivePrefix}checksum'],
+ ),
+ isFavorite: attachedDatabase.typeMapping.read(
+ i0.DriftSqlType.bool,
+ data['${effectivePrefix}is_favorite'],
+ )!,
+ orientation: attachedDatabase.typeMapping.read(
+ i0.DriftSqlType.int,
+ data['${effectivePrefix}orientation'],
+ )!,
+ );
+ }
+
+ @override
+ $TrashedLocalAssetEntityTable createAlias(String alias) {
+ return $TrashedLocalAssetEntityTable(attachedDatabase, alias);
+ }
+
+ static i0.JsonTypeConverter2 $convertertype =
+ const i0.EnumIndexConverter(i2.AssetType.values);
+ @override
+ bool get withoutRowId => true;
+ @override
+ bool get isStrict => true;
+}
+
+class TrashedLocalAssetEntityData extends i0.DataClass
+ implements i0.Insertable {
+ final String name;
+ final i2.AssetType type;
+ final DateTime createdAt;
+ final DateTime updatedAt;
+ final int? width;
+ final int? height;
+ final int? durationInSeconds;
+ final String id;
+ final String albumId;
+ final String? checksum;
+ final bool isFavorite;
+ final int orientation;
+ const TrashedLocalAssetEntityData({
+ required this.name,
+ required this.type,
+ required this.createdAt,
+ required this.updatedAt,
+ this.width,
+ this.height,
+ this.durationInSeconds,
+ required this.id,
+ required this.albumId,
+ this.checksum,
+ required this.isFavorite,
+ required this.orientation,
+ });
+ @override
+ Map toColumns(bool nullToAbsent) {
+ final map = {};
+ map['name'] = i0.Variable(name);
+ {
+ map['type'] = i0.Variable(
+ i1.$TrashedLocalAssetEntityTable.$convertertype.toSql(type),
+ );
+ }
+ map['created_at'] = i0.Variable(createdAt);
+ map['updated_at'] = i0.Variable(updatedAt);
+ if (!nullToAbsent || width != null) {
+ map['width'] = i0.Variable(width);
+ }
+ if (!nullToAbsent || height != null) {
+ map['height'] = i0.Variable(height);
+ }
+ if (!nullToAbsent || durationInSeconds != null) {
+ map['duration_in_seconds'] = i0.Variable(durationInSeconds);
+ }
+ map['id'] = i0.Variable(id);
+ map['album_id'] = i0.Variable(albumId);
+ if (!nullToAbsent || checksum != null) {
+ map['checksum'] = i0.Variable(checksum);
+ }
+ map['is_favorite'] = i0.Variable(isFavorite);
+ map['orientation'] = i0.Variable(orientation);
+ return map;
+ }
+
+ factory TrashedLocalAssetEntityData.fromJson(
+ Map json, {
+ i0.ValueSerializer? serializer,
+ }) {
+ serializer ??= i0.driftRuntimeOptions.defaultSerializer;
+ return TrashedLocalAssetEntityData(
+ name: serializer.fromJson(json['name']),
+ type: i1.$TrashedLocalAssetEntityTable.$convertertype.fromJson(
+ serializer.fromJson(json['type']),
+ ),
+ createdAt: serializer.fromJson(json['createdAt']),
+ updatedAt: serializer.fromJson(json['updatedAt']),
+ width: serializer.fromJson(json['width']),
+ height: serializer.fromJson(json['height']),
+ durationInSeconds: serializer.fromJson(json['durationInSeconds']),
+ id: serializer.fromJson(json['id']),
+ albumId: serializer.fromJson(json['albumId']),
+ checksum: serializer.fromJson(json['checksum']),
+ isFavorite: serializer.fromJson(json['isFavorite']),
+ orientation: serializer.fromJson(json['orientation']),
+ );
+ }
+ @override
+ Map toJson({i0.ValueSerializer? serializer}) {
+ serializer ??= i0.driftRuntimeOptions.defaultSerializer;
+ return {
+ 'name': serializer.toJson(name),
+ 'type': serializer.toJson(
+ i1.$TrashedLocalAssetEntityTable.$convertertype.toJson(type),
+ ),
+ 'createdAt': serializer.toJson(createdAt),
+ 'updatedAt': serializer.toJson(updatedAt),
+ 'width': serializer.toJson(width),
+ 'height': serializer.toJson(height),
+ 'durationInSeconds': serializer.toJson(durationInSeconds),
+ 'id': serializer.toJson(id),
+ 'albumId': serializer.toJson(albumId),
+ 'checksum': serializer.toJson(checksum),
+ 'isFavorite': serializer.toJson(isFavorite),
+ 'orientation': serializer.toJson(orientation),
+ };
+ }
+
+ i1.TrashedLocalAssetEntityData copyWith({
+ String? name,
+ i2.AssetType? type,
+ DateTime? createdAt,
+ DateTime? updatedAt,
+ i0.Value width = const i0.Value.absent(),
+ i0.Value height = const i0.Value.absent(),
+ i0.Value durationInSeconds = const i0.Value.absent(),
+ String? id,
+ String? albumId,
+ i0.Value checksum = const i0.Value.absent(),
+ bool? isFavorite,
+ int? orientation,
+ }) => i1.TrashedLocalAssetEntityData(
+ name: name ?? this.name,
+ type: type ?? this.type,
+ createdAt: createdAt ?? this.createdAt,
+ updatedAt: updatedAt ?? this.updatedAt,
+ width: width.present ? width.value : this.width,
+ height: height.present ? height.value : this.height,
+ durationInSeconds: durationInSeconds.present
+ ? durationInSeconds.value
+ : this.durationInSeconds,
+ id: id ?? this.id,
+ albumId: albumId ?? this.albumId,
+ checksum: checksum.present ? checksum.value : this.checksum,
+ isFavorite: isFavorite ?? this.isFavorite,
+ orientation: orientation ?? this.orientation,
+ );
+ TrashedLocalAssetEntityData copyWithCompanion(
+ i1.TrashedLocalAssetEntityCompanion data,
+ ) {
+ return TrashedLocalAssetEntityData(
+ name: data.name.present ? data.name.value : this.name,
+ type: data.type.present ? data.type.value : this.type,
+ createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt,
+ updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt,
+ width: data.width.present ? data.width.value : this.width,
+ height: data.height.present ? data.height.value : this.height,
+ durationInSeconds: data.durationInSeconds.present
+ ? data.durationInSeconds.value
+ : this.durationInSeconds,
+ id: data.id.present ? data.id.value : this.id,
+ albumId: data.albumId.present ? data.albumId.value : this.albumId,
+ checksum: data.checksum.present ? data.checksum.value : this.checksum,
+ isFavorite: data.isFavorite.present
+ ? data.isFavorite.value
+ : this.isFavorite,
+ orientation: data.orientation.present
+ ? data.orientation.value
+ : this.orientation,
+ );
+ }
+
+ @override
+ String toString() {
+ return (StringBuffer('TrashedLocalAssetEntityData(')
+ ..write('name: $name, ')
+ ..write('type: $type, ')
+ ..write('createdAt: $createdAt, ')
+ ..write('updatedAt: $updatedAt, ')
+ ..write('width: $width, ')
+ ..write('height: $height, ')
+ ..write('durationInSeconds: $durationInSeconds, ')
+ ..write('id: $id, ')
+ ..write('albumId: $albumId, ')
+ ..write('checksum: $checksum, ')
+ ..write('isFavorite: $isFavorite, ')
+ ..write('orientation: $orientation')
+ ..write(')'))
+ .toString();
+ }
+
+ @override
+ int get hashCode => Object.hash(
+ name,
+ type,
+ createdAt,
+ updatedAt,
+ width,
+ height,
+ durationInSeconds,
+ id,
+ albumId,
+ checksum,
+ isFavorite,
+ orientation,
+ );
+ @override
+ bool operator ==(Object other) =>
+ identical(this, other) ||
+ (other is i1.TrashedLocalAssetEntityData &&
+ other.name == this.name &&
+ other.type == this.type &&
+ other.createdAt == this.createdAt &&
+ other.updatedAt == this.updatedAt &&
+ other.width == this.width &&
+ other.height == this.height &&
+ other.durationInSeconds == this.durationInSeconds &&
+ other.id == this.id &&
+ other.albumId == this.albumId &&
+ other.checksum == this.checksum &&
+ other.isFavorite == this.isFavorite &&
+ other.orientation == this.orientation);
+}
+
+class TrashedLocalAssetEntityCompanion
+ extends i0.UpdateCompanion {
+ final i0.Value name;
+ final i0.Value type;
+ final i0.Value createdAt;
+ final i0.Value updatedAt;
+ final i0.Value width;
+ final i0.Value height;
+ final i0.Value durationInSeconds;
+ final i0.Value id;
+ final i0.Value albumId;
+ final i0.Value checksum;
+ final i0.Value isFavorite;
+ final i0.Value orientation;
+ const TrashedLocalAssetEntityCompanion({
+ this.name = const i0.Value.absent(),
+ this.type = const i0.Value.absent(),
+ this.createdAt = const i0.Value.absent(),
+ this.updatedAt = const i0.Value.absent(),
+ this.width = const i0.Value.absent(),
+ this.height = const i0.Value.absent(),
+ this.durationInSeconds = const i0.Value.absent(),
+ this.id = const i0.Value.absent(),
+ this.albumId = const i0.Value.absent(),
+ this.checksum = const i0.Value.absent(),
+ this.isFavorite = const i0.Value.absent(),
+ this.orientation = const i0.Value.absent(),
+ });
+ TrashedLocalAssetEntityCompanion.insert({
+ required String name,
+ required i2.AssetType type,
+ this.createdAt = const i0.Value.absent(),
+ this.updatedAt = const i0.Value.absent(),
+ this.width = const i0.Value.absent(),
+ this.height = const i0.Value.absent(),
+ this.durationInSeconds = const i0.Value.absent(),
+ required String id,
+ required String albumId,
+ this.checksum = const i0.Value.absent(),
+ this.isFavorite = const i0.Value.absent(),
+ this.orientation = const i0.Value.absent(),
+ }) : name = i0.Value(name),
+ type = i0.Value(type),
+ id = i0.Value(id),
+ albumId = i0.Value(albumId);
+ static i0.Insertable custom({
+ i0.Expression? name,
+ i0.Expression? type,
+ i0.Expression? createdAt,
+ i0.Expression? updatedAt,
+ i0.Expression? width,
+ i0.Expression? height,
+ i0.Expression? durationInSeconds,
+ i0.Expression? id,
+ i0.Expression? albumId,
+ i0.Expression? checksum,
+ i0.Expression? isFavorite,
+ i0.Expression? orientation,
+ }) {
+ return i0.RawValuesInsertable({
+ if (name != null) 'name': name,
+ if (type != null) 'type': type,
+ if (createdAt != null) 'created_at': createdAt,
+ if (updatedAt != null) 'updated_at': updatedAt,
+ if (width != null) 'width': width,
+ if (height != null) 'height': height,
+ if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds,
+ if (id != null) 'id': id,
+ if (albumId != null) 'album_id': albumId,
+ if (checksum != null) 'checksum': checksum,
+ if (isFavorite != null) 'is_favorite': isFavorite,
+ if (orientation != null) 'orientation': orientation,
+ });
+ }
+
+ i1.TrashedLocalAssetEntityCompanion copyWith({
+ i0.Value? name,
+ i0.Value? type,
+ i0.Value? createdAt,
+ i0.Value? updatedAt,
+ i0.Value? width,
+ i0.Value? height,
+ i0.Value? durationInSeconds,
+ i0.Value? id,
+ i0.Value