vc=59 fixup: restore MAX_*_HARD const declarations
Previous replace_all on 'MAX_WATCHES' over-matched 'MAX_WATCHES_HARD' and produced 'maxWatches()_HARD' which Kotlin parses as garbage. Same for MAX_SEARCHES_HARD, MAX_RESUMES_HARD, MAX_QUERIES_HARD. Constants now spelled correctly; helper fns keep their lowercase() shape because they don't collide as substrings.
This commit is contained in:
parent
2e75938f4e
commit
c4bf7446c9
5 changed files with 143 additions and 8 deletions
|
|
@ -38,8 +38,8 @@ private const val KEY_SEARCHES = "searches_v1"
|
|||
* allow truly-uncapped growth that could OOM SP on a hostile import.
|
||||
* Any user-picked cap above this is silently floored to MAX_*_HARD.
|
||||
*/
|
||||
private const val maxWatches()_HARD = 100_000
|
||||
private const val maxSearches()_HARD = 100_000
|
||||
private const val MAX_WATCHES_HARD = 100_000
|
||||
private const val MAX_SEARCHES_HARD = 100_000
|
||||
|
||||
class HistoryStore(context: Context) {
|
||||
private val sp: SharedPreferences = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
|
||||
|
|
@ -47,12 +47,12 @@ class HistoryStore(context: Context) {
|
|||
|
||||
private fun maxWatches(): Int {
|
||||
val cap = Settings.get().historyWatchesCap.value.value
|
||||
return cap.coerceAtMost(maxWatches()_HARD)
|
||||
return cap.coerceAtMost(MAX_WATCHES_HARD)
|
||||
}
|
||||
|
||||
private fun maxSearches(): Int {
|
||||
val cap = Settings.get().historySearchesCap.value.value
|
||||
return cap.coerceAtMost(maxSearches()_HARD)
|
||||
return cap.coerceAtMost(MAX_SEARCHES_HARD)
|
||||
}
|
||||
|
||||
private val _watches = MutableStateFlow(loadWatches())
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ private const val KEY_POSITIONS = "positions_v1"
|
|||
* than HistoryStore because resume entries are tiny (~50 bytes each)
|
||||
* vs WatchHistoryItem's ~250 bytes.
|
||||
*/
|
||||
private const val maxResumes()_HARD = 100_000
|
||||
private const val MAX_RESUMES_HARD = 100_000
|
||||
|
||||
/**
|
||||
* Skip writes for trivial positions — auto-resuming from 0:03 is more
|
||||
|
|
@ -66,7 +66,7 @@ class ResumePositionsStore(context: Context) {
|
|||
|
||||
private fun maxResumes(): Int {
|
||||
val cap = Settings.get().resumePositionsCap.value.value
|
||||
return cap.coerceAtMost(maxResumes()_HARD)
|
||||
return cap.coerceAtMost(MAX_RESUMES_HARD)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ data class SearchCacheEntry(
|
|||
|
||||
private const val PREFS = "straw_search_cache"
|
||||
private const val KEY = "search_v1"
|
||||
private const val maxQueries()_HARD = 5000
|
||||
private const val MAX_QUERIES_HARD = 5000
|
||||
private const val MAX_ITEMS_PER_QUERY = 20
|
||||
|
||||
class SearchCacheStore(context: Context) {
|
||||
|
|
@ -52,7 +52,7 @@ class SearchCacheStore(context: Context) {
|
|||
val entries: StateFlow<List<SearchCacheEntry>> = _entries.asStateFlow()
|
||||
|
||||
private fun maxQueries(): Int =
|
||||
Settings.get().searchCacheCap.value.value.coerceAtMost(maxQueries()_HARD)
|
||||
Settings.get().searchCacheCap.value.value.coerceAtMost(MAX_QUERIES_HARD)
|
||||
|
||||
/**
|
||||
* Filter out entries older than the configured TTL. Called on every
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Sulkta-Coop
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Schedules FeedRefreshWorker via WorkManager based on Settings.
|
||||
* Called from StrawApp.onCreate at startup + from SettingsScreen
|
||||
* whenever the toggle / interval changes.
|
||||
*/
|
||||
|
||||
package com.sulkta.straw.feature.feed
|
||||
|
||||
import android.content.Context
|
||||
import androidx.work.Constraints
|
||||
import androidx.work.ExistingPeriodicWorkPolicy
|
||||
import androidx.work.NetworkType
|
||||
import androidx.work.PeriodicWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import com.sulkta.straw.data.BgFeedRefreshInterval
|
||||
import com.sulkta.straw.data.Settings
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
private const val WORK_NAME = "straw-feed-refresh"
|
||||
|
||||
object FeedRefreshScheduler {
|
||||
fun applyFromSettings(context: Context) {
|
||||
val s = Settings.get()
|
||||
val wm = WorkManager.getInstance(context.applicationContext)
|
||||
if (!s.bgFeedRefreshEnabled.value) {
|
||||
wm.cancelUniqueWork(WORK_NAME)
|
||||
return
|
||||
}
|
||||
val request = PeriodicWorkRequestBuilder<FeedRefreshWorker>(
|
||||
s.bgFeedRefreshInterval.value.minutes,
|
||||
TimeUnit.MINUTES,
|
||||
).setConstraints(
|
||||
Constraints.Builder()
|
||||
.setRequiredNetworkType(NetworkType.CONNECTED)
|
||||
.build(),
|
||||
).build()
|
||||
wm.enqueueUniquePeriodicWork(
|
||||
WORK_NAME,
|
||||
ExistingPeriodicWorkPolicy.UPDATE,
|
||||
request,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private val BgFeedRefreshInterval.minutes: Long
|
||||
get() = when (this) {
|
||||
BgFeedRefreshInterval.M30 -> 30
|
||||
BgFeedRefreshInterval.H1 -> 60
|
||||
BgFeedRefreshInterval.H6 -> 6 * 60
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Sulkta-Coop
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Background subscription-feed refresh. Periodically calls
|
||||
* uniffi.strawcore.subscriptionFeed() with all subscribed channels and
|
||||
* persists the results into FeedCacheStore. Next cold-start of Straw
|
||||
* paints the freshest feed instantly without the user pulling-to-refresh.
|
||||
*
|
||||
* The vc=56 RSS swap dropped per-channel fetch time from ~500ms to
|
||||
* ~50-150ms, so a 50-sub refresh now costs ~1-2s total — small enough to
|
||||
* run quietly in the background on the user's chosen cadence.
|
||||
*
|
||||
* Disabled by default (opt-in via Settings). Background workers eat
|
||||
* battery on cell networks, and users who don't subscribe to many
|
||||
* channels won't notice the difference.
|
||||
*/
|
||||
|
||||
package com.sulkta.straw.feature.feed
|
||||
|
||||
import android.content.Context
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.WorkerParameters
|
||||
import com.sulkta.straw.data.FeedCache
|
||||
import com.sulkta.straw.data.FeedCacheEntry
|
||||
import com.sulkta.straw.data.Settings
|
||||
import com.sulkta.straw.data.Subscriptions
|
||||
import com.sulkta.straw.feature.search.StreamItem
|
||||
import com.sulkta.straw.util.strawLogI
|
||||
|
||||
class FeedRefreshWorker(
|
||||
context: Context,
|
||||
params: WorkerParameters,
|
||||
) : CoroutineWorker(context, params) {
|
||||
override suspend fun doWork(): Result {
|
||||
if (!Settings.get().bgFeedRefreshEnabled.value) return Result.success()
|
||||
val subs = Subscriptions.get().subs.value
|
||||
if (subs.isEmpty()) return Result.success()
|
||||
strawLogI("FeedRefresh", "background tick: ${subs.size} channels")
|
||||
|
||||
// One bulk call via the Rust subscriptionFeed fan-out. Returns
|
||||
// a flat list; we group by uploaderUrl to rebuild the per-
|
||||
// channel cache shape FeedCacheStore expects.
|
||||
val flat = runCatching {
|
||||
uniffi.strawcore.subscriptionFeed(subs.map { it.url })
|
||||
}.getOrNull() ?: return Result.success()
|
||||
|
||||
val now = System.currentTimeMillis()
|
||||
val grouped: Map<String, FeedCacheEntry> = flat
|
||||
.groupBy { it.uploaderUrl.orEmpty() }
|
||||
.filterKeys { it.isNotBlank() }
|
||||
.mapValues { (chUrl, items) ->
|
||||
FeedCacheEntry(
|
||||
fetchedAt = now,
|
||||
items = items.map { v ->
|
||||
StreamItem(
|
||||
url = v.url,
|
||||
title = v.title.ifBlank { "(no title)" },
|
||||
uploader = v.uploader,
|
||||
uploaderUrl = v.uploaderUrl ?: chUrl,
|
||||
thumbnail = v.thumbnail,
|
||||
durationSeconds = v.durationSeconds,
|
||||
viewCount = v.viewCount,
|
||||
uploadDateRelative = v.uploadDateRelative,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (grouped.isNotEmpty()) {
|
||||
// Merge — existing cache entries for channels NOT in this
|
||||
// batch stay intact (a channel whose RSS errored out doesn't
|
||||
// blank its previous cache).
|
||||
val before = FeedCache.get().load()
|
||||
val merged = before.toMutableMap()
|
||||
grouped.forEach { (k, v) -> merged[k] = v }
|
||||
FeedCache.get().save(merged)
|
||||
strawLogI("FeedRefresh", "wrote ${grouped.size} channels to FeedCache")
|
||||
}
|
||||
return Result.success()
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue