Store: pruning rechecks each APK before deleting and spares ones being installed

Deletion re-stats under a lock shared with touch() (F-Droid cache reuse) and
claim()/release() (held by the store around install), so a reused or
installing APK is never removed from an out-of-date scan.

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 22:29:39 +10:00
1 parent 4fd8bb79af
commit a7261b1601
5 files changed
+91 -13

No files matched your search

+25 -1
View File
@@ -1,4 +1,4 @@
import hashlib, io, json, os, sys, tempfile, unittest, urllib.error, urllib.request, zipfile
import hashlib, io, json, os, sys, tempfile, time, unittest, urllib.error, urllib.request, zipfile
from pathlib import Path
from unittest.mock import patch
@@ -173,6 +173,30 @@ class PublisherSources(unittest.TestCase):
self.assertEqual([p.exists() for p in (oldest, old, kept, recent)], [False, False, True, True])
self.assertEqual([p.exists() for p in (orphan, busy, listing, fresh, tmpdir)], [False, True, False, True, False])
def test_prune_rechecks_before_deleting_and_spares_apks_in_use(self):
self.root.mkdir(parents=True)
old = time.time() - 7200
reused, claimed, stale = self.root / 'a.apk', self.root / 'b.apk', self.root / 'c.apk'
for path in (reused, claimed, stale):
path.write_bytes(b'x' * 40)
_web.claim(claimed)
self.addCleanup(_web.release, claimed)
for path in (reused, claimed, stale):
os.utime(str(path), (old, old))
real = os.listdir
def listdir(folder):
if folder == self.tmp.name: # scanning the second folder: a download reuses a.apk meanwhile
self.assertTrue(_web.touch(reused))
return real(folder)
with patch.object(_web, 'APK_CAP', 10), patch.object(_web.os, 'listdir', listdir):
_web.prune()
self.assertEqual([p.exists() for p in (reused, claimed, stale)], [True, True, False])
_web.release(claimed)
with patch.object(_web, 'APK_CAP', 10):
_web.prune()
self.assertFalse(claimed.exists())
self.assertFalse(_web.touch(stale)) # a pruned APK is reported gone, so it's downloaded again
def test_backoff_honours_retry_after_per_host(self):
from apk_sources import SourceLimited
from email.utils import formatdate