mirror of
https://github.com/calibrain/shelfmark.git
synced 2026-11-10 12:28:27 +00:00
Download to Browser only ever sent the first file of a multi-file release (audiobook chapters, packs): the folder output returned final_paths[0] and /api/localdownload served that one path. The folder output now keeps every final path on the in-memory task (download_paths), and /api/localdownload streams them as one uncompressed ZIP named after the book when there is more than one. The archive is never staged on disk or held in memory, and ZIP64 covers large audiobooks. History still stores only the first path, so a re-download from history after a dismiss or restart serves a single file, as before. Also brings every linter back to green on all files: - pre-commit: bump the ruff hook to v0.17.1 to match uv.lock; ruff-format the Python examples in docs/dev and skills - ruff: fix the remaining issues in skills/shelfmark/download_books.py; format base_handler.py - basedpyright: Optional and attrs errors in databazeknih.py - knip: drop 31 unused exports, reuse ColumnColorHint in colorMaps - oxfmt 0.72: styles.css - direct download: fix the "Welib, mirrors" typo and the test that still expected the pre-OceanofPDF message Fixes #1467
42 lines
1.5 KiB
Python
42 lines
1.5 KiB
Python
import io
|
|
import zipfile
|
|
|
|
from shelfmark.download.zip_stream import stream_zip, zip_entry_names
|
|
|
|
|
|
def test_stream_zip_holds_every_file_uncompressed(tmp_path):
|
|
book = tmp_path / "Author" / "Title"
|
|
book.mkdir(parents=True)
|
|
(book / "Part 01.m4b").write_bytes(b"chapter one")
|
|
(book / "Part 02.m4b").write_bytes(b"chapter two" * 1000)
|
|
|
|
data = b"".join(stream_zip([str(book / "Part 01.m4b"), str(book / "Part 02.m4b")]))
|
|
|
|
with zipfile.ZipFile(io.BytesIO(data)) as archive:
|
|
assert archive.testzip() is None
|
|
assert archive.namelist() == ["Part 01.m4b", "Part 02.m4b"]
|
|
assert archive.read("Part 01.m4b") == b"chapter one"
|
|
assert archive.read("Part 02.m4b") == b"chapter two" * 1000
|
|
assert {info.compress_type for info in archive.infolist()} == {zipfile.ZIP_STORED}
|
|
|
|
|
|
def test_stream_zip_yields_while_reading_rather_than_at_the_end(tmp_path, monkeypatch):
|
|
import shelfmark.download.zip_stream as zip_stream
|
|
|
|
monkeypatch.setattr(zip_stream, "_ZIP_STREAM_CHUNK_SIZE", 16)
|
|
big = tmp_path / "big.mp3"
|
|
big.write_bytes(b"x" * 160)
|
|
|
|
chunks = list(stream_zip([str(big)]))
|
|
|
|
assert len(chunks) > 10
|
|
|
|
|
|
def test_zip_entry_names_keep_folders_below_the_common_parent(tmp_path):
|
|
first = tmp_path / "library" / "Book One" / "a.mp3"
|
|
second = tmp_path / "library" / "Book Two" / "b.mp3"
|
|
|
|
names = [name for _, name in zip_entry_names([str(first), str(second)])]
|
|
|
|
assert names == ["Book One/a.mp3", "Book Two/b.mp3"]
|