Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 2 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,8 @@ dependencies {
// JavaSteam
val localBuild = false // Change to 'true' needed when building JavaSteam manually
if (localBuild) {
implementation(files("../../JavaSteam/build/libs/javasteam-1.8.0-SNAPSHOT.jar"))
implementation(files("../../JavaSteam/javasteam-depotdownloader/build/libs/javasteam-depotdownloader-1.8.0-SNAPSHOT.jar"))
implementation(files("../../JavaSteam/build/libs/javasteam-1.8.0-5-SNAPSHOT.jar"))
implementation(files("../../JavaSteam/javasteam-depotdownloader/build/libs/javasteam-depotdownloader-1.8.0-5-SNAPSHOT.jar"))
implementation(libs.bundles.javasteam.dev)
} else {
implementation(libs.javasteam) {
Expand Down
12 changes: 11 additions & 1 deletion app/src/main/java/app/gamenative/data/DownloadInfo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import java.util.concurrent.CopyOnWriteArrayList

data class DownloadInfo(
val jobCount: Int = 1,
val gameId: Int,
var downloadingAppIds: CopyOnWriteArrayList<Int>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Encapsulate the mutable collection to prevent external modification.

Exposing downloadingAppIds as a public mutable var allows external code to reassign or modify the list, which could lead to inconsistent state or unexpected behavior. Consider exposing it as a read-only List with controlled mutation methods, or at minimum make it val to prevent reassignment.

🔎 Proposed fix to expose as read-only
-    var downloadingAppIds: CopyOnWriteArrayList<Int>,
+    private val _downloadingAppIds: CopyOnWriteArrayList<Int>,
+) {
+    val downloadingAppIds: List<Int>
+        get() = _downloadingAppIds
+
+    fun addDownloadingAppId(appId: Int) {
+        _downloadingAppIds.add(appId)
+    }
+
+    fun removeDownloadingAppId(appId: Int) {
+        _downloadingAppIds.remove(appId)
+    }
+
+    fun clearDownloadingAppIds() {
+        _downloadingAppIds.clear()
+    }

If callers need to replace the entire list, retain the current design but change var to val:

-    var downloadingAppIds: CopyOnWriteArrayList<Int>,
+    val downloadingAppIds: CopyOnWriteArrayList<Int>,

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In @app/src/main/java/app/gamenative/data/DownloadInfo.kt at line 13, The
DownloadInfo data holder exposes a public mutable var downloadingAppIds:
CopyOnWriteArrayList<Int>, which allows external reassignment and mutation;
change this to a read-only exposure by making the property immutable (val) and
type it as List<Int> (or provide a private backing CopyOnWriteArrayList<Int>
with a public val downloadingAppIds: List<Int>) and add explicit methods on
DownloadInfo (e.g., addDownloadingAppId, removeDownloadingAppId,
replaceDownloadingAppIds) to control mutation so callers cannot reassign or
directly modify the internal list.

) {
private var downloadJob: Job? = null
private val downloadProgressListeners = mutableListOf<((Float) -> Unit)>()
Expand All @@ -32,13 +34,21 @@ data class DownloadInfo(
private val statusMessage = MutableStateFlow<String?>(null)

fun cancel() {
cancel("Cancelled by user")
}

fun failedToDownload() {
cancel("Failed to download")
}

fun cancel(message: String) {
// Persist the most recent progress so a resume can pick up where it left off.
persistProgressSnapshot()
// Mark as inactive and clear speed tracking so a future resume
// does not use stale samples.
setActive(false)
resetSpeedTracking()
downloadJob?.cancel(CancellationException("Cancelled by user"))
downloadJob?.cancel(CancellationException(message))
}

fun setDownloadJob(job: Job) {
Expand Down
14 changes: 14 additions & 0 deletions app/src/main/java/app/gamenative/data/DownloadingAppInfo.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package app.gamenative.data

import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity("downloading_app_info")
data class DownloadingAppInfo (
@PrimaryKey
val appId: Int,

@ColumnInfo("dlcAppIds")
val dlcAppIds: List<Int> = emptyList<Int>()
){}
7 changes: 6 additions & 1 deletion app/src/main/java/app/gamenative/db/PluviaDatabase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import app.gamenative.data.FileChangeLists
import app.gamenative.data.SteamApp
import app.gamenative.data.SteamLicense
import app.gamenative.data.CachedLicense
import app.gamenative.data.DownloadingAppInfo
import app.gamenative.data.EncryptedAppTicket
import app.gamenative.data.GOGGame
import app.gamenative.db.converters.AppConverter
Expand All @@ -24,6 +25,7 @@ import app.gamenative.db.dao.SteamAppDao
import app.gamenative.db.dao.SteamLicenseDao
import app.gamenative.db.dao.AppInfoDao
import app.gamenative.db.dao.CachedLicenseDao
import app.gamenative.db.dao.DownloadingAppInfoDao
import app.gamenative.db.dao.EncryptedAppTicketDao
import app.gamenative.db.dao.GOGGameDao

Expand All @@ -39,8 +41,9 @@ const val DATABASE_NAME = "pluvia.db"
SteamApp::class,
SteamLicense::class,
GOGGame::class,
DownloadingAppInfo::class
],
version = 9,
version = 10,
exportSchema = false, // Should export once stable.
)
@TypeConverters(
Expand Down Expand Up @@ -69,4 +72,6 @@ abstract class PluviaDatabase : RoomDatabase() {
abstract fun encryptedAppTicketDao(): EncryptedAppTicketDao

abstract fun gogGameDao(): GOGGameDao

abstract fun downloadingAppInfoDao(): DownloadingAppInfoDao
}
2 changes: 1 addition & 1 deletion app/src/main/java/app/gamenative/db/dao/AppInfoDao.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ interface AppInfoDao {
suspend fun update(appInfo: AppInfo)

@Query("SELECT * FROM app_info WHERE id = :appId")
suspend fun getInstalledDepots(appId: Int): AppInfo?
suspend fun getInstalledApp(appId: Int): AppInfo?

@Query("SELECT * FROM app_info WHERE id = :appId")
suspend fun get(appId: Int): AppInfo?
Expand Down
22 changes: 22 additions & 0 deletions app/src/main/java/app/gamenative/db/dao/DownloadingAppInfoDao.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package app.gamenative.db.dao

import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import app.gamenative.data.DownloadingAppInfo

@Dao
interface DownloadingAppInfoDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(appInfo: DownloadingAppInfo)

@Query("SELECT * FROM downloading_app_info WHERE appId = :appId")
suspend fun getDownloadingApp(appId: Int): DownloadingAppInfo?

@Query("DELETE from downloading_app_info WHERE appId = :appId")
suspend fun deleteApp(appId: Int)

@Query("DELETE from downloading_app_info")
suspend fun deleteAll()
}
13 changes: 11 additions & 2 deletions app/src/main/java/app/gamenative/db/dao/SteamAppDao.kt
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,20 @@ interface SteamAppDao {
@Query("SELECT * FROM steam_app WHERE id = :appId")
suspend fun findApp(appId: Int): SteamApp?

@Query("SELECT * FROM steam_app AS app WHERE dlc_for_app_id = :appId AND " +
@Query("SELECT * FROM steam_app AS app WHERE dlc_for_app_id = :appId AND depots <> '{}' AND " +
" EXISTS (" +
" SELECT * FROM steam_license AS license " +
" WHERE license.license_type <> 0 AND " +
" REPLACE(REPLACE(app_ids, '[', ','), ']', ',') LIKE ('%,' || app.id || ',%') " +
" REPLACE(REPLACE(license.app_ids, '[', ','), ']', ',') LIKE ('%,' || app.id || ',%') " +
")"
)
suspend fun findDownloadableDLCApps(appId: Int): List<SteamApp>?

@Query("SELECT * FROM steam_app AS app WHERE dlc_for_app_id = :appId AND depots = '{}' AND " +
" EXISTS (" +
" SELECT * FROM steam_license AS license " +
" WHERE license.license_type <> 0 AND " +
" REPLACE(REPLACE(license.app_ids, '[', ','), ']', ',') LIKE ('%,' || app.id || ',%') " +
Comment on lines +47 to +60
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question - why did we need to split this into two different queries, one with depots <> '{}' and one with depots = '{}'?

Why not leave out the depots clause entirely?

")"
)
suspend fun findHiddenDLCApps(appId: Int): List<SteamApp>?
Expand Down
5 changes: 5 additions & 0 deletions app/src/main/java/app/gamenative/di/DatabaseModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import app.gamenative.db.DATABASE_NAME
import app.gamenative.db.PluviaDatabase
import app.gamenative.db.dao.AppInfoDao
import app.gamenative.db.dao.CachedLicenseDao
import app.gamenative.db.dao.DownloadingAppInfoDao
import app.gamenative.db.dao.EncryptedAppTicketDao
import app.gamenative.db.migration.ROOM_MIGRATION_V7_to_V8
import dagger.Module
Expand Down Expand Up @@ -61,4 +62,8 @@ class DatabaseModule {
@Provides
@Singleton
fun provideGOGGameDao(db: PluviaDatabase) = db.gogGameDao()

@Provides
@Singleton
fun provideDownloadingAppInfoDao(db: PluviaDatabase): DownloadingAppInfoDao = db.downloadingAppInfoDao()
}
Loading
Loading