Loading app/src/main/java/foundation/e/apps/data/install/DownloadProgressTracker.kt +4 −3 Original line number Diff line number Diff line Loading @@ -52,7 +52,7 @@ class DownloadProgressTracker @Inject constructor( appInstall: AppInstall, progress: DownloadProgress ): Int { val downloadIds = appInstall.downloadIdMap.keys val downloadIds = appInstall.downloadIdMap.keys.toSet() if (downloadIds.isEmpty()) { return 0 } Loading Loading @@ -85,12 +85,13 @@ class DownloadProgressTracker @Inject constructor( application?.let { app -> val appDownload = appManager.getDownloadList() .singleOrNull { it.id.contentEquals(app._id) } val downloadIds = appDownload?.downloadIdMap?.keys?.toSet().orEmpty() val downloadingMap = progress.totalSizeBytes.filter { item -> appDownload?.downloadIdMap?.keys?.contains(item.key) == true downloadIds.contains(item.key) } val totalSizeBytes = downloadingMap.values.sum() val downloadedSoFar = progress.bytesDownloadedSoFar.filter { item -> appDownload?.downloadIdMap?.keys?.contains(item.key) == true downloadIds.contains(item.key) }.values.sum() return Pair(totalSizeBytes, downloadedSoFar) Loading app/src/main/java/foundation/e/apps/data/install/download/data/DownloadProgress.kt +9 −1 Original line number Diff line number Diff line Loading @@ -23,4 +23,12 @@ data class DownloadProgress( var bytesDownloadedSoFar: MutableMap<Long, Long> = mutableMapOf(), var status: MutableMap<Long, Boolean> = mutableMapOf(), var downloadId: Long = -1 ) { fun snapshot(): DownloadProgress { return copy( totalSizeBytes = totalSizeBytes.toMutableMap(), bytesDownloadedSoFar = bytesDownloadedSoFar.toMutableMap(), status = status.toMutableMap(), ) } } app/src/main/java/foundation/e/apps/data/install/download/data/DownloadProgressLD.kt +3 −3 Original line number Diff line number Diff line Loading @@ -87,7 +87,7 @@ class DownloadProgressLD @Inject constructor( } ?: run { Timber.w("DownloadManager returned null cursor for ids $downloadingIds") postValue(downloadProgress) postValue(downloadProgress.snapshot()) } } Loading @@ -96,7 +96,7 @@ class DownloadProgressLD @Inject constructor( downloadingIds: List<Long> ) { if (!cursor.moveToFirst()) { postValue(downloadProgress) postValue(downloadProgress.snapshot()) return } Loading Loading @@ -130,7 +130,7 @@ class DownloadProgressLD @Inject constructor( } } while (cursor.moveToNext()) postValue(downloadProgress) postValue(downloadProgress.snapshot()) } override fun onInactive() { Loading app/src/test/java/foundation/e/apps/data/install/download/data/DownloadProgressLDTest.kt +60 −12 Original line number Diff line number Diff line Loading @@ -39,19 +39,12 @@ class DownloadProgressLDTest { @Test fun processCursor_updatesProgressForSuccessfulDownload() { val cursor: Cursor = mock() whenever(cursor.moveToFirst()).thenReturn(true) whenever(cursor.moveToNext()).thenReturn(false) whenever(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_ID)).thenReturn(0) whenever(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS)).thenReturn(1) whenever(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES)).thenReturn(2) whenever(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR)).thenReturn( 3 val cursor = progressCursor( id = 10L, status = DownloadManager.STATUS_SUCCESSFUL, totalSizeBytes = 200L, bytesDownloadedSoFar = 50L, ) whenever(cursor.getLong(0)).thenReturn(10L) whenever(cursor.getInt(1)).thenReturn(DownloadManager.STATUS_SUCCESSFUL) whenever(cursor.getLong(2)).thenReturn(200L) whenever(cursor.getLong(3)).thenReturn(50L) var emitted: DownloadProgress? = null downloadProgressLD.observeForever { emitted = it } Loading @@ -63,4 +56,59 @@ class DownloadProgressLDTest { assertThat(emitted?.totalSizeBytes?.get(10L)).isEqualTo(200L) assertThat(emitted?.status?.get(10L)).isTrue() } @Test fun processCursor_emitsSnapshotThatIsNotMutatedByLaterProgressUpdates() { var emitted: DownloadProgress? = null downloadProgressLD.observeForever { emitted = it } processCursor.invoke( downloadProgressLD, progressCursor( id = 10L, status = DownloadManager.STATUS_RUNNING, totalSizeBytes = 200L, bytesDownloadedSoFar = 50L, ), listOf(10L), ) val firstEmission = requireNotNull(emitted) processCursor.invoke( downloadProgressLD, progressCursor( id = 10L, status = DownloadManager.STATUS_RUNNING, totalSizeBytes = 200L, bytesDownloadedSoFar = 100L, ), listOf(10L), ) assertThat(firstEmission.bytesDownloadedSoFar[10L]).isEqualTo(50L) assertThat(emitted).isNotSameInstanceAs(firstEmission) assertThat(emitted?.bytesDownloadedSoFar?.get(10L)).isEqualTo(100L) } private fun progressCursor( id: Long, status: Int, totalSizeBytes: Long, bytesDownloadedSoFar: Long, ): Cursor { val cursor: Cursor = mock() whenever(cursor.moveToFirst()).thenReturn(true) whenever(cursor.moveToNext()).thenReturn(false) whenever(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_ID)).thenReturn(0) whenever(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS)).thenReturn(1) whenever(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES)).thenReturn(2) whenever(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR)).thenReturn( 3 ) whenever(cursor.getLong(0)).thenReturn(id) whenever(cursor.getInt(1)).thenReturn(status) whenever(cursor.getLong(2)).thenReturn(totalSizeBytes) whenever(cursor.getLong(3)).thenReturn(bytesDownloadedSoFar) return cursor } } Loading
app/src/main/java/foundation/e/apps/data/install/DownloadProgressTracker.kt +4 −3 Original line number Diff line number Diff line Loading @@ -52,7 +52,7 @@ class DownloadProgressTracker @Inject constructor( appInstall: AppInstall, progress: DownloadProgress ): Int { val downloadIds = appInstall.downloadIdMap.keys val downloadIds = appInstall.downloadIdMap.keys.toSet() if (downloadIds.isEmpty()) { return 0 } Loading Loading @@ -85,12 +85,13 @@ class DownloadProgressTracker @Inject constructor( application?.let { app -> val appDownload = appManager.getDownloadList() .singleOrNull { it.id.contentEquals(app._id) } val downloadIds = appDownload?.downloadIdMap?.keys?.toSet().orEmpty() val downloadingMap = progress.totalSizeBytes.filter { item -> appDownload?.downloadIdMap?.keys?.contains(item.key) == true downloadIds.contains(item.key) } val totalSizeBytes = downloadingMap.values.sum() val downloadedSoFar = progress.bytesDownloadedSoFar.filter { item -> appDownload?.downloadIdMap?.keys?.contains(item.key) == true downloadIds.contains(item.key) }.values.sum() return Pair(totalSizeBytes, downloadedSoFar) Loading
app/src/main/java/foundation/e/apps/data/install/download/data/DownloadProgress.kt +9 −1 Original line number Diff line number Diff line Loading @@ -23,4 +23,12 @@ data class DownloadProgress( var bytesDownloadedSoFar: MutableMap<Long, Long> = mutableMapOf(), var status: MutableMap<Long, Boolean> = mutableMapOf(), var downloadId: Long = -1 ) { fun snapshot(): DownloadProgress { return copy( totalSizeBytes = totalSizeBytes.toMutableMap(), bytesDownloadedSoFar = bytesDownloadedSoFar.toMutableMap(), status = status.toMutableMap(), ) } }
app/src/main/java/foundation/e/apps/data/install/download/data/DownloadProgressLD.kt +3 −3 Original line number Diff line number Diff line Loading @@ -87,7 +87,7 @@ class DownloadProgressLD @Inject constructor( } ?: run { Timber.w("DownloadManager returned null cursor for ids $downloadingIds") postValue(downloadProgress) postValue(downloadProgress.snapshot()) } } Loading @@ -96,7 +96,7 @@ class DownloadProgressLD @Inject constructor( downloadingIds: List<Long> ) { if (!cursor.moveToFirst()) { postValue(downloadProgress) postValue(downloadProgress.snapshot()) return } Loading Loading @@ -130,7 +130,7 @@ class DownloadProgressLD @Inject constructor( } } while (cursor.moveToNext()) postValue(downloadProgress) postValue(downloadProgress.snapshot()) } override fun onInactive() { Loading
app/src/test/java/foundation/e/apps/data/install/download/data/DownloadProgressLDTest.kt +60 −12 Original line number Diff line number Diff line Loading @@ -39,19 +39,12 @@ class DownloadProgressLDTest { @Test fun processCursor_updatesProgressForSuccessfulDownload() { val cursor: Cursor = mock() whenever(cursor.moveToFirst()).thenReturn(true) whenever(cursor.moveToNext()).thenReturn(false) whenever(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_ID)).thenReturn(0) whenever(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS)).thenReturn(1) whenever(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES)).thenReturn(2) whenever(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR)).thenReturn( 3 val cursor = progressCursor( id = 10L, status = DownloadManager.STATUS_SUCCESSFUL, totalSizeBytes = 200L, bytesDownloadedSoFar = 50L, ) whenever(cursor.getLong(0)).thenReturn(10L) whenever(cursor.getInt(1)).thenReturn(DownloadManager.STATUS_SUCCESSFUL) whenever(cursor.getLong(2)).thenReturn(200L) whenever(cursor.getLong(3)).thenReturn(50L) var emitted: DownloadProgress? = null downloadProgressLD.observeForever { emitted = it } Loading @@ -63,4 +56,59 @@ class DownloadProgressLDTest { assertThat(emitted?.totalSizeBytes?.get(10L)).isEqualTo(200L) assertThat(emitted?.status?.get(10L)).isTrue() } @Test fun processCursor_emitsSnapshotThatIsNotMutatedByLaterProgressUpdates() { var emitted: DownloadProgress? = null downloadProgressLD.observeForever { emitted = it } processCursor.invoke( downloadProgressLD, progressCursor( id = 10L, status = DownloadManager.STATUS_RUNNING, totalSizeBytes = 200L, bytesDownloadedSoFar = 50L, ), listOf(10L), ) val firstEmission = requireNotNull(emitted) processCursor.invoke( downloadProgressLD, progressCursor( id = 10L, status = DownloadManager.STATUS_RUNNING, totalSizeBytes = 200L, bytesDownloadedSoFar = 100L, ), listOf(10L), ) assertThat(firstEmission.bytesDownloadedSoFar[10L]).isEqualTo(50L) assertThat(emitted).isNotSameInstanceAs(firstEmission) assertThat(emitted?.bytesDownloadedSoFar?.get(10L)).isEqualTo(100L) } private fun progressCursor( id: Long, status: Int, totalSizeBytes: Long, bytesDownloadedSoFar: Long, ): Cursor { val cursor: Cursor = mock() whenever(cursor.moveToFirst()).thenReturn(true) whenever(cursor.moveToNext()).thenReturn(false) whenever(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_ID)).thenReturn(0) whenever(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS)).thenReturn(1) whenever(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES)).thenReturn(2) whenever(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR)).thenReturn( 3 ) whenever(cursor.getLong(0)).thenReturn(id) whenever(cursor.getInt(1)).thenReturn(status) whenever(cursor.getLong(2)).thenReturn(totalSizeBytes) whenever(cursor.getLong(3)).thenReturn(bytesDownloadedSoFar) return cursor } }