Skip to content

Instantly share code, notes, and snippets.

@ExploiTR
Created August 26, 2024 18:41
Show Gist options
  • Save ExploiTR/14671f8a21a55ff69a34d895ec877c00 to your computer and use it in GitHub Desktop.
Save ExploiTR/14671f8a21a55ff69a34d895ec877c00 to your computer and use it in GitHub Desktop.
IDM Clone :
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import kotlinx.coroutines.*
import java.io.File
import java.io.RandomAccessFile
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
import java.util.concurrent.atomic.AtomicLongArray
import kotlin.math.roundToInt
class DownloadState {
var url by mutableStateOf("https://redirector.gvt1.com/edgedl/android/studio/install/2024.1.1.13/android-studio-2024.1.1.13-windows.exe")
var filePath by mutableStateOf(System.getProperty("user.home") + File.separator + "Downloads" + File.separator + "sample.exe")
var progress by mutableStateOf(0f)
var speed by mutableStateOf("0 B/s")
var error by mutableStateOf<String?>(null)
var threads by mutableStateOf("4")
var isDownloading by mutableStateOf(false)
var isPaused by mutableStateOf(false)
var canPause by mutableStateOf(false)
var totalFileSize by mutableStateOf("0 B")
var downloadedSize by mutableStateOf("0 B")
var timeElapsed by mutableStateOf("00:00:00")
var timeLeft by mutableStateOf("--:--:--")
var partProgress by mutableStateOf(listOf<Float>())
}
@Composable
fun App() {
val state = remember { DownloadState() }
val coroutineScope = rememberCoroutineScope()
var downloadJob by remember { mutableStateOf<Job?>(null) }
MaterialTheme(
colors = darkColors()
) {
Surface(color = MaterialTheme.colors.background) {
Column(modifier = Modifier.padding(16.dp).fillMaxSize()) {
Text("File Downloader", fontSize = 24.sp, fontWeight = FontWeight.Bold, color = Color.White)
Spacer(modifier = Modifier.height(16.dp))
Card(
modifier = Modifier.fillMaxWidth(),
elevation = 4.dp,
backgroundColor = Color(0xFF2C2C2C),
shape = RoundedCornerShape(8.dp)
) {
Column(modifier = Modifier.padding(16.dp)) {
TextField(
value = state.url,
onValueChange = { state.url = it },
label = { Text("URL") },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(8.dp))
TextField(
value = state.filePath,
onValueChange = { state.filePath = it },
label = { Text("File Path") },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(8.dp))
TextField(
value = state.threads,
onValueChange = {
val newValue = it.filter { char -> char.isDigit() }
if (newValue.isNotEmpty()) {
val intValue = newValue.toInt().coerceIn(1, 32)
state.threads = intValue.toString()
} else {
state.threads = ""
}
},
label = { Text("Number of threads (1-32)") },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
modifier = Modifier.fillMaxWidth()
)
}
}
Spacer(modifier = Modifier.height(16.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Button(
onClick = {
if (!state.isDownloading) {
state.error = null
state.isDownloading = true
state.isPaused = false
downloadJob = coroutineScope.launch {
downloadFileParallel(
state.url,
state.filePath,
state.threads.toIntOrNull() ?: 1,
onProgress = { currentProgress, currentSpeed, downloadedSize, totalSize, timeElapsed, timeLeft, partProgress ->
state.progress = currentProgress
state.speed = formatSize(currentSpeed.toLong()) + "/s"
state.downloadedSize = formatSize(downloadedSize)
state.totalFileSize = formatSize(totalSize)
state.timeElapsed = formatTime(timeElapsed)
state.timeLeft = formatTime(timeLeft)
state.partProgress = partProgress
},
onPauseSupported = { supported ->
state.canPause = supported
},
state
)
state.isDownloading = false
state.isPaused = false
}
} else if (state.isPaused) {
state.isPaused = false
}
},
enabled = !state.isDownloading || state.isPaused
) {
Text(if (!state.isDownloading) "Download" else if (state.isPaused) "Resume" else "Downloading...")
}
Button(
onClick = { state.isPaused = true },
enabled = state.isDownloading && !state.isPaused && state.canPause
) {
Text("Pause")
}
Button(
onClick = {
downloadJob?.cancel()
state.isDownloading = false
state.isPaused = false
state.progress = 0f
state.speed = "0 B/s"
state.partProgress = listOf()
},
enabled = state.isDownloading
) {
Text("Cancel")
}
}
Spacer(modifier = Modifier.height(16.dp))
Card(
modifier = Modifier.fillMaxWidth(),
elevation = 4.dp,
backgroundColor = Color(0xFF2C2C2C),
shape = RoundedCornerShape(8.dp)
) {
Column(modifier = Modifier.padding(16.dp)) {
LinearProgressIndicator(
progress = state.progress,
modifier = Modifier.fillMaxWidth().height(8.dp)
)
Spacer(modifier = Modifier.height(8.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text("${(state.progress * 100).roundToInt()}%", color = Color.White)
Text("${state.downloadedSize} / ${state.totalFileSize}", color = Color.White)
}
Spacer(modifier = Modifier.height(8.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text("Speed: ${state.speed}", color = Color.White)
Text("Time Left: ${state.timeLeft}", color = Color.White)
}
Spacer(modifier = Modifier.height(8.dp))
Text("Time Elapsed: ${state.timeElapsed}", color = Color.White)
Spacer(modifier = Modifier.height(16.dp))
Text("Part Progress:", fontSize = 18.sp, fontWeight = FontWeight.Bold, color = Color.White)
Spacer(modifier = Modifier.height(8.dp))
// New composite progress bar
Row(
modifier = Modifier.fillMaxWidth().height(16.dp),
horizontalArrangement = Arrangement.spacedBy(2.dp)
) {
state.partProgress.forEachIndexed { index, progress ->
Box(
modifier = Modifier
.weight(1f)
.fillMaxHeight()
.background(Color(0xFF1E1E1E))
) {
Box(
modifier = Modifier
.fillMaxWidth(progress)
.fillMaxHeight()
.background(Color.Green)
)
}
}
}
// Optional: Display percentage for each part
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(2.dp)
) {
state.partProgress.forEachIndexed { index, progress ->
Text(
text = "${(progress * 100).roundToInt()}%",
modifier = Modifier.weight(1f),
textAlign = TextAlign.Center,
fontSize = 10.sp,
color = Color.White
)
}
}
}
}
state.error?.let {
Spacer(modifier = Modifier.height(16.dp))
Text(it, color = Color.Red)
}
}
}
}
}
suspend fun downloadFileParallel(
url: String,
filePath: String,
threads: Int,
onProgress: (Float, Double, Long, Long, Long, Long, List<Float>) -> Unit,
onPauseSupported: (Boolean) -> Unit,
state: DownloadState
) = withContext(Dispatchers.IO) {
println("1. Starting parallel download process")
println(" URL: $url")
println(" File path: $filePath")
println(" Threads: $threads")
try {
val client = HttpClient.newBuilder()
.followRedirects(HttpClient.Redirect.NORMAL)
.build()
val request = HttpRequest.newBuilder(URI(url))
.version(HttpClient.Version.HTTP_2)
.GET()
.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
.build()
val response = client.send(request, HttpResponse.BodyHandlers.ofInputStream())
val fileSize = response.headers().firstValue("Content-Length").map(String::toLong).orElse(-1L)
println("2. File size: $fileSize bytes")
if (fileSize <= 0) {
println("ERROR: Invalid file size")
return@withContext
}
val acceptRanges = response.headers().firstValue("Accept-Ranges").orElse("")
val canPause = acceptRanges.equals("bytes", ignoreCase = true)
onPauseSupported(canPause)
val partSize = fileSize / threads
val downloadedSizes = AtomicLongArray(threads)
val outputFile = RandomAccessFile(filePath, "rw")
outputFile.setLength(fileSize)
val partProgress = Array(threads) { AtomicLongArray(2) } // [0] for current, [1] for total
val jobs = List(threads) { i ->
launch {
val startByte = i * partSize
val endByte = if (i == threads - 1) fileSize - 1 else (i + 1) * partSize - 1
var currentByte = startByte
partProgress[i].set(1, endByte - startByte + 1) // Set total for this part
while (currentByte <= endByte && isActive) {
if (state.isPaused) {
delay(100)
continue
}
val rangeRequest = HttpRequest.newBuilder(URI(url))
.version(HttpClient.Version.HTTP_2)
.GET()
.header("Range", "bytes=$currentByte-$endByte")
.build()
client.send(rangeRequest, HttpResponse.BodyHandlers.ofInputStream()).body().use { input ->
val buffer = ByteArray(8192)
var bytesRead: Int
while (input.read(buffer).also { bytesRead = it } != -1 && isActive) {
if (state.isPaused) {
delay(100)
continue
}
synchronized(outputFile) {
outputFile.seek(currentByte)
outputFile.write(buffer, 0, bytesRead)
}
currentByte += bytesRead
downloadedSizes.addAndGet(i, bytesRead.toLong())
partProgress[i].addAndGet(0, bytesRead.toLong()) // Update progress for this part
}
}
}
}
}
var startTime = System.currentTimeMillis()
var lastTotalDownloaded = 0L
while (jobs.any { it.isActive }) {
delay(1000) // Update progress every second
val totalDownloaded = downloadedSizes.sum()
val progress = totalDownloaded.toFloat() / fileSize
val currentTime = System.currentTimeMillis()
val elapsedTime = (currentTime - startTime) / 1000
val speed = (totalDownloaded - lastTotalDownloaded).toDouble() // B/s
val timeLeft = if (speed > 0) ((fileSize - totalDownloaded) / speed).toLong() else 0L
val currentPartProgress = partProgress.map { it[0].toFloat() / it[1].toFloat() }
onProgress(progress, speed, totalDownloaded, fileSize, elapsedTime, timeLeft, currentPartProgress)
if (state.isPaused) {
startTime = currentTime - (elapsedTime * 1000)
}
lastTotalDownloaded = totalDownloaded
}
outputFile.close()
println("3. Download completed")
println(" Total bytes downloaded: ${downloadedSizes.sum()}")
} catch (e: Exception) {
println("ERROR: Exception occurred during download")
println("Exception type: ${e.javaClass.name}")
println("Exception message: ${e.message}")
e.printStackTrace()
state.error = e.message
}
}
// Helper extension function to sum AtomicLongArray
fun AtomicLongArray.sum(): Long = (0 until length()).sumOf { get(it) }
fun formatSize(bytes: Long): String {
val units = arrayOf("B", "KB", "MB", "GB", "TB")
var size = bytes.toDouble()
var unit = 0
while (size >= 1024 && unit < units.size - 1) {
size /= 1024
unit++
}
return "%.2f %s".format(size, units[unit])
}
fun formatTime(seconds: Long): String {
val hours = seconds / 3600
val minutes = (seconds % 3600) / 60
val secs = seconds % 60
return "%02d:%02d:%02d".format(hours, minutes, secs)
}
fun main() = application {
Window(onCloseRequest = ::exitApplication) {
App()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment