From 3fe751d11f748dd94cf5af2e90920d52f920dcd0 Mon Sep 17 00:00:00 2001 From: hans-hamel <124717244+hans-hamel@users.noreply.github.com> Date: Wed, 21 Jun 2023 14:56:49 -0400 Subject: [PATCH] Implement non-gms upload (#43) * Storage sdk - Implement non-gms upload and return null in gms(implement in other task). * Storage sdk: Add constants and missing space. Don't force unwrap nullable values. * Storage sdk - Change to private and refactor JSON_MIME_TYPE constant value * Solved comments * Solve conflicts --------- Co-authored-by: Hector A. Narvaez --- .../data/repository/GmsFileRepositoryImpl.kt | 6 +- .../source/GmsFileRemoteDataSourceImpl.kt | 11 ++-- .../drive/nongms/data/GoogleRetrofitImpl.kt | 14 ----- .../nongms/data/GoogleStorageApiService.kt | 13 +++++ .../repository/NonGmsFileRepositoryImpl.kt | 6 +- .../source/NonGmsFileRemoteDataSourceImpl.kt | 55 +++++++++++++++++++ .../android/storage/api/OmhStorageClient.kt | 17 ++++++ .../data/source/OmhFileRemoteDataSource.kt | 3 + .../domain/repository/OmhFileRepository.kt | 5 +- .../api/domain/usecase/UploadFileUseCase.kt | 23 ++++++++ 10 files changed, 129 insertions(+), 24 deletions(-) create mode 100644 storage-api/src/main/java/com/omh/android/storage/api/domain/usecase/UploadFileUseCase.kt diff --git a/storage-api-drive-gms/src/main/java/com/omh/android/storage/api/drive/gms/data/repository/GmsFileRepositoryImpl.kt b/storage-api-drive-gms/src/main/java/com/omh/android/storage/api/drive/gms/data/repository/GmsFileRepositoryImpl.kt index 7324fd0f..6de7a4b4 100644 --- a/storage-api-drive-gms/src/main/java/com/omh/android/storage/api/drive/gms/data/repository/GmsFileRepositoryImpl.kt +++ b/storage-api-drive-gms/src/main/java/com/omh/android/storage/api/drive/gms/data/repository/GmsFileRepositoryImpl.kt @@ -2,6 +2,7 @@ package com.omh.android.storage.api.drive.gms.data.repository import com.omh.android.storage.api.data.source.OmhFileRemoteDataSource import com.omh.android.storage.api.domain.repository.OmhFileRepository +import java.io.File internal class GmsFileRepositoryImpl(private val dataSource: OmhFileRemoteDataSource) : OmhFileRepository { @@ -12,11 +13,12 @@ internal class GmsFileRepositoryImpl(private val dataSource: OmhFileRemoteDataSo override fun deleteFile(fileId: String) = dataSource.deleteFile(fileId) + override fun uploadFile(localFileToUpload: File, fileName: String, parentId: String?) = + dataSource.uploadFile(localFileToUpload, fileName, parentId) + override fun open() = Unit override fun update() = Unit - override fun upload() = Unit - override fun download() = Unit } diff --git a/storage-api-drive-gms/src/main/java/com/omh/android/storage/api/drive/gms/data/source/GmsFileRemoteDataSourceImpl.kt b/storage-api-drive-gms/src/main/java/com/omh/android/storage/api/drive/gms/data/source/GmsFileRemoteDataSourceImpl.kt index f20c0d23..9655dc8f 100644 --- a/storage-api-drive-gms/src/main/java/com/omh/android/storage/api/drive/gms/data/source/GmsFileRemoteDataSourceImpl.kt +++ b/storage-api-drive-gms/src/main/java/com/omh/android/storage/api/drive/gms/data/source/GmsFileRemoteDataSourceImpl.kt @@ -1,23 +1,24 @@ package com.omh.android.storage.api.drive.gms.data.source -import com.google.api.services.drive.model.File import com.google.api.services.drive.model.FileList import com.omh.android.storage.api.data.source.OmhFileRemoteDataSource import com.omh.android.storage.api.domain.model.OmhFile import com.omh.android.storage.api.drive.gms.data.GoogleDriveApiService import com.omh.android.storage.api.drive.gms.data.source.mapper.toOmhFile +import java.io.File +import com.google.api.services.drive.model.File as GoogleApiFile internal class GmsFileRemoteDataSourceImpl(private val apiService: GoogleDriveApiService) : OmhFileRemoteDataSource { override fun getFilesList(parentId: String): List { val googleJsonFileList: FileList = apiService.getFilesList(parentId).execute() - val googleFileList: List = googleJsonFileList.files.toList() + val googleFileList: List = googleJsonFileList.files.toList() return googleFileList.mapNotNull { googleFile -> googleFile.toOmhFile() } } override fun createFile(name: String, mimeType: String, parentId: String?): OmhFile? { - val fileToBeCreated = File().apply { + val fileToBeCreated = GoogleApiFile().apply { this.name = name this.mimeType = mimeType if (parentId != null) { @@ -25,7 +26,7 @@ internal class GmsFileRemoteDataSourceImpl(private val apiService: GoogleDriveAp } } - val responseFile: File = apiService.createFile(fileToBeCreated).execute() + val responseFile: GoogleApiFile = apiService.createFile(fileToBeCreated).execute() return responseFile.toOmhFile() } @@ -39,4 +40,6 @@ internal class GmsFileRemoteDataSourceImpl(private val apiService: GoogleDriveAp false } } + + override fun uploadFile(localFileToUpload: File, fileName: String, parentId: String?) = null } diff --git a/storage-api-drive-nongms/src/main/java/com/omh/android/storage/api/drive/nongms/data/GoogleRetrofitImpl.kt b/storage-api-drive-nongms/src/main/java/com/omh/android/storage/api/drive/nongms/data/GoogleRetrofitImpl.kt index e4b90504..956bd04b 100644 --- a/storage-api-drive-nongms/src/main/java/com/omh/android/storage/api/drive/nongms/data/GoogleRetrofitImpl.kt +++ b/storage-api-drive-nongms/src/main/java/com/omh/android/storage/api/drive/nongms/data/GoogleRetrofitImpl.kt @@ -4,7 +4,6 @@ import com.omh.android.auth.api.OmhCredentials import com.omh.android.storage.api.drive.nongms.BuildConfig import okhttp3.Interceptor import okhttp3.OkHttpClient -import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit import retrofit2.converter.jackson.JacksonConverterFactory @@ -43,10 +42,7 @@ internal class GoogleRetrofitImpl(private val omhCredentials: OmhCredentials) { } private fun createOkHttpClient(): OkHttpClient { - val loggingInterceptor = setupLoggingInterceptor() - return OkHttpClient.Builder() - .addInterceptor(loggingInterceptor) .addInterceptor { chain -> val request = setupRequestInterceptor(chain) chain.proceed(request) @@ -54,16 +50,6 @@ internal class GoogleRetrofitImpl(private val omhCredentials: OmhCredentials) { .build() } - private fun setupLoggingInterceptor() = HttpLoggingInterceptor().apply { - setLevel( - if (BuildConfig.DEBUG) { - HttpLoggingInterceptor.Level.BODY - } else { - HttpLoggingInterceptor.Level.NONE - } - ) - } - private fun setupRequestInterceptor(chain: Interceptor.Chain) = chain .request() .newBuilder() diff --git a/storage-api-drive-nongms/src/main/java/com/omh/android/storage/api/drive/nongms/data/GoogleStorageApiService.kt b/storage-api-drive-nongms/src/main/java/com/omh/android/storage/api/drive/nongms/data/GoogleStorageApiService.kt index 3393dedf..7ddf2b9d 100644 --- a/storage-api-drive-nongms/src/main/java/com/omh/android/storage/api/drive/nongms/data/GoogleStorageApiService.kt +++ b/storage-api-drive-nongms/src/main/java/com/omh/android/storage/api/drive/nongms/data/GoogleStorageApiService.kt @@ -3,12 +3,16 @@ package com.omh.android.storage.api.drive.nongms.data import com.omh.android.storage.api.drive.nongms.data.source.body.CreateFileRequestBody import com.omh.android.storage.api.drive.nongms.data.source.response.FileListRemoteResponse import com.omh.android.storage.api.drive.nongms.data.source.response.FileRemoteResponse +import okhttp3.MultipartBody +import okhttp3.RequestBody import okhttp3.ResponseBody import retrofit2.Call import retrofit2.http.Body import retrofit2.http.DELETE import retrofit2.http.GET +import retrofit2.http.Multipart import retrofit2.http.POST +import retrofit2.http.Part import retrofit2.http.Path import retrofit2.http.Query @@ -16,6 +20,7 @@ internal interface GoogleStorageApiService { companion object { private const val FILES_PARTICLE = "drive/v3/files" + private const val UPLOAD_FILES_PARTICLE = "upload/drive/v3/files" private const val QUERY_Q = "q" private const val QUERY_FIELDS = "fields" @@ -28,6 +33,7 @@ internal interface GoogleStorageApiService { private const val FIELDS_VALUE = "files($QUERY_REQUESTED_FIELDS)" private const val FILE_ID = "fileId" + private const val META_DATA = "metadata" } @GET(FILES_PARTICLE) @@ -46,4 +52,11 @@ internal interface GoogleStorageApiService { fun deleteFile( @Path(FILE_ID) fileId: String ): Call + + @Multipart + @POST(UPLOAD_FILES_PARTICLE) + fun uploadFile( + @Part(META_DATA) metadata: RequestBody, + @Part filePart: MultipartBody.Part + ): Call } diff --git a/storage-api-drive-nongms/src/main/java/com/omh/android/storage/api/drive/nongms/data/repository/NonGmsFileRepositoryImpl.kt b/storage-api-drive-nongms/src/main/java/com/omh/android/storage/api/drive/nongms/data/repository/NonGmsFileRepositoryImpl.kt index fe18c38d..24686f2e 100644 --- a/storage-api-drive-nongms/src/main/java/com/omh/android/storage/api/drive/nongms/data/repository/NonGmsFileRepositoryImpl.kt +++ b/storage-api-drive-nongms/src/main/java/com/omh/android/storage/api/drive/nongms/data/repository/NonGmsFileRepositoryImpl.kt @@ -2,6 +2,7 @@ package com.omh.android.storage.api.drive.nongms.data.repository import com.omh.android.storage.api.data.source.OmhFileRemoteDataSource import com.omh.android.storage.api.domain.repository.OmhFileRepository +import java.io.File internal class NonGmsFileRepositoryImpl( private val dataSource: OmhFileRemoteDataSource @@ -15,11 +16,12 @@ internal class NonGmsFileRepositoryImpl( override fun deleteFile(fileId: String) = dataSource.deleteFile(fileId) + override fun uploadFile(localFileToUpload: File, fileName: String, parentId: String?) = + dataSource.uploadFile(localFileToUpload, fileName, parentId) + override fun open() = Unit override fun update() = Unit - override fun upload() = Unit - override fun download() = Unit } diff --git a/storage-api-drive-nongms/src/main/java/com/omh/android/storage/api/drive/nongms/data/source/NonGmsFileRemoteDataSourceImpl.kt b/storage-api-drive-nongms/src/main/java/com/omh/android/storage/api/drive/nongms/data/source/NonGmsFileRemoteDataSourceImpl.kt index 3a87329a..dec28484 100644 --- a/storage-api-drive-nongms/src/main/java/com/omh/android/storage/api/drive/nongms/data/source/NonGmsFileRemoteDataSourceImpl.kt +++ b/storage-api-drive-nongms/src/main/java/com/omh/android/storage/api/drive/nongms/data/source/NonGmsFileRemoteDataSourceImpl.kt @@ -1,5 +1,6 @@ package com.omh.android.storage.api.drive.nongms.data.source +import android.webkit.MimeTypeMap import com.omh.android.storage.api.data.source.OmhFileRemoteDataSource import com.omh.android.storage.api.domain.model.OmhFile import com.omh.android.storage.api.drive.nongms.data.GoogleRetrofitImpl @@ -7,10 +8,25 @@ import com.omh.android.storage.api.drive.nongms.data.GoogleStorageApiService import com.omh.android.storage.api.drive.nongms.data.source.body.CreateFileRequestBody import com.omh.android.storage.api.drive.nongms.data.source.mapper.toFile import com.omh.android.storage.api.drive.nongms.data.source.mapper.toFileList +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.MultipartBody +import okhttp3.RequestBody.Companion.asRequestBody +import okhttp3.RequestBody.Companion.toRequestBody +import org.json.JSONArray +import org.json.JSONObject +import java.io.File internal class NonGmsFileRemoteDataSourceImpl(private val retrofitImpl: GoogleRetrofitImpl) : OmhFileRemoteDataSource { + companion object { + private const val FILE_NAME_KEY = "name" + private const val FILE_PARENTS_KEY = "parents" + private const val ANY_MIME_TYPE = "*/*" + + private val JSON_MIME_TYPE = "application/json".toMediaTypeOrNull() + } + override fun getFilesList(parentId: String): List { val response = retrofitImpl .getGoogleStorageApiService() @@ -55,4 +71,43 @@ internal class NonGmsFileRemoteDataSourceImpl(private val retrofitImpl: GoogleRe return response.isSuccessful } + + override fun uploadFile( + localFileToUpload: File, + fileName: String, + parentId: String? + ): OmhFile? { + val stringMimeType = MimeTypeMap + .getSingleton() + .getMimeTypeFromExtension(localFileToUpload.extension) + ?: ANY_MIME_TYPE + + val mimeType = stringMimeType.toMediaTypeOrNull() + val requestFile = localFileToUpload.asRequestBody(mimeType) + val parentsList = if (parentId.isNullOrBlank()) { + emptyList() + } else { + listOf(parentId) + } + + val parentsListAsJson = JSONArray(parentsList) + val jsonMetaData = JSONObject().apply { + put(FILE_NAME_KEY, fileName) + put(FILE_PARENTS_KEY, parentsListAsJson) + } + + val jsonRequestBody = jsonMetaData.toString().toRequestBody(JSON_MIME_TYPE) + val filePart = MultipartBody.Part.createFormData(FILE_NAME_KEY, fileName, requestFile) + + val response = retrofitImpl + .getGoogleStorageApiService() + .uploadFile(jsonRequestBody, filePart) + .execute() + + return if (response.isSuccessful) { + response.body()?.toFile() + } else { + null + } + } } diff --git a/storage-api/src/main/java/com/omh/android/storage/api/OmhStorageClient.kt b/storage-api/src/main/java/com/omh/android/storage/api/OmhStorageClient.kt index 70c9b4ca..b8aff7f0 100644 --- a/storage-api/src/main/java/com/omh/android/storage/api/OmhStorageClient.kt +++ b/storage-api/src/main/java/com/omh/android/storage/api/OmhStorageClient.kt @@ -14,6 +14,10 @@ import com.omh.android.storage.api.domain.usecase.GetFilesListUseCase import com.omh.android.storage.api.domain.usecase.GetFilesListUseCaseParams import com.omh.android.storage.api.domain.usecase.GetFilesListUseCaseResult import com.omh.android.storage.api.domain.usecase.OmhResult +import com.omh.android.storage.api.domain.usecase.UploadFileUseCase +import com.omh.android.storage.api.domain.usecase.UploadFileUseCaseParams +import com.omh.android.storage.api.domain.usecase.UploadFileUseCaseResult +import java.io.File abstract class OmhStorageClient protected constructor( protected val authClient: OmhAuthClient @@ -56,4 +60,17 @@ abstract class OmhStorageClient protected constructor( result } } + + fun uploadFile( + localFileToUpload: File, + fileName: String, + parentId: String? + ): OmhTask { + val uploadFileUseCase = UploadFileUseCase(getRepository()) + return OmhStorageTaskImpl { + val parameters = UploadFileUseCaseParams(localFileToUpload, fileName, parentId) + val result = uploadFileUseCase(parameters) + result + } + } } diff --git a/storage-api/src/main/java/com/omh/android/storage/api/data/source/OmhFileRemoteDataSource.kt b/storage-api/src/main/java/com/omh/android/storage/api/data/source/OmhFileRemoteDataSource.kt index c8dbd866..495caf52 100644 --- a/storage-api/src/main/java/com/omh/android/storage/api/data/source/OmhFileRemoteDataSource.kt +++ b/storage-api/src/main/java/com/omh/android/storage/api/data/source/OmhFileRemoteDataSource.kt @@ -1,6 +1,7 @@ package com.omh.android.storage.api.data.source import com.omh.android.storage.api.domain.model.OmhFile +import java.io.File interface OmhFileRemoteDataSource { @@ -12,4 +13,6 @@ interface OmhFileRemoteDataSource { * @return true if the file was deleted, false otherwise */ fun deleteFile(fileId: String): Boolean + + fun uploadFile(localFileToUpload: File, fileName: String, parentId: String?): OmhFile? } diff --git a/storage-api/src/main/java/com/omh/android/storage/api/domain/repository/OmhFileRepository.kt b/storage-api/src/main/java/com/omh/android/storage/api/domain/repository/OmhFileRepository.kt index b345d35c..48b52485 100644 --- a/storage-api/src/main/java/com/omh/android/storage/api/domain/repository/OmhFileRepository.kt +++ b/storage-api/src/main/java/com/omh/android/storage/api/domain/repository/OmhFileRepository.kt @@ -1,6 +1,7 @@ package com.omh.android.storage.api.domain.repository import com.omh.android.storage.api.domain.model.OmhFile +import java.io.File interface OmhFileRepository { @@ -10,11 +11,11 @@ interface OmhFileRepository { fun deleteFile(fileId: String): Boolean + fun uploadFile(localFileToUpload: File, fileName: String, parentId: String?): OmhFile? + fun open() fun update() - fun upload() - fun download() } diff --git a/storage-api/src/main/java/com/omh/android/storage/api/domain/usecase/UploadFileUseCase.kt b/storage-api/src/main/java/com/omh/android/storage/api/domain/usecase/UploadFileUseCase.kt new file mode 100644 index 00000000..eb58cfbb --- /dev/null +++ b/storage-api/src/main/java/com/omh/android/storage/api/domain/usecase/UploadFileUseCase.kt @@ -0,0 +1,23 @@ +package com.omh.android.storage.api.domain.usecase + +import com.omh.android.storage.api.domain.model.OmhFile +import com.omh.android.storage.api.domain.repository.OmhFileRepository +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import java.io.File + +class UploadFileUseCase( + private val repository: OmhFileRepository, + dispatcher: CoroutineDispatcher = Dispatchers.Default +) : OmhSuspendUseCase(dispatcher) { + + override suspend fun execute(parameters: UploadFileUseCaseParams): UploadFileUseCaseResult { + return UploadFileUseCaseResult( + repository.uploadFile(parameters.localFileToUpload, parameters.fileName, parameters.parentId) + ) + } +} + +data class UploadFileUseCaseParams(val localFileToUpload: File, val fileName: String, val parentId: String?) + +data class UploadFileUseCaseResult(val file: OmhFile?)