mirror of
https://github.com/ethauvin/kobalt.git
synced 2025-04-26 08:27:12 -07:00
Progress bars for uploads.
This commit is contained in:
parent
3a9ca294e4
commit
c986fc6d65
4 changed files with 129 additions and 84 deletions
|
@ -1,8 +1,9 @@
|
||||||
package com.beust.kobalt.maven
|
package com.beust.kobalt.maven
|
||||||
|
|
||||||
|
import com.beust.kobalt.misc.CountingFileRequestBody
|
||||||
import com.beust.kobalt.misc.log
|
import com.beust.kobalt.misc.log
|
||||||
import com.squareup.okhttp.*
|
import com.squareup.okhttp.*
|
||||||
import java.io.File
|
import retrofit.mime.TypedFile
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
@ -47,15 +48,34 @@ public class Http {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun uploadFile(user: String?, password: String?, url: String, file: File,
|
fun percentProgressCallback(totalSize: Long) : (Long) -> Unit {
|
||||||
success: (Response) -> Unit,
|
return { num: Long ->
|
||||||
error: (Response) -> Unit) {
|
val progress = num * 100 / totalSize
|
||||||
val request = builder(user, password)
|
log(1, "\rUploaded: $progress%", newLine = false)
|
||||||
.url(url)
|
}
|
||||||
.put(RequestBody.create(MEDIA_TYPE_BINARY, file))
|
}
|
||||||
.build()
|
|
||||||
|
|
||||||
log(2, "Uploading $file to $url")
|
val DEFAULT_ERROR_RESPONSE = { r: Response ->
|
||||||
|
error("Couldn't upload file: " + r.message())
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun uploadFile(user: String? = null, password: String? = null, url: String, file: TypedFile,
|
||||||
|
progressCallback: (Long) -> Unit = {},
|
||||||
|
headers: Headers = Headers.of(),
|
||||||
|
success: (Response) -> Unit = {},
|
||||||
|
error: (Response) -> Unit = DEFAULT_ERROR_RESPONSE) {
|
||||||
|
|
||||||
|
val fullHeaders = Headers.Builder()
|
||||||
|
fullHeaders.set("Content-Type", file.mimeType())
|
||||||
|
headers.names().forEach { fullHeaders.set(it, headers.get(it)) }
|
||||||
|
|
||||||
|
val request = builder(user, password)
|
||||||
|
.headers(fullHeaders.build())
|
||||||
|
.url(url)
|
||||||
|
.post(CountingFileRequestBody(file.file(), file.mimeType(), progressCallback))
|
||||||
|
.build()
|
||||||
|
|
||||||
|
log(1, "Uploading $file to $url")
|
||||||
val response = OkHttpClient().newCall(request).execute()
|
val response = OkHttpClient().newCall(request).execute()
|
||||||
if (! response.isSuccessful) {
|
if (! response.isSuccessful) {
|
||||||
error(response)
|
error(response)
|
||||||
|
@ -63,17 +83,6 @@ public class Http {
|
||||||
success(response)
|
success(response)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// private val JSON = MediaType.parse("application/json; charset=utf-8")
|
|
||||||
//
|
|
||||||
// fun post(user: String?, password: String?, url: String, payload: String) : String {
|
|
||||||
// val request = builder(user, password)
|
|
||||||
// .url(url)
|
|
||||||
// .post(RequestBody.create(JSON, payload))
|
|
||||||
// .build()
|
|
||||||
// val response = OkHttpClient().newCall(request).execute()
|
|
||||||
// return response.body().string()
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class KobaltException(s: String? = null, ex: Throwable? = null) : RuntimeException(s, ex) {
|
class KobaltException(s: String? = null, ex: Throwable? = null) : RuntimeException(s, ex) {
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
package com.beust.kobalt.misc
|
||||||
|
|
||||||
|
import com.squareup.okhttp.MediaType
|
||||||
|
import com.squareup.okhttp.RequestBody
|
||||||
|
import okio.BufferedSink
|
||||||
|
import okio.Okio
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An OkHttp RequestBody subclass that counts the outgoing bytes and offers progress callbacks.
|
||||||
|
*/
|
||||||
|
class CountingFileRequestBody(val file: File, val contentType: String,
|
||||||
|
val listenerCallback: (Long) -> Unit) : RequestBody() {
|
||||||
|
|
||||||
|
val SEGMENT_SIZE = 4096L
|
||||||
|
|
||||||
|
override fun contentLength() = file.length()
|
||||||
|
|
||||||
|
override fun contentType() = MediaType.parse(contentType)
|
||||||
|
|
||||||
|
override fun writeTo(sink: BufferedSink) {
|
||||||
|
Okio.source(file).use { source ->
|
||||||
|
var total = 0L
|
||||||
|
var read: Long = source.read(sink.buffer(), SEGMENT_SIZE)
|
||||||
|
|
||||||
|
while (read != -1L) {
|
||||||
|
total += read
|
||||||
|
sink.flush();
|
||||||
|
listenerCallback(total)
|
||||||
|
read = source.read(sink.buffer(), SEGMENT_SIZE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// companion object {
|
||||||
|
// private val MEDIA_TYPE_BINARY = MediaType.parse("application/octet-stream")
|
||||||
|
//
|
||||||
|
// fun progressUpload(file: File, url: String) {
|
||||||
|
// val totalSize = file.length()
|
||||||
|
//
|
||||||
|
// val progressListener = object : ProgressListener {
|
||||||
|
// override fun transferred(num: Long) {
|
||||||
|
// val progress: Float = (num.toFloat() * 100) / totalSize
|
||||||
|
// print("\rProgress: $progress")
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// val request = Request.Builder()
|
||||||
|
// .url(url)
|
||||||
|
// // .post(RequestBody.create(MEDIA_TYPE_BINARY, file))
|
||||||
|
// .put(CountingFileRequestBody(file, "application/octet-stream", progressListener))
|
||||||
|
// // .post(requestBody)
|
||||||
|
// .build();
|
||||||
|
//
|
||||||
|
// val response = OkHttpClient().newCall(request).execute()
|
||||||
|
// if (! response.isSuccessful) {
|
||||||
|
// println("ERROR")
|
||||||
|
// } else {
|
||||||
|
// println("SUCCESS")
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,21 @@
|
||||||
package com.beust.kobalt.misc
|
package com.beust.kobalt.misc
|
||||||
|
|
||||||
|
import com.beust.kobalt.maven.Http
|
||||||
import com.beust.kobalt.maven.KobaltException
|
import com.beust.kobalt.maven.KobaltException
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.google.gson.JsonArray
|
import com.google.gson.JsonArray
|
||||||
import com.google.gson.JsonObject
|
import com.google.gson.JsonObject
|
||||||
import com.google.gson.JsonParser
|
import com.google.gson.JsonParser
|
||||||
import com.google.gson.annotations.SerializedName
|
import com.google.gson.annotations.SerializedName
|
||||||
|
import com.squareup.okhttp.Headers
|
||||||
import com.squareup.okhttp.OkHttpClient
|
import com.squareup.okhttp.OkHttpClient
|
||||||
import retrofit.RestAdapter
|
import retrofit.RestAdapter
|
||||||
import retrofit.RetrofitError
|
import retrofit.RetrofitError
|
||||||
import retrofit.client.OkClient
|
import retrofit.client.OkClient
|
||||||
import retrofit.client.Response
|
import retrofit.http.Body
|
||||||
import retrofit.http.*
|
import retrofit.http.POST
|
||||||
import retrofit.mime.MimeUtil
|
import retrofit.http.Path
|
||||||
|
import retrofit.http.Query
|
||||||
import retrofit.mime.TypedByteArray
|
import retrofit.mime.TypedByteArray
|
||||||
import retrofit.mime.TypedFile
|
import retrofit.mime.TypedFile
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
@ -29,7 +32,7 @@ import javax.inject.Inject
|
||||||
* Retrieve Kobalt's latest release version from github.
|
* Retrieve Kobalt's latest release version from github.
|
||||||
*/
|
*/
|
||||||
public class GithubApi @Inject constructor(val executors: KobaltExecutors,
|
public class GithubApi @Inject constructor(val executors: KobaltExecutors,
|
||||||
val localProperties: LocalProperties) {
|
val localProperties: LocalProperties, val http: Http) {
|
||||||
companion object {
|
companion object {
|
||||||
const val RELEASES_URL = "https://api.github.com/repos/cbeust/kobalt/releases"
|
const val RELEASES_URL = "https://api.github.com/repos/cbeust/kobalt/releases"
|
||||||
const val PROPERTY_ACCESS_TOKEN = "github.accessToken"
|
const val PROPERTY_ACCESS_TOKEN = "github.accessToken"
|
||||||
|
@ -41,24 +44,24 @@ public class GithubApi @Inject constructor(val executors: KobaltExecutors,
|
||||||
|
|
||||||
private fun parseRetrofitError(e: Throwable) : RetrofitErrorsResponse {
|
private fun parseRetrofitError(e: Throwable) : RetrofitErrorsResponse {
|
||||||
val re = e as RetrofitError
|
val re = e as RetrofitError
|
||||||
val body = e.body
|
|
||||||
val json = String((re.response.body as TypedByteArray).bytes)
|
val json = String((re.response.body as TypedByteArray).bytes)
|
||||||
return Gson().fromJson(json, RetrofitErrorsResponse::class.java)
|
return Gson().fromJson(json, RetrofitErrorsResponse::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun uploadRelease(packageName: String, tagName: String, zipFile: File) {
|
fun uploadRelease(packageName: String, tagName: String, zipFile: File) {
|
||||||
log(1, "Uploading release ${zipFile.name}")
|
log(1, "Uploading release ${zipFile.name}")
|
||||||
|
|
||||||
val username = localProperties.get(PROPERTY_USERNAME)
|
val username = localProperties.get(PROPERTY_USERNAME)
|
||||||
val accessToken = localProperties.get(PROPERTY_ACCESS_TOKEN)
|
val accessToken = localProperties.get(PROPERTY_ACCESS_TOKEN)
|
||||||
try {
|
try {
|
||||||
service.createRelease(username, accessToken, packageName, CreateRelease(tagName))
|
service.createRelease(username, accessToken, packageName, CreateRelease(tagName))
|
||||||
.flatMap { response ->
|
.flatMap { response ->
|
||||||
uploadService.uploadAsset(username, accessToken,
|
uploadAsset(accessToken, response.uploadUrl!!, TypedFile("application/zip", zipFile),
|
||||||
packageName, response.id!!, zipFile.name, TypedFile("application/zip", zipFile))
|
tagName)
|
||||||
}
|
}
|
||||||
.toBlocking()
|
.toBlocking()
|
||||||
.forEach { action ->
|
.forEach { action ->
|
||||||
log(1, "Release successfully uploaded ${zipFile.name}")
|
log(1, "\nRelease successfully uploaded ${zipFile.name}")
|
||||||
}
|
}
|
||||||
} catch(e: RetrofitError) {
|
} catch(e: RetrofitError) {
|
||||||
val error = parseRetrofitError(e)
|
val error = parseRetrofitError(e)
|
||||||
|
@ -67,6 +70,18 @@ public class GithubApi @Inject constructor(val executors: KobaltExecutors,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun uploadAsset(token: String, uploadUrl: String, typedFile: TypedFile, tagName: String)
|
||||||
|
: Observable<UploadAssetResponse> {
|
||||||
|
val strippedUrl = uploadUrl.substring(0, uploadUrl.indexOf("{"))
|
||||||
|
val url = "$strippedUrl?name=$tagName&label=$tagName"
|
||||||
|
val headers = Headers.of("Authorization", "token $token")
|
||||||
|
val totalSize = typedFile.file().length()
|
||||||
|
http.uploadFile(url = url, file = typedFile, headers = headers,
|
||||||
|
progressCallback = http.percentProgressCallback(totalSize))
|
||||||
|
|
||||||
|
return Observable.just(UploadAssetResponse(tagName, tagName))
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Read only Api
|
// Read only Api
|
||||||
//
|
//
|
||||||
|
@ -78,57 +93,20 @@ public class GithubApi @Inject constructor(val executors: KobaltExecutors,
|
||||||
.build()
|
.build()
|
||||||
.create(Api::class.java)
|
.create(Api::class.java)
|
||||||
|
|
||||||
class Release {
|
//
|
||||||
var name: String? = null
|
// JSON mapped classes that get sent up and down
|
||||||
var prerelease: Boolean? = null
|
//
|
||||||
}
|
|
||||||
|
|
||||||
class CreateRelease(@SerializedName("tag_name") var tagName: String? = null,
|
class CreateRelease(@SerializedName("tag_name") var tagName: String? = null,
|
||||||
var name: String? = tagName)
|
var name: String? = tagName)
|
||||||
class CreateReleaseResponse(var id: String? = null)
|
class CreateReleaseResponse(var id: String? = null, @SerializedName("upload_url") var uploadUrl: String?)
|
||||||
class GetReleaseResponse(var id: String? = null,
|
class UploadAssetResponse(var id: String? = null, val name: String? = null)
|
||||||
@SerializedName("upload_url") var uploadUrl: String? = null)
|
|
||||||
|
|
||||||
interface Api {
|
interface Api {
|
||||||
|
|
||||||
@GET("/repos/{owner}/{repo}/releases/tags/{tag}")
|
|
||||||
fun getReleaseByTagName(@Path("owner") owner: String, @Path("repo") repo: String,
|
|
||||||
@Path("tag") tagName: String): GetReleaseResponse
|
|
||||||
|
|
||||||
@GET("/repos/{owner}/{repo}/releases")
|
|
||||||
fun releases(@Path("owner") owner: String, @Path("repo") repo: String): List<Release>
|
|
||||||
|
|
||||||
@POST("/repos/{owner}/{repo}/releases")
|
@POST("/repos/{owner}/{repo}/releases")
|
||||||
fun createRelease(@Path("owner") owner: String,
|
fun createRelease(@Path("owner") owner: String,
|
||||||
@Query("access_token") accessToken: String,
|
@Query("access_token") accessToken: String,
|
||||||
@Path("repo") repo: String,
|
@Path("repo") repo: String,
|
||||||
@Body createRelease: CreateRelease
|
@Body createRelease: CreateRelease): Observable<CreateReleaseResponse>
|
||||||
): Observable<CreateReleaseResponse>
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Upload Api
|
|
||||||
//
|
|
||||||
|
|
||||||
val uploadService = RestAdapter.Builder()
|
|
||||||
.setEndpoint("https://uploads.github.com/")
|
|
||||||
// .setLogLevel(RestAdapter.LogLevel.FULL)
|
|
||||||
.setClient(OkClient(OkHttpClient()))
|
|
||||||
.build()
|
|
||||||
.create(UploadApi::class.java)
|
|
||||||
|
|
||||||
class UploadReleaseResponse(var id: String? = null, val name: String? = null)
|
|
||||||
|
|
||||||
interface UploadApi {
|
|
||||||
@POST("/repos/{owner}/{repo}/releases/{id}/assets")
|
|
||||||
fun uploadAsset(@Path("owner") owner: String,
|
|
||||||
@Query("access_token") accessToken: String,
|
|
||||||
@Path("repo") repo: String,
|
|
||||||
@Path("id") id: String,
|
|
||||||
@Query("name") name: String,
|
|
||||||
@Body file: TypedFile)
|
|
||||||
// @Query("Content-Type") contentType: String = "text/plain")//"application/zip")
|
|
||||||
: Observable<UploadReleaseResponse>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val latestKobaltVersion: Future<String>
|
val latestKobaltVersion: Future<String>
|
||||||
|
@ -140,7 +118,6 @@ public class GithubApi @Inject constructor(val executors: KobaltExecutors,
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
val reader = BufferedReader(InputStreamReader(ins))
|
val reader = BufferedReader(InputStreamReader(ins))
|
||||||
val jo = JsonParser().parse(reader) as JsonArray
|
val jo = JsonParser().parse(reader) as JsonArray
|
||||||
// val jo = Parser().parse(ins) as JsonArray<JsonObject>
|
|
||||||
if (jo.size() > 0) {
|
if (jo.size() > 0) {
|
||||||
var versionName = (jo.get(0) as JsonObject).get("name").asString
|
var versionName = (jo.get(0) as JsonObject).get("name").asString
|
||||||
if (Strings.isEmpty(versionName)) {
|
if (Strings.isEmpty(versionName)) {
|
||||||
|
@ -158,12 +135,3 @@ public class GithubApi @Inject constructor(val executors: KobaltExecutors,
|
||||||
return executors.miscExecutor.submit(callable)
|
return executors.miscExecutor.submit(callable)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Response.bodyContent() : String {
|
|
||||||
val bodyBytes = (body as TypedByteArray).bytes
|
|
||||||
val bodyMime = body.mimeType()
|
|
||||||
val bodyCharset = MimeUtil.parseCharset(bodyMime, "utf-8")
|
|
||||||
val result = String(bodyBytes, bodyCharset)
|
|
||||||
return result
|
|
||||||
// return new Gson().fromJson(data, type);
|
|
||||||
}
|
|
||||||
|
|
|
@ -10,11 +10,13 @@ import com.beust.kobalt.misc.KobaltExecutors
|
||||||
import com.beust.kobalt.misc.error
|
import com.beust.kobalt.misc.error
|
||||||
import com.beust.kobalt.misc.log
|
import com.beust.kobalt.misc.log
|
||||||
import com.beust.kobalt.misc.warn
|
import com.beust.kobalt.misc.warn
|
||||||
|
import com.google.common.net.MediaType
|
||||||
import com.google.gson.JsonObject
|
import com.google.gson.JsonObject
|
||||||
import com.google.gson.JsonParser
|
import com.google.gson.JsonParser
|
||||||
import com.google.inject.assistedinject.Assisted
|
import com.google.inject.assistedinject.Assisted
|
||||||
import com.squareup.okhttp.Response
|
import com.squareup.okhttp.Response
|
||||||
import org.jetbrains.annotations.Nullable
|
import org.jetbrains.annotations.Nullable
|
||||||
|
import retrofit.mime.TypedFile
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -147,9 +149,10 @@ public class JCenterApi @Inject constructor (@Nullable @Assisted("username") val
|
||||||
|
|
||||||
val results = arrayListOf<Boolean>()
|
val results = arrayListOf<Boolean>()
|
||||||
filesToUpload.forEach { file ->
|
filesToUpload.forEach { file ->
|
||||||
http.uploadFile(username, password, fileToPath(file) + optionPath, file,
|
http.uploadFile(username, password, fileToPath(file) + optionPath,
|
||||||
{ r: Response -> results.add(true)},
|
TypedFile(MediaType.ANY_APPLICATION_TYPE.toString(), file),
|
||||||
{ r: Response ->
|
success = { r: Response -> results.add(true) },
|
||||||
|
error = { r: Response ->
|
||||||
results.add(false)
|
results.add(false)
|
||||||
val jo = parseResponse(r.body().string())
|
val jo = parseResponse(r.body().string())
|
||||||
errorMessages.add(jo.get("message").asString ?: "No message found")
|
errorMessages.add(jo.get("message").asString ?: "No message found")
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue