Extract server info repo
This commit is contained in:
@@ -1,8 +1,15 @@
|
||||
package gq.kirmanak.mealient.data.auth
|
||||
|
||||
import gq.kirmanak.mealient.data.baseurl.ServerVersion
|
||||
|
||||
interface AuthDataSource {
|
||||
/**
|
||||
* Tries to acquire authentication token using the provided credentials
|
||||
*/
|
||||
suspend fun authenticate(username: String, password: String, baseUrl: String): String
|
||||
suspend fun authenticate(
|
||||
username: String,
|
||||
password: String,
|
||||
baseUrl: String,
|
||||
serverVersion: ServerVersion,
|
||||
): String
|
||||
}
|
||||
@@ -1,15 +1,25 @@
|
||||
package gq.kirmanak.mealient.data.auth.impl
|
||||
|
||||
import gq.kirmanak.mealient.data.auth.AuthDataSource
|
||||
import gq.kirmanak.mealient.data.baseurl.ServerVersion
|
||||
import gq.kirmanak.mealient.datasource.v0.MealieDataSourceV0
|
||||
import gq.kirmanak.mealient.datasource.v1.MealieDataSourceV1
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class AuthDataSourceImpl @Inject constructor(
|
||||
private val v0Source: MealieDataSourceV0,
|
||||
private val v1Source: MealieDataSourceV1,
|
||||
) : AuthDataSource {
|
||||
|
||||
override suspend fun authenticate(username: String, password: String, baseUrl: String): String =
|
||||
v0Source.authenticate(baseUrl, username, password)
|
||||
override suspend fun authenticate(
|
||||
username: String,
|
||||
password: String,
|
||||
baseUrl: String,
|
||||
serverVersion: ServerVersion,
|
||||
): String = when (serverVersion) {
|
||||
ServerVersion.V0 -> v0Source.authenticate(baseUrl, username, password)
|
||||
ServerVersion.V1 -> v1Source.authenticate(baseUrl, username, password)
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ package gq.kirmanak.mealient.data.auth.impl
|
||||
import gq.kirmanak.mealient.data.auth.AuthDataSource
|
||||
import gq.kirmanak.mealient.data.auth.AuthRepo
|
||||
import gq.kirmanak.mealient.data.auth.AuthStorage
|
||||
import gq.kirmanak.mealient.data.baseurl.BaseURLStorage
|
||||
import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo
|
||||
import gq.kirmanak.mealient.extensions.runCatchingExceptCancel
|
||||
import gq.kirmanak.mealient.logging.Logger
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@@ -15,7 +15,7 @@ import javax.inject.Singleton
|
||||
class AuthRepoImpl @Inject constructor(
|
||||
private val authStorage: AuthStorage,
|
||||
private val authDataSource: AuthDataSource,
|
||||
private val baseURLStorage: BaseURLStorage,
|
||||
private val serverInfoRepo: ServerInfoRepo,
|
||||
private val logger: Logger,
|
||||
) : AuthRepo {
|
||||
|
||||
@@ -24,7 +24,9 @@ class AuthRepoImpl @Inject constructor(
|
||||
|
||||
override suspend fun authenticate(email: String, password: String) {
|
||||
logger.v { "authenticate() called with: email = $email, password = $password" }
|
||||
authDataSource.authenticate(email, password, baseURLStorage.requireBaseURL())
|
||||
val version = serverInfoRepo.getVersion()
|
||||
val url = serverInfoRepo.requireUrl()
|
||||
authDataSource.authenticate(email, password, url, version)
|
||||
.let { AUTH_HEADER_FORMAT.format(it) }
|
||||
.let { authStorage.setAuthHeader(it) }
|
||||
authStorage.setEmail(email)
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package gq.kirmanak.mealient.data.baseurl
|
||||
|
||||
interface ServerInfoRepo {
|
||||
|
||||
suspend fun getUrl(): String?
|
||||
|
||||
suspend fun requireUrl(): String
|
||||
|
||||
suspend fun getVersion(): ServerVersion
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package gq.kirmanak.mealient.data.baseurl
|
||||
|
||||
import gq.kirmanak.mealient.datasource.NetworkError
|
||||
import gq.kirmanak.mealient.logging.Logger
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class ServerInfoRepoImpl @Inject constructor(
|
||||
private val serverInfoStorage: ServerInfoStorage,
|
||||
private val versionDataSource: VersionDataSource,
|
||||
private val logger: Logger,
|
||||
) : ServerInfoRepo {
|
||||
|
||||
override suspend fun getUrl(): String? {
|
||||
val result = serverInfoStorage.getBaseURL()
|
||||
logger.v { "getUrl() returned: $result" }
|
||||
return result
|
||||
}
|
||||
|
||||
override suspend fun requireUrl(): String {
|
||||
val result = checkNotNull(getUrl()) { "Server URL was null when it was required" }
|
||||
logger.v { "requireUrl() returned: $result" }
|
||||
return result
|
||||
}
|
||||
|
||||
override suspend fun getVersion(): ServerVersion {
|
||||
var version = serverInfoStorage.getServerVersion()
|
||||
val serverVersion = if (version == null) {
|
||||
logger.d { "getVersion: version is null, requesting" }
|
||||
version = versionDataSource.getVersionInfo(requireUrl()).version
|
||||
val result = determineServerVersion(version)
|
||||
serverInfoStorage.storeServerVersion(version)
|
||||
result
|
||||
} else {
|
||||
determineServerVersion(version)
|
||||
}
|
||||
logger.v { "getVersion() returned: $serverVersion from $version" }
|
||||
return serverVersion
|
||||
}
|
||||
|
||||
private fun determineServerVersion(version: String): ServerVersion = when {
|
||||
version.startsWith("v0") -> ServerVersion.V0
|
||||
version.startsWith("v1") -> ServerVersion.V1
|
||||
else -> throw NetworkError.NotMealie(IllegalStateException("Server version is unknown: $version"))
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,9 @@
|
||||
package gq.kirmanak.mealient.data.baseurl
|
||||
|
||||
interface BaseURLStorage {
|
||||
interface ServerInfoStorage {
|
||||
|
||||
suspend fun getBaseURL(): String?
|
||||
|
||||
suspend fun requireBaseURL(): String
|
||||
|
||||
suspend fun storeBaseURL(baseURL: String, version: String)
|
||||
|
||||
suspend fun storeServerVersion(version: String)
|
||||
@@ -0,0 +1,3 @@
|
||||
package gq.kirmanak.mealient.data.baseurl
|
||||
|
||||
enum class ServerVersion { V0, V1 }
|
||||
@@ -0,0 +1,37 @@
|
||||
package gq.kirmanak.mealient.data.baseurl
|
||||
|
||||
import gq.kirmanak.mealient.datasource.v0.MealieDataSourceV0
|
||||
import gq.kirmanak.mealient.datasource.v1.MealieDataSourceV1
|
||||
import gq.kirmanak.mealient.extensions.runCatchingExceptCancel
|
||||
import gq.kirmanak.mealient.extensions.toVersionInfo
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class VersionDataSourceImpl @Inject constructor(
|
||||
private val v0Source: MealieDataSourceV0,
|
||||
private val v1Source: MealieDataSourceV1,
|
||||
) : VersionDataSource {
|
||||
|
||||
override suspend fun getVersionInfo(baseUrl: String): VersionInfo {
|
||||
val responses = coroutineScope {
|
||||
val v0Deferred = async {
|
||||
runCatchingExceptCancel { v0Source.getVersionInfo(baseUrl).toVersionInfo() }
|
||||
}
|
||||
val v1Deferred = async {
|
||||
runCatchingExceptCancel { v1Source.getVersionInfo(baseUrl).toVersionInfo() }
|
||||
}
|
||||
listOf(v0Deferred, v1Deferred).awaitAll()
|
||||
}
|
||||
val firstSuccess = responses.firstNotNullOfOrNull { it.getOrNull() }
|
||||
if (firstSuccess == null) {
|
||||
throw responses.firstNotNullOf { it.exceptionOrNull() }
|
||||
} else {
|
||||
return firstSuccess
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,15 +1,15 @@
|
||||
package gq.kirmanak.mealient.data.baseurl.impl
|
||||
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import gq.kirmanak.mealient.data.baseurl.BaseURLStorage
|
||||
import gq.kirmanak.mealient.data.baseurl.ServerInfoStorage
|
||||
import gq.kirmanak.mealient.data.storage.PreferencesStorage
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class BaseURLStorageImpl @Inject constructor(
|
||||
class ServerInfoStorageImpl @Inject constructor(
|
||||
private val preferencesStorage: PreferencesStorage,
|
||||
) : BaseURLStorage {
|
||||
) : ServerInfoStorage {
|
||||
|
||||
private val baseUrlKey: Preferences.Key<String>
|
||||
get() = preferencesStorage.baseUrlKey
|
||||
@@ -19,10 +19,6 @@ class BaseURLStorageImpl @Inject constructor(
|
||||
|
||||
override suspend fun getBaseURL(): String? = getValue(baseUrlKey)
|
||||
|
||||
override suspend fun requireBaseURL(): String = checkNotNull(getBaseURL()) {
|
||||
"Base URL was null when it was required"
|
||||
}
|
||||
|
||||
override suspend fun storeBaseURL(baseURL: String, version: String) {
|
||||
preferencesStorage.storeValues(
|
||||
Pair(baseUrlKey, baseURL),
|
||||
@@ -2,9 +2,8 @@ package gq.kirmanak.mealient.data.network
|
||||
|
||||
import gq.kirmanak.mealient.data.add.AddRecipeDataSource
|
||||
import gq.kirmanak.mealient.data.auth.AuthRepo
|
||||
import gq.kirmanak.mealient.data.baseurl.BaseURLStorage
|
||||
import gq.kirmanak.mealient.data.baseurl.VersionDataSource
|
||||
import gq.kirmanak.mealient.data.baseurl.VersionInfo
|
||||
import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo
|
||||
import gq.kirmanak.mealient.data.baseurl.ServerVersion
|
||||
import gq.kirmanak.mealient.data.recipes.network.FullRecipeInfo
|
||||
import gq.kirmanak.mealient.data.recipes.network.RecipeDataSource
|
||||
import gq.kirmanak.mealient.data.recipes.network.RecipeSummaryInfo
|
||||
@@ -12,76 +11,52 @@ import gq.kirmanak.mealient.datasource.NetworkError
|
||||
import gq.kirmanak.mealient.datasource.v0.MealieDataSourceV0
|
||||
import gq.kirmanak.mealient.datasource.v0.models.AddRecipeRequestV0
|
||||
import gq.kirmanak.mealient.datasource.v1.MealieDataSourceV1
|
||||
import gq.kirmanak.mealient.extensions.runCatchingExceptCancel
|
||||
import gq.kirmanak.mealient.extensions.toFullRecipeInfo
|
||||
import gq.kirmanak.mealient.extensions.toRecipeSummaryInfo
|
||||
import gq.kirmanak.mealient.extensions.toVersionInfo
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class MealieDataSourceWrapper @Inject constructor(
|
||||
private val baseURLStorage: BaseURLStorage,
|
||||
private val serverInfoRepo: ServerInfoRepo,
|
||||
private val authRepo: AuthRepo,
|
||||
private val v0source: MealieDataSourceV0,
|
||||
private val v1Source: MealieDataSourceV1,
|
||||
) : AddRecipeDataSource, RecipeDataSource, VersionDataSource {
|
||||
) : AddRecipeDataSource, RecipeDataSource {
|
||||
|
||||
override suspend fun addRecipe(recipe: AddRecipeRequestV0): String =
|
||||
withAuthHeader { token -> v0source.addRecipe(getUrl(), token, recipe) }
|
||||
|
||||
override suspend fun getVersionInfo(baseUrl: String): VersionInfo {
|
||||
val responses = coroutineScope {
|
||||
val v0Deferred = async {
|
||||
runCatchingExceptCancel { v0source.getVersionInfo(baseUrl).toVersionInfo() }
|
||||
}
|
||||
val v1Deferred = async {
|
||||
runCatchingExceptCancel { v1Source.getVersionInfo(baseUrl).toVersionInfo() }
|
||||
}
|
||||
listOf(v0Deferred, v1Deferred).awaitAll()
|
||||
}
|
||||
val firstSuccess = responses.firstNotNullOfOrNull { it.getOrNull() }
|
||||
if (firstSuccess == null) {
|
||||
throw responses.firstNotNullOf { it.exceptionOrNull() }
|
||||
} else {
|
||||
return firstSuccess
|
||||
}
|
||||
override suspend fun addRecipe(recipe: AddRecipeRequestV0): String = withAuthHeader { token ->
|
||||
v0source.addRecipe(getUrl(), token, recipe)
|
||||
}
|
||||
|
||||
override suspend fun requestRecipes(start: Int, limit: Int): List<RecipeSummaryInfo> =
|
||||
withAuthHeader { token ->
|
||||
val url = getUrl()
|
||||
if (isV1()) {
|
||||
v1Source.requestRecipes(url, token, start, limit).map { it.toRecipeSummaryInfo() }
|
||||
} else {
|
||||
override suspend fun requestRecipes(
|
||||
start: Int,
|
||||
limit: Int
|
||||
): List<RecipeSummaryInfo> = withAuthHeader { token ->
|
||||
val url = getUrl()
|
||||
when (getVersion()) {
|
||||
ServerVersion.V0 -> {
|
||||
v0source.requestRecipes(url, token, start, limit).map { it.toRecipeSummaryInfo() }
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun requestRecipeInfo(slug: String): FullRecipeInfo =
|
||||
withAuthHeader { token ->
|
||||
val url = getUrl()
|
||||
if (isV1()) {
|
||||
v1Source.requestRecipeInfo(url, token, slug).toFullRecipeInfo()
|
||||
} else {
|
||||
v0source.requestRecipeInfo(url, token, slug).toFullRecipeInfo()
|
||||
ServerVersion.V1 -> {
|
||||
// Imagine start is 30 and limit is 15. It means that we already have page 1 and 2, now we need page 3
|
||||
val page = start / limit + 1
|
||||
v1Source.requestRecipes(url, token, page, limit).map { it.toRecipeSummaryInfo() }
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getUrl() = baseURLStorage.requireBaseURL()
|
||||
|
||||
private suspend fun isV1(): Boolean {
|
||||
var version = baseURLStorage.getServerVersion()
|
||||
if (version == null) {
|
||||
version = getVersionInfo(getUrl()).version
|
||||
baseURLStorage.storeServerVersion(version)
|
||||
}
|
||||
return version.startsWith("v1")
|
||||
}
|
||||
|
||||
override suspend fun requestRecipeInfo(slug: String): FullRecipeInfo = withAuthHeader { token ->
|
||||
val url = getUrl()
|
||||
when (getVersion()) {
|
||||
ServerVersion.V0 -> v0source.requestRecipeInfo(url, token, slug).toFullRecipeInfo()
|
||||
ServerVersion.V1 -> v1Source.requestRecipeInfo(url, token, slug).toFullRecipeInfo()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getUrl() = serverInfoRepo.requireUrl()
|
||||
|
||||
private suspend fun getVersion() = serverInfoRepo.getVersion()
|
||||
|
||||
private suspend inline fun <T> withAuthHeader(block: (String?) -> T): T =
|
||||
runCatching { block(authRepo.getAuthHeader()) }.getOrElse {
|
||||
if (it is NetworkError.Unauthorized) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package gq.kirmanak.mealient.data.recipes.impl
|
||||
|
||||
import gq.kirmanak.mealient.data.baseurl.BaseURLStorage
|
||||
import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo
|
||||
import gq.kirmanak.mealient.logging.Logger
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import javax.inject.Inject
|
||||
@@ -8,7 +8,7 @@ import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class RecipeImageUrlProviderImpl @Inject constructor(
|
||||
private val baseURLStorage: BaseURLStorage,
|
||||
private val serverInfoRepo: ServerInfoRepo,
|
||||
private val logger: Logger,
|
||||
) : RecipeImageUrlProvider {
|
||||
|
||||
@@ -16,7 +16,7 @@ class RecipeImageUrlProviderImpl @Inject constructor(
|
||||
logger.v { "generateImageUrl() called with: slug = $slug" }
|
||||
slug?.takeUnless { it.isBlank() } ?: return null
|
||||
val imagePath = IMAGE_PATH_FORMAT.format(slug)
|
||||
val baseUrl = baseURLStorage.getBaseURL()?.takeUnless { it.isEmpty() }
|
||||
val baseUrl = serverInfoRepo.getUrl()?.takeUnless { it.isEmpty() }
|
||||
val result = baseUrl?.toHttpUrlOrNull()
|
||||
?.newBuilder()
|
||||
?.addPathSegments(imagePath)
|
||||
|
||||
@@ -4,10 +4,8 @@ import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import gq.kirmanak.mealient.data.baseurl.BaseURLStorage
|
||||
import gq.kirmanak.mealient.data.baseurl.VersionDataSource
|
||||
import gq.kirmanak.mealient.data.baseurl.impl.BaseURLStorageImpl
|
||||
import gq.kirmanak.mealient.data.network.MealieDataSourceWrapper
|
||||
import gq.kirmanak.mealient.data.baseurl.*
|
||||
import gq.kirmanak.mealient.data.baseurl.impl.ServerInfoStorageImpl
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@@ -16,9 +14,13 @@ interface BaseURLModule {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindVersionDataSource(mealieDataSourceWrapper: MealieDataSourceWrapper): VersionDataSource
|
||||
fun bindVersionDataSource(versionDataSourceImpl: VersionDataSourceImpl): VersionDataSource
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindBaseUrlStorage(baseURLStorageImpl: BaseURLStorageImpl): BaseURLStorage
|
||||
fun bindBaseUrlStorage(baseURLStorageImpl: ServerInfoStorageImpl): ServerInfoStorage
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindServerInfoRepo(serverInfoRepoImpl: ServerInfoRepoImpl): ServerInfoRepo
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import gq.kirmanak.mealient.data.baseurl.BaseURLStorage
|
||||
import gq.kirmanak.mealient.data.baseurl.ServerInfoStorage
|
||||
import gq.kirmanak.mealient.data.baseurl.VersionDataSource
|
||||
import gq.kirmanak.mealient.extensions.runCatchingExceptCancel
|
||||
import gq.kirmanak.mealient.logging.Logger
|
||||
@@ -15,7 +15,7 @@ import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class BaseURLViewModel @Inject constructor(
|
||||
private val baseURLStorage: BaseURLStorage,
|
||||
private val serverInfoStorage: ServerInfoStorage,
|
||||
private val versionDataSource: VersionDataSource,
|
||||
private val logger: Logger,
|
||||
) : ViewModel() {
|
||||
@@ -36,7 +36,7 @@ class BaseURLViewModel @Inject constructor(
|
||||
val result = runCatchingExceptCancel {
|
||||
// If it returns proper version info then it must be a Mealie
|
||||
val version = versionDataSource.getVersionInfo(baseURL).version
|
||||
baseURLStorage.storeBaseURL(baseURL, version)
|
||||
serverInfoStorage.storeBaseURL(baseURL, version)
|
||||
}
|
||||
logger.i { "checkBaseURL: result is $result" }
|
||||
_uiState.value = OperationUiState.fromResult(result)
|
||||
|
||||
@@ -6,7 +6,7 @@ import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.navigation.NavDirections
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import gq.kirmanak.mealient.data.baseurl.BaseURLStorage
|
||||
import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo
|
||||
import gq.kirmanak.mealient.data.disclaimer.DisclaimerStorage
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -15,7 +15,7 @@ import javax.inject.Inject
|
||||
@HiltViewModel
|
||||
class SplashViewModel @Inject constructor(
|
||||
private val disclaimerStorage: DisclaimerStorage,
|
||||
private val baseURLStorage: BaseURLStorage,
|
||||
private val serverInfoRepo: ServerInfoRepo,
|
||||
) : ViewModel() {
|
||||
private val _nextDestination = MutableLiveData<NavDirections>()
|
||||
val nextDestination: LiveData<NavDirections> = _nextDestination
|
||||
@@ -25,7 +25,7 @@ class SplashViewModel @Inject constructor(
|
||||
delay(1000)
|
||||
_nextDestination.value = when {
|
||||
!disclaimerStorage.isDisclaimerAccepted() -> SplashFragmentDirections.actionSplashFragmentToDisclaimerFragment()
|
||||
baseURLStorage.getBaseURL() == null -> SplashFragmentDirections.actionSplashFragmentToBaseURLFragment()
|
||||
serverInfoRepo.getUrl() == null -> SplashFragmentDirections.actionSplashFragmentToBaseURLFragment()
|
||||
else -> SplashFragmentDirections.actionSplashFragmentToRecipesFragment()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user