test: prove the database lands at the device root, not in the music folder #10

Merged
lyrathorpe merged 4 commits from feat/database-in-sync into main 2026-08-26 20:38:53 +01:00
3 changed files with 45 additions and 21 deletions
Showing only changes of commit 19ac9e5d92 - Show all commits
+15 -7
View File
@@ -178,12 +178,20 @@ entirely for the first two seconds, where the window is microseconds wide and
would report gigabytes per second.
rsync says nothing at all while it builds its file list, which on fifty
thousand files over USB is minutes of apparent hang, and its own `progress2`
percentage is computed against a list it has not finished discovering. So the
script counts first — files and bytes both, a second pass over the tree, which
is what a percentage and an estimate that mean something cost — and renders the
rest itself. Piped to a log it prints a plain line every thirty seconds
instead, with no carriage returns, and a summary at the end either way.
thousand files is minutes of apparent hang, and its own `progress2` percentage
is computed against a list it has not finished discovering. So the script
renders its own.
**The percentage and the estimate are opt-in, via `-P`.** They need a total,
the total needs a counting pass, and that pass walks and compares both trees in
full exactly as the transfer does. Measured on a real card: read from the
source at 35 MB/s and write to the card at 21 MB/s, yet the sync crawled —
because the traversal, not the data, was the cost, and it was being paid twice.
Without `-P` the line still shows the running count, the rate and the album in
flight; only the two figures that needed the second walk are missing.
Piped to a log it prints a plain line every thirty seconds instead, with no
carriage returns, and a summary at the end either way.
### The Rockbox database
@@ -260,7 +268,7 @@ worth doing:
| ----- | --- |
| Mount the source with `actimeo=60,cache=loose` | SMB defaults to a **one second** attribute cache, so nearly every `stat` goes to the wire — twice, once per pass. This is the single biggest change and it is a mount option, not an rsync flag. |
| Put the card in a reader for the first load | USB 2.0 through an iPod in disk mode is the floor for the destination. No amount of source tuning gets past it. |
| `-Q` | Skips the counting pass entirely. Costs the percentage and the estimate, saves a whole walk of the tree. |
| Counting is off by default | The percentage costs a second full traversal of both trees. On a FAT card of fifty thousand files that is slower than the transfer. `-P` asks for it. |
| `--whole-file`, `--omit-dir-times` | Already set. The first stops rsync checksumming destination files it is about to overwrite whole; the second drops a setattr per directory, 6,150 of them. |
**NFS instead of SMB** is worth trying but is not the big win it looks like.
+17 -6
View File
@@ -176,16 +176,16 @@ def test_the_help_says_how_to_reach_and_leave_disk_mode():
assert "holding Play" in help_text
def test_quick_mode_skips_the_counting_pass(mirror, tmp_path):
"""Over SMB the walk is the expensive part, and doing it twice for a
percentage is not always the trade you want."""
def test_counting_is_off_by_default(mirror, tmp_path):
"""The counting pass walks and compares both trees exactly as the transfer
does. On a FAT card of fifty thousand files that costs more than moving the
data, so the percentage has to be asked for."""
destination = tmp_path / "dest"
destination.mkdir()
result = run("-f", "-S", "-U", "-Q", str(mirror), str(destination))
result = run("-f", "-S", "-U", str(mirror), str(destination))
assert result.returncode == 0, result.stderr
assert "skipping the count" in result.stderr
assert "files to copy" not in result.stderr
assert (destination / "Album" / "track.mp3").is_file()
@@ -314,7 +314,7 @@ def test_the_database_lands_at_the_device_root_not_the_music_folder(tmp_path):
script = (
f"mount --bind {card} {device} && "
f"XDG_CACHE_HOME={tmp_path / 'cache'} MUSIC_MIRROR_DATABASE_TOOL={tool} "
f"bash {SCRIPT} -f -S -U -Q {tmp_path / 'mirror'} {device / 'Music'}"
f"bash {SCRIPT} -f -S -U {tmp_path / 'mirror'} {device / 'Music'}"
)
result = subprocess.run(
["unshare", "-Umr", "sh", "-c", script], capture_output=True, text=True
@@ -335,3 +335,14 @@ def test_the_database_lands_at_the_device_root_not_the_music_folder(tmp_path):
# prefix, which is how the paths come out as /Music/... while the bytes are
# read from somewhere else entirely.
assert "Pendulum" in (scratch / "saw.txt").read_text()
def test_counting_can_be_asked_for(mirror, tmp_path):
"""When the destination is cheap to traverse, the percentage is worth it."""
destination = tmp_path / "dest"
destination.mkdir()
result = run("-f", "-S", "-U", "-P", str(mirror), str(destination))
assert result.returncode == 0, result.stderr
assert "files to copy" in result.stderr
+13 -8
View File
@@ -22,8 +22,9 @@ usage() {
usage: sync-to-ipod.sh [options] <mirror> <destination>
-n dry run; show what would change and touch nothing
-Q skip the counting pass; no percentage or estimate, but one less walk
of the source tree, which over SMB is the expensive part
-P count what needs copying first, so progress can show a percentage and
an estimate. Costs a second full traversal of both trees, which on a
FAT card of fifty thousand files is slower than the transfer itself
-f copy even if the FAT32 check finds unacceptable paths
-S skip submitting the Rockbox scrobbler log to Last.fm
-B skip rebuilding the Rockbox database
@@ -68,7 +69,7 @@ USAGE
}
dry_run=false
quick=false
counting=false
force=false
unmount=true
scrobble=true
@@ -76,10 +77,10 @@ database=true
for argument in "$@"; do
[ "$argument" = "--help" ] && usage help
done
while getopts ":nQfSBUh" option; do
while getopts ":nPfSBUh" option; do
case "$option" in
n) dry_run=true ;;
Q) quick=true ;;
P) counting=true ;;
f) force=true ;;
S) scrobble=false ;;
B) database=false ;;
@@ -198,11 +199,15 @@ fi
# thousand files over USB is minutes of apparent hang. Counting first costs a
# second pass over the tree but means the transfer can show a real percentage
# rather than a number that grows as rsync discovers more work.
# Counting is opt-in because it is not cheap. It walks and compares both trees
# in full, exactly as the transfer does, and on a FAT card holding fifty
# thousand files that traversal costs more than moving the data. Without it the
# progress line still shows the running count, the rate and the album in
# flight; only the percentage and the estimate are lost, and those were the
# least useful part of it.
total=0
total_bytes=0
if $quick; then
printf 'sync-to-ipod: skipping the count; no percentage or estimate\n' >&2
else
if $counting; then
printf 'sync-to-ipod: working out what needs copying...\n' >&2
# %l is the file's size, which is what makes an estimate possible.
# Directories are dropped: rsync reports those too, with an inode size that