perf(app,core): bounded image cache, shrunk poster cache, in-place watch updates

- ImageCache drops the picture asked for longest ago past 256 MiB and
  uploads at most six textures a frame
- posters and thumbnails larger than a card are stored scaled down, so
  a cached grid no longer decodes and resizes full backdrops every launch
- a saved watch state patches the library in place instead of rebuilding
  it from the catalog; the offline index reuses the library just built
- download byte progress sends one row, not a sorted copy of every row
- the CJK fallback font is found and read off the UI thread
- the watch-state upsert uses a cached statement
This commit is contained in:
nvrl committed 2026-10-02 19:34:45 +02:00
1 parent 4027ab5fb3
commit ff362d0d08
5 files changed
+441 -116

No files matched your search

+11 -9
View File
@@ -610,7 +610,9 @@ impl Catalog {
/// Record where playback is.
pub fn set_watch_state(&self, share_id: &str, link_id: &str, state: &WatchState) -> Result<()> {
self.conn.execute(
// Cached: playback saves its position every few seconds, and this is
// the one write that runs on a clock rather than on a click.
let mut upsert = self.conn.prepare_cached(
"INSERT INTO watch_state (share_id, link_id, position_secs, duration_secs, watched, updated_at)
VALUES (?1, ?2, ?3, ?4, ?5, ?6)
ON CONFLICT (share_id, link_id) DO UPDATE SET
@@ -618,15 +620,15 @@ impl Catalog {
duration_secs = excluded.duration_secs,
watched = excluded.watched,
updated_at = excluded.updated_at",
params![
share_id,
link_id,
state.position_secs,
state.duration_secs,
state.watched as i64,
state.updated_at,
],
)?;
upsert.execute(params![
share_id,
link_id,
state.position_secs,
state.duration_secs,
state.watched as i64,
state.updated_at,
])?;
Ok(())
}
+37
View File
@@ -331,6 +331,28 @@ impl Library {
self.titles.is_empty()
}
/// Record a new watch state for one file in place. Returns whether the
/// file is in the library.
///
/// What a save during playback or a "mark watched" needs: rebuilding the
/// whole library from the catalog for one changed row re-reads and re-sorts
/// every file, and on a large share that is a visible pause.
pub fn set_watch(&mut self, share_id: &str, link_id: &str, state: WatchState) -> bool {
let episode = self
.titles
.iter_mut()
.flat_map(|title| title.seasons.iter_mut())
.flat_map(|season| season.episodes.iter_mut())
.find(|episode| episode.node.share_id == share_id && episode.node.link_id == link_id);
match episode {
Some(episode) => {
episode.watch = Some(state);
true
}
None => false,
}
}
pub fn get(&self, key: &str) -> Option<&Title> {
self.titles.iter().find(|title| title.key == key)
}
@@ -590,6 +612,21 @@ mod tests {
assert_eq!(library.continue_watching().len(), 1);
}
#[test]
fn a_watch_state_set_in_place_matches_a_rebuild() {
let nodes = vec![
node("s", "1", "Show - S01E01", Some(1), Some(1)),
node("s", "2", "Show - S01E02", Some(1), Some(2)),
];
let mut states = HashMap::new();
let mut library = Library::build(nodes.clone(), &states);
let state = watch(700.0, 1440.0, false, 20);
assert!(library.set_watch("s", "2", state));
states.insert(("s".into(), "2".into()), state);
assert_eq!(library, Library::build(nodes, &states));
assert!(!library.set_watch("s", "9", state));
}
#[test]
fn next_up_falls_through_to_the_first_unwatched() {
let nodes = vec![