diff --git a/README.md b/README.md index d5cb336..753f2a8 100644 --- a/README.md +++ b/README.md @@ -48,11 +48,13 @@ SD-kaartlezer die midden in de transfer offline klapt, `I/O error`, `device offl verwijderd, wordt eerst een bestand van de kaart gelezen. Een onleesbare kaart resulteert in een duidelijke Matrix-"GESTOPT" en **niets** wordt gewist. - **Retry per bestand** — 3 pogingen met 2s pauze; vangt transiënte USB-glitches. -- **`shutil.copy` i.p.v. `copy2`** — `copy2` preserveert tijdsstempels via `os.utime`; - op de exfat-SSD (`uid=1000,allow_utime=0022`) weigert dat voor niet-eigenaars - (EPERM). Sinds de service als `aiaiaiaiagent` draait (werd voorheen als root - gedraaid) faalde daardoor elke kopie. Mode is op exfat altijd 0777 en het - opnamemoment staat al in de bestandsnaam — metadata-preserve voegt niets toe. +- **`shutil.copyfile` i.p.v. `copy`/`copy2`** — `copy`/`copy2` doen na het kopiëren + ook metadata-operaties op het doel (`chmod`, `utime`). Op de exfat-SSD + (`uid=1000` → álle bestanden zijn eigendom van uid 1000, `allow_utime=0022`) + worden die geweigerd voor een niet-eigenaar (EPERM); sinds de service als + `aiaiaiaiagent` draait (voorheen root) faalde daardoor elke kopie. `copyfile` + schrijft alleen data; mode is op exfat altijd 0777 en het opnamemoment staat al + in de bestandsnaam — metadata-operaties voegen niets toe. - **Early-abort** — bij 5 opeenvolgende fouten stopt het script en stuurt het een Matrix-bericht met de laatste fout. Bronbestanden worden nooit verwijderd zonder geverifieerde kopie. diff --git a/dashcam-transfer.py b/dashcam-transfer.py index 199118c..bbc4b1b 100644 --- a/dashcam-transfer.py +++ b/dashcam-transfer.py @@ -153,12 +153,13 @@ def copy_file(src, dst, attempts=MAX_RETRIES, delay=RETRY_DELAY): last_error = None for attempt in range(1, attempts + 1): try: - # copy (niet copy2): copy2 preserveert timestamps via os.utime, en exfat - # mountuid=1000 + allow_utime=0022 weigeren dat voor niet-eigenaars — - # daarmee faalde elke kopie toen de service als aiaiaiaiagent ging - # draaien (EPERM). Op exfat is mode altijd 0777 en staat het - # opnamemoment al in de bestandsnaam (YYYYMMDDHHMMSS). - shutil.copy(src, dst) + # copyfile (niet copy/copy2): die doen ook metadata-operaties op het + # doel — chmod en utime. Op exfat met mountuid=1000 zijn álle bestanden + # eigendom van uid 1000, en allow_utime=0022 + chmod-toestand weigeren + # dat voor de niet-eigenaar service-gebruiker (EPERM op elke kopie). + # copyfile schrijft alleen data; mode is op exfat altijd 0777 en het + # opnamemoment staat al in de bestandsnaam (YYYYMMDDHHMMSS). + shutil.copyfile(src, dst) return except OSError as e: last_error = e diff --git a/tests/test_dashcam_transfer.py b/tests/test_dashcam_transfer.py index c108aff..5ab2973 100644 --- a/tests/test_dashcam_transfer.py +++ b/tests/test_dashcam_transfer.py @@ -85,7 +85,7 @@ class CopyFileTest(unittest.TestCase): self.assertEqual(os.path.getsize(self.dst), 100) def test_retries_then_succeeds(self): - real_copy = dashcam.shutil.copy + real_copy = dashcam.shutil.copyfile calls = {"n": 0} def flaky(s, d): @@ -94,13 +94,13 @@ class CopyFileTest(unittest.TestCase): raise OSError(errno.EIO, "I/O error") real_copy(s, d) - with mock.patch.object(dashcam.shutil, "copy", side_effect=flaky): + with mock.patch.object(dashcam.shutil, "copyfile", side_effect=flaky): dashcam.copy_file(self.src, self.dst, attempts=3, delay=0) self.assertEqual(calls["n"], 3) self.assertEqual(os.path.getsize(self.dst), 100) def test_raises_after_exhausting_attempts(self): - with mock.patch.object(dashcam.shutil, "copy", side_effect=OSError(errno.EIO, "I/O error")): + with mock.patch.object(dashcam.shutil, "copyfile", side_effect=OSError(errno.EIO, "I/O error")): with self.assertRaises(OSError): dashcam.copy_file(self.src, self.dst, attempts=2, delay=0) @@ -159,7 +159,7 @@ class TransferFilesTest(unittest.TestCase): with open(d, "wb") as f: f.write(b"0" * 50) - with mock.patch.object(dashcam.shutil, "copy", side_effect=bad_copy), mock.patch.object( + with mock.patch.object(dashcam.shutil, "copyfile", side_effect=bad_copy), mock.patch.object( dashcam.time, "sleep" ): ok, errors = dashcam.transfer_files(files, self.src, self.base) @@ -172,7 +172,7 @@ class TransferFilesTest(unittest.TestCase): for i in range(dashcam.MAX_CONSECUTIVE_ERRORS + 3) ] with mock.patch.object( - dashcam.shutil, "copy", side_effect=OSError(errno.EIO, "I/O error") + dashcam.shutil, "copyfile", side_effect=OSError(errno.EIO, "I/O error") ), mock.patch.object(dashcam.time, "sleep"): with self.assertRaises(dashcam.TransferAborted) as ctx: dashcam.transfer_files(files, self.src, self.base)