diff --git a/HISTORY.md b/HISTORY.md index cda8d413..432712cc 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,4 +1,11 @@ # History +## 2.1.0 +- Example: + - Update Widget/Library versions +- Widget: + - Update Library version +- Library: + - Fix batch store/delete calls support more than 100 Files ## 2.0.1 - Example: @@ -14,7 +21,6 @@ - Set default UploadcareClient auth method to HMAC-based - Fix: "Project" data model, now **UploadcareClient.getProject()** and **UploadcareClient.getProjectAsync()** work properly - Internal network layer optimizations and improvements - ## 2.0.0 - Example: Update dependencies, rewrite example to Kotlin. diff --git a/example/build.gradle b/example/build.gradle index 6609e3d3..0bc2fc3e 100644 --- a/example/build.gradle +++ b/example/build.gradle @@ -11,8 +11,8 @@ android { applicationId "com.uploadcare.android.example" minSdkVersion versions.min_sdk targetSdkVersion versions.target_sdk - versionCode 4 - versionName "2.0.1" + versionCode 5 + versionName "2.1.0" } dataBinding { diff --git a/library/README.md b/library/README.md index 0e74ae83..2b58382f 100644 --- a/library/README.md +++ b/library/README.md @@ -22,7 +22,7 @@ Latest stable version is available from jCenter. To include it in your Android project, add this to the gradle.build file: ``` -implementation 'com.uploadcare.android.library:uploadcare-android:2.0.1' +implementation 'com.uploadcare.android.library:uploadcare-android:2.1.0' ``` diff --git a/library/build.gradle b/library/build.gradle index 78e3d888..76aed837 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -11,8 +11,8 @@ android { defaultConfig { minSdkVersion versions.min_sdk targetSdkVersion versions.target_sdk - versionCode 8 - versionName "2.0.1" + versionCode 9 + versionName "2.1.0" } androidExtensions { @@ -63,7 +63,7 @@ publish { userOrg = properties.containsKey('bintray.user') ? properties.getProperty("bintray.user") : "" groupId = 'com.uploadcare.android.library' artifactId = 'uploadcare-android' - publishVersion = '2.0.1' + publishVersion = '2.1.0' desc = 'Android client library for the Uploadcare API.' licences = ["Apache-2.0"] website = 'https://github.com/uploadcare/uploadcare-android' 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 7dfbfbd4..208ca2d8 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 @@ -475,7 +475,7 @@ class RequestHelper(private val client: UploadcareClient) { */ @Throws(IOException::class, UploadcareAuthenticationException::class, UploadcareInvalidRequestException::class, UploadcareApiException::class) - private fun checkResponseStatus(response: Response) { + internal fun checkResponseStatus(response: Response) { val statusCode = response.code() if (statusCode in 200..299) { diff --git a/library/src/main/java/com/uploadcare/android/library/api/UploadcareClient.kt b/library/src/main/java/com/uploadcare/android/library/api/UploadcareClient.kt index 29bda63e..99444e6b 100644 --- a/library/src/main/java/com/uploadcare/android/library/api/UploadcareClient.kt +++ b/library/src/main/java/com/uploadcare/android/library/api/UploadcareClient.kt @@ -1,12 +1,14 @@ package com.uploadcare.android.library.api import android.content.Context +import android.os.AsyncTask import com.squareup.moshi.Types import com.uploadcare.android.library.BuildConfig import com.uploadcare.android.library.api.RequestHelper.Companion.md5 import com.uploadcare.android.library.callbacks.* import com.uploadcare.android.library.data.CopyFileData import com.uploadcare.android.library.data.ObjectMapper +import com.uploadcare.android.library.exceptions.UploadcareApiException import com.uploadcare.android.library.urls.Urls import okhttp3.* import okhttp3.logging.HttpLoggingInterceptor @@ -211,51 +213,71 @@ class UploadcareClient constructor(val publicKey: String, /** * Marks a files as deleted. - * Maximum 100 file id's can be provided. * * @param fileIds Resource UUIDs */ fun deleteFiles(fileIds: List) { val url = Urls.apiFilesBatch() - val requestBodyContent = objectMapper.toJson(fileIds, - Types.newParameterizedType(List::class.java, String::class.java)) - val body = RequestBody.create(RequestHelper.JSON, ByteString.encodeUtf8(requestBodyContent)) - requestHelper.executeCommand(RequestHelper.REQUEST_DELETE, url.toString(), true, body, - requestBodyContent.md5()) + if (fileIds.size <= MAX_SAVE_DELETE_BATCH_SIZE) { + // Make single request. + val requestBodyContent = objectMapper.toJson(fileIds, + Types.newParameterizedType(List::class.java, String::class.java)) + val body = RequestBody.create(RequestHelper.JSON, + ByteString.encodeUtf8(requestBodyContent)) + requestHelper.executeCommand(RequestHelper.REQUEST_DELETE, url.toString(), true, body, + requestBodyContent.md5()) + } else { + // Make batch requests. + executeSaveDeleteBatchCommand(RequestHelper.REQUEST_DELETE, fileIds) + } } /** * Marks multiple files as deleted Asynchronously. - * Maximum 100 file id's can be provided. * * @param context Application context. [android.content.Context] * @param fileIds Resource UUIDs */ fun deleteFilesAsync(context: Context, fileIds: List) { val url = Urls.apiFilesBatch() - val requestBodyContent = objectMapper.toJson(fileIds, - Types.newParameterizedType(List::class.java, String::class.java)) - val body = RequestBody.create(RequestHelper.JSON, ByteString.encodeUtf8(requestBodyContent)) - requestHelper.executeCommandAsync(context, RequestHelper.REQUEST_DELETE, url.toString(), - true, null, body, requestBodyContent.md5()) + if (fileIds.size < MAX_SAVE_DELETE_BATCH_SIZE) { + // Make single request. + val requestBodyContent = objectMapper.toJson(fileIds, + Types.newParameterizedType(List::class.java, String::class.java)) + val body = RequestBody.create(RequestHelper.JSON, + ByteString.encodeUtf8(requestBodyContent)) + requestHelper.executeCommandAsync(context, RequestHelper.REQUEST_DELETE, + url.toString(), true, null, body, requestBodyContent.md5()) + } else { + // Make batch requests. + SaveDeleteBatchTask(this, RequestHelper.REQUEST_DELETE, fileIds).execute() + } } /** * Marks multiple files as deleted Asynchronously. - * Maximum 100 file id's can be provided. * * @param context Application context. [android.content.Context] * @param fileIds Resource UUIDs * @param callback callback [RequestCallback] with either * an HTTP response or a failure exception. */ - fun deleteFilesAsync(context: Context, fileIds: List, callback: RequestCallback? = null) { + fun deleteFilesAsync(context: Context, + fileIds: List, + callback: RequestCallback? = null) { val url = Urls.apiFilesBatch() - val requestBodyContent = objectMapper.toJson(fileIds, - Types.newParameterizedType(List::class.java, String::class.java)) - val body = RequestBody.create(RequestHelper.JSON, ByteString.encodeUtf8(requestBodyContent)) - requestHelper.executeCommandAsync(context, RequestHelper.REQUEST_DELETE, url.toString(), - true, callback, body, requestBodyContent.md5()) + if (fileIds.size < MAX_SAVE_DELETE_BATCH_SIZE) { + // Make single request. + val requestBodyContent = objectMapper.toJson(fileIds, + Types.newParameterizedType(List::class.java, String::class.java)) + val body = RequestBody.create(RequestHelper.JSON, + ByteString.encodeUtf8(requestBodyContent)) + requestHelper.executeCommandAsync(context, RequestHelper.REQUEST_DELETE, + url.toString(), true, callback, body, requestBodyContent.md5()) + } else { + // Make batch requests. + SaveDeleteBatchTask(this, RequestHelper.REQUEST_DELETE, fileIds, callback).execute() + } } /** @@ -312,11 +334,18 @@ class UploadcareClient constructor(val publicKey: String, */ fun saveFiles(fileIds: List) { val url = Urls.apiFilesBatch() - val requestBodyContent = objectMapper.toJson(fileIds, - Types.newParameterizedType(List::class.java, String::class.java)) - val body = RequestBody.create(RequestHelper.JSON, ByteString.encodeUtf8(requestBodyContent)) - requestHelper.executeCommand(RequestHelper.REQUEST_PUT, url.toString(), true, body, - requestBodyContent.md5()) + if (fileIds.size < MAX_SAVE_DELETE_BATCH_SIZE) { + // Make single request. + val requestBodyContent = objectMapper.toJson(fileIds, + Types.newParameterizedType(List::class.java, String::class.java)) + val body = RequestBody.create(RequestHelper.JSON, + ByteString.encodeUtf8(requestBodyContent)) + requestHelper.executeCommand(RequestHelper.REQUEST_PUT, url.toString(), true, body, + requestBodyContent.md5()) + } else { + // Make batch requests. + executeSaveDeleteBatchCommand(RequestHelper.REQUEST_PUT, fileIds) + } } /** @@ -330,11 +359,18 @@ class UploadcareClient constructor(val publicKey: String, */ fun saveFilesAsync(context: Context, fileIds: List) { val url = Urls.apiFilesBatch() - val requestBodyContent = objectMapper.toJson(fileIds, - Types.newParameterizedType(List::class.java, String::class.java)) - val body = RequestBody.create(RequestHelper.JSON, ByteString.encodeUtf8(requestBodyContent)) - requestHelper.executeCommandAsync(context, RequestHelper.REQUEST_PUT, url.toString(), - true, null, body, requestBodyContent.md5()) + if (fileIds.size < MAX_SAVE_DELETE_BATCH_SIZE) { + // Make single request. + val requestBodyContent = objectMapper.toJson(fileIds, + Types.newParameterizedType(List::class.java, String::class.java)) + val body = RequestBody.create(RequestHelper.JSON, + ByteString.encodeUtf8(requestBodyContent)) + requestHelper.executeCommandAsync(context, RequestHelper.REQUEST_PUT, url.toString(), + true, null, body, requestBodyContent.md5()) + } else { + // Make batch requests. + SaveDeleteBatchTask(this, RequestHelper.REQUEST_PUT, fileIds).execute() + } } /** @@ -350,11 +386,18 @@ class UploadcareClient constructor(val publicKey: String, */ fun saveFilesAsync(context: Context, fileIds: List, callback: RequestCallback? = null) { val url = Urls.apiFilesBatch() - val requestBodyContent = objectMapper.toJson(fileIds, - Types.newParameterizedType(List::class.java, String::class.java)) - val body = RequestBody.create(RequestHelper.JSON, ByteString.encodeUtf8(requestBodyContent)) - requestHelper.executeCommandAsync(context, RequestHelper.REQUEST_PUT, url.toString(), - true, callback, body, requestBodyContent.md5()) + if (fileIds.size < MAX_SAVE_DELETE_BATCH_SIZE) { + // Make single request. + val requestBodyContent = objectMapper.toJson(fileIds, + Types.newParameterizedType(List::class.java, String::class.java)) + val body = RequestBody.create(RequestHelper.JSON, + ByteString.encodeUtf8(requestBodyContent)) + requestHelper.executeCommandAsync(context, RequestHelper.REQUEST_PUT, url.toString(), + true, callback, body, requestBodyContent.md5()) + } else { + // Make batch requests. + SaveDeleteBatchTask(this, RequestHelper.REQUEST_PUT, fileIds, callback).execute() + } } /** @@ -396,8 +439,32 @@ class UploadcareClient constructor(val publicKey: String, CopyFileData::class.java, callback, formBody, formBody.md5()) } + internal fun executeSaveDeleteBatchCommand(requestType: String, + fileIds: List): Response? { + val url = Urls.apiFilesBatch() + var lastResponse: Response? = null + for (offset in 0 until fileIds.size step MAX_SAVE_DELETE_BATCH_SIZE) { + val endIndex = if (offset + MAX_SAVE_DELETE_BATCH_SIZE >= fileIds.size) + fileIds.size - 1 + else offset + (MAX_SAVE_DELETE_BATCH_SIZE - 1) + val ids = fileIds.subList(offset, endIndex) + + val requestBodyContent = objectMapper.toJson(ids, + Types.newParameterizedType(List::class.java, String::class.java)) + val body = RequestBody.create(RequestHelper.JSON, + ByteString.encodeUtf8(requestBodyContent)) + val response = requestHelper.executeCommand(requestType, url.toString(), true, body, + requestBodyContent.md5()) + requestHelper.checkResponseStatus(response) + lastResponse = response + } + return lastResponse + } + companion object { + private const val MAX_SAVE_DELETE_BATCH_SIZE = 100 + @JvmStatic fun demoClient(): UploadcareClient { return UploadcareClient("demopublickey", "demoprivatekey") @@ -415,4 +482,27 @@ private class PublicKeyInterceptor constructor(private val publicKey: String) return chain.proceed(requestBuilder.build()) } +} + +private class SaveDeleteBatchTask(private val client: UploadcareClient, + private val requestType: String, + private val fileIds: List, + private val callback: RequestCallback? = null) + : AsyncTask() { + override fun doInBackground(vararg params: Void?): Response? { + return try { + client.executeSaveDeleteBatchCommand(requestType, fileIds) + } catch (e: Exception) { + null + } + } + + override fun onPostExecute(result: Response?) { + if (result != null) { + callback?.onSuccess(result) + } else { + callback?.onFailure(UploadcareApiException()) + } + } + } \ No newline at end of file diff --git a/widget/README.md b/widget/README.md index 61c92ee9..8cb31b19 100644 --- a/widget/README.md +++ b/widget/README.md @@ -19,7 +19,7 @@ Latest stable version is available from jCenter. To include it in your Android project, add this to the gradle.build file: ``` -implementation 'com.uploadcare.android.widget:uploadcare-android-widget:2.0.1' +implementation 'com.uploadcare.android.widget:uploadcare-android-widget:2.1.0' ``` diff --git a/widget/build.gradle b/widget/build.gradle index 29088e5c..f0370b89 100644 --- a/widget/build.gradle +++ b/widget/build.gradle @@ -12,8 +12,8 @@ android { defaultConfig { minSdkVersion versions.min_sdk targetSdkVersion versions.target_sdk - versionCode 7 - versionName "2.0.1" + versionCode 8 + versionName "2.1.0" } dataBinding { @@ -81,7 +81,7 @@ publish { userOrg = properties.containsKey('bintray.user') ? properties.getProperty("bintray.user") : "" groupId = 'com.uploadcare.android.widget' artifactId = 'uploadcare-android-widget' - publishVersion = '2.0.1' + publishVersion = '2.1.0' desc = 'Android client library for the Uploadcare API.' licences = ["Apache-2.0"] website = 'https://github.com/uploadcare/uploadcare-android'