foxygit / infodisplay Log in
commits tags

/kiosk/addons/emulator/routes.py · 5.26 KB

raw
from pathlib import Path
from flask import Blueprint, jsonify, request, abort, render_template, send_file
import db
import extensions
from auth import login_required
from config import ALLOWED_ROOTS

bp = Blueprint('emulator', __name__)

# Filändelse → EJS_core-namn
_EXT_TO_CORE = {
    '.sfc': 'snes', '.smc': 'snes',
    '.nes': 'nes',
    '.gba': 'gba',
    '.gbc': 'gbc', '.gb': 'gb',
    '.n64': 'n64', '.z64': 'n64', '.v64': 'n64',
    '.nds': 'nds',
    '.md':  'segaMD', '.gen': 'segaMD', '.smd': 'segaMD',
    '.bin': 'psx',   '.iso': 'psx',
    '.psp': 'psp',
    '.a26': 'atari2600',
    '.gba': 'gba',
}

_CORE_LABELS = {
    'snes':      'SNES',
    'nes':       'NES / Famicom',
    'gba':       'Game Boy Advance',
    'gbc':       'Game Boy Color',
    'gb':        'Game Boy',
    'n64':       'Nintendo 64',
    'nds':       'Nintendo DS',
    'segaMD':    'Sega Mega Drive',
    'psx':       'PlayStation 1',
    'psp':       'PSP',
    'atari2600': 'Atari 2600',
}


def _safe_path(path_str):
    """Returnerar ett validerat Path-objekt eller None om utanför ALLOWED_ROOTS."""
    try:
        p = Path(path_str).resolve()
        if any(str(p).startswith(str(r)) for r in ALLOWED_ROOTS):
            return p
    except Exception:
        pass
    return None


# --- Spelsida (laddas i display-iframe) ---

@bp.route('/emulator')
def emulator_page():
    rom_path = request.args.get('rom', '')
    core     = request.args.get('core', '')
    # Validera path
    if not rom_path or not _safe_path(rom_path):
        abort(403)
    if core not in _CORE_LABELS:
        core = _EXT_TO_CORE.get(Path(rom_path).suffix.lower(), 'snes')
    ejs_path = db.get_state().get('emulator_ejs_path',
                                  'https://cdn.emulatorjs.org/stable/data/')
    return render_template('emulator.html',
                           rom_path=rom_path,
                           rom_name=Path(rom_path).name,
                           core=core,
                           core_label=_CORE_LABELS.get(core, core),
                           ejs_path=ejs_path)


# --- ROM-fil (serveras till webbläsaren) ---

@bp.route('/api/emulator/rom')
def serve_rom():
    path_str = request.args.get('path', '')
    p = _safe_path(path_str)
    if not p or not p.is_file():
        abort(404)
    if p.suffix.lower() not in _EXT_TO_CORE:
        abort(403)
    return send_file(p)


# --- ROM-lista ---

@bp.route('/api/emulator/roms')
@login_required
def list_roms():
    folder_str = request.args.get('path', db.get_state().get('emulator_rom_dir', ''))
    if not folder_str:
        return jsonify([])
    folder = _safe_path(folder_str)
    if not folder or not folder.is_dir():
        return jsonify([])
    roms = []
    for f in sorted(folder.rglob('*')):
        if f.is_file() and f.suffix.lower() in _EXT_TO_CORE:
            core = _EXT_TO_CORE[f.suffix.lower()]
            roms.append({
                'name':       f.name,
                'path':       str(f),
                'core':       core,
                'core_label': _CORE_LABELS.get(core, core),
                'size_mb':    round(f.stat().st_size / 1_048_576, 1),
            })
    return jsonify(roms)


# --- Starta emulator på display ---

@bp.route('/api/emulator/launch', methods=['POST'])
@login_required
def launch():
    data     = request.get_json() or {}
    rom_path = str(data.get('rom', '')).strip()
    core     = str(data.get('core', '')).strip()

    if not rom_path or not _safe_path(rom_path):
        return jsonify({'error': 'Ogiltig ROM-sökväg'}), 400
    if core not in _CORE_LABELS:
        core = _EXT_TO_CORE.get(Path(rom_path).suffix.lower(), 'snes')

    db.set_state('emulator_active',  '1')
    db.set_state('emulator_rom',     Path(rom_path).name)
    db.set_state('emulator_rom_path', rom_path)
    db.set_state('emulator_core',    core)

    url = f'/emulator?rom={rom_path}&core={core}'
    extensions.socketio.emit('switch', {
        'type':      'url',
        'source':    url,
        'scheduled': False,
    })
    return '', 204


# --- Stoppa emulator ---

@bp.route('/api/emulator/stop', methods=['POST'])
@login_required
def stop():
    db.set_state('emulator_active', '0')
    extensions.socketio.emit('switch', {
        'type':      'local',
        'source':    '',
        'html':      '',
        'scheduled': False,
    })
    return '', 204


# --- Status ---

@bp.route('/api/emulator/status')
@login_required
def status():
    state = db.get_state()
    return jsonify({
        'active':     state.get('emulator_active', '0') == '1',
        'rom':        state.get('emulator_rom', ''),
        'core':       state.get('emulator_core', ''),
        'core_label': _CORE_LABELS.get(state.get('emulator_core', ''), ''),
        'rom_dir':    state.get('emulator_rom_dir', ''),
        'ejs_path':   state.get('emulator_ejs_path',
                                'https://cdn.emulatorjs.org/stable/data/'),
    })


# --- Spara inställningar ---

@bp.route('/api/emulator/settings', methods=['POST'])
@login_required
def save_settings():
    data     = request.get_json() or {}
    rom_dir  = str(data.get('rom_dir', '')).strip()
    ejs_path = str(data.get('ejs_path', '')).strip()
    if rom_dir:
        db.set_state('emulator_rom_dir', rom_dir)
    if ejs_path:
        db.set_state('emulator_ejs_path', ejs_path)
    return '', 204