com.pubnub.api.endpoints.files.UploadFile.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of pubnub-kotlin Show documentation
Show all versions of pubnub-kotlin Show documentation
PubNub is a cross-platform client-to-client (1:1 and 1:many) push service in the cloud, capable of
broadcasting real-time messages to millions of web and mobile clients simultaneously, in less than a quarter
second!
package com.pubnub.api.endpoints.files
import com.pubnub.api.PubNub
import com.pubnub.api.PubNubError
import com.pubnub.api.PubNubException
import com.pubnub.api.endpoints.remoteaction.ExtendedRemoteAction
import com.pubnub.api.enums.PNOperationType
import com.pubnub.api.enums.PNStatusCategory
import com.pubnub.api.models.consumer.PNStatus
import com.pubnub.api.models.server.files.FileUploadRequestDetails
import com.pubnub.api.models.server.files.FormField
import com.pubnub.api.services.S3Service
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.MultipartBody
import okhttp3.RequestBody.Companion.toRequestBody
import org.slf4j.LoggerFactory
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import java.io.IOException
import java.net.HttpURLConnection
import java.net.SocketException
import java.net.SocketTimeoutException
import java.net.UnknownHostException
import javax.net.ssl.SSLException
import javax.xml.parsers.DocumentBuilderFactory
internal class UploadFile(
private val s3Service: S3Service,
private val fileName: String,
private val content: ByteArray,
private val key: FormField,
private val formParams: List,
private val baseUrl: String
) : ExtendedRemoteAction {
private var call: Call? = null
@Throws(PubNubException::class)
private fun prepareCall(): Call {
val builder = MultipartBody.Builder().setType(MultipartBody.FORM)
addFormParamsWithKeyFirst(key, formParams, builder)
val mediaType = getMediaType(formParams.findContentType())
builder.addFormDataPart(FILE_PART_MULTIPART, fileName, content.toRequestBody(mediaType, 0, content.size))
return s3Service.upload(baseUrl, builder.build())
}
private fun List.findContentType(): String? {
return find { (key, _) ->
key.equals(CONTENT_TYPE_HEADER, ignoreCase = true)
}?.value
}
private fun getMediaType(contentType: String?): MediaType {
return if (contentType == null) {
APPLICATION_OCTET_STREAM
} else try {
contentType.toMediaType()
} catch (t: Throwable) {
log.warn("Content-Type: $contentType was not recognized by MediaType.get", t)
APPLICATION_OCTET_STREAM
}
}
@Throws(PubNubException::class)
override fun sync() {
call = prepareCall()
val serverResponse = try {
call!!.execute()
} catch (e: IOException) {
throw PubNubException(
errorMessage = e.message,
affectedCall = call,
pubnubError = PubNubError.PARSING_ERROR
)
}
if (!serverResponse.isSuccessful) {
throw createException(serverResponse)
}
}
override fun async(callback: (result: Unit?, status: PNStatus) -> Unit) {
try {
call = prepareCall()
call!!.enqueue(object : Callback {
override fun onResponse(performedCall: Call, response: Response) {
if (!response.isSuccessful) {
val ex = createException(response)
val pnStatusCategory = response.getCategory()
callback(
null,
createStatusResponse(pnStatusCategory, response, ex)
)
return
}
callback(
Unit,
createStatusResponse(
PNStatusCategory.PNAcknowledgmentCategory, response,
null
)
)
}
override fun onFailure(performedCall: Call, throwable: Throwable) {
if (call!!.isCanceled) {
return
}
val (statusCategory, error) = when (throwable) {
is UnknownHostException -> PNStatusCategory.PNUnexpectedDisconnectCategory to PubNubError.CONNECTION_NOT_SET
is SocketException, is SSLException -> PNStatusCategory.PNUnexpectedDisconnectCategory to PubNubError.CONNECT_EXCEPTION
is SocketTimeoutException -> PNStatusCategory.PNTimeoutCategory to PubNubError.SUBSCRIBE_TIMEOUT
else -> if (performedCall.isCanceled) {
PNStatusCategory.PNCancelledCategory to PubNubError.HTTP_ERROR
} else {
PNStatusCategory.PNBadRequestCategory to PubNubError.HTTP_ERROR
}
}
callback(
null,
createStatusResponse(
statusCategory,
null,
PubNubException(error).copy(
errorMessage = throwable.message ?: error.message
)
)
)
}
})
} catch (e: IOException) {
callback(
null,
createStatusResponse(PNStatusCategory.PNUnknownCategory, null, e)
)
} catch (e: PubNubException) {
callback(
null,
createStatusResponse(PNStatusCategory.PNUnknownCategory, null, e)
)
}
}
private fun Response<*>.getCategory(): PNStatusCategory = when (code()) {
HttpURLConnection.HTTP_UNAUTHORIZED,
HttpURLConnection.HTTP_FORBIDDEN -> PNStatusCategory.PNAccessDeniedCategory
HttpURLConnection.HTTP_BAD_REQUEST -> PNStatusCategory.PNBadRequestCategory
else -> PNStatusCategory.PNUnknownCategory
}
override fun retry() {}
override fun silentCancel() {
if (!call!!.isCanceled) {
call!!.cancel()
}
}
private fun createException(response: Response): PubNubException {
return try {
PubNubException(
errorMessage = response.readErrorMessage(),
affectedCall = call,
statusCode = response.code()
)
} catch (e: Exception) {
PubNubException(
errorMessage = e.message,
affectedCall = call,
statusCode = response.code()
)
}
}
private fun Response.readErrorMessage(): String {
val dbFactory = DocumentBuilderFactory.newInstance()
val dBuilder = dbFactory.newDocumentBuilder()
val doc = dBuilder.parse(errorBody()!!.byteStream())
doc.documentElement.normalize()
val elements = doc.getElementsByTagName("Message")
return elements.item(0)?.firstChild?.nodeValue ?: "N/A"
}
private fun createStatusResponse(
category: PNStatusCategory,
response: Response?,
throwable: Exception?
): PNStatus {
return PNStatus(
category = category,
operation = operationType(),
statusCode = response?.code(),
tlsEnabled = response?.raw()?.request?.url?.isHttps,
origin = response?.raw()?.request?.url?.host,
error = response == null || throwable != null
).apply { executedEndpoint = this@UploadFile }
}
internal class Factory(private val pubNub: PubNub) {
fun create(
fileName: String,
content: ByteArray,
fileUploadRequestDetails: FileUploadRequestDetails
): ExtendedRemoteAction {
return UploadFile(
pubNub.retrofitManager.s3Service,
fileName,
content,
fileUploadRequestDetails.keyFormField, fileUploadRequestDetails.formFields,
fileUploadRequestDetails.url
)
}
}
companion object {
private val APPLICATION_OCTET_STREAM = "application/octet-stream".toMediaType()
private const val CONTENT_TYPE_HEADER = "Content-Type"
private const val FILE_PART_MULTIPART = "file"
private val log = LoggerFactory.getLogger(UploadFile::class.java)
private fun addFormParamsWithKeyFirst(
keyValue: FormField,
formParams: List,
builder: MultipartBody.Builder
) {
builder.addFormDataPart(keyValue.key, keyValue.value)
formParams
.filter { it.key != keyValue.key }
.forEach { builder.addFormDataPart(it.key, it.value) }
}
}
override fun operationType(): PNOperationType = PNOperationType.FileOperation
}