All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.pubnub.api.endpoints.files.UploadFile.kt Maven / Gradle / Ivy

Go to download

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!

There is a newer version: 10.2.0
Show newest version
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
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy