Compare commits

...

8 Commits

Author SHA1 Message Date
Matthew Witman
30bfeb93c4 changed getCount to non-suspend, used property delegation 2024-03-22 14:33:21 -04:00
Matthew Witman
cf93e02f47 Moved getCount() to flow 2024-03-22 14:20:32 -04:00
Matthew Witman
52db344b21 Tweak for multiline when 2024-03-22 13:27:57 -04:00
Matthew Witman
2f549db3b0 Fixed error introduced in cleanup 2024-03-22 13:25:55 -04:00
Maddie Witman
f32244142d Apply suggestions from code review
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
2024-03-22 13:07:28 -04:00
Matthew Witman
b08aabf136 Detekt cleanup 2024-03-22 13:05:06 -04:00
Matthew Witman
b1de63d35e Removed extra function, reverted small change 2024-03-22 13:03:05 -04:00
Matthew Witman
4ba7c6b82b Cleanup, removed platform specific code, PR comments 2024-03-22 11:29:42 -04:00
12 changed files with 74 additions and 49 deletions

View File

@@ -2,6 +2,8 @@ package eu.kanade.presentation.more.settings.screen
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.fragment.app.FragmentActivity
@@ -12,7 +14,6 @@ import eu.kanade.presentation.more.settings.Preference
import eu.kanade.presentation.more.settings.screen.browse.ExtensionReposScreen
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.authenticate
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.runBlocking
import mihon.domain.extensionrepo.interactor.GetExtensionRepoCount
import tachiyomi.core.common.i18n.stringResource
import tachiyomi.i18n.MR
@@ -35,9 +36,8 @@ object SettingsBrowseScreen : SearchableSettings {
val sourcePreferences = remember { Injekt.get<SourcePreferences>() }
val getExtensionRepoCount = remember { Injekt.get<GetExtensionRepoCount>() }
// Run this blocking. It should be a quick operation and not cause issues
// However future enhancements may add a ViewState
val reposCount = remember { runBlocking { getExtensionRepoCount.await() } }
val reposFlow = remember { getExtensionRepoCount.subscribe() }
val reposCount by reposFlow.collectAsState(0)
return listOf(
Preference.PreferenceGroup(

View File

@@ -1,6 +1,5 @@
package eu.kanade.presentation.more.settings.screen.browse
import android.content.Context
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
@@ -18,7 +17,6 @@ import eu.kanade.tachiyomi.util.system.openInBrowser
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.collections.immutable.toImmutableSet
import kotlinx.coroutines.flow.collectLatest
import mihon.domain.extensionrepo.model.ExtensionRepo
import tachiyomi.presentation.core.screens.LoadingScreen
class ExtensionReposScreen(
@@ -47,7 +45,7 @@ class ExtensionReposScreen(
ExtensionReposScreen(
state = successState,
onClickCreate = { screenModel.showDialog(RepoDialog.Create) },
onClickOpen = { openInBrowser(context, it) },
onClickOpen = { context.openInBrowser(it.website) },
onClickDelete = { screenModel.showDialog(RepoDialog.Delete(it)) },
onClickRefresh = { screenModel.refreshRepos() },
navigateUp = navigator::pop,
@@ -82,16 +80,10 @@ class ExtensionReposScreen(
LaunchedEffect(Unit) {
screenModel.events.collectLatest { event ->
when (event) {
is RepoEvent.LocalizedMessage -> {
context.toast(event.stringRes)
}
if (event is RepoEvent.LocalizedMessage) {
context.toast(event.stringRes)
}
}
}
}
private fun openInBrowser(context: Context, extensionRepo: ExtensionRepo) {
context.openInBrowser(extensionRepo.website)
}
}

View File

@@ -34,7 +34,6 @@ class ExtensionReposScreenModel(
init {
screenModelScope.launchIO {
getExtensionRepo.subscribeAll()
.collectLatest { repos ->
mutableState.update {
@@ -56,8 +55,9 @@ class ExtensionReposScreenModel(
when (val result = createExtensionRepo.await(name)) {
CreateExtensionRepo.Result.InvalidUrl -> _events.send(RepoEvent.InvalidUrl)
CreateExtensionRepo.Result.RepoAlreadyExists -> _events.send(RepoEvent.RepoAlreadyExists)
is CreateExtensionRepo.Result.DuplicateFingerprint ->
is CreateExtensionRepo.Result.DuplicateFingerprint -> {
showDialog(RepoDialog.Conflict(result.oldRepo, result.newRepo))
}
else -> {}
}
}

View File

@@ -1,13 +1,13 @@
package eu.kanade.tachiyomi
import android.content.Context
import android.database.sqlite.SQLiteException
import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.tachiyomi.data.backup.create.BackupCreateJob
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import logcat.LogPriority
import mihon.domain.extensionrepo.exception.SaveExtensionRepoException
import mihon.domain.extensionrepo.repository.ExtensionRepoRepository
import tachiyomi.core.common.preference.Preference
import tachiyomi.core.common.preference.PreferenceStore
@@ -55,8 +55,8 @@ object Migrations {
source,
"NOFINGERPRINT-${index + 1}",
)
} catch (ex: SQLiteException) {
ex.logcat(LogPriority.ERROR) { "Error Migrating Extension Repo with baseUrl: $source" }
} catch (e: SaveExtensionRepoException) {
logcat(LogPriority.ERROR, e) { "Error Migrating Extension Repo with baseUrl: $source" }
}
}
sourcePreferences.extensionRepos().delete()

View File

@@ -1,6 +1,9 @@
package mihon.data.repository
import android.database.sqlite.SQLiteException
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import mihon.domain.extensionrepo.exception.SaveExtensionRepoException
import mihon.domain.extensionrepo.model.ExtensionRepo
import mihon.domain.extensionrepo.repository.ExtensionRepoRepository
import tachiyomi.data.DatabaseHandler
@@ -24,8 +27,8 @@ class ExtensionRepoRepositoryImpl(
return handler.awaitOneOrNull { extension_reposQueries.findOneByFingerprint(fingerprint, ::mapExtensionRepo) }
}
override suspend fun getCount(): Int {
return handler.awaitOne { extension_reposQueries.count() }.toInt()
override fun getCount(): Flow<Int> {
return handler.subscribeToOne { extension_reposQueries.count() }.map { it.toInt() }
}
override suspend fun insertRepository(
@@ -35,7 +38,11 @@ class ExtensionRepoRepositoryImpl(
website: String,
fingerprint: String,
) {
handler.await { extension_reposQueries.insert(baseUrl, name, shortName, website, fingerprint) }
try {
handler.await { extension_reposQueries.insert(baseUrl, name, shortName, website, fingerprint) }
} catch (ex: SQLiteException) {
throw SaveExtensionRepoException(ex)
}
}
override suspend fun upsertRepository(
@@ -45,7 +52,11 @@ class ExtensionRepoRepositoryImpl(
website: String,
fingerprint: String,
) {
handler.await { extension_reposQueries.upsert(baseUrl, name, shortName, website, fingerprint) }
try {
handler.await { extension_reposQueries.upsert(baseUrl, name, shortName, website, fingerprint) }
} catch (ex: SQLiteException) {
throw SaveExtensionRepoException(ex)
}
}
override suspend fun replaceRepository(newRepo: ExtensionRepo) {

View File

@@ -54,5 +54,4 @@ WHERE fingerprint = fingerprint;
delete:
DELETE FROM extension_repos
WHERE
base_url = :base_url;
WHERE base_url = :base_url;

View File

@@ -5,4 +5,4 @@ CREATE TABLE extension_repos (
short_name TEXT,
website TEXT NOT NULL,
fingerprint TEXT UNIQUE NOT NULL
);
);

View File

@@ -0,0 +1,10 @@
package mihon.domain.extensionrepo.exception
import java.io.IOException
/**
* Exception to abstract over SQLiteException and SQLiteConstraintException for multiplatform.
*
* @param throwable the source throwable to include for tracing.
*/
class SaveExtensionRepoException(throwable: Throwable) : IOException("Error Saving Repository to Database", throwable)

View File

@@ -1,9 +1,9 @@
package mihon.domain.extensionrepo.interactor
import android.database.sqlite.SQLiteConstraintException
import eu.kanade.tachiyomi.network.NetworkHelper
import logcat.LogPriority
import mihon.domain.extensionrepo.api.ExtensionRepoApi
import mihon.domain.extensionrepo.exception.SaveExtensionRepoException
import mihon.domain.extensionrepo.model.ExtensionRepo
import mihon.domain.extensionrepo.repository.ExtensionRepoRepository
import okhttp3.OkHttpClient
@@ -32,7 +32,7 @@ class CreateExtensionRepo(
}
private suspend fun insert(repo: ExtensionRepo): Result {
val result = try {
return try {
extensionRepoRepository.insertRepository(
repo.baseUrl,
repo.name,
@@ -41,18 +41,35 @@ class CreateExtensionRepo(
repo.fingerprint,
)
Result.Success
} catch (ex: SQLiteConstraintException) {
} catch (ex: SaveExtensionRepoException) {
logcat(LogPriority.WARN, ex) { "SQL Conflict attempting to add new repository ${repo.baseUrl}" }
// SQLDelight doesn't provide constraint info in exceptions.
// First check if the conflict was on primary key. if so return RepoAlreadyExists
// Then check if the conflict was on fingerprint. if so Return DuplicateFingerprint
extensionRepoRepository.getRepository(baseUrl = repo.baseUrl)
?.let { Result.RepoAlreadyExists }
?: extensionRepoRepository.getRepositoryByFingerprint(fingerprint = repo.fingerprint)
?.let { Result.DuplicateFingerprint(it, repo) }
?: Result.Error
return handleInsertionError(repo)
}
return result
}
@Suppress("ReturnCount")
// Three returns is much cleaner than let + elvis chaining here.
// When matching does not work because we need to capture the result from getRepositoryByFingerprint
/**
* Error Handler for insert when there are trying to create new repositories
*
* SaveExtensionRepoException doesn't provide constraint info in exceptions.
* First check if the conflict was on primary key. if so return RepoAlreadyExists
* Then check if the conflict was on fingerprint. if so Return DuplicateFingerprint
* If neither are found, there was some other Error, and return Result.Error
*
* @param repo Extension Repo holder for passing to DB/Error Dialog
*/
private suspend fun handleInsertionError(repo: ExtensionRepo): Result {
val repoExists = extensionRepoRepository.getRepository(repo.baseUrl)
if (repoExists != null) {
return Result.RepoAlreadyExists
}
val matchingFingerprintRepo = extensionRepoRepository.getRepositoryByFingerprint(repo.fingerprint)
if (matchingFingerprintRepo != null) {
return Result.DuplicateFingerprint(matchingFingerprintRepo, repo)
}
return Result.Error
}
sealed interface Result {

View File

@@ -5,5 +5,5 @@ import mihon.domain.extensionrepo.repository.ExtensionRepoRepository
class GetExtensionRepoCount(
private val extensionRepoRepository: ExtensionRepoRepository,
) {
suspend fun await() = extensionRepoRepository.getCount()
fun subscribe() = extensionRepoRepository.getCount()
}

View File

@@ -12,14 +12,10 @@ class UpdateExtensionRepo(
private val extensionRepoApi = ExtensionRepoApi(networkService.client)
suspend fun await(repo: ExtensionRepo): ExtensionRepo? {
val newRepo = extensionRepoApi.fetchRepoDetails(repo.baseUrl)
newRepo?.let {
if (repo.fingerprint.startsWith("NOFINGERPRINT") || repo.fingerprint == newRepo.fingerprint) {
extensionRepoRepository.upsertRepository(newRepo)
}
return newRepo
suspend fun await(repo: ExtensionRepo) {
val newRepo = extensionRepoApi.fetchRepoDetails(repo.baseUrl) ?: return
if (repo.fingerprint.startsWith("NOFINGERPRINT") || repo.fingerprint == newRepo.fingerprint) {
extensionRepoRepository.upsertRepository(newRepo)
}
return null
}
}

View File

@@ -13,7 +13,7 @@ interface ExtensionRepoRepository {
suspend fun getRepositoryByFingerprint(fingerprint: String): ExtensionRepo?
suspend fun getCount(): Int
fun getCount(): Flow<Int>
suspend fun insertRepository(
baseUrl: String,