42 KiB
Предложение треков (загрузка с модерацией)
Модераторы (и админы) предлагают новые треки. Файлы попадают в очередь на добавление с общим лимитом размера, дубли отбрасываются сразу, аудитор загрузок одобряет или отклоняет, одобренные файлы раскладываются в хранилище по той же структуре папок, что принята в этом хранилище.
1. Роли
| Действие | user | moderator | admin |
|---|---|---|---|
| Загрузить треки в очередь | — | ✅ | ✅ |
| Видеть свои предложения и их статус, отозвать до рассмотрения | — | ✅ | ✅ |
| Настраивать лимит очереди, целевое хранилище и шаблон раскладки | — | — | ✅ |
Видеть всю очередь, слушать, править метаданные, одобрять и отклонять может только тот, у кого есть право
аудита загрузок (users.reviews_uploads, в API — reviews_uploads в Me / AdminUser). Это не роль: право
выдаёт админ, оно может быть у нескольких людей сразу, у модератора или даже у обычного пользователя, а у админа
его может не быть. Без него админ по-прежнему настраивает лимиты и хранилища, но очередь не видит.
Загрузки админа проходят ту же проверку на дубли; в очередь они тоже попадают, но админ может одобрить их сразу.
2. Жизненный цикл загрузки
предпроверка (хэш + теги на клиенте)
│ дубль → отказ сразу, файл не загружается, квота не тратится
▼
uploading ──► processing ──► pending_review ──► approved ──► importing ──► imported
│ │ │ │
│ ├─► duplicate ├─► rejected (причина) └─► import_failed
│ └─► invalid └─► withdrawn (отозвал автор) (можно повторить)
└─► expired (не докачан за 24 ч)
| Статус | Занимает квоту | Файл в staging |
|---|---|---|
uploading |
✅ (резерв по заявленному размеру) | частично |
processing |
✅ | ✅ |
pending_review |
✅ | ✅ |
approved, importing |
✅ (до окончания копирования) | ✅ |
imported, import_failed→после ретрая |
— | удаляется |
duplicate, invalid, rejected, withdrawn, expired |
— | удаляется сразу |
3. Квота очереди
- Глобальный лимит
uploads.queue_limit_bytes(по умолчанию 10 ГБ), меняет только админ в настройках админки. - Опционально — лимит на одного модератора
uploads.per_user_limit_bytes(по умолчанию выключен) и максимальный размер одного файлаuploads.max_file_bytes(по умолчанию 2 ГБ). - Занято = сумма
size_bytesзагрузок в статусах, отмеченных в таблице выше. Дубли и отклонённые не учитываются никогда. - Перед загрузкой клиент запрашивает резерв (
POST /uploadsсо списком файлов и размерами). Сервер в транзакции (SELECT … FOR UPDATEстроки счётчика) проверяетзанято + запрошено ≤ лимити резервирует место. Не помещающиеся файлы отклоняются с кодомuploads.queue_full(клиент показывает, сколько места осталось). - Снижение лимита ниже занятого не удаляет существующие загрузки — просто новые не принимаются, пока очередь не освободится.
- Индикатор заполненности очереди виден модераторам и админу («6.2 из 10 ГБ»).
4. Проверка на дубли
Три уровня, от дешёвого к дорогому. Дубль на любом уровне = статус duplicate, файл удаляется,
квота освобождается, автор видит, с каким существующим треком совпало (ссылка на трек).
| Уровень | Когда | Как |
|---|---|---|
| 1. Точная копия файла | До загрузки (на клиенте) и после | SHA-256 всего файла: клиент считает потоково в Web Worker через js-sha256, без WebAssembly и загрузки всего файла в память, и отправляет в предпроверке. Сравнение с media_files.sha256 и с другими загрузками в очереди. |
| 2. Совпадение метаданных | До загрузки (теги читаются на клиенте библиотекой music-metadata) и после (на сервере по ffprobe) |
Нормализованные artist + title (+ album, если есть) и длительность ±2 с против tracks. |
| 3. Акустический отпечаток | После загрузки | Chromaprint (fpcalc), сравнение с track_fingerprints библиотеки и очереди. Ловит тот же трек в другом формате / битрейте / с другими тегами. |
Правила:
- Совпадение уровня 1 или 3 — дубль безусловно.
- Совпадение только уровня 2 (метаданные совпали, отпечаток — нет: ремастер, live, другая версия) — не отбрасывается,
а помечается флагом
possible_duplicateдля решения админа. - Если загружаемый файл того же трека лучшего качества (lossless вместо MP3) — дубль всё равно отбрасывается автоматически,
но в настройках можно включить режим
uploads.allow_quality_upgrade: тогда такой файл идёт на рассмотрение с пометкой «замена на лучшее качество». - Дубли внутри одной пачки загрузки (два одинаковых файла) — второй отбрасывается.
- Отпечатки для существующей библиотеки считает сканер (задача
fingerprint_track, ~0.3 с на трек) — выполняется при первом скане и для новых файлов.
5. Загрузка
- Протокол tus (resumable upload): сервер —
tusdкак библиотека внутриapi(/api/v1/uploads/files/), клиент —tus-js-client. Докачка после обрыва, чанки по 8 МБ. - Веб: drag-and-drop файлов и папок (сохраняются относительные пути — помогают угадать альбом), до 4 параллельных загрузок, прогресс по каждому файлу.
- Мобильный клиент загрузку не поддерживает (первая версия).
- Staging — локальный диск сервера
/srv/pipu/uploads/<upload_id>/<file_id>(не NFS). - Разрешённые форматы:
flac mp3 m4a ogg opus wav aiff wv. Просим присылать lossless — FLAC, WAV или AIFF (WavPack тоже считается lossless); MP3 и остальные lossy — крайний случай: форма предупреждает об этом, а очередь на добавление помечает такой файл «обратить внимание». Проверяется не расширение, а содержимое (ffprobe): должен быть ровно один аудиопоток, длительность 5 с – 3 ч. Иначеinvalidс кодом причины. - Картинки
cover.jpg,cover.png(такжеfolder/front, JPEG/PNG/WebP) загружаются как обложка альбома, а не пропускаются как не-аудио. Если отдельной картинки нет, берётся встроенная обложка из первого трека, в котором она найдена. На каждую папку альбома создаётся своя пачка с обложкой, поэтому разные альбомы не смешиваются. Обложка до 8 МБ сохраняется вместе с резервом пачки и встраивается при импорте в поддерживаемые контейнеры FLAC/MP3/M4A/Ogg/Opus без перекодирования аудио. Обложки больше 8 МБ клиент автоматически уменьшает до JPEG с длинной стороной не более 2048 пикселей перед base64-кодированием; это относится и к отдельным файлам, и к встроенной обложке, и к правке пачки. Меньшие картинки остаются без изменений. Код HTTP 413 от прокси показывается как превышение размера запроса. artist.jpg(такжеartist.jpeg,artist.png,artist.webp, регистр не важен) в папке исполнителя передаётся отдельно от обложки. Ближайшая такая картинка наследуется вложенными папками альбомов; соседние исполнители не смешиваются. НапримерИсполнитель/artist.jpgи две папки альбомов дают две отдельные пачки с одной фотографией исполнителя и своими обложками. После одобрения и импорта трека фотография сохраняется в кеш WebP и ставится исполнителю альбома, только если у него нет ни локальной аватарки, ни изображения Genius. Гостям она не назначается. Существующая аватарка сохраняется, в том числе при одновременном импорте нескольких альбомов. До одобрения каталог не меняется. Лимит фотографии — 8 МБ после уменьшения, как у обложки; nginx принимает до 32 МБ, чтобы вместить обе картинки в base64. Временная фотография удаляется после завершения всех файлов пачки, а готовая аватарка остаётся в кеше.
6. Обработка (processing)
Задача River process_upload_file:
ffprobe→ формат, длительность, битрейт, теги.- SHA-256,
content_hash,fpcalc→ проверки дублей (раздел 4). - Метаданные-кандидаты: теги → если пусто, разбор относительного пути из браузера шаблонами целевого хранилища → если пусто, имя файла.
- Нормализация и сопоставление с существующими исполнителями / альбомами каталога (чтобы «Кино» не превратилось во второго исполнителя «КИНО»): найденные id подставляются, админ видит «будет добавлено к существующему альбому» или «новый альбом».
- Расчёт предварительного целевого пути (раздел 7).
- Статус
pending_review.
7. Раскладка в хранилище «как принято на сервере»
Целевое хранилище
- Админ отмечает одно или несколько хранилищ как принимающие загрузки (
accepts_uploads = true). Только они монтируются на запись (см. library-and-storage.md). - Хранилище по умолчанию — в настройках; при одобрении админ может выбрать другое.
Шаблон раскладки
Регэкспы умеют только разбирать путь, поэтому для записи у хранилища есть шаблон пути (write_template):
{albumartist}/{year} - {album}/{disc:1-}{track:02} - {title}.{ext}
| Плейсхолдер | Значение |
|---|---|
{artist}, {albumartist} |
Исполнитель трека / альбома (если нет — исполнитель трека) |
{album}, {title}, {genre} |
|
{year} |
4 цифры |
{track:02} |
Номер с дополнением нулями |
{disc:1-} |
Номер диска с суффиксом -, выводится только если дисков > 1 |
{ext} |
Расширение по реальному формату |
[ ... ] |
Необязательный фрагмент: опускается целиком, если любой плейсхолдер внутри пуст, например [{year} - ]{album} |
Как получить шаблон:
- Автоподбор: система сравнивает пути и метаданные до 200 существующих файлов хранилища, строит варианты раскладки и выбирает вариант с наибольшим числом точных совпадений. Сохранённые регэкспы также используются как кандидаты, но не обязательны. В интерфейсе показаны число совпадений и примеры реальных путей. Если шаблон ещё не задан, предложение заполняется автоматически; администратор может изменить его и сохранить.
- Проверка обратимости (обязательна при сохранении): шаблон применяется к 50 существующим трекам хранилища, полученные пути прогоняются через регэкспы этого хранилища — поля должны распознаться обратно без изменений, а в ≥ 90% случаев путь должен совпасть с реальным путём файла. Иначе — предупреждение с примерами расхождений. Так гарантируется, что новые файлы лежат так же, как старые, и сканер разберёт их правильно.
Очистка имён
- Запрещённые символы
/ \ : * ? " < > |и управляющие →_; точки и пробелы в конце сегмента удаляются. - Длина сегмента ≤ 200 байт UTF-8, полный путь ≤ 1000 байт (обрезка по границе символа с сохранением расширения).
- Unicode NFC. Регистр не меняется.
- Существующая папка исполнителя / альбома переиспользуется, если её имя совпадает с нормализованным значением
(
Кино↔КИНО) — берётся уже существующее написание, чтобы не плодить соседние папки.
Конфликты
- Если файл по целевому пути уже существует — добавляется суффикс
(2), а в задаче — предупреждение. - Путь проверяется на выход за корень хранилища (
.., абсолютные пути) — ошибка.
8. Рассмотрение (аудит загрузок)
Экран «Очередь на добавление»:
- Группировка по пачке загрузки / по альбому; фильтры: статус, автор,
possible_duplicate. Пачки по умолчанию свёрнуты; кнопка в заголовке раскрывает файлы, выбор всей пачки доступен и без раскрытия. В заголовке указано наличие общей обложки пачки: «Есть обложка» / «Без обложки». Там же показаны количества файлов по форматам и диапазоны битрейта, частоты и разрядности; одинаковые значения не повторяются, отсутствующие измерения не учитываются как нули. - Для каждого файла: прослушивание (стрим из staging), метаданные с возможностью правки, обложка, качество
(формат / битрейт / sample rate), флаги (возможный дубль — со ссылкой на похожий трек), итоговый путь в хранилище
с названием хранилища и путём относительно его корня. Полный адрес назначения (локальный путь API
или
server:/export/pathдля NFS) доступен при наведении на путь. Для ожидающих рассмотрения, одобренных и ошибочных импортов предпросмотр рассчитывается при чтении очереди по текущему шаблону — тем же планировщиком, что и импорт. Сохранение шаблона обновляет кеш очереди. Уже импортированные файлы сохраняют фактический путь. Если рассчитать путь нельзя, показаны причина и действие для исправления вместо старого пути. Отсутствующий год отмечается предупреждением и не блокирует одобрение или импорт:{year}опускается вместе со скобками или разделителем, необязательный фрагмент с годом опускается. В теги неизвестный год не записывается. - Прослушивание staging выдаёт сам API с поддержкой Range: приватные файлы tus (
0640, каталоги0750) недоступны пользователю nginx. Выдача не зависит отX_ACCEL; права существующих файлов менять не нужно. Аудио и его метаданные загружаются только после Play (preload="none"); раскрытие пачки не запускает запросы к файлам. При сбое показана ошибка (включая HTTP-статус, если доступен) и кнопка повторной попытки с обновлением подписанных ссылок. Одновременно играет только один предпросмотр: запуск другого ставит предыдущий на паузу с сохранением позиции. Сворачивание пачки или уход со страницы также останавливает её прослушивание. - Действия: одобрить / отклонить (с причиной — видна автору), массовые действия для выбранных, правка «на весь альбом» (исполнитель, альбом, год, обложка одной операцией).
- Если админ изменил метаданные — они записываются в теги файла при импорте (
ffmpeg -map 0 -c copy -metadata …без перекодирования; для FLAC/MP3/M4A/Ogg), чтобы теги и путь не противоречили друг другу.
Сопоставление релиза и внешние обложки
После обработки всех файлов пачки API ставит её в отдельную фоновую очередь. MusicBrainz ищет конкретные издания по сохранённому MusicBrainz Release ID либо исполнителю альбома и названию. Внешнему сервису передаются только поисковые метаданные и идентификаторы, аудиофайлы не отправляются. Для смешанной пачки или отсутствующих тегов показывается предложение уточнить поиск.
В заголовке пачки видно состояние поиска. Если найдено несколько изданий, администратор сначала выбирает конкретное. Проверка сопоставляет дорожки по идентификаторам, названиям, исполнителям, длительностям и нумерации дисков. «Полный состав» означает все дорожки выбранного издания с уверенными совпадениями и без несопоставленных файлов. Один файл не считается синглом без типа Single в каталоге. Несколько форматов одной песни не увеличивают число совпавших дорожек. Предположительные и неоднозначные совпадения отмечаются отдельно. Для отсутствующих дорожек отдельно показаны совпадения с доступной библиотекой по тегам и длительности; они не делают загруженную пачку полной.
Число файлов в пачке и число сопоставленных дорожек — разные показатели. «Файл не сопоставлен»
не доказывает отсутствие дорожки: она может быть среди файлов с отличающимися тегами. Для
предположительного совпадения допускается разная запись состава исполнителей (запятая, feat., ;)
и пометки feat. / Prod. в конце названия при совпадении номера, диска и длительности с допуском
2 секунды. Пометки версий (live, remix, instrumental) сохраняются. Такие совпадения требуют
проверки и сами по себе не подтверждают «Полный состав». При изменении правил сопоставления
сохранённые результаты поиска пересчитываются при открытии пачки.
Поиск сам не меняет теги, путь или обложку. В диалоге можно отдельно применить метаданные к совпавшим редактируемым файлам и/или обложку. Обложка загружается из Cover Art Archive только после запроса предпросмотра. При отсутствии обложки конкретного издания возможна обложка группы изданий с явной пометкой. Замена уже загруженной обложки требует отдельного выбора. Применение фиксируется одной транзакцией и отвергает изменившуюся пачку (409); статусы одобрения и квота не меняются. MusicBrainz ID записываются в теги при импорте вместе с остальными подтверждёнными метаданными.
MUSICBRAINZ_ENABLED=true включает поиск (можно отключить в конфигурации сервера). Нужен исходящий HTTPS
к MusicBrainz, Cover Art Archive и archive.org для обложек. Ключ API для MusicBrainz не требуется;
запросы ограничены одним за 1,1 секунды, результат кешируется на 7 дней, просроченный кеш удаляется.
Сбой провайдера не мешает загрузке или импорту: повтор через 10 минут, не более трёх автоматических попыток,
после чего доступен ручной поиск. Очередь и кеш сохраняются в PostgreSQL, незавершённая задача
после перезапуска повторяется по истечении пятиминутной аренды.
Источники: MusicBrainz API, Cover Art Archive API.
Несколько релизов в одной пачке
После завершения обработки всей пачки треки получают постоянные группы релизов внутри исходной пачки. Группировка использует MusicBrainz Release ID, затем album artist + album + год. Единственный известный Release ID подхватывается треками того же альбома без идентификатора; разные известные издания разделяются. При неполных тегах используется исходная папка альбома. Подпапки CD1/CD2, Disc 1/2 и «Диск 1/2» относятся к общей родительской папке. Album artist сохраняет сборники с разными исполнителями треков; без него помогает общая папка. Файлы без альбомных тегов и без папок остаются одной группой для ручной проверки.
У групп отдельные фоновые задачи, аренды, результаты MusicBrainz/AcoustID, выбранные издания и обложки. После первичного разделения подтверждение тегов не меняет принадлежность файлов и не объединяет группы. Правка метаданных или отзыв файла сбрасывает только поиск его группы. Ошибка одной группы не блокирует поиск соседних. В очереди сохраняется исходная пачка с общими действиями и вложенными заголовками релизов; выбор строк релиза действует на загруженные строки страницы. В диалоге сначала выбирается релиз внутри пачки, затем его издание. Проверка состава учитывает все файлы группы, включая другие страницы очереди.
HTTP: group_id в запросах поиска/применения и query-параметр group_id при чтении отчёта/обложки.
Для единственной группы параметр можно опустить; в смешанной пачке сервер требует явного выбора и проверяет
принадлежность группы пачке. Применение блокирует и проверяет только файлы выбранной группы и её задачу.
Квота и статусы файлов не меняются. При импорте используется обложка своей группы; staging-очистка сохраняет
общую обложку, пока она нужна хотя бы одной активной группе.
В одиночном релизе сохраняется исходная обложка пачки. В смешанной пачке одну общую обложку нельзя надёжно приписать всем альбомам, поэтому группы начинают без неё; исходная обложка остаётся в пачке. Обложку из каталога можно применить отдельно к нужному релизу. Явное изменение обложки «На всю пачку» по-прежнему меняет обложки всех её групп.
Распознавание по звуку
Кнопка «Распознать по звуку» запускает отдельный запрос для пачки до 50 редактируемых треков.
Нужен ключ приложения AcoustID: зарегистрируйте приложение на AcoustID,
задайте секрет ACOUSTID_API_KEY в конфигурации сервера и примените изменения. Пользовательский ключ
для отправки записей не нужен. При пустом ключе кнопка недоступна, поиск по тегам продолжает работать.
fpcalc локально вычисляет сжатый Chromaprint первых 120 секунд. В AcoustID отправляются только
отпечаток и полная длительность из ffprobe; аудиофайл, теги, имя файла и ключи пользователя не передаются.
Автоматического распознавания всех загрузок и отправки отпечатков в базу AcoustID нет.
Ключ хранится как секрет; POST не помещает его и отпечаток в URL, редиректы запрещены.
Результат используется при оценке не ниже 0,90 и отрыве от следующей другой записи не менее 0,05. Для каждого файла показаны распознавание, неоднозначность, отсутствие совпадений или недоступный отпечаток. Слабые результаты не исправляют теги. Уверенное распознавание имеет приоритет над ошибочными тегами; для подтверждения полного состава нужна также совместимая длительность дорожки (±5 секунд). Повтор одной записи на разных позициях остаётся неоднозначным.
MusicBrainz ищет издания по первым восьми уникальным распознанным записям (до 100 изданий на запись),
выбирает до пяти кандидатов по числу совпадений и сопоставляет с ними все файлы. Это ограниченный набор
кандидатов, не гарантия нахождения нужного издания. Отпечаток не определяет конкретный мастеринг или издание;
выбор и применение метаданных/обложки остаются за администратором. Поиск имеет общий таймаут 4 минуты,
лимит AcoustID — один запрос за 350 мс, кеш — 7 дней. Нужен исходящий HTTPS к api.acoustid.org.
Условия бесплатного сервиса AcoustID допускают некоммерческое использование; для коммерческого нужен
отдельный договор с провайдером (см. документацию AcoustID).
9. Импорт (importing)
Задача River import_upload_file (повторяемая, идемпотентная):
- Пересчитать целевой путь с финальными метаданными.
- Запросить у
storage-agentзапись:POST /internal/files{source_id, rel_path, staging_path}. Агент:- создаёт каталоги;
- создаёт файл сразу под итоговым именем (
O_EXCL, существующий не перезаписывается), копирует,fsync; - сверяет SHA-256 копии со staging; при ошибке удаляет свой файл и созданные пустые каталоги — на музыкальный диск не попадает ничего, кроме треков.
- Запустить точечный скан файла (
scan_path) → трек появляется в каталоге и поиске, ставится в аудиоанализ. Вmedia_filesсохраняетсяupload_id— история происхождения. - Удалить файл из staging, освободить квоту, статус
imported, запись вaudit_log.
При ошибке (NFS недоступен, нет места) — import_failed с сообщением, квота не освобождается, файл остаётся в staging;
админ может повторить или отклонить.
10. Уведомления
- В «Моих предложениях» файлы сгруппированы по пачкам. Можно выбрать отдельные файлы, загруженные строки страницы или всю собственную пачку (с загрузкой всех её страниц) и отозвать выбранные до решения администратора. Успешные отзывы освобождают квоту; при частичной ошибке показана причина, неудавшиеся файлы остаются выбранными.
- Автор видит статусы в «Мои предложения» (раздел админки модератора), бейдж с числом решений с последнего визита.
- Аудитор загрузок видит бейдж «N на рассмотрении» в сайдбаре.
Реализация (services/api/internal/uploads)
- Таблицы — миграция
00008_uploads.sql; смещение tus —upload_files.received_bytes, отпечаток Chromaprint файла очереди —upload_files.fingerprint(дубли внутри очереди ловятся так же, как с библиотекой). - Квота:
Store.CreateBatchрезервирует подSELECT … FOR UPDATEстрокиupload_quota; любая смена статуса идёт черезtransition— место освобождается в той же транзакции, если новый статус его не занимает (таблица раздела 2). - Приём — ядро tus:
HEAD/PATCH /api/v1/uploads/files/{id}, только автор и только вuploading. Принят целиком →processing. - Обработка (
worker.go, по одному файлу): размер →ffprobe(5 с – 3 ч) → SHA-256 (сверка с заявленным) → дубль по SHA-256 → Chromaprint против библиотеки и очереди (длительность ±5 с,library.Similarity≥ 0.75) → метаданные (теги → путь из папки по регэкспам целевого хранилища → имя файла) → совпадение метаданных (possible_duplicate) → путь поwrite_templateс переиспользованием существующих папок →pending_review. Если путь не строится (нет хранилища / шаблона / поля), файл всё равно ждёт рассмотрения, причина — вstatus_reason. - Хранилище: выбранное админом →
default_source_idиз настроек → единственное сaccepts_uploads. - Импорт: правленые метаданные → копия с тегами (
ffmpeg -map 0 -c copy -metadata …, FLAC/MP3/M4A/Ogg/Opus) →storage-agentPOST /internal/files→Scanner.ScanFile(точечный скан) →imported, связьmedia_files.upload_file_id. Повторяемо: если по пути уже лежит файл с тем же хэшем, запись пропускается. Прерванный импорт при старте возвращается вapproved. - Очистка: раз в 10 минут
uploadingстарше 24 ч →expired; при старте — удаление файлов staging без записи в БД. - Настройки:
GET/PATCH /admin/settings/uploads(ключuploads.settingsвapp_settings). - Веб (
apps/web/src/features/uploads,pages/uploads,pages/admin/UploadQueuePage.tsx):- «Предложить треки» (
/uploads/new): drag-and-drop файлов и папок (FileSystemEntry API, относительные пути), выбор файлов / папки; SHA-256 потоком в Web Worker (js-sha256); теги читаетmusic-metadataи отдаёт их в предпроверку;POST /uploads;tus-js-clientс готовымuploadUrl, куски по 8 МБ, 4 файла параллельно, 401 → обновление токена и повтор. Загрузчик — синглтон модуля: переходы по приложению не прерывают загрузку. Незавершённые tus-ссылки пишутся вlocalStorage; после перезагрузки пользователь выбирает те же файлы, клиент сверяет SHA-256 и продолжает с принятого смещения. - «Мои предложения» (
/uploads/mine): статусы, причины отказа, отзыв до решения, квота. Бейдж — число решений новееupload_visits; открытие страницы вызываетPOST /uploads/seen. - «Очередь на добавление» (
/admin/uploads, admin): вкладки по статусам, фильтр возможных дублей, группировка по пачкам, правка метаданных и хранилища, правка исполнителя / альбома / года / обложки на всю пачку, прослушивание staging по подписанной ссылке, флаг улучшения качества, массовое одобрение / отклонение с причиной, повтор импорта. Настройки очереди — лимиты, хранилище иallow_quality_upgrade.
- «Предложить треки» (
- Сверка квоты:
used_bytesобновляется в транзакции смены статуса и дополнительно пересчитывается поSUM(size_bytes)занимающих статусов при стартеapiи в цикле очистки (раз в 10 минут).
11. Очистка
uploadingстарше 24 ч →expired, файлы удаляются.- Записи завершённых загрузок (
imported,rejected, …) хранятся 180 дней для истории, без файлов. - При старте
api— сверка staging-каталога с БД: осиротевшие файлы удаляются.