vc=33 fix: add FeedCacheStore.kt to git

git commit -am only stages tracked files. The new file existed locally
but wasn't in the previous commit; build failed with Unresolved
reference 'FeedCache' on every site that used it.
This commit is contained in:
Kayos 2026-05-25 12:54:09 -07:00
parent 69560889ae
commit c74b06436f

View file

@ -0,0 +1,71 @@
/*
* SPDX-FileCopyrightText: 2026 Sulkta-Coop
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Persistent per-channel cache for the subscription feed. Survives
* process death, so opening Subs after a cold start shows the last
* successful fetch immediately instead of waiting 5+ seconds for 30
* channel browses to resolve.
*
* Storage: SharedPreferences with a single JSON blob. Total payload is
* small (30 subs * 30 items * ~250 bytes = ~225 KB), well within SP's
* comfortable size and well below the multi-MB threshold where you'd
* want to graduate to Room or a file.
*
* Concurrency: writes from the feed VM are debounced via the single
* `persist` call inside fetchChannelInto's success path. Reads happen
* on VM init and are synchronous.
*/
package com.sulkta.straw.data
import android.content.Context
import android.content.SharedPreferences
import com.sulkta.straw.feature.search.StreamItem
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
@Serializable
data class FeedCacheEntry(
val fetchedAt: Long,
val items: List<StreamItem>,
)
private const val PREFS = "straw_feed_cache"
private const val KEY = "cache_v1"
class FeedCacheStore(context: Context) {
private val sp: SharedPreferences = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
private val json = Json { ignoreUnknownKeys = true; isLenient = true }
/** Snapshot of the disk cache. Returns empty map if nothing saved. */
fun load(): Map<String, FeedCacheEntry> = runCatching {
val s = sp.getString(KEY, null) ?: return emptyMap()
json.decodeFromString<Map<String, FeedCacheEntry>>(s)
}.getOrDefault(emptyMap())
/** Atomic write. Caller is responsible for diffing if needed. */
fun save(map: Map<String, FeedCacheEntry>) {
val s = json.encodeToString(map)
sp.edit().putString(KEY, s).apply()
}
fun clear() {
sp.edit().remove(KEY).apply()
}
}
object FeedCache {
@Volatile private var instance: FeedCacheStore? = null
fun init(context: Context) {
if (instance == null) {
synchronized(this) {
if (instance == null) instance = FeedCacheStore(context.applicationContext)
}
}
}
fun get(): FeedCacheStore = instance
?: error("FeedCacheStore not initialized — call FeedCache.init(context)")
}