roundabout,
created on Monday, 9 December 2024, 19:17:33 (1733771853),
received on Thursday, 12 December 2024, 14:59:13 (1734015553)
Author identity: vlad <vlad.muntoiu@gmail.com>
73af93568f38d2b440bc19ff7075cf352991dfe3
main.py
@@ -17,6 +17,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
""" import os import subprocessimport gi import asyncio import importlib
@@ -30,12 +32,23 @@ gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, Gdk, Pango, GLib import gettext import json import traceback import sys_ = gettext.gettext _T = TypeVar("_T") def get_parent_package(path): parent_path = os.path.split(path)[0] while parent_path != "/": if "__init__.py" in os.listdir(parent_path): return Path(parent_path) parent_path = os.path.split(parent_path)[0] return None async def merge_generators_with_params(generator_funcs: list[Callable[..., AsyncIterable]], *args, **kwargs): iterators = [gen_func(*args, **kwargs).__aiter__() for gen_func in generator_funcs]
@@ -91,6 +104,35 @@ class ResultInfoWidget(Gtk.ListBoxRow):
self.description_label.set_halign(Gtk.Align.START) self.label_box.pack_start(self.description_label, True, True, 0) class ProviderExceptionDialog(Gtk.Dialog): def __init__(self, provider: Path, exception: Exception, parent=None, tb=None): super().__init__(title=_("{provider} raised a {exception}".format(provider=get_parent_package(provider), exception=type(exception).__name__)), transient_for=parent) self.add_button(_("Open config"), 0) self.add_button(_("Reset config"), 1) self.add_button(_("Quit app"), Gtk.ResponseType.CLOSE) self.set_default_response(Gtk.ResponseType.CLOSE) self.set_default_size(360, 240) self.set_resizable(True) self.provider = provider self.exception = exception traceback_text = "\n".join(traceback.format_exception(type(exception), exception, tb)) self.label = Gtk.Label(label=traceback_text) monospaced_font = Pango.FontDescription() monospaced_font.set_family("monospace") self.label.override_font(monospaced_font) self.label.set_line_wrap(True) self.label.set_justify(Gtk.Justification.LEFT) self.label.set_halign(Gtk.Align.START) self.label.set_selectable(True) self.get_content_area().add(self.label) self.label.show() class Izvor(Gtk.Application): USER_CONFIGS = Path(os.getenv("XDG_CONFIG_HOME", Path.home() / ".config")) / "izvor" USER_DATA = Path(os.getenv("XDG_DATA_HOME", Path.home() / ".local" / "share")) / "izvor"
@@ -140,6 +182,17 @@ class Izvor(Gtk.Application):
print(f"Available providers: {list(self.available_providers.keys())}") for provider, path in self.available_providers.items(): if not (self.USER_PROVIDER_CONFIGS / f"{provider}.json").exists(): with open(self.USER_PROVIDER_CONFIGS / f"{provider}.json", "w") as f: spec = importlib.util.spec_from_file_location(provider, path / "__init__.py") module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) if hasattr(module, "config_template"): json.dump(module.config_template, f) else: json.dump({}, f) if not self.ENABLED_PROVIDERS_FILE.exists(): self.ENABLED_PROVIDERS_FILE.touch()
@@ -176,7 +229,9 @@ class Izvor(Gtk.Application):
spec = importlib.util.spec_from_file_location(provider.stem, provider / "__init__.py") module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) loaded_provider = module.Provider({})with open(self.USER_PROVIDER_CONFIGS / f"{provider.stem}.json", "r") as f: provider_config = json.load(f) loaded_provider = module.Provider(provider_config)self.providers.append(loaded_provider) print(f"Loaded provider: {loaded_provider.name}") print(loaded_provider.description)
@@ -211,9 +266,17 @@ class Izvor(Gtk.Application):
GLib.idle_add(self.update_enabled_providers, None) def update_enabled_providers(self, widget=None): self.providers = []for provider, checkbox in self.provider_checkboxes.items(): if checkbox.get_active(): self.enabled_providers[provider] = self.permanently_enabled_providers[provider] spec = importlib.util.spec_from_file_location(provider, self.enabled_providers[provider] / "__init__.py") module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) with open(self.USER_PROVIDER_CONFIGS / f"{provider}.json", "r") as f: provider_config = json.load(f) loaded_provider = module.Provider(provider_config) self.providers.append(loaded_provider)else: self.enabled_providers.pop(provider, None)
@@ -391,25 +454,49 @@ class Izvor(Gtk.Application):
generators = [] query = self.builder.get_object("search-query-buffer").get_text() # print(f"Query: {query}") for provider in self.enabled_providers.values():spec = importlib.util.spec_from_file_location(provider.stem, provider / "__init__.py")module = importlib.util.module_from_spec(spec)spec.loader.exec_module(module)loaded_provider = module.Provider({})# print(f"Searching with provider: {loaded_provider.name}")generators.append(loaded_provider.search)for provider in self.providers: generators.append(provider.search)# Clear the results list results_list = self.builder.get_object("results-list") for row in results_list.get_children(): results_list.remove(row) async for result in merge_generators_with_params(generators, query):# print(result)result_box = ResultInfoWidget(result["name"], result["description"], result["image"], result["execute"], self.icon_size, self.show_result_descriptions)results_list.add(result_box)result_box.show_all()try: async for result in merge_generators_with_params(generators, query): # print(result) result_box = ResultInfoWidget(result["name"], result["description"], result["image"], result["execute"], self.icon_size, self.show_result_descriptions) results_list.add(result_box) result_box.show_all() except Exception as e: exception_type, exception, tb = sys.exc_info() filename, line_number, function_name, line = traceback.extract_tb(tb)[-1] print(f"{filename} raised an exception!") print(f"{type(e).__name__}: {str(e)}") dialog = ProviderExceptionDialog(Path(filename), e, self.window, tb=tb) dialog.show_all() response = dialog.run() if response == 0: # Open config subprocess.Popen(["xdg-open", str(self.USER_PROVIDER_CONFIGS / f"{get_parent_package(filename).stem}.json")]) self.kill() elif response == 1: # Reset config spec = importlib.util.spec_from_file_location(get_parent_package(filename).stem, Path(filename)) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) if hasattr(module, "config_template"): with open(self.USER_PROVIDER_CONFIGS / f"{get_parent_package(filename).stem}.json", "w") as f: json.dump(module.config_template, f) else: with open(self.USER_PROVIDER_CONFIGS / f"{get_parent_package(filename).stem}.json", "w") as f: json.dump({}, f) self.kill() elif response == Gtk.ResponseType.CLOSE: self.kill()results_list.show_all() spinner.stop()