importadded: preserve_write_mtimes targets the written file, not the source - #6992
Huang-404-Q wants to merge 7 commits into
Conversation
…source The after_write listener passed the written file's path to write_item_mtime, which ignored it and always rewrote the mtime of the item's own file. When beet convert wrote tags to a converted file, the plugin instead reset the mtime of the original source file, and crashed with a PermissionError when that file was read-only (e.g. immutable). Write item.added to the file that was actually written, and update the item's mtime field only when that file is the item's own. Fixes beetbox#6954
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #6992 +/- ##
=======================================
Coverage 77.27% 77.27%
=======================================
Files 163 163
Lines 21845 21847 +2
Branches 3370 3371 +1
=======================================
+ Hits 16881 16883 +2
Misses 4147 4147
Partials 817 817
🚀 New features to boost your workflow:
|
snejus
left a comment
There was a problem hiding this comment.
Thanks! Just move the changelog under the Unreleased section please.
Merging master (b07203f) dropped master's four Unreleased "Bug fixes" entries, commented the heading out, and left this branch's beetbox#6954 entry inside the released 2.14.0 section — which is what the docs check rejects: docs/changelog.rst:69: Changelog entry must be added under Unreleased section above. (changelog-unreleased) Take master's changelog and append the entry to the bottom of the Unreleased Bug fixes list, as the original commit had it. The four dropped entries come back with it. Verified against the check's own command: it reports the violation on the previous head and passes here.
|
The Root cause was the merge, not the original commit. Fixed by taking master's changelog and appending the Verified with the check's own command from Net diff against |
|
I'll allow this one as the policy had not been present when this PR was opened. |
importadded: preserve_write_mtimes targets the written file, not the source
What this fixes
With
importaddedandpreserve_write_mtimesenabled,beet convertcrashes when the source file is read-only (in my case, files with the
immutable bit set):
The conversion itself never needs to write the source file — but the
plugin does.
Why it happens
update_after_write_time(item, path)is invoked from theafter_writeevent that
Item.write()sends at the end of every write(
beets/library/models.py), wherepathis the file that was justwritten. Under
beet convertthat is the converted file(
item.try_write(path=converted)), not the source. The plugin currentlyignores
pathand unconditionally callswrite_item_mtime(item, ...),which does
os.utimeonitem.path— the source file — and stompsitem.mtimeeven though the source was not the file being written.The fix
Point the mtime write at
path(the file actually written), and onlysync
item.mtimewhenpathis the item's own file:This mirrors what
Item.write()itself does(
if path == self.path: self.mtime = self.current_mtime()), so the DBfield stays consistent with the same rule the library already applies.
Ordinary writes (
beet write, tagging after import) passpath == item.path, so their behavior is unchanged — all existingimportaddedtests pass as-is.Tests
New
test/plugins/test_importadded_convert.py(2 tests, convert +importadded with both preserve options on):
test_convert_does_not_touch_source_mtime: the source file's mtime isleft alone and the converted file carries the
addedmtime.test_convert_with_readonly_source: a source whoseos.utimeraisesPermissionErrorno longer crashes the convert.Red without the fix (2 failed: mtime assert + the PermissionError from
the issue), green with it.
pytest -k "importadded or convert": 48passed, 6 skipped (optional dependencies).
ruff checkandruff format --checkclean.Fixes #6954