Кеши, PR-юнит 2 (пакет C): единая канонизация ключа канала + one-shot миграция данных #24
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
ТЗ: канонизация ключа канала
Часть принятой спеки
plans/cache-optimization-spec.md. Зависимость: строго после #23 (задание 9.1 использует_cache_file_path, создаваемый там). Один PR. Комментарии в коде — только английские.Проблема
Один канал живёт под ключами
Durov/durov/@nameв трёх слоях (tgcache-файлы, каталогиdata/cache/, строки SQLite) → дубли кеша, двойные походы в Telegram, осиротевшие деревья. Унификация ID↔username — сознательная НЕ-цель (потребовала бы резолва черезget_chat):/rss/-100…и/rss/durovостаются разными ключами.Задание 8 — новый модуль
channel_key.pyОтдельный модуль без зависимостей (импортируют
tg_cache,post_parser,api_server— исключает циклические импорты):Задание 9 — применить ключ во всех трёх слоях
tg_cache.py: в_cache_file_path(key, suffix)прогонять ключ черезcanonical_channel_key.post_parser.get_channel_username(~1081–1097): возвращать username в нижнем регистре (обе ветки —usernames-список и одиночныйusername). Это канонизирует записи SQLite, генерируемые media-URL и t.me-ссылки (t.me к регистру нечувствителен).api_server.get_media(~1153–1183): после проверки digest вычислитьfs_channel = canonical_channel_key(channel)и использовать вместоstr(channel)везде ниже: pre-semaphore путь, ключ_access_updates,media_key, аргумент_download_deduped(протекает вdownload_media_file→ каталоги, API-вызов, удаление из SQLite — для API канонический идентификатор безопасен). Digest проверять по ИСХОДНОЙ строке URL — иначе сломаются все выданные ссылки.Задание 10 — one-shot миграция существующих данных
migrate_channel_keys_sync(db_path: str, cache_dir: str) -> None. Вызов — вlifespanпослеinit_db_sync, ДОclient.start()и до фоновых задач, строгоawait asyncio.to_thread(...)(sync-функция: FS-rename + SQLite; голый вызов заблокирует event loop).Единица работы — канал целиком, сначала FS, потом SQL (порядок принципиален: при провале FS-части SQL-строки канала остаются старорегистровыми, и старое дерево видно свиперу через строки БД — вечных orphan-каталогов не возникает):
cache_dirс не-lowercase именем (кроме-100...) ∪SELECT DISTINCT channel ... WHERE channel != lower(channel) AND channel NOT LIKE '-100%'.os.path.exists(dst) and os.path.samefile(src, dst)→ no-op FS-шага, перейти к SQL-шагу. На case-insensitive FS (macOS/APFS, Docker Desktop for Mac)Durov/иdurov/— ОДИН каталог; merge без guard удалит весь кеш канала.dstнет →os.rename(src, dst).dstесть и это другой каталог (case-sensitive FS, обе формы) → пофайловый merge: существующий файл вdstвыигрывает; удалить пустые остаткиsrc.(post_id, file_unique_id)нет →UPDATEканала; есть → merge (added = max(added),mime_type— предпочесть non-NULL), затемDELETEстарой. ГолыйUPDATE ... SET channel=lower(channel)запрещён (PK-конфликт).migration_summary: rows merged N, dirs renamed M, samefile no-ops K, failures F.Задание 11 — тесты
'Durov'→'durov','@durov'→'durov',-1001…(int и str) →'-1001…'.Durov/durov;get_channel_usernameсusername='MixedCase'→'mixedcase'.max(added)и non-NULLmime_type; (б) samefile-guard → no-op, данные целы; (в) merge двух реально разных каталогов → файлы объединены, target выигрывает; (г) повторный запуск — no-op.Критерии приёмки
/rss/Durovи/rss/durov— один tgcache-файл;/media/Durov/...и/media/durov/...— один файл на диске.pytestзелёный; правкиpost_parser.py— только lowercase вget_channel_username.В описание PR обязательно
Пересечение с эпиком рефакторинга рендера (#34)
Задание 9.2 меняет
post_parser.get_channel_username→ возвращать username в нижнем регистре. Два следствия для рендер-эпика:Golden рендера (#27) придётся переснять.
get_channel_usernameпитает подписанные media-URL (/media/{channel}/...), t.me-ссылки и «Open in Telegram/Web» в футере. Lowercase изменит байты фида для любого канала со смешанным регистром username. Кто из эпиков мёржится вторым — переснимает feed-golden с пометкой (для рендера это НЕ пункт реестра §3, а внешнее изменение из #24; зафиксировать в PR обоих сторон).Совместное редактирование
post_parser.py. #24 правитget_channel_username; этапы рендера 2 (#29) и 5 (#32) правят соседние функции. Конкретноget_channel_usernameрефактор рендера НЕ трогает — конфликт только текстовый (merge), не семантический. Кто второй — ребейзит.Связано: #34, #27, #32.