diff --git a/mobile/android/app/build.gradle b/mobile/android/app/build.gradle index 3c2125e24e..937409a6d5 100644 --- a/mobile/android/app/build.gradle +++ b/mobile/android/app/build.gradle @@ -105,6 +105,7 @@ dependencies { def serialization_version = '1.8.1' def compose_version = '1.1.1' def gson_version = '2.10.1' + def room_version = "2.8.3" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version" @@ -113,6 +114,8 @@ dependencies { implementation "com.google.guava:guava:$guava_version" implementation "com.github.bumptech.glide:glide:$glide_version" implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:$serialization_version" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-guava:1.10.2" + implementation "com.squareup.okhttp3:okhttp:5.3.1" ksp "com.github.bumptech.glide:ksp:$glide_version" coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.2' @@ -127,6 +130,10 @@ dependencies { implementation "androidx.compose.ui:ui-tooling:$compose_version" implementation "androidx.compose.material3:material3:1.2.1" implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.6.2" + + // Room Database + implementation "androidx.room:room-runtime:$room_version" + ksp "androidx.room:room-compiler:$room_version" } // This is uncommented in F-Droid build script diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/ImmichApp.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/ImmichApp.kt index 4474c63e09..308f02fd7c 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/ImmichApp.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/ImmichApp.kt @@ -7,11 +7,13 @@ import androidx.work.Configuration import androidx.work.WorkManager import app.alextran.immich.background.BackgroundEngineLock import app.alextran.immich.background.BackgroundWorkerApiImpl +import app.alextran.immich.upload.NetworkMonitor class ImmichApp : Application() { override fun onCreate() { super.onCreate() val config = Configuration.Builder().build() + NetworkMonitor.initialize(this) WorkManager.initialize(this, config) // always start BackupWorker after WorkManager init; this fixes the following bug: // After the process is killed (by user or system), the first trigger (taking a new picture) is lost. 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 4383b3098d..e399d13fd8 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 @@ -15,6 +15,8 @@ import app.alextran.immich.images.ThumbnailsImpl import app.alextran.immich.sync.NativeSyncApi import app.alextran.immich.sync.NativeSyncApiImpl26 import app.alextran.immich.sync.NativeSyncApiImpl30 +import app.alextran.immich.upload.UploadApi +import app.alextran.immich.upload.UploadTaskImpl import io.flutter.embedding.android.FlutterFragmentActivity import io.flutter.embedding.engine.FlutterEngine @@ -39,6 +41,7 @@ class MainActivity : FlutterFragmentActivity() { ThumbnailApi.setUp(messenger, ThumbnailsImpl(ctx)) BackgroundWorkerFgHostApi.setUp(messenger, BackgroundWorkerApiImpl(ctx)) ConnectivityApi.setUp(messenger, ConnectivityApiImpl(ctx)) + UploadApi.setUp(messenger, UploadTaskImpl(ctx)) flutterEngine.plugins.add(BackgroundServicePlugin()) flutterEngine.plugins.add(HttpSSLOptionsPlugin()) diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/schema/Converters.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/schema/Converters.kt new file mode 100644 index 0000000000..e25a3f919d --- /dev/null +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/schema/Converters.kt @@ -0,0 +1,114 @@ +package app.alextran.immich.schema + +import androidx.room.TypeConverter +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import java.net.URL +import java.util.Date + +class Converters { + private val gson = Gson() + + @TypeConverter + fun fromTimestamp(value: Long?): Date? = value?.let { Date(it * 1000) } + + @TypeConverter + fun dateToTimestamp(date: Date?): Long? = date?.let { it.time / 1000 } + + @TypeConverter + fun fromUrl(value: String?): URL? = value?.let { URL(it) } + + @TypeConverter + fun urlToString(url: URL?): String? = url?.toString() + + @TypeConverter + fun fromStoreKey(value: Int?): StoreKey? = value?.let { StoreKey.fromInt(it) } + + @TypeConverter + fun storeKeyToInt(storeKey: StoreKey?): Int? = storeKey?.rawValue + + @TypeConverter + fun fromTaskStatus(value: Int?): TaskStatus? = value?.let { TaskStatus.entries[it] } + + @TypeConverter + fun taskStatusToInt(status: TaskStatus?): Int? = status?.ordinal + + @TypeConverter + fun fromBackupSelection(value: Int?): BackupSelection? = value?.let { BackupSelection.entries[it] } + + @TypeConverter + fun backupSelectionToInt(selection: BackupSelection?): Int? = selection?.ordinal + + @TypeConverter + fun fromAvatarColor(value: Int?): AvatarColor? = value?.let { AvatarColor.entries[it] } + + @TypeConverter + fun avatarColorToInt(color: AvatarColor?): Int? = color?.ordinal + + @TypeConverter + fun fromAlbumUserRole(value: Int?): AlbumUserRole? = value?.let { AlbumUserRole.entries[it] } + + @TypeConverter + fun albumUserRoleToInt(role: AlbumUserRole?): Int? = role?.ordinal + + @TypeConverter + fun fromMemoryType(value: Int?): MemoryType? = value?.let { MemoryType.entries[it] } + + @TypeConverter + fun memoryTypeToInt(type: MemoryType?): Int? = type?.ordinal + + @TypeConverter + fun fromAssetVisibility(value: Int?): AssetVisibility? = value?.let { AssetVisibility.entries[it] } + + @TypeConverter + fun assetVisibilityToInt(visibility: AssetVisibility?): Int? = visibility?.ordinal + + @TypeConverter + fun fromSourceType(value: String?): SourceType? = value?.let { SourceType.fromString(it) } + + @TypeConverter + fun sourceTypeToString(type: SourceType?): String? = type?.value + + @TypeConverter + fun fromUploadMethod(value: Int?): UploadMethod? = value?.let { UploadMethod.entries[it] } + + @TypeConverter + fun uploadMethodToInt(method: UploadMethod?): Int? = method?.ordinal + + @TypeConverter + fun fromUploadErrorCode(value: Int?): UploadErrorCode? = value?.let { UploadErrorCode.entries[it] } + + @TypeConverter + fun uploadErrorCodeToInt(code: UploadErrorCode?): Int? = code?.ordinal + + @TypeConverter + fun fromAssetType(value: Int?): AssetType? = value?.let { AssetType.entries[it] } + + @TypeConverter + fun assetTypeToInt(type: AssetType?): Int? = type?.ordinal + + @TypeConverter + fun fromStringMap(value: String?): Map? { + val type = object : TypeToken>() {}.type + return gson.fromJson(value, type) + } + + @TypeConverter + fun stringMapToString(map: Map?): String? = gson.toJson(map) + + @TypeConverter + fun fromEndpointStatus(value: String?): EndpointStatus? = value?.let { EndpointStatus.fromString(it) } + + @TypeConverter + fun endpointStatusToString(status: EndpointStatus?): String? = status?.value + + @TypeConverter + fun fromEndpointList(value: String?): List? { + val type = object : TypeToken>() {}.type + return gson.fromJson(value, type) + } + + @TypeConverter + fun endpointListToString(list: List?): String? = gson.toJson(list) +} + diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/schema/Database.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/schema/Database.kt new file mode 100644 index 0000000000..bc885adee2 --- /dev/null +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/schema/Database.kt @@ -0,0 +1,59 @@ +package app.alextran.immich.schema + +import android.content.Context +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase +import androidx.room.TypeConverters + + +@Database( + entities = [ + AssetFace::class, + AuthUser::class, + LocalAlbum::class, + LocalAlbumAsset::class, + LocalAsset::class, + MemoryAsset::class, + Memory::class, + Partner::class, + Person::class, + RemoteAlbum::class, + RemoteAlbumAsset::class, + RemoteAlbumUser::class, + RemoteAsset::class, + RemoteExif::class, + Stack::class, + Store::class, + UploadTask::class, + UploadTaskStat::class, + User::class, + UserMetadata::class + ], + version = 1, + exportSchema = false +) +@TypeConverters(Converters::class) +abstract class AppDatabase : RoomDatabase() { + abstract fun localAssetDao(): LocalAssetDao + abstract fun storeDao(): StoreDao + abstract fun uploadTaskDao(): UploadTaskDao + abstract fun uploadTaskStatDao(): UploadTaskStatDao + + companion object { + @Volatile + private var INSTANCE: AppDatabase? = null + + fun getDatabase(context: Context): AppDatabase { + return INSTANCE ?: synchronized(this) { + val instance = Room.databaseBuilder( + context.applicationContext, + AppDatabase::class.java, + "app_database" + ).build() + INSTANCE = instance + instance + } + } + } +} diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/schema/Enums.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/schema/Enums.kt new file mode 100644 index 0000000000..86768fa8a2 --- /dev/null +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/schema/Enums.kt @@ -0,0 +1,267 @@ +package app.alextran.immich.schema + +import java.net.URL +import java.util.Date + +enum class StoreKey(val rawValue: Int) { + VERSION(0), + DEVICE_ID_HASH(3), + BACKUP_TRIGGER_DELAY(8), + TILES_PER_ROW(103), + GROUP_ASSETS_BY(105), + UPLOAD_ERROR_NOTIFICATION_GRACE_PERIOD(106), + THUMBNAIL_CACHE_SIZE(110), + IMAGE_CACHE_SIZE(111), + ALBUM_THUMBNAIL_CACHE_SIZE(112), + SELECTED_ALBUM_SORT_ORDER(113), + LOG_LEVEL(115), + MAP_RELATIVE_DATE(119), + MAP_THEME_MODE(124), + + ASSET_ETAG(1), + CURRENT_USER(2), + DEVICE_ID(4), + ACCESS_TOKEN(11), + SERVER_ENDPOINT(12), + SSL_CLIENT_CERT_DATA(15), + SSL_CLIENT_PASSWD(16), + THEME_MODE(102), + CUSTOM_HEADERS(127), + PRIMARY_COLOR(128), + PREFERRED_WIFI_NAME(133), + + EXTERNAL_ENDPOINT_LIST(135), + + LOCAL_ENDPOINT(134), + SERVER_URL(10), + + BACKUP_FAILED_SINCE(5), + + BACKUP_REQUIRE_WIFI(6), + BACKUP_REQUIRE_CHARGING(7), + AUTO_BACKUP(13), + BACKGROUND_BACKUP(14), + LOAD_PREVIEW(100), + LOAD_ORIGINAL(101), + DYNAMIC_LAYOUT(104), + BACKGROUND_BACKUP_TOTAL_PROGRESS(107), + BACKGROUND_BACKUP_SINGLE_PROGRESS(108), + STORAGE_INDICATOR(109), + ADVANCED_TROUBLESHOOTING(114), + PREFER_REMOTE_IMAGE(116), + LOOP_VIDEO(117), + MAP_SHOW_FAVORITE_ONLY(118), + SELF_SIGNED_CERT(120), + MAP_INCLUDE_ARCHIVED(121), + IGNORE_ICLOUD_ASSETS(122), + SELECTED_ALBUM_SORT_REVERSE(123), + MAP_WITH_PARTNERS(125), + ENABLE_HAPTIC_FEEDBACK(126), + DYNAMIC_THEME(129), + COLORFUL_INTERFACE(130), + SYNC_ALBUMS(131), + AUTO_ENDPOINT_SWITCHING(132), + LOAD_ORIGINAL_VIDEO(136), + MANAGE_LOCAL_MEDIA_ANDROID(137), + READONLY_MODE_ENABLED(138), + AUTO_PLAY_VIDEO(139), + PHOTO_MANAGER_CUSTOM_FILTER(1000), + BETA_PROMPT_SHOWN(1001), + BETA_TIMELINE(1002), + ENABLE_BACKUP(1003), + USE_WIFI_FOR_UPLOAD_VIDEOS(1004), + USE_WIFI_FOR_UPLOAD_PHOTOS(1005), + NEED_BETA_MIGRATION(1006), + SHOULD_RESET_SYNC(1007); + + companion object { + fun fromInt(value: Int): StoreKey? = entries.find { it.rawValue == value } + + // Int keys + val version = TypedStoreKey(VERSION) + val deviceIdHash = TypedStoreKey(DEVICE_ID_HASH) + val backupTriggerDelay = TypedStoreKey(BACKUP_TRIGGER_DELAY) + val tilesPerRow = TypedStoreKey(TILES_PER_ROW) + val groupAssetsBy = TypedStoreKey(GROUP_ASSETS_BY) + val uploadErrorNotificationGracePeriod = TypedStoreKey(UPLOAD_ERROR_NOTIFICATION_GRACE_PERIOD) + val thumbnailCacheSize = TypedStoreKey(THUMBNAIL_CACHE_SIZE) + val imageCacheSize = TypedStoreKey(IMAGE_CACHE_SIZE) + val albumThumbnailCacheSize = TypedStoreKey(ALBUM_THUMBNAIL_CACHE_SIZE) + val selectedAlbumSortOrder = TypedStoreKey(SELECTED_ALBUM_SORT_ORDER) + val logLevel = TypedStoreKey(LOG_LEVEL) + val mapRelativeDate = TypedStoreKey(MAP_RELATIVE_DATE) + val mapThemeMode = TypedStoreKey(MAP_THEME_MODE) + + // String keys + val assetETag = TypedStoreKey(ASSET_ETAG) + val currentUser = TypedStoreKey(CURRENT_USER) + val deviceId = TypedStoreKey(DEVICE_ID) + val accessToken = TypedStoreKey(ACCESS_TOKEN) + val sslClientCertData = TypedStoreKey(SSL_CLIENT_CERT_DATA) + val sslClientPasswd = TypedStoreKey(SSL_CLIENT_PASSWD) + val themeMode = TypedStoreKey(THEME_MODE) + val customHeaders = TypedStoreKey>(CUSTOM_HEADERS) + val primaryColor = TypedStoreKey(PRIMARY_COLOR) + val preferredWifiName = TypedStoreKey(PREFERRED_WIFI_NAME) + + // Endpoint keys + val externalEndpointList = TypedStoreKey>(EXTERNAL_ENDPOINT_LIST) + + // URL keys + val localEndpoint = TypedStoreKey(LOCAL_ENDPOINT) + val serverEndpoint = TypedStoreKey(SERVER_ENDPOINT) + val serverUrl = TypedStoreKey(SERVER_URL) + + // Date keys + val backupFailedSince = TypedStoreKey(BACKUP_FAILED_SINCE) + + // Bool keys + val backupRequireWifi = TypedStoreKey(BACKUP_REQUIRE_WIFI) + val backupRequireCharging = TypedStoreKey(BACKUP_REQUIRE_CHARGING) + val autoBackup = TypedStoreKey(AUTO_BACKUP) + val backgroundBackup = TypedStoreKey(BACKGROUND_BACKUP) + val loadPreview = TypedStoreKey(LOAD_PREVIEW) + val loadOriginal = TypedStoreKey(LOAD_ORIGINAL) + val dynamicLayout = TypedStoreKey(DYNAMIC_LAYOUT) + val backgroundBackupTotalProgress = TypedStoreKey(BACKGROUND_BACKUP_TOTAL_PROGRESS) + val backgroundBackupSingleProgress = TypedStoreKey(BACKGROUND_BACKUP_SINGLE_PROGRESS) + val storageIndicator = TypedStoreKey(STORAGE_INDICATOR) + val advancedTroubleshooting = TypedStoreKey(ADVANCED_TROUBLESHOOTING) + val preferRemoteImage = TypedStoreKey(PREFER_REMOTE_IMAGE) + val loopVideo = TypedStoreKey(LOOP_VIDEO) + val mapShowFavoriteOnly = TypedStoreKey(MAP_SHOW_FAVORITE_ONLY) + val selfSignedCert = TypedStoreKey(SELF_SIGNED_CERT) + val mapIncludeArchived = TypedStoreKey(MAP_INCLUDE_ARCHIVED) + val ignoreIcloudAssets = TypedStoreKey(IGNORE_ICLOUD_ASSETS) + val selectedAlbumSortReverse = TypedStoreKey(SELECTED_ALBUM_SORT_REVERSE) + val mapwithPartners = TypedStoreKey(MAP_WITH_PARTNERS) + val enableHapticFeedback = TypedStoreKey(ENABLE_HAPTIC_FEEDBACK) + val dynamicTheme = TypedStoreKey(DYNAMIC_THEME) + val colorfulInterface = TypedStoreKey(COLORFUL_INTERFACE) + val syncAlbums = TypedStoreKey(SYNC_ALBUMS) + val autoEndpointSwitching = TypedStoreKey(AUTO_ENDPOINT_SWITCHING) + val loadOriginalVideo = TypedStoreKey(LOAD_ORIGINAL_VIDEO) + val manageLocalMediaAndroid = TypedStoreKey(MANAGE_LOCAL_MEDIA_ANDROID) + val readonlyModeEnabled = TypedStoreKey(READONLY_MODE_ENABLED) + val autoPlayVideo = TypedStoreKey(AUTO_PLAY_VIDEO) + val photoManagerCustomFilter = TypedStoreKey(PHOTO_MANAGER_CUSTOM_FILTER) + val betaPromptShown = TypedStoreKey(BETA_PROMPT_SHOWN) + val betaTimeline = TypedStoreKey(BETA_TIMELINE) + val enableBackup = TypedStoreKey(ENABLE_BACKUP) + val useWifiForUploadVideos = TypedStoreKey(USE_WIFI_FOR_UPLOAD_VIDEOS) + val useWifiForUploadPhotos = TypedStoreKey(USE_WIFI_FOR_UPLOAD_PHOTOS) + val needBetaMigration = TypedStoreKey(NEED_BETA_MIGRATION) + val shouldResetSync = TypedStoreKey(SHOULD_RESET_SYNC) + } +} + +enum class TaskStatus { + DOWNLOAD_PENDING, + DOWNLOAD_QUEUED, + DOWNLOAD_FAILED, + UPLOAD_PENDING, + UPLOAD_QUEUED, + UPLOAD_FAILED, + UPLOAD_COMPLETE +} + +enum class BackupSelection { + SELECTED, + NONE, + EXCLUDED +} + +enum class AvatarColor { + PRIMARY, + PINK, + RED, + YELLOW, + BLUE, + GREEN, + PURPLE, + ORANGE, + GRAY, + AMBER +} + +enum class AlbumUserRole { + EDITOR, + VIEWER +} + +enum class MemoryType { + ON_THIS_DAY +} + +enum class AssetVisibility { + TIMELINE, + HIDDEN, + ARCHIVE, + LOCKED +} + +enum class SourceType(val value: String) { + MACHINE_LEARNING("machine-learning"), + EXIF("exif"), + MANUAL("manual"); + + companion object { + fun fromString(value: String): SourceType? = entries.find { it.value == value } + } +} + +enum class UploadMethod { + MULTIPART, + RESUMABLE +} + +enum class UploadErrorCode { + UNKNOWN, + ASSET_NOT_FOUND, + FILE_NOT_FOUND, + RESOURCE_NOT_FOUND, + INVALID_RESOURCE, + ENCODING_FAILED, + WRITE_FAILED, + NOT_ENOUGH_SPACE, + NETWORK_ERROR, + PHOTOS_INTERNAL_ERROR, + PHOTOS_UNKNOWN_ERROR, + NO_SERVER_URL, + NO_DEVICE_ID, + NO_ACCESS_TOKEN, + INTERRUPTED, + CANCELLED, + DOWNLOAD_STALLED, + FORCE_QUIT, + OUT_OF_RESOURCES, + BACKGROUND_UPDATES_DISABLED, + UPLOAD_TIMEOUT, + ICLOUD_RATE_LIMIT, + ICLOUD_THROTTLED, + INVALID_SERVER_RESPONSE, +} + +enum class AssetType { + OTHER, + IMAGE, + VIDEO, + AUDIO +} + +enum class EndpointStatus(val value: String) { + LOADING("loading"), + VALID("valid"), + ERROR("error"), + UNKNOWN("unknown"); + + companion object { + fun fromString(value: String): EndpointStatus? = entries.find { it.value == value } + } +} + +// Endpoint data class +data class Endpoint( + val url: String, + val status: EndpointStatus +) diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/schema/Queries.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/schema/Queries.kt new file mode 100644 index 0000000000..1ec1cdad73 --- /dev/null +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/schema/Queries.kt @@ -0,0 +1,168 @@ +package app.alextran.immich.schema + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import app.alextran.immich.upload.TaskConfig +import java.util.Date + +@Dao +interface LocalAssetDao { + @Query(""" + SELECT a.id, a.type FROM local_asset_entity a + WHERE EXISTS ( + SELECT 1 FROM local_album_asset_entity laa + INNER JOIN local_album_entity la ON laa.album_id = la.id + WHERE laa.asset_id = a.id + AND la.backup_selection = 0 -- selected + ) + AND NOT EXISTS ( + SELECT 1 FROM local_album_asset_entity laa2 + INNER JOIN local_album_entity la2 ON laa2.album_id = la2.id + WHERE laa2.asset_id = a.id + AND la2.backup_selection = 2 -- excluded + ) + AND NOT EXISTS ( + SELECT 1 FROM remote_asset_entity ra + WHERE ra.checksum = a.checksum + AND ra.owner_id = (SELECT string_value FROM store_entity WHERE id = 14) -- current_user + ) + AND NOT EXISTS ( + SELECT 1 FROM upload_tasks ut + WHERE ut.local_id = a.id + ) + LIMIT :limit + """) + suspend fun getCandidatesForBackup(limit: Int): List +} + +@Dao +interface StoreDao { + @Query("SELECT * FROM store_entity WHERE id = :key") + suspend fun get(key: StoreKey): Store? + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insert(store: Store) + + // Extension functions for type-safe access + suspend fun get( + typedKey: TypedStoreKey, + storage: StorageType + ): T? { + val store = get(typedKey.key) ?: return null + + return when (storage) { + is StorageType.IntStorage, + is StorageType.BoolStorage, + is StorageType.DateStorage -> { + store.intValue?.let { storage.fromDb(it) } + } + else -> { + store.stringValue?.let { storage.fromDb(it) } + } + } + } + + suspend fun set( + typedKey: TypedStoreKey, + value: T, + storage: StorageType + ) { + val dbValue = storage.toDb(value) + + val store = when (storage) { + is StorageType.IntStorage, + is StorageType.BoolStorage, + is StorageType.DateStorage -> { + Store( + id = typedKey.key, + stringValue = null, + intValue = dbValue as Int + ) + } + else -> { + Store( + id = typedKey.key, + stringValue = dbValue as String, + intValue = null + ) + } + } + + insert(store) + } +} + +@Dao +interface UploadTaskDao { + @Insert(onConflict = OnConflictStrategy.IGNORE) + suspend fun insertAll(tasks: List) + + @Query(""" + SELECT id FROM upload_tasks + WHERE status IN (:statuses) + """) + suspend fun getTaskIdsByStatus(statuses: List): List + + @Query(""" + UPDATE upload_tasks + SET status = 3, -- upload_pending + file_path = NULL, + attempts = 0 + WHERE id IN (:taskIds) + """) + suspend fun resetOrphanedTasks(taskIds: List) + + @Query(""" + SELECT + t.attempts, + a.checksum, + a.created_at as createdAt, + a.name as fileName, + t.file_path as filePath, + a.is_favorite as isFavorite, + a.id as localId, + t.priority, + t.id as taskId, + a.type, + a.updated_at as updatedAt + FROM upload_tasks t + INNER JOIN local_asset_entity a ON t.local_id = a.id + WHERE t.status = 3 -- upload_pending + AND t.attempts < :maxAttempts + AND a.checksum IS NOT NULL + AND (t.retry_after IS NULL OR t.retry_after <= :currentTime) + ORDER BY t.priority DESC, t.created_at ASC + LIMIT :limit + """) + suspend fun getTasksForUpload(limit: Int, maxAttempts: Int = TaskConfig.MAX_ATTEMPTS, currentTime: Long = System.currentTimeMillis() / 1000): List + + @Query("SELECT EXISTS(SELECT 1 FROM upload_tasks WHERE status = 3 LIMIT 1)") // upload_pending + suspend fun hasPendingTasks(): Boolean + + @Query(""" + UPDATE upload_tasks + SET attempts = :attempts, + last_error = :errorCode, + status = :status, + retry_after = :retryAfter + WHERE id = :taskId + """) + suspend fun updateTaskAfterFailure( + taskId: Long, + attempts: Int, + errorCode: UploadErrorCode, + status: TaskStatus, + retryAfter: Date? + ) + + @Query("UPDATE upload_tasks SET status = :status WHERE id = :id") + suspend fun updateStatus(id: Long, status: TaskStatus) +} + +@Dao +interface UploadTaskStatDao { + @Query("SELECT * FROM upload_task_stats") + suspend fun getStats(): UploadTaskStat? +} diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/schema/Store.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/schema/Store.kt new file mode 100644 index 0000000000..49ea14b5ee --- /dev/null +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/schema/Store.kt @@ -0,0 +1,93 @@ +package app.alextran.immich.schema + +import com.google.gson.Gson +import java.net.URL +import java.util.Date + +// Sealed interface representing storage types +sealed interface StorageType { + fun toDb(value: T): Any + fun fromDb(value: Any): T + + data object IntStorage : StorageType { + override fun toDb(value: Int) = value + override fun fromDb(value: Any) = value as Int + } + + data object BoolStorage : StorageType { + override fun toDb(value: Boolean) = if (value) 1 else 0 + override fun fromDb(value: Any) = (value as Int) == 1 + } + + data object StringStorage : StorageType { + override fun toDb(value: String) = value + override fun fromDb(value: Any) = value as String + } + + data object DateStorage : StorageType { + override fun toDb(value: Date) = value.time / 1000 + override fun fromDb(value: Any) = Date((value as Long) * 1000) + } + + data object UrlStorage : StorageType { + override fun toDb(value: URL) = value.toString() + override fun fromDb(value: Any) = URL(value as String) + } + + class JsonStorage( + private val clazz: Class, + private val gson: Gson = Gson() + ) : StorageType { + override fun toDb(value: T) = gson.toJson(value) + override fun fromDb(value: Any) = gson.fromJson(value as String, clazz) + } +} + +// Typed key wrapper +@JvmInline +value class TypedStoreKey(val key: StoreKey) { + companion object { + // Factory methods for type-safe key creation + inline fun of(key: StoreKey): TypedStoreKey = TypedStoreKey(key) + } +} + +// Registry mapping keys to their storage types +object StoreRegistry { + private val intKeys = setOf( + StoreKey.VERSION, + StoreKey.DEVICE_ID_HASH, + StoreKey.BACKUP_TRIGGER_DELAY + ) + + private val stringKeys = setOf( + StoreKey.CURRENT_USER, + StoreKey.DEVICE_ID, + StoreKey.ACCESS_TOKEN + ) + + fun usesIntStorage(key: StoreKey): Boolean = key in intKeys + fun usesStringStorage(key: StoreKey): Boolean = key in stringKeys +} + +// Storage type registry for automatic selection +@Suppress("UNCHECKED_CAST") +object StorageTypes { + inline fun get(): StorageType = when (T::class) { + Int::class -> StorageType.IntStorage as StorageType + Boolean::class -> StorageType.BoolStorage as StorageType + String::class -> StorageType.StringStorage as StorageType + Date::class -> StorageType.DateStorage as StorageType + URL::class -> StorageType.UrlStorage as StorageType + else -> StorageType.JsonStorage(T::class.java) + } +} + +// Simplified extension functions with automatic storage +suspend inline fun StoreDao.get(typedKey: TypedStoreKey): T? { + return get(typedKey, StorageTypes.get()) +} + +suspend inline fun StoreDao.set(typedKey: TypedStoreKey, value: T) { + set(typedKey, value, StorageTypes.get()) +} diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/schema/Tables.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/schema/Tables.kt new file mode 100644 index 0000000000..b842586041 --- /dev/null +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/schema/Tables.kt @@ -0,0 +1,405 @@ +package app.alextran.immich.schema + +import androidx.room.* +import java.net.URL +import java.util.Date + +@Entity(tableName = "asset_face_entity") +data class AssetFace( + @PrimaryKey + val id: String, + @ColumnInfo(name = "asset_id") + val assetId: String, + @ColumnInfo(name = "person_id") + val personId: String?, + @ColumnInfo(name = "image_width") + val imageWidth: Int, + @ColumnInfo(name = "image_height") + val imageHeight: Int, + @ColumnInfo(name = "bounding_box_x1") + val boundingBoxX1: Int, + @ColumnInfo(name = "bounding_box_y1") + val boundingBoxY1: Int, + @ColumnInfo(name = "bounding_box_x2") + val boundingBoxX2: Int, + @ColumnInfo(name = "bounding_box_y2") + val boundingBoxY2: Int, + @ColumnInfo(name = "source_type") + val sourceType: SourceType +) + +@Entity(tableName = "auth_user_entity") +data class AuthUser( + @PrimaryKey + val id: String, + val name: String, + val email: String, + @ColumnInfo(name = "is_admin") + val isAdmin: Boolean, + @ColumnInfo(name = "has_profile_image") + val hasProfileImage: Boolean, + @ColumnInfo(name = "profile_changed_at") + val profileChangedAt: Date, + @ColumnInfo(name = "avatar_color") + val avatarColor: AvatarColor, + @ColumnInfo(name = "quota_size_in_bytes") + val quotaSizeInBytes: Int, + @ColumnInfo(name = "quota_usage_in_bytes") + val quotaUsageInBytes: Int, + @ColumnInfo(name = "pin_code") + val pinCode: String? +) + +@Entity(tableName = "local_album_entity") +data class LocalAlbum( + @PrimaryKey + val id: String, + @ColumnInfo(name = "backup_selection") + val backupSelection: BackupSelection, + @ColumnInfo(name = "linked_remote_album_id") + val linkedRemoteAlbumId: String?, + @ColumnInfo(name = "marker") + val marker: Boolean?, + val name: String, + @ColumnInfo(name = "is_ios_shared_album") + val isIosSharedAlbum: Boolean, + @ColumnInfo(name = "updated_at") + val updatedAt: Date +) + +@Entity( + tableName = "local_album_asset_entity", + primaryKeys = ["asset_id", "album_id"] +) +data class LocalAlbumAsset( + @ColumnInfo(name = "asset_id") + val assetId: String, + @ColumnInfo(name = "album_id") + val albumId: String, + @ColumnInfo(name = "marker") + val marker: String? +) + +@Entity(tableName = "local_asset_entity") +data class LocalAsset( + @PrimaryKey + val id: String, + val checksum: String?, + @ColumnInfo(name = "created_at") + val createdAt: Date, + @ColumnInfo(name = "duration_in_seconds") + val durationInSeconds: Int?, + val height: Int?, + @ColumnInfo(name = "is_favorite") + val isFavorite: Boolean, + val name: String, + val orientation: String, + val type: AssetType, + @ColumnInfo(name = "updated_at") + val updatedAt: Date, + val width: Int? +) + +data class BackupCandidate( + val id: String, + val type: AssetType +) + +@Entity( + tableName = "memory_asset_entity", + primaryKeys = ["asset_id", "album_id"] +) +data class MemoryAsset( + @ColumnInfo(name = "asset_id") + val assetId: String, + @ColumnInfo(name = "album_id") + val albumId: String +) + +@Entity(tableName = "memory_entity") +data class Memory( + @PrimaryKey + val id: String, + @ColumnInfo(name = "created_at") + val createdAt: Date, + @ColumnInfo(name = "updated_at") + val updatedAt: Date, + @ColumnInfo(name = "deleted_at") + val deletedAt: Date?, + @ColumnInfo(name = "owner_id") + val ownerId: String, + val type: MemoryType, + val data: String, + @ColumnInfo(name = "is_saved") + val isSaved: Boolean, + @ColumnInfo(name = "memory_at") + val memoryAt: Date, + @ColumnInfo(name = "seen_at") + val seenAt: Date?, + @ColumnInfo(name = "show_at") + val showAt: Date?, + @ColumnInfo(name = "hide_at") + val hideAt: Date? +) + +@Entity( + tableName = "partner_entity", + primaryKeys = ["shared_by_id", "shared_with_id"] +) +data class Partner( + @ColumnInfo(name = "shared_by_id") + val sharedById: String, + @ColumnInfo(name = "shared_with_id") + val sharedWithId: String, + @ColumnInfo(name = "in_timeline") + val inTimeline: Boolean +) + +@Entity(tableName = "person_entity") +data class Person( + @PrimaryKey + val id: String, + @ColumnInfo(name = "created_at") + val createdAt: Date, + @ColumnInfo(name = "updated_at") + val updatedAt: Date, + @ColumnInfo(name = "owner_id") + val ownerId: String, + val name: String, + @ColumnInfo(name = "face_asset_id") + val faceAssetId: String?, + @ColumnInfo(name = "is_favorite") + val isFavorite: Boolean, + @ColumnInfo(name = "is_hidden") + val isHidden: Boolean, + val color: String?, + @ColumnInfo(name = "birth_date") + val birthDate: Date? +) + +@Entity(tableName = "remote_album_entity") +data class RemoteAlbum( + @PrimaryKey + val id: String, + @ColumnInfo(name = "created_at") + val createdAt: Date, + val description: String?, + @ColumnInfo(name = "is_activity_enabled") + val isActivityEnabled: Boolean, + val name: String, + val order: Int, + @ColumnInfo(name = "owner_id") + val ownerId: String, + @ColumnInfo(name = "thumbnail_asset_id") + val thumbnailAssetId: String?, + @ColumnInfo(name = "updated_at") + val updatedAt: Date +) + +@Entity( + tableName = "remote_album_asset_entity", + primaryKeys = ["asset_id", "album_id"] +) +data class RemoteAlbumAsset( + @ColumnInfo(name = "asset_id") + val assetId: String, + @ColumnInfo(name = "album_id") + val albumId: String +) + +@Entity( + tableName = "remote_album_user_entity", + primaryKeys = ["album_id", "user_id"] +) +data class RemoteAlbumUser( + @ColumnInfo(name = "album_id") + val albumId: String, + @ColumnInfo(name = "user_id") + val userId: String, + val role: AlbumUserRole +) + +@Entity(tableName = "remote_asset_entity") +data class RemoteAsset( + @PrimaryKey + val id: String, + val checksum: String, + @ColumnInfo(name = "is_favorite") + val isFavorite: Boolean, + @ColumnInfo(name = "deleted_at") + val deletedAt: Date?, + @ColumnInfo(name = "owner_id") + val ownerId: String, + @ColumnInfo(name = "local_date_time") + val localDateTime: Date?, + @ColumnInfo(name = "thumb_hash") + val thumbHash: String?, + @ColumnInfo(name = "library_id") + val libraryId: String?, + @ColumnInfo(name = "live_photo_video_id") + val livePhotoVideoId: String?, + @ColumnInfo(name = "stack_id") + val stackId: String?, + val visibility: AssetVisibility +) + +@Entity(tableName = "remote_exif_entity") +data class RemoteExif( + @PrimaryKey + @ColumnInfo(name = "asset_id") + val assetId: String, + val city: String?, + val state: String?, + val country: String?, + @ColumnInfo(name = "date_time_original") + val dateTimeOriginal: Date?, + val description: String?, + val height: Int?, + val width: Int?, + @ColumnInfo(name = "exposure_time") + val exposureTime: String?, + @ColumnInfo(name = "f_number") + val fNumber: Double?, + @ColumnInfo(name = "file_size") + val fileSize: Int?, + @ColumnInfo(name = "focal_length") + val focalLength: Double?, + val latitude: Double?, + val longitude: Double?, + val iso: Int?, + val make: String?, + val model: String?, + val lens: String?, + val orientation: String?, + @ColumnInfo(name = "time_zone") + val timeZone: String?, + val rating: Int?, + @ColumnInfo(name = "projection_type") + val projectionType: String? +) + +@Entity(tableName = "stack_entity") +data class Stack( + @PrimaryKey + val id: String, + @ColumnInfo(name = "created_at") + val createdAt: Date, + @ColumnInfo(name = "updated_at") + val updatedAt: Date, + @ColumnInfo(name = "owner_id") + val ownerId: String, + @ColumnInfo(name = "primary_asset_id") + val primaryAssetId: String +) + +@Entity(tableName = "store_entity") +data class Store( + @PrimaryKey + val id: StoreKey, + @ColumnInfo(name = "string_value") + val stringValue: String?, + @ColumnInfo(name = "int_value") + val intValue: Int? +) + +@Entity(tableName = "upload_task_entity") +data class UploadTask( + @PrimaryKey(autoGenerate = true) + val id: Long = 0, + val attempts: Int, + @ColumnInfo(name = "created_at") + val createdAt: Date, + @ColumnInfo(name = "file_path") + val filePath: URL?, + @ColumnInfo(name = "is_live_photo") + val isLivePhoto: Boolean?, + @ColumnInfo(name = "last_error") + val lastError: UploadErrorCode?, + @ColumnInfo(name = "live_photo_video_id") + val livePhotoVideoId: String?, + @ColumnInfo(name = "local_id") + val localId: String, + val method: UploadMethod, + val priority: Float, + @ColumnInfo(name = "retry_after") + val retryAfter: Date?, + val status: TaskStatus +) + +// Data class for query results +data class LocalAssetTaskData( + val attempts: Int, + val checksum: String, + val createdAt: Date, + val fileName: String, + val filePath: URL?, + val isFavorite: Boolean, + val localId: String, + val priority: Float, + val taskId: Long, + val type: AssetType, + val updatedAt: Date +) + +@Entity(tableName = "upload_task_stats") +data class UploadTaskStat( + @ColumnInfo(name = "pending_downloads") + val pendingDownloads: Int, + @ColumnInfo(name = "pending_uploads") + val pendingUploads: Int, + @ColumnInfo(name = "queued_downloads") + val queuedDownloads: Int, + @ColumnInfo(name = "queued_uploads") + val queuedUploads: Int, + @ColumnInfo(name = "failed_downloads") + val failedDownloads: Int, + @ColumnInfo(name = "failed_uploads") + val failedUploads: Int, + @ColumnInfo(name = "completed_uploads") + val completedUploads: Int +) + +@Entity(tableName = "user_entity") +data class User( + @PrimaryKey + val id: String, + val name: String, + val email: String, + @ColumnInfo(name = "has_profile_image") + val hasProfileImage: Boolean, + @ColumnInfo(name = "profile_changed_at") + val profileChangedAt: Date, + @ColumnInfo(name = "avatar_color") + val avatarColor: AvatarColor +) + +@Entity( + tableName = "user_metadata_entity", + primaryKeys = ["user_id", "key"] +) +data class UserMetadata( + @ColumnInfo(name = "user_id") + val userId: String, + val key: Date, + val value: ByteArray +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as UserMetadata + + if (userId != other.userId) return false + if (key != other.key) return false + if (!value.contentEquals(other.value)) return false + + return true + } + + override fun hashCode(): Int { + var result = userId.hashCode() + result = 31 * result + key.hashCode() + result = 31 * result + value.contentHashCode() + return result + } +} diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/upload/NetworkMonitor.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/upload/NetworkMonitor.kt new file mode 100644 index 0000000000..a25ce4c5a4 --- /dev/null +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/upload/NetworkMonitor.kt @@ -0,0 +1,54 @@ +package app.alextran.immich.upload + +import android.content.Context +import android.net.ConnectivityManager +import android.net.Network +import android.net.NetworkCapabilities +import android.net.NetworkRequest + +object NetworkMonitor { + @Volatile + private var isConnected = false + + @Volatile + private var isWifi = false + + fun initialize(context: Context) { + val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + + val networkRequest = NetworkRequest.Builder() + .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + .build() + + connectivityManager.registerNetworkCallback(networkRequest, object : ConnectivityManager.NetworkCallback() { + override fun onAvailable(network: Network) { + isConnected = true + checkWifi(connectivityManager, network) + } + + override fun onLost(network: Network) { + isConnected = false + isWifi = false + } + + override fun onCapabilitiesChanged(network: Network, capabilities: NetworkCapabilities) { + checkWifi(connectivityManager, network) + } + + private fun checkWifi(cm: ConnectivityManager, network: Network) { + val capabilities = cm.getNetworkCapabilities(network) + isWifi = capabilities?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true + } + }) + } + + fun isConnected(): Boolean = isConnected + + fun isWifiConnected(context: Context): Boolean { + if (!isConnected) return false + + val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + val capabilities = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork) + return capabilities?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true + } +} diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/upload/TaskConfig.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/upload/TaskConfig.kt new file mode 100644 index 0000000000..472191bd73 --- /dev/null +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/upload/TaskConfig.kt @@ -0,0 +1,8 @@ +package app.alextran.immich.upload + +object TaskConfig { + const val MAX_ATTEMPTS = 3 + const val MAX_PENDING_DOWNLOADS = 10 + const val MAX_PENDING_UPLOADS = 10 + const val MAX_ACTIVE_UPLOADS = 3 +} diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/upload/UploadTask.g.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/upload/UploadTask.g.kt new file mode 100644 index 0000000000..9be979318b --- /dev/null +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/upload/UploadTask.g.kt @@ -0,0 +1,429 @@ +// Autogenerated from Pigeon (v26.0.2), do not edit directly. +// See also: https://pub.dev/packages/pigeon +@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass") + +package app.alextran.immich.upload + +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 UploadTaskPigeonUtils { + + 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 deepEquals(a: Any?, b: Any?): Boolean { + 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) { + return a.contentEquals(b) + } + if (a is Array<*> && b is Array<*>) { + return a.size == b.size && + a.indices.all{ deepEquals(a[it], b[it]) } + } + if (a is List<*> && b is List<*>) { + return a.size == b.size && + a.indices.all{ deepEquals(a[it], b[it]) } + } + if (a is Map<*, *> && b is Map<*, *>) { + return a.size == b.size && a.all { + (b as Map).containsKey(it.key) && + deepEquals(it.value, b[it.key]) + } + } + return a == b + } + +} + +/** + * 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 +) : Throwable() + +enum class UploadApiErrorCode(val raw: Int) { + UNKNOWN(0), + ASSET_NOT_FOUND(1), + FILE_NOT_FOUND(2), + RESOURCE_NOT_FOUND(3), + INVALID_RESOURCE(4), + ENCODING_FAILED(5), + WRITE_FAILED(6), + NOT_ENOUGH_SPACE(7), + NETWORK_ERROR(8), + PHOTOS_INTERNAL_ERROR(9), + PHOTOS_UNKNOWN_ERROR(10), + NO_SERVER_URL(11), + NO_DEVICE_ID(12), + NO_ACCESS_TOKEN(13), + INTERRUPTED(14), + CANCELLED(15), + DOWNLOAD_STALLED(16), + FORCE_QUIT(17), + OUT_OF_RESOURCES(18), + BACKGROUND_UPDATES_DISABLED(19), + UPLOAD_TIMEOUT(20), + I_CLOUD_RATE_LIMIT(21), + I_CLOUD_THROTTLED(22); + + companion object { + fun ofRaw(raw: Int): UploadApiErrorCode? { + return values().firstOrNull { it.raw == raw } + } + } +} + +enum class UploadApiStatus(val raw: Int) { + DOWNLOAD_PENDING(0), + DOWNLOAD_QUEUED(1), + DOWNLOAD_FAILED(2), + UPLOAD_PENDING(3), + UPLOAD_QUEUED(4), + UPLOAD_FAILED(5), + UPLOAD_COMPLETE(6), + UPLOAD_SKIPPED(7); + + companion object { + fun ofRaw(raw: Int): UploadApiStatus? { + return values().firstOrNull { it.raw == raw } + } + } +} + +/** Generated class from Pigeon that represents data sent in messages. */ +data class UploadApiTaskStatus ( + val id: String, + val filename: String, + val status: UploadApiStatus, + val errorCode: UploadApiErrorCode? = null, + val httpStatusCode: Long? = null +) + { + companion object { + fun fromList(pigeonVar_list: List): UploadApiTaskStatus { + val id = pigeonVar_list[0] as String + val filename = pigeonVar_list[1] as String + val status = pigeonVar_list[2] as UploadApiStatus + val errorCode = pigeonVar_list[3] as UploadApiErrorCode? + val httpStatusCode = pigeonVar_list[4] as Long? + return UploadApiTaskStatus(id, filename, status, errorCode, httpStatusCode) + } + } + fun toList(): List { + return listOf( + id, + filename, + status, + errorCode, + httpStatusCode, + ) + } + override fun equals(other: Any?): Boolean { + if (other !is UploadApiTaskStatus) { + return false + } + if (this === other) { + return true + } + return UploadTaskPigeonUtils.deepEquals(toList(), other.toList()) } + + override fun hashCode(): Int = toList().hashCode() +} + +/** Generated class from Pigeon that represents data sent in messages. */ +data class UploadApiTaskProgress ( + val id: String, + val progress: Double, + val speed: Double? = null, + val totalBytes: Long? = null +) + { + companion object { + fun fromList(pigeonVar_list: List): UploadApiTaskProgress { + val id = pigeonVar_list[0] as String + val progress = pigeonVar_list[1] as Double + val speed = pigeonVar_list[2] as Double? + val totalBytes = pigeonVar_list[3] as Long? + return UploadApiTaskProgress(id, progress, speed, totalBytes) + } + } + fun toList(): List { + return listOf( + id, + progress, + speed, + totalBytes, + ) + } + override fun equals(other: Any?): Boolean { + if (other !is UploadApiTaskProgress) { + return false + } + if (this === other) { + return true + } + return UploadTaskPigeonUtils.deepEquals(toList(), other.toList()) } + + override fun hashCode(): Int = toList().hashCode() +} +private open class UploadTaskPigeonCodec : StandardMessageCodec() { + override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { + return when (type) { + 129.toByte() -> { + return (readValue(buffer) as Long?)?.let { + UploadApiErrorCode.ofRaw(it.toInt()) + } + } + 130.toByte() -> { + return (readValue(buffer) as Long?)?.let { + UploadApiStatus.ofRaw(it.toInt()) + } + } + 131.toByte() -> { + return (readValue(buffer) as? List)?.let { + UploadApiTaskStatus.fromList(it) + } + } + 132.toByte() -> { + return (readValue(buffer) as? List)?.let { + UploadApiTaskProgress.fromList(it) + } + } + else -> super.readValueOfType(type, buffer) + } + } + override fun writeValue(stream: ByteArrayOutputStream, value: Any?) { + when (value) { + is UploadApiErrorCode -> { + stream.write(129) + writeValue(stream, value.raw) + } + is UploadApiStatus -> { + stream.write(130) + writeValue(stream, value.raw) + } + is UploadApiTaskStatus -> { + stream.write(131) + writeValue(stream, value.toList()) + } + is UploadApiTaskProgress -> { + stream.write(132) + writeValue(stream, value.toList()) + } + else -> super.writeValue(stream, value) + } + } +} + +val UploadTaskPigeonMethodCodec = StandardMethodCodec(UploadTaskPigeonCodec()) + + +/** Generated interface from Pigeon that represents a handler of messages from Flutter. */ +interface UploadApi { + fun initialize(callback: (Result) -> Unit) + fun refresh(callback: (Result) -> Unit) + fun cancelAll(callback: (Result) -> Unit) + fun enqueueAssets(localIds: List, callback: (Result) -> Unit) + fun enqueueFiles(paths: List, callback: (Result) -> Unit) + + companion object { + /** The codec used by UploadApi. */ + val codec: MessageCodec by lazy { + UploadTaskPigeonCodec() + } + /** Sets up an instance of `UploadApi` to handle messages through the `binaryMessenger`. */ + @JvmOverloads + fun setUp(binaryMessenger: BinaryMessenger, api: UploadApi?, messageChannelSuffix: String = "") { + val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.UploadApi.initialize$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + api.initialize{ result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(UploadTaskPigeonUtils.wrapError(error)) + } else { + reply.reply(UploadTaskPigeonUtils.wrapResult(null)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.UploadApi.refresh$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + api.refresh{ result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(UploadTaskPigeonUtils.wrapError(error)) + } else { + reply.reply(UploadTaskPigeonUtils.wrapResult(null)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.UploadApi.cancelAll$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + api.cancelAll{ result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(UploadTaskPigeonUtils.wrapError(error)) + } else { + reply.reply(UploadTaskPigeonUtils.wrapResult(null)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.UploadApi.enqueueAssets$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val localIdsArg = args[0] as List + api.enqueueAssets(localIdsArg) { result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(UploadTaskPigeonUtils.wrapError(error)) + } else { + reply.reply(UploadTaskPigeonUtils.wrapResult(null)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.UploadApi.enqueueFiles$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pathsArg = args[0] as List + api.enqueueFiles(pathsArg) { result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(UploadTaskPigeonUtils.wrapError(error)) + } else { + reply.reply(UploadTaskPigeonUtils.wrapResult(null)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + } + } +} + +private class UploadTaskPigeonStreamHandler( + val wrapper: UploadTaskPigeonEventChannelWrapper +) : EventChannel.StreamHandler { + var pigeonSink: PigeonEventSink? = null + + override fun onListen(p0: Any?, sink: EventChannel.EventSink) { + pigeonSink = PigeonEventSink(sink) + wrapper.onListen(p0, pigeonSink!!) + } + + override fun onCancel(p0: Any?) { + pigeonSink = null + wrapper.onCancel(p0) + } +} + +interface UploadTaskPigeonEventChannelWrapper { + open fun onListen(p0: Any?, sink: PigeonEventSink) {} + + open fun onCancel(p0: Any?) {} +} + +class PigeonEventSink(private val sink: EventChannel.EventSink) { + fun success(value: T) { + sink.success(value) + } + + fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) { + sink.error(errorCode, errorMessage, errorDetails) + } + + fun endOfStream() { + sink.endOfStream() + } +} + +abstract class StreamStatusStreamHandler : UploadTaskPigeonEventChannelWrapper { + companion object { + fun register(messenger: BinaryMessenger, streamHandler: StreamStatusStreamHandler, instanceName: String = "") { + var channelName: String = "dev.flutter.pigeon.immich_mobile.UploadFlutterApi.streamStatus" + if (instanceName.isNotEmpty()) { + channelName += ".$instanceName" + } + val internalStreamHandler = UploadTaskPigeonStreamHandler(streamHandler) + EventChannel(messenger, channelName, UploadTaskPigeonMethodCodec).setStreamHandler(internalStreamHandler) + } + } +} + +abstract class StreamProgressStreamHandler : UploadTaskPigeonEventChannelWrapper { + companion object { + fun register(messenger: BinaryMessenger, streamHandler: StreamProgressStreamHandler, instanceName: String = "") { + var channelName: String = "dev.flutter.pigeon.immich_mobile.UploadFlutterApi.streamProgress" + if (instanceName.isNotEmpty()) { + channelName += ".$instanceName" + } + val internalStreamHandler = UploadTaskPigeonStreamHandler(streamHandler) + EventChannel(messenger, channelName, UploadTaskPigeonMethodCodec).setStreamHandler(internalStreamHandler) + } + } +} + diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/upload/UploadTaskImpl.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/upload/UploadTaskImpl.kt new file mode 100644 index 0000000000..d3c1f0aca8 --- /dev/null +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/upload/UploadTaskImpl.kt @@ -0,0 +1,175 @@ +package app.alextran.immich.upload + +import android.content.Context +import androidx.work.* +import app.alextran.immich.schema.AppDatabase +import app.alextran.immich.schema.AssetType +import app.alextran.immich.schema.StorageType +import app.alextran.immich.schema.StoreKey +import app.alextran.immich.schema.TaskStatus +import app.alextran.immich.schema.UploadMethod +import app.alextran.immich.schema.UploadTask +import kotlinx.coroutines.* +import kotlinx.coroutines.guava.await +import java.util.Date +import java.util.concurrent.TimeUnit + +// TODO: this is almost entirely LLM-generated (ported from Swift), need to verify behavior +class UploadTaskImpl(context: Context) : UploadApi { + private val ctx: Context = context.applicationContext + private val db: AppDatabase = AppDatabase.getDatabase(ctx) + private val workManager: WorkManager = WorkManager.getInstance(ctx) + + @Volatile + private var isInitialized = false + private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default) + + override fun initialize(callback: (Result) -> Unit) { + scope.launch { + try { + // Clean up orphaned tasks + val activeWorkInfos = workManager.getWorkInfosByTag(UPLOAD_WORK_TAG).await() + val activeTaskIds = activeWorkInfos + .filter { it.state == WorkInfo.State.RUNNING || it.state == WorkInfo.State.ENQUEUED } + .mapNotNull { + it.tags.find { tag -> tag.startsWith("task_") }?.substringAfter("task_")?.toLongOrNull() + } + .toSet() + + db.uploadTaskDao().run { + withContext(Dispatchers.IO) { + // Find tasks marked as queued but not actually running + val dbQueuedIds = getTaskIdsByStatus( + listOf( + TaskStatus.DOWNLOAD_QUEUED, + TaskStatus.UPLOAD_QUEUED, + TaskStatus.UPLOAD_PENDING + ) + ) + + val orphanIds = dbQueuedIds.filterNot { it in activeTaskIds } + + if (orphanIds.isNotEmpty()) { + resetOrphanedTasks(orphanIds) + } + } + } + + // Clean up temp files + val tempDir = getTempDirectory() + tempDir.deleteRecursively() + + isInitialized = true + startBackup() + + withContext(Dispatchers.Main) { + callback(Result.success(Unit)) + } + } catch (e: Exception) { + withContext(Dispatchers.Main) { + callback(Result.failure(e)) + } + } + } + } + + override fun refresh(callback: (Result) -> Unit) { + scope.launch { + try { + startBackup() + withContext(Dispatchers.Main) { + callback(Result.success(Unit)) + } + } catch (e: Exception) { + withContext(Dispatchers.Main) { + callback(Result.failure(e)) + } + } + } + } + + private suspend fun startBackup() { + if (!isInitialized) return + + withContext(Dispatchers.IO) { + try { + // Check if backup is enabled + val backupEnabled = db.storeDao().get(StoreKey.enableBackup, StorageType.BoolStorage) + if (backupEnabled != true) return@withContext + + // Get upload statistics + val stats = db.uploadTaskStatDao().getStats() ?: return@withContext + val availableSlots = TaskConfig.MAX_PENDING_UPLOADS + TaskConfig.MAX_PENDING_DOWNLOADS - + (stats.pendingDownloads + stats.queuedDownloads + stats.pendingUploads + stats.queuedUploads) + + if (availableSlots <= 0) return@withContext + + // Find candidate assets for backup + val candidates = db.localAssetDao().getCandidatesForBackup(availableSlots) + + if (candidates.isEmpty()) return@withContext + + // Create upload tasks for candidates + db.uploadTaskDao().insertAll(candidates.map { candidate -> + UploadTask( + attempts = 0, + createdAt = Date(), + filePath = null, + isLivePhoto = null, + lastError = null, + livePhotoVideoId = null, + localId = candidate.id, + method = UploadMethod.MULTIPART, + priority = when (candidate.type) { + AssetType.IMAGE -> 0.5f + else -> 0.3f + }, + retryAfter = null, + status = TaskStatus.UPLOAD_PENDING + ) + }) + + // Start upload workers + enqueueUploadWorkers() + } catch (e: Exception) { + android.util.Log.e(TAG, "Backup queue error", e) + } + } + } + + private fun enqueueUploadWorkers() { + // Create constraints + val constraints = Constraints.Builder() + .setRequiredNetworkType(NetworkType.CONNECTED) + .build() + + // Create work request + val uploadWorkRequest = OneTimeWorkRequestBuilder() + .setConstraints(constraints) + .addTag(UPLOAD_WORK_TAG) + .setBackoffCriteria( + BackoffPolicy.EXPONENTIAL, + WorkRequest.MIN_BACKOFF_MILLIS, + TimeUnit.MILLISECONDS + ) + .build() + + workManager.enqueueUniqueWork( + UPLOAD_WORK_NAME, + ExistingWorkPolicy.KEEP, + uploadWorkRequest + ) + } + + private fun getTempDirectory(): java.io.File { + return java.io.File(ctx.cacheDir, "upload_temp").apply { + if (!exists()) mkdirs() + } + } + + companion object { + private const val TAG = "UploadTaskImpl" + private const val UPLOAD_WORK_TAG = "immich_upload" + private const val UPLOAD_WORK_NAME = "immich_upload_unique" + } +} diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/upload/UploadWorker.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/upload/UploadWorker.kt new file mode 100644 index 0000000000..e4f96b2a49 --- /dev/null +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/upload/UploadWorker.kt @@ -0,0 +1,265 @@ +package app.alextran.immich.upload + +import android.content.Context +import android.provider.MediaStore +import androidx.work.* +import app.alextran.immich.schema.AppDatabase +import app.alextran.immich.schema.AssetType +import app.alextran.immich.schema.LocalAssetTaskData +import app.alextran.immich.schema.StorageType +import app.alextran.immich.schema.StoreKey +import app.alextran.immich.schema.TaskStatus +import app.alextran.immich.schema.UploadErrorCode +import kotlinx.coroutines.* +import okhttp3.* +import okhttp3.MediaType.Companion.toMediaType +import java.io.File +import java.io.IOException +import java.net.URL +import java.util.* +import java.util.concurrent.TimeUnit + +class UploadWorker( + context: Context, + params: WorkerParameters +) : CoroutineWorker(context, params) { + + private val db = AppDatabase.getDatabase(applicationContext) + private val client = createOkHttpClient() + + override suspend fun doWork(): Result = withContext(Dispatchers.IO) { + try { + // Check if backup is enabled + val backupEnabled = db.storeDao().get(StoreKey.enableBackup, StorageType.BoolStorage) + if (backupEnabled != true) { + return@withContext Result.success() + } + + // Get pending upload tasks + val tasks = db.uploadTaskDao().getTasksForUpload(TaskConfig.MAX_ACTIVE_UPLOADS) + + if (tasks.isEmpty()) { + return@withContext Result.success() + } + + // Process tasks concurrently + val results = tasks.map { task -> + async { processUploadTask(task) } + }.awaitAll() + + // Check if we should continue processing + val hasMore = db.uploadTaskDao().hasPendingTasks() + + if (hasMore) { + // Schedule next batch + enqueueNextBatch() + } + + // Determine result based on processing outcomes + when { + results.all { it } -> Result.success() + results.any { it } -> Result.success() // Partial success + else -> Result.retry() + } + } catch (e: Exception) { + android.util.Log.e(TAG, "Upload worker error", e) + Result.retry() + } + } + + private suspend fun processUploadTask(task: LocalAssetTaskData): Boolean { + return try { + // Get asset from MediaStore + val assetUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI + .buildUpon() + .appendPath(task.localId) + .build() + + val cursor = applicationContext.contentResolver.query( + assetUri, + arrayOf(MediaStore.Images.Media.DATA), + null, + null, + null + ) ?: return handleFailure(task, UploadErrorCode.ASSET_NOT_FOUND) + + val filePath = cursor.use { + if (it.moveToFirst()) { + it.getString(it.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)) + } else null + } ?: return handleFailure(task, UploadErrorCode.ASSET_NOT_FOUND) + + val file = File(filePath) + if (!file.exists()) { + return handleFailure(task, UploadErrorCode.FILE_NOT_FOUND) + } + + // Get server configuration + val serverUrl = db.storeDao().get(StoreKey.serverEndpoint, StorageType.UrlStorage) + ?: return handleFailure(task, UploadErrorCode.NO_SERVER_URL) + val accessToken = db.storeDao().get(StoreKey.accessToken, StorageType.StringStorage) + ?: return handleFailure(task, UploadErrorCode.NO_ACCESS_TOKEN) + val deviceId = db.storeDao().get(StoreKey.deviceId, StorageType.StringStorage) + ?: return handleFailure(task, UploadErrorCode.NO_DEVICE_ID) + + // Check network constraints + val useWifiOnly = when (task.type) { + AssetType.IMAGE -> db.storeDao().get(StoreKey.useWifiForUploadPhotos, StorageType.BoolStorage) ?: false + AssetType.VIDEO -> db.storeDao().get(StoreKey.useWifiForUploadVideos, StorageType.BoolStorage) ?: false + else -> false + } + + if (useWifiOnly && !NetworkMonitor.isWifiConnected(applicationContext)) { + // Wait for WiFi + return true + } + + // Update task status + db.uploadTaskDao().updateStatus(task.taskId, TaskStatus.UPLOAD_QUEUED) + + // Perform upload + uploadFile(task, file, serverUrl, accessToken, deviceId) + + // Mark as complete + db.uploadTaskDao().updateStatus(task.taskId, TaskStatus.UPLOAD_COMPLETE) + + true + } catch (e: Exception) { + android.util.Log.e(TAG, "Upload task ${task.taskId} failed", e) + handleFailure(task, UploadErrorCode.UNKNOWN) + } + } + + private suspend fun uploadFile( + task: LocalAssetTaskData, + file: File, + serverUrl: URL, + accessToken: String, + deviceId: String + ) { + val requestBody = createMultipartBody(task, file, deviceId) + + val request = Request.Builder() + .url("${serverUrl}/api/upload") + .post(requestBody) + .header("x-immich-user-token", accessToken) + .tag(task.taskId) + .build() + + client.newCall(request).execute().use { response -> + if (!response.isSuccessful) { + throw IOException("Upload failed: ${response.code}") + } + } + } + + private fun createMultipartBody( + task: LocalAssetTaskData, + file: File, + deviceId: String + ): RequestBody { + val boundary = "Boundary-${UUID.randomUUID()}" + + return object : RequestBody() { + override fun contentType() = "multipart/form-data; boundary=$boundary".toMediaType() + + override fun writeTo(sink: okio.BufferedSink) { + // Write form fields + writeFormField(sink, boundary, "deviceAssetId", task.localId) + writeFormField(sink, boundary, "deviceId", deviceId) + writeFormField(sink, boundary, "fileCreatedAt", (task.createdAt.time / 1000).toString()) + writeFormField(sink, boundary, "fileModifiedAt", (task.updatedAt.time / 1000).toString()) + writeFormField(sink, boundary, "fileName", task.fileName) + writeFormField(sink, boundary, "isFavorite", task.isFavorite.toString()) + + // Write file + sink.writeUtf8("--$boundary\r\n") + sink.writeUtf8("Content-Disposition: form-data; name=\"assetData\"; filename=\"asset\"\r\n") + sink.writeUtf8("Content-Type: application/octet-stream\r\n\r\n") + + file.inputStream().use { input -> + val buffer = ByteArray(8192) + var bytesRead: Int + while (input.read(buffer).also { bytesRead = it } != -1) { + sink.write(buffer, 0, bytesRead) + + // Report progress (simplified - could be enhanced with listeners) + setProgressAsync( + workDataOf( + PROGRESS_TASK_ID to task.taskId, + PROGRESS_BYTES to file.length() + ) + ) + } + } + + sink.writeUtf8("\r\n--$boundary--\r\n") + } + + private fun writeFormField(sink: okio.BufferedSink, boundary: String, name: String, value: String) { + sink.writeUtf8("--$boundary\r\n") + sink.writeUtf8("Content-Disposition: form-data; name=\"$name\"\r\n\r\n") + sink.writeUtf8(value) + sink.writeUtf8("\r\n") + } + } + } + + private suspend fun handleFailure(task: LocalAssetTaskData, code: UploadErrorCode): Boolean { + val newAttempts = task.attempts + 1 + val status = if (newAttempts >= TaskConfig.MAX_ATTEMPTS) { + TaskStatus.UPLOAD_FAILED + } else { + TaskStatus.UPLOAD_PENDING + } + + val retryAfter = if (status == TaskStatus.UPLOAD_PENDING) { + Date(System.currentTimeMillis() + (Math.pow(3.0, newAttempts.toDouble()) * 1000).toLong()) + } else null + + db.uploadTaskDao().updateTaskAfterFailure( + task.taskId, + newAttempts, + code, + status, + retryAfter + ) + + return false + } + + private fun enqueueNextBatch() { + val constraints = Constraints.Builder() + .setRequiredNetworkType(NetworkType.CONNECTED) + .build() + + val nextWorkRequest = OneTimeWorkRequestBuilder() + .setConstraints(constraints) + .addTag(UPLOAD_WORK_TAG) + .setInitialDelay(1, TimeUnit.SECONDS) + .build() + + WorkManager.getInstance(applicationContext) + .enqueueUniqueWork( + UPLOAD_WORK_NAME, + ExistingWorkPolicy.KEEP, + nextWorkRequest + ) + } + + private fun createOkHttpClient(): OkHttpClient { + return OkHttpClient.Builder() + .connectTimeout(30, TimeUnit.SECONDS) + .readTimeout(300, TimeUnit.SECONDS) + .writeTimeout(300, TimeUnit.SECONDS) + .build() + } + + companion object { + private const val TAG = "UploadWorker" + private const val UPLOAD_WORK_TAG = "immich_upload" + private const val UPLOAD_WORK_NAME = "immich_upload_unique" + const val PROGRESS_TASK_ID = "progress_task_id" + const val PROGRESS_BYTES = "progress_bytes" + } +} diff --git a/mobile/android/build.gradle b/mobile/android/build.gradle index 719c946bd6..2215eaad6f 100644 --- a/mobile/android/build.gradle +++ b/mobile/android/build.gradle @@ -36,4 +36,3 @@ tasks.register("clean", Delete) { tasks.named('wrapper') { distributionType = Wrapper.DistributionType.ALL } - diff --git a/mobile/drift_schemas/main/drift_schema_v14.json b/mobile/drift_schemas/main/drift_schema_v14.json new file mode 100644 index 0000000000..b8f388b372 --- /dev/null +++ b/mobile/drift_schemas/main/drift_schema_v14.json @@ -0,0 +1 @@ +{"_meta":{"description":"This file contains a serialized version of schema entities for drift.","version":"1.2.0"},"options":{"store_date_time_values_as_text":true},"entities":[{"id":0,"references":[],"type":"table","data":{"name":"upload_tasks","was_declared_in_moor":true,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":true,"customConstraints":"PRIMARY KEY AUTOINCREMENT","default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"attempts","getter_name":"attempts","moor_type":"int","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"created_at","getter_name":"createdAt","moor_type":"int","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"file_path","getter_name":"filePath","moor_type":"string","nullable":true,"customConstraints":"","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"is_live_photo","getter_name":"isLivePhoto","moor_type":"int","nullable":true,"customConstraints":"","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"last_error","getter_name":"lastError","moor_type":"int","nullable":true,"customConstraints":"","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"live_photo_video_id","getter_name":"livePhotoVideoId","moor_type":"string","nullable":true,"customConstraints":"","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"local_id","getter_name":"localId","moor_type":"string","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"method","getter_name":"method","moor_type":"int","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"priority","getter_name":"priority","moor_type":"double","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"retry_after","getter_name":"retryAfter","moor_type":"int","nullable":true,"customConstraints":"","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"status","getter_name":"status","moor_type":"int","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":1,"references":[],"type":"table","data":{"name":"upload_task_stats","was_declared_in_moor":true,"columns":[{"name":"pending_downloads","getter_name":"pendingDownloads","moor_type":"int","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"pending_uploads","getter_name":"pendingUploads","moor_type":"int","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"queued_downloads","getter_name":"queuedDownloads","moor_type":"int","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"queued_uploads","getter_name":"queuedUploads","moor_type":"int","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"failed_downloads","getter_name":"failedDownloads","moor_type":"int","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"failed_uploads","getter_name":"failedUploads","moor_type":"int","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"completed_uploads","getter_name":"completedUploads","moor_type":"int","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"skipped_uploads","getter_name":"skippedUploads","moor_type":"int","nullable":false,"customConstraints":"NOT NULL","default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":2,"references":[0,1],"type":"trigger","data":{"on":0,"references_in_body":[0,1],"name":"update_stats_insert","sql":"CREATE TRIGGER update_stats_insert\n BEFORE INSERT\n ON upload_tasks\nBEGIN\n UPDATE upload_task_stats\n SET pending_downloads = pending_downloads + (NEW.status = 0),\n queued_downloads = queued_downloads + (NEW.status = 1),\n failed_downloads = failed_downloads + (NEW.status = 2),\n pending_uploads = pending_uploads + (NEW.status = 3),\n queued_uploads = queued_uploads + (NEW.status = 4),\n failed_uploads = failed_uploads + (NEW.status = 5),\n completed_uploads = completed_uploads + (NEW.status = 6),\n skipped_uploads = skipped_uploads + (NEW.status = 7);\nEND;"}},{"id":3,"references":[0,1],"type":"trigger","data":{"on":0,"references_in_body":[0,1],"name":"update_stats_update","sql":"CREATE TRIGGER update_stats_update\n BEFORE UPDATE OF status\n ON upload_tasks\n WHEN OLD.status != NEW.status\nBEGIN\n UPDATE upload_task_stats\n SET pending_downloads = pending_downloads - (OLD.status = 0) + (NEW.status = 0),\n queued_downloads = queued_downloads - (OLD.status = 1) + (NEW.status = 1),\n failed_downloads = failed_downloads - (OLD.status = 2) + (NEW.status = 2),\n pending_uploads = pending_uploads - (OLD.status = 3) + (NEW.status = 3),\n queued_uploads = queued_uploads - (OLD.status = 4) + (NEW.status = 4),\n failed_uploads = failed_uploads - (OLD.status = 5) + (NEW.status = 5),\n completed_uploads = completed_uploads - (OLD.status = 6) + (NEW.status = 6),\n skipped_uploads = skipped_uploads - (OLD.status = 7) + (NEW.status = 7);\nEND;"}},{"id":4,"references":[0,1],"type":"trigger","data":{"on":0,"references_in_body":[0,1],"name":"update_stats_delete","sql":"CREATE TRIGGER update_stats_delete\n BEFORE DELETE\n ON upload_tasks\nBEGIN\n UPDATE upload_task_stats\n SET pending_downloads = pending_downloads - (OLD.status = 0),\n queued_downloads = queued_downloads - (OLD.status = 1),\n failed_downloads = failed_downloads - (OLD.status = 2),\n pending_uploads = pending_uploads - (OLD.status = 3),\n queued_uploads = queued_uploads - (OLD.status = 4),\n failed_uploads = failed_uploads - (OLD.status = 5),\n completed_uploads = completed_uploads - (OLD.status = 6),\n skipped_uploads = skipped_uploads - (OLD.status = 7);\nEND;"}},{"id":5,"references":[0],"type":"index","data":{"on":0,"name":"idx_upload_tasks_local_id","sql":"CREATE UNIQUE INDEX idx_upload_tasks_local_id ON upload_tasks (local_id, live_photo_video_id);","unique":true,"columns":[]}},{"id":6,"references":[0],"type":"index","data":{"on":0,"name":"idx_upload_tasks_asset_data","sql":"CREATE INDEX idx_upload_tasks_asset_data ON upload_tasks (status, priority DESC, created_at);","unique":false,"columns":[]}},{"id":7,"references":[1],"type":"special-query","data":{"scenario":"create","sql":"INSERT INTO upload_task_stats VALUES (0, 0, 0, 0, 0, 0, 0, 0)"}},{"id":8,"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":9,"references":[8],"type":"table","data":{"name":"remote_asset_entity","was_declared_in_moor":false,"columns":[{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"type","getter_name":"type","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(AssetType.values)","dart_type_name":"AssetType"}},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"updated_at","getter_name":"updatedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"width","getter_name":"width","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"height","getter_name":"height","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"duration_in_seconds","getter_name":"durationInSeconds","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"checksum","getter_name":"checksum","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"is_favorite","getter_name":"isFavorite","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_favorite\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_favorite\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"owner_id","getter_name":"ownerId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"local_date_time","getter_name":"localDateTime","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"thumb_hash","getter_name":"thumbHash","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"deleted_at","getter_name":"deletedAt","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"live_photo_video_id","getter_name":"livePhotoVideoId","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"visibility","getter_name":"visibility","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(AssetVisibility.values)","dart_type_name":"AssetVisibility"}},{"name":"stack_id","getter_name":"stackId","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"library_id","getter_name":"libraryId","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":10,"references":[8],"type":"table","data":{"name":"stack_entity","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"updated_at","getter_name":"updatedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"owner_id","getter_name":"ownerId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"primary_asset_id","getter_name":"primaryAssetId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":11,"references":[],"type":"table","data":{"name":"local_asset_entity","was_declared_in_moor":false,"columns":[{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"type","getter_name":"type","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(AssetType.values)","dart_type_name":"AssetType"}},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"updated_at","getter_name":"updatedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"width","getter_name":"width","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"height","getter_name":"height","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"duration_in_seconds","getter_name":"durationInSeconds","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"checksum","getter_name":"checksum","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"is_favorite","getter_name":"isFavorite","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_favorite\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_favorite\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"orientation","getter_name":"orientation","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":12,"references":[8,9],"type":"table","data":{"name":"remote_album_entity","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"description","getter_name":"description","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('\\'\\'')","default_client_dart":null,"dsl_features":[]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"updated_at","getter_name":"updatedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"owner_id","getter_name":"ownerId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"thumbnail_asset_id","getter_name":"thumbnailAssetId","moor_type":"string","nullable":true,"customConstraints":null,"defaultConstraints":"REFERENCES remote_asset_entity (id) ON DELETE SET NULL","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES remote_asset_entity (id) ON DELETE SET NULL"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"is_activity_enabled","getter_name":"isActivityEnabled","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_activity_enabled\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_activity_enabled\" IN (0, 1))"},"default_dart":"const CustomExpression('1')","default_client_dart":null,"dsl_features":[]},{"name":"order","getter_name":"order","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(AlbumAssetOrder.values)","dart_type_name":"AlbumAssetOrder"}}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":13,"references":[12],"type":"table","data":{"name":"local_album_entity","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"updated_at","getter_name":"updatedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"backup_selection","getter_name":"backupSelection","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(BackupSelection.values)","dart_type_name":"BackupSelection"}},{"name":"is_ios_shared_album","getter_name":"isIosSharedAlbum","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_ios_shared_album\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_ios_shared_album\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"linked_remote_album_id","getter_name":"linkedRemoteAlbumId","moor_type":"string","nullable":true,"customConstraints":null,"defaultConstraints":"REFERENCES remote_album_entity (id) ON DELETE SET NULL","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES remote_album_entity (id) ON DELETE SET NULL"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"marker","getter_name":"marker_","moor_type":"bool","nullable":true,"customConstraints":null,"defaultConstraints":"CHECK (\"marker\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"marker\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":14,"references":[11,13],"type":"table","data":{"name":"local_album_asset_entity","was_declared_in_moor":false,"columns":[{"name":"asset_id","getter_name":"assetId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES local_asset_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES local_asset_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"album_id","getter_name":"albumId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES local_album_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES local_album_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"marker","getter_name":"marker_","moor_type":"bool","nullable":true,"customConstraints":null,"defaultConstraints":"CHECK (\"marker\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"marker\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["asset_id","album_id"]}},{"id":15,"references":[11],"type":"index","data":{"on":11,"name":"idx_local_asset_checksum","sql":"CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)","unique":false,"columns":[]}},{"id":16,"references":[9],"type":"index","data":{"on":9,"name":"idx_remote_asset_owner_checksum","sql":"CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)","unique":false,"columns":[]}},{"id":17,"references":[9],"type":"index","data":{"on":9,"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":18,"references":[9],"type":"index","data":{"on":9,"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":19,"references":[9],"type":"index","data":{"on":9,"name":"idx_remote_asset_checksum","sql":"CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)","unique":false,"columns":[]}},{"id":20,"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":21,"references":[8],"type":"table","data":{"name":"user_metadata_entity","was_declared_in_moor":false,"columns":[{"name":"user_id","getter_name":"userId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"key","getter_name":"key","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(UserMetadataKey.values)","dart_type_name":"UserMetadataKey"}},{"name":"value","getter_name":"value","moor_type":"blob","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"userMetadataConverter","dart_type_name":"Map"}}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["user_id","key"]}},{"id":22,"references":[8],"type":"table","data":{"name":"partner_entity","was_declared_in_moor":false,"columns":[{"name":"shared_by_id","getter_name":"sharedById","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"shared_with_id","getter_name":"sharedWithId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"in_timeline","getter_name":"inTimeline","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"in_timeline\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"in_timeline\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["shared_by_id","shared_with_id"]}},{"id":23,"references":[9],"type":"table","data":{"name":"remote_exif_entity","was_declared_in_moor":false,"columns":[{"name":"asset_id","getter_name":"assetId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES remote_asset_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES remote_asset_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"city","getter_name":"city","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"state","getter_name":"state","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"country","getter_name":"country","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"date_time_original","getter_name":"dateTimeOriginal","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"description","getter_name":"description","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"height","getter_name":"height","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"width","getter_name":"width","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"exposure_time","getter_name":"exposureTime","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"f_number","getter_name":"fNumber","moor_type":"double","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"file_size","getter_name":"fileSize","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"focal_length","getter_name":"focalLength","moor_type":"double","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"latitude","getter_name":"latitude","moor_type":"double","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"longitude","getter_name":"longitude","moor_type":"double","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"iso","getter_name":"iso","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"make","getter_name":"make","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"model","getter_name":"model","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"lens","getter_name":"lens","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"orientation","getter_name":"orientation","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"time_zone","getter_name":"timeZone","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"rating","getter_name":"rating","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"projection_type","getter_name":"projectionType","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["asset_id"]}},{"id":24,"references":[9,12],"type":"table","data":{"name":"remote_album_asset_entity","was_declared_in_moor":false,"columns":[{"name":"asset_id","getter_name":"assetId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES remote_asset_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES remote_asset_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"album_id","getter_name":"albumId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES remote_album_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES remote_album_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["asset_id","album_id"]}},{"id":25,"references":[12,8],"type":"table","data":{"name":"remote_album_user_entity","was_declared_in_moor":false,"columns":[{"name":"album_id","getter_name":"albumId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES remote_album_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES remote_album_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"user_id","getter_name":"userId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"role","getter_name":"role","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(AlbumUserRole.values)","dart_type_name":"AlbumUserRole"}}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["album_id","user_id"]}},{"id":26,"references":[8],"type":"table","data":{"name":"memory_entity","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"updated_at","getter_name":"updatedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"deleted_at","getter_name":"deletedAt","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"owner_id","getter_name":"ownerId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"type","getter_name":"type","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(MemoryTypeEnum.values)","dart_type_name":"MemoryTypeEnum"}},{"name":"data","getter_name":"data","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"is_saved","getter_name":"isSaved","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_saved\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_saved\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"memory_at","getter_name":"memoryAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"seen_at","getter_name":"seenAt","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"show_at","getter_name":"showAt","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"hide_at","getter_name":"hideAt","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":27,"references":[9,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":["unknown"]},{"name":"memory_id","getter_name":"memoryId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES memory_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES memory_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["asset_id","memory_id"]}},{"id":28,"references":[8],"type":"table","data":{"name":"person_entity","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"updated_at","getter_name":"updatedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"owner_id","getter_name":"ownerId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"face_asset_id","getter_name":"faceAssetId","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"is_favorite","getter_name":"isFavorite","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_favorite\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_favorite\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"is_hidden","getter_name":"isHidden","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_hidden\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_hidden\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"color","getter_name":"color","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"birth_date","getter_name":"birthDate","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":29,"references":[9,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":["unknown"]},{"name":"person_id","getter_name":"personId","moor_type":"string","nullable":true,"customConstraints":null,"defaultConstraints":"REFERENCES person_entity (id) ON DELETE SET NULL","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES person_entity (id) ON DELETE SET NULL"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"image_width","getter_name":"imageWidth","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"image_height","getter_name":"imageHeight","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"bounding_box_x1","getter_name":"boundingBoxX1","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"bounding_box_y1","getter_name":"boundingBoxY1","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"bounding_box_x2","getter_name":"boundingBoxX2","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"bounding_box_y2","getter_name":"boundingBoxY2","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"source_type","getter_name":"sourceType","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":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":"trashed_local_asset_entity","was_declared_in_moor":false,"columns":[{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"type","getter_name":"type","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(AssetType.values)","dart_type_name":"AssetType"}},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"updated_at","getter_name":"updatedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"width","getter_name":"width","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"height","getter_name":"height","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"duration_in_seconds","getter_name":"durationInSeconds","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"album_id","getter_name":"albumId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"checksum","getter_name":"checksum","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"is_favorite","getter_name":"isFavorite","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_favorite\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_favorite\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"orientation","getter_name":"orientation","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id","album_id"]}},{"id":32,"references":[23],"type":"index","data":{"on":23,"name":"idx_lat_lng","sql":"CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)","unique":false,"columns":[]}},{"id":33,"references":[31],"type":"index","data":{"on":31,"name":"idx_trashed_local_asset_checksum","sql":"CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)","unique":false,"columns":[]}},{"id":34,"references":[31],"type":"index","data":{"on":31,"name":"idx_trashed_local_asset_album","sql":"CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)","unique":false,"columns":[]}}]} \ No newline at end of file diff --git a/mobile/ios/Podfile b/mobile/ios/Podfile index ca0166a382..cf8721799e 100644 --- a/mobile/ios/Podfile +++ b/mobile/ios/Podfile @@ -32,7 +32,6 @@ target 'Runner' do use_modular_headers! flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) - # share_handler addition start target 'ShareExtension' do inherit! :search_paths diff --git a/mobile/ios/Podfile.lock b/mobile/ios/Podfile.lock index d869aa9c08..c6fb08e96d 100644 --- a/mobile/ios/Podfile.lock +++ b/mobile/ios/Podfile.lock @@ -88,9 +88,9 @@ PODS: - Flutter - FlutterMacOS - SAMKeychain (1.5.3) - - SDWebImage (5.21.0): - - SDWebImage/Core (= 5.21.0) - - SDWebImage/Core (5.21.0) + - SDWebImage (5.21.3): + - SDWebImage/Core (= 5.21.3) + - SDWebImage/Core (5.21.3) - share_handler_ios (0.0.14): - Flutter - share_handler_ios/share_handler_ios_models (= 0.0.14) @@ -107,16 +107,16 @@ PODS: - sqflite_darwin (0.0.4): - Flutter - FlutterMacOS - - sqlite3 (3.49.1): - - sqlite3/common (= 3.49.1) - - sqlite3/common (3.49.1) - - sqlite3/dbstatvtab (3.49.1): + - sqlite3 (3.49.2): + - sqlite3/common (= 3.49.2) + - sqlite3/common (3.49.2) + - sqlite3/dbstatvtab (3.49.2): - sqlite3/common - - sqlite3/fts5 (3.49.1): + - sqlite3/fts5 (3.49.2): - sqlite3/common - - sqlite3/perf-threadsafe (3.49.1): + - sqlite3/perf-threadsafe (3.49.2): - sqlite3/common - - sqlite3/rtree (3.49.1): + - sqlite3/rtree (3.49.2): - sqlite3/common - sqlite3_flutter_libs (0.0.1): - Flutter @@ -275,18 +275,18 @@ SPEC CHECKSUMS: permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d photo_manager: 1d80ae07a89a67dfbcae95953a1e5a24af7c3e62 SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c - SDWebImage: f84b0feeb08d2d11e6a9b843cb06d75ebf5b8868 + SDWebImage: 16309af6d214ba3f77a7c6f6fdda888cb313a50a share_handler_ios: e2244e990f826b2c8eaa291ac3831569438ba0fb share_handler_ios_models: fc638c9b4330dc7f082586c92aee9dfa0b87b871 share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 - sqlite3: fc1400008a9b3525f5914ed715a5d1af0b8f4983 + sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1 sqlite3_flutter_libs: f8fc13346870e73fe35ebf6dbb997fbcd156b241 SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 url_launcher_ios: 694010445543906933d732453a59da0a173ae33d wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556 -PODFILE CHECKSUM: 7ce312f2beab01395db96f6969d90a447279cf45 +PODFILE CHECKSUM: 95621706d175fee669455a5946a602e2a775019c COCOAPODS: 1.16.2 diff --git a/mobile/ios/Runner.xcodeproj/project.pbxproj b/mobile/ios/Runner.xcodeproj/project.pbxproj index 599e7990f4..876732f8fc 100644 --- a/mobile/ios/Runner.xcodeproj/project.pbxproj +++ b/mobile/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 77; objects = { /* Begin PBXBuildFile section */ @@ -77,6 +77,16 @@ name = "Embed Foundation Extensions"; runOnlyForDeploymentPostprocessing = 0; }; + FE4C52462EAFE736009EEB47 /* Embed ExtensionKit Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "$(EXTENSIONS_FOLDER_PATH)"; + dstSubfolderSpec = 16; + files = ( + ); + name = "Embed ExtensionKit Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ @@ -136,15 +146,11 @@ /* Begin PBXFileSystemSynchronizedRootGroup section */ B231F52D2E93A44A00BC45D1 /* Core */ = { isa = PBXFileSystemSynchronizedRootGroup; - exceptions = ( - ); path = Core; sourceTree = ""; }; B2CF7F8C2DDE4EBB00744BF6 /* Sync */ = { isa = PBXFileSystemSynchronizedRootGroup; - exceptions = ( - ); path = Sync; sourceTree = ""; }; @@ -156,10 +162,18 @@ path = WidgetExtension; sourceTree = ""; }; + FE14355D2EC446E90009D5AC /* Upload */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = Upload; + sourceTree = ""; + }; + FEB3BA112EBD52860081A5EB /* Schemas */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = Schemas; + sourceTree = ""; + }; FEE084F22EC172080045228E /* Schemas */ = { isa = PBXFileSystemSynchronizedRootGroup; - exceptions = ( - ); path = Schemas; sourceTree = ""; }; @@ -267,7 +281,9 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( + FE14355D2EC446E90009D5AC /* Upload */, FEE084F22EC172080045228E /* Schemas */, + FEB3BA112EBD52860081A5EB /* Schemas */, B231F52D2E93A44A00BC45D1 /* Core */, B25D37792E72CA15008B6CA7 /* Connectivity */, B21E34A62E5AF9760031FDB9 /* Background */, @@ -345,6 +361,7 @@ 3B06AD1E1E4923F5004D2608 /* Thin Binary */, D218A34AEE62BC1EF119F5B0 /* [CP] Embed Pods Frameworks */, 6724EEB7D74949FA08581154 /* [CP] Copy Pods Resources */, + FE4C52462EAFE736009EEB47 /* Embed ExtensionKit Extensions */, ); buildRules = ( ); @@ -355,6 +372,8 @@ fileSystemSynchronizedGroups = ( B231F52D2E93A44A00BC45D1 /* Core */, B2CF7F8C2DDE4EBB00744BF6 /* Sync */, + FE14355D2EC446E90009D5AC /* Upload */, + FEB3BA112EBD52860081A5EB /* Schemas */, FEE084F22EC172080045228E /* Schemas */, ); name = Runner; @@ -407,7 +426,7 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; - LastSwiftUpdateCheck = 1640; + LastSwiftUpdateCheck = 1620; LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { @@ -549,10 +568,14 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", ); + inputPaths = ( + ); name = "[CP] Copy Pods Resources"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", ); + outputPaths = ( + ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; @@ -581,10 +604,14 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); + inputPaths = ( + ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); + outputPaths = ( + ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; @@ -735,7 +762,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 233; CUSTOM_GROUP_ID = group.app.immich.share; - DEVELOPMENT_TEAM = 2F67MQ8R79; + DEVELOPMENT_TEAM = 33MF3D8ZGA; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -744,7 +771,8 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.121.0; - PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich.profile; + OTHER_SWIFT_FLAGS = "$(inherited) -D COCOAPODS -D DEBUG -Xllvm -sil-disable-pass=performance-linker"; + PRODUCT_BUNDLE_IDENTIFIER = app.mertalev.immich.profile; PRODUCT_NAME = "Immich-Profile"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -879,7 +907,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 233; CUSTOM_GROUP_ID = group.app.immich.share; - DEVELOPMENT_TEAM = 2F67MQ8R79; + DEVELOPMENT_TEAM = 33MF3D8ZGA; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -888,7 +916,8 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.121.0; - PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich.vdebug; + OTHER_SWIFT_FLAGS = "$(inherited) -D COCOAPODS -D DEBUG -Xllvm -sil-disable-pass=performance-linker"; + PRODUCT_BUNDLE_IDENTIFIER = app.mertalev.immich.vdebug; PRODUCT_NAME = "Immich-Debug"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -909,7 +938,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 233; CUSTOM_GROUP_ID = group.app.immich.share; - DEVELOPMENT_TEAM = 2F67MQ8R79; + DEVELOPMENT_TEAM = 33MF3D8ZGA; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -918,7 +947,8 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.121.0; - PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich; + OTHER_SWIFT_FLAGS = "$(inherited) -D COCOAPODS -Xllvm -sil-disable-pass=performance-linker"; + PRODUCT_BUNDLE_IDENTIFIER = app.mertalev.immich; PRODUCT_NAME = Immich; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -942,7 +972,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 233; - DEVELOPMENT_TEAM = 2F67MQ8R79; + DEVELOPMENT_TEAM = 33MF3D8ZGA; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GENERATE_INFOPLIST_FILE = YES; @@ -959,7 +989,7 @@ MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich.vdebug.Widget; + PRODUCT_BUNDLE_IDENTIFIER = app.mertalev.immich.vdebug.Widget; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; @@ -985,7 +1015,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 233; - DEVELOPMENT_TEAM = 2F67MQ8R79; + DEVELOPMENT_TEAM = 33MF3D8ZGA; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GENERATE_INFOPLIST_FILE = YES; @@ -1001,7 +1031,7 @@ LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 1.0; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich.Widget; + PRODUCT_BUNDLE_IDENTIFIER = app.mertalev.immich.Widget; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; @@ -1025,7 +1055,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 233; - DEVELOPMENT_TEAM = 2F67MQ8R79; + DEVELOPMENT_TEAM = 33MF3D8ZGA; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GENERATE_INFOPLIST_FILE = YES; @@ -1041,7 +1071,7 @@ LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 1.0; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich.profile.Widget; + PRODUCT_BUNDLE_IDENTIFIER = app.mertalev.immich.profile.Widget; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; @@ -1065,7 +1095,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 233; CUSTOM_GROUP_ID = group.app.immich.share; - DEVELOPMENT_TEAM = 2F67MQ8R79; + DEVELOPMENT_TEAM = 33MF3D8ZGA; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GENERATE_INFOPLIST_FILE = YES; @@ -1082,7 +1112,7 @@ MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich.vdebug.ShareExtension; + PRODUCT_BUNDLE_IDENTIFIER = app.mertalev.immich.vdebug.ShareExtension; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; @@ -1109,7 +1139,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 233; CUSTOM_GROUP_ID = group.app.immich.share; - DEVELOPMENT_TEAM = 2F67MQ8R79; + DEVELOPMENT_TEAM = 33MF3D8ZGA; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GENERATE_INFOPLIST_FILE = YES; @@ -1125,7 +1155,7 @@ LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 1.0; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich.ShareExtension; + PRODUCT_BUNDLE_IDENTIFIER = app.mertalev.immich.ShareExtension; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; @@ -1150,7 +1180,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 233; CUSTOM_GROUP_ID = group.app.immich.share; - DEVELOPMENT_TEAM = 2F67MQ8R79; + DEVELOPMENT_TEAM = 33MF3D8ZGA; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GENERATE_INFOPLIST_FILE = YES; @@ -1166,7 +1196,7 @@ LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 1.0; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich.profile.ShareExtension; + PRODUCT_BUNDLE_IDENTIFIER = app.mertakev.immich.profile.ShareExtension; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; diff --git a/mobile/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved b/mobile/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved index ff8a53ff4b..2a7bf18f95 100644 --- a/mobile/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/mobile/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -28,15 +28,6 @@ "version" : "1.3.0" } }, - { - "identity" : "swift-case-paths", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/swift-case-paths", - "state" : { - "revision" : "6989976265be3f8d2b5802c722f9ba168e227c71", - "version" : "1.7.2" - } - }, { "identity" : "swift-clocks", "kind" : "remoteSourceControl", @@ -132,8 +123,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-structured-queries", "state" : { - "revision" : "1447ea20550f6f02c4b48cc80931c3ed40a9c756", - "version" : "0.25.0" + "revision" : "9c84335373bae5f5c9f7b5f0adf3ae10f2cab5b9", + "version" : "0.25.2" } }, { @@ -145,15 +136,6 @@ "version" : "602.0.0" } }, - { - "identity" : "swift-tagged", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/swift-tagged", - "state" : { - "revision" : "3907a9438f5b57d317001dc99f3f11b46882272b", - "version" : "0.10.0" - } - }, { "identity" : "xctest-dynamic-overlay", "kind" : "remoteSourceControl", diff --git a/mobile/ios/Runner/AppDelegate.swift b/mobile/ios/Runner/AppDelegate.swift index 4e4cb2ed13..fea19ac1c7 100644 --- a/mobile/ios/Runner/AppDelegate.swift +++ b/mobile/ios/Runner/AppDelegate.swift @@ -1,11 +1,11 @@ import BackgroundTasks import Flutter +import UIKit import network_info_plus import path_provider_foundation import permission_handler_apple import photo_manager import shared_preferences_foundation -import UIKit @main @objc class AppDelegate: FlutterAppDelegate { @@ -15,7 +15,7 @@ import UIKit ) -> Bool { // Required for flutter_local_notification if #available(iOS 10.0, *) { - UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate + UNUserNotificationCenter.current().delegate = self } GeneratedPluginRegistrant.register(with: self) @@ -36,7 +36,9 @@ import UIKit } if !registry.hasPlugin("org.cocoapods.shared-preferences-foundation") { - SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "org.cocoapods.shared-preferences-foundation")!) + SharedPreferencesPlugin.register( + with: registry.registrar(forPlugin: "org.cocoapods.shared-preferences-foundation")! + ) } if !registry.hasPlugin("org.cocoapods.permission-handler-apple") { @@ -50,13 +52,22 @@ import UIKit return super.application(application, didFinishLaunchingWithOptions: launchOptions) } - + public static func registerPlugins(with engine: FlutterEngine) { NativeSyncApiImpl.register(with: engine.registrar(forPlugin: NativeSyncApiImpl.name)!) ThumbnailApiSetup.setUp(binaryMessenger: engine.binaryMessenger, api: ThumbnailApiImpl()) BackgroundWorkerFgHostApiSetup.setUp(binaryMessenger: engine.binaryMessenger, api: BackgroundWorkerApiImpl()) + + let statusListener = StatusEventListener() + StreamStatusStreamHandler.register(with: engine.binaryMessenger, streamHandler: statusListener) + let progressListener = ProgressEventListener() + StreamProgressStreamHandler.register(with: engine.binaryMessenger, streamHandler: progressListener) + UploadApiSetup.setUp( + binaryMessenger: engine.binaryMessenger, + api: UploadApiImpl(statusListener: statusListener, progressListener: progressListener) + ) } - + public static func cancelPlugins(with engine: FlutterEngine) { (engine.valuePublished(byPlugin: NativeSyncApiImpl.name) as? NativeSyncApiImpl)?.detachFromEngine() } diff --git a/mobile/ios/Runner/BackgroundSync/BackgroundServicePlugin.swift b/mobile/ios/Runner/BackgroundSync/BackgroundServicePlugin.swift index cac9faab01..e1143dd7d4 100644 --- a/mobile/ios/Runner/BackgroundSync/BackgroundServicePlugin.swift +++ b/mobile/ios/Runner/BackgroundSync/BackgroundServicePlugin.swift @@ -350,16 +350,12 @@ class BackgroundServicePlugin: NSObject, FlutterPlugin { // If we have required Wi-Fi, we can check the isExpensive property let requireWifi = defaults.value(forKey: "require_wifi") as? Bool ?? false - if (requireWifi) { - let wifiMonitor = NWPathMonitor(requiredInterfaceType: .wifi) - let isExpensive = wifiMonitor.currentPath.isExpensive - if (isExpensive) { - // The network is expensive and we have required Wi-Fi - // Therefore, we will simply complete the task without - // running it - task.setTaskCompleted(success: true) - return - } + + // The network is expensive and we have required Wi-Fi + // Therefore, we will simply complete the task without + // running it + if (requireWifi && NetworkMonitor.shared.isExpensive) { + return task.setTaskCompleted(success: true) } // Schedule the next sync task so we can run this again later diff --git a/mobile/ios/Runner/Core/ImmichPlugin.swift b/mobile/ios/Runner/Core/ImmichPlugin.swift index db10b7a75d..22724fb001 100644 --- a/mobile/ios/Runner/Core/ImmichPlugin.swift +++ b/mobile/ios/Runner/Core/ImmichPlugin.swift @@ -1,17 +1,24 @@ class ImmichPlugin: NSObject { var detached: Bool - + override init() { detached = false super.init() } - + func detachFromEngine() { self.detached = true } - + func completeWhenActive(for completion: @escaping (T) -> Void, with value: T) { guard !self.detached else { return } completion(value) } } + +@inline(__always) +func dPrint(_ item: Any) { + #if DEBUG + print(item) + #endif +} diff --git a/mobile/ios/Runner/Runner.entitlements b/mobile/ios/Runner/Runner.entitlements index e5862cb213..cfbada638d 100644 --- a/mobile/ios/Runner/Runner.entitlements +++ b/mobile/ios/Runner/Runner.entitlements @@ -9,8 +9,6 @@ com.apple.developer.networking.wifi-info com.apple.security.application-groups - - group.app.immich.share - + diff --git a/mobile/ios/Runner/RunnerProfile.entitlements b/mobile/ios/Runner/RunnerProfile.entitlements index 6a5c086baf..6a397cfd54 100644 --- a/mobile/ios/Runner/RunnerProfile.entitlements +++ b/mobile/ios/Runner/RunnerProfile.entitlements @@ -11,8 +11,6 @@ com.apple.developer.networking.wifi-info com.apple.security.application-groups - - group.app.immich.share - + diff --git a/mobile/ios/Runner/Schemas/Constants.swift b/mobile/ios/Runner/Schemas/Constants.swift index a4b0f701a1..07c47dbe12 100644 --- a/mobile/ios/Runner/Schemas/Constants.swift +++ b/mobile/ios/Runner/Schemas/Constants.swift @@ -1,12 +1,22 @@ import SQLiteData -struct Endpoint: Codable { - let url: URL - let status: Status +extension Notification.Name { + static let networkDidConnect = Notification.Name("networkDidConnect") +} - enum Status: String, Codable { - case loading, valid, error, unknown - } +enum TaskConfig { + static let maxActiveDownloads = 3 + static let maxPendingDownloads = 50 + static let maxPendingUploads = 50 + static let maxRetries = 10 + static let sessionId = "app.mertalev.immich.upload" + static let downloadCheckIntervalNs: UInt64 = 30_000_000_000 // 30 seconds + static let downloadTimeoutS = TimeInterval(60) + static let transferSpeedAlpha = 0.4 + static let originalsDir = FileManager.default.temporaryDirectory.appendingPathComponent( + "originals", + isDirectory: true + ) } enum StoreKey: Int, CaseIterable, QueryBindable { @@ -47,8 +57,6 @@ enum StoreKey: Int, CaseIterable, QueryBindable { static let deviceId = Typed(rawValue: ._deviceId) case _accessToken = 11 static let accessToken = Typed(rawValue: ._accessToken) - case _serverEndpoint = 12 - static let serverEndpoint = Typed(rawValue: ._serverEndpoint) case _sslClientCertData = 15 static let sslClientCertData = Typed(rawValue: ._sslClientCertData) case _sslClientPasswd = 16 @@ -67,10 +75,12 @@ enum StoreKey: Int, CaseIterable, QueryBindable { static let externalEndpointList = Typed<[Endpoint]>(rawValue: ._externalEndpointList) // MARK: - URL - case _localEndpoint = 134 - static let localEndpoint = Typed(rawValue: ._localEndpoint) case _serverUrl = 10 static let serverUrl = Typed(rawValue: ._serverUrl) + case _serverEndpoint = 12 + static let serverEndpoint = Typed(rawValue: ._serverEndpoint) + case _localEndpoint = 134 + static let localEndpoint = Typed(rawValue: ._localEndpoint) // MARK: - Date case _backupFailedSince = 5 @@ -160,6 +170,17 @@ enum StoreKey: Int, CaseIterable, QueryBindable { } } +enum UploadHeaders: String { + case reprDigest = "Repr-Digest" + case userToken = "X-Immich-User-Token" + case assetData = "X-Immich-Asset-Data" +} + +enum TaskStatus: Int, QueryBindable { + case downloadPending, downloadQueued, downloadFailed, uploadPending, uploadQueued, uploadFailed, uploadComplete, + uploadSkipped +} + enum BackupSelection: Int, QueryBindable { case selected, none, excluded } @@ -175,3 +196,77 @@ enum AlbumUserRole: Int, QueryBindable { enum MemoryType: Int, QueryBindable { case onThisDay } + +enum AssetVisibility: Int, QueryBindable { + case timeline, hidden, archive, locked +} + +enum SourceType: String, QueryBindable { + case machineLearning = "machine-learning" + case exif, manual +} + +enum UploadMethod: Int, QueryBindable { + case multipart, resumable +} + +enum UploadError: Error { + case fileCreationFailed + case iCloudError(UploadErrorCode) + case photosError(UploadErrorCode) +} + +enum UploadErrorCode: Int, QueryBindable { + case unknown + case assetNotFound + case fileNotFound + case resourceNotFound + case invalidResource + case encodingFailed + case writeFailed + case notEnoughSpace + case networkError + case photosInternalError + case photosUnknownError + case noServerUrl + case noDeviceId + case noAccessToken + case interrupted + case cancelled + case downloadStalled + case forceQuit + case outOfResources + case backgroundUpdatesDisabled + case uploadTimeout + case iCloudRateLimit + case iCloudThrottled + case invalidResponse + case badRequest + case internalServerError +} + +enum AssetType: Int, QueryBindable { + case other, image, video, audio +} + +enum AssetMediaStatus: String, Codable { + case created, replaced, duplicate +} + +struct Endpoint: Codable { + let url: URL + let status: Status + + enum Status: String, Codable { + case loading, valid, error, unknown + } +} + +struct UploadSuccessResponse: Codable { + let status: AssetMediaStatus + let id: String +} + +struct UploadErrorResponse: Codable { + let message: String +} diff --git a/mobile/ios/Runner/Schemas/Store.swift b/mobile/ios/Runner/Schemas/Store.swift index ee5280b6c0..b80f99a621 100644 --- a/mobile/ios/Runner/Schemas/Store.swift +++ b/mobile/ios/Runner/Schemas/Store.swift @@ -4,35 +4,75 @@ enum StoreError: Error { case invalidJSON(String) case invalidURL(String) case encodingFailed + case notFound } protocol StoreConvertible { + static var cacheKeyPath: ReferenceWritableKeyPath { get } associatedtype StorageType static func fromValue(_ value: StorageType) throws(StoreError) -> Self static func toValue(_ value: Self) throws(StoreError) -> StorageType } +extension StoreConvertible { + static func get(_ cache: StoreCache, key: StoreKey) -> Self? { + os_unfair_lock_lock(&cache.lock) + defer { os_unfair_lock_unlock(&cache.lock) } + return cache[keyPath: cacheKeyPath][key] + } + + static func set(_ cache: StoreCache, key: StoreKey, value: Self?) { + os_unfair_lock_lock(&cache.lock) + defer { os_unfair_lock_unlock(&cache.lock) } + cache[keyPath: cacheKeyPath][key] = value + } +} + +final class StoreCache { + fileprivate var lock = os_unfair_lock() + fileprivate var intCache: [StoreKey: Int] = [:] + fileprivate var boolCache: [StoreKey: Bool] = [:] + fileprivate var dateCache: [StoreKey: Date] = [:] + fileprivate var stringCache: [StoreKey: String] = [:] + fileprivate var urlCache: [StoreKey: URL] = [:] + fileprivate var endpointArrayCache: [StoreKey: [Endpoint]] = [:] + fileprivate var stringDictCache: [StoreKey: [String: String]] = [:] + + func get(_ key: StoreKey.Typed) -> T? { + T.get(self, key: key.rawValue) + } + + func set(_ key: StoreKey.Typed, value: T?) { + T.set(self, key: key.rawValue, value: value) + } +} + extension Int: StoreConvertible { + static let cacheKeyPath = \StoreCache.intCache static func fromValue(_ value: Int) -> Int { value } static func toValue(_ value: Int) -> Int { value } } extension Bool: StoreConvertible { + static let cacheKeyPath = \StoreCache.boolCache static func fromValue(_ value: Int) -> Bool { value == 1 } static func toValue(_ value: Bool) -> Int { value ? 1 : 0 } } extension Date: StoreConvertible { + static let cacheKeyPath = \StoreCache.dateCache static func fromValue(_ value: Int) -> Date { Date(timeIntervalSince1970: TimeInterval(value) / 1000) } static func toValue(_ value: Date) -> Int { Int(value.timeIntervalSince1970 * 1000) } } extension String: StoreConvertible { + static let cacheKeyPath = \StoreCache.stringCache static func fromValue(_ value: String) -> String { value } static func toValue(_ value: String) -> String { value } } extension URL: StoreConvertible { + static let cacheKeyPath = \StoreCache.urlCache static func fromValue(_ value: String) throws(StoreError) -> URL { guard let url = URL(string: value) else { throw StoreError.invalidURL(value) @@ -69,78 +109,52 @@ extension StoreConvertible where Self: Codable, StorageType == String { } } -extension Array: StoreConvertible where Element: Codable { +extension Array: StoreConvertible where Element == Endpoint { + static let cacheKeyPath = \StoreCache.endpointArrayCache typealias StorageType = String } -extension Dictionary: StoreConvertible where Key == String, Value: Codable { +extension Dictionary: StoreConvertible where Key == String, Value == String { + static let cacheKeyPath = \StoreCache.stringDictCache typealias StorageType = String } -class StoreRepository { - private let db: DatabasePool - - init(db: DatabasePool) { - self.db = db - } - - func get(_ key: StoreKey.Typed) throws -> T? where T.StorageType == Int { +extension Store { + static let cache = StoreCache() + + static func get(_ conn: Database, _ key: StoreKey.Typed) throws -> T? + where T.StorageType == Int { + if let cached = cache.get(key) { return cached } let query = Store.select(\.intValue).where { $0.id.eq(key.rawValue) } - if let value = try db.read({ conn in try query.fetchOne(conn) }) ?? nil { - return try T.fromValue(value) + if let value = try query.fetchOne(conn) ?? nil { + let converted = try T.fromValue(value) + cache.set(key, value: converted) } return nil } - func get(_ key: StoreKey.Typed) throws -> T? where T.StorageType == String { + static func get(_ conn: Database, _ key: StoreKey.Typed) throws -> T? + where T.StorageType == String { + if let cached = cache.get(key) { return cached } let query = Store.select(\.stringValue).where { $0.id.eq(key.rawValue) } - if let value = try db.read({ conn in try query.fetchOne(conn) }) ?? nil { - return try T.fromValue(value) + if let value = try query.fetchOne(conn) ?? nil { + let converted = try T.fromValue(value) + cache.set(key, value: converted) } return nil } - func get(_ key: StoreKey.Typed) async throws -> T? where T.StorageType == Int { - let query = Store.select(\.intValue).where { $0.id.eq(key.rawValue) } - if let value = try await db.read({ conn in try query.fetchOne(conn) }) ?? nil { - return try T.fromValue(value) - } - return nil + static func set(_ conn: Database, _ key: StoreKey.Typed, value: T) throws + where T.StorageType == Int { + let converted = try T.toValue(value) + try Store.upsert { Store(id: key.rawValue, stringValue: nil, intValue: converted) }.execute(conn) + cache.set(key, value: value) } - func get(_ key: StoreKey.Typed) async throws -> T? where T.StorageType == String { - let query = Store.select(\.stringValue).where { $0.id.eq(key.rawValue) } - if let value = try await db.read({ conn in try query.fetchOne(conn) }) ?? nil { - return try T.fromValue(value) - } - return nil - } - - func set(_ key: StoreKey.Typed, value: T) throws where T.StorageType == Int { - let value = try T.toValue(value) - try db.write { conn in - try Store.upsert { Store(id: key.rawValue, stringValue: nil, intValue: value) }.execute(conn) - } - } - - func set(_ key: StoreKey.Typed, value: T) throws where T.StorageType == String { - let value = try T.toValue(value) - try db.write { conn in - try Store.upsert { Store(id: key.rawValue, stringValue: value, intValue: nil) }.execute(conn) - } - } - - func set(_ key: StoreKey.Typed, value: T) async throws where T.StorageType == Int { - let value = try T.toValue(value) - try await db.write { conn in - try Store.upsert { Store(id: key.rawValue, stringValue: nil, intValue: value) }.execute(conn) - } - } - - func set(_ key: StoreKey.Typed, value: T) async throws where T.StorageType == String { - let value = try T.toValue(value) - try await db.write { conn in - try Store.upsert { Store(id: key.rawValue, stringValue: value, intValue: nil) }.execute(conn) - } + static func set(_ conn: Database, _ key: StoreKey.Typed, value: T) throws + where T.StorageType == String { + let converted = try T.toValue(value) + try Store.upsert { Store(id: key.rawValue, stringValue: converted, intValue: nil) }.execute(conn) + cache.set(key, value: value) } } diff --git a/mobile/ios/Runner/Schemas/Tables.swift b/mobile/ios/Runner/Schemas/Tables.swift index c256b0d0ed..b9f18417f8 100644 --- a/mobile/ios/Runner/Schemas/Tables.swift +++ b/mobile/ios/Runner/Schemas/Tables.swift @@ -1,70 +1,160 @@ -import GRDB import SQLiteData +extension QueryExpression where QueryValue: _OptionalProtocol { + // asserts column result cannot be nil + var unwrapped: SQLQueryExpression { + SQLQueryExpression(self.queryFragment, as: QueryValue.Wrapped.self) + } +} + +extension Date { + var unixTime: Date.UnixTimeRepresentation { + return Date.UnixTimeRepresentation(queryOutput: self) + } +} + @Table("asset_face_entity") -struct AssetFace { +struct AssetFace: Identifiable { let id: String - let assetId: String - let personId: String? + @Column("asset_id") + let assetId: RemoteAsset.ID + @Column("person_id") + let personId: Person.ID? + @Column("image_width") let imageWidth: Int + @Column("image_height") let imageHeight: Int + @Column("bounding_box_x1") let boundingBoxX1: Int + @Column("bounding_box_y1") let boundingBoxY1: Int + @Column("bounding_box_x2") let boundingBoxX2: Int + @Column("bounding_box_y2") let boundingBoxY2: Int - let sourceType: String + @Column("source_type") + let sourceType: SourceType } @Table("auth_user_entity") -struct AuthUser { +struct AuthUser: Identifiable { let id: String let name: String let email: String + @Column("is_admin") let isAdmin: Bool + @Column("has_profile_image") let hasProfileImage: Bool + @Column("profile_changed_at") let profileChangedAt: Date + @Column("avatar_color") let avatarColor: AvatarColor + @Column("quota_size_in_bytes") let quotaSizeInBytes: Int + @Column("quota_usage_in_bytes") let quotaUsageInBytes: Int + @Column("pin_code") let pinCode: String? } @Table("local_album_entity") -struct LocalAlbum { +struct LocalAlbum: Identifiable { let id: String + @Column("backup_selection") let backupSelection: BackupSelection - let linkedRemoteAlbumId: String? + @Column("linked_remote_album_id") + let linkedRemoteAlbumId: RemoteAlbum.ID? + @Column("marker") let marker_: Bool? let name: String + @Column("is_ios_shared_album") let isIosSharedAlbum: Bool + @Column("updated_at") let updatedAt: Date } +extension LocalAlbum { + static let selected = Self.where { $0.backupSelection.eq(BackupSelection.selected) } + static let excluded = Self.where { $0.backupSelection.eq(BackupSelection.excluded) } +} + @Table("local_album_asset_entity") struct LocalAlbumAsset { let id: ID + @Column("marker") let marker_: String? @Selection struct ID { + @Column("asset_id") let assetId: String + @Column("album_id") let albumId: String } } +extension LocalAlbumAsset { + static let selected = Self.where { + $0.id.assetId.eq(LocalAsset.columns.id) && $0.id.albumId.in(LocalAlbum.selected.select(\.id)) + } + static let excluded = Self.where { + $0.id.assetId.eq(LocalAsset.columns.id) && $0.id.albumId.in(LocalAlbum.excluded.select(\.id)) + } +} + @Table("local_asset_entity") -struct LocalAsset { +struct LocalAsset: Identifiable { let id: String let checksum: String? - let createdAt: Date + @Column("created_at") + let createdAt: String + @Column("duration_in_seconds") let durationInSeconds: Int? let height: Int? + @Column("is_favorite") let isFavorite: Bool let name: String let orientation: String - let type: Int - let updatedAt: Date + let type: AssetType + @Column("updated_at") + let updatedAt: String let width: Int? + + static func getCandidates() -> Where { + return Self.where { local in + LocalAlbumAsset.selected.exists() + && !LocalAlbumAsset.excluded.exists() + && !RemoteAsset.where { + local.checksum.eq($0.checksum) + && $0.ownerId.eq(Store.select(\.stringValue).where { $0.id.eq(StoreKey.currentUser.rawValue) }.unwrapped) + }.exists() + && !UploadTask.where { $0.localId.eq(local.id) }.exists() + } + } +} + +@Selection +struct LocalAssetCandidate { + let id: LocalAsset.ID + let type: AssetType +} + +@Selection +struct LocalAssetDownloadData { + let checksum: String? + let createdAt: String + let livePhotoVideoId: RemoteAsset.ID? + let localId: LocalAsset.ID + let taskId: UploadTask.ID + let updatedAt: String +} + +@Selection +struct LocalAssetUploadData { + let filePath: URL + let priority: Float + let taskId: UploadTask.ID + let type: AssetType } @Table("memory_asset_entity") @@ -73,63 +163,89 @@ struct MemoryAsset { @Selection struct ID { + @Column("asset_id") let assetId: String + @Column("album_id") let albumId: String } } @Table("memory_entity") -struct Memory { +struct Memory: Identifiable { let id: String + @Column("created_at") let createdAt: Date + @Column("updated_at") let updatedAt: Date + @Column("deleted_at") let deletedAt: Date? - let ownerId: String + @Column("owner_id") + let ownerId: User.ID let type: MemoryType let data: String + @Column("is_saved") let isSaved: Bool + @Column("memory_at") let memoryAt: Date + @Column("seen_at") let seenAt: Date? + @Column("show_at") let showAt: Date? + @Column("hide_at") let hideAt: Date? } @Table("partner_entity") struct Partner { let id: ID + @Column("in_timeline") let inTimeline: Bool @Selection struct ID { + @Column("shared_by_id") let sharedById: String + @Column("shared_with_id") let sharedWithId: String } } @Table("person_entity") -struct Person { +struct Person: Identifiable { let id: String + @Column("created_at") let createdAt: Date + @Column("updated_at") let updatedAt: Date + @Column("owner_id") let ownerId: String let name: String - let faceAssetId: String? + @Column("face_asset_id") + let faceAssetId: AssetFace.ID? + @Column("is_favorite") let isFavorite: Bool + @Column("is_hidden") let isHidden: Bool let color: String? + @Column("birth_date") let birthDate: Date? } @Table("remote_album_entity") -struct RemoteAlbum { +struct RemoteAlbum: Identifiable { let id: String + @Column("created_at") let createdAt: Date let description: String? + @Column("is_activity_enabled") let isActivityEnabled: Bool let name: String let order: Int + @Column("owner_id") let ownerId: String - let thumbnailAssetId: String? + @Column("thumbnail_asset_id") + let thumbnailAssetId: RemoteAsset.ID? + @Column("updated_at") let updatedAt: Date } @@ -139,7 +255,9 @@ struct RemoteAlbumAsset { @Selection struct ID { + @Column("asset_id") let assetId: String + @Column("album_id") let albumId: String } } @@ -151,40 +269,55 @@ struct RemoteAlbumUser { @Selection struct ID { + @Column("album_id") let albumId: String + @Column("user_id") let userId: String } } @Table("remote_asset_entity") -struct RemoteAsset { +struct RemoteAsset: Identifiable { let id: String - let checksum: String? + let checksum: String + @Column("is_favorite") + let isFavorite: Bool + @Column("deleted_at") let deletedAt: Date? - let isFavorite: Int - let libraryId: String? - let livePhotoVideoId: String? + @Column("owner_id") + let ownerId: User.ID + @Column("local_date_time") let localDateTime: Date? - let orientation: String - let ownerId: String - let stackId: String? - let visibility: Int + @Column("thumb_hash") + let thumbHash: String? + @Column("library_id") + let libraryId: String? + @Column("live_photo_video_id") + let livePhotoVideoId: String? + @Column("stack_id") + let stackId: Stack.ID? + let visibility: AssetVisibility } @Table("remote_exif_entity") struct RemoteExif { - @Column(primaryKey: true) - let assetId: String + @Column("asset_id", primaryKey: true) + let assetId: RemoteAsset.ID let city: String? let state: String? let country: String? + @Column("date_time_original") let dateTimeOriginal: Date? let description: String? let height: Int? let width: Int? + @Column("exposure_time") let exposureTime: String? + @Column("f_number") let fNumber: Double? + @Column("file_size") let fileSize: Int? + @Column("focal_length") let focalLength: Double? let latitude: Double? let longitude: Double? @@ -193,34 +326,110 @@ struct RemoteExif { let model: String? let lens: String? let orientation: String? + @Column("time_zone") let timeZone: String? let rating: Int? + @Column("projection_type") let projectionType: String? } @Table("stack_entity") -struct Stack { +struct Stack: Identifiable { let id: String + @Column("created_at") let createdAt: Date + @Column("updated_at") let updatedAt: Date - let ownerId: String + @Column("owner_id") + let ownerId: User.ID + @Column("primary_asset_id") let primaryAssetId: String } @Table("store_entity") -struct Store { +struct Store: Identifiable { let id: StoreKey + @Column("string_value") let stringValue: String? + @Column("int_value") let intValue: Int? } +@Table("upload_tasks") +struct UploadTask: Identifiable { + let id: Int64 + let attempts: Int + @Column("created_at", as: Date.UnixTimeRepresentation.self) + let createdAt: Date + @Column("file_path") + var filePath: URL? + @Column("is_live_photo") + let isLivePhoto: Bool? + @Column("last_error") + let lastError: UploadErrorCode? + @Column("live_photo_video_id") + let livePhotoVideoId: RemoteAsset.ID? + @Column("local_id") + var localId: LocalAsset.ID? + let method: UploadMethod + var priority: Float + @Column("retry_after", as: Date?.UnixTimeRepresentation.self) + let retryAfter: Date? + let status: TaskStatus + + static func retryOrFail(code: UploadErrorCode, status: TaskStatus) -> Update { + return Self.update { row in + row.status = Case().when(row.attempts.lte(TaskConfig.maxRetries), then: TaskStatus.downloadPending).else(status) + row.attempts += 1 + row.lastError = code + row.retryAfter = #sql("unixepoch('now') + (\(4 << row.attempts))") + } + } +} + +@Table("upload_task_stats") +struct UploadTaskStat { + @Column("pending_downloads") + let pendingDownloads: Int + @Column("pending_uploads") + let pendingUploads: Int + @Column("queued_downloads") + let queuedDownloads: Int + @Column("queued_uploads") + let queuedUploads: Int + @Column("failed_downloads") + let failedDownloads: Int + @Column("failed_uploads") + let failedUploads: Int + @Column("completed_uploads") + let completedUploads: Int + @Column("skipped_uploads") + let skippedUploads: Int + + static let availableDownloadSlots = Self.select { + TaskConfig.maxPendingDownloads - ($0.pendingDownloads + $0.queuedDownloads) + } + + static let availableUploadSlots = Self.select { + TaskConfig.maxPendingUploads - ($0.pendingUploads + $0.queuedUploads) + } + + static let availableSlots = Self.select { + TaskConfig.maxPendingUploads + TaskConfig.maxPendingDownloads + - ($0.pendingDownloads + $0.queuedDownloads + $0.pendingUploads + $0.queuedUploads) + } +} + @Table("user_entity") -struct User { +struct User: Identifiable { let id: String let name: String let email: String + @Column("has_profile_image") let hasProfileImage: Bool + @Column("profile_changed_at") let profileChangedAt: Date + @Column("avatar_color") let avatarColor: AvatarColor } @@ -231,6 +440,7 @@ struct UserMetadata { @Selection struct ID { + @Column("user_id") let userId: String let key: Date } diff --git a/mobile/ios/Runner/Sync/PHAssetExtensions.swift b/mobile/ios/Runner/Sync/PHAssetExtensions.swift index 2b1ef6ac88..e33881a693 100644 --- a/mobile/ios/Runner/Sync/PHAssetExtensions.swift +++ b/mobile/ios/Runner/Sync/PHAssetExtensions.swift @@ -52,6 +52,23 @@ extension PHAsset { return nil } + + func getLivePhotoResource() -> PHAssetResource? { + let resources = PHAssetResource.assetResources(for: self) + + var livePhotoResource: PHAssetResource? + for resource in resources { + if resource.type == .fullSizePairedVideo { + return resource + } + + if resource.type == .pairedVideo { + livePhotoResource = resource + } + } + + return livePhotoResource + } private func isValidResourceType(_ type: PHAssetResourceType) -> Bool { switch mediaType { diff --git a/mobile/ios/Runner/Upload/Delegate.swift b/mobile/ios/Runner/Upload/Delegate.swift new file mode 100644 index 0000000000..b3c5a4bdff --- /dev/null +++ b/mobile/ios/Runner/Upload/Delegate.swift @@ -0,0 +1,208 @@ +import SQLiteData + +class UploadApiDelegate: NSObject, URLSessionDataDelegate, URLSessionTaskDelegate { + private static let stateLock = NSLock() + private static var transferStates: [Int64: NetworkTransferState] = [:] + private static var responseData: [Int64: Data] = [:] + private static let jsonDecoder = JSONDecoder() + + private let db: DatabasePool + private let statusListener: StatusEventListener + private let progressListener: ProgressEventListener + weak var downloadQueue: DownloadQueue? + weak var uploadQueue: UploadQueue? + + init(db: DatabasePool, statusListener: StatusEventListener, progressListener: ProgressEventListener) { + self.db = db + self.statusListener = statusListener + self.progressListener = progressListener + } + + static func reset() { + stateLock.withLock { + transferStates.removeAll() + responseData.removeAll() + } + } + + func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { + guard let taskIdStr = dataTask.taskDescription, + let taskId = Int64(taskIdStr) + else { return } + + Self.stateLock.withLock { + if var response = Self.responseData[taskId] { + response.append(data) + } else { + Self.responseData[taskId] = data + } + } + } + + func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { + Task { + defer { + downloadQueue?.startQueueProcessing() + uploadQueue?.startQueueProcessing() + } + + guard let taskDescriptionId = task.taskDescription, + let taskId = Int64(taskDescriptionId) + else { + return dPrint("Unexpected: task without session ID completed") + } + + defer { + Self.stateLock.withLock { let _ = Self.transferStates.removeValue(forKey: taskId) } + } + + if let responseData = Self.stateLock.withLock({ Self.responseData.removeValue(forKey: taskId) }), + let httpResponse = task.response as? HTTPURLResponse + { + switch httpResponse.statusCode { + case 200, 201: + do { + let response = try Self.jsonDecoder.decode(UploadSuccessResponse.self, from: responseData) + return await handleSuccess(taskId: taskId, response: response) + } catch { + return await handleFailure(taskId: taskId, code: .invalidResponse) + } + case 400..<500: + dPrint( + "Response \(httpResponse.statusCode): \(String(data: responseData, encoding: .utf8) ?? "No response body")" + ) + return await handleFailure(taskId: taskId, code: .badRequest) + default: + break + } + } + + guard let urlError = error as? URLError else { + return await handleFailure(taskId: taskId) + } + + if #available(iOS 17, *), let resumeData = urlError.uploadTaskResumeData { + return await handleFailure(taskDescriptionId: taskDescriptionId, session: session, resumeData: resumeData) + } + + let code: UploadErrorCode = + switch urlError.backgroundTaskCancelledReason { + case .backgroundUpdatesDisabled: .backgroundUpdatesDisabled + case .insufficientSystemResources: .outOfResources + case .userForceQuitApplication: .forceQuit + default: + switch urlError.code { + case .networkConnectionLost, .notConnectedToInternet: .networkError + case .timedOut: .uploadTimeout + case .resourceUnavailable, .fileDoesNotExist: .fileNotFound + default: .unknown + } + } + await handleFailure(taskId: taskId, code: code) + } + } + + func urlSession( + _ session: URLSession, + task: URLSessionTask, + didSendBodyData bytesSent: Int64, + totalBytesSent: Int64, + totalBytesExpectedToSend: Int64 + ) { + guard let sessionTaskId = task.taskDescription, let taskId = Int64(sessionTaskId) else { return } + let currentTime = Date() + let state = Self.stateLock.withLock { + if let existing = Self.transferStates[taskId] { + return existing + } + let new = NetworkTransferState( + lastUpdateTime: currentTime, + totalBytesTransferred: totalBytesSent, + currentSpeed: nil + ) + Self.transferStates[taskId] = new + return new + } + + let timeDelta = currentTime.timeIntervalSince(state.lastUpdateTime) + guard timeDelta > 0 else { return } + + let bytesDelta = totalBytesSent - state.totalBytesTransferred + let instantSpeed = Double(bytesDelta) / timeDelta + let currentSpeed = + if let previousSpeed = state.currentSpeed { + TaskConfig.transferSpeedAlpha * instantSpeed + (1 - TaskConfig.transferSpeedAlpha) * previousSpeed + } else { + instantSpeed + } + state.currentSpeed = currentSpeed + state.lastUpdateTime = currentTime + state.totalBytesTransferred = totalBytesSent + self.progressListener.onTaskProgress( + UploadApiTaskProgress( + id: sessionTaskId, + progress: Double(totalBytesSent) / Double(totalBytesExpectedToSend), + speed: currentSpeed + ) + ) + } + + private func handleSuccess(taskId: Int64, response: UploadSuccessResponse) async { + dPrint("Upload succeeded for task \(taskId), server ID: \(response.id)") + do { + try await db.write { conn in + let task = try UploadTask.update { $0.status = .uploadComplete }.where({ $0.id.eq(taskId) }) + .returning(\.self).fetchOne(conn) + guard let task, let isLivePhoto = task.isLivePhoto, isLivePhoto, task.livePhotoVideoId == nil else { return } + try UploadTask.insert { + UploadTask.Draft( + attempts: 0, + createdAt: Date(), + filePath: nil, + isLivePhoto: true, + lastError: nil, + livePhotoVideoId: response.id, + localId: task.localId, + method: .multipart, + priority: 0.7, + retryAfter: nil, + status: .downloadPending, + ) + }.execute(conn) + } + dPrint("Updated upload success status for session task \(taskId)") + } catch { + dPrint( + "Failed to update upload success status for session task \(taskId): \(error.localizedDescription)" + ) + } + } + + private func handleFailure(taskId: Int64, code: UploadErrorCode = .unknown) async { + dPrint("Upload failed for task \(taskId) with code \(code)") + try? await db.write { conn in + try UploadTask.retryOrFail(code: code, status: .uploadFailed).where { $0.id.eq(taskId) } + .execute(conn) + } + } + + @available(iOS 17, *) + private func handleFailure(taskDescriptionId: String, session: URLSession, resumeData: Data) async { + dPrint("Resuming upload for task \(taskDescriptionId)") + let resumeTask = session.uploadTask(withResumeData: resumeData) + resumeTask.taskDescription = taskDescriptionId + resumeTask.resume() + } + + private class NetworkTransferState { + var lastUpdateTime: Date + var totalBytesTransferred: Int64 + var currentSpeed: Double? + + init(lastUpdateTime: Date, totalBytesTransferred: Int64, currentSpeed: Double?) { + self.lastUpdateTime = lastUpdateTime + self.totalBytesTransferred = totalBytesTransferred + self.currentSpeed = currentSpeed + } + } +} diff --git a/mobile/ios/Runner/Upload/DownloadQueue.swift b/mobile/ios/Runner/Upload/DownloadQueue.swift new file mode 100644 index 0000000000..f7fcb55800 --- /dev/null +++ b/mobile/ios/Runner/Upload/DownloadQueue.swift @@ -0,0 +1,351 @@ +import CryptoKit +import Photos +import SQLiteData + +class DownloadQueue { + private static let resourceManager = PHAssetResourceManager.default() + private static var queueProcessingTask: Task? + private static var queueProcessingLock = NSLock() + + private let db: DatabasePool + private let uploadQueue: UploadQueue + private let statusListener: StatusEventListener + private let progressListener: ProgressEventListener + + init( + db: DatabasePool, + uploadQueue: UploadQueue, + statusListener: StatusEventListener, + progressListener: ProgressEventListener + ) { + self.db = db + self.uploadQueue = uploadQueue + self.statusListener = statusListener + self.progressListener = progressListener + NotificationCenter.default.addObserver(forName: .networkDidConnect, object: nil, queue: nil) { [weak self] _ in + dPrint("Network connected") + self?.startQueueProcessing() + } + } + + func enqueueAssets(localIds: [String]) async throws { + guard !localIds.isEmpty else { return dPrint("No assets to enqueue") } + + defer { startQueueProcessing() } + let candidates = try await db.read { conn in + return try LocalAsset.all + .where { asset in asset.id.in(localIds) } + .select { LocalAssetCandidate.Columns(id: $0.id, type: $0.type) } + .limit { _ in UploadTaskStat.availableSlots } + .fetchAll(conn) + } + + guard !candidates.isEmpty else { return dPrint("No candidates to enqueue") } + + try await db.write { conn in + var draft = UploadTask.Draft( + attempts: 0, + createdAt: Date(), + filePath: nil, + isLivePhoto: nil, + lastError: nil, + livePhotoVideoId: nil, + localId: "", + method: .multipart, + priority: 0.5, + retryAfter: nil, + status: .downloadPending, + ) + for candidate in candidates { + draft.localId = candidate.id + draft.priority = candidate.type == .image ? 0.9 : 0.8 + try UploadTask.insert { + draft + } onConflict: { + ($0.localId, $0.livePhotoVideoId) + }.execute(conn) + } + } + dPrint("Enqueued \(candidates.count) assets for upload") + } + + func startQueueProcessing() { + dPrint("Starting download queue processing") + Self.queueProcessingLock.withLock { + guard Self.queueProcessingTask == nil else { return } + Self.queueProcessingTask = Task { + await startDownloads() + Self.queueProcessingLock.withLock { Self.queueProcessingTask = nil } + } + } + } + + private func startDownloads() async { + dPrint("Processing download queue") + guard NetworkMonitor.shared.isConnected else { + return dPrint("Download queue paused: network disconnected") + } + + do { + let tasks: [LocalAssetDownloadData] = try await db.read({ conn in + guard let backupEnabled = try Store.get(conn, StoreKey.enableBackup), backupEnabled else { return [] } + return try UploadTask.join(LocalAsset.all) { task, asset in task.localId.eq(asset.id) } + .where { task, asset in + asset.checksum.isNot(nil) && task.status.eq(TaskStatus.downloadPending) + && task.attempts < TaskConfig.maxRetries + && (task.retryAfter.is(nil) || task.retryAfter.unwrapped <= Date().unixTime) + && (task.lastError.is(nil) + || !task.lastError.unwrapped.in([ + UploadErrorCode.assetNotFound, UploadErrorCode.resourceNotFound, UploadErrorCode.invalidResource, + ])) + } + .select { task, asset in + LocalAssetDownloadData.Columns( + checksum: asset.checksum, + createdAt: asset.createdAt, + livePhotoVideoId: task.livePhotoVideoId, + localId: asset.id, + taskId: task.id, + updatedAt: asset.updatedAt + ) + } + .order { task, asset in (task.priority.desc(), task.createdAt) } + .limit { _, _ in UploadTaskStat.availableDownloadSlots } + .fetchAll(conn) + }) + if tasks.isEmpty { return dPrint("No download tasks to process") } + + try await withThrowingTaskGroup(of: Void.self) { group in + var iterator = tasks.makeIterator() + for _ in 0.. String? + { + dPrint("Downloading asset resource \(resource.assetLocalIdentifier) of type \(resource.type.rawValue)") + let options = PHAssetResourceRequestOptions() + options.isNetworkAccessAllowed = true + let (header, footer) = AssetData( + deviceAssetId: task.localId, + deviceId: deviceId, + fileCreatedAt: task.createdAt, + fileModifiedAt: task.updatedAt, + fileName: resource.originalFilename, + isFavorite: asset.isFavorite, + livePhotoVideoId: nil + ).multipart() + + guard let fileHandle = try? FileHandle.createOrOverwrite(atPath: filePath.path) else { + dPrint("Failed to open file handle for download task \(task.taskId), path: \(filePath.path)") + throw UploadError.fileCreationFailed + } + try fileHandle.write(contentsOf: header) + + class RequestRef { + var id: PHAssetResourceDataRequestID? + var lastProgressTime = Date() + var didStall = false + } + + var lastProgressTime = Date() + nonisolated(unsafe) let progressListener = self.progressListener + let taskIdStr = String(task.taskId) + options.progressHandler = { progress in + lastProgressTime = Date() + progressListener.onTaskProgress(UploadApiTaskProgress(id: taskIdStr, progress: progress)) + } + + let request = RequestRef() + let timeoutTask = Task { + while !Task.isCancelled { + try? await Task.sleep(nanoseconds: TaskConfig.downloadCheckIntervalNs) + request.didStall = Date().timeIntervalSince(lastProgressTime) > TaskConfig.downloadTimeoutS + if request.didStall { + if let requestId = request.id { + Self.resourceManager.cancelDataRequest(requestId) + } + break + } + } + } + + return try await withTaskCancellationHandler { + try await withCheckedThrowingContinuation { continuation in + var hasher = task.checksum == nil && task.livePhotoVideoId == nil ? Insecure.SHA1() : nil + request.id = Self.resourceManager.requestData( + for: resource, + options: options, + dataReceivedHandler: { data in + guard let requestId = request.id else { return } + do { + hasher?.update(data: data) + try fileHandle.write(contentsOf: data) + } catch { + request.id = nil + Self.resourceManager.cancelDataRequest(requestId) + } + }, + completionHandler: { error in + timeoutTask.cancel() + switch error { + case let e as NSError where e.domain == "CloudPhotoLibraryErrorDomain": + dPrint("iCloud error during download: \(e)") + let code: UploadErrorCode = + switch e.code { + case 1005: .iCloudRateLimit + case 81: .iCloudThrottled + default: .photosUnknownError + } + self.handleFailure(task: task, code: code, filePath: filePath) + case let e as PHPhotosError: + dPrint("Photos error during download: \(e)") + let code: UploadErrorCode = + switch e.code { + case .notEnoughSpace: .notEnoughSpace + case .missingResource: .resourceNotFound + case .networkError: .networkError + case .internalError: .photosInternalError + case .invalidResource: .invalidResource + case .operationInterrupted: .interrupted + case .userCancelled where request.didStall: .downloadStalled + case .userCancelled: .cancelled + default: .photosUnknownError + } + self.handleFailure(task: task, code: code, filePath: filePath) + case .some: + dPrint("Unknown error during download: \(String(describing: error))") + self.handleFailure(task: task, code: .unknown, filePath: filePath) + case .none: + dPrint("Download completed for task \(task.taskId)") + do { + try fileHandle.write(contentsOf: footer) + continuation.resume(returning: hasher.map { hasher in Data(hasher.finalize()).base64EncodedString() }) + } catch { + try? FileManager.default.removeItem(at: filePath) + continuation.resume(throwing: error) + } + } + } + ) + } + } onCancel: { + if let requestId = request.id { + Self.resourceManager.cancelDataRequest(requestId) + } + } + } + + private func handleFailure(task: LocalAssetDownloadData, code: UploadErrorCode, filePath: URL? = nil) { + dPrint("Handling failure for task \(task.taskId) with code \(code.rawValue)") + do { + if let filePath { + try? FileManager.default.removeItem(at: filePath) + } + + try db.write { conn in + try UploadTask.retryOrFail(code: code, status: .downloadFailed).where { $0.id.eq(task.taskId) }.execute(conn) + } + } catch { + dPrint("Failed to update download failure status for task \(task.taskId): \(error)") + } + } +} diff --git a/mobile/ios/Runner/Upload/Listeners.swift b/mobile/ios/Runner/Upload/Listeners.swift new file mode 100644 index 0000000000..dff7e1efdc --- /dev/null +++ b/mobile/ios/Runner/Upload/Listeners.swift @@ -0,0 +1,39 @@ +class StatusEventListener: StreamStatusStreamHandler { + var eventSink: PigeonEventSink? + + override func onListen(withArguments arguments: Any?, sink: PigeonEventSink) { + eventSink = sink + } + + func onTaskStatus(_ event: UploadApiTaskStatus) { + if let eventSink = eventSink { + eventSink.success(event) + } + } + + func onEventsDone() { + eventSink?.endOfStream() + eventSink = nil + } +} + +class ProgressEventListener: StreamProgressStreamHandler { + var eventSink: PigeonEventSink? + + override func onListen(withArguments arguments: Any?, sink: PigeonEventSink) { + eventSink = sink + } + + func onTaskProgress(_ event: UploadApiTaskProgress) { + if let eventSink = eventSink { + DispatchQueue.main.async { eventSink.success(event) } + } + } + + func onEventsDone() { + DispatchQueue.main.async { + self.eventSink?.endOfStream() + self.eventSink = nil + } + } +} diff --git a/mobile/ios/Runner/Upload/NetworkMonitor.swift b/mobile/ios/Runner/Upload/NetworkMonitor.swift new file mode 100644 index 0000000000..1d239beb2a --- /dev/null +++ b/mobile/ios/Runner/Upload/NetworkMonitor.swift @@ -0,0 +1,22 @@ +import Network + +class NetworkMonitor { + static let shared = NetworkMonitor() + private let monitor = NWPathMonitor() + private(set) var isConnected = false + private(set) var isExpensive = false + + private init() { + monitor.pathUpdateHandler = { [weak self] path in + guard let self else { return } + let wasConnected = self.isConnected + self.isConnected = path.status == .satisfied + self.isExpensive = path.isExpensive + + if !wasConnected && self.isConnected { + NotificationCenter.default.post(name: .networkDidConnect, object: nil) + } + } + monitor.start(queue: .global(qos: .utility)) + } +} diff --git a/mobile/ios/Runner/Upload/UploadQueue.swift b/mobile/ios/Runner/Upload/UploadQueue.swift new file mode 100644 index 0000000000..fd828e2ad6 --- /dev/null +++ b/mobile/ios/Runner/Upload/UploadQueue.swift @@ -0,0 +1,221 @@ +import SQLiteData +import StructuredFieldValues + +class UploadQueue { + private static let structuredEncoder = StructuredFieldValueEncoder() + private static var queueProcessingTask: Task? + private static var queueProcessingLock = NSLock() + + private let db: DatabasePool + private let cellularSession: URLSession + private let wifiOnlySession: URLSession + private let statusListener: StatusEventListener + + init(db: DatabasePool, cellularSession: URLSession, wifiOnlySession: URLSession, statusListener: StatusEventListener) + { + self.db = db + self.cellularSession = cellularSession + self.wifiOnlySession = wifiOnlySession + self.statusListener = statusListener + } + + func enqueueFiles(paths: [String]) async throws { + guard !paths.isEmpty else { return dPrint("No paths to enqueue") } + + guard let deviceId = (try? await db.read { conn in try Store.get(conn, StoreKey.deviceId) }) else { + throw StoreError.notFound + } + + defer { startQueueProcessing() } + + try await withThrowingTaskGroup(of: Void.self, returning: Void.self) { group in + let date = Date() + try FileManager.default.createDirectory( + at: TaskConfig.originalsDir, + withIntermediateDirectories: true, + attributes: nil + ) + + for path in paths { + group.addTask { + let inputURL = URL(fileURLWithPath: path, isDirectory: false) + let outputURL = TaskConfig.originalsDir.appendingPathComponent(UUID().uuidString) + let resources = try inputURL.resourceValues(forKeys: [.creationDateKey, .contentModificationDateKey]) + + let formatter = ISO8601DateFormatter() + let (header, footer) = AssetData( + deviceAssetId: "", + deviceId: deviceId, + fileCreatedAt: formatter.string(from: resources.creationDate ?? date), + fileModifiedAt: formatter.string(from: resources.contentModificationDate ?? date), + fileName: resources.name ?? inputURL.lastPathComponent, + isFavorite: false, + livePhotoVideoId: nil + ).multipart() + + do { + let writeHandle = try FileHandle.createOrOverwrite(atPath: outputURL.path) + try writeHandle.write(contentsOf: header) + let readHandle = try FileHandle(forReadingFrom: inputURL) + + let bufferSize = 1024 * 1024 + while true { + let data = try readHandle.read(upToCount: bufferSize) + guard let data = data, !data.isEmpty else { break } + try writeHandle.write(contentsOf: data) + } + try writeHandle.write(contentsOf: footer) + } catch { + try? FileManager.default.removeItem(at: outputURL) + throw error + } + } + } + + try await group.waitForAll() + } + + try await db.write { conn in + var draft = UploadTask.Draft( + attempts: 0, + createdAt: Date(), + filePath: nil, + isLivePhoto: nil, + lastError: nil, + livePhotoVideoId: nil, + localId: "", + method: .multipart, + priority: 0.5, + retryAfter: nil, + status: .downloadPending, + ) + for path in paths { + draft.filePath = URL(fileURLWithPath: path, isDirectory: false) + try UploadTask.insert { draft }.execute(conn) + } + } + dPrint("Enqueued \(paths.count) assets for upload") + } + + func startQueueProcessing() { + dPrint("Starting upload queue processing") + Self.queueProcessingLock.withLock { + guard Self.queueProcessingTask == nil else { return } + Self.queueProcessingTask = Task { + await startUploads() + Self.queueProcessingLock.withLock { Self.queueProcessingTask = nil } + } + } + } + + private func startUploads() async { + dPrint("Processing download queue") + guard NetworkMonitor.shared.isConnected, + let backupEnabled = try? await db.read({ conn in try Store.get(conn, StoreKey.enableBackup) }), + backupEnabled + else { return dPrint("Download queue paused: network disconnected or backup disabled") } + + do { + let tasks: [LocalAssetUploadData] = try await db.read({ conn in + guard let backupEnabled = try Store.get(conn, StoreKey.enableBackup), backupEnabled else { return [] } + return try UploadTask.join(LocalAsset.all) { task, asset in task.localId.eq(asset.id) } + .where { task, asset in + asset.checksum.isNot(nil) && task.status.eq(TaskStatus.uploadPending) + && task.attempts < TaskConfig.maxRetries + && task.filePath.isNot(nil) + } + .select { task, asset in + LocalAssetUploadData.Columns( + filePath: task.filePath.unwrapped, + priority: task.priority, + taskId: task.id, + type: asset.type + ) + } + .limit { task, _ in UploadTaskStat.availableUploadSlots } + .order { task, asset in (task.priority.desc(), task.createdAt) } + .fetchAll(conn) + }) + if tasks.isEmpty { return dPrint("No upload tasks to process") } + + await withTaskGroup(of: Void.self) { group in + for task in tasks { + group.addTask { await self.startUpload(multipart: task) } + } + } + } catch { + dPrint("Upload queue error: \(error)") + } + } + + private func startUpload(multipart task: LocalAssetUploadData) async { + dPrint("Uploading asset resource at \(task.filePath) of task \(task.taskId)") + defer { startQueueProcessing() } + + let (url, accessToken, session): (URL, String, URLSession) + do { + (url, accessToken, session) = try await db.read { conn in + guard let url = try Store.get(conn, StoreKey.serverEndpoint), + let accessToken = try Store.get(conn, StoreKey.accessToken) + else { + throw StoreError.notFound + } + + let session = + switch task.type { + case .image: + (try? Store.get(conn, StoreKey.useWifiForUploadPhotos)) ?? false ? cellularSession : wifiOnlySession + case .video: + (try? Store.get(conn, StoreKey.useWifiForUploadVideos)) ?? false ? cellularSession : wifiOnlySession + default: wifiOnlySession + } + return (url, accessToken, session) + } + } catch { + dPrint("Upload failed for \(task.taskId), could not retrieve server URL or access token: \(error)") + return handleFailure(task: task, code: .noServerUrl) + } + + var request = URLRequest(url: url.appendingPathComponent("/assets")) + request.httpMethod = "POST" + request.setValue(accessToken, forHTTPHeaderField: UploadHeaders.userToken.rawValue) + request.setValue(AssetData.contentType, forHTTPHeaderField: "Content-Type") + + let sessionTask = session.uploadTask(with: request, fromFile: task.filePath) + sessionTask.taskDescription = String(task.taskId) + sessionTask.priority = task.priority + do { + try? FileManager.default.removeItem(at: task.filePath) // upload task already copied the file + try await db.write { conn in + try UploadTask.update { row in + row.status = .uploadQueued + row.filePath = nil + } + .where { $0.id.eq(task.taskId) } + .execute(conn) + } + statusListener.onTaskStatus( + UploadApiTaskStatus( + id: String(task.taskId), + filename: task.filePath.lastPathComponent, + status: .uploadQueued, + ) + ) + + sessionTask.resume() + dPrint("Upload started for task \(task.taskId) using \(session == wifiOnlySession ? "WiFi" : "Cellular") session") + } catch { + dPrint("Upload failed for \(task.taskId), could not update queue status: \(error.localizedDescription)") + } + } + + private func handleFailure(task: LocalAssetUploadData, code: UploadErrorCode) { + do { + try db.write { conn in + try UploadTask.retryOrFail(code: code, status: .uploadFailed).where { $0.id.eq(task.taskId) }.execute(conn) + } + } catch { + dPrint("Failed to update upload failure status for task \(task.taskId): \(error)") + } + } +} diff --git a/mobile/ios/Runner/Upload/UploadTask.g.swift b/mobile/ios/Runner/Upload/UploadTask.g.swift new file mode 100644 index 0000000000..5b70d9c211 --- /dev/null +++ b/mobile/ios/Runner/Upload/UploadTask.g.swift @@ -0,0 +1,463 @@ +// Autogenerated from Pigeon (v26.0.2), 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)", + "\(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? +} + +func deepEqualsUploadTask(_ 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 is (Void, Void): + return true + + case let (cleanLhsHashable, cleanRhsHashable) as (AnyHashable, AnyHashable): + return cleanLhsHashable == cleanRhsHashable + + case let (cleanLhsArray, cleanRhsArray) as ([Any?], [Any?]): + guard cleanLhsArray.count == cleanRhsArray.count else { return false } + for (index, element) in cleanLhsArray.enumerated() { + if !deepEqualsUploadTask(element, cleanRhsArray[index]) { + return false + } + } + return true + + case let (cleanLhsDictionary, cleanRhsDictionary) as ([AnyHashable: Any?], [AnyHashable: Any?]): + guard cleanLhsDictionary.count == cleanRhsDictionary.count else { return false } + for (key, cleanLhsValue) in cleanLhsDictionary { + guard cleanRhsDictionary.index(forKey: key) != nil else { return false } + if !deepEqualsUploadTask(cleanLhsValue, cleanRhsDictionary[key]!) { + return false + } + } + return true + + default: + // Any other type shouldn't be able to be used with pigeon. File an issue if you find this to be untrue. + return false + } +} + +func deepHashUploadTask(value: Any?, hasher: inout Hasher) { + if let valueList = value as? [AnyHashable] { + for item in valueList { deepHashUploadTask(value: item, hasher: &hasher) } + return + } + + if let valueDict = value as? [AnyHashable: AnyHashable] { + for key in valueDict.keys { + hasher.combine(key) + deepHashUploadTask(value: valueDict[key]!, hasher: &hasher) + } + return + } + + if let hashableValue = value as? AnyHashable { + hasher.combine(hashableValue.hashValue) + } + + return hasher.combine(String(describing: value)) +} + + + +enum UploadApiErrorCode: Int { + case unknown = 0 + case assetNotFound = 1 + case fileNotFound = 2 + case resourceNotFound = 3 + case invalidResource = 4 + case encodingFailed = 5 + case writeFailed = 6 + case notEnoughSpace = 7 + case networkError = 8 + case photosInternalError = 9 + case photosUnknownError = 10 + case noServerUrl = 11 + case noDeviceId = 12 + case noAccessToken = 13 + case interrupted = 14 + case cancelled = 15 + case downloadStalled = 16 + case forceQuit = 17 + case outOfResources = 18 + case backgroundUpdatesDisabled = 19 + case uploadTimeout = 20 + case iCloudRateLimit = 21 + case iCloudThrottled = 22 +} + +enum UploadApiStatus: Int { + case downloadPending = 0 + case downloadQueued = 1 + case downloadFailed = 2 + case uploadPending = 3 + case uploadQueued = 4 + case uploadFailed = 5 + case uploadComplete = 6 + case uploadSkipped = 7 +} + +/// Generated class from Pigeon that represents data sent in messages. +struct UploadApiTaskStatus: Hashable { + var id: String + var filename: String + var status: UploadApiStatus + var errorCode: UploadApiErrorCode? = nil + var httpStatusCode: Int64? = nil + + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> UploadApiTaskStatus? { + let id = pigeonVar_list[0] as! String + let filename = pigeonVar_list[1] as! String + let status = pigeonVar_list[2] as! UploadApiStatus + let errorCode: UploadApiErrorCode? = nilOrValue(pigeonVar_list[3]) + let httpStatusCode: Int64? = nilOrValue(pigeonVar_list[4]) + + return UploadApiTaskStatus( + id: id, + filename: filename, + status: status, + errorCode: errorCode, + httpStatusCode: httpStatusCode + ) + } + func toList() -> [Any?] { + return [ + id, + filename, + status, + errorCode, + httpStatusCode, + ] + } + static func == (lhs: UploadApiTaskStatus, rhs: UploadApiTaskStatus) -> Bool { + return deepEqualsUploadTask(lhs.toList(), rhs.toList()) } + func hash(into hasher: inout Hasher) { + deepHashUploadTask(value: toList(), hasher: &hasher) + } +} + +/// Generated class from Pigeon that represents data sent in messages. +struct UploadApiTaskProgress: Hashable { + var id: String + var progress: Double + var speed: Double? = nil + var totalBytes: Int64? = nil + + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> UploadApiTaskProgress? { + let id = pigeonVar_list[0] as! String + let progress = pigeonVar_list[1] as! Double + let speed: Double? = nilOrValue(pigeonVar_list[2]) + let totalBytes: Int64? = nilOrValue(pigeonVar_list[3]) + + return UploadApiTaskProgress( + id: id, + progress: progress, + speed: speed, + totalBytes: totalBytes + ) + } + func toList() -> [Any?] { + return [ + id, + progress, + speed, + totalBytes, + ] + } + static func == (lhs: UploadApiTaskProgress, rhs: UploadApiTaskProgress) -> Bool { + return deepEqualsUploadTask(lhs.toList(), rhs.toList()) } + func hash(into hasher: inout Hasher) { + deepHashUploadTask(value: toList(), hasher: &hasher) + } +} + +private class UploadTaskPigeonCodecReader: 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 UploadApiErrorCode(rawValue: enumResultAsInt) + } + return nil + case 130: + let enumResultAsInt: Int? = nilOrValue(self.readValue() as! Int?) + if let enumResultAsInt = enumResultAsInt { + return UploadApiStatus(rawValue: enumResultAsInt) + } + return nil + case 131: + return UploadApiTaskStatus.fromList(self.readValue() as! [Any?]) + case 132: + return UploadApiTaskProgress.fromList(self.readValue() as! [Any?]) + default: + return super.readValue(ofType: type) + } + } +} + +private class UploadTaskPigeonCodecWriter: FlutterStandardWriter { + override func writeValue(_ value: Any) { + if let value = value as? UploadApiErrorCode { + super.writeByte(129) + super.writeValue(value.rawValue) + } else if let value = value as? UploadApiStatus { + super.writeByte(130) + super.writeValue(value.rawValue) + } else if let value = value as? UploadApiTaskStatus { + super.writeByte(131) + super.writeValue(value.toList()) + } else if let value = value as? UploadApiTaskProgress { + super.writeByte(132) + super.writeValue(value.toList()) + } else { + super.writeValue(value) + } + } +} + +private class UploadTaskPigeonCodecReaderWriter: FlutterStandardReaderWriter { + override func reader(with data: Data) -> FlutterStandardReader { + return UploadTaskPigeonCodecReader(data: data) + } + + override func writer(with data: NSMutableData) -> FlutterStandardWriter { + return UploadTaskPigeonCodecWriter(data: data) + } +} + +class UploadTaskPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable { + static let shared = UploadTaskPigeonCodec(readerWriter: UploadTaskPigeonCodecReaderWriter()) +} + +var uploadTaskPigeonMethodCodec = FlutterStandardMethodCodec(readerWriter: UploadTaskPigeonCodecReaderWriter()); + + +/// Generated protocol from Pigeon that represents a handler of messages from Flutter. +protocol UploadApi { + func initialize(completion: @escaping (Result) -> Void) + func refresh(completion: @escaping (Result) -> Void) + func cancelAll(completion: @escaping (Result) -> Void) + func enqueueAssets(localIds: [String], completion: @escaping (Result) -> Void) + func enqueueFiles(paths: [String], completion: @escaping (Result) -> Void) +} + +/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. +class UploadApiSetup { + static var codec: FlutterStandardMessageCodec { UploadTaskPigeonCodec.shared } + /// Sets up an instance of `UploadApi` to handle messages through the `binaryMessenger`. + static func setUp(binaryMessenger: FlutterBinaryMessenger, api: UploadApi?, messageChannelSuffix: String = "") { + let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : "" + let initializeChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.UploadApi.initialize\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + initializeChannel.setMessageHandler { _, reply in + api.initialize { result in + switch result { + case .success: + reply(wrapResult(nil)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + initializeChannel.setMessageHandler(nil) + } + let refreshChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.UploadApi.refresh\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + refreshChannel.setMessageHandler { _, reply in + api.refresh { result in + switch result { + case .success: + reply(wrapResult(nil)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + refreshChannel.setMessageHandler(nil) + } + let cancelAllChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.UploadApi.cancelAll\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + cancelAllChannel.setMessageHandler { _, reply in + api.cancelAll { result in + switch result { + case .success: + reply(wrapResult(nil)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + cancelAllChannel.setMessageHandler(nil) + } + let enqueueAssetsChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.UploadApi.enqueueAssets\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + enqueueAssetsChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let localIdsArg = args[0] as! [String] + api.enqueueAssets(localIds: localIdsArg) { result in + switch result { + case .success: + reply(wrapResult(nil)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + enqueueAssetsChannel.setMessageHandler(nil) + } + let enqueueFilesChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.UploadApi.enqueueFiles\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + enqueueFilesChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let pathsArg = args[0] as! [String] + api.enqueueFiles(paths: pathsArg) { result in + switch result { + case .success: + reply(wrapResult(nil)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + enqueueFilesChannel.setMessageHandler(nil) + } + } +} + +private class PigeonStreamHandler: NSObject, FlutterStreamHandler { + private let wrapper: PigeonEventChannelWrapper + private var pigeonSink: PigeonEventSink? = nil + + init(wrapper: PigeonEventChannelWrapper) { + self.wrapper = wrapper + } + + func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) + -> FlutterError? + { + pigeonSink = PigeonEventSink(events) + wrapper.onListen(withArguments: arguments, sink: pigeonSink!) + return nil + } + + func onCancel(withArguments arguments: Any?) -> FlutterError? { + pigeonSink = nil + wrapper.onCancel(withArguments: arguments) + return nil + } +} + +class PigeonEventChannelWrapper { + func onListen(withArguments arguments: Any?, sink: PigeonEventSink) {} + func onCancel(withArguments arguments: Any?) {} +} + +class PigeonEventSink { + private let sink: FlutterEventSink + + init(_ sink: @escaping FlutterEventSink) { + self.sink = sink + } + + func success(_ value: ReturnType) { + sink(value) + } + + func error(code: String, message: String?, details: Any?) { + sink(FlutterError(code: code, message: message, details: details)) + } + + func endOfStream() { + sink(FlutterEndOfEventStream) + } + +} + +class StreamStatusStreamHandler: PigeonEventChannelWrapper { + static func register(with messenger: FlutterBinaryMessenger, + instanceName: String = "", + streamHandler: StreamStatusStreamHandler) { + var channelName = "dev.flutter.pigeon.immich_mobile.UploadFlutterApi.streamStatus" + if !instanceName.isEmpty { + channelName += ".\(instanceName)" + } + let internalStreamHandler = PigeonStreamHandler(wrapper: streamHandler) + let channel = FlutterEventChannel(name: channelName, binaryMessenger: messenger, codec: uploadTaskPigeonMethodCodec) + channel.setStreamHandler(internalStreamHandler) + } +} + +class StreamProgressStreamHandler: PigeonEventChannelWrapper { + static func register(with messenger: FlutterBinaryMessenger, + instanceName: String = "", + streamHandler: StreamProgressStreamHandler) { + var channelName = "dev.flutter.pigeon.immich_mobile.UploadFlutterApi.streamProgress" + if !instanceName.isEmpty { + channelName += ".\(instanceName)" + } + let internalStreamHandler = PigeonStreamHandler(wrapper: streamHandler) + let channel = FlutterEventChannel(name: channelName, binaryMessenger: messenger, codec: uploadTaskPigeonMethodCodec) + channel.setStreamHandler(internalStreamHandler) + } +} + diff --git a/mobile/ios/Runner/Upload/UploadTask.swift b/mobile/ios/Runner/Upload/UploadTask.swift new file mode 100644 index 0000000000..41140c9824 --- /dev/null +++ b/mobile/ios/Runner/Upload/UploadTask.swift @@ -0,0 +1,271 @@ +import SQLiteData +import StructuredFieldValues + +extension FileHandle { + static func createOrOverwrite(atPath path: String) throws -> FileHandle { + let fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0o644) + guard fd >= 0 else { + throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno)) + } + return FileHandle(fileDescriptor: fd, closeOnDealloc: true) + } +} + +class UploadApiImpl: ImmichPlugin, UploadApi { + private let db: DatabasePool + private let downloadQueue: DownloadQueue + private let uploadQueue: UploadQueue + + private var isInitialized = false + private let initLock = NSLock() + + private var backupTask: Task? + private let backupLock = NSLock() + + private let cellularSession: URLSession + private let wifiOnlySession: URLSession + + init(statusListener: StatusEventListener, progressListener: ProgressEventListener) { + let dbUrl = try! FileManager.default.url( + for: .documentDirectory, + in: .userDomainMask, + appropriateFor: nil, + create: true + ).appendingPathComponent("immich.sqlite") + + self.db = try! DatabasePool(path: dbUrl.path) + let cellularConfig = URLSessionConfiguration.background(withIdentifier: "\(TaskConfig.sessionId).cellular") + cellularConfig.allowsCellularAccess = true + cellularConfig.waitsForConnectivity = true + let delegate = UploadApiDelegate(db: db, statusListener: statusListener, progressListener: progressListener) + self.cellularSession = URLSession(configuration: cellularConfig, delegate: delegate, delegateQueue: nil) + + let wifiOnlyConfig = URLSessionConfiguration.background(withIdentifier: "\(TaskConfig.sessionId).wifi") + wifiOnlyConfig.allowsCellularAccess = false + wifiOnlyConfig.waitsForConnectivity = true + self.wifiOnlySession = URLSession(configuration: wifiOnlyConfig, delegate: delegate, delegateQueue: nil) + + self.uploadQueue = UploadQueue( + db: db, + cellularSession: cellularSession, + wifiOnlySession: wifiOnlySession, + statusListener: statusListener + ) + self.downloadQueue = DownloadQueue( + db: db, + uploadQueue: uploadQueue, + statusListener: statusListener, + progressListener: progressListener + ) + delegate.downloadQueue = downloadQueue + delegate.uploadQueue = uploadQueue + } + + func initialize(completion: @escaping (Result) -> Void) { + Task(priority: .high) { + do { + async let dbIds = db.read { conn in + try UploadTask.select(\.id).where { $0.status.eq(TaskStatus.uploadQueued) }.fetchAll(conn) + } + async let cellularTasks = cellularSession.allTasks + async let wifiTasks = wifiOnlySession.allTasks + + var dbTaskIds = Set(try await dbIds) + func validateTasks(_ tasks: [URLSessionTask]) { + for task in tasks { + if let taskIdStr = task.taskDescription, let taskId = Int64(taskIdStr), task.state != .canceling { + dbTaskIds.remove(taskId) + } else { + task.cancel() + } + } + } + + validateTasks(await cellularTasks) + validateTasks(await wifiTasks) + + let orphanIds = Array(dbTaskIds) + try await db.write { conn in + try UploadTask.update { + $0.filePath = nil + $0.status = .downloadPending + } + .where { row in row.status.in([TaskStatus.downloadQueued, TaskStatus.uploadPending]) || row.id.in(orphanIds) } + .execute(conn) + } + + try? FileManager.default.removeItem(at: TaskConfig.originalsDir) + initLock.withLock { isInitialized = true } + startBackup() + self.completeWhenActive(for: completion, with: .success(())) + } catch { + self.completeWhenActive(for: completion, with: .failure(error)) + } + } + } + + func refresh(completion: @escaping (Result) -> Void) { + Task { + startBackup() + self.completeWhenActive(for: completion, with: .success(())) + } + } + + func startBackup() { + dPrint("Starting backup task") + guard (initLock.withLock { isInitialized }) else { return dPrint("Not initialized, skipping backup") } + + backupLock.withLock { + guard backupTask == nil else { return dPrint("Backup task already running") } + backupTask = Task { + await _startBackup() + backupLock.withLock { backupTask = nil } + } + } + } + + func cancelAll(completion: @escaping (Result) -> Void) { + Task { + async let cellularTasks = cellularSession.allTasks + async let wifiTasks = wifiOnlySession.allTasks + + cancelSessionTasks(await cellularTasks) + cancelSessionTasks(await wifiTasks) + self.completeWhenActive(for: completion, with: .success(())) + } + } + + func enqueueAssets(localIds: [String], completion: @escaping (Result) -> Void) { + Task { + do { + try await downloadQueue.enqueueAssets(localIds: localIds) + completion(.success(())) + } catch { + completion(.failure(error)) + } + } + } + + func enqueueFiles(paths: [String], completion: @escaping (Result) -> Void) { + Task { + do { + try await uploadQueue.enqueueFiles(paths: paths) + completion(.success(())) + } catch { + completion(.failure(error)) + } + } + } + + private func cancelSessionTasks(_ tasks: [URLSessionTask]) { + dPrint("Canceling \(tasks.count) tasks") + for task in tasks { + task.cancel() + } + } + + private func _startBackup() async { + defer { downloadQueue.startQueueProcessing() } + do { + let candidates = try await db.read { conn in + return try LocalAsset.getCandidates() + .where { asset in !UploadTask.where { task in task.localId.eq(asset.id) }.exists() } + .select { LocalAssetCandidate.Columns(id: $0.id, type: $0.type) } + .limit { _ in UploadTaskStat.availableSlots } + .fetchAll(conn) + } + + guard !candidates.isEmpty else { return dPrint("No candidates for backup") } + + try await db.write { conn in + var draft = UploadTask.Draft( + attempts: 0, + createdAt: Date(), + filePath: nil, + isLivePhoto: nil, + lastError: nil, + livePhotoVideoId: nil, + localId: "", + method: .multipart, + priority: 0.5, + retryAfter: nil, + status: .downloadPending, + ) + for candidate in candidates { + draft.localId = candidate.id + draft.priority = candidate.type == .image ? 0.5 : 0.3 + try UploadTask.insert { + draft + } onConflict: { + ($0.localId, $0.livePhotoVideoId) + } + .execute(conn) + } + } + dPrint("Backup enqueued \(candidates.count) assets for upload") + } catch { + print("Backup queue error: \(error)") + } + } +} + +struct AssetData: StructuredFieldValue { + static let structuredFieldType: StructuredFieldType = .dictionary + + let deviceAssetId: String + let deviceId: String + let fileCreatedAt: String + let fileModifiedAt: String + let fileName: String + let isFavorite: Bool + let livePhotoVideoId: String? + + static let boundary = "Boundary-\(UUID().uuidString)" + static let deviceAssetIdField = "--\(boundary)\r\nContent-Disposition: form-data; name=\"deviceAssetId\"\r\n\r\n" + .data(using: .utf8)! + static let deviceIdField = "\r\n--\(boundary)\r\nContent-Disposition: form-data; name=\"deviceId\"\r\n\r\n" + .data(using: .utf8)! + static let fileCreatedAtField = + "\r\n--\(boundary)\r\nContent-Disposition: form-data; name=\"fileCreatedAt\"\r\n\r\n" + .data(using: .utf8)! + static let fileModifiedAtField = + "\r\n--\(boundary)\r\nContent-Disposition: form-data; name=\"fileModifiedAt\"\r\n\r\n" + .data(using: .utf8)! + static let isFavoriteField = "\r\n--\(boundary)\r\nContent-Disposition: form-data; name=\"isFavorite\"\r\n\r\n" + .data(using: .utf8)! + static let livePhotoVideoIdField = + "\r\n--\(boundary)\r\nContent-Disposition: form-data; name=\"livePhotoVideoId\"\r\n\r\n" + .data(using: .utf8)! + static let trueData = "true".data(using: .utf8)! + static let falseData = "false".data(using: .utf8)! + static let footer = "\r\n--\(boundary)--\r\n".data(using: .utf8)! + static let contentType = "multipart/form-data; boundary=\(boundary)" + + func multipart() -> (Data, Data) { + var header = Data() + header.append(Self.deviceAssetIdField) + header.append(deviceAssetId.data(using: .utf8)!) + + header.append(Self.deviceIdField) + header.append(deviceId.data(using: .utf8)!) + + header.append(Self.fileCreatedAtField) + header.append(fileCreatedAt.data(using: .utf8)!) + + header.append(Self.fileModifiedAtField) + header.append(fileModifiedAt.data(using: .utf8)!) + + header.append(Self.isFavoriteField) + header.append(isFavorite ? Self.trueData : Self.falseData) + + if let livePhotoVideoId { + header.append(Self.livePhotoVideoIdField) + header.append(livePhotoVideoId.data(using: .utf8)!) + } + header.append( + "\r\n--\(Self.boundary)\r\nContent-Disposition: form-data; name=\"assetData\"; filename=\"\(fileName)\"\r\nContent-Type: application/octet-stream\r\n\r\n" + .data(using: .utf8)! + ) + return (header, Self.footer) + } +} diff --git a/mobile/ios/ShareExtension/Info.plist b/mobile/ios/ShareExtension/Info.plist index 0f52fbffdf..dbed75e380 100644 --- a/mobile/ios/ShareExtension/Info.plist +++ b/mobile/ios/ShareExtension/Info.plist @@ -1,35 +1,35 @@ - - AppGroupId - $(CUSTOM_GROUP_ID) - NSExtension - - NSExtensionAttributes - - IntentsSupported - - INSendMessageIntent - - NSExtensionActivationRule - SUBQUERY ( extensionItems, $extensionItem, SUBQUERY ( $extensionItem.attachments, + + AppGroupId + $(CUSTOM_GROUP_ID) + NSExtension + + NSExtensionAttributes + + IntentsSupported + + INSendMessageIntent + + NSExtensionActivationRule + SUBQUERY ( extensionItems, $extensionItem, SUBQUERY ( $extensionItem.attachments, $attachment, ( ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.file-url" || ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.image" || ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.text" || ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.movie" || ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.url" ) ).@count > 0 ).@count > 0 - PHSupportedMediaTypes - - Video - Image - - - NSExtensionMainStoryboard - MainInterface - NSExtensionPointIdentifier - com.apple.share-services - - - \ No newline at end of file + PHSupportedMediaTypes + + Video + Image + + + NSExtensionMainStoryboard + MainInterface + NSExtensionPointIdentifier + com.apple.share-services + + + diff --git a/mobile/ios/ShareExtension/ShareExtension.entitlements b/mobile/ios/ShareExtension/ShareExtension.entitlements index 4ad1a257d8..2eb7e333a6 100644 --- a/mobile/ios/ShareExtension/ShareExtension.entitlements +++ b/mobile/ios/ShareExtension/ShareExtension.entitlements @@ -3,8 +3,6 @@ com.apple.security.application-groups - - group.app.immich.share - + diff --git a/mobile/ios/WidgetExtension/WidgetExtension.entitlements b/mobile/ios/WidgetExtension/WidgetExtension.entitlements index 4ad1a257d8..2eb7e333a6 100644 --- a/mobile/ios/WidgetExtension/WidgetExtension.entitlements +++ b/mobile/ios/WidgetExtension/WidgetExtension.entitlements @@ -3,8 +3,6 @@ com.apple.security.application-groups - - group.app.immich.share - + diff --git a/mobile/lib/domain/services/background_worker.service.dart b/mobile/lib/domain/services/background_worker.service.dart index 8a237f801a..76e3899642 100644 --- a/mobile/lib/domain/services/background_worker.service.dart +++ b/mobile/lib/domain/services/background_worker.service.dart @@ -1,15 +1,11 @@ import 'dart:async'; -import 'dart:io'; import 'dart:ui'; -import 'package:background_downloader/background_downloader.dart'; import 'package:cancellation_token_http/http.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/constants/constants.dart'; import 'package:immich_mobile/domain/services/log.service.dart'; import 'package:immich_mobile/entities/store.entity.dart'; -import 'package:immich_mobile/extensions/network_capability_extensions.dart'; import 'package:immich_mobile/extensions/platform_extensions.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/logger_db.repository.dart'; @@ -17,16 +13,13 @@ 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/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/background_sync.provider.dart'; -import 'package:immich_mobile/providers/backup/drift_backup.provider.dart'; import 'package:immich_mobile/providers/db.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/user.provider.dart'; import 'package:immich_mobile/repositories/file_media.repository.dart'; import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/services/auth.service.dart'; import 'package:immich_mobile/services/localization.service.dart'; -import 'package:immich_mobile/services/upload.service.dart'; import 'package:immich_mobile/utils/bootstrap.dart'; import 'package:immich_mobile/utils/debug_print.dart'; import 'package:immich_mobile/utils/http_ssl_options.dart'; @@ -96,23 +89,10 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi { loadTranslations(), workerManagerPatch.init(dynamicSpawning: true), _ref?.read(authServiceProvider).setOpenApiServiceEndpoint(), - // Initialize the file downloader - FileDownloader().configure( - globalConfig: [ - // maxConcurrent: 6, maxConcurrentByHost(server):6, maxConcurrentByGroup: 3 - (Config.holdingQueue, (6, 6, 3)), - // On Android, if files are larger than 256MB, run in foreground service - (Config.runInForegroundIfFileLargerThan, 256), - ], - ), - FileDownloader().trackTasksInGroup(kDownloadGroupLivePhoto, markDownloadedComplete: false), - FileDownloader().trackTasks(), _ref?.read(fileMediaRepositoryProvider).enableBackgroundAccess(), ].nonNulls, ); - configureFileDownloaderNotifications(); - // Notify the host that the background worker service has been initialized and is ready to use unawaited(_backgroundHostApi.onInitialized()); } catch (error, stack) { @@ -130,7 +110,7 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi { _logger.warning("Remote sync did not complete successfully, skipping backup"); return; } - await _handleBackup(); + await uploadApi.refresh(); } catch (error, stack) { _logger.severe("Failed to complete Android background processing", error, stack); } finally { @@ -150,13 +130,7 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi { _logger.warning("Remote sync did not complete successfully, skipping backup"); return; } - - final backupFuture = _handleBackup(); - if (maxSeconds != null) { - await backupFuture.timeout(Duration(seconds: maxSeconds - 1), onTimeout: () {}); - } else { - await backupFuture; - } + await uploadApi.refresh(); } catch (error, stack) { _logger.severe("Failed to complete iOS background upload", error, stack); } finally { @@ -224,39 +198,6 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi { } } - Future _handleBackup() async { - await runZonedGuarded( - () async { - if (_isCleanedUp) { - return; - } - - if (!_isBackupEnabled) { - _logger.info("Backup is disabled. Skipping backup routine"); - return; - } - - final currentUser = _ref?.read(currentUserProvider); - if (currentUser == null) { - _logger.warning("No current user found. Skipping backup from background"); - return; - } - - if (Platform.isIOS) { - return _ref?.read(driftBackupProvider.notifier).handleBackupResume(currentUser.id); - } - - final networkCapabilities = await _ref?.read(connectivityApiProvider).getCapabilities() ?? []; - return _ref - ?.read(uploadServiceProvider) - .startBackupWithHttpClient(currentUser.id, networkCapabilities.isUnmetered, _cancellationToken); - }, - (error, stack) { - dPrint(() => "Error in backup zone $error, $stack"); - }, - ); - } - Future _syncAssets({Duration? hashTimeout}) async { await _ref?.read(backgroundSyncProvider).syncLocal(); if (_isCleanedUp) { diff --git a/mobile/lib/infrastructure/entities/upload_task.entity.dart b/mobile/lib/infrastructure/entities/upload_task.entity.dart new file mode 100644 index 0000000000..378d76ed11 --- /dev/null +++ b/mobile/lib/infrastructure/entities/upload_task.entity.dart @@ -0,0 +1,23 @@ +import 'package:drift/drift.dart' hide Index; + +@TableIndex.sql('CREATE INDEX IF NOT EXISTS idx_upload_tasks_local_id ON upload_task_entity(local_id);') +@TableIndex.sql('CREATE INDEX idx_upload_tasks_asset_data ON upload_task_entity(status, priority DESC, created_at);') +class UploadTaskEntity extends Table { + const UploadTaskEntity(); + + IntColumn get id => integer().autoIncrement()(); + IntColumn get attempts => integer()(); + DateTimeColumn get createdAt => dateTime()(); + TextColumn get filePath => text()(); + BoolColumn get isLivePhoto => boolean().nullable()(); + IntColumn get lastError => integer().nullable()(); + TextColumn get livePhotoVideoId => text().nullable()(); + TextColumn get localId => text()(); + IntColumn get method => integer()(); + RealColumn get priority => real()(); + DateTimeColumn get retryAfter => dateTime().nullable()(); + IntColumn get status => integer()(); + + @override + bool get isStrict => true; +} diff --git a/mobile/lib/infrastructure/entities/upload_task.entity.drift.dart b/mobile/lib/infrastructure/entities/upload_task.entity.drift.dart new file mode 100644 index 0000000000..c8d675fdc7 --- /dev/null +++ b/mobile/lib/infrastructure/entities/upload_task.entity.drift.dart @@ -0,0 +1,1063 @@ +// dart format width=80 +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:immich_mobile/infrastructure/entities/upload_task.entity.drift.dart' + as i1; +import 'package:immich_mobile/infrastructure/entities/upload_task.entity.dart' + as i2; + +typedef $$UploadTaskEntityTableCreateCompanionBuilder = + i1.UploadTaskEntityCompanion Function({ + i0.Value id, + required int attempts, + required DateTime createdAt, + required String filePath, + i0.Value isLivePhoto, + i0.Value lastError, + i0.Value livePhotoVideoId, + required String localId, + required int method, + required double priority, + i0.Value retryAfter, + required int status, + }); +typedef $$UploadTaskEntityTableUpdateCompanionBuilder = + i1.UploadTaskEntityCompanion Function({ + i0.Value id, + i0.Value attempts, + i0.Value createdAt, + i0.Value filePath, + i0.Value isLivePhoto, + i0.Value lastError, + i0.Value livePhotoVideoId, + i0.Value localId, + i0.Value method, + i0.Value priority, + i0.Value retryAfter, + i0.Value status, + }); + +class $$UploadTaskEntityTableFilterComposer + extends i0.Composer { + $$UploadTaskEntityTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get attempts => $composableBuilder( + column: $table.attempts, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get filePath => $composableBuilder( + column: $table.filePath, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get isLivePhoto => $composableBuilder( + column: $table.isLivePhoto, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get lastError => $composableBuilder( + column: $table.lastError, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get livePhotoVideoId => $composableBuilder( + column: $table.livePhotoVideoId, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get localId => $composableBuilder( + column: $table.localId, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get method => $composableBuilder( + column: $table.method, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get priority => $composableBuilder( + column: $table.priority, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get retryAfter => $composableBuilder( + column: $table.retryAfter, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get status => $composableBuilder( + column: $table.status, + builder: (column) => i0.ColumnFilters(column), + ); +} + +class $$UploadTaskEntityTableOrderingComposer + extends i0.Composer { + $$UploadTaskEntityTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get attempts => $composableBuilder( + column: $table.attempts, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get filePath => $composableBuilder( + column: $table.filePath, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get isLivePhoto => $composableBuilder( + column: $table.isLivePhoto, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get lastError => $composableBuilder( + column: $table.lastError, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get livePhotoVideoId => $composableBuilder( + column: $table.livePhotoVideoId, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get localId => $composableBuilder( + column: $table.localId, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get method => $composableBuilder( + column: $table.method, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get priority => $composableBuilder( + column: $table.priority, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get retryAfter => $composableBuilder( + column: $table.retryAfter, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get status => $composableBuilder( + column: $table.status, + builder: (column) => i0.ColumnOrderings(column), + ); +} + +class $$UploadTaskEntityTableAnnotationComposer + extends i0.Composer { + $$UploadTaskEntityTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + i0.GeneratedColumn get attempts => + $composableBuilder(column: $table.attempts, builder: (column) => column); + + i0.GeneratedColumn get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => column); + + i0.GeneratedColumn get filePath => + $composableBuilder(column: $table.filePath, builder: (column) => column); + + i0.GeneratedColumn get isLivePhoto => $composableBuilder( + column: $table.isLivePhoto, + builder: (column) => column, + ); + + i0.GeneratedColumn get lastError => + $composableBuilder(column: $table.lastError, builder: (column) => column); + + i0.GeneratedColumn get livePhotoVideoId => $composableBuilder( + column: $table.livePhotoVideoId, + builder: (column) => column, + ); + + i0.GeneratedColumn get localId => + $composableBuilder(column: $table.localId, builder: (column) => column); + + i0.GeneratedColumn get method => + $composableBuilder(column: $table.method, builder: (column) => column); + + i0.GeneratedColumn get priority => + $composableBuilder(column: $table.priority, builder: (column) => column); + + i0.GeneratedColumn get retryAfter => $composableBuilder( + column: $table.retryAfter, + builder: (column) => column, + ); + + i0.GeneratedColumn get status => + $composableBuilder(column: $table.status, builder: (column) => column); +} + +class $$UploadTaskEntityTableTableManager + extends + i0.RootTableManager< + i0.GeneratedDatabase, + i1.$UploadTaskEntityTable, + i1.UploadTaskEntityData, + i1.$$UploadTaskEntityTableFilterComposer, + i1.$$UploadTaskEntityTableOrderingComposer, + i1.$$UploadTaskEntityTableAnnotationComposer, + $$UploadTaskEntityTableCreateCompanionBuilder, + $$UploadTaskEntityTableUpdateCompanionBuilder, + ( + i1.UploadTaskEntityData, + i0.BaseReferences< + i0.GeneratedDatabase, + i1.$UploadTaskEntityTable, + i1.UploadTaskEntityData + >, + ), + i1.UploadTaskEntityData, + i0.PrefetchHooks Function() + > { + $$UploadTaskEntityTableTableManager( + i0.GeneratedDatabase db, + i1.$UploadTaskEntityTable table, + ) : super( + i0.TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + i1.$$UploadTaskEntityTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => i1 + .$$UploadTaskEntityTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + i1.$$UploadTaskEntityTableAnnotationComposer( + $db: db, + $table: table, + ), + updateCompanionCallback: + ({ + i0.Value id = const i0.Value.absent(), + i0.Value attempts = const i0.Value.absent(), + i0.Value createdAt = const i0.Value.absent(), + i0.Value filePath = const i0.Value.absent(), + i0.Value isLivePhoto = const i0.Value.absent(), + i0.Value lastError = const i0.Value.absent(), + i0.Value livePhotoVideoId = const i0.Value.absent(), + i0.Value localId = const i0.Value.absent(), + i0.Value method = const i0.Value.absent(), + i0.Value priority = const i0.Value.absent(), + i0.Value retryAfter = const i0.Value.absent(), + i0.Value status = const i0.Value.absent(), + }) => i1.UploadTaskEntityCompanion( + id: id, + attempts: attempts, + createdAt: createdAt, + filePath: filePath, + isLivePhoto: isLivePhoto, + lastError: lastError, + livePhotoVideoId: livePhotoVideoId, + localId: localId, + method: method, + priority: priority, + retryAfter: retryAfter, + status: status, + ), + createCompanionCallback: + ({ + i0.Value id = const i0.Value.absent(), + required int attempts, + required DateTime createdAt, + required String filePath, + i0.Value isLivePhoto = const i0.Value.absent(), + i0.Value lastError = const i0.Value.absent(), + i0.Value livePhotoVideoId = const i0.Value.absent(), + required String localId, + required int method, + required double priority, + i0.Value retryAfter = const i0.Value.absent(), + required int status, + }) => i1.UploadTaskEntityCompanion.insert( + id: id, + attempts: attempts, + createdAt: createdAt, + filePath: filePath, + isLivePhoto: isLivePhoto, + lastError: lastError, + livePhotoVideoId: livePhotoVideoId, + localId: localId, + method: method, + priority: priority, + retryAfter: retryAfter, + status: status, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + ), + ); +} + +typedef $$UploadTaskEntityTableProcessedTableManager = + i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.$UploadTaskEntityTable, + i1.UploadTaskEntityData, + i1.$$UploadTaskEntityTableFilterComposer, + i1.$$UploadTaskEntityTableOrderingComposer, + i1.$$UploadTaskEntityTableAnnotationComposer, + $$UploadTaskEntityTableCreateCompanionBuilder, + $$UploadTaskEntityTableUpdateCompanionBuilder, + ( + i1.UploadTaskEntityData, + i0.BaseReferences< + i0.GeneratedDatabase, + i1.$UploadTaskEntityTable, + i1.UploadTaskEntityData + >, + ), + i1.UploadTaskEntityData, + i0.PrefetchHooks Function() + >; +i0.Index get idxUploadTasksLocalId => i0.Index( + 'idx_upload_tasks_local_id', + 'CREATE INDEX IF NOT EXISTS idx_upload_tasks_local_id ON upload_task_entity (local_id)', +); + +class $UploadTaskEntityTable extends i2.UploadTaskEntity + with i0.TableInfo<$UploadTaskEntityTable, i1.UploadTaskEntityData> { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + $UploadTaskEntityTable(this.attachedDatabase, [this._alias]); + static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id'); + @override + late final i0.GeneratedColumn id = i0.GeneratedColumn( + 'id', + aliasedName, + false, + hasAutoIncrement: true, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: i0.GeneratedColumn.constraintIsAlways( + 'PRIMARY KEY AUTOINCREMENT', + ), + ); + static const i0.VerificationMeta _attemptsMeta = const i0.VerificationMeta( + 'attempts', + ); + @override + late final i0.GeneratedColumn attempts = i0.GeneratedColumn( + 'attempts', + aliasedName, + false, + type: i0.DriftSqlType.int, + requiredDuringInsert: true, + ); + 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: true, + ); + static const i0.VerificationMeta _filePathMeta = const i0.VerificationMeta( + 'filePath', + ); + @override + late final i0.GeneratedColumn filePath = i0.GeneratedColumn( + 'file_path', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + ); + static const i0.VerificationMeta _isLivePhotoMeta = const i0.VerificationMeta( + 'isLivePhoto', + ); + @override + late final i0.GeneratedColumn isLivePhoto = i0.GeneratedColumn( + 'is_live_photo', + aliasedName, + true, + type: i0.DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: i0.GeneratedColumn.constraintIsAlways( + 'CHECK ("is_live_photo" IN (0, 1))', + ), + ); + static const i0.VerificationMeta _lastErrorMeta = const i0.VerificationMeta( + 'lastError', + ); + @override + late final i0.GeneratedColumn lastError = i0.GeneratedColumn( + 'last_error', + aliasedName, + true, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _livePhotoVideoIdMeta = + const i0.VerificationMeta('livePhotoVideoId'); + @override + late final i0.GeneratedColumn livePhotoVideoId = + i0.GeneratedColumn( + 'live_photo_video_id', + aliasedName, + true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _localIdMeta = const i0.VerificationMeta( + 'localId', + ); + @override + late final i0.GeneratedColumn localId = i0.GeneratedColumn( + 'local_id', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + ); + static const i0.VerificationMeta _methodMeta = const i0.VerificationMeta( + 'method', + ); + @override + late final i0.GeneratedColumn method = i0.GeneratedColumn( + 'method', + aliasedName, + false, + type: i0.DriftSqlType.int, + requiredDuringInsert: true, + ); + static const i0.VerificationMeta _priorityMeta = const i0.VerificationMeta( + 'priority', + ); + @override + late final i0.GeneratedColumn priority = i0.GeneratedColumn( + 'priority', + aliasedName, + false, + type: i0.DriftSqlType.double, + requiredDuringInsert: true, + ); + static const i0.VerificationMeta _retryAfterMeta = const i0.VerificationMeta( + 'retryAfter', + ); + @override + late final i0.GeneratedColumn retryAfter = + i0.GeneratedColumn( + 'retry_after', + aliasedName, + true, + type: i0.DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _statusMeta = const i0.VerificationMeta( + 'status', + ); + @override + late final i0.GeneratedColumn status = i0.GeneratedColumn( + 'status', + aliasedName, + false, + type: i0.DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + attempts, + createdAt, + filePath, + isLivePhoto, + lastError, + livePhotoVideoId, + localId, + method, + priority, + retryAfter, + status, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'upload_task_entity'; + @override + i0.VerificationContext validateIntegrity( + i0.Insertable instance, { + bool isInserting = false, + }) { + final context = i0.VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('attempts')) { + context.handle( + _attemptsMeta, + attempts.isAcceptableOrUnknown(data['attempts']!, _attemptsMeta), + ); + } else if (isInserting) { + context.missing(_attemptsMeta); + } + if (data.containsKey('created_at')) { + context.handle( + _createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta), + ); + } else if (isInserting) { + context.missing(_createdAtMeta); + } + if (data.containsKey('file_path')) { + context.handle( + _filePathMeta, + filePath.isAcceptableOrUnknown(data['file_path']!, _filePathMeta), + ); + } else if (isInserting) { + context.missing(_filePathMeta); + } + if (data.containsKey('is_live_photo')) { + context.handle( + _isLivePhotoMeta, + isLivePhoto.isAcceptableOrUnknown( + data['is_live_photo']!, + _isLivePhotoMeta, + ), + ); + } + if (data.containsKey('last_error')) { + context.handle( + _lastErrorMeta, + lastError.isAcceptableOrUnknown(data['last_error']!, _lastErrorMeta), + ); + } + if (data.containsKey('live_photo_video_id')) { + context.handle( + _livePhotoVideoIdMeta, + livePhotoVideoId.isAcceptableOrUnknown( + data['live_photo_video_id']!, + _livePhotoVideoIdMeta, + ), + ); + } + if (data.containsKey('local_id')) { + context.handle( + _localIdMeta, + localId.isAcceptableOrUnknown(data['local_id']!, _localIdMeta), + ); + } else if (isInserting) { + context.missing(_localIdMeta); + } + if (data.containsKey('method')) { + context.handle( + _methodMeta, + method.isAcceptableOrUnknown(data['method']!, _methodMeta), + ); + } else if (isInserting) { + context.missing(_methodMeta); + } + if (data.containsKey('priority')) { + context.handle( + _priorityMeta, + priority.isAcceptableOrUnknown(data['priority']!, _priorityMeta), + ); + } else if (isInserting) { + context.missing(_priorityMeta); + } + if (data.containsKey('retry_after')) { + context.handle( + _retryAfterMeta, + retryAfter.isAcceptableOrUnknown(data['retry_after']!, _retryAfterMeta), + ); + } + if (data.containsKey('status')) { + context.handle( + _statusMeta, + status.isAcceptableOrUnknown(data['status']!, _statusMeta), + ); + } else if (isInserting) { + context.missing(_statusMeta); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + i1.UploadTaskEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.UploadTaskEntityData( + id: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + attempts: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}attempts'], + )!, + createdAt: attachedDatabase.typeMapping.read( + i0.DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + filePath: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}file_path'], + )!, + isLivePhoto: attachedDatabase.typeMapping.read( + i0.DriftSqlType.bool, + data['${effectivePrefix}is_live_photo'], + ), + lastError: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}last_error'], + ), + livePhotoVideoId: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}live_photo_video_id'], + ), + localId: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}local_id'], + )!, + method: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}method'], + )!, + priority: attachedDatabase.typeMapping.read( + i0.DriftSqlType.double, + data['${effectivePrefix}priority'], + )!, + retryAfter: attachedDatabase.typeMapping.read( + i0.DriftSqlType.dateTime, + data['${effectivePrefix}retry_after'], + ), + status: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}status'], + )!, + ); + } + + @override + $UploadTaskEntityTable createAlias(String alias) { + return $UploadTaskEntityTable(attachedDatabase, alias); + } + + @override + bool get isStrict => true; +} + +class UploadTaskEntityData extends i0.DataClass + implements i0.Insertable { + final int id; + final int attempts; + final DateTime createdAt; + final String filePath; + final bool? isLivePhoto; + final int? lastError; + final String? livePhotoVideoId; + final String localId; + final int method; + final double priority; + final DateTime? retryAfter; + final int status; + const UploadTaskEntityData({ + required this.id, + required this.attempts, + required this.createdAt, + required this.filePath, + this.isLivePhoto, + this.lastError, + this.livePhotoVideoId, + required this.localId, + required this.method, + required this.priority, + this.retryAfter, + required this.status, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = i0.Variable(id); + map['attempts'] = i0.Variable(attempts); + map['created_at'] = i0.Variable(createdAt); + map['file_path'] = i0.Variable(filePath); + if (!nullToAbsent || isLivePhoto != null) { + map['is_live_photo'] = i0.Variable(isLivePhoto); + } + if (!nullToAbsent || lastError != null) { + map['last_error'] = i0.Variable(lastError); + } + if (!nullToAbsent || livePhotoVideoId != null) { + map['live_photo_video_id'] = i0.Variable(livePhotoVideoId); + } + map['local_id'] = i0.Variable(localId); + map['method'] = i0.Variable(method); + map['priority'] = i0.Variable(priority); + if (!nullToAbsent || retryAfter != null) { + map['retry_after'] = i0.Variable(retryAfter); + } + map['status'] = i0.Variable(status); + return map; + } + + factory UploadTaskEntityData.fromJson( + Map json, { + i0.ValueSerializer? serializer, + }) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return UploadTaskEntityData( + id: serializer.fromJson(json['id']), + attempts: serializer.fromJson(json['attempts']), + createdAt: serializer.fromJson(json['createdAt']), + filePath: serializer.fromJson(json['filePath']), + isLivePhoto: serializer.fromJson(json['isLivePhoto']), + lastError: serializer.fromJson(json['lastError']), + livePhotoVideoId: serializer.fromJson(json['livePhotoVideoId']), + localId: serializer.fromJson(json['localId']), + method: serializer.fromJson(json['method']), + priority: serializer.fromJson(json['priority']), + retryAfter: serializer.fromJson(json['retryAfter']), + status: serializer.fromJson(json['status']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'attempts': serializer.toJson(attempts), + 'createdAt': serializer.toJson(createdAt), + 'filePath': serializer.toJson(filePath), + 'isLivePhoto': serializer.toJson(isLivePhoto), + 'lastError': serializer.toJson(lastError), + 'livePhotoVideoId': serializer.toJson(livePhotoVideoId), + 'localId': serializer.toJson(localId), + 'method': serializer.toJson(method), + 'priority': serializer.toJson(priority), + 'retryAfter': serializer.toJson(retryAfter), + 'status': serializer.toJson(status), + }; + } + + i1.UploadTaskEntityData copyWith({ + int? id, + int? attempts, + DateTime? createdAt, + String? filePath, + i0.Value isLivePhoto = const i0.Value.absent(), + i0.Value lastError = const i0.Value.absent(), + i0.Value livePhotoVideoId = const i0.Value.absent(), + String? localId, + int? method, + double? priority, + i0.Value retryAfter = const i0.Value.absent(), + int? status, + }) => i1.UploadTaskEntityData( + id: id ?? this.id, + attempts: attempts ?? this.attempts, + createdAt: createdAt ?? this.createdAt, + filePath: filePath ?? this.filePath, + isLivePhoto: isLivePhoto.present ? isLivePhoto.value : this.isLivePhoto, + lastError: lastError.present ? lastError.value : this.lastError, + livePhotoVideoId: livePhotoVideoId.present + ? livePhotoVideoId.value + : this.livePhotoVideoId, + localId: localId ?? this.localId, + method: method ?? this.method, + priority: priority ?? this.priority, + retryAfter: retryAfter.present ? retryAfter.value : this.retryAfter, + status: status ?? this.status, + ); + UploadTaskEntityData copyWithCompanion(i1.UploadTaskEntityCompanion data) { + return UploadTaskEntityData( + id: data.id.present ? data.id.value : this.id, + attempts: data.attempts.present ? data.attempts.value : this.attempts, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + filePath: data.filePath.present ? data.filePath.value : this.filePath, + isLivePhoto: data.isLivePhoto.present + ? data.isLivePhoto.value + : this.isLivePhoto, + lastError: data.lastError.present ? data.lastError.value : this.lastError, + livePhotoVideoId: data.livePhotoVideoId.present + ? data.livePhotoVideoId.value + : this.livePhotoVideoId, + localId: data.localId.present ? data.localId.value : this.localId, + method: data.method.present ? data.method.value : this.method, + priority: data.priority.present ? data.priority.value : this.priority, + retryAfter: data.retryAfter.present + ? data.retryAfter.value + : this.retryAfter, + status: data.status.present ? data.status.value : this.status, + ); + } + + @override + String toString() { + return (StringBuffer('UploadTaskEntityData(') + ..write('id: $id, ') + ..write('attempts: $attempts, ') + ..write('createdAt: $createdAt, ') + ..write('filePath: $filePath, ') + ..write('isLivePhoto: $isLivePhoto, ') + ..write('lastError: $lastError, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('localId: $localId, ') + ..write('method: $method, ') + ..write('priority: $priority, ') + ..write('retryAfter: $retryAfter, ') + ..write('status: $status') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + attempts, + createdAt, + filePath, + isLivePhoto, + lastError, + livePhotoVideoId, + localId, + method, + priority, + retryAfter, + status, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.UploadTaskEntityData && + other.id == this.id && + other.attempts == this.attempts && + other.createdAt == this.createdAt && + other.filePath == this.filePath && + other.isLivePhoto == this.isLivePhoto && + other.lastError == this.lastError && + other.livePhotoVideoId == this.livePhotoVideoId && + other.localId == this.localId && + other.method == this.method && + other.priority == this.priority && + other.retryAfter == this.retryAfter && + other.status == this.status); +} + +class UploadTaskEntityCompanion + extends i0.UpdateCompanion { + final i0.Value id; + final i0.Value attempts; + final i0.Value createdAt; + final i0.Value filePath; + final i0.Value isLivePhoto; + final i0.Value lastError; + final i0.Value livePhotoVideoId; + final i0.Value localId; + final i0.Value method; + final i0.Value priority; + final i0.Value retryAfter; + final i0.Value status; + const UploadTaskEntityCompanion({ + this.id = const i0.Value.absent(), + this.attempts = const i0.Value.absent(), + this.createdAt = const i0.Value.absent(), + this.filePath = const i0.Value.absent(), + this.isLivePhoto = const i0.Value.absent(), + this.lastError = const i0.Value.absent(), + this.livePhotoVideoId = const i0.Value.absent(), + this.localId = const i0.Value.absent(), + this.method = const i0.Value.absent(), + this.priority = const i0.Value.absent(), + this.retryAfter = const i0.Value.absent(), + this.status = const i0.Value.absent(), + }); + UploadTaskEntityCompanion.insert({ + this.id = const i0.Value.absent(), + required int attempts, + required DateTime createdAt, + required String filePath, + this.isLivePhoto = const i0.Value.absent(), + this.lastError = const i0.Value.absent(), + this.livePhotoVideoId = const i0.Value.absent(), + required String localId, + required int method, + required double priority, + this.retryAfter = const i0.Value.absent(), + required int status, + }) : attempts = i0.Value(attempts), + createdAt = i0.Value(createdAt), + filePath = i0.Value(filePath), + localId = i0.Value(localId), + method = i0.Value(method), + priority = i0.Value(priority), + status = i0.Value(status); + static i0.Insertable custom({ + i0.Expression? id, + i0.Expression? attempts, + i0.Expression? createdAt, + i0.Expression? filePath, + i0.Expression? isLivePhoto, + i0.Expression? lastError, + i0.Expression? livePhotoVideoId, + i0.Expression? localId, + i0.Expression? method, + i0.Expression? priority, + i0.Expression? retryAfter, + i0.Expression? status, + }) { + return i0.RawValuesInsertable({ + if (id != null) 'id': id, + if (attempts != null) 'attempts': attempts, + if (createdAt != null) 'created_at': createdAt, + if (filePath != null) 'file_path': filePath, + if (isLivePhoto != null) 'is_live_photo': isLivePhoto, + if (lastError != null) 'last_error': lastError, + if (livePhotoVideoId != null) 'live_photo_video_id': livePhotoVideoId, + if (localId != null) 'local_id': localId, + if (method != null) 'method': method, + if (priority != null) 'priority': priority, + if (retryAfter != null) 'retry_after': retryAfter, + if (status != null) 'status': status, + }); + } + + i1.UploadTaskEntityCompanion copyWith({ + i0.Value? id, + i0.Value? attempts, + i0.Value? createdAt, + i0.Value? filePath, + i0.Value? isLivePhoto, + i0.Value? lastError, + i0.Value? livePhotoVideoId, + i0.Value? localId, + i0.Value? method, + i0.Value? priority, + i0.Value? retryAfter, + i0.Value? status, + }) { + return i1.UploadTaskEntityCompanion( + id: id ?? this.id, + attempts: attempts ?? this.attempts, + createdAt: createdAt ?? this.createdAt, + filePath: filePath ?? this.filePath, + isLivePhoto: isLivePhoto ?? this.isLivePhoto, + lastError: lastError ?? this.lastError, + livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId, + localId: localId ?? this.localId, + method: method ?? this.method, + priority: priority ?? this.priority, + retryAfter: retryAfter ?? this.retryAfter, + status: status ?? this.status, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = i0.Variable(id.value); + } + if (attempts.present) { + map['attempts'] = i0.Variable(attempts.value); + } + if (createdAt.present) { + map['created_at'] = i0.Variable(createdAt.value); + } + if (filePath.present) { + map['file_path'] = i0.Variable(filePath.value); + } + if (isLivePhoto.present) { + map['is_live_photo'] = i0.Variable(isLivePhoto.value); + } + if (lastError.present) { + map['last_error'] = i0.Variable(lastError.value); + } + if (livePhotoVideoId.present) { + map['live_photo_video_id'] = i0.Variable(livePhotoVideoId.value); + } + if (localId.present) { + map['local_id'] = i0.Variable(localId.value); + } + if (method.present) { + map['method'] = i0.Variable(method.value); + } + if (priority.present) { + map['priority'] = i0.Variable(priority.value); + } + if (retryAfter.present) { + map['retry_after'] = i0.Variable(retryAfter.value); + } + if (status.present) { + map['status'] = i0.Variable(status.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UploadTaskEntityCompanion(') + ..write('id: $id, ') + ..write('attempts: $attempts, ') + ..write('createdAt: $createdAt, ') + ..write('filePath: $filePath, ') + ..write('isLivePhoto: $isLivePhoto, ') + ..write('lastError: $lastError, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('localId: $localId, ') + ..write('method: $method, ') + ..write('priority: $priority, ') + ..write('retryAfter: $retryAfter, ') + ..write('status: $status') + ..write(')')) + .toString(); + } +} + +i0.Index get idxUploadTasksAssetData => i0.Index( + 'idx_upload_tasks_asset_data', + 'CREATE INDEX idx_upload_tasks_asset_data ON upload_task_entity (status, priority DESC, created_at)', +); diff --git a/mobile/lib/infrastructure/entities/upload_tasks.drift b/mobile/lib/infrastructure/entities/upload_tasks.drift new file mode 100644 index 0000000000..2a99f020a4 --- /dev/null +++ b/mobile/lib/infrastructure/entities/upload_tasks.drift @@ -0,0 +1,78 @@ +CREATE TABLE upload_tasks +( + id integer primary key autoincrement, + attempts integer not null, + created_at integer not null, + file_path text, + is_live_photo integer, + last_error integer, + live_photo_video_id text, + local_id text not null, + method integer not null, + priority real not null, + retry_after integer, + status integer not null +); + +CREATE TABLE upload_task_stats +( + pending_downloads integer not null, + pending_uploads integer not null, + queued_downloads integer not null, + queued_uploads integer not null, + failed_downloads integer not null, + failed_uploads integer not null, + completed_uploads integer not null, + skipped_uploads integer not null +); + +CREATE TRIGGER update_stats_insert + BEFORE INSERT + ON upload_tasks +BEGIN + UPDATE upload_task_stats + SET pending_downloads = pending_downloads + (NEW.status = 0), + queued_downloads = queued_downloads + (NEW.status = 1), + failed_downloads = failed_downloads + (NEW.status = 2), + pending_uploads = pending_uploads + (NEW.status = 3), + queued_uploads = queued_uploads + (NEW.status = 4), + failed_uploads = failed_uploads + (NEW.status = 5), + completed_uploads = completed_uploads + (NEW.status = 6), + skipped_uploads = skipped_uploads + (NEW.status = 7); +END; + +CREATE TRIGGER update_stats_update + BEFORE UPDATE OF status + ON upload_tasks + WHEN OLD.status != NEW.status +BEGIN + UPDATE upload_task_stats + SET pending_downloads = pending_downloads - (OLD.status = 0) + (NEW.status = 0), + queued_downloads = queued_downloads - (OLD.status = 1) + (NEW.status = 1), + failed_downloads = failed_downloads - (OLD.status = 2) + (NEW.status = 2), + pending_uploads = pending_uploads - (OLD.status = 3) + (NEW.status = 3), + queued_uploads = queued_uploads - (OLD.status = 4) + (NEW.status = 4), + failed_uploads = failed_uploads - (OLD.status = 5) + (NEW.status = 5), + completed_uploads = completed_uploads - (OLD.status = 6) + (NEW.status = 6), + skipped_uploads = skipped_uploads - (OLD.status = 7) + (NEW.status = 7); +END; + +CREATE TRIGGER update_stats_delete + BEFORE DELETE + ON upload_tasks +BEGIN + UPDATE upload_task_stats + SET pending_downloads = pending_downloads - (OLD.status = 0), + queued_downloads = queued_downloads - (OLD.status = 1), + failed_downloads = failed_downloads - (OLD.status = 2), + pending_uploads = pending_uploads - (OLD.status = 3), + queued_uploads = queued_uploads - (OLD.status = 4), + failed_uploads = failed_uploads - (OLD.status = 5), + completed_uploads = completed_uploads - (OLD.status = 6), + skipped_uploads = skipped_uploads - (OLD.status = 7); +END; + +CREATE UNIQUE INDEX idx_upload_tasks_local_id ON upload_tasks (local_id, live_photo_video_id); +CREATE INDEX idx_upload_tasks_asset_data ON upload_tasks (status, priority DESC, created_at); + +@create: INSERT INTO upload_task_stats VALUES (0, 0, 0, 0, 0, 0, 0, 0); diff --git a/mobile/lib/infrastructure/entities/upload_tasks.drift.dart b/mobile/lib/infrastructure/entities/upload_tasks.drift.dart new file mode 100644 index 0000000000..16f555b2c8 --- /dev/null +++ b/mobile/lib/infrastructure/entities/upload_tasks.drift.dart @@ -0,0 +1,1888 @@ +// dart format width=80 +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:immich_mobile/infrastructure/entities/upload_tasks.drift.dart' + as i1; +import 'package:drift/internal/modular.dart' as i2; + +typedef $UploadTasksCreateCompanionBuilder = + i1.UploadTasksCompanion Function({ + i0.Value id, + required int attempts, + required int createdAt, + i0.Value filePath, + i0.Value isLivePhoto, + i0.Value lastError, + i0.Value livePhotoVideoId, + required String localId, + required int method, + required double priority, + i0.Value retryAfter, + required int status, + }); +typedef $UploadTasksUpdateCompanionBuilder = + i1.UploadTasksCompanion Function({ + i0.Value id, + i0.Value attempts, + i0.Value createdAt, + i0.Value filePath, + i0.Value isLivePhoto, + i0.Value lastError, + i0.Value livePhotoVideoId, + i0.Value localId, + i0.Value method, + i0.Value priority, + i0.Value retryAfter, + i0.Value status, + }); + +class $UploadTasksFilterComposer + extends i0.Composer { + $UploadTasksFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get attempts => $composableBuilder( + column: $table.attempts, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get filePath => $composableBuilder( + column: $table.filePath, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get isLivePhoto => $composableBuilder( + column: $table.isLivePhoto, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get lastError => $composableBuilder( + column: $table.lastError, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get livePhotoVideoId => $composableBuilder( + column: $table.livePhotoVideoId, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get localId => $composableBuilder( + column: $table.localId, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get method => $composableBuilder( + column: $table.method, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get priority => $composableBuilder( + column: $table.priority, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get retryAfter => $composableBuilder( + column: $table.retryAfter, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get status => $composableBuilder( + column: $table.status, + builder: (column) => i0.ColumnFilters(column), + ); +} + +class $UploadTasksOrderingComposer + extends i0.Composer { + $UploadTasksOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get attempts => $composableBuilder( + column: $table.attempts, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get filePath => $composableBuilder( + column: $table.filePath, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get isLivePhoto => $composableBuilder( + column: $table.isLivePhoto, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get lastError => $composableBuilder( + column: $table.lastError, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get livePhotoVideoId => $composableBuilder( + column: $table.livePhotoVideoId, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get localId => $composableBuilder( + column: $table.localId, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get method => $composableBuilder( + column: $table.method, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get priority => $composableBuilder( + column: $table.priority, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get retryAfter => $composableBuilder( + column: $table.retryAfter, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get status => $composableBuilder( + column: $table.status, + builder: (column) => i0.ColumnOrderings(column), + ); +} + +class $UploadTasksAnnotationComposer + extends i0.Composer { + $UploadTasksAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + i0.GeneratedColumn get attempts => + $composableBuilder(column: $table.attempts, builder: (column) => column); + + i0.GeneratedColumn get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => column); + + i0.GeneratedColumn get filePath => + $composableBuilder(column: $table.filePath, builder: (column) => column); + + i0.GeneratedColumn get isLivePhoto => $composableBuilder( + column: $table.isLivePhoto, + builder: (column) => column, + ); + + i0.GeneratedColumn get lastError => + $composableBuilder(column: $table.lastError, builder: (column) => column); + + i0.GeneratedColumn get livePhotoVideoId => $composableBuilder( + column: $table.livePhotoVideoId, + builder: (column) => column, + ); + + i0.GeneratedColumn get localId => + $composableBuilder(column: $table.localId, builder: (column) => column); + + i0.GeneratedColumn get method => + $composableBuilder(column: $table.method, builder: (column) => column); + + i0.GeneratedColumn get priority => + $composableBuilder(column: $table.priority, builder: (column) => column); + + i0.GeneratedColumn get retryAfter => $composableBuilder( + column: $table.retryAfter, + builder: (column) => column, + ); + + i0.GeneratedColumn get status => + $composableBuilder(column: $table.status, builder: (column) => column); +} + +class $UploadTasksTableManager + extends + i0.RootTableManager< + i0.GeneratedDatabase, + i1.UploadTasks, + i1.UploadTask, + i1.$UploadTasksFilterComposer, + i1.$UploadTasksOrderingComposer, + i1.$UploadTasksAnnotationComposer, + $UploadTasksCreateCompanionBuilder, + $UploadTasksUpdateCompanionBuilder, + ( + i1.UploadTask, + i0.BaseReferences< + i0.GeneratedDatabase, + i1.UploadTasks, + i1.UploadTask + >, + ), + i1.UploadTask, + i0.PrefetchHooks Function() + > { + $UploadTasksTableManager(i0.GeneratedDatabase db, i1.UploadTasks table) + : super( + i0.TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + i1.$UploadTasksFilterComposer($db: db, $table: table), + createOrderingComposer: () => + i1.$UploadTasksOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + i1.$UploadTasksAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + i0.Value id = const i0.Value.absent(), + i0.Value attempts = const i0.Value.absent(), + i0.Value createdAt = const i0.Value.absent(), + i0.Value filePath = const i0.Value.absent(), + i0.Value isLivePhoto = const i0.Value.absent(), + i0.Value lastError = const i0.Value.absent(), + i0.Value livePhotoVideoId = const i0.Value.absent(), + i0.Value localId = const i0.Value.absent(), + i0.Value method = const i0.Value.absent(), + i0.Value priority = const i0.Value.absent(), + i0.Value retryAfter = const i0.Value.absent(), + i0.Value status = const i0.Value.absent(), + }) => i1.UploadTasksCompanion( + id: id, + attempts: attempts, + createdAt: createdAt, + filePath: filePath, + isLivePhoto: isLivePhoto, + lastError: lastError, + livePhotoVideoId: livePhotoVideoId, + localId: localId, + method: method, + priority: priority, + retryAfter: retryAfter, + status: status, + ), + createCompanionCallback: + ({ + i0.Value id = const i0.Value.absent(), + required int attempts, + required int createdAt, + i0.Value filePath = const i0.Value.absent(), + i0.Value isLivePhoto = const i0.Value.absent(), + i0.Value lastError = const i0.Value.absent(), + i0.Value livePhotoVideoId = const i0.Value.absent(), + required String localId, + required int method, + required double priority, + i0.Value retryAfter = const i0.Value.absent(), + required int status, + }) => i1.UploadTasksCompanion.insert( + id: id, + attempts: attempts, + createdAt: createdAt, + filePath: filePath, + isLivePhoto: isLivePhoto, + lastError: lastError, + livePhotoVideoId: livePhotoVideoId, + localId: localId, + method: method, + priority: priority, + retryAfter: retryAfter, + status: status, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + ), + ); +} + +typedef $UploadTasksProcessedTableManager = + i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.UploadTasks, + i1.UploadTask, + i1.$UploadTasksFilterComposer, + i1.$UploadTasksOrderingComposer, + i1.$UploadTasksAnnotationComposer, + $UploadTasksCreateCompanionBuilder, + $UploadTasksUpdateCompanionBuilder, + ( + i1.UploadTask, + i0.BaseReferences, + ), + i1.UploadTask, + i0.PrefetchHooks Function() + >; +typedef $UploadTaskStatsCreateCompanionBuilder = + i1.UploadTaskStatsCompanion Function({ + required int pendingDownloads, + required int pendingUploads, + required int queuedDownloads, + required int queuedUploads, + required int failedDownloads, + required int failedUploads, + required int completedUploads, + required int skippedUploads, + i0.Value rowid, + }); +typedef $UploadTaskStatsUpdateCompanionBuilder = + i1.UploadTaskStatsCompanion Function({ + i0.Value pendingDownloads, + i0.Value pendingUploads, + i0.Value queuedDownloads, + i0.Value queuedUploads, + i0.Value failedDownloads, + i0.Value failedUploads, + i0.Value completedUploads, + i0.Value skippedUploads, + i0.Value rowid, + }); + +class $UploadTaskStatsFilterComposer + extends i0.Composer { + $UploadTaskStatsFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnFilters get pendingDownloads => $composableBuilder( + column: $table.pendingDownloads, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get pendingUploads => $composableBuilder( + column: $table.pendingUploads, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get queuedDownloads => $composableBuilder( + column: $table.queuedDownloads, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get queuedUploads => $composableBuilder( + column: $table.queuedUploads, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get failedDownloads => $composableBuilder( + column: $table.failedDownloads, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get failedUploads => $composableBuilder( + column: $table.failedUploads, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get completedUploads => $composableBuilder( + column: $table.completedUploads, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get skippedUploads => $composableBuilder( + column: $table.skippedUploads, + builder: (column) => i0.ColumnFilters(column), + ); +} + +class $UploadTaskStatsOrderingComposer + extends i0.Composer { + $UploadTaskStatsOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnOrderings get pendingDownloads => $composableBuilder( + column: $table.pendingDownloads, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get pendingUploads => $composableBuilder( + column: $table.pendingUploads, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get queuedDownloads => $composableBuilder( + column: $table.queuedDownloads, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get queuedUploads => $composableBuilder( + column: $table.queuedUploads, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get failedDownloads => $composableBuilder( + column: $table.failedDownloads, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get failedUploads => $composableBuilder( + column: $table.failedUploads, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get completedUploads => $composableBuilder( + column: $table.completedUploads, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get skippedUploads => $composableBuilder( + column: $table.skippedUploads, + builder: (column) => i0.ColumnOrderings(column), + ); +} + +class $UploadTaskStatsAnnotationComposer + extends i0.Composer { + $UploadTaskStatsAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.GeneratedColumn get pendingDownloads => $composableBuilder( + column: $table.pendingDownloads, + builder: (column) => column, + ); + + i0.GeneratedColumn get pendingUploads => $composableBuilder( + column: $table.pendingUploads, + builder: (column) => column, + ); + + i0.GeneratedColumn get queuedDownloads => $composableBuilder( + column: $table.queuedDownloads, + builder: (column) => column, + ); + + i0.GeneratedColumn get queuedUploads => $composableBuilder( + column: $table.queuedUploads, + builder: (column) => column, + ); + + i0.GeneratedColumn get failedDownloads => $composableBuilder( + column: $table.failedDownloads, + builder: (column) => column, + ); + + i0.GeneratedColumn get failedUploads => $composableBuilder( + column: $table.failedUploads, + builder: (column) => column, + ); + + i0.GeneratedColumn get completedUploads => $composableBuilder( + column: $table.completedUploads, + builder: (column) => column, + ); + + i0.GeneratedColumn get skippedUploads => $composableBuilder( + column: $table.skippedUploads, + builder: (column) => column, + ); +} + +class $UploadTaskStatsTableManager + extends + i0.RootTableManager< + i0.GeneratedDatabase, + i1.UploadTaskStats, + i1.UploadTaskStat, + i1.$UploadTaskStatsFilterComposer, + i1.$UploadTaskStatsOrderingComposer, + i1.$UploadTaskStatsAnnotationComposer, + $UploadTaskStatsCreateCompanionBuilder, + $UploadTaskStatsUpdateCompanionBuilder, + ( + i1.UploadTaskStat, + i0.BaseReferences< + i0.GeneratedDatabase, + i1.UploadTaskStats, + i1.UploadTaskStat + >, + ), + i1.UploadTaskStat, + i0.PrefetchHooks Function() + > { + $UploadTaskStatsTableManager( + i0.GeneratedDatabase db, + i1.UploadTaskStats table, + ) : super( + i0.TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + i1.$UploadTaskStatsFilterComposer($db: db, $table: table), + createOrderingComposer: () => + i1.$UploadTaskStatsOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + i1.$UploadTaskStatsAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + i0.Value pendingDownloads = const i0.Value.absent(), + i0.Value pendingUploads = const i0.Value.absent(), + i0.Value queuedDownloads = const i0.Value.absent(), + i0.Value queuedUploads = const i0.Value.absent(), + i0.Value failedDownloads = const i0.Value.absent(), + i0.Value failedUploads = const i0.Value.absent(), + i0.Value completedUploads = const i0.Value.absent(), + i0.Value skippedUploads = const i0.Value.absent(), + i0.Value rowid = const i0.Value.absent(), + }) => i1.UploadTaskStatsCompanion( + pendingDownloads: pendingDownloads, + pendingUploads: pendingUploads, + queuedDownloads: queuedDownloads, + queuedUploads: queuedUploads, + failedDownloads: failedDownloads, + failedUploads: failedUploads, + completedUploads: completedUploads, + skippedUploads: skippedUploads, + rowid: rowid, + ), + createCompanionCallback: + ({ + required int pendingDownloads, + required int pendingUploads, + required int queuedDownloads, + required int queuedUploads, + required int failedDownloads, + required int failedUploads, + required int completedUploads, + required int skippedUploads, + i0.Value rowid = const i0.Value.absent(), + }) => i1.UploadTaskStatsCompanion.insert( + pendingDownloads: pendingDownloads, + pendingUploads: pendingUploads, + queuedDownloads: queuedDownloads, + queuedUploads: queuedUploads, + failedDownloads: failedDownloads, + failedUploads: failedUploads, + completedUploads: completedUploads, + skippedUploads: skippedUploads, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + ), + ); +} + +typedef $UploadTaskStatsProcessedTableManager = + i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.UploadTaskStats, + i1.UploadTaskStat, + i1.$UploadTaskStatsFilterComposer, + i1.$UploadTaskStatsOrderingComposer, + i1.$UploadTaskStatsAnnotationComposer, + $UploadTaskStatsCreateCompanionBuilder, + $UploadTaskStatsUpdateCompanionBuilder, + ( + i1.UploadTaskStat, + i0.BaseReferences< + i0.GeneratedDatabase, + i1.UploadTaskStats, + i1.UploadTaskStat + >, + ), + i1.UploadTaskStat, + i0.PrefetchHooks Function() + >; + +class UploadTasks extends i0.Table + with i0.TableInfo { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + UploadTasks(this.attachedDatabase, [this._alias]); + static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id'); + late final i0.GeneratedColumn id = i0.GeneratedColumn( + 'id', + aliasedName, + true, + hasAutoIncrement: true, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'PRIMARY KEY AUTOINCREMENT', + ); + static const i0.VerificationMeta _attemptsMeta = const i0.VerificationMeta( + 'attempts', + ); + late final i0.GeneratedColumn attempts = i0.GeneratedColumn( + 'attempts', + aliasedName, + false, + type: i0.DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + static const i0.VerificationMeta _createdAtMeta = const i0.VerificationMeta( + 'createdAt', + ); + late final i0.GeneratedColumn createdAt = i0.GeneratedColumn( + 'created_at', + aliasedName, + false, + type: i0.DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + static const i0.VerificationMeta _filePathMeta = const i0.VerificationMeta( + 'filePath', + ); + late final i0.GeneratedColumn filePath = i0.GeneratedColumn( + 'file_path', + aliasedName, + true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: '', + ); + static const i0.VerificationMeta _isLivePhotoMeta = const i0.VerificationMeta( + 'isLivePhoto', + ); + late final i0.GeneratedColumn isLivePhoto = i0.GeneratedColumn( + 'is_live_photo', + aliasedName, + true, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: '', + ); + static const i0.VerificationMeta _lastErrorMeta = const i0.VerificationMeta( + 'lastError', + ); + late final i0.GeneratedColumn lastError = i0.GeneratedColumn( + 'last_error', + aliasedName, + true, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: '', + ); + static const i0.VerificationMeta _livePhotoVideoIdMeta = + const i0.VerificationMeta('livePhotoVideoId'); + late final i0.GeneratedColumn livePhotoVideoId = + i0.GeneratedColumn( + 'live_photo_video_id', + aliasedName, + true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: '', + ); + static const i0.VerificationMeta _localIdMeta = const i0.VerificationMeta( + 'localId', + ); + late final i0.GeneratedColumn localId = i0.GeneratedColumn( + 'local_id', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + static const i0.VerificationMeta _methodMeta = const i0.VerificationMeta( + 'method', + ); + late final i0.GeneratedColumn method = i0.GeneratedColumn( + 'method', + aliasedName, + false, + type: i0.DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + static const i0.VerificationMeta _priorityMeta = const i0.VerificationMeta( + 'priority', + ); + late final i0.GeneratedColumn priority = i0.GeneratedColumn( + 'priority', + aliasedName, + false, + type: i0.DriftSqlType.double, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + static const i0.VerificationMeta _retryAfterMeta = const i0.VerificationMeta( + 'retryAfter', + ); + late final i0.GeneratedColumn retryAfter = i0.GeneratedColumn( + 'retry_after', + aliasedName, + true, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: '', + ); + static const i0.VerificationMeta _statusMeta = const i0.VerificationMeta( + 'status', + ); + late final i0.GeneratedColumn status = i0.GeneratedColumn( + 'status', + aliasedName, + false, + type: i0.DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + @override + List get $columns => [ + id, + attempts, + createdAt, + filePath, + isLivePhoto, + lastError, + livePhotoVideoId, + localId, + method, + priority, + retryAfter, + status, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'upload_tasks'; + @override + i0.VerificationContext validateIntegrity( + i0.Insertable instance, { + bool isInserting = false, + }) { + final context = i0.VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('attempts')) { + context.handle( + _attemptsMeta, + attempts.isAcceptableOrUnknown(data['attempts']!, _attemptsMeta), + ); + } else if (isInserting) { + context.missing(_attemptsMeta); + } + if (data.containsKey('created_at')) { + context.handle( + _createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta), + ); + } else if (isInserting) { + context.missing(_createdAtMeta); + } + if (data.containsKey('file_path')) { + context.handle( + _filePathMeta, + filePath.isAcceptableOrUnknown(data['file_path']!, _filePathMeta), + ); + } + if (data.containsKey('is_live_photo')) { + context.handle( + _isLivePhotoMeta, + isLivePhoto.isAcceptableOrUnknown( + data['is_live_photo']!, + _isLivePhotoMeta, + ), + ); + } + if (data.containsKey('last_error')) { + context.handle( + _lastErrorMeta, + lastError.isAcceptableOrUnknown(data['last_error']!, _lastErrorMeta), + ); + } + if (data.containsKey('live_photo_video_id')) { + context.handle( + _livePhotoVideoIdMeta, + livePhotoVideoId.isAcceptableOrUnknown( + data['live_photo_video_id']!, + _livePhotoVideoIdMeta, + ), + ); + } + if (data.containsKey('local_id')) { + context.handle( + _localIdMeta, + localId.isAcceptableOrUnknown(data['local_id']!, _localIdMeta), + ); + } else if (isInserting) { + context.missing(_localIdMeta); + } + if (data.containsKey('method')) { + context.handle( + _methodMeta, + method.isAcceptableOrUnknown(data['method']!, _methodMeta), + ); + } else if (isInserting) { + context.missing(_methodMeta); + } + if (data.containsKey('priority')) { + context.handle( + _priorityMeta, + priority.isAcceptableOrUnknown(data['priority']!, _priorityMeta), + ); + } else if (isInserting) { + context.missing(_priorityMeta); + } + if (data.containsKey('retry_after')) { + context.handle( + _retryAfterMeta, + retryAfter.isAcceptableOrUnknown(data['retry_after']!, _retryAfterMeta), + ); + } + if (data.containsKey('status')) { + context.handle( + _statusMeta, + status.isAcceptableOrUnknown(data['status']!, _statusMeta), + ); + } else if (isInserting) { + context.missing(_statusMeta); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + i1.UploadTask map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.UploadTask( + id: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}id'], + ), + attempts: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}attempts'], + )!, + createdAt: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}created_at'], + )!, + filePath: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}file_path'], + ), + isLivePhoto: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}is_live_photo'], + ), + lastError: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}last_error'], + ), + livePhotoVideoId: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}live_photo_video_id'], + ), + localId: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}local_id'], + )!, + method: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}method'], + )!, + priority: attachedDatabase.typeMapping.read( + i0.DriftSqlType.double, + data['${effectivePrefix}priority'], + )!, + retryAfter: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}retry_after'], + ), + status: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}status'], + )!, + ); + } + + @override + UploadTasks createAlias(String alias) { + return UploadTasks(attachedDatabase, alias); + } + + @override + bool get dontWriteConstraints => true; +} + +class UploadTask extends i0.DataClass implements i0.Insertable { + final int? id; + final int attempts; + final int createdAt; + final String? filePath; + final int? isLivePhoto; + final int? lastError; + final String? livePhotoVideoId; + final String localId; + final int method; + final double priority; + final int? retryAfter; + final int status; + const UploadTask({ + this.id, + required this.attempts, + required this.createdAt, + this.filePath, + this.isLivePhoto, + this.lastError, + this.livePhotoVideoId, + required this.localId, + required this.method, + required this.priority, + this.retryAfter, + required this.status, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (!nullToAbsent || id != null) { + map['id'] = i0.Variable(id); + } + map['attempts'] = i0.Variable(attempts); + map['created_at'] = i0.Variable(createdAt); + if (!nullToAbsent || filePath != null) { + map['file_path'] = i0.Variable(filePath); + } + if (!nullToAbsent || isLivePhoto != null) { + map['is_live_photo'] = i0.Variable(isLivePhoto); + } + if (!nullToAbsent || lastError != null) { + map['last_error'] = i0.Variable(lastError); + } + if (!nullToAbsent || livePhotoVideoId != null) { + map['live_photo_video_id'] = i0.Variable(livePhotoVideoId); + } + map['local_id'] = i0.Variable(localId); + map['method'] = i0.Variable(method); + map['priority'] = i0.Variable(priority); + if (!nullToAbsent || retryAfter != null) { + map['retry_after'] = i0.Variable(retryAfter); + } + map['status'] = i0.Variable(status); + return map; + } + + factory UploadTask.fromJson( + Map json, { + i0.ValueSerializer? serializer, + }) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return UploadTask( + id: serializer.fromJson(json['id']), + attempts: serializer.fromJson(json['attempts']), + createdAt: serializer.fromJson(json['created_at']), + filePath: serializer.fromJson(json['file_path']), + isLivePhoto: serializer.fromJson(json['is_live_photo']), + lastError: serializer.fromJson(json['last_error']), + livePhotoVideoId: serializer.fromJson( + json['live_photo_video_id'], + ), + localId: serializer.fromJson(json['local_id']), + method: serializer.fromJson(json['method']), + priority: serializer.fromJson(json['priority']), + retryAfter: serializer.fromJson(json['retry_after']), + status: serializer.fromJson(json['status']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'attempts': serializer.toJson(attempts), + 'created_at': serializer.toJson(createdAt), + 'file_path': serializer.toJson(filePath), + 'is_live_photo': serializer.toJson(isLivePhoto), + 'last_error': serializer.toJson(lastError), + 'live_photo_video_id': serializer.toJson(livePhotoVideoId), + 'local_id': serializer.toJson(localId), + 'method': serializer.toJson(method), + 'priority': serializer.toJson(priority), + 'retry_after': serializer.toJson(retryAfter), + 'status': serializer.toJson(status), + }; + } + + i1.UploadTask copyWith({ + i0.Value id = const i0.Value.absent(), + int? attempts, + int? createdAt, + i0.Value filePath = const i0.Value.absent(), + i0.Value isLivePhoto = const i0.Value.absent(), + i0.Value lastError = const i0.Value.absent(), + i0.Value livePhotoVideoId = const i0.Value.absent(), + String? localId, + int? method, + double? priority, + i0.Value retryAfter = const i0.Value.absent(), + int? status, + }) => i1.UploadTask( + id: id.present ? id.value : this.id, + attempts: attempts ?? this.attempts, + createdAt: createdAt ?? this.createdAt, + filePath: filePath.present ? filePath.value : this.filePath, + isLivePhoto: isLivePhoto.present ? isLivePhoto.value : this.isLivePhoto, + lastError: lastError.present ? lastError.value : this.lastError, + livePhotoVideoId: livePhotoVideoId.present + ? livePhotoVideoId.value + : this.livePhotoVideoId, + localId: localId ?? this.localId, + method: method ?? this.method, + priority: priority ?? this.priority, + retryAfter: retryAfter.present ? retryAfter.value : this.retryAfter, + status: status ?? this.status, + ); + UploadTask copyWithCompanion(i1.UploadTasksCompanion data) { + return UploadTask( + id: data.id.present ? data.id.value : this.id, + attempts: data.attempts.present ? data.attempts.value : this.attempts, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + filePath: data.filePath.present ? data.filePath.value : this.filePath, + isLivePhoto: data.isLivePhoto.present + ? data.isLivePhoto.value + : this.isLivePhoto, + lastError: data.lastError.present ? data.lastError.value : this.lastError, + livePhotoVideoId: data.livePhotoVideoId.present + ? data.livePhotoVideoId.value + : this.livePhotoVideoId, + localId: data.localId.present ? data.localId.value : this.localId, + method: data.method.present ? data.method.value : this.method, + priority: data.priority.present ? data.priority.value : this.priority, + retryAfter: data.retryAfter.present + ? data.retryAfter.value + : this.retryAfter, + status: data.status.present ? data.status.value : this.status, + ); + } + + @override + String toString() { + return (StringBuffer('UploadTask(') + ..write('id: $id, ') + ..write('attempts: $attempts, ') + ..write('createdAt: $createdAt, ') + ..write('filePath: $filePath, ') + ..write('isLivePhoto: $isLivePhoto, ') + ..write('lastError: $lastError, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('localId: $localId, ') + ..write('method: $method, ') + ..write('priority: $priority, ') + ..write('retryAfter: $retryAfter, ') + ..write('status: $status') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + attempts, + createdAt, + filePath, + isLivePhoto, + lastError, + livePhotoVideoId, + localId, + method, + priority, + retryAfter, + status, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.UploadTask && + other.id == this.id && + other.attempts == this.attempts && + other.createdAt == this.createdAt && + other.filePath == this.filePath && + other.isLivePhoto == this.isLivePhoto && + other.lastError == this.lastError && + other.livePhotoVideoId == this.livePhotoVideoId && + other.localId == this.localId && + other.method == this.method && + other.priority == this.priority && + other.retryAfter == this.retryAfter && + other.status == this.status); +} + +class UploadTasksCompanion extends i0.UpdateCompanion { + final i0.Value id; + final i0.Value attempts; + final i0.Value createdAt; + final i0.Value filePath; + final i0.Value isLivePhoto; + final i0.Value lastError; + final i0.Value livePhotoVideoId; + final i0.Value localId; + final i0.Value method; + final i0.Value priority; + final i0.Value retryAfter; + final i0.Value status; + const UploadTasksCompanion({ + this.id = const i0.Value.absent(), + this.attempts = const i0.Value.absent(), + this.createdAt = const i0.Value.absent(), + this.filePath = const i0.Value.absent(), + this.isLivePhoto = const i0.Value.absent(), + this.lastError = const i0.Value.absent(), + this.livePhotoVideoId = const i0.Value.absent(), + this.localId = const i0.Value.absent(), + this.method = const i0.Value.absent(), + this.priority = const i0.Value.absent(), + this.retryAfter = const i0.Value.absent(), + this.status = const i0.Value.absent(), + }); + UploadTasksCompanion.insert({ + this.id = const i0.Value.absent(), + required int attempts, + required int createdAt, + this.filePath = const i0.Value.absent(), + this.isLivePhoto = const i0.Value.absent(), + this.lastError = const i0.Value.absent(), + this.livePhotoVideoId = const i0.Value.absent(), + required String localId, + required int method, + required double priority, + this.retryAfter = const i0.Value.absent(), + required int status, + }) : attempts = i0.Value(attempts), + createdAt = i0.Value(createdAt), + localId = i0.Value(localId), + method = i0.Value(method), + priority = i0.Value(priority), + status = i0.Value(status); + static i0.Insertable custom({ + i0.Expression? id, + i0.Expression? attempts, + i0.Expression? createdAt, + i0.Expression? filePath, + i0.Expression? isLivePhoto, + i0.Expression? lastError, + i0.Expression? livePhotoVideoId, + i0.Expression? localId, + i0.Expression? method, + i0.Expression? priority, + i0.Expression? retryAfter, + i0.Expression? status, + }) { + return i0.RawValuesInsertable({ + if (id != null) 'id': id, + if (attempts != null) 'attempts': attempts, + if (createdAt != null) 'created_at': createdAt, + if (filePath != null) 'file_path': filePath, + if (isLivePhoto != null) 'is_live_photo': isLivePhoto, + if (lastError != null) 'last_error': lastError, + if (livePhotoVideoId != null) 'live_photo_video_id': livePhotoVideoId, + if (localId != null) 'local_id': localId, + if (method != null) 'method': method, + if (priority != null) 'priority': priority, + if (retryAfter != null) 'retry_after': retryAfter, + if (status != null) 'status': status, + }); + } + + i1.UploadTasksCompanion copyWith({ + i0.Value? id, + i0.Value? attempts, + i0.Value? createdAt, + i0.Value? filePath, + i0.Value? isLivePhoto, + i0.Value? lastError, + i0.Value? livePhotoVideoId, + i0.Value? localId, + i0.Value? method, + i0.Value? priority, + i0.Value? retryAfter, + i0.Value? status, + }) { + return i1.UploadTasksCompanion( + id: id ?? this.id, + attempts: attempts ?? this.attempts, + createdAt: createdAt ?? this.createdAt, + filePath: filePath ?? this.filePath, + isLivePhoto: isLivePhoto ?? this.isLivePhoto, + lastError: lastError ?? this.lastError, + livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId, + localId: localId ?? this.localId, + method: method ?? this.method, + priority: priority ?? this.priority, + retryAfter: retryAfter ?? this.retryAfter, + status: status ?? this.status, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = i0.Variable(id.value); + } + if (attempts.present) { + map['attempts'] = i0.Variable(attempts.value); + } + if (createdAt.present) { + map['created_at'] = i0.Variable(createdAt.value); + } + if (filePath.present) { + map['file_path'] = i0.Variable(filePath.value); + } + if (isLivePhoto.present) { + map['is_live_photo'] = i0.Variable(isLivePhoto.value); + } + if (lastError.present) { + map['last_error'] = i0.Variable(lastError.value); + } + if (livePhotoVideoId.present) { + map['live_photo_video_id'] = i0.Variable(livePhotoVideoId.value); + } + if (localId.present) { + map['local_id'] = i0.Variable(localId.value); + } + if (method.present) { + map['method'] = i0.Variable(method.value); + } + if (priority.present) { + map['priority'] = i0.Variable(priority.value); + } + if (retryAfter.present) { + map['retry_after'] = i0.Variable(retryAfter.value); + } + if (status.present) { + map['status'] = i0.Variable(status.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UploadTasksCompanion(') + ..write('id: $id, ') + ..write('attempts: $attempts, ') + ..write('createdAt: $createdAt, ') + ..write('filePath: $filePath, ') + ..write('isLivePhoto: $isLivePhoto, ') + ..write('lastError: $lastError, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('localId: $localId, ') + ..write('method: $method, ') + ..write('priority: $priority, ') + ..write('retryAfter: $retryAfter, ') + ..write('status: $status') + ..write(')')) + .toString(); + } +} + +class UploadTaskStats extends i0.Table + with i0.TableInfo { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + UploadTaskStats(this.attachedDatabase, [this._alias]); + static const i0.VerificationMeta _pendingDownloadsMeta = + const i0.VerificationMeta('pendingDownloads'); + late final i0.GeneratedColumn pendingDownloads = i0.GeneratedColumn( + 'pending_downloads', + aliasedName, + false, + type: i0.DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + static const i0.VerificationMeta _pendingUploadsMeta = + const i0.VerificationMeta('pendingUploads'); + late final i0.GeneratedColumn pendingUploads = i0.GeneratedColumn( + 'pending_uploads', + aliasedName, + false, + type: i0.DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + static const i0.VerificationMeta _queuedDownloadsMeta = + const i0.VerificationMeta('queuedDownloads'); + late final i0.GeneratedColumn queuedDownloads = i0.GeneratedColumn( + 'queued_downloads', + aliasedName, + false, + type: i0.DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + static const i0.VerificationMeta _queuedUploadsMeta = + const i0.VerificationMeta('queuedUploads'); + late final i0.GeneratedColumn queuedUploads = i0.GeneratedColumn( + 'queued_uploads', + aliasedName, + false, + type: i0.DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + static const i0.VerificationMeta _failedDownloadsMeta = + const i0.VerificationMeta('failedDownloads'); + late final i0.GeneratedColumn failedDownloads = i0.GeneratedColumn( + 'failed_downloads', + aliasedName, + false, + type: i0.DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + static const i0.VerificationMeta _failedUploadsMeta = + const i0.VerificationMeta('failedUploads'); + late final i0.GeneratedColumn failedUploads = i0.GeneratedColumn( + 'failed_uploads', + aliasedName, + false, + type: i0.DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + static const i0.VerificationMeta _completedUploadsMeta = + const i0.VerificationMeta('completedUploads'); + late final i0.GeneratedColumn completedUploads = i0.GeneratedColumn( + 'completed_uploads', + aliasedName, + false, + type: i0.DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + static const i0.VerificationMeta _skippedUploadsMeta = + const i0.VerificationMeta('skippedUploads'); + late final i0.GeneratedColumn skippedUploads = i0.GeneratedColumn( + 'skipped_uploads', + aliasedName, + false, + type: i0.DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + @override + List get $columns => [ + pendingDownloads, + pendingUploads, + queuedDownloads, + queuedUploads, + failedDownloads, + failedUploads, + completedUploads, + skippedUploads, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'upload_task_stats'; + @override + i0.VerificationContext validateIntegrity( + i0.Insertable instance, { + bool isInserting = false, + }) { + final context = i0.VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('pending_downloads')) { + context.handle( + _pendingDownloadsMeta, + pendingDownloads.isAcceptableOrUnknown( + data['pending_downloads']!, + _pendingDownloadsMeta, + ), + ); + } else if (isInserting) { + context.missing(_pendingDownloadsMeta); + } + if (data.containsKey('pending_uploads')) { + context.handle( + _pendingUploadsMeta, + pendingUploads.isAcceptableOrUnknown( + data['pending_uploads']!, + _pendingUploadsMeta, + ), + ); + } else if (isInserting) { + context.missing(_pendingUploadsMeta); + } + if (data.containsKey('queued_downloads')) { + context.handle( + _queuedDownloadsMeta, + queuedDownloads.isAcceptableOrUnknown( + data['queued_downloads']!, + _queuedDownloadsMeta, + ), + ); + } else if (isInserting) { + context.missing(_queuedDownloadsMeta); + } + if (data.containsKey('queued_uploads')) { + context.handle( + _queuedUploadsMeta, + queuedUploads.isAcceptableOrUnknown( + data['queued_uploads']!, + _queuedUploadsMeta, + ), + ); + } else if (isInserting) { + context.missing(_queuedUploadsMeta); + } + if (data.containsKey('failed_downloads')) { + context.handle( + _failedDownloadsMeta, + failedDownloads.isAcceptableOrUnknown( + data['failed_downloads']!, + _failedDownloadsMeta, + ), + ); + } else if (isInserting) { + context.missing(_failedDownloadsMeta); + } + if (data.containsKey('failed_uploads')) { + context.handle( + _failedUploadsMeta, + failedUploads.isAcceptableOrUnknown( + data['failed_uploads']!, + _failedUploadsMeta, + ), + ); + } else if (isInserting) { + context.missing(_failedUploadsMeta); + } + if (data.containsKey('completed_uploads')) { + context.handle( + _completedUploadsMeta, + completedUploads.isAcceptableOrUnknown( + data['completed_uploads']!, + _completedUploadsMeta, + ), + ); + } else if (isInserting) { + context.missing(_completedUploadsMeta); + } + if (data.containsKey('skipped_uploads')) { + context.handle( + _skippedUploadsMeta, + skippedUploads.isAcceptableOrUnknown( + data['skipped_uploads']!, + _skippedUploadsMeta, + ), + ); + } else if (isInserting) { + context.missing(_skippedUploadsMeta); + } + return context; + } + + @override + Set get $primaryKey => const {}; + @override + i1.UploadTaskStat map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.UploadTaskStat( + pendingDownloads: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}pending_downloads'], + )!, + pendingUploads: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}pending_uploads'], + )!, + queuedDownloads: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}queued_downloads'], + )!, + queuedUploads: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}queued_uploads'], + )!, + failedDownloads: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}failed_downloads'], + )!, + failedUploads: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}failed_uploads'], + )!, + completedUploads: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}completed_uploads'], + )!, + skippedUploads: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}skipped_uploads'], + )!, + ); + } + + @override + UploadTaskStats createAlias(String alias) { + return UploadTaskStats(attachedDatabase, alias); + } + + @override + bool get dontWriteConstraints => true; +} + +class UploadTaskStat extends i0.DataClass + implements i0.Insertable { + final int pendingDownloads; + final int pendingUploads; + final int queuedDownloads; + final int queuedUploads; + final int failedDownloads; + final int failedUploads; + final int completedUploads; + final int skippedUploads; + const UploadTaskStat({ + required this.pendingDownloads, + required this.pendingUploads, + required this.queuedDownloads, + required this.queuedUploads, + required this.failedDownloads, + required this.failedUploads, + required this.completedUploads, + required this.skippedUploads, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['pending_downloads'] = i0.Variable(pendingDownloads); + map['pending_uploads'] = i0.Variable(pendingUploads); + map['queued_downloads'] = i0.Variable(queuedDownloads); + map['queued_uploads'] = i0.Variable(queuedUploads); + map['failed_downloads'] = i0.Variable(failedDownloads); + map['failed_uploads'] = i0.Variable(failedUploads); + map['completed_uploads'] = i0.Variable(completedUploads); + map['skipped_uploads'] = i0.Variable(skippedUploads); + return map; + } + + factory UploadTaskStat.fromJson( + Map json, { + i0.ValueSerializer? serializer, + }) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return UploadTaskStat( + pendingDownloads: serializer.fromJson(json['pending_downloads']), + pendingUploads: serializer.fromJson(json['pending_uploads']), + queuedDownloads: serializer.fromJson(json['queued_downloads']), + queuedUploads: serializer.fromJson(json['queued_uploads']), + failedDownloads: serializer.fromJson(json['failed_downloads']), + failedUploads: serializer.fromJson(json['failed_uploads']), + completedUploads: serializer.fromJson(json['completed_uploads']), + skippedUploads: serializer.fromJson(json['skipped_uploads']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'pending_downloads': serializer.toJson(pendingDownloads), + 'pending_uploads': serializer.toJson(pendingUploads), + 'queued_downloads': serializer.toJson(queuedDownloads), + 'queued_uploads': serializer.toJson(queuedUploads), + 'failed_downloads': serializer.toJson(failedDownloads), + 'failed_uploads': serializer.toJson(failedUploads), + 'completed_uploads': serializer.toJson(completedUploads), + 'skipped_uploads': serializer.toJson(skippedUploads), + }; + } + + i1.UploadTaskStat copyWith({ + int? pendingDownloads, + int? pendingUploads, + int? queuedDownloads, + int? queuedUploads, + int? failedDownloads, + int? failedUploads, + int? completedUploads, + int? skippedUploads, + }) => i1.UploadTaskStat( + pendingDownloads: pendingDownloads ?? this.pendingDownloads, + pendingUploads: pendingUploads ?? this.pendingUploads, + queuedDownloads: queuedDownloads ?? this.queuedDownloads, + queuedUploads: queuedUploads ?? this.queuedUploads, + failedDownloads: failedDownloads ?? this.failedDownloads, + failedUploads: failedUploads ?? this.failedUploads, + completedUploads: completedUploads ?? this.completedUploads, + skippedUploads: skippedUploads ?? this.skippedUploads, + ); + UploadTaskStat copyWithCompanion(i1.UploadTaskStatsCompanion data) { + return UploadTaskStat( + pendingDownloads: data.pendingDownloads.present + ? data.pendingDownloads.value + : this.pendingDownloads, + pendingUploads: data.pendingUploads.present + ? data.pendingUploads.value + : this.pendingUploads, + queuedDownloads: data.queuedDownloads.present + ? data.queuedDownloads.value + : this.queuedDownloads, + queuedUploads: data.queuedUploads.present + ? data.queuedUploads.value + : this.queuedUploads, + failedDownloads: data.failedDownloads.present + ? data.failedDownloads.value + : this.failedDownloads, + failedUploads: data.failedUploads.present + ? data.failedUploads.value + : this.failedUploads, + completedUploads: data.completedUploads.present + ? data.completedUploads.value + : this.completedUploads, + skippedUploads: data.skippedUploads.present + ? data.skippedUploads.value + : this.skippedUploads, + ); + } + + @override + String toString() { + return (StringBuffer('UploadTaskStat(') + ..write('pendingDownloads: $pendingDownloads, ') + ..write('pendingUploads: $pendingUploads, ') + ..write('queuedDownloads: $queuedDownloads, ') + ..write('queuedUploads: $queuedUploads, ') + ..write('failedDownloads: $failedDownloads, ') + ..write('failedUploads: $failedUploads, ') + ..write('completedUploads: $completedUploads, ') + ..write('skippedUploads: $skippedUploads') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + pendingDownloads, + pendingUploads, + queuedDownloads, + queuedUploads, + failedDownloads, + failedUploads, + completedUploads, + skippedUploads, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.UploadTaskStat && + other.pendingDownloads == this.pendingDownloads && + other.pendingUploads == this.pendingUploads && + other.queuedDownloads == this.queuedDownloads && + other.queuedUploads == this.queuedUploads && + other.failedDownloads == this.failedDownloads && + other.failedUploads == this.failedUploads && + other.completedUploads == this.completedUploads && + other.skippedUploads == this.skippedUploads); +} + +class UploadTaskStatsCompanion extends i0.UpdateCompanion { + final i0.Value pendingDownloads; + final i0.Value pendingUploads; + final i0.Value queuedDownloads; + final i0.Value queuedUploads; + final i0.Value failedDownloads; + final i0.Value failedUploads; + final i0.Value completedUploads; + final i0.Value skippedUploads; + final i0.Value rowid; + const UploadTaskStatsCompanion({ + this.pendingDownloads = const i0.Value.absent(), + this.pendingUploads = const i0.Value.absent(), + this.queuedDownloads = const i0.Value.absent(), + this.queuedUploads = const i0.Value.absent(), + this.failedDownloads = const i0.Value.absent(), + this.failedUploads = const i0.Value.absent(), + this.completedUploads = const i0.Value.absent(), + this.skippedUploads = const i0.Value.absent(), + this.rowid = const i0.Value.absent(), + }); + UploadTaskStatsCompanion.insert({ + required int pendingDownloads, + required int pendingUploads, + required int queuedDownloads, + required int queuedUploads, + required int failedDownloads, + required int failedUploads, + required int completedUploads, + required int skippedUploads, + this.rowid = const i0.Value.absent(), + }) : pendingDownloads = i0.Value(pendingDownloads), + pendingUploads = i0.Value(pendingUploads), + queuedDownloads = i0.Value(queuedDownloads), + queuedUploads = i0.Value(queuedUploads), + failedDownloads = i0.Value(failedDownloads), + failedUploads = i0.Value(failedUploads), + completedUploads = i0.Value(completedUploads), + skippedUploads = i0.Value(skippedUploads); + static i0.Insertable custom({ + i0.Expression? pendingDownloads, + i0.Expression? pendingUploads, + i0.Expression? queuedDownloads, + i0.Expression? queuedUploads, + i0.Expression? failedDownloads, + i0.Expression? failedUploads, + i0.Expression? completedUploads, + i0.Expression? skippedUploads, + i0.Expression? rowid, + }) { + return i0.RawValuesInsertable({ + if (pendingDownloads != null) 'pending_downloads': pendingDownloads, + if (pendingUploads != null) 'pending_uploads': pendingUploads, + if (queuedDownloads != null) 'queued_downloads': queuedDownloads, + if (queuedUploads != null) 'queued_uploads': queuedUploads, + if (failedDownloads != null) 'failed_downloads': failedDownloads, + if (failedUploads != null) 'failed_uploads': failedUploads, + if (completedUploads != null) 'completed_uploads': completedUploads, + if (skippedUploads != null) 'skipped_uploads': skippedUploads, + if (rowid != null) 'rowid': rowid, + }); + } + + i1.UploadTaskStatsCompanion copyWith({ + i0.Value? pendingDownloads, + i0.Value? pendingUploads, + i0.Value? queuedDownloads, + i0.Value? queuedUploads, + i0.Value? failedDownloads, + i0.Value? failedUploads, + i0.Value? completedUploads, + i0.Value? skippedUploads, + i0.Value? rowid, + }) { + return i1.UploadTaskStatsCompanion( + pendingDownloads: pendingDownloads ?? this.pendingDownloads, + pendingUploads: pendingUploads ?? this.pendingUploads, + queuedDownloads: queuedDownloads ?? this.queuedDownloads, + queuedUploads: queuedUploads ?? this.queuedUploads, + failedDownloads: failedDownloads ?? this.failedDownloads, + failedUploads: failedUploads ?? this.failedUploads, + completedUploads: completedUploads ?? this.completedUploads, + skippedUploads: skippedUploads ?? this.skippedUploads, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (pendingDownloads.present) { + map['pending_downloads'] = i0.Variable(pendingDownloads.value); + } + if (pendingUploads.present) { + map['pending_uploads'] = i0.Variable(pendingUploads.value); + } + if (queuedDownloads.present) { + map['queued_downloads'] = i0.Variable(queuedDownloads.value); + } + if (queuedUploads.present) { + map['queued_uploads'] = i0.Variable(queuedUploads.value); + } + if (failedDownloads.present) { + map['failed_downloads'] = i0.Variable(failedDownloads.value); + } + if (failedUploads.present) { + map['failed_uploads'] = i0.Variable(failedUploads.value); + } + if (completedUploads.present) { + map['completed_uploads'] = i0.Variable(completedUploads.value); + } + if (skippedUploads.present) { + map['skipped_uploads'] = i0.Variable(skippedUploads.value); + } + if (rowid.present) { + map['rowid'] = i0.Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UploadTaskStatsCompanion(') + ..write('pendingDownloads: $pendingDownloads, ') + ..write('pendingUploads: $pendingUploads, ') + ..write('queuedDownloads: $queuedDownloads, ') + ..write('queuedUploads: $queuedUploads, ') + ..write('failedDownloads: $failedDownloads, ') + ..write('failedUploads: $failedUploads, ') + ..write('completedUploads: $completedUploads, ') + ..write('skippedUploads: $skippedUploads, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +i0.Trigger get updateStatsInsert => i0.Trigger( + 'CREATE TRIGGER update_stats_insert BEFORE INSERT ON upload_tasks BEGIN UPDATE upload_task_stats SET pending_downloads = pending_downloads +(NEW.status = 0), queued_downloads = queued_downloads +(NEW.status = 1), failed_downloads = failed_downloads +(NEW.status = 2), pending_uploads = pending_uploads +(NEW.status = 3), queued_uploads = queued_uploads +(NEW.status = 4), failed_uploads = failed_uploads +(NEW.status = 5), completed_uploads = completed_uploads +(NEW.status = 6), skipped_uploads = skipped_uploads +(NEW.status = 7);END', + 'update_stats_insert', +); +i0.Trigger get updateStatsUpdate => i0.Trigger( + 'CREATE TRIGGER update_stats_update BEFORE UPDATE OF status ON upload_tasks WHEN OLD.status != NEW.status BEGIN UPDATE upload_task_stats SET pending_downloads = pending_downloads -(OLD.status = 0)+(NEW.status = 0), queued_downloads = queued_downloads -(OLD.status = 1)+(NEW.status = 1), failed_downloads = failed_downloads -(OLD.status = 2)+(NEW.status = 2), pending_uploads = pending_uploads -(OLD.status = 3)+(NEW.status = 3), queued_uploads = queued_uploads -(OLD.status = 4)+(NEW.status = 4), failed_uploads = failed_uploads -(OLD.status = 5)+(NEW.status = 5), completed_uploads = completed_uploads -(OLD.status = 6)+(NEW.status = 6), skipped_uploads = skipped_uploads -(OLD.status = 7)+(NEW.status = 7);END', + 'update_stats_update', +); +i0.Trigger get updateStatsDelete => i0.Trigger( + 'CREATE TRIGGER update_stats_delete BEFORE DELETE ON upload_tasks BEGIN UPDATE upload_task_stats SET pending_downloads = pending_downloads -(OLD.status = 0), queued_downloads = queued_downloads -(OLD.status = 1), failed_downloads = failed_downloads -(OLD.status = 2), pending_uploads = pending_uploads -(OLD.status = 3), queued_uploads = queued_uploads -(OLD.status = 4), failed_uploads = failed_uploads -(OLD.status = 5), completed_uploads = completed_uploads -(OLD.status = 6), skipped_uploads = skipped_uploads -(OLD.status = 7);END', + 'update_stats_delete', +); +i0.Index get idxUploadTasksLocalId => i0.Index( + 'idx_upload_tasks_local_id', + 'CREATE UNIQUE INDEX idx_upload_tasks_local_id ON upload_tasks (local_id, live_photo_video_id)', +); +i0.Index get idxUploadTasksAssetData => i0.Index( + 'idx_upload_tasks_asset_data', + 'CREATE INDEX idx_upload_tasks_asset_data ON upload_tasks (status, priority DESC, created_at)', +); +i0.OnCreateQuery get $drift0 => i0.OnCreateQuery( + 'INSERT INTO upload_task_stats VALUES (0, 0, 0, 0, 0, 0, 0, 0)', +); + +class UploadTasksDrift extends i2.ModularAccessor { + UploadTasksDrift(i0.GeneratedDatabase db) : super(db); + i1.UploadTaskStats get uploadTaskStats => i2.ReadDatabaseContainer( + attachedDatabase, + ).resultSet('upload_task_stats'); +} diff --git a/mobile/lib/infrastructure/repositories/db.repository.dart b/mobile/lib/infrastructure/repositories/db.repository.dart index 548aa2e384..538be43cc0 100644 --- a/mobile/lib/infrastructure/repositories/db.repository.dart +++ b/mobile/lib/infrastructure/repositories/db.repository.dart @@ -21,6 +21,7 @@ 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/stack.entity.dart'; import 'package:immich_mobile/infrastructure/entities/store.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/upload_tasks.drift.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.steps.dart'; @@ -65,7 +66,10 @@ class IsarDatabaseRepository implements IDatabaseRepository { StoreEntity, TrashedLocalAssetEntity, ], - include: {'package:immich_mobile/infrastructure/entities/merged_asset.drift'}, + include: { + 'package:immich_mobile/infrastructure/entities/merged_asset.drift', + 'package:immich_mobile/infrastructure/entities/upload_tasks.drift', + }, ) class Drift extends $Drift implements IDatabaseRepository { Drift([QueryExecutor? executor]) @@ -95,7 +99,7 @@ class Drift extends $Drift implements IDatabaseRepository { } @override - int get schemaVersion => 13; + int get schemaVersion => 14; @override MigrationStrategy get migration => MigrationStrategy( @@ -185,6 +189,16 @@ class Drift extends $Drift implements IDatabaseRepository { await m.createIndex(v13.idxTrashedLocalAssetChecksum); await m.createIndex(v13.idxTrashedLocalAssetAlbum); }, + from13To14: (m, v14) async { + await m.createTable(UploadTasks(m.database)); + await m.createTable(UploadTaskStats(m.database)); + await m.create($drift0); + await m.createTrigger(updateStatsInsert); + await m.createTrigger(updateStatsUpdate); + await m.createTrigger(updateStatsDelete); + await m.createIndex(idxUploadTasksLocalId); + await m.createIndex(idxUploadTasksAssetData); + }, ), ); diff --git a/mobile/lib/infrastructure/repositories/db.repository.drift.dart b/mobile/lib/infrastructure/repositories/db.repository.drift.dart index bd72da949c..7a8f595561 100644 --- a/mobile/lib/infrastructure/repositories/db.repository.drift.dart +++ b/mobile/lib/infrastructure/repositories/db.repository.drift.dart @@ -1,94 +1,109 @@ // dart format width=80 // ignore_for_file: type=lint import 'package:drift/drift.dart' as i0; -import 'package:immich_mobile/infrastructure/entities/user.entity.drift.dart' +import 'package:immich_mobile/infrastructure/entities/upload_tasks.drift.dart' as i1; -import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart' +import 'package:immich_mobile/infrastructure/entities/user.entity.drift.dart' as i2; -import 'package:immich_mobile/infrastructure/entities/stack.entity.drift.dart' +import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart' as i3; -import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart' +import 'package:immich_mobile/infrastructure/entities/stack.entity.drift.dart' as i4; -import 'package:immich_mobile/infrastructure/entities/remote_album.entity.drift.dart' +import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart' as i5; -import 'package:immich_mobile/infrastructure/entities/local_album.entity.drift.dart' +import 'package:immich_mobile/infrastructure/entities/remote_album.entity.drift.dart' as i6; -import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.drift.dart' +import 'package:immich_mobile/infrastructure/entities/local_album.entity.drift.dart' as i7; -import 'package:immich_mobile/infrastructure/entities/auth_user.entity.drift.dart' +import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.drift.dart' as i8; -import 'package:immich_mobile/infrastructure/entities/user_metadata.entity.drift.dart' +import 'package:immich_mobile/infrastructure/entities/auth_user.entity.drift.dart' as i9; -import 'package:immich_mobile/infrastructure/entities/partner.entity.drift.dart' +import 'package:immich_mobile/infrastructure/entities/user_metadata.entity.drift.dart' as i10; -import 'package:immich_mobile/infrastructure/entities/exif.entity.drift.dart' +import 'package:immich_mobile/infrastructure/entities/partner.entity.drift.dart' as i11; -import 'package:immich_mobile/infrastructure/entities/remote_album_asset.entity.drift.dart' +import 'package:immich_mobile/infrastructure/entities/exif.entity.drift.dart' as i12; -import 'package:immich_mobile/infrastructure/entities/remote_album_user.entity.drift.dart' +import 'package:immich_mobile/infrastructure/entities/remote_album_asset.entity.drift.dart' as i13; -import 'package:immich_mobile/infrastructure/entities/memory.entity.drift.dart' +import 'package:immich_mobile/infrastructure/entities/remote_album_user.entity.drift.dart' as i14; -import 'package:immich_mobile/infrastructure/entities/memory_asset.entity.drift.dart' +import 'package:immich_mobile/infrastructure/entities/memory.entity.drift.dart' as i15; -import 'package:immich_mobile/infrastructure/entities/person.entity.drift.dart' +import 'package:immich_mobile/infrastructure/entities/memory_asset.entity.drift.dart' as i16; -import 'package:immich_mobile/infrastructure/entities/asset_face.entity.drift.dart' +import 'package:immich_mobile/infrastructure/entities/person.entity.drift.dart' as i17; -import 'package:immich_mobile/infrastructure/entities/store.entity.drift.dart' +import 'package:immich_mobile/infrastructure/entities/asset_face.entity.drift.dart' as i18; -import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.drift.dart' +import 'package:immich_mobile/infrastructure/entities/store.entity.drift.dart' as i19; -import 'package:immich_mobile/infrastructure/entities/merged_asset.drift.dart' +import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.drift.dart' as i20; -import 'package:drift/internal/modular.dart' as i21; +import 'package:immich_mobile/infrastructure/entities/merged_asset.drift.dart' + as i21; +import 'package:drift/internal/modular.dart' as i22; abstract class $Drift extends i0.GeneratedDatabase { $Drift(i0.QueryExecutor e) : super(e); $DriftManager get managers => $DriftManager(this); - late final i1.$UserEntityTable userEntity = i1.$UserEntityTable(this); - late final i2.$RemoteAssetEntityTable remoteAssetEntity = i2 + late final i1.UploadTasks uploadTasks = i1.UploadTasks(this); + late final i1.UploadTaskStats uploadTaskStats = i1.UploadTaskStats(this); + late final i2.$UserEntityTable userEntity = i2.$UserEntityTable(this); + late final i3.$RemoteAssetEntityTable remoteAssetEntity = i3 .$RemoteAssetEntityTable(this); - late final i3.$StackEntityTable stackEntity = i3.$StackEntityTable(this); - late final i4.$LocalAssetEntityTable localAssetEntity = i4 + late final i4.$StackEntityTable stackEntity = i4.$StackEntityTable(this); + late final i5.$LocalAssetEntityTable localAssetEntity = i5 .$LocalAssetEntityTable(this); - late final i5.$RemoteAlbumEntityTable remoteAlbumEntity = i5 + late final i6.$RemoteAlbumEntityTable remoteAlbumEntity = i6 .$RemoteAlbumEntityTable(this); - late final i6.$LocalAlbumEntityTable localAlbumEntity = i6 + late final i7.$LocalAlbumEntityTable localAlbumEntity = i7 .$LocalAlbumEntityTable(this); - late final i7.$LocalAlbumAssetEntityTable localAlbumAssetEntity = i7 + late final i8.$LocalAlbumAssetEntityTable localAlbumAssetEntity = i8 .$LocalAlbumAssetEntityTable(this); - late final i8.$AuthUserEntityTable authUserEntity = i8.$AuthUserEntityTable( + late final i9.$AuthUserEntityTable authUserEntity = i9.$AuthUserEntityTable( this, ); - late final i9.$UserMetadataEntityTable userMetadataEntity = i9 + late final i10.$UserMetadataEntityTable userMetadataEntity = i10 .$UserMetadataEntityTable(this); - late final i10.$PartnerEntityTable partnerEntity = i10.$PartnerEntityTable( + late final i11.$PartnerEntityTable partnerEntity = i11.$PartnerEntityTable( this, ); - late final i11.$RemoteExifEntityTable remoteExifEntity = i11 + late final i12.$RemoteExifEntityTable remoteExifEntity = i12 .$RemoteExifEntityTable(this); - late final i12.$RemoteAlbumAssetEntityTable remoteAlbumAssetEntity = i12 + late final i13.$RemoteAlbumAssetEntityTable remoteAlbumAssetEntity = i13 .$RemoteAlbumAssetEntityTable(this); - late final i13.$RemoteAlbumUserEntityTable remoteAlbumUserEntity = i13 + late final i14.$RemoteAlbumUserEntityTable remoteAlbumUserEntity = i14 .$RemoteAlbumUserEntityTable(this); - late final i14.$MemoryEntityTable memoryEntity = i14.$MemoryEntityTable(this); - late final i15.$MemoryAssetEntityTable memoryAssetEntity = i15 + late final i15.$MemoryEntityTable memoryEntity = i15.$MemoryEntityTable(this); + late final i16.$MemoryAssetEntityTable memoryAssetEntity = i16 .$MemoryAssetEntityTable(this); - late final i16.$PersonEntityTable personEntity = i16.$PersonEntityTable(this); - late final i17.$AssetFaceEntityTable assetFaceEntity = i17 + late final i17.$PersonEntityTable personEntity = i17.$PersonEntityTable(this); + late final i18.$AssetFaceEntityTable assetFaceEntity = i18 .$AssetFaceEntityTable(this); - late final i18.$StoreEntityTable storeEntity = i18.$StoreEntityTable(this); - late final i19.$TrashedLocalAssetEntityTable trashedLocalAssetEntity = i19 + late final i19.$StoreEntityTable storeEntity = i19.$StoreEntityTable(this); + late final i20.$TrashedLocalAssetEntityTable trashedLocalAssetEntity = i20 .$TrashedLocalAssetEntityTable(this); - i20.MergedAssetDrift get mergedAssetDrift => i21.ReadDatabaseContainer( + i21.MergedAssetDrift get mergedAssetDrift => i22.ReadDatabaseContainer( this, - ).accessor(i20.MergedAssetDrift.new); + ).accessor(i21.MergedAssetDrift.new); + i1.UploadTasksDrift get uploadTasksDrift => i22.ReadDatabaseContainer( + this, + ).accessor(i1.UploadTasksDrift.new); @override Iterable> get allTables => allSchemaEntities.whereType>(); @override List get allSchemaEntities => [ + uploadTasks, + uploadTaskStats, + i1.updateStatsInsert, + i1.updateStatsUpdate, + i1.updateStatsDelete, + i1.idxUploadTasksLocalId, + i1.idxUploadTasksAssetData, + i1.$drift0, userEntity, remoteAssetEntity, stackEntity, @@ -96,11 +111,11 @@ abstract class $Drift extends i0.GeneratedDatabase { remoteAlbumEntity, localAlbumEntity, localAlbumAssetEntity, - i4.idxLocalAssetChecksum, - i2.idxRemoteAssetOwnerChecksum, - i2.uQRemoteAssetsOwnerChecksum, - i2.uQRemoteAssetsOwnerLibraryChecksum, - i2.idxRemoteAssetChecksum, + i5.idxLocalAssetChecksum, + i3.idxRemoteAssetOwnerChecksum, + i3.uQRemoteAssetsOwnerChecksum, + i3.uQRemoteAssetsOwnerLibraryChecksum, + i3.idxRemoteAssetChecksum, authUserEntity, userMetadataEntity, partnerEntity, @@ -113,13 +128,34 @@ abstract class $Drift extends i0.GeneratedDatabase { assetFaceEntity, storeEntity, trashedLocalAssetEntity, - i11.idxLatLng, - i19.idxTrashedLocalAssetChecksum, - i19.idxTrashedLocalAssetAlbum, + i12.idxLatLng, + i20.idxTrashedLocalAssetChecksum, + i20.idxTrashedLocalAssetAlbum, ]; @override i0.StreamQueryUpdateRules get streamUpdateRules => const i0.StreamQueryUpdateRules([ + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName( + 'upload_tasks', + limitUpdateKind: i0.UpdateKind.insert, + ), + result: [i0.TableUpdate('upload_task_stats', kind: i0.UpdateKind.update)], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName( + 'upload_tasks', + limitUpdateKind: i0.UpdateKind.update, + ), + result: [i0.TableUpdate('upload_task_stats', kind: i0.UpdateKind.update)], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName( + 'upload_tasks', + limitUpdateKind: i0.UpdateKind.delete, + ), + result: [i0.TableUpdate('upload_task_stats', kind: i0.UpdateKind.update)], + ), i0.WritePropagation( on: i0.TableUpdateQuery.onTableName( 'user_entity', @@ -304,47 +340,51 @@ abstract class $Drift extends i0.GeneratedDatabase { class $DriftManager { final $Drift _db; $DriftManager(this._db); - i1.$$UserEntityTableTableManager get userEntity => - i1.$$UserEntityTableTableManager(_db, _db.userEntity); - i2.$$RemoteAssetEntityTableTableManager get remoteAssetEntity => - i2.$$RemoteAssetEntityTableTableManager(_db, _db.remoteAssetEntity); - i3.$$StackEntityTableTableManager get stackEntity => - i3.$$StackEntityTableTableManager(_db, _db.stackEntity); - i4.$$LocalAssetEntityTableTableManager get localAssetEntity => - i4.$$LocalAssetEntityTableTableManager(_db, _db.localAssetEntity); - i5.$$RemoteAlbumEntityTableTableManager get remoteAlbumEntity => - i5.$$RemoteAlbumEntityTableTableManager(_db, _db.remoteAlbumEntity); - i6.$$LocalAlbumEntityTableTableManager get localAlbumEntity => - i6.$$LocalAlbumEntityTableTableManager(_db, _db.localAlbumEntity); - i7.$$LocalAlbumAssetEntityTableTableManager get localAlbumAssetEntity => i7 + i1.$UploadTasksTableManager get uploadTasks => + i1.$UploadTasksTableManager(_db, _db.uploadTasks); + i1.$UploadTaskStatsTableManager get uploadTaskStats => + i1.$UploadTaskStatsTableManager(_db, _db.uploadTaskStats); + i2.$$UserEntityTableTableManager get userEntity => + i2.$$UserEntityTableTableManager(_db, _db.userEntity); + i3.$$RemoteAssetEntityTableTableManager get remoteAssetEntity => + i3.$$RemoteAssetEntityTableTableManager(_db, _db.remoteAssetEntity); + i4.$$StackEntityTableTableManager get stackEntity => + i4.$$StackEntityTableTableManager(_db, _db.stackEntity); + i5.$$LocalAssetEntityTableTableManager get localAssetEntity => + i5.$$LocalAssetEntityTableTableManager(_db, _db.localAssetEntity); + i6.$$RemoteAlbumEntityTableTableManager get remoteAlbumEntity => + i6.$$RemoteAlbumEntityTableTableManager(_db, _db.remoteAlbumEntity); + i7.$$LocalAlbumEntityTableTableManager get localAlbumEntity => + i7.$$LocalAlbumEntityTableTableManager(_db, _db.localAlbumEntity); + i8.$$LocalAlbumAssetEntityTableTableManager get localAlbumAssetEntity => i8 .$$LocalAlbumAssetEntityTableTableManager(_db, _db.localAlbumAssetEntity); - i8.$$AuthUserEntityTableTableManager get authUserEntity => - i8.$$AuthUserEntityTableTableManager(_db, _db.authUserEntity); - i9.$$UserMetadataEntityTableTableManager get userMetadataEntity => - i9.$$UserMetadataEntityTableTableManager(_db, _db.userMetadataEntity); - i10.$$PartnerEntityTableTableManager get partnerEntity => - i10.$$PartnerEntityTableTableManager(_db, _db.partnerEntity); - i11.$$RemoteExifEntityTableTableManager get remoteExifEntity => - i11.$$RemoteExifEntityTableTableManager(_db, _db.remoteExifEntity); - i12.$$RemoteAlbumAssetEntityTableTableManager get remoteAlbumAssetEntity => - i12.$$RemoteAlbumAssetEntityTableTableManager( + i9.$$AuthUserEntityTableTableManager get authUserEntity => + i9.$$AuthUserEntityTableTableManager(_db, _db.authUserEntity); + i10.$$UserMetadataEntityTableTableManager get userMetadataEntity => + i10.$$UserMetadataEntityTableTableManager(_db, _db.userMetadataEntity); + i11.$$PartnerEntityTableTableManager get partnerEntity => + i11.$$PartnerEntityTableTableManager(_db, _db.partnerEntity); + i12.$$RemoteExifEntityTableTableManager get remoteExifEntity => + i12.$$RemoteExifEntityTableTableManager(_db, _db.remoteExifEntity); + i13.$$RemoteAlbumAssetEntityTableTableManager get remoteAlbumAssetEntity => + i13.$$RemoteAlbumAssetEntityTableTableManager( _db, _db.remoteAlbumAssetEntity, ); - i13.$$RemoteAlbumUserEntityTableTableManager get remoteAlbumUserEntity => i13 + i14.$$RemoteAlbumUserEntityTableTableManager get remoteAlbumUserEntity => i14 .$$RemoteAlbumUserEntityTableTableManager(_db, _db.remoteAlbumUserEntity); - i14.$$MemoryEntityTableTableManager get memoryEntity => - i14.$$MemoryEntityTableTableManager(_db, _db.memoryEntity); - i15.$$MemoryAssetEntityTableTableManager get memoryAssetEntity => - i15.$$MemoryAssetEntityTableTableManager(_db, _db.memoryAssetEntity); - i16.$$PersonEntityTableTableManager get personEntity => - i16.$$PersonEntityTableTableManager(_db, _db.personEntity); - i17.$$AssetFaceEntityTableTableManager get assetFaceEntity => - i17.$$AssetFaceEntityTableTableManager(_db, _db.assetFaceEntity); - i18.$$StoreEntityTableTableManager get storeEntity => - i18.$$StoreEntityTableTableManager(_db, _db.storeEntity); - i19.$$TrashedLocalAssetEntityTableTableManager get trashedLocalAssetEntity => - i19.$$TrashedLocalAssetEntityTableTableManager( + i15.$$MemoryEntityTableTableManager get memoryEntity => + i15.$$MemoryEntityTableTableManager(_db, _db.memoryEntity); + i16.$$MemoryAssetEntityTableTableManager get memoryAssetEntity => + i16.$$MemoryAssetEntityTableTableManager(_db, _db.memoryAssetEntity); + i17.$$PersonEntityTableTableManager get personEntity => + i17.$$PersonEntityTableTableManager(_db, _db.personEntity); + i18.$$AssetFaceEntityTableTableManager get assetFaceEntity => + i18.$$AssetFaceEntityTableTableManager(_db, _db.assetFaceEntity); + i19.$$StoreEntityTableTableManager get storeEntity => + i19.$$StoreEntityTableTableManager(_db, _db.storeEntity); + i20.$$TrashedLocalAssetEntityTableTableManager get trashedLocalAssetEntity => + i20.$$TrashedLocalAssetEntityTableTableManager( _db, _db.trashedLocalAssetEntity, ); diff --git a/mobile/lib/infrastructure/repositories/db.repository.steps.dart b/mobile/lib/infrastructure/repositories/db.repository.steps.dart index f2d87a7f83..0d5b9bfa00 100644 --- a/mobile/lib/infrastructure/repositories/db.repository.steps.dart +++ b/mobile/lib/infrastructure/repositories/db.repository.steps.dart @@ -5485,6 +5485,701 @@ i1.GeneratedColumn _column_95(String aliasedName) => false, type: i1.DriftSqlType.string, ); + +final class Schema14 extends i0.VersionedSchema { + Schema14({required super.database}) : super(version: 14); + @override + late final List entities = [ + uploadTasks, + uploadTaskStats, + updateStatsInsert, + updateStatsUpdate, + updateStatsDelete, + idxUploadTasksLocalId, + idxUploadTasksAssetData, + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + remoteAlbumEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAssetChecksum, + idxRemoteAssetOwnerChecksum, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + authUserEntity, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + storeEntity, + trashedLocalAssetEntity, + idxLatLng, + idxTrashedLocalAssetChecksum, + idxTrashedLocalAssetAlbum, + ]; + late final Shape24 uploadTasks = Shape24( + source: i0.VersionedTable( + entityName: 'upload_tasks', + withoutRowId: false, + isStrict: false, + tableConstraints: [], + columns: [ + _column_96, + _column_97, + _column_98, + _column_99, + _column_100, + _column_101, + _column_102, + _column_103, + _column_104, + _column_105, + _column_106, + _column_107, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape25 uploadTaskStats = Shape25( + source: i0.VersionedTable( + entityName: 'upload_task_stats', + withoutRowId: false, + isStrict: false, + tableConstraints: [], + columns: [ + _column_108, + _column_109, + _column_110, + _column_111, + _column_112, + _column_113, + _column_114, + _column_115, + ], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Trigger updateStatsInsert = i1.Trigger( + 'CREATE TRIGGER update_stats_insert BEFORE INSERT ON upload_tasks BEGIN UPDATE upload_task_stats SET pending_downloads = pending_downloads +(NEW.status = 0), queued_downloads = queued_downloads +(NEW.status = 1), failed_downloads = failed_downloads +(NEW.status = 2), pending_uploads = pending_uploads +(NEW.status = 3), queued_uploads = queued_uploads +(NEW.status = 4), failed_uploads = failed_uploads +(NEW.status = 5), completed_uploads = completed_uploads +(NEW.status = 6), skipped_uploads = skipped_uploads +(NEW.status = 7);END', + 'update_stats_insert', + ); + final i1.Trigger updateStatsUpdate = i1.Trigger( + 'CREATE TRIGGER update_stats_update BEFORE UPDATE OF status ON upload_tasks WHEN OLD.status != NEW.status BEGIN UPDATE upload_task_stats SET pending_downloads = pending_downloads -(OLD.status = 0)+(NEW.status = 0), queued_downloads = queued_downloads -(OLD.status = 1)+(NEW.status = 1), failed_downloads = failed_downloads -(OLD.status = 2)+(NEW.status = 2), pending_uploads = pending_uploads -(OLD.status = 3)+(NEW.status = 3), queued_uploads = queued_uploads -(OLD.status = 4)+(NEW.status = 4), failed_uploads = failed_uploads -(OLD.status = 5)+(NEW.status = 5), completed_uploads = completed_uploads -(OLD.status = 6)+(NEW.status = 6), skipped_uploads = skipped_uploads -(OLD.status = 7)+(NEW.status = 7);END', + 'update_stats_update', + ); + final i1.Trigger updateStatsDelete = i1.Trigger( + 'CREATE TRIGGER update_stats_delete BEFORE DELETE ON upload_tasks BEGIN UPDATE upload_task_stats SET pending_downloads = pending_downloads -(OLD.status = 0), queued_downloads = queued_downloads -(OLD.status = 1), failed_downloads = failed_downloads -(OLD.status = 2), pending_uploads = pending_uploads -(OLD.status = 3), queued_uploads = queued_uploads -(OLD.status = 4), failed_uploads = failed_uploads -(OLD.status = 5), completed_uploads = completed_uploads -(OLD.status = 6), skipped_uploads = skipped_uploads -(OLD.status = 7);END', + 'update_stats_delete', + ); + final i1.Index idxUploadTasksLocalId = i1.Index( + 'idx_upload_tasks_local_id', + 'CREATE UNIQUE INDEX idx_upload_tasks_local_id ON upload_tasks (local_id, live_photo_video_id)', + ); + final i1.Index idxUploadTasksAssetData = i1.Index( + 'idx_upload_tasks_asset_data', + 'CREATE INDEX idx_upload_tasks_asset_data ON upload_tasks (status, priority DESC, created_at)', + ); + late final Shape20 userEntity = Shape20( + source: i0.VersionedTable( + entityName: 'user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_3, + _column_84, + _column_85, + _column_91, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape17 remoteAssetEntity = Shape17( + source: i0.VersionedTable( + entityName: 'remote_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_13, + _column_14, + _column_15, + _column_16, + _column_17, + _column_18, + _column_19, + _column_20, + _column_21, + _column_86, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape3 stackEntity = Shape3( + source: i0.VersionedTable( + entityName: 'stack_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [_column_0, _column_9, _column_5, _column_15, _column_75], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape2 localAssetEntity = Shape2( + source: i0.VersionedTable( + entityName: 'local_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_22, + _column_14, + _column_23, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape9 remoteAlbumEntity = Shape9( + source: i0.VersionedTable( + entityName: 'remote_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_56, + _column_9, + _column_5, + _column_15, + _column_57, + _column_58, + _column_59, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape19 localAlbumEntity = Shape19( + source: i0.VersionedTable( + entityName: 'local_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_5, + _column_31, + _column_32, + _column_90, + _column_33, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape22 localAlbumAssetEntity = Shape22( + source: i0.VersionedTable( + entityName: 'local_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_34, _column_35, _column_33], + attachedDatabase: database, + ), + alias: null, + ); + 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 idxRemoteAssetOwnerChecksum = i1.Index( + 'idx_remote_asset_owner_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)', + ); + 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)', + ); + late final Shape21 authUserEntity = Shape21( + source: i0.VersionedTable( + entityName: 'auth_user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_3, + _column_2, + _column_84, + _column_85, + _column_92, + _column_93, + _column_7, + _column_94, + ], + 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_25, _column_26, _column_27], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape5 partnerEntity = Shape5( + source: i0.VersionedTable( + entityName: 'partner_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(shared_by_id, shared_with_id)'], + columns: [_column_28, _column_29, _column_30], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape8 remoteExifEntity = Shape8( + source: i0.VersionedTable( + entityName: 'remote_exif_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id)'], + columns: [ + _column_36, + _column_37, + _column_38, + _column_39, + _column_40, + _column_41, + _column_11, + _column_10, + _column_42, + _column_43, + _column_44, + _column_45, + _column_46, + _column_47, + _column_48, + _column_49, + _column_50, + _column_51, + _column_52, + _column_53, + _column_54, + _column_55, + ], + 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_36, _column_60], + 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_60, _column_25, _column_61], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape11 memoryEntity = Shape11( + source: i0.VersionedTable( + entityName: 'memory_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_9, + _column_5, + _column_18, + _column_15, + _column_8, + _column_62, + _column_63, + _column_64, + _column_65, + _column_66, + _column_67, + ], + 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_36, _column_68], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape14 personEntity = Shape14( + source: i0.VersionedTable( + entityName: 'person_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_9, + _column_5, + _column_15, + _column_1, + _column_69, + _column_71, + _column_72, + _column_73, + _column_74, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape15 assetFaceEntity = Shape15( + source: i0.VersionedTable( + entityName: 'asset_face_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_36, + _column_76, + _column_77, + _column_78, + _column_79, + _column_80, + _column_81, + _column_82, + _column_83, + ], + 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_87, _column_88, _column_89], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape23 trashedLocalAssetEntity = Shape23( + source: i0.VersionedTable( + entityName: 'trashed_local_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id, album_id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_95, + _column_22, + _column_14, + _column_23, + ], + attachedDatabase: database, + ), + alias: null, + ); + 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 idxTrashedLocalAssetChecksum = i1.Index( + 'idx_trashed_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)', + ); + final i1.Index idxTrashedLocalAssetAlbum = i1.Index( + 'idx_trashed_local_asset_album', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)', + ); +} + +class Shape24 extends i0.VersionedTable { + Shape24({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get id => + columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get attempts => + columnsByName['attempts']! as i1.GeneratedColumn; + i1.GeneratedColumn get createdAt => + columnsByName['created_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get filePath => + columnsByName['file_path']! as i1.GeneratedColumn; + i1.GeneratedColumn get isLivePhoto => + columnsByName['is_live_photo']! as i1.GeneratedColumn; + i1.GeneratedColumn get lastError => + columnsByName['last_error']! as i1.GeneratedColumn; + i1.GeneratedColumn get livePhotoVideoId => + columnsByName['live_photo_video_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get localId => + columnsByName['local_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get method => + columnsByName['method']! as i1.GeneratedColumn; + i1.GeneratedColumn get priority => + columnsByName['priority']! as i1.GeneratedColumn; + i1.GeneratedColumn get retryAfter => + columnsByName['retry_after']! as i1.GeneratedColumn; + i1.GeneratedColumn get status => + columnsByName['status']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_96(String aliasedName) => + i1.GeneratedColumn( + 'id', + aliasedName, + true, + hasAutoIncrement: true, + type: i1.DriftSqlType.int, + $customConstraints: 'PRIMARY KEY AUTOINCREMENT', + ); +i1.GeneratedColumn _column_97(String aliasedName) => + i1.GeneratedColumn( + 'attempts', + aliasedName, + false, + type: i1.DriftSqlType.int, + $customConstraints: 'NOT NULL', + ); +i1.GeneratedColumn _column_98(String aliasedName) => + i1.GeneratedColumn( + 'created_at', + aliasedName, + false, + type: i1.DriftSqlType.int, + $customConstraints: 'NOT NULL', + ); +i1.GeneratedColumn _column_99(String aliasedName) => + i1.GeneratedColumn( + 'file_path', + aliasedName, + true, + type: i1.DriftSqlType.string, + $customConstraints: '', + ); +i1.GeneratedColumn _column_100(String aliasedName) => + i1.GeneratedColumn( + 'is_live_photo', + aliasedName, + true, + type: i1.DriftSqlType.int, + $customConstraints: '', + ); +i1.GeneratedColumn _column_101(String aliasedName) => + i1.GeneratedColumn( + 'last_error', + aliasedName, + true, + type: i1.DriftSqlType.int, + $customConstraints: '', + ); +i1.GeneratedColumn _column_102(String aliasedName) => + i1.GeneratedColumn( + 'live_photo_video_id', + aliasedName, + true, + type: i1.DriftSqlType.string, + $customConstraints: '', + ); +i1.GeneratedColumn _column_103(String aliasedName) => + i1.GeneratedColumn( + 'local_id', + aliasedName, + false, + type: i1.DriftSqlType.string, + $customConstraints: 'NOT NULL', + ); +i1.GeneratedColumn _column_104(String aliasedName) => + i1.GeneratedColumn( + 'method', + aliasedName, + false, + type: i1.DriftSqlType.int, + $customConstraints: 'NOT NULL', + ); +i1.GeneratedColumn _column_105(String aliasedName) => + i1.GeneratedColumn( + 'priority', + aliasedName, + false, + type: i1.DriftSqlType.double, + $customConstraints: 'NOT NULL', + ); +i1.GeneratedColumn _column_106(String aliasedName) => + i1.GeneratedColumn( + 'retry_after', + aliasedName, + true, + type: i1.DriftSqlType.int, + $customConstraints: '', + ); +i1.GeneratedColumn _column_107(String aliasedName) => + i1.GeneratedColumn( + 'status', + aliasedName, + false, + type: i1.DriftSqlType.int, + $customConstraints: 'NOT NULL', + ); + +class Shape25 extends i0.VersionedTable { + Shape25({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get pendingDownloads => + columnsByName['pending_downloads']! as i1.GeneratedColumn; + i1.GeneratedColumn get pendingUploads => + columnsByName['pending_uploads']! as i1.GeneratedColumn; + i1.GeneratedColumn get queuedDownloads => + columnsByName['queued_downloads']! as i1.GeneratedColumn; + i1.GeneratedColumn get queuedUploads => + columnsByName['queued_uploads']! as i1.GeneratedColumn; + i1.GeneratedColumn get failedDownloads => + columnsByName['failed_downloads']! as i1.GeneratedColumn; + i1.GeneratedColumn get failedUploads => + columnsByName['failed_uploads']! as i1.GeneratedColumn; + i1.GeneratedColumn get completedUploads => + columnsByName['completed_uploads']! as i1.GeneratedColumn; + i1.GeneratedColumn get skippedUploads => + columnsByName['skipped_uploads']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_108(String aliasedName) => + i1.GeneratedColumn( + 'pending_downloads', + aliasedName, + false, + type: i1.DriftSqlType.int, + $customConstraints: 'NOT NULL', + ); +i1.GeneratedColumn _column_109(String aliasedName) => + i1.GeneratedColumn( + 'pending_uploads', + aliasedName, + false, + type: i1.DriftSqlType.int, + $customConstraints: 'NOT NULL', + ); +i1.GeneratedColumn _column_110(String aliasedName) => + i1.GeneratedColumn( + 'queued_downloads', + aliasedName, + false, + type: i1.DriftSqlType.int, + $customConstraints: 'NOT NULL', + ); +i1.GeneratedColumn _column_111(String aliasedName) => + i1.GeneratedColumn( + 'queued_uploads', + aliasedName, + false, + type: i1.DriftSqlType.int, + $customConstraints: 'NOT NULL', + ); +i1.GeneratedColumn _column_112(String aliasedName) => + i1.GeneratedColumn( + 'failed_downloads', + aliasedName, + false, + type: i1.DriftSqlType.int, + $customConstraints: 'NOT NULL', + ); +i1.GeneratedColumn _column_113(String aliasedName) => + i1.GeneratedColumn( + 'failed_uploads', + aliasedName, + false, + type: i1.DriftSqlType.int, + $customConstraints: 'NOT NULL', + ); +i1.GeneratedColumn _column_114(String aliasedName) => + i1.GeneratedColumn( + 'completed_uploads', + aliasedName, + false, + type: i1.DriftSqlType.int, + $customConstraints: 'NOT NULL', + ); +i1.GeneratedColumn _column_115(String aliasedName) => + i1.GeneratedColumn( + 'skipped_uploads', + aliasedName, + false, + type: i1.DriftSqlType.int, + $customConstraints: 'NOT NULL', + ); i0.MigrationStepWithVersion migrationSteps({ required Future Function(i1.Migrator m, Schema2 schema) from1To2, required Future Function(i1.Migrator m, Schema3 schema) from2To3, @@ -5498,6 +6193,7 @@ i0.MigrationStepWithVersion migrationSteps({ required Future Function(i1.Migrator m, Schema11 schema) from10To11, required Future Function(i1.Migrator m, Schema12 schema) from11To12, required Future Function(i1.Migrator m, Schema13 schema) from12To13, + required Future Function(i1.Migrator m, Schema14 schema) from13To14, }) { return (currentVersion, database) async { switch (currentVersion) { @@ -5561,6 +6257,11 @@ i0.MigrationStepWithVersion migrationSteps({ final migrator = i1.Migrator(database, schema); await from12To13(migrator, schema); return 13; + case 13: + final schema = Schema14(database: database); + final migrator = i1.Migrator(database, schema); + await from13To14(migrator, schema); + return 14; default: throw ArgumentError.value('Unknown migration from $currentVersion'); } @@ -5580,6 +6281,7 @@ i1.OnUpgrade stepByStep({ required Future Function(i1.Migrator m, Schema11 schema) from10To11, required Future Function(i1.Migrator m, Schema12 schema) from11To12, required Future Function(i1.Migrator m, Schema13 schema) from12To13, + required Future Function(i1.Migrator m, Schema14 schema) from13To14, }) => i0.VersionedSchema.stepByStepHelper( step: migrationSteps( from1To2: from1To2, @@ -5594,5 +6296,6 @@ i1.OnUpgrade stepByStep({ from10To11: from10To11, from11To12: from11To12, from12To13: from12To13, + from13To14: from13To14, ), ); diff --git a/mobile/lib/infrastructure/repositories/storage.repository.dart b/mobile/lib/infrastructure/repositories/storage.repository.dart index 9532025d58..164fa04529 100644 --- a/mobile/lib/infrastructure/repositories/storage.repository.dart +++ b/mobile/lib/infrastructure/repositories/storage.repository.dart @@ -1,7 +1,6 @@ import 'dart:io'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/extensions/platform_extensions.dart'; import 'package:logging/logging.dart'; import 'package:photo_manager/photo_manager.dart'; @@ -90,17 +89,5 @@ class StorageRepository { } catch (error, stackTrace) { log.warning("Error clearing cache", error, stackTrace); } - - if (!CurrentPlatform.isIOS) { - return; - } - - try { - if (await Directory.systemTemp.exists()) { - await Directory.systemTemp.delete(recursive: true); - } - } catch (error, stackTrace) { - log.warning("Error deleting temporary directory", error, stackTrace); - } } } diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart index c3804d97f6..e032722da2 100644 --- a/mobile/lib/main.dart +++ b/mobile/lib/main.dart @@ -3,7 +3,6 @@ import 'dart:io'; import 'dart:math'; import 'package:auto_route/auto_route.dart'; -import 'package:background_downloader/background_downloader.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/foundation.dart'; @@ -11,7 +10,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_displaymode/flutter_displaymode.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/constants/constants.dart'; import 'package:immich_mobile/constants/locales.dart'; import 'package:immich_mobile/domain/services/background_worker.service.dart'; import 'package:immich_mobile/entities/store.entity.dart'; @@ -55,6 +53,9 @@ void main() async { // Warm-up isolate pool for worker manager await workerManagerPatch.init(dynamicSpawning: true, isolatesCount: max(Platform.numberOfProcessors - 1, 5)); await migrateDatabaseIfNeeded(isar, drift); + if (Store.isBetaTimelineEnabled) { + await uploadApi.initialize(); + } HttpSSLOptions.apply(); runApp( @@ -102,18 +103,6 @@ Future initApp() async { initializeTimeZones(); - // Initialize the file downloader - await FileDownloader().configure( - // maxConcurrent: 6, maxConcurrentByHost(server):6, maxConcurrentByGroup: 3 - - // On Android, if files are larger than 256MB, run in foreground service - globalConfig: [(Config.holdingQueue, (6, 6, 3)), (Config.runInForegroundIfFileLargerThan, 256)], - ); - - await FileDownloader().trackTasksInGroup(kDownloadGroupLivePhoto, markDownloadedComplete: false); - - await FileDownloader().trackTasks(); - LicenseRegistry.addLicense(() async* { for (final license in nonPubLicenses.entries) { yield LicenseEntryWithLineBreaks([license.key], license.value); @@ -199,9 +188,6 @@ class ImmichAppState extends ConsumerState with WidgetsBindingObserve void didChangeDependencies() { super.didChangeDependencies(); Intl.defaultLocale = context.locale.toLanguageTag(); - WidgetsBinding.instance.addPostFrameCallback((_) { - configureFileDownloaderNotifications(); - }); } @override diff --git a/mobile/lib/models/upload/share_intent_attachment.model.dart b/mobile/lib/models/upload/share_intent_attachment.model.dart index ae05e4c492..1e9db08310 100644 --- a/mobile/lib/models/upload/share_intent_attachment.model.dart +++ b/mobile/lib/models/upload/share_intent_attachment.model.dart @@ -7,7 +7,7 @@ import 'package:path/path.dart'; enum ShareIntentAttachmentType { image, video } -enum UploadStatus { enqueued, running, complete, notFound, failed, canceled, waitingToRetry, paused } +enum UploadStatus { enqueued, running, complete, notFound, failed, canceled, waitingToRetry, paused, preparing } class ShareIntentAttachment { final String path; diff --git a/mobile/lib/pages/backup/drift_backup.page.dart b/mobile/lib/pages/backup/drift_backup.page.dart index 47052ea436..3cb5c74fce 100644 --- a/mobile/lib/pages/backup/drift_backup.page.dart +++ b/mobile/lib/pages/backup/drift_backup.page.dart @@ -93,7 +93,7 @@ class _DriftBackupPageState extends ConsumerState { Logger("DriftBackupPage").warning("Remote sync did not complete successfully, skipping backup"); return; } - await backupNotifier.startBackup(currentUser.id); + await backupNotifier.startBackup(); } Future stopBackup() async { diff --git a/mobile/lib/pages/backup/drift_backup_album_selection.page.dart b/mobile/lib/pages/backup/drift_backup_album_selection.page.dart index cae9f0a408..5aa8294dbb 100644 --- a/mobile/lib/pages/backup/drift_backup_album_selection.page.dart +++ b/mobile/lib/pages/backup/drift_backup_album_selection.page.dart @@ -116,11 +116,10 @@ class _DriftBackupAlbumSelectionPageState extends ConsumerState backgroundSync.syncRemote().then((success) { - if (success) { - return backupNotifier.startBackup(user.id); - } else { - Logger('DriftBackupAlbumSelectionPage').warning('Background sync failed, not starting backup'); + if (!success) { + Logger('DriftBackupAlbumSelectionPage').warning('Remote sync failed'); } + return backupNotifier.startBackup(); }), ), ); diff --git a/mobile/lib/pages/backup/drift_backup_options.page.dart b/mobile/lib/pages/backup/drift_backup_options.page.dart index 1e5c326478..ca714bcef4 100644 --- a/mobile/lib/pages/backup/drift_backup_options.page.dart +++ b/mobile/lib/pages/backup/drift_backup_options.page.dart @@ -63,7 +63,7 @@ class DriftBackupOptionsPage extends ConsumerWidget { backupNotifier.cancel().whenComplete( () => backgroundSync.syncRemote().then((success) { if (success) { - return backupNotifier.startBackup(currentUser.id); + return backupNotifier.startBackup(); } else { Logger('DriftBackupOptionsPage').warning('Background sync failed, not starting backup'); } diff --git a/mobile/lib/pages/common/splash_screen.page.dart b/mobile/lib/pages/common/splash_screen.page.dart index 79db33104d..27da5a4624 100644 --- a/mobile/lib/pages/common/splash_screen.page.dart +++ b/mobile/lib/pages/common/splash_screen.page.dart @@ -8,8 +8,8 @@ import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/providers/auth.provider.dart'; import 'package:immich_mobile/providers/background_sync.provider.dart'; import 'package:immich_mobile/providers/backup/backup.provider.dart'; -import 'package:immich_mobile/providers/backup/drift_backup.provider.dart'; import 'package:immich_mobile/providers/gallery_permission.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/providers/websocket.provider.dart'; import 'package:immich_mobile/routing/router.dart'; @@ -53,7 +53,6 @@ class SplashScreenPageState extends ConsumerState { final infoProvider = ref.read(serverInfoProvider.notifier); final wsProvider = ref.read(websocketProvider.notifier); final backgroundManager = ref.read(backgroundSyncProvider); - final backupProvider = ref.read(driftBackupProvider.notifier); unawaited( ref.read(authProvider.notifier).saveAuthInfo(accessToken: accessToken).then( @@ -63,22 +62,13 @@ class SplashScreenPageState extends ConsumerState { unawaited(infoProvider.getServerInfo()); if (Store.isBetaTimelineEnabled) { - bool syncSuccess = false; await Future.wait([ backgroundManager.syncLocal(full: true), - backgroundManager.syncRemote().then((success) => syncSuccess = success), + backgroundManager.syncRemote(), ]); - if (syncSuccess) { - await Future.wait([ - backgroundManager.hashAssets().then((_) { - _resumeBackup(backupProvider); - }), - _resumeBackup(backupProvider), - ]); - } else { - await backgroundManager.hashAssets(); - } + await backgroundManager.hashAssets(); + await uploadApi.refresh(); if (Store.get(StoreKey.syncAlbums, false)) { await backgroundManager.syncLinkedAlbum(); @@ -126,17 +116,6 @@ class SplashScreenPageState extends ConsumerState { } } - Future _resumeBackup(DriftBackupNotifier notifier) async { - final isEnableBackup = Store.get(StoreKey.enableBackup, false); - - if (isEnableBackup) { - final currentUser = Store.tryGet(StoreKey.currentUser); - if (currentUser != null) { - unawaited(notifier.handleBackupResume(currentUser.id)); - } - } - } - @override Widget build(BuildContext context) { return const Scaffold( diff --git a/mobile/lib/pages/share_intent/share_intent.page.dart b/mobile/lib/pages/share_intent/share_intent.page.dart index 9d2dbe80c2..f1bab65d55 100644 --- a/mobile/lib/pages/share_intent/share_intent.page.dart +++ b/mobile/lib/pages/share_intent/share_intent.page.dart @@ -37,10 +37,7 @@ class ShareIntentPage extends HookConsumerWidget { } void upload() async { - for (final attachment in candidates) { - await ref.read(shareIntentUploadProvider.notifier).upload(attachment.file); - } - + await ref.read(shareIntentUploadProvider.notifier).upload(candidates); isUploaded.value = true; } @@ -212,6 +209,11 @@ class UploadStatusIcon extends StatelessWidget { color: context.primaryColor, semanticLabel: 'paused'.tr(), ), + UploadStatus.preparing => Icon( + Icons.hourglass_top_rounded, + color: context.primaryColor, + semanticLabel: 'preparing'.tr(), + ), }; return statusIcon; diff --git a/mobile/lib/platform/upload_api.g.dart b/mobile/lib/platform/upload_api.g.dart new file mode 100644 index 0000000000..47755d22d2 --- /dev/null +++ b/mobile/lib/platform/upload_api.g.dart @@ -0,0 +1,368 @@ +// Autogenerated from Pigeon (v26.0.2), do not edit directly. +// See also: https://pub.dev/packages/pigeon +// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers + +import 'dart:async'; +import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; + +import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; +import 'package:flutter/services.dart'; + +PlatformException _createConnectionError(String channelName) { + return PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel: "$channelName".', + ); +} + +bool _deepEquals(Object? a, Object? 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) { + return a.length == b.length && + a.entries.every( + (MapEntry entry) => + (b as Map).containsKey(entry.key) && _deepEquals(entry.value, b[entry.key]), + ); + } + return a == b; +} + +enum UploadApiErrorCode { + unknown, + assetNotFound, + fileNotFound, + resourceNotFound, + invalidResource, + encodingFailed, + writeFailed, + notEnoughSpace, + networkError, + photosInternalError, + photosUnknownError, + noServerUrl, + noDeviceId, + noAccessToken, + interrupted, + cancelled, + downloadStalled, + forceQuit, + outOfResources, + backgroundUpdatesDisabled, + uploadTimeout, + iCloudRateLimit, + iCloudThrottled, +} + +enum UploadApiStatus { + downloadPending, + downloadQueued, + downloadFailed, + uploadPending, + uploadQueued, + uploadFailed, + uploadComplete, + uploadSkipped, +} + +class UploadApiTaskStatus { + UploadApiTaskStatus({ + required this.id, + required this.filename, + required this.status, + this.errorCode, + this.httpStatusCode, + }); + + String id; + + String filename; + + UploadApiStatus status; + + UploadApiErrorCode? errorCode; + + int? httpStatusCode; + + List _toList() { + return [id, filename, status, errorCode, httpStatusCode]; + } + + Object encode() { + return _toList(); + } + + static UploadApiTaskStatus decode(Object result) { + result as List; + return UploadApiTaskStatus( + id: result[0]! as String, + filename: result[1]! as String, + status: result[2]! as UploadApiStatus, + errorCode: result[3] as UploadApiErrorCode?, + httpStatusCode: result[4] as int?, + ); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + bool operator ==(Object other) { + if (other is! UploadApiTaskStatus || other.runtimeType != runtimeType) { + return false; + } + if (identical(this, other)) { + return true; + } + return _deepEquals(encode(), other.encode()); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + int get hashCode => Object.hashAll(_toList()); +} + +class UploadApiTaskProgress { + UploadApiTaskProgress({required this.id, required this.progress, this.speed, this.totalBytes}); + + String id; + + double progress; + + double? speed; + + int? totalBytes; + + List _toList() { + return [id, progress, speed, totalBytes]; + } + + Object encode() { + return _toList(); + } + + static UploadApiTaskProgress decode(Object result) { + result as List; + return UploadApiTaskProgress( + id: result[0]! as String, + progress: result[1]! as double, + speed: result[2] as double?, + totalBytes: result[3] as int?, + ); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + bool operator ==(Object other) { + if (other is! UploadApiTaskProgress || other.runtimeType != runtimeType) { + return false; + } + if (identical(this, other)) { + return true; + } + return _deepEquals(encode(), other.encode()); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + int get hashCode => Object.hashAll(_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 UploadApiErrorCode) { + buffer.putUint8(129); + writeValue(buffer, value.index); + } else if (value is UploadApiStatus) { + buffer.putUint8(130); + writeValue(buffer, value.index); + } else if (value is UploadApiTaskStatus) { + buffer.putUint8(131); + writeValue(buffer, value.encode()); + } else if (value is UploadApiTaskProgress) { + buffer.putUint8(132); + writeValue(buffer, value.encode()); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 129: + final int? value = readValue(buffer) as int?; + return value == null ? null : UploadApiErrorCode.values[value]; + case 130: + final int? value = readValue(buffer) as int?; + return value == null ? null : UploadApiStatus.values[value]; + case 131: + return UploadApiTaskStatus.decode(readValue(buffer)!); + case 132: + return UploadApiTaskProgress.decode(readValue(buffer)!); + default: + return super.readValueOfType(type, buffer); + } + } +} + +const StandardMethodCodec pigeonMethodCodec = StandardMethodCodec(_PigeonCodec()); + +class UploadApi { + /// Constructor for [UploadApi]. 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. + UploadApi({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 initialize() async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.UploadApi.initialize$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + Future refresh() async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.UploadApi.refresh$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + Future cancelAll() async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.UploadApi.cancelAll$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + Future enqueueAssets(List localIds) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.UploadApi.enqueueAssets$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([localIds]); + final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + Future enqueueFiles(List paths) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.UploadApi.enqueueFiles$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([paths]); + final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } +} + +Stream streamStatus({String instanceName = ''}) { + if (instanceName.isNotEmpty) { + instanceName = '.$instanceName'; + } + final EventChannel streamStatusChannel = EventChannel( + 'dev.flutter.pigeon.immich_mobile.UploadFlutterApi.streamStatus$instanceName', + pigeonMethodCodec, + ); + return streamStatusChannel.receiveBroadcastStream().map((dynamic event) { + return event as UploadApiTaskStatus; + }); +} + +Stream streamProgress({String instanceName = ''}) { + if (instanceName.isNotEmpty) { + instanceName = '.$instanceName'; + } + final EventChannel streamProgressChannel = EventChannel( + 'dev.flutter.pigeon.immich_mobile.UploadFlutterApi.streamProgress$instanceName', + pigeonMethodCodec, + ); + return streamProgressChannel.receiveBroadcastStream().map((dynamic event) { + return event as UploadApiTaskProgress; + }); +} diff --git a/mobile/lib/providers/app_life_cycle.provider.dart b/mobile/lib/providers/app_life_cycle.provider.dart index 6d0c0acb0d..989fa59b46 100644 --- a/mobile/lib/providers/app_life_cycle.provider.dart +++ b/mobile/lib/providers/app_life_cycle.provider.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/services/log.service.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/models/backup/backup_state.model.dart'; @@ -11,7 +10,6 @@ import 'package:immich_mobile/providers/asset.provider.dart'; import 'package:immich_mobile/providers/auth.provider.dart'; import 'package:immich_mobile/providers/background_sync.provider.dart'; import 'package:immich_mobile/providers/backup/backup.provider.dart'; -import 'package:immich_mobile/providers/backup/drift_backup.provider.dart'; import 'package:immich_mobile/providers/backup/ios_background_settings.provider.dart'; import 'package:immich_mobile/providers/backup/manual_upload.provider.dart'; import 'package:immich_mobile/providers/gallery_permission.provider.dart'; @@ -148,21 +146,13 @@ class AppLifeCycleNotifier extends StateNotifier { final isAlbumLinkedSyncEnable = _ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.syncAlbums); try { - bool syncSuccess = false; await Future.wait([ _safeRun(backgroundManager.syncLocal(), "syncLocal"), - _safeRun(backgroundManager.syncRemote().then((success) => syncSuccess = success), "syncRemote"), + _safeRun(backgroundManager.syncRemote(), "syncRemote"), ]); - if (syncSuccess) { - await Future.wait([ - _safeRun(backgroundManager.hashAssets(), "hashAssets").then((_) { - _resumeBackup(); - }), - _resumeBackup(), - ]); - } else { - await _safeRun(backgroundManager.hashAssets(), "hashAssets"); - } + + await _safeRun(backgroundManager.hashAssets(), "hashAssets"); + await _safeRun(uploadApi.refresh(), "refresh"); if (isAlbumLinkedSyncEnable) { await _safeRun(backgroundManager.syncLinkedAlbum(), "syncLinkedAlbum"); @@ -172,20 +162,6 @@ class AppLifeCycleNotifier extends StateNotifier { } } - Future _resumeBackup() async { - final isEnableBackup = _ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableBackup); - - if (isEnableBackup) { - final currentUser = Store.tryGet(StoreKey.currentUser); - if (currentUser != null) { - await _safeRun( - _ref.read(driftBackupProvider.notifier).handleBackupResume(currentUser.id), - "handleBackupResume", - ); - } - } - } - // Helper method to check if operations should continue bool _shouldContinueOperation() { return [AppLifeCycleEnum.resumed, AppLifeCycleEnum.active].contains(state) && diff --git a/mobile/lib/providers/asset_viewer/share_intent_upload.provider.dart b/mobile/lib/providers/asset_viewer/share_intent_upload.provider.dart index 0f9c32b410..54740730f1 100644 --- a/mobile/lib/providers/asset_viewer/share_intent_upload.provider.dart +++ b/mobile/lib/providers/asset_viewer/share_intent_upload.provider.dart @@ -1,17 +1,14 @@ -import 'dart:io'; +import 'dart:async'; -import 'package:background_downloader/background_downloader.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/constants.dart'; -import 'package:immich_mobile/domain/models/store.model.dart'; -import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/string_extensions.dart'; import 'package:immich_mobile/models/upload/share_intent_attachment.model.dart'; +import 'package:immich_mobile/platform/upload_api.g.dart'; +import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; import 'package:immich_mobile/routing/router.dart'; -import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/services/share_intent_service.dart'; import 'package:immich_mobile/services/upload.service.dart'; -import 'package:path/path.dart'; final shareIntentUploadProvider = StateNotifierProvider>( ((ref) => ShareIntentUploadStateNotifier( @@ -25,10 +22,12 @@ class ShareIntentUploadStateNotifier extends StateNotifier _taskStatusStream; + late final StreamSubscription _taskProgressStream; ShareIntentUploadStateNotifier(this.router, this._uploadService, this._shareIntentService) : super([]) { - _uploadService.taskStatusStream.listen(_updateUploadStatus); - _uploadService.taskProgressStream.listen(_taskProgressCallback); + _taskStatusStream = _uploadService.taskStatusStream.listen(_updateUploadStatus); + _taskProgressStream = _uploadService.taskProgressStream.listen(_taskProgressCallback); } void init() { @@ -36,6 +35,13 @@ class ShareIntentUploadStateNotifier extends StateNotifier attachments) { router.removeWhere((route) => route.name == "ShareIntentRoute"); clearAttachments(); @@ -65,82 +71,35 @@ class ShareIntentUploadStateNotifier extends StateNotifier UploadStatus.complete, - TaskStatus.failed => UploadStatus.failed, - TaskStatus.canceled => UploadStatus.canceled, - TaskStatus.enqueued => UploadStatus.enqueued, - TaskStatus.running => UploadStatus.running, - TaskStatus.paused => UploadStatus.paused, - TaskStatus.notFound => UploadStatus.notFound, - TaskStatus.waitingToRetry => UploadStatus.waitingToRetry, + UploadApiStatus.uploadComplete => UploadStatus.complete, + UploadApiStatus.uploadFailed || UploadApiStatus.downloadFailed => UploadStatus.failed, + UploadApiStatus.uploadQueued => UploadStatus.enqueued, + _ => UploadStatus.preparing, }; + final taskId = task.id.toInt(); state = [ for (final attachment in state) - if (attachment.id == taskId.toInt()) attachment.copyWith(status: uploadStatus) else attachment, + if (attachment.id == taskId) attachment.copyWith(status: uploadStatus) else attachment, ]; } - void _taskProgressCallback(TaskProgressUpdate update) { + void _taskProgressCallback(UploadApiTaskProgress update) { // Ignore if the task is canceled or completed if (update.progress == downloadFailed || update.progress == downloadCompleted) { return; } - final taskId = update.task.taskId; + final taskId = update.id.toInt(); state = [ for (final attachment in state) - if (attachment.id == taskId.toInt()) attachment.copyWith(uploadProgress: update.progress) else attachment, + if (attachment.id == taskId) attachment.copyWith(uploadProgress: update.progress) else attachment, ]; } - Future upload(File file) async { - final task = await _buildUploadTask(hash(file.path).toString(), file); - - await _uploadService.enqueueTasks([task]); - } - - Future _buildUploadTask(String id, File file, {Map? fields}) async { - final serverEndpoint = Store.get(StoreKey.serverEndpoint); - final url = Uri.parse('$serverEndpoint/assets').toString(); - final headers = ApiService.getRequestHeaders(); - final deviceId = Store.get(StoreKey.deviceId); - - final (baseDirectory, directory, filename) = await Task.split(filePath: file.path); - final stats = await file.stat(); - final fileCreatedAt = stats.changed; - final fileModifiedAt = stats.modified; - - final fieldsMap = { - 'filename': filename, - 'deviceAssetId': id, - 'deviceId': deviceId, - 'fileCreatedAt': fileCreatedAt.toUtc().toIso8601String(), - 'fileModifiedAt': fileModifiedAt.toUtc().toIso8601String(), - 'isFavorite': 'false', - 'duration': '0', - if (fields != null) ...fields, - }; - - return UploadTask( - taskId: id, - httpRequestMethod: 'POST', - url: url, - headers: headers, - filename: filename, - fields: fieldsMap, - baseDirectory: baseDirectory, - directory: directory, - fileField: 'assetData', - group: kManualUploadGroup, - updates: Updates.statusAndProgress, - ); + Future upload(List files) { + return uploadApi.enqueueFiles(files.map((e) => e.path).toList(growable: false)); } } diff --git a/mobile/lib/providers/backup/drift_backup.provider.dart b/mobile/lib/providers/backup/drift_backup.provider.dart index f52fc654f2..8baebc6d82 100644 --- a/mobile/lib/providers/backup/drift_backup.provider.dart +++ b/mobile/lib/providers/backup/drift_backup.provider.dart @@ -1,14 +1,12 @@ // ignore_for_file: public_member_api_docs, sort_constructors_first import 'dart:async'; -import 'package:background_downloader/background_downloader.dart'; import 'package:collection/collection.dart'; import 'package:hooks_riverpod/hooks_riverpod.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/string_extensions.dart'; import 'package:immich_mobile/infrastructure/repositories/backup.repository.dart'; +import 'package:immich_mobile/platform/upload_api.g.dart'; import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/services/upload.service.dart'; @@ -196,6 +194,9 @@ final driftBackupProvider = StateNotifierProvider { + late final StreamSubscription _taskStatusStream; + late final StreamSubscription _taskProgressStream; + DriftBackupNotifier(this._uploadService) : super( const DriftBackupState( @@ -211,17 +212,20 @@ class DriftBackupNotifier extends StateNotifier { error: BackupError.none, ), ) { - { - _uploadService.taskStatusStream.listen(_handleTaskStatusUpdate); - _uploadService.taskProgressStream.listen(_handleTaskProgressUpdate); - } + _taskStatusStream = _uploadService.taskStatusStream.listen(_handleTaskStatusUpdate); + _taskProgressStream = _uploadService.taskProgressStream.listen(_handleTaskProgressUpdate); } final UploadService _uploadService; - StreamSubscription? _statusSubscription; - StreamSubscription? _progressSubscription; final _logger = Logger("DriftBackupNotifier"); + @override + void dispose() { + unawaited(_taskStatusStream.cancel()); + unawaited(_taskProgressStream.cancel()); + super.dispose(); + } + /// Remove upload item from state void _removeUploadItem(String taskId) { if (state.uploadItems.containsKey(taskId)) { @@ -231,16 +235,12 @@ class DriftBackupNotifier extends StateNotifier { } } - void _handleTaskStatusUpdate(TaskStatusUpdate update) { - final taskId = update.task.taskId; + void _handleTaskStatusUpdate(UploadApiTaskStatus update) { + final taskId = update.id; switch (update.status) { - case TaskStatus.complete: - if (update.task.group == kBackupGroup) { - if (update.responseStatusCode == 201) { - state = state.copyWith(backupCount: state.backupCount + 1, remainderCount: state.remainderCount - 1); - } - } + case UploadApiStatus.uploadComplete: + state = state.copyWith(backupCount: state.backupCount + 1, remainderCount: state.remainderCount - 1); // Remove the completed task from the upload items if (state.uploadItems.containsKey(taskId)) { @@ -249,40 +249,45 @@ class DriftBackupNotifier extends StateNotifier { }); } - case TaskStatus.failed: - // Ignore retry errors to avoid confusing users - if (update.exception?.description == 'Delayed or retried enqueue failed') { - _removeUploadItem(taskId); - return; - } - + case UploadApiStatus.uploadFailed: final currentItem = state.uploadItems[taskId]; if (currentItem == null) { return; } - String? error; - final exception = update.exception; - if (exception != null && exception is TaskHttpException) { - final message = tryJsonDecode(exception.description)?['message'] as String?; - if (message != null) { - final responseCode = exception.httpResponseCode; - error = "${exception.exceptionType}, response code $responseCode: $message"; - } - } - error ??= update.exception?.toString(); + // String? error; + // final exception = update.exception; + // if (exception != null && exception is TaskHttpException) { + // final message = tryJsonDecode(exception.description)?['message'] as String?; + // if (message != null) { + // final responseCode = exception.httpResponseCode; + // error = "${exception.exceptionType}, response code $responseCode: $message"; + // } + // } + // error ??= update.exception?.toString(); + // state = state.copyWith( + // uploadItems: { + // ...state.uploadItems, + // taskId: currentItem.copyWith(isFailed: true, error: error), + // }, + // ); + // _logger.fine("Upload failed for taskId: $taskId, exception: ${update.exception}"); + break; + + case UploadApiStatus.uploadPending: state = state.copyWith( uploadItems: { ...state.uploadItems, - taskId: currentItem.copyWith(isFailed: true, error: error), + taskId: DriftUploadStatus( + taskId: update.id.toString(), + filename: update.filename, + fileSize: 0, + networkSpeedAsString: "", + progress: 0.0, + ), }, ); - _logger.fine("Upload failed for taskId: $taskId, exception: ${update.exception}"); - break; - - case TaskStatus.canceled: - _removeUploadItem(update.task.taskId); break; default: @@ -290,42 +295,21 @@ class DriftBackupNotifier extends StateNotifier { } } - void _handleTaskProgressUpdate(TaskProgressUpdate update) { - final taskId = update.task.taskId; - final filename = update.task.displayName; + void _handleTaskProgressUpdate(UploadApiTaskProgress update) { + final taskId = update.id; final progress = update.progress; final currentItem = state.uploadItems[taskId]; - if (currentItem != null) { - if (progress == kUploadStatusCanceled) { - _removeUploadItem(update.task.taskId); - return; - } - - state = state.copyWith( - uploadItems: { - ...state.uploadItems, - taskId: update.hasExpectedFileSize - ? currentItem.copyWith( - progress: progress, - fileSize: update.expectedFileSize, - networkSpeedAsString: update.networkSpeedAsString, - ) - : currentItem.copyWith(progress: progress), - }, - ); - + if (currentItem == null) { return; } state = state.copyWith( uploadItems: { ...state.uploadItems, - taskId: DriftUploadStatus( - taskId: taskId, - filename: filename, + taskId: currentItem.copyWith( progress: progress, - fileSize: update.expectedFileSize, - networkSpeedAsString: update.networkSpeedAsString, + fileSize: update.totalBytes, + networkSpeedAsString: update.speed?.toStringAsFixed(2) ?? "", ), }, ); @@ -350,52 +334,18 @@ class DriftBackupNotifier extends StateNotifier { state = state.copyWith(isSyncing: isSyncing); } - Future startBackup(String userId) { + Future startBackup() { state = state.copyWith(error: BackupError.none); - return _uploadService.startBackup(userId, _updateEnqueueCount); - } - - void _updateEnqueueCount(EnqueueStatus status) { - state = state.copyWith(enqueueCount: status.enqueueCount, enqueueTotalCount: status.totalCount); + return _uploadService.startBackup(); } Future cancel() async { dPrint(() => "Canceling backup tasks..."); state = state.copyWith(enqueueCount: 0, enqueueTotalCount: 0, isCanceling: true, error: BackupError.none); - final activeTaskCount = await _uploadService.cancelBackup(); - - if (activeTaskCount > 0) { - dPrint(() => "$activeTaskCount tasks left, continuing to cancel..."); - await cancel(); - } else { - dPrint(() => "All tasks canceled successfully."); - // Clear all upload items when cancellation is complete - state = state.copyWith(isCanceling: false, uploadItems: {}); - } - } - - Future handleBackupResume(String userId) async { - _logger.info("Resuming backup tasks..."); - state = state.copyWith(error: BackupError.none); - final tasks = await _uploadService.getActiveTasks(kBackupGroup); - _logger.info("Found ${tasks.length} tasks"); - - if (tasks.isEmpty) { - // Start a new backup queue - _logger.info("Start a new backup queue"); - return startBackup(userId); - } - - _logger.info("Tasks to resume: ${tasks.length}"); - return _uploadService.resumeBackup(); - } - - @override - void dispose() { - _statusSubscription?.cancel(); - _progressSubscription?.cancel(); - super.dispose(); + await _uploadService.cancelBackup(); + dPrint(() => "All tasks canceled successfully."); + state = state.copyWith(isCanceling: false, uploadItems: {}); } } diff --git a/mobile/lib/providers/infrastructure/platform.provider.dart b/mobile/lib/providers/infrastructure/platform.provider.dart index 11c5280c02..ea8a6b4492 100644 --- a/mobile/lib/providers/infrastructure/platform.provider.dart +++ b/mobile/lib/providers/infrastructure/platform.provider.dart @@ -5,6 +5,7 @@ import 'package:immich_mobile/platform/background_worker_lock_api.g.dart'; import 'package:immich_mobile/platform/connectivity_api.g.dart'; import 'package:immich_mobile/platform/native_sync_api.g.dart'; import 'package:immich_mobile/platform/thumbnail_api.g.dart'; +import 'package:immich_mobile/platform/upload_api.g.dart'; final backgroundWorkerFgServiceProvider = Provider((_) => BackgroundWorkerFgService(BackgroundWorkerFgHostApi())); @@ -17,3 +18,5 @@ final nativeSyncApiProvider = Provider((_) => NativeSyncApi()); final connectivityApiProvider = Provider((_) => ConnectivityApi()); final thumbnailApi = ThumbnailApi(); + +final uploadApi = UploadApi(); diff --git a/mobile/lib/repositories/upload.repository.dart b/mobile/lib/repositories/upload.repository.dart deleted file mode 100644 index 38f2c22cf2..0000000000 --- a/mobile/lib/repositories/upload.repository.dart +++ /dev/null @@ -1,143 +0,0 @@ -import 'dart:convert'; -import 'dart:io'; - -import 'package:background_downloader/background_downloader.dart'; -import 'package:cancellation_token_http/http.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/constants/constants.dart'; -import 'package:immich_mobile/domain/models/store.model.dart'; -import 'package:immich_mobile/entities/store.entity.dart'; -import 'package:logging/logging.dart'; -import 'package:immich_mobile/utils/debug_print.dart'; - -class UploadTaskWithFile { - final File file; - final UploadTask task; - - const UploadTaskWithFile({required this.file, required this.task}); -} - -final uploadRepositoryProvider = Provider((ref) => UploadRepository()); - -class UploadRepository { - void Function(TaskStatusUpdate)? onUploadStatus; - void Function(TaskProgressUpdate)? onTaskProgress; - - UploadRepository() { - FileDownloader().registerCallbacks( - group: kBackupGroup, - taskStatusCallback: (update) => onUploadStatus?.call(update), - taskProgressCallback: (update) => onTaskProgress?.call(update), - ); - FileDownloader().registerCallbacks( - group: kBackupLivePhotoGroup, - taskStatusCallback: (update) => onUploadStatus?.call(update), - taskProgressCallback: (update) => onTaskProgress?.call(update), - ); - FileDownloader().registerCallbacks( - group: kManualUploadGroup, - taskStatusCallback: (update) => onUploadStatus?.call(update), - taskProgressCallback: (update) => onTaskProgress?.call(update), - ); - } - - Future enqueueBackground(UploadTask task) { - return FileDownloader().enqueue(task); - } - - Future> enqueueBackgroundAll(List tasks) { - return FileDownloader().enqueueAll(tasks); - } - - Future deleteDatabaseRecords(String group) { - return FileDownloader().database.deleteAllRecords(group: group); - } - - Future cancelAll(String group) { - return FileDownloader().cancelAll(group: group); - } - - Future reset(String group) { - return FileDownloader().reset(group: group); - } - - /// Get a list of tasks that are ENQUEUED or RUNNING - Future> getActiveTasks(String group) { - return FileDownloader().allTasks(group: group); - } - - Future start() { - return FileDownloader().start(); - } - - Future getUploadInfo() async { - final [enqueuedTasks, runningTasks, canceledTasks, waitingTasks, pausedTasks] = await Future.wait([ - FileDownloader().database.allRecordsWithStatus(TaskStatus.enqueued, group: kBackupGroup), - FileDownloader().database.allRecordsWithStatus(TaskStatus.running, group: kBackupGroup), - FileDownloader().database.allRecordsWithStatus(TaskStatus.canceled, group: kBackupGroup), - FileDownloader().database.allRecordsWithStatus(TaskStatus.waitingToRetry, group: kBackupGroup), - FileDownloader().database.allRecordsWithStatus(TaskStatus.paused, group: kBackupGroup), - ]); - - dPrint( - () => - """ - Upload Info: - Enqueued: ${enqueuedTasks.length} - Running: ${runningTasks.length} - Canceled: ${canceledTasks.length} - Waiting: ${waitingTasks.length} - Paused: ${pausedTasks.length} - """, - ); - } - - Future backupWithDartClient(Iterable tasks, CancellationToken cancelToken) async { - final httpClient = Client(); - final String savedEndpoint = Store.get(StoreKey.serverEndpoint); - - Logger logger = Logger('UploadRepository'); - for (final candidate in tasks) { - if (cancelToken.isCancelled) { - logger.warning("Backup was cancelled by the user"); - break; - } - - try { - final fileStream = candidate.file.openRead(); - final assetRawUploadData = MultipartFile( - "assetData", - fileStream, - candidate.file.lengthSync(), - filename: candidate.task.filename, - ); - - final baseRequest = MultipartRequest('POST', Uri.parse('$savedEndpoint/assets')); - - baseRequest.headers.addAll(candidate.task.headers); - baseRequest.fields.addAll(candidate.task.fields); - baseRequest.files.add(assetRawUploadData); - - final response = await httpClient.send(baseRequest, cancellationToken: cancelToken); - - final responseBody = jsonDecode(await response.stream.bytesToString()); - - if (![200, 201].contains(response.statusCode)) { - final error = responseBody; - - logger.warning( - "Error(${error['statusCode']}) uploading ${candidate.task.filename} | Created on ${candidate.task.fields["fileCreatedAt"]} | ${error['error']}", - ); - - continue; - } - } on CancelledException { - logger.warning("Backup was cancelled by the user"); - break; - } catch (error, stackTrace) { - logger.warning("Error backup asset: ${error.toString()}: $stackTrace"); - continue; - } - } - } -} diff --git a/mobile/lib/services/upload.service.dart b/mobile/lib/services/upload.service.dart index 1ce0cf0322..755e792961 100644 --- a/mobile/lib/services/upload.service.dart +++ b/mobile/lib/services/upload.service.dart @@ -1,502 +1,43 @@ import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; -import 'package:background_downloader/background_downloader.dart'; -import 'package:cancellation_token_http/http.dart'; -import 'package:flutter/foundation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/constants/constants.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/backup.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart'; -import 'package:immich_mobile/providers/app_settings.provider.dart'; -import 'package:immich_mobile/providers/backup/drift_backup.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/storage.provider.dart'; -import 'package:immich_mobile/repositories/asset_media.repository.dart'; -import 'package:immich_mobile/repositories/upload.repository.dart'; -import 'package:immich_mobile/services/api.service.dart'; -import 'package:immich_mobile/services/app_settings.service.dart'; -import 'package:immich_mobile/utils/debug_print.dart'; -import 'package:logging/logging.dart'; -import 'package:path/path.dart' as p; +import 'package:immich_mobile/platform/upload_api.g.dart'; +import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; final uploadServiceProvider = Provider((ref) { - final service = UploadService( - ref.watch(uploadRepositoryProvider), - ref.watch(backupRepositoryProvider), - ref.watch(storageRepositoryProvider), - ref.watch(localAssetRepository), - ref.watch(appSettingsServiceProvider), - ref.watch(assetMediaRepositoryProvider), - ); - - ref.onDispose(service.dispose); + final service = UploadService(ref.watch(backupRepositoryProvider)); return service; }); class UploadService { - UploadService( - this._uploadRepository, - this._backupRepository, - this._storageRepository, - this._localAssetRepository, - this._appSettingsService, - this._assetMediaRepository, - ) { - _uploadRepository.onUploadStatus = _onUploadCallback; - _uploadRepository.onTaskProgress = _onTaskProgressCallback; - } + final Stream taskStatusStream; + final Stream taskProgressStream; + UploadService(this._backupRepository) : taskStatusStream = streamStatus(), taskProgressStream = streamProgress(); - final UploadRepository _uploadRepository; final DriftBackupRepository _backupRepository; - final StorageRepository _storageRepository; - final DriftLocalAssetRepository _localAssetRepository; - final AppSettingsService _appSettingsService; - final AssetMediaRepository _assetMediaRepository; - final Logger _logger = Logger('UploadService'); - - final StreamController _taskStatusController = StreamController.broadcast(); - final StreamController _taskProgressController = StreamController.broadcast(); - - Stream get taskStatusStream => _taskStatusController.stream; - Stream get taskProgressStream => _taskProgressController.stream; - bool shouldAbortQueuingTasks = false; - void _onTaskProgressCallback(TaskProgressUpdate update) { - if (!_taskProgressController.isClosed) { - _taskProgressController.add(update); - } - } - - void _onUploadCallback(TaskStatusUpdate update) { - if (!_taskStatusController.isClosed) { - _taskStatusController.add(update); - } - _handleTaskStatusUpdate(update); - } - - void dispose() { - _taskStatusController.close(); - _taskProgressController.close(); - } - - Future> enqueueTasks(List tasks) { - return _uploadRepository.enqueueBackgroundAll(tasks); - } - - Future> getActiveTasks(String group) { - return _uploadRepository.getActiveTasks(group); - } - Future<({int total, int remainder, int processing})> getBackupCounts(String userId) { return _backupRepository.getAllCounts(userId); } - Future manualBackup(List localAssets) async { - await _storageRepository.clearCache(); - List tasks = []; - for (final asset in localAssets) { - final task = await getUploadTask( - asset, - group: kManualUploadGroup, - priority: 1, // High priority after upload motion photo part - ); - if (task != null) { - tasks.add(task); - } - } - - if (tasks.isNotEmpty) { - await enqueueTasks(tasks); - } + Future manualBackup(List localAssets) { + return uploadApi.enqueueAssets(localAssets.map((e) => e.id).toList(growable: false)); } /// Find backup candidates /// Build the upload tasks /// Enqueue the tasks - Future startBackup(String userId, void Function(EnqueueStatus status) onEnqueueTasks) async { - await _storageRepository.clearCache(); - - shouldAbortQueuingTasks = false; - - final candidates = await _backupRepository.getCandidates(userId); - if (candidates.isEmpty) { - return; - } - - const batchSize = 100; - int count = 0; - for (int i = 0; i < candidates.length; i += batchSize) { - if (shouldAbortQueuingTasks) { - break; - } - - final batch = candidates.skip(i).take(batchSize).toList(); - List tasks = []; - for (final asset in batch) { - final task = await getUploadTask(asset); - if (task != null) { - tasks.add(task); - } - } - - if (tasks.isNotEmpty && !shouldAbortQueuingTasks) { - count += tasks.length; - await enqueueTasks(tasks); - - onEnqueueTasks(EnqueueStatus(enqueueCount: count, totalCount: candidates.length)); - } - } - } - - Future startBackupWithHttpClient(String userId, bool hasWifi, CancellationToken token) async { - await _storageRepository.clearCache(); - - shouldAbortQueuingTasks = false; - - final candidates = await _backupRepository.getCandidates(userId); - if (candidates.isEmpty) { - return; - } - - const batchSize = 100; - for (int i = 0; i < candidates.length; i += batchSize) { - if (shouldAbortQueuingTasks || token.isCancelled) { - break; - } - - final batch = candidates.skip(i).take(batchSize).toList(); - List tasks = []; - for (final asset in batch) { - final requireWifi = _shouldRequireWiFi(asset); - if (requireWifi && !hasWifi) { - _logger.warning('Skipping upload for ${asset.id} because it requires WiFi'); - continue; - } - - final task = await _getUploadTaskWithFile(asset); - if (task != null) { - tasks.add(task); - } - } - - if (tasks.isNotEmpty && !shouldAbortQueuingTasks) { - await _uploadRepository.backupWithDartClient(tasks, token); - } - } + Future startBackup() async { + return uploadApi.refresh(); } /// Cancel all ongoing uploads and reset the upload queue /// /// Return the number of left over tasks in the queue - Future cancelBackup() async { - shouldAbortQueuingTasks = true; - - await _storageRepository.clearCache(); - await _uploadRepository.reset(kBackupGroup); - await _uploadRepository.deleteDatabaseRecords(kBackupGroup); - - final activeTasks = await _uploadRepository.getActiveTasks(kBackupGroup); - return activeTasks.length; - } - - Future resumeBackup() { - return _uploadRepository.start(); - } - - void _handleTaskStatusUpdate(TaskStatusUpdate update) async { - switch (update.status) { - case TaskStatus.complete: - unawaited(_handleLivePhoto(update)); - - if (CurrentPlatform.isIOS) { - try { - final path = await update.task.filePath(); - await File(path).delete(); - } catch (e) { - _logger.severe('Error deleting file path for iOS: $e'); - } - } - - break; - - default: - break; - } - } - - Future _handleLivePhoto(TaskStatusUpdate update) async { - try { - if (update.task.metaData.isEmpty || update.task.metaData == '') { - return; - } - - final metadata = UploadTaskMetadata.fromJson(update.task.metaData); - if (!metadata.isLivePhotos) { - return; - } - - if (update.responseBody == null || update.responseBody!.isEmpty) { - return; - } - final response = jsonDecode(update.responseBody!); - - final localAsset = await _localAssetRepository.getById(metadata.localAssetId); - if (localAsset == null) { - return; - } - - final uploadTask = await getLivePhotoUploadTask(localAsset, response['id'] as String); - - if (uploadTask == null) { - return; - } - - await enqueueTasks([uploadTask]); - } catch (error, stackTrace) { - dPrint(() => "Error handling live photo upload task: $error $stackTrace"); - } - } - - Future _getUploadTaskWithFile(LocalAsset asset) async { - final entity = await _storageRepository.getAssetEntityForAsset(asset); - if (entity == null) { - return null; - } - - final file = await _storageRepository.getFileForAsset(asset.id); - if (file == null) { - return null; - } - - final originalFileName = entity.isLivePhoto ? p.setExtension(asset.name, p.extension(file.path)) : asset.name; - - String metadata = UploadTaskMetadata( - localAssetId: asset.id, - isLivePhotos: entity.isLivePhoto, - livePhotoVideoId: '', - ).toJson(); - - return UploadTaskWithFile( - file: file, - task: await buildUploadTask( - file, - createdAt: asset.createdAt, - modifiedAt: asset.updatedAt, - originalFileName: originalFileName, - deviceAssetId: asset.id, - metadata: metadata, - group: "group", - priority: 0, - isFavorite: asset.isFavorite, - requiresWiFi: false, - ), - ); - } - - @visibleForTesting - Future getUploadTask(LocalAsset asset, {String group = kBackupGroup, int? priority}) async { - final entity = await _storageRepository.getAssetEntityForAsset(asset); - if (entity == null) { - return null; - } - - File? file; - - /// iOS LivePhoto has two files: a photo and a video. - /// They are uploaded separately, with video file being upload first, then returned with the assetId - /// The assetId is then used as a metadata for the photo file upload task. - /// - /// We implement two separate upload groups for this, the normal one for the video file - /// and the higher priority group for the photo file because the video file is already uploaded. - /// - /// The cancel operation will only cancel the video group (normal group), the photo group will not - /// be touched, as the video file is already uploaded. - - if (entity.isLivePhoto) { - file = await _storageRepository.getMotionFileForAsset(asset); - } else { - file = await _storageRepository.getFileForAsset(asset.id); - } - - if (file == null) { - return null; - } - - final fileName = await _assetMediaRepository.getOriginalFilename(asset.id) ?? asset.name; - final originalFileName = entity.isLivePhoto ? p.setExtension(fileName, p.extension(file.path)) : fileName; - - String metadata = UploadTaskMetadata( - localAssetId: asset.id, - isLivePhotos: entity.isLivePhoto, - livePhotoVideoId: '', - ).toJson(); - - final requiresWiFi = _shouldRequireWiFi(asset); - - return buildUploadTask( - file, - createdAt: asset.createdAt, - modifiedAt: asset.updatedAt, - originalFileName: originalFileName, - deviceAssetId: asset.id, - metadata: metadata, - group: group, - priority: priority, - isFavorite: asset.isFavorite, - requiresWiFi: requiresWiFi, - ); - } - - @visibleForTesting - Future getLivePhotoUploadTask(LocalAsset asset, String livePhotoVideoId) async { - final entity = await _storageRepository.getAssetEntityForAsset(asset); - if (entity == null) { - return null; - } - - final file = await _storageRepository.getFileForAsset(asset.id); - if (file == null) { - return null; - } - - final fields = {'livePhotoVideoId': livePhotoVideoId}; - - final requiresWiFi = _shouldRequireWiFi(asset); - final originalFileName = await _assetMediaRepository.getOriginalFilename(asset.id) ?? asset.name; - - return buildUploadTask( - file, - createdAt: asset.createdAt, - modifiedAt: asset.updatedAt, - originalFileName: originalFileName, - deviceAssetId: asset.id, - fields: fields, - group: kBackupLivePhotoGroup, - priority: 0, // Highest priority to get upload immediately - isFavorite: asset.isFavorite, - requiresWiFi: requiresWiFi, - ); - } - - bool _shouldRequireWiFi(LocalAsset asset) { - bool requiresWiFi = true; - - if (asset.isVideo && _appSettingsService.getSetting(AppSettingsEnum.useCellularForUploadVideos)) { - requiresWiFi = false; - } else if (!asset.isVideo && _appSettingsService.getSetting(AppSettingsEnum.useCellularForUploadPhotos)) { - requiresWiFi = false; - } - - return requiresWiFi; - } - - Future buildUploadTask( - File file, { - required String group, - required DateTime createdAt, - required DateTime modifiedAt, - Map? fields, - String? originalFileName, - String? deviceAssetId, - String? metadata, - int? priority, - bool? isFavorite, - bool requiresWiFi = true, - }) async { - final serverEndpoint = Store.get(StoreKey.serverEndpoint); - final url = Uri.parse('$serverEndpoint/assets').toString(); - final headers = ApiService.getRequestHeaders(); - final deviceId = Store.get(StoreKey.deviceId); - final (baseDirectory, directory, filename) = await Task.split(filePath: file.path); - final fieldsMap = { - 'filename': originalFileName ?? filename, - 'deviceAssetId': deviceAssetId ?? '', - 'deviceId': deviceId, - 'fileCreatedAt': createdAt.toUtc().toIso8601String(), - 'fileModifiedAt': modifiedAt.toUtc().toIso8601String(), - 'isFavorite': isFavorite?.toString() ?? 'false', - 'duration': '0', - if (fields != null) ...fields, - }; - - return UploadTask( - taskId: deviceAssetId, - displayName: originalFileName ?? filename, - httpRequestMethod: 'POST', - url: url, - headers: headers, - filename: filename, - fields: fieldsMap, - baseDirectory: baseDirectory, - directory: directory, - fileField: 'assetData', - metaData: metadata ?? '', - group: group, - requiresWiFi: requiresWiFi, - priority: priority ?? 5, - updates: Updates.statusAndProgress, - retries: 3, - ); + Future cancelBackup() { + return uploadApi.cancelAll(); } } - -class UploadTaskMetadata { - final String localAssetId; - final bool isLivePhotos; - final String livePhotoVideoId; - - const UploadTaskMetadata({required this.localAssetId, required this.isLivePhotos, required this.livePhotoVideoId}); - - UploadTaskMetadata copyWith({String? localAssetId, bool? isLivePhotos, String? livePhotoVideoId}) { - return UploadTaskMetadata( - localAssetId: localAssetId ?? this.localAssetId, - isLivePhotos: isLivePhotos ?? this.isLivePhotos, - livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId, - ); - } - - Map toMap() { - return { - 'localAssetId': localAssetId, - 'isLivePhotos': isLivePhotos, - 'livePhotoVideoId': livePhotoVideoId, - }; - } - - factory UploadTaskMetadata.fromMap(Map map) { - return UploadTaskMetadata( - localAssetId: map['localAssetId'] as String, - isLivePhotos: map['isLivePhotos'] as bool, - livePhotoVideoId: map['livePhotoVideoId'] as String, - ); - } - - String toJson() => json.encode(toMap()); - - factory UploadTaskMetadata.fromJson(String source) => - UploadTaskMetadata.fromMap(json.decode(source) as Map); - - @override - String toString() => - 'UploadTaskMetadata(localAssetId: $localAssetId, isLivePhotos: $isLivePhotos, livePhotoVideoId: $livePhotoVideoId)'; - - @override - bool operator ==(covariant UploadTaskMetadata other) { - if (identical(this, other)) return true; - - return other.localAssetId == localAssetId && - other.isLivePhotos == isLivePhotos && - other.livePhotoVideoId == livePhotoVideoId; - } - - @override - int get hashCode => localAssetId.hashCode ^ isLivePhotos.hashCode ^ livePhotoVideoId.hashCode; -} diff --git a/mobile/lib/utils/bootstrap.dart b/mobile/lib/utils/bootstrap.dart index f5c7513d1b..b08df92cdc 100644 --- a/mobile/lib/utils/bootstrap.dart +++ b/mobile/lib/utils/bootstrap.dart @@ -39,20 +39,6 @@ void configureFileDownloaderNotifications() { complete: TaskNotification('download_finished'.t(), '${'file_name'.t()}: {filename}'), progressBar: true, ); - - FileDownloader().configureNotificationForGroup( - kManualUploadGroup, - running: TaskNotification('uploading_media'.t(), 'backup_background_service_in_progress_notification'.t()), - complete: TaskNotification('upload_finished'.t(), 'backup_background_service_complete_notification'.t()), - groupNotificationId: kManualUploadGroup, - ); - - FileDownloader().configureNotificationForGroup( - kBackupGroup, - running: TaskNotification('uploading_media'.t(), 'backup_background_service_in_progress_notification'.t()), - complete: TaskNotification('upload_finished'.t(), 'backup_background_service_complete_notification'.t()), - groupNotificationId: kBackupGroup, - ); } abstract final class Bootstrap { diff --git a/mobile/makefile b/mobile/makefile index b90e95c902..18b5a559da 100644 --- a/mobile/makefile +++ b/mobile/makefile @@ -11,11 +11,13 @@ pigeon: dart run pigeon --input pigeon/background_worker_api.dart dart run pigeon --input pigeon/background_worker_lock_api.dart dart run pigeon --input pigeon/connectivity_api.dart + dart run pigeon --input pigeon/upload_api.dart dart format lib/platform/native_sync_api.g.dart dart format lib/platform/thumbnail_api.g.dart dart format lib/platform/background_worker_api.g.dart dart format lib/platform/background_worker_lock_api.g.dart dart format lib/platform/connectivity_api.g.dart + dart format lib/platform/upload_api.g.dart watch: dart run build_runner watch --delete-conflicting-outputs diff --git a/mobile/pigeon/upload_api.dart b/mobile/pigeon/upload_api.dart new file mode 100644 index 0000000000..8d26003d00 --- /dev/null +++ b/mobile/pigeon/upload_api.dart @@ -0,0 +1,97 @@ +import 'package:pigeon/pigeon.dart'; + +enum UploadApiErrorCode { + unknown("An unknown error occurred"), + assetNotFound("Asset not found"), + fileNotFound("File not found"), + resourceNotFound("Resource not found"), + invalidResource("Invalid resource"), + encodingFailed("Encoding failed"), + writeFailed("Write failed"), + notEnoughSpace("Not enough space"), + networkError("Network error"), + photosInternalError("Apple Photos internal error"), + photosUnknownError("Apple Photos unknown error"), + noServerUrl("Server URL is not set"), + noDeviceId("Device ID is not set"), + noAccessToken("Access token is not set"), + interrupted("Upload interrupted"), + cancelled("Upload cancelled"), + downloadStalled("Download stalled"), + forceQuit("App was force quit"), + outOfResources("Out of resources"), + backgroundUpdatesDisabled("Background updates are disabled"), + uploadTimeout("Upload timed out"), + iCloudRateLimit("iCloud rate limit reached"), + iCloudThrottled("iCloud requests are being throttled"); + + final String message; + + const UploadApiErrorCode(this.message); +} + +enum UploadApiStatus { + downloadPending, + downloadQueued, + downloadFailed, + uploadPending, + uploadQueued, + uploadFailed, + uploadComplete, + uploadSkipped, +} + +class UploadApiTaskStatus { + final String id; + final String filename; + final UploadApiStatus status; + final UploadApiErrorCode? errorCode; + final int? httpStatusCode; + + const UploadApiTaskStatus(this.id, this.filename, this.status, this.errorCode, this.httpStatusCode); +} + +class UploadApiTaskProgress { + final String id; + final double progress; + final double? speed; + final int? totalBytes; + + const UploadApiTaskProgress(this.id, this.progress, this.speed, this.totalBytes); +} + +@ConfigurePigeon( + PigeonOptions( + dartOut: 'lib/platform/upload_api.g.dart', + swiftOut: 'ios/Runner/Upload/UploadTask.g.swift', + swiftOptions: SwiftOptions(includeErrorClass: false), + kotlinOut: 'android/app/src/main/kotlin/app/alextran/immich/upload/UploadTask.g.kt', + kotlinOptions: KotlinOptions(package: 'app.alextran.immich.upload'), + dartOptions: DartOptions(), + dartPackageName: 'immich_mobile', + ), +) +@HostApi() +abstract class UploadApi { + @async + void initialize(); + + @async + void refresh(); + + @async + void cancelAll(); + + @async + void enqueueAssets(List localIds); + + @async + void enqueueFiles(List paths); +} + +@EventChannelApi() +abstract class UploadFlutterApi { + UploadApiTaskStatus streamStatus(); + + UploadApiTaskProgress streamProgress(); +} diff --git a/mobile/test/drift/main/generated/schema.dart b/mobile/test/drift/main/generated/schema.dart index 69dff89fb1..5e19610574 100644 --- a/mobile/test/drift/main/generated/schema.dart +++ b/mobile/test/drift/main/generated/schema.dart @@ -16,6 +16,7 @@ import 'schema_v10.dart' as v10; import 'schema_v11.dart' as v11; import 'schema_v12.dart' as v12; import 'schema_v13.dart' as v13; +import 'schema_v14.dart' as v14; class GeneratedHelper implements SchemaInstantiationHelper { @override @@ -47,10 +48,12 @@ class GeneratedHelper implements SchemaInstantiationHelper { return v12.DatabaseAtV12(db); case 13: return v13.DatabaseAtV13(db); + case 14: + return v14.DatabaseAtV14(db); default: throw MissingSchemaException(version, versions); } } - static const versions = const [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]; + static const versions = const [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]; } diff --git a/mobile/test/drift/main/generated/schema_v14.dart b/mobile/test/drift/main/generated/schema_v14.dart new file mode 100644 index 0000000000..1789cf9514 --- /dev/null +++ b/mobile/test/drift/main/generated/schema_v14.dart @@ -0,0 +1,8800 @@ +// dart format width=80 +// GENERATED CODE, DO NOT EDIT BY HAND. +// ignore_for_file: type=lint +import 'package:drift/drift.dart'; + +class UploadTasks extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UploadTasks(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + true, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'PRIMARY KEY AUTOINCREMENT', + ); + late final GeneratedColumn attempts = GeneratedColumn( + 'attempts', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn filePath = GeneratedColumn( + 'file_path', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: '', + ); + late final GeneratedColumn isLivePhoto = GeneratedColumn( + 'is_live_photo', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: '', + ); + late final GeneratedColumn lastError = GeneratedColumn( + 'last_error', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: '', + ); + late final GeneratedColumn livePhotoVideoId = GeneratedColumn( + 'live_photo_video_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: '', + ); + late final GeneratedColumn localId = GeneratedColumn( + 'local_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn method = GeneratedColumn( + 'method', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn priority = GeneratedColumn( + 'priority', + aliasedName, + false, + type: DriftSqlType.double, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn retryAfter = GeneratedColumn( + 'retry_after', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: '', + ); + late final GeneratedColumn status = GeneratedColumn( + 'status', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + @override + List get $columns => [ + id, + attempts, + createdAt, + filePath, + isLivePhoto, + lastError, + livePhotoVideoId, + localId, + method, + priority, + retryAfter, + status, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'upload_tasks'; + @override + Set get $primaryKey => {id}; + @override + UploadTasksData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UploadTasksData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + ), + attempts: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}attempts'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}created_at'], + )!, + filePath: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}file_path'], + ), + isLivePhoto: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_live_photo'], + ), + lastError: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}last_error'], + ), + livePhotoVideoId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}live_photo_video_id'], + ), + localId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}local_id'], + )!, + method: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}method'], + )!, + priority: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}priority'], + )!, + retryAfter: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}retry_after'], + ), + status: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}status'], + )!, + ); + } + + @override + UploadTasks createAlias(String alias) { + return UploadTasks(attachedDatabase, alias); + } + + @override + bool get dontWriteConstraints => true; +} + +class UploadTasksData extends DataClass implements Insertable { + final int? id; + final int attempts; + final int createdAt; + final String? filePath; + final int? isLivePhoto; + final int? lastError; + final String? livePhotoVideoId; + final String localId; + final int method; + final double priority; + final int? retryAfter; + final int status; + const UploadTasksData({ + this.id, + required this.attempts, + required this.createdAt, + this.filePath, + this.isLivePhoto, + this.lastError, + this.livePhotoVideoId, + required this.localId, + required this.method, + required this.priority, + this.retryAfter, + required this.status, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (!nullToAbsent || id != null) { + map['id'] = Variable(id); + } + map['attempts'] = Variable(attempts); + map['created_at'] = Variable(createdAt); + if (!nullToAbsent || filePath != null) { + map['file_path'] = Variable(filePath); + } + if (!nullToAbsent || isLivePhoto != null) { + map['is_live_photo'] = Variable(isLivePhoto); + } + if (!nullToAbsent || lastError != null) { + map['last_error'] = Variable(lastError); + } + if (!nullToAbsent || livePhotoVideoId != null) { + map['live_photo_video_id'] = Variable(livePhotoVideoId); + } + map['local_id'] = Variable(localId); + map['method'] = Variable(method); + map['priority'] = Variable(priority); + if (!nullToAbsent || retryAfter != null) { + map['retry_after'] = Variable(retryAfter); + } + map['status'] = Variable(status); + return map; + } + + factory UploadTasksData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UploadTasksData( + id: serializer.fromJson(json['id']), + attempts: serializer.fromJson(json['attempts']), + createdAt: serializer.fromJson(json['createdAt']), + filePath: serializer.fromJson(json['filePath']), + isLivePhoto: serializer.fromJson(json['isLivePhoto']), + lastError: serializer.fromJson(json['lastError']), + livePhotoVideoId: serializer.fromJson(json['livePhotoVideoId']), + localId: serializer.fromJson(json['localId']), + method: serializer.fromJson(json['method']), + priority: serializer.fromJson(json['priority']), + retryAfter: serializer.fromJson(json['retryAfter']), + status: serializer.fromJson(json['status']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'attempts': serializer.toJson(attempts), + 'createdAt': serializer.toJson(createdAt), + 'filePath': serializer.toJson(filePath), + 'isLivePhoto': serializer.toJson(isLivePhoto), + 'lastError': serializer.toJson(lastError), + 'livePhotoVideoId': serializer.toJson(livePhotoVideoId), + 'localId': serializer.toJson(localId), + 'method': serializer.toJson(method), + 'priority': serializer.toJson(priority), + 'retryAfter': serializer.toJson(retryAfter), + 'status': serializer.toJson(status), + }; + } + + UploadTasksData copyWith({ + Value id = const Value.absent(), + int? attempts, + int? createdAt, + Value filePath = const Value.absent(), + Value isLivePhoto = const Value.absent(), + Value lastError = const Value.absent(), + Value livePhotoVideoId = const Value.absent(), + String? localId, + int? method, + double? priority, + Value retryAfter = const Value.absent(), + int? status, + }) => UploadTasksData( + id: id.present ? id.value : this.id, + attempts: attempts ?? this.attempts, + createdAt: createdAt ?? this.createdAt, + filePath: filePath.present ? filePath.value : this.filePath, + isLivePhoto: isLivePhoto.present ? isLivePhoto.value : this.isLivePhoto, + lastError: lastError.present ? lastError.value : this.lastError, + livePhotoVideoId: livePhotoVideoId.present + ? livePhotoVideoId.value + : this.livePhotoVideoId, + localId: localId ?? this.localId, + method: method ?? this.method, + priority: priority ?? this.priority, + retryAfter: retryAfter.present ? retryAfter.value : this.retryAfter, + status: status ?? this.status, + ); + UploadTasksData copyWithCompanion(UploadTasksCompanion data) { + return UploadTasksData( + id: data.id.present ? data.id.value : this.id, + attempts: data.attempts.present ? data.attempts.value : this.attempts, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + filePath: data.filePath.present ? data.filePath.value : this.filePath, + isLivePhoto: data.isLivePhoto.present + ? data.isLivePhoto.value + : this.isLivePhoto, + lastError: data.lastError.present ? data.lastError.value : this.lastError, + livePhotoVideoId: data.livePhotoVideoId.present + ? data.livePhotoVideoId.value + : this.livePhotoVideoId, + localId: data.localId.present ? data.localId.value : this.localId, + method: data.method.present ? data.method.value : this.method, + priority: data.priority.present ? data.priority.value : this.priority, + retryAfter: data.retryAfter.present + ? data.retryAfter.value + : this.retryAfter, + status: data.status.present ? data.status.value : this.status, + ); + } + + @override + String toString() { + return (StringBuffer('UploadTasksData(') + ..write('id: $id, ') + ..write('attempts: $attempts, ') + ..write('createdAt: $createdAt, ') + ..write('filePath: $filePath, ') + ..write('isLivePhoto: $isLivePhoto, ') + ..write('lastError: $lastError, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('localId: $localId, ') + ..write('method: $method, ') + ..write('priority: $priority, ') + ..write('retryAfter: $retryAfter, ') + ..write('status: $status') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + attempts, + createdAt, + filePath, + isLivePhoto, + lastError, + livePhotoVideoId, + localId, + method, + priority, + retryAfter, + status, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UploadTasksData && + other.id == this.id && + other.attempts == this.attempts && + other.createdAt == this.createdAt && + other.filePath == this.filePath && + other.isLivePhoto == this.isLivePhoto && + other.lastError == this.lastError && + other.livePhotoVideoId == this.livePhotoVideoId && + other.localId == this.localId && + other.method == this.method && + other.priority == this.priority && + other.retryAfter == this.retryAfter && + other.status == this.status); +} + +class UploadTasksCompanion extends UpdateCompanion { + final Value id; + final Value attempts; + final Value createdAt; + final Value filePath; + final Value isLivePhoto; + final Value lastError; + final Value livePhotoVideoId; + final Value localId; + final Value method; + final Value priority; + final Value retryAfter; + final Value status; + const UploadTasksCompanion({ + this.id = const Value.absent(), + this.attempts = const Value.absent(), + this.createdAt = const Value.absent(), + this.filePath = const Value.absent(), + this.isLivePhoto = const Value.absent(), + this.lastError = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + this.localId = const Value.absent(), + this.method = const Value.absent(), + this.priority = const Value.absent(), + this.retryAfter = const Value.absent(), + this.status = const Value.absent(), + }); + UploadTasksCompanion.insert({ + this.id = const Value.absent(), + required int attempts, + required int createdAt, + this.filePath = const Value.absent(), + this.isLivePhoto = const Value.absent(), + this.lastError = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + required String localId, + required int method, + required double priority, + this.retryAfter = const Value.absent(), + required int status, + }) : attempts = Value(attempts), + createdAt = Value(createdAt), + localId = Value(localId), + method = Value(method), + priority = Value(priority), + status = Value(status); + static Insertable custom({ + Expression? id, + Expression? attempts, + Expression? createdAt, + Expression? filePath, + Expression? isLivePhoto, + Expression? lastError, + Expression? livePhotoVideoId, + Expression? localId, + Expression? method, + Expression? priority, + Expression? retryAfter, + Expression? status, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (attempts != null) 'attempts': attempts, + if (createdAt != null) 'created_at': createdAt, + if (filePath != null) 'file_path': filePath, + if (isLivePhoto != null) 'is_live_photo': isLivePhoto, + if (lastError != null) 'last_error': lastError, + if (livePhotoVideoId != null) 'live_photo_video_id': livePhotoVideoId, + if (localId != null) 'local_id': localId, + if (method != null) 'method': method, + if (priority != null) 'priority': priority, + if (retryAfter != null) 'retry_after': retryAfter, + if (status != null) 'status': status, + }); + } + + UploadTasksCompanion copyWith({ + Value? id, + Value? attempts, + Value? createdAt, + Value? filePath, + Value? isLivePhoto, + Value? lastError, + Value? livePhotoVideoId, + Value? localId, + Value? method, + Value? priority, + Value? retryAfter, + Value? status, + }) { + return UploadTasksCompanion( + id: id ?? this.id, + attempts: attempts ?? this.attempts, + createdAt: createdAt ?? this.createdAt, + filePath: filePath ?? this.filePath, + isLivePhoto: isLivePhoto ?? this.isLivePhoto, + lastError: lastError ?? this.lastError, + livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId, + localId: localId ?? this.localId, + method: method ?? this.method, + priority: priority ?? this.priority, + retryAfter: retryAfter ?? this.retryAfter, + status: status ?? this.status, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (attempts.present) { + map['attempts'] = Variable(attempts.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (filePath.present) { + map['file_path'] = Variable(filePath.value); + } + if (isLivePhoto.present) { + map['is_live_photo'] = Variable(isLivePhoto.value); + } + if (lastError.present) { + map['last_error'] = Variable(lastError.value); + } + if (livePhotoVideoId.present) { + map['live_photo_video_id'] = Variable(livePhotoVideoId.value); + } + if (localId.present) { + map['local_id'] = Variable(localId.value); + } + if (method.present) { + map['method'] = Variable(method.value); + } + if (priority.present) { + map['priority'] = Variable(priority.value); + } + if (retryAfter.present) { + map['retry_after'] = Variable(retryAfter.value); + } + if (status.present) { + map['status'] = Variable(status.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UploadTasksCompanion(') + ..write('id: $id, ') + ..write('attempts: $attempts, ') + ..write('createdAt: $createdAt, ') + ..write('filePath: $filePath, ') + ..write('isLivePhoto: $isLivePhoto, ') + ..write('lastError: $lastError, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('localId: $localId, ') + ..write('method: $method, ') + ..write('priority: $priority, ') + ..write('retryAfter: $retryAfter, ') + ..write('status: $status') + ..write(')')) + .toString(); + } +} + +class UploadTaskStats extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UploadTaskStats(this.attachedDatabase, [this._alias]); + late final GeneratedColumn pendingDownloads = GeneratedColumn( + 'pending_downloads', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn pendingUploads = GeneratedColumn( + 'pending_uploads', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn queuedDownloads = GeneratedColumn( + 'queued_downloads', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn queuedUploads = GeneratedColumn( + 'queued_uploads', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn failedDownloads = GeneratedColumn( + 'failed_downloads', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn failedUploads = GeneratedColumn( + 'failed_uploads', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn completedUploads = GeneratedColumn( + 'completed_uploads', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn skippedUploads = GeneratedColumn( + 'skipped_uploads', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + @override + List get $columns => [ + pendingDownloads, + pendingUploads, + queuedDownloads, + queuedUploads, + failedDownloads, + failedUploads, + completedUploads, + skippedUploads, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'upload_task_stats'; + @override + Set get $primaryKey => const {}; + @override + UploadTaskStatsData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UploadTaskStatsData( + pendingDownloads: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}pending_downloads'], + )!, + pendingUploads: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}pending_uploads'], + )!, + queuedDownloads: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}queued_downloads'], + )!, + queuedUploads: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}queued_uploads'], + )!, + failedDownloads: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}failed_downloads'], + )!, + failedUploads: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}failed_uploads'], + )!, + completedUploads: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}completed_uploads'], + )!, + skippedUploads: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}skipped_uploads'], + )!, + ); + } + + @override + UploadTaskStats createAlias(String alias) { + return UploadTaskStats(attachedDatabase, alias); + } + + @override + bool get dontWriteConstraints => true; +} + +class UploadTaskStatsData extends DataClass + implements Insertable { + final int pendingDownloads; + final int pendingUploads; + final int queuedDownloads; + final int queuedUploads; + final int failedDownloads; + final int failedUploads; + final int completedUploads; + final int skippedUploads; + const UploadTaskStatsData({ + required this.pendingDownloads, + required this.pendingUploads, + required this.queuedDownloads, + required this.queuedUploads, + required this.failedDownloads, + required this.failedUploads, + required this.completedUploads, + required this.skippedUploads, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['pending_downloads'] = Variable(pendingDownloads); + map['pending_uploads'] = Variable(pendingUploads); + map['queued_downloads'] = Variable(queuedDownloads); + map['queued_uploads'] = Variable(queuedUploads); + map['failed_downloads'] = Variable(failedDownloads); + map['failed_uploads'] = Variable(failedUploads); + map['completed_uploads'] = Variable(completedUploads); + map['skipped_uploads'] = Variable(skippedUploads); + return map; + } + + factory UploadTaskStatsData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UploadTaskStatsData( + pendingDownloads: serializer.fromJson(json['pendingDownloads']), + pendingUploads: serializer.fromJson(json['pendingUploads']), + queuedDownloads: serializer.fromJson(json['queuedDownloads']), + queuedUploads: serializer.fromJson(json['queuedUploads']), + failedDownloads: serializer.fromJson(json['failedDownloads']), + failedUploads: serializer.fromJson(json['failedUploads']), + completedUploads: serializer.fromJson(json['completedUploads']), + skippedUploads: serializer.fromJson(json['skippedUploads']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'pendingDownloads': serializer.toJson(pendingDownloads), + 'pendingUploads': serializer.toJson(pendingUploads), + 'queuedDownloads': serializer.toJson(queuedDownloads), + 'queuedUploads': serializer.toJson(queuedUploads), + 'failedDownloads': serializer.toJson(failedDownloads), + 'failedUploads': serializer.toJson(failedUploads), + 'completedUploads': serializer.toJson(completedUploads), + 'skippedUploads': serializer.toJson(skippedUploads), + }; + } + + UploadTaskStatsData copyWith({ + int? pendingDownloads, + int? pendingUploads, + int? queuedDownloads, + int? queuedUploads, + int? failedDownloads, + int? failedUploads, + int? completedUploads, + int? skippedUploads, + }) => UploadTaskStatsData( + pendingDownloads: pendingDownloads ?? this.pendingDownloads, + pendingUploads: pendingUploads ?? this.pendingUploads, + queuedDownloads: queuedDownloads ?? this.queuedDownloads, + queuedUploads: queuedUploads ?? this.queuedUploads, + failedDownloads: failedDownloads ?? this.failedDownloads, + failedUploads: failedUploads ?? this.failedUploads, + completedUploads: completedUploads ?? this.completedUploads, + skippedUploads: skippedUploads ?? this.skippedUploads, + ); + UploadTaskStatsData copyWithCompanion(UploadTaskStatsCompanion data) { + return UploadTaskStatsData( + pendingDownloads: data.pendingDownloads.present + ? data.pendingDownloads.value + : this.pendingDownloads, + pendingUploads: data.pendingUploads.present + ? data.pendingUploads.value + : this.pendingUploads, + queuedDownloads: data.queuedDownloads.present + ? data.queuedDownloads.value + : this.queuedDownloads, + queuedUploads: data.queuedUploads.present + ? data.queuedUploads.value + : this.queuedUploads, + failedDownloads: data.failedDownloads.present + ? data.failedDownloads.value + : this.failedDownloads, + failedUploads: data.failedUploads.present + ? data.failedUploads.value + : this.failedUploads, + completedUploads: data.completedUploads.present + ? data.completedUploads.value + : this.completedUploads, + skippedUploads: data.skippedUploads.present + ? data.skippedUploads.value + : this.skippedUploads, + ); + } + + @override + String toString() { + return (StringBuffer('UploadTaskStatsData(') + ..write('pendingDownloads: $pendingDownloads, ') + ..write('pendingUploads: $pendingUploads, ') + ..write('queuedDownloads: $queuedDownloads, ') + ..write('queuedUploads: $queuedUploads, ') + ..write('failedDownloads: $failedDownloads, ') + ..write('failedUploads: $failedUploads, ') + ..write('completedUploads: $completedUploads, ') + ..write('skippedUploads: $skippedUploads') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + pendingDownloads, + pendingUploads, + queuedDownloads, + queuedUploads, + failedDownloads, + failedUploads, + completedUploads, + skippedUploads, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UploadTaskStatsData && + other.pendingDownloads == this.pendingDownloads && + other.pendingUploads == this.pendingUploads && + other.queuedDownloads == this.queuedDownloads && + other.queuedUploads == this.queuedUploads && + other.failedDownloads == this.failedDownloads && + other.failedUploads == this.failedUploads && + other.completedUploads == this.completedUploads && + other.skippedUploads == this.skippedUploads); +} + +class UploadTaskStatsCompanion extends UpdateCompanion { + final Value pendingDownloads; + final Value pendingUploads; + final Value queuedDownloads; + final Value queuedUploads; + final Value failedDownloads; + final Value failedUploads; + final Value completedUploads; + final Value skippedUploads; + final Value rowid; + const UploadTaskStatsCompanion({ + this.pendingDownloads = const Value.absent(), + this.pendingUploads = const Value.absent(), + this.queuedDownloads = const Value.absent(), + this.queuedUploads = const Value.absent(), + this.failedDownloads = const Value.absent(), + this.failedUploads = const Value.absent(), + this.completedUploads = const Value.absent(), + this.skippedUploads = const Value.absent(), + this.rowid = const Value.absent(), + }); + UploadTaskStatsCompanion.insert({ + required int pendingDownloads, + required int pendingUploads, + required int queuedDownloads, + required int queuedUploads, + required int failedDownloads, + required int failedUploads, + required int completedUploads, + required int skippedUploads, + this.rowid = const Value.absent(), + }) : pendingDownloads = Value(pendingDownloads), + pendingUploads = Value(pendingUploads), + queuedDownloads = Value(queuedDownloads), + queuedUploads = Value(queuedUploads), + failedDownloads = Value(failedDownloads), + failedUploads = Value(failedUploads), + completedUploads = Value(completedUploads), + skippedUploads = Value(skippedUploads); + static Insertable custom({ + Expression? pendingDownloads, + Expression? pendingUploads, + Expression? queuedDownloads, + Expression? queuedUploads, + Expression? failedDownloads, + Expression? failedUploads, + Expression? completedUploads, + Expression? skippedUploads, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (pendingDownloads != null) 'pending_downloads': pendingDownloads, + if (pendingUploads != null) 'pending_uploads': pendingUploads, + if (queuedDownloads != null) 'queued_downloads': queuedDownloads, + if (queuedUploads != null) 'queued_uploads': queuedUploads, + if (failedDownloads != null) 'failed_downloads': failedDownloads, + if (failedUploads != null) 'failed_uploads': failedUploads, + if (completedUploads != null) 'completed_uploads': completedUploads, + if (skippedUploads != null) 'skipped_uploads': skippedUploads, + if (rowid != null) 'rowid': rowid, + }); + } + + UploadTaskStatsCompanion copyWith({ + Value? pendingDownloads, + Value? pendingUploads, + Value? queuedDownloads, + Value? queuedUploads, + Value? failedDownloads, + Value? failedUploads, + Value? completedUploads, + Value? skippedUploads, + Value? rowid, + }) { + return UploadTaskStatsCompanion( + pendingDownloads: pendingDownloads ?? this.pendingDownloads, + pendingUploads: pendingUploads ?? this.pendingUploads, + queuedDownloads: queuedDownloads ?? this.queuedDownloads, + queuedUploads: queuedUploads ?? this.queuedUploads, + failedDownloads: failedDownloads ?? this.failedDownloads, + failedUploads: failedUploads ?? this.failedUploads, + completedUploads: completedUploads ?? this.completedUploads, + skippedUploads: skippedUploads ?? this.skippedUploads, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (pendingDownloads.present) { + map['pending_downloads'] = Variable(pendingDownloads.value); + } + if (pendingUploads.present) { + map['pending_uploads'] = Variable(pendingUploads.value); + } + if (queuedDownloads.present) { + map['queued_downloads'] = Variable(queuedDownloads.value); + } + if (queuedUploads.present) { + map['queued_uploads'] = Variable(queuedUploads.value); + } + if (failedDownloads.present) { + map['failed_downloads'] = Variable(failedDownloads.value); + } + if (failedUploads.present) { + map['failed_uploads'] = Variable(failedUploads.value); + } + if (completedUploads.present) { + map['completed_uploads'] = Variable(completedUploads.value); + } + if (skippedUploads.present) { + map['skipped_uploads'] = Variable(skippedUploads.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UploadTaskStatsCompanion(') + ..write('pendingDownloads: $pendingDownloads, ') + ..write('pendingUploads: $pendingUploads, ') + ..write('queuedDownloads: $queuedDownloads, ') + ..write('queuedUploads: $queuedUploads, ') + ..write('failedDownloads: $failedDownloads, ') + ..write('failedUploads: $failedUploads, ') + ..write('completedUploads: $completedUploads, ') + ..write('skippedUploads: $skippedUploads, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +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, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn hasProfileImage = GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("has_profile_image" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn profileChangedAt = + GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn avatarColor = GeneratedColumn( + 'avatar_color', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + 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.bool, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + 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; +} + +class UserEntityData extends DataClass implements Insertable { + final String id; + final String name; + final String email; + final bool hasProfileImage; + final DateTime 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, + bool? hasProfileImage, + DateTime? 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, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn localDateTime = + GeneratedColumn( + 'local_date_time', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn thumbHash = GeneratedColumn( + 'thumb_hash', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn livePhotoVideoId = GeneratedColumn( + 'live_photo_video_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn visibility = GeneratedColumn( + 'visibility', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn stackId = GeneratedColumn( + 'stack_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn libraryId = GeneratedColumn( + 'library_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + ]; + @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.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + )!, + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + localDateTime: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}local_date_time'], + ), + thumbHash: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumb_hash'], + ), + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}deleted_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'], + ), + ); + } + + @override + RemoteAssetEntity createAlias(String alias) { + return RemoteAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String checksum; + final bool isFavorite; + final String ownerId; + final DateTime? localDateTime; + final String? thumbHash; + final DateTime? deletedAt; + final String? livePhotoVideoId; + final int visibility; + final String? stackId; + final String? libraryId; + const RemoteAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + required this.checksum, + required this.isFavorite, + required this.ownerId, + this.localDateTime, + this.thumbHash, + this.deletedAt, + this.livePhotoVideoId, + required this.visibility, + this.stackId, + this.libraryId, + }); + @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 || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + 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 || 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); + } + 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']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + 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']), + livePhotoVideoId: serializer.fromJson(json['livePhotoVideoId']), + visibility: serializer.fromJson(json['visibility']), + stackId: serializer.fromJson(json['stackId']), + libraryId: serializer.fromJson(json['libraryId']), + ); + } + @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), + 'durationInSeconds': serializer.toJson(durationInSeconds), + '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), + 'livePhotoVideoId': serializer.toJson(livePhotoVideoId), + 'visibility': serializer.toJson(visibility), + 'stackId': serializer.toJson(stackId), + 'libraryId': serializer.toJson(libraryId), + }; + } + + RemoteAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + String? checksum, + bool? isFavorite, + String? ownerId, + Value localDateTime = const Value.absent(), + Value thumbHash = const Value.absent(), + Value deletedAt = const Value.absent(), + Value livePhotoVideoId = const Value.absent(), + int? visibility, + Value stackId = const Value.absent(), + Value libraryId = const Value.absent(), + }) => 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, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + 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, + 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, + ); + 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, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + 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, + 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, + ); + } + + @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('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + ); + @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.durationInSeconds == this.durationInSeconds && + 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.livePhotoVideoId == this.livePhotoVideoId && + other.visibility == this.visibility && + other.stackId == this.stackId && + other.libraryId == this.libraryId); +} + +class RemoteAssetEntityCompanion + extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value checksum; + final Value isFavorite; + final Value ownerId; + final Value localDateTime; + final Value thumbHash; + final Value deletedAt; + final Value livePhotoVideoId; + final Value visibility; + final Value stackId; + final Value libraryId; + 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.durationInSeconds = 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.livePhotoVideoId = const Value.absent(), + this.visibility = const Value.absent(), + this.stackId = const Value.absent(), + this.libraryId = 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.durationInSeconds = 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.livePhotoVideoId = const Value.absent(), + required int visibility, + this.stackId = const Value.absent(), + this.libraryId = 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? durationInSeconds, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? ownerId, + Expression? localDateTime, + Expression? thumbHash, + Expression? deletedAt, + Expression? livePhotoVideoId, + Expression? visibility, + Expression? stackId, + Expression? libraryId, + }) { + 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 (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + 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 (livePhotoVideoId != null) 'live_photo_video_id': livePhotoVideoId, + if (visibility != null) 'visibility': visibility, + if (stackId != null) 'stack_id': stackId, + if (libraryId != null) 'library_id': libraryId, + }); + } + + RemoteAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? checksum, + Value? isFavorite, + Value? ownerId, + Value? localDateTime, + Value? thumbHash, + Value? deletedAt, + Value? livePhotoVideoId, + Value? visibility, + Value? stackId, + Value? libraryId, + }) { + 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, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + 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, + livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId ?? this.stackId, + libraryId: libraryId ?? this.libraryId, + ); + } + + @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 (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.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 (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); + } + 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('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId') + ..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, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn primaryAssetId = GeneratedColumn( + 'primary_asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @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.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + 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; +} + +class StackEntityData extends DataClass implements Insertable { + final String id; + final DateTime createdAt; + final DateTime 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, + DateTime? createdAt, + DateTime? 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, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + orientation, + ]; + @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.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}orientation'], + )!, + ); + } + + @override + LocalAssetEntity createAlias(String alias) { + return LocalAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String? checksum; + final bool isFavorite; + final int orientation; + const LocalAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + this.checksum, + required this.isFavorite, + required this.orientation, + }); + @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 || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + if (!nullToAbsent || checksum != null) { + map['checksum'] = Variable(checksum); + } + map['is_favorite'] = Variable(isFavorite); + map['orientation'] = Variable(orientation); + 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']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + orientation: serializer.fromJson(json['orientation']), + ); + } + @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), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'orientation': serializer.toJson(orientation), + }; + } + + LocalAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + Value checksum = const Value.absent(), + bool? isFavorite, + int? orientation, + }) => 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, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + checksum: checksum.present ? checksum.value : this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + ); + 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, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + 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, + ); + } + + @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('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + orientation, + ); + @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.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.orientation == this.orientation); +} + +class LocalAssetEntityCompanion extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value checksum; + final Value isFavorite; + final Value orientation; + 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.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = 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.durationInSeconds = const Value.absent(), + required String id, + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = 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? durationInSeconds, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? orientation, + }) { + 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 (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (orientation != null) 'orientation': orientation, + }); + } + + LocalAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? checksum, + Value? isFavorite, + Value? orientation, + }) { + 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, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + ); + } + + @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 (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.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); + } + 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('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation') + ..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, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const CustomExpression('\'\''), + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn thumbnailAssetId = GeneratedColumn( + 'thumbnail_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn isActivityEnabled = GeneratedColumn( + 'is_activity_enabled', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_activity_enabled" IN (0, 1))', + ), + defaultValue: const CustomExpression('1'), + ); + late final GeneratedColumn order = GeneratedColumn( + 'order', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + name, + description, + createdAt, + updatedAt, + ownerId, + 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.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + thumbnailAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumbnail_asset_id'], + ), + isActivityEnabled: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + 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; +} + +class RemoteAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String description; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String? thumbnailAssetId; + final bool isActivityEnabled; + final int order; + const RemoteAlbumEntityData({ + required this.id, + required this.name, + required this.description, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + 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); + map['owner_id'] = Variable(ownerId); + 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']), + ownerId: serializer.fromJson(json['ownerId']), + 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), + 'ownerId': serializer.toJson(ownerId), + 'thumbnailAssetId': serializer.toJson(thumbnailAssetId), + 'isActivityEnabled': serializer.toJson(isActivityEnabled), + 'order': serializer.toJson(order), + }; + } + + RemoteAlbumEntityData copyWith({ + String? id, + String? name, + String? description, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + Value thumbnailAssetId = const Value.absent(), + bool? isActivityEnabled, + int? order, + }) => RemoteAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + 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, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + 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('ownerId: $ownerId, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + description, + createdAt, + updatedAt, + ownerId, + 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.ownerId == this.ownerId && + 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 ownerId; + 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.ownerId = 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(), + required String ownerId, + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + required int order, + }) : id = Value(id), + name = Value(name), + ownerId = Value(ownerId), + order = Value(order); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? description, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + 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 (ownerId != null) 'owner_id': ownerId, + 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? ownerId, + 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, + ownerId: ownerId ?? this.ownerId, + 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 (ownerId.present) { + map['owner_id'] = Variable(ownerId.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('ownerId: $ownerId, ') + ..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, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn backupSelection = GeneratedColumn( + 'backup_selection', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn isIosSharedAlbum = GeneratedColumn( + 'is_ios_shared_album', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + '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, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn marker_ = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + '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.dateTime, + data['${effectivePrefix}updated_at'], + )!, + backupSelection: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}backup_selection'], + )!, + isIosSharedAlbum: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_ios_shared_album'], + )!, + linkedRemoteAlbumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}linked_remote_album_id'], + ), + marker_: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumEntity createAlias(String alias) { + return LocalAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final DateTime updatedAt; + final int backupSelection; + final bool isIosSharedAlbum; + final String? linkedRemoteAlbumId; + final bool? 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, + DateTime? updatedAt, + int? backupSelection, + bool? 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, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES local_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES local_album_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn marker_ = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + '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.bool, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumAssetEntity createAlias(String alias) { + return LocalAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + final bool? 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, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isAdmin = GeneratedColumn( + 'is_admin', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_admin" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn hasProfileImage = GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("has_profile_image" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn profileChangedAt = + GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn avatarColor = GeneratedColumn( + 'avatar_color', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn quotaSizeInBytes = GeneratedColumn( + 'quota_size_in_bytes', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn quotaUsageInBytes = GeneratedColumn( + 'quota_usage_in_bytes', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn pinCode = GeneratedColumn( + 'pin_code', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @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.bool, + data['${effectivePrefix}is_admin'], + )!, + hasProfileImage: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + 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; +} + +class AuthUserEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String email; + final bool isAdmin; + final bool hasProfileImage; + final DateTime 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, + bool? isAdmin, + bool? hasProfileImage, + DateTime? 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, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn key = GeneratedColumn( + 'key', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn value = GeneratedColumn( + 'value', + aliasedName, + false, + type: DriftSqlType.blob, + requiredDuringInsert: true, + ); + @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; +} + +class UserMetadataEntityData extends DataClass + implements Insertable { + final String userId; + final int key; + final 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, + 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 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, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn sharedWithId = GeneratedColumn( + 'shared_with_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn inTimeline = GeneratedColumn( + 'in_timeline', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + '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.bool, + data['${effectivePrefix}in_timeline'], + )!, + ); + } + + @override + PartnerEntity createAlias(String alias) { + return PartnerEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class PartnerEntityData extends DataClass + implements Insertable { + final String sharedById; + final String sharedWithId; + final bool 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, + bool? 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, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn city = GeneratedColumn( + 'city', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn state = GeneratedColumn( + 'state', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn country = GeneratedColumn( + 'country', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn dateTimeOriginal = + GeneratedColumn( + 'date_time_original', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn exposureTime = GeneratedColumn( + 'exposure_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn fNumber = GeneratedColumn( + 'f_number', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn fileSize = GeneratedColumn( + 'file_size', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn focalLength = GeneratedColumn( + 'focal_length', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn iso = GeneratedColumn( + 'iso', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn make = GeneratedColumn( + 'make', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn model = GeneratedColumn( + 'model', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn lens = GeneratedColumn( + 'lens', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn timeZone = GeneratedColumn( + 'time_zone', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn rating = GeneratedColumn( + 'rating', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn projectionType = GeneratedColumn( + 'projection_type', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @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.dateTime, + 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; +} + +class RemoteExifEntityData extends DataClass + implements Insertable { + final String assetId; + final String? city; + final String? state; + final String? country; + final DateTime? 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, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + '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; +} + +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, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn role = GeneratedColumn( + 'role', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @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; +} + +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 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, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn data = GeneratedColumn( + 'data', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isSaved = GeneratedColumn( + 'is_saved', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_saved" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn memoryAt = GeneratedColumn( + 'memory_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: true, + ); + late final GeneratedColumn seenAt = GeneratedColumn( + 'seen_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn showAt = GeneratedColumn( + 'show_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn hideAt = GeneratedColumn( + 'hide_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @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.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + 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.bool, + data['${effectivePrefix}is_saved'], + )!, + memoryAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}memory_at'], + )!, + seenAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}seen_at'], + ), + showAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}show_at'], + ), + hideAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}hide_at'], + ), + ); + } + + @override + MemoryEntity createAlias(String alias) { + return MemoryEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class MemoryEntityData extends DataClass + implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final DateTime? deletedAt; + final String ownerId; + final int type; + final String data; + final bool isSaved; + final DateTime memoryAt; + final DateTime? seenAt; + final DateTime? showAt; + final DateTime? 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, + DateTime? createdAt, + DateTime? updatedAt, + Value deletedAt = const Value.absent(), + String? ownerId, + int? type, + String? data, + bool? isSaved, + DateTime? 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 DateTime 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, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn memoryId = GeneratedColumn( + 'memory_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + '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; +} + +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, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn faceAssetId = GeneratedColumn( + 'face_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + ); + late final GeneratedColumn isHidden = GeneratedColumn( + 'is_hidden', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_hidden" IN (0, 1))', + ), + ); + late final GeneratedColumn color = GeneratedColumn( + 'color', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn birthDate = GeneratedColumn( + 'birth_date', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @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.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + 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.bool, + data['${effectivePrefix}is_favorite'], + )!, + isHidden: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_hidden'], + )!, + color: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}color'], + ), + birthDate: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}birth_date'], + ), + ); + } + + @override + PersonEntity createAlias(String alias) { + return PersonEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class PersonEntityData extends DataClass + implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String name; + final String? faceAssetId; + final bool isFavorite; + final bool isHidden; + final String? color; + final DateTime? 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, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + String? name, + Value faceAssetId = const Value.absent(), + bool? isFavorite, + bool? 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 bool isFavorite, + required bool 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, + ); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn personId = GeneratedColumn( + 'person_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES person_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn imageWidth = GeneratedColumn( + 'image_width', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn imageHeight = GeneratedColumn( + 'image_height', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxX1 = GeneratedColumn( + 'bounding_box_x1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxY1 = GeneratedColumn( + 'bounding_box_y1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxX2 = GeneratedColumn( + 'bounding_box_x2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxY2 = GeneratedColumn( + 'bounding_box_y2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn sourceType = GeneratedColumn( + 'source_type', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + ]; + @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'], + )!, + ); + } + + @override + AssetFaceEntity createAlias(String alias) { + return AssetFaceEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => 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; + 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, + }); + @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); + 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']), + ); + } + @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), + }; + } + + 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, + }) => 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, + ); + 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, + ); + } + + @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(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + ); + @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); +} + +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; + 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(), + }); + 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, + }) : 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, + }) { + 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, + }); + } + + AssetFaceEntityCompanion copyWith({ + Value? id, + Value? assetId, + Value? personId, + Value? imageWidth, + Value? imageHeight, + Value? boundingBoxX1, + Value? boundingBoxY1, + Value? boundingBoxX2, + Value? boundingBoxY2, + Value? sourceType, + }) { + 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, + ); + } + + @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); + } + 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(')')) + .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, + ); + late final GeneratedColumn stringValue = GeneratedColumn( + 'string_value', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn intValue = GeneratedColumn( + 'int_value', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + @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; +} + +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 TrashedLocalAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + TrashedLocalAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + albumId, + checksum, + isFavorite, + orientation, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'trashed_local_asset_entity'; + @override + Set get $primaryKey => {id, albumId}; + @override + TrashedLocalAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return TrashedLocalAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}orientation'], + )!, + ); + } + + @override + TrashedLocalAssetEntity createAlias(String alias) { + return TrashedLocalAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class TrashedLocalAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String albumId; + final String? checksum; + final bool isFavorite; + final int orientation; + const TrashedLocalAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + required this.albumId, + this.checksum, + required this.isFavorite, + required this.orientation, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = 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 || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + map['album_id'] = Variable(albumId); + if (!nullToAbsent || checksum != null) { + map['checksum'] = Variable(checksum); + } + map['is_favorite'] = Variable(isFavorite); + map['orientation'] = Variable(orientation); + return map; + } + + factory TrashedLocalAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return TrashedLocalAssetEntityData( + 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']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + albumId: serializer.fromJson(json['albumId']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + orientation: serializer.fromJson(json['orientation']), + ); + } + @override + Map toJson({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), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'albumId': serializer.toJson(albumId), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'orientation': serializer.toJson(orientation), + }; + } + + TrashedLocalAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + String? albumId, + Value checksum = const Value.absent(), + bool? isFavorite, + int? orientation, + }) => TrashedLocalAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + albumId: albumId ?? this.albumId, + checksum: checksum.present ? checksum.value : this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + ); + TrashedLocalAssetEntityData copyWithCompanion( + TrashedLocalAssetEntityCompanion data, + ) { + return TrashedLocalAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + ); + } + + @override + String toString() { + return (StringBuffer('TrashedLocalAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('albumId: $albumId, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + albumId, + checksum, + isFavorite, + orientation, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is TrashedLocalAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.albumId == this.albumId && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.orientation == this.orientation); +} + +class TrashedLocalAssetEntityCompanion + extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value albumId; + final Value checksum; + final Value isFavorite; + final Value orientation; + const TrashedLocalAssetEntityCompanion({ + 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.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.albumId = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + }); + TrashedLocalAssetEntityCompanion.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.durationInSeconds = const Value.absent(), + required String id, + required String albumId, + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id), + albumId = Value(albumId); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? albumId, + Expression? checksum, + Expression? isFavorite, + Expression? orientation, + }) { + 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 (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (albumId != null) 'album_id': albumId, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (orientation != null) 'orientation': orientation, + }); + } + + TrashedLocalAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? albumId, + Value? checksum, + Value? isFavorite, + Value? orientation, + }) { + return 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, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + albumId: albumId ?? this.albumId, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + ); + } + + @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 (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.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); + } + 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('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('albumId: $albumId, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation') + ..write(')')) + .toString(); + } +} + +class DatabaseAtV14 extends GeneratedDatabase { + DatabaseAtV14(QueryExecutor e) : super(e); + late final UploadTasks uploadTasks = UploadTasks(this); + late final UploadTaskStats uploadTaskStats = UploadTaskStats(this); + late final Trigger updateStatsInsert = Trigger( + 'CREATE TRIGGER update_stats_insert BEFORE INSERT ON upload_tasks BEGIN UPDATE upload_task_stats SET pending_downloads = pending_downloads +(NEW.status = 0), queued_downloads = queued_downloads +(NEW.status = 1), failed_downloads = failed_downloads +(NEW.status = 2), pending_uploads = pending_uploads +(NEW.status = 3), queued_uploads = queued_uploads +(NEW.status = 4), failed_uploads = failed_uploads +(NEW.status = 5), completed_uploads = completed_uploads +(NEW.status = 6), skipped_uploads = skipped_uploads +(NEW.status = 7);END', + 'update_stats_insert', + ); + late final Trigger updateStatsUpdate = Trigger( + 'CREATE TRIGGER update_stats_update BEFORE UPDATE OF status ON upload_tasks WHEN OLD.status != NEW.status BEGIN UPDATE upload_task_stats SET pending_downloads = pending_downloads -(OLD.status = 0)+(NEW.status = 0), queued_downloads = queued_downloads -(OLD.status = 1)+(NEW.status = 1), failed_downloads = failed_downloads -(OLD.status = 2)+(NEW.status = 2), pending_uploads = pending_uploads -(OLD.status = 3)+(NEW.status = 3), queued_uploads = queued_uploads -(OLD.status = 4)+(NEW.status = 4), failed_uploads = failed_uploads -(OLD.status = 5)+(NEW.status = 5), completed_uploads = completed_uploads -(OLD.status = 6)+(NEW.status = 6), skipped_uploads = skipped_uploads -(OLD.status = 7)+(NEW.status = 7);END', + 'update_stats_update', + ); + late final Trigger updateStatsDelete = Trigger( + 'CREATE TRIGGER update_stats_delete BEFORE DELETE ON upload_tasks BEGIN UPDATE upload_task_stats SET pending_downloads = pending_downloads -(OLD.status = 0), queued_downloads = queued_downloads -(OLD.status = 1), failed_downloads = failed_downloads -(OLD.status = 2), pending_uploads = pending_uploads -(OLD.status = 3), queued_uploads = queued_uploads -(OLD.status = 4), failed_uploads = failed_uploads -(OLD.status = 5), completed_uploads = completed_uploads -(OLD.status = 6), skipped_uploads = skipped_uploads -(OLD.status = 7);END', + 'update_stats_delete', + ); + late final Index idxUploadTasksLocalId = Index( + 'idx_upload_tasks_local_id', + 'CREATE UNIQUE INDEX idx_upload_tasks_local_id ON upload_tasks (local_id, live_photo_video_id)', + ); + late final Index idxUploadTasksAssetData = Index( + 'idx_upload_tasks_asset_data', + 'CREATE INDEX idx_upload_tasks_asset_data ON upload_tasks (status, priority DESC, created_at)', + ); + 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 idxLocalAssetChecksum = Index( + 'idx_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + late final Index idxRemoteAssetOwnerChecksum = Index( + 'idx_remote_asset_owner_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)', + ); + 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 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 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 TrashedLocalAssetEntity trashedLocalAssetEntity = + TrashedLocalAssetEntity(this); + 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 idxTrashedLocalAssetChecksum = Index( + 'idx_trashed_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)', + ); + late final Index idxTrashedLocalAssetAlbum = Index( + 'idx_trashed_local_asset_album', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)', + ); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + uploadTasks, + uploadTaskStats, + updateStatsInsert, + updateStatsUpdate, + updateStatsDelete, + idxUploadTasksLocalId, + idxUploadTasksAssetData, + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + remoteAlbumEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAssetChecksum, + idxRemoteAssetOwnerChecksum, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + authUserEntity, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + storeEntity, + trashedLocalAssetEntity, + idxLatLng, + idxTrashedLocalAssetChecksum, + idxTrashedLocalAssetAlbum, + ]; + @override + StreamQueryUpdateRules get streamUpdateRules => const StreamQueryUpdateRules([ + WritePropagation( + on: TableUpdateQuery.onTableName( + 'upload_tasks', + limitUpdateKind: UpdateKind.delete, + ), + result: [], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'upload_tasks', + limitUpdateKind: UpdateKind.delete, + ), + result: [], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'upload_tasks', + limitUpdateKind: UpdateKind.delete, + ), + result: [], + ), + ]); + @override + int get schemaVersion => 14; + @override + DriftDatabaseOptions get options => + const DriftDatabaseOptions(storeDateTimeAsText: true); +}