Merge origin/main: background install jobs and tabbed pages

Flatpak installs record their outcome inside main's background job; failed
jobs are diagnostics too. The Privacy panel lives on the Tools page (#privacy
opens it), tab analytics use the four page names, and "Test it now?" reads the
install job's result.

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 17:37:28 +10:00
commit 97d70d0c80
106 files changed
+9056 -253

No files matched your search

+28 -16
View File
@@ -30,6 +30,7 @@ FIELDS = ('package', 'version', 'result', 'rating', 'notes', 'via', 'date', 'ste
'label', 'source')
TTL = 60 # seconds a fetched copy is reused
_lock = threading.Lock()
_load_lock = threading.Lock() # refreshing the cached reports (load); separate from _lock, which flush takes
_mem = {'at': 0, 'reports': None, 'source': None}
@@ -185,25 +186,36 @@ def _read_mirror():
return []
def _save_mirror(reports):
"""Keep a copy for offline use. Failing to write it mustn't fail the read."""
try:
os.makedirs(STATE, exist_ok=True)
with open(MIRROR + '.tmp', 'w') as f:
json.dump({'fetched': time.strftime('%Y-%m-%dT%H:%M:%S'), 'reports': reports}, f)
os.replace(MIRROR + '.tmp', MIRROR)
except OSError:
pass
def load():
"""All reports: the database (cached for TTL s), else the offline mirror; plus unsent ones."""
now = time.time()
if _mem['reports'] is None or now - _mem['at'] > TTL:
try:
if not shared():
raise DBError('no key')
# The page asks for the catalogue and the reports at once: one refresh at a
# time, so they share a fetch and never write the mirror's .tmp together.
with _load_lock:
now = time.time()
if _mem['reports'] is None or now - _mem['at'] > TTL:
try:
flush()
except Exception:
pass # sending can fail for any reason; reading must still work
reports, source = fetch_all(), 'lakebed'
os.makedirs(STATE, exist_ok=True)
with open(MIRROR + '.tmp', 'w') as f:
json.dump({'fetched': time.strftime('%Y-%m-%dT%H:%M:%S'), 'reports': reports}, f)
os.replace(MIRROR + '.tmp', MIRROR)
except DBError:
reports, source = _read_mirror(), 'mirror'
_mem.update(at=now, reports=reports, source=source)
if not shared():
raise DBError('no key')
try:
flush()
except Exception:
pass # sending can fail for any reason; reading must still work
reports, source = fetch_all(), 'lakebed'
_save_mirror(reports)
except DBError:
reports, source = _read_mirror(), 'mirror'
_mem.update(at=now, reports=reports, source=source)
sent = {r.get('id') for r in _mem['reports']}
return _mem['reports'] + [r for r in _outbox() if r['id'] not in sent]