mirror of
https://github.com/saphid/frame-control.git
synced 2026-10-06 06:00:33 +02:00
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:
1 parent
4fd8bb79af
commit
a7261b1601
5 files changed
+91
-13
No files matched your search
@@ -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
|
||||
|
||||
@@ -33,6 +33,12 @@ class SettingsTest(unittest.TestCase):
|
||||
self.addCleanup(p.stop)
|
||||
for state in (search._running, search._pending, search._status, search._game_data):
|
||||
state.clear()
|
||||
from apk_sources import _web
|
||||
self.claims = []
|
||||
for name in ('claim', 'release'): # fake downloads aren't real files
|
||||
p = patch.object(_web, name, side_effect=lambda path, name=name: self.claims.append((name, path)))
|
||||
p.start()
|
||||
self.addCleanup(p.stop)
|
||||
|
||||
|
||||
class SearchTests(SettingsTest):
|
||||
@@ -204,6 +210,17 @@ class SearchTests(SettingsTest):
|
||||
search.install('one', 'brush')
|
||||
install.assert_not_called()
|
||||
|
||||
def test_downloaded_apk_is_protected_from_pruning_while_installing(self):
|
||||
mod = fake()
|
||||
def install(apk, **kwargs):
|
||||
self.assertEqual(self.claims, [('claim', '/fake.apk')])
|
||||
raise server.frame_android.FrameError('adb failed')
|
||||
with patch.object(search, 'modules', return_value=([mod], [])), \
|
||||
patch.object(server.frame_android, 'install', install):
|
||||
with self.assertRaises(server.frame_android.FrameError):
|
||||
search.install('one', 'brush')
|
||||
self.assertEqual(self.claims, [('claim', '/fake.apk'), ('release', '/fake.apk')])
|
||||
|
||||
def test_listing_cannot_download(self):
|
||||
mod = fake()
|
||||
mod.details = lambda s, i: dict(ENTRIES[0], downloadable=False)
|
||||
|
||||
Reference in new issue
Block a user