fix: stop reporting a Tor wait during the fetch, and serve the cache on

the rate limit that triggers the gate

Addresses review on #812.

The awaiting-route phase was never cleared. Reporting it before the wait
fixed the label on the wait itself, but nothing restored ResolvingRelease
afterwards, so the UI and notification claimed "Waiting for Tor" for the
whole metadata request and its retries -- and in direct mode, where the
wait returns immediately, for a wait that never happened. An
onResolvingRelease callback now fires once the route is ready.

A rate-limit rejection recorded the gate and returned the failure, while
the stale-cache fallback sat at the top of the function and was only
reached on a later call. The first About check therefore reported an
error the user could not act on, and an immediate retry succeeded from
metadata that was already present. The response that sets the gate now
serves the cached release straight away.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Moe Hamade 2026-07-29 15:13:45 +03:00
parent f33ce0bb98
commit b79ae8e7f1
2 changed files with 21 additions and 0 deletions

View File

@ -69,6 +69,7 @@ object GitHubReleaseClient {
suspend fun fetchLatestRelease(
forceRefresh: Boolean = false,
onAwaitingNetworkRoute: (() -> Unit)? = null,
onResolvingRelease: (() -> Unit)? = null,
): Result<Release> =
withContext(Dispatchers.IO) {
fetchMutex.withLock {
@ -108,6 +109,11 @@ object GitHubReleaseClient {
)
)
}
// The wait is over, so stop saying we are waiting. In direct mode it
// returned immediately and never really started, and the fetch below
// retries -- either way the caller must not keep reporting a Tor wait
// for the whole metadata request.
onResolvingRelease?.invoke()
var lastFailure: Throwable = ReleaseFetchException(
"Failed to fetch the latest release from GitHub"
@ -121,6 +127,18 @@ object GitHubReleaseClient {
}
lastFailure = result.exceptionOrNull() ?: lastFailure
// The response that just set the gate is the one the user is waiting
// on. Reporting an error here and only serving the cache on the next
// call makes the first check fail and an immediate retry succeed from
// metadata we already had.
if (System.currentTimeMillis() < blockedUntilMillis) {
cached?.let {
Log.w(TAG, "Rate limited; serving the cached release instead of failing")
return@withLock Result.success(it.release)
}
return@withLock Result.failure(lastFailure)
}
if (!isRetryable(lastFailure) || attempt == MAX_FETCH_ATTEMPTS - 1) {
return@withLock Result.failure(lastFailure)
}

View File

@ -228,6 +228,9 @@ class UniversalApkManager(private val context: Context) {
val release = GitHubReleaseClient.fetchLatestRelease(
onAwaitingNetworkRoute = {
phaseCallback?.invoke(ApkDownloader.DownloadPhase.AwaitingNetworkRoute)
},
onResolvingRelease = {
phaseCallback?.invoke(ApkDownloader.DownloadPhase.ResolvingRelease)
}
).getOrElse { error ->
return@withContext Result.failure(error)