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 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) {

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 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);
}

View file

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