Skip to content

Commit

Permalink
feat(#133): synchronize observation records through dedicated worker
Browse files Browse the repository at this point in the history
  • Loading branch information
sgrimault committed Feb 27, 2023
1 parent bcfc93f commit bb3626c
Show file tree
Hide file tree
Showing 17 changed files with 1,626 additions and 194 deletions.
1 change: 1 addition & 0 deletions occtax/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ dependencies {
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation 'androidx.preference:preference-ktx:1.2.0'
implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation 'androidx.work:work-runtime-ktx:2.7.1'
implementation 'com.google.android.material:material:1.5.0'
implementation "io.github.l4digital:fastscroll:2.0.1"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@ import fr.geonature.datasync.auth.IAuthManager
import fr.geonature.datasync.packageinfo.ISynchronizeObservationRecordRepository
import fr.geonature.occtax.api.IOcctaxAPIClient
import fr.geonature.occtax.features.record.data.IMediaRecordLocalDataSource
import fr.geonature.occtax.features.record.data.IMediaRecordRemoteDataSource
import fr.geonature.occtax.features.record.data.IObservationRecordLocalDataSource
import fr.geonature.occtax.features.record.data.IObservationRecordRemoteDataSource
import fr.geonature.occtax.features.record.data.MediaRecordLocalDataSourceImpl
import fr.geonature.occtax.features.record.data.MediaRecordRemoteDataSourceImpl
import fr.geonature.occtax.features.record.data.ObservationRecordFileDataSourceImpl
import fr.geonature.occtax.features.record.data.ObservationRecordLocalDataSourceImpl
import fr.geonature.occtax.features.record.data.ObservationRecordRemoteDataSourceImpl
import fr.geonature.occtax.features.record.repository.IMediaRecordRepository
Expand All @@ -26,8 +29,15 @@ import fr.geonature.occtax.features.record.repository.MediaRecordRepositoryImpl
import fr.geonature.occtax.features.record.repository.ObservationRecordRepositoryImpl
import fr.geonature.occtax.features.record.repository.SynchronizeObservationRecordRepositoryImpl
import fr.geonature.occtax.settings.AppSettings
import javax.inject.Qualifier
import javax.inject.Singleton

@Qualifier
annotation class ObservationRecordLocalDataSource

@Qualifier
annotation class ObservationRecordFileDataSource

/**
* Observation record module.
*
Expand All @@ -39,6 +49,7 @@ object ObservationRecordModule {

@Singleton
@Provides
@ObservationRecordLocalDataSource
fun provideObservationRecordLocalDataSource(
@ApplicationContext appContext: Context,
@GeoNatureModuleName moduleName: String
Expand All @@ -49,6 +60,19 @@ object ObservationRecordModule {
)
}

@Singleton
@Provides
@ObservationRecordFileDataSource
fun provideObservationRecordFileDataSource(
@ApplicationContext appContext: Context,
@GeoNatureModuleName moduleName: String
): IObservationRecordLocalDataSource {
return ObservationRecordFileDataSourceImpl(
appContext,
moduleName
)
}

@Singleton
@Provides
fun provideObservationRecordRemoteDataSource(occtaxAPIClient: IOcctaxAPIClient): IObservationRecordRemoteDataSource {
Expand All @@ -61,22 +85,46 @@ object ObservationRecordModule {
return MediaRecordLocalDataSourceImpl(appContext)
}

@Singleton
@Provides
fun provideMediaRecordRemoteDataSource(
geoNatureAPIClient: IGeoNatureAPIClient,
nomenclatureLocalDataSource: INomenclatureLocalDataSource,
): IMediaRecordRemoteDataSource {
return MediaRecordRemoteDataSourceImpl(
geoNatureAPIClient,
nomenclatureLocalDataSource
)
}

@Singleton
@Provides
fun provideObservationRecordRepository(
observationRecordLocalDataSource: IObservationRecordLocalDataSource,
@ObservationRecordLocalDataSource observationRecordLocalDataSource: IObservationRecordLocalDataSource,
@ObservationRecordFileDataSource observationRecordFileDataSource: IObservationRecordLocalDataSource,
taxonLocalDataSource: ITaxonLocalDataSource
): IObservationRecordRepository {
return ObservationRecordRepositoryImpl(
observationRecordLocalDataSource,
observationRecordFileDataSource,
taxonLocalDataSource
)
}

@Singleton
@Provides
fun provideMediaRecordRepository(mediaRecordLocalDataSource: IMediaRecordLocalDataSource): IMediaRecordRepository {
return MediaRecordRepositoryImpl(mediaRecordLocalDataSource)
fun provideMediaRecordRepository(
@ApplicationContext appContext: Context,
authManager: IAuthManager,
mediaRecordLocalDataSource: IMediaRecordLocalDataSource,
mediaRecordRemoteDataSource: IMediaRecordRemoteDataSource
): IMediaRecordRepository {
return MediaRecordRepositoryImpl(
appContext,
authManager,
mediaRecordLocalDataSource,
mediaRecordRemoteDataSource
)
}

@Singleton
Expand All @@ -87,7 +135,7 @@ object ObservationRecordModule {
authManager: IAuthManager,
appSettingsManager: IAppSettingsManager<AppSettings>,
nomenclatureLocalDataSource: INomenclatureLocalDataSource,
observationRecordLocalDataSource: IObservationRecordLocalDataSource,
@ObservationRecordLocalDataSource observationRecordLocalDataSource: IObservationRecordLocalDataSource,
observationRecordRemoteDataSource: IObservationRecordRemoteDataSource,
mediaRecordLocalDataSource: IMediaRecordLocalDataSource
): ISynchronizeObservationRecordRepository {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package fr.geonature.occtax.features.record.data

import fr.geonature.datasync.api.model.Media
import fr.geonature.occtax.features.record.domain.MediaRecord
import fr.geonature.occtax.features.record.error.MediaRecordException
import java.io.File

/**
* Remote data source about [MediaRecord].
*
* @author S. Grimault
*/
interface IMediaRecordRemoteDataSource {

/**
* Sends given file as [Media].
*
* @throws [MediaRecordException.SynchronizeException] if something goes wrong
*/
suspend fun sendMediaFile(file: File, author: String, title: String, description: String): Media

/**
* Deletes already uploaded [Media] file.
*
* @throws [MediaRecordException.DeleteException] if the given [Media] cannot be deleted
*/
suspend fun deleteMediaFile(media: Media)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package fr.geonature.occtax.features.record.data

import fr.geonature.commons.data.entity.Nomenclature
import fr.geonature.commons.features.nomenclature.data.INomenclatureLocalDataSource
import fr.geonature.commons.features.nomenclature.error.NomenclatureException
import fr.geonature.datasync.api.IGeoNatureAPIClient
import fr.geonature.datasync.api.model.Media
import fr.geonature.occtax.features.record.error.MediaRecordException
import org.tinylog.kotlin.Logger
import retrofit2.await
import retrofit2.awaitResponse
import java.io.File
import java.util.Locale

/**
* Default implementation of [IMediaRecordRemoteDataSource].
*
* @author S. Grimault
*/
class MediaRecordRemoteDataSourceImpl(
private val geoNatureAPIClient: IGeoNatureAPIClient,
private val nomenclatureLocalDataSource: INomenclatureLocalDataSource,
) :
IMediaRecordRemoteDataSource {

override suspend fun sendMediaFile(
file: File,
author: String,
title: String,
description: String
): Media {
val nomenclatureForImage = getNomenclatureForImage()

val idTableLocation =
runCatching {
geoNatureAPIClient.getIdTableLocation()
.await()
}.onFailure {
Logger.warn { "failed to fetch ID table location" }
}
.getOrNull() ?: throw MediaRecordException.SynchronizeException(file)

val isFrenchLocale = Locale.getDefault().isO3Language == Locale.FRENCH.isO3Language

return runCatching {
geoNatureAPIClient.sendMediaFile(
nomenclatureForImage.id,
idTableLocation,
author = author,
titleEn = if (isFrenchLocale) null else title,
titleFr = if (isFrenchLocale) title else null,
descriptionEn = if (isFrenchLocale) null else description,
descriptionFr = if (isFrenchLocale) description else null,
file
)
.await()
}.onSuccess {
Logger.info { "media file '${file.absolutePath}' successfully synchronized" }
}
.onFailure {
Logger.warn(it) { "failed to send media file '${file.absolutePath}'..." }
}
.getOrNull() ?: throw MediaRecordException.SynchronizeException(file)
}

override suspend fun deleteMediaFile(media: Media) {
runCatching {
geoNatureAPIClient.deleteMediaFile(media.id)
.awaitResponse()
}.onFailure {
Logger.warn(it) { "failed to delete media file ${media.id}..." }
}
.getOrNull() ?: throw MediaRecordException.DeleteException(media)
}

private suspend fun getNomenclatureForImage(): Nomenclature {
return nomenclatureLocalDataSource.getNomenclatureValuesByTypeAndTaxonomy("TYPE_MEDIA")
.firstOrNull {
it.defaultLabel.lowercase()
.contains("photo")
}
?: throw NomenclatureException.NoNomenclatureFoundException("no nomenclature found matching media type 'image/*'")
}
}
Loading

0 comments on commit bb3626c

Please sign in to comment.