From a17ea6e0c31b2f0a8548af62dcc1ed93bf9ec9ec Mon Sep 17 00:00:00 2001 From: CheK539 Date: Thu, 9 Nov 2023 17:33:31 +0500 Subject: [PATCH 1/4] Add support for latest add-ons --- CHANGELOG.md | 2 + documentation/LIBRARY.md | 321 ++++++++++++++++++ .../android/example/fragments/CdnFragment.kt | 16 + .../example/viewmodels/CdnViewModel.kt | 77 ++++- example/src/main/res/layout/fragment_cdn.xml | 4 +- .../src/main/res/menu/cdn_file_actions.xml | 22 +- example/src/main/res/values/strings.xml | 9 + .../library/addon/AWSRekognitionAddOn.kt | 10 + .../addon/AWSRekognitionModerationAddOn.kt | 10 + .../android/library/addon/AddOnExecutor.kt | 123 +++++++ .../android/library/addon/ClamAVAddOn.kt | 10 + .../android/library/addon/RemoveBgAddOn.kt | 10 + .../android/library/api/RequestHelper.kt | 8 +- .../android/library/data/AddOnData.kt | 18 + .../android/library/data/ObjectMapper.kt | 2 + .../android/library/urls/UrlParameters.kt | 7 + .../uploadcare/android/library/urls/Urls.kt | 82 ++++- .../library/utils/AddOnStatusAdapter.kt | 16 + 18 files changed, 731 insertions(+), 16 deletions(-) create mode 100644 library/src/main/java/com/uploadcare/android/library/addon/AWSRekognitionAddOn.kt create mode 100644 library/src/main/java/com/uploadcare/android/library/addon/AWSRekognitionModerationAddOn.kt create mode 100644 library/src/main/java/com/uploadcare/android/library/addon/AddOnExecutor.kt create mode 100644 library/src/main/java/com/uploadcare/android/library/addon/ClamAVAddOn.kt create mode 100644 library/src/main/java/com/uploadcare/android/library/addon/RemoveBgAddOn.kt create mode 100644 library/src/main/java/com/uploadcare/android/library/data/AddOnData.kt create mode 100644 library/src/main/java/com/uploadcare/android/library/utils/AddOnStatusAdapter.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index e0056ec9..875ad5e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ - Added support for converting multipage into a group of files. - Added `default_effects` field in `UploadcareFile`. - Added react to request throttling (429 code response). + - Added support for latest add-ons. + - Fixed authorization signature for requests with url parameters. - Widget: - SocialApi doesn't use `GET /sources` method anymore. - Project: diff --git a/documentation/LIBRARY.md b/documentation/LIBRARY.md index 57bf478b..733d4f5b 100644 --- a/documentation/LIBRARY.md +++ b/documentation/LIBRARY.md @@ -1251,3 +1251,324 @@ List fileIds = ... // list of file UUID's UploadcareGroup uploadcareGroup = uploadcare.createGroup(fileIds, null); ``` + +## Execute AWS Rekognition ([API Reference](https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/Add-Ons/operation/awsRekognitionExecute)) ## + +##### Asynchronous AWS Rekognition execute. + +Kotlin +```kotlin +// Create AWS Rekognition Add-On +val addOn = AWSRekognitionAddOn(uploadcare) + +// Execute +addOn.executeAsync("YOUR_FILE_UUID", object : UploadcareFileCallback { + + override fun onFailure(e: UploadcareApiException) { + // Handle errors. + } + + override fun onSuccess(result: UploadcareFile) { + // Successfully execute Add-On. + } +}) +``` +Java +```java +// Create AWS Rekognition Add-On +AWSRekognitionAddOn addOn = new AWSRekognitionAddOn(uploadcare); + +// Execute +addOn.executeAsync( + "YOUR_FILE_UUID", + new UploadcareFileCallback() { + @Override + public void onFailure(@NotNull UploadcareApiException e) { + // Handle errors. + } + + @Override + public void onSuccess(@NonNull UploadcareFile result) { + // Successfully execute Add-On. + } + }); +``` + +##### Synchronous AWS Rekognition execute. + +Kotlin +```kotlin +// Create AWS Rekognition Add-On +val addOn = AWSRekognitionAddOn(uploadcare) + +// Execute +val executeResult = addOn.execute("YOUR_FILE_UUID") + +// Check status +val statusResult = addOn.check(executeResult.requestId) + +if (statusResult.status == AddOnStatus.DONE) { + // Get file with appdata field + uploadcare.getFileWithAppData("YOUR_FILE_UUID") +} else { + // Handle other statuses +} +``` +Java +```java +// Create AWS Rekognition Add-On +AWSRekognitionAddOn addOn = new AWSRekognitionAddOn(uploadcare); + +// Execute +AddOnExecuteResult executeResult = addOn.execute("YOUR_FILE_UUID"); + +// Check status +AddOnStatusResult statusResult = addOn.check(executeResult.getRequestId()); + +if (statusResult.getStatus() == AddOnStatus.DONE) { + // Get file with appdata field + uploadcare.getFileWithAppData("YOUR_FILE_UUID"); +} else { + // Handle other statuses +} +``` + +## Execute AWS Rekognition Moderation ([API Reference](https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/Add-Ons/operation/awsRekognitionDetectModerationLabelsExecute)) ## + +##### Asynchronous AWS Rekognition Moderation execute. + +Kotlin +```kotlin +// Create AWS Rekognition Add-On +val addOn = AWSRekognitionModerationAddOn(uploadcare) + +// Execute +addOn.executeAsync(object : UploadcareFileCallback { + + override fun onFailure(e: UploadcareApiException) { + // Handle errors. + } + + override fun onSuccess(result: UploadcareFile) { + // Successfully execute Add-On. + } +}) +``` +Java +```java +// Create AWS Rekognition Add-On +AWSRekognitionModerationAddOn addOn = new AWSRekognitionModerationAddOn(uploadcare); + +// Execute +addOn.executeAsync( + new UploadcareFileCallback() { + @Override + public void onFailure(@NotNull UploadcareApiException e) { + // Handle errors. + } + + @Override + public void onSuccess(@NonNull UploadcareFile result) { + // Successfully execute Add-On. + } + }); +``` + +##### Synchronous AWS Rekognition Moderation execute. + +Kotlin +```kotlin +// Create AWS Rekognition Moderation Add-On +val addOn = AWSRekognitionModerationAddOn(uploadcare) + +// Execute +val executeResult = addOn.execute("YOUR_FILE_UUID") + +// Check status +val statusResult = addOn.check(executeResult.requestId) + +if (statusResult.status == AddOnStatus.DONE) { + // Get file with appdata field + uploadcare.getFileWithAppData("YOUR_FILE_UUID") +} else { + // Handle other statuses +} +``` +Java +```java +// Create AWS Rekognition Moderation Add-On +AWSRekognitionModerationAddOn addOn = new AWSRekognitionModerationAddOn(uploadcare); + +// Execute +AddOnExecuteResult executeResult = addOn.execute("YOUR_FILE_UUID"); + +// Check status +AddOnStatusResult statusResult = addOn.check(executeResult.getRequestId()); + +if (statusResult.getStatus() == AddOnStatus.DONE) { + // Get file with appdata field + uploadcare.getFileWithAppData("YOUR_FILE_UUID"); +} else { + // Handle other statuses +} +``` + +## Execute ClamAV ([API Reference](https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/Add-Ons/operation/ucClamavVirusScanExecute)) ## + +##### Asynchronous ClamAV execute. + +Kotlin +```kotlin +// Create ClamAV Add-On +val addOn = ClamAVAddOn(uploadcare) + +// Execute +addOn.executeAsync(object : UploadcareFileCallback { + + override fun onFailure(e: UploadcareApiException) { + // Handle errors. + } + + override fun onSuccess(result: UploadcareFile) { + // Successfully execute Add-On. + } +}) +``` +Java +```java +// Create ClamAV Add-On +ClamAVAddOn addOn = new ClamAVAddOn(uploadcare); + +// Execute +addOn.executeAsync( + new UploadcareFileCallback() { + @Override + public void onFailure(@NotNull UploadcareApiException e) { + // Handle errors. + } + + @Override + public void onSuccess(@NonNull UploadcareFile result) { + // Successfully execute Add-On. + } + }); +``` + +##### Synchronous ClamAV execute. + +Kotlin +```kotlin +// Create ClamAV Add-On +val addOn = ClamAVAddOn(uploadcare) + +// Execute +val executeResult = addOn.execute("YOUR_FILE_UUID") + +// Check status +val statusResult = addOn.check(executeResult.requestId) + +if (statusResult.status == AddOnStatus.DONE) { + // Get file with appdata field + uploadcare.getFileWithAppData("YOUR_FILE_UUID") +} else { + // Handle other statuses +} +``` +Java +```java +// Create ClamAV Add-On +ClamAVAddOn addOn = new ClamAVAddOn(uploadcare); + +// Execute +AddOnExecuteResult executeResult = addOn.execute("YOUR_FILE_UUID"); + +// Check status +AddOnStatusResult statusResult = addOn.check(executeResult.getRequestId()); + +if (statusResult.getStatus() == AddOnStatus.DONE) { + // Get file with appdata field + uploadcare.getFileWithAppData("YOUR_FILE_UUID") +} else { + // Handle other statuses +} +``` + +## Execute Remove.bg ([API Reference](https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/Add-Ons/operation/removeBgExecute)) ## + +##### Asynchronous Remove.bg execute. + +Kotlin +```kotlin +// Create Remove.bg Add-On +val addOn = RemoveBgAddOn(uploadcare) + +// Execute +addOn.executeAsync(object : UploadcareFileCallback { + + override fun onFailure(e: UploadcareApiException) { + // Handle errors. + } + + override fun onSuccess(result: UploadcareFile) { + // Successfully execute Add-On. + } +}) +``` +Java +```java +// Create Remove.bg Add-On +RemoveBgAddOn addOn = new RemoveBgAddOn(uploadcare); + +// Execute +addOn.executeAsync( + new UploadcareFileCallback() { + @Override + public void onFailure(@NotNull UploadcareApiException e) { + // Handle errors. + } + + @Override + public void onSuccess(@NonNull UploadcareFile result) { + // Successfully execute Add-On. + } + }); +``` + +##### Synchronous Remove.bg execute. + +Kotlin +```kotlin +// Create Remove.bg Add-On +val addOn = RemoveBgAddOn(uploadcare) + +// Execute +val executeResult = addOn.execute("YOUR_FILE_UUID") + +// Check status +val statusResult = addOn.check(executeResult.requestId) + +if (statusResult.status == AddOnStatus.DONE) { + // Get file with appdata field + statusResult.result?.let { uploadcare.getFile(it.fileId) } +} else { + // Handle other statuses +} +``` +Java +```java +// Create Remove.bg Add-On +RemoveBgAddOn addOn = new RemoveBgAddOn(uploadcare); + +// Execute +AddOnExecuteResult executeResult = addOn.execute("YOUR_FILE_UUID"); + +// Check status +AddOnStatusResult statusResult = addOn.check(executeResult.getRequestId()); + +if (statusResult.getStatus() == AddOnStatus.DONE && statusResult.getResult() != null) { + // Get file with appdata field + uploadcare.getFile(statusResult.getResult().getFileId()); +} else { + // Handle other statuses +} +``` diff --git a/example/src/main/java/com/uploadcare/android/example/fragments/CdnFragment.kt b/example/src/main/java/com/uploadcare/android/example/fragments/CdnFragment.kt index bb2b6a03..6fb250b5 100644 --- a/example/src/main/java/com/uploadcare/android/example/fragments/CdnFragment.kt +++ b/example/src/main/java/com/uploadcare/android/example/fragments/CdnFragment.kt @@ -75,6 +75,22 @@ class CdnFragment : Fragment(), MenuProvider { viewModel.convertVideo() true } + R.id.action_execute_aws_rekognition -> { + viewModel.executeAWSRekognitionAddOn() + true + } + R.id.action_execute_aws_rekognition_moderation -> { + viewModel.executeAWSRekognitionModerationAddOn() + true + } + R.id.action_execute_clam_av -> { + viewModel.executeClamAVAddOn() + true + } + R.id.action_execute_remove_bg -> { + viewModel.executeRemoveBgAddOn() + true + } else -> false } diff --git a/example/src/main/java/com/uploadcare/android/example/viewmodels/CdnViewModel.kt b/example/src/main/java/com/uploadcare/android/example/viewmodels/CdnViewModel.kt index 5f298841..d4dc0bdc 100644 --- a/example/src/main/java/com/uploadcare/android/example/viewmodels/CdnViewModel.kt +++ b/example/src/main/java/com/uploadcare/android/example/viewmodels/CdnViewModel.kt @@ -5,8 +5,14 @@ import android.content.Context import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.MutableLiveData import com.uploadcare.android.example.R +import com.uploadcare.android.library.addon.AWSRekognitionAddOn +import com.uploadcare.android.library.addon.AWSRekognitionModerationAddOn +import com.uploadcare.android.library.addon.AddOnExecutor +import com.uploadcare.android.library.addon.ClamAVAddOn +import com.uploadcare.android.library.addon.RemoveBgAddOn import com.uploadcare.android.library.api.UploadcareFile import com.uploadcare.android.library.callbacks.ConversionFilesCallback +import com.uploadcare.android.library.callbacks.UploadcareFileCallback import com.uploadcare.android.library.conversion.* import com.uploadcare.android.library.exceptions.UploadcareApiException import com.uploadcare.android.widget.controller.UploadcareWidget @@ -19,8 +25,23 @@ class CdnViewModel(application: Application) : AndroidViewModel(application) { val status = MutableLiveData() + private val client = UploadcareWidget.getInstance().uploadcareClient + private var converter: Converter? = null + private var executor: AddOnExecutor? = null + private val executorCallback = object : UploadcareFileCallback { + + override fun onFailure(e: UploadcareApiException) { + showProgressOrResult(false, e.message) + } + + override fun onSuccess(result: UploadcareFile) { + showProgressOrResult(false, getContext().getString(R.string.cdn_status_execute_sucess)) + setFile(result) + } + } + fun setFile(uploadcareFile: UploadcareFile?) { this.uploadcareFile.value = uploadcareFile } @@ -31,9 +52,7 @@ class CdnViewModel(application: Application) : AndroidViewModel(application) { val conversionJob = DocumentConversionJob(it.uuid).apply { setFormat(DocumentFormat.JPG) } - converter = DocumentConverter( - UploadcareWidget.getInstance().uploadcareClient, - listOf(conversionJob)) + converter = DocumentConverter(client, listOf(conversionJob)) converter?.convertAsync(object : ConversionFilesCallback { override fun onFailure(e: UploadcareApiException) { showProgressOrResult(false, e.message) @@ -55,9 +74,7 @@ class CdnViewModel(application: Application) : AndroidViewModel(application) { quality(VideoQuality.NORMAL) thumbnails(2) } - converter = VideoConverter( - UploadcareWidget.getInstance().uploadcareClient, - listOf(conversionJob)) + converter = VideoConverter(client, listOf(conversionJob)) converter?.convertAsync(object : ConversionFilesCallback { override fun onFailure(e: UploadcareApiException) { showProgressOrResult(false, e.message) @@ -71,12 +88,56 @@ class CdnViewModel(application: Application) : AndroidViewModel(application) { } } - fun cancelConversion() { + fun cancel() { converter?.cancel() converter = null + + executor?.cancel() + executor = null + showProgressOrResult(false, "Canceled") } + fun executeAWSRekognitionAddOn() { + cancel() + showProgressOrResult(true, getContext().getString(R.string.cdn_status_execute_aws_rekognition)) + uploadcareFile.value?.let { file -> + executor = AWSRekognitionAddOn(client).apply { + executeAsync(file.uuid, executorCallback) + } + } + } + + fun executeAWSRekognitionModerationAddOn() { + cancel() + showProgressOrResult(true, getContext().getString(R.string.cdn_status_execute_aws_rekognition_moderation)) + uploadcareFile.value?.let { file -> + executor = AWSRekognitionModerationAddOn(client).apply { + executeAsync(file.uuid, executorCallback) + } + } + } + + fun executeClamAVAddOn() { + cancel() + showProgressOrResult(true, getContext().getString(R.string.cdn_status_execute_clam_av)) + uploadcareFile.value?.let { file -> + executor = ClamAVAddOn(client).apply { + executeAsync(file.uuid, executorCallback) + } + } + } + + fun executeRemoveBgAddOn() { + cancel() + showProgressOrResult(true, getContext().getString(R.string.cdn_status_execute_remove_bg)) + uploadcareFile.value?.let { file -> + executor = RemoveBgAddOn(client).apply { + executeAsync(file.uuid, executorCallback) + } + } + } + /** * Shows current status message. * @@ -94,4 +155,4 @@ class CdnViewModel(application: Application) : AndroidViewModel(application) { private fun getContext(): Context { return getApplication() } -} \ No newline at end of file +} diff --git a/example/src/main/res/layout/fragment_cdn.xml b/example/src/main/res/layout/fragment_cdn.xml index 9259e100..1944df31 100644 --- a/example/src/main/res/layout/fragment_cdn.xml +++ b/example/src/main/res/layout/fragment_cdn.xml @@ -49,7 +49,7 @@ style="@style/Widget.MaterialComponents.Button.OutlinedButton" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:onClick="@{() -> viewModel.cancelConversion()}" + android:onClick="@{() -> viewModel.cancel()}" android:text="@string/cdn_cancel_conversion" app:isVisible="@{viewModel.loading}" app:layout_constraintBottom_toBottomOf="parent" @@ -267,4 +267,4 @@ - \ No newline at end of file + diff --git a/example/src/main/res/menu/cdn_file_actions.xml b/example/src/main/res/menu/cdn_file_actions.xml index 686420ba..19ec01e0 100644 --- a/example/src/main/res/menu/cdn_file_actions.xml +++ b/example/src/main/res/menu/cdn_file_actions.xml @@ -11,4 +11,24 @@ android:orderInCategory="100" android:title="@string/action_convert_video" app:showAsAction="never" /> - \ No newline at end of file + + + + + diff --git a/example/src/main/res/values/strings.xml b/example/src/main/res/values/strings.xml index 20fde968..4d588691 100644 --- a/example/src/main/res/values/strings.xml +++ b/example/src/main/res/values/strings.xml @@ -45,9 +45,18 @@ Converting video… Successfully converted Cancel + Executing AWS Rekognition… + Executing AWS Rekognition Moderation… + Executing ClamAV… + Executing Remove.bg… + Successfully executed Convert Document Convert Video + Execute AWS Rekognition + Execute AWS Rekognition Moderation + Execute ClamAV + Execute Remove.bg Upload time, Ascending (Default) diff --git a/library/src/main/java/com/uploadcare/android/library/addon/AWSRekognitionAddOn.kt b/library/src/main/java/com/uploadcare/android/library/addon/AWSRekognitionAddOn.kt new file mode 100644 index 00000000..bd04036a --- /dev/null +++ b/library/src/main/java/com/uploadcare/android/library/addon/AWSRekognitionAddOn.kt @@ -0,0 +1,10 @@ +package com.uploadcare.android.library.addon + +import com.uploadcare.android.library.api.UploadcareClient +import com.uploadcare.android.library.urls.Urls + +class AWSRekognitionAddOn(client: UploadcareClient) : AddOnExecutor( + client = client, + executeUri = Urls.apiExecuteAWSRekognition(), + checkUri = Urls.apiAWSRekognitionStatus() +) diff --git a/library/src/main/java/com/uploadcare/android/library/addon/AWSRekognitionModerationAddOn.kt b/library/src/main/java/com/uploadcare/android/library/addon/AWSRekognitionModerationAddOn.kt new file mode 100644 index 00000000..eb771025 --- /dev/null +++ b/library/src/main/java/com/uploadcare/android/library/addon/AWSRekognitionModerationAddOn.kt @@ -0,0 +1,10 @@ +package com.uploadcare.android.library.addon + +import com.uploadcare.android.library.api.UploadcareClient +import com.uploadcare.android.library.urls.Urls + +class AWSRekognitionModerationAddOn(client: UploadcareClient) : AddOnExecutor( + client = client, + executeUri = Urls.apiExecuteAWSRekognitionModeration(), + checkUri = Urls.apiAWSRekognitionModerationStatus() +) diff --git a/library/src/main/java/com/uploadcare/android/library/addon/AddOnExecutor.kt b/library/src/main/java/com/uploadcare/android/library/addon/AddOnExecutor.kt new file mode 100644 index 00000000..922ec5b0 --- /dev/null +++ b/library/src/main/java/com/uploadcare/android/library/addon/AddOnExecutor.kt @@ -0,0 +1,123 @@ +package com.uploadcare.android.library.addon + +import com.uploadcare.android.library.api.RequestHelper +import com.uploadcare.android.library.api.RequestHelper.Companion.md5 +import com.uploadcare.android.library.api.UploadcareClient +import com.uploadcare.android.library.api.UploadcareFile +import com.uploadcare.android.library.callbacks.UploadcareFileCallback +import com.uploadcare.android.library.data.AddOnExecuteData +import com.uploadcare.android.library.data.AddOnExecuteResult +import com.uploadcare.android.library.data.AddOnStatus +import com.uploadcare.android.library.data.AddOnStatusResult +import com.uploadcare.android.library.exceptions.UploadcareApiException +import com.uploadcare.android.library.upload.UrlUploader +import com.uploadcare.android.library.urls.RequestIdParameter +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancelChildren +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import okhttp3.RequestBody.Companion.toRequestBody +import okio.ByteString.Companion.encodeUtf8 +import java.net.URI + +abstract class AddOnExecutor( + private val client: UploadcareClient, + private val executeUri: URI, + private val checkUri: URI +) { + + private val scope = MainScope() + + fun execute(fileId: String): AddOnExecuteResult { + val executeData = AddOnExecuteData(fileId) + val requestBodyContent = client.objectMapper.toJson( + executeData, + AddOnExecuteData::class.java + ) + val body = requestBodyContent.encodeUtf8().toRequestBody(RequestHelper.JSON) + + return client.requestHelper.executeQuery( + RequestHelper.REQUEST_POST, + executeUri.toString(), + true, + AddOnExecuteResult::class.java, + body, + requestBodyContent.md5() + ) + } + + fun check(requestId: String): AddOnStatusResult { + return client.requestHelper.executeQuery( + RequestHelper.REQUEST_GET, + checkUri.toString(), + true, + AddOnStatusResult::class.java, + urlParameters = listOf(RequestIdParameter(requestId)) + ) + } + + fun executeAsync(fileId: String, callback: UploadcareFileCallback) { + scope.launch(Dispatchers.IO) { + try { + val file = executeWithResult(fileId) + withContext(Dispatchers.Main) { + callback.onSuccess(file) + } + } catch (e: Exception) { + withContext(Dispatchers.Main) { + callback.onFailure(UploadcareApiException(e.message)) + } + } + } + } + + private suspend fun executeWithResult(fileId: String): UploadcareFile { + val requestId = execute(fileId).requestId + var retryCount = 0 + var lastPendingStatus = 0L + val startTime = System.currentTimeMillis() + var waitTime = UrlUploader.DEFAULT_POLLING_INTERVAL + + while (retryCount < MAX_CHECK_STATUS_ATTEMPTS) { + val statusResult = check(requestId) + + when (statusResult.status) { + AddOnStatus.IN_PROGRESS-> { + if (lastPendingStatus < PROCESSING_TIMEOUT) { + lastPendingStatus = System.currentTimeMillis() - startTime + } else { + waitTime = UrlUploader.calculateTimeToWait(retryCount) + retryCount++ + } + } + + AddOnStatus.ERROR -> { + throw UploadcareApiException("Execution error: Unexpected processing error.") + } + + AddOnStatus.UNKNOWN -> { + throw UploadcareApiException("Execution error: TTL expired.") + } + + AddOnStatus.DONE -> { + return statusResult.result?.fileId?.let { resultFileId -> + client.getFile(resultFileId) + } ?: client.getFileWithAppData(fileId) + } + } + + delay(waitTime) + } + + throw UploadcareApiException("Execution error: Unexpected processing error.") + } + + fun cancel() = scope.coroutineContext.cancelChildren() + + companion object { + private const val PROCESSING_TIMEOUT = 300000L + private const val MAX_CHECK_STATUS_ATTEMPTS: Int = 10 + } +} diff --git a/library/src/main/java/com/uploadcare/android/library/addon/ClamAVAddOn.kt b/library/src/main/java/com/uploadcare/android/library/addon/ClamAVAddOn.kt new file mode 100644 index 00000000..5daf12b8 --- /dev/null +++ b/library/src/main/java/com/uploadcare/android/library/addon/ClamAVAddOn.kt @@ -0,0 +1,10 @@ +package com.uploadcare.android.library.addon + +import com.uploadcare.android.library.api.UploadcareClient +import com.uploadcare.android.library.urls.Urls + +class ClamAVAddOn(client: UploadcareClient) : AddOnExecutor( + client = client, + executeUri = Urls.apiExecuteClamAV(), + checkUri = Urls.apiClamAVStatus() +) diff --git a/library/src/main/java/com/uploadcare/android/library/addon/RemoveBgAddOn.kt b/library/src/main/java/com/uploadcare/android/library/addon/RemoveBgAddOn.kt new file mode 100644 index 00000000..e5cd1f43 --- /dev/null +++ b/library/src/main/java/com/uploadcare/android/library/addon/RemoveBgAddOn.kt @@ -0,0 +1,10 @@ +package com.uploadcare.android.library.addon + +import com.uploadcare.android.library.api.UploadcareClient +import com.uploadcare.android.library.urls.Urls + +class RemoveBgAddOn(client: UploadcareClient) : AddOnExecutor( + client = client, + executeUri = Urls.apiExecuteRemoveBg(), + checkUri = Urls.apiRemoveBgStatus() +) diff --git a/library/src/main/java/com/uploadcare/android/library/api/RequestHelper.kt b/library/src/main/java/com/uploadcare/android/library/api/RequestHelper.kt index 9ed372b8..218ff22d 100644 --- a/library/src/main/java/com/uploadcare/android/library/api/RequestHelper.kt +++ b/library/src/main/java/com/uploadcare/android/library/api/RequestHelper.kt @@ -122,7 +122,7 @@ class RequestHelper(private val client: UploadcareClient) { REQUEST_PUT -> requestBody?.let { requestBuilder.put(it) } } if (apiHeaders) { - setApiHeaders(requestBuilder, url, requestType, requestBodyMD5 = requestBodyMD5, + setApiHeaders(requestBuilder, pageUrl.toString(), requestType, requestBodyMD5 = requestBodyMD5, contentType = requestBody?.contentType().toString()) } try { @@ -162,7 +162,7 @@ class RequestHelper(private val client: UploadcareClient) { REQUEST_PUT -> requestBody?.let { requestBuilder.put(it) } } if (apiHeaders) { - setApiHeaders(requestBuilder, url, requestType, requestBodyMD5 = requestBodyMD5, + setApiHeaders(requestBuilder, pageUrl.toString(), requestType, requestBodyMD5 = requestBodyMD5, contentType = requestBody?.contentType().toString()) } try { @@ -203,7 +203,7 @@ class RequestHelper(private val client: UploadcareClient) { REQUEST_PUT -> requestBody?.let { requestBuilder.put(it) } } if (apiHeaders) { - setApiHeaders(requestBuilder, url, requestType, callback, requestBodyMD5, + setApiHeaders(requestBuilder, pageUrl.toString(), requestType, callback, requestBodyMD5, requestBody?.contentType().toString()) } client.httpClient.newCall(requestBuilder.build()).enqueue(object : Callback { @@ -265,7 +265,7 @@ class RequestHelper(private val client: UploadcareClient) { REQUEST_PUT -> requestBody?.let { requestBuilder.put(it) } } if (apiHeaders) { - setApiHeaders(requestBuilder, url, requestType, callback, requestBodyMD5, + setApiHeaders(requestBuilder, pageUrl.toString(), requestType, callback, requestBodyMD5, requestBody?.contentType().toString()) } client.httpClient.newCall(requestBuilder.build()).enqueue(object : Callback { diff --git a/library/src/main/java/com/uploadcare/android/library/data/AddOnData.kt b/library/src/main/java/com/uploadcare/android/library/data/AddOnData.kt new file mode 100644 index 00000000..200c2563 --- /dev/null +++ b/library/src/main/java/com/uploadcare/android/library/data/AddOnData.kt @@ -0,0 +1,18 @@ +package com.uploadcare.android.library.data + +import com.squareup.moshi.Json + +data class AddOnExecuteData(val target: String) + +data class AddOnExecuteResult(@Json(name = "request_id") val requestId: String) + +data class AddOnStatusResult(val status: AddOnStatus, val result: AddOnStatusResultData?) + +data class AddOnStatusResultData(@Json(name = "file_id") val fileId: String) + +enum class AddOnStatus(val value: String) { + IN_PROGRESS("in_progress"), + ERROR("error"), + DONE("done"), + UNKNOWN("unknown") +} diff --git a/library/src/main/java/com/uploadcare/android/library/data/ObjectMapper.kt b/library/src/main/java/com/uploadcare/android/library/data/ObjectMapper.kt index a2c216dc..22f0bc72 100644 --- a/library/src/main/java/com/uploadcare/android/library/data/ObjectMapper.kt +++ b/library/src/main/java/com/uploadcare/android/library/data/ObjectMapper.kt @@ -4,6 +4,7 @@ import com.squareup.moshi.JsonAdapter import com.squareup.moshi.Moshi import com.squareup.moshi.adapters.Rfc3339DateJsonAdapter import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory +import com.uploadcare.android.library.utils.AddOnStatusAdapter import com.uploadcare.android.library.utils.EventTypeAdapter import com.uploadcare.android.library.utils.AsStringAdapter @@ -44,6 +45,7 @@ class ObjectMapper(val moshi: Moshi) { .add(Date::class.java, Rfc3339DateJsonAdapter().nullSafe())//rfc2822 .add(MoshiAdapter()) .add(EventTypeAdapter()) + .add(AddOnStatusAdapter) .build() ) } diff --git a/library/src/main/java/com/uploadcare/android/library/urls/UrlParameters.kt b/library/src/main/java/com/uploadcare/android/library/urls/UrlParameters.kt index 662df5bf..7afcc587 100644 --- a/library/src/main/java/com/uploadcare/android/library/urls/UrlParameters.kt +++ b/library/src/main/java/com/uploadcare/android/library/urls/UrlParameters.kt @@ -79,3 +79,10 @@ enum class Order constructor(val rawValue: String) { UPLOAD_TIME_ASC("datetime_uploaded"), UPLOAD_TIME_DESC("-datetime_uploaded") } + +class RequestIdParameter(private val requestId: String) : UrlParameter { + + override fun getParam() = "request_id" + + override fun getValue() = requestId +} diff --git a/library/src/main/java/com/uploadcare/android/library/urls/Urls.kt b/library/src/main/java/com/uploadcare/android/library/urls/Urls.kt index be1e5804..e063e25d 100644 --- a/library/src/main/java/com/uploadcare/android/library/urls/Urls.kt +++ b/library/src/main/java/com/uploadcare/android/library/urls/Urls.kt @@ -350,5 +350,85 @@ class Urls private constructor() { fun apiConvertVideoStatus(token: Int): URI { return URI.create("$API_BASE/convert/video/status/$token/") } + + /** + * Creates a URL to the AWS Rekognition execution. + * + * @see com.uploadcare.android.library.addon.AWSRekognitionAddOn + */ + @JvmStatic + fun apiExecuteAWSRekognition(): URI { + return URI.create("$API_BASE/addons/aws_rekognition_detect_labels/execute/") + } + + /** + * Creates a URL to the AWS Rekognition status. + * + * @see com.uploadcare.android.library.addon.AWSRekognitionAddOn + */ + @JvmStatic + fun apiAWSRekognitionStatus(): URI { + return URI.create("$API_BASE/addons/aws_rekognition_detect_labels/execute/status/") + } + + /** + * Creates a URL to the AWS Rekognition Moderation execution. + * + * @see com.uploadcare.android.library.addon.AWSRekognitionModerationAddOn + */ + @JvmStatic + fun apiExecuteAWSRekognitionModeration(): URI { + return URI.create("$API_BASE/addons/aws_rekognition_detect_moderation_labels/execute/") + } + + /** + * Creates a URL to the AWS Rekognition Moderation status. + * + * @see com.uploadcare.android.library.addon.AWSRekognitionModerationAddOn + */ + @JvmStatic + fun apiAWSRekognitionModerationStatus(): URI { + return URI.create("$API_BASE/addons/aws_rekognition_detect_moderation_labels/execute/status/") + } + + /** + * Creates a URL to the ClamAV execution. + * + * @see com.uploadcare.android.library.addon.ClamAVAddOn + */ + @JvmStatic + fun apiExecuteClamAV(): URI { + return URI.create("$API_BASE/addons/uc_clamav_virus_scan/execute/") + } + + /** + * Creates a URL to the ClamAV status. + * + * @see com.uploadcare.android.library.addon.ClamAVAddOn + */ + @JvmStatic + fun apiClamAVStatus(): URI { + return URI.create("$API_BASE/addons/uc_clamav_virus_scan/execute/status/") + } + + /** + * Creates a URL to the Remove.bg execution. + * + * @see com.uploadcare.android.library.addon.RemoveBgAddOn + */ + @JvmStatic + fun apiExecuteRemoveBg(): URI { + return URI.create("$API_BASE/addons/remove_bg/execute/") + } + + /** + * Creates a URL to the Remove.bg status. + * + * @see com.uploadcare.android.library.addon.RemoveBgAddOn + */ + @JvmStatic + fun apiRemoveBgStatus(): URI { + return URI.create("$API_BASE/addons/remove_bg/execute/status/") + } } -} \ No newline at end of file +} diff --git a/library/src/main/java/com/uploadcare/android/library/utils/AddOnStatusAdapter.kt b/library/src/main/java/com/uploadcare/android/library/utils/AddOnStatusAdapter.kt new file mode 100644 index 00000000..6dacc860 --- /dev/null +++ b/library/src/main/java/com/uploadcare/android/library/utils/AddOnStatusAdapter.kt @@ -0,0 +1,16 @@ +package com.uploadcare.android.library.utils + +import com.squareup.moshi.FromJson +import com.squareup.moshi.ToJson +import com.uploadcare.android.library.data.AddOnStatus + +internal object AddOnStatusAdapter { + + @ToJson + fun toJson(type: AddOnStatus): String = type.value + + @FromJson + fun fromJson(value: String): AddOnStatus = AddOnStatus.entries + .firstOrNull { type -> type.value == value } + ?: throw IllegalArgumentException("Unknown status type") +} From 098177b6c7e592fe70817115ca4ecbc7b613ecf2 Mon Sep 17 00:00:00 2001 From: CheK539 Date: Fri, 10 Nov 2023 16:01:29 +0500 Subject: [PATCH 2/4] Update CHANGELOG.md and LIBRARY.md --- CHANGELOG.md | 5 ++++- documentation/LIBRARY.md | 8 ++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 875ad5e5..24267daa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,10 @@ - Added support for converting multipage into a group of files. - Added `default_effects` field in `UploadcareFile`. - Added react to request throttling (429 code response). - - Added support for latest add-ons. + - Added support for [object recognition](https://uploadcare.com/docs/intelligence/object-recognition/), + [unsafe content detection](https://uploadcare.com/docs/unsafe-content/), + [malware protection](https://uploadcare.com/docs/security/malware-protection/), + and [background removal](https://uploadcare.com/docs/remove-bg/). - Fixed authorization signature for requests with url parameters. - Widget: - SocialApi doesn't use `GET /sources` method anymore. diff --git a/documentation/LIBRARY.md b/documentation/LIBRARY.md index 733d4f5b..c498c975 100644 --- a/documentation/LIBRARY.md +++ b/documentation/LIBRARY.md @@ -1273,6 +1273,7 @@ addOn.executeAsync("YOUR_FILE_UUID", object : UploadcareFileCallback { } }) ``` + Java ```java // Create AWS Rekognition Add-On @@ -1314,6 +1315,7 @@ if (statusResult.status == AddOnStatus.DONE) { // Handle other statuses } ``` + Java ```java // Create AWS Rekognition Add-On @@ -1354,6 +1356,7 @@ addOn.executeAsync(object : UploadcareFileCallback { } }) ``` + Java ```java // Create AWS Rekognition Add-On @@ -1394,6 +1397,7 @@ if (statusResult.status == AddOnStatus.DONE) { // Handle other statuses } ``` + Java ```java // Create AWS Rekognition Moderation Add-On @@ -1434,6 +1438,7 @@ addOn.executeAsync(object : UploadcareFileCallback { } }) ``` + Java ```java // Create ClamAV Add-On @@ -1474,6 +1479,7 @@ if (statusResult.status == AddOnStatus.DONE) { // Handle other statuses } ``` + Java ```java // Create ClamAV Add-On @@ -1514,6 +1520,7 @@ addOn.executeAsync(object : UploadcareFileCallback { } }) ``` + Java ```java // Create Remove.bg Add-On @@ -1554,6 +1561,7 @@ if (statusResult.status == AddOnStatus.DONE) { // Handle other statuses } ``` + Java ```java // Create Remove.bg Add-On From e659ba7d18303384f1a2dac71c9f9b6483d23026 Mon Sep 17 00:00:00 2001 From: CheK539 Date: Mon, 13 Nov 2023 18:04:24 +0500 Subject: [PATCH 3/4] Update LIBRARY.md --- documentation/LIBRARY.md | 4 ---- .../com/uploadcare/android/example/viewmodels/CdnViewModel.kt | 1 - 2 files changed, 5 deletions(-) diff --git a/documentation/LIBRARY.md b/documentation/LIBRARY.md index c498c975..227e0a10 100644 --- a/documentation/LIBRARY.md +++ b/documentation/LIBRARY.md @@ -1263,7 +1263,6 @@ val addOn = AWSRekognitionAddOn(uploadcare) // Execute addOn.executeAsync("YOUR_FILE_UUID", object : UploadcareFileCallback { - override fun onFailure(e: UploadcareApiException) { // Handle errors. } @@ -1346,7 +1345,6 @@ val addOn = AWSRekognitionModerationAddOn(uploadcare) // Execute addOn.executeAsync(object : UploadcareFileCallback { - override fun onFailure(e: UploadcareApiException) { // Handle errors. } @@ -1428,7 +1426,6 @@ val addOn = ClamAVAddOn(uploadcare) // Execute addOn.executeAsync(object : UploadcareFileCallback { - override fun onFailure(e: UploadcareApiException) { // Handle errors. } @@ -1510,7 +1507,6 @@ val addOn = RemoveBgAddOn(uploadcare) // Execute addOn.executeAsync(object : UploadcareFileCallback { - override fun onFailure(e: UploadcareApiException) { // Handle errors. } diff --git a/example/src/main/java/com/uploadcare/android/example/viewmodels/CdnViewModel.kt b/example/src/main/java/com/uploadcare/android/example/viewmodels/CdnViewModel.kt index d4dc0bdc..c3d8c895 100644 --- a/example/src/main/java/com/uploadcare/android/example/viewmodels/CdnViewModel.kt +++ b/example/src/main/java/com/uploadcare/android/example/viewmodels/CdnViewModel.kt @@ -31,7 +31,6 @@ class CdnViewModel(application: Application) : AndroidViewModel(application) { private var executor: AddOnExecutor? = null private val executorCallback = object : UploadcareFileCallback { - override fun onFailure(e: UploadcareApiException) { showProgressOrResult(false, e.message) } From ab5a9ada32ceb6e470f2ef138f53a1f3ba8177c0 Mon Sep 17 00:00:00 2001 From: CheK539 Date: Mon, 13 Nov 2023 18:47:29 +0500 Subject: [PATCH 4/4] Add common method `executeAddOn()` --- .../example/viewmodels/CdnViewModel.kt | 36 +++++++------------ 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/example/src/main/java/com/uploadcare/android/example/viewmodels/CdnViewModel.kt b/example/src/main/java/com/uploadcare/android/example/viewmodels/CdnViewModel.kt index c3d8c895..68a76934 100644 --- a/example/src/main/java/com/uploadcare/android/example/viewmodels/CdnViewModel.kt +++ b/example/src/main/java/com/uploadcare/android/example/viewmodels/CdnViewModel.kt @@ -2,6 +2,7 @@ package com.uploadcare.android.example.viewmodels import android.app.Application import android.content.Context +import androidx.annotation.StringRes import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.MutableLiveData import com.uploadcare.android.example.R @@ -98,40 +99,29 @@ class CdnViewModel(application: Application) : AndroidViewModel(application) { } fun executeAWSRekognitionAddOn() { - cancel() - showProgressOrResult(true, getContext().getString(R.string.cdn_status_execute_aws_rekognition)) - uploadcareFile.value?.let { file -> - executor = AWSRekognitionAddOn(client).apply { - executeAsync(file.uuid, executorCallback) - } - } + executeAddOn(R.string.cdn_status_execute_aws_rekognition, AWSRekognitionAddOn(client)) } fun executeAWSRekognitionModerationAddOn() { - cancel() - showProgressOrResult(true, getContext().getString(R.string.cdn_status_execute_aws_rekognition_moderation)) - uploadcareFile.value?.let { file -> - executor = AWSRekognitionModerationAddOn(client).apply { - executeAsync(file.uuid, executorCallback) - } - } + executeAddOn( + R.string.cdn_status_execute_aws_rekognition_moderation, + AWSRekognitionModerationAddOn(client) + ) } fun executeClamAVAddOn() { - cancel() - showProgressOrResult(true, getContext().getString(R.string.cdn_status_execute_clam_av)) - uploadcareFile.value?.let { file -> - executor = ClamAVAddOn(client).apply { - executeAsync(file.uuid, executorCallback) - } - } + executeAddOn(R.string.cdn_status_execute_clam_av, ClamAVAddOn(client)) } fun executeRemoveBgAddOn() { + executeAddOn(R.string.cdn_status_execute_remove_bg, RemoveBgAddOn(client)) + } + + private fun executeAddOn(@StringRes progressMessageResId: Int, addOnExecutor: AddOnExecutor) { cancel() - showProgressOrResult(true, getContext().getString(R.string.cdn_status_execute_remove_bg)) + showProgressOrResult(true, getContext().getString(progressMessageResId)) uploadcareFile.value?.let { file -> - executor = RemoveBgAddOn(client).apply { + executor = addOnExecutor.apply { executeAsync(file.uuid, executorCallback) } }