@@ -852,6 +852,10 @@ class GOGDownloadManager @Inject constructor(
852852 networkChunkFlow
853853 .flatMapMerge<String , Unit >(concurrency = parallelDownloads) { chunkMd5 ->
854854 flow<Unit > {
855+ if (! downloadInfo.isActive()) {
856+ return @flow
857+ }
858+
855859 val result = run {
856860 val urls = currentChunkUrlCandidates[chunkMd5] ? : return @run Result .failure<File >(
857861 Exception (" No URL candidates found for chunk $chunkMd5 " ),
@@ -872,6 +876,10 @@ class GOGDownloadManager @Inject constructor(
872876 assembleFlow
873877 .flatMapMerge<Pair <String , Result <File >>, Unit > (concurrency = parallelAssemble) { (chunkMd5, result) ->
874878 flow<Unit > {
879+ if (! downloadInfo.isActive()) {
880+ return @flow
881+ }
882+
875883 if (result.isSuccess && assemblyFailure == null ) {
876884 // Successful download - add to completed set and try assembly
877885 downloadedChunkIds.add(chunkMd5)
@@ -943,6 +951,10 @@ class GOGDownloadManager @Inject constructor(
943951 val chunksAdded = mutableListOf<String >()
944952
945953 files.forEach { file ->
954+ if (! downloadInfo.isActive()) {
955+ Timber .tag(" GOG" ).w(" Download cancelled during file iteration" )
956+ return @launch
957+ }
946958 Timber .tag(" GOG" ).v(" Pre-allocating ${file.path} " )
947959
948960 // Allocating file before download
@@ -975,6 +987,12 @@ class GOGDownloadManager @Inject constructor(
975987 var currentPendingChunks = lastPendingChunks
976988 var samePendingChunksAttempts = 0
977989 while (currentPendingChunks > 0 ) {
990+ if (! downloadInfo.isActive()) {
991+ networkChunkJob.cancel()
992+ assembleJob.cancel()
993+ return @withContext Result .failure(Exception (" Download cancelled" ))
994+ }
995+
978996 Timber .tag(" GOG" ).d(" Waiting for $currentPendingChunks pending chunks to complete" )
979997
980998 if (currentPendingChunks == lastPendingChunks) {
@@ -1009,9 +1027,6 @@ class GOGDownloadManager @Inject constructor(
10091027 // Cancel the assemble flow jobs since no more files will be added
10101028 assembleJob.cancel()
10111029
1012- // Remove the cache dir
1013- chunkCacheDir.deleteRecursively()
1014-
10151030 if (assemblyFailure != null ) {
10161031 return @withContext Result .failure(assemblyFailure!! )
10171032 }
@@ -1289,6 +1304,10 @@ class GOGDownloadManager @Inject constructor(
12891304 var lastException: Exception ? = null
12901305
12911306 repeat(MAX_CHUNK_RETRIES ) { attempt ->
1307+ if (! downloadInfo.isActive()) {
1308+ return @withContext Result .failure(Exception (" Download cancelled" ))
1309+ }
1310+
12921311 val url = urlCandidates[attempt % urlCandidates.size]
12931312 val result = downloadChunk(chunkMd5, url, chunkCacheDir, downloadInfo, httpClient)
12941313
@@ -1303,8 +1322,11 @@ class GOGDownloadManager @Inject constructor(
13031322
13041323 if (attempt < MAX_CHUNK_RETRIES - 1 ) {
13051324 val delay = RETRY_DELAY_MS * (1 shl attempt) // Exponential backoff: 1s, 2s, 4s
1306- Timber .tag(" GOG" ).w(" Chunk $chunkMd5 download failed (attempt ${attempt + 1 } /$MAX_CHUNK_RETRIES ): ${lastException?.message} . Retrying in ${delay} ms..." )
1307- kotlinx.coroutines.delay(delay)
1325+ if (downloadInfo.isActive()) {
1326+ Timber .tag(" GOG" )
1327+ .w(" Chunk $chunkMd5 download failed (attempt ${attempt + 1 } /$MAX_CHUNK_RETRIES ): ${lastException?.message} . Retrying in ${delay} ms..." )
1328+ delay(delay)
1329+ }
13081330 }
13091331 }
13101332
@@ -1328,6 +1350,10 @@ class GOGDownloadManager @Inject constructor(
13281350 httpClient : OkHttpClient ,
13291351 ): Result <File > = withContext(Dispatchers .IO ) {
13301352 try {
1353+ if (! downloadInfo.isActive()) {
1354+ return @withContext Result .failure(Exception (" Download cancelled" ))
1355+ }
1356+
13311357 val chunkFile = File (chunkCacheDir, " $chunkMd5 .chunk" )
13321358 val tempChunkFile = File (chunkCacheDir, " $chunkMd5 .chunk.part" )
13331359
@@ -1580,6 +1606,7 @@ class GOGDownloadManager @Inject constructor(
15801606 /* *
15811607 * Check if file exists and has the expected size. When [expectedMd5] is non-null/non-blank,
15821608 * also verifies content MD5 to reject corrupted files; short-circuits on size mismatch before hashing.
1609+ * When [expectedMd5] is null/blank, returns false to avoid treating pre-allocated files as complete.
15831610 */
15841611 private fun fileExistsWithCorrectSize (
15851612 outputFile : File ,
@@ -1588,7 +1615,8 @@ class GOGDownloadManager @Inject constructor(
15881615 ): Boolean {
15891616 if (! outputFile.exists()) return false
15901617 if (outputFile.length() != expectedSize) return false
1591- return expectedMd5.isNullOrBlank() || calculateMd5File(outputFile).equals(expectedMd5, ignoreCase = true )
1618+ if (expectedMd5.isNullOrBlank()) return false
1619+ return calculateMd5File(outputFile).equals(expectedMd5, ignoreCase = true )
15921620 }
15931621 /* *
15941622 * Calculate MD5 hash of file
0 commit comments