Show search progress per plugin during global search

This commit is contained in:
2026-02-01 23:26:12 +01:00
parent 1e3c6ffdf6
commit 372d443cb2

View File

@@ -147,6 +147,45 @@ def _busy_dialog():
_busy_close() _busy_close()
@contextmanager
def _progress_dialog(heading: str, message: str = ""):
"""Zeigt einen Fortschrittsdialog in Kodi und liefert eine Update-Funktion."""
dialog = None
try: # pragma: no cover - Kodi runtime
if xbmcgui is not None and hasattr(xbmcgui, "DialogProgress"):
dialog = xbmcgui.DialogProgress()
dialog.create(heading, message)
except Exception:
dialog = None
def _update(percent: int, text: str = "") -> bool:
if dialog is None:
return False
percent = max(0, min(100, int(percent)))
try: # Kodi Matrix/Nexus
dialog.update(percent, text)
except TypeError:
try: # Kodi Leia fallback
dialog.update(percent, text, "", "")
except Exception:
pass
except Exception:
pass
try:
return bool(dialog.iscanceled())
except Exception:
return False
try:
yield _update
finally:
if dialog is not None:
try:
dialog.close()
except Exception:
pass
def _get_handle() -> int: def _get_handle() -> int:
return int(sys.argv[1]) if len(sys.argv) > 1 else -1 return int(sys.argv[1]) if len(sys.argv) > 1 else -1
@@ -919,21 +958,27 @@ def _show_plugin_search_results(plugin_name: str, query: str) -> None:
_set_content(handle, "movies" if plugin_name.casefold() == "einschalten" else "tvshows") _set_content(handle, "movies" if plugin_name.casefold() == "einschalten" else "tvshows")
_log(f"Suche nach Titeln (Plugin={plugin_name}): {query}") _log(f"Suche nach Titeln (Plugin={plugin_name}): {query}")
list_items: list[dict[str, object]] = []
canceled = False
try: try:
with _progress_dialog("Suche läuft", f"{plugin_name} (1/1) starte…") as progress:
canceled = progress(5, f"{plugin_name} (1/1) Suche…")
results = _run_async(plugin.search_titles(query)) results = _run_async(plugin.search_titles(query))
except Exception as exc:
_log(f"Suche fehlgeschlagen ({plugin_name}): {exc}", xbmc.LOGWARNING)
xbmcgui.Dialog().notification("Suche", "Suche fehlgeschlagen.", xbmcgui.NOTIFICATION_INFO, 3000)
xbmcplugin.endOfDirectory(handle)
return
results = [str(t).strip() for t in (results or []) if t and str(t).strip()] results = [str(t).strip() for t in (results or []) if t and str(t).strip()]
results.sort(key=lambda value: value.casefold()) results.sort(key=lambda value: value.casefold())
tmdb_prefetched: dict[str, tuple[dict[str, str], dict[str, str], list[TmdbCastMember]]] = {} tmdb_prefetched: dict[str, tuple[dict[str, str], dict[str, str], list[TmdbCastMember]]] = {}
if results: if results and not canceled:
with _busy_dialog(): canceled = progress(35, f"{plugin_name} (1/1) Metadaten…")
tmdb_prefetched = _tmdb_labels_and_art_bulk(list(results)) tmdb_prefetched = _tmdb_labels_and_art_bulk(list(results))
for title in results:
total_results = max(1, len(results))
for index, title in enumerate(results, start=1):
if canceled:
break
if index == 1 or index == total_results or (index % 10 == 0):
pct = 35 + int((index / float(total_results)) * 60)
canceled = progress(pct, f"{plugin_name} (1/1) aufbereiten {index}/{total_results}")
info_labels, art, cast = tmdb_prefetched.get(title, _tmdb_labels_and_art(title)) info_labels, art, cast = tmdb_prefetched.get(title, _tmdb_labels_and_art(title))
info_labels = dict(info_labels or {}) info_labels = dict(info_labels or {})
info_labels.setdefault("mediatype", "tvshow") info_labels.setdefault("mediatype", "tvshow")
@@ -945,15 +990,37 @@ def _show_plugin_search_results(plugin_name: str, query: str) -> None:
display_label = _label_with_playstate(display_label, playstate) display_label = _label_with_playstate(display_label, playstate)
direct_play = bool(plugin_name.casefold() == "einschalten" and _get_setting_bool("einschalten_enable_playback", default=False)) direct_play = bool(plugin_name.casefold() == "einschalten" and _get_setting_bool("einschalten_enable_playback", default=False))
extra_params = _series_url_params(plugin, title) extra_params = _series_url_params(plugin, title)
list_items.append(
{
"label": display_label,
"action": "play_movie" if direct_play else "seasons",
"params": {"plugin": plugin_name, "title": title, **extra_params},
"is_folder": (not direct_play),
"info_labels": merged_info,
"art": art,
"cast": cast,
}
)
except Exception as exc:
_log(f"Suche fehlgeschlagen ({plugin_name}): {exc}", xbmc.LOGWARNING)
xbmcgui.Dialog().notification("Suche", "Suche fehlgeschlagen.", xbmcgui.NOTIFICATION_INFO, 3000)
xbmcplugin.endOfDirectory(handle)
return
if canceled and not list_items:
xbmcgui.Dialog().notification("Suche", "Suche abgebrochen.", xbmcgui.NOTIFICATION_INFO, 2500)
xbmcplugin.endOfDirectory(handle)
return
for item in list_items:
_add_directory_item( _add_directory_item(
handle, handle,
display_label, str(item["label"]),
"play_movie" if direct_play else "seasons", str(item["action"]),
{"plugin": plugin_name, "title": title, **extra_params}, dict(item["params"]),
is_folder=not direct_play, is_folder=bool(item["is_folder"]),
info_labels=merged_info, info_labels=item["info_labels"],
art=art, art=item["art"],
cast=cast, cast=item["cast"],
) )
xbmcplugin.endOfDirectory(handle) xbmcplugin.endOfDirectory(handle)
@@ -1058,18 +1125,42 @@ def _show_search_results(query: str) -> None:
xbmcgui.Dialog().notification("Suche", "Keine Plugins gefunden.", xbmcgui.NOTIFICATION_INFO, 3000) xbmcgui.Dialog().notification("Suche", "Keine Plugins gefunden.", xbmcgui.NOTIFICATION_INFO, 3000)
xbmcplugin.endOfDirectory(handle) xbmcplugin.endOfDirectory(handle)
return return
for plugin_name, plugin in plugins.items(): list_items: list[dict[str, object]] = []
canceled = False
plugin_entries = list(plugins.items())
total_plugins = max(1, len(plugin_entries))
with _progress_dialog("Suche läuft", "Suche gestartet…") as progress:
for plugin_index, (plugin_name, plugin) in enumerate(plugin_entries, start=1):
range_start = int(((plugin_index - 1) / float(total_plugins)) * 100)
range_end = int((plugin_index / float(total_plugins)) * 100)
canceled = progress(range_start, f"{plugin_name} ({plugin_index}/{total_plugins}) Suche…")
if canceled:
break
try: try:
results = _run_async(plugin.search_titles(query)) results = _run_async(plugin.search_titles(query))
except Exception as exc: except Exception as exc:
_log(f"Suche fehlgeschlagen ({plugin_name}): {exc}", xbmc.LOGWARNING) _log(f"Suche fehlgeschlagen ({plugin_name}): {exc}", xbmc.LOGWARNING)
continue continue
results = [str(t).strip() for t in (results or []) if t and str(t).strip()]
_log(f"Treffer ({plugin_name}): {len(results)}", xbmc.LOGDEBUG) _log(f"Treffer ({plugin_name}): {len(results)}", xbmc.LOGDEBUG)
tmdb_prefetched: dict[str, tuple[dict[str, str], dict[str, str], list[TmdbCastMember]]] = {} tmdb_prefetched: dict[str, tuple[dict[str, str], dict[str, str], list[TmdbCastMember]]] = {}
if results: if results:
with _busy_dialog(): canceled = progress(
range_start + int((range_end - range_start) * 0.35),
f"{plugin_name} ({plugin_index}/{total_plugins}) Metadaten…",
)
if canceled:
break
tmdb_prefetched = _tmdb_labels_and_art_bulk(list(results)) tmdb_prefetched = _tmdb_labels_and_art_bulk(list(results))
for title in results: total_results = max(1, len(results))
for title_index, title in enumerate(results, start=1):
if title_index == 1 or title_index == total_results or (title_index % 10 == 0):
canceled = progress(
range_start + int((range_end - range_start) * (0.35 + 0.65 * (title_index / float(total_results)))),
f"{plugin_name} ({plugin_index}/{total_plugins}) aufbereiten {title_index}/{total_results}",
)
if canceled:
break
info_labels, art, cast = tmdb_prefetched.get(title, _tmdb_labels_and_art(title)) info_labels, art, cast = tmdb_prefetched.get(title, _tmdb_labels_and_art(title))
info_labels = dict(info_labels or {}) info_labels = dict(info_labels or {})
info_labels.setdefault("mediatype", "tvshow") info_labels.setdefault("mediatype", "tvshow")
@@ -1084,15 +1175,36 @@ def _show_search_results(query: str) -> None:
plugin_name.casefold() == "einschalten" and _get_setting_bool("einschalten_enable_playback", default=False) plugin_name.casefold() == "einschalten" and _get_setting_bool("einschalten_enable_playback", default=False)
) )
extra_params = _series_url_params(plugin, title) extra_params = _series_url_params(plugin, title)
list_items.append(
{
"label": label,
"action": "play_movie" if direct_play else "seasons",
"params": {"plugin": plugin_name, "title": title, **extra_params},
"is_folder": (not direct_play),
"info_labels": merged_info,
"art": art,
"cast": cast,
}
)
if canceled:
break
if not canceled:
progress(100, "Suche abgeschlossen")
if canceled and not list_items:
xbmcgui.Dialog().notification("Suche", "Suche abgebrochen.", xbmcgui.NOTIFICATION_INFO, 2500)
xbmcplugin.endOfDirectory(handle)
return
for item in list_items:
_add_directory_item( _add_directory_item(
handle, handle,
label, str(item["label"]),
"play_movie" if direct_play else "seasons", str(item["action"]),
{"plugin": plugin_name, "title": title, **extra_params}, dict(item["params"]),
is_folder=not direct_play, is_folder=bool(item["is_folder"]),
info_labels=merged_info, info_labels=item["info_labels"],
art=art, art=item["art"],
cast=cast, cast=item["cast"],
) )
xbmcplugin.endOfDirectory(handle) xbmcplugin.endOfDirectory(handle)