Library artwork backfill: devkit titles in refresh-art; missing art offered and re-applied

- 'Refresh artwork' (settings) and the API's refresh-art --all cover devkit
  titles as well as Android apps; frame_titles.py gains refresh-art ID|--all.
- Apps and titles without complete Steam artwork are flagged (art_missing):
  the app shows 'Add artwork', and the CLIs' list prints the refresh command.
- When the app lists them and Steam answers, Frame Control re-applies their
  art in the background (at most every five minutes), e.g. for a title Steam
  registered after an install made while it wasn't running.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
saphidandClaude Opus 5.5 committed 2026-09-28 23:06:48 +10:00
1 parent f0db805d4b
commit fde2f9620a
5 files changed
+94 -10

No files matched your search

+29 -4
View File
@@ -81,12 +81,37 @@ class EntryPoints(unittest.TestCase):
self.assertEqual(result['kind'], 'apk')
self.assert_art()
def test_refresh_api(self):
def test_refresh_api_covers_android_apps_and_titles(self):
with patch.object(server, 'ensure_master'), \
patch.object(server, 'start_job', side_effect=lambda label, work: work()), \
patch.object(android, 'refresh_art', return_value=[]) as refresh:
self.assertEqual(server.android({'action':'refresh-art', 'all':True}), {'apps':[]})
refresh.assert_called_once_with(None)
patch.object(android, 'refresh_art', return_value=[]) as refresh, \
patch.object(titles, 'refresh_art', return_value=[{'id':'G','error':'x'}]) as title_refresh:
self.assertEqual(server.android({'action':'refresh-art', 'all':True}),
{'apps':[], 'titles':[{'id':'G','error':'x'}]})
refresh.assert_called_once_with(); title_refresh.assert_called_once_with()
server.titles({'action':'refresh-art', 'id':'G'})
title_refresh.assert_called_with('G')
def test_backfill_applies_missing_art_once_steam_answers(self):
import threading
ran = threading.Event()
with patch.dict(server._backfill, {'running': False, 'last': 0.0}), \
patch.object(android, 'refresh_art', side_effect=[RuntimeError('odd'), None]) as refresh, \
patch.object(titles, 'refresh_art', side_effect=lambda gid: ran.set()) as title_refresh:
apps = [{'package':'org.a.x','art_missing':True}, {'package':'org.b.x','art_missing':True},
{'package':'org.c.x','art_missing':False}]
with contextlib.redirect_stderr(io.StringIO()):
self.assertTrue(server.backfill_art(apps=apps, titles=[{'id':'G','art_missing':True}]))
self.assertTrue(ran.wait(5))
self.assertFalse(server.backfill_art(apps=apps)) # throttled
self.assertEqual([c.args for c in refresh.call_args_list], [('org.a.x',), ('org.b.x',)])
title_refresh.assert_called_once_with('G')
self.assertFalse(server.backfill_art(apps=[{'package':'org.c.x','art_missing':False}]))
def test_art_missing_flags(self):
self.assertTrue(android.art_missing({'artwork': {}}))
self.assertTrue(android.art_missing({'artwork': {'grid': 'x'}}))
self.assertFalse(android.art_missing({'artwork': {s: 'x' for s in artwork.SLOTS}}))
def test_source_search_shared_installer_contract(self):
# Source workers hand their download and optional images to this public seam.