add(core): re-apply the naming scheme to imported titles

This commit is contained in:
Steppenstreuner
2026-08-29 06:11:35 +02:00
parent 87b139e236
commit 3e28ad5f94
8 changed files with 345 additions and 9 deletions
+133
View File
@@ -687,3 +687,136 @@ def test_search_passes_language_to_the_provider(client, monkeypatch):
}).json()
assert seen == {"query": "Game of Thrones", "language": "english"}
assert res[0]["language"] == "english"
def test_folder_of_separate_books_is_flagged(client):
"""One book per file (a series bought as single titles) still scans as one
entry - but gets flagged so the UI can offer to split it."""
got = client.downloads / "George R. R. Martin" / "A Game of Thrones"
got.mkdir(parents=True)
for name in ("1 - A Game of Thrones- A Song of Ice and Fire, Book 1 (Unabridged)",
"2 - A Clash of Kings- A Song of Ice and Fire, Book 2 (Unabridged)",
"A - The World of Ice & Fire - The Untold History of Westeros"):
with open(got / f"{name}.mp3", "wb") as f:
f.truncate(700 * 1024 * 1024) # sparse, costs no disk space
chapters = client.downloads / "Ein Hoerbuch"
chapters.mkdir()
for i, title in enumerate(["Prolog", "Die Ankunft", "Das Ende"], 1):
with open(chapters / f"{i:02d} - {title}.mp3", "wb") as f:
f.truncate(25 * 1024 * 1024)
tracks = client.downloads / "Folge 100"
tracks.mkdir()
for i in (1, 2, 3):
with open(tracks / f"{i:02d} Track.mp3", "wb") as f:
f.truncate(200 * 1024 * 1024)
flags = {i["name"]: i["maybe_separate"]
for i in client.get("/api/import/scan").json()["items"]}
assert flags == {
"A Game of Thrones": True, # different titles, each big enough
"Ein Hoerbuch": False, # different titles, but chapter sized
"Folge 100": False, # same name beyond the index
}
def test_split_dirs_still_lists_every_file(client):
got = client.downloads / "A Game of Thrones"
got.mkdir()
for n in (1, 2, 3):
(got / f"{n} - Book {n}.mp3").write_bytes(b"")
items = client.get("/api/import/scan", params={"split_dirs": True}).json()["items"]
assert len(items) == 3
assert all(not i["is_dir"] for i in items)
def test_relocate_after_fixing_the_series(client):
"""Audible files Harry Potter under "Wizarding World"; after correcting the
series the folder on disk should follow."""
root = client.tmp_path / "library" / "english"
lib_id = client.post("/api/libraries", json={
"name": "english", "media_type": "audiobook", "root_path": str(root),
"folder_template": "{Series}/{Title}", "file_template": "{Title}",
}).json()["id"]
req_id = client.post("/api/requests", json={
"library_id": lib_id, "title": "Philosopher's Stone",
"series": "Wizarding World", "volume": 1,
}).json()["id"]
src = client.downloads / "hp1"
src.mkdir()
for i in (1, 2):
(src / f"{i:02d}.mp3").write_bytes(b"")
dest = client.post("/api/import", json={"items": [{
"path": str(src), "is_dir": True,
"files": [str(f) for f in sorted(src.iterdir())], "request_id": req_id,
}]}).json()["results"][0]["dest"]
assert Path(dest) == root / "Wizarding World" / "Philosopher's Stone"
# a cover dropped next to the audio must survive the move
(Path(dest) / "cover.jpg").write_bytes(b"")
client.put(f"/api/requests/{req_id}", json={
"library_id": lib_id, "title": "Philosopher's Stone",
"series": "Harry Potter", "volume": 1,
})
res = client.post(f"/api/requests/{req_id}/relocate").json()
assert res["ok"] and res["moved"]
new = root / "Harry Potter" / "Philosopher's Stone"
assert Path(res["dest"]) == new
assert sorted(p.name for p in new.iterdir()) == [
"Philosopher's Stone - Part 01.mp3", "Philosopher's Stone - Part 02.mp3", "cover.jpg",
]
assert not (root / "Wizarding World").exists() # emptied parent is gone
assert client.get("/api/requests", params={"status": "imported"}).json()[0][
"imported_path"] == str(new)
def test_relocate_is_a_no_op_when_nothing_changed(client):
root = client.tmp_path / "library" / "l"
lib_id = client.post("/api/libraries", json={
"name": "L", "media_type": "audiobook", "root_path": str(root),
"folder_template": "{Title}", "file_template": "{Title}",
}).json()["id"]
req_id = client.post("/api/requests",
json={"library_id": lib_id, "title": "Ein Buch"}).json()["id"]
src = client.downloads / "buch"
src.mkdir()
for i in (1, 2):
(src / f"{i:02d}.mp3").write_bytes(b"")
client.post("/api/import", json={"items": [{
"path": str(src), "is_dir": True,
"files": [str(f) for f in sorted(src.iterdir())], "request_id": req_id,
}]})
res = client.post(f"/api/requests/{req_id}/relocate").json()
assert res["ok"] and not res["moved"]
assert sorted(p.name for p in (root / "Ein Buch").iterdir()) == [
"Ein Buch - Part 01.mp3", "Ein Buch - Part 02.mp3",
]
def test_relocate_refuses_to_overwrite(client):
root = client.tmp_path / "library" / "l"
lib_id = client.post("/api/libraries", json={
"name": "L", "media_type": "audiobook", "root_path": str(root),
"folder_template": "{Title}", "file_template": "{Title}",
}).json()["id"]
req_id = client.post("/api/requests",
json={"library_id": lib_id, "title": "Erstes"}).json()["id"]
src = client.downloads / "b"
src.mkdir()
(src / "01.mp3").write_bytes(b"")
(src / "02.mp3").write_bytes(b"")
client.post("/api/import", json={"items": [{
"path": str(src), "is_dir": True,
"files": [str(f) for f in sorted(src.iterdir())], "request_id": req_id,
}]})
occupied = root / "Zweites"
occupied.mkdir(parents=True)
(occupied / "fremd.mp3").write_bytes(b"")
client.put(f"/api/requests/{req_id}", json={"library_id": lib_id, "title": "Zweites"})
res = client.post(f"/api/requests/{req_id}/relocate")
assert res.status_code == 409
assert (occupied / "fremd.mp3").exists()
assert (root / "Erstes" / "Erstes - Part 01.mp3").exists()