[db] add archive repo adding to RepoAdder

This commit is contained in:
Torsten Grote
2023-11-28 09:45:04 -03:00
parent 4831cd8a8d
commit e47ef72f75
2 changed files with 96 additions and 17 deletions

View File

@@ -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.
*/

View File

@@ -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.")