diff --git a/build.gradle.kts b/build.gradle.kts index a1ff82738..1986b52ea 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,10 +9,10 @@ plugins { alias(libs.plugins.kotlin.serialization) apply false alias(libs.plugins.ktorfit) apply false alias(libs.plugins.ktlint) apply false - alias(libs.plugins.sqldelight) apply false alias(libs.plugins.skie) apply false alias(libs.plugins.ben.manes.versions) alias(libs.plugins.compose.compiler) apply false + alias(libs.plugins.room) apply false } allprojects { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index fa3c30664..718c6267e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -30,7 +30,6 @@ compose-lint-checks = "1.3.1" ktlintPlugin = "12.1.1" apng-android = "3.0.1" ktlint = "1.3.1" -sqldelight = "2.0.2" okio = "3.9.1" skie = "0.9.0-RC.3" ksoup = "0.1.8" @@ -46,6 +45,7 @@ google-services = "4.4.2" firebase-crashlytics = "3.0.2" napier = "2.7.1" materialKolor = "1.7.1" +room = "2.7.0-alpha08" [libraries] bluesky = { module = "moe.tlaster.ozone:bluesky", version.ref = "bluesky" } @@ -83,6 +83,9 @@ datastore = { group = "androidx.datastore", name = "datastore", version = "1.1.1 desugar_jdk_libs = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desugar_jdk_libs" } androidx-window = { group = "androidx.window", name = "window-core", version = "1.3.0" } androidx-splash = { group = "androidx.core", name = "core-splashscreen", version = "1.2.0-alpha02" } +room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" } +room-paging = { group = "androidx.room", name = "room-paging", version.ref = "room" } +room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" } media3-exoplayer = { group = "androidx.media3", name = "media3-exoplayer", version.ref = "media3" } media3-ui = { group = "androidx.media3", name = "media3-ui", version.ref = "media3" } @@ -141,12 +144,6 @@ compose-webview = { group = "io.github.kevinnzou", name = "compose-webview", ver compose-placeholder-material3 = { group = "com.eygraber", name = "compose-placeholder-material3", version = "1.0.8" } -sqldelight-android-driver = { group = "app.cash.sqldelight", name = "android-driver", version.ref = "sqldelight" } -sqldelight-native-driver = { group = "app.cash.sqldelight", name = "native-driver", version.ref = "sqldelight" } -sqldelight-coroutines-extensions = { group = "app.cash.sqldelight", name = "coroutines-extensions", version.ref = "sqldelight" } -sqldelight-androidx-paging3-extensions = { group = "app.cash.sqldelight", name = "androidx-paging3-extensions", version.ref = "sqldelight" } -sqldelight-jvm-driver = { group = "app.cash.sqldelight", name = "sqlite-driver", version.ref = "sqldelight" } - okio = { group = "com.squareup.okio", name = "okio", version.ref = "okio" } napier = { group = "io.github.aakira", name = "napier", version.ref = "napier" } @@ -173,7 +170,6 @@ ktor = ["ktor-client-content-negotiation", "ktor-serialization-kotlinx-json", "k coil = ["coil-compose", "coil-gif", "apng", "coil-svg", "awebp", "vectordrawable-animated", "zoomable-image"] accompanist = ["accompanist-permissions", "accompanist-drawablepainter"] compose-destinations = ["compose-destinations"] -sqldelight = ["sqldelight-coroutines-extensions", "sqldelight-androidx-paging3-extensions"] media3 = ["media3-exoplayer", "media3-ui", "media3-hls"] firebase = ["firebase-analytics-ktx", "firebase-crashlytics-ktx"] ktorfit = ["ktorfit-lib", "ktorfit-converters-response", "ktorfit-converters-flow", "ktorfit-converters-call"] @@ -187,9 +183,9 @@ kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", versi ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } ktorfit = { id = "de.jensklingenberg.ktorfit", version.ref = "ktorfit" } ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlintPlugin" } -sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" } skie = { id = "co.touchlab.skie", version.ref = "skie" } ben-manes-versions = { id = "com.github.ben-manes.versions", version.ref = "versionUpdate" } google-services = { id = "com.google.gms.google-services", version.ref = "google-services" } firebase-crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "firebase-crashlytics" } compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } +room = { id = "androidx.room", version.ref = "room" } \ No newline at end of file diff --git a/iosApp/iosApp.xcodeproj/project.pbxproj b/iosApp/iosApp.xcodeproj/project.pbxproj index f9e465174..593bb7848 100644 --- a/iosApp/iosApp.xcodeproj/project.pbxproj +++ b/iosApp/iosApp.xcodeproj/project.pbxproj @@ -770,7 +770,8 @@ "PRODUCT_BUNDLE_IDENTIFIER[sdk=iphoneos*]" = dev.dimension.flare; PRODUCT_NAME = "${APP_NAME}"; PROVISIONING_PROFILE_SPECIFIER = ""; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + REGISTER_APP_GROUPS = NO; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_VERSION = 5.0; @@ -816,7 +817,8 @@ "PRODUCT_BUNDLE_IDENTIFIER[sdk=iphoneos*]" = dev.dimension.flare; PRODUCT_NAME = "${APP_NAME}"; PROVISIONING_PROFILE_SPECIFIER = ""; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + REGISTER_APP_GROUPS = NO; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_VERSION = 5.0; diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts index d5104748c..fe2658523 100644 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -1,16 +1,15 @@ - -import app.cash.sqldelight.core.capitalize +import java.util.Locale plugins { alias(libs.plugins.android.library) alias(libs.plugins.kotlin.multiplatform) - alias(libs.plugins.sqldelight) alias(libs.plugins.kotlin.serialization) alias(libs.plugins.ksp) alias(libs.plugins.ktorfit) alias(libs.plugins.skie) alias(libs.plugins.ktlint) alias(libs.plugins.compose.compiler) + alias(libs.plugins.room) } kotlin { @@ -22,29 +21,35 @@ kotlin { iosX64(), iosArm64(), iosSimulatorArm64(), - // macosArm64(), - // macosX64(), + macosArm64(), + macosX64(), ).forEach { appleTarget -> appleTarget.binaries.framework { baseName = "shared" isStatic = true embedBitcode(org.jetbrains.kotlin.gradle.plugin.mpp.BitcodeEmbeddingMode.DISABLE) + linkerOpts.add("-lsqlite3") } } targets.forEach { target -> target.name.takeIf { it != "metadata" }?.let { - "ksp${it.capitalize()}" + "ksp${it.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }}" }?.let { dependencies.add(it, libs.ktorfit.ksp) + dependencies.add(it, libs.room.compiler) } } sourceSets { + all { + languageSettings { + optIn("kotlin.uuid.ExperimentalUuidApi") + } + } val commonMain by getting { dependencies { - implementation(libs.bundles.sqldelight) implementation(libs.bundles.kotlinx) implementation(dependencies.platform(libs.koin.bom)) implementation(libs.koin.core) @@ -60,6 +65,8 @@ kotlin { implementation(libs.twitter.parser) implementation(libs.molecule.runtime) api(libs.bluesky) + implementation(libs.room.runtime) + implementation(libs.room.paging) } } val commonTest by getting { @@ -69,7 +76,6 @@ kotlin { } val androidMain by getting { dependencies { - implementation(libs.sqldelight.android.driver) implementation(project.dependencies.platform(libs.compose.bom)) implementation(libs.compose.foundation) } @@ -81,7 +87,6 @@ kotlin { } val nativeMain by getting { dependencies { - implementation(libs.sqldelight.native.driver) implementation(libs.stately.isolate) implementation(libs.stately.iso.collections) } @@ -89,22 +94,8 @@ kotlin { } } -sqldelight { - databases { - create("AppDatabase") { - packageName.set("dev.dimension.flare.data.database.app") - srcDirs("src/commonMain/sqldelight/app") - } - create("CacheDatabase") { - packageName.set("dev.dimension.flare.data.database.cache") - srcDirs("src/commonMain/sqldelight/cache") - } - create("VersionDatabase") { - packageName.set("dev.dimension.flare.data.database.version") - srcDirs("src/commonMain/sqldelight/version") - } - } - linkSqlite.set(true) +room { + schemaDirectory("$projectDir/schemas") } android { diff --git a/shared/schemas/dev.dimension.flare.data.database.app.AppDatabase/3.json b/shared/schemas/dev.dimension.flare.data.database.app.AppDatabase/3.json new file mode 100644 index 000000000..d3164ded1 --- /dev/null +++ b/shared/schemas/dev.dimension.flare.data.database.app.AppDatabase/3.json @@ -0,0 +1,151 @@ +{ + "formatVersion": 1, + "database": { + "version": 3, + "identityHash": "eb270b69d739601dc27be96b9c8aa192", + "entities": [ + { + "tableName": "DbAccount", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`account_key` TEXT NOT NULL, `credential_json` TEXT NOT NULL, `platform_type` TEXT NOT NULL, `last_active` INTEGER NOT NULL, PRIMARY KEY(`account_key`))", + "fields": [ + { + "fieldPath": "account_key", + "columnName": "account_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "credential_json", + "columnName": "credential_json", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "platform_type", + "columnName": "platform_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "last_active", + "columnName": "last_active", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "account_key" + ] + } + }, + { + "tableName": "DbApplication", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`host` TEXT NOT NULL, `credential_json` TEXT NOT NULL, `platform_type` TEXT NOT NULL, `has_pending_oauth_request` INTEGER NOT NULL, PRIMARY KEY(`host`))", + "fields": [ + { + "fieldPath": "host", + "columnName": "host", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "credential_json", + "columnName": "credential_json", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "platform_type", + "columnName": "platform_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "has_pending_oauth_request", + "columnName": "has_pending_oauth_request", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "host" + ] + } + }, + { + "tableName": "DbKeywordFilter", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`keyword` TEXT NOT NULL, `for_timeline` INTEGER NOT NULL, `for_notification` INTEGER NOT NULL, `for_search` INTEGER NOT NULL, `expired_at` INTEGER NOT NULL, PRIMARY KEY(`keyword`))", + "fields": [ + { + "fieldPath": "keyword", + "columnName": "keyword", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "for_timeline", + "columnName": "for_timeline", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "for_notification", + "columnName": "for_notification", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "for_search", + "columnName": "for_search", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "expired_at", + "columnName": "expired_at", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "keyword" + ] + } + }, + { + "tableName": "DbSearchHistory", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`search` TEXT NOT NULL, `created_at` INTEGER NOT NULL, PRIMARY KEY(`search`))", + "fields": [ + { + "fieldPath": "search", + "columnName": "search", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "created_at", + "columnName": "created_at", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "search" + ] + } + } + ], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'eb270b69d739601dc27be96b9c8aa192')" + ] + } +} \ No newline at end of file diff --git a/shared/schemas/dev.dimension.flare.data.database.cache.CacheDatabase/10.json b/shared/schemas/dev.dimension.flare.data.database.cache.CacheDatabase/10.json new file mode 100644 index 000000000..f621a40b2 --- /dev/null +++ b/shared/schemas/dev.dimension.flare.data.database.cache.CacheDatabase/10.json @@ -0,0 +1,267 @@ +{ + "formatVersion": 1, + "database": { + "version": 10, + "identityHash": "da001076f37d321e9fae05eaa53b8fb1", + "entities": [ + { + "tableName": "DbEmoji", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`host` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`host`))", + "fields": [ + { + "fieldPath": "host", + "columnName": "host", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "host" + ] + } + }, + { + "tableName": "status_reference", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` TEXT NOT NULL, `referenceType` TEXT NOT NULL, `statusKey` TEXT NOT NULL, `referenceStatusKey` TEXT NOT NULL, PRIMARY KEY(`_id`))", + "fields": [ + { + "fieldPath": "_id", + "columnName": "_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "referenceType", + "columnName": "referenceType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "statusKey", + "columnName": "statusKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "referenceStatusKey", + "columnName": "referenceStatusKey", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "_id" + ] + }, + "indices": [ + { + "name": "index_status_reference_referenceType_statusKey_referenceStatusKey", + "unique": true, + "columnNames": [ + "referenceType", + "statusKey", + "referenceStatusKey" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_status_reference_referenceType_statusKey_referenceStatusKey` ON `${TABLE_NAME}` (`referenceType`, `statusKey`, `referenceStatusKey`)" + } + ] + }, + { + "tableName": "DbStatus", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `statusKey` TEXT NOT NULL, `accountKey` TEXT NOT NULL, `userKey` TEXT, `platformType` TEXT NOT NULL, `content` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "statusKey", + "columnName": "statusKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "accountKey", + "columnName": "accountKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userKey", + "columnName": "userKey", + "affinity": "TEXT" + }, + { + "fieldPath": "platformType", + "columnName": "platformType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_DbStatus_statusKey_accountKey", + "unique": true, + "columnNames": [ + "statusKey", + "accountKey" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_DbStatus_statusKey_accountKey` ON `${TABLE_NAME}` (`statusKey`, `accountKey`)" + } + ] + }, + { + "tableName": "DbUser", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`userKey` TEXT NOT NULL, `platformType` TEXT NOT NULL, `name` TEXT NOT NULL, `handle` TEXT NOT NULL, `host` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`userKey`))", + "fields": [ + { + "fieldPath": "userKey", + "columnName": "userKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "platformType", + "columnName": "platformType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "handle", + "columnName": "handle", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "host", + "columnName": "host", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "userKey" + ] + }, + "indices": [ + { + "name": "index_DbUser_handle_host_platformType", + "unique": true, + "columnNames": [ + "handle", + "host", + "platformType" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_DbUser_handle_host_platformType` ON `${TABLE_NAME}` (`handle`, `host`, `platformType`)" + } + ] + }, + { + "tableName": "DbPagingTimeline", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` TEXT NOT NULL, `accountKey` TEXT NOT NULL, `pagingKey` TEXT NOT NULL, `statusKey` TEXT NOT NULL, `sortId` INTEGER NOT NULL, PRIMARY KEY(`_id`))", + "fields": [ + { + "fieldPath": "_id", + "columnName": "_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "accountKey", + "columnName": "accountKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pagingKey", + "columnName": "pagingKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "statusKey", + "columnName": "statusKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sortId", + "columnName": "sortId", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "_id" + ] + }, + "indices": [ + { + "name": "index_DbPagingTimeline_accountKey_statusKey_pagingKey", + "unique": true, + "columnNames": [ + "accountKey", + "statusKey", + "pagingKey" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_DbPagingTimeline_accountKey_statusKey_pagingKey` ON `${TABLE_NAME}` (`accountKey`, `statusKey`, `pagingKey`)" + } + ] + } + ], + "views": [ + { + "viewName": "PagingTimelineView", + "createSql": "CREATE VIEW `${VIEW_NAME}` AS SELECT\ntimeline.*,\nstatus.content AS status_content,\nuser.*,\nretweetStatus.content AS retweet_status_content,\nquoteStatus.content AS quote_status_content,\nreplyStatus.content AS reply_status_content,\nnotificationStatus.content AS notification_status_content\nFROM DbPagingTimeline AS timeline\nJOIN DbStatus status ON timeline.statusKey = status.statusKey AND timeline.accountKey = status.accountKey\nLEFT JOIN DbUser user ON status.userKey = user.userKey\nLEFT JOIN status_reference retweet ON status.statusKey = retweet.statusKey AND retweet.referenceType = 'Retweet'\nLEFT JOIN DbStatus retweetStatus ON retweet.referenceStatusKey = retweetStatus.statusKey\nLEFT JOIN status_reference reply ON status.statusKey = reply.statusKey AND reply.referenceType = 'Reply'\nLEFT JOIN DbStatus replyStatus ON reply.referenceStatusKey = replyStatus.statusKey\nLEFT JOIN status_reference quote ON status.statusKey = quote.statusKey AND quote.referenceType = 'Quote'\nLEFT JOIN DbStatus quoteStatus ON quote.referenceStatusKey = quoteStatus.statusKey\nLEFT JOIN status_reference notification ON status.statusKey = notification.statusKey AND notification.referenceType = 'Notification'\nLEFT JOIN DbStatus notificationStatus ON notification.referenceStatusKey = notificationStatus.statusKey" + } + ], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'da001076f37d321e9fae05eaa53b8fb1')" + ] + } +} \ No newline at end of file diff --git a/shared/src/androidMain/kotlin/dev/dimension/flare/data/database/DriverFactory.android.kt b/shared/src/androidMain/kotlin/dev/dimension/flare/data/database/DriverFactory.android.kt index faaa9cfa4..cd2c243d0 100644 --- a/shared/src/androidMain/kotlin/dev/dimension/flare/data/database/DriverFactory.android.kt +++ b/shared/src/androidMain/kotlin/dev/dimension/flare/data/database/DriverFactory.android.kt @@ -1,24 +1,26 @@ package dev.dimension.flare.data.database import android.content.Context -import androidx.sqlite.db.SupportSQLiteOpenHelper -import app.cash.sqldelight.db.QueryResult -import app.cash.sqldelight.db.SqlDriver -import app.cash.sqldelight.db.SqlSchema -import app.cash.sqldelight.driver.android.AndroidSqliteDriver +import androidx.room.Room +import androidx.room.RoomDatabase +import androidx.sqlite.SQLiteDriver +import androidx.sqlite.driver.AndroidSQLiteDriver internal actual class DriverFactory( private val context: Context, ) { - actual fun createDriver( - schema: SqlSchema>, - name: String, - ): SqlDriver { - SupportSQLiteOpenHelper.Configuration.builder(context) - return AndroidSqliteDriver(schema, context, name) + actual inline fun createBuilder(name: String): RoomDatabase.Builder { + val appContext = context.applicationContext + val dbFile = appContext.getDatabasePath(name) + return Room.databaseBuilder( + context = appContext, + name = dbFile.absolutePath, + ) } actual fun deleteDatabase(name: String) { context.deleteDatabase(name) } + + actual fun createSQLiteDriver(): SQLiteDriver = AndroidSQLiteDriver() } diff --git a/shared/src/appleMain/kotlin/dev/dimension/flare/data/database/DriverFactory.apple.kt b/shared/src/appleMain/kotlin/dev/dimension/flare/data/database/DriverFactory.apple.kt index ee74532d9..2725f3e31 100644 --- a/shared/src/appleMain/kotlin/dev/dimension/flare/data/database/DriverFactory.apple.kt +++ b/shared/src/appleMain/kotlin/dev/dimension/flare/data/database/DriverFactory.apple.kt @@ -1,18 +1,43 @@ package dev.dimension.flare.data.database -import app.cash.sqldelight.db.QueryResult -import app.cash.sqldelight.db.SqlDriver -import app.cash.sqldelight.db.SqlSchema -import app.cash.sqldelight.driver.native.NativeSqliteDriver -import co.touchlab.sqliter.DatabaseFileContext +import androidx.room.Room +import androidx.room.RoomDatabase +import androidx.sqlite.SQLiteDriver +import androidx.sqlite.driver.NativeSQLiteDriver +import platform.Foundation.NSApplicationSupportDirectory +import platform.Foundation.NSFileManager +import platform.Foundation.NSSearchPathForDirectoriesInDomains +import platform.Foundation.NSUserDomainMask internal actual class DriverFactory { - actual fun createDriver( - schema: SqlSchema>, - name: String, - ): SqlDriver = NativeSqliteDriver(schema, name) + actual inline fun createBuilder(name: String): RoomDatabase.Builder { + val dbFilePath = databaseDirPath() + "/$name" + return Room.databaseBuilder( + name = dbFilePath, + ) + } actual fun deleteDatabase(name: String) { - DatabaseFileContext.deleteDatabase(name, null) +// DatabaseFileContext.deleteDatabase(name, null) } + + internal fun databaseDirPath(): String = iosDirPath("databases") + + @OptIn(kotlinx.cinterop.ExperimentalForeignApi::class, kotlinx.cinterop.UnsafeNumber::class) + internal fun iosDirPath(folder: String): String { + val paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, true) + val documentsDirectory = paths[0] as String + + val databaseDirectory = "$documentsDirectory/$folder" + + val fileManager = NSFileManager.defaultManager() + + if (!fileManager.fileExistsAtPath(databaseDirectory)) { + fileManager.createDirectoryAtPath(databaseDirectory, true, null, null) + }; // Create folder + + return databaseDirectory + } + + actual fun createSQLiteDriver(): SQLiteDriver = NativeSQLiteDriver() } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/DriverFactory.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/DriverFactory.kt index 1097ecf9a..f442f2c2e 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/DriverFactory.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/DriverFactory.kt @@ -1,14 +1,12 @@ package dev.dimension.flare.data.database -import app.cash.sqldelight.db.QueryResult -import app.cash.sqldelight.db.SqlDriver -import app.cash.sqldelight.db.SqlSchema +import androidx.room.RoomDatabase +import androidx.sqlite.SQLiteDriver internal expect class DriverFactory { - fun createDriver( - schema: SqlSchema>, - name: String, - ): SqlDriver + inline fun createBuilder(name: String): RoomDatabase.Builder + + fun createSQLiteDriver(): SQLiteDriver fun deleteDatabase(name: String) } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/ProvideDatabase.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/ProvideDatabase.kt index fc444981e..d8a43e983 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/ProvideDatabase.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/ProvideDatabase.kt @@ -1,94 +1,21 @@ package dev.dimension.flare.data.database -import app.cash.sqldelight.EnumColumnAdapter -import dev.dimension.flare.data.cache.DbStatus -import dev.dimension.flare.data.cache.DbUser -import dev.dimension.flare.data.database.adapter.JsonColumnAdapter -import dev.dimension.flare.data.database.adapter.MicroblogKeyAdapter import dev.dimension.flare.data.database.app.AppDatabase -import dev.dimension.flare.data.database.app.DbAccount -import dev.dimension.flare.data.database.app.DbApplication import dev.dimension.flare.data.database.cache.CacheDatabase -import dev.dimension.flare.data.database.cache.model.EmojiContent -import dev.dimension.flare.data.database.cache.model.StatusContent -import dev.dimension.flare.data.database.cache.model.UserContent -import dev.dimension.flare.data.database.version.VersionDatabase -import dev.dimension.flare.data.version.DbVersion +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO -internal fun provideVersionDatabase(driverFactory: DriverFactory): VersionDatabase = - VersionDatabase( - driverFactory.createDriver(VersionDatabase.Schema, "version.db"), - ) +internal fun provideAppDatabase(driverFactory: DriverFactory): AppDatabase = + driverFactory + .createBuilder("app.db") + .setDriver(driverFactory.createSQLiteDriver()) + .setQueryCoroutineContext(Dispatchers.IO) + .build() -internal fun provideAppDatabase( - driverFactory: DriverFactory, - versionDatabase: VersionDatabase, -): AppDatabase { - val data = versionDatabase.versionQueries.find(0).executeAsOneOrNull() - val driver = driverFactory.createDriver(AppDatabase.Schema, "app.db") - if (data != null) { - val version = data.version - if (version != AppDatabase.Schema.version) { - AppDatabase.Schema.migrate(driver, version, AppDatabase.Schema.version) - versionDatabase.versionQueries.insert(DbVersion(0, AppDatabase.Schema.version)) - } - } else { - AppDatabase.Schema.create(driver) - versionDatabase.versionQueries.insert(DbVersion(0, AppDatabase.Schema.version)) - } - return AppDatabase( - driver, - DbAccountAdapter = - DbAccount.Adapter( - account_keyAdapter = MicroblogKeyAdapter(), - platform_typeAdapter = EnumColumnAdapter(), - ), - DbApplicationAdapter = - DbApplication.Adapter( - platform_typeAdapter = EnumColumnAdapter(), - ), - ) -} - -internal fun provideCacheDatabase( - driverFactory: DriverFactory, - versionDb: VersionDatabase, -): CacheDatabase { - val data = versionDb.versionQueries.find(1).executeAsOneOrNull() - val version = data?.version - val shouldRecreateDatabase = version != null && version != CacheDatabase.Schema.version - if (shouldRecreateDatabase) { - driverFactory.deleteDatabase("cache.db") - } - val driver = driverFactory.createDriver(CacheDatabase.Schema, "cache.db") - if (data == null || shouldRecreateDatabase) { - CacheDatabase.Schema.create(driver) - versionDb.versionQueries.insert(DbVersion(1, CacheDatabase.Schema.version)) - } - return CacheDatabase( - driver, - DbStatusAdapter = - DbStatus.Adapter( - status_keyAdapter = MicroblogKeyAdapter(), - platform_typeAdapter = EnumColumnAdapter(), - account_keyAdapter = MicroblogKeyAdapter(), - user_keyAdapter = MicroblogKeyAdapter(), - contentAdapter = JsonColumnAdapter(StatusContent.serializer()), - ), - DbUserAdapter = - DbUser.Adapter( - user_keyAdapter = MicroblogKeyAdapter(), - platform_typeAdapter = EnumColumnAdapter(), - contentAdapter = JsonColumnAdapter(UserContent.serializer()), - ), - DbPagingTimelineAdapter = - dev.dimension.flare.data.cache.DbPagingTimeline.Adapter( - account_keyAdapter = MicroblogKeyAdapter(), - status_keyAdapter = MicroblogKeyAdapter(), - ), - DbEmojiAdapter = - dev.dimension.flare.data.cache.DbEmoji.Adapter( - contentAdapter = JsonColumnAdapter(EmojiContent.serializer()), - ), - ) -} +internal fun provideCacheDatabase(driverFactory: DriverFactory): CacheDatabase = + driverFactory + .createBuilder("cache.db") + .fallbackToDestructiveMigration(dropAllTables = true) + .setDriver(driverFactory.createSQLiteDriver()) + .setQueryCoroutineContext(Dispatchers.IO) + .build() diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/adapter/JsonColumnAdapter.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/adapter/JsonColumnAdapter.kt deleted file mode 100644 index 3edf23e4f..000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/adapter/JsonColumnAdapter.kt +++ /dev/null @@ -1,17 +0,0 @@ -package dev.dimension.flare.data.database.adapter - -import app.cash.sqldelight.ColumnAdapter -import dev.dimension.flare.common.decodeJson -import dev.dimension.flare.common.encodeJson -import kotlinx.serialization.KSerializer -import kotlin.io.encoding.Base64 -import kotlin.io.encoding.ExperimentalEncodingApi - -@OptIn(ExperimentalEncodingApi::class) -internal class JsonColumnAdapter( - private val serializer: KSerializer, -) : ColumnAdapter { - override fun decode(databaseValue: String): T = Base64.decode(databaseValue).decodeToString().decodeJson(serializer) - - override fun encode(value: T) = Base64.encode(value.encodeJson(serializer).encodeToByteArray()) -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/adapter/MicroBlogKeyConverter.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/adapter/MicroBlogKeyConverter.kt new file mode 100644 index 000000000..6067d3fba --- /dev/null +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/adapter/MicroBlogKeyConverter.kt @@ -0,0 +1,11 @@ +package dev.dimension.flare.data.database.adapter + +import dev.dimension.flare.model.MicroBlogKey + +class MicroBlogKeyConverter { + @androidx.room.TypeConverter + fun fromString(value: String): MicroBlogKey = MicroBlogKey.valueOf(value) + + @androidx.room.TypeConverter + fun fromEnum(value: MicroBlogKey): String = value.toString() +} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/adapter/MicroblogKeyAdapter.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/adapter/MicroblogKeyAdapter.kt deleted file mode 100644 index e39765fe9..000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/adapter/MicroblogKeyAdapter.kt +++ /dev/null @@ -1,10 +0,0 @@ -package dev.dimension.flare.data.database.adapter - -import app.cash.sqldelight.ColumnAdapter -import dev.dimension.flare.model.MicroBlogKey - -internal class MicroblogKeyAdapter : ColumnAdapter { - override fun decode(databaseValue: String): MicroBlogKey = MicroBlogKey.valueOf(databaseValue) - - override fun encode(value: MicroBlogKey): String = value.toString() -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/adapter/PlatformTypeConverter.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/adapter/PlatformTypeConverter.kt new file mode 100644 index 000000000..bfa60b6c5 --- /dev/null +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/adapter/PlatformTypeConverter.kt @@ -0,0 +1,11 @@ +package dev.dimension.flare.data.database.adapter + +import dev.dimension.flare.model.PlatformType + +class PlatformTypeConverter { + @androidx.room.TypeConverter + fun fromString(value: String): PlatformType = PlatformType.valueOf(value) + + @androidx.room.TypeConverter + fun fromEnum(value: PlatformType): String = value.name +} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/AppDatabase.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/AppDatabase.kt new file mode 100644 index 000000000..e10d86201 --- /dev/null +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/AppDatabase.kt @@ -0,0 +1,41 @@ +package dev.dimension.flare.data.database.app + +import androidx.room.ConstructedBy +import androidx.room.Database +import androidx.room.RoomDatabase +import androidx.room.RoomDatabaseConstructor +import androidx.room.TypeConverters +import dev.dimension.flare.data.database.app.dao.AccountDao +import dev.dimension.flare.data.database.app.dao.ApplicationDao +import dev.dimension.flare.data.database.app.dao.KeywordFilterDao +import dev.dimension.flare.data.database.app.dao.SearchHistoryDao + +@Database( + entities = [ + dev.dimension.flare.data.database.app.model.DbAccount::class, + dev.dimension.flare.data.database.app.model.DbApplication::class, + dev.dimension.flare.data.database.app.model.DbKeywordFilter::class, + dev.dimension.flare.data.database.app.model.DbSearchHistory::class, + ], + version = 3, +) +@TypeConverters( + dev.dimension.flare.data.database.adapter.MicroBlogKeyConverter::class, + dev.dimension.flare.data.database.adapter.PlatformTypeConverter::class, +) +@ConstructedBy(AppDatabaseConstructor::class) +abstract class AppDatabase : RoomDatabase() { + abstract fun accountDao(): AccountDao + + abstract fun applicationDao(): ApplicationDao + + abstract fun keywordFilterDao(): KeywordFilterDao + + abstract fun searchHistoryDao(): SearchHistoryDao +} + +// The Room compiler generates the `actual` implementations. +@Suppress("NO_ACTUAL_FOR_EXPECT") +expect object AppDatabaseConstructor : RoomDatabaseConstructor { + override fun initialize(): AppDatabase +} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/dao/AccountDao.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/dao/AccountDao.kt new file mode 100644 index 000000000..a0b912562 --- /dev/null +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/dao/AccountDao.kt @@ -0,0 +1,39 @@ +package dev.dimension.flare.data.database.app.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import dev.dimension.flare.data.database.app.model.DbAccount +import dev.dimension.flare.model.MicroBlogKey +import kotlinx.coroutines.flow.Flow + +@Dao +interface AccountDao { + @Query("SELECT * FROM DbAccount ORDER BY last_active DESC LIMIT 1") + fun activeAccount(): Flow + + @Query("SELECT * FROM DbAccount") + fun allAccounts(): Flow> + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insert(account: DbAccount) + + @Query("UPDATE DbAccount SET last_active = :lastActive WHERE account_key = :accountKey") + suspend fun setLastActive( + accountKey: MicroBlogKey, + lastActive: Long, + ) + + @Query("SELECT * FROM DbAccount WHERE account_key = :accountKey") + fun get(accountKey: MicroBlogKey): Flow + + @Query("DELETE FROM DbAccount WHERE account_key = :accountKey") + suspend fun delete(accountKey: MicroBlogKey) + + @Query("UPDATE DbAccount SET credential_json = :credentialJson WHERE account_key = :accountKey") + suspend fun setCredential( + accountKey: MicroBlogKey, + credentialJson: String, + ) +} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/dao/ApplicationDao.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/dao/ApplicationDao.kt new file mode 100644 index 000000000..2a97c234e --- /dev/null +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/dao/ApplicationDao.kt @@ -0,0 +1,43 @@ +package dev.dimension.flare.data.database.app.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import dev.dimension.flare.data.database.app.model.DbApplication +import dev.dimension.flare.model.PlatformType +import kotlinx.coroutines.flow.Flow + +@Dao +interface ApplicationDao { + @Query("SELECT * FROM DbApplication") + fun allApplication(): Flow> + + @Query("SELECT * FROM DbApplication WHERE host = :host") + fun get(host: String): Flow + + @Query("SELECT * FROM DbApplication WHERE has_pending_oauth_request = 1") + fun getPending(): Flow> + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insert(application: DbApplication) + + @Query("UPDATE DbApplication SET credential_json = :credentialJson, platform_type = :platformType WHERE host = :host") + suspend fun update( + host: String, + credentialJson: String, + platformType: PlatformType, + ) + + @Query("UPDATE DbApplication SET has_pending_oauth_request = :hasPendingOauthRequest WHERE host = :host") + suspend fun updatePending( + host: String, + hasPendingOauthRequest: Long, + ) + + @Query("DELETE FROM DbApplication WHERE host = :host") + suspend fun delete(host: String) + + @Query("UPDATE DbApplication SET has_pending_oauth_request = 0 WHERE has_pending_oauth_request = 1") + suspend fun clearPending() +} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/dao/KeywordFilterDao.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/dao/KeywordFilterDao.kt new file mode 100644 index 000000000..ec76e557c --- /dev/null +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/dao/KeywordFilterDao.kt @@ -0,0 +1,50 @@ +package dev.dimension.flare.data.database.app.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import dev.dimension.flare.data.database.app.model.DbKeywordFilter +import kotlinx.coroutines.flow.Flow + +@Dao +interface KeywordFilterDao { + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insert(keywordFilter: DbKeywordFilter) + + @Query("SELECT * FROM DbKeywordFilter") + fun selectAll(): Flow> + + @Query("SELECT * FROM DbKeywordFilter WHERE expired_at = 0 OR expired_at > :currentTime") + fun selectAllNotExpired(currentTime: Long): Flow> + + @Query( + "SELECT * FROM DbKeywordFilter WHERE (expired_at = 0 OR expired_at > :currentTime) AND (for_timeline = :forTimeline OR for_notification = :forNotification OR for_search = :forSearch)", + ) + fun selectNotExpiredFor( + currentTime: Long, + forTimeline: Long, + forNotification: Long, + forSearch: Long, + ): Flow> + + @Query("SELECT * FROM DbKeywordFilter WHERE keyword = :keyword") + fun selectByKeyword(keyword: String): Flow + + @Query("DELETE FROM DbKeywordFilter WHERE keyword = :keyword") + suspend fun deleteByKeyword(keyword: String) + + @Query("DELETE FROM DbKeywordFilter") + suspend fun deleteAll() + + @Query( + "UPDATE DbKeywordFilter SET for_timeline = :forTimeline, for_notification = :forNotification, for_search = :forSearch, expired_at = :expiredAt WHERE keyword = :keyword", + ) + suspend fun update( + keyword: String, + forTimeline: Long, + forNotification: Long, + forSearch: Long, + expiredAt: Long, + ) +} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/dao/SearchHistoryDao.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/dao/SearchHistoryDao.kt new file mode 100644 index 000000000..292bf0346 --- /dev/null +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/dao/SearchHistoryDao.kt @@ -0,0 +1,23 @@ +package dev.dimension.flare.data.database.app.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import dev.dimension.flare.data.database.app.model.DbSearchHistory +import kotlinx.coroutines.flow.Flow + +@Dao +interface SearchHistoryDao { + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insert(searchHistory: DbSearchHistory) + + @Query("SELECT * FROM DbSearchHistory ORDER BY created_at DESC") + fun select(): Flow> + + @Query("DELETE FROM DbSearchHistory WHERE search = :search") + suspend fun delete(search: String) + + @Query("DELETE FROM DbSearchHistory") + suspend fun deleteAll() +} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/DbAccount.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/DbAccount.kt new file mode 100644 index 000000000..59c49144f --- /dev/null +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/DbAccount.kt @@ -0,0 +1,14 @@ +package dev.dimension.flare.data.database.app.model + +import androidx.room.Entity +import androidx.room.PrimaryKey +import dev.dimension.flare.model.MicroBlogKey +import dev.dimension.flare.model.PlatformType + +@Entity +data class DbAccount( + @PrimaryKey val account_key: MicroBlogKey, + val credential_json: String, + val platform_type: PlatformType, + val last_active: Long, +) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/DbApplication.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/DbApplication.kt new file mode 100644 index 000000000..aa133e413 --- /dev/null +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/DbApplication.kt @@ -0,0 +1,13 @@ +package dev.dimension.flare.data.database.app.model + +import androidx.room.Entity +import androidx.room.PrimaryKey +import dev.dimension.flare.model.PlatformType + +@Entity +data class DbApplication( + @PrimaryKey val host: String, + val credential_json: String, + val platform_type: PlatformType, + val has_pending_oauth_request: Int = 0, +) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/DbKeywordFilter.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/DbKeywordFilter.kt new file mode 100644 index 000000000..db3a70b90 --- /dev/null +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/DbKeywordFilter.kt @@ -0,0 +1,14 @@ +package dev.dimension.flare.data.database.app.model + +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity +data class DbKeywordFilter( + @PrimaryKey + val keyword: String, + val for_timeline: Long, + val for_notification: Long, + val for_search: Long, + val expired_at: Long, +) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/DbSearchHistory.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/DbSearchHistory.kt new file mode 100644 index 000000000..fd80bce91 --- /dev/null +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/DbSearchHistory.kt @@ -0,0 +1,11 @@ +package dev.dimension.flare.data.database.app.model + +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity +data class DbSearchHistory( + @PrimaryKey + val search: String, + val created_at: Long, +) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/CacaheDatabase.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/CacaheDatabase.kt new file mode 100644 index 000000000..b961f2ddb --- /dev/null +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/CacaheDatabase.kt @@ -0,0 +1,46 @@ +package dev.dimension.flare.data.database.cache + +import androidx.room.ConstructedBy +import androidx.room.Database +import androidx.room.RoomDatabase +import androidx.room.RoomDatabaseConstructor +import androidx.room.TypeConverters + +@Database( + entities = [ + dev.dimension.flare.data.database.cache.model.DbEmoji::class, + dev.dimension.flare.data.database.cache.model.DbStatusReference::class, + dev.dimension.flare.data.database.cache.model.DbStatus::class, + dev.dimension.flare.data.database.cache.model.DbUser::class, + dev.dimension.flare.data.database.cache.model.DbPagingTimeline::class, + ], + views = [ + dev.dimension.flare.data.database.cache.model.DbPagingTimelineView::class, + ], + version = 10, +) +@TypeConverters( + dev.dimension.flare.data.database.adapter.MicroBlogKeyConverter::class, + dev.dimension.flare.data.database.adapter.PlatformTypeConverter::class, + dev.dimension.flare.data.database.cache.model.EmojiContentConverter::class, + dev.dimension.flare.data.database.cache.model.StatusConverter::class, + dev.dimension.flare.data.database.cache.model.UserContentConverters::class, +) +@ConstructedBy(CacheDatabaseConstructor::class) +abstract class CacheDatabase : RoomDatabase() { + abstract fun emojiDao(): dev.dimension.flare.data.database.cache.dao.EmojiDao + + abstract fun statusReferenceDao(): dev.dimension.flare.data.database.cache.dao.StatusReferenceDao + + abstract fun statusDao(): dev.dimension.flare.data.database.cache.dao.StatusDao + + abstract fun userDao(): dev.dimension.flare.data.database.cache.dao.UserDao + + abstract fun pagingTimelineDao(): dev.dimension.flare.data.database.cache.dao.PagingTimelineDao +} + +// The Room compiler generates the `actual` implementations. +@Suppress("NO_ACTUAL_FOR_EXPECT") +expect object CacheDatabaseConstructor : RoomDatabaseConstructor { + override fun initialize(): CacheDatabase +} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/EmojiDao.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/EmojiDao.kt new file mode 100644 index 000000000..f19638b82 --- /dev/null +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/EmojiDao.kt @@ -0,0 +1,20 @@ +package dev.dimension.flare.data.database.cache.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import dev.dimension.flare.data.database.cache.model.DbEmoji +import kotlinx.coroutines.flow.Flow + +@Dao +interface EmojiDao { + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insert(emoji: DbEmoji) + + @Query("SELECT * FROM DbEmoji WHERE host = :host") + fun get(host: String): Flow + + @Query("DELETE FROM DbEmoji") + suspend fun clear() +} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/PagingTimelineDao.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/PagingTimelineDao.kt new file mode 100644 index 000000000..f3c9aaf74 --- /dev/null +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/PagingTimelineDao.kt @@ -0,0 +1,60 @@ +package dev.dimension.flare.data.database.cache.dao + +import androidx.paging.PagingSource +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import dev.dimension.flare.data.database.cache.model.DbPagingTimeline +import dev.dimension.flare.data.database.cache.model.DbPagingTimelineView +import dev.dimension.flare.model.MicroBlogKey + +@Dao +interface PagingTimelineDao { +// @Transaction +// @Query("SELECT * FROM DbPagingTimeline WHERE pagingKey = :pagingKey AND accountKey = :accountKey ORDER BY sortId DESC") +// fun getPagingSource( +// pagingKey: String, +// accountKey: MicroBlogKey, +// ): PagingSource + + @Query("SELECT * FROM PagingTimelineView WHERE pagingKey = :pagingKey AND accountKey = :accountKey ORDER BY sortId DESC") + fun getDbPagingTimelineView( + pagingKey: String, + accountKey: MicroBlogKey, + ): PagingSource + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertAll(timeline: List) + + @Query("SELECT EXISTS(SELECT * FROM DbPagingTimeline WHERE pagingKey = :pagingKey AND accountKey = :accountKey)") + suspend fun exists( + pagingKey: String, + accountKey: MicroBlogKey, + ): Boolean + + @Delete + suspend fun delete(timeline: List) + + @Query("DELETE FROM DbPagingTimeline WHERE pagingKey = :pagingKey AND accountKey = :accountKey") + suspend fun delete( + pagingKey: String, + accountKey: MicroBlogKey, + ) + + @Query("DELETE FROM DbPagingTimeline WHERE accountKey = :accountKey AND statusKey = :statusKey") + suspend fun deleteStatus( + accountKey: MicroBlogKey, + statusKey: MicroBlogKey, + ) + + @Query("SELECT EXISTS(SELECT 1 FROM DbPagingTimeline WHERE accountKey = :account_key AND pagingKey = :paging_key)") + suspend fun existsPaging( + account_key: MicroBlogKey, + paging_key: String, + ): Boolean + + @Query("DELETE FROM DbPagingTimeline") + suspend fun clear() +} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/StatusDao.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/StatusDao.kt new file mode 100644 index 000000000..6080f8a41 --- /dev/null +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/StatusDao.kt @@ -0,0 +1,44 @@ +package dev.dimension.flare.data.database.cache.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import dev.dimension.flare.data.database.cache.model.DbStatus +import dev.dimension.flare.data.database.cache.model.StatusContent +import dev.dimension.flare.model.MicroBlogKey +import kotlinx.coroutines.flow.Flow + +@Dao +interface StatusDao { + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insert(status: DbStatus) + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertAll(statuses: List) + + @Query("SELECT * FROM DbStatus WHERE statusKey = :statusKey AND accountKey = :accountKey") + fun get( + statusKey: MicroBlogKey, + accountKey: MicroBlogKey, + ): Flow + + @Query("UPDATE DbStatus SET content = :content WHERE statusKey = :statusKey AND accountKey = :accountKey") + suspend fun update( + statusKey: MicroBlogKey, + accountKey: MicroBlogKey, + content: StatusContent, + ) + + @Query("DELETE FROM DbStatus WHERE statusKey = :statusKey AND accountKey = :accountKey") + suspend fun delete( + statusKey: MicroBlogKey, + accountKey: MicroBlogKey, + ) + + @Query("SELECT COUNT(*) FROM DbStatus") + fun count(): Flow + + @Query("DELETE FROM DbStatus") + suspend fun clear() +} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/StatusReferenceDao.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/StatusReferenceDao.kt new file mode 100644 index 000000000..12c51210e --- /dev/null +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/StatusReferenceDao.kt @@ -0,0 +1,17 @@ +package dev.dimension.flare.data.database.cache.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import dev.dimension.flare.data.database.cache.model.DbStatusReference +import dev.dimension.flare.model.MicroBlogKey + +@Dao +interface StatusReferenceDao { + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertAll(items: List) + + @Query("DELETE FROM status_reference WHERE statusKey = :key") + suspend fun delete(key: MicroBlogKey) +} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/UserDao.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/UserDao.kt new file mode 100644 index 000000000..b1953a7b3 --- /dev/null +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/UserDao.kt @@ -0,0 +1,45 @@ +package dev.dimension.flare.data.database.cache.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import dev.dimension.flare.data.database.cache.model.DbUser +import dev.dimension.flare.data.database.cache.model.UserContent +import dev.dimension.flare.model.MicroBlogKey +import dev.dimension.flare.model.PlatformType +import kotlinx.coroutines.flow.Flow + +@Dao +interface UserDao { + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insert(user: DbUser) + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertAll(users: List) + + @Query("UPDATE DbUser SET content = :content WHERE userKey = :userKey") + suspend fun update( + userKey: MicroBlogKey, + content: UserContent, + ) + + @Query("SELECT * FROM DbUser WHERE userKey IN (:userKeys)") + fun findByKeys(userKeys: List): Flow> + + @Query("SELECT * FROM DbUser WHERE userKey = :userKey") + fun findByKey(userKey: MicroBlogKey): Flow + + @Query("SELECT * FROM DbUser WHERE handle = :handle AND host = :host AND platformType = :platformType") + fun findByHandleAndHost( + handle: String, + host: String, + platformType: PlatformType, + ): Flow + + @Query("SELECT COUNT(*) FROM DbUser") + fun count(): Flow + + @Query("DELETE FROM DbUser") + suspend fun clear() +} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/mapper/Bluesky.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/mapper/Bluesky.kt index 6e0f06581..478b9dd1a 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/mapper/Bluesky.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/mapper/Bluesky.kt @@ -1,3 +1,4 @@ + package dev.dimension.flare.data.database.cache.mapper import app.bsky.actor.ProfileView @@ -6,18 +7,22 @@ import app.bsky.actor.ProfileViewDetailed import app.bsky.feed.FeedViewPost import app.bsky.feed.FeedViewPostReasonUnion import app.bsky.feed.PostView +import app.bsky.feed.ReplyRefParentUnion import app.bsky.notification.ListNotificationsNotification -import dev.dimension.flare.data.cache.DbPagingTimeline -import dev.dimension.flare.data.cache.DbStatus -import dev.dimension.flare.data.cache.DbUser import dev.dimension.flare.data.database.cache.CacheDatabase +import dev.dimension.flare.data.database.cache.model.DbPagingTimelineWithStatus +import dev.dimension.flare.data.database.cache.model.DbStatus +import dev.dimension.flare.data.database.cache.model.DbStatusWithUser +import dev.dimension.flare.data.database.cache.model.DbUser import dev.dimension.flare.data.database.cache.model.StatusContent import dev.dimension.flare.data.database.cache.model.UserContent import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.model.PlatformType +import dev.dimension.flare.model.ReferenceType +import kotlinx.coroutines.flow.firstOrNull -object Bluesky { - fun saveFeed( +internal object Bluesky { + suspend fun saveFeed( accountKey: MicroBlogKey, pagingKey: String, database: CacheDatabase, @@ -31,83 +36,62 @@ object Bluesky { } }, ) { - val timeline = data.map { it.toDbPagingTimeline(accountKey, pagingKey, sortIdProvider) } - val status = data.map { it.toDbStatus(accountKey) } - val user = data.map { it.post.author.toDbUser(accountKey.host) } - save(database, timeline, status, user) + save(database, data.toDbPagingTimeline(accountKey, pagingKey, sortIdProvider)) } - fun saveNotification( + suspend fun saveNotification( accountKey: MicroBlogKey, pagingKey: String, database: CacheDatabase, data: List, ) { - val timeline = data.map { it.toDbPagingTimeline(accountKey, pagingKey) } - val status = data.map { it.toDbStatus(accountKey) } - val user = data.map { it.author.toDbUser(accountKey.host) } - save(database, timeline, status, user) + save(database, data.toDb(accountKey, pagingKey)) } - fun savePost( + suspend fun savePost( accountKey: MicroBlogKey, pagingKey: String, database: CacheDatabase, data: List, sortIdProvider: (PostView) -> Long = { it.indexedAt.toEpochMilliseconds() }, ) { - val timeline = data.map { it.toDbPagingTimeline(accountKey, pagingKey, sortIdProvider) } - val status = data.map { it.toDbStatus(accountKey) } - val user = data.map { it.author.toDbUser(accountKey.host) } - save(database, timeline, status, user) + save(database, data.toDb(accountKey, pagingKey, sortIdProvider)) } - private fun save( + private suspend fun save( database: CacheDatabase, - timeline: List, - status: List, - user: List, + timeline: List, ) { - database.transaction { - timeline.forEach { - database.dbPagingTimelineQueries.insert( - account_key = it.account_key, - status_key = it.status_key, - paging_key = it.paging_key, - sort_id = it.sort_id, - ) - } - status.forEach { - database.dbStatusQueries.insert( - status_key = it.status_key, - platform_type = it.platform_type, - user_key = it.user_key, - content = it.content, - account_key = it.account_key, - ) - } - + ( + timeline.mapNotNull { it.status.status.user } + + timeline + .flatMap { it.status.references } + .mapNotNull { it.status.user } + ).let { allUsers -> val exsitingUsers = - database.dbUserQueries - .findByKeys(user.map { it.user_key }) - .executeAsList() + database + .userDao() + .findByKeys(allUsers.map { it.userKey }) + .firstOrNull() + .orEmpty() .filter { it.content is UserContent.Bluesky }.map { val content = it.content as UserContent.Bluesky - val item = - user.find { user -> - user.user_key == it.user_key + val user = + allUsers.find { user -> + user.userKey == it.userKey } - if (item != null && item.content is UserContent.BlueskyLite) { + + if (user != null && user.content is UserContent.BlueskyLite) { it.copy( content = content.copy( data = content.data.copy( - displayName = item.content.data.displayName, - handle = item.content.data.handle, - avatar = item.content.data.avatar, + handle = user.content.data.handle, + displayName = user.content.data.displayName, + avatar = user.content.data.avatar, ), ), ) @@ -115,141 +99,176 @@ object Bluesky { it } } - val result = (exsitingUsers + user).distinctBy { it.user_key } - result.forEach { - database.dbUserQueries.insert( - user_key = it.user_key, - platform_type = it.platform_type, - name = it.name, - handle = it.handle, - content = it.content, - host = it.host, - ) - } + + val result = (exsitingUsers + allUsers).distinctBy { it.userKey } + database.userDao().insertAll(result) + } + ( + timeline.map { it.status.status.data } + + timeline + .flatMap { it.status.references } + .map { it.status.data } + ).let { + database.statusDao().insertAll(it) } + timeline.flatMap { it.status.references }.map { it.reference }.let { + database.statusReferenceDao().insertAll(it) + } + database.pagingTimelineDao().insertAll(timeline.map { it.timeline }) } } -fun PostView.toDbPagingTimeline( +private fun List.toDb( accountKey: MicroBlogKey, pagingKey: String, - sortIdProvider: (PostView) -> Long = { it.indexedAt.toEpochMilliseconds() }, -): DbPagingTimeline { - val sortId = sortIdProvider(this) - val status = this.toDbStatus(accountKey) - return DbPagingTimeline( - id = 0, - account_key = accountKey, - status_key = status.status_key, - paging_key = pagingKey, - sort_id = sortId, - ) -} + sortIdProvider: (PostView) -> Long, +): List = + this.map { + createDbPagingTimelineWithStatus( + accountKey = accountKey, + pagingKey = pagingKey, + sortId = sortIdProvider(it), + status = it.toDbStatusWithUser(accountKey), + references = mapOf(), + ) + } -fun ListNotificationsNotification.toDbPagingTimeline( +internal fun List.toDb( accountKey: MicroBlogKey, pagingKey: String, -): DbPagingTimeline { - val sortId = this.indexedAt.toEpochMilliseconds() +): List = + this.map { + createDbPagingTimelineWithStatus( + accountKey = accountKey, + pagingKey = pagingKey, + sortId = it.indexedAt.toEpochMilliseconds(), + status = it.toDbStatusWithUser(accountKey), + references = mapOf(), + ) + } + +private fun ListNotificationsNotification.toDbStatusWithUser(accountKey: MicroBlogKey): DbStatusWithUser { + val user = this.author.toDbUser(accountKey.host) val status = this.toDbStatus(accountKey) - return DbPagingTimeline( - id = 0, - account_key = accountKey, - status_key = status.status_key, - paging_key = pagingKey, - sort_id = sortId, + return DbStatusWithUser( + data = status, + user = user, ) } private fun ListNotificationsNotification.toDbStatus(accountKey: MicroBlogKey): DbStatus { val user = this.author.toDbUser(accountKey.host) return DbStatus( - status_key = + statusKey = MicroBlogKey( - uri.atUri + "_" + user.user_key, + uri.atUri + "_" + user.userKey, accountKey.host, ), - platform_type = PlatformType.Bluesky, - user_key = user.user_key, + platformType = PlatformType.Bluesky, + userKey = user.userKey, content = StatusContent.BlueskyNotification(this), - account_key = accountKey, - id = 0, + accountKey = accountKey, ) } -fun FeedViewPost.toDbPagingTimeline( +internal fun List.toDbPagingTimeline( accountKey: MicroBlogKey, pagingKey: String, - sortIdProvider: (FeedViewPost) -> Long = { - val data = it.reason - if (data is FeedViewPostReasonUnion.ReasonRepost) { - data.value.indexedAt.toEpochMilliseconds() - } else { - it.post.indexedAt.toEpochMilliseconds() - } - }, -): DbPagingTimeline { - val sortId = sortIdProvider(this) - val status = this.toDbStatus(accountKey) - return DbPagingTimeline( - id = 0, - account_key = accountKey, - status_key = status.status_key, - paging_key = pagingKey, - sort_id = sortId, - ) -} - -fun FeedViewPost.toDbStatus(accountKey: MicroBlogKey): DbStatus { - when (val data = reason) { - is FeedViewPostReasonUnion.ReasonRepost -> { - val user = data.value.by.toDbUser(accountKey.host) - return DbStatus( - status_key = - MicroBlogKey( - post.uri.atUri + "_reblog_${user.user_key}", - accountKey.host, - ), - platform_type = PlatformType.Bluesky, - user_key = user.user_key, - content = StatusContent.Bluesky(post, data), - account_key = accountKey, - id = 0, - ) - } - else -> { - // bluesky doesn't have "quote" and "retweet" as the same as the other platforms - return with(post) { - toDbStatus(accountKey) + sortIdProvider: (FeedViewPost) -> Long, +): List = + this.map { + val reply = + when (val reply = it.reply?.parent) { + is ReplyRefParentUnion.PostView -> reply.value.toDbStatusWithUser(accountKey) + else -> null } - } + val status = + when (val data = it.reason) { + is FeedViewPostReasonUnion.ReasonRepost -> { + val user = data.value.by.toDbUser(accountKey.host) + val reasonStatus = + DbStatusWithUser( + user = user, + data = + DbStatus( + statusKey = + MicroBlogKey( + it.post.uri.atUri + "_reblog_${user.userKey}", + accountKey.host, + ), + platformType = PlatformType.Bluesky, + userKey = + data.value.by + .toDbUser(accountKey.host) + .userKey, + content = StatusContent.BlueskyReason(data, it.post), + accountKey = accountKey, + ), + ) + reasonStatus + } + else -> { + // bluesky doesn't have "quote" and "retweet" as the same as the other platforms + it.post.toDbStatusWithUser(accountKey) + } + } + val references = + when (val data = it.reason) { + is FeedViewPostReasonUnion.ReasonRepost -> + listOfNotNull( + if (reply != null) { + ReferenceType.Reply to reply + } else { + null + }, + ReferenceType.Retweet to status, + ).toMap() + else -> + listOfNotNull( + if (reply != null) { + ReferenceType.Reply to reply + } else { + null + }, + ).toMap() + } + createDbPagingTimelineWithStatus( + accountKey = accountKey, + pagingKey = pagingKey, + sortId = sortIdProvider(it), + status = status, + references = references, + ) } -} -private fun PostView.toDbStatus(accountKey: MicroBlogKey): DbStatus { +private fun PostView.toDbStatusWithUser(accountKey: MicroBlogKey): DbStatusWithUser { val user = author.toDbUser(accountKey.host) - return DbStatus( - status_key = - MicroBlogKey( - uri.atUri, - host = user.user_key.host, - ), - platform_type = PlatformType.Bluesky, - content = StatusContent.Bluesky(this, null), - user_key = user.user_key, - account_key = accountKey, - id = 0, + val status = + DbStatus( + statusKey = + MicroBlogKey( + uri.atUri, + host = user.userKey.host, + ), + platformType = PlatformType.Bluesky, + content = StatusContent.Bluesky(this), + userKey = user.userKey, + accountKey = accountKey, + ) + return DbStatusWithUser( + data = status, + user = user, ) } private fun ProfileView.toDbUser(host: String) = DbUser( - user_key = + userKey = MicroBlogKey( id = did.did, host = host, ), - platform_type = PlatformType.Bluesky, + platformType = PlatformType.Bluesky, name = displayName.orEmpty(), handle = handle.handle, host = host, @@ -266,26 +285,26 @@ private fun ProfileView.toDbUser(host: String) = private fun ProfileViewBasic.toDbUser(host: String) = DbUser( - user_key = + userKey = MicroBlogKey( id = did.did, host = host, ), - platform_type = PlatformType.Bluesky, + platformType = PlatformType.Bluesky, name = displayName.orEmpty(), handle = handle.handle, host = host, content = UserContent.BlueskyLite(this), ) -fun ProfileViewDetailed.toDbUser(host: String) = +internal fun ProfileViewDetailed.toDbUser(host: String) = DbUser( - user_key = + userKey = MicroBlogKey( id = did.did, host = host, ), - platform_type = PlatformType.Bluesky, + platformType = PlatformType.Bluesky, name = displayName.orEmpty(), handle = handle.handle, host = host, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/mapper/Mastodon.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/mapper/Mastodon.kt index b5ebb15fb..400083278 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/mapper/Mastodon.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/mapper/Mastodon.kt @@ -1,11 +1,11 @@ package dev.dimension.flare.data.database.cache.mapper -import dev.dimension.flare.data.cache.DbEmoji -import dev.dimension.flare.data.cache.DbPagingTimeline -import dev.dimension.flare.data.cache.DbStatus -import dev.dimension.flare.data.cache.DbUser import dev.dimension.flare.data.database.cache.CacheDatabase -import dev.dimension.flare.data.database.cache.model.EmojiContent +import dev.dimension.flare.data.database.cache.model.DbEmoji +import dev.dimension.flare.data.database.cache.model.DbPagingTimelineWithStatus +import dev.dimension.flare.data.database.cache.model.DbStatus +import dev.dimension.flare.data.database.cache.model.DbStatusWithUser +import dev.dimension.flare.data.database.cache.model.DbUser import dev.dimension.flare.data.database.cache.model.StatusContent import dev.dimension.flare.data.network.mastodon.api.model.Account import dev.dimension.flare.data.network.mastodon.api.model.Emoji @@ -13,172 +13,119 @@ import dev.dimension.flare.data.network.mastodon.api.model.Notification import dev.dimension.flare.data.network.mastodon.api.model.Status import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.model.PlatformType +import dev.dimension.flare.model.ReferenceType internal object Mastodon { - fun save( + suspend fun save( accountKey: MicroBlogKey, pagingKey: String, database: CacheDatabase, data: List, sortIdProvider: (Status) -> Long = { it.createdAt?.toEpochMilliseconds() ?: 0 }, ) { - val timeline = data.map { it.toDbPagingTimeline(accountKey, pagingKey, sortIdProvider) } - val status = - data.flatMap { - listOfNotNull( - it.toDbStatus(accountKey), - it.reblog?.toDbStatus(accountKey), - ) - } - val user = data.mapNotNull { it.account?.toDbUser(accountKey.host) } - database.transaction { - timeline.forEach { - database.dbPagingTimelineQueries.insert( - account_key = it.account_key, - status_key = it.status_key, - paging_key = it.paging_key, - sort_id = it.sort_id, - ) - } - status.forEach { - database.dbStatusQueries.insert( - status_key = it.status_key, - platform_type = it.platform_type, - user_key = it.user_key, - content = it.content, - account_key = it.account_key, - ) - } - user.forEach { - database.dbUserQueries.insert( - user_key = it.user_key, - platform_type = it.platform_type, - name = it.name, - handle = it.handle, - content = it.content, - host = it.host, - ) - } - } + val items = data.toDbPagingTimeline(accountKey, pagingKey, sortIdProvider) + saveToDatabase(database, items) } - fun save( + suspend fun save( accountKey: MicroBlogKey, pagingKey: String, database: CacheDatabase, data: List, ) { - val timeline = data.map { it.toDbPagingTimeline(accountKey, pagingKey) } - val status = - data.flatMap { - listOfNotNull( - it.toDbStatus(accountKey), - it.status?.toDbStatus(accountKey), - ) - } - val user = data.mapNotNull { it.account?.toDbUser(accountKey.host) } - database.transaction { - timeline.forEach { - database.dbPagingTimelineQueries.insert( - account_key = it.account_key, - status_key = it.status_key, - paging_key = it.paging_key, - sort_id = it.sort_id, - ) - } - status.forEach { - database.dbStatusQueries.insert( - status_key = it.status_key, - platform_type = it.platform_type, - user_key = it.user_key, - content = it.content, - account_key = it.account_key, - ) - } - user.forEach { - database.dbUserQueries.insert( - user_key = it.user_key, - platform_type = it.platform_type, - name = it.name, - handle = it.handle, - content = it.content, - host = it.host, - ) - } - } + val items = data.toDb(accountKey, pagingKey) + saveToDatabase(database, items) } } -private fun Notification.toDbPagingTimeline( +internal fun List.toDb( accountKey: MicroBlogKey, pagingKey: String, -): DbPagingTimeline { - val user = - this.account?.toDbUser(accountKey.host) ?: throw IllegalStateException("account is null") - val sortId = this.createdAt?.toEpochMilliseconds() ?: 0 - return DbPagingTimeline( - id = 0, - account_key = accountKey, - status_key = - MicroBlogKey( - this.id ?: throw IllegalStateException("id is null"), - user.user_key.host, - ), - paging_key = pagingKey, - sort_id = sortId, +): List = + this.map { + createDbPagingTimelineWithStatus( + accountKey = accountKey, + pagingKey = pagingKey, + sortId = it.createdAt?.toEpochMilliseconds() ?: 0, + status = it.toDbStatusWithUser(accountKey), + references = + listOfNotNull( + if (it.status != null) { + ReferenceType.Notification to it.status.toDbStatusWithUser(accountKey) + } else { + null + }, + ).toMap(), + ) + } + +private fun Notification.toDbStatusWithUser(accountKey: MicroBlogKey): DbStatusWithUser { + val user = this.account?.toDbUser(accountKey.host) ?: throw IllegalStateException("account is null") + val status = this.toDbStatus(accountKey) + return DbStatusWithUser( + data = status, + user = user, ) } private fun Notification.toDbStatus(accountKey: MicroBlogKey): DbStatus { - val user = - this.account?.toDbUser(accountKey.host) ?: throw IllegalStateException("account is null") + val user = this.account?.toDbUser(accountKey.host) ?: throw IllegalStateException("account is null") return DbStatus( - id = 0, - status_key = + statusKey = MicroBlogKey( this.id ?: throw IllegalStateException("id is null"), - user.user_key.host, + user.userKey.host, ), - platform_type = PlatformType.Mastodon, - user_key = user.user_key, + platformType = PlatformType.Mastodon, + userKey = user.userKey, content = StatusContent.MastodonNotification(this), - account_key = accountKey, + accountKey = accountKey, ) } -internal fun Status.toDbPagingTimeline( +internal fun List.toDbPagingTimeline( accountKey: MicroBlogKey, pagingKey: String, sortIdProvider: (Status) -> Long = { it.createdAt?.toEpochMilliseconds() ?: 0 }, -): DbPagingTimeline { - val status = this.toDbStatus(accountKey) - val sortId = sortIdProvider(this) - return DbPagingTimeline( - id = 0, - account_key = accountKey, - status_key = status.status_key, - paging_key = pagingKey, - sort_id = sortId, - ) -} +): List = + this.map { + createDbPagingTimelineWithStatus( + accountKey = accountKey, + pagingKey = pagingKey, + sortId = sortIdProvider(it), + status = it.toDbStatusWithUser(accountKey), + references = + listOfNotNull( + if (it.reblog != null) { + ReferenceType.Retweet to it.reblog.toDbStatusWithUser(accountKey) + } else { + null + }, + ).toMap(), + ) + } -private fun Status.toDbStatus(accountKey: MicroBlogKey): DbStatus { +private fun Status.toDbStatusWithUser(accountKey: MicroBlogKey): DbStatusWithUser { val user = account?.toDbUser(accountKey.host) ?: throw IllegalArgumentException("mastodon Status.user should not be null") - return DbStatus( - id = 0, - status_key = - MicroBlogKey( - id ?: throw IllegalArgumentException("mastodon Status.idStr should not be null"), - host = user.user_key.host, - ), - platform_type = PlatformType.Mastodon, - content = - dev.dimension.flare.data.database.cache.model.StatusContent - .Mastodon(this), - user_key = user.user_key, - account_key = accountKey, + val status = + DbStatus( + statusKey = + MicroBlogKey( + id ?: throw IllegalArgumentException("mastodon Status.idStr should not be null"), + host = user.userKey.host, + ), + platformType = PlatformType.Mastodon, + content = + dev.dimension.flare.data.database.cache.model.StatusContent + .Mastodon(this), + userKey = user.userKey, + accountKey = accountKey, + ) + return DbStatusWithUser( + data = status, + user = user, ) } @@ -190,12 +137,12 @@ internal fun Account.toDbUser(host: String): DbUser { host } return DbUser( - user_key = + userKey = MicroBlogKey( id = id ?: throw IllegalArgumentException("mastodon Account.id should not be null"), host = host, ), - platform_type = PlatformType.Mastodon, + platformType = PlatformType.Mastodon, name = displayName ?: throw IllegalArgumentException("mastodon Account.displayName should not be null"), @@ -212,5 +159,7 @@ internal fun Account.toDbUser(host: String): DbUser { internal fun List.toDb(host: String): DbEmoji = DbEmoji( host = host, - content = EmojiContent.Mastodon(this), + content = + dev.dimension.flare.data.database.cache.model.EmojiContent + .Mastodon(this), ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/mapper/Microblog.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/mapper/Microblog.kt new file mode 100644 index 000000000..3426a6497 --- /dev/null +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/mapper/Microblog.kt @@ -0,0 +1,81 @@ +package dev.dimension.flare.data.database.cache.mapper + +import dev.dimension.flare.data.database.cache.CacheDatabase +import dev.dimension.flare.data.database.cache.model.DbPagingTimeline +import dev.dimension.flare.data.database.cache.model.DbPagingTimelineWithStatus +import dev.dimension.flare.data.database.cache.model.DbStatusReference +import dev.dimension.flare.data.database.cache.model.DbStatusReferenceWithStatus +import dev.dimension.flare.data.database.cache.model.DbStatusWithReference +import dev.dimension.flare.data.database.cache.model.DbStatusWithUser +import dev.dimension.flare.model.MicroBlogKey +import dev.dimension.flare.model.ReferenceType +import kotlin.uuid.Uuid + +internal suspend fun saveToDatabase( + database: CacheDatabase, + items: List, +) { + ( + items.mapNotNull { it.status.status.user } + + items + .flatMap { it.status.references } + .mapNotNull { it.status.user } + ).let { + database.userDao().insertAll(it) + } + ( + items.map { it.status.status.data } + + items + .flatMap { it.status.references } + .map { it.status.data } + ).let { + database.statusDao().insertAll(it) + } + items.flatMap { it.status.references }.map { it.reference }.let { + database.statusReferenceDao().insertAll(it) + } + database.pagingTimelineDao().insertAll(items.map { it.timeline }) +} + +internal fun createDbPagingTimelineWithStatus( + accountKey: MicroBlogKey, + pagingKey: String, + sortId: Long, + status: DbStatusWithUser, + references: Map, +): DbPagingTimelineWithStatus { + val timeline = + DbPagingTimeline( + _id = Uuid.random().toString(), + accountKey = accountKey, + statusKey = status.data.statusKey, + pagingKey = pagingKey, + sortId = sortId, + ) + return DbPagingTimelineWithStatus( + timeline = timeline, + status = + DbStatusWithReference( + status = status, + references = + references.map { (type, reference) -> + reference.toDbStatusReference(status.data.statusKey, type) + }, + ), + ) +} + +private fun DbStatusWithUser.toDbStatusReference( + statusKey: MicroBlogKey, + referenceType: ReferenceType, +): DbStatusReferenceWithStatus = + DbStatusReferenceWithStatus( + reference = + DbStatusReference( + _id = Uuid.random().toString(), + referenceType = referenceType, + statusKey = statusKey, + referenceStatusKey = data.statusKey, + ), + status = this, + ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/mapper/Misskey.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/mapper/Misskey.kt index 4a1131faa..a3a1d98a0 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/mapper/Misskey.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/mapper/Misskey.kt @@ -1,10 +1,12 @@ + package dev.dimension.flare.data.database.cache.mapper -import dev.dimension.flare.data.cache.DbEmoji -import dev.dimension.flare.data.cache.DbPagingTimeline -import dev.dimension.flare.data.cache.DbStatus -import dev.dimension.flare.data.cache.DbUser import dev.dimension.flare.data.database.cache.CacheDatabase +import dev.dimension.flare.data.database.cache.model.DbEmoji +import dev.dimension.flare.data.database.cache.model.DbPagingTimelineWithStatus +import dev.dimension.flare.data.database.cache.model.DbStatus +import dev.dimension.flare.data.database.cache.model.DbStatusWithUser +import dev.dimension.flare.data.database.cache.model.DbUser import dev.dimension.flare.data.database.cache.model.EmojiContent import dev.dimension.flare.data.database.cache.model.StatusContent import dev.dimension.flare.data.database.cache.model.UserContent @@ -15,91 +17,64 @@ import dev.dimension.flare.data.network.misskey.api.model.User import dev.dimension.flare.data.network.misskey.api.model.UserLite import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.model.PlatformType +import dev.dimension.flare.model.ReferenceType +import kotlinx.coroutines.flow.firstOrNull import kotlinx.datetime.Instant internal object Misskey { - fun save( + suspend fun save( accountKey: MicroBlogKey, pagingKey: String, database: CacheDatabase, data: List, sortIdProvider: (Note) -> Long = { Instant.parse(it.createdAt).toEpochMilliseconds() }, ) { - val timeline = data.map { it.toDbPagingTimeline(accountKey, pagingKey, sortIdProvider) } - val status = - data.flatMap { - listOfNotNull( - it.toDbStatus(accountKey), - it.renote?.toDbStatus(accountKey), - ) - } - val user = data.map { it.user.toDbUser(accountKey.host) } - save(database, timeline, status, user) + save(database, data.toDbPagingTimeline(accountKey, pagingKey, sortIdProvider)) } - fun save( + suspend fun save( accountKey: MicroBlogKey, pagingKey: String, database: CacheDatabase, data: List, ) { - val timeline = data.map { it.toDbPagingTimeline(accountKey, pagingKey) } - val status = - data.flatMap { - listOfNotNull( - it.toDbStatus(accountKey), - it.note?.toDbStatus(accountKey), - ) - } - val user = data.mapNotNull { it.user?.toDbUser(accountKey.host) } - save(database, timeline, status, user) + save(database, data.toDb(accountKey, pagingKey)) } - private fun save( + private suspend fun save( database: CacheDatabase, - timeline: List, - status: List, - user: List, + timeline: List, ) { - database.transaction { - timeline.forEach { - database.dbPagingTimelineQueries.insert( - account_key = it.account_key, - status_key = it.status_key, - paging_key = it.paging_key, - sort_id = it.sort_id, - ) - } - status.forEach { - database.dbStatusQueries.insert( - status_key = it.status_key, - platform_type = it.platform_type, - user_key = it.user_key, - content = it.content, - account_key = it.account_key, - ) - } + ( + timeline.mapNotNull { it.status.status.user } + + timeline + .flatMap { it.status.references } + .mapNotNull { it.status.user } + ).let { allUsers -> val exsitingUsers = - database.dbUserQueries - .findByKeys(user.map { it.user_key }) - .executeAsList() + database + .userDao() + .findByKeys(allUsers.map { it.userKey }) + .firstOrNull() + .orEmpty() .filter { it.content is UserContent.Misskey }.map { val content = it.content as UserContent.Misskey - val item = - user.find { user -> - user.user_key == it.user_key + val user = + allUsers.find { user -> + user.userKey == it.userKey } - if (item != null && item.content is UserContent.MisskeyLite) { + + if (user != null && user.content is UserContent.MisskeyLite) { it.copy( content = content.copy( data = content.data.copy( - name = item.content.data.name, - username = item.content.data.username, - avatarUrl = item.content.data.avatarUrl, + name = user.content.data.name, + username = user.content.data.username, + avatarUrl = user.content.data.avatarUrl, ), ), ) @@ -107,92 +82,125 @@ internal object Misskey { it } } - val result = (exsitingUsers + user).distinctBy { it.user_key } - result.forEach { - database.dbUserQueries.insert( - user_key = it.user_key, - platform_type = it.platform_type, - name = it.name, - handle = it.handle, - content = it.content, - host = it.host, - ) - } + + val result = (exsitingUsers + allUsers).distinctBy { it.userKey } + database.userDao().insertAll(result) } + ( + timeline.map { it.status.status.data } + + timeline + .flatMap { it.status.references } + .map { it.status.data } + ).let { + database.statusDao().insertAll(it) + } + timeline.flatMap { it.status.references }.map { it.reference }.let { + database.statusReferenceDao().insertAll(it) + } + database.pagingTimelineDao().insertAll(timeline.map { it.timeline }) } } -private fun Notification.toDbPagingTimeline( +internal fun List.toDb( accountKey: MicroBlogKey, pagingKey: String, -): DbPagingTimeline { - val sortId = Instant.parse(this.createdAt).toEpochMilliseconds() +): List = + this.map { + createDbPagingTimelineWithStatus( + accountKey = accountKey, + pagingKey = pagingKey, + sortId = Instant.parse(it.createdAt).toEpochMilliseconds(), + status = it.toDbStatusWithUser(accountKey), + references = + listOfNotNull( + if (it.note != null) { + ReferenceType.Notification to it.note.toDbStatusWithUser(accountKey) + } else { + null + }, + ).toMap(), + ) + } + +private fun Notification.toDbStatusWithUser(accountKey: MicroBlogKey): DbStatusWithUser { + val user = this.user?.toDbUser(accountKey.host) val status = this.toDbStatus(accountKey) - return DbPagingTimeline( - id = 0, - account_key = accountKey, - status_key = status.status_key, - paging_key = pagingKey, - sort_id = sortId, + return DbStatusWithUser( + data = status, + user = user, ) } private fun Notification.toDbStatus(accountKey: MicroBlogKey): DbStatus { val user = this.user?.toDbUser(accountKey.host) return DbStatus( - id = 0, - status_key = + statusKey = MicroBlogKey( this.id, accountKey.host, ), - platform_type = PlatformType.Misskey, - user_key = user?.user_key, + platformType = PlatformType.Misskey, + userKey = user?.userKey, content = StatusContent.MisskeyNotification(this), - account_key = accountKey, + accountKey = accountKey, ) } -private fun Note.toDbPagingTimeline( +private fun List.toDbPagingTimeline( accountKey: MicroBlogKey, pagingKey: String, sortIdProvider: (Note) -> Long = { Instant.parse(it.createdAt).toEpochMilliseconds() }, -): DbPagingTimeline { - val sortId = sortIdProvider(this) - val status = this.toDbStatus(accountKey) - return DbPagingTimeline( - id = 0, - account_key = accountKey, - status_key = status.status_key, - paging_key = pagingKey, - sort_id = sortId, - ) -} +): List = + this.map { + createDbPagingTimelineWithStatus( + accountKey = accountKey, + pagingKey = pagingKey, + sortId = sortIdProvider(it), + status = it.toDbStatusWithUser(accountKey), + references = + listOfNotNull( + if (it.renote != null) { + ReferenceType.Retweet to it.renote.toDbStatusWithUser(accountKey) + } else { + null + }, + if (it.reply != null) { + ReferenceType.Reply to it.reply.toDbStatusWithUser(accountKey) + } else { + null + }, + ).toMap(), + ) + } -private fun Note.toDbStatus(accountKey: MicroBlogKey): DbStatus { - val user = this.user.toDbUser(accountKey.host) - return DbStatus( - id = 0, - status_key = - MicroBlogKey( - id = id, - host = user.user_key.host, - ), - platform_type = PlatformType.Misskey, - content = StatusContent.Misskey(this), - user_key = user.user_key, - account_key = accountKey, +private fun Note.toDbStatusWithUser(accountKey: MicroBlogKey): DbStatusWithUser { + val user = user.toDbUser(accountKey.host) + val status = + DbStatus( + statusKey = + MicroBlogKey( + id = id, + host = user.userKey.host, + ), + platformType = PlatformType.Misskey, + content = StatusContent.Misskey(this), + userKey = user.userKey, + accountKey = accountKey, + ) + return DbStatusWithUser( + data = status, + user = user, ) } private fun UserLite.toDbUser(accountHost: String) = DbUser( - user_key = + userKey = MicroBlogKey( id = id, host = accountHost, ), - platform_type = dev.dimension.flare.model.PlatformType.Misskey, + platformType = PlatformType.Misskey, name = name ?: "", handle = username, content = UserContent.MisskeyLite(this), @@ -206,12 +214,12 @@ private fun UserLite.toDbUser(accountHost: String) = internal fun User.toDbUser(accountHost: String) = DbUser( - user_key = + userKey = MicroBlogKey( id = id, host = accountHost, ), - platform_type = PlatformType.Misskey, + platformType = dev.dimension.flare.model.PlatformType.Misskey, name = name ?: "", handle = username, content = UserContent.Misskey(this), diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/mapper/VVO.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/mapper/VVO.kt index c478a4472..55af1d6f0 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/mapper/VVO.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/mapper/VVO.kt @@ -1,114 +1,45 @@ package dev.dimension.flare.data.database.cache.mapper +import dev.dimension.flare.data.database.cache.model.DbPagingTimelineWithStatus +import dev.dimension.flare.data.database.cache.model.DbStatus +import dev.dimension.flare.data.database.cache.model.DbStatusWithUser +import dev.dimension.flare.data.database.cache.model.DbUser +import dev.dimension.flare.data.database.cache.model.UserContent import dev.dimension.flare.data.network.vvo.model.Comment import dev.dimension.flare.data.network.vvo.model.Status import dev.dimension.flare.data.network.vvo.model.User import dev.dimension.flare.model.MicroBlogKey +import dev.dimension.flare.model.PlatformType +import dev.dimension.flare.model.ReferenceType import dev.dimension.flare.model.vvoHost internal object VVO { - fun saveStatus( + suspend fun saveStatus( accountKey: MicroBlogKey, pagingKey: String, database: dev.dimension.flare.data.database.cache.CacheDatabase, statuses: List, sortIdProvider: (Status) -> Long = { it.createdAt?.toEpochMilliseconds() ?: 0L }, ) { - val timeline = statuses.map { it.toDbPagingTimeline(accountKey, pagingKey, sortIdProvider) } - val status = - statuses.flatMap { - listOfNotNull( - it.toDbStatus(accountKey), - it.retweetedStatus?.toDbStatus(accountKey), - ) + val items = + statuses.map { + it.toDbPagingTimeline(accountKey, pagingKey, sortIdProvider) } - val user = - statuses.flatMap { - listOfNotNull( - it.user?.toDbUser(), - it.retweetedStatus?.user?.toDbUser(), - ) - } - database.transaction { - timeline.forEach { - database.dbPagingTimelineQueries.insert( - account_key = it.account_key, - status_key = it.status_key, - paging_key = it.paging_key, - sort_id = it.sort_id, - ) - } - status.forEach { - database.dbStatusQueries.insert( - status_key = it.status_key, - platform_type = it.platform_type, - user_key = it.user_key, - content = it.content, - account_key = it.account_key, - ) - } - user.forEach { - database.dbUserQueries.insert( - user_key = it.user_key, - platform_type = it.platform_type, - name = it.name, - handle = it.handle, - content = it.content, - host = it.host, - ) - } - } + saveToDatabase(database, items) } - fun saveComment( + suspend fun saveComment( accountKey: MicroBlogKey, pagingKey: String, database: dev.dimension.flare.data.database.cache.CacheDatabase, statuses: List, sortIdProvider: (Comment) -> Long = { it.createdAt?.toEpochMilliseconds() ?: 0L }, ) { - val timeline = statuses.map { it.toDbPagingTimeline(accountKey, pagingKey, sortIdProvider) } - val status = - statuses.flatMap { - listOfNotNull( - it.toDbStatus(accountKey), - ) - } - val user = - statuses.flatMap { - listOfNotNull( - it.user?.toDbUser(), - ) - } - database.transaction { - timeline.forEach { - database.dbPagingTimelineQueries.insert( - account_key = it.account_key, - status_key = it.status_key, - paging_key = it.paging_key, - sort_id = it.sort_id, - ) - } - status.forEach { - database.dbStatusQueries.insert( - status_key = it.status_key, - platform_type = it.platform_type, - user_key = it.user_key, - content = it.content, - account_key = it.account_key, - ) + val items = + statuses.map { + it.toDbPagingTimeline(accountKey, pagingKey, sortIdProvider) } - user.forEach { - database.dbUserQueries.insert( - user_key = it.user_key, - platform_type = it.platform_type, - name = it.name, - handle = it.handle, - content = it.content, - host = it.host, - ) - } - } + saveToDatabase(database, items) } } @@ -116,22 +47,37 @@ private fun Status.toDbPagingTimeline( accountKey: MicroBlogKey, pagingKey: String, sortIdProvider: (Status) -> Long = { it.createdAt?.toEpochMilliseconds() ?: 0L }, -): dev.dimension.flare.data.cache.DbPagingTimeline = - dev.dimension.flare.data.cache.DbPagingTimeline( - id = 0, - account_key = accountKey, - status_key = MicroBlogKey(id = id, host = vvoHost), - paging_key = pagingKey, - sort_id = sortIdProvider(this), +): DbPagingTimelineWithStatus = + createDbPagingTimelineWithStatus( + accountKey = accountKey, + pagingKey = pagingKey, + sortId = sortIdProvider(this), + status = this.toDbStatusWithUser(accountKey), + references = + listOfNotNull( + if (this.retweetedStatus != null) { + ReferenceType.Retweet to this.retweetedStatus.toDbStatusWithUser(accountKey) + } else { + null + }, + ).toMap(), + ) + +private fun Status.toDbStatusWithUser(accountKey: MicroBlogKey): DbStatusWithUser { + val user = this.user?.toDbUser() + val status = this.toDbStatus(accountKey) + return DbStatusWithUser( + data = status, + user = user, ) +} -private fun Status.toDbStatus(accountKey: MicroBlogKey): dev.dimension.flare.data.cache.DbStatus = - dev.dimension.flare.data.cache.DbStatus( - id = 0, - status_key = MicroBlogKey(id = id, host = vvoHost), - account_key = accountKey, - user_key = user?.id?.let { MicroBlogKey(id = it.toString(), host = vvoHost) }, - platform_type = dev.dimension.flare.model.PlatformType.VVo, +private fun Status.toDbStatus(accountKey: MicroBlogKey): DbStatus = + DbStatus( + statusKey = MicroBlogKey(id = id, host = vvoHost), + accountKey = accountKey, + userKey = user?.id?.let { MicroBlogKey(id = it.toString(), host = vvoHost) }, + platformType = dev.dimension.flare.model.PlatformType.VVo, content = dev.dimension.flare.data.database.cache.model.StatusContent .VVO(data = this), @@ -141,35 +87,46 @@ private fun Comment.toDbPagingTimeline( accountKey: MicroBlogKey, pagingKey: String, sortIdProvider: (Comment) -> Long = { it.createdAt?.toEpochMilliseconds() ?: 0L }, -): dev.dimension.flare.data.cache.DbPagingTimeline = - dev.dimension.flare.data.cache.DbPagingTimeline( - id = 0, - account_key = accountKey, - status_key = MicroBlogKey(id = id, host = vvoHost), - paging_key = pagingKey, - sort_id = sortIdProvider(this), +): DbPagingTimelineWithStatus = + createDbPagingTimelineWithStatus( + accountKey = accountKey, + pagingKey = pagingKey, + sortId = sortIdProvider(this), + status = this.toDbStatusWithUser(accountKey), + references = + commentList.orEmpty().associate { + ReferenceType.Reply to it.toDbStatusWithUser(accountKey) + }, ) -private fun Comment.toDbStatus(accountKey: MicroBlogKey): dev.dimension.flare.data.cache.DbStatus = - dev.dimension.flare.data.cache.DbStatus( - id = 0, - status_key = MicroBlogKey(id = id, host = vvoHost), - account_key = accountKey, - user_key = user?.id?.let { MicroBlogKey(id = it.toString(), host = vvoHost) }, - platform_type = dev.dimension.flare.model.PlatformType.VVo, +private fun Comment.toDbStatusWithUser(accountKey: MicroBlogKey): DbStatusWithUser { + val user = this.user?.toDbUser() + val status = this.toDbStatus(accountKey) + return DbStatusWithUser( + data = status, + user = user, + ) +} + +private fun Comment.toDbStatus(accountKey: MicroBlogKey): DbStatus = + DbStatus( + statusKey = MicroBlogKey(id = id, host = vvoHost), + accountKey = accountKey, + userKey = user?.id?.let { MicroBlogKey(id = it.toString(), host = vvoHost) }, + platformType = dev.dimension.flare.model.PlatformType.VVo, content = dev.dimension.flare.data.database.cache.model.StatusContent .VVOComment(data = this), ) -internal fun User.toDbUser(): dev.dimension.flare.data.cache.DbUser = - dev.dimension.flare.data.cache.DbUser( +internal fun User.toDbUser(): DbUser = + DbUser( handle = screenName, host = vvoHost, name = screenName, - user_key = MicroBlogKey(id = id.toString(), host = vvoHost), - platform_type = dev.dimension.flare.model.PlatformType.VVo, + userKey = MicroBlogKey(id = id.toString(), host = vvoHost), + platformType = PlatformType.VVo, content = - dev.dimension.flare.data.database.cache.model.UserContent + UserContent .VVO(data = this), ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/mapper/XQT.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/mapper/XQT.kt index b2eb3d2bb..60947bb33 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/mapper/XQT.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/mapper/XQT.kt @@ -1,8 +1,9 @@ package dev.dimension.flare.data.database.cache.mapper -import dev.dimension.flare.data.cache.DbPagingTimeline -import dev.dimension.flare.data.cache.DbStatus -import dev.dimension.flare.data.cache.DbUser +import dev.dimension.flare.data.database.cache.model.DbPagingTimelineWithStatus +import dev.dimension.flare.data.database.cache.model.DbStatus +import dev.dimension.flare.data.database.cache.model.DbStatusWithUser +import dev.dimension.flare.data.database.cache.model.DbUser import dev.dimension.flare.data.database.cache.model.StatusContent import dev.dimension.flare.data.database.cache.model.UserContent import dev.dimension.flare.data.network.xqt.model.CursorType @@ -28,59 +29,19 @@ import dev.dimension.flare.data.network.xqt.model.UserUnavailable import dev.dimension.flare.data.network.xqt.model.legacy.TopLevel import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.model.PlatformType +import dev.dimension.flare.model.ReferenceType import dev.dimension.flare.model.xqtHost internal object XQT { - fun save( + suspend fun save( accountKey: MicroBlogKey, pagingKey: String, database: dev.dimension.flare.data.database.cache.CacheDatabase, tweet: List, sortIdProvider: (XQTTimeline) -> Long = { it.sortedIndex }, ) { - val timeline = tweet.map { it.toDbPagingTimeline(accountKey, pagingKey, sortIdProvider) } - val status = - tweet.flatMap { - listOfNotNull( - it.tweets.toDbStatus(accountKey), - it.tweets.tweetResults.result - ?.getRetweet() - ?.toDbStatus(accountKey), - it.tweets.tweetResults.result - ?.getQuoted() - ?.toDbStatus(accountKey), - ) - } - val user = tweet.map { it.tweets.toDbUser() } - database.transaction { - timeline.forEach { - database.dbPagingTimelineQueries.insert( - account_key = it.account_key, - status_key = it.status_key, - paging_key = it.paging_key, - sort_id = it.sort_id, - ) - } - status.forEach { - database.dbStatusQueries.insert( - status_key = it.status_key, - platform_type = it.platform_type, - user_key = it.user_key, - content = it.content, - account_key = it.account_key, - ) - } - user.forEach { - database.dbUserQueries.insert( - user_key = it.user_key, - platform_type = it.platform_type, - name = it.name, - handle = it.handle, - content = it.content, - host = it.host, - ) - } - } + val items = tweet.map { it.toDbPagingTimeline(accountKey, pagingKey, sortIdProvider) } + saveToDatabase(database, items) } } @@ -105,35 +66,41 @@ private fun XQTTimeline.toDbPagingTimeline( accountKey: MicroBlogKey, pagingKey: String, sortIdProvider: (XQTTimeline) -> Long = { sortedIndex }, -): DbPagingTimeline { - val status = tweets.toDbStatus(accountKey) - return DbPagingTimeline( - id = 0, - account_key = accountKey, - paging_key = pagingKey, - status_key = status.status_key, - sort_id = sortIdProvider(this), +): DbPagingTimelineWithStatus = + createDbPagingTimelineWithStatus( + accountKey = accountKey, + pagingKey = pagingKey, + sortId = sortIdProvider(this), + status = tweets.toDbStatusWithUser(accountKey), + references = + listOfNotNull( + tweets.tweetResults.result?.getRetweet()?.toDbStatusWithUser(accountKey)?.let { + ReferenceType.Retweet to it + }, + tweets.tweetResults.result?.getQuoted()?.toDbStatusWithUser(accountKey)?.let { + ReferenceType.Quote to it + }, + ).toMap(), ) -} -private fun TimelineTweet.toDbStatus(accountKey: MicroBlogKey): DbStatus = - tweetResults.result?.toDbStatus(accountKey) +private fun TimelineTweet.toDbStatusWithUser(accountKey: MicroBlogKey): DbStatusWithUser = + tweetResults.result?.toDbStatusWithUser(accountKey) ?: throw IllegalStateException("Tweet should not be null") -private fun TweetUnion.toDbStatus(accountKey: MicroBlogKey): DbStatus? = +private fun TweetUnion.toDbStatusWithUser(accountKey: MicroBlogKey): DbStatusWithUser? = when (this) { - is Tweet -> toDbStatus(this, accountKey) + is Tweet -> toDbStatusWithUser(this, accountKey) // You’re unable to view this Post because // this account owner limits who can view their Posts. Learn more // throw IllegalStateException("Tweet tombstone should not be saved") is TweetTombstone -> null - is TweetWithVisibilityResults -> toDbStatus(this.tweet, accountKey) + is TweetWithVisibilityResults -> toDbStatusWithUser(this.tweet, accountKey) } -private fun toDbStatus( +private fun toDbStatusWithUser( tweet: Tweet, accountKey: MicroBlogKey, -): DbStatus { +): DbStatusWithUser { val user = tweet.core ?.userResults @@ -141,17 +108,21 @@ private fun toDbStatus( ?.let { it as? User }?.toDbUser() ?: throw IllegalStateException("Tweet.user should not be null") - return DbStatus( - id = 0, - status_key = - MicroBlogKey( - id = tweet.restId, - host = user.user_key.host, + return DbStatusWithUser( + data = + DbStatus( + id = 0, + statusKey = + MicroBlogKey( + id = tweet.restId, + host = user.userKey.host, + ), + platformType = PlatformType.xQt, + content = StatusContent.XQT(tweet), + userKey = user.userKey, + accountKey = accountKey, ), - platform_type = PlatformType.xQt, - content = StatusContent.XQT(tweet), - user_key = user.user_key, - account_key = accountKey, + user = user, ) } @@ -175,12 +146,12 @@ private fun TimelineTweet.toDbUser(): DbUser { internal fun User.toDbUser() = DbUser( - user_key = + userKey = MicroBlogKey( id = restId, host = xqtHost, ), - platform_type = PlatformType.xQt, + platformType = PlatformType.xQt, name = legacy.name, handle = legacy.screenName, host = xqtHost, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbEmoji.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbEmoji.kt new file mode 100644 index 000000000..9babbf52f --- /dev/null +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbEmoji.kt @@ -0,0 +1,25 @@ +package dev.dimension.flare.data.database.cache.model + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import androidx.room.TypeConverter +import dev.dimension.flare.common.decodeJson +import dev.dimension.flare.common.encodeJson + +@Entity +data class DbEmoji( + @PrimaryKey + @ColumnInfo(name = "host") + val host: String, + @ColumnInfo(name = "content") + val content: EmojiContent, +) + +class EmojiContentConverter { + @TypeConverter + fun fromEmojiContent(emojiContent: EmojiContent): String = emojiContent.encodeJson() + + @TypeConverter + fun toEmojiContent(data: String): EmojiContent = data.decodeJson() +} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbPagingTimeline.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbPagingTimeline.kt new file mode 100644 index 000000000..11d22c9b8 --- /dev/null +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbPagingTimeline.kt @@ -0,0 +1,83 @@ +package dev.dimension.flare.data.database.cache.model + +import androidx.room.Embedded +import androidx.room.Entity +import androidx.room.Index +import androidx.room.PrimaryKey +import androidx.room.Relation +import androidx.room.TypeConverter +import dev.dimension.flare.common.decodeJson +import dev.dimension.flare.common.encodeJson +import dev.dimension.flare.model.MicroBlogKey +import dev.dimension.flare.model.ReferenceType + +@Entity( + indices = [ + Index( + value = ["accountKey", "statusKey", "pagingKey"], + unique = true, + ), + ], +) +data class DbPagingTimeline( + @PrimaryKey + val _id: String, + val accountKey: MicroBlogKey, + val pagingKey: String, + val statusKey: MicroBlogKey, + val sortId: Long, +) + +data class DbPagingTimelineWithStatus( + @Embedded + val timeline: DbPagingTimeline, + @Relation( + parentColumn = "statusKey", + entityColumn = "statusKey", + entity = DbStatus::class, + ) + val status: DbStatusWithReference, +) + +data class DbStatusWithUser( + @Embedded + val data: DbStatus, + @Relation(parentColumn = "userKey", entityColumn = "userKey") + val user: DbUser?, +) + +data class DbStatusReferenceWithStatus( + @Embedded + val reference: DbStatusReference, + @Relation( + parentColumn = "referenceStatusKey", + entityColumn = "statusKey", + entity = DbStatus::class, + ) + val status: DbStatusWithUser, +) + +data class DbStatusWithReference( + @Embedded + val status: DbStatusWithUser, + @Relation( + parentColumn = "statusKey", + entityColumn = "statusKey", + entity = DbStatusReference::class, + ) + val references: List, +) + +class StatusConverter { + @TypeConverter + fun fromStatusContent(value: StatusContent): String = value.encodeJson() + + @TypeConverter + fun toStatusContent(value: String): StatusContent = value.decodeJson() + + @TypeConverter + fun fromReferenceType(value: ReferenceType): String = value.name + + @TypeConverter + fun toReferenceType(value: String): ReferenceType = ReferenceType.valueOf(value) +} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbPagingTimelineView.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbPagingTimelineView.kt new file mode 100644 index 000000000..cbf806239 --- /dev/null +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbPagingTimelineView.kt @@ -0,0 +1,40 @@ +package dev.dimension.flare.data.database.cache.model + +import androidx.room.DatabaseView +import androidx.room.Embedded + +@DatabaseView( + viewName = "PagingTimelineView", + value = """ +SELECT +timeline.*, +status.content AS status_content, +user.*, +retweetStatus.content AS retweet_status_content, +quoteStatus.content AS quote_status_content, +replyStatus.content AS reply_status_content, +notificationStatus.content AS notification_status_content +FROM DbPagingTimeline AS timeline +JOIN DbStatus status ON timeline.statusKey = status.statusKey AND timeline.accountKey = status.accountKey +LEFT JOIN DbUser user ON status.userKey = user.userKey +LEFT JOIN status_reference retweet ON status.statusKey = retweet.statusKey AND retweet.referenceType = 'Retweet' +LEFT JOIN DbStatus retweetStatus ON retweet.referenceStatusKey = retweetStatus.statusKey +LEFT JOIN status_reference reply ON status.statusKey = reply.statusKey AND reply.referenceType = 'Reply' +LEFT JOIN DbStatus replyStatus ON reply.referenceStatusKey = replyStatus.statusKey +LEFT JOIN status_reference quote ON status.statusKey = quote.statusKey AND quote.referenceType = 'Quote' +LEFT JOIN DbStatus quoteStatus ON quote.referenceStatusKey = quoteStatus.statusKey +LEFT JOIN status_reference notification ON status.statusKey = notification.statusKey AND notification.referenceType = 'Notification' +LEFT JOIN DbStatus notificationStatus ON notification.referenceStatusKey = notificationStatus.statusKey +""", +) +data class DbPagingTimelineView( + @Embedded + val timeline: DbPagingTimeline, + val status_content: StatusContent, + @Embedded + val user: DbUser?, + val retweet_status_content: StatusContent?, + val quote_status_content: StatusContent?, + val reply_status_content: StatusContent?, + val notification_status_content: StatusContent?, +) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbStatus.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbStatus.kt new file mode 100644 index 000000000..421ddcb7b --- /dev/null +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbStatus.kt @@ -0,0 +1,31 @@ +package dev.dimension.flare.data.database.cache.model + +import androidx.room.Entity +import androidx.room.Index +import androidx.room.PrimaryKey +import androidx.room.TypeConverter +import dev.dimension.flare.common.decodeJson +import dev.dimension.flare.common.encodeJson +import dev.dimension.flare.model.MicroBlogKey +import dev.dimension.flare.model.PlatformType + +@Entity( + indices = [Index(value = ["statusKey", "accountKey"], unique = true)], +) +data class DbStatus( + @PrimaryKey(autoGenerate = true) + val id: Long = 0, + val statusKey: MicroBlogKey, + val accountKey: MicroBlogKey, + val userKey: MicroBlogKey?, + val platformType: PlatformType, + val content: StatusContent, +) + +class StatusContentConverters { + @TypeConverter + fun fromStatusContent(content: StatusContent): String = content.encodeJson() + + @TypeConverter + fun toStatusContent(value: String): StatusContent = value.decodeJson() +} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbStatusReference.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbStatusReference.kt new file mode 100644 index 000000000..83c2623fe --- /dev/null +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbStatusReference.kt @@ -0,0 +1,31 @@ +package dev.dimension.flare.data.database.cache.model + +import androidx.room.Entity +import androidx.room.Index +import androidx.room.PrimaryKey +import dev.dimension.flare.model.MicroBlogKey +import dev.dimension.flare.model.ReferenceType + +@Entity( + tableName = "status_reference", + indices = [ + Index( + value = [ + "referenceType", + "statusKey", + "referenceStatusKey", + ], + unique = true, + ), + ], +) +data class DbStatusReference( + /** + * Id that being used in the database + */ + @PrimaryKey + val _id: String, + val referenceType: ReferenceType, + val statusKey: MicroBlogKey, + val referenceStatusKey: MicroBlogKey, +) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbUser.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbUser.kt new file mode 100644 index 000000000..6288b84e9 --- /dev/null +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbUser.kt @@ -0,0 +1,33 @@ +package dev.dimension.flare.data.database.cache.model + +import androidx.room.Entity +import androidx.room.Index +import androidx.room.PrimaryKey +import androidx.room.TypeConverter +import dev.dimension.flare.common.decodeJson +import dev.dimension.flare.common.encodeJson +import dev.dimension.flare.model.MicroBlogKey +import dev.dimension.flare.model.PlatformType + +@Entity( + indices = [ + Index(value = ["handle", "host", "platformType"], unique = true), + ], +) +data class DbUser( + @PrimaryKey + val userKey: MicroBlogKey, + val platformType: PlatformType, + val name: String, + val handle: String, + val host: String, + val content: UserContent, +) + +class UserContentConverters { + @TypeConverter + fun fromUserContent(content: UserContent): String = content.encodeJson() + + @TypeConverter + fun toUserContent(value: String): UserContent = value.decodeJson() +} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/StatusContent.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/StatusContent.kt index c540707bb..90be1ce66 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/StatusContent.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/StatusContent.kt @@ -4,10 +4,10 @@ import app.bsky.feed.FeedViewPostReasonUnion import app.bsky.feed.PostView import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.model.MicroBlogKey +import kotlinx.coroutines.flow.firstOrNull import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -// https://github.com/cashapp/sqldelight/issues/1333 @Serializable sealed interface StatusContent { @Serializable @@ -38,7 +38,13 @@ sealed interface StatusContent { @SerialName("bluesky") data class Bluesky internal constructor( val data: PostView, - val reason: FeedViewPostReasonUnion?, + ) : StatusContent + + @Serializable + @SerialName("bluesky-reason") + data class BlueskyReason internal constructor( + val reason: FeedViewPostReasonUnion, + val data: PostView, ) : StatusContent @Serializable @@ -66,25 +72,32 @@ sealed interface StatusContent { ) : StatusContent } -internal inline fun updateStatusUseCase( +internal suspend inline fun updateStatusUseCase( statusKey: MicroBlogKey, accountKey: MicroBlogKey, cacheDatabase: CacheDatabase, update: (content: T) -> T, ) { - val status = cacheDatabase.dbStatusQueries.get(statusKey, accountKey).executeAsOneOrNull() + val status = cacheDatabase.statusDao().get(statusKey, accountKey).firstOrNull() if (status != null && status.content is T) { - cacheDatabase.dbStatusQueries.update(update(status.content), statusKey, accountKey) + cacheDatabase.statusDao().update( + statusKey = statusKey, + accountKey = accountKey, + content = update(status.content), + ) } } -internal inline fun updateUserUseCase( +internal suspend inline fun updateUserUseCase( userKey: MicroBlogKey, cacheDatabase: CacheDatabase, update: (content: T) -> T, ) { - val user = cacheDatabase.dbUserQueries.findByKey(userKey).executeAsOneOrNull() + val user = cacheDatabase.userDao().findByKey(userKey).firstOrNull() if (user != null && user.content is T) { - cacheDatabase.dbUserQueries.update(update(user.content), userKey) + cacheDatabase.userDao().update( + userKey = userKey, + content = update(user.content), + ) } } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BlueskyDataSource.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BlueskyDataSource.kt index 3f4614826..05dd8b594 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BlueskyDataSource.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BlueskyDataSource.kt @@ -31,8 +31,6 @@ import app.bsky.graph.Listitem import app.bsky.graph.MuteActorRequest import app.bsky.graph.UnmuteActorRequest import app.bsky.unspecced.GetPopularFeedGeneratorsQueryParams -import app.cash.sqldelight.coroutines.asFlow -import app.cash.sqldelight.coroutines.mapToOneNotNull import com.atproto.moderation.CreateReportRequest import com.atproto.moderation.CreateReportRequestSubjectUnion import com.atproto.moderation.Token @@ -91,8 +89,6 @@ import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.IO import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.mapNotNull @@ -108,7 +104,6 @@ import sh.christian.ozone.api.Did import sh.christian.ozone.api.Handle import sh.christian.ozone.api.Nsid import sh.christian.ozone.api.response.AtpResponse -import kotlin.uuid.ExperimentalUuidApi import kotlin.uuid.Uuid @OptIn(ExperimentalPagingApi::class) @@ -184,21 +179,13 @@ class BlueskyDataSource( .getProfile(GetProfileQueryParams(actor = Handle(handle = name))) .requireResponse() .toDbUser(account.accountKey.host) - database.dbUserQueries.insert( - user_key = user.user_key, - platform_type = user.platform_type, - name = user.name, - handle = user.handle, - content = user.content, - host = user.host, - ) + database.userDao().insert(user) }, cacheSource = { - database.dbUserQueries + database + .userDao() .findByHandleAndHost(name, host, PlatformType.Bluesky) - .asFlow() - .mapToOneNotNull(Dispatchers.IO) - .mapNotNull { it.render(account.accountKey) } + .mapNotNull { it?.render(account.accountKey) } }, ) } @@ -212,21 +199,13 @@ class BlueskyDataSource( .getProfile(GetProfileQueryParams(actor = Did(did = id))) .requireResponse() .toDbUser(account.accountKey.host) - database.dbUserQueries.insert( - user_key = user.user_key, - platform_type = user.platform_type, - name = user.name, - handle = user.handle, - content = user.content, - host = user.host, - ) + database.userDao().insert(user) }, cacheSource = { - database.dbUserQueries + database + .userDao() .findByKey(MicroBlogKey(id, account.accountKey.host)) - .asFlow() - .mapToOneNotNull(Dispatchers.IO) - .mapNotNull { it.render(account.accountKey) } + .mapNotNull { it?.render(account.accountKey) } }, ) @@ -321,11 +300,10 @@ class BlueskyDataSource( ) }, cacheSource = { - database.dbStatusQueries + database + .statusDao() .get(statusKey, account.accountKey) - .asFlow() - .mapToOneNotNull(Dispatchers.IO) - .mapNotNull { it.content.render(account.accountKey, this) } + .mapNotNull { it?.content?.render(account.accountKey, this) } }, ) } @@ -707,13 +685,13 @@ class BlueskyDataSource( ), ) // delete status from cache - database.dbStatusQueries.delete( - status_key = statusKey, - account_key = account.accountKey, + database.statusDao().delete( + statusKey = statusKey, + accountKey = account.accountKey, ) - database.dbPagingTimelineQueries.deleteStatus( - account_key = account.accountKey, - status_key = statusKey, + database.pagingTimelineDao().deleteStatus( + accountKey = account.accountKey, + statusKey = statusKey, ) } } @@ -1157,7 +1135,6 @@ class BlueskyDataSource( ) } - @OptIn(ExperimentalUuidApi::class) suspend fun subscribeFeed(data: UiList) { MemCacheable.updateWith>( key = myFeedsKey, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/FeedTimelineRemoteMediator.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/FeedTimelineRemoteMediator.kt index 26c877a60..06ff0fe7b 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/FeedTimelineRemoteMediator.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/FeedTimelineRemoteMediator.kt @@ -5,9 +5,9 @@ import androidx.paging.LoadType import androidx.paging.PagingState import androidx.paging.RemoteMediator import app.bsky.feed.GetFeedQueryParams -import dev.dimension.flare.data.cache.DbPagingTimelineWithStatusView import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.mapper.Bluesky +import dev.dimension.flare.data.database.cache.model.DbPagingTimelineView import dev.dimension.flare.data.network.bluesky.BlueskyService import dev.dimension.flare.model.MicroBlogKey import sh.christian.ozone.api.AtUri @@ -19,12 +19,12 @@ internal class FeedTimelineRemoteMediator( private val database: CacheDatabase, private val uri: String, private val pagingKey: String, -) : RemoteMediator() { +) : RemoteMediator() { var cursor: String? = null override suspend fun load( loadType: LoadType, - state: PagingState, + state: PagingState, ): MediatorResult { return try { val response = diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/HomeTimelineRemoteMediator.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/HomeTimelineRemoteMediator.kt index 6a7cebcca..301eae53b 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/HomeTimelineRemoteMediator.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/HomeTimelineRemoteMediator.kt @@ -5,9 +5,9 @@ import androidx.paging.LoadType import androidx.paging.PagingState import androidx.paging.RemoteMediator import app.bsky.feed.GetTimelineQueryParams -import dev.dimension.flare.data.cache.DbPagingTimelineWithStatusView import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.mapper.Bluesky +import dev.dimension.flare.data.database.cache.model.DbPagingTimelineView import dev.dimension.flare.data.network.bluesky.BlueskyService import dev.dimension.flare.model.MicroBlogKey @@ -17,12 +17,12 @@ internal class HomeTimelineRemoteMediator( private val accountKey: MicroBlogKey, private val database: CacheDatabase, private val pagingKey: String, -) : RemoteMediator() { +) : RemoteMediator() { var cursor: String? = null override suspend fun load( loadType: LoadType, - state: PagingState, + state: PagingState, ): MediatorResult { return try { val response = @@ -55,7 +55,7 @@ internal class HomeTimelineRemoteMediator( endOfPaginationReached = true, ) if (loadType == LoadType.REFRESH) { - database.dbPagingTimelineQueries.deletePaging(accountKey, pagingKey) + database.pagingTimelineDao().delete(pagingKey = pagingKey, accountKey = accountKey) } cursor = response.cursor Bluesky.saveFeed( diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/ListTimelineRemoteMediator.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/ListTimelineRemoteMediator.kt index 70bd35bcc..fc9133336 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/ListTimelineRemoteMediator.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/ListTimelineRemoteMediator.kt @@ -5,9 +5,9 @@ import androidx.paging.LoadType import androidx.paging.PagingState import androidx.paging.RemoteMediator import app.bsky.feed.GetListFeedQueryParams -import dev.dimension.flare.data.cache.DbPagingTimelineWithStatusView import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.mapper.Bluesky +import dev.dimension.flare.data.database.cache.model.DbPagingTimelineView import dev.dimension.flare.data.network.bluesky.BlueskyService import dev.dimension.flare.model.MicroBlogKey import sh.christian.ozone.api.AtUri @@ -19,12 +19,12 @@ internal class ListTimelineRemoteMediator( private val database: CacheDatabase, private val uri: String, private val pagingKey: String, -) : RemoteMediator() { +) : RemoteMediator() { var cursor: String? = null override suspend fun load( loadType: LoadType, - state: PagingState, + state: PagingState, ): MediatorResult { return try { val response = diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/NotificationRemoteMediator.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/NotificationRemoteMediator.kt index afb10d5ce..3d93cd8a5 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/NotificationRemoteMediator.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/NotificationRemoteMediator.kt @@ -5,9 +5,9 @@ import androidx.paging.LoadType import androidx.paging.PagingState import androidx.paging.RemoteMediator import app.bsky.notification.ListNotificationsQueryParams -import dev.dimension.flare.data.cache.DbPagingTimelineWithStatusView import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.mapper.Bluesky +import dev.dimension.flare.data.database.cache.model.DbPagingTimelineView import dev.dimension.flare.data.network.bluesky.BlueskyService import dev.dimension.flare.model.MicroBlogKey @@ -17,12 +17,12 @@ internal class NotificationRemoteMediator( private val accountKey: MicroBlogKey, private val database: CacheDatabase, private val pagingKey: String, -) : RemoteMediator() { +) : RemoteMediator() { private var cursor: String? = null override suspend fun load( loadType: LoadType, - state: PagingState, + state: PagingState, ): MediatorResult { return try { val response = diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/SearchStatusRemoteMediator.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/SearchStatusRemoteMediator.kt index 31dfd9b7b..78c7fd178 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/SearchStatusRemoteMediator.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/SearchStatusRemoteMediator.kt @@ -5,9 +5,9 @@ import androidx.paging.LoadType import androidx.paging.PagingState import androidx.paging.RemoteMediator import app.bsky.feed.SearchPostsQueryParams -import dev.dimension.flare.data.cache.DbPagingTimelineWithStatusView import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.mapper.Bluesky +import dev.dimension.flare.data.database.cache.model.DbPagingTimelineView import dev.dimension.flare.data.network.bluesky.BlueskyService import dev.dimension.flare.model.MicroBlogKey @@ -18,12 +18,12 @@ internal class SearchStatusRemoteMediator( private val accountKey: MicroBlogKey, private val pagingKey: String, private val query: String, -) : RemoteMediator() { +) : RemoteMediator() { var cursor: String? = null override suspend fun load( loadType: LoadType, - state: PagingState, + state: PagingState, ): MediatorResult { return try { val response = diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/StatusDetailRemoteMediator.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/StatusDetailRemoteMediator.kt index ba9f79050..09a807f41 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/StatusDetailRemoteMediator.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/StatusDetailRemoteMediator.kt @@ -9,13 +9,16 @@ import app.bsky.feed.GetPostThreadResponseThreadUnion import app.bsky.feed.GetPostsQueryParams import app.bsky.feed.ThreadViewPostParentUnion import app.bsky.feed.ThreadViewPostReplieUnion -import dev.dimension.flare.data.cache.DbPagingTimelineWithStatusView import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.mapper.Bluesky +import dev.dimension.flare.data.database.cache.model.DbPagingTimeline +import dev.dimension.flare.data.database.cache.model.DbPagingTimelineView import dev.dimension.flare.data.network.bluesky.BlueskyService import dev.dimension.flare.model.MicroBlogKey import kotlinx.collections.immutable.persistentListOf +import kotlinx.coroutines.flow.firstOrNull import sh.christian.ozone.api.AtUri +import kotlin.uuid.Uuid @OptIn(ExperimentalPagingApi::class) internal class StatusDetailRemoteMediator( @@ -25,10 +28,10 @@ internal class StatusDetailRemoteMediator( private val database: CacheDatabase, private val pagingKey: String, private val statusOnly: Boolean, -) : RemoteMediator() { +) : RemoteMediator() { override suspend fun load( loadType: LoadType, - state: PagingState, + state: PagingState, ): MediatorResult { return try { if (loadType != LoadType.REFRESH) { @@ -36,14 +39,20 @@ internal class StatusDetailRemoteMediator( endOfPaginationReached = true, ) } - if (!database.dbPagingTimelineQueries.existsPaging(accountKey, pagingKey).executeAsOne()) { - database.dbStatusQueries.get(statusKey, accountKey).executeAsOneOrNull()?.let { - database.dbPagingTimelineQueries - .insert( - account_key = accountKey, - status_key = statusKey, - paging_key = pagingKey, - sort_id = 0, + if (!database.pagingTimelineDao().existsPaging(accountKey, pagingKey)) { + database.statusDao().get(statusKey, accountKey).firstOrNull()?.let { + database + .pagingTimelineDao() + .insertAll( + listOf( + DbPagingTimeline( + accountKey = accountKey, + statusKey = statusKey, + pagingKey = pagingKey, + sortId = 0, + _id = Uuid.random().toString(), + ), + ), ) } } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/UserTimelineRemoteMediator.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/UserTimelineRemoteMediator.kt index 24fd35c05..aa3a8fa8a 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/UserTimelineRemoteMediator.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/UserTimelineRemoteMediator.kt @@ -6,9 +6,9 @@ import androidx.paging.PagingState import androidx.paging.RemoteMediator import app.bsky.feed.GetAuthorFeedFilter import app.bsky.feed.GetAuthorFeedQueryParams -import dev.dimension.flare.data.cache.DbPagingTimelineWithStatusView import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.mapper.Bluesky +import dev.dimension.flare.data.database.cache.model.DbPagingTimelineView import dev.dimension.flare.data.network.bluesky.BlueskyService import dev.dimension.flare.model.MicroBlogKey import sh.christian.ozone.api.Did @@ -21,12 +21,12 @@ internal class UserTimelineRemoteMediator( private val userKey: MicroBlogKey, private val pagingKey: String, private val onlyMedia: Boolean, -) : RemoteMediator() { +) : RemoteMediator() { var cursor: String? = null override suspend fun load( loadType: LoadType, - state: PagingState, + state: PagingState, ): MediatorResult { return try { val response = diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/GuestDataSource.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/GuestDataSource.kt index 052a9fb9c..ec2f70320 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/GuestDataSource.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/GuestDataSource.kt @@ -4,8 +4,6 @@ import androidx.paging.Pager import androidx.paging.PagingConfig import androidx.paging.PagingData import androidx.paging.cachedIn -import app.cash.sqldelight.coroutines.asFlow -import app.cash.sqldelight.coroutines.mapToOneNotNull import dev.dimension.flare.common.CacheData import dev.dimension.flare.common.Cacheable import dev.dimension.flare.common.MemCacheable @@ -33,11 +31,9 @@ import dev.dimension.flare.ui.model.UiTimeline import dev.dimension.flare.ui.model.UiUserV2 import dev.dimension.flare.ui.model.mapper.render import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.IO import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapNotNull import org.koin.core.component.KoinComponent import org.koin.core.component.inject @@ -75,21 +71,13 @@ object GuestDataSource : MicroblogDataSource, KoinComponent, StatusEvent.Mastodo GuestMastodonService .lookupUserByAcct("$name@$host") ?.toDbUser(GuestMastodonService.HOST) ?: throw Exception("User not found") - database.dbUserQueries.insert( - user_key = user.user_key, - platform_type = user.platform_type, - name = user.name, - handle = user.handle, - host = user.host, - content = user.content, - ) + database.userDao().insert(user) }, cacheSource = { - database.dbUserQueries + database + .userDao() .findByHandleAndHost(name, host, PlatformType.Mastodon) - .asFlow() - .mapToOneNotNull(Dispatchers.IO) - .map { it.render(account.accountKey) } + .mapNotNull { it?.render(account.accountKey) } }, ) } @@ -99,21 +87,13 @@ object GuestDataSource : MicroblogDataSource, KoinComponent, StatusEvent.Mastodo return Cacheable( fetchSource = { val user = GuestMastodonService.lookupUser(id).toDbUser(GuestMastodonService.HOST) - database.dbUserQueries.insert( - user_key = user.user_key, - platform_type = user.platform_type, - name = user.name, - handle = user.handle, - host = user.host, - content = user.content, - ) + database.userDao().insert(user) }, cacheSource = { - database.dbUserQueries + database + .userDao() .findByKey(userKey) - .asFlow() - .mapToOneNotNull(Dispatchers.IO) - .map { it.render(account.accountKey) } + .mapNotNull { it?.render(account.accountKey) } }, ) } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/BookmarkTimelineRemoteMediator.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/BookmarkTimelineRemoteMediator.kt index 5b618af5f..1e2c1c1fc 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/BookmarkTimelineRemoteMediator.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/BookmarkTimelineRemoteMediator.kt @@ -4,9 +4,9 @@ import androidx.paging.ExperimentalPagingApi import androidx.paging.LoadType import androidx.paging.PagingState import androidx.paging.RemoteMediator -import dev.dimension.flare.data.cache.DbPagingTimelineWithStatusView import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.mapper.Mastodon +import dev.dimension.flare.data.database.cache.model.DbPagingTimelineView import dev.dimension.flare.data.network.mastodon.MastodonService import dev.dimension.flare.model.MicroBlogKey @@ -16,10 +16,10 @@ internal class BookmarkTimelineRemoteMediator( private val database: CacheDatabase, private val accountKey: MicroBlogKey, private val pagingKey: String, -) : RemoteMediator() { +) : RemoteMediator() { override suspend fun load( loadType: LoadType, - state: PagingState, + state: PagingState, ): MediatorResult { return try { val response = @@ -29,9 +29,7 @@ internal class BookmarkTimelineRemoteMediator( .bookmarks( limit = state.config.pageSize, ).also { - database.transaction { - database.dbPagingTimelineQueries.deletePaging(accountKey, pagingKey) - } + database.pagingTimelineDao().delete(pagingKey = pagingKey, accountKey = accountKey) } } LoadType.PREPEND -> { @@ -48,7 +46,7 @@ internal class BookmarkTimelineRemoteMediator( ) service.bookmarks( limit = state.config.pageSize, - max_id = lastItem.timeline_status_key.id, + max_id = lastItem.timeline.statusKey.id, ) } } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/DiscoverStatusRemoteMediator.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/DiscoverStatusRemoteMediator.kt index 2fb0ac6ab..985cf9f0d 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/DiscoverStatusRemoteMediator.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/DiscoverStatusRemoteMediator.kt @@ -4,9 +4,9 @@ import androidx.paging.ExperimentalPagingApi import androidx.paging.LoadType import androidx.paging.PagingState import androidx.paging.RemoteMediator -import dev.dimension.flare.data.cache.DbPagingTimelineWithStatusView import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.mapper.Mastodon +import dev.dimension.flare.data.database.cache.model.DbPagingTimelineView import dev.dimension.flare.data.network.mastodon.MastodonService import dev.dimension.flare.model.MicroBlogKey @@ -16,10 +16,10 @@ internal class DiscoverStatusRemoteMediator( private val database: CacheDatabase, private val accountKey: MicroBlogKey, private val pagingKey: String, -) : RemoteMediator() { +) : RemoteMediator() { override suspend fun load( loadType: LoadType, - state: PagingState, + state: PagingState, ): MediatorResult { return try { val response = diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/FavouriteTimelineRemoteMediator.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/FavouriteTimelineRemoteMediator.kt index 822e88561..e5bfabc21 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/FavouriteTimelineRemoteMediator.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/FavouriteTimelineRemoteMediator.kt @@ -4,9 +4,9 @@ import androidx.paging.ExperimentalPagingApi import androidx.paging.LoadType import androidx.paging.PagingState import androidx.paging.RemoteMediator -import dev.dimension.flare.data.cache.DbPagingTimelineWithStatusView import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.mapper.Mastodon +import dev.dimension.flare.data.database.cache.model.DbPagingTimelineView import dev.dimension.flare.data.network.mastodon.MastodonService import dev.dimension.flare.model.MicroBlogKey @@ -16,10 +16,10 @@ internal class FavouriteTimelineRemoteMediator( private val database: CacheDatabase, private val accountKey: MicroBlogKey, private val pagingKey: String, -) : RemoteMediator() { +) : RemoteMediator() { override suspend fun load( loadType: LoadType, - state: PagingState, + state: PagingState, ): MediatorResult { return try { val response = @@ -29,9 +29,7 @@ internal class FavouriteTimelineRemoteMediator( .favorites( limit = state.config.pageSize, ).also { - database.transaction { - database.dbPagingTimelineQueries.deletePaging(accountKey, pagingKey) - } + database.pagingTimelineDao().delete(pagingKey = pagingKey, accountKey = accountKey) } } LoadType.PREPEND -> { @@ -48,7 +46,7 @@ internal class FavouriteTimelineRemoteMediator( ) service.favorites( limit = state.config.pageSize, - max_id = lastItem.timeline_status_key.id, + max_id = lastItem.timeline.statusKey.id, ) } } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/HomeTimelineRemoteMediator.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/HomeTimelineRemoteMediator.kt index e7bce20d9..ea6ec80d2 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/HomeTimelineRemoteMediator.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/HomeTimelineRemoteMediator.kt @@ -4,9 +4,9 @@ import androidx.paging.ExperimentalPagingApi import androidx.paging.LoadType import androidx.paging.PagingState import androidx.paging.RemoteMediator -import dev.dimension.flare.data.cache.DbPagingTimelineWithStatusView import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.mapper.Mastodon +import dev.dimension.flare.data.database.cache.model.DbPagingTimelineView import dev.dimension.flare.data.network.mastodon.MastodonService import dev.dimension.flare.model.MicroBlogKey @@ -16,12 +16,12 @@ internal class HomeTimelineRemoteMediator( private val database: CacheDatabase, private val accountKey: MicroBlogKey, private val pagingKey: String, -) : RemoteMediator() { +) : RemoteMediator() { override suspend fun initialize(): InitializeAction = InitializeAction.SKIP_INITIAL_REFRESH override suspend fun load( loadType: LoadType, - state: PagingState, + state: PagingState, ): MediatorResult { return try { val response = @@ -31,16 +31,14 @@ internal class HomeTimelineRemoteMediator( .homeTimeline( limit = state.config.pageSize, ).also { - database.transaction { - database.dbPagingTimelineQueries.deletePaging(accountKey, pagingKey) - } + database.pagingTimelineDao().delete(pagingKey = pagingKey, accountKey = accountKey) } } LoadType.PREPEND -> { val firstItem = state.firstItemOrNull() service.homeTimeline( limit = state.config.pageSize, - min_id = firstItem?.timeline_status_key?.id, + min_id = firstItem?.timeline?.statusKey?.id, ) } @@ -52,7 +50,7 @@ internal class HomeTimelineRemoteMediator( ) service.homeTimeline( limit = state.config.pageSize, - max_id = lastItem.timeline_status_key.id, + max_id = lastItem.timeline.statusKey.id, ) } } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/ListTimelineRemoteMediator.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/ListTimelineRemoteMediator.kt index b18a467df..0955558b4 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/ListTimelineRemoteMediator.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/ListTimelineRemoteMediator.kt @@ -4,9 +4,9 @@ import androidx.paging.ExperimentalPagingApi import androidx.paging.LoadType import androidx.paging.PagingState import androidx.paging.RemoteMediator -import dev.dimension.flare.data.cache.DbPagingTimelineWithStatusView import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.mapper.Mastodon +import dev.dimension.flare.data.database.cache.model.DbPagingTimelineView import dev.dimension.flare.data.network.mastodon.MastodonService import dev.dimension.flare.model.MicroBlogKey @@ -17,10 +17,10 @@ internal class ListTimelineRemoteMediator( private val database: CacheDatabase, private val accountKey: MicroBlogKey, private val pagingKey: String, -) : RemoteMediator() { +) : RemoteMediator() { override suspend fun load( loadType: LoadType, - state: PagingState, + state: PagingState, ): MediatorResult { return try { val response = @@ -31,9 +31,7 @@ internal class ListTimelineRemoteMediator( listId = listId, limit = state.config.pageSize, ).also { - database.transaction { - database.dbPagingTimelineQueries.deletePaging(accountKey, pagingKey) - } + database.pagingTimelineDao().delete(pagingKey = pagingKey, accountKey = accountKey) } } LoadType.PREPEND -> { @@ -51,7 +49,7 @@ internal class ListTimelineRemoteMediator( service.listTimeline( listId = listId, limit = state.config.pageSize, - max_id = lastItem.timeline_status_key.id, + max_id = lastItem.timeline.statusKey.id, ) } } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MastodonDataSource.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MastodonDataSource.kt index 3399219c4..298b093c6 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MastodonDataSource.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MastodonDataSource.kt @@ -8,8 +8,6 @@ import androidx.paging.PagingData import androidx.paging.PagingState import androidx.paging.RemoteMediator import androidx.paging.cachedIn -import app.cash.sqldelight.coroutines.asFlow -import app.cash.sqldelight.coroutines.mapToOneNotNull import dev.dimension.flare.common.CacheData import dev.dimension.flare.common.Cacheable import dev.dimension.flare.common.MemCacheable @@ -59,15 +57,11 @@ import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.IO import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.launch import org.koin.core.component.KoinComponent import org.koin.core.component.inject -import kotlin.uuid.ExperimentalUuidApi import kotlin.uuid.Uuid @OptIn(ExperimentalPagingApi::class) @@ -266,21 +260,13 @@ class MastodonDataSource( service .lookupUserByAcct("$name@$host") ?.toDbUser(account.accountKey.host) ?: throw Exception("User not found") - database.dbUserQueries.insert( - user_key = user.user_key, - platform_type = user.platform_type, - name = user.name, - handle = user.handle, - host = user.host, - content = user.content, - ) + database.userDao().insert(user) }, cacheSource = { - database.dbUserQueries + database + .userDao() .findByHandleAndHost(name, host, PlatformType.Mastodon) - .asFlow() - .mapToOneNotNull(Dispatchers.IO) - .map { it.render(account.accountKey) } + .mapNotNull { it?.render(account.accountKey) } }, ) } @@ -290,21 +276,13 @@ class MastodonDataSource( return Cacheable( fetchSource = { val user = service.lookupUser(id).toDbUser(account.accountKey.host) - database.dbUserQueries.insert( - user_key = user.user_key, - platform_type = user.platform_type, - name = user.name, - handle = user.handle, - host = user.host, - content = user.content, - ) + database.userDao().insert(user) }, cacheSource = { - database.dbUserQueries + database + .userDao() .findByKey(userKey) - .asFlow() - .mapToOneNotNull(Dispatchers.IO) - .map { it.render(account.accountKey) } + .mapNotNull { it?.render(account.accountKey) } }, ) } @@ -381,11 +359,10 @@ class MastodonDataSource( ) }, cacheSource = { - database.dbStatusQueries + database + .statusDao() .get(statusKey, account.accountKey) - .asFlow() - .mapToOneNotNull(Dispatchers.IO) - .mapNotNull { it.content.render(account.accountKey, this) } + .mapNotNull { it?.content?.render(account.accountKey, this) } }, ) } @@ -394,21 +371,16 @@ class MastodonDataSource( Cacheable( fetchSource = { val emojis = service.emojis() - database.dbEmojiQueries.insert( - account.accountKey.host, - emojis.toDb(account.accountKey.host).content, - ) + database.emojiDao().insert(emojis.toDb(account.accountKey.host)) }, cacheSource = { - database.dbEmojiQueries + database + .emojiDao() .get(account.accountKey.host) - .asFlow() - .mapToOneNotNull(Dispatchers.IO) - .map { it.toUi().toImmutableList() } + .mapNotNull { it?.toUi()?.toImmutableList() } }, ) - @OptIn(ExperimentalUuidApi::class) override suspend fun compose( data: ComposeData, progress: (ComposeProgress) -> Unit, @@ -588,13 +560,13 @@ class MastodonDataSource( runCatching { service.delete(statusKey.id) // delete status from cache - database.dbStatusQueries.delete( - status_key = statusKey, - account_key = account.accountKey, + database.statusDao().delete( + statusKey = statusKey, + accountKey = account.accountKey, ) - database.dbPagingTimelineQueries.deleteStatus( - account_key = account.accountKey, - status_key = statusKey, + database.pagingTimelineDao().deleteStatus( + accountKey = account.accountKey, + statusKey = statusKey, ) } } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MentionRemoteMediator.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MentionRemoteMediator.kt index c57960520..bd1747dc4 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MentionRemoteMediator.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MentionRemoteMediator.kt @@ -4,9 +4,9 @@ import androidx.paging.ExperimentalPagingApi import androidx.paging.LoadType import androidx.paging.PagingState import androidx.paging.RemoteMediator -import dev.dimension.flare.data.cache.DbPagingTimelineWithStatusView import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.mapper.Mastodon +import dev.dimension.flare.data.database.cache.model.DbPagingTimelineView import dev.dimension.flare.data.network.mastodon.MastodonService import dev.dimension.flare.data.network.mastodon.api.model.NotificationTypes import dev.dimension.flare.model.MicroBlogKey @@ -17,10 +17,10 @@ internal class MentionRemoteMediator( private val database: CacheDatabase, private val accountKey: MicroBlogKey, private val pagingKey: String, -) : RemoteMediator() { +) : RemoteMediator() { override suspend fun load( loadType: LoadType, - state: PagingState, + state: PagingState, ): MediatorResult { return try { val response = @@ -31,16 +31,14 @@ internal class MentionRemoteMediator( limit = state.config.pageSize, exclude_types = NotificationTypes.entries.filter { it != NotificationTypes.Mention }, ).also { - database.transaction { - database.dbPagingTimelineQueries.deletePaging(accountKey, pagingKey) - } + database.pagingTimelineDao().delete(pagingKey = pagingKey, accountKey = accountKey) } } LoadType.PREPEND -> { val firstItem = state.firstItemOrNull() service.notification( limit = state.config.pageSize, - min_id = firstItem?.timeline_status_key?.id, + min_id = firstItem?.timeline?.statusKey?.id, exclude_types = NotificationTypes.entries.filter { it != NotificationTypes.Mention }, ) } @@ -53,7 +51,7 @@ internal class MentionRemoteMediator( ) service.notification( limit = state.config.pageSize, - max_id = lastItem.timeline_status_key.id, + max_id = lastItem.timeline.statusKey.id, exclude_types = NotificationTypes.entries.filter { it != NotificationTypes.Mention }, ) } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/NotificationRemoteMediator.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/NotificationRemoteMediator.kt index ecbea28b0..0a7871024 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/NotificationRemoteMediator.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/NotificationRemoteMediator.kt @@ -4,9 +4,9 @@ import androidx.paging.ExperimentalPagingApi import androidx.paging.LoadType import androidx.paging.PagingState import androidx.paging.RemoteMediator -import dev.dimension.flare.data.cache.DbPagingTimelineWithStatusView import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.mapper.Mastodon +import dev.dimension.flare.data.database.cache.model.DbPagingTimelineView import dev.dimension.flare.data.network.mastodon.MastodonService import dev.dimension.flare.model.MicroBlogKey @@ -16,10 +16,10 @@ internal class NotificationRemoteMediator( private val database: CacheDatabase, private val accountKey: MicroBlogKey, private val pagingKey: String, -) : RemoteMediator() { +) : RemoteMediator() { override suspend fun load( loadType: LoadType, - state: PagingState, + state: PagingState, ): MediatorResult { return try { val response = @@ -29,9 +29,7 @@ internal class NotificationRemoteMediator( .notification( limit = state.config.pageSize, ).also { - database.transaction { - database.dbPagingTimelineQueries.deletePaging(accountKey, pagingKey) - } + database.pagingTimelineDao().delete(pagingKey = pagingKey, accountKey = accountKey) } } @@ -39,7 +37,7 @@ internal class NotificationRemoteMediator( val firstItem = state.firstItemOrNull() service.notification( limit = state.config.pageSize, - min_id = firstItem?.timeline_status_key?.id, + min_id = firstItem?.timeline?.statusKey?.id, ) } @@ -51,7 +49,7 @@ internal class NotificationRemoteMediator( ) service.notification( limit = state.config.pageSize, - max_id = lastItem.timeline_status_key.id, + max_id = lastItem.timeline.statusKey.id, ) } } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/PublicTimelineRemoteMediator.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/PublicTimelineRemoteMediator.kt index 89e144930..bf5774d84 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/PublicTimelineRemoteMediator.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/PublicTimelineRemoteMediator.kt @@ -4,9 +4,9 @@ import androidx.paging.ExperimentalPagingApi import androidx.paging.LoadType import androidx.paging.PagingState import androidx.paging.RemoteMediator -import dev.dimension.flare.data.cache.DbPagingTimelineWithStatusView import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.mapper.Mastodon +import dev.dimension.flare.data.database.cache.model.DbPagingTimelineView import dev.dimension.flare.data.network.mastodon.MastodonService import dev.dimension.flare.model.MicroBlogKey @@ -17,10 +17,10 @@ internal class PublicTimelineRemoteMediator( private val accountKey: MicroBlogKey, private val pagingKey: String, private val local: Boolean, -) : RemoteMediator() { +) : RemoteMediator() { override suspend fun load( loadType: LoadType, - state: PagingState, + state: PagingState, ): MediatorResult { return try { val response = @@ -31,9 +31,7 @@ internal class PublicTimelineRemoteMediator( limit = state.config.pageSize, local = local, ).also { - database.transaction { - database.dbPagingTimelineQueries.deletePaging(accountKey, pagingKey) - } + database.pagingTimelineDao().delete(pagingKey = pagingKey, accountKey = accountKey) } } LoadType.PREPEND -> { @@ -50,7 +48,7 @@ internal class PublicTimelineRemoteMediator( ) service.publicTimeline( limit = state.config.pageSize, - max_id = lastItem.timeline_status_key.id, + max_id = lastItem.timeline.statusKey.id, local = local, ) } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/SearchStatusPagingSource.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/SearchStatusPagingSource.kt index a132d5769..6bcae85b9 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/SearchStatusPagingSource.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/SearchStatusPagingSource.kt @@ -4,9 +4,9 @@ import androidx.paging.ExperimentalPagingApi import androidx.paging.LoadType import androidx.paging.PagingState import androidx.paging.RemoteMediator -import dev.dimension.flare.data.cache.DbPagingTimelineWithStatusView import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.mapper.Mastodon +import dev.dimension.flare.data.database.cache.model.DbPagingTimelineView import dev.dimension.flare.data.network.mastodon.MastodonService import dev.dimension.flare.model.MicroBlogKey @@ -17,10 +17,10 @@ internal class SearchStatusPagingSource( private val accountKey: MicroBlogKey, private val pagingKey: String, private val query: String, -) : RemoteMediator() { +) : RemoteMediator() { override suspend fun load( loadType: LoadType, - state: PagingState, + state: PagingState, ): MediatorResult { return try { val response = @@ -45,7 +45,7 @@ internal class SearchStatusPagingSource( type = "statuses", ).statuses }.also { - database.dbPagingTimelineQueries.deletePaging(accountKey, pagingKey) + database.pagingTimelineDao().delete(pagingKey = pagingKey, accountKey = accountKey) } } @@ -59,14 +59,14 @@ internal class SearchStatusPagingSource( service.hashtagTimeline( hashtag = query.removePrefix("#"), limit = state.config.pageSize, - max_id = lastItem.timeline_status_key.id, + max_id = lastItem.timeline.statusKey.id, ) } else { service .searchV2( query = query, limit = state.config.pageSize, - max_id = lastItem.timeline_status_key.id, + max_id = lastItem.timeline.statusKey.id, type = "statuses", ).statuses } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/StatusDetailRemoteMediator.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/StatusDetailRemoteMediator.kt index a4ceda89e..28d6b4261 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/StatusDetailRemoteMediator.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/StatusDetailRemoteMediator.kt @@ -4,11 +4,14 @@ import androidx.paging.ExperimentalPagingApi import androidx.paging.LoadType import androidx.paging.PagingState import androidx.paging.RemoteMediator -import dev.dimension.flare.data.cache.DbPagingTimelineWithStatusView import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.mapper.Mastodon +import dev.dimension.flare.data.database.cache.model.DbPagingTimeline +import dev.dimension.flare.data.database.cache.model.DbPagingTimelineView import dev.dimension.flare.data.network.mastodon.MastodonService import dev.dimension.flare.model.MicroBlogKey +import kotlinx.coroutines.flow.firstOrNull +import kotlin.uuid.Uuid @OptIn(ExperimentalPagingApi::class) internal class StatusDetailRemoteMediator( @@ -18,10 +21,10 @@ internal class StatusDetailRemoteMediator( private val accountKey: MicroBlogKey, private val pagingKey: String, private val statusOnly: Boolean, -) : RemoteMediator() { +) : RemoteMediator() { override suspend fun load( loadType: LoadType, - state: PagingState, + state: PagingState, ): MediatorResult { return try { if (loadType != LoadType.REFRESH) { @@ -29,14 +32,20 @@ internal class StatusDetailRemoteMediator( endOfPaginationReached = true, ) } - if (!database.dbPagingTimelineQueries.existsPaging(accountKey, pagingKey).executeAsOne()) { - database.dbStatusQueries.get(statusKey, accountKey).executeAsOneOrNull()?.let { - database.dbPagingTimelineQueries - .insert( - account_key = accountKey, - status_key = statusKey, - paging_key = pagingKey, - sort_id = 0, + if (!database.pagingTimelineDao().existsPaging(accountKey, pagingKey)) { + database.statusDao().get(statusKey, accountKey).firstOrNull()?.let { + database + .pagingTimelineDao() + .insertAll( + listOf( + DbPagingTimeline( + accountKey = accountKey, + statusKey = statusKey, + pagingKey = pagingKey, + sortId = 0, + _id = Uuid.random().toString(), + ), + ), ) } } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/UserTimelineRemoteMediator.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/UserTimelineRemoteMediator.kt index 740b32f86..8f0d469d9 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/UserTimelineRemoteMediator.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/UserTimelineRemoteMediator.kt @@ -4,9 +4,9 @@ import androidx.paging.ExperimentalPagingApi import androidx.paging.LoadType import androidx.paging.PagingState import androidx.paging.RemoteMediator -import dev.dimension.flare.data.cache.DbPagingTimelineWithStatusView import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.mapper.Mastodon +import dev.dimension.flare.data.database.cache.model.DbPagingTimelineView import dev.dimension.flare.data.network.mastodon.MastodonService import dev.dimension.flare.model.MicroBlogKey @@ -18,10 +18,10 @@ internal class UserTimelineRemoteMediator( private val userKey: MicroBlogKey, private val pagingKey: String, private val onlyMedia: Boolean, -) : RemoteMediator() { +) : RemoteMediator() { override suspend fun load( loadType: LoadType, - state: PagingState, + state: PagingState, ): MediatorResult { return try { val response = @@ -38,7 +38,7 @@ internal class UserTimelineRemoteMediator( service.userTimeline( user_id = userKey.id, limit = state.config.pageSize, - min_id = firstItem?.timeline_status_key?.id, + min_id = firstItem?.timeline?.statusKey?.id, only_media = onlyMedia, ) } @@ -52,7 +52,7 @@ internal class UserTimelineRemoteMediator( service.userTimeline( user_id = userKey.id, limit = state.config.pageSize, - max_id = lastItem.timeline_status_key.id, + max_id = lastItem.timeline.statusKey.id, only_media = onlyMedia, ) } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/Paging.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/Paging.kt index 767a37e01..00b9e60b5 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/Paging.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/Paging.kt @@ -10,9 +10,8 @@ import androidx.paging.RemoteMediator import androidx.paging.cachedIn import androidx.paging.filter import androidx.paging.map -import app.cash.sqldelight.paging3.QueryPagingSource -import dev.dimension.flare.data.cache.DbPagingTimelineWithStatusView import dev.dimension.flare.data.database.cache.CacheDatabase +import dev.dimension.flare.data.database.cache.model.DbPagingTimelineView import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.ui.model.UiTimeline import dev.dimension.flare.ui.model.mapper.render @@ -38,29 +37,16 @@ internal fun StatusEvent.timelinePager( database: CacheDatabase, scope: CoroutineScope, filterFlow: Flow>, - mediator: RemoteMediator, + mediator: RemoteMediator, ): Flow> { val pagerFlow = Pager( config = PagingConfig(pageSize = pageSize), remoteMediator = mediator, pagingSourceFactory = { - QueryPagingSource( - countQuery = - database.dbPagingTimelineQueries.pageCount( - account_key = accountKey, - paging_key = pagingKey, - ), - transacter = database.dbPagingTimelineQueries, - context = Dispatchers.IO, - queryProvider = { limit, offset -> - database.dbPagingTimelineQueries.getPage( - account_key = accountKey, - paging_key = pagingKey, - offset = offset, - limit = limit, - ) - }, + database.pagingTimelineDao().getDbPagingTimelineView( + accountKey = accountKey, + pagingKey = pagingKey, ) }, ).flow.cachedIn(scope) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/DiscoverStatusRemoteMediator.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/DiscoverStatusRemoteMediator.kt index 55f829828..b8cf844a2 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/DiscoverStatusRemoteMediator.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/DiscoverStatusRemoteMediator.kt @@ -4,9 +4,9 @@ import androidx.paging.ExperimentalPagingApi import androidx.paging.LoadType import androidx.paging.PagingState import androidx.paging.RemoteMediator -import dev.dimension.flare.data.cache.DbPagingTimelineWithStatusView import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.mapper.Misskey +import dev.dimension.flare.data.database.cache.model.DbPagingTimelineView import dev.dimension.flare.data.network.misskey.MisskeyService import dev.dimension.flare.data.network.misskey.api.model.NotesFeaturedRequest import dev.dimension.flare.model.MicroBlogKey @@ -17,10 +17,10 @@ internal class DiscoverStatusRemoteMediator( private val database: CacheDatabase, private val accountKey: MicroBlogKey, private val pagingKey: String, -) : RemoteMediator() { +) : RemoteMediator() { override suspend fun load( loadType: LoadType, - state: PagingState, + state: PagingState, ): MediatorResult { return try { val response = @@ -32,7 +32,12 @@ internal class DiscoverStatusRemoteMediator( service.notesFeatured( NotesFeaturedRequest( limit = state.config.pageSize, - untilId = state.lastItemOrNull()?.timeline_status_key?.id, + untilId = + state + .lastItemOrNull() + ?.timeline + ?.statusKey + ?.id, ), ) } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/HomeTimelineRemoteMediator.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/HomeTimelineRemoteMediator.kt index 0baac200f..f19fcd9f7 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/HomeTimelineRemoteMediator.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/HomeTimelineRemoteMediator.kt @@ -4,9 +4,9 @@ import androidx.paging.ExperimentalPagingApi import androidx.paging.LoadType import androidx.paging.PagingState import androidx.paging.RemoteMediator -import dev.dimension.flare.data.cache.DbPagingTimelineWithStatusView import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.mapper.Misskey +import dev.dimension.flare.data.database.cache.model.DbPagingTimelineView import dev.dimension.flare.data.network.misskey.MisskeyService import dev.dimension.flare.data.network.misskey.api.model.NotesHybridTimelineRequest import dev.dimension.flare.ui.model.UiAccount @@ -17,10 +17,10 @@ internal class HomeTimelineRemoteMediator( private val service: MisskeyService, private val database: CacheDatabase, private val pagingKey: String, -) : RemoteMediator() { +) : RemoteMediator() { override suspend fun load( loadType: LoadType, - state: PagingState, + state: PagingState, ): MediatorResult { return try { val response = @@ -45,7 +45,7 @@ internal class HomeTimelineRemoteMediator( service.notesTimeline( NotesHybridTimelineRequest( limit = state.config.pageSize, - untilId = lastItem.timeline_status_key.id, + untilId = lastItem.timeline.statusKey.id, ), ) } @@ -53,7 +53,7 @@ internal class HomeTimelineRemoteMediator( endOfPaginationReached = true, ) if (loadType == LoadType.REFRESH) { - database.dbPagingTimelineQueries.deletePaging(account.accountKey, pagingKey) + database.pagingTimelineDao().delete(pagingKey = pagingKey, accountKey = account.accountKey) } Misskey.save( database = database, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/LocalTimelineRemoteMediator.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/LocalTimelineRemoteMediator.kt index e4f536150..cc7fbafbc 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/LocalTimelineRemoteMediator.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/LocalTimelineRemoteMediator.kt @@ -4,9 +4,9 @@ import androidx.paging.ExperimentalPagingApi import androidx.paging.LoadType import androidx.paging.PagingState import androidx.paging.RemoteMediator -import dev.dimension.flare.data.cache.DbPagingTimelineWithStatusView import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.mapper.Misskey +import dev.dimension.flare.data.database.cache.model.DbPagingTimelineView import dev.dimension.flare.data.network.misskey.MisskeyService import dev.dimension.flare.data.network.misskey.api.model.NotesLocalTimelineRequest import dev.dimension.flare.ui.model.UiAccount @@ -17,10 +17,10 @@ internal class LocalTimelineRemoteMediator( private val service: MisskeyService, private val database: CacheDatabase, private val pagingKey: String, -) : RemoteMediator() { +) : RemoteMediator() { override suspend fun load( loadType: LoadType, - state: PagingState, + state: PagingState, ): MediatorResult { return try { val response = @@ -45,7 +45,7 @@ internal class LocalTimelineRemoteMediator( service.notesLocalTimeline( NotesLocalTimelineRequest( limit = state.config.pageSize, - untilId = lastItem.timeline_status_key.id, + untilId = lastItem.timeline.statusKey.id, ), ) } @@ -53,7 +53,7 @@ internal class LocalTimelineRemoteMediator( endOfPaginationReached = true, ) if (loadType == LoadType.REFRESH) { - database.dbPagingTimelineQueries.deletePaging(account.accountKey, pagingKey) + database.pagingTimelineDao().delete(pagingKey = pagingKey, accountKey = account.accountKey) } Misskey.save( database = database, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/MentionTimelineRemoteMediator.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/MentionTimelineRemoteMediator.kt index 75dd6044d..dcb115821 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/MentionTimelineRemoteMediator.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/MentionTimelineRemoteMediator.kt @@ -4,9 +4,9 @@ import androidx.paging.ExperimentalPagingApi import androidx.paging.LoadType import androidx.paging.PagingState import androidx.paging.RemoteMediator -import dev.dimension.flare.data.cache.DbPagingTimelineWithStatusView import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.mapper.Misskey +import dev.dimension.flare.data.database.cache.model.DbPagingTimelineView import dev.dimension.flare.data.network.misskey.MisskeyService import dev.dimension.flare.data.network.misskey.api.model.NotesMentionsRequest import dev.dimension.flare.ui.model.UiAccount @@ -17,10 +17,10 @@ internal class MentionTimelineRemoteMediator( private val service: MisskeyService, private val database: CacheDatabase, private val pagingKey: String, -) : RemoteMediator() { +) : RemoteMediator() { override suspend fun load( loadType: LoadType, - state: PagingState, + state: PagingState, ): MediatorResult { return try { val response = @@ -45,7 +45,7 @@ internal class MentionTimelineRemoteMediator( service.notesMentions( NotesMentionsRequest( limit = state.config.pageSize, - untilId = lastItem.timeline_status_key.id, + untilId = lastItem.timeline.statusKey.id, ), ) } @@ -53,7 +53,7 @@ internal class MentionTimelineRemoteMediator( endOfPaginationReached = true, ) if (loadType == LoadType.REFRESH) { - database.dbPagingTimelineQueries.deletePaging(account.accountKey, pagingKey) + database.pagingTimelineDao().delete(pagingKey = pagingKey, accountKey = account.accountKey) } Misskey.save( database = database, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/MisskeyDataSource.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/MisskeyDataSource.kt index e5669c285..77d93995d 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/MisskeyDataSource.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/MisskeyDataSource.kt @@ -5,8 +5,6 @@ import androidx.paging.Pager import androidx.paging.PagingConfig import androidx.paging.PagingData import androidx.paging.cachedIn -import app.cash.sqldelight.coroutines.asFlow -import app.cash.sqldelight.coroutines.mapToOneNotNull import dev.dimension.flare.common.CacheData import dev.dimension.flare.common.Cacheable import dev.dimension.flare.common.MemCacheable @@ -49,10 +47,7 @@ import dev.dimension.flare.ui.model.mapper.toUi import dev.dimension.flare.ui.model.toUi import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.IO import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.launch import org.koin.core.component.KoinComponent @@ -189,21 +184,13 @@ class MisskeyDataSource( .body() ?.toDbUser(account.accountKey.host) ?: throw Exception("User not found") - database.dbUserQueries.insert( - user_key = user.user_key, - platform_type = user.platform_type, - name = user.name, - handle = user.handle, - host = user.host, - content = user.content, - ) + database.userDao().insert(user) }, cacheSource = { - database.dbUserQueries + database + .userDao() .findByHandleAndHost(name, host, PlatformType.Misskey) - .asFlow() - .mapToOneNotNull(Dispatchers.IO) - .map { it.render(account.accountKey) } + .mapNotNull { it?.render(account.accountKey) } }, ) } @@ -218,21 +205,13 @@ class MisskeyDataSource( .body() ?.toDbUser(account.accountKey.host) ?: throw Exception("User not found") - database.dbUserQueries.insert( - user_key = user.user_key, - platform_type = user.platform_type, - name = user.name, - handle = user.handle, - host = user.host, - content = user.content, - ) + database.userDao().insert(user) }, cacheSource = { - database.dbUserQueries + database + .userDao() .findByKey(userKey) - .asFlow() - .mapToOneNotNull(Dispatchers.IO) - .map { it.render(account.accountKey) } + .mapNotNull { it?.render(account.accountKey) } }, ) } @@ -321,11 +300,10 @@ class MisskeyDataSource( ) }, cacheSource = { - database.dbStatusQueries + database + .statusDao() .get(statusKey, account.accountKey) - .asFlow() - .mapToOneNotNull(Dispatchers.IO) - .mapNotNull { it.content.render(account.accountKey, this) } + .mapNotNull { it?.content?.render(account.accountKey, this) } }, ) } @@ -340,17 +318,15 @@ class MisskeyDataSource( ?.emojis .orEmpty() .toImmutableList() - database.dbEmojiQueries.insert( - account.accountKey.host, - emojis.toDb(account.accountKey.host).content, + database.emojiDao().insert( + emojis.toDb(account.accountKey.host), ) }, cacheSource = { - database.dbEmojiQueries + database + .emojiDao() .get(account.accountKey.host) - .asFlow() - .mapToOneNotNull(Dispatchers.IO) - .map { it.toUi().toImmutableList() } + .mapNotNull { it?.toUi()?.toImmutableList() } }, ) @@ -421,13 +397,13 @@ class MisskeyDataSource( ) // delete status from cache - database.dbStatusQueries.delete( - status_key = statusKey, - account_key = account.accountKey, + database.statusDao().delete( + statusKey = statusKey, + accountKey = account.accountKey, ) - database.dbPagingTimelineQueries.deleteStatus( - account_key = account.accountKey, - status_key = statusKey, + database.pagingTimelineDao().deleteStatus( + accountKey = account.accountKey, + statusKey = statusKey, ) } } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/NotificationRemoteMediator.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/NotificationRemoteMediator.kt index 91e4986f2..57338ffde 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/NotificationRemoteMediator.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/NotificationRemoteMediator.kt @@ -4,9 +4,9 @@ import androidx.paging.ExperimentalPagingApi import androidx.paging.LoadType import androidx.paging.PagingState import androidx.paging.RemoteMediator -import dev.dimension.flare.data.cache.DbPagingTimelineWithStatusView import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.mapper.Misskey +import dev.dimension.flare.data.database.cache.model.DbPagingTimelineView import dev.dimension.flare.data.network.misskey.MisskeyService import dev.dimension.flare.data.network.misskey.api.model.INotificationsRequest import dev.dimension.flare.ui.model.UiAccount @@ -17,10 +17,10 @@ internal class NotificationRemoteMediator( private val service: MisskeyService, private val database: CacheDatabase, private val pagingKey: String, -) : RemoteMediator() { +) : RemoteMediator() { override suspend fun load( loadType: LoadType, - state: PagingState, + state: PagingState, ): MediatorResult { return try { val response = @@ -45,7 +45,7 @@ internal class NotificationRemoteMediator( service.iNotifications( INotificationsRequest( limit = state.config.pageSize, - untilId = lastItem.timeline_status_key.id, + untilId = lastItem.timeline.statusKey.id, ), ) } @@ -53,7 +53,7 @@ internal class NotificationRemoteMediator( endOfPaginationReached = true, ) if (loadType == LoadType.REFRESH) { - database.dbPagingTimelineQueries.deletePaging(account.accountKey, pagingKey) + database.pagingTimelineDao().delete(pagingKey = pagingKey, accountKey = account.accountKey) } Misskey.save( database = database, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/PublicTimelineRemoteMediator.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/PublicTimelineRemoteMediator.kt index 543ca3434..867cffe86 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/PublicTimelineRemoteMediator.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/PublicTimelineRemoteMediator.kt @@ -4,9 +4,9 @@ import androidx.paging.ExperimentalPagingApi import androidx.paging.LoadType import androidx.paging.PagingState import androidx.paging.RemoteMediator -import dev.dimension.flare.data.cache.DbPagingTimelineWithStatusView import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.mapper.Misskey +import dev.dimension.flare.data.database.cache.model.DbPagingTimelineView import dev.dimension.flare.data.network.misskey.MisskeyService import dev.dimension.flare.data.network.misskey.api.model.NotesGlobalTimelineRequest import dev.dimension.flare.ui.model.UiAccount @@ -17,10 +17,10 @@ internal class PublicTimelineRemoteMediator( private val service: MisskeyService, private val database: CacheDatabase, private val pagingKey: String, -) : RemoteMediator() { +) : RemoteMediator() { override suspend fun load( loadType: LoadType, - state: PagingState, + state: PagingState, ): MediatorResult { return try { val response = @@ -45,7 +45,7 @@ internal class PublicTimelineRemoteMediator( service.notesGlobalTimeline( NotesGlobalTimelineRequest( limit = state.config.pageSize, - untilId = lastItem.timeline_status_key.id, + untilId = lastItem.timeline.statusKey.id, ), ) } @@ -53,7 +53,7 @@ internal class PublicTimelineRemoteMediator( endOfPaginationReached = true, ) if (loadType == LoadType.REFRESH) { - database.dbPagingTimelineQueries.deletePaging(account.accountKey, pagingKey) + database.pagingTimelineDao().delete(pagingKey = pagingKey, accountKey = account.accountKey) } Misskey.save( database = database, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/SearchStatusRemoteMediator.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/SearchStatusRemoteMediator.kt index 2e35573d6..76e96587d 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/SearchStatusRemoteMediator.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/SearchStatusRemoteMediator.kt @@ -4,9 +4,9 @@ import androidx.paging.ExperimentalPagingApi import androidx.paging.LoadType import androidx.paging.PagingState import androidx.paging.RemoteMediator -import dev.dimension.flare.data.cache.DbPagingTimelineWithStatusView import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.mapper.Misskey +import dev.dimension.flare.data.database.cache.model.DbPagingTimelineView import dev.dimension.flare.data.network.misskey.MisskeyService import dev.dimension.flare.data.network.misskey.api.model.NotesSearchRequest import dev.dimension.flare.model.MicroBlogKey @@ -18,10 +18,10 @@ internal class SearchStatusRemoteMediator( private val accountKey: MicroBlogKey, private val pagingKey: String, private val query: String, -) : RemoteMediator() { +) : RemoteMediator() { override suspend fun load( loadType: LoadType, - state: PagingState, + state: PagingState, ): MediatorResult { return try { val response = @@ -39,7 +39,7 @@ internal class SearchStatusRemoteMediator( limit = state.config.pageSize, ), ).also { - database.dbPagingTimelineQueries.deletePaging(accountKey, pagingKey) + database.pagingTimelineDao().delete(pagingKey = pagingKey, accountKey = accountKey) } } @@ -53,7 +53,7 @@ internal class SearchStatusRemoteMediator( NotesSearchRequest( query = query, limit = state.config.pageSize, - untilId = lastItem.timeline_status_key.id, + untilId = lastItem.timeline.statusKey.id, ), ) } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/StatusDetailRemoteMediator.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/StatusDetailRemoteMediator.kt index 1a7f8cfd2..20506ea3f 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/StatusDetailRemoteMediator.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/StatusDetailRemoteMediator.kt @@ -4,14 +4,17 @@ import androidx.paging.ExperimentalPagingApi import androidx.paging.LoadType import androidx.paging.PagingState import androidx.paging.RemoteMediator -import dev.dimension.flare.data.cache.DbPagingTimelineWithStatusView import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.mapper.Misskey +import dev.dimension.flare.data.database.cache.model.DbPagingTimeline +import dev.dimension.flare.data.database.cache.model.DbPagingTimelineView import dev.dimension.flare.data.network.misskey.MisskeyService import dev.dimension.flare.data.network.misskey.api.model.IPinRequest import dev.dimension.flare.data.network.misskey.api.model.NotesChildrenRequest import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.ui.model.UiAccount +import kotlinx.coroutines.flow.firstOrNull +import kotlin.uuid.Uuid @OptIn(ExperimentalPagingApi::class) internal class StatusDetailRemoteMediator( @@ -21,10 +24,10 @@ internal class StatusDetailRemoteMediator( private val service: MisskeyService, private val pagingKey: String, private val statusOnly: Boolean, -) : RemoteMediator() { +) : RemoteMediator() { override suspend fun load( loadType: LoadType, - state: PagingState, + state: PagingState, ): MediatorResult { return try { if (loadType == LoadType.PREPEND) { @@ -32,14 +35,20 @@ internal class StatusDetailRemoteMediator( endOfPaginationReached = true, ) } - if (!database.dbPagingTimelineQueries.existsPaging(account.accountKey, pagingKey).executeAsOne()) { - database.dbStatusQueries.get(statusKey, account.accountKey).executeAsOneOrNull()?.let { - database.dbPagingTimelineQueries - .insert( - account_key = account.accountKey, - status_key = statusKey, - paging_key = pagingKey, - sort_id = 0, + if (!database.pagingTimelineDao().existsPaging(account.accountKey, pagingKey)) { + database.statusDao().get(statusKey, account.accountKey).firstOrNull()?.let { + database + .pagingTimelineDao() + .insertAll( + listOf( + DbPagingTimeline( + accountKey = account.accountKey, + statusKey = statusKey, + pagingKey = pagingKey, + sortId = 0, + _id = Uuid.random().toString(), + ), + ), ) } } @@ -71,7 +80,7 @@ internal class StatusDetailRemoteMediator( .notesChildren( NotesChildrenRequest( noteId = statusKey.id, - untilId = lastItem.timeline_status_key.id, + untilId = lastItem.timeline.statusKey.id, limit = state.config.pageSize, ), ).body() diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/UserTimelineRemoteMediator.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/UserTimelineRemoteMediator.kt index 802f0821b..868017a16 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/UserTimelineRemoteMediator.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/UserTimelineRemoteMediator.kt @@ -4,9 +4,9 @@ import androidx.paging.ExperimentalPagingApi import androidx.paging.LoadType import androidx.paging.PagingState import androidx.paging.RemoteMediator -import dev.dimension.flare.data.cache.DbPagingTimelineWithStatusView import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.mapper.Misskey +import dev.dimension.flare.data.database.cache.model.DbPagingTimelineView import dev.dimension.flare.data.network.misskey.MisskeyService import dev.dimension.flare.data.network.misskey.api.model.UsersNotesRequest import dev.dimension.flare.model.MicroBlogKey @@ -20,10 +20,10 @@ internal class UserTimelineRemoteMediator( private val database: CacheDatabase, private val pagingKey: String, private val onlyMedia: Boolean, -) : RemoteMediator() { +) : RemoteMediator() { override suspend fun load( loadType: LoadType, - state: PagingState, + state: PagingState, ): MediatorResult { return try { val response = @@ -62,7 +62,7 @@ internal class UserTimelineRemoteMediator( UsersNotesRequest( userId = userKey.id, limit = state.config.pageSize, - untilId = lastItem.timeline_status_key.id, + untilId = lastItem.timeline.statusKey.id, ).let { if (onlyMedia) { it.copy( @@ -81,7 +81,7 @@ internal class UserTimelineRemoteMediator( endOfPaginationReached = true, ) if (loadType == LoadType.REFRESH) { - database.dbPagingTimelineQueries.deletePaging(account.accountKey, pagingKey) + database.pagingTimelineDao().delete(pagingKey = pagingKey, accountKey = account.accountKey) } Misskey.save( diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/CommentChildRemoteMediator.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/CommentChildRemoteMediator.kt index 17fc21992..d98b3359d 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/CommentChildRemoteMediator.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/CommentChildRemoteMediator.kt @@ -4,9 +4,9 @@ import androidx.paging.ExperimentalPagingApi import androidx.paging.LoadType import androidx.paging.PagingState import androidx.paging.RemoteMediator -import dev.dimension.flare.data.cache.DbPagingTimelineWithStatusView import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.mapper.VVO +import dev.dimension.flare.data.database.cache.model.DbPagingTimelineView import dev.dimension.flare.data.network.vvo.VVOService import dev.dimension.flare.data.repository.LoginExpiredException import dev.dimension.flare.model.MicroBlogKey @@ -18,13 +18,13 @@ internal class CommentChildRemoteMediator( private val accountKey: MicroBlogKey, private val pagingKey: String, private val database: CacheDatabase, -) : RemoteMediator() { +) : RemoteMediator() { private var maxId: Long? = null private var page = 0 override suspend fun load( loadType: LoadType, - state: PagingState, + state: PagingState, ): MediatorResult { return try { val config = service.config() @@ -41,9 +41,7 @@ internal class CommentChildRemoteMediator( .getHotFlowChild( cid = commentKey.id, ).also { - database.transaction { - database.dbPagingTimelineQueries.deletePaging(accountKey, pagingKey) - } + database.pagingTimelineDao().delete(pagingKey = pagingKey, accountKey = accountKey) } } LoadType.PREPEND -> { diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/DiscoverStatusRemoteMediator.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/DiscoverStatusRemoteMediator.kt index 0d44b3022..62d43fc45 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/DiscoverStatusRemoteMediator.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/DiscoverStatusRemoteMediator.kt @@ -4,9 +4,9 @@ import androidx.paging.ExperimentalPagingApi import androidx.paging.LoadType import androidx.paging.PagingState import androidx.paging.RemoteMediator -import dev.dimension.flare.data.cache.DbPagingTimelineWithStatusView import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.mapper.VVO +import dev.dimension.flare.data.database.cache.model.DbPagingTimelineView import dev.dimension.flare.data.network.vvo.VVOService import dev.dimension.flare.data.repository.LoginExpiredException import dev.dimension.flare.model.MicroBlogKey @@ -17,13 +17,13 @@ internal class DiscoverStatusRemoteMediator( private val database: CacheDatabase, private val accountKey: MicroBlogKey, private val pagingKey: String, -) : RemoteMediator() { +) : RemoteMediator() { private var page = 0 private val containerId = "102803" override suspend fun load( loadType: LoadType, - state: PagingState, + state: PagingState, ): MediatorResult { return try { val config = service.config() @@ -37,9 +37,7 @@ internal class DiscoverStatusRemoteMediator( LoadType.REFRESH -> { page = 0 service.getContainerIndex(containerId = containerId).also { - database.transaction { - database.dbPagingTimelineQueries.deletePaging(accountKey, pagingKey) - } + database.pagingTimelineDao().delete(pagingKey = pagingKey, accountKey = accountKey) } } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/HomeTimelineRemoteMediator.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/HomeTimelineRemoteMediator.kt index 313f703cf..fbcecf46c 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/HomeTimelineRemoteMediator.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/HomeTimelineRemoteMediator.kt @@ -4,9 +4,9 @@ import androidx.paging.ExperimentalPagingApi import androidx.paging.LoadType import androidx.paging.PagingState import androidx.paging.RemoteMediator -import dev.dimension.flare.data.cache.DbPagingTimelineWithStatusView import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.mapper.VVO +import dev.dimension.flare.data.database.cache.model.DbPagingTimelineView import dev.dimension.flare.data.network.vvo.VVOService import dev.dimension.flare.data.repository.LoginExpiredException import dev.dimension.flare.model.MicroBlogKey @@ -17,12 +17,12 @@ internal class HomeTimelineRemoteMediator( private val database: CacheDatabase, private val accountKey: MicroBlogKey, private val pagingKey: String, -) : RemoteMediator() { +) : RemoteMediator() { override suspend fun initialize(): InitializeAction = InitializeAction.SKIP_INITIAL_REFRESH override suspend fun load( loadType: LoadType, - state: PagingState, + state: PagingState, ): MediatorResult { return try { val config = service.config() @@ -35,9 +35,7 @@ internal class HomeTimelineRemoteMediator( when (loadType) { LoadType.REFRESH -> { service.getFriendsTimeline().also { - database.transaction { - database.dbPagingTimelineQueries.deletePaging(accountKey, pagingKey) - } + database.pagingTimelineDao().delete(pagingKey = pagingKey, accountKey = accountKey) } } @@ -54,7 +52,7 @@ internal class HomeTimelineRemoteMediator( endOfPaginationReached = true, ) service.getFriendsTimeline( - maxId = lastItem.timeline_status_key.id, + maxId = lastItem.timeline.statusKey.id, ) } } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/MentionRemoteMediator.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/MentionRemoteMediator.kt index db94950f7..a2d1ad5de 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/MentionRemoteMediator.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/MentionRemoteMediator.kt @@ -4,9 +4,9 @@ import androidx.paging.ExperimentalPagingApi import androidx.paging.LoadType import androidx.paging.PagingState import androidx.paging.RemoteMediator -import dev.dimension.flare.data.cache.DbPagingTimelineWithStatusView import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.mapper.VVO +import dev.dimension.flare.data.database.cache.model.DbPagingTimelineView import dev.dimension.flare.data.network.vvo.VVOService import dev.dimension.flare.data.repository.LoginExpiredException import dev.dimension.flare.model.MicroBlogKey @@ -17,12 +17,12 @@ internal class MentionRemoteMediator( private val database: CacheDatabase, private val accountKey: MicroBlogKey, private val pagingKey: String, -) : RemoteMediator() { +) : RemoteMediator() { var page = 1 override suspend fun load( loadType: LoadType, - state: PagingState, + state: PagingState, ): MediatorResult { return try { val config = service.config() @@ -39,9 +39,7 @@ internal class MentionRemoteMediator( .getMentionsAt( page = page, ).also { - database.transaction { - database.dbPagingTimelineQueries.deletePaging(accountKey, pagingKey) - } + database.pagingTimelineDao().delete(pagingKey = pagingKey, accountKey = accountKey) } } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/SearchStatusPagingSource.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/SearchStatusPagingSource.kt index 49b2e3551..88929e0f5 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/SearchStatusPagingSource.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/SearchStatusPagingSource.kt @@ -4,9 +4,9 @@ import androidx.paging.ExperimentalPagingApi import androidx.paging.LoadType import androidx.paging.PagingState import androidx.paging.RemoteMediator -import dev.dimension.flare.data.cache.DbPagingTimelineWithStatusView import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.mapper.VVO +import dev.dimension.flare.data.database.cache.model.DbPagingTimelineView import dev.dimension.flare.data.network.vvo.VVOService import dev.dimension.flare.data.repository.LoginExpiredException import dev.dimension.flare.model.MicroBlogKey @@ -20,7 +20,7 @@ internal class SearchStatusRemoteMediator( private val accountKey: MicroBlogKey, private val pagingKey: String, private val query: String, -) : RemoteMediator() { +) : RemoteMediator() { private var page = 1 private val containerId by lazy { "100103type=1&q=$query&t=" @@ -28,7 +28,7 @@ internal class SearchStatusRemoteMediator( override suspend fun load( loadType: LoadType, - state: PagingState, + state: PagingState, ): MediatorResult { return try { val config = service.config() @@ -46,9 +46,7 @@ internal class SearchStatusRemoteMediator( containerId = containerId, pageType = "searchall", ).also { - database.transaction { - database.dbPagingTimelineQueries.deletePaging(accountKey, pagingKey) - } + database.pagingTimelineDao().delete(pagingKey = pagingKey, accountKey = accountKey) } } LoadType.PREPEND -> { diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/StatusCommentRemoteMediator.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/StatusCommentRemoteMediator.kt index 237eb468c..bdf3b729c 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/StatusCommentRemoteMediator.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/StatusCommentRemoteMediator.kt @@ -4,9 +4,9 @@ import androidx.paging.ExperimentalPagingApi import androidx.paging.LoadType import androidx.paging.PagingState import androidx.paging.RemoteMediator -import dev.dimension.flare.data.cache.DbPagingTimelineWithStatusView import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.mapper.VVO +import dev.dimension.flare.data.database.cache.model.DbPagingTimelineView import dev.dimension.flare.data.network.vvo.VVOService import dev.dimension.flare.data.repository.LoginExpiredException import dev.dimension.flare.model.MicroBlogKey @@ -18,13 +18,13 @@ internal class StatusCommentRemoteMediator( private val service: VVOService, private val statusKey: MicroBlogKey, private val accountKey: MicroBlogKey, -) : RemoteMediator() { +) : RemoteMediator() { private var maxId: Long? = null private var page = 0 override suspend fun load( loadType: LoadType, - state: PagingState, + state: PagingState, ): MediatorResult { return try { val config = service.config() @@ -43,9 +43,7 @@ internal class StatusCommentRemoteMediator( mid = statusKey.id, maxId = null, ).also { - database.transaction { - database.dbPagingTimelineQueries.deletePaging(accountKey, pagingKey) - } + database.pagingTimelineDao().delete(pagingKey = pagingKey, accountKey = accountKey) } } LoadType.PREPEND -> { diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/StatusRepostRemoteMediator.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/StatusRepostRemoteMediator.kt index 3c4542f56..597b908d1 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/StatusRepostRemoteMediator.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/StatusRepostRemoteMediator.kt @@ -4,9 +4,9 @@ import androidx.paging.ExperimentalPagingApi import androidx.paging.LoadType import androidx.paging.PagingState import androidx.paging.RemoteMediator -import dev.dimension.flare.data.cache.DbPagingTimelineWithStatusView import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.mapper.VVO +import dev.dimension.flare.data.database.cache.model.DbPagingTimelineView import dev.dimension.flare.data.network.vvo.VVOService import dev.dimension.flare.data.repository.LoginExpiredException import dev.dimension.flare.model.MicroBlogKey @@ -18,12 +18,12 @@ internal class StatusRepostRemoteMediator( private val accountKey: MicroBlogKey, private val pagingKey: String, private val database: CacheDatabase, -) : RemoteMediator() { +) : RemoteMediator() { private var page = 1 override suspend fun load( loadType: LoadType, - state: PagingState, + state: PagingState, ): MediatorResult { return try { val config = service.config() @@ -41,9 +41,7 @@ internal class StatusRepostRemoteMediator( id = statusKey.id, page = page, ).also { - database.transaction { - database.dbPagingTimelineQueries.deletePaging(accountKey, pagingKey) - } + database.pagingTimelineDao().delete(pagingKey = pagingKey, accountKey = accountKey) } } LoadType.PREPEND -> { diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/UserTimelineRemoteMediator.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/UserTimelineRemoteMediator.kt index f672e821d..0ea6147d2 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/UserTimelineRemoteMediator.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/UserTimelineRemoteMediator.kt @@ -4,9 +4,9 @@ import androidx.paging.ExperimentalPagingApi import androidx.paging.LoadType import androidx.paging.PagingState import androidx.paging.RemoteMediator -import dev.dimension.flare.data.cache.DbPagingTimelineWithStatusView import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.mapper.VVO +import dev.dimension.flare.data.database.cache.model.DbPagingTimelineView import dev.dimension.flare.data.network.vvo.VVOService import dev.dimension.flare.data.repository.LoginExpiredException import dev.dimension.flare.model.MicroBlogKey @@ -19,13 +19,13 @@ internal class UserTimelineRemoteMediator( private val accountKey: MicroBlogKey, private val pagingKey: String, private val mediaOnly: Boolean, -) : RemoteMediator() { +) : RemoteMediator() { private var containerid: String? = null var page = 0 override suspend fun load( loadType: LoadType, - state: PagingState, + state: PagingState, ): MediatorResult { if (mediaOnly) { // Not supported yet @@ -61,9 +61,7 @@ internal class UserTimelineRemoteMediator( value = userKey.id, containerId = containerid, ).also { - database.transaction { - database.dbPagingTimelineQueries.deletePaging(accountKey, pagingKey) - } + database.pagingTimelineDao().delete(pagingKey = pagingKey, accountKey = accountKey) } } @@ -84,7 +82,7 @@ internal class UserTimelineRemoteMediator( type = "uid", value = userKey.id, containerId = containerid, - sinceId = lastItem.timeline_status_key.id, + sinceId = lastItem.timeline.statusKey.id, ) } } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/VVODataSource.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/VVODataSource.kt index 92e0039fc..4aa4786bd 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/VVODataSource.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/VVODataSource.kt @@ -5,8 +5,6 @@ import androidx.paging.Pager import androidx.paging.PagingConfig import androidx.paging.PagingData import androidx.paging.cachedIn -import app.cash.sqldelight.coroutines.asFlow -import app.cash.sqldelight.coroutines.mapToOneNotNull import dev.dimension.flare.common.CacheData import dev.dimension.flare.common.Cacheable import dev.dimension.flare.common.FileItem @@ -42,10 +40,7 @@ import dev.dimension.flare.ui.model.UiUserV2 import dev.dimension.flare.ui.model.mapper.render import dev.dimension.flare.ui.model.toUi import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.IO import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.launch import org.koin.core.component.KoinComponent @@ -153,21 +148,13 @@ class VVODataSource( val profile = service.profileInfo(uid, st) val user = profile.data?.user?.toDbUser() requireNotNull(user) { "user not found" } - database.dbUserQueries.insert( - user_key = user.user_key, - platform_type = user.platform_type, - name = user.name, - handle = user.handle, - host = user.host, - content = user.content, - ) + database.userDao().insert(user) }, cacheSource = { - database.dbUserQueries + database + .userDao() .findByHandleAndHost(name, host, PlatformType.VVo) - .asFlow() - .mapToOneNotNull(Dispatchers.IO) - .map { it.render(account.accountKey) } + .mapNotNull { it?.render(account.accountKey) } }, ) } @@ -182,21 +169,13 @@ class VVODataSource( val profile = service.profileInfo(id, st) val user = profile.data?.user?.toDbUser() requireNotNull(user) { "user not found" } - database.dbUserQueries.insert( - user_key = user.user_key, - platform_type = user.platform_type, - name = user.name, - handle = user.handle, - host = user.host, - content = user.content, - ) + database.userDao().insert(user) }, cacheSource = { - database.dbUserQueries + database + .userDao() .findByKey(userKey) - .asFlow() - .mapToOneNotNull(Dispatchers.IO) - .map { it.render(account.accountKey) } + .mapNotNull { it?.render(account.accountKey) } }, ) } @@ -286,11 +265,10 @@ class VVODataSource( } }, cacheSource = { - database.dbStatusQueries + database + .statusDao() .get(statusKey, account.accountKey) - .asFlow() - .mapToOneNotNull(Dispatchers.IO) - .mapNotNull { it.content.render(account.accountKey, this) } + .mapNotNull { it?.content?.render(account.accountKey, this) } }, ) } @@ -316,11 +294,10 @@ class VVODataSource( } }, cacheSource = { - database.dbStatusQueries + database + .statusDao() .get(statusKey, account.accountKey) - .asFlow() - .mapToOneNotNull(Dispatchers.IO) - .mapNotNull { it.content.render(account.accountKey, event = this) } + .mapNotNull { it?.content?.render(account.accountKey, event = this) } }, ) } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/xqt/HomeTimelineRemoteMediator.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/xqt/HomeTimelineRemoteMediator.kt index f3072d844..96e3e75f4 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/xqt/HomeTimelineRemoteMediator.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/xqt/HomeTimelineRemoteMediator.kt @@ -5,11 +5,11 @@ import androidx.paging.LoadType import androidx.paging.PagingState import androidx.paging.RemoteMediator import dev.dimension.flare.common.encodeJson -import dev.dimension.flare.data.cache.DbPagingTimelineWithStatusView import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.mapper.XQT import dev.dimension.flare.data.database.cache.mapper.cursor import dev.dimension.flare.data.database.cache.mapper.tweets +import dev.dimension.flare.data.database.cache.model.DbPagingTimelineView import dev.dimension.flare.data.network.xqt.XQTService import dev.dimension.flare.model.MicroBlogKey import kotlinx.serialization.Required @@ -21,14 +21,14 @@ internal class HomeTimelineRemoteMediator( private val database: CacheDatabase, private val accountKey: MicroBlogKey, private val pagingKey: String, -) : RemoteMediator() { +) : RemoteMediator() { private var cursor: String? = null override suspend fun initialize(): InitializeAction = InitializeAction.SKIP_INITIAL_REFRESH override suspend fun load( loadType: LoadType, - state: PagingState, + state: PagingState, ): MediatorResult { return try { val response = @@ -42,9 +42,7 @@ internal class HomeTimelineRemoteMediator( count = state.config.pageSize.toLong(), ).encodeJson(), ).also { - database.transaction { - database.dbPagingTimelineQueries.deletePaging(accountKey, pagingKey) - } + database.pagingTimelineDao().delete(pagingKey = pagingKey, accountKey = accountKey) } } @@ -73,6 +71,7 @@ internal class HomeTimelineRemoteMediator( .orEmpty() cursor = instructions.cursor() val tweet = instructions.tweets() + XQT.save( accountKey = accountKey, pagingKey = pagingKey, @@ -94,12 +93,12 @@ internal class FeaturedTimelineRemoteMediator( private val database: CacheDatabase, private val accountKey: MicroBlogKey, private val pagingKey: String, -) : RemoteMediator() { +) : RemoteMediator() { private var cursor: String? = null override suspend fun load( loadType: LoadType, - state: PagingState, + state: PagingState, ): MediatorResult { return try { val response = @@ -113,9 +112,7 @@ internal class FeaturedTimelineRemoteMediator( count = state.config.pageSize.toLong(), ).encodeJson(), ).also { - database.transaction { - database.dbPagingTimelineQueries.deletePaging(accountKey, pagingKey) - } + database.pagingTimelineDao().delete(pagingKey = pagingKey, accountKey = accountKey) } } @@ -165,12 +162,12 @@ internal class BookmarkTimelineRemoteMediator( private val database: CacheDatabase, private val accountKey: MicroBlogKey, private val pagingKey: String, -) : RemoteMediator() { +) : RemoteMediator() { private var cursor: String? = null override suspend fun load( loadType: LoadType, - state: PagingState, + state: PagingState, ): MediatorResult { return try { val response = @@ -184,9 +181,7 @@ internal class BookmarkTimelineRemoteMediator( count = state.config.pageSize.toLong(), ).encodeJson(), ).also { - database.transaction { - database.dbPagingTimelineQueries.deletePaging(accountKey, pagingKey) - } + database.pagingTimelineDao().delete(pagingKey = pagingKey, accountKey = accountKey) } } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/xqt/MentionRemoteMediator.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/xqt/MentionRemoteMediator.kt index 801a740c9..5a28367d8 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/xqt/MentionRemoteMediator.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/xqt/MentionRemoteMediator.kt @@ -4,11 +4,11 @@ import androidx.paging.ExperimentalPagingApi import androidx.paging.LoadType import androidx.paging.PagingState import androidx.paging.RemoteMediator -import dev.dimension.flare.data.cache.DbPagingTimelineWithStatusView import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.mapper.XQT import dev.dimension.flare.data.database.cache.mapper.cursor import dev.dimension.flare.data.database.cache.mapper.tweets +import dev.dimension.flare.data.database.cache.model.DbPagingTimelineView import dev.dimension.flare.data.network.xqt.XQTService import dev.dimension.flare.model.MicroBlogKey @@ -18,12 +18,12 @@ internal class MentionRemoteMediator( private val database: CacheDatabase, private val accountKey: MicroBlogKey, private val pagingKey: String, -) : RemoteMediator() { +) : RemoteMediator() { private var cursor: String? = null override suspend fun load( loadType: LoadType, - state: PagingState, + state: PagingState, ): MediatorResult { return try { val response = @@ -34,9 +34,7 @@ internal class MentionRemoteMediator( .getNotificationsMentions( count = state.config.pageSize, ).also { - database.transaction { - database.dbPagingTimelineQueries.deletePaging(accountKey, pagingKey) - } + database.pagingTimelineDao().delete(pagingKey = pagingKey, accountKey = accountKey) } } LoadType.PREPEND -> { diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/xqt/SearchStatusPagingSource.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/xqt/SearchStatusPagingSource.kt index 916da6257..72dfae8ca 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/xqt/SearchStatusPagingSource.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/xqt/SearchStatusPagingSource.kt @@ -5,11 +5,11 @@ import androidx.paging.LoadType import androidx.paging.PagingState import androidx.paging.RemoteMediator import dev.dimension.flare.common.encodeJson -import dev.dimension.flare.data.cache.DbPagingTimelineWithStatusView import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.mapper.XQT import dev.dimension.flare.data.database.cache.mapper.cursor import dev.dimension.flare.data.database.cache.mapper.tweets +import dev.dimension.flare.data.database.cache.model.DbPagingTimelineView import dev.dimension.flare.data.network.xqt.XQTService import dev.dimension.flare.model.MicroBlogKey import kotlinx.serialization.Required @@ -22,12 +22,12 @@ internal class SearchStatusPagingSource( private val accountKey: MicroBlogKey, private val pagingKey: String, private val query: String, -) : RemoteMediator() { +) : RemoteMediator() { private var cursor: String? = null override suspend fun load( loadType: LoadType, - state: PagingState, + state: PagingState, ): MediatorResult { return try { val response = @@ -42,9 +42,7 @@ internal class SearchStatusPagingSource( count = state.config.pageSize.toLong(), ).encodeJson(), ).also { - database.transaction { - database.dbPagingTimelineQueries.deletePaging(accountKey, pagingKey) - } + database.pagingTimelineDao().delete(pagingKey = pagingKey, accountKey = accountKey) } } LoadType.PREPEND -> { diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/xqt/StatusDetailRemoteMediator.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/xqt/StatusDetailRemoteMediator.kt index 8207b58fd..f7743b2f7 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/xqt/StatusDetailRemoteMediator.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/xqt/StatusDetailRemoteMediator.kt @@ -5,11 +5,12 @@ import androidx.paging.LoadType import androidx.paging.PagingState import androidx.paging.RemoteMediator import dev.dimension.flare.common.encodeJson -import dev.dimension.flare.data.cache.DbPagingTimelineWithStatusView import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.mapper.XQT import dev.dimension.flare.data.database.cache.mapper.cursor import dev.dimension.flare.data.database.cache.mapper.tweets +import dev.dimension.flare.data.database.cache.model.DbPagingTimeline +import dev.dimension.flare.data.database.cache.model.DbPagingTimelineView import dev.dimension.flare.data.database.cache.model.StatusContent import dev.dimension.flare.data.datasource.microblog.StatusEvent import dev.dimension.flare.data.network.xqt.XQTService @@ -17,9 +18,11 @@ import dev.dimension.flare.data.network.xqt.model.Tweet import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.ui.model.UiTimeline import dev.dimension.flare.ui.model.mapper.render +import kotlinx.coroutines.flow.firstOrNull import kotlinx.serialization.Required import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import kotlin.uuid.Uuid @OptIn(ExperimentalPagingApi::class) internal class StatusDetailRemoteMediator( @@ -30,24 +33,30 @@ internal class StatusDetailRemoteMediator( private val event: StatusEvent.XQT, private val pagingKey: String, private val statusOnly: Boolean, -) : RemoteMediator() { +) : RemoteMediator() { private var cursor: String? = null private var actualId: String? = null override suspend fun load( loadType: LoadType, - state: PagingState, + state: PagingState, ): MediatorResult = try { if (loadType == LoadType.REFRESH) { - if (!database.dbPagingTimelineQueries.existsPaging(accountKey, pagingKey).executeAsOne()) { - database.dbStatusQueries.get(statusKey, accountKey).executeAsOneOrNull()?.let { - database.dbPagingTimelineQueries - .insert( - account_key = accountKey, - status_key = statusKey, - paging_key = pagingKey, - sort_id = 0, + if (!database.pagingTimelineDao().existsPaging(accountKey, pagingKey)) { + database.statusDao().get(statusKey, accountKey).firstOrNull()?.let { + database + .pagingTimelineDao() + .insertAll( + listOf( + DbPagingTimeline( + accountKey = accountKey, + statusKey = statusKey, + pagingKey = pagingKey, + sortId = 0, + _id = Uuid.random().toString(), + ), + ), ) } } @@ -84,9 +93,10 @@ internal class StatusDetailRemoteMediator( val id = actualId ?: run { val result = - database.dbStatusQueries + database + .statusDao() .get(statusKey, accountKey) - .executeAsOneOrNull() + .firstOrNull() ?.content ?.let { it as? StatusContent.XQT } ?.data @@ -184,9 +194,7 @@ internal class StatusDetailRemoteMediator( cursor = actualResponse.cursor() - database.transaction { - database.dbPagingTimelineQueries.deletePaging(accountKey, pagingKey) - } + database.pagingTimelineDao().delete(pagingKey = pagingKey, accountKey = accountKey) XQT.save( accountKey = accountKey, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/xqt/UserMediaTimelineRemoteMediator.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/xqt/UserMediaTimelineRemoteMediator.kt index 13dd4c63a..083775248 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/xqt/UserMediaTimelineRemoteMediator.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/xqt/UserMediaTimelineRemoteMediator.kt @@ -5,11 +5,11 @@ import androidx.paging.LoadType import androidx.paging.PagingState import androidx.paging.RemoteMediator import dev.dimension.flare.common.encodeJson -import dev.dimension.flare.data.cache.DbPagingTimelineWithStatusView import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.mapper.XQT import dev.dimension.flare.data.database.cache.mapper.cursor import dev.dimension.flare.data.database.cache.mapper.tweets +import dev.dimension.flare.data.database.cache.model.DbPagingTimelineView import dev.dimension.flare.data.network.xqt.XQTService import dev.dimension.flare.model.MicroBlogKey @@ -20,12 +20,12 @@ internal class UserMediaTimelineRemoteMediator( private val database: CacheDatabase, private val accountKey: MicroBlogKey, private val pagingKey: String, -) : RemoteMediator() { +) : RemoteMediator() { private var cursor: String? = null override suspend fun load( loadType: LoadType, - state: PagingState, + state: PagingState, ): MediatorResult { return try { val response = @@ -40,9 +40,7 @@ internal class UserMediaTimelineRemoteMediator( count = state.config.pageSize.toLong(), ).encodeJson(), ).also { - database.transaction { - database.dbPagingTimelineQueries.deletePaging(accountKey, pagingKey) - } + database.pagingTimelineDao().delete(pagingKey = pagingKey, accountKey = accountKey) } } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/xqt/UserTimelineRemoteMediator.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/xqt/UserTimelineRemoteMediator.kt index 5a7af96a9..5eccefc45 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/xqt/UserTimelineRemoteMediator.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/xqt/UserTimelineRemoteMediator.kt @@ -5,11 +5,11 @@ import androidx.paging.LoadType import androidx.paging.PagingState import androidx.paging.RemoteMediator import dev.dimension.flare.common.encodeJson -import dev.dimension.flare.data.cache.DbPagingTimelineWithStatusView import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.mapper.XQT import dev.dimension.flare.data.database.cache.mapper.cursor import dev.dimension.flare.data.database.cache.mapper.tweets +import dev.dimension.flare.data.database.cache.model.DbPagingTimelineView import dev.dimension.flare.data.network.xqt.XQTService import dev.dimension.flare.model.MicroBlogKey import kotlinx.serialization.Required @@ -23,12 +23,12 @@ internal class UserTimelineRemoteMediator( private val database: CacheDatabase, private val accountKey: MicroBlogKey, private val pagingKey: String, -) : RemoteMediator() { +) : RemoteMediator() { private var cursor: String? = null override suspend fun load( loadType: LoadType, - state: PagingState, + state: PagingState, ): MediatorResult { return try { val response = @@ -43,9 +43,7 @@ internal class UserTimelineRemoteMediator( count = state.config.pageSize.toLong(), ).encodeJson(), ).also { - database.transaction { - database.dbPagingTimelineQueries.deletePaging(accountKey, pagingKey) - } + database.pagingTimelineDao().delete(pagingKey = pagingKey, accountKey = accountKey) } } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/xqt/XQTDataSource.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/xqt/XQTDataSource.kt index 486dfcb7b..af6060cd9 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/xqt/XQTDataSource.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/xqt/XQTDataSource.kt @@ -5,8 +5,6 @@ import androidx.paging.Pager import androidx.paging.PagingConfig import androidx.paging.PagingData import androidx.paging.cachedIn -import app.cash.sqldelight.coroutines.asFlow -import app.cash.sqldelight.coroutines.mapToOneNotNull import dev.dimension.flare.common.CacheData import dev.dimension.flare.common.Cacheable import dev.dimension.flare.common.MemCacheable @@ -62,14 +60,11 @@ import dev.dimension.flare.ui.model.mapper.render import dev.dimension.flare.ui.model.mapper.toUi import dev.dimension.flare.ui.model.toUi import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.IO import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.launch import org.koin.core.component.KoinComponent @@ -213,21 +208,13 @@ class XQTDataSource( is UserUnavailable -> null } }?.toDbUser() ?: throw Exception("User not found") - database.dbUserQueries.insert( - user_key = user.user_key, - platform_type = user.platform_type, - name = user.name, - handle = user.handle, - host = user.host, - content = user.content, - ) + database.userDao().insert(user) }, cacheSource = { - database.dbUserQueries + database + .userDao() .findByHandleAndHost(name, host, PlatformType.xQt) - .asFlow() - .mapToOneNotNull(Dispatchers.IO) - .map { it.render(account.accountKey) } + .mapNotNull { it?.render(account.accountKey) } }, ) } @@ -249,21 +236,13 @@ class XQTDataSource( is UserUnavailable -> null } }?.toDbUser() ?: throw Exception("User not found") - database.dbUserQueries.insert( - user_key = user.user_key, - platform_type = user.platform_type, - name = user.name, - handle = user.handle, - host = user.host, - content = user.content, - ) + database.userDao().insert(user) }, cacheSource = { - database.dbUserQueries + database + .userDao() .findByKey(userKey) - .asFlow() - .mapToOneNotNull(Dispatchers.IO) - .map { it.render(account.accountKey) } + .mapNotNull { it?.render(account.accountKey) } }, ) } @@ -383,11 +362,10 @@ class XQTDataSource( } }, cacheSource = { - database.dbStatusQueries + database + .statusDao() .get(statusKey, account.accountKey) - .asFlow() - .mapToOneNotNull(Dispatchers.IO) - .mapNotNull { it.content.render(account.accountKey, this) } + .mapNotNull { it?.content?.render(account.accountKey, this) } }, ) } @@ -541,13 +519,13 @@ class XQTDataSource( ), ) // delete status from cache - database.dbStatusQueries.delete( - status_key = statusKey, - account_key = account.accountKey, + database.statusDao().delete( + statusKey = statusKey, + accountKey = account.accountKey, ) - database.dbPagingTimelineQueries.deleteStatus( - account_key = account.accountKey, - status_key = statusKey, + database.pagingTimelineDao().deleteStatus( + accountKey = account.accountKey, + statusKey = statusKey, ) } } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/bluesky/BlueskyService.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/bluesky/BlueskyService.kt index f48ec5bfd..b92870340 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/bluesky/BlueskyService.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/bluesky/BlueskyService.kt @@ -4,7 +4,7 @@ import com.atproto.server.RefreshSessionResponse import dev.dimension.flare.common.JSON import dev.dimension.flare.common.encodeJson import dev.dimension.flare.data.database.app.AppDatabase -import dev.dimension.flare.data.database.app.DbAccountQueries +import dev.dimension.flare.data.database.app.dao.AccountDao import dev.dimension.flare.data.network.ktorClient import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.ui.model.UiAccount @@ -24,6 +24,7 @@ import io.ktor.http.HttpHeaders.Authorization import io.ktor.http.HttpStatusCode.Companion.BadRequest import io.ktor.http.Url import io.ktor.util.AttributeKey +import kotlinx.coroutines.flow.firstOrNull import kotlinx.serialization.json.Json import sh.christian.ozone.BlueskyApi import sh.christian.ozone.XrpcBlueskyApi @@ -32,7 +33,7 @@ import sh.christian.ozone.api.response.AtpErrorDescription internal class BlueskyService( private val baseUrl: String, private val accountKey: MicroBlogKey? = null, - private val accountQueries: DbAccountQueries? = null, + private val accountQueries: AccountDao? = null, ) : BlueskyApi by XrpcBlueskyApi( ktorClient { install(DefaultRequest) { @@ -58,12 +59,12 @@ internal class BlueskyService( internal class XrpcAuthPlugin( private val json: Json, private val accountKey: MicroBlogKey?, - private val accountQueries: DbAccountQueries?, + private val accountQueries: AccountDao?, ) { class Config( var json: Json = Json { ignoreUnknownKeys = true }, var accountKey: MicroBlogKey? = null, - var accountQueries: DbAccountQueries? = null, + var accountQueries: AccountDao? = null, ) companion object : HttpClientPlugin { @@ -83,7 +84,7 @@ internal class XrpcAuthPlugin( val account = plugin.accountQueries .get(plugin.accountKey) - .executeAsOneOrNull() + .firstOrNull() ?.toUi() as? UiAccount.Bluesky if (account != null) { context.bearerAuth(account.credential.accessToken) @@ -107,7 +108,7 @@ internal class XrpcAuthPlugin( val account = plugin.accountQueries .get(plugin.accountKey) - .executeAsOneOrNull() + .firstOrNull() ?.toUi() as? UiAccount.Bluesky if (account != null) { val refreshResponse = @@ -146,5 +147,5 @@ internal fun UiAccount.Bluesky.getService(appDatabase: AppDatabase) = BlueskyService( baseUrl = credential.baseUrl, accountKey = accountKey, - accountQueries = appDatabase.dbAccountQueries, + accountQueries = appDatabase.accountDao(), ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/MisskeyOauthService.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/MisskeyOauthService.kt index 40cbd38d6..7f39fd727 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/MisskeyOauthService.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/MisskeyOauthService.kt @@ -7,7 +7,6 @@ import dev.dimension.flare.data.network.misskey.api.model.response.MiAuthCheckRe import io.ktor.http.URLBuilder import io.ktor.http.URLProtocol import io.ktor.http.appendPathSegments -import kotlin.uuid.ExperimentalUuidApi import kotlin.uuid.Uuid private val defaultPermission = @@ -39,7 +38,6 @@ private val defaultPermission = "read:gallery-likes", ) -@OptIn(ExperimentalUuidApi::class) internal class MisskeyOauthService( private val host: String, private val name: String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/repository/AccountRepository.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/repository/AccountRepository.kt index 7a26ee557..1f7926f63 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/repository/AccountRepository.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/repository/AccountRepository.kt @@ -6,11 +6,9 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.produceState import androidx.compose.runtime.remember -import app.cash.sqldelight.coroutines.asFlow -import app.cash.sqldelight.coroutines.mapToList -import app.cash.sqldelight.coroutines.mapToOneOrNull import dev.dimension.flare.common.encodeJson import dev.dimension.flare.data.database.app.AppDatabase +import dev.dimension.flare.data.database.app.model.DbAccount import dev.dimension.flare.data.datasource.guest.GuestDataSource import dev.dimension.flare.data.datasource.microblog.MicroblogDataSource import dev.dimension.flare.data.network.mastodon.GuestMastodonService @@ -22,57 +20,57 @@ import dev.dimension.flare.ui.model.UiState import dev.dimension.flare.ui.model.map import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.IO +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch import kotlinx.datetime.Clock import org.koin.compose.koinInject class AccountRepository( private val appDatabase: AppDatabase, + private val coroutineScope: CoroutineScope, ) { val activeAccount: Flow by lazy { - appDatabase.dbAccountQueries.activeAccount().asFlow().mapToOneOrNull(Dispatchers.IO).map { + appDatabase.accountDao().activeAccount().map { it?.toUi() } } val allAccounts: Flow> by lazy { - appDatabase.dbAccountQueries.allAccounts().asFlow().mapToList(Dispatchers.IO).map { + appDatabase.accountDao().allAccounts().map { it.map { it.toUi() }.toImmutableList() } } - fun addAccount(account: UiAccount) { - appDatabase.dbAccountQueries.insert( - accountKey = account.accountKey, - platformType = account.platformType, - lastActive = Clock.System.now().toEpochMilliseconds(), - credentialJson = account.credential.encodeJson(), - ) - } - - fun setActiveAccount(accountKey: MicroBlogKey) { - appDatabase.dbAccountQueries.setLastActive( - Clock.System.now().toEpochMilliseconds(), - accountKey, - ) - } + fun addAccount(account: UiAccount) = + coroutineScope.launch { + appDatabase.accountDao().insert( + DbAccount( + account_key = account.accountKey, + platform_type = account.platformType, + last_active = Clock.System.now().toEpochMilliseconds(), + credential_json = account.credential.encodeJson(), + ), + ) + } - fun delete(accountKey: MicroBlogKey) { - appDatabase.dbAccountQueries.delete(accountKey) - } + fun setActiveAccount(accountKey: MicroBlogKey) = + coroutineScope.launch { + appDatabase.accountDao().setLastActive( + accountKey, + Clock.System.now().toEpochMilliseconds(), + ) + } - fun get(accountKey: MicroBlogKey): UiAccount? = - appDatabase.dbAccountQueries - .get(accountKey) - .executeAsOneOrNull() - ?.toUi() + fun delete(accountKey: MicroBlogKey) = + coroutineScope.launch { + appDatabase.accountDao().delete(accountKey) + } fun getFlow(accountKey: MicroBlogKey): Flow = - appDatabase.dbAccountQueries.get(accountKey).asFlow().mapToOneOrNull(Dispatchers.IO).map { + appDatabase.accountDao().get(accountKey).map { it?.toUi() } } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/repository/ApplicationRepository.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/repository/ApplicationRepository.kt index b54f33dbf..5626c3232 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/repository/ApplicationRepository.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/repository/ApplicationRepository.kt @@ -1,41 +1,52 @@ package dev.dimension.flare.data.repository import dev.dimension.flare.data.database.app.AppDatabase +import dev.dimension.flare.data.database.app.model.DbApplication import dev.dimension.flare.model.PlatformType import dev.dimension.flare.ui.model.UiApplication import dev.dimension.flare.ui.model.UiApplication.Companion.toUi +import kotlinx.coroutines.flow.firstOrNull class ApplicationRepository( private val database: AppDatabase, ) { - fun findByHost(host: String): UiApplication? = - database.dbApplicationQueries + suspend fun findByHost(host: String): UiApplication? = + database + .applicationDao() .get(host) - .executeAsOneOrNull() + .firstOrNull() ?.toUi() - fun addApplication( + suspend fun addApplication( host: String, credentialJson: String, platformType: PlatformType, ) { - database.dbApplicationQueries.insert(host, credentialJson, platformType) + database.applicationDao().insert( + DbApplication( + host = host, + credential_json = credentialJson, + platform_type = platformType, + ), + ) } - fun setPendingOAuth( + suspend fun setPendingOAuth( host: String, pendingOAuth: Boolean, ) { - database.dbApplicationQueries.updatePending(if (pendingOAuth) 1L else 0L, host) + database.applicationDao().updatePending(host, if (pendingOAuth) 1L else 0L) } - fun getPendingOAuth(): UiApplication? = - database.dbApplicationQueries + suspend fun getPendingOAuth(): UiApplication? = + database + .applicationDao() .getPending() - .executeAsOneOrNull() + .firstOrNull() + ?.firstOrNull() ?.toUi() - fun clearPendingOAuth() { - database.dbApplicationQueries.clearPending() + suspend fun clearPendingOAuth() { + database.applicationDao().clearPending() } } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/repository/LocalFilterRepository.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/repository/LocalFilterRepository.kt index ae35bb37e..31725969d 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/repository/LocalFilterRepository.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/repository/LocalFilterRepository.kt @@ -1,25 +1,24 @@ package dev.dimension.flare.data.repository -import app.cash.sqldelight.coroutines.asFlow -import app.cash.sqldelight.coroutines.mapToList import dev.dimension.flare.data.database.app.AppDatabase +import dev.dimension.flare.data.database.app.model.DbKeywordFilter import dev.dimension.flare.ui.model.UiKeywordFilter import dev.dimension.flare.ui.presenter.settings.toImmutableListWrapper import kotlinx.collections.immutable.toImmutableList -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.IO +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch import kotlinx.datetime.Clock import kotlinx.datetime.Instant internal class LocalFilterRepository( private val database: AppDatabase, + private val coroutineScope: CoroutineScope, ) { fun getAllFlow() = - database.dbKeywordFilterQueries + database + .keywordFilterDao() .selectAll() - .asFlow() - .mapToList(Dispatchers.IO) .map { it .map { @@ -41,15 +40,14 @@ internal class LocalFilterRepository( forTimeline: Boolean = false, forNotification: Boolean = false, forSearch: Boolean = false, - ) = database.dbKeywordFilterQueries + ) = database + .keywordFilterDao() .selectNotExpiredFor( currentTime = Clock.System.now().toEpochMilliseconds(), forTimeline = if (forTimeline) 1L else 0L, forNotification = if (forNotification) 1L else 0L, forSearch = if (forSearch) 1L else 0L, - ).asFlow() - .mapToList(Dispatchers.IO) - .map { + ).map { it.map { it.keyword } @@ -61,13 +59,15 @@ internal class LocalFilterRepository( forNotification: Boolean, forSearch: Boolean, expiredAt: Instant?, - ) { - database.dbKeywordFilterQueries.insert( - keyword = keyword, - forTimeline = if (forTimeline) 1L else 0L, - forNotification = if (forNotification) 1L else 0L, - forSearch = if (forSearch) 1L else 0L, - expiredAt = expiredAt?.toEpochMilliseconds() ?: 0L, + ) = coroutineScope.launch { + database.keywordFilterDao().insert( + DbKeywordFilter( + keyword = keyword, + for_timeline = if (forTimeline) 1L else 0L, + for_notification = if (forNotification) 1L else 0L, + for_search = if (forSearch) 1L else 0L, + expired_at = expiredAt?.toEpochMilliseconds() ?: 0L, + ), ) } @@ -77,8 +77,8 @@ internal class LocalFilterRepository( forNotification: Boolean, forSearch: Boolean, expiredAt: Instant?, - ) { - database.dbKeywordFilterQueries.update( + ) = coroutineScope.launch { + database.keywordFilterDao().update( forTimeline = if (forTimeline) 1L else 0L, forNotification = if (forNotification) 1L else 0L, forSearch = if (forSearch) 1L else 0L, @@ -87,11 +87,13 @@ internal class LocalFilterRepository( ) } - fun delete(filter: String) { - database.dbKeywordFilterQueries.deleteByKeyword(filter) - } + fun delete(filter: String) = + coroutineScope.launch { + database.keywordFilterDao().deleteByKeyword(filter) + } - fun clear() { - database.dbKeywordFilterQueries.deleteAll() - } + fun clear() = + coroutineScope.launch { + database.keywordFilterDao().deleteAll() + } } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/repository/SearchHistoryRepository.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/repository/SearchHistoryRepository.kt index 9932b8111..4f275af61 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/repository/SearchHistoryRepository.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/repository/SearchHistoryRepository.kt @@ -1,26 +1,24 @@ package dev.dimension.flare.data.repository -import app.cash.sqldelight.coroutines.asFlow -import app.cash.sqldelight.coroutines.mapToList import dev.dimension.flare.data.database.app.AppDatabase +import dev.dimension.flare.data.database.app.model.DbSearchHistory import dev.dimension.flare.ui.model.UiSearchHistory import dev.dimension.flare.ui.presenter.settings.toImmutableListWrapper import kotlinx.collections.immutable.toImmutableList -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.IO +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch import kotlinx.datetime.Clock import kotlinx.datetime.Instant internal class SearchHistoryRepository( private val database: AppDatabase, + private val coroutineScope: CoroutineScope, ) { val allSearchHistory = database - .dbSearchHistoryQueries + .searchHistoryDao() .select() - .asFlow() - .mapToList(Dispatchers.IO) .map { it .map { @@ -32,18 +30,23 @@ internal class SearchHistoryRepository( .toImmutableListWrapper() } - fun addSearchHistory(keyword: String) { - database.dbSearchHistoryQueries.insert( - search = keyword, - created_at = Clock.System.now().toEpochMilliseconds(), - ) - } + fun addSearchHistory(keyword: String) = + coroutineScope.launch { + database.searchHistoryDao().insert( + DbSearchHistory( + search = keyword, + created_at = Clock.System.now().toEpochMilliseconds(), + ), + ) + } - fun deleteSearchHistory(keyword: String) { - database.dbSearchHistoryQueries.delete(keyword) - } + fun deleteSearchHistory(keyword: String) = + coroutineScope.launch { + database.searchHistoryDao().delete(keyword) + } - fun deleteAllSearchHistory() { - database.dbSearchHistoryQueries.deleteAll() - } + fun deleteAllSearchHistory() = + coroutineScope.launch { + database.searchHistoryDao().deleteAll() + } } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/di/CommonModule.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/di/CommonModule.kt index 0ea01ddce..1cd636707 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/di/CommonModule.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/di/CommonModule.kt @@ -2,7 +2,6 @@ package dev.dimension.flare.di import dev.dimension.flare.data.database.provideAppDatabase import dev.dimension.flare.data.database.provideCacheDatabase -import dev.dimension.flare.data.database.provideVersionDatabase import dev.dimension.flare.data.repository.AccountRepository import dev.dimension.flare.data.repository.ApplicationRepository import dev.dimension.flare.data.repository.LocalFilterRepository @@ -17,9 +16,8 @@ import org.koin.dsl.module val commonModule = module { singleOf(::AccountRepository) - single { provideVersionDatabase(get()) } - single { provideAppDatabase(get(), get()) } - single { provideCacheDatabase(get(), get()) } + singleOf(::provideAppDatabase) + singleOf(::provideCacheDatabase) singleOf(::ApplicationRepository) singleOf(::LocalFilterRepository) single { CoroutineScope(Dispatchers.IO) } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiAccount.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiAccount.kt index 6ef46a707..5459784c4 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiAccount.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiAccount.kt @@ -2,7 +2,7 @@ package dev.dimension.flare.ui.model import androidx.compose.runtime.Immutable import dev.dimension.flare.common.decodeJson -import dev.dimension.flare.data.database.app.DbAccount +import dev.dimension.flare.data.database.app.model.DbAccount import dev.dimension.flare.data.datasource.bluesky.BlueskyDataSource import dev.dimension.flare.data.datasource.mastodon.MastodonDataSource import dev.dimension.flare.data.datasource.misskey.MisskeyDataSource diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiApplication.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiApplication.kt index 40f436e5b..cd7d6e004 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiApplication.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiApplication.kt @@ -2,7 +2,7 @@ package dev.dimension.flare.ui.model import androidx.compose.runtime.Immutable import dev.dimension.flare.common.decodeJson -import dev.dimension.flare.data.database.app.DbApplication +import dev.dimension.flare.data.database.app.model.DbApplication import dev.dimension.flare.data.network.mastodon.api.model.CreateApplicationResponse import dev.dimension.flare.model.PlatformType import dev.dimension.flare.model.vvoHost diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/mapper/Mastodon.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/mapper/Mastodon.kt index 33e399e5f..040e2b601 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/mapper/Mastodon.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/mapper/Mastodon.kt @@ -3,7 +3,7 @@ package dev.dimension.flare.ui.model.mapper import com.fleeksoft.ksoup.nodes.Element import com.fleeksoft.ksoup.nodes.Node import dev.dimension.flare.common.AppDeepLink -import dev.dimension.flare.data.cache.DbEmoji +import dev.dimension.flare.data.database.cache.model.DbEmoji import dev.dimension.flare.data.database.cache.model.EmojiContent import dev.dimension.flare.data.datasource.guest.GuestDataSource import dev.dimension.flare.data.datasource.microblog.StatusAction diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/mapper/Render.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/mapper/Render.kt index bd9ab34de..c37a113c8 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/mapper/Render.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/mapper/Render.kt @@ -1,16 +1,16 @@ package dev.dimension.flare.ui.model.mapper -import dev.dimension.flare.data.cache.DbPagingTimelineWithStatusView -import dev.dimension.flare.data.cache.DbUser +import dev.dimension.flare.data.database.cache.model.DbPagingTimelineView +import dev.dimension.flare.data.database.cache.model.DbUser import dev.dimension.flare.data.database.cache.model.StatusContent import dev.dimension.flare.data.database.cache.model.UserContent import dev.dimension.flare.data.datasource.microblog.StatusEvent import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.ui.model.UiTimeline -internal fun DbPagingTimelineWithStatusView.render(event: StatusEvent): UiTimeline = +internal fun DbPagingTimelineView.render(event: StatusEvent): UiTimeline = status_content.render( - timeline_account_key, + timeline.accountKey, event, ) @@ -42,12 +42,15 @@ internal fun StatusContent.render( event = event as StatusEvent.Misskey, ) - is StatusContent.Bluesky -> - reason?.render( + is StatusContent.BlueskyReason -> + reason.render( accountKey = accountKey, data = data, event = event as StatusEvent.Bluesky, - ) ?: data.render( + ) + + is StatusContent.Bluesky -> + data.render( accountKey = accountKey, event = event as StatusEvent.Bluesky, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/mapper/XQT.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/mapper/XQT.kt index c5edd11ec..aa7e1d087 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/mapper/XQT.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/mapper/XQT.kt @@ -39,14 +39,12 @@ import kotlinx.datetime.TimeZone import kotlinx.datetime.toInstant import moe.tlaster.twitter.parser.TwitterParser import moe.tlaster.twitter.parser.UrlToken -import kotlin.uuid.ExperimentalUuidApi import kotlin.uuid.Uuid private val twitterParser by lazy { TwitterParser(enableNonAsciiInUrl = false) } -@OptIn(ExperimentalUuidApi::class) internal fun TopLevel.renderNotifications( accountKey: MicroBlogKey, event: StatusEvent.XQT, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/MisskeyCallbackPresenter.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/MisskeyCallbackPresenter.kt index af49c6c4e..b73fe7e73 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/MisskeyCallbackPresenter.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/MisskeyCallbackPresenter.kt @@ -19,7 +19,6 @@ import dev.dimension.flare.ui.presenter.PresenterBase import kotlinx.coroutines.delay import org.koin.compose.koinInject import kotlin.time.Duration.Companion.seconds -import kotlin.uuid.ExperimentalUuidApi import kotlin.uuid.Uuid class MisskeyCallbackPresenter( @@ -91,8 +90,7 @@ class MisskeyCallbackPresenter( } } -@OptIn(ExperimentalUuidApi::class) -fun misskeyLoginUseCase( +suspend fun misskeyLoginUseCase( host: String, applicationRepository: ApplicationRepository, launchOAuth: (String) -> Unit, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/StoragePresenter.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/StoragePresenter.kt index bac1cab7d..561c9fb58 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/StoragePresenter.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/StoragePresenter.kt @@ -4,40 +4,37 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember -import app.cash.sqldelight.coroutines.asFlow -import app.cash.sqldelight.coroutines.mapToOneNotNull +import androidx.compose.runtime.rememberCoroutineScope import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.ui.presenter.PresenterBase -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.IO +import kotlinx.coroutines.launch import org.koin.compose.koinInject class StoragePresenter : PresenterBase() { @Composable override fun body(): StorageState { + val scope = rememberCoroutineScope() val cacheDatabase = koinInject() val statusCount by remember { - cacheDatabase.dbStatusQueries + cacheDatabase + .statusDao() .count() - .asFlow() - .mapToOneNotNull(Dispatchers.IO) }.collectAsState(0L) val userCount by remember { - cacheDatabase.dbUserQueries + cacheDatabase + .userDao() .count() - .asFlow() - .mapToOneNotNull(Dispatchers.IO) }.collectAsState(0L) return object : StorageState { override val userCount: Long = userCount override val statusCount: Long = statusCount override fun clearCache() { - cacheDatabase.transaction { - cacheDatabase.dbPagingTimelineQueries.clear() - cacheDatabase.dbStatusQueries.clear() - cacheDatabase.dbUserQueries.clear() - cacheDatabase.dbEmojiQueries.clear() + scope.launch { + cacheDatabase.pagingTimelineDao().clear() + cacheDatabase.statusDao().clear() + cacheDatabase.userDao().clear() + cacheDatabase.emojiDao().clear() } } } diff --git a/shared/src/commonMain/sqldelight/app/dev/dimension/flare/data/database/app/DbAccount.sq b/shared/src/commonMain/sqldelight/app/dev/dimension/flare/data/database/app/DbAccount.sq deleted file mode 100644 index 725a58bac..000000000 --- a/shared/src/commonMain/sqldelight/app/dev/dimension/flare/data/database/app/DbAccount.sq +++ /dev/null @@ -1,30 +0,0 @@ -import dev.dimension.flare.model.MicroBlogKey; -import dev.dimension.flare.model.PlatformType; - -CREATE TABLE IF NOT EXISTS DbAccount ( - account_key TEXT AS MicroBlogKey PRIMARY KEY NOT NULL, - credential_json TEXT NOT NULL, - platform_type TEXT AS PlatformType NOT NULL, - last_active INTEGER NOT NULL -); - -activeAccount: -SELECT * FROM DbAccount ORDER BY last_active DESC LIMIT 1; - -allAccounts: -SELECT * FROM DbAccount; - -insert: -INSERT OR REPLACE INTO DbAccount (account_key, credential_json, platform_type, last_active) VALUES (:accountKey, :credentialJson, :platformType, :lastActive); - -setLastActive: -UPDATE DbAccount SET last_active = :lastActive WHERE account_key = :accountKey; - -get: -SELECT * FROM DbAccount WHERE account_key = :accountKey; - -delete: -DELETE FROM DbAccount WHERE account_key = :accountKey; - -setCredential: -UPDATE DbAccount SET credential_json = :credentialJson WHERE account_key = :accountKey; \ No newline at end of file diff --git a/shared/src/commonMain/sqldelight/app/dev/dimension/flare/data/database/app/DbApplication.sq b/shared/src/commonMain/sqldelight/app/dev/dimension/flare/data/database/app/DbApplication.sq deleted file mode 100644 index 366426540..000000000 --- a/shared/src/commonMain/sqldelight/app/dev/dimension/flare/data/database/app/DbApplication.sq +++ /dev/null @@ -1,32 +0,0 @@ -import dev.dimension.flare.model.PlatformType; - -CREATE TABLE IF NOT EXISTS DbApplication ( - host TEXT NOT NULL PRIMARY KEY, - credential_json TEXT NOT NULL, - platform_type TEXT AS PlatformType NOT NULL, - has_pending_oauth_request INTEGER NOT NULL DEFAULT 0 -); - -allApplication: -SELECT * FROM DbApplication; - -get: -SELECT * FROM DbApplication WHERE host = :host; - -getPending: -SELECT * FROM DbApplication WHERE has_pending_oauth_request = 1; - -insert: -INSERT OR REPLACE INTO DbApplication (host, credential_json, platform_type) VALUES (:host, :credential_json, :platform_type); - -update: -UPDATE DbApplication SET credential_json = :credential_json, platform_type = :platform_type WHERE host = :host; - -updatePending: -UPDATE DbApplication SET has_pending_oauth_request = :has_pending_oauth_request WHERE host = :host; - -delete: -DELETE FROM DbApplication WHERE host = :host; - -clearPending: -UPDATE DbApplication SET has_pending_oauth_request = 0 WHERE has_pending_oauth_request = 1; diff --git a/shared/src/commonMain/sqldelight/app/dev/dimension/flare/data/database/app/DbKeywordFilter.sq b/shared/src/commonMain/sqldelight/app/dev/dimension/flare/data/database/app/DbKeywordFilter.sq deleted file mode 100644 index f33f459f6..000000000 --- a/shared/src/commonMain/sqldelight/app/dev/dimension/flare/data/database/app/DbKeywordFilter.sq +++ /dev/null @@ -1,31 +0,0 @@ -CREATE TABLE IF NOT EXISTS DbKeywordFilter( - keyword TEXT NOT NULL PRIMARY KEY, - for_timeline INTEGER NOT NULL, - for_notification INTEGER NOT NULL, - for_search INTEGER NOT NULL, - expired_at INTEGER NOT NULL -); - -insert: -INSERT OR REPLACE INTO DbKeywordFilter(keyword, for_timeline, for_notification, for_search, expired_at) VALUES(:keyword, :forTimeline, :forNotification, :forSearch, :expiredAt); - -selectAll: -SELECT * FROM DbKeywordFilter; - -selectAllNotExpired: -SELECT * FROM DbKeywordFilter WHERE expired_at = 0 OR expired_at > :currentTime; - -selectNotExpiredFor: -SELECT * FROM DbKeywordFilter WHERE (expired_at = 0 OR expired_at > :currentTime) AND (for_timeline = :forTimeline OR for_notification = :forNotification OR for_search = :forSearch); - -selectByKeyword: -SELECT * FROM DbKeywordFilter WHERE keyword = :keyword; - -deleteByKeyword: -DELETE FROM DbKeywordFilter WHERE keyword = :keyword; - -deleteAll: -DELETE FROM DbKeywordFilter; - -update: -UPDATE DbKeywordFilter SET for_timeline = :forTimeline, for_notification = :forNotification, for_search = :forSearch, expired_at = :expiredAt WHERE keyword = :keyword; \ No newline at end of file diff --git a/shared/src/commonMain/sqldelight/app/dev/dimension/flare/data/database/app/DbSearchHistory.sq b/shared/src/commonMain/sqldelight/app/dev/dimension/flare/data/database/app/DbSearchHistory.sq deleted file mode 100644 index 2ecd90e45..000000000 --- a/shared/src/commonMain/sqldelight/app/dev/dimension/flare/data/database/app/DbSearchHistory.sq +++ /dev/null @@ -1,16 +0,0 @@ -CREATE TABLE IF NOT EXISTS DbSearchHistory ( - search TEXT NOT NULL PRIMARY KEY, - created_at INTEGER NOT NULL -); - -insert: -INSERT OR REPLACE INTO DbSearchHistory (search, created_at) VALUES (?, ?); - -select: -SELECT * FROM DbSearchHistory ORDER BY created_at DESC; - -delete: -DELETE FROM DbSearchHistory WHERE search = ?; - -deleteAll: -DELETE FROM DbSearchHistory; \ No newline at end of file diff --git a/shared/src/commonMain/sqldelight/app/migrations/1.sqm b/shared/src/commonMain/sqldelight/app/migrations/1.sqm deleted file mode 100644 index d1e7fd594..000000000 --- a/shared/src/commonMain/sqldelight/app/migrations/1.sqm +++ /dev/null @@ -1,7 +0,0 @@ -CREATE TABLE IF NOT EXISTS DbKeywordFilter( - keyword TEXT NOT NULL PRIMARY KEY, - for_timeline INTEGER NOT NULL, - for_notification INTEGER NOT NULL, - for_search INTEGER NOT NULL, - expired_at INTEGER NOT NULL -); diff --git a/shared/src/commonMain/sqldelight/app/migrations/2.sqm b/shared/src/commonMain/sqldelight/app/migrations/2.sqm deleted file mode 100644 index f7984a9e5..000000000 --- a/shared/src/commonMain/sqldelight/app/migrations/2.sqm +++ /dev/null @@ -1,4 +0,0 @@ -CREATE TABLE IF NOT EXISTS DbSearchHistory ( - search TEXT NOT NULL PRIMARY KEY, - created_at INTEGER NOT NULL -); \ No newline at end of file diff --git a/shared/src/commonMain/sqldelight/cache/dev/dimension/flare/data/cache/DbEmoji.sq b/shared/src/commonMain/sqldelight/cache/dev/dimension/flare/data/cache/DbEmoji.sq deleted file mode 100644 index b0f25a766..000000000 --- a/shared/src/commonMain/sqldelight/cache/dev/dimension/flare/data/cache/DbEmoji.sq +++ /dev/null @@ -1,16 +0,0 @@ -import dev.dimension.flare.data.database.cache.model.EmojiContent; - -CREATE TABLE IF NOT EXISTS DbEmoji ( - host TEXT NOT NULL PRIMARY KEY, - content TEXT AS EmojiContent NOT NULL, - UNIQUE(host) -); - -insert: -INSERT OR REPLACE INTO DbEmoji (host, content) VALUES (:host, :content); - -get: -SELECT * FROM DbEmoji WHERE host = :host; - -clear: -DELETE FROM DbEmoji; \ No newline at end of file diff --git a/shared/src/commonMain/sqldelight/cache/dev/dimension/flare/data/cache/DbPagingTimeline.sq b/shared/src/commonMain/sqldelight/cache/dev/dimension/flare/data/cache/DbPagingTimeline.sq deleted file mode 100644 index 35eb0415f..000000000 --- a/shared/src/commonMain/sqldelight/cache/dev/dimension/flare/data/cache/DbPagingTimeline.sq +++ /dev/null @@ -1,52 +0,0 @@ -import dev.dimension.flare.model.MicroBlogKey; - -CREATE TABLE IF NOT EXISTS DbPagingTimeline ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - account_key TEXT AS MicroBlogKey NOT NULL, - paging_key TEXT NOT NULL, - status_key TEXT AS MicroBlogKey NOT NULL, - sort_id INTEGER NOT NULL, - UNIQUE (account_key, paging_key, status_key) -); - -CREATE VIEW IF NOT EXISTS DbPagingTimelineWithStatusView AS -SELECT -timeline.account_key AS timeline_account_key, -timeline.paging_key AS timeline_paging_key, -timeline.status_key AS timeline_status_key, -timeline.sort_id AS timeline_sort_id, -status.platform_type AS status_platform_type, -status.content AS status_content, -user.user_key AS user_user_key, -user.name AS user_name, -user.handle AS user_handle, -user.host AS user_host, -user.content AS user_content -FROM DbPagingTimeline AS timeline -JOIN DbStatus status ON timeline.status_key = status.status_key AND timeline.account_key = status.account_key -LEFT JOIN DbUser user ON status.user_key = user.user_key; - -insert: -INSERT OR REPLACE INTO DbPagingTimeline (account_key, paging_key, status_key, sort_id) - VALUES (:account_key, :paging_key, :status_key, :sort_id); - -delete: -DELETE FROM DbPagingTimeline WHERE account_key = :account_key AND paging_key = :paging_key AND status_key = :status_key; - -deletePaging: -DELETE FROM DbPagingTimeline WHERE account_key = :account_key AND paging_key = :paging_key; - -deleteStatus: -DELETE FROM DbPagingTimeline WHERE account_key = :account_key AND status_key = :status_key; - -existsPaging: -SELECT EXISTS(SELECT 1 FROM DbPagingTimeline WHERE account_key = :account_key AND paging_key = :paging_key); - -pageCount: -SELECT COUNT(*) FROM DbPagingTimeline WHERE account_key = :account_key AND paging_key = :paging_key; - -getPage: -SELECT * FROM DbPagingTimelineWithStatusView WHERE timeline_account_key = :account_key AND timeline_paging_key = :paging_key ORDER BY timeline_sort_id DESC LIMIT :limit OFFSET :offset; - -clear: -DELETE FROM DbPagingTimeline; \ No newline at end of file diff --git a/shared/src/commonMain/sqldelight/cache/dev/dimension/flare/data/cache/DbStatus.sq b/shared/src/commonMain/sqldelight/cache/dev/dimension/flare/data/cache/DbStatus.sq deleted file mode 100644 index 496e67245..000000000 --- a/shared/src/commonMain/sqldelight/cache/dev/dimension/flare/data/cache/DbStatus.sq +++ /dev/null @@ -1,32 +0,0 @@ -import dev.dimension.flare.data.database.cache.model.StatusContent; -import dev.dimension.flare.model.MicroBlogKey; -import dev.dimension.flare.model.PlatformType; - -CREATE TABLE IF NOT EXISTS DbStatus ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - status_key TEXT AS MicroBlogKey NOT NULL, - account_key TEXT AS MicroBlogKey NOT NULL, - user_key TEXT AS MicroBlogKey, - platform_type TEXT AS PlatformType NOT NULL, - content TEXT AS StatusContent NOT NULL, - UNIQUE (status_key, account_key) -); - -insert: -INSERT OR REPLACE INTO DbStatus (status_key, account_key, user_key, platform_type, content) -VALUES (:status_key, :account_key, :user_key, :platform_type, :content); - -get: -SELECT * FROM DbStatus WHERE status_key = :status_key AND account_key = :account_key; - -update: -UPDATE DbStatus SET content = :content WHERE status_key = :status_key AND account_key = :account_key; - -delete: -DELETE FROM DbStatus WHERE status_key = :status_key AND account_key = :account_key; - -count: -SELECT COUNT(*) FROM DbStatus; - -clear: -DELETE FROM DbStatus; diff --git a/shared/src/commonMain/sqldelight/cache/dev/dimension/flare/data/cache/DbUser.sq b/shared/src/commonMain/sqldelight/cache/dev/dimension/flare/data/cache/DbUser.sq deleted file mode 100644 index 52e648dd6..000000000 --- a/shared/src/commonMain/sqldelight/cache/dev/dimension/flare/data/cache/DbUser.sq +++ /dev/null @@ -1,35 +0,0 @@ -import dev.dimension.flare.model.MicroBlogKey; -import dev.dimension.flare.model.PlatformType; -import dev.dimension.flare.data.database.cache.model.UserContent; - -CREATE TABLE IF NOT EXISTS DbUser ( - user_key TEXT AS MicroBlogKey NOT NULL PRIMARY KEY, - platform_type TEXT AS PlatformType NOT NULL, - name TEXT NOT NULL, - handle TEXT NOT NULL, - host TEXT NOT NULL, - content TEXT AS UserContent NOT NULL, - UNIQUE (handle, host, platform_type) -); - - -insert: -INSERT OR REPLACE INTO DbUser (user_key, platform_type, name, handle, host, content) VALUES (:user_key, :platform_type, :name, :handle, :host, :content); - -update: -UPDATE DbUser SET content = :content WHERE user_key = :user_key; - -findByKeys: -SELECT * FROM DbUser WHERE user_key IN :user_keys; - -findByKey: -SELECT * FROM DbUser WHERE user_key = :user_key; - -findByHandleAndHost: -SELECT * FROM DbUser WHERE handle = :handle AND host = :host AND platform_type = :platform_type; - -count: -SELECT COUNT(*) FROM DbUser; - -clear: -DELETE FROM DbUser; \ No newline at end of file diff --git a/shared/src/commonMain/sqldelight/cache/migrations/1.sqm b/shared/src/commonMain/sqldelight/cache/migrations/1.sqm deleted file mode 100644 index 44d146881..000000000 --- a/shared/src/commonMain/sqldelight/cache/migrations/1.sqm +++ /dev/null @@ -1,15 +0,0 @@ -import dev.dimension.flare.data.database.cache.model.StatusContent; -import dev.dimension.flare.model.MicroBlogKey; -import dev.dimension.flare.model.PlatformType; - -DROP TABLE IF EXISTS DbStatus; - -CREATE TABLE IF NOT EXISTS DbStatus ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - status_key TEXT AS MicroBlogKey NOT NULL, - account_key TEXT AS MicroBlogKey NOT NULL, - user_key TEXT AS MicroBlogKey, - platform_type TEXT AS PlatformType NOT NULL, - content TEXT AS StatusContent NOT NULL, - UNIQUE (status_key, account_key) -); \ No newline at end of file diff --git a/shared/src/commonMain/sqldelight/version/dev/dimension/flare/data/version/Version.sq b/shared/src/commonMain/sqldelight/version/dev/dimension/flare/data/version/Version.sq deleted file mode 100644 index 216aa5629..000000000 --- a/shared/src/commonMain/sqldelight/version/dev/dimension/flare/data/version/Version.sq +++ /dev/null @@ -1,15 +0,0 @@ -CREATE TABLE IF NOT EXISTS DbVersion ( - id INTEGER PRIMARY KEY NOT NULL, - version INTEGER NOT NULL -); - -findAll: -SELECT * FROM DbVersion; - -find: -SELECT * FROM DbVersion WHERE id=:id; - -insert: -INSERT OR REPLACE INTO DbVersion( - id, version -) VALUES ?;