1
0
Fork 0
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:
Cedric Beust 2015-11-13 23:18:07 -08:00
parent 3a9ca294e4
commit c986fc6d65
4 changed files with 129 additions and 84 deletions

View file

@ -1,8 +1,9 @@
package com.beust.kobalt.maven
import com.beust.kobalt.misc.CountingFileRequestBody
import com.beust.kobalt.misc.log
import com.squareup.okhttp.*
import java.io.File
import retrofit.mime.TypedFile
import java.io.IOException
import java.io.InputStream
import javax.inject.Singleton
@ -47,15 +48,34 @@ public class Http {
return result
}
public fun uploadFile(user: String?, password: String?, url: String, file: File,
success: (Response) -> Unit,
error: (Response) -> Unit) {
val request = builder(user, password)
.url(url)
.put(RequestBody.create(MEDIA_TYPE_BINARY, file))
.build()
fun percentProgressCallback(totalSize: Long) : (Long) -> Unit {
return { num: Long ->
val progress = num * 100 / totalSize
log(1, "\rUploaded: $progress%", newLine = false)
}
}
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()
if (! response.isSuccessful) {
error(response)
@ -63,17 +83,6 @@ public class Http {
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) {

View file

@ -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")
// }
// }
// }
}

View file

@ -1,18 +1,21 @@
package com.beust.kobalt.misc
import com.beust.kobalt.maven.Http
import com.beust.kobalt.maven.KobaltException
import com.google.gson.Gson
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import com.google.gson.annotations.SerializedName
import com.squareup.okhttp.Headers
import com.squareup.okhttp.OkHttpClient
import retrofit.RestAdapter
import retrofit.RetrofitError
import retrofit.client.OkClient
import retrofit.client.Response
import retrofit.http.*
import retrofit.mime.MimeUtil
import retrofit.http.Body
import retrofit.http.POST
import retrofit.http.Path
import retrofit.http.Query
import retrofit.mime.TypedByteArray
import retrofit.mime.TypedFile
import rx.Observable
@ -29,7 +32,7 @@ import javax.inject.Inject
* Retrieve Kobalt's latest release version from github.
*/
public class GithubApi @Inject constructor(val executors: KobaltExecutors,
val localProperties: LocalProperties) {
val localProperties: LocalProperties, val http: Http) {
companion object {
const val RELEASES_URL = "https://api.github.com/repos/cbeust/kobalt/releases"
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 {
val re = e as RetrofitError
val body = e.body
val json = String((re.response.body as TypedByteArray).bytes)
return Gson().fromJson(json, RetrofitErrorsResponse::class.java)
}
fun uploadRelease(packageName: String, tagName: String, zipFile: File) {
log(1, "Uploading release ${zipFile.name}")
val username = localProperties.get(PROPERTY_USERNAME)
val accessToken = localProperties.get(PROPERTY_ACCESS_TOKEN)
try {
service.createRelease(username, accessToken, packageName, CreateRelease(tagName))
.flatMap { response ->
uploadService.uploadAsset(username, accessToken,
packageName, response.id!!, zipFile.name, TypedFile("application/zip", zipFile))
uploadAsset(accessToken, response.uploadUrl!!, TypedFile("application/zip", zipFile),
tagName)
}
.toBlocking()
.forEach { action ->
log(1, "Release successfully uploaded ${zipFile.name}")
log(1, "\nRelease successfully uploaded ${zipFile.name}")
}
} catch(e: RetrofitError) {
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
//
@ -78,57 +93,20 @@ public class GithubApi @Inject constructor(val executors: KobaltExecutors,
.build()
.create(Api::class.java)
class Release {
var name: String? = null
var prerelease: Boolean? = null
}
//
// JSON mapped classes that get sent up and down
//
class CreateRelease(@SerializedName("tag_name") var tagName: String? = null,
var name: String? = tagName)
class CreateReleaseResponse(var id: String? = null)
class GetReleaseResponse(var id: String? = null,
@SerializedName("upload_url") var uploadUrl: String? = null)
class CreateReleaseResponse(var id: String? = null, @SerializedName("upload_url") var uploadUrl: String?)
class UploadAssetResponse(var id: String? = null, val name: String? = null)
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")
fun createRelease(@Path("owner") owner: String,
@Query("access_token") accessToken: String,
@Path("repo") repo: String,
@Body createRelease: CreateRelease
): 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>
@Body createRelease: CreateRelease): Observable<CreateReleaseResponse>
}
val latestKobaltVersion: Future<String>
@ -140,7 +118,6 @@ public class GithubApi @Inject constructor(val executors: KobaltExecutors,
@Suppress("UNCHECKED_CAST")
val reader = BufferedReader(InputStreamReader(ins))
val jo = JsonParser().parse(reader) as JsonArray
// val jo = Parser().parse(ins) as JsonArray<JsonObject>
if (jo.size() > 0) {
var versionName = (jo.get(0) as JsonObject).get("name").asString
if (Strings.isEmpty(versionName)) {
@ -158,12 +135,3 @@ public class GithubApi @Inject constructor(val executors: KobaltExecutors,
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);
}

View file

@ -10,11 +10,13 @@ import com.beust.kobalt.misc.KobaltExecutors
import com.beust.kobalt.misc.error
import com.beust.kobalt.misc.log
import com.beust.kobalt.misc.warn
import com.google.common.net.MediaType
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import com.google.inject.assistedinject.Assisted
import com.squareup.okhttp.Response
import org.jetbrains.annotations.Nullable
import retrofit.mime.TypedFile
import java.io.File
import javax.inject.Inject
@ -147,9 +149,10 @@ public class JCenterApi @Inject constructor (@Nullable @Assisted("username") val
val results = arrayListOf<Boolean>()
filesToUpload.forEach { file ->
http.uploadFile(username, password, fileToPath(file) + optionPath, file,
{ r: Response -> results.add(true)},
{ r: Response ->
http.uploadFile(username, password, fileToPath(file) + optionPath,
TypedFile(MediaType.ANY_APPLICATION_TYPE.toString(), file),
success = { r: Response -> results.add(true) },
error = { r: Response ->
results.add(false)
val jo = parseResponse(r.body().string())
errorMessages.add(jo.get("message").asString ?: "No message found")