Show search progress per plugin during global search
This commit is contained in:
234
addon/default.py
234
addon/default.py
@@ -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,41 +958,69 @@ 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:
|
||||||
results = _run_async(plugin.search_titles(query))
|
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 = [str(t).strip() for t in (results or []) if t and str(t).strip()]
|
||||||
|
results.sort(key=lambda value: value.casefold())
|
||||||
|
|
||||||
|
tmdb_prefetched: dict[str, tuple[dict[str, str], dict[str, str], list[TmdbCastMember]]] = {}
|
||||||
|
if results and not canceled:
|
||||||
|
canceled = progress(35, f"{plugin_name} (1/1) Metadaten…")
|
||||||
|
tmdb_prefetched = _tmdb_labels_and_art_bulk(list(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 = dict(info_labels or {})
|
||||||
|
info_labels.setdefault("mediatype", "tvshow")
|
||||||
|
if (info_labels.get("mediatype") or "").strip().casefold() == "tvshow":
|
||||||
|
info_labels.setdefault("tvshowtitle", title)
|
||||||
|
playstate = _title_playstate(plugin_name, title)
|
||||||
|
merged_info = _apply_playstate_to_info(dict(info_labels), playstate)
|
||||||
|
display_label = _label_with_duration(title, info_labels)
|
||||||
|
display_label = _label_with_playstate(display_label, playstate)
|
||||||
|
direct_play = bool(plugin_name.casefold() == "einschalten" and _get_setting_bool("einschalten_enable_playback", default=False))
|
||||||
|
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:
|
except Exception as exc:
|
||||||
_log(f"Suche fehlgeschlagen ({plugin_name}): {exc}", xbmc.LOGWARNING)
|
_log(f"Suche fehlgeschlagen ({plugin_name}): {exc}", xbmc.LOGWARNING)
|
||||||
xbmcgui.Dialog().notification("Suche", "Suche fehlgeschlagen.", xbmcgui.NOTIFICATION_INFO, 3000)
|
xbmcgui.Dialog().notification("Suche", "Suche fehlgeschlagen.", xbmcgui.NOTIFICATION_INFO, 3000)
|
||||||
xbmcplugin.endOfDirectory(handle)
|
xbmcplugin.endOfDirectory(handle)
|
||||||
return
|
return
|
||||||
|
if canceled and not list_items:
|
||||||
|
xbmcgui.Dialog().notification("Suche", "Suche abgebrochen.", xbmcgui.NOTIFICATION_INFO, 2500)
|
||||||
|
xbmcplugin.endOfDirectory(handle)
|
||||||
|
return
|
||||||
|
|
||||||
results = [str(t).strip() for t in (results or []) if t and str(t).strip()]
|
for item in list_items:
|
||||||
results.sort(key=lambda value: value.casefold())
|
|
||||||
tmdb_prefetched: dict[str, tuple[dict[str, str], dict[str, str], list[TmdbCastMember]]] = {}
|
|
||||||
if results:
|
|
||||||
with _busy_dialog():
|
|
||||||
tmdb_prefetched = _tmdb_labels_and_art_bulk(list(results))
|
|
||||||
for title in results:
|
|
||||||
info_labels, art, cast = tmdb_prefetched.get(title, _tmdb_labels_and_art(title))
|
|
||||||
info_labels = dict(info_labels or {})
|
|
||||||
info_labels.setdefault("mediatype", "tvshow")
|
|
||||||
if (info_labels.get("mediatype") or "").strip().casefold() == "tvshow":
|
|
||||||
info_labels.setdefault("tvshowtitle", title)
|
|
||||||
playstate = _title_playstate(plugin_name, title)
|
|
||||||
merged_info = _apply_playstate_to_info(dict(info_labels), playstate)
|
|
||||||
display_label = _label_with_duration(title, info_labels)
|
|
||||||
display_label = _label_with_playstate(display_label, playstate)
|
|
||||||
direct_play = bool(plugin_name.casefold() == "einschalten" and _get_setting_bool("einschalten_enable_playback", default=False))
|
|
||||||
extra_params = _series_url_params(plugin, title)
|
|
||||||
_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,42 +1125,87 @@ 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]] = []
|
||||||
try:
|
canceled = False
|
||||||
results = _run_async(plugin.search_titles(query))
|
plugin_entries = list(plugins.items())
|
||||||
except Exception as exc:
|
total_plugins = max(1, len(plugin_entries))
|
||||||
_log(f"Suche fehlgeschlagen ({plugin_name}): {exc}", xbmc.LOGWARNING)
|
with _progress_dialog("Suche läuft", "Suche gestartet…") as progress:
|
||||||
continue
|
for plugin_index, (plugin_name, plugin) in enumerate(plugin_entries, start=1):
|
||||||
_log(f"Treffer ({plugin_name}): {len(results)}", xbmc.LOGDEBUG)
|
range_start = int(((plugin_index - 1) / float(total_plugins)) * 100)
|
||||||
tmdb_prefetched: dict[str, tuple[dict[str, str], dict[str, str], list[TmdbCastMember]]] = {}
|
range_end = int((plugin_index / float(total_plugins)) * 100)
|
||||||
if results:
|
canceled = progress(range_start, f"{plugin_name} ({plugin_index}/{total_plugins}) Suche…")
|
||||||
with _busy_dialog():
|
if canceled:
|
||||||
|
break
|
||||||
|
try:
|
||||||
|
results = _run_async(plugin.search_titles(query))
|
||||||
|
except Exception as exc:
|
||||||
|
_log(f"Suche fehlgeschlagen ({plugin_name}): {exc}", xbmc.LOGWARNING)
|
||||||
|
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)
|
||||||
|
tmdb_prefetched: dict[str, tuple[dict[str, str], dict[str, str], list[TmdbCastMember]]] = {}
|
||||||
|
if results:
|
||||||
|
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))
|
||||||
info_labels, art, cast = tmdb_prefetched.get(title, _tmdb_labels_and_art(title))
|
for title_index, title in enumerate(results, start=1):
|
||||||
info_labels = dict(info_labels or {})
|
if title_index == 1 or title_index == total_results or (title_index % 10 == 0):
|
||||||
info_labels.setdefault("mediatype", "tvshow")
|
canceled = progress(
|
||||||
if (info_labels.get("mediatype") or "").strip().casefold() == "tvshow":
|
range_start + int((range_end - range_start) * (0.35 + 0.65 * (title_index / float(total_results)))),
|
||||||
info_labels.setdefault("tvshowtitle", title)
|
f"{plugin_name} ({plugin_index}/{total_plugins}) aufbereiten {title_index}/{total_results}",
|
||||||
playstate = _title_playstate(plugin_name, title)
|
)
|
||||||
merged_info = _apply_playstate_to_info(dict(info_labels), playstate)
|
if canceled:
|
||||||
label = _label_with_duration(title, info_labels)
|
break
|
||||||
label = _label_with_playstate(label, playstate)
|
info_labels, art, cast = tmdb_prefetched.get(title, _tmdb_labels_and_art(title))
|
||||||
label = f"{label} [{plugin_name}]"
|
info_labels = dict(info_labels or {})
|
||||||
direct_play = bool(
|
info_labels.setdefault("mediatype", "tvshow")
|
||||||
plugin_name.casefold() == "einschalten" and _get_setting_bool("einschalten_enable_playback", default=False)
|
if (info_labels.get("mediatype") or "").strip().casefold() == "tvshow":
|
||||||
)
|
info_labels.setdefault("tvshowtitle", title)
|
||||||
extra_params = _series_url_params(plugin, title)
|
playstate = _title_playstate(plugin_name, title)
|
||||||
_add_directory_item(
|
merged_info = _apply_playstate_to_info(dict(info_labels), playstate)
|
||||||
handle,
|
label = _label_with_duration(title, info_labels)
|
||||||
label,
|
label = _label_with_playstate(label, playstate)
|
||||||
"play_movie" if direct_play else "seasons",
|
label = f"{label} [{plugin_name}]"
|
||||||
{"plugin": plugin_name, "title": title, **extra_params},
|
direct_play = bool(
|
||||||
is_folder=not direct_play,
|
plugin_name.casefold() == "einschalten" and _get_setting_bool("einschalten_enable_playback", default=False)
|
||||||
info_labels=merged_info,
|
)
|
||||||
art=art,
|
extra_params = _series_url_params(plugin, title)
|
||||||
cast=cast,
|
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(
|
||||||
|
handle,
|
||||||
|
str(item["label"]),
|
||||||
|
str(item["action"]),
|
||||||
|
dict(item["params"]),
|
||||||
|
is_folder=bool(item["is_folder"]),
|
||||||
|
info_labels=item["info_labels"],
|
||||||
|
art=item["art"],
|
||||||
|
cast=item["cast"],
|
||||||
|
)
|
||||||
xbmcplugin.endOfDirectory(handle)
|
xbmcplugin.endOfDirectory(handle)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user