diff --git a/.gitignore b/.gitignore index 516722c..6920558 100644 --- a/.gitignore +++ b/.gitignore @@ -9,5 +9,11 @@ # Local tests (not committed) /tests/ +/TESTING/ /.pytest_cache/ /pytest.ini + +# Python artifacts +__pycache__/ +*.pyc +.coverage diff --git a/README.md b/README.md index 831ee23..b87f3e6 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,12 @@ ViewIT ist ein Kodi‑Addon zum Durchsuchen und Abspielen von Inhalten der unter - Kodi‑ZIP bauen: `./scripts/build_kodi_zip.sh` → `dist/-.zip` - Addon‑Version in `addon/addon.xml` +## Lokales Kodi-Repository +- Repository bauen (inkl. ZIPs + `addons.xml` + `addons.xml.md5`): `./scripts/build_local_kodi_repo.sh` +- Lokal bereitstellen: `./scripts/serve_local_kodi_repo.sh` +- Standard-URL: `http://127.0.0.1:8080/repo/addons.xml` +- Optional eigene URL beim Build setzen: `REPO_BASE_URL=http://:/repo ./scripts/build_local_kodi_repo.sh` + ## Entwicklung (kurz) - Hauptlogik: `addon/default.py` - Plugins: `addon/plugins/*_plugin.py` diff --git a/repository.viewit/addon.xml b/repository.viewit/addon.xml new file mode 100644 index 0000000..a69b3a0 --- /dev/null +++ b/repository.viewit/addon.xml @@ -0,0 +1,17 @@ + + + + + http://127.0.0.1:8080/repo/addons.xml + http://127.0.0.1:8080/repo/addons.xml.md5 + http://127.0.0.1:8080/repo/ + + + + Lokales Repository fuer ViewIT Updates + Local repository for ViewIT updates + Stellt das ViewIT Addon ueber ein Kodi Repository bereit. + Provides the ViewIT addon via a Kodi repository. + all + + diff --git a/scripts/build_local_kodi_repo.sh b/scripts/build_local_kodi_repo.sh new file mode 100755 index 0000000..ddedb92 --- /dev/null +++ b/scripts/build_local_kodi_repo.sh @@ -0,0 +1,110 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +DIST_DIR="${ROOT_DIR}/dist" +REPO_DIR="${DIST_DIR}/repo" +PLUGIN_ADDON_XML="${ROOT_DIR}/addon/addon.xml" +REPO_SRC_DIR="${ROOT_DIR}/repository.viewit" +REPO_ADDON_XML="${REPO_SRC_DIR}/addon.xml" +REPO_BASE_URL="${REPO_BASE_URL:-http://127.0.0.1:8080/repo}" + +if [[ ! -f "${PLUGIN_ADDON_XML}" ]]; then + echo "Missing: ${PLUGIN_ADDON_XML}" >&2 + exit 1 +fi + +if [[ ! -f "${REPO_ADDON_XML}" ]]; then + echo "Missing: ${REPO_ADDON_XML}" >&2 + exit 1 +fi + +mkdir -p "${REPO_DIR}" + +PLUGIN_ZIP="$("${ROOT_DIR}/scripts/build_kodi_zip.sh")" +cp -f "${PLUGIN_ZIP}" "${REPO_DIR}/" + +read -r REPO_ADDON_ID REPO_ADDON_VERSION < <(python3 - "${REPO_ADDON_XML}" <<'PY' +import sys +import xml.etree.ElementTree as ET + +root = ET.parse(sys.argv[1]).getroot() +print(root.attrib.get("id", "repository.viewit"), root.attrib.get("version", "0.0.0")) +PY +) + +TMP_DIR="$(mktemp -d)" +trap 'rm -rf "${TMP_DIR}"' EXIT +TMP_REPO_ADDON_DIR="${TMP_DIR}/${REPO_ADDON_ID}" +mkdir -p "${TMP_REPO_ADDON_DIR}" + +if command -v rsync >/dev/null 2>&1; then + rsync -a --delete "${REPO_SRC_DIR}/" "${TMP_REPO_ADDON_DIR}/" +else + cp -a "${REPO_SRC_DIR}/." "${TMP_REPO_ADDON_DIR}/" +fi + +python3 - "${TMP_REPO_ADDON_DIR}/addon.xml" "${REPO_BASE_URL}" <<'PY' +import sys +import xml.etree.ElementTree as ET + +addon_xml = sys.argv[1] +base_url = sys.argv[2].rstrip("/") + +tree = ET.parse(addon_xml) +root = tree.getroot() +dir_node = root.find(".//dir") +if dir_node is None: + raise SystemExit("Invalid repository addon.xml: missing ") + +info = dir_node.find("info") +checksum = dir_node.find("checksum") +datadir = dir_node.find("datadir") +if info is None or checksum is None or datadir is None: + raise SystemExit("Invalid repository addon.xml: missing info/checksum/datadir") + +info.text = f"{base_url}/addons.xml" +checksum.text = f"{base_url}/addons.xml.md5" +datadir.text = f"{base_url}/" + +tree.write(addon_xml, encoding="utf-8", xml_declaration=True) +PY + +REPO_ZIP_NAME="${REPO_ADDON_ID}-${REPO_ADDON_VERSION}.zip" +REPO_ZIP_PATH="${REPO_DIR}/${REPO_ZIP_NAME}" +rm -f "${REPO_ZIP_PATH}" +(cd "${TMP_DIR}" && zip -r "${REPO_ZIP_PATH}" "${REPO_ADDON_ID}" >/dev/null) + +python3 - "${PLUGIN_ADDON_XML}" "${TMP_REPO_ADDON_DIR}/addon.xml" "${REPO_DIR}/addons.xml" <<'PY' +import sys +import xml.etree.ElementTree as ET +from pathlib import Path + +plugin_xml = Path(sys.argv[1]) +repo_xml = Path(sys.argv[2]) +target = Path(sys.argv[3]) + +addons = ET.Element("addons") +for source in (plugin_xml, repo_xml): + root = ET.parse(source).getroot() + addons.append(root) + +target.write_text('\n' + ET.tostring(addons, encoding="unicode"), encoding="utf-8") +PY + +python3 - "${REPO_DIR}/addons.xml" "${REPO_DIR}/addons.xml.md5" <<'PY' +import hashlib +import sys +from pathlib import Path + +addons_xml = Path(sys.argv[1]) +md5_file = Path(sys.argv[2]) +md5 = hashlib.md5(addons_xml.read_bytes()).hexdigest() +md5_file.write_text(md5, encoding="ascii") +PY + +echo "Repo built:" +echo " ${REPO_DIR}/addons.xml" +echo " ${REPO_DIR}/addons.xml.md5" +echo " ${REPO_ZIP_PATH}" +echo " ${REPO_DIR}/$(basename "${PLUGIN_ZIP}")" diff --git a/scripts/serve_local_kodi_repo.sh b/scripts/serve_local_kodi_repo.sh new file mode 100755 index 0000000..4abc46c --- /dev/null +++ b/scripts/serve_local_kodi_repo.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +DIST_DIR="${ROOT_DIR}/dist" +HOST="${HOST:-127.0.0.1}" +PORT="${PORT:-8080}" + +if [[ ! -f "${DIST_DIR}/repo/addons.xml" ]]; then + echo "Missing ${DIST_DIR}/repo/addons.xml" >&2 + echo "Run ./scripts/build_local_kodi_repo.sh first." >&2 + exit 1 +fi + +echo "Serving local Kodi repo from ${DIST_DIR}" +echo "Repository URL: http://${HOST}:${PORT}/repo/addons.xml" +(cd "${DIST_DIR}" && python3 -m http.server "${PORT}" --bind "${HOST}")