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 subprocess
import 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()