From 2fa33ad9d1e63be91a6a93548c8f592765b82080 Mon Sep 17 00:00:00 2001 From: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com> Date: Wed, 15 Jul 2026 05:39:32 +0530 Subject: [PATCH] refactor: auto trash / restore sync --- .../app/alextran/immich/MainActivity.kt | 4 + .../app/alextran/immich/core/ImmichPlugin.kt | 22 +- .../app/alextran/immich/media/AssetMedia.g.kt | 354 + .../immich/media/AssetMediaApiImpl.kt | 239 + .../immich/sync/MediaTrashDelegate.kt | 133 - .../app/alextran/immich/sync/Messages.g.kt | 38 - .../alextran/immich/sync/MessagesImpl26.kt | 5 - .../alextran/immich/sync/MessagesImpl30.kt | 25 - .../alextran/immich/sync/MessagesImplBase.kt | 37 +- .../drift_schemas/main/drift_schema_v32.json | 3550 ++++++ mobile/ios/Runner.xcodeproj/project.pbxproj | 7 + mobile/ios/Runner/AppDelegate.swift | 2 + .../ios/Runner/AssetMedia/AssetMedia.g.swift | 313 + .../Runner/AssetMedia/AssetMediaApiImpl.swift | 26 + mobile/ios/Runner/Sync/Messages.g.swift | 35 - mobile/ios/Runner/Sync/MessagesImpl.swift | 8 - mobile/lib/constants/enums.dart | 14 + .../lib/domain/models/config/app_config.dart | 10 +- mobile/lib/domain/models/settings_key.dart | 3 + mobile/lib/domain/models/store.model.dart | 2 +- .../services/background_worker.service.dart | 13 +- mobile/lib/domain/services/hash.service.dart | 26 +- .../domain/services/local_sync.service.dart | 71 +- .../domain/services/sync_stream.service.dart | 91 +- .../domain/services/trash_sync.service.dart | 124 + mobile/lib/domain/utils/background_sync.dart | 19 + .../entities/remote_asset.entity.dart | 5 + .../entities/remote_asset.entity.drift.dart | 4 + .../server_deleted_checksum.entity.dart | 14 + .../server_deleted_checksum.entity.drift.dart | 318 + .../entities/trash_sync.entity.dart | 22 + .../entities/trash_sync.entity.drift.dart | 524 + .../entities/trashed_local_asset.entity.dart | 53 - .../trashed_local_asset.entity.drift.dart | 1241 --- .../repositories/backup.repository.dart | 25 +- .../repositories/db.repository.dart | 26 +- .../repositories/db.repository.drift.dart | 62 +- .../repositories/db.repository.steps.dart | 634 ++ .../repositories/settings.repository.dart | 7 + .../repositories/sync_stream.repository.dart | 2 + .../repositories/trash_sync.repository.dart | 280 + .../trashed_local_asset.repository.dart | 311 - .../lib/pages/common/splash_screen.page.dart | 1 + mobile/lib/platform/asset_media_api.g.dart | 228 + mobile/lib/platform/native_sync_api.g.dart | 38 - .../providers/app_life_cycle.provider.dart | 1 + .../infrastructure/asset.provider.dart | 5 - .../infrastructure/platform.provider.dart | 3 + .../infrastructure/sync.provider.dart | 13 +- .../infrastructure/trash_sync.provider.dart | 26 +- .../repositories/asset_media.repository.dart | 30 +- mobile/lib/services/action.service.dart | 13 +- mobile/lib/services/app_settings.service.dart | 1 - mobile/lib/utils/migration.dart | 12 +- .../lib/widgets/forms/login/login_form.dart | 110 +- .../widgets/settings/advanced_settings.dart | 22 +- .../sync_status_and_actions.dart | 45 - mobile/pigeon/asset_media_api.dart | 33 + mobile/pigeon/native_sync_api.dart | 6 - mobile/test/api.mocks.dart | 3 + .../services/local_sync_service_test.dart | 169 +- .../services/sync_stream_service_test.dart | 210 +- mobile/test/drift/main/generated/schema.dart | 4 + .../test/drift/main/generated/schema_v32.dart | 9792 +++++++++++++++++ .../test/infrastructure/repository.mock.dart | 4 +- .../repositories/backup_repository_test.dart | 52 + .../trash_sync_repository_test.dart | 505 + mobile/test/medium/repository_context.dart | 17 +- mobile/test/medium/service_context.dart | 50 +- .../medium/services/partner_service_test.dart | 4 +- .../services/trash_sync_service_test.dart | 249 + mobile/test/repository.mocks.dart | 2 +- mobile/test/services/action.service_test.dart | 25 +- mobile/test/unit/mocks.dart | 2 - .../test/unit/services/hash_service_test.dart | 2 - 75 files changed, 17657 insertions(+), 2719 deletions(-) create mode 100644 mobile/android/app/src/main/kotlin/app/alextran/immich/media/AssetMedia.g.kt create mode 100644 mobile/android/app/src/main/kotlin/app/alextran/immich/media/AssetMediaApiImpl.kt delete mode 100644 mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MediaTrashDelegate.kt create mode 100644 mobile/drift_schemas/main/drift_schema_v32.json create mode 100644 mobile/ios/Runner/AssetMedia/AssetMedia.g.swift create mode 100644 mobile/ios/Runner/AssetMedia/AssetMediaApiImpl.swift create mode 100644 mobile/lib/domain/services/trash_sync.service.dart create mode 100644 mobile/lib/infrastructure/entities/server_deleted_checksum.entity.dart create mode 100644 mobile/lib/infrastructure/entities/server_deleted_checksum.entity.drift.dart create mode 100644 mobile/lib/infrastructure/entities/trash_sync.entity.dart create mode 100644 mobile/lib/infrastructure/entities/trash_sync.entity.drift.dart delete mode 100644 mobile/lib/infrastructure/entities/trashed_local_asset.entity.dart delete mode 100644 mobile/lib/infrastructure/entities/trashed_local_asset.entity.drift.dart create mode 100644 mobile/lib/infrastructure/repositories/trash_sync.repository.dart delete mode 100644 mobile/lib/infrastructure/repositories/trashed_local_asset.repository.dart create mode 100644 mobile/lib/platform/asset_media_api.g.dart create mode 100644 mobile/pigeon/asset_media_api.dart create mode 100644 mobile/test/drift/main/generated/schema_v32.dart create mode 100644 mobile/test/medium/repositories/trash_sync_repository_test.dart create mode 100644 mobile/test/medium/services/trash_sync_service_test.dart diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/MainActivity.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/MainActivity.kt index fc9ab28fa2..ce83a04b7e 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/MainActivity.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/MainActivity.kt @@ -18,6 +18,7 @@ import app.alextran.immich.images.LocalImageApi import app.alextran.immich.images.LocalImagesImpl import app.alextran.immich.images.RemoteImageApi import app.alextran.immich.images.RemoteImagesImpl +import app.alextran.immich.media.AssetMediaApiImpl import app.alextran.immich.permission.PermissionApi import app.alextran.immich.permission.PermissionApiImpl import app.alextran.immich.sync.NativeSyncApi @@ -66,6 +67,7 @@ class MainActivity : FlutterFragmentActivity() { flutterEngine.plugins.add(backgroundEngineLockImpl) flutterEngine.plugins.add(nativeSyncApiImpl) flutterEngine.plugins.add(permissionApiImpl) + flutterEngine.plugins.add(AssetMediaApiImpl(ctx)) } fun cancelPlugins(flutterEngine: FlutterEngine) { @@ -75,6 +77,8 @@ class MainActivity : FlutterFragmentActivity() { nativeApi?.detachFromEngine() val permissionApi = flutterEngine.plugins.get(PermissionApiImpl::class.java) as ImmichPlugin? permissionApi?.detachFromEngine() + val assetMediaApi = flutterEngine.plugins.get(AssetMediaApiImpl::class.java) as ImmichPlugin? + assetMediaApi?.detachFromEngine() } } } diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/core/ImmichPlugin.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/core/ImmichPlugin.kt index 4cc131b058..d26776f35e 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/core/ImmichPlugin.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/core/ImmichPlugin.kt @@ -2,17 +2,30 @@ package app.alextran.immich.core import androidx.annotation.CallSuper import io.flutter.embedding.engine.plugins.FlutterPlugin +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.isActive abstract class ImmichPlugin : FlutterPlugin { - private var detached: Boolean = false; + private var detached: Boolean = false + + private var supervisor = SupervisorJob() + protected var scope = CoroutineScope(supervisor + Dispatchers.Default) + private set @CallSuper override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) { - detached = false; + detached = false + if (!scope.isActive) { + supervisor = SupervisorJob() + scope = CoroutineScope(supervisor + Dispatchers.Default) + } } fun detachFromEngine() { detached = true + supervisor.cancel() } @CallSuper @@ -22,8 +35,9 @@ abstract class ImmichPlugin : FlutterPlugin { fun completeWhenActive(callback: (T) -> Unit, value: T) { if (detached) { - return; + return } - callback(value); + + callback(value) } } diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/media/AssetMedia.g.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/media/AssetMedia.g.kt new file mode 100644 index 0000000000..d897e60a50 --- /dev/null +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/media/AssetMedia.g.kt @@ -0,0 +1,354 @@ +// Autogenerated from Pigeon (v26.3.4), do not edit directly. +// See also: https://pub.dev/packages/pigeon +@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass") + +package app.alextran.immich.media + +import android.util.Log +import io.flutter.plugin.common.BasicMessageChannel +import io.flutter.plugin.common.BinaryMessenger +import io.flutter.plugin.common.EventChannel +import io.flutter.plugin.common.MessageCodec +import io.flutter.plugin.common.StandardMethodCodec +import io.flutter.plugin.common.StandardMessageCodec +import java.io.ByteArrayOutputStream +import java.nio.ByteBuffer +private object AssetMediaPigeonUtils { + + fun wrapResult(result: Any?): List { + return listOf(result) + } + + fun wrapError(exception: Throwable): List { + return if (exception is FlutterError) { + listOf( + exception.code, + exception.message, + exception.details + ) + } else { + listOf( + exception.javaClass.simpleName, + exception.toString(), + "Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception) + ) + } + } + fun doubleEquals(a: Double, b: Double): Boolean { + // Normalize -0.0 to 0.0 and handle NaN equality. + return (if (a == 0.0) 0.0 else a) == (if (b == 0.0) 0.0 else b) || (a.isNaN() && b.isNaN()) + } + + fun floatEquals(a: Float, b: Float): Boolean { + // Normalize -0.0 to 0.0 and handle NaN equality. + return (if (a == 0.0f) 0.0f else a) == (if (b == 0.0f) 0.0f else b) || (a.isNaN() && b.isNaN()) + } + + fun doubleHash(d: Double): Int { + // Normalize -0.0 to 0.0 and handle NaN to ensure consistent hash codes. + val normalized = if (d == 0.0) 0.0 else d + val bits = java.lang.Double.doubleToLongBits(normalized) + return (bits xor (bits ushr 32)).toInt() + } + + fun floatHash(f: Float): Int { + // Normalize -0.0 to 0.0 and handle NaN to ensure consistent hash codes. + val normalized = if (f == 0.0f) 0.0f else f + return java.lang.Float.floatToIntBits(normalized) + } + + fun deepEquals(a: Any?, b: Any?): Boolean { + if (a === b) { + return true + } + if (a == null || b == null) { + return false + } + if (a is ByteArray && b is ByteArray) { + return a.contentEquals(b) + } + if (a is IntArray && b is IntArray) { + return a.contentEquals(b) + } + if (a is LongArray && b is LongArray) { + return a.contentEquals(b) + } + if (a is DoubleArray && b is DoubleArray) { + if (a.size != b.size) return false + for (i in a.indices) { + if (!doubleEquals(a[i], b[i])) return false + } + return true + } + if (a is FloatArray && b is FloatArray) { + if (a.size != b.size) return false + for (i in a.indices) { + if (!floatEquals(a[i], b[i])) return false + } + return true + } + if (a is Array<*> && b is Array<*>) { + if (a.size != b.size) return false + for (i in a.indices) { + if (!deepEquals(a[i], b[i])) return false + } + return true + } + if (a is List<*> && b is List<*>) { + if (a.size != b.size) return false + val iterA = a.iterator() + val iterB = b.iterator() + while (iterA.hasNext() && iterB.hasNext()) { + if (!deepEquals(iterA.next(), iterB.next())) return false + } + return true + } + if (a is Map<*, *> && b is Map<*, *>) { + if (a.size != b.size) return false + for (entry in a) { + val key = entry.key + var found = false + for (bEntry in b) { + if (deepEquals(key, bEntry.key)) { + if (deepEquals(entry.value, bEntry.value)) { + found = true + break + } else { + return false + } + } + } + if (!found) return false + } + return true + } + if (a is Double && b is Double) { + return doubleEquals(a, b) + } + if (a is Float && b is Float) { + return floatEquals(a, b) + } + return a == b + } + + fun deepHash(value: Any?): Int { + return when (value) { + null -> 0 + is ByteArray -> value.contentHashCode() + is IntArray -> value.contentHashCode() + is LongArray -> value.contentHashCode() + is DoubleArray -> { + var result = 1 + for (item in value) { + result = 31 * result + doubleHash(item) + } + result + } + is FloatArray -> { + var result = 1 + for (item in value) { + result = 31 * result + floatHash(item) + } + result + } + is Array<*> -> { + var result = 1 + for (item in value) { + result = 31 * result + deepHash(item) + } + result + } + is List<*> -> { + var result = 1 + for (item in value) { + result = 31 * result + deepHash(item) + } + result + } + is Map<*, *> -> { + var result = 0 + for (entry in value) { + result += ((deepHash(entry.key) * 31) xor deepHash(entry.value)) + } + result + } + is Double -> doubleHash(value) + is Float -> floatHash(value) + else -> value.hashCode() + } + } + +} + +/** + * Error class for passing custom error details to Flutter via a thrown PlatformException. + * @property code The error code. + * @property message The error message. + * @property details The error details. Must be a datatype supported by the api codec. + */ +class FlutterError ( + val code: String, + override val message: String? = null, + val details: Any? = null +) : RuntimeException() + +enum class AssetMediaActionStatus(val raw: Int) { + DONE(0), + ALREADY_IN_STATE(1), + NOT_FOUND(2), + FAILED(3); + + companion object { + fun ofRaw(raw: Int): AssetMediaActionStatus? { + return values().firstOrNull { it.raw == raw } + } + } +} + +/** Generated class from Pigeon that represents data sent in messages. */ +data class AssetMediaActionResult ( + val id: String, + val status: AssetMediaActionStatus +) + { + companion object { + fun fromList(pigeonVar_list: List): AssetMediaActionResult { + val id = pigeonVar_list[0] as String + val status = pigeonVar_list[1] as AssetMediaActionStatus + return AssetMediaActionResult(id, status) + } + } + fun toList(): List { + return listOf( + id, + status, + ) + } + override fun equals(other: Any?): Boolean { + if (other == null || other.javaClass != javaClass) { + return false + } + if (this === other) { + return true + } + val other = other as AssetMediaActionResult + return AssetMediaPigeonUtils.deepEquals(this.id, other.id) && AssetMediaPigeonUtils.deepEquals(this.status, other.status) + } + + override fun hashCode(): Int { + var result = javaClass.hashCode() + result = 31 * result + AssetMediaPigeonUtils.deepHash(this.id) + result = 31 * result + AssetMediaPigeonUtils.deepHash(this.status) + return result + } +} +private open class AssetMediaPigeonCodec : StandardMessageCodec() { + override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { + return when (type) { + 129.toByte() -> { + return (readValue(buffer) as Long?)?.let { + AssetMediaActionStatus.ofRaw(it.toInt()) + } + } + 130.toByte() -> { + return (readValue(buffer) as? List)?.let { + AssetMediaActionResult.fromList(it) + } + } + else -> super.readValueOfType(type, buffer) + } + } + override fun writeValue(stream: ByteArrayOutputStream, value: Any?) { + when (value) { + is AssetMediaActionStatus -> { + stream.write(129) + writeValue(stream, value.raw.toLong()) + } + is AssetMediaActionResult -> { + stream.write(130) + writeValue(stream, value.toList()) + } + else -> super.writeValue(stream, value) + } + } +} + + +/** Generated interface from Pigeon that represents a handler of messages from Flutter. */ +interface AssetMediaApi { + fun trash(ids: List, callback: (Result>) -> Unit) + fun restore(ids: List, callback: (Result>) -> Unit) + fun trashedAmong(ids: List, callback: (Result>) -> Unit) + + companion object { + /** The codec used by AssetMediaApi. */ + val codec: MessageCodec by lazy { + AssetMediaPigeonCodec() + } + /** Sets up an instance of `AssetMediaApi` to handle messages through the `binaryMessenger`. */ + @JvmOverloads + fun setUp(binaryMessenger: BinaryMessenger, api: AssetMediaApi?, messageChannelSuffix: String = "") { + val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.AssetMediaApi.trash$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val idsArg = args[0] as List + api.trash(idsArg) { result: Result> -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(AssetMediaPigeonUtils.wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(AssetMediaPigeonUtils.wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.AssetMediaApi.restore$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val idsArg = args[0] as List + api.restore(idsArg) { result: Result> -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(AssetMediaPigeonUtils.wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(AssetMediaPigeonUtils.wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.AssetMediaApi.trashedAmong$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val idsArg = args[0] as List + api.trashedAmong(idsArg) { result: Result> -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(AssetMediaPigeonUtils.wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(AssetMediaPigeonUtils.wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + } + } +} diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/media/AssetMediaApiImpl.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/media/AssetMediaApiImpl.kt new file mode 100644 index 0000000000..f7430e62fc --- /dev/null +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/media/AssetMediaApiImpl.kt @@ -0,0 +1,239 @@ +package app.alextran.immich.media + +import android.annotation.SuppressLint +import android.app.Activity +import android.content.ContentResolver +import android.content.ContentUris +import android.content.Context +import android.net.Uri +import android.os.Build +import android.os.Bundle +import android.provider.MediaStore +import androidx.activity.ComponentActivity +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.IntentSenderRequest +import androidx.activity.result.contract.ActivityResultContracts +import app.alextran.immich.core.ImmichPlugin +import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.embedding.engine.plugins.activity.ActivityAware +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding +import io.flutter.plugin.common.BinaryMessenger +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ensureActive +import kotlinx.coroutines.launch +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.withContext +import java.util.UUID +import kotlin.coroutines.cancellation.CancellationException +import kotlin.coroutines.resume + +private enum class MediaAction { TRASH, RESTORE } + +private data class MediaItem(val uri: Uri, val isTrashed: Boolean) + +private const val MAX_QUERY_ARGS = 900 + +private const val MAX_CONSENT_URIS = 500 + +@SuppressLint("NewApi", "InlinedApi") +class AssetMediaApiImpl(context: Context) : ImmichPlugin(), AssetMediaApi, ActivityAware { + private val ctx: Context = context.applicationContext + private var binaryMessenger: BinaryMessenger? = null + private var activityBinding: ActivityPluginBinding? = null + + private val supportsMediaRequest: Boolean + get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.R + + override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) { + super.onAttachedToEngine(binding) + binaryMessenger = binding.binaryMessenger + AssetMediaApi.setUp(binding.binaryMessenger, this) + } + + override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { + super.onDetachedFromEngine(binding) + binaryMessenger?.let { AssetMediaApi.setUp(it, null) } + binaryMessenger = null + activityBinding = null + } + + override fun onAttachedToActivity(binding: ActivityPluginBinding) { + activityBinding = binding + } + + override fun onDetachedFromActivityForConfigChanges() { + activityBinding = null + } + + override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { + activityBinding = binding + } + + override fun onDetachedFromActivity() { + activityBinding = null + } + + override fun trash(ids: List, callback: (Result>) -> Unit) = + runAction(ids, MediaAction.TRASH, callback) + + override fun restore( + ids: List, + callback: (Result>) -> Unit + ) = + runAction(ids, MediaAction.RESTORE, callback) + + override fun trashedAmong(ids: List, callback: (Result>) -> Unit) = + respond(callback, "TRASHED_AMONG_ERROR") { + if (!supportsMediaRequest) return@respond emptyList() + val items = queryMediaItems(ids) + ids.filter { items[it]?.isTrashed == true } + } + + private fun runAction( + ids: List, + action: MediaAction, + callback: (Result>) -> Unit, + ) = respond(callback, "MEDIA_ACTION_ERROR") { + if (ids.isEmpty()) return@respond emptyList() + if (!supportsMediaRequest) { + return@respond ids.map { AssetMediaActionResult(it, AssetMediaActionStatus.FAILED) } + } + + val items = queryMediaItems(ids) + + val targets = ids.mapNotNull { id -> + val item = items[id] ?: return@mapNotNull null + val needsAction = when (action) { + MediaAction.TRASH -> !item.isTrashed + MediaAction.RESTORE -> item.isTrashed + } + + if (needsAction) id to item.uri else null + }.toMap() + + val applied = buildMap { + for (batch in targets.entries.chunked(MAX_CONSENT_URIS)) { + val result = if (requestConsent(action, batch.map { it.value })) { + AssetMediaActionStatus.DONE + } else { + AssetMediaActionStatus.FAILED + } + batch.forEach { put(it.key, result) } + } + } + + ids.map { id -> + val status = when (id) { + in applied -> applied.getValue(id) + !in items -> AssetMediaActionStatus.NOT_FOUND + else -> AssetMediaActionStatus.ALREADY_IN_STATE + } + AssetMediaActionResult(id, status) + } + } + + + private fun respond(callback: (Result) -> Unit, errorCode: String, work: suspend () -> T) { + scope.launch { + try { + completeWhenActive(callback, Result.success(work())) + } catch (e: CancellationException) { + throw e + } catch (e: Exception) { + completeWhenActive(callback, Result.failure(FlutterError(errorCode, e.message, null))) + } + } + } + + private suspend fun requestConsent(action: MediaAction, uris: List): Boolean = + withContext(Dispatchers.Main) { + val activity = activityBinding?.activity as? ComponentActivity ?: return@withContext false + val resolver = ctx.contentResolver + val request = when (action) { + MediaAction.TRASH -> MediaStore.createTrashRequest(resolver, uris, true) + MediaAction.RESTORE -> MediaStore.createTrashRequest(resolver, uris, false) + } + + suspendCancellableCoroutine { continuation -> + val key = "immich_asset_media_api_${UUID.randomUUID()}" + var launcher: ActivityResultLauncher? = null + launcher = activity.activityResultRegistry.register( + key, + ActivityResultContracts.StartIntentSenderForResult() + ) { result -> + launcher?.unregister() + if (continuation.isActive) { + continuation.resume(result.resultCode == Activity.RESULT_OK) + } + } + continuation.invokeOnCancellation { launcher.unregister() } + try { + launcher.launch(IntentSenderRequest.Builder(request.intentSender).build()) + } catch (_: Exception) { + launcher.unregister() + if (continuation.isActive) { + continuation.resume(false) + } + } + } + } + + private suspend fun queryMediaItems(ids: List): Map = + withContext(Dispatchers.IO) { + val numeric = ids.filter { it.toLongOrNull() != null } + if (numeric.isEmpty()) { + return@withContext emptyMap() + } + + buildMap { + for (chunk in numeric.chunked(MAX_QUERY_ARGS)) { + ensureActive() + val placeholders = chunk.joinToString(",") { "?" } + val args = Bundle().apply { + putString( + ContentResolver.QUERY_ARG_SQL_SELECTION, + "${MediaStore.Files.FileColumns._ID} IN ($placeholders)" + ) + putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, chunk.toTypedArray()) + putInt(MediaStore.QUERY_ARG_MATCH_TRASHED, MediaStore.MATCH_INCLUDE) + } + + ctx.contentResolver.query( + MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL), + arrayOf( + MediaStore.Files.FileColumns._ID, + MediaStore.Files.FileColumns.MEDIA_TYPE, + MediaStore.Files.FileColumns.IS_TRASHED, + ), + args, + null, + )?.use { cursor -> + val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID) + val typeColumn = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.MEDIA_TYPE) + val trashedColumn = + cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.IS_TRASHED) + while (cursor.moveToNext()) { + val id = cursor.getLong(idColumn) + val uri = mediaContentUri(cursor.getInt(typeColumn), id) ?: continue + put(id.toString(), MediaItem(uri, cursor.getInt(trashedColumn) == 1)) + } + } + } + } + } + + private fun mediaContentUri(mediaType: Int, id: Long): Uri? = when (mediaType) { + MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE -> + ContentUris.withAppendedId( + MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id + ) + + MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO -> + ContentUris.withAppendedId( + MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL), + id + ) + + else -> null + } +} diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MediaTrashDelegate.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MediaTrashDelegate.kt deleted file mode 100644 index fbf4d7e919..0000000000 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MediaTrashDelegate.kt +++ /dev/null @@ -1,133 +0,0 @@ -package app.alextran.immich.sync - -import android.app.Activity -import android.content.ContentResolver -import android.content.ContentUris -import android.content.Context -import android.content.Intent -import android.net.Uri -import android.os.Build -import android.os.Bundle -import android.provider.MediaStore -import androidx.annotation.RequiresApi -import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding -import io.flutter.plugin.common.PluginRegistry - -class MediaTrashDelegate( - context: Context, - private val trashRequestCode: Int = 1002, -) : PluginRegistry.ActivityResultListener { - private val ctx = context.applicationContext - private var activityBinding: ActivityPluginBinding? = null - private var pendingResult: ((Result) -> Unit)? = null - - private fun hasManageMediaPermission(): Boolean { - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - MediaStore.canManageMedia(ctx) - } else { - false - } - } - - fun restoreFromTrashById(mediaId: String, type: Long, callback: (Result) -> Unit) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R || !hasManageMediaPermission()) { - callback(Result.failure(FlutterError("PERMISSION_DENIED", "Media permission required", null))) - return - } - - val id = mediaId.toLongOrNull() - if (id == null) { - callback(Result.failure(FlutterError("INVALID_ID", "The file id is not a valid number: $mediaId", null))) - return - } - - if (!isInTrash(id)) { - callback(Result.failure(FlutterError("TRASH_NOT_FOUND", "Item with id=$id not found in trash", null))) - return - } - - restoreUri(ContentUris.withAppendedId(contentUriForType(type.toInt()), id), callback) - } - - @RequiresApi(Build.VERSION_CODES.R) - private fun restoreUri( - contentUri: Uri, - callback: (Result) -> Unit, - ) { - val activity = activityBinding?.activity - if (activity == null) { - callback(Result.failure(FlutterError("NO_ACTIVITY", "Activity not available", null))) - return - } - - try { - val pendingIntent = MediaStore.createTrashRequest(ctx.contentResolver, listOf(contentUri), false) - pendingResult = callback - activity.startIntentSenderForResult( - pendingIntent.intentSender, - trashRequestCode, - null, - 0, - 0, - 0, - ) - } catch (e: Exception) { - pendingResult = null - callback( - Result.failure( - FlutterError("TRASH_ERROR", "Error creating or starting trash request", e.toString()) - ) - ) - } - } - - @RequiresApi(Build.VERSION_CODES.R) - private fun isInTrash(id: Long): Boolean { - 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 ctx.contentResolver.query(filesUri, arrayOf(MediaStore.Files.FileColumns._ID), args, null) - ?.use { it.moveToFirst() } == true - } - - 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) - } - - fun onAttachedToActivity(binding: ActivityPluginBinding) { - activityBinding = binding - binding.addActivityResultListener(this) - } - - fun onDetachedFromActivity() { - failPending("ACTIVITY_DETACHED", "Activity detached before trash result") - activityBinding?.removeActivityResultListener(this) - activityBinding = null - } - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean { - if (requestCode == trashRequestCode) { - val callback = pendingResult - pendingResult = null - callback?.invoke(Result.success(resultCode == Activity.RESULT_OK)) - return true - } - - return false - } - - private fun failPending(code: String, message: String) { - val callback = pendingResult ?: return - pendingResult = null - callback(Result.failure(FlutterError(code, message, null))) - } -} 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 02f1cb237d..547985d28b 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 @@ -553,8 +553,6 @@ interface NativeSyncApi { fun hashAssets(assetIds: List, allowNetworkAccess: Boolean, callback: (Result>) -> Unit) fun cancelHashing() fun cancelSync() - fun getTrashedAssets(): Map> - fun restoreFromTrashById(mediaId: String, type: Long, callback: (Result) -> Unit) fun getCloudIdForAssetIds(assetIds: List): List companion object { @@ -765,42 +763,6 @@ 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) - } - } - run { - val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.restoreFromTrashById$separatedMessageChannelSuffix", codec) - if (api != null) { - channel.setMessageHandler { message, reply -> - val args = message as List - val mediaIdArg = args[0] as String - val typeArg = args[1] as Long - api.restoreFromTrashById(mediaIdArg, typeArg) { result: Result -> - val error = result.exceptionOrNull() - if (error != null) { - reply.reply(MessagesPigeonUtils.wrapError(error)) - } else { - val data = result.getOrNull() - reply.reply(MessagesPigeonUtils.wrapResult(data)) - } - } - } - } else { - channel.setMessageHandler(null) - } - } run { val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getCloudIdForAssetIds$separatedMessageChannelSuffix", codec, taskQueue) if (api != 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 180e23286c..0babcb293e 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 @@ -29,9 +29,4 @@ class NativeSyncApiImpl26(context: Context) : NativeSyncApiImplBase(context), Na private 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 4785b751c0..4cc8a2be53 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 @@ -99,29 +99,4 @@ 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 18b771a613..71e7a799d0 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,7 +4,6 @@ import android.annotation.SuppressLint import android.content.ContentUris import android.content.Context import android.database.Cursor -import androidx.exifinterface.media.ExifInterface import android.os.Build import android.os.Bundle import android.os.ext.SdkExtensions @@ -17,8 +16,6 @@ import com.bumptech.glide.Glide import com.bumptech.glide.load.ImageHeaderParser import com.bumptech.glide.load.ImageHeaderParserUtils import com.bumptech.glide.load.resource.bitmap.DefaultImageHeaderParser -import io.flutter.embedding.engine.plugins.activity.ActivityAware -import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -41,12 +38,11 @@ sealed class AssetResult { private const val TAG = "NativeSyncApiImplBase" @SuppressLint("InlinedApi") -open class NativeSyncApiImplBase(context: Context) : ImmichPlugin(), ActivityAware { +open class NativeSyncApiImplBase(context: Context) : ImmichPlugin() { private val ctx: Context = context.applicationContext private var hashTask: Job? = null private var syncJob: Job? = null - private val mediaTrashDelegate = MediaTrashDelegate(ctx) companion object { private const val MAX_CONCURRENT_HASH_OPERATIONS = 16 @@ -377,7 +373,11 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin(), ActivityAwa )?.use { cursor -> cursor.count.toLong() } ?: 0L - fun getAssetsForAlbum(albumId: String, updatedTimeCond: Long?, callback: (Result>) -> Unit) { + fun getAssetsForAlbum( + albumId: String, + updatedTimeCond: Long?, + callback: (Result>) -> Unit + ) { runSync(callback) { getAssetsForAlbum(albumId, updatedTimeCond) } } @@ -477,33 +477,16 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin(), ActivityAwa try { completeWhenActive(callback, Result.success(work())) } catch (e: CancellationException) { - completeWhenActive(callback, Result.failure(FlutterError(SYNC_CANCELLED_CODE, "Sync cancelled", null))) + completeWhenActive( + callback, + Result.failure(FlutterError(SYNC_CANCELLED_CODE, "Sync cancelled", null)) + ) } catch (e: Exception) { completeWhenActive(callback, Result.failure(e)) } } } - fun restoreFromTrashById(mediaId: String, type: Long, callback: (Result) -> Unit) { - mediaTrashDelegate.restoreFromTrashById(mediaId, type) { completeWhenActive(callback, it) } - } - - override fun onAttachedToActivity(binding: ActivityPluginBinding) { - mediaTrashDelegate.onAttachedToActivity(binding) - } - - override fun onDetachedFromActivityForConfigChanges() { - mediaTrashDelegate.onDetachedFromActivity() - } - - override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { - mediaTrashDelegate.onAttachedToActivity(binding) - } - - override fun onDetachedFromActivity() { - mediaTrashDelegate.onDetachedFromActivity() - } - // This method is only implemented on iOS; on Android, we do not have a concept of cloud IDs @Suppress("unused", "UNUSED_PARAMETER") fun getCloudIdForAssetIds(assetIds: List): List { diff --git a/mobile/drift_schemas/main/drift_schema_v32.json b/mobile/drift_schemas/main/drift_schema_v32.json new file mode 100644 index 0000000000..489f96c6e5 --- /dev/null +++ b/mobile/drift_schemas/main/drift_schema_v32.json @@ -0,0 +1,3550 @@ +{ + "_meta": { + "description": "This file contains a serialized version of schema entities for drift.", + "version": "1.3.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_ms", + "getter_name": "durationMs", + "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": [ + { + "foreign_key": { + "to": { + "table": "user_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "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": "uploaded_at", + "getter_name": "uploadedAt", + "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": [] + }, + { + "name": "is_edited", + "getter_name": "isEdited", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_edited\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_edited\" 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": [ + "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": [ + { + "foreign_key": { + "to": { + "table": "user_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "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_ms", + "getter_name": "durationMs", + "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": [] + }, + { + "name": "i_cloud_id", + "getter_name": "iCloudId", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "adjustment_time", + "getter_name": "adjustmentTime", + "moor_type": "dateTime", + "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": "playback_style", + "getter_name": "playbackStyle", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AssetPlaybackStyle.values)", + "dart_type_name": "AssetPlaybackStyle" + } + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 4, + "references": [ + 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": "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": [ + { + "foreign_key": { + "to": { + "table": "remote_asset_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "setNull" + } + } + ] + }, + { + "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": [ + { + "foreign_key": { + "to": { + "table": "remote_album_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "setNull" + } + } + ] + }, + { + "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": [ + { + "foreign_key": { + "to": { + "table": "local_asset_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "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": [ + { + "foreign_key": { + "to": { + "table": "local_album_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "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": [ + 6 + ], + "type": "index", + "data": { + "on": 6, + "name": "idx_local_album_asset_album_asset", + "sql": "CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 8, + "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": 9, + "references": [ + 3 + ], + "type": "index", + "data": { + "on": 3, + "name": "idx_local_asset_cloud_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 10, + "references": [ + 3 + ], + "type": "index", + "data": { + "on": 3, + "name": "idx_local_asset_created_at", + "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_created_at ON local_asset_entity (created_at)", + "unique": false, + "columns": [] + } + }, + { + "id": 11, + "references": [ + 2 + ], + "type": "index", + "data": { + "on": 2, + "name": "idx_stack_primary_asset_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 12, + "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": 13, + "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": 14, + "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": 15, + "references": [ + 1 + ], + "type": "index", + "data": { + "on": 1, + "name": "idx_remote_asset_soft_deleted_checksum", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_soft_deleted_checksum\nON remote_asset_entity (checksum)\nWHERE deleted_at IS NOT NULL\n", + "unique": false, + "columns": [] + } + }, + { + "id": 16, + "references": [ + 1 + ], + "type": "index", + "data": { + "on": 1, + "name": "idx_remote_asset_stack_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 17, + "references": [ + 1 + ], + "type": "index", + "data": { + "on": 1, + "name": "idx_remote_asset_owner_visibility_deleted_created", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created\nON remote_asset_entity (owner_id, visibility, deleted_at, created_at DESC)\n", + "unique": false, + "columns": [] + } + }, + { + "id": 18, + "references": [ + 1 + ], + "type": "index", + "data": { + "on": 1, + "name": "idx_remote_asset_uploaded", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_uploaded ON remote_asset_entity (uploaded_at)", + "unique": false, + "columns": [] + } + }, + { + "id": 19, + "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": 20, + "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": [ + { + "foreign_key": { + "to": { + "table": "user_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "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": 21, + "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": [ + { + "foreign_key": { + "to": { + "table": "user_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "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": [ + { + "foreign_key": { + "to": { + "table": "user_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "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": 22, + "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": [ + { + "foreign_key": { + "to": { + "table": "remote_asset_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "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": 23, + "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": [ + { + "foreign_key": { + "to": { + "table": "remote_asset_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "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": [ + { + "foreign_key": { + "to": { + "table": "remote_album_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "asset_id", + "album_id" + ] + } + }, + { + "id": 24, + "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": [ + { + "foreign_key": { + "to": { + "table": "remote_album_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "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": [ + { + "foreign_key": { + "to": { + "table": "user_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "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": 25, + "references": [ + 1 + ], + "type": "table", + "data": { + "name": "remote_asset_cloud_id_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": [ + { + "foreign_key": { + "to": { + "table": "remote_asset_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "cloud_id", + "getter_name": "cloudId", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "created_at", + "getter_name": "createdAt", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "adjustment_time", + "getter_name": "adjustmentTime", + "moor_type": "dateTime", + "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": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "asset_id" + ] + } + }, + { + "id": 26, + "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": [ + { + "foreign_key": { + "to": { + "table": "user_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "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": 27, + "references": [ + 1, + 26 + ], + "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": [ + { + "foreign_key": { + "to": { + "table": "remote_asset_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "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": [ + { + "foreign_key": { + "to": { + "table": "memory_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "asset_id", + "memory_id" + ] + } + }, + { + "id": 28, + "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": [ + { + "foreign_key": { + "to": { + "table": "user_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "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": 29, + "references": [ + 1, + 28 + ], + "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": [ + { + "foreign_key": { + "to": { + "table": "remote_asset_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "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": [ + { + "foreign_key": { + "to": { + "table": "person_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "setNull" + } + } + ] + }, + { + "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": [] + }, + { + "name": "is_visible", + "getter_name": "isVisible", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_visible\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_visible\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('1')", + "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": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 30, + "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": 31, + "references": [], + "type": "table", + "data": { + "name": "trash_sync", + "was_declared_in_moor": false, + "columns": [ + { + "name": "asset_id", + "getter_name": "assetId", + "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": "status", + "getter_name": "status", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(TrashSyncStatus.values)", + "dart_type_name": "TrashSyncStatus" + } + }, + { + "name": "asset_updated_at", + "getter_name": "assetUpdatedAt", + "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": [ + "asset_id" + ] + } + }, + { + "id": 32, + "references": [], + "type": "table", + "data": { + "name": "server_deleted_checksum", + "was_declared_in_moor": false, + "columns": [ + { + "name": "checksum", + "getter_name": "checksum", + "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": [ + "checksum" + ] + } + }, + { + "id": 33, + "references": [ + 1 + ], + "type": "table", + "data": { + "name": "asset_edit_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": [ + { + "foreign_key": { + "to": { + "table": "remote_asset_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "action", + "getter_name": "action", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AssetEditAction.values)", + "dart_type_name": "AssetEditAction" + } + }, + { + "name": "parameters", + "getter_name": "parameters", + "moor_type": "blob", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "editParameterConverter", + "dart_type_name": "Map" + } + }, + { + "name": "sequence", + "getter_name": "sequence", + "moor_type": "int", + "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": 34, + "references": [], + "type": "table", + "data": { + "name": "settings", + "was_declared_in_moor": false, + "columns": [ + { + "name": "key", + "getter_name": "key", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "value", + "getter_name": "value", + "moor_type": "string", + "nullable": true, + "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": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "key" + ] + } + }, + { + "id": 35, + "references": [ + 1 + ], + "type": "table", + "data": { + "name": "asset_ocr_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": [ + { + "foreign_key": { + "to": { + "table": "remote_asset_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "x1", + "getter_name": "x1", + "moor_type": "double", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "y1", + "getter_name": "y1", + "moor_type": "double", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "x2", + "getter_name": "x2", + "moor_type": "double", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "y2", + "getter_name": "y2", + "moor_type": "double", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "x3", + "getter_name": "x3", + "moor_type": "double", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "y3", + "getter_name": "y3", + "moor_type": "double", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "x4", + "getter_name": "x4", + "moor_type": "double", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "y4", + "getter_name": "y4", + "moor_type": "double", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "box_score", + "getter_name": "boxScore", + "moor_type": "double", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "text_score", + "getter_name": "textScore", + "moor_type": "double", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "recognized_text", + "getter_name": "recognizedText", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_visible", + "getter_name": "isVisible", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_visible\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_visible\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('1')", + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 36, + "references": [ + 21 + ], + "type": "index", + "data": { + "on": 21, + "name": "idx_partner_shared_with_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 37, + "references": [ + 22 + ], + "type": "index", + "data": { + "on": 22, + "name": "idx_lat_lng", + "sql": "CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)", + "unique": false, + "columns": [] + } + }, + { + "id": 38, + "references": [ + 22 + ], + "type": "index", + "data": { + "on": 22, + "name": "idx_remote_exif_city", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_exif_city\nON remote_exif_entity (city) WHERE city IS NOT NULL\n", + "unique": false, + "columns": [] + } + }, + { + "id": 39, + "references": [ + 23 + ], + "type": "index", + "data": { + "on": 23, + "name": "idx_remote_album_asset_album_asset", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 40, + "references": [ + 25 + ], + "type": "index", + "data": { + "on": 25, + "name": "idx_remote_asset_cloud_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 41, + "references": [ + 28 + ], + "type": "index", + "data": { + "on": 28, + "name": "idx_person_owner_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 42, + "references": [ + 29 + ], + "type": "index", + "data": { + "on": 29, + "name": "idx_asset_face_person_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 43, + "references": [ + 29 + ], + "type": "index", + "data": { + "on": 29, + "name": "idx_asset_face_asset_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 44, + "references": [ + 29 + ], + "type": "index", + "data": { + "on": 29, + "name": "idx_asset_face_visible_person", + "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_visible_person\nON asset_face_entity (person_id, asset_id)\nWHERE is_visible = 1 AND deleted_at IS NULL\n", + "unique": false, + "columns": [] + } + }, + { + "id": 45, + "references": [ + 31 + ], + "type": "index", + "data": { + "on": 31, + "name": "idx_trash_sync_checksum", + "sql": "CREATE INDEX IF NOT EXISTS idx_trash_sync_checksum ON trash_sync (checksum)", + "unique": false, + "columns": [] + } + }, + { + "id": 46, + "references": [ + 33 + ], + "type": "index", + "data": { + "on": 33, + "name": "idx_asset_edit_asset_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 47, + "references": [ + 35 + ], + "type": "index", + "data": { + "on": 35, + "name": "idx_asset_ocr_asset_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_asset_ocr_asset_id ON asset_ocr_entity (asset_id)", + "unique": false, + "columns": [] + } + } + ], + "fixed_sql": [ + { + "name": "user_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"user_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"email\" TEXT NOT NULL, \"has_profile_image\" INTEGER NOT NULL DEFAULT 0 CHECK (\"has_profile_image\" IN (0, 1)), \"profile_changed_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"avatar_color\" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "remote_asset_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"remote_asset_entity\" (\"name\" TEXT NOT NULL, \"type\" INTEGER NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"width\" INTEGER NULL, \"height\" INTEGER NULL, \"duration_ms\" INTEGER NULL, \"id\" TEXT NOT NULL, \"checksum\" TEXT NOT NULL, \"is_favorite\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_favorite\" IN (0, 1)), \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"local_date_time\" TEXT NULL, \"thumb_hash\" TEXT NULL, \"deleted_at\" TEXT NULL, \"uploaded_at\" TEXT NULL, \"live_photo_video_id\" TEXT NULL, \"visibility\" INTEGER NOT NULL, \"stack_id\" TEXT NULL, \"library_id\" TEXT NULL, \"is_edited\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_edited\" IN (0, 1)), PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "stack_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"stack_entity\" (\"id\" TEXT NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"primary_asset_id\" TEXT NOT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "local_asset_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"local_asset_entity\" (\"name\" TEXT NOT NULL, \"type\" INTEGER NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"width\" INTEGER NULL, \"height\" INTEGER NULL, \"duration_ms\" INTEGER NULL, \"id\" TEXT NOT NULL, \"checksum\" TEXT NULL, \"is_favorite\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_favorite\" IN (0, 1)), \"orientation\" INTEGER NOT NULL DEFAULT 0, \"i_cloud_id\" TEXT NULL, \"adjustment_time\" TEXT NULL, \"latitude\" REAL NULL, \"longitude\" REAL NULL, \"playback_style\" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "remote_album_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"remote_album_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"description\" TEXT NOT NULL DEFAULT '', \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"thumbnail_asset_id\" TEXT NULL REFERENCES remote_asset_entity (id) ON DELETE SET NULL, \"is_activity_enabled\" INTEGER NOT NULL DEFAULT 1 CHECK (\"is_activity_enabled\" IN (0, 1)), \"order\" INTEGER NOT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "local_album_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"local_album_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"backup_selection\" INTEGER NOT NULL, \"is_ios_shared_album\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_ios_shared_album\" IN (0, 1)), \"linked_remote_album_id\" TEXT NULL REFERENCES remote_album_entity (id) ON DELETE SET NULL, \"marker\" INTEGER NULL CHECK (\"marker\" IN (0, 1)), PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "local_album_asset_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"local_album_asset_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES local_asset_entity (id) ON DELETE CASCADE, \"album_id\" TEXT NOT NULL REFERENCES local_album_entity (id) ON DELETE CASCADE, \"marker\" INTEGER NULL CHECK (\"marker\" IN (0, 1)), PRIMARY KEY (\"asset_id\", \"album_id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "idx_local_album_asset_album_asset", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)" + } + ] + }, + { + "name": "idx_local_asset_checksum", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)" + } + ] + }, + { + "name": "idx_local_asset_cloud_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)" + } + ] + }, + { + "name": "idx_local_asset_created_at", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_created_at ON local_asset_entity (created_at)" + } + ] + }, + { + "name": "idx_stack_primary_asset_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)" + } + ] + }, + { + "name": "UQ_remote_assets_owner_checksum", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)" + } + ] + }, + { + "name": "UQ_remote_assets_owner_library_checksum", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)" + } + ] + }, + { + "name": "idx_remote_asset_checksum", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)" + } + ] + }, + { + "name": "idx_remote_asset_soft_deleted_checksum", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_soft_deleted_checksum ON remote_asset_entity (checksum) WHERE deleted_at IS NOT NULL" + } + ] + }, + { + "name": "idx_remote_asset_stack_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)" + } + ] + }, + { + "name": "idx_remote_asset_owner_visibility_deleted_created", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created ON remote_asset_entity (owner_id, visibility, deleted_at, created_at DESC)" + } + ] + }, + { + "name": "idx_remote_asset_uploaded", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_uploaded ON remote_asset_entity (uploaded_at)" + } + ] + }, + { + "name": "auth_user_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"auth_user_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"email\" TEXT NOT NULL, \"is_admin\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_admin\" IN (0, 1)), \"has_profile_image\" INTEGER NOT NULL DEFAULT 0 CHECK (\"has_profile_image\" IN (0, 1)), \"profile_changed_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"avatar_color\" INTEGER NOT NULL, \"quota_size_in_bytes\" INTEGER NOT NULL DEFAULT 0, \"quota_usage_in_bytes\" INTEGER NOT NULL DEFAULT 0, \"pin_code\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "user_metadata_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"user_metadata_entity\" (\"user_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"key\" INTEGER NOT NULL, \"value\" BLOB NOT NULL, PRIMARY KEY (\"user_id\", \"key\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "partner_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"partner_entity\" (\"shared_by_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"shared_with_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"in_timeline\" INTEGER NOT NULL DEFAULT 0 CHECK (\"in_timeline\" IN (0, 1)), PRIMARY KEY (\"shared_by_id\", \"shared_with_id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "remote_exif_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"remote_exif_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"city\" TEXT NULL, \"state\" TEXT NULL, \"country\" TEXT NULL, \"date_time_original\" TEXT NULL, \"description\" TEXT NULL, \"height\" INTEGER NULL, \"width\" INTEGER NULL, \"exposure_time\" TEXT NULL, \"f_number\" REAL NULL, \"file_size\" INTEGER NULL, \"focal_length\" REAL NULL, \"latitude\" REAL NULL, \"longitude\" REAL NULL, \"iso\" INTEGER NULL, \"make\" TEXT NULL, \"model\" TEXT NULL, \"lens\" TEXT NULL, \"orientation\" TEXT NULL, \"time_zone\" TEXT NULL, \"rating\" INTEGER NULL, \"projection_type\" TEXT NULL, PRIMARY KEY (\"asset_id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "remote_album_asset_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"remote_album_asset_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"album_id\" TEXT NOT NULL REFERENCES remote_album_entity (id) ON DELETE CASCADE, PRIMARY KEY (\"asset_id\", \"album_id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "remote_album_user_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"remote_album_user_entity\" (\"album_id\" TEXT NOT NULL REFERENCES remote_album_entity (id) ON DELETE CASCADE, \"user_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"role\" INTEGER NOT NULL, PRIMARY KEY (\"album_id\", \"user_id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "remote_asset_cloud_id_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"remote_asset_cloud_id_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"cloud_id\" TEXT NULL, \"created_at\" TEXT NULL, \"adjustment_time\" TEXT NULL, \"latitude\" REAL NULL, \"longitude\" REAL NULL, PRIMARY KEY (\"asset_id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "memory_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"memory_entity\" (\"id\" TEXT NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"deleted_at\" TEXT NULL, \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"type\" INTEGER NOT NULL, \"data\" TEXT NOT NULL, \"is_saved\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_saved\" IN (0, 1)), \"memory_at\" TEXT NOT NULL, \"seen_at\" TEXT NULL, \"show_at\" TEXT NULL, \"hide_at\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "memory_asset_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"memory_asset_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"memory_id\" TEXT NOT NULL REFERENCES memory_entity (id) ON DELETE CASCADE, PRIMARY KEY (\"asset_id\", \"memory_id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "person_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"person_entity\" (\"id\" TEXT NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"name\" TEXT NOT NULL, \"face_asset_id\" TEXT NULL, \"is_favorite\" INTEGER NOT NULL CHECK (\"is_favorite\" IN (0, 1)), \"is_hidden\" INTEGER NOT NULL CHECK (\"is_hidden\" IN (0, 1)), \"color\" TEXT NULL, \"birth_date\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "asset_face_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"asset_face_entity\" (\"id\" TEXT NOT NULL, \"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"person_id\" TEXT NULL REFERENCES person_entity (id) ON DELETE SET NULL, \"image_width\" INTEGER NOT NULL, \"image_height\" INTEGER NOT NULL, \"bounding_box_x1\" INTEGER NOT NULL, \"bounding_box_y1\" INTEGER NOT NULL, \"bounding_box_x2\" INTEGER NOT NULL, \"bounding_box_y2\" INTEGER NOT NULL, \"source_type\" TEXT NOT NULL, \"is_visible\" INTEGER NOT NULL DEFAULT 1 CHECK (\"is_visible\" IN (0, 1)), \"deleted_at\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "store_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"store_entity\" (\"id\" INTEGER NOT NULL, \"string_value\" TEXT NULL, \"int_value\" INTEGER NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "trash_sync", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"trash_sync\" (\"asset_id\" TEXT NOT NULL, \"checksum\" TEXT NOT NULL, \"status\" INTEGER NOT NULL DEFAULT 0, \"asset_updated_at\" TEXT NULL, PRIMARY KEY (\"asset_id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "server_deleted_checksum", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"server_deleted_checksum\" (\"checksum\" TEXT NOT NULL, PRIMARY KEY (\"checksum\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "asset_edit_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"asset_edit_entity\" (\"id\" TEXT NOT NULL, \"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"action\" INTEGER NOT NULL, \"parameters\" BLOB NOT NULL, \"sequence\" INTEGER NOT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "settings", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"settings\" (\"key\" TEXT NOT NULL, \"value\" TEXT NULL, \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), PRIMARY KEY (\"key\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "asset_ocr_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"asset_ocr_entity\" (\"id\" TEXT NOT NULL, \"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"x1\" REAL NOT NULL, \"y1\" REAL NOT NULL, \"x2\" REAL NOT NULL, \"y2\" REAL NOT NULL, \"x3\" REAL NOT NULL, \"y3\" REAL NOT NULL, \"x4\" REAL NOT NULL, \"y4\" REAL NOT NULL, \"box_score\" REAL NOT NULL, \"text_score\" REAL NOT NULL, \"recognized_text\" TEXT NOT NULL, \"is_visible\" INTEGER NOT NULL DEFAULT 1 CHECK (\"is_visible\" IN (0, 1)), PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "idx_partner_shared_with_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)" + } + ] + }, + { + "name": "idx_lat_lng", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)" + } + ] + }, + { + "name": "idx_remote_exif_city", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_exif_city ON remote_exif_entity (city) WHERE city IS NOT NULL" + } + ] + }, + { + "name": "idx_remote_album_asset_album_asset", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)" + } + ] + }, + { + "name": "idx_remote_asset_cloud_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)" + } + ] + }, + { + "name": "idx_person_owner_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)" + } + ] + }, + { + "name": "idx_asset_face_person_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)" + } + ] + }, + { + "name": "idx_asset_face_asset_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)" + } + ] + }, + { + "name": "idx_asset_face_visible_person", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_visible_person ON asset_face_entity (person_id, asset_id) WHERE is_visible = 1 AND deleted_at IS NULL" + } + ] + }, + { + "name": "idx_trash_sync_checksum", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_trash_sync_checksum ON trash_sync (checksum)" + } + ] + }, + { + "name": "idx_asset_edit_asset_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)" + } + ] + }, + { + "name": "idx_asset_ocr_asset_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_asset_ocr_asset_id ON asset_ocr_entity (asset_id)" + } + ] + } + ] +} \ No newline at end of file diff --git a/mobile/ios/Runner.xcodeproj/project.pbxproj b/mobile/ios/Runner.xcodeproj/project.pbxproj index b569693971..4c8a010359 100644 --- a/mobile/ios/Runner.xcodeproj/project.pbxproj +++ b/mobile/ios/Runner.xcodeproj/project.pbxproj @@ -166,6 +166,11 @@ path = Sync; sourceTree = ""; }; + B2D5F7B13005FCA00080CE3E /* AssetMedia */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = AssetMedia; + sourceTree = ""; + }; F0B57D3D2DF764BD00DC5BCC /* WidgetExtension */ = { isa = PBXFileSystemSynchronizedRootGroup; exceptions = ( @@ -287,6 +292,7 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( + B2D5F7B13005FCA00080CE3E /* AssetMedia */, FE1BB4562F8319560087DBF9 /* Utility */, FEE084F22EC172080045228E /* Schemas */, B231F52D2E93A44A00BC45D1 /* Core */, @@ -388,6 +394,7 @@ fileSystemSynchronizedGroups = ( B231F52D2E93A44A00BC45D1 /* Core */, B2CF7F8C2DDE4EBB00744BF6 /* Sync */, + B2D5F7B13005FCA00080CE3E /* AssetMedia */, FEE084F22EC172080045228E /* Schemas */, ); name = Runner; diff --git a/mobile/ios/Runner/AppDelegate.swift b/mobile/ios/Runner/AppDelegate.swift index e1ff3b1f6e..e39d01661b 100644 --- a/mobile/ios/Runner/AppDelegate.swift +++ b/mobile/ios/Runner/AppDelegate.swift @@ -26,6 +26,7 @@ import native_video_player public static func registerPlugins(with registry: FlutterPluginRegistry, messenger: FlutterBinaryMessenger) { NativeSyncApiImpl.register(with: registry.registrar(forPlugin: NativeSyncApiImpl.name)!) + AssetMediaApiImpl.register(with: registry.registrar(forPlugin: AssetMediaApiImpl.name)!) PermissionApiSetup.setUp(binaryMessenger: messenger, api: PermissionApiImpl()) LocalImageApiSetup.setUp(binaryMessenger: messenger, api: LocalImageApiImpl()) RemoteImageApiSetup.setUp(binaryMessenger: messenger, api: RemoteImageApiImpl()) @@ -36,5 +37,6 @@ import native_video_player public static func cancelPlugins(with engine: FlutterEngine) { (engine.valuePublished(byPlugin: NativeSyncApiImpl.name) as? NativeSyncApiImpl)?.detachFromEngine() + (engine.valuePublished(byPlugin: AssetMediaApiImpl.name) as? AssetMediaApiImpl)?.detachFromEngine() } } diff --git a/mobile/ios/Runner/AssetMedia/AssetMedia.g.swift b/mobile/ios/Runner/AssetMedia/AssetMedia.g.swift new file mode 100644 index 0000000000..2eca6137d3 --- /dev/null +++ b/mobile/ios/Runner/AssetMedia/AssetMedia.g.swift @@ -0,0 +1,313 @@ +// Autogenerated from Pigeon (v26.3.4), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +import Foundation + +#if os(iOS) + import Flutter +#elseif os(macOS) + import FlutterMacOS +#else + #error("Unsupported platform.") +#endif + +private func wrapResult(_ result: Any?) -> [Any?] { + return [result] +} + +private func wrapError(_ error: Any) -> [Any?] { + if let pigeonError = error as? PigeonError { + return [ + pigeonError.code, + pigeonError.message, + pigeonError.details, + ] + } + if let flutterError = error as? FlutterError { + return [ + flutterError.code, + flutterError.message, + flutterError.details, + ] + } + return [ + "\(error)", + "\(Swift.type(of: error))", + "Stacktrace: \(Thread.callStackSymbols)", + ] +} + +private func isNullish(_ value: Any?) -> Bool { + return value is NSNull || value == nil +} + +private func nilOrValue(_ value: Any?) -> T? { + if value is NSNull { return nil } + return value as! T? +} + +private func doubleEqualsAssetMedia(_ lhs: Double, _ rhs: Double) -> Bool { + return (lhs.isNaN && rhs.isNaN) || lhs == rhs +} + +private func doubleHashAssetMedia(_ value: Double, _ hasher: inout Hasher) { + if value.isNaN { + hasher.combine(0x7FF8000000000000) + } else { + // Normalize -0.0 to 0.0 + hasher.combine(value == 0 ? 0 : value) + } +} + +func deepEqualsAssetMedia(_ lhs: Any?, _ rhs: Any?) -> Bool { + let cleanLhs = nilOrValue(lhs) as Any? + let cleanRhs = nilOrValue(rhs) as Any? + switch (cleanLhs, cleanRhs) { + case (nil, nil): + return true + + case (nil, _), (_, nil): + return false + + case (let lhs as AnyObject, let rhs as AnyObject) where lhs === rhs: + return true + + case is (Void, Void): + return true + + case (let lhsArray, let rhsArray) as ([Any?], [Any?]): + guard lhsArray.count == rhsArray.count else { return false } + for (index, element) in lhsArray.enumerated() { + if !deepEqualsAssetMedia(element, rhsArray[index]) { + return false + } + } + return true + + case (let lhsArray, let rhsArray) as ([Double], [Double]): + guard lhsArray.count == rhsArray.count else { return false } + for (index, element) in lhsArray.enumerated() { + if !doubleEqualsAssetMedia(element, rhsArray[index]) { + return false + } + } + return true + + case (let lhsDictionary, let rhsDictionary) as ([AnyHashable: Any?], [AnyHashable: Any?]): + guard lhsDictionary.count == rhsDictionary.count else { return false } + for (lhsKey, lhsValue) in lhsDictionary { + var found = false + for (rhsKey, rhsValue) in rhsDictionary { + if deepEqualsAssetMedia(lhsKey, rhsKey) { + if deepEqualsAssetMedia(lhsValue, rhsValue) { + found = true + break + } else { + return false + } + } + } + if !found { return false } + } + return true + + case (let lhs as Double, let rhs as Double): + return doubleEqualsAssetMedia(lhs, rhs) + + case (let lhsHashable, let rhsHashable) as (AnyHashable, AnyHashable): + return lhsHashable == rhsHashable + + default: + return false + } +} + +func deepHashAssetMedia(value: Any?, hasher: inout Hasher) { + let cleanValue = nilOrValue(value) as Any? + if let cleanValue = cleanValue { + if let doubleValue = cleanValue as? Double { + doubleHashAssetMedia(doubleValue, &hasher) + } else if let valueList = cleanValue as? [Any?] { + for item in valueList { + deepHashAssetMedia(value: item, hasher: &hasher) + } + } else if let valueList = cleanValue as? [Double] { + for item in valueList { + doubleHashAssetMedia(item, &hasher) + } + } else if let valueDict = cleanValue as? [AnyHashable: Any?] { + var result = 0 + for (key, value) in valueDict { + var entryKeyHasher = Hasher() + deepHashAssetMedia(value: key, hasher: &entryKeyHasher) + var entryValueHasher = Hasher() + deepHashAssetMedia(value: value, hasher: &entryValueHasher) + result = result &+ ((entryKeyHasher.finalize() &* 31) ^ entryValueHasher.finalize()) + } + hasher.combine(result) + } else if let hashableValue = cleanValue as? AnyHashable { + hasher.combine(hashableValue) + } else { + hasher.combine(String(describing: cleanValue)) + } + } else { + hasher.combine(0) + } +} + + +enum AssetMediaActionStatus: Int { + case done = 0 + case alreadyInState = 1 + case notFound = 2 + case failed = 3 +} + +/// Generated class from Pigeon that represents data sent in messages. +struct AssetMediaActionResult: Hashable { + var id: String + var status: AssetMediaActionStatus + + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> AssetMediaActionResult? { + let id = pigeonVar_list[0] as! String + let status = pigeonVar_list[1] as! AssetMediaActionStatus + + return AssetMediaActionResult( + id: id, + status: status + ) + } + func toList() -> [Any?] { + return [ + id, + status, + ] + } + static func == (lhs: AssetMediaActionResult, rhs: AssetMediaActionResult) -> Bool { + if Swift.type(of: lhs) != Swift.type(of: rhs) { + return false + } + return deepEqualsAssetMedia(lhs.id, rhs.id) && deepEqualsAssetMedia(lhs.status, rhs.status) + } + + func hash(into hasher: inout Hasher) { + hasher.combine("AssetMediaActionResult") + deepHashAssetMedia(value: id, hasher: &hasher) + deepHashAssetMedia(value: status, hasher: &hasher) + } +} + +private class AssetMediaPigeonCodecReader: FlutterStandardReader { + override func readValue(ofType type: UInt8) -> Any? { + switch type { + case 129: + let enumResultAsInt: Int? = nilOrValue(self.readValue() as! Int?) + if let enumResultAsInt = enumResultAsInt { + return AssetMediaActionStatus(rawValue: enumResultAsInt) + } + return nil + case 130: + return AssetMediaActionResult.fromList(self.readValue() as! [Any?]) + default: + return super.readValue(ofType: type) + } + } +} + +private class AssetMediaPigeonCodecWriter: FlutterStandardWriter { + override func writeValue(_ value: Any) { + if let value = value as? AssetMediaActionStatus { + super.writeByte(129) + super.writeValue(value.rawValue) + } else if let value = value as? AssetMediaActionResult { + super.writeByte(130) + super.writeValue(value.toList()) + } else { + super.writeValue(value) + } + } +} + +private class AssetMediaPigeonCodecReaderWriter: FlutterStandardReaderWriter { + override func reader(with data: Data) -> FlutterStandardReader { + return AssetMediaPigeonCodecReader(data: data) + } + + override func writer(with data: NSMutableData) -> FlutterStandardWriter { + return AssetMediaPigeonCodecWriter(data: data) + } +} + +class AssetMediaPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable { + static let shared = AssetMediaPigeonCodec(readerWriter: AssetMediaPigeonCodecReaderWriter()) +} + + +/// Generated protocol from Pigeon that represents a handler of messages from Flutter. +protocol AssetMediaApi { + func trash(ids: [String], completion: @escaping (Result<[AssetMediaActionResult], Error>) -> Void) + func restore(ids: [String], completion: @escaping (Result<[AssetMediaActionResult], Error>) -> Void) + func trashedAmong(ids: [String], completion: @escaping (Result<[String], Error>) -> Void) +} + +/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. +class AssetMediaApiSetup { + static var codec: FlutterStandardMessageCodec { AssetMediaPigeonCodec.shared } + /// Sets up an instance of `AssetMediaApi` to handle messages through the `binaryMessenger`. + static func setUp(binaryMessenger: FlutterBinaryMessenger, api: AssetMediaApi?, messageChannelSuffix: String = "") { + let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : "" + let trashChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.AssetMediaApi.trash\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + trashChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let idsArg = args[0] as! [String] + api.trash(ids: idsArg) { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + trashChannel.setMessageHandler(nil) + } + let restoreChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.AssetMediaApi.restore\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + restoreChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let idsArg = args[0] as! [String] + api.restore(ids: idsArg) { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + restoreChannel.setMessageHandler(nil) + } + let trashedAmongChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.AssetMediaApi.trashedAmong\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + trashedAmongChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let idsArg = args[0] as! [String] + api.trashedAmong(ids: idsArg) { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + trashedAmongChannel.setMessageHandler(nil) + } + } +} diff --git a/mobile/ios/Runner/AssetMedia/AssetMediaApiImpl.swift b/mobile/ios/Runner/AssetMedia/AssetMediaApiImpl.swift new file mode 100644 index 0000000000..ef7c7816c6 --- /dev/null +++ b/mobile/ios/Runner/AssetMedia/AssetMediaApiImpl.swift @@ -0,0 +1,26 @@ +class AssetMediaApiImpl: ImmichPlugin, AssetMediaApi, FlutterPlugin { + static let name = "AssetMediaApi" + + static func register(with registrar: FlutterPluginRegistrar) { + let instance = AssetMediaApiImpl() + AssetMediaApiSetup.setUp(binaryMessenger: registrar.messenger(), api: instance) + registrar.publish(instance) + } + + func detachFromEngine(for registrar: FlutterPluginRegistrar) { + AssetMediaApiSetup.setUp(binaryMessenger: registrar.messenger(), api: nil) + super.detachFromEngine() + } + + func trash(ids: [String], completion: @escaping (Result<[AssetMediaActionResult], Error>) -> Void) { + completeWhenActive(for: completion, with: .success(ids.map { AssetMediaActionResult(id: $0, status: .failed) })) + } + + func restore(ids: [String], completion: @escaping (Result<[AssetMediaActionResult], Error>) -> Void) { + completeWhenActive(for: completion, with: .success(ids.map { AssetMediaActionResult(id: $0, status: .failed) })) + } + + func trashedAmong(ids: [String], completion: @escaping (Result<[String], Error>) -> Void) { + completeWhenActive(for: completion, with: .success([])) + } +} diff --git a/mobile/ios/Runner/Sync/Messages.g.swift b/mobile/ios/Runner/Sync/Messages.g.swift index a752785c5b..139d0a03a6 100644 --- a/mobile/ios/Runner/Sync/Messages.g.swift +++ b/mobile/ios/Runner/Sync/Messages.g.swift @@ -537,8 +537,6 @@ protocol NativeSyncApi { func hashAssets(assetIds: [String], allowNetworkAccess: Bool, completion: @escaping (Result<[HashResult], Error>) -> Void) func cancelHashing() throws func cancelSync() throws - func getTrashedAssets() throws -> [String: [PlatformAsset]] - func restoreFromTrashById(mediaId: String, type: Int64, completion: @escaping (Result) -> Void) func getCloudIdForAssetIds(assetIds: [String]) throws -> [CloudIdResult] } @@ -723,39 +721,6 @@ class NativeSyncApiSetup { } else { cancelSyncChannel.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) - } - let restoreFromTrashByIdChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.restoreFromTrashById\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) - if let api = api { - restoreFromTrashByIdChannel.setMessageHandler { message, reply in - let args = message as! [Any?] - let mediaIdArg = args[0] as! String - let typeArg = args[1] as! Int64 - api.restoreFromTrashById(mediaId: mediaIdArg, type: typeArg) { result in - switch result { - case .success(let res): - reply(wrapResult(res)) - case .failure(let error): - reply(wrapError(error)) - } - } - } - } else { - restoreFromTrashByIdChannel.setMessageHandler(nil) - } let getCloudIdForAssetIdsChannel = taskQueue == nil ? FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getCloudIdForAssetIds\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) : FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getCloudIdForAssetIds\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue) diff --git a/mobile/ios/Runner/Sync/MessagesImpl.swift b/mobile/ios/Runner/Sync/MessagesImpl.swift index ddfd023690..322d93b4cf 100644 --- a/mobile/ios/Runner/Sync/MessagesImpl.swift +++ b/mobile/ios/Runner/Sync/MessagesImpl.swift @@ -436,14 +436,6 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin { }) } - func getTrashedAssets() throws -> [String: [PlatformAsset]] { - throw PigeonError(code: "UNSUPPORTED_OS", message: "This feature not supported on iOS.", details: nil) - } - - func restoreFromTrashById(mediaId: String, type: Int64, completion: @escaping (Result) -> Void) { - completion(.success(false)) - } - 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/enums.dart b/mobile/lib/constants/enums.dart index d59c48c045..40e4b2812a 100644 --- a/mobile/lib/constants/enums.dart +++ b/mobile/lib/constants/enums.dart @@ -24,3 +24,17 @@ enum SlideshowLook { contain, cover, blurredBackground } enum SlideshowDirection { forward, backward, shuffle } enum PartnerDirection { sharedBy, sharedWith } + +enum TrashSyncStatus { + pending, + trashed, + /* The asset was restored outside of Immich. The implications of this are: + * - Assets matching the same checksum won't be re-trashed + * - Backups will ignore the asset from re-uploading + */ + dismissed, + /* The asset was restored back out of the OS trash by us. This allows us + * to copy the checksum back into the local asset table + */ + restored, +} diff --git a/mobile/lib/domain/models/config/app_config.dart b/mobile/lib/domain/models/config/app_config.dart index e4e11baf9d..d8bb7c38c3 100644 --- a/mobile/lib/domain/models/config/app_config.dart +++ b/mobile/lib/domain/models/config/app_config.dart @@ -23,6 +23,7 @@ const defaultConfig = AppConfig(); class AppConfig { final LogLevel logLevel; + final bool trashSyncEnabled; final ThemeConfig theme; final CleanupConfig cleanup; final MapConfig map; @@ -38,6 +39,7 @@ class AppConfig { const AppConfig({ this.logLevel = .info, + this.trashSyncEnabled = false, this.theme = const .new(), this.cleanup = const .new(), this.map = const .new(), @@ -54,6 +56,7 @@ class AppConfig { AppConfig copyWith({ LogLevel? logLevel, + bool? trashSyncEnabled, ThemeConfig? theme, CleanupConfig? cleanup, MapConfig? map, @@ -68,6 +71,7 @@ class AppConfig { FeatureMessageConfig? featureMessage, }) => .new( logLevel: logLevel ?? this.logLevel, + trashSyncEnabled: trashSyncEnabled ?? this.trashSyncEnabled, theme: theme ?? this.theme, cleanup: cleanup ?? this.cleanup, map: map ?? this.map, @@ -87,6 +91,7 @@ class AppConfig { identical(this, other) || (other is AppConfig && other.logLevel == logLevel && + other.trashSyncEnabled == trashSyncEnabled && other.theme == theme && other.cleanup == cleanup && other.map == map && @@ -103,6 +108,7 @@ class AppConfig { @override int get hashCode => Object.hash( logLevel, + trashSyncEnabled, theme, cleanup, map, @@ -119,11 +125,12 @@ class AppConfig { @override String toString() => - 'AppConfig(logLevel: $logLevel, theme: $theme, cleanup: $cleanup, map: $map, timeline: $timeline, image: $image, viewer: $viewer, slideshow: $slideshow, album: $album, backup: $backup, network: $network, share: $share, featureMessage: $featureMessage)'; + 'AppConfig(logLevel: $logLevel, trashSyncEnabled: $trashSyncEnabled, theme: $theme, cleanup: $cleanup, map: $map, timeline: $timeline, image: $image, viewer: $viewer, slideshow: $slideshow, album: $album, backup: $backup, network: $network, share: $share, featureMessage: $featureMessage)'; T read(SettingsKey key) => (switch (key) { .logLevel => logLevel, + .trashSyncEnabled => trashSyncEnabled, .themePrimaryColor => theme.primaryColor, .themeMode => theme.mode, .themeDynamic => theme.dynamicTheme, @@ -178,6 +185,7 @@ class AppConfig { AppConfig write(SettingsKey key, U value) { return switch (key) { .logLevel => copyWith(logLevel: value as LogLevel), + .trashSyncEnabled => copyWith(trashSyncEnabled: value as bool), .themePrimaryColor => copyWith(theme: theme.copyWith(primaryColor: value as ImmichColorPreset)), .themeMode => copyWith(theme: theme.copyWith(mode: value as ThemeMode)), .themeDynamic => copyWith(theme: theme.copyWith(dynamicTheme: value as bool)), diff --git a/mobile/lib/domain/models/settings_key.dart b/mobile/lib/domain/models/settings_key.dart index 85e58ffcf1..eea33619f8 100644 --- a/mobile/lib/domain/models/settings_key.dart +++ b/mobile/lib/domain/models/settings_key.dart @@ -36,6 +36,9 @@ enum SettingsKey { albumIsReverse(), albumIsGrid(), + // Sync + trashSyncEnabled(), + // Backup backupEnabled(), backupUseCellularForVideos(), diff --git a/mobile/lib/domain/models/store.model.dart b/mobile/lib/domain/models/store.model.dart index be1b0c5fb8..2f0e22c021 100644 --- a/mobile/lib/domain/models/store.model.dart +++ b/mobile/lib/domain/models/store.model.dart @@ -12,13 +12,13 @@ enum StoreKey { advancedTroubleshooting._(114), enableHapticFeedback._(126), - manageLocalMediaAndroid._(137), // Read-only Mode settings readonlyModeEnabled._(138), syncMigrationStatus._(1013), // Legacy keys that have been migrated to the new metadata store + legacyManageLocalMediaAndroid._(137), legacyBackupRequireCharging._(7), legacyBackupTriggerDelay._(8), legacySyncAlbums._(131), diff --git a/mobile/lib/domain/services/background_worker.service.dart b/mobile/lib/domain/services/background_worker.service.dart index 8161df5c51..0bd8fe5c8f 100644 --- a/mobile/lib/domain/services/background_worker.service.dart +++ b/mobile/lib/domain/services/background_worker.service.dart @@ -24,9 +24,8 @@ import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; import 'package:immich_mobile/providers/infrastructure/sync.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/trash_sync.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; -import 'package:immich_mobile/repositories/asset_media.repository.dart'; -import 'package:immich_mobile/repositories/permission.repository.dart'; import 'package:immich_mobile/services/auth.service.dart'; import 'package:immich_mobile/services/foreground_upload.service.dart'; import 'package:immich_mobile/services/localization.service.dart'; @@ -80,18 +79,13 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi { localAlbumRepository: ref.read(localAlbumRepository), localAssetRepository: ref.read(localAssetRepository), nativeSyncApi: ref.read(nativeSyncApiProvider), - trashedLocalAssetRepository: ref.read(trashedLocalAssetRepository), - assetMediaRepository: ref.read(assetMediaRepositoryProvider), - permissionRepository: ref.read(permissionRepositoryProvider), + trashSyncRepository: ref.read(trashSyncRepositoryProvider), cancellation: _cancellationToken, ); _remoteSyncService = SyncStreamService( syncApiRepository: ref.read(syncApiRepositoryProvider), syncStreamRepository: ref.read(syncStreamRepositoryProvider), - localAssetRepository: ref.read(localAssetRepository), - trashedLocalAssetRepository: ref.read(trashedLocalAssetRepository), - assetMediaRepository: ref.read(assetMediaRepositoryProvider), - permissionRepository: ref.read(permissionRepositoryProvider), + trashSyncRepository: ref.read(trashSyncRepositoryProvider), syncMigrationRepository: ref.read(syncMigrationRepositoryProvider), api: ref.read(apiServiceProvider), cancellation: _cancellationToken, @@ -100,7 +94,6 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi { localAlbumRepository: ref.read(localAlbumRepository), localAssetRepository: ref.read(localAssetRepository), nativeSyncApi: ref.read(nativeSyncApiProvider), - trashedLocalAssetRepository: ref.read(trashedLocalAssetRepository), cancellation: _cancellationToken, ); BackgroundWorkerFlutterApi.setUp(this); diff --git a/mobile/lib/domain/services/hash.service.dart b/mobile/lib/domain/services/hash.service.dart index e4c332b283..3570ec9aa2 100644 --- a/mobile/lib/domain/services/hash.service.dart +++ b/mobile/lib/domain/services/hash.service.dart @@ -4,10 +4,8 @@ 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'; @@ -17,7 +15,6 @@ class HashService { final int _batchSize; final DriftLocalAlbumRepository _localAlbumRepository; final DriftLocalAssetRepository _localAssetRepository; - final DriftTrashedLocalAssetRepository _trashedLocalAssetRepository; final NativeSyncApi _nativeSyncApi; final Completer? _cancellation; final _log = Logger('HashService'); @@ -25,7 +22,6 @@ class HashService { HashService({ required this._localAlbumRepository, required this._localAssetRepository, - required this._trashedLocalAssetRepository, required this._nativeSyncApi, this._cancellation, int? batchSize, @@ -58,14 +54,6 @@ 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"); @@ -82,7 +70,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, {bool isTrashed = false}) async { + Future _hashAssets(LocalAlbum album, List assetsToHash) async { final toHash = {}; for (final asset in assetsToHash) { @@ -93,16 +81,16 @@ class HashService { toHash[asset.id] = asset; if (toHash.length == _batchSize) { - await _processBatch(album, toHash, isTrashed); + await _processBatch(album, toHash); toHash.clear(); } } - await _processBatch(album, toHash, isTrashed); + await _processBatch(album, toHash); } /// Processes a batch of assets. - Future _processBatch(LocalAlbum album, Map toHash, bool isTrashed) async { + Future _processBatch(LocalAlbum album, Map toHash) async { if (toHash.isEmpty) { return; } @@ -137,10 +125,6 @@ class HashService { } _log.fine("Hashed ${hashed.length}/${toHash.length} assets"); - if (isTrashed) { - await _trashedLocalAssetRepository.updateHashes(hashed); - } else { - await _localAssetRepository.updateHashes(hashed); - } + 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 feb104f90d..b66b9317d3 100644 --- a/mobile/lib/domain/services/local_sync.service.dart +++ b/mobile/lib/domain/services/local_sync.service.dart @@ -5,15 +5,11 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/services.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/local_asset.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/trash_sync.repository.dart'; import 'package:immich_mobile/platform/native_sync_api.g.dart'; -import 'package:immich_mobile/repositories/asset_media.repository.dart'; -import 'package:immich_mobile/repositories/permission.repository.dart'; import 'package:immich_mobile/utils/datetime_helpers.dart'; import 'package:immich_mobile/utils/diff.dart'; import 'package:logging/logging.dart'; @@ -24,20 +20,16 @@ class LocalSyncService { final DriftLocalAlbumRepository _localAlbumRepository; // ignore: unused_field final DriftLocalAssetRepository _localAssetRepository; + final DriftTrashSyncRepository _trashSyncRepository; final NativeSyncApi _nativeSyncApi; - final DriftTrashedLocalAssetRepository _trashedLocalAssetRepository; - final AssetMediaRepository _assetMediaRepository; - final IPermissionRepository _permissionRepository; final Completer? _cancellation; final Logger _log = Logger("DeviceSyncService"); LocalSyncService({ required this._localAlbumRepository, required this._localAssetRepository, + required this._trashSyncRepository, required this._nativeSyncApi, - required this._trashedLocalAssetRepository, - required this._assetMediaRepository, - required this._permissionRepository, this._cancellation, }) { _cancellation?.future.then((_) => _nativeSyncApi.cancelSync().onError(_log.warning)); @@ -48,15 +40,6 @@ class LocalSyncService { Future sync({bool full = false}) async { final Stopwatch stopwatch = Stopwatch()..start(); try { - if (CurrentPlatform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false)) { - final hasPermission = await _permissionRepository.hasManageMediaPermission(); - if (hasPermission) { - await _syncTrashedAssets(); - } else { - _log.warning("syncTrashedAssets cannot proceed because MANAGE_MEDIA permission is missing"); - } - } - if (CurrentPlatform.isIOS) { // final assets = await _localAssetRepository.getEmptyCloudIdAssets(); // await _mapIosCloudIds(assets); @@ -119,6 +102,7 @@ class LocalSyncService { await _mapIosCloudIds(newAssets); } await _nativeSyncApi.checkpointSync(); + await _trashSyncRepository.restoreChecksums(); } on PlatformException catch (e, s) { if (e.code == _kSyncCancelledCode) { _log.warning("Local sync cancelled"); @@ -150,6 +134,7 @@ class LocalSyncService { ); await _nativeSyncApi.checkpointSync(); + await _trashSyncRepository.restoreChecksums(); stopwatch.stop(); _log.info("Full device sync took - ${stopwatch.elapsedMilliseconds}ms"); } on PlatformException catch (e, s) { @@ -383,48 +368,6 @@ 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 _assetMediaRepository.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 localIds = localAssetsToTrash.values.expand((assets) => assets).map((asset) => asset.id).toList(); - _log.info("Moving to trash ${localIds.join(", ")} assets"); - final movedIds = await _assetMediaRepository.deleteAll(localIds); - if (movedIds.isNotEmpty) { - final movedAssetsByAlbum = localAssetsToTrash.map( - (albumId, assets) => MapEntry(albumId, assets.where((asset) => movedIds.contains(asset.id)).toList()), - )..removeWhere((_, assets) => assets.isEmpty); - - await _trashedLocalAssetRepository.trashLocalAsset(movedAssetsByAlbum); - } - } else { - _log.info("syncTrashedAssets, No assets found in backup-enabled albums for move to trash"); - } - } } extension on Iterable { @@ -445,10 +388,6 @@ extension on Iterable { List toLocalAssets() { return map((e) => e.toLocalAsset()).toList(); } - - Iterable toTrashedAssets(String albumId) { - return map((e) => (albumId: albumId, asset: e.toLocalAsset())); - } } extension PlatformToLocalAsset on PlatformAsset { diff --git a/mobile/lib/domain/services/sync_stream.service.dart b/mobile/lib/domain/services/sync_stream.service.dart index 9ebce300ba..1713bcee43 100644 --- a/mobile/lib/domain/services/sync_stream.service.dart +++ b/mobile/lib/domain/services/sync_stream.service.dart @@ -3,18 +3,13 @@ import 'dart:async'; import 'dart:convert'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/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/sync_api.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/sync_migration.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/asset_media.repository.dart'; -import 'package:immich_mobile/repositories/permission.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/trash_sync.repository.dart'; import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/utils/semver.dart'; import 'package:logging/logging.dart'; @@ -33,10 +28,7 @@ class SyncStreamService { final SyncApiRepository _syncApiRepository; final SyncStreamRepository _syncStreamRepository; - final DriftLocalAssetRepository _localAssetRepository; - final DriftTrashedLocalAssetRepository _trashedLocalAssetRepository; - final AssetMediaRepository _assetMediaRepository; - final IPermissionRepository _permissionRepository; + final DriftTrashSyncRepository _trashSyncRepository; final SyncMigrationRepository _syncMigrationRepository; final ApiService _api; final Completer? _cancellation; @@ -44,10 +36,7 @@ class SyncStreamService { SyncStreamService({ required this._syncApiRepository, required this._syncStreamRepository, - required this._localAssetRepository, - required this._trashedLocalAssetRepository, - required this._assetMediaRepository, - required this._permissionRepository, + required this._trashSyncRepository, required this._syncMigrationRepository, required this._api, this._cancellation, @@ -202,24 +191,13 @@ class SyncStreamService { case SyncEntityType.partnerDeleteV1: return _syncStreamRepository.deletePartnerV1(data.cast()); case SyncEntityType.assetV1: - final remoteSyncAssets = data.cast(); - await _syncStreamRepository.updateAssetsV1(remoteSyncAssets); - if (CurrentPlatform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false)) { - await _syncAssetTrashStatus(remoteSyncAssets.where((e) => e.deletedAt != null).map((e) => e.id).toList()); - } - return; + return _syncStreamRepository.updateAssetsV1(data.cast()); case SyncEntityType.assetV2: - final remoteSyncAssets = data.cast(); - await _syncStreamRepository.updateAssetsV2(remoteSyncAssets); - if (CurrentPlatform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false)) { - await _syncAssetTrashStatus(remoteSyncAssets.where((e) => e.deletedAt != null).map((e) => e.id).toList()); - } - return; + return _syncStreamRepository.updateAssetsV2(data.cast()); case SyncEntityType.assetDeleteV1: final remoteSyncAssets = data.cast(); - if (CurrentPlatform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false)) { - await _syncAssetDeletion(remoteSyncAssets.map((e) => e.assetId).toList()); - } + // Has to be before the delete call so we can record the checksums of the assets as well + await _trashSyncRepository.recordHardDeletedChecksums(remoteSyncAssets.map((e) => e.assetId)); return _syncStreamRepository.deleteAssetsV1(remoteSyncAssets); case SyncEntityType.assetExifV1: return _syncStreamRepository.updateAssetsExifV1(data.cast()); @@ -497,59 +475,4 @@ class SyncStreamService { _logger.severe("Error processing AssetEditReadyV2 websocket event", error, stackTrace); } } - - Future _handleRemoteDeleted(Iterable remoteIds) async { - if (remoteIds.isEmpty) { - return Future.value(); - } else { - final localAssetsToTrash = await _localAssetRepository.getAssetsFromBackupAlbums(remoteIds); - if (localAssetsToTrash.isNotEmpty) { - await _trashLocalAssets(localAssetsToTrash); - } else { - _logger.info("No assets found in backup-enabled albums for remote assets: $remoteIds"); - } - } - } - - Future _trashLocalAssets(Map> localAssetsToTrash) async { - final localIds = localAssetsToTrash.values.expand((assets) => assets).map((asset) => asset.id).toList(); - _logger.info("Moving to trash ${localIds.join(", ")} assets"); - final movedIds = await _assetMediaRepository.deleteAll(localIds); - if (movedIds.isNotEmpty) { - final movedAssetsByAlbum = localAssetsToTrash.map( - (albumId, assets) => MapEntry(albumId, assets.where((asset) => movedIds.contains(asset.id)).toList()), - )..removeWhere((_, assets) => assets.isEmpty); - - await _trashedLocalAssetRepository.trashLocalAsset(movedAssetsByAlbum); - } - } - - Future _applyRemoteRestoreToLocal() async { - final assetsToRestore = await _trashedLocalAssetRepository.getToRestore(); - if (assetsToRestore.isNotEmpty) { - final restoredIds = await _assetMediaRepository.restoreAssetsFromTrash(assetsToRestore); - await _trashedLocalAssetRepository.applyRestoredAssets(restoredIds); - } else { - _logger.info("No remote assets found for restoration"); - } - } - - Future _syncAssetTrashStatus(List remoteIds) async { - if (!(await _permissionRepository.hasManageMediaPermission())) { - _logger.warning("Syncing asset trash status cannot proceed because MANAGE_MEDIA permission is missing"); - return; - } - - await _handleRemoteDeleted(remoteIds); - await _applyRemoteRestoreToLocal(); - } - - Future _syncAssetDeletion(List remoteIds) async { - if (!(await _permissionRepository.hasManageMediaPermission())) { - _logger.warning("Syncing asset deletion cannot proceed because MANAGE_MEDIA permission is missing"); - return; - } - - await _handleRemoteDeleted(remoteIds); - } } diff --git a/mobile/lib/domain/services/trash_sync.service.dart b/mobile/lib/domain/services/trash_sync.service.dart new file mode 100644 index 0000000000..5d270c2ace --- /dev/null +++ b/mobile/lib/domain/services/trash_sync.service.dart @@ -0,0 +1,124 @@ +import 'package:immich_mobile/extensions/platform_extensions.dart'; +import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/trash_sync.repository.dart'; +import 'package:immich_mobile/platform/asset_media_api.g.dart'; +import 'package:immich_mobile/repositories/permission.repository.dart'; +import 'package:logging/logging.dart'; + +class TrashSyncService { + final DriftTrashSyncRepository _repo; + final AssetMediaApi _assetMediaApi; + final PermissionRepository _permission; + final SettingsRepository _settings; + final Logger _log = Logger('TrashSyncService'); + + TrashSyncService({ + required this._repo, + required this._assetMediaApi, + required this._permission, + required this._settings, + }); + + Future reconcile() async { + try { + await _prune(); + + if (!_settings.appConfig.trashSyncEnabled || !await _canApplyToOsTrash()) { + return; + } + + await _record(); + await _act(); + } catch (error, stack) { + _log.severe("Trash reconcile failed", error, stack); + } + } + + Future _prune() async { + await _repo.pruneStaleMarkers(); + await _repo.pruneDismissedMarkers(); + await _repo.prunePendingMarkers(); + } + + Future _record() async { + await _repo.recordSoftDeleteAssets(); + await _repo.recordHardDeletedAssets(); + } + + Future _act() async { + await _trashAssets(); + await _restoreAssets(); + await _reconcileWithOSTrash(); + } + + Future _trashAssets() async { + final pending = await _repo.getPendingAssetIds(); + if (pending.isEmpty) { + return; + } + + final results = await _assetMediaApi.trash(pending); + final movedIds = results.whereStatusIn(const {.done, .alreadyInState}); + final removedIds = results.whereStatusIn(const {.notFound}); + + if (movedIds.isNotEmpty) { + await _repo.markTrashed(movedIds); + } + + if (removedIds.isNotEmpty) { + await _repo.deleteMarkers(removedIds); + } + + _log.fine("Trashed ${movedIds.length}, dropped ${removedIds.length} out of ${pending.length} pending"); + } + + Future _restoreAssets() async { + final restorable = await _repo.getRestorableAssetIds(); + if (restorable.isEmpty) { + return; + } + + final results = await _assetMediaApi.restore(restorable); + final restoredIds = results.whereStatusIn(const {.done, .alreadyInState}); + final goneIds = results.whereStatusIn(const {.notFound}); + + if (restoredIds.isNotEmpty) { + await _repo.markRestored(restoredIds); + } + + if (goneIds.isNotEmpty) { + await _repo.deleteMarkers(goneIds); + } + } + + Future _reconcileWithOSTrash() async { + final trashed = await _repo.getTrashedAssetIds(); + if (trashed.isEmpty) { + return; + } + + final stillTrashed = (await _assetMediaApi.trashedAmong(trashed)).toSet(); + final gone = trashed.where((id) => !stillTrashed.contains(id)).toList(); + if (gone.isNotEmpty) { + await _repo.reconcileTrashed(gone); + } + } + + Future _canApplyToOsTrash() async { + if (!CurrentPlatform.isAndroid) { + return false; + } + + final hasPermission = await _permission.hasManageMediaPermission(); + if (!hasPermission) { + _log.fine("OS trash sync skipped: MANAGE_MEDIA permission not granted"); + } + + return hasPermission; + } +} + +extension on Iterable { + Set whereStatusIn(Set statuses) => + where((r) => statuses.contains(r.status)).map((r) => r.id).toSet(); +} diff --git a/mobile/lib/domain/utils/background_sync.dart b/mobile/lib/domain/utils/background_sync.dart index 82f397d9b6..7dcea8231b 100644 --- a/mobile/lib/domain/utils/background_sync.dart +++ b/mobile/lib/domain/utils/background_sync.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:immich_mobile/domain/utils/migrate_cloud_ids.dart' as m; import 'package:immich_mobile/domain/utils/sync_linked_album.dart'; import 'package:immich_mobile/providers/infrastructure/sync.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/trash_sync.provider.dart'; import 'package:immich_mobile/utils/isolate.dart'; import 'package:worker_manager/worker_manager.dart'; @@ -33,6 +34,7 @@ class BackgroundSyncManager { Cancelable? _deviceAlbumSyncTask; Cancelable? _linkedAlbumSyncTask; Cancelable? _hashTask; + Cancelable? _trashSyncTask; BackgroundSyncManager({ this.onRemoteSyncStart, @@ -57,6 +59,7 @@ class BackgroundSyncManager { _linkedAlbumSyncTask, _deviceAlbumSyncTask, _hashTask, + _trashSyncTask, ]; final futures = [ for (final task in tasks) @@ -71,6 +74,7 @@ class BackgroundSyncManager { _linkedAlbumSyncTask = null; _deviceAlbumSyncTask = null; _hashTask = null; + _trashSyncTask = null; try { await Future.wait(futures); @@ -160,6 +164,21 @@ class BackgroundSyncManager { }); } + Future syncTrash() { + if (_trashSyncTask != null) { + return _trashSyncTask!.future; + } + + _trashSyncTask = runInIsolateGentle( + computation: (ref) => ref.read(trashSyncServiceProvider).reconcile(), + debugLabel: 'trash-sync', + ); + + return _trashSyncTask!.whenComplete(() { + _trashSyncTask = null; + }); + } + Future syncWebsocketBatchV1(List batchData) { if (_syncWebsocketTask != null) { return _syncWebsocketTask!.future; diff --git a/mobile/lib/infrastructure/entities/remote_asset.entity.dart b/mobile/lib/infrastructure/entities/remote_asset.entity.dart index 3fe56ae431..377ad66c78 100644 --- a/mobile/lib/infrastructure/entities/remote_asset.entity.dart +++ b/mobile/lib/infrastructure/entities/remote_asset.entity.dart @@ -16,6 +16,11 @@ ON remote_asset_entity (owner_id, library_id, checksum) WHERE (library_id IS NOT NULL); ''') @TableIndex.sql('CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)') +@TableIndex.sql(''' +CREATE INDEX IF NOT EXISTS idx_remote_asset_soft_deleted_checksum +ON remote_asset_entity (checksum) +WHERE deleted_at IS NOT NULL +''') @TableIndex.sql('CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)') @TableIndex.sql(''' CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created diff --git a/mobile/lib/infrastructure/entities/remote_asset.entity.drift.dart b/mobile/lib/infrastructure/entities/remote_asset.entity.drift.dart index 314e93a9a0..71d3a9a85f 100644 --- a/mobile/lib/infrastructure/entities/remote_asset.entity.drift.dart +++ b/mobile/lib/infrastructure/entities/remote_asset.entity.drift.dart @@ -1762,6 +1762,10 @@ i0.Index get idxRemoteAssetChecksum => i0.Index( 'idx_remote_asset_checksum', 'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)', ); +i0.Index get idxRemoteAssetSoftDeletedChecksum => i0.Index( + 'idx_remote_asset_soft_deleted_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_soft_deleted_checksum ON remote_asset_entity (checksum) WHERE deleted_at IS NOT NULL', +); i0.Index get idxRemoteAssetStackId => i0.Index( 'idx_remote_asset_stack_id', 'CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)', diff --git a/mobile/lib/infrastructure/entities/server_deleted_checksum.entity.dart b/mobile/lib/infrastructure/entities/server_deleted_checksum.entity.dart new file mode 100644 index 0000000000..de5c136ea6 --- /dev/null +++ b/mobile/lib/infrastructure/entities/server_deleted_checksum.entity.dart @@ -0,0 +1,14 @@ +import 'package:drift/drift.dart'; +import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart'; + +class ServerDeletedChecksumEntity extends Table with DriftDefaultsMixin { + const ServerDeletedChecksumEntity(); + + TextColumn get checksum => text()(); + + @override + Set get primaryKey => {checksum}; + + @override + String get tableName => "server_deleted_checksum"; +} diff --git a/mobile/lib/infrastructure/entities/server_deleted_checksum.entity.drift.dart b/mobile/lib/infrastructure/entities/server_deleted_checksum.entity.drift.dart new file mode 100644 index 0000000000..64f29dd1f0 --- /dev/null +++ b/mobile/lib/infrastructure/entities/server_deleted_checksum.entity.drift.dart @@ -0,0 +1,318 @@ +// dart format width=80 +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:immich_mobile/infrastructure/entities/server_deleted_checksum.entity.drift.dart' + as i1; +import 'package:immich_mobile/infrastructure/entities/server_deleted_checksum.entity.dart' + as i2; + +typedef $$ServerDeletedChecksumEntityTableCreateCompanionBuilder = + i1.ServerDeletedChecksumEntityCompanion Function({ + required String checksum, + }); +typedef $$ServerDeletedChecksumEntityTableUpdateCompanionBuilder = + i1.ServerDeletedChecksumEntityCompanion Function({ + i0.Value checksum, + }); + +class $$ServerDeletedChecksumEntityTableFilterComposer + extends + i0.Composer< + i0.GeneratedDatabase, + i1.$ServerDeletedChecksumEntityTable + > { + $$ServerDeletedChecksumEntityTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnFilters get checksum => $composableBuilder( + column: $table.checksum, + builder: (column) => i0.ColumnFilters(column), + ); +} + +class $$ServerDeletedChecksumEntityTableOrderingComposer + extends + i0.Composer< + i0.GeneratedDatabase, + i1.$ServerDeletedChecksumEntityTable + > { + $$ServerDeletedChecksumEntityTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnOrderings get checksum => $composableBuilder( + column: $table.checksum, + builder: (column) => i0.ColumnOrderings(column), + ); +} + +class $$ServerDeletedChecksumEntityTableAnnotationComposer + extends + i0.Composer< + i0.GeneratedDatabase, + i1.$ServerDeletedChecksumEntityTable + > { + $$ServerDeletedChecksumEntityTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.GeneratedColumn get checksum => + $composableBuilder(column: $table.checksum, builder: (column) => column); +} + +class $$ServerDeletedChecksumEntityTableTableManager + extends + i0.RootTableManager< + i0.GeneratedDatabase, + i1.$ServerDeletedChecksumEntityTable, + i1.ServerDeletedChecksumEntityData, + i1.$$ServerDeletedChecksumEntityTableFilterComposer, + i1.$$ServerDeletedChecksumEntityTableOrderingComposer, + i1.$$ServerDeletedChecksumEntityTableAnnotationComposer, + $$ServerDeletedChecksumEntityTableCreateCompanionBuilder, + $$ServerDeletedChecksumEntityTableUpdateCompanionBuilder, + ( + i1.ServerDeletedChecksumEntityData, + i0.BaseReferences< + i0.GeneratedDatabase, + i1.$ServerDeletedChecksumEntityTable, + i1.ServerDeletedChecksumEntityData + >, + ), + i1.ServerDeletedChecksumEntityData, + i0.PrefetchHooks Function() + > { + $$ServerDeletedChecksumEntityTableTableManager( + i0.GeneratedDatabase db, + i1.$ServerDeletedChecksumEntityTable table, + ) : super( + i0.TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + i1.$$ServerDeletedChecksumEntityTableFilterComposer( + $db: db, + $table: table, + ), + createOrderingComposer: () => + i1.$$ServerDeletedChecksumEntityTableOrderingComposer( + $db: db, + $table: table, + ), + createComputedFieldComposer: () => + i1.$$ServerDeletedChecksumEntityTableAnnotationComposer( + $db: db, + $table: table, + ), + updateCompanionCallback: + ({i0.Value checksum = const i0.Value.absent()}) => + i1.ServerDeletedChecksumEntityCompanion(checksum: checksum), + createCompanionCallback: ({required String checksum}) => + i1.ServerDeletedChecksumEntityCompanion.insert( + checksum: checksum, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + ), + ); +} + +typedef $$ServerDeletedChecksumEntityTableProcessedTableManager = + i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.$ServerDeletedChecksumEntityTable, + i1.ServerDeletedChecksumEntityData, + i1.$$ServerDeletedChecksumEntityTableFilterComposer, + i1.$$ServerDeletedChecksumEntityTableOrderingComposer, + i1.$$ServerDeletedChecksumEntityTableAnnotationComposer, + $$ServerDeletedChecksumEntityTableCreateCompanionBuilder, + $$ServerDeletedChecksumEntityTableUpdateCompanionBuilder, + ( + i1.ServerDeletedChecksumEntityData, + i0.BaseReferences< + i0.GeneratedDatabase, + i1.$ServerDeletedChecksumEntityTable, + i1.ServerDeletedChecksumEntityData + >, + ), + i1.ServerDeletedChecksumEntityData, + i0.PrefetchHooks Function() + >; + +class $ServerDeletedChecksumEntityTable extends i2.ServerDeletedChecksumEntity + with + i0.TableInfo< + $ServerDeletedChecksumEntityTable, + i1.ServerDeletedChecksumEntityData + > { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + $ServerDeletedChecksumEntityTable(this.attachedDatabase, [this._alias]); + static const i0.VerificationMeta _checksumMeta = const i0.VerificationMeta( + 'checksum', + ); + @override + late final i0.GeneratedColumn checksum = i0.GeneratedColumn( + 'checksum', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [checksum]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'server_deleted_checksum'; + @override + i0.VerificationContext validateIntegrity( + i0.Insertable instance, { + bool isInserting = false, + }) { + final context = i0.VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('checksum')) { + context.handle( + _checksumMeta, + checksum.isAcceptableOrUnknown(data['checksum']!, _checksumMeta), + ); + } else if (isInserting) { + context.missing(_checksumMeta); + } + return context; + } + + @override + Set get $primaryKey => {checksum}; + @override + i1.ServerDeletedChecksumEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.ServerDeletedChecksumEntityData( + checksum: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}checksum'], + )!, + ); + } + + @override + $ServerDeletedChecksumEntityTable createAlias(String alias) { + return $ServerDeletedChecksumEntityTable(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class ServerDeletedChecksumEntityData extends i0.DataClass + implements i0.Insertable { + final String checksum; + const ServerDeletedChecksumEntityData({required this.checksum}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['checksum'] = i0.Variable(checksum); + return map; + } + + factory ServerDeletedChecksumEntityData.fromJson( + Map json, { + i0.ValueSerializer? serializer, + }) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return ServerDeletedChecksumEntityData( + checksum: serializer.fromJson(json['checksum']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return {'checksum': serializer.toJson(checksum)}; + } + + i1.ServerDeletedChecksumEntityData copyWith({String? checksum}) => + i1.ServerDeletedChecksumEntityData(checksum: checksum ?? this.checksum); + ServerDeletedChecksumEntityData copyWithCompanion( + i1.ServerDeletedChecksumEntityCompanion data, + ) { + return ServerDeletedChecksumEntityData( + checksum: data.checksum.present ? data.checksum.value : this.checksum, + ); + } + + @override + String toString() { + return (StringBuffer('ServerDeletedChecksumEntityData(') + ..write('checksum: $checksum') + ..write(')')) + .toString(); + } + + @override + int get hashCode => checksum.hashCode; + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.ServerDeletedChecksumEntityData && + other.checksum == this.checksum); +} + +class ServerDeletedChecksumEntityCompanion + extends i0.UpdateCompanion { + final i0.Value checksum; + const ServerDeletedChecksumEntityCompanion({ + this.checksum = const i0.Value.absent(), + }); + ServerDeletedChecksumEntityCompanion.insert({required String checksum}) + : checksum = i0.Value(checksum); + static i0.Insertable custom({ + i0.Expression? checksum, + }) { + return i0.RawValuesInsertable({if (checksum != null) 'checksum': checksum}); + } + + i1.ServerDeletedChecksumEntityCompanion copyWith({ + i0.Value? checksum, + }) { + return i1.ServerDeletedChecksumEntityCompanion( + checksum: checksum ?? this.checksum, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (checksum.present) { + map['checksum'] = i0.Variable(checksum.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('ServerDeletedChecksumEntityCompanion(') + ..write('checksum: $checksum') + ..write(')')) + .toString(); + } +} diff --git a/mobile/lib/infrastructure/entities/trash_sync.entity.dart b/mobile/lib/infrastructure/entities/trash_sync.entity.dart new file mode 100644 index 0000000000..b6e0f8a2a9 --- /dev/null +++ b/mobile/lib/infrastructure/entities/trash_sync.entity.dart @@ -0,0 +1,22 @@ +import 'package:drift/drift.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart'; + +@TableIndex.sql('CREATE INDEX IF NOT EXISTS idx_trash_sync_checksum ON trash_sync (checksum)') +class TrashSyncEntity extends Table with DriftDefaultsMixin { + const TrashSyncEntity(); + + TextColumn get assetId => text()(); + + TextColumn get checksum => text()(); + + IntColumn get status => intEnum().withDefault(Constant(TrashSyncStatus.pending.index))(); + + DateTimeColumn get assetUpdatedAt => dateTime().nullable()(); + + @override + Set get primaryKey => {assetId}; + + @override + String get tableName => "trash_sync"; +} diff --git a/mobile/lib/infrastructure/entities/trash_sync.entity.drift.dart b/mobile/lib/infrastructure/entities/trash_sync.entity.drift.dart new file mode 100644 index 0000000000..fb831f98e5 --- /dev/null +++ b/mobile/lib/infrastructure/entities/trash_sync.entity.drift.dart @@ -0,0 +1,524 @@ +// dart format width=80 +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:immich_mobile/infrastructure/entities/trash_sync.entity.drift.dart' + as i1; +import 'package:immich_mobile/constants/enums.dart' as i2; +import 'package:immich_mobile/infrastructure/entities/trash_sync.entity.dart' + as i3; +import 'package:drift/src/runtime/query_builder/query_builder.dart' as i4; + +typedef $$TrashSyncEntityTableCreateCompanionBuilder = + i1.TrashSyncEntityCompanion Function({ + required String assetId, + required String checksum, + i0.Value status, + i0.Value assetUpdatedAt, + }); +typedef $$TrashSyncEntityTableUpdateCompanionBuilder = + i1.TrashSyncEntityCompanion Function({ + i0.Value assetId, + i0.Value checksum, + i0.Value status, + i0.Value assetUpdatedAt, + }); + +class $$TrashSyncEntityTableFilterComposer + extends i0.Composer { + $$TrashSyncEntityTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnFilters get assetId => $composableBuilder( + column: $table.assetId, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get checksum => $composableBuilder( + column: $table.checksum, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnWithTypeConverterFilters + get status => $composableBuilder( + column: $table.status, + builder: (column) => i0.ColumnWithTypeConverterFilters(column), + ); + + i0.ColumnFilters get assetUpdatedAt => $composableBuilder( + column: $table.assetUpdatedAt, + builder: (column) => i0.ColumnFilters(column), + ); +} + +class $$TrashSyncEntityTableOrderingComposer + extends i0.Composer { + $$TrashSyncEntityTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnOrderings get assetId => $composableBuilder( + column: $table.assetId, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get checksum => $composableBuilder( + column: $table.checksum, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get status => $composableBuilder( + column: $table.status, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get assetUpdatedAt => $composableBuilder( + column: $table.assetUpdatedAt, + builder: (column) => i0.ColumnOrderings(column), + ); +} + +class $$TrashSyncEntityTableAnnotationComposer + extends i0.Composer { + $$TrashSyncEntityTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.GeneratedColumn get assetId => + $composableBuilder(column: $table.assetId, builder: (column) => column); + + i0.GeneratedColumn get checksum => + $composableBuilder(column: $table.checksum, builder: (column) => column); + + i0.GeneratedColumnWithTypeConverter get status => + $composableBuilder(column: $table.status, builder: (column) => column); + + i0.GeneratedColumn get assetUpdatedAt => $composableBuilder( + column: $table.assetUpdatedAt, + builder: (column) => column, + ); +} + +class $$TrashSyncEntityTableTableManager + extends + i0.RootTableManager< + i0.GeneratedDatabase, + i1.$TrashSyncEntityTable, + i1.TrashSyncEntityData, + i1.$$TrashSyncEntityTableFilterComposer, + i1.$$TrashSyncEntityTableOrderingComposer, + i1.$$TrashSyncEntityTableAnnotationComposer, + $$TrashSyncEntityTableCreateCompanionBuilder, + $$TrashSyncEntityTableUpdateCompanionBuilder, + ( + i1.TrashSyncEntityData, + i0.BaseReferences< + i0.GeneratedDatabase, + i1.$TrashSyncEntityTable, + i1.TrashSyncEntityData + >, + ), + i1.TrashSyncEntityData, + i0.PrefetchHooks Function() + > { + $$TrashSyncEntityTableTableManager( + i0.GeneratedDatabase db, + i1.$TrashSyncEntityTable table, + ) : super( + i0.TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + i1.$$TrashSyncEntityTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + i1.$$TrashSyncEntityTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => i1 + .$$TrashSyncEntityTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + i0.Value assetId = const i0.Value.absent(), + i0.Value checksum = const i0.Value.absent(), + i0.Value status = const i0.Value.absent(), + i0.Value assetUpdatedAt = const i0.Value.absent(), + }) => i1.TrashSyncEntityCompanion( + assetId: assetId, + checksum: checksum, + status: status, + assetUpdatedAt: assetUpdatedAt, + ), + createCompanionCallback: + ({ + required String assetId, + required String checksum, + i0.Value status = const i0.Value.absent(), + i0.Value assetUpdatedAt = const i0.Value.absent(), + }) => i1.TrashSyncEntityCompanion.insert( + assetId: assetId, + checksum: checksum, + status: status, + assetUpdatedAt: assetUpdatedAt, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + ), + ); +} + +typedef $$TrashSyncEntityTableProcessedTableManager = + i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.$TrashSyncEntityTable, + i1.TrashSyncEntityData, + i1.$$TrashSyncEntityTableFilterComposer, + i1.$$TrashSyncEntityTableOrderingComposer, + i1.$$TrashSyncEntityTableAnnotationComposer, + $$TrashSyncEntityTableCreateCompanionBuilder, + $$TrashSyncEntityTableUpdateCompanionBuilder, + ( + i1.TrashSyncEntityData, + i0.BaseReferences< + i0.GeneratedDatabase, + i1.$TrashSyncEntityTable, + i1.TrashSyncEntityData + >, + ), + i1.TrashSyncEntityData, + i0.PrefetchHooks Function() + >; +i0.Index get idxTrashSyncChecksum => i0.Index( + 'idx_trash_sync_checksum', + 'CREATE INDEX IF NOT EXISTS idx_trash_sync_checksum ON trash_sync (checksum)', +); + +class $TrashSyncEntityTable extends i3.TrashSyncEntity + with i0.TableInfo<$TrashSyncEntityTable, i1.TrashSyncEntityData> { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + $TrashSyncEntityTable(this.attachedDatabase, [this._alias]); + static const i0.VerificationMeta _assetIdMeta = const i0.VerificationMeta( + 'assetId', + ); + @override + late final i0.GeneratedColumn assetId = i0.GeneratedColumn( + 'asset_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, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + late final i0.GeneratedColumnWithTypeConverter + status = + i0.GeneratedColumn( + 'status', + aliasedName, + false, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: i4.Constant(i2.TrashSyncStatus.pending.index), + ).withConverter( + i1.$TrashSyncEntityTable.$converterstatus, + ); + static const i0.VerificationMeta _assetUpdatedAtMeta = + const i0.VerificationMeta('assetUpdatedAt'); + @override + late final i0.GeneratedColumn assetUpdatedAt = + i0.GeneratedColumn( + 'asset_updated_at', + aliasedName, + true, + type: i0.DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + assetId, + checksum, + status, + assetUpdatedAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'trash_sync'; + @override + i0.VerificationContext validateIntegrity( + i0.Insertable instance, { + bool isInserting = false, + }) { + final context = i0.VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('asset_id')) { + context.handle( + _assetIdMeta, + assetId.isAcceptableOrUnknown(data['asset_id']!, _assetIdMeta), + ); + } else if (isInserting) { + context.missing(_assetIdMeta); + } + if (data.containsKey('checksum')) { + context.handle( + _checksumMeta, + checksum.isAcceptableOrUnknown(data['checksum']!, _checksumMeta), + ); + } else if (isInserting) { + context.missing(_checksumMeta); + } + if (data.containsKey('asset_updated_at')) { + context.handle( + _assetUpdatedAtMeta, + assetUpdatedAt.isAcceptableOrUnknown( + data['asset_updated_at']!, + _assetUpdatedAtMeta, + ), + ); + } + return context; + } + + @override + Set get $primaryKey => {assetId}; + @override + i1.TrashSyncEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.TrashSyncEntityData( + assetId: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + checksum: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}checksum'], + )!, + status: i1.$TrashSyncEntityTable.$converterstatus.fromSql( + attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}status'], + )!, + ), + assetUpdatedAt: attachedDatabase.typeMapping.read( + i0.DriftSqlType.dateTime, + data['${effectivePrefix}asset_updated_at'], + ), + ); + } + + @override + $TrashSyncEntityTable createAlias(String alias) { + return $TrashSyncEntityTable(attachedDatabase, alias); + } + + static i0.JsonTypeConverter2 $converterstatus = + const i0.EnumIndexConverter( + i2.TrashSyncStatus.values, + ); + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class TrashSyncEntityData extends i0.DataClass + implements i0.Insertable { + final String assetId; + final String checksum; + final i2.TrashSyncStatus status; + final DateTime? assetUpdatedAt; + const TrashSyncEntityData({ + required this.assetId, + required this.checksum, + required this.status, + this.assetUpdatedAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = i0.Variable(assetId); + map['checksum'] = i0.Variable(checksum); + { + map['status'] = i0.Variable( + i1.$TrashSyncEntityTable.$converterstatus.toSql(status), + ); + } + if (!nullToAbsent || assetUpdatedAt != null) { + map['asset_updated_at'] = i0.Variable(assetUpdatedAt); + } + return map; + } + + factory TrashSyncEntityData.fromJson( + Map json, { + i0.ValueSerializer? serializer, + }) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return TrashSyncEntityData( + assetId: serializer.fromJson(json['assetId']), + checksum: serializer.fromJson(json['checksum']), + status: i1.$TrashSyncEntityTable.$converterstatus.fromJson( + serializer.fromJson(json['status']), + ), + assetUpdatedAt: serializer.fromJson(json['assetUpdatedAt']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'checksum': serializer.toJson(checksum), + 'status': serializer.toJson( + i1.$TrashSyncEntityTable.$converterstatus.toJson(status), + ), + 'assetUpdatedAt': serializer.toJson(assetUpdatedAt), + }; + } + + i1.TrashSyncEntityData copyWith({ + String? assetId, + String? checksum, + i2.TrashSyncStatus? status, + i0.Value assetUpdatedAt = const i0.Value.absent(), + }) => i1.TrashSyncEntityData( + assetId: assetId ?? this.assetId, + checksum: checksum ?? this.checksum, + status: status ?? this.status, + assetUpdatedAt: assetUpdatedAt.present + ? assetUpdatedAt.value + : this.assetUpdatedAt, + ); + TrashSyncEntityData copyWithCompanion(i1.TrashSyncEntityCompanion data) { + return TrashSyncEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + status: data.status.present ? data.status.value : this.status, + assetUpdatedAt: data.assetUpdatedAt.present + ? data.assetUpdatedAt.value + : this.assetUpdatedAt, + ); + } + + @override + String toString() { + return (StringBuffer('TrashSyncEntityData(') + ..write('assetId: $assetId, ') + ..write('checksum: $checksum, ') + ..write('status: $status, ') + ..write('assetUpdatedAt: $assetUpdatedAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, checksum, status, assetUpdatedAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.TrashSyncEntityData && + other.assetId == this.assetId && + other.checksum == this.checksum && + other.status == this.status && + other.assetUpdatedAt == this.assetUpdatedAt); +} + +class TrashSyncEntityCompanion + extends i0.UpdateCompanion { + final i0.Value assetId; + final i0.Value checksum; + final i0.Value status; + final i0.Value assetUpdatedAt; + const TrashSyncEntityCompanion({ + this.assetId = const i0.Value.absent(), + this.checksum = const i0.Value.absent(), + this.status = const i0.Value.absent(), + this.assetUpdatedAt = const i0.Value.absent(), + }); + TrashSyncEntityCompanion.insert({ + required String assetId, + required String checksum, + this.status = const i0.Value.absent(), + this.assetUpdatedAt = const i0.Value.absent(), + }) : assetId = i0.Value(assetId), + checksum = i0.Value(checksum); + static i0.Insertable custom({ + i0.Expression? assetId, + i0.Expression? checksum, + i0.Expression? status, + i0.Expression? assetUpdatedAt, + }) { + return i0.RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (checksum != null) 'checksum': checksum, + if (status != null) 'status': status, + if (assetUpdatedAt != null) 'asset_updated_at': assetUpdatedAt, + }); + } + + i1.TrashSyncEntityCompanion copyWith({ + i0.Value? assetId, + i0.Value? checksum, + i0.Value? status, + i0.Value? assetUpdatedAt, + }) { + return i1.TrashSyncEntityCompanion( + assetId: assetId ?? this.assetId, + checksum: checksum ?? this.checksum, + status: status ?? this.status, + assetUpdatedAt: assetUpdatedAt ?? this.assetUpdatedAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = i0.Variable(assetId.value); + } + if (checksum.present) { + map['checksum'] = i0.Variable(checksum.value); + } + if (status.present) { + map['status'] = i0.Variable( + i1.$TrashSyncEntityTable.$converterstatus.toSql(status.value), + ); + } + if (assetUpdatedAt.present) { + map['asset_updated_at'] = i0.Variable(assetUpdatedAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('TrashSyncEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('checksum: $checksum, ') + ..write('status: $status, ') + ..write('assetUpdatedAt: $assetUpdatedAt') + ..write(')')) + .toString(); + } +} diff --git a/mobile/lib/infrastructure/entities/trashed_local_asset.entity.dart b/mobile/lib/infrastructure/entities/trashed_local_asset.entity.dart deleted file mode 100644 index 6bcff5d591..0000000000 --- a/mobile/lib/infrastructure/entities/trashed_local_asset.entity.dart +++ /dev/null @@ -1,53 +0,0 @@ -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'; - -enum TrashOrigin { - // do not change this order! - localSync, - remoteSync, - localUser, -} - -@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))(); - - IntColumn get source => intEnum()(); - - IntColumn get playbackStyle => intEnum().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, - durationMs: durationMs, - isFavorite: isFavorite, - height: height, - width: width, - orientation: orientation, - playbackStyle: playbackStyle, - isEdited: false, - ); -} diff --git a/mobile/lib/infrastructure/entities/trashed_local_asset.entity.drift.dart b/mobile/lib/infrastructure/entities/trashed_local_asset.entity.drift.dart deleted file mode 100644 index 068d008e92..0000000000 --- a/mobile/lib/infrastructure/entities/trashed_local_asset.entity.drift.dart +++ /dev/null @@ -1,1241 +0,0 @@ -// 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 durationMs, - required String id, - required String albumId, - i0.Value checksum, - i0.Value isFavorite, - i0.Value orientation, - required i3.TrashOrigin source, - i0.Value playbackStyle, - }); -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 durationMs, - i0.Value id, - i0.Value albumId, - i0.Value checksum, - i0.Value isFavorite, - i0.Value orientation, - i0.Value source, - i0.Value playbackStyle, - }); - -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 durationMs => $composableBuilder( - column: $table.durationMs, - 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), - ); - - i0.ColumnWithTypeConverterFilters - get source => $composableBuilder( - column: $table.source, - builder: (column) => i0.ColumnWithTypeConverterFilters(column), - ); - - i0.ColumnWithTypeConverterFilters< - i2.AssetPlaybackStyle, - i2.AssetPlaybackStyle, - int - > - get playbackStyle => $composableBuilder( - column: $table.playbackStyle, - builder: (column) => i0.ColumnWithTypeConverterFilters(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 durationMs => $composableBuilder( - column: $table.durationMs, - 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), - ); - - i0.ColumnOrderings get source => $composableBuilder( - column: $table.source, - builder: (column) => i0.ColumnOrderings(column), - ); - - i0.ColumnOrderings get playbackStyle => $composableBuilder( - column: $table.playbackStyle, - 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 durationMs => $composableBuilder( - column: $table.durationMs, - 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, - ); - - i0.GeneratedColumnWithTypeConverter get source => - $composableBuilder(column: $table.source, builder: (column) => column); - - i0.GeneratedColumnWithTypeConverter - get playbackStyle => $composableBuilder( - column: $table.playbackStyle, - 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 durationMs = 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(), - i0.Value source = const i0.Value.absent(), - i0.Value playbackStyle = - const i0.Value.absent(), - }) => i1.TrashedLocalAssetEntityCompanion( - name: name, - type: type, - createdAt: createdAt, - updatedAt: updatedAt, - width: width, - height: height, - durationMs: durationMs, - id: id, - albumId: albumId, - checksum: checksum, - isFavorite: isFavorite, - orientation: orientation, - source: source, - playbackStyle: playbackStyle, - ), - 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 durationMs = 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(), - required i3.TrashOrigin source, - i0.Value playbackStyle = - const i0.Value.absent(), - }) => i1.TrashedLocalAssetEntityCompanion.insert( - name: name, - type: type, - createdAt: createdAt, - updatedAt: updatedAt, - width: width, - height: height, - durationMs: durationMs, - id: id, - albumId: albumId, - checksum: checksum, - isFavorite: isFavorite, - orientation: orientation, - source: source, - playbackStyle: playbackStyle, - ), - 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 _durationMsMeta = const i0.VerificationMeta( - 'durationMs', - ); - @override - late final i0.GeneratedColumn durationMs = i0.GeneratedColumn( - 'duration_ms', - 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 - late final i0.GeneratedColumnWithTypeConverter source = - i0.GeneratedColumn( - 'source', - aliasedName, - false, - type: i0.DriftSqlType.int, - requiredDuringInsert: true, - ).withConverter( - i1.$TrashedLocalAssetEntityTable.$convertersource, - ); - @override - late final i0.GeneratedColumnWithTypeConverter - playbackStyle = - i0.GeneratedColumn( - 'playback_style', - aliasedName, - false, - type: i0.DriftSqlType.int, - requiredDuringInsert: false, - defaultValue: const i4.Constant(0), - ).withConverter( - i1.$TrashedLocalAssetEntityTable.$converterplaybackStyle, - ); - @override - List get $columns => [ - name, - type, - createdAt, - updatedAt, - width, - height, - durationMs, - id, - albumId, - checksum, - isFavorite, - orientation, - source, - playbackStyle, - ]; - @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_ms')) { - context.handle( - _durationMsMeta, - durationMs.isAcceptableOrUnknown(data['duration_ms']!, _durationMsMeta), - ); - } - 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'], - ), - durationMs: attachedDatabase.typeMapping.read( - i0.DriftSqlType.int, - data['${effectivePrefix}duration_ms'], - ), - 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'], - )!, - source: i1.$TrashedLocalAssetEntityTable.$convertersource.fromSql( - attachedDatabase.typeMapping.read( - i0.DriftSqlType.int, - data['${effectivePrefix}source'], - )!, - ), - playbackStyle: i1.$TrashedLocalAssetEntityTable.$converterplaybackStyle - .fromSql( - attachedDatabase.typeMapping.read( - i0.DriftSqlType.int, - data['${effectivePrefix}playback_style'], - )!, - ), - ); - } - - @override - $TrashedLocalAssetEntityTable createAlias(String alias) { - return $TrashedLocalAssetEntityTable(attachedDatabase, alias); - } - - static i0.JsonTypeConverter2 $convertertype = - const i0.EnumIndexConverter(i2.AssetType.values); - static i0.JsonTypeConverter2 $convertersource = - const i0.EnumIndexConverter(i3.TrashOrigin.values); - static i0.JsonTypeConverter2 - $converterplaybackStyle = const i0.EnumIndexConverter( - i2.AssetPlaybackStyle.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? durationMs; - final String id; - final String albumId; - final String? checksum; - final bool isFavorite; - final int orientation; - final i3.TrashOrigin source; - final i2.AssetPlaybackStyle playbackStyle; - const TrashedLocalAssetEntityData({ - required this.name, - required this.type, - required this.createdAt, - required this.updatedAt, - this.width, - this.height, - this.durationMs, - required this.id, - required this.albumId, - this.checksum, - required this.isFavorite, - required this.orientation, - required this.source, - required this.playbackStyle, - }); - @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 || durationMs != null) { - map['duration_ms'] = i0.Variable(durationMs); - } - 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); - { - map['source'] = i0.Variable( - i1.$TrashedLocalAssetEntityTable.$convertersource.toSql(source), - ); - } - { - map['playback_style'] = i0.Variable( - i1.$TrashedLocalAssetEntityTable.$converterplaybackStyle.toSql( - playbackStyle, - ), - ); - } - 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']), - durationMs: serializer.fromJson(json['durationMs']), - 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']), - source: i1.$TrashedLocalAssetEntityTable.$convertersource.fromJson( - serializer.fromJson(json['source']), - ), - playbackStyle: i1.$TrashedLocalAssetEntityTable.$converterplaybackStyle - .fromJson(serializer.fromJson(json['playbackStyle'])), - ); - } - @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), - 'durationMs': serializer.toJson(durationMs), - 'id': serializer.toJson(id), - 'albumId': serializer.toJson(albumId), - 'checksum': serializer.toJson(checksum), - 'isFavorite': serializer.toJson(isFavorite), - 'orientation': serializer.toJson(orientation), - 'source': serializer.toJson( - i1.$TrashedLocalAssetEntityTable.$convertersource.toJson(source), - ), - 'playbackStyle': serializer.toJson( - i1.$TrashedLocalAssetEntityTable.$converterplaybackStyle.toJson( - playbackStyle, - ), - ), - }; - } - - 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 durationMs = const i0.Value.absent(), - String? id, - String? albumId, - i0.Value checksum = const i0.Value.absent(), - bool? isFavorite, - int? orientation, - i3.TrashOrigin? source, - i2.AssetPlaybackStyle? playbackStyle, - }) => 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, - durationMs: durationMs.present ? durationMs.value : this.durationMs, - id: id ?? this.id, - albumId: albumId ?? this.albumId, - checksum: checksum.present ? checksum.value : this.checksum, - isFavorite: isFavorite ?? this.isFavorite, - orientation: orientation ?? this.orientation, - source: source ?? this.source, - playbackStyle: playbackStyle ?? this.playbackStyle, - ); - 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, - durationMs: data.durationMs.present - ? data.durationMs.value - : this.durationMs, - 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, - source: data.source.present ? data.source.value : this.source, - playbackStyle: data.playbackStyle.present - ? data.playbackStyle.value - : this.playbackStyle, - ); - } - - @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('durationMs: $durationMs, ') - ..write('id: $id, ') - ..write('albumId: $albumId, ') - ..write('checksum: $checksum, ') - ..write('isFavorite: $isFavorite, ') - ..write('orientation: $orientation, ') - ..write('source: $source, ') - ..write('playbackStyle: $playbackStyle') - ..write(')')) - .toString(); - } - - @override - int get hashCode => Object.hash( - name, - type, - createdAt, - updatedAt, - width, - height, - durationMs, - id, - albumId, - checksum, - isFavorite, - orientation, - source, - playbackStyle, - ); - @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.durationMs == this.durationMs && - other.id == this.id && - other.albumId == this.albumId && - other.checksum == this.checksum && - other.isFavorite == this.isFavorite && - other.orientation == this.orientation && - other.source == this.source && - other.playbackStyle == this.playbackStyle); -} - -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 durationMs; - final i0.Value id; - final i0.Value albumId; - final i0.Value checksum; - final i0.Value isFavorite; - final i0.Value orientation; - final i0.Value source; - final i0.Value playbackStyle; - 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.durationMs = 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(), - this.source = const i0.Value.absent(), - this.playbackStyle = 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.durationMs = 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(), - required i3.TrashOrigin source, - this.playbackStyle = const i0.Value.absent(), - }) : name = i0.Value(name), - type = i0.Value(type), - id = i0.Value(id), - albumId = i0.Value(albumId), - source = i0.Value(source); - static i0.Insertable custom({ - i0.Expression? name, - i0.Expression? type, - i0.Expression? createdAt, - i0.Expression? updatedAt, - i0.Expression? width, - i0.Expression? height, - i0.Expression? durationMs, - i0.Expression? id, - i0.Expression? albumId, - i0.Expression? checksum, - i0.Expression? isFavorite, - i0.Expression? orientation, - i0.Expression? source, - i0.Expression? playbackStyle, - }) { - 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 (durationMs != null) 'duration_ms': durationMs, - 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, - if (source != null) 'source': source, - if (playbackStyle != null) 'playback_style': playbackStyle, - }); - } - - i1.TrashedLocalAssetEntityCompanion copyWith({ - i0.Value? name, - i0.Value? type, - i0.Value? createdAt, - i0.Value? updatedAt, - i0.Value? width, - i0.Value? height, - i0.Value? durationMs, - i0.Value? id, - i0.Value? albumId, - i0.Value? checksum, - i0.Value? isFavorite, - i0.Value? orientation, - i0.Value? source, - i0.Value? playbackStyle, - }) { - return i1.TrashedLocalAssetEntityCompanion( - name: name ?? this.name, - type: type ?? this.type, - createdAt: createdAt ?? this.createdAt, - updatedAt: updatedAt ?? this.updatedAt, - width: width ?? this.width, - height: height ?? this.height, - durationMs: durationMs ?? this.durationMs, - id: id ?? this.id, - albumId: albumId ?? this.albumId, - checksum: checksum ?? this.checksum, - isFavorite: isFavorite ?? this.isFavorite, - orientation: orientation ?? this.orientation, - source: source ?? this.source, - playbackStyle: playbackStyle ?? this.playbackStyle, - ); - } - - @override - Map toColumns(bool nullToAbsent) { - final map = {}; - if (name.present) { - map['name'] = i0.Variable(name.value); - } - if (type.present) { - map['type'] = i0.Variable( - i1.$TrashedLocalAssetEntityTable.$convertertype.toSql(type.value), - ); - } - if (createdAt.present) { - map['created_at'] = i0.Variable(createdAt.value); - } - if (updatedAt.present) { - map['updated_at'] = i0.Variable(updatedAt.value); - } - if (width.present) { - map['width'] = i0.Variable(width.value); - } - if (height.present) { - map['height'] = i0.Variable(height.value); - } - if (durationMs.present) { - map['duration_ms'] = i0.Variable(durationMs.value); - } - if (id.present) { - map['id'] = i0.Variable(id.value); - } - if (albumId.present) { - map['album_id'] = i0.Variable(albumId.value); - } - if (checksum.present) { - map['checksum'] = i0.Variable(checksum.value); - } - if (isFavorite.present) { - map['is_favorite'] = i0.Variable(isFavorite.value); - } - if (orientation.present) { - map['orientation'] = i0.Variable(orientation.value); - } - if (source.present) { - map['source'] = i0.Variable( - i1.$TrashedLocalAssetEntityTable.$convertersource.toSql(source.value), - ); - } - if (playbackStyle.present) { - map['playback_style'] = i0.Variable( - i1.$TrashedLocalAssetEntityTable.$converterplaybackStyle.toSql( - playbackStyle.value, - ), - ); - } - return map; - } - - @override - String toString() { - return (StringBuffer('TrashedLocalAssetEntityCompanion(') - ..write('name: $name, ') - ..write('type: $type, ') - ..write('createdAt: $createdAt, ') - ..write('updatedAt: $updatedAt, ') - ..write('width: $width, ') - ..write('height: $height, ') - ..write('durationMs: $durationMs, ') - ..write('id: $id, ') - ..write('albumId: $albumId, ') - ..write('checksum: $checksum, ') - ..write('isFavorite: $isFavorite, ') - ..write('orientation: $orientation, ') - ..write('source: $source, ') - ..write('playbackStyle: $playbackStyle') - ..write(')')) - .toString(); - } -} - -i0.Index get idxTrashedLocalAssetAlbum => i0.Index( - 'idx_trashed_local_asset_album', - 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)', -); diff --git a/mobile/lib/infrastructure/repositories/backup.repository.dart b/mobile/lib/infrastructure/repositories/backup.repository.dart index 0241711d4b..149ccf7671 100644 --- a/mobile/lib/infrastructure/repositories/backup.repository.dart +++ b/mobile/lib/infrastructure/repositories/backup.repository.dart @@ -58,6 +58,12 @@ class DriftBackupRepository extends DriftDatabaseRepository { INNER JOIN main.local_album_entity la on laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = ?3 + ) + AND NOT EXISTS ( + SELECT 1 FROM trash_sync ts WHERE ts.asset_id = lae.id + ) + AND NOT EXISTS ( + SELECT 1 FROM server_deleted_checksum sd WHERE sd.checksum = lae.checksum ); '''; @@ -69,7 +75,14 @@ class DriftBackupRepository extends DriftDatabaseRepository { Variable.withInt(BackupSelection.selected.index), Variable.withInt(BackupSelection.excluded.index), ], - readsFrom: {_db.localAlbumAssetEntity, _db.localAlbumEntity, _db.localAssetEntity, _db.remoteAssetEntity}, + readsFrom: { + _db.localAlbumAssetEntity, + _db.localAlbumEntity, + _db.localAssetEntity, + _db.remoteAssetEntity, + _db.trashSyncEntity, + _db.serverDeletedChecksumEntity, + }, ) .getSingle(); @@ -104,6 +117,16 @@ class DriftBackupRepository extends DriftDatabaseRepository { _db.remoteAssetEntity.checksum.equalsExp(lae.checksum) & _db.remoteAssetEntity.ownerId.equals(userId), ), ) & + notExistsQuery( + _db.trashSyncEntity.selectOnly() + ..addColumns([_db.trashSyncEntity.assetId]) + ..where(_db.trashSyncEntity.assetId.equalsExp(lae.id)), + ) & + notExistsQuery( + _db.serverDeletedChecksumEntity.selectOnly() + ..addColumns([_db.serverDeletedChecksumEntity.checksum]) + ..where(_db.serverDeletedChecksumEntity.checksum.equalsExp(lae.checksum)), + ) & lae.id.isNotInQuery(_getExcludedSubquery()), ) ..orderBy([(localAsset) => OrderingTerm.desc(localAsset.createdAt)]); diff --git a/mobile/lib/infrastructure/repositories/db.repository.dart b/mobile/lib/infrastructure/repositories/db.repository.dart index 69e45685de..2d91d67c8e 100644 --- a/mobile/lib/infrastructure/repositories/db.repository.dart +++ b/mobile/lib/infrastructure/repositories/db.repository.dart @@ -25,11 +25,11 @@ import 'package:immich_mobile/infrastructure/entities/remote_album_user.entity.d import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.dart'; import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/remote_asset_cloud_id.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/server_deleted_checksum.entity.dart'; import 'package:immich_mobile/infrastructure/entities/settings.entity.dart'; import 'package:immich_mobile/infrastructure/entities/stack.entity.dart'; import 'package:immich_mobile/infrastructure/entities/store.entity.dart'; -import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.dart'; -import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.drift.dart'; +import 'package:immich_mobile/infrastructure/entities/trash_sync.entity.dart'; import 'package:immich_mobile/infrastructure/entities/user.entity.dart'; import 'package:immich_mobile/infrastructure/entities/user_metadata.entity.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.drift.dart'; @@ -63,7 +63,8 @@ import 'package:sqlite_async/sqlite_async.dart'; PersonEntity, AssetFaceEntity, StoreEntity, - TrashedLocalAssetEntity, + TrashSyncEntity, + ServerDeletedChecksumEntity, AssetEditEntity, SettingsEntity, AssetOcrEntity, @@ -120,7 +121,7 @@ class Drift extends $Drift { } @override - int get schemaVersion => 31; + int get schemaVersion => 32; @override MigrationStrategy get migration => MigrationStrategy( @@ -221,7 +222,7 @@ class Drift extends $Drift { await m.alterTable( TableMigration( v15.trashedLocalAssetEntity, - columnTransformer: {v15.trashedLocalAssetEntity.source: Constant(TrashOrigin.localSync.index)}, + columnTransformer: {v15.trashedLocalAssetEntity.source: const Constant(0)}, // TrashOrigin.localSync newColumns: [v15.trashedLocalAssetEntity.source], ), ); @@ -280,11 +281,7 @@ class Drift extends $Drift { durationMs: v23.remoteAssetEntity.durationMs * const Constant(1000), ), ); - await trashedLocalAssetEntity.update().write( - TrashedLocalAssetEntityCompanion.custom( - durationMs: v23.trashedLocalAssetEntity.durationMs * const Constant(1000), - ), - ); + await customStatement('UPDATE trashed_local_asset_entity SET duration_ms = duration_ms * 1000'); }, from23To24: (m, v24) async { await customStatement('DROP INDEX IF EXISTS idx_remote_album_owner_id'); @@ -318,6 +315,13 @@ class Drift extends $Drift { from30To31: (m, v31) async { await m.createIndex(v31.idxRemoteAssetUploaded); }, + from31To32: (m, v32) async { + await m.createTable(v32.trashSync); + await m.create(v32.idxTrashSyncChecksum); + await m.createTable(v32.serverDeletedChecksum); + await m.create(v32.idxRemoteAssetSoftDeletedChecksum); + await m.deleteTable('trashed_local_asset_entity'); + }, ), ), ); @@ -349,6 +353,8 @@ class DriftDatabaseRepository { const DriftDatabaseRepository(this._db); Future transaction(Future Function() callback) => _db.transaction(callback); + + BaseSelectStatement currentUserIdQuery() => _db.selectOnly(_db.authUserEntity)..addColumns([_db.authUserEntity.id]); } // ignore: invalid_use_of_internal_member diff --git a/mobile/lib/infrastructure/repositories/db.repository.drift.dart b/mobile/lib/infrastructure/repositories/db.repository.drift.dart index a5996716ed..a42c0dc7d7 100644 --- a/mobile/lib/infrastructure/repositories/db.repository.drift.dart +++ b/mobile/lib/infrastructure/repositories/db.repository.drift.dart @@ -39,17 +39,19 @@ import 'package:immich_mobile/infrastructure/entities/asset_face.entity.drift.da as i18; import 'package:immich_mobile/infrastructure/entities/store.entity.drift.dart' as i19; -import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.drift.dart' +import 'package:immich_mobile/infrastructure/entities/trash_sync.entity.drift.dart' as i20; -import 'package:immich_mobile/infrastructure/entities/asset_edit.entity.drift.dart' +import 'package:immich_mobile/infrastructure/entities/server_deleted_checksum.entity.drift.dart' as i21; -import 'package:immich_mobile/infrastructure/entities/settings.entity.drift.dart' +import 'package:immich_mobile/infrastructure/entities/asset_edit.entity.drift.dart' as i22; -import 'package:immich_mobile/infrastructure/entities/asset_ocr.entity.drift.dart' +import 'package:immich_mobile/infrastructure/entities/settings.entity.drift.dart' as i23; -import 'package:immich_mobile/infrastructure/entities/merged_asset.drift.dart' +import 'package:immich_mobile/infrastructure/entities/asset_ocr.entity.drift.dart' as i24; -import 'package:drift/internal/modular.dart' as i25; +import 'package:immich_mobile/infrastructure/entities/merged_asset.drift.dart' + as i25; +import 'package:drift/internal/modular.dart' as i26; abstract class $Drift extends i0.GeneratedDatabase { $Drift(i0.QueryExecutor e) : super(e); @@ -89,19 +91,21 @@ abstract class $Drift extends i0.GeneratedDatabase { late final i18.$AssetFaceEntityTable assetFaceEntity = i18 .$AssetFaceEntityTable(this); late final i19.$StoreEntityTable storeEntity = i19.$StoreEntityTable(this); - late final i20.$TrashedLocalAssetEntityTable trashedLocalAssetEntity = i20 - .$TrashedLocalAssetEntityTable(this); - late final i21.$AssetEditEntityTable assetEditEntity = i21 + late final i20.$TrashSyncEntityTable trashSyncEntity = i20 + .$TrashSyncEntityTable(this); + late final i21.$ServerDeletedChecksumEntityTable serverDeletedChecksumEntity = + i21.$ServerDeletedChecksumEntityTable(this); + late final i22.$AssetEditEntityTable assetEditEntity = i22 .$AssetEditEntityTable(this); - late final i22.$SettingsEntityTable settingsEntity = i22.$SettingsEntityTable( + late final i23.$SettingsEntityTable settingsEntity = i23.$SettingsEntityTable( this, ); - late final i23.$AssetOcrEntityTable assetOcrEntity = i23.$AssetOcrEntityTable( + late final i24.$AssetOcrEntityTable assetOcrEntity = i24.$AssetOcrEntityTable( this, ); - i24.MergedAssetDrift get mergedAssetDrift => i25.ReadDatabaseContainer( + i25.MergedAssetDrift get mergedAssetDrift => i26.ReadDatabaseContainer( this, - ).accessor(i24.MergedAssetDrift.new); + ).accessor(i25.MergedAssetDrift.new); @override Iterable> get allTables => allSchemaEntities.whereType>(); @@ -122,6 +126,7 @@ abstract class $Drift extends i0.GeneratedDatabase { i2.uQRemoteAssetsOwnerChecksum, i2.uQRemoteAssetsOwnerLibraryChecksum, i2.idxRemoteAssetChecksum, + i2.idxRemoteAssetSoftDeletedChecksum, i2.idxRemoteAssetStackId, i2.idxRemoteAssetOwnerVisibilityDeletedCreated, i2.idxRemoteAssetUploaded, @@ -137,7 +142,8 @@ abstract class $Drift extends i0.GeneratedDatabase { personEntity, assetFaceEntity, storeEntity, - trashedLocalAssetEntity, + trashSyncEntity, + serverDeletedChecksumEntity, assetEditEntity, settingsEntity, assetOcrEntity, @@ -150,10 +156,9 @@ abstract class $Drift extends i0.GeneratedDatabase { i18.idxAssetFacePersonId, i18.idxAssetFaceAssetId, i18.idxAssetFaceVisiblePerson, - i20.idxTrashedLocalAssetChecksum, - i20.idxTrashedLocalAssetAlbum, - i21.idxAssetEditAssetId, - i23.idxAssetOcrAssetId, + i20.idxTrashSyncChecksum, + i22.idxAssetEditAssetId, + i24.idxAssetOcrAssetId, ]; @override i0.StreamQueryUpdateRules @@ -404,15 +409,18 @@ class $DriftManager { i18.$$AssetFaceEntityTableTableManager(_db, _db.assetFaceEntity); i19.$$StoreEntityTableTableManager get storeEntity => i19.$$StoreEntityTableTableManager(_db, _db.storeEntity); - i20.$$TrashedLocalAssetEntityTableTableManager get trashedLocalAssetEntity => - i20.$$TrashedLocalAssetEntityTableTableManager( + i20.$$TrashSyncEntityTableTableManager get trashSyncEntity => + i20.$$TrashSyncEntityTableTableManager(_db, _db.trashSyncEntity); + i21.$$ServerDeletedChecksumEntityTableTableManager + get serverDeletedChecksumEntity => + i21.$$ServerDeletedChecksumEntityTableTableManager( _db, - _db.trashedLocalAssetEntity, + _db.serverDeletedChecksumEntity, ); - i21.$$AssetEditEntityTableTableManager get assetEditEntity => - i21.$$AssetEditEntityTableTableManager(_db, _db.assetEditEntity); - i22.$$SettingsEntityTableTableManager get settingsEntity => - i22.$$SettingsEntityTableTableManager(_db, _db.settingsEntity); - i23.$$AssetOcrEntityTableTableManager get assetOcrEntity => - i23.$$AssetOcrEntityTableTableManager(_db, _db.assetOcrEntity); + i22.$$AssetEditEntityTableTableManager get assetEditEntity => + i22.$$AssetEditEntityTableTableManager(_db, _db.assetEditEntity); + i23.$$SettingsEntityTableTableManager get settingsEntity => + i23.$$SettingsEntityTableTableManager(_db, _db.settingsEntity); + i24.$$AssetOcrEntityTableTableManager get assetOcrEntity => + i24.$$AssetOcrEntityTableTableManager(_db, _db.assetOcrEntity); } diff --git a/mobile/lib/infrastructure/repositories/db.repository.steps.dart b/mobile/lib/infrastructure/repositories/db.repository.steps.dart index 39f9c2ea04..25ae92cdb8 100644 --- a/mobile/lib/infrastructure/repositories/db.repository.steps.dart +++ b/mobile/lib/infrastructure/repositories/db.repository.steps.dart @@ -16506,6 +16506,632 @@ final class Schema31 extends i0.VersionedSchema { ); } +final class Schema32 extends i0.VersionedSchema { + Schema32({required super.database}) : super(version: 32); + @override + late final List entities = [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + remoteAlbumEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAlbumAssetAlbumAsset, + idxLocalAssetChecksum, + idxLocalAssetCloudId, + idxLocalAssetCreatedAt, + idxStackPrimaryAssetId, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + idxRemoteAssetSoftDeletedChecksum, + idxRemoteAssetStackId, + idxRemoteAssetOwnerVisibilityDeletedCreated, + idxRemoteAssetUploaded, + authUserEntity, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + remoteAssetCloudIdEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + storeEntity, + trashSync, + serverDeletedChecksum, + assetEditEntity, + settings, + assetOcrEntity, + idxPartnerSharedWithId, + idxLatLng, + idxRemoteExifCity, + idxRemoteAlbumAssetAlbumAsset, + idxRemoteAssetCloudId, + idxPersonOwnerId, + idxAssetFacePersonId, + idxAssetFaceAssetId, + idxAssetFaceVisiblePerson, + idxTrashSyncChecksum, + idxAssetEditAssetId, + idxAssetOcrAssetId, + ]; + late final Shape33 userEntity = Shape33( + source: i0.VersionedTable( + entityName: 'user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_108, + _column_109, + _column_110, + _column_111, + _column_112, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape50 remoteAssetEntity = Shape50( + source: i0.VersionedTable( + entityName: 'remote_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_108, + _column_113, + _column_114, + _column_115, + _column_116, + _column_117, + _column_118, + _column_107, + _column_119, + _column_120, + _column_121, + _column_122, + _column_123, + _column_124, + _column_212, + _column_125, + _column_126, + _column_127, + _column_128, + _column_129, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape35 stackEntity = Shape35( + source: i0.VersionedTable( + entityName: 'stack_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_114, + _column_115, + _column_121, + _column_130, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape36 localAssetEntity = Shape36( + source: i0.VersionedTable( + entityName: 'local_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_108, + _column_113, + _column_114, + _column_115, + _column_116, + _column_117, + _column_118, + _column_107, + _column_131, + _column_120, + _column_132, + _column_133, + _column_134, + _column_135, + _column_136, + _column_137, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape48 remoteAlbumEntity = Shape48( + source: i0.VersionedTable( + entityName: 'remote_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_108, + _column_138, + _column_114, + _column_115, + _column_139, + _column_140, + _column_141, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape38 localAlbumEntity = Shape38( + source: i0.VersionedTable( + entityName: 'local_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_108, + _column_115, + _column_142, + _column_143, + _column_144, + _column_145, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape39 localAlbumAssetEntity = Shape39( + source: i0.VersionedTable( + entityName: 'local_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_146, _column_147, _column_145], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxLocalAlbumAssetAlbumAsset = i1.Index( + 'idx_local_album_asset_album_asset', + 'CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)', + ); + final i1.Index idxLocalAssetChecksum = i1.Index( + 'idx_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + final i1.Index idxLocalAssetCloudId = i1.Index( + 'idx_local_asset_cloud_id', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)', + ); + final i1.Index idxLocalAssetCreatedAt = i1.Index( + 'idx_local_asset_created_at', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_created_at ON local_asset_entity (created_at)', + ); + final i1.Index idxStackPrimaryAssetId = i1.Index( + 'idx_stack_primary_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)', + ); + final i1.Index uQRemoteAssetsOwnerChecksum = i1.Index( + 'UQ_remote_assets_owner_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', + ); + final i1.Index uQRemoteAssetsOwnerLibraryChecksum = i1.Index( + 'UQ_remote_assets_owner_library_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)', + ); + final i1.Index idxRemoteAssetChecksum = i1.Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + final i1.Index idxRemoteAssetSoftDeletedChecksum = i1.Index( + 'idx_remote_asset_soft_deleted_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_soft_deleted_checksum ON remote_asset_entity (checksum) WHERE deleted_at IS NOT NULL', + ); + final i1.Index idxRemoteAssetStackId = i1.Index( + 'idx_remote_asset_stack_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)', + ); + final i1.Index idxRemoteAssetOwnerVisibilityDeletedCreated = i1.Index( + 'idx_remote_asset_owner_visibility_deleted_created', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created ON remote_asset_entity (owner_id, visibility, deleted_at, created_at DESC)', + ); + final i1.Index idxRemoteAssetUploaded = i1.Index( + 'idx_remote_asset_uploaded', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_uploaded ON remote_asset_entity (uploaded_at)', + ); + late final Shape40 authUserEntity = Shape40( + source: i0.VersionedTable( + entityName: 'auth_user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_108, + _column_109, + _column_148, + _column_110, + _column_111, + _column_149, + _column_150, + _column_151, + _column_152, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape4 userMetadataEntity = Shape4( + source: i0.VersionedTable( + entityName: 'user_metadata_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(user_id, "key")'], + columns: [_column_153, _column_154, _column_155], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape41 partnerEntity = Shape41( + source: i0.VersionedTable( + entityName: 'partner_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(shared_by_id, shared_with_id)'], + columns: [_column_156, _column_157, _column_158], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape42 remoteExifEntity = Shape42( + source: i0.VersionedTable( + entityName: 'remote_exif_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id)'], + columns: [ + _column_159, + _column_160, + _column_161, + _column_162, + _column_163, + _column_164, + _column_117, + _column_116, + _column_165, + _column_166, + _column_167, + _column_168, + _column_135, + _column_136, + _column_169, + _column_170, + _column_171, + _column_172, + _column_173, + _column_174, + _column_175, + _column_176, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape7 remoteAlbumAssetEntity = Shape7( + source: i0.VersionedTable( + entityName: 'remote_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_159, _column_177], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape10 remoteAlbumUserEntity = Shape10( + source: i0.VersionedTable( + entityName: 'remote_album_user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(album_id, user_id)'], + columns: [_column_177, _column_153, _column_178], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape43 remoteAssetCloudIdEntity = Shape43( + source: i0.VersionedTable( + entityName: 'remote_asset_cloud_id_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id)'], + columns: [ + _column_159, + _column_179, + _column_180, + _column_134, + _column_135, + _column_136, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape44 memoryEntity = Shape44( + source: i0.VersionedTable( + entityName: 'memory_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_114, + _column_115, + _column_124, + _column_121, + _column_113, + _column_181, + _column_182, + _column_183, + _column_184, + _column_185, + _column_186, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape12 memoryAssetEntity = Shape12( + source: i0.VersionedTable( + entityName: 'memory_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, memory_id)'], + columns: [_column_159, _column_187], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape45 personEntity = Shape45( + source: i0.VersionedTable( + entityName: 'person_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_114, + _column_115, + _column_121, + _column_108, + _column_188, + _column_189, + _column_190, + _column_191, + _column_192, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape46 assetFaceEntity = Shape46( + source: i0.VersionedTable( + entityName: 'asset_face_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_159, + _column_193, + _column_194, + _column_195, + _column_196, + _column_197, + _column_198, + _column_199, + _column_200, + _column_201, + _column_124, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape18 storeEntity = Shape18( + source: i0.VersionedTable( + entityName: 'store_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [_column_202, _column_203, _column_204], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape52 trashSync = Shape52( + source: i0.VersionedTable( + entityName: 'trash_sync', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id)'], + columns: [_column_225, _column_119, _column_226, _column_227], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape53 serverDeletedChecksum = Shape53( + source: i0.VersionedTable( + entityName: 'server_deleted_checksum', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(checksum)'], + columns: [_column_119], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape32 assetEditEntity = Shape32( + source: i0.VersionedTable( + entityName: 'asset_edit_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_159, + _column_207, + _column_208, + _column_209, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape49 settings = Shape49( + source: i0.VersionedTable( + entityName: 'settings', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY("key")'], + columns: [_column_210, _column_224, _column_115], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape51 assetOcrEntity = Shape51( + source: i0.VersionedTable( + entityName: 'asset_ocr_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_159, + _column_213, + _column_214, + _column_215, + _column_216, + _column_217, + _column_218, + _column_219, + _column_220, + _column_221, + _column_222, + _column_223, + _column_201, + ], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxPartnerSharedWithId = i1.Index( + 'idx_partner_shared_with_id', + 'CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)', + ); + final i1.Index idxLatLng = i1.Index( + 'idx_lat_lng', + 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', + ); + final i1.Index idxRemoteExifCity = i1.Index( + 'idx_remote_exif_city', + 'CREATE INDEX IF NOT EXISTS idx_remote_exif_city ON remote_exif_entity (city) WHERE city IS NOT NULL', + ); + final i1.Index idxRemoteAlbumAssetAlbumAsset = i1.Index( + 'idx_remote_album_asset_album_asset', + 'CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)', + ); + final i1.Index idxRemoteAssetCloudId = i1.Index( + 'idx_remote_asset_cloud_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)', + ); + final i1.Index idxPersonOwnerId = i1.Index( + 'idx_person_owner_id', + 'CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)', + ); + final i1.Index idxAssetFacePersonId = i1.Index( + 'idx_asset_face_person_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)', + ); + final i1.Index idxAssetFaceAssetId = i1.Index( + 'idx_asset_face_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)', + ); + final i1.Index idxAssetFaceVisiblePerson = i1.Index( + 'idx_asset_face_visible_person', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_visible_person ON asset_face_entity (person_id, asset_id) WHERE is_visible = 1 AND deleted_at IS NULL', + ); + final i1.Index idxTrashSyncChecksum = i1.Index( + 'idx_trash_sync_checksum', + 'CREATE INDEX IF NOT EXISTS idx_trash_sync_checksum ON trash_sync (checksum)', + ); + final i1.Index idxAssetEditAssetId = i1.Index( + 'idx_asset_edit_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)', + ); + final i1.Index idxAssetOcrAssetId = i1.Index( + 'idx_asset_ocr_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_ocr_asset_id ON asset_ocr_entity (asset_id)', + ); +} + +class Shape52 extends i0.VersionedTable { + Shape52({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get assetId => + columnsByName['asset_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get checksum => + columnsByName['checksum']! as i1.GeneratedColumn; + i1.GeneratedColumn get status => + columnsByName['status']! as i1.GeneratedColumn; + i1.GeneratedColumn get assetUpdatedAt => + columnsByName['asset_updated_at']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_225(String aliasedName) => + i1.GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: i1.DriftSqlType.string, + $customConstraints: 'NOT NULL', + ); +i1.GeneratedColumn _column_226(String aliasedName) => + i1.GeneratedColumn( + 'status', + aliasedName, + false, + type: i1.DriftSqlType.int, + $customConstraints: 'NOT NULL DEFAULT 0', + defaultValue: const i1.CustomExpression('0'), + ); +i1.GeneratedColumn _column_227(String aliasedName) => + i1.GeneratedColumn( + 'asset_updated_at', + aliasedName, + true, + type: i1.DriftSqlType.string, + $customConstraints: 'NULL', + ); + +class Shape53 extends i0.VersionedTable { + Shape53({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get checksum => + columnsByName['checksum']! as i1.GeneratedColumn; +} + i0.MigrationStepWithVersion migrationSteps({ required Future Function(i1.Migrator m, Schema2 schema) from1To2, required Future Function(i1.Migrator m, Schema3 schema) from2To3, @@ -16537,6 +17163,7 @@ i0.MigrationStepWithVersion migrationSteps({ required Future Function(i1.Migrator m, Schema29 schema) from28To29, required Future Function(i1.Migrator m, Schema30 schema) from29To30, required Future Function(i1.Migrator m, Schema31 schema) from30To31, + required Future Function(i1.Migrator m, Schema32 schema) from31To32, }) { return (currentVersion, database) async { switch (currentVersion) { @@ -16690,6 +17317,11 @@ i0.MigrationStepWithVersion migrationSteps({ final migrator = i1.Migrator(database, schema); await from30To31(migrator, schema); return 31; + case 31: + final schema = Schema32(database: database); + final migrator = i1.Migrator(database, schema); + await from31To32(migrator, schema); + return 32; default: throw ArgumentError.value('Unknown migration from $currentVersion'); } @@ -16727,6 +17359,7 @@ i1.OnUpgrade stepByStep({ required Future Function(i1.Migrator m, Schema29 schema) from28To29, required Future Function(i1.Migrator m, Schema30 schema) from29To30, required Future Function(i1.Migrator m, Schema31 schema) from30To31, + required Future Function(i1.Migrator m, Schema32 schema) from31To32, }) => i0.VersionedSchema.stepByStepHelper( step: migrationSteps( from1To2: from1To2, @@ -16759,5 +17392,6 @@ i1.OnUpgrade stepByStep({ from28To29: from28To29, from29To30: from29To30, from30To31: from30To31, + from31To32: from31To32, ), ); diff --git a/mobile/lib/infrastructure/repositories/settings.repository.dart b/mobile/lib/infrastructure/repositories/settings.repository.dart index 7063779336..decdb2fd9d 100644 --- a/mobile/lib/infrastructure/repositories/settings.repository.dart +++ b/mobile/lib/infrastructure/repositories/settings.repository.dart @@ -1,4 +1,5 @@ import 'package:drift/drift.dart'; +import 'package:flutter/foundation.dart'; import 'package:immich_mobile/domain/models/config/app_config.dart'; import 'package:immich_mobile/domain/models/settings_key.dart'; import 'package:immich_mobile/infrastructure/entities/settings.entity.drift.dart'; @@ -29,6 +30,12 @@ class SettingsRepository extends CachedKeyValueRepository reset() async { + await _instance?.clear(SettingsKey.values); + _instance = null; + } + @override List get keys => SettingsKey.values; diff --git a/mobile/lib/infrastructure/repositories/sync_stream.repository.dart b/mobile/lib/infrastructure/repositories/sync_stream.repository.dart index bdfb1942ab..4e044b01f1 100644 --- a/mobile/lib/infrastructure/repositories/sync_stream.repository.dart +++ b/mobile/lib/infrastructure/repositories/sync_stream.repository.dart @@ -71,6 +71,8 @@ class SyncStreamRepository extends DriftDatabaseRepository { await _db.remoteAssetCloudIdEntity.deleteAll(); await _db.assetEditEntity.deleteAll(); await _db.assetOcrEntity.deleteAll(); + await _db.trashSyncEntity.deleteAll(); + await _db.serverDeletedChecksumEntity.deleteAll(); }); } finally { // re-enable FK even if the transaction throws, otherwise the connection diff --git a/mobile/lib/infrastructure/repositories/trash_sync.repository.dart b/mobile/lib/infrastructure/repositories/trash_sync.repository.dart new file mode 100644 index 0000000000..5313e44db8 --- /dev/null +++ b/mobile/lib/infrastructure/repositories/trash_sync.repository.dart @@ -0,0 +1,280 @@ +import 'package:collection/collection.dart'; +import 'package:drift/drift.dart'; +import 'package:immich_mobile/constants/constants.dart'; +import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart'; +import 'package:immich_mobile/infrastructure/entities/server_deleted_checksum.entity.drift.dart'; +import 'package:immich_mobile/infrastructure/entities/trash_sync.entity.drift.dart'; +import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; + +class DriftTrashSyncRepository extends DriftDatabaseRepository { + final Drift _db; + + const DriftTrashSyncRepository(this._db) : super(_db); + + // Prunes asset markers for assets that are now live on the server + Future pruneStaleMarkers() async { + final liveChecksums = _db.selectOnly(_db.remoteAssetEntity) + ..addColumns([_db.remoteAssetEntity.checksum]) + ..where(_db.remoteAssetEntity.deletedAt.isNull() & _db.remoteAssetEntity.ownerId.isInQuery(currentUserIdQuery())); + + await _db.transaction(() async { + await (_db.delete(_db.serverDeletedChecksumEntity)..where((t) => t.checksum.isInQuery(liveChecksums))).go(); + await (_db.delete( + _db.trashSyncEntity, + )..where((t) => t.checksum.isInQuery(liveChecksums) & t.status.equalsValue(.pending))).go(); + }); + } + + // Prunes dismissed assets marker for assets that are not on the server anymore + Future pruneDismissedMarkers() async { + final softDeletedChecksums = _db.selectOnly(_db.remoteAssetEntity) + ..addColumns([_db.remoteAssetEntity.checksum]) + ..where( + _db.remoteAssetEntity.deletedAt.isNotNull() & _db.remoteAssetEntity.ownerId.isInQuery(currentUserIdQuery()), + ); + + final serverDeletedChecksums = _db.selectOnly(_db.serverDeletedChecksumEntity) + ..addColumns([_db.serverDeletedChecksumEntity.checksum]); + + await (_db.delete(_db.trashSyncEntity)..where( + (t) => + t.status.equalsValue(.dismissed) & + t.checksum.isNotInQuery(softDeletedChecksums) & + t.checksum.isNotInQuery(serverDeletedChecksums), + )) + .go(); + } + + // Prunes pending markers for assets that has been modified locally + Future prunePendingMarkers() async { + final staleIds = _db.selectOnly(_db.trashSyncEntity) + ..addColumns([_db.trashSyncEntity.assetId]) + ..join([ + innerJoin( + _db.localAssetEntity, + _db.localAssetEntity.id.equalsExp(_db.trashSyncEntity.assetId), + useColumns: false, + ), + ]) + ..where( + _db.trashSyncEntity.status.equalsValue(.pending) & + _db.localAssetEntity.checksum.isNotNull() & + _db.localAssetEntity.checksum.equalsExp(_db.trashSyncEntity.checksum).not(), + ); + await (_db.delete(_db.trashSyncEntity)..where((t) => t.assetId.isInQuery(staleIds))).go(); + } + + Future recordHardDeletedChecksums(Iterable remoteIds) async { + for (final slice in remoteIds.toSet().slices(kDriftMaxChunk)) { + final checksums = + await (_db.selectOnly(_db.remoteAssetEntity, distinct: true) + ..addColumns([_db.remoteAssetEntity.checksum]) + ..where( + _db.remoteAssetEntity.id.isIn(slice) & _db.remoteAssetEntity.ownerId.isInQuery(currentUserIdQuery()), + )) + .map((r) => r.read(_db.remoteAssetEntity.checksum)!) + .get(); + if (checksums.isEmpty) { + continue; + } + + await _db.batch((batch) { + for (final checksum in checksums) { + batch.insert( + _db.serverDeletedChecksumEntity, + ServerDeletedChecksumEntityCompanion.insert(checksum: checksum), + onConflict: DoNothing(), + ); + } + }); + } + } + + Future recordSoftDeleteAssets() => _recordAssets( + innerJoin( + _db.remoteAssetEntity, + _db.remoteAssetEntity.checksum.equalsExp(_db.localAssetEntity.checksum) & + _db.remoteAssetEntity.deletedAt.isNotNull() & + _db.remoteAssetEntity.ownerId.isInQuery(currentUserIdQuery()), + useColumns: false, + ), + ); + + Future recordHardDeletedAssets() => _recordAssets( + innerJoin( + _db.serverDeletedChecksumEntity, + _db.serverDeletedChecksumEntity.checksum.equalsExp(_db.localAssetEntity.checksum), + useColumns: false, + ), + ); + + Future _recordAssets(Join contentJoin) async { + final excludedAssetIds = _db.selectOnly(_db.localAlbumAssetEntity) + ..addColumns([_db.localAlbumAssetEntity.assetId]) + ..join([ + innerJoin( + _db.localAlbumEntity, + _db.localAlbumEntity.id.equalsExp(_db.localAlbumAssetEntity.albumId), + useColumns: false, + ), + ]) + ..where(_db.localAlbumEntity.backupSelection.equalsValue(.excluded)); + + final selectedAssetsQuery = _db.selectOnly(_db.localAlbumAssetEntity) + ..addColumns([_db.localAlbumAssetEntity.assetId]) + ..where( + _db.localAlbumAssetEntity.assetId.equalsExp(_db.localAssetEntity.id) & + _db.localAlbumAssetEntity.albumId.isInQuery( + _db.selectOnly(_db.localAlbumEntity) + ..addColumns([_db.localAlbumEntity.id]) + ..where(_db.localAlbumEntity.backupSelection.equalsValue(.selected)), + ), + ); + + final dismissedAssetsQuery = _db.selectOnly(_db.trashSyncEntity) + ..addColumns([_db.trashSyncEntity.assetId]) + ..where( + _db.trashSyncEntity.checksum.equalsExp(_db.localAssetEntity.checksum) & + _db.trashSyncEntity.status.equalsValue(.dismissed), + ); + + final source = _db.selectOnly(_db.localAssetEntity) + ..addColumns([_db.localAssetEntity.id, _db.localAssetEntity.checksum, _db.localAssetEntity.updatedAt]) + ..join([contentJoin]) + ..where( + _db.localAssetEntity.checksum.isNotNull() & + existsQuery(selectedAssetsQuery) & + _db.localAssetEntity.id.isNotInQuery(excludedAssetIds) & + notExistsQuery(dismissedAssetsQuery), + ); + + await _db + .into(_db.trashSyncEntity) + .insertFromSelect( + source, + columns: { + _db.trashSyncEntity.assetId: _db.localAssetEntity.id, + _db.trashSyncEntity.checksum: _db.localAssetEntity.checksum, + _db.trashSyncEntity.assetUpdatedAt: _db.localAssetEntity.updatedAt, + }, + mode: .insertOrIgnore, + ); + } + + Future markTrashed(Iterable assetIds) async { + final set = assetIds.toSet(); + if (set.isEmpty) { + return; + } + await _db.transaction(() async { + for (final slice in set.slices(kDriftMaxChunk)) { + await (_db.update( + _db.trashSyncEntity, + )..where((t) => t.assetId.isIn(slice))).write(const TrashSyncEntityCompanion(status: .new(.trashed))); + await (_db.delete(_db.localAssetEntity)..where((t) => t.id.isIn(slice))).go(); + } + }); + } + + Future markRestored(Iterable assetIds) async { + final set = assetIds.toSet(); + if (set.isEmpty) { + return; + } + for (final slice in set.slices(kDriftMaxChunk)) { + await (_db.update( + _db.trashSyncEntity, + )..where((t) => t.assetId.isIn(slice))).write(const TrashSyncEntityCompanion(status: .new(.restored))); + } + } + + Future restoreChecksums() async { + final restored = await (_db.select(_db.trashSyncEntity)..where((t) => t.status.equalsValue(.restored))).get(); + if (restored.isEmpty) { + return; + } + + await _db.batch((batch) { + for (final row in restored) { + final assetUpdatedAt = row.assetUpdatedAt; + if (assetUpdatedAt == null) { + continue; + } + + batch.update( + _db.localAssetEntity, + LocalAssetEntityCompanion(checksum: .new(row.checksum)), + where: (t) => t.id.equals(row.assetId) & t.checksum.isNull() & t.updatedAt.equals(assetUpdatedAt), + ); + } + + batch.deleteWhere(_db.trashSyncEntity, (t) => t.status.equalsValue(.restored)); + }); + } + + // Mark assets that were previously marked as trashed but are now live on the device as dismissed + Future reconcileTrashed(Iterable assetIds) async { + final set = assetIds.toSet(); + if (set.isEmpty) { + return; + } + + JoinedSelectStatement localAssetQuery($TrashSyncEntityTable trash) => _db.selectOnly(_db.localAssetEntity) + ..addColumns([_db.localAssetEntity.id]) + ..where(_db.localAssetEntity.id.equalsExp(trash.assetId)); + + await _db.transaction(() async { + for (final slice in set.slices(kDriftMaxChunk)) { + await (_db.update(_db.trashSyncEntity)..where((t) => t.assetId.isIn(slice) & existsQuery(localAssetQuery(t)))) + .write(const TrashSyncEntityCompanion(status: .new(.dismissed))); + + await (_db.delete( + _db.trashSyncEntity, + )..where((t) => t.assetId.isIn(slice) & notExistsQuery(localAssetQuery(t)))).go(); + } + }); + } + + Future deleteMarkers(Iterable assetIds) async { + final set = assetIds.toSet(); + if (set.isEmpty) { + return; + } + for (final slice in set.slices(kDriftMaxChunk)) { + await (_db.delete(_db.trashSyncEntity)..where((t) => t.assetId.isIn(slice))).go(); + } + } + + Future> getPendingAssetIds() => + _trashSyncAssetIdsWhere(_db.trashSyncEntity.status.equalsValue(.pending)); + + Future> getTrashedAssetIds() => + _trashSyncAssetIdsWhere(_db.trashSyncEntity.status.equalsValue(.trashed)); + + Future> _trashSyncAssetIdsWhere(Expression filter) { + return (_db.selectOnly(_db.trashSyncEntity) + ..addColumns([_db.trashSyncEntity.assetId]) + ..where(filter)) + .map((row) => row.read(_db.trashSyncEntity.assetId)!) + .get(); + } + + Future> getRestorableAssetIds() { + return (_db.selectOnly(_db.trashSyncEntity, distinct: true) + ..addColumns([_db.trashSyncEntity.assetId]) + ..join([ + innerJoin( + _db.remoteAssetEntity, + _db.remoteAssetEntity.checksum.equalsExp(_db.trashSyncEntity.checksum), + useColumns: false, + ), + ]) + ..where( + _db.trashSyncEntity.status.equalsValue(.trashed) & + _db.remoteAssetEntity.deletedAt.isNull() & + _db.remoteAssetEntity.ownerId.isInQuery(currentUserIdQuery()), + )) + .map((row) => row.read(_db.trashSyncEntity.assetId)!) + .get(); + } +} diff --git a/mobile/lib/infrastructure/repositories/trashed_local_asset.repository.dart b/mobile/lib/infrastructure/repositories/trashed_local_asset.repository.dart deleted file mode 100644 index 08712588d9..0000000000 --- a/mobile/lib/infrastructure/repositories/trashed_local_asset.repository.dart +++ /dev/null @@ -1,311 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:drift/drift.dart'; -import 'package:immich_mobile/constants/constants.dart'; -import 'package:immich_mobile/domain/models/album/local_album.model.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart'; -import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart'; -import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.dart'; -import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.drift.dart'; -import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; - -typedef TrashedAsset = ({String albumId, LocalAsset asset}); - -class DriftTrashedLocalAssetRepository extends DriftDatabaseRepository { - final Drift _db; - - const DriftTrashedLocalAssetRepository(this._db) : super(_db); - - Future updateHashes(Map hashes) { - if (hashes.isEmpty) { - return Future.value(); - } - return _db.batch((batch) async { - for (final entry in hashes.entries) { - batch.update( - _db.trashedLocalAssetEntity, - TrashedLocalAssetEntityCompanion(checksum: Value(entry.value)), - where: (e) => e.id.equals(entry.key), - ); - } - }); - } - - Future> getAssetsToHash(Iterable albumIds) { - final query = _db.trashedLocalAssetEntity.select()..where((r) => r.albumId.isIn(albumIds) & r.checksum.isNull()); - return query.map((row) => row.toLocalAsset()).get(); - } - - Future> getToRestore() async { - final selectedAlbumIds = (_db.selectOnly(_db.localAlbumEntity) - ..addColumns([_db.localAlbumEntity.id]) - ..where(_db.localAlbumEntity.backupSelection.equalsValue(BackupSelection.selected))); - - final rows = - await (_db.select(_db.trashedLocalAssetEntity).join([ - innerJoin( - _db.remoteAssetEntity, - _db.remoteAssetEntity.checksum.equalsExp(_db.trashedLocalAssetEntity.checksum), - ), - ])..where( - _db.trashedLocalAssetEntity.source.equalsValue(TrashOrigin.remoteSync) & - _db.trashedLocalAssetEntity.albumId.isInQuery(selectedAlbumIds) & - _db.remoteAssetEntity.deletedAt.isNull(), - )) - .get(); - - return rows.map((result) => result.readTable(_db.trashedLocalAssetEntity).toLocalAsset()); - } - - /// Applies resulted snapshot of trashed assets: - /// - upserts incoming rows - /// - deletes rows that are not present in the snapshot - Future processTrashSnapshot(Iterable trashedAssets) async { - if (trashedAssets.isEmpty) { - await _db.delete(_db.trashedLocalAssetEntity).go(); - return; - } - final assetIds = trashedAssets.map((e) => e.asset.id).toSet(); - Map localChecksumById = await _getCachedChecksums(assetIds); - - return _db.transaction(() async { - await _db.batch((batch) { - for (final item in trashedAssets) { - final effectiveChecksum = localChecksumById[item.asset.id] ?? item.asset.checksum; - final companion = TrashedLocalAssetEntityCompanion.insert( - id: item.asset.id, - albumId: item.albumId, - checksum: Value(effectiveChecksum), - name: item.asset.name, - type: item.asset.type, - createdAt: Value(item.asset.createdAt), - updatedAt: Value(item.asset.updatedAt), - width: Value(item.asset.width), - height: Value(item.asset.height), - durationMs: Value(item.asset.durationMs), - isFavorite: Value(item.asset.isFavorite), - orientation: Value(item.asset.orientation), - playbackStyle: Value(item.asset.playbackStyle), - source: TrashOrigin.localSync, - ); - - batch.insert<$TrashedLocalAssetEntityTable, TrashedLocalAssetEntityData>( - _db.trashedLocalAssetEntity, - companion, - onConflict: DoUpdate((_) => companion, where: (old) => old.updatedAt.isNotValue(item.asset.updatedAt)), - ); - } - }); - - if (assetIds.length <= kDriftMaxChunk) { - await (_db.delete(_db.trashedLocalAssetEntity)..where((row) => row.id.isNotIn(assetIds))).go(); - } else { - final existingIds = await (_db.selectOnly( - _db.trashedLocalAssetEntity, - )..addColumns([_db.trashedLocalAssetEntity.id])).map((r) => r.read(_db.trashedLocalAssetEntity.id)!).get(); - final idToDelete = existingIds.where((id) => !assetIds.contains(id)); - for (final slice in idToDelete.slices(kDriftMaxChunk)) { - await (_db.delete(_db.trashedLocalAssetEntity)..where((t) => t.id.isIn(slice))).go(); - } - } - }); - } - - Stream watchCount() { - return (_db.selectOnly(_db.trashedLocalAssetEntity)..addColumns([_db.trashedLocalAssetEntity.id.count()])) - .watchSingle() - .map((row) => row.read(_db.trashedLocalAssetEntity.id.count()) ?? 0); - } - - Stream watchHashedCount() { - return (_db.selectOnly(_db.trashedLocalAssetEntity) - ..addColumns([_db.trashedLocalAssetEntity.id.count()]) - ..where(_db.trashedLocalAssetEntity.checksum.isNotNull())) - .watchSingle() - .map((row) => row.read(_db.trashedLocalAssetEntity.id.count()) ?? 0); - } - - Future trashLocalAsset(Map> assetsByAlbums) async { - if (assetsByAlbums.isEmpty) { - return Future.value(); - } - - final companions = []; - final idToDelete = {}; - - for (final entry in assetsByAlbums.entries) { - for (final asset in entry.value) { - idToDelete.add(asset.id); - companions.add( - TrashedLocalAssetEntityCompanion( - id: Value(asset.id), - name: Value(asset.name), - albumId: Value(entry.key), - checksum: Value(asset.checksum), - type: Value(asset.type), - width: Value(asset.width), - height: Value(asset.height), - durationMs: Value(asset.durationMs), - isFavorite: Value(asset.isFavorite), - orientation: Value(asset.orientation), - playbackStyle: Value(asset.playbackStyle), - createdAt: Value(asset.createdAt), - updatedAt: Value(asset.updatedAt), - source: const Value(TrashOrigin.remoteSync), - ), - ); - } - } - - await _db.transaction(() async { - for (final companion in companions) { - await _db.into(_db.trashedLocalAssetEntity).insertOnConflictUpdate(companion); - } - - for (final slice in idToDelete.slices(kDriftMaxChunk)) { - await (_db.delete(_db.localAssetEntity)..where((t) => t.id.isIn(slice))).go(); - } - }); - } - - Future applyRestoredAssets(List idList) async { - if (idList.isEmpty) { - return Future.value(); - } - - final trashedAssets = []; - - for (final slice in idList.slices(kDriftMaxChunk)) { - final q = _db.select(_db.trashedLocalAssetEntity)..where((t) => t.id.isIn(slice)); - trashedAssets.addAll(await q.get()); - } - - if (trashedAssets.isEmpty) { - return; - } - - final companions = trashedAssets.map((e) { - return LocalAssetEntityCompanion.insert( - id: e.id, - name: e.name, - type: e.type, - createdAt: Value(e.createdAt), - updatedAt: Value(e.updatedAt), - width: Value(e.width), - height: Value(e.height), - durationMs: Value(e.durationMs), - checksum: Value(e.checksum), - isFavorite: Value(e.isFavorite), - orientation: Value(e.orientation), - playbackStyle: Value(e.playbackStyle), - ); - }); - - await _db.transaction(() async { - for (final companion in companions) { - await _db.into(_db.localAssetEntity).insertOnConflictUpdate(companion); - } - for (final slice in idList.slices(kDriftMaxChunk)) { - await (_db.delete(_db.trashedLocalAssetEntity)..where((t) => t.id.isIn(slice))).go(); - } - }); - } - - Future applyTrashedAssets(List idList) async { - if (idList.isEmpty) { - return Future.value(); - } - - final trashedAssets = <({LocalAssetEntityData asset, String albumId})>[]; - - for (final slice in idList.slices(kDriftMaxChunk)) { - final rows = await (_db.select(_db.localAlbumAssetEntity).join([ - innerJoin(_db.localAssetEntity, _db.localAlbumAssetEntity.assetId.equalsExp(_db.localAssetEntity.id)), - ])..where(_db.localAlbumAssetEntity.assetId.isIn(slice))).get(); - - final assetsWithAlbum = rows.map( - (row) => - (albumId: row.readTable(_db.localAlbumAssetEntity).albumId, asset: row.readTable(_db.localAssetEntity)), - ); - - trashedAssets.addAll(assetsWithAlbum); - } - - if (trashedAssets.isEmpty) { - return; - } - - final companions = trashedAssets.map((e) { - return TrashedLocalAssetEntityCompanion.insert( - id: e.asset.id, - name: e.asset.name, - type: e.asset.type, - createdAt: Value(e.asset.createdAt), - updatedAt: Value(e.asset.updatedAt), - width: Value(e.asset.width), - height: Value(e.asset.height), - durationMs: Value(e.asset.durationMs), - checksum: Value(e.asset.checksum), - isFavorite: Value(e.asset.isFavorite), - orientation: Value(e.asset.orientation), - playbackStyle: Value(e.asset.playbackStyle), - source: TrashOrigin.localUser, - albumId: e.albumId, - ); - }); - - await _db.transaction(() async { - for (final companion in companions) { - await _db.into(_db.trashedLocalAssetEntity).insertOnConflictUpdate(companion); - } - for (final slice in idList.slices(kDriftMaxChunk)) { - await (_db.delete(_db.localAssetEntity)..where((t) => t.id.isIn(slice))).go(); - } - }); - } - - Future>> getToTrash() async { - final result = >{}; - - final rows = - await (_db.select(_db.localAlbumAssetEntity).join([ - innerJoin(_db.localAlbumEntity, _db.localAlbumAssetEntity.albumId.equalsExp(_db.localAlbumEntity.id)), - innerJoin(_db.localAssetEntity, _db.localAlbumAssetEntity.assetId.equalsExp(_db.localAssetEntity.id)), - leftOuterJoin( - _db.remoteAssetEntity, - _db.remoteAssetEntity.checksum.equalsExp(_db.localAssetEntity.checksum), - ), - ])..where( - _db.localAlbumEntity.backupSelection.equalsValue(BackupSelection.selected) & - _db.remoteAssetEntity.deletedAt.isNotNull(), - )) - .get(); - - for (final row in rows) { - final albumId = row.readTable(_db.localAlbumAssetEntity).albumId; - final asset = row.readTable(_db.localAssetEntity).toDto(); - (result[albumId] ??= []).add(asset); - } - - return result; - } - - //attempt to reuse existing checksums - Future> _getCachedChecksums(Set assetIds) async { - final localChecksumById = {}; - - for (final slice in assetIds.slices(kDriftMaxChunk)) { - final rows = - await (_db.selectOnly(_db.localAssetEntity) - ..where(_db.localAssetEntity.id.isIn(slice) & _db.localAssetEntity.checksum.isNotNull()) - ..addColumns([_db.localAssetEntity.id, _db.localAssetEntity.checksum])) - .get(); - - for (final r in rows) { - localChecksumById[r.read(_db.localAssetEntity.id)!] = r.read(_db.localAssetEntity.checksum)!; - } - } - - return localChecksumById; - } -} diff --git a/mobile/lib/pages/common/splash_screen.page.dart b/mobile/lib/pages/common/splash_screen.page.dart index 0d423875cb..f1b693acac 100644 --- a/mobile/lib/pages/common/splash_screen.page.dart +++ b/mobile/lib/pages/common/splash_screen.page.dart @@ -333,6 +333,7 @@ class SplashScreenPageState extends ConsumerState { // TODO: Bring back when the soft freeze issue is addressed // backgroundManager.syncCloudIds(), ]); + await backgroundManager.syncTrash(); } else { await backgroundManager.hashAssets(); } diff --git a/mobile/lib/platform/asset_media_api.g.dart b/mobile/lib/platform/asset_media_api.g.dart new file mode 100644 index 0000000000..59c5b5dca5 --- /dev/null +++ b/mobile/lib/platform/asset_media_api.g.dart @@ -0,0 +1,228 @@ +// Autogenerated from Pigeon (v26.3.4), do not edit directly. +// See also: https://pub.dev/packages/pigeon +// ignore_for_file: unused_import, unused_shown_name +// ignore_for_file: type=lint + +import 'dart:async'; +import 'dart:typed_data' show Float64List, Int32List, Int64List; + +import 'package:flutter/services.dart'; +import 'package:meta/meta.dart' show immutable, protected, visibleForTesting; + +Object? _extractReplyValueOrThrow(List? replyList, String channelName, {required bool isNullValid}) { + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel: "$channelName".', + ); + } else if (replyList.length > 1) { + throw PlatformException(code: replyList[0]! as String, message: replyList[1] as String?, details: replyList[2]); + } else if (!isNullValid && (replyList.isNotEmpty && replyList[0] == null)) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } + return replyList.firstOrNull; +} + +bool _deepEquals(Object? a, Object? b) { + if (identical(a, b)) { + return true; + } + if (a is double && b is double) { + if (a.isNaN && b.isNaN) { + return true; + } + return a == b; + } + if (a is List && b is List) { + return a.length == b.length && a.indexed.every(((int, dynamic) item) => _deepEquals(item.$2, b[item.$1])); + } + if (a is Map && b is Map) { + if (a.length != b.length) { + return false; + } + for (final MapEntry entryA in a.entries) { + bool found = false; + for (final MapEntry entryB in b.entries) { + if (_deepEquals(entryA.key, entryB.key)) { + if (_deepEquals(entryA.value, entryB.value)) { + found = true; + break; + } else { + return false; + } + } + } + if (!found) { + return false; + } + } + return true; + } + return a == b; +} + +int _deepHash(Object? value) { + if (value is List) { + return Object.hashAll(value.map(_deepHash)); + } + if (value is Map) { + int result = 0; + for (final MapEntry entry in value.entries) { + result += (_deepHash(entry.key) * 31) ^ _deepHash(entry.value); + } + return result; + } + if (value is double && value.isNaN) { + // Normalize NaN to a consistent hash. + return 0x7FF8000000000000.hashCode; + } + if (value is double && value == 0.0) { + // Normalize -0.0 to 0.0 so they have the same hash code. + return 0.0.hashCode; + } + return value.hashCode; +} + +enum AssetMediaActionStatus { done, alreadyInState, notFound, failed } + +class AssetMediaActionResult { + AssetMediaActionResult({required this.id, required this.status}); + + String id; + + AssetMediaActionStatus status; + + List _toList() { + return [id, status]; + } + + Object encode() { + return _toList(); + } + + static AssetMediaActionResult decode(Object result) { + result as List; + return AssetMediaActionResult(id: result[0]! as String, status: result[1]! as AssetMediaActionStatus); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + bool operator ==(Object other) { + if (other is! AssetMediaActionResult || other.runtimeType != runtimeType) { + return false; + } + if (identical(this, other)) { + return true; + } + return _deepEquals(id, other.id) && _deepEquals(status, other.status); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + int get hashCode => _deepHash([runtimeType, ..._toList()]); +} + +class _PigeonCodec extends StandardMessageCodec { + const _PigeonCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is int) { + buffer.putUint8(4); + buffer.putInt64(value); + } else if (value is AssetMediaActionStatus) { + buffer.putUint8(129); + writeValue(buffer, value.index); + } else if (value is AssetMediaActionResult) { + buffer.putUint8(130); + writeValue(buffer, value.encode()); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 129: + final value = readValue(buffer) as int?; + return value == null ? null : AssetMediaActionStatus.values[value]; + case 130: + return AssetMediaActionResult.decode(readValue(buffer)!); + default: + return super.readValueOfType(type, buffer); + } + } +} + +class AssetMediaApi { + /// Constructor for [AssetMediaApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + AssetMediaApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + : pigeonVar_binaryMessenger = binaryMessenger, + pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + final BinaryMessenger? pigeonVar_binaryMessenger; + + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); + + final String pigeonVar_messageChannelSuffix; + + Future> trash(List ids) async { + final pigeonVar_channelName = 'dev.flutter.pigeon.immich_mobile.AssetMediaApi.trash$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([ids]); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ); + return (pigeonVar_replyValue! as List).cast(); + } + + Future> restore(List ids) async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.AssetMediaApi.restore$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([ids]); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ); + return (pigeonVar_replyValue! as List).cast(); + } + + Future> trashedAmong(List ids) async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.AssetMediaApi.trashedAmong$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([ids]); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ); + return (pigeonVar_replyValue! as List).cast(); + } +} diff --git a/mobile/lib/platform/native_sync_api.g.dart b/mobile/lib/platform/native_sync_api.g.dart index bd979af87b..5c25a21b9e 100644 --- a/mobile/lib/platform/native_sync_api.g.dart +++ b/mobile/lib/platform/native_sync_api.g.dart @@ -649,44 +649,6 @@ class NativeSyncApi { _extractReplyValueOrThrow(pigeonVar_replyList, pigeonVar_channelName, isNullValid: true); } - Future>> getTrashedAssets() async { - final pigeonVar_channelName = - 'dev.flutter.pigeon.immich_mobile.NativeSyncApi.getTrashedAssets$pigeonVar_messageChannelSuffix'; - final pigeonVar_channel = BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); - final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); - final pigeonVar_replyList = await pigeonVar_sendFuture as List?; - - final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( - pigeonVar_replyList, - pigeonVar_channelName, - isNullValid: false, - ); - return (pigeonVar_replyValue! as Map).cast>(); - } - - Future restoreFromTrashById(String mediaId, int type) async { - final pigeonVar_channelName = - 'dev.flutter.pigeon.immich_mobile.NativeSyncApi.restoreFromTrashById$pigeonVar_messageChannelSuffix'; - final pigeonVar_channel = BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); - final Future pigeonVar_sendFuture = pigeonVar_channel.send([mediaId, type]); - final pigeonVar_replyList = await pigeonVar_sendFuture as List?; - - final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( - pigeonVar_replyList, - pigeonVar_channelName, - isNullValid: false, - ); - return pigeonVar_replyValue! as bool; - } - Future> getCloudIdForAssetIds(List assetIds) async { final pigeonVar_channelName = 'dev.flutter.pigeon.immich_mobile.NativeSyncApi.getCloudIdForAssetIds$pigeonVar_messageChannelSuffix'; diff --git a/mobile/lib/providers/app_life_cycle.provider.dart b/mobile/lib/providers/app_life_cycle.provider.dart index ad0940b776..3b04f312b0 100644 --- a/mobile/lib/providers/app_life_cycle.provider.dart +++ b/mobile/lib/providers/app_life_cycle.provider.dart @@ -126,6 +126,7 @@ class AppLifeCycleNotifier extends StateNotifier { // TODO: Bring back when the soft freeze issue is addressed // _safeRun(backgroundManager.syncCloudIds(), "syncCloudIds"), ]); + await _safeRun(backgroundManager.syncTrash(), "syncTrash"); } else { await _safeRun(backgroundManager.hashAssets(), "hashAssets"); } diff --git a/mobile/lib/providers/infrastructure/asset.provider.dart b/mobile/lib/providers/infrastructure/asset.provider.dart index fed55208e4..f7342e0eef 100644 --- a/mobile/lib/providers/infrastructure/asset.provider.dart +++ b/mobile/lib/providers/infrastructure/asset.provider.dart @@ -3,7 +3,6 @@ import 'package:immich_mobile/domain/services/asset.service.dart'; import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/remote_asset.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/remote_exif.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart'; import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/repositories/asset_api.repository.dart'; @@ -18,10 +17,6 @@ final remoteAssetRepositoryProvider = Provider( final remoteExifRepositoryProvider = Provider((ref) => RemoteExifRepository(ref.watch(driftProvider))); -final trashedLocalAssetRepository = Provider( - (ref) => DriftTrashedLocalAssetRepository(ref.watch(driftProvider)), -); - final assetServiceProvider = Provider( (ref) => AssetService( remoteRepository: ref.watch(remoteAssetRepositoryProvider), diff --git a/mobile/lib/providers/infrastructure/platform.provider.dart b/mobile/lib/providers/infrastructure/platform.provider.dart index 9f85235927..f8f32665cd 100644 --- a/mobile/lib/providers/infrastructure/platform.provider.dart +++ b/mobile/lib/providers/infrastructure/platform.provider.dart @@ -1,5 +1,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/services/background_worker.service.dart'; +import 'package:immich_mobile/platform/asset_media_api.g.dart'; import 'package:immich_mobile/platform/background_worker_api.g.dart'; import 'package:immich_mobile/platform/background_worker_lock_api.g.dart'; import 'package:immich_mobile/platform/connectivity_api.g.dart'; @@ -17,6 +18,8 @@ final backgroundWorkerLockServiceProvider = Provider((_) => NativeSyncApi()); +final assetMediaApiProvider = Provider((_) => AssetMediaApi()); + final permissionApiProvider = Provider((_) => PermissionApi()); final connectivityApiProvider = Provider((_) => ConnectivityApi()); diff --git a/mobile/lib/providers/infrastructure/sync.provider.dart b/mobile/lib/providers/infrastructure/sync.provider.dart index 700b51f12d..ce7ad88bf7 100644 --- a/mobile/lib/providers/infrastructure/sync.provider.dart +++ b/mobile/lib/providers/infrastructure/sync.provider.dart @@ -11,8 +11,7 @@ import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; import 'package:immich_mobile/providers/infrastructure/cancel.provider.dart'; import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; -import 'package:immich_mobile/repositories/asset_media.repository.dart'; -import 'package:immich_mobile/repositories/permission.repository.dart'; +import 'package:immich_mobile/providers/infrastructure/trash_sync.provider.dart'; final syncMigrationRepositoryProvider = Provider((ref) => SyncMigrationRepository(ref.watch(driftProvider))); @@ -20,10 +19,7 @@ final syncStreamServiceProvider = Provider( (ref) => SyncStreamService( syncApiRepository: ref.watch(syncApiRepositoryProvider), syncStreamRepository: ref.watch(syncStreamRepositoryProvider), - localAssetRepository: ref.watch(localAssetRepository), - trashedLocalAssetRepository: ref.watch(trashedLocalAssetRepository), - assetMediaRepository: ref.watch(assetMediaRepositoryProvider), - permissionRepository: ref.watch(permissionRepositoryProvider), + trashSyncRepository: ref.watch(trashSyncRepositoryProvider), syncMigrationRepository: ref.watch(syncMigrationRepositoryProvider), api: ref.watch(apiServiceProvider), cancellation: ref.watch(cancellationProvider), @@ -38,9 +34,7 @@ final localSyncServiceProvider = Provider( (ref) => LocalSyncService( localAlbumRepository: ref.watch(localAlbumRepository), localAssetRepository: ref.watch(localAssetRepository), - trashedLocalAssetRepository: ref.watch(trashedLocalAssetRepository), - assetMediaRepository: ref.watch(assetMediaRepositoryProvider), - permissionRepository: ref.watch(permissionRepositoryProvider), + trashSyncRepository: ref.watch(trashSyncRepositoryProvider), nativeSyncApi: ref.watch(nativeSyncApiProvider), cancellation: ref.watch(cancellationProvider), ), @@ -51,7 +45,6 @@ final hashServiceProvider = Provider( localAlbumRepository: ref.watch(localAlbumRepository), localAssetRepository: ref.watch(localAssetRepository), nativeSyncApi: ref.watch(nativeSyncApiProvider), - trashedLocalAssetRepository: ref.watch(trashedLocalAssetRepository), cancellation: ref.watch(cancellationProvider), ), ); diff --git a/mobile/lib/providers/infrastructure/trash_sync.provider.dart b/mobile/lib/providers/infrastructure/trash_sync.provider.dart index a783080f33..5be75f0436 100644 --- a/mobile/lib/providers/infrastructure/trash_sync.provider.dart +++ b/mobile/lib/providers/infrastructure/trash_sync.provider.dart @@ -1,12 +1,20 @@ -import 'package:async/async.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; +import 'package:immich_mobile/domain/services/trash_sync.service.dart'; +import 'package:immich_mobile/infrastructure/repositories/trash_sync.repository.dart'; +import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; +import 'package:immich_mobile/repositories/permission.repository.dart'; -typedef TrashedAssetsCount = ({int total, int hashed}); +final trashSyncRepositoryProvider = Provider( + (ref) => DriftTrashSyncRepository(ref.watch(driftProvider)), +); -final trashedAssetsCountProvider = StreamProvider((ref) { - final repo = ref.watch(trashedLocalAssetRepository); - final total$ = repo.watchCount(); - final hashed$ = repo.watchHashedCount(); - return StreamZip([total$, hashed$]).map((values) => (total: values[0], hashed: values[1])); -}); +final trashSyncServiceProvider = Provider( + (ref) => TrashSyncService( + repo: ref.watch(trashSyncRepositoryProvider), + assetMediaApi: ref.watch(assetMediaApiProvider), + permission: ref.watch(permissionRepositoryProvider), + settings: ref.watch(settingsProvider), + ), +); diff --git a/mobile/lib/repositories/asset_media.repository.dart b/mobile/lib/repositories/asset_media.repository.dart index 4c9b6f6009..b54efe6878 100644 --- a/mobile/lib/repositories/asset_media.repository.dart +++ b/mobile/lib/repositories/asset_media.repository.dart @@ -11,8 +11,6 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/platform_extensions.dart'; import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart'; -import 'package:immich_mobile/platform/native_sync_api.g.dart'; -import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; import 'package:immich_mobile/providers/infrastructure/storage.provider.dart'; import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/utils/image_url_builder.dart'; @@ -24,16 +22,13 @@ import 'package:share_plus/share_plus.dart'; typedef _ShareFile = ({File file, bool cleanup, String displayName}); -final assetMediaRepositoryProvider = Provider( - (ref) => AssetMediaRepository(ref.watch(nativeSyncApiProvider), ref.watch(storageRepositoryProvider)), -); +final assetMediaRepositoryProvider = Provider((ref) => AssetMediaRepository(ref.watch(storageRepositoryProvider))); class AssetMediaRepository { - final NativeSyncApi _nativeSyncApi; final StorageRepository _storageRepository; static final Logger _log = Logger("AssetMediaRepository"); - const AssetMediaRepository(this._nativeSyncApi, this._storageRepository); + const AssetMediaRepository(this._storageRepository); Future _androidSupportsTrash() async { if (Platform.isAndroid) { @@ -58,27 +53,6 @@ class AssetMediaRepository { return PhotoManager.editor.deleteWithIds(ids); } - Future _restoreFromTrashById(String mediaId, int type) async { - try { - return await _nativeSyncApi.restoreFromTrashById(mediaId, type); - } catch (e, s) { - _log.warning('Error restore file from trash by Id', e, s); - return false; - } - } - - Future> restoreAssetsFromTrash(Iterable assets) async { - final restoredIds = []; - for (final asset in assets) { - _log.info("Restoring from trash, localId: ${asset.id}, checksum: ${asset.checksum}"); - final result = await _restoreFromTrashById(asset.id, asset.type.index); - if (result) { - restoredIds.add(asset.id); - } - } - return restoredIds; - } - Future get(String id) async { final entity = await AssetEntity.fromId(id); return entity; diff --git a/mobile/lib/services/action.service.dart b/mobile/lib/services/action.service.dart index 19782c8512..c7a6481552 100644 --- a/mobile/lib/services/action.service.dart +++ b/mobile/lib/services/action.service.dart @@ -6,14 +6,10 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/asset_edit.model.dart'; -import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/services/tag.service.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/remote_album.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/remote_asset.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart'; import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; import 'package:immich_mobile/repositories/asset_api.repository.dart'; @@ -34,7 +30,6 @@ final actionServiceProvider = Provider( ref.watch(localAssetRepository), ref.watch(driftAlbumApiRepositoryProvider), ref.watch(remoteAlbumRepository), - ref.watch(trashedLocalAssetRepository), ref.watch(assetMediaRepositoryProvider), ref.watch(downloadRepositoryProvider), ref.watch(tagServiceProvider), @@ -47,7 +42,6 @@ class ActionService { final DriftLocalAssetRepository _localAssetRepository; final DriftAlbumApiRepository _albumApiRepository; final DriftRemoteAlbumRepository _remoteAlbumRepository; - final DriftTrashedLocalAssetRepository _trashedLocalAssetRepository; final AssetMediaRepository _assetMediaRepository; final DownloadRepository _downloadRepository; final TagService _tagService; @@ -58,7 +52,6 @@ class ActionService { this._localAssetRepository, this._albumApiRepository, this._remoteAlbumRepository, - this._trashedLocalAssetRepository, this._assetMediaRepository, this._downloadRepository, this._tagService, @@ -318,11 +311,7 @@ class ActionService { if (deletedIds.isEmpty) { return 0; } - if (CurrentPlatform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false)) { - await _trashedLocalAssetRepository.applyTrashedAssets(deletedIds); - } else { - await _localAssetRepository.delete(deletedIds); - } + await _localAssetRepository.delete(deletedIds); return deletedIds.length; } } diff --git a/mobile/lib/services/app_settings.service.dart b/mobile/lib/services/app_settings.service.dart index 28bce32bb5..d66c9267ba 100644 --- a/mobile/lib/services/app_settings.service.dart +++ b/mobile/lib/services/app_settings.service.dart @@ -3,7 +3,6 @@ import 'package:immich_mobile/entities/store.entity.dart'; enum AppSettingsEnum { advancedTroubleshooting(StoreKey.advancedTroubleshooting, null, false), - manageLocalMediaAndroid(StoreKey.manageLocalMediaAndroid, null, false), enableHapticFeedback(StoreKey.enableHapticFeedback, null, true), readonlyModeEnabled(StoreKey.readonlyModeEnabled, "readonlyModeEnabled", false); diff --git a/mobile/lib/utils/migration.dart b/mobile/lib/utils/migration.dart index 56fccf4610..802a987cff 100644 --- a/mobile/lib/utils/migration.dart +++ b/mobile/lib/utils/migration.dart @@ -20,7 +20,7 @@ import 'package:immich_mobile/infrastructure/repositories/settings.repository.da import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart'; import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart'; -const int targetVersion = 26; +const int targetVersion = 27; Future migrateDatabaseIfNeeded(Drift drift) async { final int? storedVersion = Store.tryGet(StoreKey.version); @@ -34,6 +34,10 @@ Future migrateDatabaseIfNeeded(Drift drift) async { await _migrateTo26(drift); } + if (version < 27) { + await _migrateTo27(drift); + } + if (storedVersion == null) { await FeatureMessageService(SettingsRepository.instance).markSeen(); } @@ -145,6 +149,12 @@ Future _migrateTo26(Drift drift) async { await migrator.complete(); } +Future _migrateTo27(Drift drift) async { + final migrator = _StoreMigrator(drift); + await migrator.migrateBool(.legacyManageLocalMediaAndroid, .trashSyncEnabled); + await migrator.complete(); +} + Future _migrateAlbumSortMode(_StoreMigrator migrator) async { final raw = await migrator.readLegacyStoreInt(StoreKey.legacySelectedAlbumSortOrder.id); final mode = AlbumSortMode.values.firstWhereOrNull((e) => raw != null && e.storeIndex == raw); diff --git a/mobile/lib/widgets/forms/login/login_form.dart b/mobile/lib/widgets/forms/login/login_form.dart index 79617f8fe4..85c54ffa91 100644 --- a/mobile/lib/widgets/forms/login/login_form.dart +++ b/mobile/lib/widgets/forms/login/login_form.dart @@ -11,15 +11,16 @@ import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart' hide Store; import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/store.model.dart'; -import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/extensions/platform_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/generated/translations.g.dart'; import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; import 'package:immich_mobile/providers/auth.provider.dart'; import 'package:immich_mobile/providers/background_sync.provider.dart'; import 'package:immich_mobile/providers/feature_message.provider.dart'; import 'package:immich_mobile/providers/gallery_permission.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; import 'package:immich_mobile/providers/oauth.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/providers/view_intent/view_intent_handler.provider.dart'; @@ -168,64 +169,71 @@ class LoginForm extends HookConsumerWidget { final viewIntentHandler = ref.read(viewIntentHandlerProvider); await backgroundManager.syncLocal(full: true); - await backgroundManager.syncRemote(); + final syncSuccess = await backgroundManager.syncRemote(); await viewIntentHandler.flushDeferredViewIntent(); await backgroundManager.hashAssets(); + if (syncSuccess) { + await backgroundManager.syncTrash(); + } if (SettingsRepository.instance.appConfig.backup.syncAlbums) { await backgroundManager.syncLinkedAlbum(); } } - getManageMediaPermission() async { - final hasPermission = await ref.read(permissionRepositoryProvider).hasManageMediaPermission(); - if (!hasPermission) { - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(10))), - elevation: 5, - title: Text( - 'manage_media_access_title', - style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: context.primaryColor), - ).tr(), - content: SingleChildScrollView( - child: ListBody( - children: [ - const Text('manage_media_access_subtitle', style: TextStyle(fontSize: 14)).tr(), - const SizedBox(height: 4), - const Text('manage_media_access_rationale', style: TextStyle(fontSize: 12)).tr(), - ], + Future promptManageMediaIfNeeded() async { + if (!CurrentPlatform.isAndroid || !ref.read(appConfigProvider).trashSyncEnabled) { + return; + } + + final permission = ref.read(permissionRepositoryProvider); + if (await permission.hasManageMediaPermission() || !context.mounted) { + return; + } + + await showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + shape: const RoundedRectangleBorder(borderRadius: .all(.circular(10))), + elevation: 5, + title: Text( + context.t.manage_media_access_title, + style: .new(fontSize: 16, fontWeight: .bold, color: context.primaryColor), + ), + content: SingleChildScrollView( + child: ListBody( + children: [ + Text(context.t.manage_media_access_subtitle, style: const .new(fontSize: 14)), + const SizedBox(height: 4), + Text(context.t.manage_media_access_rationale, style: const .new(fontSize: 12)), + ], + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: Text( + context.t.cancel, + style: .new(fontWeight: .w600, color: context.primaryColor), ), ), - actions: [ - TextButton( - onPressed: () => Navigator.of(context).pop(), - child: Text( - 'cancel'.tr(), - style: TextStyle(fontWeight: FontWeight.w600, color: context.primaryColor), - ), + TextButton( + onPressed: () { + unawaited(permission.requestManageMediaPermission()); + Navigator.of(context).pop(); + }, + child: Text( + context.t.manage_media_access_settings, + style: .new(fontWeight: .w600, color: context.primaryColor), ), - TextButton( - onPressed: () { - unawaited(ref.read(permissionRepositoryProvider).requestManageMediaPermission()); - Navigator.of(context).pop(); - }, - child: Text( - 'manage_media_access_settings'.tr(), - style: TextStyle(fontWeight: FontWeight.w600, color: context.primaryColor), - ), - ), - ], - ); - }, - ); - } + ), + ], + ); + }, + ); } - bool isSyncRemoteDeletionsMode() => Platform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false); - login() async { TextInput.finishAutofillContext(); @@ -239,9 +247,7 @@ class LoginForm extends HookConsumerWidget { unawaited(context.pushRoute(const ChangePasswordRoute())); } else { await ref.read(galleryPermissionNotifier.notifier).requestGalleryPermission(); - if (isSyncRemoteDeletionsMode()) { - await getManageMediaPermission(); - } + await promptManageMediaIfNeeded(); unawaited(handleSyncFlow()); ref.read(websocketProvider.notifier).connect(); unawaited(ref.read(featureMessageServiceProvider).markSeen()); @@ -328,9 +334,7 @@ class LoginForm extends HookConsumerWidget { if (isSuccess) { await ref.read(galleryPermissionNotifier.notifier).requestGalleryPermission(); - if (isSyncRemoteDeletionsMode()) { - await getManageMediaPermission(); - } + await promptManageMediaIfNeeded(); unawaited(handleSyncFlow()); unawaited(ref.read(featureMessageServiceProvider).markSeen()); unawaited(context.router.replaceAll([const TabShellRoute()])); diff --git a/mobile/lib/widgets/settings/advanced_settings.dart b/mobile/lib/widgets/settings/advanced_settings.dart index 542a7cc5e2..043b2fb450 100644 --- a/mobile/lib/widgets/settings/advanced_settings.dart +++ b/mobile/lib/widgets/settings/advanced_settings.dart @@ -7,9 +7,9 @@ import 'package:flutter_hooks/flutter_hooks.dart' hide Store; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/services/log.service.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; import 'package:immich_mobile/repositories/permission.repository.dart'; import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/utils/bytes_units.dart'; @@ -28,7 +28,7 @@ class AdvancedSettings extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final advancedTroubleshooting = useAppSettingsState(AppSettingsEnum.advancedTroubleshooting); - final manageLocalMediaAndroid = useAppSettingsState(AppSettingsEnum.manageLocalMediaAndroid); + final trashSyncEnabled = useState(ref.read(appConfigProvider).trashSyncEnabled); final isManageMediaSupported = useState(false); final manageMediaAndroidPermission = useState(false); final levelId = useState(ref.read(appConfigProvider).logLevel.index); @@ -75,14 +75,16 @@ class AdvancedSettings extends HookConsumerWidget { children: [ SettingsSwitchListTile( enabled: true, - valueNotifier: manageLocalMediaAndroid, + valueNotifier: trashSyncEnabled, title: "advanced_settings_sync_remote_deletions_title".tr(), subtitle: "advanced_settings_sync_remote_deletions_subtitle".tr(), onChanged: (value) async { + trashSyncEnabled.value = value; + await ref.read(settingsProvider).write(.trashSyncEnabled, value); if (value) { - final result = await ref.read(permissionRepositoryProvider).requestManageMediaPermission(); - manageLocalMediaAndroid.value = result; - manageMediaAndroidPermission.value = result; + manageMediaAndroidPermission.value = await ref + .read(permissionRepositoryProvider) + .requestManageMediaPermission(); } }, ), @@ -90,12 +92,14 @@ class AdvancedSettings extends HookConsumerWidget { title: "manage_media_access_title".tr(), statusText: manageMediaAndroidPermission.value ? "allowed".tr() : "not_allowed".tr(), subtitle: "manage_media_access_rationale".tr(), - statusColor: manageLocalMediaAndroid.value && !manageMediaAndroidPermission.value + statusColor: trashSyncEnabled.value && !manageMediaAndroidPermission.value ? const Color.fromARGB(255, 243, 188, 106) : null, onActionTap: () async { - final result = await ref.read(permissionRepositoryProvider).manageMediaPermission(); - manageMediaAndroidPermission.value = result; + await ref.read(permissionRepositoryProvider).manageMediaPermission(); + manageMediaAndroidPermission.value = await ref + .read(permissionRepositoryProvider) + .hasManageMediaPermission(); }, ), ], diff --git a/mobile/lib/widgets/settings/beta_sync_settings/sync_status_and_actions.dart b/mobile/lib/widgets/settings/beta_sync_settings/sync_status_and_actions.dart index 92787077a1..f026f3e182 100644 --- a/mobile/lib/widgets/settings/beta_sync_settings/sync_status_and_actions.dart +++ b/mobile/lib/widgets/settings/beta_sync_settings/sync_status_and_actions.dart @@ -7,17 +7,14 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/platform_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/background_sync.provider.dart'; import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; import 'package:immich_mobile/providers/infrastructure/memory.provider.dart'; import 'package:immich_mobile/providers/infrastructure/storage.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/trash_sync.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/providers/sync_status.provider.dart'; -import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/widgets/settings/beta_sync_settings/entity_count_tile.dart'; import 'package:immich_mobile/widgets/settings/setting_group_title.dart'; import 'package:immich_mobile/widgets/settings/setting_list_tile.dart'; @@ -219,7 +216,6 @@ class _SyncStatsCounts extends ConsumerWidget { final localAlbumService = ref.watch(localAlbumServiceProvider); final remoteAlbumService = ref.watch(remoteAlbumServiceProvider); final memoryService = ref.watch(driftMemoryServiceProvider); - final appSettingsService = ref.watch(appSettingsServiceProvider); Future> loadCounts() async { final assetCounts = assetService.getAssetCounts(); @@ -353,47 +349,6 @@ class _SyncStatsCounts extends ConsumerWidget { ), ), ), - // To be removed once the experimental feature is stable - if (CurrentPlatform.isAndroid && - appSettingsService.getSetting(AppSettingsEnum.manageLocalMediaAndroid)) ...[ - SettingGroupTitle(title: "trash".t(context: context)), - Consumer( - builder: (context, ref, _) { - final counts = ref.watch(trashedAssetsCountProvider); - return counts.when( - data: (c) => Padding( - padding: const EdgeInsets.fromLTRB(16, 8, 16, 16), - child: IntrinsicHeight( - child: Flex( - direction: Axis.horizontal, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.stretch, // Added - spacing: 8.0, - children: [ - Expanded( - child: EntityCountTile( - label: "local".t(context: context), - count: c.total, - icon: Icons.delete_outline, - ), - ), - Expanded( - child: EntityCountTile( - label: "hashed_assets".t(context: context), - count: c.hashed, - icon: Icons.tag, - ), - ), - ], - ), - ), - ), - loading: () => const CircularProgressIndicator(), - error: (e, st) => Text('Error: $e'), - ); - }, - ), - ], ], ); }, diff --git a/mobile/pigeon/asset_media_api.dart b/mobile/pigeon/asset_media_api.dart new file mode 100644 index 0000000000..dab1245c2c --- /dev/null +++ b/mobile/pigeon/asset_media_api.dart @@ -0,0 +1,33 @@ +import 'package:pigeon/pigeon.dart'; + +@ConfigurePigeon( + PigeonOptions( + dartOut: 'lib/platform/asset_media_api.g.dart', + swiftOut: 'ios/Runner/AssetMedia/AssetMedia.g.swift', + swiftOptions: SwiftOptions(includeErrorClass: false), + kotlinOut: 'android/app/src/main/kotlin/app/alextran/immich/media/AssetMedia.g.kt', + kotlinOptions: KotlinOptions(package: 'app.alextran.immich.media'), + dartOptions: DartOptions(), + dartPackageName: 'immich_mobile', + ), +) +enum AssetMediaActionStatus { done, alreadyInState, notFound, failed } + +class AssetMediaActionResult { + final String id; + final AssetMediaActionStatus status; + + const AssetMediaActionResult({required this.id, required this.status}); +} + +@HostApi() +abstract class AssetMediaApi { + @async + List trash(List ids); + + @async + List restore(List ids); + + @async + List trashedAmong(List ids); +} diff --git a/mobile/pigeon/native_sync_api.dart b/mobile/pigeon/native_sync_api.dart index 433b154cd1..1b108c3c6d 100644 --- a/mobile/pigeon/native_sync_api.dart +++ b/mobile/pigeon/native_sync_api.dart @@ -135,12 +135,6 @@ abstract class NativeSyncApi { void cancelSync(); - @TaskQueue(type: TaskQueueType.serialBackgroundThread) - Map> getTrashedAssets(); - - @async - bool restoreFromTrashById(String mediaId, int type); - @TaskQueue(type: TaskQueueType.serialBackgroundThread) List getCloudIdForAssetIds(List assetIds); } diff --git a/mobile/test/api.mocks.dart b/mobile/test/api.mocks.dart index 40cdd29143..ae686bd449 100644 --- a/mobile/test/api.mocks.dart +++ b/mobile/test/api.mocks.dart @@ -1,3 +1,4 @@ +import 'package:immich_mobile/platform/asset_media_api.g.dart'; import 'package:immich_mobile/platform/connectivity_api.g.dart'; import 'package:immich_mobile/repositories/partner_api.repository.dart'; import 'package:mocktail/mocktail.dart'; @@ -10,3 +11,5 @@ class MockServerApi extends Mock implements ServerApi {} class MockPartnerApiRepository extends Mock implements PartnerApiRepository {} class MockConnectivityApi extends Mock implements ConnectivityApi {} + +class MockAssetMediaApi extends Mock implements AssetMediaApi {} diff --git a/mobile/test/domain/services/local_sync_service_test.dart b/mobile/test/domain/services/local_sync_service_test.dart index 14277709da..8e129c0109 100644 --- a/mobile/test/domain/services/local_sync_service_test.dart +++ b/mobile/test/domain/services/local_sync_service_test.dart @@ -3,7 +3,6 @@ import 'package:drift/native.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/services/local_sync.service.dart'; import 'package:immich_mobile/domain/services/store.service.dart'; import 'package:immich_mobile/entities/store.entity.dart'; @@ -11,23 +10,17 @@ import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/store.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/asset_media.repository.dart'; import 'package:mocktail/mocktail.dart'; -import '../../fixtures/asset.stub.dart'; import '../../infrastructure/repository.mock.dart'; -import '../../repository.mocks.dart'; import '../../service.mocks.dart'; void main() { late LocalSyncService sut; late DriftLocalAlbumRepository mockLocalAlbumRepository; late DriftLocalAssetRepository mockLocalAssetRepository; - late DriftTrashedLocalAssetRepository mockTrashedLocalAssetRepository; - late AssetMediaRepository mockAssetMediaRepository; - late MockPermissionRepository mockPermissionRepository; + late MockTrashSyncRepository mockTrashSyncRepository; late MockNativeSyncApi mockNativeSyncApi; late Drift db; @@ -48,175 +41,29 @@ void main() { setUp(() async { mockLocalAlbumRepository = MockLocalAlbumRepository(); mockLocalAssetRepository = MockLocalAssetRepository(); - mockTrashedLocalAssetRepository = MockTrashedLocalAssetRepository(); - mockAssetMediaRepository = MockAssetMediaRepository(); - mockPermissionRepository = MockPermissionRepository(); + mockTrashSyncRepository = MockTrashSyncRepository(); mockNativeSyncApi = MockNativeSyncApi(); when(() => mockNativeSyncApi.shouldFullSync()).thenAnswer((_) async => false); when(() => mockNativeSyncApi.getMediaChanges()).thenAnswer( (_) async => SyncDelta(hasChanges: false, updates: const [], deletes: const [], assetAlbums: const {}), ); - when(() => mockNativeSyncApi.getTrashedAssets()).thenAnswer((_) async => {}); - when(() => mockTrashedLocalAssetRepository.processTrashSnapshot(any())).thenAnswer((_) async {}); - when(() => mockTrashedLocalAssetRepository.getToRestore()).thenAnswer((_) async => []); - when(() => mockTrashedLocalAssetRepository.getToTrash()).thenAnswer((_) async => {}); - when(() => mockTrashedLocalAssetRepository.applyRestoredAssets(any())).thenAnswer((_) async {}); - when(() => mockTrashedLocalAssetRepository.trashLocalAsset(any())).thenAnswer((_) async {}); - when(() => mockAssetMediaRepository.deleteAll(any())).thenAnswer((invocation) async { - final ids = invocation.positionalArguments.first as List; - return ids; - }); + when(() => mockTrashSyncRepository.restoreChecksums()).thenAnswer((_) async {}); sut = LocalSyncService( localAlbumRepository: mockLocalAlbumRepository, localAssetRepository: mockLocalAssetRepository, - trashedLocalAssetRepository: mockTrashedLocalAssetRepository, - assetMediaRepository: mockAssetMediaRepository, - permissionRepository: mockPermissionRepository, + trashSyncRepository: mockTrashSyncRepository, nativeSyncApi: mockNativeSyncApi, ); - - await Store.put(StoreKey.manageLocalMediaAndroid, false); - when(() => mockPermissionRepository.hasManageMediaPermission()).thenAnswer((_) async => false); }); - group('LocalSyncService - syncTrashedAssets gating', () { - test('invokes syncTrashedAssets when Android flag enabled and permission granted', () async { - await Store.put(StoreKey.manageLocalMediaAndroid, true); - when(() => mockPermissionRepository.hasManageMediaPermission()).thenAnswer((_) async => true); - + group('LocalSyncService - no changes', () { + test('does nothing when there are no media changes', () async { await sut.sync(); - verify(() => mockNativeSyncApi.getTrashedAssets()).called(1); - verify(() => mockTrashedLocalAssetRepository.processTrashSnapshot(any())).called(1); - }); - - test('skips syncTrashedAssets when store flag disabled', () async { - await Store.put(StoreKey.manageLocalMediaAndroid, false); - when(() => mockPermissionRepository.hasManageMediaPermission()).thenAnswer((_) async => true); - - await sut.sync(); - - verifyNever(() => mockNativeSyncApi.getTrashedAssets()); - }); - - test('skips syncTrashedAssets when MANAGE_MEDIA permission absent', () async { - await Store.put(StoreKey.manageLocalMediaAndroid, true); - when(() => mockPermissionRepository.hasManageMediaPermission()).thenAnswer((_) async => false); - - await sut.sync(); - - verifyNever(() => mockNativeSyncApi.getTrashedAssets()); - }); - - test('skips syncTrashedAssets on non-Android platforms', () async { - debugDefaultTargetPlatformOverride = TargetPlatform.iOS; - addTearDown(() => debugDefaultTargetPlatformOverride = TargetPlatform.android); - - await Store.put(StoreKey.manageLocalMediaAndroid, true); - when(() => mockPermissionRepository.hasManageMediaPermission()).thenAnswer((_) async => true); - - await sut.sync(); - - verifyNever(() => mockNativeSyncApi.getTrashedAssets()); - }); - }); - - group('LocalSyncService - syncTrashedAssets behavior', () { - test('processes trashed snapshot, restores assets, and trashes local files', () async { - final platformAsset = PlatformAsset( - id: 'remote-id', - name: 'remote.jpg', - type: AssetType.image.index, - durationMs: 0, - orientation: 0, - isFavorite: false, - playbackStyle: PlatformAssetPlaybackStyle.image, - ); - - final assetsToRestore = [LocalAssetStub.image1]; - when(() => mockTrashedLocalAssetRepository.getToRestore()).thenAnswer((_) async => assetsToRestore); - final restoredIds = ['image1']; - when(() => mockAssetMediaRepository.restoreAssetsFromTrash(any())).thenAnswer((invocation) async { - final Iterable requested = invocation.positionalArguments.first as Iterable; - expect(requested, orderedEquals(assetsToRestore)); - return restoredIds; - }); - - final localAssetToTrash = LocalAssetStub.image2.copyWith(id: 'local-trash', checksum: 'checksum-trash'); - when(() => mockTrashedLocalAssetRepository.getToTrash()).thenAnswer( - (_) async => { - 'album-a': [localAssetToTrash], - }, - ); - - await sut.processTrashedAssets({ - 'album-a': [platformAsset], - }); - - final trashedSnapshot = - verify(() => mockTrashedLocalAssetRepository.processTrashSnapshot(captureAny())).captured.single - as Iterable; - expect(trashedSnapshot.length, 1); - final trashedEntry = trashedSnapshot.single; - expect(trashedEntry.albumId, 'album-a'); - expect(trashedEntry.asset.id, platformAsset.id); - expect(trashedEntry.asset.name, platformAsset.name); - verify(() => mockTrashedLocalAssetRepository.getToTrash()).called(1); - - verify(() => mockAssetMediaRepository.restoreAssetsFromTrash(any())).called(1); - verify(() => mockTrashedLocalAssetRepository.applyRestoredAssets(restoredIds)).called(1); - - final moveArgs = verify(() => mockAssetMediaRepository.deleteAll(captureAny())).captured.single as List; - expect(moveArgs, ['local-trash']); - final trashArgs = - verify(() => mockTrashedLocalAssetRepository.trashLocalAsset(captureAny())).captured.single - as Map>; - expect(trashArgs.keys, ['album-a']); - expect(trashArgs['album-a'], [localAssetToTrash]); - }); - - test('records only local assets that were moved to device trash', () async { - final movedAsset = LocalAssetStub.image1.copyWith(id: 'moved-local', checksum: 'checksum-moved'); - final skippedAsset = LocalAssetStub.image2.copyWith(id: 'skipped-local', checksum: 'checksum-skipped'); - when(() => mockTrashedLocalAssetRepository.getToTrash()).thenAnswer( - (_) async => { - 'album-a': [movedAsset], - 'album-b': [skippedAsset], - }, - ); - when(() => mockAssetMediaRepository.deleteAll(any())).thenAnswer((_) async => ['moved-local']); - - await sut.processTrashedAssets({}); - - final trashArgs = - verify(() => mockTrashedLocalAssetRepository.trashLocalAsset(captureAny())).captured.single - as Map>; - expect(trashArgs.keys, ['album-a']); - expect(trashArgs['album-a'], [movedAsset]); - }); - - test('does not attempt restore when repository has no assets to restore', () async { - when(() => mockTrashedLocalAssetRepository.getToRestore()).thenAnswer((_) async => []); - - await sut.processTrashedAssets({}); - - final trashedSnapshot = - verify(() => mockTrashedLocalAssetRepository.processTrashSnapshot(captureAny())).captured.single - as Iterable; - expect(trashedSnapshot, isEmpty); - verifyNever(() => mockAssetMediaRepository.restoreAssetsFromTrash(any())); - verifyNever(() => mockTrashedLocalAssetRepository.applyRestoredAssets(any())); - }); - - test('does not move local assets when repository finds nothing to trash', () async { - when(() => mockTrashedLocalAssetRepository.getToTrash()).thenAnswer((_) async => {}); - - await sut.processTrashedAssets({}); - - verifyNever(() => mockAssetMediaRepository.deleteAll(any())); - verifyNever(() => mockTrashedLocalAssetRepository.trashLocalAsset(any())); + verify(() => mockNativeSyncApi.getMediaChanges()).called(1); + verifyNever(() => mockLocalAlbumRepository.updateAll(any())); }); }); diff --git a/mobile/test/domain/services/sync_stream_service_test.dart b/mobile/test/domain/services/sync_stream_service_test.dart index e033229408..874557a595 100644 --- a/mobile/test/domain/services/sync_stream_service_test.dart +++ b/mobile/test/domain/services/sync_stream_service_test.dart @@ -4,19 +4,15 @@ import 'package:drift/drift.dart' as drift; import 'package:drift/native.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/models/sync_event.model.dart'; import 'package:immich_mobile/domain/services/store.service.dart'; import 'package:immich_mobile/domain/services/sync_stream.service.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/store.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/asset_media.repository.dart'; import 'package:immich_mobile/utils/semver.dart'; import 'package:mocktail/mocktail.dart'; import 'package:openapi/api.dart'; @@ -25,7 +21,6 @@ import '../../api.mocks.dart'; import '../../fixtures/asset.stub.dart'; import '../../fixtures/sync_stream.stub.dart'; import '../../infrastructure/repository.mock.dart'; -import '../../repository.mocks.dart'; import '../../service.mocks.dart'; class _AbortCallbackWrapper { @@ -36,15 +31,11 @@ class _AbortCallbackWrapper { class _MockAbortCallbackWrapper extends Mock implements _AbortCallbackWrapper {} - void main() { late SyncStreamService sut; late SyncStreamRepository mockSyncStreamRepo; late SyncApiRepository mockSyncApiRepo; - late DriftLocalAssetRepository mockLocalAssetRepo; - late DriftTrashedLocalAssetRepository mockTrashedLocalAssetRepo; - late AssetMediaRepository mockAssetMediaRepo; - late MockPermissionRepository mockPermissionRepo; + late MockTrashSyncRepository mockTrashSyncRepo; late MockApiService mockApi; late MockServerApi mockServerApi; late MockSyncMigrationRepository mockSyncMigrationRepo; @@ -52,7 +43,6 @@ void main() { late _MockAbortCallbackWrapper mockAbortCallbackWrapper; late _MockAbortCallbackWrapper mockResetCallbackWrapper; late Drift db; - late bool hasManageMediaPermission; setUpAll(() async { TestWidgetsFlutterBinding.ensureInitialized(); @@ -75,10 +65,7 @@ void main() { setUp(() async { mockSyncStreamRepo = MockSyncStreamRepository(); mockSyncApiRepo = MockSyncApiRepository(); - mockLocalAssetRepo = MockLocalAssetRepository(); - mockTrashedLocalAssetRepo = MockTrashedLocalAssetRepository(); - mockAssetMediaRepo = MockAssetMediaRepository(); - mockPermissionRepo = MockPermissionRepository(); + mockTrashSyncRepo = MockTrashSyncRepository(); mockAbortCallbackWrapper = _MockAbortCallbackWrapper(); mockResetCallbackWrapper = _MockAbortCallbackWrapper(); mockApi = MockApiService(); @@ -153,26 +140,12 @@ void main() { sut = SyncStreamService( syncApiRepository: mockSyncApiRepo, syncStreamRepository: mockSyncStreamRepo, - localAssetRepository: mockLocalAssetRepo, - trashedLocalAssetRepository: mockTrashedLocalAssetRepo, - assetMediaRepository: mockAssetMediaRepo, - permissionRepository: mockPermissionRepo, + trashSyncRepository: mockTrashSyncRepo, api: mockApi, syncMigrationRepository: mockSyncMigrationRepo, ); - when(() => mockLocalAssetRepo.getAssetsFromBackupAlbums(any())).thenAnswer((_) async => {}); - when(() => mockTrashedLocalAssetRepo.trashLocalAsset(any())).thenAnswer((_) async {}); - when(() => mockTrashedLocalAssetRepo.getToRestore()).thenAnswer((_) async => []); - when(() => mockTrashedLocalAssetRepo.applyRestoredAssets(any())).thenAnswer((_) async {}); - hasManageMediaPermission = false; - when(() => mockPermissionRepo.hasManageMediaPermission()).thenAnswer((_) async => hasManageMediaPermission); - when(() => mockAssetMediaRepo.deleteAll(any())).thenAnswer((invocation) async { - final ids = invocation.positionalArguments.first as List; - return ids; - }); - when(() => mockAssetMediaRepo.restoreAssetsFromTrash(any())).thenAnswer((_) async => []); - await Store.put(StoreKey.manageLocalMediaAndroid, false); + when(() => mockTrashSyncRepo.recordHardDeletedChecksums(any())).thenAnswer((_) => Future.value()); }); Future simulateEvents(List events) async { @@ -205,6 +178,13 @@ void main() { verifyNever(() => mockAbortCallbackWrapper()); }); + test("assetDeleteV1 records the server deleted checksums", () async { + await simulateEvents([SyncStreamStub.assetDeleteV1]); + + verify(() => mockTrashSyncRepo.recordHardDeletedChecksums(any())).called(1); + verify(() => mockSyncStreamRepo.deleteAssetsV1(any())).called(1); + }); + test("processes final batch correctly", () async { final events = [SyncStreamStub.userDeleteV1, SyncStreamStub.userV1Admin]; @@ -236,10 +216,7 @@ void main() { sut = SyncStreamService( syncApiRepository: mockSyncApiRepo, syncStreamRepository: mockSyncStreamRepo, - localAssetRepository: mockLocalAssetRepo, - trashedLocalAssetRepository: mockTrashedLocalAssetRepo, - assetMediaRepository: mockAssetMediaRepo, - permissionRepository: mockPermissionRepo, + trashSyncRepository: mockTrashSyncRepo, cancellation: cancellation, api: mockApi, syncMigrationRepository: mockSyncMigrationRepo, @@ -276,10 +253,7 @@ void main() { sut = SyncStreamService( syncApiRepository: mockSyncApiRepo, syncStreamRepository: mockSyncStreamRepo, - localAssetRepository: mockLocalAssetRepo, - trashedLocalAssetRepository: mockTrashedLocalAssetRepo, - assetMediaRepository: mockAssetMediaRepo, - permissionRepository: mockPermissionRepo, + trashSyncRepository: mockTrashSyncRepo, cancellation: cancellation, api: mockApi, syncMigrationRepository: mockSyncMigrationRepo, @@ -392,164 +366,6 @@ void main() { }); }); - group("SyncStreamService - remote trash & restore", () { - setUp(() async { - await Store.put(StoreKey.manageLocalMediaAndroid, true); - hasManageMediaPermission = true; - }); - - tearDown(() async { - await Store.put(StoreKey.manageLocalMediaAndroid, false); - hasManageMediaPermission = false; - }); - - test("moves backed up local and merged assets to device trash when remote trash events are received", () async { - final localAsset = LocalAssetStub.image1.copyWith(id: 'local-only', checksum: 'checksum-local', remoteId: null); - final mergedAsset = LocalAssetStub.image2.copyWith( - id: 'merged-local', - checksum: 'checksum-merged', - remoteId: 'remote-merged', - ); - final assetsByAlbum = { - 'album-a': [localAsset], - 'album-b': [mergedAsset], - }; - when(() => mockLocalAssetRepo.getAssetsFromBackupAlbums(any())).thenAnswer((invocation) async { - final Iterable requestedRemoteIds = invocation.positionalArguments.first as Iterable; - expect(requestedRemoteIds.toSet(), equals({'remote-1', 'remote-2', 'remote-3'})); - return assetsByAlbum; - }); - - when(() => mockAssetMediaRepo.deleteAll(any())).thenAnswer((invocation) async { - final ids = invocation.positionalArguments.first as List; - expect(ids, unorderedEquals(['local-only', 'merged-local'])); - return ids; - }); - - final events = [ - SyncStreamStub.assetTrashed( - id: 'remote-1', - checksum: localAsset.checksum!, - ack: 'asset-remote-local-1', - trashedAt: DateTime(2025, 5, 1), - ), - SyncStreamStub.assetTrashed( - id: 'remote-2', - checksum: mergedAsset.checksum!, - ack: 'asset-remote-merged-2', - trashedAt: DateTime(2025, 5, 2), - ), - SyncStreamStub.assetTrashed( - id: 'remote-3', - checksum: 'checksum-remote-only', - ack: 'asset-remote-only-3', - trashedAt: DateTime(2025, 5, 3), - ), - ]; - - await simulateEvents(events); - - final trashArgs = - verify(() => mockTrashedLocalAssetRepo.trashLocalAsset(captureAny())).captured.single - as Map>; - expect(trashArgs.keys, unorderedEquals(['album-a', 'album-b'])); - expect(trashArgs['album-a'], [localAsset]); - expect(trashArgs['album-b'], [mergedAsset]); - verify(() => mockAssetMediaRepo.deleteAll(any())).called(1); - verify(() => mockSyncApiRepo.ack(['asset-remote-only-3'])).called(1); - }); - - test("records only assets that were moved to device trash", () async { - final movedAsset = LocalAssetStub.image1.copyWith(id: 'moved-local', checksum: 'checksum-moved'); - final skippedAsset = LocalAssetStub.image2.copyWith(id: 'skipped-local', checksum: 'checksum-skipped'); - when(() => mockLocalAssetRepo.getAssetsFromBackupAlbums(any())).thenAnswer( - (_) async => { - 'album-a': [movedAsset], - 'album-b': [skippedAsset], - }, - ); - when(() => mockAssetMediaRepo.deleteAll(any())).thenAnswer((_) async => ['moved-local']); - - final events = [ - SyncStreamStub.assetTrashed( - id: 'remote-moved', - checksum: movedAsset.checksum!, - ack: 'asset-remote-moved', - trashedAt: DateTime(2025, 5, 1), - ), - SyncStreamStub.assetTrashed( - id: 'remote-skipped', - checksum: skippedAsset.checksum!, - ack: 'asset-remote-skipped', - trashedAt: DateTime(2025, 5, 2), - ), - ]; - - await simulateEvents(events); - - final trashArgs = - verify(() => mockTrashedLocalAssetRepo.trashLocalAsset(captureAny())).captured.single - as Map>; - expect(trashArgs.keys, ['album-a']); - expect(trashArgs['album-a'], [movedAsset]); - }); - - test("skips device trashing when no local assets match the remote trash payload", () async { - final events = [ - SyncStreamStub.assetTrashed( - id: 'remote-only', - checksum: 'checksum-only', - ack: 'asset-remote-only-9', - trashedAt: DateTime(2025, 6, 1), - ), - ]; - - await simulateEvents(events); - - verify(() => mockLocalAssetRepo.getAssetsFromBackupAlbums(any())).called(1); - verifyNever(() => mockAssetMediaRepo.deleteAll(any())); - verifyNever(() => mockTrashedLocalAssetRepo.trashLocalAsset(any())); - }); - - test("requests local deletions lookup by remote ids for permanent remote delete events", () async { - when(() => mockLocalAssetRepo.getAssetsFromBackupAlbums(any())).thenAnswer((invocation) async { - final Iterable requestedRemoteIds = invocation.positionalArguments.first as Iterable; - expect(requestedRemoteIds.toSet(), equals({'remote-asset'})); - return {}; - }); - - final events = [SyncStreamStub.assetDeleteV1]; - - await simulateEvents(events); - - verify(() => mockLocalAssetRepo.getAssetsFromBackupAlbums(any())).called(1); - verifyNever(() => mockAssetMediaRepo.deleteAll(any())); - verify(() => mockSyncStreamRepo.deleteAssetsV1(any())).called(1); - }); - - test("restores trashed local assets once the matching remote assets leave the trash", () async { - final trashedAssets = [ - LocalAssetStub.image1.copyWith(id: 'trashed-1', checksum: 'checksum-trash', remoteId: 'remote-1'), - ]; - when(() => mockTrashedLocalAssetRepo.getToRestore()).thenAnswer((_) async => trashedAssets); - - final restoredIds = ['trashed-1']; - when(() => mockAssetMediaRepo.restoreAssetsFromTrash(any())).thenAnswer((invocation) async { - final Iterable requestedAssets = invocation.positionalArguments.first as Iterable; - expect(requestedAssets, orderedEquals(trashedAssets)); - return restoredIds; - }); - - final events = [ - SyncStreamStub.assetModified(id: 'remote-1', checksum: 'checksum-trash', ack: 'asset-remote-1-11'), - ]; - - await simulateEvents(events); - - verify(() => mockTrashedLocalAssetRepo.applyRestoredAssets(restoredIds)).called(1); - }); - }); - group('SyncStreamService - Sync Migration', () { test('ensure that <2.5.0 migrations run', () async { await Store.put(StoreKey.syncMigrationStatus, "[]"); diff --git a/mobile/test/drift/main/generated/schema.dart b/mobile/test/drift/main/generated/schema.dart index ee5900c1d1..94913560da 100644 --- a/mobile/test/drift/main/generated/schema.dart +++ b/mobile/test/drift/main/generated/schema.dart @@ -35,6 +35,7 @@ import 'schema_v28.dart' as v28; import 'schema_v29.dart' as v29; import 'schema_v30.dart' as v30; import 'schema_v31.dart' as v31; +import 'schema_v32.dart' as v32; class GeneratedHelper implements SchemaInstantiationHelper { @override @@ -102,6 +103,8 @@ class GeneratedHelper implements SchemaInstantiationHelper { return v30.DatabaseAtV30(db); case 31: return v31.DatabaseAtV31(db); + case 32: + return v32.DatabaseAtV32(db); default: throw MissingSchemaException(version, versions); } @@ -139,5 +142,6 @@ class GeneratedHelper implements SchemaInstantiationHelper { 29, 30, 31, + 32, ]; } diff --git a/mobile/test/drift/main/generated/schema_v32.dart b/mobile/test/drift/main/generated/schema_v32.dart new file mode 100644 index 0000000000..35397dc96b --- /dev/null +++ b/mobile/test/drift/main/generated/schema_v32.dart @@ -0,0 +1,9792 @@ +// dart format width=80 +import 'dart:typed_data' as i2; +// GENERATED BY drift_dev, DO NOT MODIFY. +// ignore_for_file: type=lint,unused_import +// +import 'package:drift/drift.dart'; + +class UserEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn hasProfileImage = GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: + 'NOT NULL DEFAULT 0 CHECK (has_profile_image IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn profileChangedAt = GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn avatarColor = GeneratedColumn( + 'avatar_color', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0', + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + id, + name, + email, + hasProfileImage, + profileChangedAt, + avatarColor, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_entity'; + @override + Set get $primaryKey => {id}; + @override + UserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + email: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + hasProfileImage: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}profile_changed_at'], + )!, + avatarColor: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}avatar_color'], + )!, + ); + } + + @override + UserEntity createAlias(String alias) { + return UserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class UserEntityData extends DataClass implements Insertable { + final String id; + final String name; + final String email; + final int hasProfileImage; + final String profileChangedAt; + final int avatarColor; + const UserEntityData({ + required this.id, + required this.name, + required this.email, + required this.hasProfileImage, + required this.profileChangedAt, + required this.avatarColor, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['email'] = Variable(email); + map['has_profile_image'] = Variable(hasProfileImage); + map['profile_changed_at'] = Variable(profileChangedAt); + map['avatar_color'] = Variable(avatarColor); + return map; + } + + factory UserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + email: serializer.fromJson(json['email']), + hasProfileImage: serializer.fromJson(json['hasProfileImage']), + profileChangedAt: serializer.fromJson(json['profileChangedAt']), + avatarColor: serializer.fromJson(json['avatarColor']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'email': serializer.toJson(email), + 'hasProfileImage': serializer.toJson(hasProfileImage), + 'profileChangedAt': serializer.toJson(profileChangedAt), + 'avatarColor': serializer.toJson(avatarColor), + }; + } + + UserEntityData copyWith({ + String? id, + String? name, + String? email, + int? hasProfileImage, + String? profileChangedAt, + int? avatarColor, + }) => UserEntityData( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + ); + UserEntityData copyWithCompanion(UserEntityCompanion data) { + return UserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + email: data.email.present ? data.email.value : this.email, + hasProfileImage: data.hasProfileImage.present + ? data.hasProfileImage.value + : this.hasProfileImage, + profileChangedAt: data.profileChangedAt.present + ? data.profileChangedAt.value + : this.profileChangedAt, + avatarColor: data.avatarColor.present + ? data.avatarColor.value + : this.avatarColor, + ); + } + + @override + String toString() { + return (StringBuffer('UserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + email, + hasProfileImage, + profileChangedAt, + avatarColor, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserEntityData && + other.id == this.id && + other.name == this.name && + other.email == this.email && + other.hasProfileImage == this.hasProfileImage && + other.profileChangedAt == this.profileChangedAt && + other.avatarColor == this.avatarColor); +} + +class UserEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value email; + final Value hasProfileImage; + final Value profileChangedAt; + final Value avatarColor; + const UserEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.email = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + }); + UserEntityCompanion.insert({ + required String id, + required String name, + required String email, + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + }) : id = Value(id), + name = Value(name), + email = Value(email); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? email, + Expression? hasProfileImage, + Expression? profileChangedAt, + Expression? avatarColor, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (email != null) 'email': email, + if (hasProfileImage != null) 'has_profile_image': hasProfileImage, + if (profileChangedAt != null) 'profile_changed_at': profileChangedAt, + if (avatarColor != null) 'avatar_color': avatarColor, + }); + } + + UserEntityCompanion copyWith({ + Value? id, + Value? name, + Value? email, + Value? hasProfileImage, + Value? profileChangedAt, + Value? avatarColor, + }) { + return UserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (hasProfileImage.present) { + map['has_profile_image'] = Variable(hasProfileImage.value); + } + if (profileChangedAt.present) { + map['profile_changed_at'] = Variable(profileChangedAt.value); + } + if (avatarColor.present) { + map['avatar_color'] = Variable(avatarColor.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor') + ..write(')')) + .toString(); + } +} + +class RemoteAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn durationMs = GeneratedColumn( + 'duration_ms', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0 CHECK (is_favorite IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn localDateTime = GeneratedColumn( + 'local_date_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn thumbHash = GeneratedColumn( + 'thumb_hash', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn uploadedAt = GeneratedColumn( + 'uploaded_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn livePhotoVideoId = GeneratedColumn( + 'live_photo_video_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn visibility = GeneratedColumn( + 'visibility', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn stackId = GeneratedColumn( + 'stack_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn libraryId = GeneratedColumn( + 'library_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn isEdited = GeneratedColumn( + 'is_edited', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0 CHECK (is_edited IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationMs, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + uploadedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + isEdited, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationMs: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_ms'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + )!, + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_favorite'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + localDateTime: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}local_date_time'], + ), + thumbHash: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumb_hash'], + ), + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}deleted_at'], + ), + uploadedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}uploaded_at'], + ), + livePhotoVideoId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}live_photo_video_id'], + ), + visibility: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}visibility'], + )!, + stackId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}stack_id'], + ), + libraryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}library_id'], + ), + isEdited: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_edited'], + )!, + ); + } + + @override + RemoteAssetEntity createAlias(String alias) { + return RemoteAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class RemoteAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final String createdAt; + final String updatedAt; + final int? width; + final int? height; + final int? durationMs; + final String id; + final String checksum; + final int isFavorite; + final String ownerId; + final String? localDateTime; + final String? thumbHash; + final String? deletedAt; + final String? uploadedAt; + final String? livePhotoVideoId; + final int visibility; + final String? stackId; + final String? libraryId; + final int isEdited; + const RemoteAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationMs, + required this.id, + required this.checksum, + required this.isFavorite, + required this.ownerId, + this.localDateTime, + this.thumbHash, + this.deletedAt, + this.uploadedAt, + this.livePhotoVideoId, + required this.visibility, + this.stackId, + this.libraryId, + required this.isEdited, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationMs != null) { + map['duration_ms'] = Variable(durationMs); + } + map['id'] = Variable(id); + map['checksum'] = Variable(checksum); + map['is_favorite'] = Variable(isFavorite); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || localDateTime != null) { + map['local_date_time'] = Variable(localDateTime); + } + if (!nullToAbsent || thumbHash != null) { + map['thumb_hash'] = Variable(thumbHash); + } + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + if (!nullToAbsent || uploadedAt != null) { + map['uploaded_at'] = Variable(uploadedAt); + } + if (!nullToAbsent || livePhotoVideoId != null) { + map['live_photo_video_id'] = Variable(livePhotoVideoId); + } + map['visibility'] = Variable(visibility); + if (!nullToAbsent || stackId != null) { + map['stack_id'] = Variable(stackId); + } + if (!nullToAbsent || libraryId != null) { + map['library_id'] = Variable(libraryId); + } + map['is_edited'] = Variable(isEdited); + return map; + } + + factory RemoteAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationMs: serializer.fromJson(json['durationMs']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + ownerId: serializer.fromJson(json['ownerId']), + localDateTime: serializer.fromJson(json['localDateTime']), + thumbHash: serializer.fromJson(json['thumbHash']), + deletedAt: serializer.fromJson(json['deletedAt']), + uploadedAt: serializer.fromJson(json['uploadedAt']), + livePhotoVideoId: serializer.fromJson(json['livePhotoVideoId']), + visibility: serializer.fromJson(json['visibility']), + stackId: serializer.fromJson(json['stackId']), + libraryId: serializer.fromJson(json['libraryId']), + isEdited: serializer.fromJson(json['isEdited']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationMs': serializer.toJson(durationMs), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'ownerId': serializer.toJson(ownerId), + 'localDateTime': serializer.toJson(localDateTime), + 'thumbHash': serializer.toJson(thumbHash), + 'deletedAt': serializer.toJson(deletedAt), + 'uploadedAt': serializer.toJson(uploadedAt), + 'livePhotoVideoId': serializer.toJson(livePhotoVideoId), + 'visibility': serializer.toJson(visibility), + 'stackId': serializer.toJson(stackId), + 'libraryId': serializer.toJson(libraryId), + 'isEdited': serializer.toJson(isEdited), + }; + } + + RemoteAssetEntityData copyWith({ + String? name, + int? type, + String? createdAt, + String? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationMs = const Value.absent(), + String? id, + String? checksum, + int? isFavorite, + String? ownerId, + Value localDateTime = const Value.absent(), + Value thumbHash = const Value.absent(), + Value deletedAt = const Value.absent(), + Value uploadedAt = const Value.absent(), + Value livePhotoVideoId = const Value.absent(), + int? visibility, + Value stackId = const Value.absent(), + Value libraryId = const Value.absent(), + int? isEdited, + }) => RemoteAssetEntityData( + 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, + durationMs: durationMs.present ? durationMs.value : this.durationMs, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime.present + ? localDateTime.value + : this.localDateTime, + thumbHash: thumbHash.present ? thumbHash.value : this.thumbHash, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + uploadedAt: uploadedAt.present ? uploadedAt.value : this.uploadedAt, + livePhotoVideoId: livePhotoVideoId.present + ? livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId.present ? stackId.value : this.stackId, + libraryId: libraryId.present ? libraryId.value : this.libraryId, + isEdited: isEdited ?? this.isEdited, + ); + RemoteAssetEntityData copyWithCompanion(RemoteAssetEntityCompanion data) { + return RemoteAssetEntityData( + 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, + durationMs: data.durationMs.present + ? data.durationMs.value + : this.durationMs, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + localDateTime: data.localDateTime.present + ? data.localDateTime.value + : this.localDateTime, + thumbHash: data.thumbHash.present ? data.thumbHash.value : this.thumbHash, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + uploadedAt: data.uploadedAt.present + ? data.uploadedAt.value + : this.uploadedAt, + livePhotoVideoId: data.livePhotoVideoId.present + ? data.livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: data.visibility.present + ? data.visibility.value + : this.visibility, + stackId: data.stackId.present ? data.stackId.value : this.stackId, + libraryId: data.libraryId.present ? data.libraryId.value : this.libraryId, + isEdited: data.isEdited.present ? data.isEdited.value : this.isEdited, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationMs: $durationMs, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('uploadedAt: $uploadedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId, ') + ..write('isEdited: $isEdited') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationMs, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + uploadedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + isEdited, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAssetEntityData && + 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.durationMs == this.durationMs && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.ownerId == this.ownerId && + other.localDateTime == this.localDateTime && + other.thumbHash == this.thumbHash && + other.deletedAt == this.deletedAt && + other.uploadedAt == this.uploadedAt && + other.livePhotoVideoId == this.livePhotoVideoId && + other.visibility == this.visibility && + other.stackId == this.stackId && + other.libraryId == this.libraryId && + other.isEdited == this.isEdited); +} + +class RemoteAssetEntityCompanion + extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationMs; + final Value id; + final Value checksum; + final Value isFavorite; + final Value ownerId; + final Value localDateTime; + final Value thumbHash; + final Value deletedAt; + final Value uploadedAt; + final Value livePhotoVideoId; + final Value visibility; + final Value stackId; + final Value libraryId; + final Value isEdited; + const RemoteAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationMs = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.ownerId = const Value.absent(), + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.uploadedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + this.visibility = const Value.absent(), + this.stackId = const Value.absent(), + this.libraryId = const Value.absent(), + this.isEdited = const Value.absent(), + }); + RemoteAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationMs = const Value.absent(), + required String id, + required String checksum, + this.isFavorite = const Value.absent(), + required String ownerId, + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.uploadedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + required int visibility, + this.stackId = const Value.absent(), + this.libraryId = const Value.absent(), + this.isEdited = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id), + checksum = Value(checksum), + ownerId = Value(ownerId), + visibility = Value(visibility); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationMs, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? ownerId, + Expression? localDateTime, + Expression? thumbHash, + Expression? deletedAt, + Expression? uploadedAt, + Expression? livePhotoVideoId, + Expression? visibility, + Expression? stackId, + Expression? libraryId, + Expression? isEdited, + }) { + return 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 (durationMs != null) 'duration_ms': durationMs, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (ownerId != null) 'owner_id': ownerId, + if (localDateTime != null) 'local_date_time': localDateTime, + if (thumbHash != null) 'thumb_hash': thumbHash, + if (deletedAt != null) 'deleted_at': deletedAt, + if (uploadedAt != null) 'uploaded_at': uploadedAt, + if (livePhotoVideoId != null) 'live_photo_video_id': livePhotoVideoId, + if (visibility != null) 'visibility': visibility, + if (stackId != null) 'stack_id': stackId, + if (libraryId != null) 'library_id': libraryId, + if (isEdited != null) 'is_edited': isEdited, + }); + } + + RemoteAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationMs, + Value? id, + Value? checksum, + Value? isFavorite, + Value? ownerId, + Value? localDateTime, + Value? thumbHash, + Value? deletedAt, + Value? uploadedAt, + Value? livePhotoVideoId, + Value? visibility, + Value? stackId, + Value? libraryId, + Value? isEdited, + }) { + return RemoteAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationMs: durationMs ?? this.durationMs, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime ?? this.localDateTime, + thumbHash: thumbHash ?? this.thumbHash, + deletedAt: deletedAt ?? this.deletedAt, + uploadedAt: uploadedAt ?? this.uploadedAt, + livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId ?? this.stackId, + libraryId: libraryId ?? this.libraryId, + isEdited: isEdited ?? this.isEdited, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationMs.present) { + map['duration_ms'] = Variable(durationMs.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (localDateTime.present) { + map['local_date_time'] = Variable(localDateTime.value); + } + if (thumbHash.present) { + map['thumb_hash'] = Variable(thumbHash.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (uploadedAt.present) { + map['uploaded_at'] = Variable(uploadedAt.value); + } + if (livePhotoVideoId.present) { + map['live_photo_video_id'] = Variable(livePhotoVideoId.value); + } + if (visibility.present) { + map['visibility'] = Variable(visibility.value); + } + if (stackId.present) { + map['stack_id'] = Variable(stackId.value); + } + if (libraryId.present) { + map['library_id'] = Variable(libraryId.value); + } + if (isEdited.present) { + map['is_edited'] = Variable(isEdited.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationMs: $durationMs, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('uploadedAt: $uploadedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId, ') + ..write('isEdited: $isEdited') + ..write(')')) + .toString(); + } +} + +class StackEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StackEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn primaryAssetId = GeneratedColumn( + 'primary_asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + primaryAssetId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'stack_entity'; + @override + Set get $primaryKey => {id}; + @override + StackEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StackEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + primaryAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}primary_asset_id'], + )!, + ); + } + + @override + StackEntity createAlias(String alias) { + return StackEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class StackEntityData extends DataClass implements Insertable { + final String id; + final String createdAt; + final String updatedAt; + final String ownerId; + final String primaryAssetId; + const StackEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.primaryAssetId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['primary_asset_id'] = Variable(primaryAssetId); + return map; + } + + factory StackEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StackEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + primaryAssetId: serializer.fromJson(json['primaryAssetId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'primaryAssetId': serializer.toJson(primaryAssetId), + }; + } + + StackEntityData copyWith({ + String? id, + String? createdAt, + String? updatedAt, + String? ownerId, + String? primaryAssetId, + }) => StackEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + StackEntityData copyWithCompanion(StackEntityCompanion data) { + return StackEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + primaryAssetId: data.primaryAssetId.present + ? data.primaryAssetId.value + : this.primaryAssetId, + ); + } + + @override + String toString() { + return (StringBuffer('StackEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, createdAt, updatedAt, ownerId, primaryAssetId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StackEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.primaryAssetId == this.primaryAssetId); +} + +class StackEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value primaryAssetId; + const StackEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.primaryAssetId = const Value.absent(), + }); + StackEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String primaryAssetId, + }) : id = Value(id), + ownerId = Value(ownerId), + primaryAssetId = Value(primaryAssetId); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? primaryAssetId, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (primaryAssetId != null) 'primary_asset_id': primaryAssetId, + }); + } + + StackEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? primaryAssetId, + }) { + return StackEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (primaryAssetId.present) { + map['primary_asset_id'] = Variable(primaryAssetId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StackEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } +} + +class LocalAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn durationMs = GeneratedColumn( + 'duration_ms', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0 CHECK (is_favorite IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn iCloudId = GeneratedColumn( + 'i_cloud_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn adjustmentTime = GeneratedColumn( + 'adjustment_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn playbackStyle = GeneratedColumn( + 'playback_style', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0', + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationMs, + id, + checksum, + isFavorite, + orientation, + iCloudId, + adjustmentTime, + latitude, + longitude, + playbackStyle, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationMs: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_ms'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_favorite'], + )!, + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}orientation'], + )!, + iCloudId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}i_cloud_id'], + ), + adjustmentTime: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}adjustment_time'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + playbackStyle: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}playback_style'], + )!, + ); + } + + @override + LocalAssetEntity createAlias(String alias) { + return LocalAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class LocalAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final String createdAt; + final String updatedAt; + final int? width; + final int? height; + final int? durationMs; + final String id; + final String? checksum; + final int isFavorite; + final int orientation; + final String? iCloudId; + final String? adjustmentTime; + final double? latitude; + final double? longitude; + final int playbackStyle; + const LocalAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationMs, + required this.id, + this.checksum, + required this.isFavorite, + required this.orientation, + this.iCloudId, + this.adjustmentTime, + this.latitude, + this.longitude, + required this.playbackStyle, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationMs != null) { + map['duration_ms'] = Variable(durationMs); + } + map['id'] = Variable(id); + if (!nullToAbsent || checksum != null) { + map['checksum'] = Variable(checksum); + } + map['is_favorite'] = Variable(isFavorite); + map['orientation'] = Variable(orientation); + if (!nullToAbsent || iCloudId != null) { + map['i_cloud_id'] = Variable(iCloudId); + } + if (!nullToAbsent || adjustmentTime != null) { + map['adjustment_time'] = Variable(adjustmentTime); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + map['playback_style'] = Variable(playbackStyle); + return map; + } + + factory LocalAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationMs: serializer.fromJson(json['durationMs']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + orientation: serializer.fromJson(json['orientation']), + iCloudId: serializer.fromJson(json['iCloudId']), + adjustmentTime: serializer.fromJson(json['adjustmentTime']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + playbackStyle: serializer.fromJson(json['playbackStyle']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationMs': serializer.toJson(durationMs), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'orientation': serializer.toJson(orientation), + 'iCloudId': serializer.toJson(iCloudId), + 'adjustmentTime': serializer.toJson(adjustmentTime), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + 'playbackStyle': serializer.toJson(playbackStyle), + }; + } + + LocalAssetEntityData copyWith({ + String? name, + int? type, + String? createdAt, + String? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationMs = const Value.absent(), + String? id, + Value checksum = const Value.absent(), + int? isFavorite, + int? orientation, + Value iCloudId = const Value.absent(), + Value adjustmentTime = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + int? playbackStyle, + }) => LocalAssetEntityData( + 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, + durationMs: durationMs.present ? durationMs.value : this.durationMs, + id: id ?? this.id, + checksum: checksum.present ? checksum.value : this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + iCloudId: iCloudId.present ? iCloudId.value : this.iCloudId, + adjustmentTime: adjustmentTime.present + ? adjustmentTime.value + : this.adjustmentTime, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + playbackStyle: playbackStyle ?? this.playbackStyle, + ); + LocalAssetEntityData copyWithCompanion(LocalAssetEntityCompanion data) { + return LocalAssetEntityData( + 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, + durationMs: data.durationMs.present + ? data.durationMs.value + : this.durationMs, + id: data.id.present ? data.id.value : this.id, + 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, + iCloudId: data.iCloudId.present ? data.iCloudId.value : this.iCloudId, + adjustmentTime: data.adjustmentTime.present + ? data.adjustmentTime.value + : this.adjustmentTime, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + playbackStyle: data.playbackStyle.present + ? data.playbackStyle.value + : this.playbackStyle, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationMs: $durationMs, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('iCloudId: $iCloudId, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('playbackStyle: $playbackStyle') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationMs, + id, + checksum, + isFavorite, + orientation, + iCloudId, + adjustmentTime, + latitude, + longitude, + playbackStyle, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAssetEntityData && + 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.durationMs == this.durationMs && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.orientation == this.orientation && + other.iCloudId == this.iCloudId && + other.adjustmentTime == this.adjustmentTime && + other.latitude == this.latitude && + other.longitude == this.longitude && + other.playbackStyle == this.playbackStyle); +} + +class LocalAssetEntityCompanion extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationMs; + final Value id; + final Value checksum; + final Value isFavorite; + final Value orientation; + final Value iCloudId; + final Value adjustmentTime; + final Value latitude; + final Value longitude; + final Value playbackStyle; + const LocalAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationMs = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + this.iCloudId = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.playbackStyle = const Value.absent(), + }); + LocalAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationMs = const Value.absent(), + required String id, + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + this.iCloudId = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.playbackStyle = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationMs, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? orientation, + Expression? iCloudId, + Expression? adjustmentTime, + Expression? latitude, + Expression? longitude, + Expression? playbackStyle, + }) { + return 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 (durationMs != null) 'duration_ms': durationMs, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (orientation != null) 'orientation': orientation, + if (iCloudId != null) 'i_cloud_id': iCloudId, + if (adjustmentTime != null) 'adjustment_time': adjustmentTime, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + if (playbackStyle != null) 'playback_style': playbackStyle, + }); + } + + LocalAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationMs, + Value? id, + Value? checksum, + Value? isFavorite, + Value? orientation, + Value? iCloudId, + Value? adjustmentTime, + Value? latitude, + Value? longitude, + Value? playbackStyle, + }) { + return LocalAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationMs: durationMs ?? this.durationMs, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + iCloudId: iCloudId ?? this.iCloudId, + adjustmentTime: adjustmentTime ?? this.adjustmentTime, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + playbackStyle: playbackStyle ?? this.playbackStyle, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationMs.present) { + map['duration_ms'] = Variable(durationMs.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (iCloudId.present) { + map['i_cloud_id'] = Variable(iCloudId.value); + } + if (adjustmentTime.present) { + map['adjustment_time'] = Variable(adjustmentTime.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + if (playbackStyle.present) { + map['playback_style'] = Variable(playbackStyle.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationMs: $durationMs, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('iCloudId: $iCloudId, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('playbackStyle: $playbackStyle') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT \'\'', + defaultValue: const CustomExpression('\'\''), + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn thumbnailAssetId = GeneratedColumn( + 'thumbnail_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: + 'NULL REFERENCES remote_asset_entity(id)ON DELETE SET NULL', + ); + late final GeneratedColumn isActivityEnabled = GeneratedColumn( + 'is_activity_enabled', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: + 'NOT NULL DEFAULT 1 CHECK (is_activity_enabled IN (0, 1))', + defaultValue: const CustomExpression('1'), + ); + late final GeneratedColumn order = GeneratedColumn( + 'order', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + @override + List get $columns => [ + id, + name, + description, + createdAt, + updatedAt, + thumbnailAssetId, + isActivityEnabled, + order, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + thumbnailAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumbnail_asset_id'], + ), + isActivityEnabled: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_activity_enabled'], + )!, + order: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}order'], + )!, + ); + } + + @override + RemoteAlbumEntity createAlias(String alias) { + return RemoteAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class RemoteAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String description; + final String createdAt; + final String updatedAt; + final String? thumbnailAssetId; + final int isActivityEnabled; + final int order; + const RemoteAlbumEntityData({ + required this.id, + required this.name, + required this.description, + required this.createdAt, + required this.updatedAt, + this.thumbnailAssetId, + required this.isActivityEnabled, + required this.order, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['description'] = Variable(description); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || thumbnailAssetId != null) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId); + } + map['is_activity_enabled'] = Variable(isActivityEnabled); + map['order'] = Variable(order); + return map; + } + + factory RemoteAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + description: serializer.fromJson(json['description']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + thumbnailAssetId: serializer.fromJson(json['thumbnailAssetId']), + isActivityEnabled: serializer.fromJson(json['isActivityEnabled']), + order: serializer.fromJson(json['order']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'description': serializer.toJson(description), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'thumbnailAssetId': serializer.toJson(thumbnailAssetId), + 'isActivityEnabled': serializer.toJson(isActivityEnabled), + 'order': serializer.toJson(order), + }; + } + + RemoteAlbumEntityData copyWith({ + String? id, + String? name, + String? description, + String? createdAt, + String? updatedAt, + Value thumbnailAssetId = const Value.absent(), + int? isActivityEnabled, + int? order, + }) => RemoteAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + thumbnailAssetId: thumbnailAssetId.present + ? thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + RemoteAlbumEntityData copyWithCompanion(RemoteAlbumEntityCompanion data) { + return RemoteAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + description: data.description.present + ? data.description.value + : this.description, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + thumbnailAssetId: data.thumbnailAssetId.present + ? data.thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: data.isActivityEnabled.present + ? data.isActivityEnabled.value + : this.isActivityEnabled, + order: data.order.present ? data.order.value : this.order, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + description, + createdAt, + updatedAt, + thumbnailAssetId, + isActivityEnabled, + order, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.description == this.description && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.thumbnailAssetId == this.thumbnailAssetId && + other.isActivityEnabled == this.isActivityEnabled && + other.order == this.order); +} + +class RemoteAlbumEntityCompanion + extends UpdateCompanion { + final Value id; + final Value name; + final Value description; + final Value createdAt; + final Value updatedAt; + final Value thumbnailAssetId; + final Value isActivityEnabled; + final Value order; + const RemoteAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + this.order = const Value.absent(), + }); + RemoteAlbumEntityCompanion.insert({ + required String id, + required String name, + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + required int order, + }) : id = Value(id), + name = Value(name), + order = Value(order); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? description, + Expression? createdAt, + Expression? updatedAt, + Expression? thumbnailAssetId, + Expression? isActivityEnabled, + Expression? order, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (description != null) 'description': description, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (thumbnailAssetId != null) 'thumbnail_asset_id': thumbnailAssetId, + if (isActivityEnabled != null) 'is_activity_enabled': isActivityEnabled, + if (order != null) 'order': order, + }); + } + + RemoteAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? description, + Value? createdAt, + Value? updatedAt, + Value? thumbnailAssetId, + Value? isActivityEnabled, + Value? order, + }) { + return RemoteAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + thumbnailAssetId: thumbnailAssetId ?? this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (thumbnailAssetId.present) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId.value); + } + if (isActivityEnabled.present) { + map['is_activity_enabled'] = Variable(isActivityEnabled.value); + } + if (order.present) { + map['order'] = Variable(order.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } +} + +class LocalAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn backupSelection = GeneratedColumn( + 'backup_selection', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn isIosSharedAlbum = GeneratedColumn( + 'is_ios_shared_album', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: + 'NOT NULL DEFAULT 0 CHECK (is_ios_shared_album IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn linkedRemoteAlbumId = + GeneratedColumn( + 'linked_remote_album_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: + 'NULL REFERENCES remote_album_entity(id)ON DELETE SET NULL', + ); + late final GeneratedColumn marker = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL CHECK (marker IN (0, 1))', + ); + @override + List get $columns => [ + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + linkedRemoteAlbumId, + marker, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + backupSelection: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}backup_selection'], + )!, + isIosSharedAlbum: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_ios_shared_album'], + )!, + linkedRemoteAlbumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}linked_remote_album_id'], + ), + marker: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumEntity createAlias(String alias) { + return LocalAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class LocalAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String updatedAt; + final int backupSelection; + final int isIosSharedAlbum; + final String? linkedRemoteAlbumId; + final int? marker; + const LocalAlbumEntityData({ + required this.id, + required this.name, + required this.updatedAt, + required this.backupSelection, + required this.isIosSharedAlbum, + this.linkedRemoteAlbumId, + this.marker, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['updated_at'] = Variable(updatedAt); + map['backup_selection'] = Variable(backupSelection); + map['is_ios_shared_album'] = Variable(isIosSharedAlbum); + if (!nullToAbsent || linkedRemoteAlbumId != null) { + map['linked_remote_album_id'] = Variable(linkedRemoteAlbumId); + } + if (!nullToAbsent || marker != null) { + map['marker'] = Variable(marker); + } + return map; + } + + factory LocalAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + updatedAt: serializer.fromJson(json['updatedAt']), + backupSelection: serializer.fromJson(json['backupSelection']), + isIosSharedAlbum: serializer.fromJson(json['isIosSharedAlbum']), + linkedRemoteAlbumId: serializer.fromJson( + json['linkedRemoteAlbumId'], + ), + marker: serializer.fromJson(json['marker']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'updatedAt': serializer.toJson(updatedAt), + 'backupSelection': serializer.toJson(backupSelection), + 'isIosSharedAlbum': serializer.toJson(isIosSharedAlbum), + 'linkedRemoteAlbumId': serializer.toJson(linkedRemoteAlbumId), + 'marker': serializer.toJson(marker), + }; + } + + LocalAlbumEntityData copyWith({ + String? id, + String? name, + String? updatedAt, + int? backupSelection, + int? isIosSharedAlbum, + Value linkedRemoteAlbumId = const Value.absent(), + Value marker = const Value.absent(), + }) => LocalAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + updatedAt: updatedAt ?? this.updatedAt, + backupSelection: backupSelection ?? this.backupSelection, + isIosSharedAlbum: isIosSharedAlbum ?? this.isIosSharedAlbum, + linkedRemoteAlbumId: linkedRemoteAlbumId.present + ? linkedRemoteAlbumId.value + : this.linkedRemoteAlbumId, + marker: marker.present ? marker.value : this.marker, + ); + LocalAlbumEntityData copyWithCompanion(LocalAlbumEntityCompanion data) { + return LocalAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + backupSelection: data.backupSelection.present + ? data.backupSelection.value + : this.backupSelection, + isIosSharedAlbum: data.isIosSharedAlbum.present + ? data.isIosSharedAlbum.value + : this.isIosSharedAlbum, + linkedRemoteAlbumId: data.linkedRemoteAlbumId.present + ? data.linkedRemoteAlbumId.value + : this.linkedRemoteAlbumId, + marker: data.marker.present ? data.marker.value : this.marker, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('linkedRemoteAlbumId: $linkedRemoteAlbumId, ') + ..write('marker: $marker') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + linkedRemoteAlbumId, + marker, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.updatedAt == this.updatedAt && + other.backupSelection == this.backupSelection && + other.isIosSharedAlbum == this.isIosSharedAlbum && + other.linkedRemoteAlbumId == this.linkedRemoteAlbumId && + other.marker == this.marker); +} + +class LocalAlbumEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value updatedAt; + final Value backupSelection; + final Value isIosSharedAlbum; + final Value linkedRemoteAlbumId; + final Value marker; + const LocalAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.updatedAt = const Value.absent(), + this.backupSelection = const Value.absent(), + this.isIosSharedAlbum = const Value.absent(), + this.linkedRemoteAlbumId = const Value.absent(), + this.marker = const Value.absent(), + }); + LocalAlbumEntityCompanion.insert({ + required String id, + required String name, + this.updatedAt = const Value.absent(), + required int backupSelection, + this.isIosSharedAlbum = const Value.absent(), + this.linkedRemoteAlbumId = const Value.absent(), + this.marker = const Value.absent(), + }) : id = Value(id), + name = Value(name), + backupSelection = Value(backupSelection); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? updatedAt, + Expression? backupSelection, + Expression? isIosSharedAlbum, + Expression? linkedRemoteAlbumId, + Expression? marker, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (updatedAt != null) 'updated_at': updatedAt, + if (backupSelection != null) 'backup_selection': backupSelection, + if (isIosSharedAlbum != null) 'is_ios_shared_album': isIosSharedAlbum, + if (linkedRemoteAlbumId != null) + 'linked_remote_album_id': linkedRemoteAlbumId, + if (marker != null) 'marker': marker, + }); + } + + LocalAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? updatedAt, + Value? backupSelection, + Value? isIosSharedAlbum, + Value? linkedRemoteAlbumId, + Value? marker, + }) { + return LocalAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + updatedAt: updatedAt ?? this.updatedAt, + backupSelection: backupSelection ?? this.backupSelection, + isIosSharedAlbum: isIosSharedAlbum ?? this.isIosSharedAlbum, + linkedRemoteAlbumId: linkedRemoteAlbumId ?? this.linkedRemoteAlbumId, + marker: marker ?? this.marker, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (backupSelection.present) { + map['backup_selection'] = Variable(backupSelection.value); + } + if (isIosSharedAlbum.present) { + map['is_ios_shared_album'] = Variable(isIosSharedAlbum.value); + } + if (linkedRemoteAlbumId.present) { + map['linked_remote_album_id'] = Variable( + linkedRemoteAlbumId.value, + ); + } + if (marker.present) { + map['marker'] = Variable(marker.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('linkedRemoteAlbumId: $linkedRemoteAlbumId, ') + ..write('marker: $marker') + ..write(')')) + .toString(); + } +} + +class LocalAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES local_asset_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES local_album_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn marker = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL CHECK (marker IN (0, 1))', + ); + @override + List get $columns => [assetId, albumId, marker]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + LocalAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + marker: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumAssetEntity createAlias(String alias) { + return LocalAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const [ + 'PRIMARY KEY(asset_id, album_id)', + ]; + @override + bool get dontWriteConstraints => true; +} + +class LocalAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + final int? marker; + const LocalAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + this.marker, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + if (!nullToAbsent || marker != null) { + map['marker'] = Variable(marker); + } + return map; + } + + factory LocalAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + marker: serializer.fromJson(json['marker']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + 'marker': serializer.toJson(marker), + }; + } + + LocalAlbumAssetEntityData copyWith({ + String? assetId, + String? albumId, + Value marker = const Value.absent(), + }) => LocalAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + marker: marker.present ? marker.value : this.marker, + ); + LocalAlbumAssetEntityData copyWithCompanion( + LocalAlbumAssetEntityCompanion data, + ) { + return LocalAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + marker: data.marker.present ? data.marker.value : this.marker, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId, ') + ..write('marker: $marker') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId, marker); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId && + other.marker == this.marker); +} + +class LocalAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + final Value marker; + const LocalAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + this.marker = const Value.absent(), + }); + LocalAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + this.marker = const Value.absent(), + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + Expression? marker, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + if (marker != null) 'marker': marker, + }); + } + + LocalAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + Value? marker, + }) { + return LocalAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + marker: marker ?? this.marker, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (marker.present) { + map['marker'] = Variable(marker.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId, ') + ..write('marker: $marker') + ..write(')')) + .toString(); + } +} + +class AuthUserEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AuthUserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn isAdmin = GeneratedColumn( + 'is_admin', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0 CHECK (is_admin IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn hasProfileImage = GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: + 'NOT NULL DEFAULT 0 CHECK (has_profile_image IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn profileChangedAt = GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn avatarColor = GeneratedColumn( + 'avatar_color', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn quotaSizeInBytes = GeneratedColumn( + 'quota_size_in_bytes', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn quotaUsageInBytes = GeneratedColumn( + 'quota_usage_in_bytes', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn pinCode = GeneratedColumn( + 'pin_code', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + @override + List get $columns => [ + id, + name, + email, + isAdmin, + hasProfileImage, + profileChangedAt, + avatarColor, + quotaSizeInBytes, + quotaUsageInBytes, + pinCode, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'auth_user_entity'; + @override + Set get $primaryKey => {id}; + @override + AuthUserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AuthUserEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + email: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + isAdmin: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_admin'], + )!, + hasProfileImage: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}profile_changed_at'], + )!, + avatarColor: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}avatar_color'], + )!, + quotaSizeInBytes: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}quota_size_in_bytes'], + )!, + quotaUsageInBytes: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}quota_usage_in_bytes'], + )!, + pinCode: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}pin_code'], + ), + ); + } + + @override + AuthUserEntity createAlias(String alias) { + return AuthUserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class AuthUserEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String email; + final int isAdmin; + final int hasProfileImage; + final String profileChangedAt; + final int avatarColor; + final int quotaSizeInBytes; + final int quotaUsageInBytes; + final String? pinCode; + const AuthUserEntityData({ + required this.id, + required this.name, + required this.email, + required this.isAdmin, + required this.hasProfileImage, + required this.profileChangedAt, + required this.avatarColor, + required this.quotaSizeInBytes, + required this.quotaUsageInBytes, + this.pinCode, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['email'] = Variable(email); + map['is_admin'] = Variable(isAdmin); + map['has_profile_image'] = Variable(hasProfileImage); + map['profile_changed_at'] = Variable(profileChangedAt); + map['avatar_color'] = Variable(avatarColor); + map['quota_size_in_bytes'] = Variable(quotaSizeInBytes); + map['quota_usage_in_bytes'] = Variable(quotaUsageInBytes); + if (!nullToAbsent || pinCode != null) { + map['pin_code'] = Variable(pinCode); + } + return map; + } + + factory AuthUserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AuthUserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + email: serializer.fromJson(json['email']), + isAdmin: serializer.fromJson(json['isAdmin']), + hasProfileImage: serializer.fromJson(json['hasProfileImage']), + profileChangedAt: serializer.fromJson(json['profileChangedAt']), + avatarColor: serializer.fromJson(json['avatarColor']), + quotaSizeInBytes: serializer.fromJson(json['quotaSizeInBytes']), + quotaUsageInBytes: serializer.fromJson(json['quotaUsageInBytes']), + pinCode: serializer.fromJson(json['pinCode']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'email': serializer.toJson(email), + 'isAdmin': serializer.toJson(isAdmin), + 'hasProfileImage': serializer.toJson(hasProfileImage), + 'profileChangedAt': serializer.toJson(profileChangedAt), + 'avatarColor': serializer.toJson(avatarColor), + 'quotaSizeInBytes': serializer.toJson(quotaSizeInBytes), + 'quotaUsageInBytes': serializer.toJson(quotaUsageInBytes), + 'pinCode': serializer.toJson(pinCode), + }; + } + + AuthUserEntityData copyWith({ + String? id, + String? name, + String? email, + int? isAdmin, + int? hasProfileImage, + String? profileChangedAt, + int? avatarColor, + int? quotaSizeInBytes, + int? quotaUsageInBytes, + Value pinCode = const Value.absent(), + }) => AuthUserEntityData( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + isAdmin: isAdmin ?? this.isAdmin, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + pinCode: pinCode.present ? pinCode.value : this.pinCode, + ); + AuthUserEntityData copyWithCompanion(AuthUserEntityCompanion data) { + return AuthUserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + email: data.email.present ? data.email.value : this.email, + isAdmin: data.isAdmin.present ? data.isAdmin.value : this.isAdmin, + hasProfileImage: data.hasProfileImage.present + ? data.hasProfileImage.value + : this.hasProfileImage, + profileChangedAt: data.profileChangedAt.present + ? data.profileChangedAt.value + : this.profileChangedAt, + avatarColor: data.avatarColor.present + ? data.avatarColor.value + : this.avatarColor, + quotaSizeInBytes: data.quotaSizeInBytes.present + ? data.quotaSizeInBytes.value + : this.quotaSizeInBytes, + quotaUsageInBytes: data.quotaUsageInBytes.present + ? data.quotaUsageInBytes.value + : this.quotaUsageInBytes, + pinCode: data.pinCode.present ? data.pinCode.value : this.pinCode, + ); + } + + @override + String toString() { + return (StringBuffer('AuthUserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('isAdmin: $isAdmin, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor, ') + ..write('quotaSizeInBytes: $quotaSizeInBytes, ') + ..write('quotaUsageInBytes: $quotaUsageInBytes, ') + ..write('pinCode: $pinCode') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + email, + isAdmin, + hasProfileImage, + profileChangedAt, + avatarColor, + quotaSizeInBytes, + quotaUsageInBytes, + pinCode, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AuthUserEntityData && + other.id == this.id && + other.name == this.name && + other.email == this.email && + other.isAdmin == this.isAdmin && + other.hasProfileImage == this.hasProfileImage && + other.profileChangedAt == this.profileChangedAt && + other.avatarColor == this.avatarColor && + other.quotaSizeInBytes == this.quotaSizeInBytes && + other.quotaUsageInBytes == this.quotaUsageInBytes && + other.pinCode == this.pinCode); +} + +class AuthUserEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value email; + final Value isAdmin; + final Value hasProfileImage; + final Value profileChangedAt; + final Value avatarColor; + final Value quotaSizeInBytes; + final Value quotaUsageInBytes; + final Value pinCode; + const AuthUserEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.email = const Value.absent(), + this.isAdmin = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + this.quotaSizeInBytes = const Value.absent(), + this.quotaUsageInBytes = const Value.absent(), + this.pinCode = const Value.absent(), + }); + AuthUserEntityCompanion.insert({ + required String id, + required String name, + required String email, + this.isAdmin = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + required int avatarColor, + this.quotaSizeInBytes = const Value.absent(), + this.quotaUsageInBytes = const Value.absent(), + this.pinCode = const Value.absent(), + }) : id = Value(id), + name = Value(name), + email = Value(email), + avatarColor = Value(avatarColor); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? email, + Expression? isAdmin, + Expression? hasProfileImage, + Expression? profileChangedAt, + Expression? avatarColor, + Expression? quotaSizeInBytes, + Expression? quotaUsageInBytes, + Expression? pinCode, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (email != null) 'email': email, + if (isAdmin != null) 'is_admin': isAdmin, + if (hasProfileImage != null) 'has_profile_image': hasProfileImage, + if (profileChangedAt != null) 'profile_changed_at': profileChangedAt, + if (avatarColor != null) 'avatar_color': avatarColor, + if (quotaSizeInBytes != null) 'quota_size_in_bytes': quotaSizeInBytes, + if (quotaUsageInBytes != null) 'quota_usage_in_bytes': quotaUsageInBytes, + if (pinCode != null) 'pin_code': pinCode, + }); + } + + AuthUserEntityCompanion copyWith({ + Value? id, + Value? name, + Value? email, + Value? isAdmin, + Value? hasProfileImage, + Value? profileChangedAt, + Value? avatarColor, + Value? quotaSizeInBytes, + Value? quotaUsageInBytes, + Value? pinCode, + }) { + return AuthUserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + isAdmin: isAdmin ?? this.isAdmin, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + pinCode: pinCode ?? this.pinCode, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (isAdmin.present) { + map['is_admin'] = Variable(isAdmin.value); + } + if (hasProfileImage.present) { + map['has_profile_image'] = Variable(hasProfileImage.value); + } + if (profileChangedAt.present) { + map['profile_changed_at'] = Variable(profileChangedAt.value); + } + if (avatarColor.present) { + map['avatar_color'] = Variable(avatarColor.value); + } + if (quotaSizeInBytes.present) { + map['quota_size_in_bytes'] = Variable(quotaSizeInBytes.value); + } + if (quotaUsageInBytes.present) { + map['quota_usage_in_bytes'] = Variable(quotaUsageInBytes.value); + } + if (pinCode.present) { + map['pin_code'] = Variable(pinCode.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AuthUserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('isAdmin: $isAdmin, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor, ') + ..write('quotaSizeInBytes: $quotaSizeInBytes, ') + ..write('quotaUsageInBytes: $quotaUsageInBytes, ') + ..write('pinCode: $pinCode') + ..write(')')) + .toString(); + } +} + +class UserMetadataEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserMetadataEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn key = GeneratedColumn( + 'key', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn value = + GeneratedColumn( + 'value', + aliasedName, + false, + type: DriftSqlType.blob, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + @override + List get $columns => [userId, key, value]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_metadata_entity'; + @override + Set get $primaryKey => {userId, key}; + @override + UserMetadataEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserMetadataEntityData( + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + key: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}key'], + )!, + value: attachedDatabase.typeMapping.read( + DriftSqlType.blob, + data['${effectivePrefix}value'], + )!, + ); + } + + @override + UserMetadataEntity createAlias(String alias) { + return UserMetadataEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(user_id, "key")']; + @override + bool get dontWriteConstraints => true; +} + +class UserMetadataEntityData extends DataClass + implements Insertable { + final String userId; + final int key; + final i2.Uint8List value; + const UserMetadataEntityData({ + required this.userId, + required this.key, + required this.value, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['user_id'] = Variable(userId); + map['key'] = Variable(key); + map['value'] = Variable(value); + return map; + } + + factory UserMetadataEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserMetadataEntityData( + userId: serializer.fromJson(json['userId']), + key: serializer.fromJson(json['key']), + value: serializer.fromJson(json['value']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'userId': serializer.toJson(userId), + 'key': serializer.toJson(key), + 'value': serializer.toJson(value), + }; + } + + UserMetadataEntityData copyWith({ + String? userId, + int? key, + i2.Uint8List? value, + }) => UserMetadataEntityData( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + UserMetadataEntityData copyWithCompanion(UserMetadataEntityCompanion data) { + return UserMetadataEntityData( + userId: data.userId.present ? data.userId.value : this.userId, + key: data.key.present ? data.key.value : this.key, + value: data.value.present ? data.value.value : this.value, + ); + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityData(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(userId, key, $driftBlobEquality.hash(value)); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserMetadataEntityData && + other.userId == this.userId && + other.key == this.key && + $driftBlobEquality.equals(other.value, this.value)); +} + +class UserMetadataEntityCompanion + extends UpdateCompanion { + final Value userId; + final Value key; + final Value value; + const UserMetadataEntityCompanion({ + this.userId = const Value.absent(), + this.key = const Value.absent(), + this.value = const Value.absent(), + }); + UserMetadataEntityCompanion.insert({ + required String userId, + required int key, + required i2.Uint8List value, + }) : userId = Value(userId), + key = Value(key), + value = Value(value); + static Insertable custom({ + Expression? userId, + Expression? key, + Expression? value, + }) { + return RawValuesInsertable({ + if (userId != null) 'user_id': userId, + if (key != null) 'key': key, + if (value != null) 'value': value, + }); + } + + UserMetadataEntityCompanion copyWith({ + Value? userId, + Value? key, + Value? value, + }) { + return UserMetadataEntityCompanion( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (key.present) { + map['key'] = Variable(key.value); + } + if (value.present) { + map['value'] = Variable(value.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityCompanion(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } +} + +class PartnerEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PartnerEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn sharedById = GeneratedColumn( + 'shared_by_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn sharedWithId = GeneratedColumn( + 'shared_with_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn inTimeline = GeneratedColumn( + 'in_timeline', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0 CHECK (in_timeline IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [sharedById, sharedWithId, inTimeline]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'partner_entity'; + @override + Set get $primaryKey => {sharedById, sharedWithId}; + @override + PartnerEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PartnerEntityData( + sharedById: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_by_id'], + )!, + sharedWithId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_with_id'], + )!, + inTimeline: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}in_timeline'], + )!, + ); + } + + @override + PartnerEntity createAlias(String alias) { + return PartnerEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const [ + 'PRIMARY KEY(shared_by_id, shared_with_id)', + ]; + @override + bool get dontWriteConstraints => true; +} + +class PartnerEntityData extends DataClass + implements Insertable { + final String sharedById; + final String sharedWithId; + final int inTimeline; + const PartnerEntityData({ + required this.sharedById, + required this.sharedWithId, + required this.inTimeline, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['shared_by_id'] = Variable(sharedById); + map['shared_with_id'] = Variable(sharedWithId); + map['in_timeline'] = Variable(inTimeline); + return map; + } + + factory PartnerEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PartnerEntityData( + sharedById: serializer.fromJson(json['sharedById']), + sharedWithId: serializer.fromJson(json['sharedWithId']), + inTimeline: serializer.fromJson(json['inTimeline']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'sharedById': serializer.toJson(sharedById), + 'sharedWithId': serializer.toJson(sharedWithId), + 'inTimeline': serializer.toJson(inTimeline), + }; + } + + PartnerEntityData copyWith({ + String? sharedById, + String? sharedWithId, + int? inTimeline, + }) => PartnerEntityData( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + PartnerEntityData copyWithCompanion(PartnerEntityCompanion data) { + return PartnerEntityData( + sharedById: data.sharedById.present + ? data.sharedById.value + : this.sharedById, + sharedWithId: data.sharedWithId.present + ? data.sharedWithId.value + : this.sharedWithId, + inTimeline: data.inTimeline.present + ? data.inTimeline.value + : this.inTimeline, + ); + } + + @override + String toString() { + return (StringBuffer('PartnerEntityData(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(sharedById, sharedWithId, inTimeline); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PartnerEntityData && + other.sharedById == this.sharedById && + other.sharedWithId == this.sharedWithId && + other.inTimeline == this.inTimeline); +} + +class PartnerEntityCompanion extends UpdateCompanion { + final Value sharedById; + final Value sharedWithId; + final Value inTimeline; + const PartnerEntityCompanion({ + this.sharedById = const Value.absent(), + this.sharedWithId = const Value.absent(), + this.inTimeline = const Value.absent(), + }); + PartnerEntityCompanion.insert({ + required String sharedById, + required String sharedWithId, + this.inTimeline = const Value.absent(), + }) : sharedById = Value(sharedById), + sharedWithId = Value(sharedWithId); + static Insertable custom({ + Expression? sharedById, + Expression? sharedWithId, + Expression? inTimeline, + }) { + return RawValuesInsertable({ + if (sharedById != null) 'shared_by_id': sharedById, + if (sharedWithId != null) 'shared_with_id': sharedWithId, + if (inTimeline != null) 'in_timeline': inTimeline, + }); + } + + PartnerEntityCompanion copyWith({ + Value? sharedById, + Value? sharedWithId, + Value? inTimeline, + }) { + return PartnerEntityCompanion( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (sharedById.present) { + map['shared_by_id'] = Variable(sharedById.value); + } + if (sharedWithId.present) { + map['shared_with_id'] = Variable(sharedWithId.value); + } + if (inTimeline.present) { + map['in_timeline'] = Variable(inTimeline.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PartnerEntityCompanion(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } +} + +class RemoteExifEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteExifEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES remote_asset_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn city = GeneratedColumn( + 'city', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn state = GeneratedColumn( + 'state', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn country = GeneratedColumn( + 'country', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn dateTimeOriginal = GeneratedColumn( + 'date_time_original', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn exposureTime = GeneratedColumn( + 'exposure_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn fNumber = GeneratedColumn( + 'f_number', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn fileSize = GeneratedColumn( + 'file_size', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn focalLength = GeneratedColumn( + 'focal_length', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn iso = GeneratedColumn( + 'iso', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn make = GeneratedColumn( + 'make', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn model = GeneratedColumn( + 'model', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn lens = GeneratedColumn( + 'lens', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn timeZone = GeneratedColumn( + 'time_zone', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn rating = GeneratedColumn( + 'rating', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn projectionType = GeneratedColumn( + 'projection_type', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + @override + List get $columns => [ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_exif_entity'; + @override + Set get $primaryKey => {assetId}; + @override + RemoteExifEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteExifEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + city: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}city'], + ), + state: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}state'], + ), + country: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}country'], + ), + dateTimeOriginal: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}date_time_original'], + ), + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + exposureTime: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}exposure_time'], + ), + fNumber: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}f_number'], + ), + fileSize: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}file_size'], + ), + focalLength: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}focal_length'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + iso: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}iso'], + ), + make: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}make'], + ), + model: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}model'], + ), + lens: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}lens'], + ), + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}orientation'], + ), + timeZone: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}time_zone'], + ), + rating: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}rating'], + ), + projectionType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}projection_type'], + ), + ); + } + + @override + RemoteExifEntity createAlias(String alias) { + return RemoteExifEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(asset_id)']; + @override + bool get dontWriteConstraints => true; +} + +class RemoteExifEntityData extends DataClass + implements Insertable { + final String assetId; + final String? city; + final String? state; + final String? country; + final String? dateTimeOriginal; + final String? description; + final int? height; + final int? width; + final String? exposureTime; + final double? fNumber; + final int? fileSize; + final double? focalLength; + final double? latitude; + final double? longitude; + final int? iso; + final String? make; + final String? model; + final String? lens; + final String? orientation; + final String? timeZone; + final int? rating; + final String? projectionType; + const RemoteExifEntityData({ + required this.assetId, + this.city, + this.state, + this.country, + this.dateTimeOriginal, + this.description, + this.height, + this.width, + this.exposureTime, + this.fNumber, + this.fileSize, + this.focalLength, + this.latitude, + this.longitude, + this.iso, + this.make, + this.model, + this.lens, + this.orientation, + this.timeZone, + this.rating, + this.projectionType, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || city != null) { + map['city'] = Variable(city); + } + if (!nullToAbsent || state != null) { + map['state'] = Variable(state); + } + if (!nullToAbsent || country != null) { + map['country'] = Variable(country); + } + if (!nullToAbsent || dateTimeOriginal != null) { + map['date_time_original'] = Variable(dateTimeOriginal); + } + if (!nullToAbsent || description != null) { + map['description'] = Variable(description); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || exposureTime != null) { + map['exposure_time'] = Variable(exposureTime); + } + if (!nullToAbsent || fNumber != null) { + map['f_number'] = Variable(fNumber); + } + if (!nullToAbsent || fileSize != null) { + map['file_size'] = Variable(fileSize); + } + if (!nullToAbsent || focalLength != null) { + map['focal_length'] = Variable(focalLength); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + if (!nullToAbsent || iso != null) { + map['iso'] = Variable(iso); + } + if (!nullToAbsent || make != null) { + map['make'] = Variable(make); + } + if (!nullToAbsent || model != null) { + map['model'] = Variable(model); + } + if (!nullToAbsent || lens != null) { + map['lens'] = Variable(lens); + } + if (!nullToAbsent || orientation != null) { + map['orientation'] = Variable(orientation); + } + if (!nullToAbsent || timeZone != null) { + map['time_zone'] = Variable(timeZone); + } + if (!nullToAbsent || rating != null) { + map['rating'] = Variable(rating); + } + if (!nullToAbsent || projectionType != null) { + map['projection_type'] = Variable(projectionType); + } + return map; + } + + factory RemoteExifEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteExifEntityData( + assetId: serializer.fromJson(json['assetId']), + city: serializer.fromJson(json['city']), + state: serializer.fromJson(json['state']), + country: serializer.fromJson(json['country']), + dateTimeOriginal: serializer.fromJson(json['dateTimeOriginal']), + description: serializer.fromJson(json['description']), + height: serializer.fromJson(json['height']), + width: serializer.fromJson(json['width']), + exposureTime: serializer.fromJson(json['exposureTime']), + fNumber: serializer.fromJson(json['fNumber']), + fileSize: serializer.fromJson(json['fileSize']), + focalLength: serializer.fromJson(json['focalLength']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + iso: serializer.fromJson(json['iso']), + make: serializer.fromJson(json['make']), + model: serializer.fromJson(json['model']), + lens: serializer.fromJson(json['lens']), + orientation: serializer.fromJson(json['orientation']), + timeZone: serializer.fromJson(json['timeZone']), + rating: serializer.fromJson(json['rating']), + projectionType: serializer.fromJson(json['projectionType']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'city': serializer.toJson(city), + 'state': serializer.toJson(state), + 'country': serializer.toJson(country), + 'dateTimeOriginal': serializer.toJson(dateTimeOriginal), + 'description': serializer.toJson(description), + 'height': serializer.toJson(height), + 'width': serializer.toJson(width), + 'exposureTime': serializer.toJson(exposureTime), + 'fNumber': serializer.toJson(fNumber), + 'fileSize': serializer.toJson(fileSize), + 'focalLength': serializer.toJson(focalLength), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + 'iso': serializer.toJson(iso), + 'make': serializer.toJson(make), + 'model': serializer.toJson(model), + 'lens': serializer.toJson(lens), + 'orientation': serializer.toJson(orientation), + 'timeZone': serializer.toJson(timeZone), + 'rating': serializer.toJson(rating), + 'projectionType': serializer.toJson(projectionType), + }; + } + + RemoteExifEntityData copyWith({ + String? assetId, + Value city = const Value.absent(), + Value state = const Value.absent(), + Value country = const Value.absent(), + Value dateTimeOriginal = const Value.absent(), + Value description = const Value.absent(), + Value height = const Value.absent(), + Value width = const Value.absent(), + Value exposureTime = const Value.absent(), + Value fNumber = const Value.absent(), + Value fileSize = const Value.absent(), + Value focalLength = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + Value iso = const Value.absent(), + Value make = const Value.absent(), + Value model = const Value.absent(), + Value lens = const Value.absent(), + Value orientation = const Value.absent(), + Value timeZone = const Value.absent(), + Value rating = const Value.absent(), + Value projectionType = const Value.absent(), + }) => RemoteExifEntityData( + assetId: assetId ?? this.assetId, + city: city.present ? city.value : this.city, + state: state.present ? state.value : this.state, + country: country.present ? country.value : this.country, + dateTimeOriginal: dateTimeOriginal.present + ? dateTimeOriginal.value + : this.dateTimeOriginal, + description: description.present ? description.value : this.description, + height: height.present ? height.value : this.height, + width: width.present ? width.value : this.width, + exposureTime: exposureTime.present ? exposureTime.value : this.exposureTime, + fNumber: fNumber.present ? fNumber.value : this.fNumber, + fileSize: fileSize.present ? fileSize.value : this.fileSize, + focalLength: focalLength.present ? focalLength.value : this.focalLength, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + iso: iso.present ? iso.value : this.iso, + make: make.present ? make.value : this.make, + model: model.present ? model.value : this.model, + lens: lens.present ? lens.value : this.lens, + orientation: orientation.present ? orientation.value : this.orientation, + timeZone: timeZone.present ? timeZone.value : this.timeZone, + rating: rating.present ? rating.value : this.rating, + projectionType: projectionType.present + ? projectionType.value + : this.projectionType, + ); + RemoteExifEntityData copyWithCompanion(RemoteExifEntityCompanion data) { + return RemoteExifEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + city: data.city.present ? data.city.value : this.city, + state: data.state.present ? data.state.value : this.state, + country: data.country.present ? data.country.value : this.country, + dateTimeOriginal: data.dateTimeOriginal.present + ? data.dateTimeOriginal.value + : this.dateTimeOriginal, + description: data.description.present + ? data.description.value + : this.description, + height: data.height.present ? data.height.value : this.height, + width: data.width.present ? data.width.value : this.width, + exposureTime: data.exposureTime.present + ? data.exposureTime.value + : this.exposureTime, + fNumber: data.fNumber.present ? data.fNumber.value : this.fNumber, + fileSize: data.fileSize.present ? data.fileSize.value : this.fileSize, + focalLength: data.focalLength.present + ? data.focalLength.value + : this.focalLength, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + iso: data.iso.present ? data.iso.value : this.iso, + make: data.make.present ? data.make.value : this.make, + model: data.model.present ? data.model.value : this.model, + lens: data.lens.present ? data.lens.value : this.lens, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + timeZone: data.timeZone.present ? data.timeZone.value : this.timeZone, + rating: data.rating.present ? data.rating.value : this.rating, + projectionType: data.projectionType.present + ? data.projectionType.value + : this.projectionType, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityData(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hashAll([ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteExifEntityData && + other.assetId == this.assetId && + other.city == this.city && + other.state == this.state && + other.country == this.country && + other.dateTimeOriginal == this.dateTimeOriginal && + other.description == this.description && + other.height == this.height && + other.width == this.width && + other.exposureTime == this.exposureTime && + other.fNumber == this.fNumber && + other.fileSize == this.fileSize && + other.focalLength == this.focalLength && + other.latitude == this.latitude && + other.longitude == this.longitude && + other.iso == this.iso && + other.make == this.make && + other.model == this.model && + other.lens == this.lens && + other.orientation == this.orientation && + other.timeZone == this.timeZone && + other.rating == this.rating && + other.projectionType == this.projectionType); +} + +class RemoteExifEntityCompanion extends UpdateCompanion { + final Value assetId; + final Value city; + final Value state; + final Value country; + final Value dateTimeOriginal; + final Value description; + final Value height; + final Value width; + final Value exposureTime; + final Value fNumber; + final Value fileSize; + final Value focalLength; + final Value latitude; + final Value longitude; + final Value iso; + final Value make; + final Value model; + final Value lens; + final Value orientation; + final Value timeZone; + final Value rating; + final Value projectionType; + const RemoteExifEntityCompanion({ + this.assetId = const Value.absent(), + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }); + RemoteExifEntityCompanion.insert({ + required String assetId, + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }) : assetId = Value(assetId); + static Insertable custom({ + Expression? assetId, + Expression? city, + Expression? state, + Expression? country, + Expression? dateTimeOriginal, + Expression? description, + Expression? height, + Expression? width, + Expression? exposureTime, + Expression? fNumber, + Expression? fileSize, + Expression? focalLength, + Expression? latitude, + Expression? longitude, + Expression? iso, + Expression? make, + Expression? model, + Expression? lens, + Expression? orientation, + Expression? timeZone, + Expression? rating, + Expression? projectionType, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (city != null) 'city': city, + if (state != null) 'state': state, + if (country != null) 'country': country, + if (dateTimeOriginal != null) 'date_time_original': dateTimeOriginal, + if (description != null) 'description': description, + if (height != null) 'height': height, + if (width != null) 'width': width, + if (exposureTime != null) 'exposure_time': exposureTime, + if (fNumber != null) 'f_number': fNumber, + if (fileSize != null) 'file_size': fileSize, + if (focalLength != null) 'focal_length': focalLength, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + if (iso != null) 'iso': iso, + if (make != null) 'make': make, + if (model != null) 'model': model, + if (lens != null) 'lens': lens, + if (orientation != null) 'orientation': orientation, + if (timeZone != null) 'time_zone': timeZone, + if (rating != null) 'rating': rating, + if (projectionType != null) 'projection_type': projectionType, + }); + } + + RemoteExifEntityCompanion copyWith({ + Value? assetId, + Value? city, + Value? state, + Value? country, + Value? dateTimeOriginal, + Value? description, + Value? height, + Value? width, + Value? exposureTime, + Value? fNumber, + Value? fileSize, + Value? focalLength, + Value? latitude, + Value? longitude, + Value? iso, + Value? make, + Value? model, + Value? lens, + Value? orientation, + Value? timeZone, + Value? rating, + Value? projectionType, + }) { + return RemoteExifEntityCompanion( + assetId: assetId ?? this.assetId, + city: city ?? this.city, + state: state ?? this.state, + country: country ?? this.country, + dateTimeOriginal: dateTimeOriginal ?? this.dateTimeOriginal, + description: description ?? this.description, + height: height ?? this.height, + width: width ?? this.width, + exposureTime: exposureTime ?? this.exposureTime, + fNumber: fNumber ?? this.fNumber, + fileSize: fileSize ?? this.fileSize, + focalLength: focalLength ?? this.focalLength, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + iso: iso ?? this.iso, + make: make ?? this.make, + model: model ?? this.model, + lens: lens ?? this.lens, + orientation: orientation ?? this.orientation, + timeZone: timeZone ?? this.timeZone, + rating: rating ?? this.rating, + projectionType: projectionType ?? this.projectionType, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (city.present) { + map['city'] = Variable(city.value); + } + if (state.present) { + map['state'] = Variable(state.value); + } + if (country.present) { + map['country'] = Variable(country.value); + } + if (dateTimeOriginal.present) { + map['date_time_original'] = Variable(dateTimeOriginal.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (exposureTime.present) { + map['exposure_time'] = Variable(exposureTime.value); + } + if (fNumber.present) { + map['f_number'] = Variable(fNumber.value); + } + if (fileSize.present) { + map['file_size'] = Variable(fileSize.value); + } + if (focalLength.present) { + map['focal_length'] = Variable(focalLength.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + if (iso.present) { + map['iso'] = Variable(iso.value); + } + if (make.present) { + map['make'] = Variable(make.value); + } + if (model.present) { + map['model'] = Variable(model.value); + } + if (lens.present) { + map['lens'] = Variable(lens.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (timeZone.present) { + map['time_zone'] = Variable(timeZone.value); + } + if (rating.present) { + map['rating'] = Variable(rating.value); + } + if (projectionType.present) { + map['projection_type'] = Variable(projectionType.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES remote_asset_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES remote_album_entity(id)ON DELETE CASCADE', + ); + @override + List get $columns => [assetId, albumId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + RemoteAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + ); + } + + @override + RemoteAlbumAssetEntity createAlias(String alias) { + return RemoteAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const [ + 'PRIMARY KEY(asset_id, album_id)', + ]; + @override + bool get dontWriteConstraints => true; +} + +class RemoteAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + const RemoteAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + return map; + } + + factory RemoteAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + }; + } + + RemoteAlbumAssetEntityData copyWith({String? assetId, String? albumId}) => + RemoteAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + RemoteAlbumAssetEntityData copyWithCompanion( + RemoteAlbumAssetEntityCompanion data, + ) { + return RemoteAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId); +} + +class RemoteAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + const RemoteAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + }); + RemoteAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + }); + } + + RemoteAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + }) { + return RemoteAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumUserEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumUserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES remote_album_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn role = GeneratedColumn( + 'role', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + @override + List get $columns => [albumId, userId, role]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_user_entity'; + @override + Set get $primaryKey => {albumId, userId}; + @override + RemoteAlbumUserEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumUserEntityData( + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + role: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}role'], + )!, + ); + } + + @override + RemoteAlbumUserEntity createAlias(String alias) { + return RemoteAlbumUserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const [ + 'PRIMARY KEY(album_id, user_id)', + ]; + @override + bool get dontWriteConstraints => true; +} + +class RemoteAlbumUserEntityData extends DataClass + implements Insertable { + final String albumId; + final String userId; + final int role; + const RemoteAlbumUserEntityData({ + required this.albumId, + required this.userId, + required this.role, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['album_id'] = Variable(albumId); + map['user_id'] = Variable(userId); + map['role'] = Variable(role); + return map; + } + + factory RemoteAlbumUserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumUserEntityData( + albumId: serializer.fromJson(json['albumId']), + userId: serializer.fromJson(json['userId']), + role: serializer.fromJson(json['role']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'albumId': serializer.toJson(albumId), + 'userId': serializer.toJson(userId), + 'role': serializer.toJson(role), + }; + } + + RemoteAlbumUserEntityData copyWith({ + String? albumId, + String? userId, + int? role, + }) => RemoteAlbumUserEntityData( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + RemoteAlbumUserEntityData copyWithCompanion( + RemoteAlbumUserEntityCompanion data, + ) { + return RemoteAlbumUserEntityData( + albumId: data.albumId.present ? data.albumId.value : this.albumId, + userId: data.userId.present ? data.userId.value : this.userId, + role: data.role.present ? data.role.value : this.role, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityData(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(albumId, userId, role); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumUserEntityData && + other.albumId == this.albumId && + other.userId == this.userId && + other.role == this.role); +} + +class RemoteAlbumUserEntityCompanion + extends UpdateCompanion { + final Value albumId; + final Value userId; + final Value role; + const RemoteAlbumUserEntityCompanion({ + this.albumId = const Value.absent(), + this.userId = const Value.absent(), + this.role = const Value.absent(), + }); + RemoteAlbumUserEntityCompanion.insert({ + required String albumId, + required String userId, + required int role, + }) : albumId = Value(albumId), + userId = Value(userId), + role = Value(role); + static Insertable custom({ + Expression? albumId, + Expression? userId, + Expression? role, + }) { + return RawValuesInsertable({ + if (albumId != null) 'album_id': albumId, + if (userId != null) 'user_id': userId, + if (role != null) 'role': role, + }); + } + + RemoteAlbumUserEntityCompanion copyWith({ + Value? albumId, + Value? userId, + Value? role, + }) { + return RemoteAlbumUserEntityCompanion( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (role.present) { + map['role'] = Variable(role.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityCompanion(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } +} + +class RemoteAssetCloudIdEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAssetCloudIdEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES remote_asset_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn cloudId = GeneratedColumn( + 'cloud_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn adjustmentTime = GeneratedColumn( + 'adjustment_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + @override + List get $columns => [ + assetId, + cloudId, + createdAt, + adjustmentTime, + latitude, + longitude, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_asset_cloud_id_entity'; + @override + Set get $primaryKey => {assetId}; + @override + RemoteAssetCloudIdEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAssetCloudIdEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + cloudId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}cloud_id'], + ), + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + ), + adjustmentTime: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}adjustment_time'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + ); + } + + @override + RemoteAssetCloudIdEntity createAlias(String alias) { + return RemoteAssetCloudIdEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(asset_id)']; + @override + bool get dontWriteConstraints => true; +} + +class RemoteAssetCloudIdEntityData extends DataClass + implements Insertable { + final String assetId; + final String? cloudId; + final String? createdAt; + final String? adjustmentTime; + final double? latitude; + final double? longitude; + const RemoteAssetCloudIdEntityData({ + required this.assetId, + this.cloudId, + this.createdAt, + this.adjustmentTime, + this.latitude, + this.longitude, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || cloudId != null) { + map['cloud_id'] = Variable(cloudId); + } + if (!nullToAbsent || createdAt != null) { + map['created_at'] = Variable(createdAt); + } + if (!nullToAbsent || adjustmentTime != null) { + map['adjustment_time'] = Variable(adjustmentTime); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + return map; + } + + factory RemoteAssetCloudIdEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAssetCloudIdEntityData( + assetId: serializer.fromJson(json['assetId']), + cloudId: serializer.fromJson(json['cloudId']), + createdAt: serializer.fromJson(json['createdAt']), + adjustmentTime: serializer.fromJson(json['adjustmentTime']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'cloudId': serializer.toJson(cloudId), + 'createdAt': serializer.toJson(createdAt), + 'adjustmentTime': serializer.toJson(adjustmentTime), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + }; + } + + RemoteAssetCloudIdEntityData copyWith({ + String? assetId, + Value cloudId = const Value.absent(), + Value createdAt = const Value.absent(), + Value adjustmentTime = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + }) => RemoteAssetCloudIdEntityData( + assetId: assetId ?? this.assetId, + cloudId: cloudId.present ? cloudId.value : this.cloudId, + createdAt: createdAt.present ? createdAt.value : this.createdAt, + adjustmentTime: adjustmentTime.present + ? adjustmentTime.value + : this.adjustmentTime, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + ); + RemoteAssetCloudIdEntityData copyWithCompanion( + RemoteAssetCloudIdEntityCompanion data, + ) { + return RemoteAssetCloudIdEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + cloudId: data.cloudId.present ? data.cloudId.value : this.cloudId, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + adjustmentTime: data.adjustmentTime.present + ? data.adjustmentTime.value + : this.adjustmentTime, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAssetCloudIdEntityData(') + ..write('assetId: $assetId, ') + ..write('cloudId: $cloudId, ') + ..write('createdAt: $createdAt, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + assetId, + cloudId, + createdAt, + adjustmentTime, + latitude, + longitude, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAssetCloudIdEntityData && + other.assetId == this.assetId && + other.cloudId == this.cloudId && + other.createdAt == this.createdAt && + other.adjustmentTime == this.adjustmentTime && + other.latitude == this.latitude && + other.longitude == this.longitude); +} + +class RemoteAssetCloudIdEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value cloudId; + final Value createdAt; + final Value adjustmentTime; + final Value latitude; + final Value longitude; + const RemoteAssetCloudIdEntityCompanion({ + this.assetId = const Value.absent(), + this.cloudId = const Value.absent(), + this.createdAt = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + }); + RemoteAssetCloudIdEntityCompanion.insert({ + required String assetId, + this.cloudId = const Value.absent(), + this.createdAt = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + }) : assetId = Value(assetId); + static Insertable custom({ + Expression? assetId, + Expression? cloudId, + Expression? createdAt, + Expression? adjustmentTime, + Expression? latitude, + Expression? longitude, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (cloudId != null) 'cloud_id': cloudId, + if (createdAt != null) 'created_at': createdAt, + if (adjustmentTime != null) 'adjustment_time': adjustmentTime, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + }); + } + + RemoteAssetCloudIdEntityCompanion copyWith({ + Value? assetId, + Value? cloudId, + Value? createdAt, + Value? adjustmentTime, + Value? latitude, + Value? longitude, + }) { + return RemoteAssetCloudIdEntityCompanion( + assetId: assetId ?? this.assetId, + cloudId: cloudId ?? this.cloudId, + createdAt: createdAt ?? this.createdAt, + adjustmentTime: adjustmentTime ?? this.adjustmentTime, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (cloudId.present) { + map['cloud_id'] = Variable(cloudId.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (adjustmentTime.present) { + map['adjustment_time'] = Variable(adjustmentTime.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAssetCloudIdEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('cloudId: $cloudId, ') + ..write('createdAt: $createdAt, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude') + ..write(')')) + .toString(); + } +} + +class MemoryEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn data = GeneratedColumn( + 'data', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn isSaved = GeneratedColumn( + 'is_saved', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0 CHECK (is_saved IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn memoryAt = GeneratedColumn( + 'memory_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn seenAt = GeneratedColumn( + 'seen_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn showAt = GeneratedColumn( + 'show_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn hideAt = GeneratedColumn( + 'hide_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_entity'; + @override + Set get $primaryKey => {id}; + @override + MemoryEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}deleted_at'], + ), + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + data: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}data'], + )!, + isSaved: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_saved'], + )!, + memoryAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}memory_at'], + )!, + seenAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}seen_at'], + ), + showAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}show_at'], + ), + hideAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}hide_at'], + ), + ); + } + + @override + MemoryEntity createAlias(String alias) { + return MemoryEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class MemoryEntityData extends DataClass + implements Insertable { + final String id; + final String createdAt; + final String updatedAt; + final String? deletedAt; + final String ownerId; + final int type; + final String data; + final int isSaved; + final String memoryAt; + final String? seenAt; + final String? showAt; + final String? hideAt; + const MemoryEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + this.deletedAt, + required this.ownerId, + required this.type, + required this.data, + required this.isSaved, + required this.memoryAt, + this.seenAt, + this.showAt, + this.hideAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + map['owner_id'] = Variable(ownerId); + map['type'] = Variable(type); + map['data'] = Variable(data); + map['is_saved'] = Variable(isSaved); + map['memory_at'] = Variable(memoryAt); + if (!nullToAbsent || seenAt != null) { + map['seen_at'] = Variable(seenAt); + } + if (!nullToAbsent || showAt != null) { + map['show_at'] = Variable(showAt); + } + if (!nullToAbsent || hideAt != null) { + map['hide_at'] = Variable(hideAt); + } + return map; + } + + factory MemoryEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + deletedAt: serializer.fromJson(json['deletedAt']), + ownerId: serializer.fromJson(json['ownerId']), + type: serializer.fromJson(json['type']), + data: serializer.fromJson(json['data']), + isSaved: serializer.fromJson(json['isSaved']), + memoryAt: serializer.fromJson(json['memoryAt']), + seenAt: serializer.fromJson(json['seenAt']), + showAt: serializer.fromJson(json['showAt']), + hideAt: serializer.fromJson(json['hideAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'deletedAt': serializer.toJson(deletedAt), + 'ownerId': serializer.toJson(ownerId), + 'type': serializer.toJson(type), + 'data': serializer.toJson(data), + 'isSaved': serializer.toJson(isSaved), + 'memoryAt': serializer.toJson(memoryAt), + 'seenAt': serializer.toJson(seenAt), + 'showAt': serializer.toJson(showAt), + 'hideAt': serializer.toJson(hideAt), + }; + } + + MemoryEntityData copyWith({ + String? id, + String? createdAt, + String? updatedAt, + Value deletedAt = const Value.absent(), + String? ownerId, + int? type, + String? data, + int? isSaved, + String? memoryAt, + Value seenAt = const Value.absent(), + Value showAt = const Value.absent(), + Value hideAt = const Value.absent(), + }) => MemoryEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt.present ? seenAt.value : this.seenAt, + showAt: showAt.present ? showAt.value : this.showAt, + hideAt: hideAt.present ? hideAt.value : this.hideAt, + ); + MemoryEntityData copyWithCompanion(MemoryEntityCompanion data) { + return MemoryEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + type: data.type.present ? data.type.value : this.type, + data: data.data.present ? data.data.value : this.data, + isSaved: data.isSaved.present ? data.isSaved.value : this.isSaved, + memoryAt: data.memoryAt.present ? data.memoryAt.value : this.memoryAt, + seenAt: data.seenAt.present ? data.seenAt.value : this.seenAt, + showAt: data.showAt.present ? data.showAt.value : this.showAt, + hideAt: data.hideAt.present ? data.hideAt.value : this.hideAt, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.deletedAt == this.deletedAt && + other.ownerId == this.ownerId && + other.type == this.type && + other.data == this.data && + other.isSaved == this.isSaved && + other.memoryAt == this.memoryAt && + other.seenAt == this.seenAt && + other.showAt == this.showAt && + other.hideAt == this.hideAt); +} + +class MemoryEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value deletedAt; + final Value ownerId; + final Value type; + final Value data; + final Value isSaved; + final Value memoryAt; + final Value seenAt; + final Value showAt; + final Value hideAt; + const MemoryEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.type = const Value.absent(), + this.data = const Value.absent(), + this.isSaved = const Value.absent(), + this.memoryAt = const Value.absent(), + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }); + MemoryEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + required String ownerId, + required int type, + required String data, + this.isSaved = const Value.absent(), + required String memoryAt, + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + type = Value(type), + data = Value(data), + memoryAt = Value(memoryAt); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? deletedAt, + Expression? ownerId, + Expression? type, + Expression? data, + Expression? isSaved, + Expression? memoryAt, + Expression? seenAt, + Expression? showAt, + Expression? hideAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (deletedAt != null) 'deleted_at': deletedAt, + if (ownerId != null) 'owner_id': ownerId, + if (type != null) 'type': type, + if (data != null) 'data': data, + if (isSaved != null) 'is_saved': isSaved, + if (memoryAt != null) 'memory_at': memoryAt, + if (seenAt != null) 'seen_at': seenAt, + if (showAt != null) 'show_at': showAt, + if (hideAt != null) 'hide_at': hideAt, + }); + } + + MemoryEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? deletedAt, + Value? ownerId, + Value? type, + Value? data, + Value? isSaved, + Value? memoryAt, + Value? seenAt, + Value? showAt, + Value? hideAt, + }) { + return MemoryEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt ?? this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt ?? this.seenAt, + showAt: showAt ?? this.showAt, + hideAt: hideAt ?? this.hideAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (data.present) { + map['data'] = Variable(data.value); + } + if (isSaved.present) { + map['is_saved'] = Variable(isSaved.value); + } + if (memoryAt.present) { + map['memory_at'] = Variable(memoryAt.value); + } + if (seenAt.present) { + map['seen_at'] = Variable(seenAt.value); + } + if (showAt.present) { + map['show_at'] = Variable(showAt.value); + } + if (hideAt.present) { + map['hide_at'] = Variable(hideAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } +} + +class MemoryAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES remote_asset_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn memoryId = GeneratedColumn( + 'memory_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES memory_entity(id)ON DELETE CASCADE', + ); + @override + List get $columns => [assetId, memoryId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_asset_entity'; + @override + Set get $primaryKey => {assetId, memoryId}; + @override + MemoryAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + memoryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}memory_id'], + )!, + ); + } + + @override + MemoryAssetEntity createAlias(String alias) { + return MemoryAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const [ + 'PRIMARY KEY(asset_id, memory_id)', + ]; + @override + bool get dontWriteConstraints => true; +} + +class MemoryAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String memoryId; + const MemoryAssetEntityData({required this.assetId, required this.memoryId}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['memory_id'] = Variable(memoryId); + return map; + } + + factory MemoryAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + memoryId: serializer.fromJson(json['memoryId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'memoryId': serializer.toJson(memoryId), + }; + } + + MemoryAssetEntityData copyWith({String? assetId, String? memoryId}) => + MemoryAssetEntityData( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + MemoryAssetEntityData copyWithCompanion(MemoryAssetEntityCompanion data) { + return MemoryAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + memoryId: data.memoryId.present ? data.memoryId.value : this.memoryId, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, memoryId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryAssetEntityData && + other.assetId == this.assetId && + other.memoryId == this.memoryId); +} + +class MemoryAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value memoryId; + const MemoryAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.memoryId = const Value.absent(), + }); + MemoryAssetEntityCompanion.insert({ + required String assetId, + required String memoryId, + }) : assetId = Value(assetId), + memoryId = Value(memoryId); + static Insertable custom({ + Expression? assetId, + Expression? memoryId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (memoryId != null) 'memory_id': memoryId, + }); + } + + MemoryAssetEntityCompanion copyWith({ + Value? assetId, + Value? memoryId, + }) { + return MemoryAssetEntityCompanion( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (memoryId.present) { + map['memory_id'] = Variable(memoryId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } +} + +class PersonEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PersonEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn faceAssetId = GeneratedColumn( + 'face_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL CHECK (is_favorite IN (0, 1))', + ); + late final GeneratedColumn isHidden = GeneratedColumn( + 'is_hidden', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL CHECK (is_hidden IN (0, 1))', + ); + late final GeneratedColumn color = GeneratedColumn( + 'color', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn birthDate = GeneratedColumn( + 'birth_date', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'person_entity'; + @override + Set get $primaryKey => {id}; + @override + PersonEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PersonEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + faceAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}face_asset_id'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_favorite'], + )!, + isHidden: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_hidden'], + )!, + color: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}color'], + ), + birthDate: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}birth_date'], + ), + ); + } + + @override + PersonEntity createAlias(String alias) { + return PersonEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class PersonEntityData extends DataClass + implements Insertable { + final String id; + final String createdAt; + final String updatedAt; + final String ownerId; + final String name; + final String? faceAssetId; + final int isFavorite; + final int isHidden; + final String? color; + final String? birthDate; + const PersonEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.name, + this.faceAssetId, + required this.isFavorite, + required this.isHidden, + this.color, + this.birthDate, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['name'] = Variable(name); + if (!nullToAbsent || faceAssetId != null) { + map['face_asset_id'] = Variable(faceAssetId); + } + map['is_favorite'] = Variable(isFavorite); + map['is_hidden'] = Variable(isHidden); + if (!nullToAbsent || color != null) { + map['color'] = Variable(color); + } + if (!nullToAbsent || birthDate != null) { + map['birth_date'] = Variable(birthDate); + } + return map; + } + + factory PersonEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PersonEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + name: serializer.fromJson(json['name']), + faceAssetId: serializer.fromJson(json['faceAssetId']), + isFavorite: serializer.fromJson(json['isFavorite']), + isHidden: serializer.fromJson(json['isHidden']), + color: serializer.fromJson(json['color']), + birthDate: serializer.fromJson(json['birthDate']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'name': serializer.toJson(name), + 'faceAssetId': serializer.toJson(faceAssetId), + 'isFavorite': serializer.toJson(isFavorite), + 'isHidden': serializer.toJson(isHidden), + 'color': serializer.toJson(color), + 'birthDate': serializer.toJson(birthDate), + }; + } + + PersonEntityData copyWith({ + String? id, + String? createdAt, + String? updatedAt, + String? ownerId, + String? name, + Value faceAssetId = const Value.absent(), + int? isFavorite, + int? isHidden, + Value color = const Value.absent(), + Value birthDate = const Value.absent(), + }) => PersonEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId.present ? faceAssetId.value : this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color.present ? color.value : this.color, + birthDate: birthDate.present ? birthDate.value : this.birthDate, + ); + PersonEntityData copyWithCompanion(PersonEntityCompanion data) { + return PersonEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + name: data.name.present ? data.name.value : this.name, + faceAssetId: data.faceAssetId.present + ? data.faceAssetId.value + : this.faceAssetId, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + isHidden: data.isHidden.present ? data.isHidden.value : this.isHidden, + color: data.color.present ? data.color.value : this.color, + birthDate: data.birthDate.present ? data.birthDate.value : this.birthDate, + ); + } + + @override + String toString() { + return (StringBuffer('PersonEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PersonEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.name == this.name && + other.faceAssetId == this.faceAssetId && + other.isFavorite == this.isFavorite && + other.isHidden == this.isHidden && + other.color == this.color && + other.birthDate == this.birthDate); +} + +class PersonEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value name; + final Value faceAssetId; + final Value isFavorite; + final Value isHidden; + final Value color; + final Value birthDate; + const PersonEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.name = const Value.absent(), + this.faceAssetId = const Value.absent(), + this.isFavorite = const Value.absent(), + this.isHidden = const Value.absent(), + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }); + PersonEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String name, + this.faceAssetId = const Value.absent(), + required int isFavorite, + required int isHidden, + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + name = Value(name), + isFavorite = Value(isFavorite), + isHidden = Value(isHidden); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? name, + Expression? faceAssetId, + Expression? isFavorite, + Expression? isHidden, + Expression? color, + Expression? birthDate, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (name != null) 'name': name, + if (faceAssetId != null) 'face_asset_id': faceAssetId, + if (isFavorite != null) 'is_favorite': isFavorite, + if (isHidden != null) 'is_hidden': isHidden, + if (color != null) 'color': color, + if (birthDate != null) 'birth_date': birthDate, + }); + } + + PersonEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? name, + Value? faceAssetId, + Value? isFavorite, + Value? isHidden, + Value? color, + Value? birthDate, + }) { + return PersonEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId ?? this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color ?? this.color, + birthDate: birthDate ?? this.birthDate, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (faceAssetId.present) { + map['face_asset_id'] = Variable(faceAssetId.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (isHidden.present) { + map['is_hidden'] = Variable(isHidden.value); + } + if (color.present) { + map['color'] = Variable(color.value); + } + if (birthDate.present) { + map['birth_date'] = Variable(birthDate.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PersonEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } +} + +class AssetFaceEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AssetFaceEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES remote_asset_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn personId = GeneratedColumn( + 'person_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL REFERENCES person_entity(id)ON DELETE SET NULL', + ); + late final GeneratedColumn imageWidth = GeneratedColumn( + 'image_width', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn imageHeight = GeneratedColumn( + 'image_height', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn boundingBoxX1 = GeneratedColumn( + 'bounding_box_x1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn boundingBoxY1 = GeneratedColumn( + 'bounding_box_y1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn boundingBoxX2 = GeneratedColumn( + 'bounding_box_x2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn boundingBoxY2 = GeneratedColumn( + 'bounding_box_y2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn sourceType = GeneratedColumn( + 'source_type', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn isVisible = GeneratedColumn( + 'is_visible', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 1 CHECK (is_visible IN (0, 1))', + defaultValue: const CustomExpression('1'), + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + @override + List get $columns => [ + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + isVisible, + deletedAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'asset_face_entity'; + @override + Set get $primaryKey => {id}; + @override + AssetFaceEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AssetFaceEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + personId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}person_id'], + ), + imageWidth: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_width'], + )!, + imageHeight: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_height'], + )!, + boundingBoxX1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x1'], + )!, + boundingBoxY1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y1'], + )!, + boundingBoxX2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x2'], + )!, + boundingBoxY2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y2'], + )!, + sourceType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}source_type'], + )!, + isVisible: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_visible'], + )!, + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}deleted_at'], + ), + ); + } + + @override + AssetFaceEntity createAlias(String alias) { + return AssetFaceEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class AssetFaceEntityData extends DataClass + implements Insertable { + final String id; + final String assetId; + final String? personId; + final int imageWidth; + final int imageHeight; + final int boundingBoxX1; + final int boundingBoxY1; + final int boundingBoxX2; + final int boundingBoxY2; + final String sourceType; + final int isVisible; + final String? deletedAt; + const AssetFaceEntityData({ + required this.id, + required this.assetId, + this.personId, + required this.imageWidth, + required this.imageHeight, + required this.boundingBoxX1, + required this.boundingBoxY1, + required this.boundingBoxX2, + required this.boundingBoxY2, + required this.sourceType, + required this.isVisible, + this.deletedAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || personId != null) { + map['person_id'] = Variable(personId); + } + map['image_width'] = Variable(imageWidth); + map['image_height'] = Variable(imageHeight); + map['bounding_box_x1'] = Variable(boundingBoxX1); + map['bounding_box_y1'] = Variable(boundingBoxY1); + map['bounding_box_x2'] = Variable(boundingBoxX2); + map['bounding_box_y2'] = Variable(boundingBoxY2); + map['source_type'] = Variable(sourceType); + map['is_visible'] = Variable(isVisible); + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + return map; + } + + factory AssetFaceEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AssetFaceEntityData( + id: serializer.fromJson(json['id']), + assetId: serializer.fromJson(json['assetId']), + personId: serializer.fromJson(json['personId']), + imageWidth: serializer.fromJson(json['imageWidth']), + imageHeight: serializer.fromJson(json['imageHeight']), + boundingBoxX1: serializer.fromJson(json['boundingBoxX1']), + boundingBoxY1: serializer.fromJson(json['boundingBoxY1']), + boundingBoxX2: serializer.fromJson(json['boundingBoxX2']), + boundingBoxY2: serializer.fromJson(json['boundingBoxY2']), + sourceType: serializer.fromJson(json['sourceType']), + isVisible: serializer.fromJson(json['isVisible']), + deletedAt: serializer.fromJson(json['deletedAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'assetId': serializer.toJson(assetId), + 'personId': serializer.toJson(personId), + 'imageWidth': serializer.toJson(imageWidth), + 'imageHeight': serializer.toJson(imageHeight), + 'boundingBoxX1': serializer.toJson(boundingBoxX1), + 'boundingBoxY1': serializer.toJson(boundingBoxY1), + 'boundingBoxX2': serializer.toJson(boundingBoxX2), + 'boundingBoxY2': serializer.toJson(boundingBoxY2), + 'sourceType': serializer.toJson(sourceType), + 'isVisible': serializer.toJson(isVisible), + 'deletedAt': serializer.toJson(deletedAt), + }; + } + + AssetFaceEntityData copyWith({ + String? id, + String? assetId, + Value personId = const Value.absent(), + int? imageWidth, + int? imageHeight, + int? boundingBoxX1, + int? boundingBoxY1, + int? boundingBoxX2, + int? boundingBoxY2, + String? sourceType, + int? isVisible, + Value deletedAt = const Value.absent(), + }) => AssetFaceEntityData( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId.present ? personId.value : this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + isVisible: isVisible ?? this.isVisible, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + ); + AssetFaceEntityData copyWithCompanion(AssetFaceEntityCompanion data) { + return AssetFaceEntityData( + id: data.id.present ? data.id.value : this.id, + assetId: data.assetId.present ? data.assetId.value : this.assetId, + personId: data.personId.present ? data.personId.value : this.personId, + imageWidth: data.imageWidth.present + ? data.imageWidth.value + : this.imageWidth, + imageHeight: data.imageHeight.present + ? data.imageHeight.value + : this.imageHeight, + boundingBoxX1: data.boundingBoxX1.present + ? data.boundingBoxX1.value + : this.boundingBoxX1, + boundingBoxY1: data.boundingBoxY1.present + ? data.boundingBoxY1.value + : this.boundingBoxY1, + boundingBoxX2: data.boundingBoxX2.present + ? data.boundingBoxX2.value + : this.boundingBoxX2, + boundingBoxY2: data.boundingBoxY2.present + ? data.boundingBoxY2.value + : this.boundingBoxY2, + sourceType: data.sourceType.present + ? data.sourceType.value + : this.sourceType, + isVisible: data.isVisible.present ? data.isVisible.value : this.isVisible, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + ); + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityData(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType, ') + ..write('isVisible: $isVisible, ') + ..write('deletedAt: $deletedAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + isVisible, + deletedAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AssetFaceEntityData && + other.id == this.id && + other.assetId == this.assetId && + other.personId == this.personId && + other.imageWidth == this.imageWidth && + other.imageHeight == this.imageHeight && + other.boundingBoxX1 == this.boundingBoxX1 && + other.boundingBoxY1 == this.boundingBoxY1 && + other.boundingBoxX2 == this.boundingBoxX2 && + other.boundingBoxY2 == this.boundingBoxY2 && + other.sourceType == this.sourceType && + other.isVisible == this.isVisible && + other.deletedAt == this.deletedAt); +} + +class AssetFaceEntityCompanion extends UpdateCompanion { + final Value id; + final Value assetId; + final Value personId; + final Value imageWidth; + final Value imageHeight; + final Value boundingBoxX1; + final Value boundingBoxY1; + final Value boundingBoxX2; + final Value boundingBoxY2; + final Value sourceType; + final Value isVisible; + final Value deletedAt; + const AssetFaceEntityCompanion({ + this.id = const Value.absent(), + this.assetId = const Value.absent(), + this.personId = const Value.absent(), + this.imageWidth = const Value.absent(), + this.imageHeight = const Value.absent(), + this.boundingBoxX1 = const Value.absent(), + this.boundingBoxY1 = const Value.absent(), + this.boundingBoxX2 = const Value.absent(), + this.boundingBoxY2 = const Value.absent(), + this.sourceType = const Value.absent(), + this.isVisible = const Value.absent(), + this.deletedAt = const Value.absent(), + }); + AssetFaceEntityCompanion.insert({ + required String id, + required String assetId, + this.personId = const Value.absent(), + required int imageWidth, + required int imageHeight, + required int boundingBoxX1, + required int boundingBoxY1, + required int boundingBoxX2, + required int boundingBoxY2, + required String sourceType, + this.isVisible = const Value.absent(), + this.deletedAt = const Value.absent(), + }) : id = Value(id), + assetId = Value(assetId), + imageWidth = Value(imageWidth), + imageHeight = Value(imageHeight), + boundingBoxX1 = Value(boundingBoxX1), + boundingBoxY1 = Value(boundingBoxY1), + boundingBoxX2 = Value(boundingBoxX2), + boundingBoxY2 = Value(boundingBoxY2), + sourceType = Value(sourceType); + static Insertable custom({ + Expression? id, + Expression? assetId, + Expression? personId, + Expression? imageWidth, + Expression? imageHeight, + Expression? boundingBoxX1, + Expression? boundingBoxY1, + Expression? boundingBoxX2, + Expression? boundingBoxY2, + Expression? sourceType, + Expression? isVisible, + Expression? deletedAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (assetId != null) 'asset_id': assetId, + if (personId != null) 'person_id': personId, + if (imageWidth != null) 'image_width': imageWidth, + if (imageHeight != null) 'image_height': imageHeight, + if (boundingBoxX1 != null) 'bounding_box_x1': boundingBoxX1, + if (boundingBoxY1 != null) 'bounding_box_y1': boundingBoxY1, + if (boundingBoxX2 != null) 'bounding_box_x2': boundingBoxX2, + if (boundingBoxY2 != null) 'bounding_box_y2': boundingBoxY2, + if (sourceType != null) 'source_type': sourceType, + if (isVisible != null) 'is_visible': isVisible, + if (deletedAt != null) 'deleted_at': deletedAt, + }); + } + + AssetFaceEntityCompanion copyWith({ + Value? id, + Value? assetId, + Value? personId, + Value? imageWidth, + Value? imageHeight, + Value? boundingBoxX1, + Value? boundingBoxY1, + Value? boundingBoxX2, + Value? boundingBoxY2, + Value? sourceType, + Value? isVisible, + Value? deletedAt, + }) { + return AssetFaceEntityCompanion( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId ?? this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + isVisible: isVisible ?? this.isVisible, + deletedAt: deletedAt ?? this.deletedAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (personId.present) { + map['person_id'] = Variable(personId.value); + } + if (imageWidth.present) { + map['image_width'] = Variable(imageWidth.value); + } + if (imageHeight.present) { + map['image_height'] = Variable(imageHeight.value); + } + if (boundingBoxX1.present) { + map['bounding_box_x1'] = Variable(boundingBoxX1.value); + } + if (boundingBoxY1.present) { + map['bounding_box_y1'] = Variable(boundingBoxY1.value); + } + if (boundingBoxX2.present) { + map['bounding_box_x2'] = Variable(boundingBoxX2.value); + } + if (boundingBoxY2.present) { + map['bounding_box_y2'] = Variable(boundingBoxY2.value); + } + if (sourceType.present) { + map['source_type'] = Variable(sourceType.value); + } + if (isVisible.present) { + map['is_visible'] = Variable(isVisible.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityCompanion(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType, ') + ..write('isVisible: $isVisible, ') + ..write('deletedAt: $deletedAt') + ..write(')')) + .toString(); + } +} + +class StoreEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StoreEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn stringValue = GeneratedColumn( + 'string_value', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn intValue = GeneratedColumn( + 'int_value', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + @override + List get $columns => [id, stringValue, intValue]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'store_entity'; + @override + Set get $primaryKey => {id}; + @override + StoreEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StoreEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + stringValue: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}string_value'], + ), + intValue: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}int_value'], + ), + ); + } + + @override + StoreEntity createAlias(String alias) { + return StoreEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class StoreEntityData extends DataClass implements Insertable { + final int id; + final String? stringValue; + final int? intValue; + const StoreEntityData({required this.id, this.stringValue, this.intValue}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + if (!nullToAbsent || stringValue != null) { + map['string_value'] = Variable(stringValue); + } + if (!nullToAbsent || intValue != null) { + map['int_value'] = Variable(intValue); + } + return map; + } + + factory StoreEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StoreEntityData( + id: serializer.fromJson(json['id']), + stringValue: serializer.fromJson(json['stringValue']), + intValue: serializer.fromJson(json['intValue']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'stringValue': serializer.toJson(stringValue), + 'intValue': serializer.toJson(intValue), + }; + } + + StoreEntityData copyWith({ + int? id, + Value stringValue = const Value.absent(), + Value intValue = const Value.absent(), + }) => StoreEntityData( + id: id ?? this.id, + stringValue: stringValue.present ? stringValue.value : this.stringValue, + intValue: intValue.present ? intValue.value : this.intValue, + ); + StoreEntityData copyWithCompanion(StoreEntityCompanion data) { + return StoreEntityData( + id: data.id.present ? data.id.value : this.id, + stringValue: data.stringValue.present + ? data.stringValue.value + : this.stringValue, + intValue: data.intValue.present ? data.intValue.value : this.intValue, + ); + } + + @override + String toString() { + return (StringBuffer('StoreEntityData(') + ..write('id: $id, ') + ..write('stringValue: $stringValue, ') + ..write('intValue: $intValue') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, stringValue, intValue); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StoreEntityData && + other.id == this.id && + other.stringValue == this.stringValue && + other.intValue == this.intValue); +} + +class StoreEntityCompanion extends UpdateCompanion { + final Value id; + final Value stringValue; + final Value intValue; + const StoreEntityCompanion({ + this.id = const Value.absent(), + this.stringValue = const Value.absent(), + this.intValue = const Value.absent(), + }); + StoreEntityCompanion.insert({ + required int id, + this.stringValue = const Value.absent(), + this.intValue = const Value.absent(), + }) : id = Value(id); + static Insertable custom({ + Expression? id, + Expression? stringValue, + Expression? intValue, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (stringValue != null) 'string_value': stringValue, + if (intValue != null) 'int_value': intValue, + }); + } + + StoreEntityCompanion copyWith({ + Value? id, + Value? stringValue, + Value? intValue, + }) { + return StoreEntityCompanion( + id: id ?? this.id, + stringValue: stringValue ?? this.stringValue, + intValue: intValue ?? this.intValue, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (stringValue.present) { + map['string_value'] = Variable(stringValue.value); + } + if (intValue.present) { + map['int_value'] = Variable(intValue.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StoreEntityCompanion(') + ..write('id: $id, ') + ..write('stringValue: $stringValue, ') + ..write('intValue: $intValue') + ..write(')')) + .toString(); + } +} + +class TrashSync extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + TrashSync(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn status = GeneratedColumn( + 'status', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn assetUpdatedAt = GeneratedColumn( + 'asset_updated_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + @override + List get $columns => [ + assetId, + checksum, + status, + assetUpdatedAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'trash_sync'; + @override + Set get $primaryKey => {assetId}; + @override + TrashSyncData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return TrashSyncData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + )!, + status: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}status'], + )!, + assetUpdatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_updated_at'], + ), + ); + } + + @override + TrashSync createAlias(String alias) { + return TrashSync(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(asset_id)']; + @override + bool get dontWriteConstraints => true; +} + +class TrashSyncData extends DataClass implements Insertable { + final String assetId; + final String checksum; + final int status; + final String? assetUpdatedAt; + const TrashSyncData({ + required this.assetId, + required this.checksum, + required this.status, + this.assetUpdatedAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['checksum'] = Variable(checksum); + map['status'] = Variable(status); + if (!nullToAbsent || assetUpdatedAt != null) { + map['asset_updated_at'] = Variable(assetUpdatedAt); + } + return map; + } + + factory TrashSyncData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return TrashSyncData( + assetId: serializer.fromJson(json['assetId']), + checksum: serializer.fromJson(json['checksum']), + status: serializer.fromJson(json['status']), + assetUpdatedAt: serializer.fromJson(json['assetUpdatedAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'checksum': serializer.toJson(checksum), + 'status': serializer.toJson(status), + 'assetUpdatedAt': serializer.toJson(assetUpdatedAt), + }; + } + + TrashSyncData copyWith({ + String? assetId, + String? checksum, + int? status, + Value assetUpdatedAt = const Value.absent(), + }) => TrashSyncData( + assetId: assetId ?? this.assetId, + checksum: checksum ?? this.checksum, + status: status ?? this.status, + assetUpdatedAt: assetUpdatedAt.present + ? assetUpdatedAt.value + : this.assetUpdatedAt, + ); + TrashSyncData copyWithCompanion(TrashSyncCompanion data) { + return TrashSyncData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + status: data.status.present ? data.status.value : this.status, + assetUpdatedAt: data.assetUpdatedAt.present + ? data.assetUpdatedAt.value + : this.assetUpdatedAt, + ); + } + + @override + String toString() { + return (StringBuffer('TrashSyncData(') + ..write('assetId: $assetId, ') + ..write('checksum: $checksum, ') + ..write('status: $status, ') + ..write('assetUpdatedAt: $assetUpdatedAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, checksum, status, assetUpdatedAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is TrashSyncData && + other.assetId == this.assetId && + other.checksum == this.checksum && + other.status == this.status && + other.assetUpdatedAt == this.assetUpdatedAt); +} + +class TrashSyncCompanion extends UpdateCompanion { + final Value assetId; + final Value checksum; + final Value status; + final Value assetUpdatedAt; + const TrashSyncCompanion({ + this.assetId = const Value.absent(), + this.checksum = const Value.absent(), + this.status = const Value.absent(), + this.assetUpdatedAt = const Value.absent(), + }); + TrashSyncCompanion.insert({ + required String assetId, + required String checksum, + this.status = const Value.absent(), + this.assetUpdatedAt = const Value.absent(), + }) : assetId = Value(assetId), + checksum = Value(checksum); + static Insertable custom({ + Expression? assetId, + Expression? checksum, + Expression? status, + Expression? assetUpdatedAt, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (checksum != null) 'checksum': checksum, + if (status != null) 'status': status, + if (assetUpdatedAt != null) 'asset_updated_at': assetUpdatedAt, + }); + } + + TrashSyncCompanion copyWith({ + Value? assetId, + Value? checksum, + Value? status, + Value? assetUpdatedAt, + }) { + return TrashSyncCompanion( + assetId: assetId ?? this.assetId, + checksum: checksum ?? this.checksum, + status: status ?? this.status, + assetUpdatedAt: assetUpdatedAt ?? this.assetUpdatedAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (status.present) { + map['status'] = Variable(status.value); + } + if (assetUpdatedAt.present) { + map['asset_updated_at'] = Variable(assetUpdatedAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('TrashSyncCompanion(') + ..write('assetId: $assetId, ') + ..write('checksum: $checksum, ') + ..write('status: $status, ') + ..write('assetUpdatedAt: $assetUpdatedAt') + ..write(')')) + .toString(); + } +} + +class ServerDeletedChecksum extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + ServerDeletedChecksum(this.attachedDatabase, [this._alias]); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + @override + List get $columns => [checksum]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'server_deleted_checksum'; + @override + Set get $primaryKey => {checksum}; + @override + ServerDeletedChecksumData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return ServerDeletedChecksumData( + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + )!, + ); + } + + @override + ServerDeletedChecksum createAlias(String alias) { + return ServerDeletedChecksum(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(checksum)']; + @override + bool get dontWriteConstraints => true; +} + +class ServerDeletedChecksumData extends DataClass + implements Insertable { + final String checksum; + const ServerDeletedChecksumData({required this.checksum}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['checksum'] = Variable(checksum); + return map; + } + + factory ServerDeletedChecksumData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return ServerDeletedChecksumData( + checksum: serializer.fromJson(json['checksum']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return {'checksum': serializer.toJson(checksum)}; + } + + ServerDeletedChecksumData copyWith({String? checksum}) => + ServerDeletedChecksumData(checksum: checksum ?? this.checksum); + ServerDeletedChecksumData copyWithCompanion( + ServerDeletedChecksumCompanion data, + ) { + return ServerDeletedChecksumData( + checksum: data.checksum.present ? data.checksum.value : this.checksum, + ); + } + + @override + String toString() { + return (StringBuffer('ServerDeletedChecksumData(') + ..write('checksum: $checksum') + ..write(')')) + .toString(); + } + + @override + int get hashCode => checksum.hashCode; + @override + bool operator ==(Object other) => + identical(this, other) || + (other is ServerDeletedChecksumData && other.checksum == this.checksum); +} + +class ServerDeletedChecksumCompanion + extends UpdateCompanion { + final Value checksum; + const ServerDeletedChecksumCompanion({this.checksum = const Value.absent()}); + ServerDeletedChecksumCompanion.insert({required String checksum}) + : checksum = Value(checksum); + static Insertable custom({ + Expression? checksum, + }) { + return RawValuesInsertable({if (checksum != null) 'checksum': checksum}); + } + + ServerDeletedChecksumCompanion copyWith({Value? checksum}) { + return ServerDeletedChecksumCompanion(checksum: checksum ?? this.checksum); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('ServerDeletedChecksumCompanion(') + ..write('checksum: $checksum') + ..write(')')) + .toString(); + } +} + +class AssetEditEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AssetEditEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES remote_asset_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn action = GeneratedColumn( + 'action', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn parameters = + GeneratedColumn( + 'parameters', + aliasedName, + false, + type: DriftSqlType.blob, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn sequence = GeneratedColumn( + 'sequence', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + @override + List get $columns => [ + id, + assetId, + action, + parameters, + sequence, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'asset_edit_entity'; + @override + Set get $primaryKey => {id}; + @override + AssetEditEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AssetEditEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + action: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}action'], + )!, + parameters: attachedDatabase.typeMapping.read( + DriftSqlType.blob, + data['${effectivePrefix}parameters'], + )!, + sequence: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}sequence'], + )!, + ); + } + + @override + AssetEditEntity createAlias(String alias) { + return AssetEditEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class AssetEditEntityData extends DataClass + implements Insertable { + final String id; + final String assetId; + final int action; + final i2.Uint8List parameters; + final int sequence; + const AssetEditEntityData({ + required this.id, + required this.assetId, + required this.action, + required this.parameters, + required this.sequence, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['asset_id'] = Variable(assetId); + map['action'] = Variable(action); + map['parameters'] = Variable(parameters); + map['sequence'] = Variable(sequence); + return map; + } + + factory AssetEditEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AssetEditEntityData( + id: serializer.fromJson(json['id']), + assetId: serializer.fromJson(json['assetId']), + action: serializer.fromJson(json['action']), + parameters: serializer.fromJson(json['parameters']), + sequence: serializer.fromJson(json['sequence']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'assetId': serializer.toJson(assetId), + 'action': serializer.toJson(action), + 'parameters': serializer.toJson(parameters), + 'sequence': serializer.toJson(sequence), + }; + } + + AssetEditEntityData copyWith({ + String? id, + String? assetId, + int? action, + i2.Uint8List? parameters, + int? sequence, + }) => AssetEditEntityData( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + action: action ?? this.action, + parameters: parameters ?? this.parameters, + sequence: sequence ?? this.sequence, + ); + AssetEditEntityData copyWithCompanion(AssetEditEntityCompanion data) { + return AssetEditEntityData( + id: data.id.present ? data.id.value : this.id, + assetId: data.assetId.present ? data.assetId.value : this.assetId, + action: data.action.present ? data.action.value : this.action, + parameters: data.parameters.present + ? data.parameters.value + : this.parameters, + sequence: data.sequence.present ? data.sequence.value : this.sequence, + ); + } + + @override + String toString() { + return (StringBuffer('AssetEditEntityData(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('action: $action, ') + ..write('parameters: $parameters, ') + ..write('sequence: $sequence') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + assetId, + action, + $driftBlobEquality.hash(parameters), + sequence, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AssetEditEntityData && + other.id == this.id && + other.assetId == this.assetId && + other.action == this.action && + $driftBlobEquality.equals(other.parameters, this.parameters) && + other.sequence == this.sequence); +} + +class AssetEditEntityCompanion extends UpdateCompanion { + final Value id; + final Value assetId; + final Value action; + final Value parameters; + final Value sequence; + const AssetEditEntityCompanion({ + this.id = const Value.absent(), + this.assetId = const Value.absent(), + this.action = const Value.absent(), + this.parameters = const Value.absent(), + this.sequence = const Value.absent(), + }); + AssetEditEntityCompanion.insert({ + required String id, + required String assetId, + required int action, + required i2.Uint8List parameters, + required int sequence, + }) : id = Value(id), + assetId = Value(assetId), + action = Value(action), + parameters = Value(parameters), + sequence = Value(sequence); + static Insertable custom({ + Expression? id, + Expression? assetId, + Expression? action, + Expression? parameters, + Expression? sequence, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (assetId != null) 'asset_id': assetId, + if (action != null) 'action': action, + if (parameters != null) 'parameters': parameters, + if (sequence != null) 'sequence': sequence, + }); + } + + AssetEditEntityCompanion copyWith({ + Value? id, + Value? assetId, + Value? action, + Value? parameters, + Value? sequence, + }) { + return AssetEditEntityCompanion( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + action: action ?? this.action, + parameters: parameters ?? this.parameters, + sequence: sequence ?? this.sequence, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (action.present) { + map['action'] = Variable(action.value); + } + if (parameters.present) { + map['parameters'] = Variable(parameters.value); + } + if (sequence.present) { + map['sequence'] = Variable(sequence.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AssetEditEntityCompanion(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('action: $action, ') + ..write('parameters: $parameters, ') + ..write('sequence: $sequence') + ..write(')')) + .toString(); + } +} + +class Settings extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + Settings(this.attachedDatabase, [this._alias]); + late final GeneratedColumn key = GeneratedColumn( + 'key', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn value = GeneratedColumn( + 'value', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + @override + List get $columns => [key, value, updatedAt]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'settings'; + @override + Set get $primaryKey => {key}; + @override + SettingsData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return SettingsData( + key: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}key'], + )!, + value: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}value'], + ), + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + ); + } + + @override + Settings createAlias(String alias) { + return Settings(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY("key")']; + @override + bool get dontWriteConstraints => true; +} + +class SettingsData extends DataClass implements Insertable { + final String key; + final String? value; + final String updatedAt; + const SettingsData({required this.key, this.value, required this.updatedAt}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['key'] = Variable(key); + if (!nullToAbsent || value != null) { + map['value'] = Variable(value); + } + map['updated_at'] = Variable(updatedAt); + return map; + } + + factory SettingsData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return SettingsData( + key: serializer.fromJson(json['key']), + value: serializer.fromJson(json['value']), + updatedAt: serializer.fromJson(json['updatedAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'key': serializer.toJson(key), + 'value': serializer.toJson(value), + 'updatedAt': serializer.toJson(updatedAt), + }; + } + + SettingsData copyWith({ + String? key, + Value value = const Value.absent(), + String? updatedAt, + }) => SettingsData( + key: key ?? this.key, + value: value.present ? value.value : this.value, + updatedAt: updatedAt ?? this.updatedAt, + ); + SettingsData copyWithCompanion(SettingsCompanion data) { + return SettingsData( + key: data.key.present ? data.key.value : this.key, + value: data.value.present ? data.value.value : this.value, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ); + } + + @override + String toString() { + return (StringBuffer('SettingsData(') + ..write('key: $key, ') + ..write('value: $value, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(key, value, updatedAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is SettingsData && + other.key == this.key && + other.value == this.value && + other.updatedAt == this.updatedAt); +} + +class SettingsCompanion extends UpdateCompanion { + final Value key; + final Value value; + final Value updatedAt; + const SettingsCompanion({ + this.key = const Value.absent(), + this.value = const Value.absent(), + this.updatedAt = const Value.absent(), + }); + SettingsCompanion.insert({ + required String key, + this.value = const Value.absent(), + this.updatedAt = const Value.absent(), + }) : key = Value(key); + static Insertable custom({ + Expression? key, + Expression? value, + Expression? updatedAt, + }) { + return RawValuesInsertable({ + if (key != null) 'key': key, + if (value != null) 'value': value, + if (updatedAt != null) 'updated_at': updatedAt, + }); + } + + SettingsCompanion copyWith({ + Value? key, + Value? value, + Value? updatedAt, + }) { + return SettingsCompanion( + key: key ?? this.key, + value: value ?? this.value, + updatedAt: updatedAt ?? this.updatedAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (key.present) { + map['key'] = Variable(key.value); + } + if (value.present) { + map['value'] = Variable(value.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('SettingsCompanion(') + ..write('key: $key, ') + ..write('value: $value, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } +} + +class AssetOcrEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AssetOcrEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES remote_asset_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn x1 = GeneratedColumn( + 'x1', + aliasedName, + false, + type: DriftSqlType.double, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn y1 = GeneratedColumn( + 'y1', + aliasedName, + false, + type: DriftSqlType.double, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn x2 = GeneratedColumn( + 'x2', + aliasedName, + false, + type: DriftSqlType.double, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn y2 = GeneratedColumn( + 'y2', + aliasedName, + false, + type: DriftSqlType.double, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn x3 = GeneratedColumn( + 'x3', + aliasedName, + false, + type: DriftSqlType.double, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn y3 = GeneratedColumn( + 'y3', + aliasedName, + false, + type: DriftSqlType.double, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn x4 = GeneratedColumn( + 'x4', + aliasedName, + false, + type: DriftSqlType.double, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn y4 = GeneratedColumn( + 'y4', + aliasedName, + false, + type: DriftSqlType.double, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn boxScore = GeneratedColumn( + 'box_score', + aliasedName, + false, + type: DriftSqlType.double, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn textScore = GeneratedColumn( + 'text_score', + aliasedName, + false, + type: DriftSqlType.double, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn recognizedText = GeneratedColumn( + 'recognized_text', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn isVisible = GeneratedColumn( + 'is_visible', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 1 CHECK (is_visible IN (0, 1))', + defaultValue: const CustomExpression('1'), + ); + @override + List get $columns => [ + id, + assetId, + x1, + y1, + x2, + y2, + x3, + y3, + x4, + y4, + boxScore, + textScore, + recognizedText, + isVisible, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'asset_ocr_entity'; + @override + Set get $primaryKey => {id}; + @override + AssetOcrEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AssetOcrEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + x1: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}x1'], + )!, + y1: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}y1'], + )!, + x2: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}x2'], + )!, + y2: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}y2'], + )!, + x3: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}x3'], + )!, + y3: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}y3'], + )!, + x4: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}x4'], + )!, + y4: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}y4'], + )!, + boxScore: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}box_score'], + )!, + textScore: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}text_score'], + )!, + recognizedText: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}recognized_text'], + )!, + isVisible: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_visible'], + )!, + ); + } + + @override + AssetOcrEntity createAlias(String alias) { + return AssetOcrEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class AssetOcrEntityData extends DataClass + implements Insertable { + final String id; + final String assetId; + final double x1; + final double y1; + final double x2; + final double y2; + final double x3; + final double y3; + final double x4; + final double y4; + final double boxScore; + final double textScore; + final String recognizedText; + final int isVisible; + const AssetOcrEntityData({ + required this.id, + required this.assetId, + required this.x1, + required this.y1, + required this.x2, + required this.y2, + required this.x3, + required this.y3, + required this.x4, + required this.y4, + required this.boxScore, + required this.textScore, + required this.recognizedText, + required this.isVisible, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['asset_id'] = Variable(assetId); + map['x1'] = Variable(x1); + map['y1'] = Variable(y1); + map['x2'] = Variable(x2); + map['y2'] = Variable(y2); + map['x3'] = Variable(x3); + map['y3'] = Variable(y3); + map['x4'] = Variable(x4); + map['y4'] = Variable(y4); + map['box_score'] = Variable(boxScore); + map['text_score'] = Variable(textScore); + map['recognized_text'] = Variable(recognizedText); + map['is_visible'] = Variable(isVisible); + return map; + } + + factory AssetOcrEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AssetOcrEntityData( + id: serializer.fromJson(json['id']), + assetId: serializer.fromJson(json['assetId']), + x1: serializer.fromJson(json['x1']), + y1: serializer.fromJson(json['y1']), + x2: serializer.fromJson(json['x2']), + y2: serializer.fromJson(json['y2']), + x3: serializer.fromJson(json['x3']), + y3: serializer.fromJson(json['y3']), + x4: serializer.fromJson(json['x4']), + y4: serializer.fromJson(json['y4']), + boxScore: serializer.fromJson(json['boxScore']), + textScore: serializer.fromJson(json['textScore']), + recognizedText: serializer.fromJson(json['recognizedText']), + isVisible: serializer.fromJson(json['isVisible']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'assetId': serializer.toJson(assetId), + 'x1': serializer.toJson(x1), + 'y1': serializer.toJson(y1), + 'x2': serializer.toJson(x2), + 'y2': serializer.toJson(y2), + 'x3': serializer.toJson(x3), + 'y3': serializer.toJson(y3), + 'x4': serializer.toJson(x4), + 'y4': serializer.toJson(y4), + 'boxScore': serializer.toJson(boxScore), + 'textScore': serializer.toJson(textScore), + 'recognizedText': serializer.toJson(recognizedText), + 'isVisible': serializer.toJson(isVisible), + }; + } + + AssetOcrEntityData copyWith({ + String? id, + String? assetId, + double? x1, + double? y1, + double? x2, + double? y2, + double? x3, + double? y3, + double? x4, + double? y4, + double? boxScore, + double? textScore, + String? recognizedText, + int? isVisible, + }) => AssetOcrEntityData( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + x1: x1 ?? this.x1, + y1: y1 ?? this.y1, + x2: x2 ?? this.x2, + y2: y2 ?? this.y2, + x3: x3 ?? this.x3, + y3: y3 ?? this.y3, + x4: x4 ?? this.x4, + y4: y4 ?? this.y4, + boxScore: boxScore ?? this.boxScore, + textScore: textScore ?? this.textScore, + recognizedText: recognizedText ?? this.recognizedText, + isVisible: isVisible ?? this.isVisible, + ); + AssetOcrEntityData copyWithCompanion(AssetOcrEntityCompanion data) { + return AssetOcrEntityData( + id: data.id.present ? data.id.value : this.id, + assetId: data.assetId.present ? data.assetId.value : this.assetId, + x1: data.x1.present ? data.x1.value : this.x1, + y1: data.y1.present ? data.y1.value : this.y1, + x2: data.x2.present ? data.x2.value : this.x2, + y2: data.y2.present ? data.y2.value : this.y2, + x3: data.x3.present ? data.x3.value : this.x3, + y3: data.y3.present ? data.y3.value : this.y3, + x4: data.x4.present ? data.x4.value : this.x4, + y4: data.y4.present ? data.y4.value : this.y4, + boxScore: data.boxScore.present ? data.boxScore.value : this.boxScore, + textScore: data.textScore.present ? data.textScore.value : this.textScore, + recognizedText: data.recognizedText.present + ? data.recognizedText.value + : this.recognizedText, + isVisible: data.isVisible.present ? data.isVisible.value : this.isVisible, + ); + } + + @override + String toString() { + return (StringBuffer('AssetOcrEntityData(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('x1: $x1, ') + ..write('y1: $y1, ') + ..write('x2: $x2, ') + ..write('y2: $y2, ') + ..write('x3: $x3, ') + ..write('y3: $y3, ') + ..write('x4: $x4, ') + ..write('y4: $y4, ') + ..write('boxScore: $boxScore, ') + ..write('textScore: $textScore, ') + ..write('recognizedText: $recognizedText, ') + ..write('isVisible: $isVisible') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + assetId, + x1, + y1, + x2, + y2, + x3, + y3, + x4, + y4, + boxScore, + textScore, + recognizedText, + isVisible, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AssetOcrEntityData && + other.id == this.id && + other.assetId == this.assetId && + other.x1 == this.x1 && + other.y1 == this.y1 && + other.x2 == this.x2 && + other.y2 == this.y2 && + other.x3 == this.x3 && + other.y3 == this.y3 && + other.x4 == this.x4 && + other.y4 == this.y4 && + other.boxScore == this.boxScore && + other.textScore == this.textScore && + other.recognizedText == this.recognizedText && + other.isVisible == this.isVisible); +} + +class AssetOcrEntityCompanion extends UpdateCompanion { + final Value id; + final Value assetId; + final Value x1; + final Value y1; + final Value x2; + final Value y2; + final Value x3; + final Value y3; + final Value x4; + final Value y4; + final Value boxScore; + final Value textScore; + final Value recognizedText; + final Value isVisible; + const AssetOcrEntityCompanion({ + this.id = const Value.absent(), + this.assetId = const Value.absent(), + this.x1 = const Value.absent(), + this.y1 = const Value.absent(), + this.x2 = const Value.absent(), + this.y2 = const Value.absent(), + this.x3 = const Value.absent(), + this.y3 = const Value.absent(), + this.x4 = const Value.absent(), + this.y4 = const Value.absent(), + this.boxScore = const Value.absent(), + this.textScore = const Value.absent(), + this.recognizedText = const Value.absent(), + this.isVisible = const Value.absent(), + }); + AssetOcrEntityCompanion.insert({ + required String id, + required String assetId, + required double x1, + required double y1, + required double x2, + required double y2, + required double x3, + required double y3, + required double x4, + required double y4, + required double boxScore, + required double textScore, + required String recognizedText, + this.isVisible = const Value.absent(), + }) : id = Value(id), + assetId = Value(assetId), + x1 = Value(x1), + y1 = Value(y1), + x2 = Value(x2), + y2 = Value(y2), + x3 = Value(x3), + y3 = Value(y3), + x4 = Value(x4), + y4 = Value(y4), + boxScore = Value(boxScore), + textScore = Value(textScore), + recognizedText = Value(recognizedText); + static Insertable custom({ + Expression? id, + Expression? assetId, + Expression? x1, + Expression? y1, + Expression? x2, + Expression? y2, + Expression? x3, + Expression? y3, + Expression? x4, + Expression? y4, + Expression? boxScore, + Expression? textScore, + Expression? recognizedText, + Expression? isVisible, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (assetId != null) 'asset_id': assetId, + if (x1 != null) 'x1': x1, + if (y1 != null) 'y1': y1, + if (x2 != null) 'x2': x2, + if (y2 != null) 'y2': y2, + if (x3 != null) 'x3': x3, + if (y3 != null) 'y3': y3, + if (x4 != null) 'x4': x4, + if (y4 != null) 'y4': y4, + if (boxScore != null) 'box_score': boxScore, + if (textScore != null) 'text_score': textScore, + if (recognizedText != null) 'recognized_text': recognizedText, + if (isVisible != null) 'is_visible': isVisible, + }); + } + + AssetOcrEntityCompanion copyWith({ + Value? id, + Value? assetId, + Value? x1, + Value? y1, + Value? x2, + Value? y2, + Value? x3, + Value? y3, + Value? x4, + Value? y4, + Value? boxScore, + Value? textScore, + Value? recognizedText, + Value? isVisible, + }) { + return AssetOcrEntityCompanion( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + x1: x1 ?? this.x1, + y1: y1 ?? this.y1, + x2: x2 ?? this.x2, + y2: y2 ?? this.y2, + x3: x3 ?? this.x3, + y3: y3 ?? this.y3, + x4: x4 ?? this.x4, + y4: y4 ?? this.y4, + boxScore: boxScore ?? this.boxScore, + textScore: textScore ?? this.textScore, + recognizedText: recognizedText ?? this.recognizedText, + isVisible: isVisible ?? this.isVisible, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (x1.present) { + map['x1'] = Variable(x1.value); + } + if (y1.present) { + map['y1'] = Variable(y1.value); + } + if (x2.present) { + map['x2'] = Variable(x2.value); + } + if (y2.present) { + map['y2'] = Variable(y2.value); + } + if (x3.present) { + map['x3'] = Variable(x3.value); + } + if (y3.present) { + map['y3'] = Variable(y3.value); + } + if (x4.present) { + map['x4'] = Variable(x4.value); + } + if (y4.present) { + map['y4'] = Variable(y4.value); + } + if (boxScore.present) { + map['box_score'] = Variable(boxScore.value); + } + if (textScore.present) { + map['text_score'] = Variable(textScore.value); + } + if (recognizedText.present) { + map['recognized_text'] = Variable(recognizedText.value); + } + if (isVisible.present) { + map['is_visible'] = Variable(isVisible.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AssetOcrEntityCompanion(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('x1: $x1, ') + ..write('y1: $y1, ') + ..write('x2: $x2, ') + ..write('y2: $y2, ') + ..write('x3: $x3, ') + ..write('y3: $y3, ') + ..write('x4: $x4, ') + ..write('y4: $y4, ') + ..write('boxScore: $boxScore, ') + ..write('textScore: $textScore, ') + ..write('recognizedText: $recognizedText, ') + ..write('isVisible: $isVisible') + ..write(')')) + .toString(); + } +} + +class DatabaseAtV32 extends GeneratedDatabase { + DatabaseAtV32(QueryExecutor e) : super(e); + late final UserEntity userEntity = UserEntity(this); + late final RemoteAssetEntity remoteAssetEntity = RemoteAssetEntity(this); + late final StackEntity stackEntity = StackEntity(this); + late final LocalAssetEntity localAssetEntity = LocalAssetEntity(this); + late final RemoteAlbumEntity remoteAlbumEntity = RemoteAlbumEntity(this); + late final LocalAlbumEntity localAlbumEntity = LocalAlbumEntity(this); + late final LocalAlbumAssetEntity localAlbumAssetEntity = + LocalAlbumAssetEntity(this); + late final Index idxLocalAlbumAssetAlbumAsset = Index( + 'idx_local_album_asset_album_asset', + 'CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)', + ); + late final Index idxLocalAssetChecksum = Index( + 'idx_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + late final Index idxLocalAssetCloudId = Index( + 'idx_local_asset_cloud_id', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)', + ); + late final Index idxLocalAssetCreatedAt = Index( + 'idx_local_asset_created_at', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_created_at ON local_asset_entity (created_at)', + ); + late final Index idxStackPrimaryAssetId = Index( + 'idx_stack_primary_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)', + ); + late final Index uQRemoteAssetsOwnerChecksum = Index( + 'UQ_remote_assets_owner_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', + ); + late final Index uQRemoteAssetsOwnerLibraryChecksum = Index( + 'UQ_remote_assets_owner_library_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)', + ); + late final Index idxRemoteAssetChecksum = Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + late final Index idxRemoteAssetSoftDeletedChecksum = Index( + 'idx_remote_asset_soft_deleted_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_soft_deleted_checksum ON remote_asset_entity (checksum) WHERE deleted_at IS NOT NULL', + ); + late final Index idxRemoteAssetStackId = Index( + 'idx_remote_asset_stack_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)', + ); + late final Index idxRemoteAssetOwnerVisibilityDeletedCreated = Index( + 'idx_remote_asset_owner_visibility_deleted_created', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created ON remote_asset_entity (owner_id, visibility, deleted_at, created_at DESC)', + ); + late final Index idxRemoteAssetUploaded = Index( + 'idx_remote_asset_uploaded', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_uploaded ON remote_asset_entity (uploaded_at)', + ); + late final AuthUserEntity authUserEntity = AuthUserEntity(this); + late final UserMetadataEntity userMetadataEntity = UserMetadataEntity(this); + late final PartnerEntity partnerEntity = PartnerEntity(this); + late final RemoteExifEntity remoteExifEntity = RemoteExifEntity(this); + late final RemoteAlbumAssetEntity remoteAlbumAssetEntity = + RemoteAlbumAssetEntity(this); + late final RemoteAlbumUserEntity remoteAlbumUserEntity = + RemoteAlbumUserEntity(this); + late final RemoteAssetCloudIdEntity remoteAssetCloudIdEntity = + RemoteAssetCloudIdEntity(this); + late final MemoryEntity memoryEntity = MemoryEntity(this); + late final MemoryAssetEntity memoryAssetEntity = MemoryAssetEntity(this); + late final PersonEntity personEntity = PersonEntity(this); + late final AssetFaceEntity assetFaceEntity = AssetFaceEntity(this); + late final StoreEntity storeEntity = StoreEntity(this); + late final TrashSync trashSync = TrashSync(this); + late final ServerDeletedChecksum serverDeletedChecksum = + ServerDeletedChecksum(this); + late final AssetEditEntity assetEditEntity = AssetEditEntity(this); + late final Settings settings = Settings(this); + late final AssetOcrEntity assetOcrEntity = AssetOcrEntity(this); + late final Index idxPartnerSharedWithId = Index( + 'idx_partner_shared_with_id', + 'CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)', + ); + late final Index idxLatLng = Index( + 'idx_lat_lng', + 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', + ); + late final Index idxRemoteExifCity = Index( + 'idx_remote_exif_city', + 'CREATE INDEX IF NOT EXISTS idx_remote_exif_city ON remote_exif_entity (city) WHERE city IS NOT NULL', + ); + late final Index idxRemoteAlbumAssetAlbumAsset = Index( + 'idx_remote_album_asset_album_asset', + 'CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)', + ); + late final Index idxRemoteAssetCloudId = Index( + 'idx_remote_asset_cloud_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)', + ); + late final Index idxPersonOwnerId = Index( + 'idx_person_owner_id', + 'CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)', + ); + late final Index idxAssetFacePersonId = Index( + 'idx_asset_face_person_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)', + ); + late final Index idxAssetFaceAssetId = Index( + 'idx_asset_face_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)', + ); + late final Index idxAssetFaceVisiblePerson = Index( + 'idx_asset_face_visible_person', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_visible_person ON asset_face_entity (person_id, asset_id) WHERE is_visible = 1 AND deleted_at IS NULL', + ); + late final Index idxTrashSyncChecksum = Index( + 'idx_trash_sync_checksum', + 'CREATE INDEX IF NOT EXISTS idx_trash_sync_checksum ON trash_sync (checksum)', + ); + late final Index idxAssetEditAssetId = Index( + 'idx_asset_edit_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)', + ); + late final Index idxAssetOcrAssetId = Index( + 'idx_asset_ocr_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_ocr_asset_id ON asset_ocr_entity (asset_id)', + ); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + remoteAlbumEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAlbumAssetAlbumAsset, + idxLocalAssetChecksum, + idxLocalAssetCloudId, + idxLocalAssetCreatedAt, + idxStackPrimaryAssetId, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + idxRemoteAssetSoftDeletedChecksum, + idxRemoteAssetStackId, + idxRemoteAssetOwnerVisibilityDeletedCreated, + idxRemoteAssetUploaded, + authUserEntity, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + remoteAssetCloudIdEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + storeEntity, + trashSync, + serverDeletedChecksum, + assetEditEntity, + settings, + assetOcrEntity, + idxPartnerSharedWithId, + idxLatLng, + idxRemoteExifCity, + idxRemoteAlbumAssetAlbumAsset, + idxRemoteAssetCloudId, + idxPersonOwnerId, + idxAssetFacePersonId, + idxAssetFaceAssetId, + idxAssetFaceVisiblePerson, + idxTrashSyncChecksum, + idxAssetEditAssetId, + idxAssetOcrAssetId, + ]; + @override + StreamQueryUpdateRules get streamUpdateRules => const StreamQueryUpdateRules([ + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('remote_asset_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('stack_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('remote_album_entity', kind: UpdateKind.update)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_album_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('local_album_entity', kind: UpdateKind.update)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'local_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [ + TableUpdate('local_album_asset_entity', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'local_album_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [ + TableUpdate('local_album_asset_entity', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('user_metadata_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('partner_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('partner_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('remote_exif_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [ + TableUpdate('remote_album_asset_entity', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_album_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [ + TableUpdate('remote_album_asset_entity', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_album_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [ + TableUpdate('remote_album_user_entity', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [ + TableUpdate('remote_album_user_entity', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [ + TableUpdate('remote_asset_cloud_id_entity', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('memory_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('memory_asset_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'memory_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('memory_asset_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('person_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('asset_face_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'person_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('asset_face_entity', kind: UpdateKind.update)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('asset_edit_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('asset_ocr_entity', kind: UpdateKind.delete)], + ), + ]); + @override + int get schemaVersion => 32; + @override + DriftDatabaseOptions get options => + const DriftDatabaseOptions(storeDateTimeAsText: true); +} diff --git a/mobile/test/infrastructure/repository.mock.dart b/mobile/test/infrastructure/repository.mock.dart index 0688576682..7a9242cebf 100644 --- a/mobile/test/infrastructure/repository.mock.dart +++ b/mobile/test/infrastructure/repository.mock.dart @@ -11,7 +11,7 @@ import 'package:immich_mobile/infrastructure/repositories/store.repository.dart' import 'package:immich_mobile/infrastructure/repositories/sync_api.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/sync_migration.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/infrastructure/repositories/trash_sync.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/user.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/user_api.repository.dart'; import 'package:immich_mobile/repositories/drift_album_api_repository.dart'; @@ -36,7 +36,7 @@ class MockDriftLocalAssetRepository extends Mock implements DriftLocalAssetRepos class MockRemoteAssetRepository extends Mock implements RemoteAssetRepository {} -class MockTrashedLocalAssetRepository extends Mock implements DriftTrashedLocalAssetRepository {} +class MockTrashSyncRepository extends Mock implements DriftTrashSyncRepository {} class MockStorageRepository extends Mock implements StorageRepository {} diff --git a/mobile/test/medium/repositories/backup_repository_test.dart b/mobile/test/medium/repositories/backup_repository_test.dart index 00ab28df87..1d0ee6693b 100644 --- a/mobile/test/medium/repositories/backup_repository_test.dart +++ b/mobile/test/medium/repositories/backup_repository_test.dart @@ -1,5 +1,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:immich_mobile/domain/models/album/local_album.model.dart'; +import 'package:immich_mobile/infrastructure/entities/server_deleted_checksum.entity.drift.dart'; +import 'package:immich_mobile/infrastructure/entities/trash_sync.entity.drift.dart'; import 'package:immich_mobile/infrastructure/repositories/backup.repository.dart'; import 'package:immich_mobile/utils/option.dart'; @@ -114,6 +116,32 @@ void main() { expect(result.remainder, 1); }); + test('excludes asset with a trash sync row from counts', () async { + final album = await ctx.newLocalAlbum(backupSelection: BackupSelection.selected); + final asset = await ctx.newLocalAsset(); + await ctx.newLocalAlbumAsset(albumId: album.id, assetId: asset.id); + await ctx.db + .into(ctx.db.trashSyncEntity) + .insert(TrashSyncEntityCompanion.insert(assetId: asset.id, checksum: 'trashed')); + + final result = await sut.getAllCounts(userId); + expect(result.total, 0); + expect(result.remainder, 0); + }); + + test('excludes an asset whose content the server permanently deleted', () async { + final album = await ctx.newLocalAlbum(backupSelection: BackupSelection.selected); + final asset = await ctx.newLocalAsset(checksum: 'deleted'); + await ctx.newLocalAlbumAsset(albumId: album.id, assetId: asset.id); + await ctx.db + .into(ctx.db.serverDeletedChecksumEntity) + .insert(ServerDeletedChecksumEntityCompanion.insert(checksum: 'deleted')); + + final result = await sut.getAllCounts(userId); + expect(result.total, 0); + expect(result.remainder, 0); + }); + test('mixed assets produce correct combined counts', () async { final selectedAlbum = await ctx.newLocalAlbum(backupSelection: BackupSelection.selected); @@ -229,6 +257,30 @@ void main() { expect(result.map((a) => a.id).toList(), [asset2.id, asset3.id, asset1.id]); }); + test('excludes asset with a trash sync row', () async { + final album = await ctx.newLocalAlbum(backupSelection: BackupSelection.selected); + final asset = await ctx.newLocalAsset(); + await ctx.newLocalAlbumAsset(albumId: album.id, assetId: asset.id); + await ctx.db + .into(ctx.db.trashSyncEntity) + .insert(TrashSyncEntityCompanion.insert(assetId: asset.id, checksum: 'trashed')); + + final result = await sut.getCandidates(userId); + expect(result, isEmpty); + }); + + test('excludes an asset whose content the server permanently deleted', () async { + final album = await ctx.newLocalAlbum(backupSelection: BackupSelection.selected); + final asset = await ctx.newLocalAsset(checksum: 'deleted'); + await ctx.newLocalAlbumAsset(albumId: album.id, assetId: asset.id); + await ctx.db + .into(ctx.db.serverDeletedChecksumEntity) + .insert(ServerDeletedChecksumEntityCompanion.insert(checksum: 'deleted')); + + final result = await sut.getCandidates(userId); + expect(result, isEmpty); + }); + test('does not return duplicate when asset is in multiple selected albums', () async { final album1 = await ctx.newLocalAlbum(backupSelection: BackupSelection.selected); final album2 = await ctx.newLocalAlbum(backupSelection: BackupSelection.selected); diff --git a/mobile/test/medium/repositories/trash_sync_repository_test.dart b/mobile/test/medium/repositories/trash_sync_repository_test.dart new file mode 100644 index 0000000000..b7d3babdf1 --- /dev/null +++ b/mobile/test/medium/repositories/trash_sync_repository_test.dart @@ -0,0 +1,505 @@ +import 'package:drift/drift.dart' show Value; +import 'package:flutter_test/flutter_test.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/domain/models/album/local_album.model.dart'; +import 'package:immich_mobile/infrastructure/entities/server_deleted_checksum.entity.drift.dart'; +import 'package:immich_mobile/infrastructure/entities/trash_sync.entity.drift.dart'; +import 'package:immich_mobile/infrastructure/repositories/trash_sync.repository.dart'; + +import '../repository_context.dart'; + +void main() { + late MediumRepositoryContext ctx; + late DriftTrashSyncRepository sut; + + setUp(() { + ctx = MediumRepositoryContext(); + sut = DriftTrashSyncRepository(ctx.db); + }); + + tearDown(() => ctx.dispose()); + + Future<({String localId, String checksum, String remoteId})> backedUpAsset({ + required String ownerId, + DateTime? remoteDeletedAt, + BackupSelection album = .selected, + }) async { + final remote = await ctx.newRemoteAsset(ownerId: ownerId, deletedAt: remoteDeletedAt); + final local = await ctx.newLocalAsset(checksum: remote.checksum); + final localAlbum = await ctx.newLocalAlbum(backupSelection: album); + await ctx.newLocalAlbumAsset(albumId: localAlbum.id, assetId: local.id); + return (localId: local.id, checksum: remote.checksum, remoteId: remote.id); + } + + Future trashStatusOf(String assetId) async { + final rows = await ctx.db.select(ctx.db.trashSyncEntity).get(); + return rows.where((r) => r.assetId == assetId).map((r) => r.status).firstOrNull; + } + + Future markAsset({ + required String assetId, + required String checksum, + required TrashSyncStatus status, + DateTime? assetUpdatedAt, + }) => ctx.db + .into(ctx.db.trashSyncEntity) + .insert( + TrashSyncEntityCompanion.insert( + assetId: assetId, + checksum: checksum, + status: Value(status), + assetUpdatedAt: Value.absentIfNull(assetUpdatedAt), + ), + ); + + Future checksumOf(String assetId) async { + final rows = await ctx.db.select(ctx.db.localAssetEntity).get(); + return rows.where((r) => r.id == assetId).map((r) => r.checksum).firstOrNull; + } + + late String userId; + setUp(() async { + userId = (await ctx.newUser()).id; + await ctx.newAuthUser(id: userId); + }); + + group('recordSoftDeleteAssets', () { + test('records marker when server asset is trashed', () async { + final asset = await backedUpAsset(ownerId: userId, remoteDeletedAt: .new(2026, 1, 1)); + + await sut.recordSoftDeleteAssets(); + + expect(await sut.getPendingAssetIds(), [asset.localId]); + }); + + test('#24124: asset on server is never trashed', () async { + await backedUpAsset(ownerId: userId, remoteDeletedAt: null); + + await sut.recordSoftDeleteAssets(); + + expect(await sut.getPendingAssetIds(), isEmpty); + }); + + test('ignores assets that are not in a backup selected album', () async { + await backedUpAsset(ownerId: userId, remoteDeletedAt: .new(2026, 1, 1), album: .none); + + await sut.recordSoftDeleteAssets(); + + expect(await sut.getPendingAssetIds(), isEmpty); + }); + + test('existing marker is not duplicated', () async { + await backedUpAsset(ownerId: userId, remoteDeletedAt: .new(2026, 1, 1)); + + await sut.recordSoftDeleteAssets(); + await sut.recordSoftDeleteAssets(); + + expect(await sut.getPendingAssetIds(), hasLength(1)); + }); + + test('#24124: does not retrash a dismissed / restored asset', () async { + final asset = await backedUpAsset(ownerId: userId, remoteDeletedAt: .new(2026, 1, 1)); + await markAsset(assetId: asset.localId, checksum: asset.checksum, status: .dismissed); + + await sut.recordSoftDeleteAssets(); + + expect(await sut.getPendingAssetIds(), isEmpty); + expect(await trashStatusOf(asset.localId), TrashSyncStatus.dismissed); + }); + }); + + group('getRestorableAssetIds', () { + test('should mark asset restorable when asset is back on server', () async { + final remote = await ctx.newRemoteAsset(ownerId: userId, deletedAt: null); + await markAsset(assetId: 'asset', checksum: remote.checksum, status: .trashed); + + expect(await sut.getRestorableAssetIds(), ['asset']); + }); + + test('pending assets are not restored', () async { + final remote = await ctx.newRemoteAsset(ownerId: userId, deletedAt: null); + await markAsset(assetId: 'asset', checksum: remote.checksum, status: .pending); + + expect(await sut.getRestorableAssetIds(), isEmpty); + }); + + test('do not mark asset restorable when server asset is still trashed', () async { + final remote = await ctx.newRemoteAsset(ownerId: userId, deletedAt: .new(2026, 1, 1)); + await markAsset(assetId: 'asset', checksum: remote.checksum, status: .trashed); + + expect(await sut.getRestorableAssetIds(), isEmpty); + }); + + test('do not mark asset restorable when server asset is permanently deleted', () async { + await markAsset(assetId: 'asset', checksum: 'checksum', status: .trashed); + + expect(await sut.getRestorableAssetIds(), isEmpty); + }); + }); + + group('duplicate local copies of the same asset', () { + test('recordSoftDeleteAssets records one marker per local copy', () async { + final remote = await ctx.newRemoteAsset(ownerId: userId, deletedAt: .new(2026, 1, 1)); + final album = await ctx.newLocalAlbum(backupSelection: .selected); + final local1 = await ctx.newLocalAsset(checksum: remote.checksum); + final local2 = await ctx.newLocalAsset(checksum: remote.checksum); + await ctx.newLocalAlbumAsset(albumId: album.id, assetId: local1.id); + await ctx.newLocalAlbumAsset(albumId: album.id, assetId: local2.id); + + await sut.recordSoftDeleteAssets(); + + expect(await sut.getPendingAssetIds(), unorderedEquals([local1.id, local2.id])); + }); + + test('getRestorableAssetIds returns every trashed copy sharing the revived checksum', () async { + final remote = await ctx.newRemoteAsset(ownerId: userId, deletedAt: null); + await markAsset(assetId: 'local1', checksum: remote.checksum, status: .trashed); + await markAsset(assetId: 'local2', checksum: remote.checksum, status: .trashed); + + expect(await sut.getRestorableAssetIds(), unorderedEquals(['local1', 'local2'])); + }); + }); + + group('pruneStaleMarkers - asset came back before trashing', () { + test('removes marker when asset is back on server', () async { + final remote = await ctx.newRemoteAsset(ownerId: userId, deletedAt: null); + await markAsset(assetId: 'asset', checksum: remote.checksum, status: .pending); + + await sut.pruneStaleMarkers(); + + expect(await trashStatusOf('asset'), isNull); + }); + + test('dismissed marker is kept even after asset is back on server to prevent re-trashing it', () async { + final remote = await ctx.newRemoteAsset(ownerId: userId, deletedAt: null); + await markAsset(assetId: 'asset', checksum: remote.checksum, status: .dismissed); + + await sut.pruneStaleMarkers(); + + expect(await trashStatusOf('asset'), TrashSyncStatus.dismissed); + }); + + test('keeps a pending marker for asset that is still deleted', () async { + final remote = await ctx.newRemoteAsset(ownerId: userId, deletedAt: .new(2026, 1, 1)); + await markAsset(assetId: 'asset', checksum: remote.checksum, status: .pending); + + await sut.pruneStaleMarkers(); + + expect(await trashStatusOf('asset'), TrashSyncStatus.pending); + }); + + test('does not remove a trashed marker', () async { + final remote = await ctx.newRemoteAsset(ownerId: userId, deletedAt: null); + await markAsset(assetId: 'asset', checksum: remote.checksum, status: .trashed); + + await sut.pruneStaleMarkers(); + + expect(await trashStatusOf('asset'), TrashSyncStatus.trashed); + }); + }); + + group('excluded album handling', () { + test('recordSoftDeleteAssets ignores an asset also in an excluded album', () async { + final remote = await ctx.newRemoteAsset(ownerId: userId, deletedAt: .new(2026, 1, 1)); + final local = await ctx.newLocalAsset(checksum: remote.checksum); + final selected = await ctx.newLocalAlbum(backupSelection: .selected); + final excluded = await ctx.newLocalAlbum(backupSelection: .excluded); + await ctx.newLocalAlbumAsset(albumId: selected.id, assetId: local.id); + await ctx.newLocalAlbumAsset(albumId: excluded.id, assetId: local.id); + + await sut.recordSoftDeleteAssets(); + + expect(await sut.getPendingAssetIds(), isEmpty); + }); + }); + + group('duplicate assets', () { + test('recordSoftDeleteAssets skips duplicate asset when previous asset is dismissed', () async { + final remote = await ctx.newRemoteAsset(ownerId: userId, deletedAt: .new(2026, 1, 1)); + final album = await ctx.newLocalAlbum(backupSelection: .selected); + final reimport = await ctx.newLocalAsset(checksum: remote.checksum); + await ctx.newLocalAlbumAsset(albumId: album.id, assetId: reimport.id); + await markAsset(assetId: 'old-asset', checksum: remote.checksum, status: .dismissed); + + await sut.recordSoftDeleteAssets(); + + expect(await sut.getPendingAssetIds(), isEmpty); + }); + }); + + group('prunePendingMarkers', () { + test('removes pending marker when local asset is modified', () async { + final local = await ctx.newLocalAsset(checksum: 'current-checksum'); + await markAsset(assetId: local.id, checksum: 'marked-checksum', status: .pending); + + await sut.prunePendingMarkers(); + + expect(await trashStatusOf(local.id), isNull); + }); + + test('keeps pending marker for local asset', () async { + final local = await ctx.newLocalAsset(checksum: 'checksum'); + await markAsset(assetId: local.id, checksum: 'checksum', status: .pending); + + await sut.prunePendingMarkers(); + + expect(await trashStatusOf(local.id), TrashSyncStatus.pending); + }); + + test('keeps pending marker for missing local asset', () async { + await markAsset(assetId: 'asset', checksum: 'checksum', status: .pending); + + await sut.prunePendingMarkers(); + + expect(await trashStatusOf('asset'), TrashSyncStatus.pending); + }); + }); + + group('server deleted checksum table', () { + Future insertDeletedChecksum(String checksum) => ctx.db + .into(ctx.db.serverDeletedChecksumEntity) + .insert(ServerDeletedChecksumEntityCompanion.insert(checksum: checksum)); + + Future> deletedChecksums() async => + (await ctx.db.select(ctx.db.serverDeletedChecksumEntity).get()).map((r) => r.checksum).toList(); + + group('recordHardDeletedChecksums', () { + test('marks owned permanently deleted assets', () async { + final remote = await ctx.newRemoteAsset(ownerId: userId); + + await sut.recordHardDeletedChecksums([remote.id]); + + expect(await deletedChecksums(), [remote.checksum]); + }); + + test('ignores a partner assets', () async { + final partner = await ctx.newUser(); + final remote = await ctx.newRemoteAsset(ownerId: partner.id); + + await sut.recordHardDeletedChecksums([remote.id]); + + expect(await deletedChecksums(), isEmpty); + }); + + test('only marks local assets from backup selected album', () async { + final album = await ctx.newLocalAlbum(backupSelection: .selected); + final local = await ctx.newLocalAsset(checksum: 'checksum'); + await ctx.newLocalAlbumAsset(albumId: album.id, assetId: local.id); + await insertDeletedChecksum('checksum'); + + await sut.recordHardDeletedAssets(); + + expect(await sut.getPendingAssetIds(), [local.id]); + }); + + test('ignores an excluded album asset', () async { + final selected = await ctx.newLocalAlbum(backupSelection: .selected); + final excluded = await ctx.newLocalAlbum(backupSelection: .excluded); + final local = await ctx.newLocalAsset(checksum: 'checksum'); + await ctx.newLocalAlbumAsset(albumId: selected.id, assetId: local.id); + await ctx.newLocalAlbumAsset(albumId: excluded.id, assetId: local.id); + await insertDeletedChecksum('checksum'); + + await sut.recordHardDeletedAssets(); + + expect(await sut.getPendingAssetIds(), isEmpty); + }); + }); + + group('pruneStaleMarkers', () { + test('removes marker for asset back on the server', () async { + await insertDeletedChecksum('checksum'); + await ctx.newRemoteAsset(ownerId: userId, deletedAt: null, checksum: 'checksum'); + + await sut.pruneStaleMarkers(); + + expect(await deletedChecksums(), isEmpty); + }); + + test('keeps a checksum with no owned remote asset', () async { + await insertDeletedChecksum('checksum'); + + await sut.pruneStaleMarkers(); + + expect(await deletedChecksums(), ['checksum']); + }); + }); + }); + + group('owner scoping', () { + test('recordSoftDeleteAssets: ignores trashed assets from partner', () async { + final partner = await ctx.newUser(); + final remote = await ctx.newRemoteAsset(ownerId: partner.id, deletedAt: .new(2026, 1, 1)); + final album = await ctx.newLocalAlbum(backupSelection: .selected); + final local = await ctx.newLocalAsset(checksum: remote.checksum); + await ctx.newLocalAlbumAsset(albumId: album.id, assetId: local.id); + + await sut.recordSoftDeleteAssets(); + + expect(await sut.getPendingAssetIds(), isEmpty); + }); + + test('getRestorableAssetIds: ignores restore when server remote asset is only partner owned', () async { + final partner = await ctx.newUser(); + final remote = await ctx.newRemoteAsset(ownerId: partner.id, deletedAt: null); + await markAsset(assetId: 'asset', checksum: remote.checksum, status: .trashed); + + expect(await sut.getRestorableAssetIds(), isEmpty); + }); + + test('pruneStaleMarkers: ignores remote asset that is only partner owned', () async { + final partner = await ctx.newUser(); + final remote = await ctx.newRemoteAsset(ownerId: partner.id, deletedAt: null); + await markAsset(assetId: 'asset', checksum: remote.checksum, status: .pending); + + await sut.pruneStaleMarkers(); + + expect(await trashStatusOf('asset'), TrashSyncStatus.pending); + }); + }); + + group('reconcileTrashed', () { + test('dismisses a trashed marker whose local asset is not trashed', () async { + final local = await ctx.newLocalAsset(); + await markAsset(assetId: local.id, checksum: 'checksum', status: .trashed); + + await sut.reconcileTrashed([local.id]); + + expect(await trashStatusOf(local.id), TrashSyncStatus.dismissed); + }); + + test('removes a trashed marker whose local asset was deleted', () async { + await markAsset(assetId: 'asset', checksum: 'checksum', status: .trashed); + + await sut.reconcileTrashed(['asset']); + + expect(await trashStatusOf('asset'), isNull); + }); + + test('splits a mixed batch in one call: live id dismissed, purged id deleted', () async { + final live = await ctx.newLocalAsset(); + await markAsset(assetId: live.id, checksum: 'checksum-1', status: .trashed); + await markAsset(assetId: 'asset', checksum: 'checksum-2', status: .trashed); + + await sut.reconcileTrashed([live.id, 'asset']); + + expect(await trashStatusOf(live.id), TrashSyncStatus.dismissed); + expect(await trashStatusOf('asset'), isNull); + }); + }); + + group('pruneDismissedMarkers', () { + Future insertDeletedChecksum(String checksum) => ctx.db + .into(ctx.db.serverDeletedChecksumEntity) + .insert(ServerDeletedChecksumEntityCompanion.insert(checksum: checksum)); + + test('removes a dismissed marker when its checksum is not server deleted', () async { + await markAsset(assetId: 'asset', checksum: 'checksum', status: .dismissed); + + await sut.pruneDismissedMarkers(); + + expect(await trashStatusOf('asset'), isNull); + }); + + test('keeps dismissed marker still trashed on the server', () async { + final remote = await ctx.newRemoteAsset(ownerId: userId, deletedAt: .new(2026, 1, 1)); + await markAsset(assetId: 'asset', checksum: remote.checksum, status: .dismissed); + + await sut.pruneDismissedMarkers(); + + expect(await trashStatusOf('asset'), TrashSyncStatus.dismissed); + }); + + test('keeps dismissed marker for permanently deleted assets on server', () async { + await insertDeletedChecksum('checksum'); + await markAsset(assetId: 'asset', checksum: 'checksum', status: .dismissed); + + await sut.pruneDismissedMarkers(); + + expect(await trashStatusOf('asset'), TrashSyncStatus.dismissed); + }); + + test('removes dismissed marker of asset matching only partner trashed asset', () async { + final partner = await ctx.newUser(); + final remote = await ctx.newRemoteAsset(ownerId: partner.id, deletedAt: .new(2026, 1, 1)); + await markAsset(assetId: 'asset', checksum: remote.checksum, status: .dismissed); + + await sut.pruneDismissedMarkers(); + + expect(await trashStatusOf('asset'), isNull); + }); + + test('do not delete trashed marker when server checksum is empty', () async { + await markAsset(assetId: 'asset', checksum: 'checksum', status: .trashed); + + await sut.pruneDismissedMarkers(); + + expect(await trashStatusOf('asset'), TrashSyncStatus.trashed); + }); + }); + + group('markTrashed', () { + test('marks trashed and removes the local asset', () async { + final local = await ctx.newLocalAsset(); + await markAsset(assetId: local.id, checksum: 'checksum', status: .pending); + + await sut.markTrashed([local.id]); + + expect(await trashStatusOf(local.id), TrashSyncStatus.trashed); + final locals = await ctx.db.select(ctx.db.localAssetEntity).get(); + expect(locals.where((l) => l.id == local.id), isEmpty); + }); + }); + + group('markRestored', () { + test('marks as restored, leaving the rows in place', () async { + await markAsset(assetId: 'asset', checksum: 'checksum', status: .trashed); + + await sut.markRestored(['asset']); + + expect(await trashStatusOf('asset'), TrashSyncStatus.restored); + }); + }); + + group('restoreChecksums', () { + test('copies the stored checksum to local asset whose modified time is unchanged', () async { + final modifiedAt = DateTime(2026, 1, 1); + await ctx.newLocalAsset(id: 'asset', checksumOption: const .none(), updatedAt: modifiedAt); + await markAsset(assetId: 'asset', checksum: 'checksum', status: .restored, assetUpdatedAt: modifiedAt); + + await sut.restoreChecksums(); + + expect(await checksumOf('asset'), 'checksum'); + expect(await trashStatusOf('asset'), isNull); + }); + + test('leaves the checksum null when the asset was modified after restore', () async { + await ctx.newLocalAsset(id: 'asset', checksumOption: const .none(), updatedAt: .new(2026, 1, 1)); + await markAsset(assetId: 'asset', checksum: 'checksum', status: .restored, assetUpdatedAt: DateTime(2024, 3, 1)); + + await sut.restoreChecksums(); + + expect(await checksumOf('asset'), isNull); + expect(await trashStatusOf('asset'), isNull); + }); + + test('does not overwrite a checksum the hash pass already computed', () async { + final modifiedAt = DateTime(2026, 1, 1); + await ctx.newLocalAsset(id: 'asset', checksum: 'checksum', updatedAt: modifiedAt); + await markAsset(assetId: 'asset', checksum: 'old-checksum', status: .restored, assetUpdatedAt: modifiedAt); + + await sut.restoreChecksums(); + + expect(await checksumOf('asset'), 'checksum'); + expect(await trashStatusOf('asset'), isNull); + }); + + test('clears restored markers even when local asset does not exist', () async { + await markAsset(assetId: 'asset', checksum: 'checksum', status: .restored, assetUpdatedAt: .new(2026, 1, 1)); + + await sut.restoreChecksums(); + + expect(await trashStatusOf('asset'), isNull); + }); + }); +} diff --git a/mobile/test/medium/repository_context.dart b/mobile/test/medium/repository_context.dart index ed06774e82..48f18cc62e 100644 --- a/mobile/test/medium/repository_context.dart +++ b/mobile/test/medium/repository_context.dart @@ -6,6 +6,7 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/memory.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/infrastructure/entities/asset_face.entity.drift.dart'; +import 'package:immich_mobile/infrastructure/entities/auth_user.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/local_album.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart'; @@ -50,6 +51,20 @@ class MediumRepositoryContext { return .new(fallback); } + Future newAuthUser({String? id, String? email}) async { + id ??= TestUtils.uuid(); + return db + .into(db.authUserEntity) + .insertReturning( + AuthUserEntityCompanion( + id: .new(id), + email: .new(email ?? '$id@demo.immich.app'), + name: .new(email ?? 'user_$id'), + avatarColor: .new(TestUtils.randElement(AvatarColor.values)), + ), + ); + } + Future newUser({ String? id, String? email, @@ -63,7 +78,7 @@ class MediumRepositoryContext { .insertReturning( UserEntityCompanion( id: .new(id), - email: .new(email ?? '$id@test.com'), + email: .new(email ?? '$id@demo.immich.app'), name: .new(email ?? 'user_$id'), avatarColor: .new(avatarColor ?? TestUtils.randElement(AvatarColor.values)), profileChangedAt: .new(TestUtils.date(profileChangedAt)), diff --git a/mobile/test/medium/service_context.dart b/mobile/test/medium/service_context.dart index 6f90b3e344..83fe01f1e0 100644 --- a/mobile/test/medium/service_context.dart +++ b/mobile/test/medium/service_context.dart @@ -1,13 +1,45 @@ import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/infrastructure/repositories/partner.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/trash_sync.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/user.repository.dart'; import 'package:immich_mobile/repositories/partner_api.repository.dart'; import 'package:mocktail/mocktail.dart'; import '../api.mocks.dart'; +import '../repository.mocks.dart'; import '../utils.dart'; import 'repository_context.dart'; +class MediumServiceContext extends MediumRepositoryContext { + late final UserRepository userRepository = UserRepository(db); + late final PartnerRepository partnerRepository = PartnerRepository(db); + late final DriftTrashSyncRepository trashSyncRepository = DriftTrashSyncRepository(db); + late final SettingsRepository settings; + + final partnerApi = MockPartnerApiRepository(); + final assetMediaApi = MockAssetMediaApi(); + final permissionRepository = MockPermissionRepository(); + + MediumServiceContext._() { + _stubPartnerApi(partnerApi); + _stubPermissionRepository(permissionRepository); + _stubAssetMediaApi(assetMediaApi); + } + + static Future init() async { + final context = MediumServiceContext._(); + context.settings = await SettingsRepository.ensureInitialized(context.db); + return context; + } + + @override + Future dispose() async { + await SettingsRepository.reset(); + await super.dispose(); + } +} + void _stubPartnerApi(MockPartnerApiRepository api) { final id = TestUtils.uuid(); final partner = UserDto(id: id, email: '$id@example.com', name: 'name $id', profileChangedAt: TestUtils.now()); @@ -19,13 +51,13 @@ void _stubPartnerApi(MockPartnerApiRepository api) { when(() => api.delete(any())).thenAnswer((_) async {}); } -class MediumServiceContext extends MediumRepositoryContext { - late final UserRepository userRepository = UserRepository(db); - late final PartnerRepository partnerRepository = PartnerRepository(db); - - final partnerApi = MockPartnerApiRepository(); - - MediumServiceContext() { - _stubPartnerApi(partnerApi); - } +void _stubPermissionRepository(MockPermissionRepository permission) { + when(() => permission.hasManageMediaPermission()).thenAnswer((_) async => true); +} + +void _stubAssetMediaApi(MockAssetMediaApi assetMediaApi) { + registerFallbackValue(const []); + when(() => assetMediaApi.trash(any())).thenAnswer((_) async => []); + when(() => assetMediaApi.restore(any())).thenAnswer((_) async => []); + when(() => assetMediaApi.trashedAmong(any())).thenAnswer((_) async => []); } diff --git a/mobile/test/medium/services/partner_service_test.dart b/mobile/test/medium/services/partner_service_test.dart index 4c31c9212e..a0a735f5d6 100644 --- a/mobile/test/medium/services/partner_service_test.dart +++ b/mobile/test/medium/services/partner_service_test.dart @@ -8,8 +8,8 @@ void main() { late MediumServiceContext ctx; late PartnerService sut; - setUp(() { - ctx = MediumServiceContext(); + setUp(() async { + ctx = await MediumServiceContext.init(); sut = PartnerService(ctx.userRepository, ctx.partnerRepository, ctx.partnerApi); }); diff --git a/mobile/test/medium/services/trash_sync_service_test.dart b/mobile/test/medium/services/trash_sync_service_test.dart new file mode 100644 index 0000000000..7b072c4045 --- /dev/null +++ b/mobile/test/medium/services/trash_sync_service_test.dart @@ -0,0 +1,249 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/domain/services/trash_sync.service.dart'; +import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart'; +import 'package:immich_mobile/infrastructure/entities/trash_sync.entity.drift.dart'; +import 'package:immich_mobile/platform/asset_media_api.g.dart'; +import 'package:mocktail/mocktail.dart'; + +import '../service_context.dart'; + +void main() { + late MediumServiceContext ctx; + late TrashSyncService sut; + late String userId; + + setUpAll(() => debugDefaultTargetPlatformOverride = .android); + tearDownAll(() => debugDefaultTargetPlatformOverride = null); + + setUp(() async { + ctx = await MediumServiceContext.init(); + sut = TrashSyncService( + repo: ctx.trashSyncRepository, + assetMediaApi: ctx.assetMediaApi, + permission: ctx.permissionRepository, + settings: ctx.settings, + ); + userId = (await ctx.newUser()).id; + await ctx.newAuthUser(id: userId); + await ctx.settings.write(.trashSyncEnabled, true); + + when(() => ctx.assetMediaApi.trash(any())).thenAnswer(_mapStatus(.done)); + when(() => ctx.assetMediaApi.restore(any())).thenAnswer(_mapStatus(.done)); + when(() => ctx.assetMediaApi.trashedAmong(any())).thenAnswer((inv) async => _ids(inv)); + }); + + tearDown(() => ctx.dispose()); + + Future<({String localId, String remoteId, String checksum})> backedUpAsset({DateTime? remoteDeletedAt}) async { + final remote = await ctx.newRemoteAsset(ownerId: userId, deletedAt: remoteDeletedAt); + final local = await ctx.newLocalAsset(checksum: remote.checksum); + final album = await ctx.newLocalAlbum(backupSelection: .selected); + await ctx.newLocalAlbumAsset(albumId: album.id, assetId: local.id); + return (localId: local.id, remoteId: remote.id, checksum: remote.checksum); + } + + Future trashStatusOf(String id) async { + final rows = (await ctx.db.select(ctx.db.trashSyncEntity).get()).where((r) => r.assetId == id).toList(); + return rows.isEmpty ? null : rows.first.status; + } + + Future localAssetExists(String id) async => + (await ctx.db.select(ctx.db.localAssetEntity).get()).any((r) => r.id == id); + + Future markAsset({required String localId, required String checksum, TrashSyncStatus status = .pending}) async { + await ctx.db + .into(ctx.db.trashSyncEntity) + .insert(TrashSyncEntityCompanion.insert(assetId: localId, checksum: checksum, status: .new(status))); + } + + test('trashed asset is recorded, trashed, and its local row pruned', () async { + final asset = await backedUpAsset(remoteDeletedAt: .new(2026, 1, 1)); + + await sut.reconcile(); + + expect(await trashStatusOf(asset.localId), TrashSyncStatus.trashed); + expect(await localAssetExists(asset.localId), isFalse); + }); + + test('trash sync disabled: trashed asset is left untouched', () async { + await ctx.settings.write(.trashSyncEnabled, false); + final asset = await backedUpAsset(remoteDeletedAt: .new(2026, 1, 1)); + + await sut.reconcile(); + + expect(await trashStatusOf(asset.localId), isNull); + expect(await localAssetExists(asset.localId), isTrue); + verifyNever(() => ctx.assetMediaApi.trash(any())); + }); + + test('trash pending asset that is removed from device is not trashed and marker is removed', () async { + final asset = await backedUpAsset(remoteDeletedAt: .new(2026, 1, 1)); + when(() => ctx.assetMediaApi.trash(any())).thenAnswer(_mapStatus(.notFound)); + + await sut.reconcile(); + + expect(await trashStatusOf(asset.localId), isNull); + expect(await localAssetExists(asset.localId), isTrue); + }); + + test('partner trash does not affect our local copy', () async { + final partner = await ctx.newUser(); + final remote = await ctx.newRemoteAsset(ownerId: partner.id, deletedAt: .new(2026, 1, 1)); + final local = await ctx.newLocalAsset(checksum: remote.checksum); + final album = await ctx.newLocalAlbum(backupSelection: .selected); + await ctx.newLocalAlbumAsset(albumId: album.id, assetId: local.id); + + await sut.reconcile(); + + expect(await trashStatusOf(local.id), isNull); + expect(await localAssetExists(local.id), isTrue); + verifyNever(() => ctx.assetMediaApi.trash(any())); + }); + + test('trash pending marker is removed when the asset is back', () async { + final asset = await backedUpAsset(remoteDeletedAt: null); + await markAsset(localId: asset.localId, checksum: asset.checksum); + + await sut.reconcile(); + + expect(await trashStatusOf(asset.localId), isNull); + expect(await localAssetExists(asset.localId), isTrue); + verifyNever(() => ctx.assetMediaApi.trash(any())); + }); + + test('reupload after a failed trash cleans the marker', () async { + final asset = await backedUpAsset(remoteDeletedAt: .new(2026, 1, 1)); + when(() => ctx.assetMediaApi.trash(any())).thenAnswer(_mapStatus(.failed)); + + await sut.reconcile(); + expect(await trashStatusOf(asset.localId), TrashSyncStatus.pending); + expect(await localAssetExists(asset.localId), isTrue); + + await (ctx.db.update( + ctx.db.remoteAssetEntity, + )..where((t) => t.id.equals(asset.remoteId))).write(const RemoteAssetEntityCompanion(deletedAt: .new(null))); + when(() => ctx.assetMediaApi.trash(any())).thenAnswer(_mapStatus(.done)); + + await sut.reconcile(); + expect(await trashStatusOf(asset.localId), isNull); + expect(await localAssetExists(asset.localId), isTrue); + }); + + test('server delete is also sent to the OS trash', () async { + final asset = await backedUpAsset(remoteDeletedAt: null); + await ctx.trashSyncRepository.recordHardDeletedChecksums([asset.remoteId]); + await (ctx.db.delete(ctx.db.remoteAssetEntity)..where((t) => t.id.equals(asset.remoteId))).go(); + + await sut.reconcile(); + + expect(await trashStatusOf(asset.localId), TrashSyncStatus.trashed); + expect(await localAssetExists(asset.localId), isFalse); + }); + + test('trashed asset permanently deleted from the server stays trashed on device', () async { + await markAsset(localId: 'asset', checksum: 'checksum', status: .trashed); + + await sut.reconcile(); + + expect(await trashStatusOf('asset'), TrashSyncStatus.trashed); + verifyNever(() => ctx.assetMediaApi.restore(any())); + }); + + test('permanently deleted asset, that was externally trashed and restored is marked as dismissed', () async { + final asset = await backedUpAsset(remoteDeletedAt: null); + await ctx.trashSyncRepository.recordHardDeletedChecksums([asset.remoteId]); + await (ctx.db.delete(ctx.db.remoteAssetEntity)..where((t) => t.id.equals(asset.remoteId))).go(); + when(() => ctx.assetMediaApi.trash(any())).thenAnswer(_mapStatus(.alreadyInState)); + + await sut.reconcile(); + expect(await trashStatusOf(asset.localId), TrashSyncStatus.trashed); + expect(await localAssetExists(asset.localId), isFalse); + + await ctx.newLocalAsset(id: asset.localId, checksum: asset.checksum); + when(() => ctx.assetMediaApi.trashedAmong(any())).thenAnswer((_) async => const []); + + await sut.reconcile(); + expect(await trashStatusOf(asset.localId), TrashSyncStatus.dismissed); + expect(await localAssetExists(asset.localId), isTrue); + }); + + test('dismissed guard prevents assets from being re-trashed again', () async { + final remote = await ctx.newRemoteAsset(ownerId: userId, deletedAt: .new(2026, 1, 1)); + final album = await ctx.newLocalAlbum(backupSelection: .selected); + await markAsset(localId: 'asset', checksum: remote.checksum, status: .dismissed); + + await sut.reconcile(); + expect(await trashStatusOf('asset'), TrashSyncStatus.dismissed); + + final local = await ctx.newLocalAsset(id: 'asset', checksum: remote.checksum); + await ctx.newLocalAlbumAsset(albumId: album.id, assetId: local.id); + + await sut.reconcile(); + + expect(await trashStatusOf('asset'), TrashSyncStatus.dismissed); + expect(await localAssetExists('asset'), isTrue); + verifyNever(() => ctx.assetMediaApi.trash(any())); + }); + + test('dismissed guard is pruned once its asset is back on the server', () async { + final remote = await ctx.newRemoteAsset(ownerId: userId, deletedAt: null); + await markAsset(localId: 'asset', checksum: remote.checksum, status: .dismissed); + + await sut.reconcile(); + + expect(await trashStatusOf('asset'), isNull); + }); + + test('restored assets are restored locally and marked for backfill', () async { + final remote = await ctx.newRemoteAsset(ownerId: userId, deletedAt: null); + await markAsset(localId: 'asset', checksum: remote.checksum, status: .trashed); + + await sut.reconcile(); + + verify(() => ctx.assetMediaApi.restore(['asset'])).called(1); + expect(await trashStatusOf('asset'), TrashSyncStatus.restored); + }); + + test('restorable asset with not found status has its marker dropped', () async { + final remote = await ctx.newRemoteAsset(ownerId: userId, deletedAt: null); + await markAsset(localId: 'asset', checksum: remote.checksum, status: .trashed); + when(() => ctx.assetMediaApi.restore(any())).thenAnswer(_mapStatus(.notFound)); + + await sut.reconcile(); + + expect(await trashStatusOf('asset'), isNull); + }); + + test('backlog handled when MANAGE_MEDIA was off and handled when on', () async { + await markAsset(localId: 'asset1', checksum: 'checksum1'); + await markAsset(localId: 'asset2', checksum: 'checksum2'); + + when(() => ctx.permissionRepository.hasManageMediaPermission()).thenAnswer((_) async => false); + await sut.reconcile(); + expect(await trashStatusOf('asset1'), TrashSyncStatus.pending); + verifyNever(() => ctx.assetMediaApi.trash(any())); + + when(() => ctx.permissionRepository.hasManageMediaPermission()).thenAnswer((_) async => true); + await sut.reconcile(); + expect(await trashStatusOf('asset1'), TrashSyncStatus.trashed); + expect(await trashStatusOf('asset2'), TrashSyncStatus.trashed); + }); + + test('iOS: reconcile maintains the list but never trashes', () async { + debugDefaultTargetPlatformOverride = .iOS; + addTearDown(() => debugDefaultTargetPlatformOverride = .android); + await markAsset(localId: 'asset', checksum: 'checksum'); + + await sut.reconcile(); + + expect(await trashStatusOf('asset'), TrashSyncStatus.pending); + verifyNever(() => ctx.assetMediaApi.trash(any())); + }); +} + +Future> Function(Invocation) _mapStatus(AssetMediaActionStatus status) => + (inv) async => _ids(inv).map((id) => AssetMediaActionResult(id: id, status: status)).toList(); + +List _ids(Invocation inv) => (inv.positionalArguments.first as List).cast(); diff --git a/mobile/test/repository.mocks.dart b/mobile/test/repository.mocks.dart index 82c9395b58..7e0cecb235 100644 --- a/mobile/test/repository.mocks.dart +++ b/mobile/test/repository.mocks.dart @@ -13,7 +13,7 @@ class MockAssetApiRepository extends Mock implements AssetApiRepository {} class MockAssetMediaRepository extends Mock implements AssetMediaRepository {} -class MockPermissionRepository extends Mock implements IPermissionRepository {} +class MockPermissionRepository extends Mock implements PermissionRepository {} class MockAuthApiRepository extends Mock implements AuthApiRepository {} diff --git a/mobile/test/services/action.service_test.dart b/mobile/test/services/action.service_test.dart index 72691a3802..59212eba5a 100644 --- a/mobile/test/services/action.service_test.dart +++ b/mobile/test/services/action.service_test.dart @@ -2,7 +2,6 @@ import 'package:drift/drift.dart' as drift; import 'package:drift/native.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/services/store.service.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; @@ -21,7 +20,6 @@ void main() { late MockDriftLocalAssetRepository localAssetRepository; late MockDriftAlbumApiRepository albumApiRepository; late MockRemoteAlbumRepository remoteAlbumRepository; - late MockTrashedLocalAssetRepository trashedLocalAssetRepository; late MockAssetMediaRepository assetMediaRepository; late MockDownloadRepository downloadRepository; late MockTagService tagService; @@ -48,7 +46,6 @@ void main() { localAssetRepository = MockDriftLocalAssetRepository(); albumApiRepository = MockDriftAlbumApiRepository(); remoteAlbumRepository = MockRemoteAlbumRepository(); - trashedLocalAssetRepository = MockTrashedLocalAssetRepository(); assetMediaRepository = MockAssetMediaRepository(); downloadRepository = MockDownloadRepository(); tagService = MockTagService(); @@ -59,7 +56,6 @@ void main() { localAssetRepository, albumApiRepository, remoteAlbumRepository, - trashedLocalAssetRepository, assetMediaRepository, downloadRepository, tagService, @@ -140,25 +136,9 @@ void main() { }); group('ActionService.deleteLocal', () { - test('routes deleted ids to trashed repository when Android trash handling is enabled', () async { - await Store.put(StoreKey.manageLocalMediaAndroid, true); + test('removes deleted ids from the local database', () async { const ids = ['a', 'b']; - when(() => assetMediaRepository.deleteAll(ids)).thenAnswer((_) async => ids); - when(() => trashedLocalAssetRepository.applyTrashedAssets(ids)).thenAnswer((_) async {}); - - final result = await sut.deleteLocal(ids); - - expect(result, ids.length); - verify(() => assetMediaRepository.deleteAll(ids)).called(1); - verify(() => trashedLocalAssetRepository.applyTrashedAssets(ids)).called(1); - verifyNever(() => localAssetRepository.delete(any())); - }); - - test('deletes locally when Android trash handling is disabled', () async { - await Store.put(StoreKey.manageLocalMediaAndroid, false); - const ids = ['c']; - when(() => assetMediaRepository.deleteAll(ids)).thenAnswer((_) async => ids); when(() => localAssetRepository.delete(ids)).thenAnswer((_) async {}); @@ -167,11 +147,9 @@ void main() { expect(result, ids.length); verify(() => assetMediaRepository.deleteAll(ids)).called(1); verify(() => localAssetRepository.delete(ids)).called(1); - verifyNever(() => trashedLocalAssetRepository.applyTrashedAssets(any())); }); test('short-circuits when nothing was deleted', () async { - await Store.put(StoreKey.manageLocalMediaAndroid, true); const ids = ['x']; when(() => assetMediaRepository.deleteAll(ids)).thenAnswer((_) async => []); @@ -180,7 +158,6 @@ void main() { expect(result, 0); verify(() => assetMediaRepository.deleteAll(ids)).called(1); - verifyNever(() => trashedLocalAssetRepository.applyTrashedAssets(any())); verifyNever(() => localAssetRepository.delete(any())); }); }); diff --git a/mobile/test/unit/mocks.dart b/mobile/test/unit/mocks.dart index d8eadda7ae..e554d90e87 100644 --- a/mobile/test/unit/mocks.dart +++ b/mobile/test/unit/mocks.dart @@ -29,7 +29,6 @@ class RepositoryMocks { final localAsset = LocalAssetRepositoryStub(MockDriftLocalAssetRepository()); final remoteAsset = RemoteAssetRepositoryStub(MockRemoteAssetRepository()); final remoteExif = RemoteExifRepositoryStub(MockRemoteExifRepository()); - final trashedAsset = MockTrashedLocalAssetRepository(); final toast = MockToastRepository(); final remoteAlbum = MockRemoteAlbumRepository(); final albumApi = MockDriftAlbumApiRepository(); @@ -49,7 +48,6 @@ class RepositoryMocks { localAsset.reset(); remoteAsset.reset(); remoteExif.reset(); - reset(trashedAsset); reset(remoteAlbum); reset(albumApi); nativeApi.reset(); diff --git a/mobile/test/unit/services/hash_service_test.dart b/mobile/test/unit/services/hash_service_test.dart index 2623042f4b..374b8c427e 100644 --- a/mobile/test/unit/services/hash_service_test.dart +++ b/mobile/test/unit/services/hash_service_test.dart @@ -17,7 +17,6 @@ void main() { localAlbumRepository: mocks.localAlbum.repo, localAssetRepository: mocks.localAsset.repo, nativeSyncApi: mocks.nativeApi.api, - trashedLocalAssetRepository: mocks.trashedAsset, ); }); @@ -106,7 +105,6 @@ void main() { localAssetRepository: mocks.localAsset.repo, nativeSyncApi: mocks.nativeApi.api, batchSize: batchSize, - trashedLocalAssetRepository: mocks.trashedAsset, ); final album = LocalAlbumFactory.create();