mirror of
https://github.com/saphid/frame-control.git
synced 2026-10-06 04:04:21 +02:00
Add IzzyOnDroid to APK alternatives; keep the catalogue's index file
Reducing an index no longer deletes apk-catalog/data/index-v2.json, which the catalogue build reads. Drop the measurement log from docs. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
32196b4260
commit
50405ccf88
6 files changed
+22
-88
No files matched your search
+1
-2
@@ -1,7 +1,6 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
__pycache__/
|
__pycache__/
|
||||||
apk-catalog/data/cache/
|
apk-catalog/data/cache/
|
||||||
apk-catalog/data/index-v2.json*
|
apk-catalog/data/index-v2*.json*
|
||||||
compat-db/.env.lakebed.server
|
compat-db/.env.lakebed.server
|
||||||
compat-db/.lakebed/
|
compat-db/.lakebed/
|
||||||
apk-catalog/data/index-v2.archive.json*
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
cold_seconds 33.303869292
|
|
||||||
fdroid {"package": "org.fdroid.fdroid", "versions": [{"version": "2.0.0", "version_code": 2000050, "min_sdk": 24, "abis": ["arm64-v8a", "armeabi-v7a", "x86", "x86_64"], "name": "/org.fdroid.fdroid_2000050.apk", "sha256": "94938d324b03755fb324f143229240c75640e809cdd3a90cc0a545b5d0a61931", "url": "https://f-droid.org/repo/org.fdroid.fdroid_2000050.apk", "source": "F-Droid"}, {"version": "2.0-rc1", "version_code": 2000041, "min_sdk": 24, "abis": ["arm64-v8a", "armeabi-v7a", "x86", "x86_64"], "name": "/org.fdroid.fdroid_2000041.apk", "sha256": "36b343514ea2cdd353b7554c14b3da3f4309653b67be5ceb2e912b25597e3d85", "url": "https://f-droid.org/repo/org.fdroid.fdroid_2000041.apk", "source": "F-Droid"}, {"version": "2.0-rc0", "version_code": 2000040, "min_sdk": 24, "abis": ["arm64-v8a", "armeabi-v7a", "x86", "x86_64"], "name": "/org.fdroid.fdroid_2000040.apk", "sha256": "cd492ba2d5a425ad80aa22f4b13d9815b2ad487e9b2b6df6b67c31b957b19f2b", "url": "https://f-droid.org/repo/org.fdroid.fdroid_2000040.apk", "source": "F-Droid"}, {"version": "2.0-alpha11", "version_code": 2000011, "min_sdk": 24, "abis": ["arm64-v8a", "armeabi-v7a", "x86", "x86_64"], "name": "/org.fdroid.fdroid_2000011.apk", "sha256": "730f81f2cf860b2671410e3bb7a9f00f99ebd8020aea941c316db1f55cd030e3", "url": "https://f-droid.org/repo/org.fdroid.fdroid_2000011.apk", "source": "F-Droid"}, {"version": "2.0-alpha10", "version_code": 2000010, "min_sdk": 24, "abis": ["arm64-v8a", "armeabi-v7a", "x86", "x86_64"], "name": "/org.fdroid.fdroid_2000010.apk", "sha256": "0b6c65becb0712828625976c6baa76fc40153f77871487b5bc231c3433856b50", "url": "https://f-droid.org/repo/org.fdroid.fdroid_2000010.apk", "source": "F-Droid"}, {"version": "2.0-alpha9", "version_code": 2000009, "min_sdk": 24, "abis": ["arm64-v8a", "armeabi-v7a", "x86", "x86_64"], "name": "/org.fdroid.fdroid_2000009.apk", "sha256": "d6e5eaea63107aed8e46a1c2de3db4c878dcafccb832ec7048798c1275b0a1b7", "url": "https://f-droid.org/repo/org.fdroid.fdroid_2000009.apk", "source": "F-Droid"}, {"version": "2.0-alpha8", "version_code": 2000008, "min_sdk": 24, "abis": ["arm64-v8a", "armeabi-v7a", "x86", "x86_64"], "name": "/org.fdroid.fdroid_2000008.apk", "sha256": "72e139a04fcd0f587a85de5b118245f1cbcf2939874a4f3e298ae6eaea98653c", "url": "https://f-droid.org/repo/org.fdroid.fdroid_2000008.apk", "source": "F-Droid"}, {"version": "2.0-alpha7", "version_code": 2000007, "min_sdk": 24, "abis": ["arm64-v8a", "armeabi-v7a", "x86", "x86_64"], "name": "/org.fdroid.fdroid_2000007.apk", "sha256": "2acb72be96ae91342d52822c1b100edc60c5240eea630e4fc59cb7c4e18d7fbf", "url": "https://f-droid.org/repo/org.fdroid.fdroid_2000007.apk", "source": "F-Droid"}], "total": 250, "links": [{"source": "APKMirror", "url": "https://www.apkmirror.com/?post_type=app_release&s=org.fdroid.fdroid"}, {"source": "APKPure", "url": "https://apkpure.com/search?q=org.fdroid.fdroid"}, {"source": "Uptodown", "url": "https://en.uptodown.com/android/search/org.fdroid.fdroid"}, {"source": "F-Droid", "url": "https://search.f-droid.org/?q=org.fdroid.fdroid"}, {"source": "GitHub", "url": "https://github.com/search?type=repositories&q=org.fdroid.fdroid"}], "note": "Pick a version whose minimum is Android 11 or lower and that has an arm64-v8a build (or no native code). Installable does not mean every feature works.", "errors": []}
|
|
||||||
termux_warm_seconds 0.00022699999999531428
|
|
||||||
termux {"package": "com.termux", "versions": [{"version": "0.119.0-beta.3", "version_code": 1022, "min_sdk": 24, "abis": ["arm64-v8a", "armeabi-v7a", "x86", "x86_64"], "name": "/com.termux_1022.apk", "sha256": "fdd476982cd74f2f00aac12d3683b1fa260a0b2d146411b94e09d773be3a7b56", "url": "https://f-droid.org/repo/com.termux_1022.apk", "source": "F-Droid"}, {"version": "0.119.0-beta.2", "version_code": 1021, "min_sdk": 24, "abis": ["arm64-v8a", "armeabi-v7a", "x86", "x86_64"], "name": "/com.termux_1021.apk", "sha256": "54f472928acf98dd58ba4a1444cac41a4f144e015696ffe1fd81f13a23b997d0", "url": "https://f-droid.org/repo/com.termux_1021.apk", "source": "F-Droid"}, {"version": "0.118.3", "version_code": 1002, "min_sdk": 24, "abis": ["arm64-v8a", "armeabi-v7a", "x86", "x86_64"], "name": "/com.termux_1002.apk", "sha256": "e6265a57eb5ca363808488e3b01955958bed93bc0c8a0d281849b363b11027ec", "url": "https://f-droid.org/repo/com.termux_1002.apk", "source": "F-Droid"}, {"version": "0.118.2", "version_code": 1001, "min_sdk": 24, "abis": ["arm64-v8a", "armeabi-v7a", "x86", "x86_64"], "name": "/com.termux_1001.apk", "sha256": "75a8dd74d6fe69681af450869d872cc6a7ce13dc6ef5f0d9c1b94ed9cc734d50", "url": "https://f-droid.org/archive/com.termux_1001.apk", "source": "F-Droid archive"}, {"version": "0.118.1", "version_code": 1000, "min_sdk": 24, "abis": ["arm64-v8a", "armeabi-v7a", "x86", "x86_64"], "name": "/com.termux_1000.apk", "sha256": "f137958392a800fca583bfc00f191b8edb29b77c705fddf27dffb6c26ca5d413", "url": "https://f-droid.org/archive/com.termux_1000.apk", "source": "F-Droid archive"}, {"version": "0.118.0", "version_code": 118, "min_sdk": 24, "abis": ["arm64-v8a", "armeabi-v7a", "x86", "x86_64"], "name": "/com.termux_118.apk", "sha256": "822ac152bd7c2d9770b87c1feea03f22f2349a91b94481b268c739493a260f0b", "url": "https://f-droid.org/archive/com.termux_118.apk", "source": "F-Droid archive"}, {"version": "0.117", "version_code": 117, "min_sdk": 24, "abis": ["arm64-v8a", "armeabi-v7a", "x86", "x86_64"], "name": "/com.termux_117.apk", "sha256": "956f5945dd616206b1f226c54b1908110a1e8e2e759ba71c6f888577fa739993", "url": "https://f-droid.org/archive/com.termux_117.apk", "source": "F-Droid archive"}, {"version": "0.116", "version_code": 116, "min_sdk": 24, "abis": ["arm64-v8a", "armeabi-v7a", "x86", "x86_64"], "name": "/com.termux_116.apk", "sha256": "fccc12892e3cfda140d23aaefb668e20934231ee5364d7ca8810b8e4ca0d178e", "url": "https://f-droid.org/archive/com.termux_116.apk", "source": "F-Droid archive"}], "total": 71, "links": [{"source": "APKMirror", "url": "https://www.apkmirror.com/?post_type=app_release&s=com.termux"}, {"source": "APKPure", "url": "https://apkpure.com/search?q=com.termux"}, {"source": "Uptodown", "url": "https://en.uptodown.com/android/search/com.termux"}, {"source": "F-Droid", "url": "https://search.f-droid.org/?q=com.termux"}, {"source": "GitHub", "url": "https://github.com/search?type=repositories&q=com.termux"}], "note": "Pick a version whose minimum is Android 11 or lower and that has an arm64-v8a build (or no native code). Installable does not mean every feature works.", "errors": []}
|
|
||||||
warm_mean_seconds 0.000251964583000003
|
|
||||||
cache_bytes 16460680
|
|
||||||
33.97 real 5.68 user 0.64 sys
|
|
||||||
105906176 maximum resident set size
|
|
||||||
0 average shared memory size
|
|
||||||
0 average unshared data size
|
|
||||||
0 average unshared stack size
|
|
||||||
25234 page reclaims
|
|
||||||
24 page faults
|
|
||||||
0 swaps
|
|
||||||
0 block input operations
|
|
||||||
0 block output operations
|
|
||||||
31 messages sent
|
|
||||||
24566 messages received
|
|
||||||
0 signals received
|
|
||||||
259 voluntary context switches
|
|
||||||
22262 involuntary context switches
|
|
||||||
52738979493 instructions retired
|
|
||||||
18814737630 cycles elapsed
|
|
||||||
117670368 peak memory footprint
|
|
||||||
+6
-53
@@ -49,11 +49,12 @@ needs to be confirmed or started in the headset.
|
|||||||
## When an app needs a newer Android
|
## When an app needs a newer Android
|
||||||
|
|
||||||
Lepton is Android 11 (API 30), with arm64-v8a only. If Frame Control refuses
|
Lepton is Android 11 (API 30), with arm64-v8a only. If Frame Control refuses
|
||||||
an APK, it shows compatible versions from F-Droid's main and archive repos.
|
an APK, it shows compatible versions from F-Droid's main and archive repos and
|
||||||
The refusal appears immediately; a separate request checks F-Droid while the
|
IzzyOnDroid. It shows at most eight version names, newest first, preferring an
|
||||||
dialog shows “Checking F-Droid for older versions…”. It shows at most eight
|
arm64-only build, and says how many compatible builds it found in total.
|
||||||
distinct version names, newest first, preferring an arm64-only build when
|
Each index is reduced to its compatible builds once a day and cached (about
|
||||||
available. The count includes all compatible builds before release deduplication.
|
16 MB). The first lookup takes about 30 s and 100 MB of memory; later ones are
|
||||||
|
instant.
|
||||||
Choose **Install** to download a listed version, verify its SHA-256 against
|
Choose **Install** to download a listed version, verify its SHA-256 against
|
||||||
the index, and install it as its own app.
|
the index, and install it as its own app.
|
||||||
|
|
||||||
@@ -324,51 +325,3 @@ because gamescope scales Lepton's surface to fit the same panel. Also unverified
|
|||||||
whether the settings survive the app or its Lepton instance relaunching.
|
whether the settings survive the app or its Lepton instance relaunching.
|
||||||
Lepton Development rebuilds its Android data on exit, so there they probably
|
Lepton Development rebuilds its Android data on exit, so there they probably
|
||||||
don't.
|
don't.
|
||||||
|
|
||||||
## Version lookup performance
|
|
||||||
|
|
||||||
Measured on 2026-09-28 with Python 3.9.6 against live f-droid.org, using an
|
|
||||||
empty temporary cache. Both repositories download sequentially under one lock.
|
|
||||||
The reducer decodes one package at a time with the standard library; the
|
|
||||||
`.installable-v1` files contain only compatible build records grouped by package.
|
|
||||||
Raw downloads are removed after reduction. The compact files refresh after one
|
|
||||||
day and are retained in memory by file mtime. Install uses the cached records
|
|
||||||
without refreshing the indexes or rerunning release selection.
|
|
||||||
|
|
||||||
- Cold lookup: 33.304 s (process wall time 33.97 s).
|
|
||||||
- Maximum RSS: 105,906,176 bytes (101 MiB).
|
|
||||||
- Warm F-Droid lookup: 0.252 ms mean over 1,000 calls.
|
|
||||||
- Warm Termux lookup: 0.227 ms.
|
|
||||||
- Combined reduced cache: 16,460,680 bytes (15.7 MiB).
|
|
||||||
- F-Droid: 8 of 250 compatible builds, newest 2.0.0 (code 2000050).
|
|
||||||
- Termux: 8 of 71 compatible builds, newest 0.119.0-beta.3 (code 1022).
|
|
||||||
- Both repository lookups returned no errors.
|
|
||||||
|
|
||||||
Full results and `/usr/bin/time -l` output: [measurement](apk-versions-measurement.txt).
|
|
||||||
Network timings depend on the connection and repository state. Reproduce with:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
/usr/bin/time -l python3 -c '
|
|
||||||
import json, sys, tempfile, time
|
|
||||||
sys.path.insert(0, "ui")
|
|
||||||
import frame_catalog as c, frame_apk_versions as v
|
|
||||||
with tempfile.TemporaryDirectory() as directory:
|
|
||||||
c.CATALOG = directory
|
|
||||||
start = time.perf_counter()
|
|
||||||
print(json.dumps(v.alternatives("org.fdroid.fdroid")))
|
|
||||||
print("cold_seconds", time.perf_counter() - start)
|
|
||||||
print(json.dumps(v.alternatives("com.termux")))
|
|
||||||
start = time.perf_counter()
|
|
||||||
for _ in range(1000):
|
|
||||||
v.alternatives("org.fdroid.fdroid")
|
|
||||||
print("warm_mean_seconds", (time.perf_counter() - start) / 1000)
|
|
||||||
'
|
|
||||||
```
|
|
||||||
|
|
||||||
Verification: `python3 -m unittest discover -s tests` passed 138 tests;
|
|
||||||
`python3 -m py_compile ui/*.py`, inline JavaScript `node --check`, and
|
|
||||||
`git diff --check` passed. Tests include concurrent cache use, failed refresh,
|
|
||||||
mtime invalidation, streaming boundaries, cap/deduplication, endpoint validation,
|
|
||||||
nonblocking refusal, and cached installation identity checks. No browser visual
|
|
||||||
verification or physical headset installation was performed. Independent model
|
|
||||||
review was omitted to honor the task's explicit no-delegation instruction.
|
|
||||||
@@ -28,7 +28,8 @@ class VersionsTest(unittest.TestCase):
|
|||||||
os.mkdir(data)
|
os.mkdir(data)
|
||||||
for name, builds in [('index-v2.json', [build(5, 33), build(4, abis=['x86_64']),
|
for name, builds in [('index-v2.json', [build(5, 33), build(4, abis=['x86_64']),
|
||||||
build(3, abis=['arm64-v8a', 'x86_64']), build(2)]),
|
build(3, abis=['arm64-v8a', 'x86_64']), build(2)]),
|
||||||
('index-v2.archive.json', [build(1, 21), build(2)])]:
|
('index-v2.archive.json', [build(1, 21), build(2)]),
|
||||||
|
('index-v2.izzy.json', [])]:
|
||||||
with open(os.path.join(data, name), 'w') as f:
|
with open(os.path.join(data, name), 'w') as f:
|
||||||
json.dump({'packages': {'org.example.app': {'versions': {str(i): b for i, b in enumerate(builds)}}}}, f)
|
json.dump({'packages': {'org.example.app': {'versions': {str(i): b for i, b in enumerate(builds)}}}}, f)
|
||||||
self.enter_patch(patch.object(frame_catalog, 'CATALOG', self.tmp.name))
|
self.enter_patch(patch.object(frame_catalog, 'CATALOG', self.tmp.name))
|
||||||
@@ -63,7 +64,7 @@ class VersionsTest(unittest.TestCase):
|
|||||||
def test_failed_indexes_keep_search_links(self):
|
def test_failed_indexes_keep_search_links(self):
|
||||||
with patch.object(frame_catalog, 'load_index', side_effect=OSError('offline')):
|
with patch.object(frame_catalog, 'load_index', side_effect=OSError('offline')):
|
||||||
result = versions.alternatives('org.example.app')
|
result = versions.alternatives('org.example.app')
|
||||||
self.assertEqual(len(result['errors']), 2)
|
self.assertEqual(len(result['errors']), 3)
|
||||||
self.assertEqual(len(result['links']), 5)
|
self.assertEqual(len(result['links']), 5)
|
||||||
|
|
||||||
def test_no_compatible_versions(self):
|
def test_no_compatible_versions(self):
|
||||||
@@ -79,7 +80,8 @@ class VersionsTest(unittest.TestCase):
|
|||||||
self.assertEqual(set(index['org.example.app'][0]),
|
self.assertEqual(set(index['org.example.app'][0]),
|
||||||
{'version', 'version_code', 'min_sdk', 'abis', 'name', 'sha256'})
|
{'version', 'version_code', 'min_sdk', 'abis', 'name', 'sha256'})
|
||||||
raw = os.path.join(self.tmp.name, 'data', 'index-v2.json')
|
raw = os.path.join(self.tmp.name, 'data', 'index-v2.json')
|
||||||
self.assertFalse(os.path.exists(raw))
|
self.assertTrue(os.path.exists(raw)) # the catalogue build reads it
|
||||||
|
os.utime(raw, ns=(1, 1))
|
||||||
with patch.object(frame_catalog.json, 'load', side_effect=AssertionError('reparsed')):
|
with patch.object(frame_catalog.json, 'load', side_effect=AssertionError('reparsed')):
|
||||||
self.assertIs(frame_catalog.load_index(repo), index)
|
self.assertIs(frame_catalog.load_index(repo), index)
|
||||||
path = raw + '.installable-v1'
|
path = raw + '.installable-v1'
|
||||||
@@ -91,7 +93,7 @@ class VersionsTest(unittest.TestCase):
|
|||||||
with patch.object(frame_catalog.urllib.request, 'urlopen', return_value=io.BytesIO(payload)) as fetch:
|
with patch.object(frame_catalog.urllib.request, 'urlopen', return_value=io.BytesIO(payload)) as fetch:
|
||||||
self.assertEqual(frame_catalog.load_index(repo)['org.example.app'][0]['version_code'], 9)
|
self.assertEqual(frame_catalog.load_index(repo)['org.example.app'][0]['version_code'], 9)
|
||||||
fetch.assert_called_once()
|
fetch.assert_called_once()
|
||||||
self.assertFalse(os.path.exists(raw))
|
self.assertTrue(os.path.exists(raw))
|
||||||
|
|
||||||
def test_concurrent_requests_share_download(self):
|
def test_concurrent_requests_share_download(self):
|
||||||
raw = os.path.join(self.tmp.name, 'data', 'index-v2.json')
|
raw = os.path.join(self.tmp.name, 'data', 'index-v2.json')
|
||||||
@@ -108,6 +110,7 @@ class VersionsTest(unittest.TestCase):
|
|||||||
index = frame_catalog.load_index(repo)
|
index = frame_catalog.load_index(repo)
|
||||||
path = os.path.join(self.tmp.name, 'data', 'index-v2.json.installable-v1')
|
path = os.path.join(self.tmp.name, 'data', 'index-v2.json.installable-v1')
|
||||||
os.utime(path, ns=(1, 1))
|
os.utime(path, ns=(1, 1))
|
||||||
|
os.utime(os.path.join(self.tmp.name, 'data', 'index-v2.json'), ns=(1, 1))
|
||||||
with patch.object(frame_catalog.urllib.request, 'urlopen', return_value=io.BytesIO(b'{')):
|
with patch.object(frame_catalog.urllib.request, 'urlopen', return_value=io.BytesIO(b'{')):
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
frame_catalog.load_index(repo)
|
frame_catalog.load_index(repo)
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ ANDROID = dict(enumerate([
|
|||||||
'12L', '13', '14', '15', '16',
|
'12L', '13', '14', '15', '16',
|
||||||
], 1))
|
], 1))
|
||||||
REPOS = (('F-Droid', 'https://f-droid.org/repo/'),
|
REPOS = (('F-Droid', 'https://f-droid.org/repo/'),
|
||||||
('F-Droid archive', 'https://f-droid.org/archive/'))
|
('F-Droid archive', 'https://f-droid.org/archive/'),
|
||||||
|
('IzzyOnDroid', 'https://apt.izzysoft.de/fdroid/repo/'))
|
||||||
NOTE = ('Pick a version whose minimum is Android 11 or lower and that has an '
|
NOTE = ('Pick a version whose minimum is Android 11 or lower and that has an '
|
||||||
'arm64-v8a build (or no native code). Installable does not mean every feature works.')
|
'arm64-v8a build (or no native code). Installable does not mean every feature works.')
|
||||||
|
|
||||||
|
|||||||
+6
-4
@@ -17,7 +17,11 @@ import frame_compat_db as compat_db # noqa: E402
|
|||||||
# the per-user cache (FRAME_CONTROL_APP is set by app/main.js).
|
# the per-user cache (FRAME_CONTROL_APP is set by app/main.js).
|
||||||
CACHE = (str(frame_host.cache_dir('apk')) if os.environ.get('FRAME_CONTROL_APP') or '.app/Contents/Resources' in CATALOG
|
CACHE = (str(frame_host.cache_dir('apk')) if os.environ.get('FRAME_CONTROL_APP') or '.app/Contents/Resources' in CATALOG
|
||||||
else os.path.join(CATALOG, 'data', 'cache'))
|
else os.path.join(CATALOG, 'data', 'cache'))
|
||||||
APK_HOSTS = ('https://f-droid.org/repo/', 'https://f-droid.org/archive/')
|
# Repo base URL -> local name of its index; every APK download must come from one of these.
|
||||||
|
INDEX_FILES = {'https://f-droid.org/repo/': 'index-v2.json',
|
||||||
|
'https://f-droid.org/archive/': 'index-v2.archive.json',
|
||||||
|
'https://apt.izzysoft.de/fdroid/repo/': 'index-v2.izzy.json'}
|
||||||
|
APK_HOSTS = tuple(INDEX_FILES)
|
||||||
_lock = threading.Lock()
|
_lock = threading.Lock()
|
||||||
_cache = {'mtime': None, 'sig': None, 'apps': None, 'by_pkg': None}
|
_cache = {'mtime': None, 'sig': None, 'apps': None, 'by_pkg': None}
|
||||||
_env = {}
|
_env = {}
|
||||||
@@ -112,7 +116,7 @@ def load_index(repo, cached_only=False):
|
|||||||
if repo not in APK_HOSTS:
|
if repo not in APK_HOSTS:
|
||||||
raise ValueError('unexpected index URL')
|
raise ValueError('unexpected index URL')
|
||||||
directory = CACHE if os.environ.get('FRAME_CONTROL_APP') or '.app/Contents/Resources' in CATALOG else os.path.join(CATALOG, 'data')
|
directory = CACHE if os.environ.get('FRAME_CONTROL_APP') or '.app/Contents/Resources' in CATALOG else os.path.join(CATALOG, 'data')
|
||||||
filename = 'index-v2.json' if repo == APK_HOSTS[0] else 'index-v2.archive.json'
|
filename = INDEX_FILES[repo]
|
||||||
raw = os.path.join(directory, filename)
|
raw = os.path.join(directory, filename)
|
||||||
path = raw + '.installable-v1'
|
path = raw + '.installable-v1'
|
||||||
with _index_lock:
|
with _index_lock:
|
||||||
@@ -143,8 +147,6 @@ def load_index(repo, cached_only=False):
|
|||||||
os.utime(tmp, ns=(refreshed, refreshed))
|
os.utime(tmp, ns=(refreshed, refreshed))
|
||||||
os.replace(tmp, path)
|
os.replace(tmp, path)
|
||||||
_indexes[path] = (os.stat(path).st_mtime_ns, index)
|
_indexes[path] = (os.stat(path).st_mtime_ns, index)
|
||||||
if os.path.exists(raw):
|
|
||||||
os.remove(raw)
|
|
||||||
return index
|
return index
|
||||||
finally:
|
finally:
|
||||||
if os.path.exists(tmp):
|
if os.path.exists(tmp):
|
||||||
|
|||||||
Reference in new issue
Block a user