Compare commits

...

7 Commits

Author SHA1 Message Date
Matthew Witman
5357b7f4c0 Renamed ExtensionRepoApi.kt to ExtensionRepoService.kt 2024-03-22 18:47:27 -04:00
Matthew Witman
b3053efb8e trying to make single line 2024-03-22 18:27:14 -04:00
Matthew Witman
6d272510e7 onOpenWebsite changes 2024-03-22 18:26:42 -04:00
Matthew Witman
ca260a4bc0 onOpenWebsite changes 2024-03-22 18:25:10 -04:00
Matthew Witman
a5d01f76d4 Big wave of PR comments, renaming/other tweaks 2024-03-22 17:55:02 -04:00
Matthew Witman
5572e9e169 Fixed formatting with updated comment string 2024-03-22 16:25:41 -04:00
Maddie Witman
febed97228 Apply suggestions from code review
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
2024-03-22 16:18:02 -04:00
16 changed files with 90 additions and 80 deletions

View File

@@ -36,8 +36,7 @@ object SettingsBrowseScreen : SearchableSettings {
val sourcePreferences = remember { Injekt.get<SourcePreferences>() }
val getExtensionRepoCount = remember { Injekt.get<GetExtensionRepoCount>() }
val reposFlow = remember { getExtensionRepoCount.subscribe() }
val reposCount by reposFlow.collectAsState(0)
val reposCount by getExtensionRepoCount.subscribe().collectAsState(0)
return listOf(
Preference.PreferenceGroup(

View File

@@ -45,7 +45,7 @@ class ExtensionReposScreen(
ExtensionReposScreen(
state = successState,
onClickCreate = { screenModel.showDialog(RepoDialog.Create) },
onClickOpen = { context.openInBrowser(it.website) },
onOpenWebsite = { context.openInBrowser(it.website) },
onClickDelete = { screenModel.showDialog(RepoDialog.Delete(it)) },
onClickRefresh = { screenModel.refreshRepos() },
navigateUp = navigator::pop,
@@ -57,7 +57,7 @@ class ExtensionReposScreen(
ExtensionRepoCreateDialog(
onDismissRequest = screenModel::dismissDialog,
onCreate = { screenModel.createRepo(it) },
repos = successState.repos.map { it.baseUrl }.toImmutableSet(),
repoUrls = successState.repos.map { it.baseUrl }.toImmutableSet(),
)
}
is RepoDialog.Delete -> {

View File

@@ -48,11 +48,11 @@ class ExtensionReposScreenModel(
/**
* Creates and adds a new repo to the database.
*
* @param name The name of the repo to create.
* @param baseUrl The baseUrl of the repo to create.
*/
fun createRepo(name: String) {
fun createRepo(baseUrl: String) {
screenModelScope.launchIO {
when (val result = createExtensionRepo.await(name)) {
when (val result = createExtensionRepo.await(baseUrl)) {
CreateExtensionRepo.Result.InvalidUrl -> _events.send(RepoEvent.InvalidUrl)
CreateExtensionRepo.Result.RepoAlreadyExists -> _events.send(RepoEvent.RepoAlreadyExists)
is CreateExtensionRepo.Result.DuplicateFingerprint -> {
@@ -64,7 +64,7 @@ class ExtensionReposScreenModel(
}
/**
* Inserts a repo to the database, replace a matching repo with the same fingerprint if found.
* Inserts a repo to the database, replace a matching repo with the same signing key fingerprint if found.
*
* @param newRepo The repo to insert
*/
@@ -82,7 +82,7 @@ class ExtensionReposScreenModel(
if (status is RepoScreenState.Success) {
screenModelScope.launchIO {
status.repos.forEach { updateExtensionRepo.await(it) }
updateExtensionRepo.awaitAll()
}
}
}

View File

@@ -9,9 +9,9 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.Label
import androidx.compose.material.icons.automirrored.outlined.OpenInNew
import androidx.compose.material.icons.outlined.ContentCopy
import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material.icons.outlined.OpenInBrowser
import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
@@ -33,7 +33,7 @@ fun ExtensionReposContent(
repos: ImmutableSet<ExtensionRepo>,
lazyListState: LazyListState,
paddingValues: PaddingValues,
onClickOpen: (ExtensionRepo) -> Unit,
onOpenWebsite: (ExtensionRepo) -> Unit,
onClickDelete: (String) -> Unit,
modifier: Modifier = Modifier,
) {
@@ -48,7 +48,7 @@ fun ExtensionReposContent(
ExtensionRepoListItem(
modifier = Modifier.animateItemPlacement(),
repo = it.name,
onClick = { onClickOpen(it) },
onOpenWebsite = { onOpenWebsite(it) },
onDelete = { onClickDelete(it.baseUrl) },
)
}
@@ -59,7 +59,7 @@ fun ExtensionReposContent(
@Composable
private fun ExtensionRepoListItem(
repo: String,
onClick: () -> Unit,
onOpenWebsite: () -> Unit,
onDelete: () -> Unit,
modifier: Modifier = Modifier,
) {
@@ -90,9 +90,9 @@ private fun ExtensionRepoListItem(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.End,
) {
IconButton(onClick = onClick) {
IconButton(onClick = onOpenWebsite) {
Icon(
imageVector = Icons.Outlined.OpenInBrowser,
imageVector = Icons.AutoMirrored.Outlined.OpenInNew,
contentDescription = stringResource(MR.strings.action_open_in_browser),
)
}

View File

@@ -25,12 +25,12 @@ import kotlin.time.Duration.Companion.seconds
fun ExtensionRepoCreateDialog(
onDismissRequest: () -> Unit,
onCreate: (String) -> Unit,
repos: ImmutableSet<String>,
repoUrls: ImmutableSet<String>,
) {
var name by remember { mutableStateOf("") }
val focusRequester = remember { FocusRequester() }
val nameAlreadyExists = remember(name) { repos.contains(name) }
val nameAlreadyExists = remember(name) { repoUrls.contains(name) }
AlertDialog(
onDismissRequest = onDismissRequest,

View File

@@ -28,7 +28,7 @@ import tachiyomi.presentation.core.util.plus
fun ExtensionReposScreen(
state: RepoScreenState.Success,
onClickCreate: () -> Unit,
onClickOpen: (ExtensionRepo) -> Unit,
onOpenWebsite: (ExtensionRepo) -> Unit,
onClickDelete: (String) -> Unit,
onClickRefresh: () -> Unit,
navigateUp: () -> Unit,
@@ -70,7 +70,7 @@ fun ExtensionReposScreen(
lazyListState = lazyListState,
paddingValues = paddingValues + topSmallPaddingValues +
PaddingValues(horizontal = MaterialTheme.padding.medium),
onClickOpen = onClickOpen,
onOpenWebsite = onOpenWebsite,
onClickDelete = onClickDelete,
)
}

View File

@@ -9,6 +9,8 @@ import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.network.awaitSuccess
import eu.kanade.tachiyomi.network.parseAs
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import logcat.LogPriority
@@ -38,7 +40,10 @@ internal class ExtensionApi {
suspend fun findExtensions(): List<Extension.Available> {
return withIOContext {
getExtensionRepo.getAll().flatMap { getExtensions(it) }
getExtensionRepo.getAll()
.map { async { getExtensions(it) } }
.awaitAll()
.flatten()
}
}
@@ -60,9 +65,6 @@ internal class ExtensionApi {
}
}
private suspend fun updateExtensionSources() =
getExtensionRepo.getAll().forEach { updateExtensionRepo.await(it) }
suspend fun checkForUpdates(
context: Context,
fromAvailableExtensionList: Boolean = false,
@@ -75,7 +77,7 @@ internal class ExtensionApi {
}
// Update extension repo details
updateExtensionSources()
updateExtensionRepo.awaitAll()
val extensions = if (fromAvailableExtensionList) {
extensionManager.availableExtensionsFlow.value

View File

@@ -23,8 +23,10 @@ class ExtensionRepoRepositoryImpl(
return handler.awaitOneOrNull { extension_reposQueries.findOne(baseUrl, ::mapExtensionRepo) }
}
override suspend fun getRepositoryByFingerprint(fingerprint: String): ExtensionRepo? {
return handler.awaitOneOrNull { extension_reposQueries.findOneByFingerprint(fingerprint, ::mapExtensionRepo) }
override suspend fun getRepositoryBySigningKeyFingerprint(fingerprint: String): ExtensionRepo? {
return handler.awaitOneOrNull {
extension_reposQueries.findOneBySigningKeyFingerprint(fingerprint, ::mapExtensionRepo)
}
}
override fun getCount(): Flow<Int> {
@@ -36,10 +38,10 @@ class ExtensionRepoRepositoryImpl(
name: String,
shortName: String?,
website: String,
fingerprint: String,
signingKeyFingerprint: String,
) {
try {
handler.await { extension_reposQueries.insert(baseUrl, name, shortName, website, fingerprint) }
handler.await { extension_reposQueries.insert(baseUrl, name, shortName, website, signingKeyFingerprint) }
} catch (ex: SQLiteException) {
throw SaveExtensionRepoException(ex)
}
@@ -50,10 +52,10 @@ class ExtensionRepoRepositoryImpl(
name: String,
shortName: String?,
website: String,
fingerprint: String,
signingKeyFingerprint: String,
) {
try {
handler.await { extension_reposQueries.upsert(baseUrl, name, shortName, website, fingerprint) }
handler.await { extension_reposQueries.upsert(baseUrl, name, shortName, website, signingKeyFingerprint) }
} catch (ex: SQLiteException) {
throw SaveExtensionRepoException(ex)
}
@@ -66,7 +68,7 @@ class ExtensionRepoRepositoryImpl(
newRepo.name,
newRepo.shortName,
newRepo.website,
newRepo.fingerprint,
newRepo.signingKeyFingerprint,
)
}
}
@@ -80,12 +82,12 @@ class ExtensionRepoRepositoryImpl(
name: String,
shortName: String?,
website: String,
fingerprint: String,
signingKeyFingerprint: String,
): ExtensionRepo = ExtensionRepo(
baseUrl = baseUrl,
name = name,
shortName = shortName,
website = website,
fingerprint = fingerprint,
signingKeyFingerprint = signingKeyFingerprint,
)
}

View File

@@ -3,7 +3,7 @@ CREATE TABLE extension_repos (
name TEXT NOT NULL,
short_name TEXT,
website TEXT NOT NULL,
fingerprint TEXT UNIQUE NOT NULL
signing_key_fingerprint TEXT UNIQUE NOT NULL
);
findOne:
@@ -11,10 +11,10 @@ SELECT *
FROM extension_repos
WHERE base_url = :base_url;
findOneByFingerprint:
findOneBySigningKeyFingerprint:
SELECT *
FROM extension_repos
WHERE fingerprint = :fingerprint;
WHERE signing_key_fingerprint = :fingerprint;
findAll:
SELECT *
@@ -25,11 +25,11 @@ SELECT COUNT(*)
FROM extension_repos;
insert:
INSERT INTO extension_repos(base_url, name, short_name, website, fingerprint)
INSERT INTO extension_repos(base_url, name, short_name, website, signing_key_fingerprint)
VALUES (:base_url, :name, :short_name, :website, :fingerprint);
upsert:
INSERT INTO extension_repos(base_url, name, short_name, website, fingerprint)
INSERT INTO extension_repos(base_url, name, short_name, website, signing_key_fingerprint)
VALUES (:base_url, :name, :short_name, :website, :fingerprint)
ON CONFLICT(base_url)
DO UPDATE
@@ -37,20 +37,20 @@ SET
name = :name,
short_name = :short_name,
website =: website,
fingerprint = :fingerprint
signing_key_fingerprint = :fingerprint
WHERE base_url = base_url;
replace:
INSERT INTO extension_repos(base_url, name, short_name, website, fingerprint)
INSERT INTO extension_repos(base_url, name, short_name, website, signing_key_fingerprint)
VALUES (:base_url, :name, :short_name, :website, :fingerprint)
ON CONFLICT(fingerprint)
ON CONFLICT(signing_key_fingerprint)
DO UPDATE
SET
base_url = :base_url,
name = :name,
short_name = :short_name,
website =: website
WHERE fingerprint = fingerprint;
WHERE signing_key_fingerprint = signing_key_fingerprint;
delete:
DELETE FROM extension_repos

View File

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

View File

@@ -2,10 +2,10 @@ package mihon.domain.extensionrepo.interactor
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 mihon.domain.extensionrepo.service.ExtensionRepoService
import okhttp3.OkHttpClient
import tachiyomi.core.common.util.system.logcat
import uy.kohesive.injekt.injectLazy
@@ -20,7 +20,7 @@ class CreateExtensionRepo(
private val client: OkHttpClient
get() = networkService.client
private val extensionRepoApi = ExtensionRepoApi(client)
private val extensionRepoService = ExtensionRepoService(client)
suspend fun await(repoUrl: String): Result {
if (!repoUrl.matches(repoRegex)) {
@@ -28,7 +28,7 @@ class CreateExtensionRepo(
}
val baseUrl = repoUrl.removeSuffix("/index.min.json")
return extensionRepoApi.fetchRepoDetails(baseUrl)?.let { insert(it) } ?: Result.InvalidUrl
return extensionRepoService.fetchRepoDetails(baseUrl)?.let { insert(it) } ?: Result.InvalidUrl
}
private suspend fun insert(repo: ExtensionRepo): Result {
@@ -38,34 +38,33 @@ class CreateExtensionRepo(
repo.name,
repo.shortName,
repo.website,
repo.fingerprint,
repo.signingKeyFingerprint,
)
Result.Success
} catch (ex: SaveExtensionRepoException) {
logcat(LogPriority.WARN, ex) { "SQL Conflict attempting to add new repository ${repo.baseUrl}" }
} catch (e: SaveExtensionRepoException) {
logcat(LogPriority.WARN, e) { "SQL Conflict attempting to add new repository ${repo.baseUrl}" }
return handleInsertionError(repo)
}
}
@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
* 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
* @param repo Extension Repo holder for passing to DB/Error Dialog
*/
@Suppress("ReturnCount")
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)
val matchingFingerprintRepo =
extensionRepoRepository.getRepositoryBySigningKeyFingerprint(repo.signingKeyFingerprint)
if (matchingFingerprintRepo != null) {
return Result.DuplicateFingerprint(matchingFingerprintRepo, repo)
}

View File

@@ -1,20 +1,32 @@
package mihon.domain.extensionrepo.interactor
import eu.kanade.tachiyomi.network.NetworkHelper
import mihon.domain.extensionrepo.api.ExtensionRepoApi
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import mihon.domain.extensionrepo.model.ExtensionRepo
import mihon.domain.extensionrepo.repository.ExtensionRepoRepository
import mihon.domain.extensionrepo.service.ExtensionRepoService
class UpdateExtensionRepo(
private val extensionRepoRepository: ExtensionRepoRepository,
networkService: NetworkHelper,
) {
private val extensionRepoApi = ExtensionRepoApi(networkService.client)
private val extensionRepoService = ExtensionRepoService(networkService.client)
suspend fun awaitAll() = coroutineScope {
extensionRepoRepository.getAll()
.map { async { await(it) } }
.awaitAll()
}
suspend fun await(repo: ExtensionRepo) {
val newRepo = extensionRepoApi.fetchRepoDetails(repo.baseUrl) ?: return
if (repo.fingerprint.startsWith("NOFINGERPRINT") || repo.fingerprint == newRepo.fingerprint) {
val newRepo = extensionRepoService.fetchRepoDetails(repo.baseUrl) ?: return
if (
repo.signingKeyFingerprint.startsWith("NOFINGERPRINT") ||
repo.signingKeyFingerprint == newRepo.signingKeyFingerprint
) {
extensionRepoRepository.upsertRepository(newRepo)
}
}

View File

@@ -5,5 +5,5 @@ data class ExtensionRepo(
val name: String,
val shortName: String?,
val website: String,
val fingerprint: String,
val signingKeyFingerprint: String,
)

View File

@@ -11,7 +11,7 @@ interface ExtensionRepoRepository {
suspend fun getRepository(baseUrl: String): ExtensionRepo?
suspend fun getRepositoryByFingerprint(fingerprint: String): ExtensionRepo?
suspend fun getRepositoryBySigningKeyFingerprint(fingerprint: String): ExtensionRepo?
fun getCount(): Flow<Int>
@@ -20,7 +20,7 @@ interface ExtensionRepoRepository {
name: String,
shortName: String?,
website: String,
fingerprint: String,
signingKeyFingerprint: String,
)
suspend fun upsertRepository(
@@ -28,7 +28,7 @@ interface ExtensionRepoRepository {
name: String,
shortName: String?,
website: String,
fingerprint: String,
signingKeyFingerprint: String,
)
suspend fun upsertRepository(repo: ExtensionRepo) {
@@ -37,7 +37,7 @@ interface ExtensionRepoRepository {
name = repo.name,
shortName = repo.shortName,
website = repo.website,
fingerprint = repo.fingerprint,
signingKeyFingerprint = repo.signingKeyFingerprint,
)
}

View File

@@ -1,4 +1,4 @@
package mihon.domain.extensionrepo.api
package mihon.domain.extensionrepo.service
import androidx.core.net.toUri
import eu.kanade.tachiyomi.network.GET
@@ -14,7 +14,7 @@ import okhttp3.OkHttpClient
import tachiyomi.core.common.util.lang.withIOContext
import uy.kohesive.injekt.injectLazy
class ExtensionRepoApi(
class ExtensionRepoService(
private val client: OkHttpClient,
) {
@@ -27,16 +27,14 @@ class ExtensionRepoApi(
val url = "$repo/repo.json".toUri()
try {
with(json) {
val response = with(json) {
client.newCall(GET(url.toString()))
.awaitSuccess()
.parseAs<JsonObject>()
.let {
it["meta"]
?.jsonObject
?.let { it1 -> jsonToExtensionRepo(baseUrl = repo, it1) }
}
}
response["meta"]
?.jsonObject
?.let { jsonToExtensionRepo(baseUrl = repo, it) }
} catch (_: HttpException) {
null
}
@@ -50,7 +48,7 @@ class ExtensionRepoApi(
name = obj["name"]!!.jsonPrimitive.content,
shortName = obj["shortName"]?.jsonPrimitive?.content,
website = obj["website"]!!.jsonPrimitive.content,
fingerprint = obj["signingKeyFingerprint"]!!.jsonPrimitive.content,
signingKeyFingerprint = obj["signingKeyFingerprint"]!!.jsonPrimitive.content,
)
} catch (_: NullPointerException) {
null

View File

@@ -346,10 +346,8 @@
<string name="delete_repo_confirmation">Do you wish to delete the repo \"%s\"?</string>
<string name="action_open_repo">Open source repo</string>
<string name="action_replace_repo">Replace</string>
<string name="action_replace_repo_title">Fingerprint Already Exists</string>
<string name="action_replace_repo_message">Repository %1$s has the same fingerprint as %2$s.\nIf
this is expected, %2$s will be replaced, otherwise contact your repo maintainer.
</string>
<string name="action_replace_repo_title">Signing Key Fingerprint Already Exists</string>
<string name="action_replace_repo_message">Repository %1$s has the same Signing Key Fingerprint as %2$s.\nIf this is expected, %2$s will be replaced, otherwise contact your repo maintainer.</string>
<!-- Reader section -->
<string name="pref_fullscreen">Fullscreen</string>