fix: tolerate files vanishing mid-scan in find_download
The sort key in find_download() called item.stat() unprotected. The search roots include the live Downloads folder, where Chrome renames "*.crdownload" files to their final name between directory listing and stat(), crashing the whole export with FileNotFoundError. Stat once while collecting (skipping entries that raise OSError), then sort the snapshot. This also halves the stat() calls per polling round. Fixes the remaining race reported in #4. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
f77ee8dfcb
commit
2730832c3f
@@ -163,6 +163,37 @@ class ExportPptxTests(unittest.TestCase):
|
||||
found = MODULE.find_download([root], timeout=2.0, since=since)
|
||||
self.assertEqual(found.resolve(), new.resolve())
|
||||
|
||||
def test_find_download_survives_files_vanishing_mid_scan(self):
|
||||
# Chrome renames "*.crdownload" files away between directory listing
|
||||
# and stat(); a vanished file must be skipped, not crash the export.
|
||||
with tempfile.TemporaryDirectory() as name:
|
||||
root = Path(name)
|
||||
deck = root / "deck.pptx"
|
||||
with zipfile.ZipFile(deck, "w") as archive:
|
||||
archive.writestr(
|
||||
"[Content_Types].xml",
|
||||
'<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">'
|
||||
'<Override PartName="/ppt/presentation.xml" '
|
||||
f'ContentType="{MODULE.PPTX_CONTENT_TYPE}"/></Types>',
|
||||
)
|
||||
archive.writestr("ppt/presentation.xml", "<p:presentation/>")
|
||||
ghost = root / "ghost.crdownload"
|
||||
ghost.write_bytes(b"partial download")
|
||||
|
||||
real_stat = Path.stat
|
||||
seen = {"count": 0}
|
||||
|
||||
def racy_stat(self, **kwargs):
|
||||
if self.name == "ghost.crdownload":
|
||||
seen["count"] += 1
|
||||
if seen["count"] > 1:
|
||||
raise FileNotFoundError(2, "vanished mid-scan", str(self))
|
||||
return real_stat(self, **kwargs)
|
||||
|
||||
with patch.object(Path, "stat", racy_stat):
|
||||
found = MODULE.find_download([root], timeout=2.0)
|
||||
self.assertEqual(found.resolve(), deck.resolve())
|
||||
|
||||
def test_browser_open_does_not_pass_download_path(self):
|
||||
session = MODULE.BrowserSession(
|
||||
"/bin/agent-browser",
|
||||
|
||||
Reference in New Issue
Block a user