#!/usr/bin/env python3
"""Tiny LAN static server with HTTP Range support (so <audio> seeking works).

Python's stdlib http.server has no Range support → mobile/desktop seeking on
FLAC/MP3 fails. This adds 206 Partial Content. Binds 0.0.0.0 so you can open
the printed LAN URL on a phone on the same wifi.

  python3 serve.py --dir tide-table/punkachien --port 8731
"""
import argparse, os, re, socket
from functools import partial
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer


class RangeHandler(SimpleHTTPRequestHandler):
    def end_headers(self):
        self.send_header("Accept-Ranges", "bytes")
        self.send_header("Cache-Control", "no-cache")
        super().end_headers()

    def send_head(self):
        rng = self.headers.get("Range")
        if not rng:
            return super().send_head()
        path = self.translate_path(self.path)
        if not os.path.isfile(path):
            return super().send_head()
        m = re.match(r"bytes=(\d*)-(\d*)", rng)
        if not m:
            return super().send_head()
        size = os.path.getsize(path)
        start = int(m.group(1)) if m.group(1) else 0
        end = int(m.group(2)) if m.group(2) else size - 1
        end = min(end, size - 1)
        if start > end:
            self.send_error(416, "Requested Range Not Satisfiable")
            return None
        length = end - start + 1
        f = open(path, "rb")
        f.seek(start)
        self.send_response(206)
        self.send_header("Content-Type", self.guess_type(path))
        self.send_header("Content-Range", f"bytes {start}-{end}/{size}")
        self.send_header("Content-Length", str(length))
        self.end_headers()
        self._range_remaining = length
        return _LimitedReader(f, length)

    def copyfile(self, source, outputfile):
        if isinstance(source, _LimitedReader):
            remaining = source.remaining
            while remaining > 0:
                chunk = source.read(min(64 * 1024, remaining))
                if not chunk:
                    break
                outputfile.write(chunk)
                remaining -= len(chunk)
            source.close()
        else:
            super().copyfile(source, outputfile)


class _LimitedReader:
    def __init__(self, f, remaining):
        self.f, self.remaining = f, remaining

    def read(self, n):
        return self.f.read(n)

    def close(self):
        self.f.close()


def lan_ip():
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    try:
        s.connect(("192.168.1.1", 1))
        return s.getsockname()[0]
    except Exception:
        return "127.0.0.1"
    finally:
        s.close()


if __name__ == "__main__":
    ap = argparse.ArgumentParser()
    ap.add_argument("--dir", default=".")
    ap.add_argument("--port", type=int, default=8731)
    a = ap.parse_args()
    os.chdir(a.dir)
    ip = lan_ip()
    httpd = ThreadingHTTPServer(("0.0.0.0", a.port), partial(RangeHandler))
    print(f"serving {os.getcwd()}")
    print(f"  local : http://127.0.0.1:{a.port}/")
    print(f"  LAN   : http://{ip}:{a.port}/   ← open on phone (same wifi)")
    httpd.serve_forever()
