Amazon download client: write final size==transferred before returning file path

The download monitor blocks post-processing with a bytes-incomplete guard:
if size > 0 and transferred < size: continue

_stream_to_file throttles engine updates to every 0.5s. The last tick before
the file finishes typically leaves transferred slightly below the Content-Length
size in the engine record. Other streaming clients (YouTube, Tidal, HiFi, etc.)
use their own download threads and don't track bytes at all, so size stays 0
and the guard is always skipped. Amazon was the only client hitting it.

Fix: just before returning the file path from _download_sync, write a final
engine record update setting size == transferred == out_path.stat().st_size
(the decrypted output size). The bytes-incomplete guard then sees
transferred == size and falls through to trigger post-processing normally.
pull/615/head
Broque Thomas 1 week ago
parent b4403ed393
commit ff27effdae

@ -285,10 +285,21 @@ class AmazonDownloadClient(DownloadSourcePlugin):
else:
enc_path.rename(out_path)
final_size = out_path.stat().st_size
logger.info(
f"Amazon download complete ({codec}): {out_path} "
f"({out_path.stat().st_size / (1024 * 1024):.1f} MB)"
f"({final_size / (1024 * 1024):.1f} MB)"
)
# Sync size == transferred so the download monitor's bytes-incomplete
# guard doesn't block post-processing. The throttled updates in
# _stream_to_file leave transferred < size after the last 0.5s tick;
# other streaming clients avoid this by not tracking bytes at all
# (size stays 0, the guard is skipped). Writing the final output size
# here restores parity.
if self._engine is not None:
self._engine.update_record(
"amazon", download_id, {'size': final_size, 'transferred': final_size}
)
return str(out_path)
logger.error(f"All codec tiers exhausted for '{display_name}' ({asin})")

@ -616,6 +616,24 @@ class TestDownloadSync:
states = [c[0][2].get("state") for c in update_calls if "state" in c[0][2]]
assert "downloading" in states
def test_final_size_update_syncs_transferred_to_size(self, tmp_path):
"""size == transferred after success so monitor bytes-incomplete guard doesn't block."""
client, audio_data = self._setup(tmp_path, decryption_key=None)
engine = MagicMock()
client._engine = engine
result = client._download_sync("dl-001", "B09XYZ1234", "Artist - Track")
assert result is not None
# Last update must set size == transferred (both equal the output file size)
final_calls = [
c[0][2] for c in engine.update_record.call_args_list
if 'size' in c[0][2] and 'transferred' in c[0][2]
and c[0][2]['size'] == c[0][2]['transferred']
and c[0][2]['size'] > 0
]
assert final_calls, "Expected a final engine update with size == transferred > 0"
def test_clear_stream_skips_ffmpeg(self, tmp_path):
client, audio_data = self._setup(tmp_path, decryption_key=None)

Loading…
Cancel
Save