Skip to content

Commit

Permalink
fix(PnX-SI/gn_mobile_occtax#133): synchronize taxa list IDs...
Browse files Browse the repository at this point in the history
  • Loading branch information
sgrimault committed May 12, 2024
1 parent 6735958 commit 351dc0f
Show file tree
Hide file tree
Showing 13 changed files with 587 additions and 318 deletions.
2 changes: 1 addition & 1 deletion datasync/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ plugins {
id 'org.jetbrains.kotlin.android'
}

version = "0.5.3"
version = "0.6.0"

android {
namespace 'fr.geonature.datasync'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import fr.geonature.datasync.api.model.DatasetQuery
import fr.geonature.datasync.api.model.Media
import fr.geonature.datasync.api.model.NomenclatureType
import fr.geonature.datasync.api.model.TaxrefArea
import fr.geonature.datasync.api.model.TaxrefListListResult
import fr.geonature.datasync.api.model.TaxrefListResult
import fr.geonature.datasync.api.model.TaxrefVersion
import fr.geonature.datasync.api.model.User
Expand Down Expand Up @@ -139,6 +140,13 @@ class GeoNatureAPIClientImpl(private val cookieManager: ICookieManager) : IGeoNa
return geoNatureService.getUsers(menuId)
}

override fun getTaxrefList(): Call<TaxrefListListResult> {
val taxHubService = taxHubService
?: throw MissingConfigurationException.MissingTaxHubBaseURLException

return taxHubService.getTaxrefList()
}

override fun getTaxonomyRanks(): Call<ResponseBody> {
val taxHubService = taxHubService
?: throw MissingConfigurationException.MissingTaxHubBaseURLException
Expand All @@ -148,14 +156,16 @@ class GeoNatureAPIClientImpl(private val cookieManager: ICookieManager) : IGeoNa

override fun getTaxref(
limit: Int?,
page: Int?
page: Int?,
list: List<Long>?
): Call<TaxrefListResult> {
val taxHubService = taxHubService
?: throw MissingConfigurationException.MissingTaxHubBaseURLException

return taxHubService.getTaxref(
limit,
page
page,
list?.joinToString(",")
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import fr.geonature.datasync.api.model.DatasetQuery
import fr.geonature.datasync.api.model.Media
import fr.geonature.datasync.api.model.NomenclatureType
import fr.geonature.datasync.api.model.TaxrefArea
import fr.geonature.datasync.api.model.TaxrefListListResult
import fr.geonature.datasync.api.model.TaxrefListResult
import fr.geonature.datasync.api.model.TaxrefVersion
import fr.geonature.datasync.api.model.User
Expand Down Expand Up @@ -96,11 +97,14 @@ interface IGeoNatureAPIClient {

fun getUsers(menuId: Int): Call<List<User>>

fun getTaxrefList(): Call<TaxrefListListResult>

fun getTaxonomyRanks(): Call<ResponseBody>

fun getTaxref(
limit: Int? = null,
page: Int? = null,
list: List<Long>? = null
): Call<TaxrefListResult>

fun getTaxrefAreas(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package fr.geonature.datasync.api

import fr.geonature.datasync.api.model.TaxrefListListResult
import fr.geonature.datasync.api.model.TaxrefListResult
import fr.geonature.datasync.api.model.TaxrefVersion
import okhttp3.ResponseBody
Expand All @@ -15,6 +16,10 @@ import retrofit2.http.Query
*/
interface ITaxHubService {

@Headers("Accept: application/json")
@GET("api/biblistes")
fun getTaxrefList(): Call<TaxrefListListResult>

@Headers("Accept: application/json")
@GET("api/taxref/regnewithgroupe2")
fun getTaxonomyRanks(): Call<ResponseBody>
Expand All @@ -23,7 +28,8 @@ interface ITaxHubService {
@GET("api/taxref")
fun getTaxref(
@Query("limit") limit: Int? = null,
@Query("page") page: Int? = null
@Query("page") page: Int? = null,
@Query("id_liste") list: String? = null
): Call<TaxrefListResult>

@Headers("Accept: application/json")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package fr.geonature.datasync.api.model

import com.google.gson.annotations.SerializedName

/**
* _GeoNature_ taxa list result.
*
* @author S. Grimault
*/
data class TaxrefListListResult(
val data: List<TaxrefList>,
val count: Int
)


/**
* _GeoNature_ taxa list definition.
*
* @author S. Grimault
*/
data class TaxrefList(
@SerializedName("id_liste") val id: Long,
@SerializedName("code_liste") val code: String,
@SerializedName("nom_liste") val name: String,
@SerializedName("desc_liste") val description: String
)
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,16 @@ import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import fr.geonature.commons.data.GeoNatureModuleName
import fr.geonature.commons.data.LocalDatabase
import fr.geonature.commons.data.dao.AppSyncDao
import fr.geonature.commons.features.nomenclature.data.IAdditionalFieldLocalDataSource
import fr.geonature.datasync.api.IGeoNatureAPIClient
import fr.geonature.datasync.sync.repository.ISynchronizeLocalDataRepository
import fr.geonature.datasync.sync.repository.ISynchronizeAdditionalFieldsRepository
import fr.geonature.datasync.sync.repository.ISynchronizeTaxaRepository
import fr.geonature.datasync.sync.repository.SynchronizeAdditionalFieldsRepositoryImpl
import javax.inject.Qualifier
import fr.geonature.datasync.sync.repository.SynchronizeTaxaRepositoryImpl
import javax.inject.Singleton

@Qualifier
annotation class SynchronizeAdditionalFieldsRepository

/**
* Data synchronization module.
*
Expand All @@ -29,13 +28,26 @@ object DataSyncModule {

@Singleton
@Provides
@SynchronizeAdditionalFieldsRepository
fun provideSynchronizeTaxaRepository(
@ApplicationContext appContext: Context,
geoNatureAPIClient: IGeoNatureAPIClient,
database: LocalDatabase
): ISynchronizeTaxaRepository {
return SynchronizeTaxaRepositoryImpl(
appContext,
geoNatureAPIClient,
database
)
}

@Singleton
@Provides
fun provideSynchronizeAdditionalFieldsRepository(
@ApplicationContext appContext: Context,
@GeoNatureModuleName moduleName: String,
additionalFieldLocalDataSource: IAdditionalFieldLocalDataSource,
geoNatureAPIClient: IGeoNatureAPIClient,
): ISynchronizeLocalDataRepository {
): ISynchronizeAdditionalFieldsRepository {
return SynchronizeAdditionalFieldsRepositoryImpl(
appContext,
moduleName,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package fr.geonature.datasync.sync

import android.content.Context
import androidx.work.WorkInfo
import fr.geonature.datasync.R
import fr.geonature.datasync.api.error.BaseApiException

/**
* Describes a data synchronization status message.
Expand All @@ -11,4 +14,42 @@ data class DataSyncStatus(
val state: WorkInfo.State,
val syncMessage: String? = null,
val serverStatus: ServerStatus = ServerStatus.OK
)
) {
companion object {

/**
* Gets a [DataSyncStatus] from given [Exception].
*/
fun fromException(
throwable: Throwable,
context: Context,
errorMessage: String? = null
): DataSyncStatus {
return when (throwable) {
is BaseApiException.UnauthorizedException -> {
DataSyncStatus(
state = WorkInfo.State.FAILED,
syncMessage = context.getString(R.string.sync_error_server_not_connected),
serverStatus = ServerStatus.UNAUTHORIZED
)
}

is BaseApiException.InternalServerException -> {
DataSyncStatus(
state = WorkInfo.State.FAILED,
syncMessage = context.getString(R.string.sync_error_server_error),
serverStatus = ServerStatus.INTERNAL_SERVER_ERROR
)
}

else -> {
DataSyncStatus(
state = WorkInfo.State.FAILED,
syncMessage = errorMessage
)
}
}
}
}
}

Original file line number Diff line number Diff line change
@@ -1,14 +1,36 @@
package fr.geonature.datasync.sync.repository

import androidx.work.WorkInfo
import fr.geonature.datasync.sync.DataSyncStatus
import kotlinx.coroutines.channels.SendChannel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow

/**
* Synchronize local data.
*
* @author S. Grimault
*/
interface ISynchronizeLocalDataRepository {
interface ISynchronizeLocalDataRepository<in Params> {

suspend operator fun invoke(): Flow<DataSyncStatus>
suspend operator fun invoke(params: Params): Flow<DataSyncStatus>

companion object {

/**
* Sends synchronization status to the given channel and throw exception if its current status
* is [WorkInfo.State.FAILED] to cancel the current transaction.
*/
suspend fun sendOrThrow(
channel: SendChannel<DataSyncStatus>,
dataSyncStatus: DataSyncStatus
) {
channel.send(dataSyncStatus)

if (dataSyncStatus.state == WorkInfo.State.FAILED) {
delay(500)
throw java.lang.Exception(dataSyncStatus.syncMessage)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,62 +14,71 @@ import retrofit2.await
import java.io.BufferedReader

/**
* Implementation of [ISynchronizeLocalDataRepository] to synchronize additional fields.
* Default repository interface to synchronize additional fields.
*/
interface ISynchronizeAdditionalFieldsRepository : ISynchronizeLocalDataRepository<Unit>

/**
* Implementation of [ISynchronizeAdditionalFieldsRepository] to synchronize additional fields.
*
* @author S. Grimault
*/
class SynchronizeAdditionalFieldsRepositoryImpl(
private val context: Context,
private val moduleName: String,
private val additionalFieldLocalDataSource: IAdditionalFieldLocalDataSource,
private val geoNatureAPIClient: IGeoNatureAPIClient,
) : ISynchronizeLocalDataRepository {
private val geoNatureAPIClient: IGeoNatureAPIClient
) : ISynchronizeAdditionalFieldsRepository {

override suspend fun invoke(): Flow<DataSyncStatus> = flow {
Logger.info { "synchronize additional fields..." }
override suspend fun invoke(params: Unit): Flow<DataSyncStatus> =
flow {
Logger.info { "synchronize additional fields..." }

val additionalFieldJsonReader = AdditionalFieldJsonReader()
val additionalFieldJsonReader = AdditionalFieldJsonReader()

val additionalFields = runCatching {
geoNatureAPIClient.getAdditionalFields(moduleName.uppercase())
.await()
.let {
additionalFieldJsonReader.read(
it.byteStream()
.bufferedReader()
.use(BufferedReader::readText)
)
}
}.onFailure { Logger.warn { it.message } }
.getOrDefault(emptyList())
val additionalFields = runCatching {
geoNatureAPIClient
.getAdditionalFields(moduleName.uppercase())
.await()
.let {
additionalFieldJsonReader.read(
it
.byteStream()
.bufferedReader()
.use(BufferedReader::readText)
)
}
}
.onFailure { Logger.warn { it.message } }
.getOrDefault(emptyList())

if (additionalFields.isEmpty()) {
emit(DataSyncStatus(state = WorkInfo.State.SUCCEEDED))
if (additionalFields.isEmpty()) {
emit(DataSyncStatus(state = WorkInfo.State.SUCCEEDED))

return@flow
}
return@flow
}

Logger.info { "${additionalFields.size} additional field(s) found" }

Logger.info { "${additionalFields.size} additional field(s) found" }
runCatching {
additionalFieldLocalDataSource.updateAdditionalFields(*additionalFields.toTypedArray())
}.onFailure {
emit(
DataSyncStatus(
state = WorkInfo.State.FAILED,
syncMessage = context.getString(R.string.sync_data_additional_fields_error)
)
)
}

runCatching {
additionalFieldLocalDataSource.updateAdditionalFields(*additionalFields.toTypedArray())
}.onFailure {
emit(
DataSyncStatus(
state = WorkInfo.State.FAILED,
syncMessage = context.getString(R.string.sync_data_additional_fields_error)
state = WorkInfo.State.SUCCEEDED,
syncMessage = context.getString(
R.string.sync_data_additional_fields,
additionalFields.size
)
)
)
}

emit(
DataSyncStatus(
state = WorkInfo.State.SUCCEEDED,
syncMessage = context.getString(
R.string.sync_data_additional_fields,
additionalFields.size
)
)
)
}
}
Loading

0 comments on commit 351dc0f

Please sign in to comment.