summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-10-14 17:42:56 +0000
committerPeter Stone <thepeterstone@gmail.com>2026-10-14 17:42:56 +0000
commiteb7eaca4448b2cb5e5bf340f356ebfc7b47e532c (patch)
tree5fd82968c3e93822783f6daddbc7f1a277434e0c /app
parent1e05c7bbf5e2789201e6f6d48fbac78784c3125f (diff)
Stop swallowing cancellation in skipFurniture()'s crash guard
Fix 2's runCatching caught Throwable, including CancellationException. Skipping quickly between episodes cancels PlaybackService's context-load coroutine mid-read; the swallowed cancellation let that launch carry on and overwrite the new episode's context with the previous one's midrolls and skip flag, so the playhead could be moved by the wrong episode's breaks. Narrowed to an explicit try/catch that rethrows CancellationException and catches IOException specifically (covers CorruptionException, a subclass) rather than a generic Exception, with the exception logged instead of silently discarded -- keeping the original crash-guard finding intact. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
Diffstat (limited to 'app')
-rw-r--r--app/src/main/java/org/terst/listen/data/Repositories.kt24
1 files changed, 22 insertions, 2 deletions
diff --git a/app/src/main/java/org/terst/listen/data/Repositories.kt b/app/src/main/java/org/terst/listen/data/Repositories.kt
index 46731c8..8a2acb2 100644
--- a/app/src/main/java/org/terst/listen/data/Repositories.kt
+++ b/app/src/main/java/org/terst/listen/data/Repositories.kt
@@ -3,6 +3,7 @@ package org.terst.listen.data
import android.content.Context
import coil3.ImageLoader
import coil3.network.okhttp.OkHttpNetworkFetcherFactory
+import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.flow.first
import okhttp3.OkHttpClient
import org.terst.listen.server.OkHttpSkipTransport
@@ -25,6 +26,7 @@ import org.terst.listen.queue.RuleApplier
import org.terst.listen.work.QueuedDownloads
import org.terst.listen.work.Scheduling
import java.io.File
+import java.io.IOException
import java.util.concurrent.TimeUnit
/**
@@ -102,8 +104,26 @@ class AppGraph(private val context: Context) {
// that is today's behaviour, and it is the fail-safe direction -- a break that
// silently stops being skipped is the failure mode that matters here, so losing the
// preference must not quietly let a show's own audio start playing again.
- override suspend fun skipFurniture() =
- runCatching { preferences.skipShowFurniture.first() }.getOrDefault(true)
+ //
+ // CancellationException is deliberately rethrown rather than caught here: this runs
+ // inside PlaybackService's `scope.launch { context = current.contextFor(episodeId) }`,
+ // and skipping quickly from one episode to the next cancels that load mid-read. A
+ // bare `runCatching` swallows that cancellation too, so the launch body carries on
+ // and overwrites the new episode's context with the previous one's -- the playhead
+ // can then be moved by the wrong episode's breaks. Do not "simplify" this back to
+ // `runCatching`; that is the regression this comment exists to prevent.
+ override suspend fun skipFurniture() = try {
+ preferences.skipShowFurniture.first()
+ } catch (e: CancellationException) {
+ throw e
+ } catch (e: IOException) {
+ // CorruptionException is a subclass of IOException, so this covers both of
+ // DataStore's documented failure modes without reaching for a generic Exception
+ // catch. Logged rather than silently discarded, so a damaged preferences file
+ // leaves a trace instead of disappearing into a boolean default.
+ android.util.Log.w("AppGraph", "skipShowFurniture unreadable, defaulting to skip", e)
+ true
+ }
})
}