Repository navigation
v0.4.0 — P0 안정성 강화 + 추천 개편 + 코드 품질 대규모 개선
brainstream v0.4.0
P0 안정성 강화, 추천 시스템 개편, 페이지네이션/검색, 대규모 코드 품질 개선을 포함한 메이저 릴리즈.
What's Changed
New Features
- P0 안정성 강화: graceful shutdown (try/finally + worker join 30s), yt-dlp 타임아웃 (메타 60s/다운로드 600s), API rate limiting (슬라이딩 윈도우), SSE 큐 TTL (30분 자동 정리), 로그 로테이션 (50MB × 5)
- 추천 시스템 개편: CF 추천 offset 페이지네이션(80%) + LB Radio 탐색(20%) 이중 소스 구조. CF 모델 갱신 감지, CF 풀 소진 시 Radio 폴백
- 파이프라인 주기 설정:
GET/PUT /api/settings/pipeline-interval+ UI 드롭다운. settings 테이블 동적 주기 적용 - 페이지네이션 + 검색:
GET /api/downloads?limit=&offset=&search=페이지네이션 API + 수동 다운로드 중복 체크 (artist+track 기준 409) - 무한 스크롤 + 검색 UI: IntersectionObserver 기반 무한 스크롤, 실시간 검색 입력
- /health 엔드포인트: 컨테이너 liveness probe 지원
- Multi-worktree Compose 격리: worktree별 고유 프로젝트명 + 포트 자동 배정
Bug Fixes
- FLAC 태그 리스트 형식 통일: scalar → list 할당으로 Opus/OGG와 일관성 확보
- mark_done album NULL 덮어쓰기: COALESCE로 기존 album 보존
- Lucene 특수문자 전체 이스케이프:
&&,||등 다문자 연산자 포함 완전 처리 - MB 아티스트명 추출: 첫 번째 결과 대신 선택된 recording의 artist-credit 사용
- auth salt 보안:
random.choices→secrets.token_hex - Rate limiter 메모리 누수: 주기적 정리 + safe dict iteration
- auto-refresh 셀렉터 버그:
#history-table→#section-history(DOM에 없는 셀렉터로 자동 갱신 미작동) - rematch_apply 파일 덮어쓰기: 이동 전
os.path.exists충돌 체크 추가 (409) - _write_single_tag 예외 삼킴: raise하도록 변경, API 엔드포인트가 태그 실패 감지 가능
- mark_pending_if_not_duplicate 경쟁 조건:
INSERT ... WHERE NOT EXISTS단일 문으로 atomic 처리 - 재시작 시 이중 enqueue:
is_downloaded()가 pending/downloading도 필터 - get_retryable source 누락: manual 실패 잡이 source='listenbrainz'로 재시도되던 버그 수정
- stale SSE 응답 race condition: requestSeq 기반 무효화
- Healthcheck 포트: 8080 → 8000 (컨테이너 내부 포트)
- tagger KeyError:
meta["artist"]→meta.get("artist") - logger dirname 빈 문자열: guard 추가
Refactoring
- musicbrainz.py 모듈 분리: MB API 상수, lookup_recording, mb_search_recording, mb_album_from_recording_id를 공유 모듈로 추출
- tagger 포맷 디스패치 딕셔너리: ~400줄 중복 제거,
_FORMAT_OPENER/_FORMAT_KEYS구조 - jobs.py 분리:
_run_download_job을 api.py에서 src/jobs.py로 추출 - move_to_music_dir 헬퍼: 파일 이동 로직 통합 (resolve_dir + case-insensitive 매칭)
- src/utils/fs.py: sanitize 함수 통합
- async httpx 마이그레이션: rematch 엔드포인트 blocking requests → async httpx
- httpx.AsyncClient 공유: FastAPI lifespan 기반 싱글톤
Technical Details
| 모듈 | 주요 변경 |
|---|---|
src/main.py |
CF offset 진행 + LB Radio 이중 소스, CF 모델 갱신 감지, daemon worker thread |
src/state.py |
settings 테이블, get_downloads_page, mark_pending_if_not_duplicate (atomic), is_downloaded 확장 |
src/api.py |
pipeline interval API, 페이지네이션, 중복 체크 409, rematch 파일 충돌 체크, rate limiting |
src/worker.py |
SSE 큐 TTL, touch_sse_queue, _cleanup_expired_queues |
src/pipeline/musicbrainz.py |
신규 — 공유 MB API 클라이언트 (4단계 검색 + Lucene 이스케이프) |
src/pipeline/tagger.py |
포맷 디스패치 딕셔너리, _write_single_tag 예외 전파 |
src/pipeline/listenbrainz.py |
fetch_user_top_artists, fetch_lb_radio, offset 파라미터 |
src/pipeline/downloader.py |
yt-dlp 타임아웃, _run_with_timeout |
src/jobs.py |
신규 — run_download_job 분리 |
src/utils/fs.py |
신규 — sanitize_path_component, resolve_dir, move_to_music_dir |
src/static/index.html |
무한 스크롤, 검색, pipeline interval 드롭다운, auto-refresh 수정 |
restart_local_docker.sh |
worktree 격리, 서브커맨드 (restart/stop/logs/status/info) |
Full Changelog: v0.3.0...v0.4.0