Skip to content

Commit

Permalink
Implement Getting Download Progress
Browse files Browse the repository at this point in the history
  • Loading branch information
gunishjain committed Nov 14, 2024
1 parent 24cc7c4 commit 5ace8b2
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 43 deletions.
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

<application
android:name=".MyApplication"
Expand Down
37 changes: 33 additions & 4 deletions app/src/main/java/com/gunishjain/sample/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.gunishjain.sample

import android.os.Bundle
import android.os.Environment
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
Expand All @@ -20,6 +22,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import java.io.File

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
Expand All @@ -40,10 +43,14 @@ fun DownloadUI() {
var isDownloading by remember { mutableStateOf(false) }
var isPaused by remember { mutableStateOf(false) }
var downloadId by remember { mutableIntStateOf(-1) }
var downloadComplete by remember { mutableStateOf(false) }
var status by remember { mutableStateOf("") }
val downloadsDirectory = context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)

// Start download
fun startDownload(url: String, dirPath: String, fileName: String) {
val request = grabbit.newRequest(url, dirPath, fileName).build()
fun startDownload(url: String, dirPath: File?, fileName: String) {
if (dirPath != null && dirPath.exists()) {
val request = grabbit.newRequest(url, dirPath.absolutePath, fileName).build()

downloadId = grabbit.enqueue(
request,
Expand All @@ -59,11 +66,21 @@ fun DownloadUI() {
},
onCompleted = {
isDownloading = false
if (File(downloadsDirectory, "lmao.pdf").exists()) {
Log.d("Compose", "File found after download")
} else {
Log.d("Compose", "File Not found after download",)
}
downloadComplete = true

},
onError = { error ->
// Handle error (optional)
}
)
} else {
// Handle invalid directory (optional)
}
}

// Pause download
Expand Down Expand Up @@ -99,6 +116,18 @@ fun DownloadUI() {

Spacer(modifier = Modifier.height(32.dp))

status = if(downloadComplete) {
"Download Completed"
} else if(isDownloading) {
"Downloading"
} else {
"Idle"
}

Text("Status : $status" )

Spacer(modifier = Modifier.height(32.dp))

// Buttons for control
if (isDownloading) {
if (isPaused) {
Expand All @@ -118,8 +147,8 @@ fun DownloadUI() {
Button(onClick = {
startDownload(
url = "https://www.learningcontainer.com/download/sample-50-mb-pdf-file/?wpdmdl=3675&refresh=6721f942bd70b1730279746",
dirPath = "/downloads",
fileName = "gunish.pdf"
dirPath = downloadsDirectory,
fileName = "himnashunew.pdf"
)
}) {
Text("Download")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import java.util.concurrent.ConcurrentHashMap

class DownloadDispatcher(private val httpClient: HttpClient) {

private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
private val downloadTasks = mutableMapOf<Int, DownloadTask>()
private val downloadTasks = ConcurrentHashMap<Int, DownloadTask>()

private fun executeOnMain(block: () -> Unit){
scope.launch {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
package com.gunishjain.grabbit.internal.download

import android.util.Log
import com.gunishjain.grabbit.internal.network.HttpClient
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
import okhttp3.internal.notify
import okhttp3.internal.wait
import java.io.File
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicLong
import kotlin.coroutines.cancellation.CancellationException
import kotlin.coroutines.resume

class DownloadTask(private val request: DownloadRequest,private val httpClient: HttpClient) {


private var isPaused = false
private var isPaused = AtomicBoolean(false)
private var isCompleted = false
private var file = File(request.dirPath, request.fileName)
private var downloadedBytes = 0L
private var downloadedBytes = AtomicLong(0L) // Use AtomicLong for thread safety
private val pauseLock = Object()

suspend fun run(
onStart: () -> Unit = {},
Expand All @@ -28,22 +37,32 @@ class DownloadTask(private val request: DownloadRequest,private val httpClient:

// Get initial file size if resuming
if (file.exists()) {
downloadedBytes = file.length()
downloadedBytes.set(file.length())
}

if (request.totalBytes <= 0) {
request.totalBytes = httpClient.getFileSize(request.url)
Log.d("Download Task",request.totalBytes.toString())
}

while (!isPaused) {
while (!isCompleted) {
if (isPaused.get()) {
Log.d("DownloadTask", "Download paused.")
onPause()
waitForResume()
Log.d("DownloadTask", "Download resumed.")
continue // Important: restart the loop after resume
}

try {
Log.d("DownloadTask", "Connecting to download from byte: ${downloadedBytes.get()}")
httpClient.connect(
url = request.url,
file = file,
startByte = downloadedBytes,
startByte = downloadedBytes.get(),
timeout = request.connectTimeout
) { currentBytes, totalBytes ->
downloadedBytes = currentBytes
downloadedBytes.set(currentBytes)

// Calculate and report progress
val progress = if (totalBytes > 0) {
Expand All @@ -52,24 +71,33 @@ class DownloadTask(private val request: DownloadRequest,private val httpClient:
-1
}
onProgress(progress)

// Check pause status during download
if (isPaused.get()) {
throw PauseException()
}
}

// If we reach here, download is complete
isCompleted = true
onCompleted()
Log.d("DownloadTask", "Download completed.")
break

} catch (e: PauseException) {
// Handle pause specifically
continue
} catch (e: CancellationException) {
throw e
} catch (e: Exception) {
if (isPaused) {
if (isPaused.get()) {
onPause()
waitForResume()
continue
}
throw e
}
}

onCompleted()

} catch (e: CancellationException){
throw e
} catch (e: Exception) {
Expand All @@ -81,11 +109,36 @@ class DownloadTask(private val request: DownloadRequest,private val httpClient:
}

fun pauseDownload() {
isPaused = true
Log.d("DownloadTask", "pauseDownload() called.")
isPaused.set(true)
}

fun resumeDownload() {
isPaused = false
Log.d("DownloadTask", "resumeDownload() called.")
isPaused.set(false)
synchronized(pauseLock) {
pauseLock.notify()
}
}

private suspend fun waitForResume() {
suspendCancellableCoroutine<Unit> { continuation ->
synchronized(pauseLock) {
while (isPaused.get()) {
try {
Log.d("DownloadTask", "Waiting for resume.")
pauseLock.wait()
} catch (e: InterruptedException) {
continuation.resume(Unit)
return@synchronized
}
}
continuation.resume(Unit)
}
}
}

private class PauseException : Exception()


}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.gunishjain.grabbit.internal.network

import android.util.Log
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
Expand Down Expand Up @@ -38,45 +39,54 @@ class DefaultHttpClient : HttpClient {
}
.build()

val response = okHttpClient.newCall(request).execute()
okHttpClient.newCall(request).execute().use { response ->

if (!response.isSuccessful) {
throw IOException("Unexpected response code: ${response.code}")
}
if (!response.isSuccessful) {
throw IOException("Unexpected response code: ${response.code}")
}

// Get content length from header
val contentLength = response.header("Content-Length")?.toLong() ?: -1L
val totalBytes = if (contentLength != -1L) contentLength + startByte else -1L
// Get content length from header
val contentLength = response.header("Content-Length")?.toLong() ?: -1L
val totalBytes = if (contentLength != -1L) contentLength + startByte else -1L


// Create parent directories if they don't exist
file.parentFile?.mkdirs()
Log.d("Default HTTPCLIENT", "Total File Size: $totalBytes")

// Use response body to write to file
response.body?.let { body ->
val bufferedSink = file.sink(append = startByte > 0).buffer()
val source = body.source()
val buffer = Buffer()
var downloadedBytes = startByte
// Create parent directories if they don't exist
file.parentFile?.mkdirs()

while (true) {
val read = source.read(buffer, 8192L) // Read chunks of 8KB
if (read == -1L) break
// Use response body to write to file
response.body?.let { body ->

bufferedSink.write(buffer, read)
downloadedBytes += read
Log.d("HTTPCLIENT",body.toString())
val bufferedSink = file.sink(append = startByte > 0).buffer()
val source = body.source()
val buffer = Buffer()
var downloadedBytes = startByte

// Report progress
onProgress(downloadedBytes, totalBytes)
}
while (true) {
val read = source.read(buffer, 8192L) // Read chunks of 8KB
if (read == -1L) break

bufferedSink.write(buffer, read)
downloadedBytes += read

// Report progress

// Log.d("HTTPCLIENT",downloadedBytes.toString())

onProgress(downloadedBytes, totalBytes)
}

bufferedSink.close()
source.close()
} ?: throw IOException("Response body is null")
bufferedSink.close()
source.close()
} ?: throw IOException("Response body is null")
}

} catch (e: CancellationException) {
throw e
} catch (e: Exception) {
Log.e("HTTPCLEINT", "Error occurred: ${e.message}", e)
throw IOException("Download failed: ${e.message}", e)
}

Expand Down

0 comments on commit 5ace8b2

Please sign in to comment.