mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-09-12 04:08:33 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dac9dd66b7 |
+13
-4
@@ -2130,16 +2130,25 @@ class MetadataDB:
|
|||||||
return self._stats_row(filename, int(arrangement))
|
return self._stats_row(filename, int(arrangement))
|
||||||
|
|
||||||
def add_play_seconds(self, filename: str, arrangement: int, seconds: float) -> dict:
|
def add_play_seconds(self, filename: str, arrangement: int, seconds: float) -> dict:
|
||||||
"""Accrue wall-clock play time only (no plays/score/position change) —
|
"""Accrue wall-clock play time (no plays/score/position change) —
|
||||||
the recorder's seconds-only flush for unscored plays that ran to the
|
the recorder's seconds-only flush for unscored plays that ran to the
|
||||||
song's natural end (no resume position to touch there: `song:ended`
|
song's natural end (no resume position to touch there: `song:ended`
|
||||||
must not overwrite Continue with the end-of-song offset)."""
|
must not overwrite Continue with the end-of-song offset). Stamps
|
||||||
|
last_played_at like touch_position does: the song WAS played, so
|
||||||
|
/api/stats/recent and Continue ordering must see it. Accepted skew:
|
||||||
|
the recorder retries FAILED flushes later, which stamps recency at
|
||||||
|
retry time — rare (offline corner), self-healing on the next play,
|
||||||
|
and preferable to the alternative (keep-existing would leave repeat
|
||||||
|
plays looking stale, the common case)."""
|
||||||
with self._lock:
|
with self._lock:
|
||||||
self.conn.execute(
|
self.conn.execute(
|
||||||
"""INSERT INTO song_stats (filename, arrangement, seconds_total, updated_at)
|
"""INSERT INTO song_stats (filename, arrangement, seconds_total,
|
||||||
VALUES (?, ?, ?, strftime('%Y-%m-%d %H:%M:%f','now'))
|
last_played_at, updated_at)
|
||||||
|
VALUES (?, ?, ?, strftime('%Y-%m-%d %H:%M:%f','now'),
|
||||||
|
strftime('%Y-%m-%d %H:%M:%f','now'))
|
||||||
ON CONFLICT(filename, arrangement) DO UPDATE SET
|
ON CONFLICT(filename, arrangement) DO UPDATE SET
|
||||||
seconds_total = song_stats.seconds_total + excluded.seconds_total,
|
seconds_total = song_stats.seconds_total + excluded.seconds_total,
|
||||||
|
last_played_at = excluded.last_played_at,
|
||||||
updated_at = excluded.updated_at""",
|
updated_at = excluded.updated_at""",
|
||||||
(filename, int(arrangement), float(seconds)),
|
(filename, int(arrangement), float(seconds)),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -101,6 +101,10 @@
|
|||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify(body),
|
body: JSON.stringify(body),
|
||||||
});
|
});
|
||||||
|
// A 4xx/5xx JSON error body must read as FAILURE — callers
|
||||||
|
// re-queue accrued seconds on null, and a parsed error object
|
||||||
|
// would silently drop them.
|
||||||
|
if (!r.ok) return null;
|
||||||
try { return await r.json(); } catch (e) { return null; }
|
try { return await r.json(); } catch (e) { return null; }
|
||||||
} catch (e) { return null; /* offline / endpoint absent — non-fatal */ }
|
} catch (e) { return null; /* offline / endpoint absent — non-fatal */ }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -480,6 +480,8 @@ def test_seconds_only_post_accrues_without_touching_position(client):
|
|||||||
# overwrite Continue with the end-of-song offset).
|
# overwrite Continue with the end-of-song offset).
|
||||||
assert row["plays"] == 0
|
assert row["plays"] == 0
|
||||||
assert row["last_position"] == pytest.approx(42.0)
|
assert row["last_position"] == pytest.approx(42.0)
|
||||||
|
# But the song WAS played — recency ordering must see it.
|
||||||
|
assert row["last_played_at"]
|
||||||
# Still counts as playing today for the streak.
|
# Still counts as playing today for the streak.
|
||||||
assert r.json()["progress"]["current_streak"] == 1
|
assert r.json()["progress"]["current_streak"] == 1
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user