Initial import: Music_Server, MusicFree, catalog-sync

This commit is contained in:
2026-05-23 16:51:14 +08:00
commit 069af30dba
847 changed files with 179878 additions and 0 deletions
@@ -0,0 +1,118 @@
from __future__ import annotations
import tempfile
import zipfile
from datetime import datetime
from pathlib import Path
from typing import Iterable
from uuid import uuid4
from .runtime import sanitize_path_component
INTERNAL_BUNDLE_NAME_SEPARATOR = "--"
def default_bundle_root() -> Path:
root = Path(tempfile.gettempdir()) / "musicdl-catalogsync" / "bundles"
root.mkdir(parents=True, exist_ok=True)
return root
def build_single_playlist_bundle_filename(
*,
platform: str,
playlist_id: int,
playlist_name: str,
) -> str:
safe_platform = sanitize_path_component(str(platform or ""), "unknown")
safe_name = sanitize_path_component(str(playlist_name or ""), f"playlist-{int(playlist_id)}")
return f"playlist-{safe_platform}-{int(playlist_id)}-{safe_name}.zip"
def build_multi_playlist_bundle_filename(*, created_at: datetime | None = None) -> str:
now = created_at or datetime.now()
return "playlists-export-" + now.strftime("%Y%m%d-%H%M%S") + ".zip"
def bundle_download_filename(bundle_path_or_name: str | Path) -> str:
filename = Path(bundle_path_or_name).name
if INTERNAL_BUNDLE_NAME_SEPARATOR not in filename:
return filename
return filename.split(INTERNAL_BUNDLE_NAME_SEPARATOR, 1)[1]
def resolve_bundle_download_path(bundle_root: Path, bundle_name: str) -> Path | None:
normalized_name = str(bundle_name or "").strip()
if not normalized_name:
return None
safe_name = sanitize_path_component(normalized_name, "")
if not safe_name or safe_name != normalized_name:
return None
return Path(bundle_root) / f"{normalized_name}.zip"
def create_single_playlist_bundle(
*,
playlist_dir: Path,
bundle_root: Path,
platform: str,
playlist_id: int,
playlist_name: str,
) -> Path:
source_dir = Path(playlist_dir)
if not source_dir.exists() or not source_dir.is_dir():
raise FileNotFoundError(f"playlist directory not found: {source_dir}")
root = Path(bundle_root)
root.mkdir(parents=True, exist_ok=True)
bundle_path = root / build_single_playlist_bundle_filename(
platform=platform,
playlist_id=playlist_id,
playlist_name=playlist_name,
)
_write_zip_from_directories(bundle_path, [(source_dir, source_dir.name)])
return bundle_path
def create_multi_playlist_bundle(
*,
playlist_dirs: Iterable[Path],
bundle_root: Path,
created_at: datetime | None = None,
) -> Path:
resolved_dirs: list[Path] = []
for item in playlist_dirs:
playlist_dir = Path(item)
if not playlist_dir.exists() or not playlist_dir.is_dir():
raise FileNotFoundError(f"playlist directory not found: {playlist_dir}")
resolved_dirs.append(playlist_dir)
if not resolved_dirs:
raise ValueError("playlist_dirs is required")
root = Path(bundle_root)
root.mkdir(parents=True, exist_ok=True)
friendly_name = build_multi_playlist_bundle_filename(created_at=created_at)
unique_storage_name = (
datetime.now().strftime("%Y%m%d%H%M%S%f")
+ "-"
+ uuid4().hex[:8]
+ INTERNAL_BUNDLE_NAME_SEPARATOR
+ friendly_name
)
bundle_path = root / unique_storage_name
_write_zip_from_directories(
bundle_path,
[(playlist_dir, f"playlists/{playlist_dir.name}") for playlist_dir in resolved_dirs],
)
return bundle_path
def _write_zip_from_directories(bundle_path: Path, directories: list[tuple[Path, str]]) -> None:
if bundle_path.exists():
bundle_path.unlink()
with zipfile.ZipFile(bundle_path, mode="w", compression=zipfile.ZIP_DEFLATED) as archive:
for source_dir, zip_root in directories:
for child in sorted(source_dir.rglob("*")):
if not child.is_file():
continue
relative_path = child.relative_to(source_dir).as_posix()
archive.write(child, arcname=f"{zip_root}/{relative_path}")