From e47ef72f7526ce4855c2e4fdc1f4e4fd739d1182 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Tue, 28 Nov 2023 09:45:04 -0300 Subject: [PATCH] [db] add archive repo adding to RepoAdder --- .../main/java/org/fdroid/index/RepoManager.kt | 34 +++++++- .../main/java/org/fdroid/repo/RepoAdder.kt | 79 +++++++++++++++---- 2 files changed, 96 insertions(+), 17 deletions(-) diff --git a/libs/database/src/main/java/org/fdroid/index/RepoManager.kt b/libs/database/src/main/java/org/fdroid/index/RepoManager.kt index edea118b2..4e0281fbf 100644 --- a/libs/database/src/main/java/org/fdroid/index/RepoManager.kt +++ b/libs/database/src/main/java/org/fdroid/index/RepoManager.kt @@ -27,6 +27,7 @@ import org.fdroid.repo.RepoAdder import org.fdroid.repo.RepoUriGetter import java.io.File import java.net.Proxy +import java.util.concurrent.CancellationException import java.util.concurrent.CountDownLatch import kotlin.coroutines.CoroutineContext @@ -36,10 +37,9 @@ public class RepoManager @JvmOverloads constructor( private val db: FDroidDatabase, downloaderFactory: DownloaderFactory, httpManager: HttpManager, - private val repoUriBuilder: RepoUriBuilder = defaultRepoUriBuilder, + repoUriBuilder: RepoUriBuilder = defaultRepoUriBuilder, private val coroutineContext: CoroutineContext = Dispatchers.IO, ) { - private val repositoryDao = db.getRepositoryDao() as RepositoryDaoInt private val appPrefsDao = db.getAppPrefsDao() as AppPrefsDaoInt private val tempFileProvider = TempFileProvider { @@ -197,6 +197,36 @@ public class RepoManager @JvmOverloads constructor( } } + /** + * Enables or disabled the archive repo for the given [repository]. + * + * Note that this can throw all kinds of exceptions, + * especially when the given [repository] does not have a (working) archive repository. + * You should catch those and update your UI accordingly. + */ + @WorkerThread + public suspend fun setArchiveRepoEnabled( + repository: Repository, + enabled: Boolean, + proxy: Proxy? = null, + ) { + val cert = repository.certificate ?: error { "$repository has no cert" } + val archiveRepoId = repositoryDao.getArchiveRepoId(cert) + if (enabled) { + if (archiveRepoId == null) { + try { + repoAdder.addArchiveRepo(repository, proxy) + } catch (e: CancellationException) { + if (e.message != "expected") throw e + } + } else { + repositoryDao.setRepositoryEnabled(archiveRepoId, true) + } + } else if (archiveRepoId != null) { + repositoryDao.setRepositoryEnabled(archiveRepoId, false) + } + } + /** * Returns true if the given [uri] belongs to a swap repo. */ diff --git a/libs/database/src/main/java/org/fdroid/repo/RepoAdder.kt b/libs/database/src/main/java/org/fdroid/repo/RepoAdder.kt index 09934ef5a..d160620d3 100644 --- a/libs/database/src/main/java/org/fdroid/repo/RepoAdder.kt +++ b/libs/database/src/main/java/org/fdroid/repo/RepoAdder.kt @@ -6,6 +6,7 @@ import android.os.Build.VERSION.SDK_INT import android.os.UserManager import android.os.UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES import android.os.UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY +import androidx.annotation.AnyThread import androidx.annotation.VisibleForTesting import androidx.annotation.WorkerThread import androidx.core.content.ContextCompat.getSystemService @@ -13,8 +14,10 @@ import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.Job +import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import kotlinx.serialization.SerializationException import mu.KotlinLogging import org.fdroid.database.AppOverviewItem @@ -166,21 +169,7 @@ internal class RepoAdder( // try fetching repo with v2 format first and fallback to v1 try { - try { - val repo = - getTempRepo(nUri.uri, IndexFormatVersion.TWO, nUri.username, nUri.password) - val repoFetcher = RepoV2Fetcher( - tempFileProvider, downloaderFactory, httpManager, repoUriBuilder, proxy - ) - repoFetcher.fetchRepo(nUri.uri, repo, receiver, nUri.fingerprint) - } catch (e: NotFoundException) { - log.warn(e) { "Did not find v2 repo, trying v1 now." } - // try to fetch v1 repo - val repo = - getTempRepo(nUri.uri, IndexFormatVersion.ONE, nUri.username, nUri.password) - val repoFetcher = RepoV1Fetcher(tempFileProvider, downloaderFactory, repoUriBuilder) - repoFetcher.fetchRepo(nUri.uri, repo, receiver, nUri.fingerprint) - } + fetchRepo(nUri.uri, nUri.fingerprint, proxy, nUri.username, nUri.password, receiver) } catch (e: SigningException) { log.error(e) { "Error verifying repo with given fingerprint." } addRepoState.value = AddRepoError(INVALID_FINGERPRINT, e) @@ -207,6 +196,31 @@ internal class RepoAdder( } } + private suspend fun fetchRepo( + uri: Uri, + fingerprint: String?, + proxy: Proxy?, + username: String?, + password: String?, + receiver: RepoPreviewReceiver, + ) { + try { + val repo = + getTempRepo(uri, IndexFormatVersion.TWO, username, password) + val repoFetcher = RepoV2Fetcher( + tempFileProvider, downloaderFactory, httpManager, repoUriBuilder, proxy + ) + repoFetcher.fetchRepo(uri, repo, receiver, fingerprint) + } catch (e: NotFoundException) { + log.warn(e) { "Did not find v2 repo, trying v1 now." } + // try to fetch v1 repo + val repo = + getTempRepo(uri, IndexFormatVersion.ONE, username, password) + val repoFetcher = RepoV1Fetcher(tempFileProvider, downloaderFactory, repoUriBuilder) + repoFetcher.fetchRepo(uri, repo, receiver, fingerprint) + } + } + private fun getFetchResult(url: String, repo: Repository): FetchResult { val cert = repo.certificate ?: error("Certificate was null") val existingRepo = repositoryDao.getRepository(cert) @@ -293,6 +307,41 @@ internal class RepoAdder( fetchJob?.cancel() } + @AnyThread + internal suspend fun addArchiveRepo(repo: Repository, proxy: Proxy? = null) = + withContext(coroutineContext) { + if (repo.isArchiveRepo) error { "Repo ${repo.address} is already an archive repo." } + + val address = repo.address.replace(Regex("repo/?$"), "archive") + @Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE") + val receiver = object : RepoPreviewReceiver { + override fun onRepoReceived(archiveRepo: Repository) { + // reset the timestamp of the actual repo, + // so a following repo update will pick this up + val newRepo = NewRepository( + name = archiveRepo.repository.name, + icon = archiveRepo.repository.icon ?: emptyMap(), + address = archiveRepo.address, + formatVersion = archiveRepo.formatVersion, + certificate = archiveRepo.certificate ?: error("Repo had no certificate"), + username = archiveRepo.username, + password = archiveRepo.password, + ) + db.runInTransaction { + val repoId = repositoryDao.insert(newRepo) + repositoryDao.setWeight(repoId, repo.weight - 1) + } + cancel("expected") // no need to continue downloading the entire repo + } + + override fun onAppReceived(app: AppOverviewItem) { + // no-op + } + } + val uri = Uri.parse(address) + fetchRepo(uri, repo.fingerprint, proxy, repo.username, repo.password, receiver) + } + private fun hasDisallowInstallUnknownSources(context: Context): Boolean { val userManager = getSystemService(context, UserManager::class.java) ?: error("No UserManager available.")