dependencies: Implement resuming downloading
This commit is contained in:
@@ -1,16 +1,16 @@
|
|||||||
package org.jetbrains.kotlin.konan.util
|
package org.jetbrains.kotlin.konan.util
|
||||||
|
|
||||||
import java.io.EOFException
|
import java.io.*
|
||||||
import java.io.File
|
import java.net.HttpURLConnection
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.io.IOException
|
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
import java.net.URLConnection
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.StandardCopyOption
|
import java.nio.file.StandardCopyOption
|
||||||
|
|
||||||
class DependencyDownloader(
|
class DependencyDownloader(
|
||||||
var maxAttempts: Int = DEFAULT_MAX_ATTEMPTS,
|
var maxAttempts: Int = DEFAULT_MAX_ATTEMPTS,
|
||||||
var attemptIntervalMs: Long = DEFAULT_ATTEMPT_INTERVAL_MS) {
|
var attemptIntervalMs: Long = DEFAULT_ATTEMPT_INTERVAL_MS
|
||||||
|
) {
|
||||||
|
|
||||||
enum class ReplacingMode {
|
enum class ReplacingMode {
|
||||||
/** Redownload the file and replace the existing one. */
|
/** Redownload the file and replace the existing one. */
|
||||||
@@ -21,34 +21,75 @@ class DependencyDownloader(
|
|||||||
RETURN_EXISTING
|
RETURN_EXISTING
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Performs an attempt to download a specified file into the specified location */
|
private fun doDownload(originalUrl: URL,
|
||||||
fun tryDownload(url: URL, dstFile: File) {
|
connection: URLConnection,
|
||||||
val connection = url.openConnection()
|
tmpFile: File,
|
||||||
val totalBytes = connection.contentLengthLong
|
currentBytes: Long,
|
||||||
var currentBytes = 0L
|
totalBytes: Long,
|
||||||
|
append: Boolean) {
|
||||||
try {
|
@Suppress("NAME_SHADOWING")
|
||||||
url.openStream().use { from ->
|
var currentBytes = currentBytes
|
||||||
FileOutputStream(dstFile, false).use { to ->
|
connection.getInputStream().use { from ->
|
||||||
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
|
FileOutputStream(tmpFile, append).use { to ->
|
||||||
var read = from.read(buffer)
|
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
|
||||||
while (read != -1) {
|
var read = from.read(buffer)
|
||||||
to.write(buffer, 0, read)
|
while (read != -1) {
|
||||||
currentBytes += read
|
to.write(buffer, 0, read)
|
||||||
updateProgressMsg(url.toString(), currentBytes, totalBytes)
|
currentBytes += read
|
||||||
read = from.read(buffer)
|
updateProgressMsg(originalUrl.toString(), currentBytes, totalBytes)
|
||||||
}
|
read = from.read(buffer)
|
||||||
if (currentBytes != totalBytes) {
|
}
|
||||||
throw EOFException("The stream closed before end of downloading.")
|
if (currentBytes != totalBytes) {
|
||||||
}
|
throw EOFException("The stream closed before end of downloading.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Throwable) {
|
}
|
||||||
dstFile.delete()
|
}
|
||||||
|
|
||||||
|
private fun tryHttpDownload(originalUrl: URL, connection: HttpURLConnection, tmpFile: File) {
|
||||||
|
@Suppress("NAME_SHADOWING")
|
||||||
|
var connection = connection
|
||||||
|
connection.connect()
|
||||||
|
val totalBytes = connection.contentLengthLong
|
||||||
|
var currentBytes = 0L
|
||||||
|
if (tmpFile.exists()) {
|
||||||
|
currentBytes = tmpFile.length()
|
||||||
|
if (currentBytes < totalBytes) {
|
||||||
|
connection.disconnect()
|
||||||
|
connection = originalUrl.openConnection() as HttpURLConnection
|
||||||
|
connection.setRequestProperty("range", "bytes=$currentBytes-")
|
||||||
|
connection.connect()
|
||||||
|
} else {
|
||||||
|
tmpFile.delete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
doDownload(originalUrl, connection, tmpFile, currentBytes, totalBytes, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun tryOtherDownload(originalUrl: URL, connection: URLConnection, tmpFile: File) {
|
||||||
|
connection.connect()
|
||||||
|
val currentBytes = 0L
|
||||||
|
val totalBytes = connection.contentLengthLong
|
||||||
|
|
||||||
|
try {
|
||||||
|
doDownload(originalUrl, connection, tmpFile, currentBytes, totalBytes, false)
|
||||||
|
} catch(e: Throwable) {
|
||||||
|
tmpFile.delete()
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Performs an attempt to download a specified file into the specified location */
|
||||||
|
private fun tryDownload(url: URL, tmpFile: File) {
|
||||||
|
var connection = url.openConnection()
|
||||||
|
if (connection is HttpURLConnection) {
|
||||||
|
tryHttpDownload(url, connection, tmpFile)
|
||||||
|
} else {
|
||||||
|
tryOtherDownload(url, connection, tmpFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Downloads a file from [source] url to [destination]. Returns [destination]. */
|
/** Downloads a file from [source] url to [destination]. Returns [destination]. */
|
||||||
fun download(source: URL,
|
fun download(source: URL,
|
||||||
destination: File,
|
destination: File,
|
||||||
@@ -59,12 +100,17 @@ class DependencyDownloader(
|
|||||||
when (replace) {
|
when (replace) {
|
||||||
ReplacingMode.RETURN_EXISTING -> return destination
|
ReplacingMode.RETURN_EXISTING -> return destination
|
||||||
ReplacingMode.THROW -> throw FileAlreadyExistsException(destination)
|
ReplacingMode.THROW -> throw FileAlreadyExistsException(destination)
|
||||||
// TODO: What if dst is a directory?
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO: resuming must be here
|
|
||||||
val tmpFile = File("${destination.canonicalPath}.$TMP_SUFFIX")
|
val tmpFile = File("${destination.canonicalPath}.$TMP_SUFFIX")
|
||||||
|
|
||||||
|
check(!tmpFile.isDirectory) {
|
||||||
|
"A temporary file is a directory: ${tmpFile.canonicalPath}. Remove it and try again."
|
||||||
|
}
|
||||||
|
check(!destination.isDirectory) {
|
||||||
|
"The destination file is a directory: ${tmpFile.canonicalPath}. Remove it and try again."
|
||||||
|
}
|
||||||
|
|
||||||
var attempt = 1
|
var attempt = 1
|
||||||
var waitTime = 0L
|
var waitTime = 0L
|
||||||
while (true) {
|
while (true) {
|
||||||
@@ -78,7 +124,7 @@ class DependencyDownloader(
|
|||||||
attempt++
|
attempt++
|
||||||
waitTime += attemptIntervalMs
|
waitTime += attemptIntervalMs
|
||||||
println("Cannot download a dependency: $e\n" +
|
println("Cannot download a dependency: $e\n" +
|
||||||
"Wait ${waitTime.toDouble() / 1000} sec and try again (attempt: $attempt/$maxAttempts).")
|
"Waiting ${waitTime.toDouble() / 1000} sec and trying again (attempt: $attempt/$maxAttempts).")
|
||||||
// TODO: Wait better
|
// TODO: Wait better
|
||||||
Thread.sleep(waitTime)
|
Thread.sleep(waitTime)
|
||||||
}
|
}
|
||||||
@@ -103,9 +149,11 @@ class DependencyDownloader(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun updateProgressMsg(url: String, currentBytes: Long, totalBytes: Long) {
|
private fun updateProgressMsg(url: String, currentBytes: Long, totalBytes: Long) {
|
||||||
print("\rDownload dependency: $url (${currentBytes.humanReadable}/${totalBytes.humanReadable}). ")
|
print("\rDownloading dependency: $url (${currentBytes.humanReadable}/${totalBytes.humanReadable}). ")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val DEFAULT_MAX_ATTEMPTS = 10
|
const val DEFAULT_MAX_ATTEMPTS = 10
|
||||||
const val DEFAULT_ATTEMPT_INTERVAL_MS = 3000L
|
const val DEFAULT_ATTEMPT_INTERVAL_MS = 3000L
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ class DependencyExtractor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun extractTarGz(tarGz: File, targetDirectory: File) {
|
private fun extractTarGz(tarGz: File, targetDirectory: File) {
|
||||||
println("Extract dependency: ${tarGz.canonicalPath} in ${targetDirectory.canonicalPath}")
|
|
||||||
val tarProcess = ProcessBuilder().apply {
|
val tarProcess = ProcessBuilder().apply {
|
||||||
command("tar", "-xzf", tarGz.canonicalPath)
|
command("tar", "-xzf", tarGz.canonicalPath)
|
||||||
directory(targetDirectory)
|
directory(targetDirectory)
|
||||||
|
|||||||
@@ -149,10 +149,10 @@ class DependencyProcessor(dependenciesRoot: File,
|
|||||||
Cannot find a dependency locally: $dependency.
|
Cannot find a dependency locally: $dependency.
|
||||||
Set `airplaneMode = false` in konan.properties to download it.
|
Set `airplaneMode = false` in konan.properties to download it.
|
||||||
""".trimIndent())
|
""".trimIndent())
|
||||||
} else {
|
|
||||||
downloader.download(url, archive)
|
|
||||||
}
|
}
|
||||||
|
downloader.download(url, archive)
|
||||||
}
|
}
|
||||||
|
println("Extracting dependency: $archive into $dependenciesDirectory")
|
||||||
extractor.extract(archive, dependenciesDirectory)
|
extractor.extract(archive, dependenciesDirectory)
|
||||||
extractedDependencies.addWithSave(depName)
|
extractedDependencies.addWithSave(depName)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user