Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions app/src/main/java/app/gamenative/data/SteamAppPicsMeta.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package app.gamenative.data

import androidx.room.ColumnInfo

// Slimmed-down projection of the columns the PICS collectors actually read
data class SteamAppPicsMeta(
val id: Int,
@ColumnInfo("package_id")
val packageId: Int,
@ColumnInfo("last_change_number")
val lastChangeNumber: Int,
@ColumnInfo("ufs_parse_version")
val ufsParseVersion: Int,
)
10 changes: 10 additions & 0 deletions app/src/main/java/app/gamenative/data/SteamLicenseForPics.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package app.gamenative.data

import androidx.room.ColumnInfo

// Slimmed down type for retrieving licences for processing.
data class SteamLicenseForPics(
val packageId: Int,
@ColumnInfo("access_token")
val accessToken: Long,
)
10 changes: 10 additions & 0 deletions app/src/main/java/app/gamenative/data/SteamLicenseStub.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package app.gamenative.data

import androidx.room.ColumnInfo

// Smallest possible stub for steam license comparisons
data class SteamLicenseStub(
val packageId: Int,
@ColumnInfo("last_change_number") val lastChangeNumber: Int,
@ColumnInfo("access_token") val accessToken: Long,
)
47 changes: 46 additions & 1 deletion app/src/main/java/app/gamenative/db/dao/SteamAppDao.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update
import app.gamenative.data.SteamApp
import app.gamenative.data.SteamAppPicsMeta
import app.gamenative.service.SteamService.Companion.INVALID_PKG_ID
import kotlin.math.min
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
Expand Down Expand Up @@ -199,5 +201,48 @@ interface SteamAppDao {
suspend fun findSteamAppWithInstallDir(dirName: String): List<SteamApp>

@Query("SELECT * FROM steam_app WHERE id IN (:appIds)")
suspend fun findSteamAppWithAppIds(appIds: List<Int>): List<SteamApp>
suspend fun _findSteamAppWithAppIds(appIds: List<Int>): List<SteamApp>

// batched to stay under SQLite's 999 bind-variable limit
@Transaction
suspend fun findSteamAppWithAppIds(appIds: List<Int>): List<SteamApp> {
if (appIds.isEmpty()) return emptyList()
val results = mutableListOf<SteamApp>()
for (chunkStart in appIds.indices step SQLITE_MAX_VARS) {
val chunkEnd = min(chunkStart + SQLITE_MAX_VARS, appIds.size)
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
results += _findSteamAppWithAppIds(appIds.subList(chunkStart, chunkEnd))
}
return results
}

@Query(
"SELECT id, package_id, last_change_number, ufs_parse_version " +
"FROM steam_app WHERE id IN (:appIds)",
)
suspend fun _findAppPicsMeta(appIds: List<Int>): List<SteamAppPicsMeta>

@Query("UPDATE steam_app SET package_id = :packageId WHERE id IN (:appIds)")
suspend fun _updatePackageIdForApps(packageId: Int, appIds: List<Int>)

// Reassigns package ownership for a set of apps in one statement per chunk
@Transaction
suspend fun updatePackageIdForApps(packageId: Int, appIds: List<Int>) {
if (appIds.isEmpty()) return
for (chunkStart in appIds.indices step SQLITE_MAX_VARS) {
val chunkEnd = min(chunkStart + SQLITE_MAX_VARS, appIds.size)
_updatePackageIdForApps(packageId, appIds.subList(chunkStart, chunkEnd))
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

// Lightweight projection of the columns the PICS collectors read to decide whether an app needs (re)processing
@Transaction
suspend fun findAppPicsMeta(appIds: List<Int>): List<SteamAppPicsMeta> {
if (appIds.isEmpty()) return emptyList()
val results = mutableListOf<SteamAppPicsMeta>()
for (chunkStart in appIds.indices step SQLITE_MAX_VARS) {
val chunkEnd = min(chunkStart + SQLITE_MAX_VARS, appIds.size)
results += _findAppPicsMeta(appIds.subList(chunkStart, chunkEnd))
}
return results
}
}
9 changes: 9 additions & 0 deletions app/src/main/java/app/gamenative/db/dao/SteamLicenseDao.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update
import app.gamenative.data.SteamLicense
import app.gamenative.data.SteamLicenseForPics
import app.gamenative.data.SteamLicenseStub
import kotlin.math.min

val SQLITE_MAX_VARS = 999
Expand All @@ -29,6 +31,13 @@ interface SteamLicenseDao {
@Query("SELECT * FROM steam_license")
suspend fun getAllLicenses(): List<SteamLicense>

//lightweight difffs for steamlicences
@Query("SELECT packageId, last_change_number, access_token FROM steam_license")
suspend fun getLicenseStubs(): List<SteamLicenseStub>

@Query("SELECT packageId, access_token FROM steam_license")
suspend fun getLicensesForPics(): List<SteamLicenseForPics>

@Query("SELECT * FROM steam_license WHERE packageId = :packageId")
suspend fun findLicense(packageId: Int): SteamLicense?

Expand Down
21 changes: 21 additions & 0 deletions app/src/main/java/app/gamenative/di/DatabaseModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package app.gamenative.di

import android.content.Context
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.sqlite.db.SupportSQLiteDatabase
import app.gamenative.db.DATABASE_NAME
import app.gamenative.db.PluviaDatabase
import app.gamenative.db.dao.AppInfoDao
Expand All @@ -19,6 +21,7 @@ import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import java.util.concurrent.Executors
import javax.inject.Singleton

@InstallIn(SingletonComponent::class)
Expand All @@ -36,6 +39,24 @@ class DatabaseModule {
ROOM_MIGRATION_V23_to_V24,
)
.fallbackToDestructiveMigration(true)
// Use unbounded thread pools for reads/writes so a long-running query (e.g. the
// library COUNT/paged load during a large PICS sync) can't starve the default
// single-threaded executors and deadlock the connection pool. SQLite still
// serializes writers, so this only removes the scheduling deadlock
.setQueryExecutor(Executors.newCachedThreadPool())
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated
.setTransactionExecutor(Executors.newCachedThreadPool())
.addCallback(object : RoomDatabase.Callback() {
override fun onOpen(db: SupportSQLiteDatabase) {
db.execSQL(
"CREATE INDEX IF NOT EXISTS idx_steam_app_dlc_for_app_id " +
"ON steam_app(dlc_for_app_id)",
)
db.execSQL(
Comment thread
phobos665 marked this conversation as resolved.
"CREATE INDEX IF NOT EXISTS idx_steam_app_package_id " +
"ON steam_app(package_id)",
)
}
})
.build()
}

Expand Down
Loading
Loading