@@ -4,13 +4,15 @@ import com.google.auth.oauth2.GoogleCredentials
44import gratatouille.tasks.GInputFiles
55import gratatouille.tasks.GLogger
66import gratatouille.tasks.GTask
7+ import kotlinx.serialization.json.JsonObject
8+ import kotlinx.serialization.json.JsonPrimitive
79import nmcp.transport.Content
810import nmcp.transport.Transport
911import nmcp.transport.publishFileByFile
10- import nmcp.transport.toRequestBody
12+ import okhttp3.*
1113import okhttp3.HttpUrl.Companion.toHttpUrl
12- import okhttp3.OkHttpClient
13- import okhttp3.Request
14+ import okhttp3.MediaType.Companion.toMediaType
15+ import okio.BufferedSink
1416import okio.BufferedSource
1517import java.time.Duration
1618import kotlin.math.pow
@@ -77,6 +79,8 @@ internal class GcsTransport(
7779 }
7880
7981 /* *
82+ * We use multipart upload to upload both the metadata and the contents at the same time.
83+ *
8084 * https://cloud.google.com/storage/docs/json_api/v1/objects/insert
8185 */
8286 override fun put (path : String , body : Content ) {
@@ -85,13 +89,51 @@ internal class GcsTransport(
8589
8690 val name = " ${prefix}${path} "
8791 logger.info(" Librarian: gcs-put $name " )
92+
93+ val cacheControl = when {
94+ name.endsWith(" maven-metadata.xml" ) -> " public, max-age=60" // This makes it easier to debug snapshots issues
95+ else -> " public, max-age=3600"
96+ }
97+ val multipartBody = MultipartBody .Builder ()
98+ .setType(" multipart/related" .toMediaType())
99+ .addPart(object : RequestBody () {
100+ override fun contentType (): MediaType {
101+ return " application/json; charset=UTF-8" .toMediaType()
102+ }
103+
104+ override fun writeTo (sink : BufferedSink ) {
105+ JsonObject (
106+ mapOf (
107+ " name" to JsonPrimitive (name),
108+ " cacheControl" to JsonPrimitive (cacheControl)
109+ )
110+ ).let {
111+ sink.writeUtf8(it.toString())
112+ }
113+ }
114+ })
115+ .addPart(object : RequestBody () {
116+ override fun contentType (): MediaType {
117+ return when {
118+ name.endsWith(" .jar" ) -> " application/java-archive"
119+ name.endsWith(" .pom" ) -> " application/xml"
120+ name.endsWith(" .module" ) -> " application/xml"
121+ name.endsWith(" .xml" ) -> " application/xml"
122+ else -> " application/octet-stream"
123+ }.toMediaType()
124+ }
125+
126+ override fun writeTo (sink : BufferedSink ) {
127+ body.writeTo(sink)
128+ }
129+ })
130+ .build()
88131 val url = postBaseUrl
89132 .newBuilder()
90- .addQueryParameter(" name" , name)
91- .addQueryParameter(" uploadType" , " media" )
133+ .addQueryParameter(" uploadType" , " multipart" )
92134 .build()
93135 val request = Request .Builder ()
94- .post(body.toRequestBody() )
136+ .post(multipartBody )
95137 .addHeader(" Authorization" , " Bearer $accessToken " )
96138 .url(url)
97139 .build()
@@ -113,7 +155,7 @@ internal class GcsTransport(
113155 Thread .sleep(delay)
114156 retry++
115157 if (retry > 5 ) {
116- error(" Too many retries ($retry ), giving up ('${response.code} '): ${response.body? .string()} " )
158+ error(" Too many retries ($retry ), giving up ('${response.code} '): ${response.body.string()} " )
117159 }
118160 }
119161
@@ -123,15 +165,15 @@ internal class GcsTransport(
123165 request.header(
124166 " content-length"
125167 )
126- } ) ('${response.code} '): ${response.body? .string()} "
168+ } ) ('${response.code} '): ${response.body.string()} "
127169 )
128170 }
129171 }
130172 }
131173 continue
132174 }
133175
134- return response.body!! .source()
176+ return response.body.source()
135177 }
136178 }
137179}
0 commit comments