Skip to content

Instantly share code, notes, and snippets.

@gmazzo
Last active August 29, 2022 15:48
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gmazzo/8ef28ec16584f6ca2b2c56bf1a8b80ae to your computer and use it in GitHub Desktop.
Save gmazzo/8ef28ec16584f6ca2b2c56bf1a8b80ae to your computer and use it in GitHub Desktop.
The missing "download to file" task for Gradle
import org.gradle.api.DefaultTask
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*
import org.gradle.workers.WorkAction
import org.gradle.workers.WorkParameters
import org.gradle.workers.WorkerExecutor
import java.net.HttpURLConnection
import java.net.URL
import javax.inject.Inject
@CacheableTask
abstract class DownloadFile @Inject constructor(
private val workerExecutor: WorkerExecutor,
) : DefaultTask() {
@get:Input
abstract val url: Property<URL>
@get:Input
abstract val httpCacheHeaders: Property<String>
@get:Input
abstract val failOnNotFound: Property<Boolean>
@get:Internal // represented by `destinationFile`
abstract val destinationDirectory: DirectoryProperty
@get:Internal // represented by `destinationFile`
abstract val destinationFileName: Property<String>
@get:OutputFile
@get:Optional
abstract val destinationFile: RegularFileProperty
init {
with(project) {
failOnNotFound.convention(true)
destinationDirectory.convention(layout.dir(provider { temporaryDir }))
destinationFileName.convention(url.map { file(it.path).name })
destinationFile.convention(destinationDirectory.zip(destinationFileName) { dir, file -> dir.file(file) })
httpCacheHeaders
.convention(url.map {
// only supports cache tags if protocol is HTTP/HTTPS, so we can do a HEAD
(it.openConnection() as? HttpURLConnection)?.run {
requestMethod = "HEAD"
"${getHeaderField("etag")}:$lastModified"
} ?: ""
})
.finalizeValueOnRead()
}
}
@TaskAction
fun download() {
workerExecutor.noIsolation().submit(Action::class.java) param@{
this@param.url.set(this@DownloadFile.url)
this@param.destinationFile.set(this@DownloadFile.destinationFile)
this@param.failOnNotFound.set(this@DownloadFile.failOnNotFound)
}
}
interface Parameters : WorkParameters {
val url: Property<URL>
val destinationFile: RegularFileProperty
val failOnNotFound: Property<Boolean>
}
interface Action : WorkAction<Parameters> {
override fun execute(): Unit = with(parameters) {
val conn = url.get().openConnection()
conn.useCaches = true
when ((conn as? HttpURLConnection)?.responseCode) {
404 -> if (failOnNotFound.get()) conn.inputStream // will fail to open the stream
else -> conn.inputStream.use { input ->
destinationFile.get().asFile.outputStream().use(input::copyTo)
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment