#!/usr/bin/env python3
"""Create one isolated MiniDLNA playlist view; never modify the M3U8 source."""
import argparse
import os
from pathlib import Path
import tempfile

MARKER = '# Generated by MusicServer Connect for MiniDLNA'


def create_view(source, view, config, music_root):
    source, view, config, music_root = map(Path, (source, view, config, music_root))
    music_root = music_root.resolve(strict=True)
    source = source.resolve(strict=True)
    source.relative_to(music_root)
    if view.is_symlink() or config.is_symlink() or not config.is_file():
        raise ValueError('Zielverzeichnis oder Konfiguration nicht eindeutig.')
    resolved_view = view.resolve()
    if resolved_view == music_root or music_root in resolved_view.parents or resolved_view in music_root.parents:
        raise ValueError('Die MiniDLNA-Ansicht muss ausserhalb der gemeinsamen Musikbibliothek liegen.')
    target = view / (source.stem + '.m3u')
    if view.exists() and (not view.is_dir() or any(p.name != target.name for p in view.iterdir())):
        raise ValueError('Das Zielverzeichnis enthaelt andere Dateien; keine Aenderung.')
    if target.is_symlink() or (target.exists() and MARKER not in target.read_text(encoding='utf-8').splitlines()):
        raise ValueError('Die vorhandene Zieldatei wurde nicht von diesem Skript erstellt.')
    lines = ['#EXTM3U', MARKER]
    count = 0
    for line in source.read_text(encoding='utf-8-sig').splitlines():
        entry = line.strip()
        if not entry or entry == '#EXTM3U':
            continue
        if entry.startswith('#'):
            lines.append(line)
            continue
        path = Path(entry)
        path = (path if path.is_absolute() else source.parent / path).resolve(strict=True)
        path.relative_to(music_root)
        if not path.is_file():
            raise ValueError('Ein Playlist-Eintrag ist keine Musikdatei: ' + str(path))
        lines.append(path.as_posix())
        count += 1
    if not count:
        raise ValueError('Die Quellplaylist enthaelt keine Dateieintraege.')
    existing = config.read_bytes()
    registered = False
    for raw in existing.decode('utf-8').splitlines():
        setting = raw.split('#', 1)[0].strip()
        if '=' not in setting:
            continue
        key, value = (part.strip() for part in setting.split('=', 1))
        if key != 'media_dir':
            continue
        category, separator, directory = value.partition(',')
        directory = directory.strip() if separator else value
        if Path(directory).resolve() == resolved_view:
            if separator and category.strip() != 'A':
                raise ValueError('Das Ziel ist bereits mit einem anderen Medientyp konfiguriert.')
            registered = True
    content = ('\n'.join(lines) + '\n').encode('utf-8')
    view.mkdir(mode=0o755, parents=True, exist_ok=True)
    view.chmod(0o755)
    if not target.exists() or target.read_bytes() != content:
        descriptor, name = tempfile.mkstemp(prefix='.msc-', suffix='.tmp', dir=view)
        try:
            with os.fdopen(descriptor, 'wb') as stream:
                stream.write(content)
            os.chmod(name, 0o644)
            os.replace(name, target)
        finally:
            if os.path.exists(name):
                os.unlink(name)
    if not registered:
        addition = ('\nmedia_dir=A,' + resolved_view.as_posix() + '\n').encode('utf-8')
        with config.open('ab') as stream:
            stream.write(addition)
    print(f'Bereit: {target}; {count} Eintraege. M3U8-Quelle unveraendert.')


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument('--source', default='/srv/music/Playlists/Sven Väth - Loveparade 2000.m3u8')
    parser.add_argument('--view', default='/media3')
    parser.add_argument('--config', default='/etc/minidlna.conf')
    parser.add_argument('--music-root', default='/srv/music')
    args = parser.parse_args()
    try:
        create_view(args.source, args.view, args.config, args.music_root)
    except (OSError, ValueError, UnicodeError) as error:
        parser.exit(1, 'Abbruch: ' + str(error) + '\n')


if __name__ == '__main__':
    main()
