Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
61 changes: 61 additions & 0 deletions app/src/main/java/app/gamenative/service/SteamService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ import `in`.dragonbra.javasteam.steam.handlers.steamapps.GamePlayedInfo
import `in`.dragonbra.javasteam.steam.handlers.steamapps.License
import `in`.dragonbra.javasteam.steam.handlers.steamapps.PICSRequest
import `in`.dragonbra.javasteam.steam.handlers.steamapps.SteamApps
import `in`.dragonbra.javasteam.steam.handlers.steamapps.callback.DepotKeyCallback
import `in`.dragonbra.javasteam.steam.handlers.steamapps.callback.LicenseListCallback
import `in`.dragonbra.javasteam.steam.handlers.steamcloud.SteamCloud
import `in`.dragonbra.javasteam.steam.handlers.steamfriends.SteamFriends
Expand Down Expand Up @@ -135,6 +136,7 @@ import java.util.concurrent.CancellationException
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger
import javax.inject.Inject
import kotlin.io.path.pathString
import kotlin.time.Duration.Companion.seconds
Expand Down Expand Up @@ -361,6 +363,53 @@ class SteamService : Service(), IChallengeUrlChanged {
/** Apps with a workshop download that was paused (cancelled) by the user. */
val workshopPausedApps: MutableSet<Int> = ConcurrentHashMap.newKeySet()

// Depot-key acquisition progress, keyed by appId. Useful for games with many depots like Borderlands 2
private val depotKeyPrep = ConcurrentHashMap<Int, DepotKeyPrep>()

private class DepotKeyPrep(val total: Int) {
val resolved = AtomicInteger(0)
}

private val depotKeyOwner = ConcurrentHashMap<Int, Int>()

// Guards the prep bookkeeping together with the status messages it writes, so a key
// callback can never re-post "Preparing depots" after the phase has already been ended.
private val depotKeyPrepLock = Any()

// Begins the depot key prep phase, seeding a status message so the UI never shows a bare 0%
private fun beginDepotKeyPrep(appId: Int, depotIds: Set<Int>, downloadInfo: DownloadInfo) {
synchronized(depotKeyPrepLock) {
depotKeyPrep[appId] = DepotKeyPrep(depotIds.size)
depotIds.forEach { depotId -> depotKeyOwner[depotId] = appId }
instance?.let { downloadInfo.updateStatusMessage(it.getString(R.string.download_preparing)) }
}
}

/** Called once per depot key Steam returns, from the DepotKeyCallback subscription. */
private fun noteDepotKeyResolved(depotId: Int) {
val appId = depotKeyOwner[depotId] ?: return
val svc = instance ?: return
synchronized(depotKeyPrepLock) {
val prep = depotKeyPrep[appId] ?: return
val done = prep.resolved.incrementAndGet().coerceAtMost(prep.total)
downloadJobs[appId]?.updateStatusMessage(
svc.getString(R.string.download_preparing_depots, done, prep.total),
)
}
}

// Ends the depot key prep phase and clears its status message, once the actual download
// begins or the job goes away. Must run before the DownloadInfo leaves downloadJobs.
private fun clearDepotKeyPrep(appId: Int) {
if (!depotKeyPrep.containsKey(appId)) return
synchronized(depotKeyPrepLock) {
depotKeyOwner.entries.removeIf { it.value == appId }
if (depotKeyPrep.remove(appId) != null) {
downloadJobs[appId]?.updateStatusMessage(null)
}
}
Comment thread
jamierajewski marked this conversation as resolved.
}

internal fun notifyDownloadStarted(appId: Int) {
PluviaApp.events.emit(AndroidEvent.DownloadStatusChanged(appId, true))
}
Expand All @@ -370,6 +419,7 @@ class SteamService : Service(), IChallengeUrlChanged {
}

fun removeDownloadJob(appId: Int) {
clearDepotKeyPrep(appId)
val removed = downloadJobs.remove(appId)
if (removed != null) {
notifyDownloadStopped(appId)
Expand Down Expand Up @@ -1828,6 +1878,14 @@ class SteamService : Service(), IChallengeUrlChanged {
// downloadApp call returns the stale DownloadInfo from the still-populated
// map (line ~1666 short-circuit).
downloadJobs[appId] = di

// Depot keys are fetched one per depot before the first chunk arrives, and
// nothing in IDownloadListener reports that phase. Seed a status message now
// so the UI never shows a bare 0% with no explanation; noteDepotKeyResolved
// refines it into a running count as keys resolve. Register before launching,
// since keys start arriving almost immediately.
beginDepotKeyPrep(appId, selectedDepots.keys, di)

notifyDownloadStarted(appId)
instance?.notifierOrNull?.trackDownload(di, getAppInfoOf(appId)?.name.orEmpty(), NotificationHelper.NOTIFICATION_ID_STEAM)

Expand Down Expand Up @@ -2275,6 +2333,8 @@ class SteamService : Service(), IChallengeUrlChanged {
) {
val isFirstCallForDepot = !depotCumulativeCompressedBytes.containsKey(depotId)

clearDepotKeyPrep(downloadInfo.gameId)
Comment thread
jamierajewski marked this conversation as resolved.
Outdated

val previousBytes = depotCumulativeCompressedBytes[depotId] ?: 0L
val deltaBytes = compressedBytes - previousBytes
depotCumulativeCompressedBytes[depotId] = compressedBytes
Expand Down Expand Up @@ -3510,6 +3570,7 @@ class SteamService : Service(), IChallengeUrlChanged {
add(subscribe(PersonaStateCallback::class.java, ::onPersonaStateReceived))
add(subscribe(LicenseListCallback::class.java, ::onLicenseList))
add(subscribe(PlayingSessionStateCallback::class.java, ::onPlayingSessionState))
add(subscribe(DepotKeyCallback::class.java) { noteDepotKeyResolved(it.depotID) })
}
}

Expand Down
2 changes: 2 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,8 @@
<string name="downloading">Downloading...</string>
<string name="calculating_time">Calculating...</string>
<string name="download_failed_try_again">Download failed. Please try again.</string>
<string name="download_preparing">Preparing download...</string>
<string name="download_preparing_depots">Preparing depots (%1$d/%2$d)</string>
<string name="update_available">Update Available</string>
<string name="game_information">Game Information</string>
<string name="status">Status</string>
Expand Down