2026-05-22 20:26:11 -04:00
|
|
|
package userstore
|
|
|
|
|
|
|
|
|
|
// ProgressThresholds bundles the watched and min-resume threshold percentages.
|
|
|
|
|
// Zero values mean "use defaults" (90% watched, 5% min-resume).
|
|
|
|
|
type ProgressThresholds struct {
|
|
|
|
|
WatchedPct int // mark completed above this % (default 90)
|
|
|
|
|
MinResumePct int // discard progress below this % (default 5)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// WatchedFraction converts a watched-threshold percentage (e.g. 90) to a
|
|
|
|
|
// fraction (0.9). If pct <= 0, returns the default of 0.9 (90%).
|
|
|
|
|
func WatchedFraction(pct int) float64 {
|
|
|
|
|
if pct <= 0 {
|
|
|
|
|
pct = 90
|
|
|
|
|
}
|
|
|
|
|
return float64(pct) / 100.0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MinResumeFraction converts a min-resume percentage (e.g. 5) to a fraction
|
|
|
|
|
// (0.05). If pct <= 0, returns the default of 0.05 (5%).
|
|
|
|
|
func MinResumeFraction(pct int) float64 {
|
|
|
|
|
if pct <= 0 {
|
|
|
|
|
pct = 5
|
|
|
|
|
}
|
|
|
|
|
return float64(pct) / 100.0
|
|
|
|
|
}
|
2026-07-01 22:05:36 -04:00
|
|
|
|
|
|
|
|
// ResolveProgressState classifies one progress event against the thresholds:
|
|
|
|
|
// skip (below the min-resume floor — discard entirely), completed (above the
|
|
|
|
|
// watched threshold — the returned position resets to 0 so completed rows
|
|
|
|
|
// hold no resume point, matching MarkWatched), or a plain in-progress update.
|
|
|
|
|
// The single home of the rule shared by both store backends and the
|
|
|
|
|
// offline-sync ingest; `completed` remains a one-way latch at the write site.
|
|
|
|
|
func ResolveProgressState(position, duration float64, t ProgressThresholds) (pos float64, completed, skip bool) {
|
|
|
|
|
// Clamp malformed client input so negative values can never classify — or
|
|
|
|
|
// persist — as real progress on any backend.
|
|
|
|
|
if position < 0 {
|
|
|
|
|
position = 0
|
|
|
|
|
}
|
|
|
|
|
if duration < 0 {
|
|
|
|
|
duration = 0
|
|
|
|
|
}
|
|
|
|
|
if duration > 0 && position > 0 && position/duration < MinResumeFraction(t.MinResumePct) {
|
|
|
|
|
return position, false, true
|
|
|
|
|
}
|
|
|
|
|
completed = duration > 0 && position/duration > WatchedFraction(t.WatchedPct)
|
|
|
|
|
if completed {
|
|
|
|
|
position = 0
|
|
|
|
|
}
|
|
|
|
|
return position, completed, false
|
|
|
|
|
}
|