diff --git a/javasteam-depotdownloader/src/main/kotlin/in/dragonbra/javasteam/depotdownloader/DepotDownloader.kt b/javasteam-depotdownloader/src/main/kotlin/in/dragonbra/javasteam/depotdownloader/DepotDownloader.kt index 3a8498f5..e7152522 100644 --- a/javasteam-depotdownloader/src/main/kotlin/in/dragonbra/javasteam/depotdownloader/DepotDownloader.kt +++ b/javasteam-depotdownloader/src/main/kotlin/in/dragonbra/javasteam/depotdownloader/DepotDownloader.kt @@ -154,6 +154,9 @@ class DepotDownloader @JvmOverloads constructor( private val listeners = CopyOnWriteArrayList() + @Volatile + private var currentPhase: DownloadPhase = DownloadPhase.UNKNOWN + private val progressUpdateInterval = 500L // ms private val scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob(parentJob)) @@ -984,6 +987,7 @@ class DepotDownloader @JvmOverloads constructor( } else { logger?.debug("Downloading depot ${depot.depotId} manifest") notifyListeners { it.onStatusUpdate("Downloading manifest for depot ${depot.depotId}") } + transitionPhase(DownloadPhase.PREPARING) var manifestRequestCode: ULong = 0U var manifestRequestCodeExpiration = Instant.MIN @@ -1270,6 +1274,7 @@ class DepotDownloader @JvmOverloads constructor( if (!fileDidExist) { logger?.debug("Pre-allocating: $fileFinalPath") notifyListeners { it.onStatusUpdate("Allocating file: ${file.fileName}") } + transitionPhase(DownloadPhase.PREPARING) // create new file. need all chunks try { @@ -1390,6 +1395,7 @@ class DepotDownloader @JvmOverloads constructor( filesystem.openReadOnly(fileFinalPath).use { handle -> logger?.debug("Validating $fileFinalPath") notifyListeners { it.onStatusUpdate("Validating: ${file.fileName}") } + transitionPhase(DownloadPhase.VERIFYING) neededChunks = Util.validateSteam3FileChecksums( handle = handle, @@ -1570,6 +1576,9 @@ class DepotDownloader @JvmOverloads constructor( // Throw the cancellation exception if requested so that this task is marked failed ensureActive() + // Transition to DOWNLOADING while actively fetching chunks from the network + transitionPhase(DownloadPhase.DOWNLOADING) + // Create temporary file path for this chunk val chunkTempDir = depot.installDir / STAGING_DIR / "chunks" / fileId filesystem.createDirectories(chunkTempDir) @@ -1625,6 +1634,7 @@ class DepotDownloader @JvmOverloads constructor( } if (processingItemsMap.isEmpty()) { + transitionPhase(DownloadPhase.COMPLETE) completionFuture.complete(null) } } @@ -1647,6 +1657,13 @@ class DepotDownloader @JvmOverloads constructor( } } + // Notify listeners only when the phase actually changes + private fun transitionPhase(newPhase: DownloadPhase) { + if (currentPhase == newPhase) return + currentPhase = newPhase + notifyListeners { it.onPhaseChanged(newPhase) } + } + // endregion // region [REGION] Queue Operations @@ -1889,6 +1906,9 @@ class DepotDownloader @JvmOverloads constructor( val depotPercentage = (sizeDownloaded.toFloat() / depotDownloadCounter.completeDownloadSize) + // Transition to DECOMPRESSING while processing decompression chunks + transitionPhase(DownloadPhase.DECOMPRESSING) + notifyListeners { listener -> listener.onChunkCompleted( depotId = depot.depotId, diff --git a/javasteam-depotdownloader/src/main/kotlin/in/dragonbra/javasteam/depotdownloader/DownloadPhase.kt b/javasteam-depotdownloader/src/main/kotlin/in/dragonbra/javasteam/depotdownloader/DownloadPhase.kt new file mode 100644 index 00000000..71ce698c --- /dev/null +++ b/javasteam-depotdownloader/src/main/kotlin/in/dragonbra/javasteam/depotdownloader/DownloadPhase.kt @@ -0,0 +1,11 @@ +package `in`.dragonbra.javasteam.depotdownloader + +// High level download phases reported by DepotDownloader +enum class DownloadPhase { + UNKNOWN, + PREPARING, + DOWNLOADING, + DECOMPRESSING, + VERIFYING, + COMPLETE, +} diff --git a/javasteam-depotdownloader/src/main/kotlin/in/dragonbra/javasteam/depotdownloader/IDownloadListener.kt b/javasteam-depotdownloader/src/main/kotlin/in/dragonbra/javasteam/depotdownloader/IDownloadListener.kt index f005c6cb..f3ecb715 100644 --- a/javasteam-depotdownloader/src/main/kotlin/in/dragonbra/javasteam/depotdownloader/IDownloadListener.kt +++ b/javasteam-depotdownloader/src/main/kotlin/in/dragonbra/javasteam/depotdownloader/IDownloadListener.kt @@ -68,4 +68,7 @@ interface IDownloadListener { * @param uncompressedBytes Actual data size (uncompressed) */ fun onDepotCompleted(depotId: Int, compressedBytes: Long, uncompressedBytes: Long) {} + + // Called when the download transitions to a new phase + fun onPhaseChanged(phase: DownloadPhase) {} }