From fccbaf8b647cf9350a00e76bab02c129dbad7dca Mon Sep 17 00:00:00 2001 From: Peter Stone Date: Sat, 22 Aug 2026 00:54:44 +0000 Subject: StashClient: cancellable requests, longer sync timeout, defensive parsing Uses the new Call.await() instead of a thread-blocking execute(). The sync=true save endpoint can take up to ~30s (server-side extract + summary), longer than the shared client's stock 10s read timeout -- overridden per-call so feed fetches don't inherit a 35s timeout they don't need. A 409 (duplicate) is authoritative from the status code alone; a body that doesn't match DuplicateErrorResponse's exact shape no longer downgrades a real "already saved" into a misleading "save failed". --- .../java/org/terst/feedreader/stash/StashClient.kt | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) (limited to 'app/src/main/java/org/terst/feedreader/stash') diff --git a/app/src/main/java/org/terst/feedreader/stash/StashClient.kt b/app/src/main/java/org/terst/feedreader/stash/StashClient.kt index b613d66..346ae49 100644 --- a/app/src/main/java/org/terst/feedreader/stash/StashClient.kt +++ b/app/src/main/java/org/terst/feedreader/stash/StashClient.kt @@ -2,6 +2,7 @@ package org.terst.feedreader.stash import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import org.terst.feedreader.data.await import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json @@ -9,6 +10,7 @@ import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.RequestBody.Companion.toRequestBody +import java.util.concurrent.TimeUnit private val json = Json { ignoreUnknownKeys = true } @@ -67,7 +69,7 @@ class StashClient( .build() runCatching { - client.newCall(request).execute().use { response -> + client.newCall(request).await().use { response -> when { response.isSuccessful -> StashVerifyResult.Valid response.code == 401 || response.code == 403 -> StashVerifyResult.Invalid("Rejected: API key not accepted") @@ -94,14 +96,28 @@ class StashClient( .build() runCatching { - client.newCall(request).execute().use { response -> + val call = client.newCall(request) + // sync=true makes the server run extract+summary_lite inline before responding + // (documented above as taking up to ~30s), but `client` is the shared Net.client + // used for feed fetching too, whose default read timeout (OkHttp's stock 10s) is + // sized for a normal feed request, not this. Overriding per-call, not on the + // shared client, so feed fetches don't inherit a 35s timeout they don't need. + call.timeout().timeout(35, TimeUnit.SECONDS) + call.await().use { response -> val responseBody = response.body?.string().orEmpty() when { response.code == 201 || response.code == 200 -> StashSaveResult.Saved(json.decodeFromString(ArtifactResponse.serializer(), responseBody)) response.code == 409 -> + // The status code alone already tells us definitively this was + // rejected as a duplicate -- that's true regardless of whether the + // body happens to match DuplicateErrorResponse's exact shape. Losing + // the existing-artifact id to a schema mismatch shouldn't downgrade + // a real "already saved" into a misleading "save failed". StashSaveResult.AlreadySaved( - json.decodeFromString(DuplicateErrorResponse.serializer(), responseBody).existingId + runCatching { + json.decodeFromString(DuplicateErrorResponse.serializer(), responseBody).existingId + }.getOrDefault("") ) else -> StashSaveResult.Failed("HTTP ${response.code}") } -- cgit v1.2.3