dev: harden resolver bootstrap and simplify update settings
This commit is contained in:
@@ -1,5 +1,14 @@
|
||||
# Changelog (Dev)
|
||||
|
||||
## 0.1.63-dev - 2026-02-24
|
||||
|
||||
- ResolveURL ist jetzt eine weiche Abhaengigkeit: ViewIt installiert auch ohne vorinstalliertes ResolveURL.
|
||||
- Neuer Settings-Action: `ResolveURL installieren/reparieren`.
|
||||
- Optionales Auto-Bootstrap: ResolveURL kann beim Start automatisch nachinstalliert werden.
|
||||
- Wiedergabe versucht bei fehlendem ResolveURL einmalig eine stille Nachinstallation und loest dann erneut auf.
|
||||
- Update-Settings aufgeraeumt: Fokus auf installierte Version, Kanalstatus und verfuegbare Version im gewaehlten Kanal.
|
||||
- Repo-Validierung als Script hinzugefuegt (`scripts/verify_repo_artifacts.py`) und in den lokalen Repo-Build eingebunden.
|
||||
|
||||
## 0.1.62-dev - 2026-02-24
|
||||
|
||||
- Neuer Dev-Stand fuer Genre-Performance (Serienstream).
|
||||
|
||||
@@ -21,6 +21,7 @@ Es durchsucht Provider und startet Streams.
|
||||
## Lokales Kodi Repository
|
||||
- Repository bauen: `./scripts/build_local_kodi_repo.sh`
|
||||
- Repository starten: `./scripts/serve_local_kodi_repo.sh`
|
||||
- Repo-Artefakte pruefen: `./scripts/verify_repo_artifacts.py ./dist/repo`
|
||||
- Standard URL: `http://127.0.0.1:8080/repo/addons.xml`
|
||||
- Eigene URL beim Build: `REPO_BASE_URL=http://<host>:<port>/repo ./scripts/build_local_kodi_repo.sh`
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<addon id="plugin.video.viewit" name="ViewIt" version="0.1.62-dev" provider-name="ViewIt">
|
||||
<addon id="plugin.video.viewit" name="ViewIt" version="0.1.63-dev" provider-name="ViewIt">
|
||||
<requires>
|
||||
<import addon="xbmc.python" version="3.0.0" />
|
||||
<import addon="script.module.requests" />
|
||||
<import addon="script.module.beautifulsoup4" />
|
||||
<import addon="script.module.resolveurl" />
|
||||
</requires>
|
||||
<extension point="xbmc.python.pluginsource" library="default.py">
|
||||
<provides>video</provides>
|
||||
|
||||
182
addon/default.py
182
addon/default.py
@@ -954,12 +954,6 @@ def _add_directory_item(
|
||||
xbmcplugin.addDirectoryItem(handle=handle, url=url, listitem=item, isFolder=is_folder)
|
||||
|
||||
|
||||
def _plugin_version(plugin: BasisPlugin) -> str:
|
||||
raw = getattr(plugin, "version", "0.0.0")
|
||||
text = str(raw or "").strip()
|
||||
return text or "0.0.0"
|
||||
|
||||
|
||||
def _normalize_update_info_url(raw: str) -> str:
|
||||
value = str(raw or "").strip()
|
||||
default = "http://127.0.0.1:8080/repo/addons.xml"
|
||||
@@ -976,6 +970,8 @@ UPDATE_CHANNEL_CUSTOM = 2
|
||||
AUTO_UPDATE_INTERVAL_SEC = 6 * 60 * 60
|
||||
UPDATE_HTTP_TIMEOUT_SEC = 8
|
||||
UPDATE_ADDON_ID = "plugin.video.viewit"
|
||||
RESOLVEURL_ADDON_ID = "script.module.resolveurl"
|
||||
RESOLVEURL_AUTO_INSTALL_INTERVAL_SEC = 6 * 60 * 60
|
||||
|
||||
|
||||
def _selected_update_channel() -> int:
|
||||
@@ -1030,10 +1026,7 @@ def _resolve_update_info_url() -> str:
|
||||
raw = _get_setting_string("update_repo_url")
|
||||
else:
|
||||
raw = _get_setting_string("update_repo_url_main")
|
||||
info_url = _normalize_update_info_url(raw)
|
||||
# Legacy-Setting beibehalten, damit bestehende Installationen und alte Builds weiterlaufen.
|
||||
_set_setting_string("update_repo_url", info_url)
|
||||
return info_url
|
||||
return _normalize_update_info_url(raw)
|
||||
|
||||
|
||||
def _read_text_url(url: str, *, timeout: int = UPDATE_HTTP_TIMEOUT_SEC) -> str:
|
||||
@@ -1247,25 +1240,10 @@ def _install_addon_version(info_url: str, version: str) -> bool:
|
||||
|
||||
def _sync_update_channel_status_settings() -> None:
|
||||
channel = _selected_update_channel()
|
||||
channel_label = _channel_label(channel)
|
||||
|
||||
selected_info_url = _resolve_update_info_url()
|
||||
main_info_url = _normalize_update_info_url(_get_setting_string("update_repo_url_main"))
|
||||
nightly_info_url = _normalize_update_info_url(_get_setting_string("update_repo_url_nightly"))
|
||||
|
||||
available_main = _fetch_repo_addon_version(main_info_url)
|
||||
available_nightly = _fetch_repo_addon_version(nightly_info_url)
|
||||
if channel == UPDATE_CHANNEL_MAIN:
|
||||
available_selected = available_main
|
||||
elif channel == UPDATE_CHANNEL_NIGHTLY:
|
||||
available_selected = available_nightly
|
||||
else:
|
||||
available_selected = _fetch_repo_addon_version(selected_info_url)
|
||||
|
||||
_set_setting_string("update_active_channel", channel_label)
|
||||
available_selected = _fetch_repo_addon_version(selected_info_url)
|
||||
_set_setting_string("update_active_channel", _channel_label(channel))
|
||||
_set_setting_string("update_active_repo_url", selected_info_url)
|
||||
_set_setting_string("update_available_main", available_main)
|
||||
_set_setting_string("update_available_nightly", available_nightly)
|
||||
_set_setting_string("update_available_selected", available_selected)
|
||||
|
||||
|
||||
@@ -1306,11 +1284,6 @@ def _update_repository_source(info_url: str) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def _settings_key_for_plugin(name: str) -> str:
|
||||
safe = re.sub(r"[^a-z0-9]+", "_", (name or "").strip().casefold()).strip("_")
|
||||
return f"update_version_{safe}" if safe else "update_version_unknown"
|
||||
|
||||
|
||||
def _installed_addon_version_from_disk() -> str:
|
||||
if xbmcvfs is None:
|
||||
return "0.0.0"
|
||||
@@ -1328,6 +1301,96 @@ def _installed_addon_version_from_disk() -> str:
|
||||
return "0.0.0"
|
||||
|
||||
|
||||
def _is_addon_installed(addon_id: str) -> bool:
|
||||
addon_id = str(addon_id or "").strip()
|
||||
if not addon_id:
|
||||
return False
|
||||
has_addon = getattr(xbmc, "getCondVisibility", None)
|
||||
if callable(has_addon):
|
||||
try:
|
||||
return bool(has_addon(f"System.HasAddon({addon_id})"))
|
||||
except Exception:
|
||||
pass
|
||||
if xbmcvfs is None:
|
||||
return False
|
||||
try:
|
||||
addon_xml = xbmcvfs.translatePath(f"special://home/addons/{addon_id}/addon.xml")
|
||||
except Exception:
|
||||
return False
|
||||
return bool(addon_xml and os.path.exists(addon_xml))
|
||||
|
||||
|
||||
def _sync_resolveurl_status_setting() -> None:
|
||||
status = "Installiert" if _is_addon_installed(RESOLVEURL_ADDON_ID) else "Fehlt"
|
||||
_set_setting_string("resolveurl_status", status)
|
||||
|
||||
|
||||
def _install_kodi_addon(addon_id: str, *, wait_seconds: int) -> bool:
|
||||
if _is_addon_installed(addon_id):
|
||||
return True
|
||||
builtin = getattr(xbmc, "executebuiltin", None)
|
||||
if not callable(builtin):
|
||||
return False
|
||||
try:
|
||||
builtin(f"InstallAddon({addon_id})")
|
||||
builtin("UpdateLocalAddons")
|
||||
except Exception as exc:
|
||||
_log(f"InstallAddon fehlgeschlagen ({addon_id}): {exc}", xbmc.LOGWARNING)
|
||||
return False
|
||||
|
||||
if wait_seconds <= 0:
|
||||
return _is_addon_installed(addon_id)
|
||||
deadline = time.time() + max(1, int(wait_seconds))
|
||||
while time.time() < deadline:
|
||||
if _is_addon_installed(addon_id):
|
||||
return True
|
||||
time.sleep(1)
|
||||
return _is_addon_installed(addon_id)
|
||||
|
||||
|
||||
def _ensure_resolveurl_installed(*, force: bool, silent: bool) -> bool:
|
||||
if _is_addon_installed(RESOLVEURL_ADDON_ID):
|
||||
_sync_resolveurl_status_setting()
|
||||
return True
|
||||
if not force and not _get_setting_bool("resolveurl_auto_install", default=True):
|
||||
_sync_resolveurl_status_setting()
|
||||
return False
|
||||
|
||||
now = int(time.time())
|
||||
if not force:
|
||||
last_try = _get_setting_int("resolveurl_last_ts", default=0)
|
||||
if last_try > 0 and (now - last_try) < RESOLVEURL_AUTO_INSTALL_INTERVAL_SEC:
|
||||
return False
|
||||
_set_setting_string("resolveurl_last_ts", str(now))
|
||||
|
||||
wait_seconds = 20 if force else 0
|
||||
ok = _install_kodi_addon(RESOLVEURL_ADDON_ID, wait_seconds=wait_seconds)
|
||||
_sync_resolveurl_status_setting()
|
||||
|
||||
if not silent:
|
||||
if ok:
|
||||
xbmcgui.Dialog().notification(
|
||||
"ResolveURL",
|
||||
"script.module.resolveurl ist installiert.",
|
||||
xbmcgui.NOTIFICATION_INFO,
|
||||
4000,
|
||||
)
|
||||
else:
|
||||
xbmcgui.Dialog().notification(
|
||||
"ResolveURL",
|
||||
"Installation fehlgeschlagen. Bitte Repository/Netzwerk pruefen.",
|
||||
xbmcgui.NOTIFICATION_ERROR,
|
||||
5000,
|
||||
)
|
||||
return ok
|
||||
|
||||
|
||||
def _maybe_auto_install_resolveurl(action: str | None) -> None:
|
||||
if (action or "").strip():
|
||||
return
|
||||
_ensure_resolveurl_installed(force=False, silent=True)
|
||||
|
||||
|
||||
def _sync_update_version_settings() -> None:
|
||||
addon_version = _installed_addon_version_from_disk()
|
||||
if addon_version == "0.0.0":
|
||||
@@ -1337,24 +1400,8 @@ def _sync_update_version_settings() -> None:
|
||||
addon_version = str(addon.getAddonInfo("version") or "0.0.0")
|
||||
except Exception:
|
||||
addon_version = "0.0.0"
|
||||
_set_setting_string("update_version_addon", addon_version)
|
||||
_set_setting_string("update_installed_version", addon_version)
|
||||
|
||||
versions = {
|
||||
"update_version_serienstream": "-",
|
||||
"update_version_aniworld": "-",
|
||||
"update_version_einschalten": "-",
|
||||
"update_version_topstreamfilm": "-",
|
||||
"update_version_filmpalast": "-",
|
||||
"update_version_doku_streams": "-",
|
||||
}
|
||||
for plugin in _discover_plugins().values():
|
||||
key = _settings_key_for_plugin(str(plugin.name))
|
||||
if key in versions:
|
||||
versions[key] = _plugin_version(plugin)
|
||||
for key, value in versions.items():
|
||||
_set_setting_string(key, value)
|
||||
|
||||
_sync_resolveurl_status_setting()
|
||||
_sync_update_channel_status_settings()
|
||||
|
||||
|
||||
@@ -3543,14 +3590,18 @@ def _show_version_selector() -> None:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
confirm_choice = xbmcgui.Dialog().select(
|
||||
"Version installieren",
|
||||
[
|
||||
f"Installieren: {version}",
|
||||
"Abbrechen",
|
||||
],
|
||||
)
|
||||
if confirm_choice != 0:
|
||||
dialog = xbmcgui.Dialog()
|
||||
try:
|
||||
confirmed = dialog.yesno(
|
||||
"Version installieren",
|
||||
f"Installiert: {installed}",
|
||||
f"Ausgewaehlt: {version}",
|
||||
yeslabel="Installieren",
|
||||
nolabel="Abbrechen",
|
||||
)
|
||||
except TypeError:
|
||||
confirmed = dialog.yesno("Version installieren", f"Installiert: {installed}", f"Ausgewaehlt: {version}")
|
||||
if not confirmed:
|
||||
return
|
||||
|
||||
xbmcgui.Dialog().notification("Updates", f"Installation gestartet: {version}", xbmcgui.NOTIFICATION_INFO, 2500)
|
||||
@@ -3609,6 +3660,10 @@ def _is_cloudflare_challenge_error(message: str) -> bool:
|
||||
return "cloudflare" in text or "challenge" in text or "attention required" in text
|
||||
|
||||
|
||||
def _is_resolveurl_missing_error(message: str) -> bool:
|
||||
return str(message or "").strip().casefold() == "resolveurl missing"
|
||||
|
||||
|
||||
def _play_final_link(
|
||||
link: str,
|
||||
*,
|
||||
@@ -3743,6 +3798,11 @@ def _play_episode(
|
||||
resolved_link = plugin.resolve_stream_link(link)
|
||||
if not resolved_link:
|
||||
err = _resolveurl_last_error()
|
||||
if _is_resolveurl_missing_error(err):
|
||||
_log("ResolveURL fehlt: versuche Auto-Installation.", xbmc.LOGWARNING)
|
||||
_ensure_resolveurl_installed(force=True, silent=True)
|
||||
resolved_link = plugin.resolve_stream_link(link)
|
||||
err = _resolveurl_last_error()
|
||||
if _is_cloudflare_challenge_error(err):
|
||||
_log(f"ResolveURL Cloudflare-Challenge: {err}", xbmc.LOGWARNING)
|
||||
xbmcgui.Dialog().notification(
|
||||
@@ -3853,6 +3913,11 @@ def _play_episode_url(
|
||||
resolved_link = plugin.resolve_stream_link(link)
|
||||
if not resolved_link:
|
||||
err = _resolveurl_last_error()
|
||||
if _is_resolveurl_missing_error(err):
|
||||
_log("ResolveURL fehlt: versuche Auto-Installation.", xbmc.LOGWARNING)
|
||||
_ensure_resolveurl_installed(force=True, silent=True)
|
||||
resolved_link = plugin.resolve_stream_link(link)
|
||||
err = _resolveurl_last_error()
|
||||
if _is_cloudflare_challenge_error(err):
|
||||
_log(f"ResolveURL Cloudflare-Challenge: {err}", xbmc.LOGWARNING)
|
||||
xbmcgui.Dialog().notification(
|
||||
@@ -3914,6 +3979,7 @@ def run() -> None:
|
||||
action = params.get("action")
|
||||
_log(f"Action: {action}", xbmc.LOGDEBUG)
|
||||
_maybe_run_auto_update_check(action)
|
||||
_maybe_auto_install_resolveurl(action)
|
||||
if action == "search":
|
||||
_show_search()
|
||||
elif action == "plugin_menu":
|
||||
@@ -3991,6 +4057,8 @@ def run() -> None:
|
||||
_apply_update_channel()
|
||||
elif action == "select_update_version":
|
||||
_show_version_selector()
|
||||
elif action == "install_resolveurl":
|
||||
_ensure_resolveurl_installed(force=True, silent=False)
|
||||
elif action == "seasons":
|
||||
_show_seasons(params.get("plugin", ""), params.get("title", ""), params.get("series_url", ""))
|
||||
elif action == "episodes":
|
||||
|
||||
@@ -23,6 +23,7 @@ def resolve(url: str) -> Optional[str]:
|
||||
try:
|
||||
import resolveurl # type: ignore
|
||||
except Exception:
|
||||
_LAST_RESOLVE_ERROR = "resolveurl missing"
|
||||
return None
|
||||
|
||||
try:
|
||||
@@ -36,7 +37,10 @@ def resolve(url: str) -> Optional[str]:
|
||||
resolver = getattr(hmf, "resolve", None)
|
||||
if callable(resolver):
|
||||
result = resolver()
|
||||
return str(result) if result else None
|
||||
if result:
|
||||
return str(result)
|
||||
_LAST_RESOLVE_ERROR = "unresolved"
|
||||
return None
|
||||
except Exception as exc:
|
||||
_LAST_RESOLVE_ERROR = str(exc or "")
|
||||
pass
|
||||
@@ -45,7 +49,10 @@ def resolve(url: str) -> Optional[str]:
|
||||
resolve_fn = getattr(resolveurl, "resolve", None)
|
||||
if callable(resolve_fn):
|
||||
result = resolve_fn(url)
|
||||
return str(result) if result else None
|
||||
if result:
|
||||
return str(result)
|
||||
_LAST_RESOLVE_ERROR = "unresolved"
|
||||
return None
|
||||
except Exception as exc:
|
||||
_LAST_RESOLVE_ERROR = str(exc or "")
|
||||
return None
|
||||
|
||||
@@ -40,24 +40,18 @@
|
||||
<setting id="apply_update_channel" type="action" label="Update-Kanal jetzt anwenden" action="RunPlugin(plugin://plugin.video.viewit/?action=apply_update_channel)" option="close" />
|
||||
<setting id="auto_update_enabled" type="bool" label="Automatische Updates (beim Start pruefen)" default="false" />
|
||||
<setting id="select_update_version" type="action" label="Version waehlen und installieren" action="RunPlugin(plugin://plugin.video.viewit/?action=select_update_version)" option="close" />
|
||||
<setting id="install_resolveurl" type="action" label="ResolveURL installieren/reparieren" action="RunPlugin(plugin://plugin.video.viewit/?action=install_resolveurl)" option="close" />
|
||||
<setting id="resolveurl_auto_install" type="bool" label="ResolveURL automatisch installieren (beim Start pruefen)" default="true" />
|
||||
<setting id="update_installed_version" type="text" label="Installierte Version" default="-" enable="false" />
|
||||
<setting id="update_available_selected" type="text" label="Verfuegbar (gewaehlter Kanal)" default="-" enable="false" />
|
||||
<setting id="update_available_main" type="text" label="Verfuegbar Main" default="-" enable="false" />
|
||||
<setting id="update_available_nightly" type="text" label="Verfuegbar Nightly" default="-" enable="false" />
|
||||
<setting id="resolveurl_status" type="text" label="ResolveURL Status" default="-" enable="false" />
|
||||
<setting id="update_active_channel" type="text" label="Aktiver Kanal" default="-" enable="false" />
|
||||
<setting id="update_active_repo_url" type="text" label="Aktive Repo URL" default="-" enable="false" />
|
||||
<setting id="update_info" type="text" label="Updates laufen ueber den normalen Kodi-Update-Mechanismus." default="" enable="false" />
|
||||
<setting id="update_repo_url_main" type="text" label="Main URL (addons.xml)" default="https://gitea.it-drui.de/viewit/ViewIT-Kodi-Repo/raw/branch/main/addons.xml" />
|
||||
<setting id="update_repo_url_nightly" type="text" label="Nightly URL (addons.xml)" default="https://gitea.it-drui.de/viewit/ViewIT-Kodi-Repo/raw/branch/nightly/addons.xml" />
|
||||
<setting id="update_repo_url" type="text" label="Custom URL (addons.xml)" default="https://gitea.it-drui.de/viewit/ViewIT-Kodi-Repo/raw/branch/nightly/addons.xml" />
|
||||
<setting id="auto_update_last_ts" type="text" label="Auto-Update letzte Pruefung (intern)" default="0" visible="false" />
|
||||
<setting id="update_version_addon" type="text" label="ViewIT Version" default="-" visible="false" />
|
||||
<setting id="update_version_serienstream" type="text" label="SerienStream Version" default="-" visible="false" />
|
||||
<setting id="update_version_aniworld" type="text" label="AniWorld Version" default="-" visible="false" />
|
||||
<setting id="update_version_einschalten" type="text" label="Einschalten Version" default="-" visible="false" />
|
||||
<setting id="update_version_topstreamfilm" type="text" label="TopStream Version" default="-" visible="false" />
|
||||
<setting id="update_version_filmpalast" type="text" label="Filmpalast Version" default="-" visible="false" />
|
||||
<setting id="update_version_doku_streams" type="text" label="Doku-Streams Version" default="-" visible="false" />
|
||||
<setting id="resolveurl_last_ts" type="text" label="ResolveURL letzte Pruefung (intern)" default="0" visible="false" />
|
||||
</category>
|
||||
|
||||
<category label="Debug Global">
|
||||
|
||||
@@ -118,6 +118,8 @@ md5 = hashlib.md5(addons_xml.read_bytes()).hexdigest()
|
||||
md5_file.write_text(md5, encoding="ascii")
|
||||
PY
|
||||
|
||||
python3 "${ROOT_DIR}/scripts/verify_repo_artifacts.py" "${REPO_DIR}" >/dev/null
|
||||
|
||||
echo "Repo built:"
|
||||
echo " ${REPO_DIR}/addons.xml"
|
||||
echo " ${REPO_DIR}/addons.xml.md5"
|
||||
|
||||
147
scripts/verify_repo_artifacts.py
Executable file
147
scripts/verify_repo_artifacts.py
Executable file
@@ -0,0 +1,147 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Validate Kodi repository artifacts for ViewIT.
|
||||
|
||||
Usage:
|
||||
verify_repo_artifacts.py <repo_dir> [--expect-branch <branch>]
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import sys
|
||||
import xml.etree.ElementTree as ET
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
PLUGIN_ID = "plugin.video.viewit"
|
||||
REPO_ID = "repository.viewit"
|
||||
|
||||
|
||||
def _find_addon(root: ET.Element, addon_id: str) -> ET.Element:
|
||||
if root.tag == "addon" and (root.attrib.get("id") or "") == addon_id:
|
||||
return root
|
||||
for addon in root.findall("addon"):
|
||||
if (addon.attrib.get("id") or "") == addon_id:
|
||||
return addon
|
||||
raise ValueError(f"addon {addon_id} not found in addons.xml")
|
||||
|
||||
|
||||
def _read_zip_addon_version(zip_path: Path, addon_id: str) -> str:
|
||||
inner_path = f"{addon_id}/addon.xml"
|
||||
with zipfile.ZipFile(zip_path, "r") as archive:
|
||||
try:
|
||||
data = archive.read(inner_path)
|
||||
except KeyError as exc:
|
||||
raise ValueError(f"{zip_path.name}: missing {inner_path}") from exc
|
||||
root = ET.fromstring(data.decode("utf-8", errors="replace"))
|
||||
version = (root.attrib.get("version") or "").strip()
|
||||
if not version:
|
||||
raise ValueError(f"{zip_path.name}: addon.xml without version")
|
||||
return version
|
||||
|
||||
|
||||
def _check_md5(repo_dir: Path) -> list[str]:
|
||||
errors: list[str] = []
|
||||
addons_xml = repo_dir / "addons.xml"
|
||||
md5_file = repo_dir / "addons.xml.md5"
|
||||
if not addons_xml.exists() or not md5_file.exists():
|
||||
return errors
|
||||
expected = md5_file.read_text(encoding="ascii", errors="ignore").strip().lower()
|
||||
actual = hashlib.md5(addons_xml.read_bytes()).hexdigest()
|
||||
if expected != actual:
|
||||
errors.append("addons.xml.md5 does not match addons.xml")
|
||||
return errors
|
||||
|
||||
|
||||
def _check_repo_zip_branch(zip_path: Path, expected_branch: str) -> list[str]:
|
||||
errors: list[str] = []
|
||||
inner_path = f"{REPO_ID}/addon.xml"
|
||||
with zipfile.ZipFile(zip_path, "r") as archive:
|
||||
try:
|
||||
data = archive.read(inner_path)
|
||||
except KeyError as exc:
|
||||
raise ValueError(f"{zip_path.name}: missing {inner_path}") from exc
|
||||
root = ET.fromstring(data.decode("utf-8", errors="replace"))
|
||||
info = root.find(".//dir/info")
|
||||
if info is None or not (info.text or "").strip():
|
||||
errors.append(f"{zip_path.name}: missing repository info URL")
|
||||
return errors
|
||||
info_url = (info.text or "").strip()
|
||||
marker = f"/branch/{expected_branch}/addons.xml"
|
||||
if marker not in info_url:
|
||||
errors.append(f"{zip_path.name}: info URL does not point to branch '{expected_branch}'")
|
||||
return errors
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("repo_dir", help="Path to repository root (contains addons.xml)")
|
||||
parser.add_argument("--expect-branch", default="", help="Expected branch in repository.viewit addon.xml URL")
|
||||
args = parser.parse_args()
|
||||
|
||||
repo_dir = Path(args.repo_dir).resolve()
|
||||
addons_xml = repo_dir / "addons.xml"
|
||||
if not addons_xml.exists():
|
||||
print(f"Missing: {addons_xml}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
errors: list[str] = []
|
||||
try:
|
||||
root = ET.parse(addons_xml).getroot()
|
||||
plugin_node = _find_addon(root, PLUGIN_ID)
|
||||
repo_node = _find_addon(root, REPO_ID)
|
||||
except Exception as exc:
|
||||
print(f"Invalid addons.xml: {exc}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
plugin_version = (plugin_node.attrib.get("version") or "").strip()
|
||||
repo_version = (repo_node.attrib.get("version") or "").strip()
|
||||
if not plugin_version:
|
||||
errors.append("plugin.video.viewit has no version in addons.xml")
|
||||
if not repo_version:
|
||||
errors.append("repository.viewit has no version in addons.xml")
|
||||
|
||||
plugin_zip = repo_dir / PLUGIN_ID / f"{PLUGIN_ID}-{plugin_version}.zip"
|
||||
repo_zip = repo_dir / REPO_ID / f"{REPO_ID}-{repo_version}.zip"
|
||||
if not plugin_zip.exists():
|
||||
errors.append(f"Missing plugin zip: {plugin_zip}")
|
||||
if not repo_zip.exists():
|
||||
errors.append(f"Missing repository zip: {repo_zip}")
|
||||
|
||||
if plugin_zip.exists():
|
||||
try:
|
||||
zip_version = _read_zip_addon_version(plugin_zip, PLUGIN_ID)
|
||||
if zip_version != plugin_version:
|
||||
errors.append(
|
||||
f"{plugin_zip.name}: version mismatch (zip={zip_version}, addons.xml={plugin_version})"
|
||||
)
|
||||
except Exception as exc:
|
||||
errors.append(str(exc))
|
||||
|
||||
if repo_zip.exists():
|
||||
try:
|
||||
zip_version = _read_zip_addon_version(repo_zip, REPO_ID)
|
||||
if zip_version != repo_version:
|
||||
errors.append(f"{repo_zip.name}: version mismatch (zip={zip_version}, addons.xml={repo_version})")
|
||||
if args.expect_branch:
|
||||
errors.extend(_check_repo_zip_branch(repo_zip, args.expect_branch))
|
||||
except Exception as exc:
|
||||
errors.append(str(exc))
|
||||
|
||||
errors.extend(_check_md5(repo_dir))
|
||||
|
||||
if errors:
|
||||
print("Repository validation failed:")
|
||||
for line in errors:
|
||||
print(f"- {line}")
|
||||
return 1
|
||||
|
||||
print("Repository validation passed.")
|
||||
print(f"- plugin: {plugin_version}")
|
||||
print(f"- repository: {repo_version}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user