mirror of
https://github.com/Horizontal42/Sort_YM.git
synced 2026-11-10 15:42:02 +03:00
users_playlists_insert_track и users_playlists_create - операции записи: таймаут не значит, что запрос не дошёл до сервера. Прошлый фикс слепо повторял такой вызов при NetworkError, и если сервер уже применил вставку (а ответ просто потерялся), трек добавлялся в плейлист второй раз. Теперь при сетевой ошибке сначала перепроверяется фактическое состояние плейлиста/списка плейлистов, и повтор происходит только если трек/плейлист там правда ещё отсутствует.
112 lines
4.4 KiB
Python
112 lines
4.4 KiB
Python
from __future__ import annotations
|
||
|
||
import time
|
||
|
||
from yandex_music import Client, Playlist
|
||
from yandex_music.exceptions import NetworkError
|
||
|
||
from .ymclient import with_retries
|
||
|
||
|
||
def _existing_playlists_by_title(client: Client) -> dict[str, Playlist]:
|
||
playlists = with_retries(lambda: client.users_playlists_list())
|
||
return {p.title: p for p in playlists if p.title}
|
||
|
||
|
||
def _existing_track_ids(client: Client, playlist: Playlist) -> set[str]:
|
||
full = with_retries(lambda: client.users_playlists(playlist.kind))
|
||
if full is None:
|
||
return set()
|
||
return {t.track_id for t in full.tracks}
|
||
|
||
|
||
def _get_or_create_playlist(client: Client, title: str, existing: dict[str, Playlist]) -> Playlist:
|
||
if title in existing:
|
||
return existing[title]
|
||
try:
|
||
playlist = with_retries(lambda: client.users_playlists_create(title, visibility="private"), max_attempts=1)
|
||
except NetworkError:
|
||
# Таймаут не значит, что запрос не дошёл до сервера - плейлист мог всё же
|
||
# создаться. Перепроверяем по имени вместо слепого повтора (иначе - дубль плейлиста).
|
||
fresh = _existing_playlists_by_title(client)
|
||
if title in fresh:
|
||
existing[title] = fresh[title]
|
||
return fresh[title]
|
||
raise
|
||
existing[title] = playlist
|
||
return playlist
|
||
|
||
|
||
def _insert_track_verified(
|
||
client: Client,
|
||
playlist: Playlist,
|
||
row: dict,
|
||
track_key: str,
|
||
revision: int,
|
||
max_attempts: int = 4,
|
||
) -> int:
|
||
"""Добавляет трек в плейлист, возвращает новую revision.
|
||
|
||
users_playlists_insert_track - операция записи, а не чтения: при таймауте
|
||
непонятно, применилась ли вставка на сервере. Слепой повтор такого вызова
|
||
рискует добавить трек второй раз. Поэтому при сетевой ошибке перепроверяем
|
||
реальное содержимое плейлиста и повторяем вставку, только если трека там
|
||
действительно ещё нет.
|
||
"""
|
||
for attempt in range(max_attempts):
|
||
try:
|
||
updated = with_retries(
|
||
lambda: client.users_playlists_insert_track(
|
||
playlist.kind, row["id"], row["album_id"], revision=revision
|
||
),
|
||
max_attempts=1,
|
||
)
|
||
return updated.revision if updated is not None and updated.revision is not None else revision
|
||
except NetworkError as e:
|
||
last_attempt = attempt == max_attempts - 1
|
||
print(f" сетевая ошибка при вставке трека ({e}), проверяю фактическое состояние плейлиста...")
|
||
fresh = with_retries(lambda: client.users_playlists(playlist.kind))
|
||
if fresh is not None:
|
||
revision = fresh.revision or revision
|
||
if track_key in {t.track_id for t in fresh.tracks}:
|
||
return revision
|
||
if last_attempt:
|
||
raise
|
||
time.sleep(2.0 * (2**attempt))
|
||
|
||
raise RuntimeError("unreachable")
|
||
|
||
|
||
def apply_classification(
|
||
client: Client,
|
||
rows: list[dict],
|
||
delay: float,
|
||
limit: int | None = None,
|
||
) -> None:
|
||
if limit is not None:
|
||
rows = rows[:limit]
|
||
|
||
existing_playlists = _existing_playlists_by_title(client)
|
||
|
||
by_playlist: dict[str, list[dict]] = {}
|
||
for row in rows:
|
||
by_playlist.setdefault(row["target_playlist"], []).append(row)
|
||
|
||
for title, playlist_rows in by_playlist.items():
|
||
playlist = _get_or_create_playlist(client, title, existing_playlists)
|
||
already = _existing_track_ids(client, playlist)
|
||
revision = playlist.revision or 1
|
||
|
||
added = 0
|
||
for row in playlist_rows:
|
||
track_key = f"{row['id']}:{row['album_id']}" if row["album_id"] else str(row["id"])
|
||
if track_key in already:
|
||
continue
|
||
|
||
revision = _insert_track_verified(client, playlist, row, track_key, revision)
|
||
already.add(track_key)
|
||
added += 1
|
||
time.sleep(delay)
|
||
|
||
skipped = len(playlist_rows) - added
|
||
print(f"{title}: добавлено {added}, уже было {skipped}")
|