diff --git a/README.rst b/README.rst index 4078f9c6..e7815836 100644 --- a/README.rst +++ b/README.rst @@ -199,7 +199,7 @@ Pinned requirements .. code:: bash pip install pip-tools - pip-compile requirements.in > requirements.txt + pip-compile --resolver=backtracking requirements.in > requirements.txt GTK debugging ^^^^^^^^^^^^^ diff --git a/dtool_lookup_gui/main.py b/dtool_lookup_gui/main.py index 18964e65..78f325cb 100644 --- a/dtool_lookup_gui/main.py +++ b/dtool_lookup_gui/main.py @@ -34,6 +34,8 @@ import dtoolcore import dtool_lookup_api.core.config +from dtool_lookup_api.core.LookupClient import authenticate + import gi gi.require_version('Gtk', '3.0') gi.require_version('GtkSource', '4') @@ -44,7 +46,9 @@ from .models.settings import settings + from .views.main_window import MainWindow +from .views.login_window import LoginWindow from .utils.logging import _log_nested @@ -56,7 +60,6 @@ import dtool_lookup_gui.widgets.progress_chart import dtool_lookup_gui.widgets.progress_popover_menu - logger = logging.getLogger(__name__) from . import __version__ @@ -225,6 +228,11 @@ def do_startup(self): export_config_action.connect("activate", self.do_export_config) self.add_action(export_config_action) + # renew-token action + renew_token_action = Gio.SimpleAction.new("renew-token", GLib.VariantType.new("(sss)")) + renew_token_action.connect("activate", self.do_renew_token) + self.add_action(renew_token_action) + Gtk.Application.do_startup(self) # custom application-scoped actions @@ -308,6 +316,27 @@ def do_dtool_config_changed(self): first when emitting dtool-config-changed signal.""" logger.debug("method handler for 'dtool-config-changed' called.") + def do_renew_token(self, action, value): + """Request new token.""" + + # Unpack the username, password, and auth_url from the tuple variant + username, password, auth_url = value.unpack() + + async def retrieve_token(auth_url, username, password): + try: + token = await authenticate(auth_url, username, password) + except Exception as e: + logger.error(str(e)) + return + + dtool_lookup_api.core.config.Config.token = token + self.emit('dtool-config-changed') + + asyncio.create_task(retrieve_token( + auth_url, + username, + password)) + def run_gui(): GObject.type_register(GtkSource.View) diff --git a/dtool_lookup_gui/models/datasets.py b/dtool_lookup_gui/models/datasets.py index 80e2cf5e..e6b0c468 100644 --- a/dtool_lookup_gui/models/datasets.py +++ b/dtool_lookup_gui/models/datasets.py @@ -26,6 +26,7 @@ import asyncio import logging import os +import json import yaml from concurrent.futures import ProcessPoolExecutor @@ -37,6 +38,8 @@ from dtool_info.inventory import _dataset_info from dtool_lookup_api.core.LookupClient import ConfigurationBasedLookupClient + + from ..utils.logging import _log_nested from ..utils.multiprocessing import StatusReportingChildProcessBuilder, process_initializer from ..utils.progressbar import ProgressBar @@ -280,12 +283,19 @@ async def query(cls, query_text, *args, **kwargs): return [await cls.from_lookup(lookup_dict) for lookup_dict in datasets] @classmethod - async def query_all(cls, page_number=1, page_size=8, pagination={}): + async def query_all(cls, page_number=1, page_size=10, pagination={}): """Query all datasets from the lookup server.""" async with ConfigurationBasedLookupClient() as lookup: datasets = await lookup.all(page_number=page_number, page_size=page_size, pagination=pagination) return [await cls.from_lookup(lookup_dict) for lookup_dict in datasets] + @classmethod + async def versions(cls): + """To return version info from the server """ + async with ConfigurationBasedLookupClient() as lookup: + version_info = await lookup.versions() + print(version_info) + def __str__(self): return self._dataset_info['uri'] diff --git a/dtool_lookup_gui/views/login_window.py b/dtool_lookup_gui/views/login_window.py new file mode 100644 index 00000000..3267f00e --- /dev/null +++ b/dtool_lookup_gui/views/login_window.py @@ -0,0 +1,81 @@ +import logging +import os +import dtoolcore +from gi.repository import Gio, GLib, Gtk, Gdk +from dtool_lookup_api.core.config import Config +from ..utils.about import pretty_version_text +from ..utils.logging import _log_nested +from .settings_dialog import SettingsDialog + +# Initialize logging +logger = logging.getLogger(__name__) + +@Gtk.Template(filename=f'{os.path.dirname(__file__)}/login_window.ui') +class LoginWindow(Gtk.Window): + __gtype_name__ = 'LoginWindow' + + # Map GTK Template Child widgets to Python attributes + username_entry = Gtk.Template.Child() + password_entry = Gtk.Template.Child() + login_button = Gtk.Template.Child() + skip_button = Gtk.Template.Child() + settings_button = Gtk.Template.Child() + + # Initialize the LoginWindow instance + def __init__(self, follow_up_action=None, *args, **kwargs): + super().__init__(*args, **kwargs) + self.application = self.get_application() + self.settings_dialog = SettingsDialog(application=self.application) + self._follow_up_action = follow_up_action + + # Set the default values from the Config, ensuring they are not None + if Config.username is not None: + self.username_entry.set_text(Config.username) + if Config.password is not None: # Consider security implications + self.password_entry.set_text(Config.password) + + + + # Handle the 'Login' button click event + @Gtk.Template.Callback() + def on_login_button_clicked(self, widget): + + + + # Fetch entered username and password + username = self.username_entry.get_text() + password = self.password_entry.get_text() + + # Create a GLib Variant tuple for authentication + user_pass_auth_variant = GLib.Variant.new_tuple( + GLib.Variant.new_string(username), + GLib.Variant.new_string(password), + GLib.Variant.new_string(Config.auth_url) + ) + + # Trigger the 'renew-token' action for authentication + self.get_action_group("app").activate_action('renew-token', user_pass_auth_variant) + + if self._follow_up_action is not None: + self._follow_up_action() + + self.close() + + # Handle the 'Skip' button click event + @Gtk.Template.Callback() + def on_skip_button_clicked(self, widget): + # Close the login window + self.close() + + @Gtk.Template.Callback() + def settings_button_clicked_cb(self, widget): + logger.info("Settings button clicked. Opening settings dialog.") + self.settings_dialog.show() + + # Handle the window close event + @Gtk.Template.Callback() + def on_delete(self, widget, event): + # Hide the window instead of deleting it + return self.hide_on_delete() + + diff --git a/dtool_lookup_gui/views/login_window.ui b/dtool_lookup_gui/views/login_window.ui new file mode 100644 index 00000000..1f28ac71 --- /dev/null +++ b/dtool_lookup_gui/views/login_window.ui @@ -0,0 +1,193 @@ + + + + + + diff --git a/dtool_lookup_gui/views/main_window.py b/dtool_lookup_gui/views/main_window.py index 52c2a660..5a7bf09b 100644 --- a/dtool_lookup_gui/views/main_window.py +++ b/dtool_lookup_gui/views/main_window.py @@ -31,7 +31,7 @@ import urllib.parse from functools import reduce -from gi.repository import Gio, GLib, Gtk, GtkSource +from gi.repository import Gio, GLib, Gtk, GtkSource, Gdk import dtoolcore.utils from dtool_info.utils import sizeof_fmt @@ -60,6 +60,8 @@ from .dataset_name_dialog import DatasetNameDialog from .about_dialog import AboutDialog from .settings_dialog import SettingsDialog +from .server_versions_dialog import ServerVersionsDialog +from .login_window import LoginWindow from .log_window import LogWindow _logger = logging.getLogger(__name__) @@ -102,8 +104,6 @@ class MainWindow(Gtk.ApplicationWindow): search_entry = Gtk.Template.Child() - # copy_dataset_spinner = Gtk.Template.Child() - base_uri_list_box = Gtk.Template.Child() dataset_list_box = Gtk.Template.Child() @@ -152,11 +152,11 @@ class MainWindow(Gtk.ApplicationWindow): manifest_view = Gtk.Template.Child() settings_button = Gtk.Template.Child() + version_button = Gtk.Template.Child() error_bar = Gtk.Template.Child() error_label = Gtk.Template.Child() - first_page_button = Gtk.Template.Child() prev_page_button = Gtk.Template.Child() curr_page_button = Gtk.Template.Child() @@ -166,9 +166,9 @@ class MainWindow(Gtk.ApplicationWindow): last_page_button = Gtk.Template.Child() main_statusbar = Gtk.Template.Child() + contents_per_page = Gtk.Template.Child() - - + test_login = Gtk.Template.Child() def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -200,6 +200,7 @@ def __init__(self, *args, **kwargs): self.log_window = LogWindow(application=self.application) self.settings_dialog = SettingsDialog(application=self.application) self.about_dialog = AboutDialog(application=self.application) + self.server_versions_dialog = ServerVersionsDialog(application=self.application) # window-scoped actions @@ -257,8 +258,7 @@ def __init__(self, *args, **kwargs): _logger.debug(f"Constructed main window for app '{self.application.get_application_id()}'") self.pagination = {} - - + self.contents_per_page_value = 10 # utility methods def refresh(self): @@ -316,51 +316,73 @@ def _update_search_summary(self, datasets): def _update_main_statusbar(self): total_number = self.pagination['total'] current_page = self.pagination['page'] - self.main_statusbar.push(0, f"Total Number of Dataset: {total_number}, Current page: {current_page}") + last_page = self.pagination['last_page'] + self.main_statusbar.push(0, + f"Total Number of Datasets: {total_number}, Current Page: {current_page} of {last_page}") - async def _fetch_search_results(self, keyword, on_show=None, page_number=1, page_size=8): + def contents_per_page_changed(self, widget): + self.contents_per_page_value = widget.get_active_text() + self.on_first_page_button_clicked(self.first_page_button) # Directly call the method + + async def _fetch_search_results(self, keyword, on_show=None, page_number=1, page_size=10, widget=None): row = self.base_uri_list_box.search_results_row row.start_spinner() - self.pagination = {} # Add pagination dictionary - - - + self.main_spinner.start() + self.pagination = {} # Add pagination dictionary try: if keyword: if is_valid_query(keyword): _logger.debug("Valid query specified.") - datasets = await DatasetModel.query(keyword, page_number=page_number, page_size=page_size, - pagination=self.pagination) # Pass pagination dictionary and - # page_number, page_size + datasets = await DatasetModel.query( + keyword, + page_number=page_number, + page_size=page_size, + pagination=self.pagination + ) else: _logger.debug("Specified search text is not a valid query, just perform free text search.") - # NOTE: server side allows a dict with the key-value pairs - # "free_text", "creator_usernames", "base_uris", "uuids", "tags", - # via route '/dataset/search', where all except "free_text" - # can be lists and are translated to logical "and" or "or" - # constructs on the server side. With the special treatment - # of the 'uuid' keyword above, should we introduce similar - # options for the other available keywords? - datasets = await DatasetModel.search(keyword, page_number=page_number, page_size=page_size, - pagination=self.pagination) # Pass pagination dictionary and - # page_number, page_size + datasets = await DatasetModel.search( + keyword, + page_number=page_number, + page_size=page_size, + pagination=self.pagination + ) else: _logger.debug("No keyword specified, list all datasets.") - datasets = await DatasetModel.query_all(page_number=page_number, page_size=page_size,pagination=self.pagination) # Pass pagination dictionary and - + datasets = await DatasetModel.query_all( + page_number=page_number, + page_size=page_size, + pagination=self.pagination + ) if len(datasets) > self._max_nb_datasets: _logger.warning( f"{len(datasets)} search results exceed allowed displayed maximum of {self._max_nb_datasets}. " - f"Only the first {self._max_nb_datasets} results are shown. Narrow down your search.") - datasets = datasets[:self._max_nb_datasets] # Limit number of datasets that are shown + f"Only the first {self._max_nb_datasets} results are shown. Narrow down your search." + ) + datasets = datasets[:self._max_nb_datasets] # Limit number of datasets that are shown + row.search_results = datasets # Cache datasets self._update_search_summary(datasets) self._update_main_statusbar() + self.contents_per_page.connect("changed", self.contents_per_page_changed) if self.base_uri_list_box.get_selected_row() == row: # Only update if the row is still selected self.dataset_list_box.fill(datasets, on_show=on_show) + except RuntimeError as e: + # TODO: There should probably be a more explicit test on authentication failure. + self.show_error(e) + + async def retry(): + await asyncio.sleep(1) # TODO: This is a dirty workaround for not having the login window pop up twice + await self._fetch_search_results(keyword, on_show, page_number, page_size, widget) + + # What happens is that the LoginWindow evokes the renew-token action via Gtk framework. + # This happens asynchronously as well. This means _fetch_search_results called again + # within the retry() function would open another LoginWindow here as the token renewal does + # not happen "quick" enough. Hence there is the asybcio.sleep(1). + LoginWindow(application=self.application, follow_up_action=lambda: asyncio.create_task(retry())).show() except Exception as e: self.show_error(e) @@ -368,7 +390,9 @@ async def _fetch_search_results(self, keyword, on_show=None, page_number=1, page self.base_uri_list_box.select_search_results_row() self.main_stack.set_visible_child(self.main_paned) row.stop_spinner() - + self.main_spinner.stop() + # If a widget was passed in, re-enable it + self.enable_pagination_buttons() def _search_by_uuid(self, uuid): @@ -458,10 +482,6 @@ def _select_base_uri_row_by_uri(self, uri): index = self.base_uri_list_box.get_row_index_from_uri(uri) self._select_base_uri_row_by_row_index(index) - # def _show_base_uri(self, dataset): - # asyncio.create_task(self._update_dataset_view(dataset)) - # self.dataset_stack.set_visible_child(self.dataset_box) - # actions # dataset selection actions @@ -540,8 +560,6 @@ async def _get_item(dataset, item_uuid): asyncio.create_task(_get_item(dataset, item_uuid)) - - def do_refresh_view(self, action, value): """Refresh view by reloading base uri list, """ self.refresh() @@ -551,11 +569,18 @@ def do_refresh_view(self, action, value): def on_settings_clicked(self, widget): self.settings_dialog.show() + @Gtk.Template.Callback() + def version_button_clicked(self, widget): + self.server_versions_dialog.show() + + @Gtk.Template.Callback() + def on_test_login_clicked(self, widget): + self.login_window.show() + @Gtk.Template.Callback() def on_logging_clicked(self, widget): self.log_window.show() - @Gtk.Template.Callback() def on_about_clicked(self, widget): self.about_dialog.show() @@ -726,7 +751,6 @@ def on_add_items_clicked(self, widget): pass dialog.destroy() - @Gtk.Template.Callback() def on_manifest_row_activated(self, tree_view, path, column): """Handler for "row-activated" signal. @@ -792,74 +816,103 @@ def on_error_bar_response(self, widget, response_id): @Gtk.Template.Callback() def on_first_page_button_clicked(self, widget): - page_number = 1 - self.pagination['page'] = page_number - asyncio.create_task( - self._fetch_search_results(keyword=None, on_show=None, page_number=page_number)) - self.curr_page_button.set_label(str(page_number)) - self.page_advancer_button.set_label(str(page_number - 1)) - self.next_page_advancer_button.set_label(str(page_number + 1)) - + if not self.fetching_results: + page_number = 1 + self.pagination['page'] = page_number + self.disable_pagination_buttons() + asyncio.create_task( + self._fetch_search_results(keyword=None, on_show=None, page_number=page_number, + page_size=int(self.contents_per_page_value), widget=widget)) + self.curr_page_button.set_label(str(page_number)) + self.page_advancer_button.set_label(str(page_number - 1)) + self.next_page_advancer_button.set_label(str(page_number + 1)) @Gtk.Template.Callback() def on_nextoption_page_button_clicked(self, widget): - if self.pagination['page'] < self.pagination['last_page']: + if not self.fetching_results and self.pagination['page'] < self.pagination['last_page']: page_number = self.pagination['page'] + 1 - self.curr_page_button.set_label(str(page_number)) - self.page_advancer_button.set_label(str(page_number - 1)) - self.next_page_advancer_button.set_label(str(page_number + 1)) + self.update_pagination_buttons(page_number, widget) + self.disable_pagination_buttons() asyncio.create_task( - self._fetch_search_results(keyword=None, on_show=None, page_number=page_number)) + self._fetch_search_results(keyword=None, on_show=None, page_number=page_number, + page_size=int(self.contents_per_page_value), widget=widget)) @Gtk.Template.Callback() def on_last_page_button_clicked(self, widget): page_number = self.pagination['last_page'] - self.curr_page_button.set_label(str(page_number)) - self.page_advancer_button.set_label(str(page_number - 1)) - self.next_page_advancer_button.set_label(str(page_number + 1)) + self.update_pagination_buttons(page_number, widget) + self.disable_pagination_buttons() asyncio.create_task( - self._fetch_search_results(keyword=None, on_show=None, page_number=page_number)) + self._fetch_search_results(keyword=None, on_show=None, page_number=page_number, + page_size=int(self.contents_per_page_value), widget=widget)) @Gtk.Template.Callback() def on_prev_page_button_clicked(self, widget): - if self.pagination['page'] > 1: + if not self.fetching_results and self.pagination['page'] > 1: self.pagination['page'] -= 1 page_number = self.pagination['page'] - self.curr_page_button.set_label(str(page_number)) # Update curr_page_button label - self.page_advancer_button.set_label(str(page_number - 1)) - self.next_page_advancer_button.set_label(str(page_number + 1)) + self.update_pagination_buttons(page_number, widget) + self.disable_pagination_buttons() asyncio.create_task( - self._fetch_search_results(keyword=None, on_show=None, page_number=page_number)) + self._fetch_search_results(keyword=None, on_show=None, page_number=page_number, + page_size=int(self.contents_per_page_value), widget=widget)) @Gtk.Template.Callback() def curr_page_button_clicked(self, widget): page_number = self.pagination['page'] + self.update_pagination_buttons(page_number, widget) + self.disable_pagination_buttons() asyncio.create_task( - self._fetch_search_results(keyword=None, on_show=None, page_number=page_number)) - self.curr_page_button.set_label(str(page_number)) - self.page_advancer_button.set_label(str(page_number - 1)) - self.next_page_advancer_button.set_label(str(page_number + 1)) + self._fetch_search_results(keyword=None, on_show=None, page_number=page_number, + page_size=int(self.contents_per_page_value), widget=widget)) @Gtk.Template.Callback() def page_advancer_button_clicked(self, widget): page_number = self.pagination['page'] - if page_number > 1: + if not self.fetching_results and page_number > 1: page_number -= 1 + self.update_pagination_buttons(page_number, widget) + self.disable_pagination_buttons() asyncio.create_task( - self._fetch_search_results(keyword=None, on_show=None, page_number=page_number)) - self.curr_page_button.set_label(str(page_number)) - self.page_advancer_button.set_label(str(page_number - 1)) - self.next_page_advancer_button.set_label(str(page_number + 1)) + self._fetch_search_results(keyword=None, on_show=None, page_number=page_number, + page_size=int(self.contents_per_page_value), widget=widget)) @Gtk.Template.Callback() def next_page_advancer_button_clicked(self, widget): - if self.pagination['page'] < self.pagination['last_page']: - page_number = self.pagination['page'] + 1 + page_number = self.pagination['page'] + if not self.fetching_results and page_number < self.pagination['last_page']: + page_number += 1 + self.update_pagination_buttons(page_number, widget) + self.disable_pagination_buttons() asyncio.create_task( - self._fetch_search_results(keyword=None, on_show=None, page_number=page_number)) - self.curr_page_button.set_label(str(page_number)) - self.page_advancer_button.set_label(str(page_number - 1)) - self.next_page_advancer_button.set_label(str(page_number + 1)) + self._fetch_search_results(keyword=None, on_show=None, page_number=page_number, + page_size=int(self.contents_per_page_value), widget=widget)) + + def update_pagination_buttons(self, page_number, widget): + self.pagination['page'] = page_number + self.curr_page_button.set_label(str(page_number)) + self.page_advancer_button.set_label(str(page_number - 1)) + self.next_page_advancer_button.set_label(str(page_number + 1)) + + def disable_pagination_buttons(self): + self.fetching_results = True + self.first_page_button.set_sensitive(False) + self.nextoption_page_button.set_sensitive(False) + self.last_page_button.set_sensitive(False) + self.prev_page_button.set_sensitive(False) + self.curr_page_button.set_sensitive(False) + self.page_advancer_button.set_sensitive(False) + self.next_page_advancer_button.set_sensitive(False) + + def enable_pagination_buttons(self): + self.fetching_results = False + self.first_page_button.set_sensitive(True) + self.nextoption_page_button.set_sensitive(True) + self.last_page_button.set_sensitive(True) + self.prev_page_button.set_sensitive(True) + self.curr_page_button.set_sensitive(True) + self.page_advancer_button.set_sensitive(True) + self.next_page_advancer_button.set_sensitive(True) # @Gtk.Template.Callback(), not in .ui def on_copy_clicked(self, widget): @@ -1005,4 +1058,4 @@ async def _compute_dependencies(self, dataset): self.dependency_stack.set_visible_child(self.dependency_view) def show_error(self, exception): - _logger.error(traceback.format_exc()) + _logger.error(traceback.format_exc()) \ No newline at end of file diff --git a/dtool_lookup_gui/views/main_window.ui b/dtool_lookup_gui/views/main_window.ui index 47aa6c07..8f7f232e 100644 --- a/dtool_lookup_gui/views/main_window.ui +++ b/dtool_lookup_gui/views/main_window.ui @@ -59,6 +59,34 @@ 2 + + + True + True + True + Version + + + + False + True + 3 + + + + + True + True + True + Test Login + + + + False + True + 4 + + submenu @@ -183,10 +211,71 @@ True False - + True False - + vertical + + + True + False + + + + True + True + 0 + + + + + True + False + 10 + 10 + + + True + False + start + False + Contents per Page: + 0 + + + False + True + 0 + + + + + True + True + start + False + 0 + + 10 + 25 + 50 + 75 + + + + + False + True + 1 + + + + + False + True + 1 + + @@ -259,10 +348,14 @@ First + 30 True True True + 0 @@ -272,10 +365,15 @@ Prev + 100 + 30 True True True + 1 @@ -285,10 +383,15 @@ Last + 100 + 30 True True True + 6 @@ -298,10 +401,15 @@ Next + 100 + 30 True True True + 5 @@ -311,10 +419,15 @@ 2 + 100 + 30 True True True + 4 @@ -324,10 +437,15 @@ 1 + 100 + 30 True True True + 3 @@ -337,10 +455,15 @@ 0 + 100 + 30 True True True + 2 diff --git a/dtool_lookup_gui/views/server_versions_dialog.py b/dtool_lookup_gui/views/server_versions_dialog.py new file mode 100644 index 00000000..835625a8 --- /dev/null +++ b/dtool_lookup_gui/views/server_versions_dialog.py @@ -0,0 +1,52 @@ +import asyncio +import logging +import os +import gi + +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk +from dtool_lookup_api.asynchronous import versions +from ..utils.logging import _log_nested + +# Set up logger for this module +logger = logging.getLogger(__name__) + + +@Gtk.Template(filename=f'{os.path.dirname(__file__)}/server_versions_dialog.ui') +class ServerVersionsDialog(Gtk.Window): + __gtype_name__ = 'ServerVersionsDialog' + server_versions_label = Gtk.Template.Child() + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + # Apply margins to the server versions label for better aesthetics + self.server_versions_label.set_margin_start(10) + self.server_versions_label.set_margin_end(10) + self.server_versions_label.set_margin_top(10) + self.server_versions_label.set_margin_bottom(10) + + async def _retrieve_versions(self): + """Asynchronously fetch server versions and update the label with formatted information.""" + server_versions = await versions() + version_info = self._format_server_versions(server_versions) + _log_nested(logger.info, version_info) + self.server_versions_label.set_markup(version_info) + + def _format_server_versions(self, server_versions): + """Format server versions, sorting components by name length.""" + sorted_components = sorted(server_versions.keys(), key=len) + + # Use list comprehension for concise formatting of server versions + formatted_versions = [f"{component}: {server_versions[component]}" for component in sorted_components] + return "\n".join(formatted_versions) + + @Gtk.Template.Callback() + def on_show(self, widget): + """Callback executed when the window is shown; fetches server versions.""" + asyncio.create_task(self._retrieve_versions()) + + @Gtk.Template.Callback() + def on_delete(self, widget, event): + """Callback executed when window close event is triggered; hides the window instead of deleting.""" + return self.hide_on_delete() diff --git a/dtool_lookup_gui/views/server_versions_dialog.ui b/dtool_lookup_gui/views/server_versions_dialog.ui new file mode 100644 index 00000000..18b93219 --- /dev/null +++ b/dtool_lookup_gui/views/server_versions_dialog.ui @@ -0,0 +1,82 @@ + + + + + + diff --git a/dtool_lookup_gui/views/settings_dialog.py b/dtool_lookup_gui/views/settings_dialog.py index fd945e80..6c54a38c 100644 --- a/dtool_lookup_gui/views/settings_dialog.py +++ b/dtool_lookup_gui/views/settings_dialog.py @@ -172,7 +172,7 @@ async def _refresh_list_of_endpoints(self): logger.debug("Done refreshing settings dialog.") def on_dtool_config_changed(self, widget): - """Signal handler for dtool-method-changed.""" + """Signal handler for dtool-config-changed.""" self._refresh_settings_dialog() # signal handlers @@ -205,14 +205,13 @@ def on_delete(self, widget, event): @Gtk.Template.Callback() def on_renew_token_clicked(self, widget): + # show authentication dialogue and get username and password def authenticate(username, password): - asyncio.create_task(self.retrieve_token( - self.authenticator_url_entry.get_text(), - username, - password)) - - # Reconnect since settings may have been changed - #asyncio.create_task(self.main_application.lookup_tab.connect()) + auth_url = self.authenticator_url_entry.get_text() + user_pass_auth_variant = GLib.Variant.new_tuple(GLib.Variant.new_string(username), + GLib.Variant.new_string(password), + GLib.Variant.new_string(auth_url)) + self.get_action_group("app").activate_action('renew-token', user_pass_auth_variant) AuthenticationDialog(authenticate, Config.username, Config.password).show() @@ -275,17 +274,6 @@ def on_item_download_directory_file_chooser_button_file_set(self, widget): logger.debug("Selected default item download directory '%s'.", item_download_directory) settings.item_download_directory = item_download_directory - async def retrieve_token(self, auth_url, username, password): - #self.main_application.error_bar.hide() - try: - token = await authenticate(auth_url, username, password) - except Exception as e: - logger.error(str(e)) - return - #self.builder.get_object('token-entry').set_text(token) - self.token_entry.set_text(token) - await self._refresh_list_of_endpoints() - _configuration_dialogs = { 's3': S3ConfigurationDialog, 'smb': SMBConfigurationDialog, diff --git a/requirements.txt b/requirements.txt index 99cbd7a2..ea5a9b1b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,9 +2,9 @@ # This file is autogenerated by pip-compile with Python 3.8 # by the following command: # -# pip-compile requirements.in +# pip-compile --resolver=backtracking requirements.in # -aiohttp==3.8.4 +aiohttp==3.8.5 # via # -r requirements.in # dtool-lookup-api @@ -12,17 +12,17 @@ aiosignal==1.3.1 # via aiohttp asgiref==3.7.2 # via dtool-lookup-api -async-timeout==4.0.2 +async-timeout==4.0.3 # via aiohttp attrs==23.1.0 # via aiohttp -boto3==1.28.2 +boto3==1.28.25 # via dtool-s3 -botocore==1.31.2 +botocore==1.31.25 # via # boto3 # s3transfer -certifi==2023.5.7 +certifi==2023.7.22 # via requests cffi==1.15.1 # via cryptography @@ -30,7 +30,7 @@ charset-normalizer==3.2.0 # via # aiohttp # requests -click==8.1.4 +click==8.1.6 # via # click-plugins # dtool-cli @@ -39,7 +39,7 @@ click==8.1.4 # dtool-s3 click-plugins==1.1.1 # via dtool-cli -cryptography==41.0.2 +cryptography==41.0.3 # via jwt dtool-cli==0.7.1 # via @@ -52,7 +52,7 @@ dtool-http==0.5.1 # via dtool-create dtool-info==0.16.2 # via -r requirements.in -dtool-lookup-api==0.6.1 +dtool-lookup-api==0.7.0 # via -r requirements.in dtool-s3==0.14.1 # via -r requirements.in @@ -71,7 +71,7 @@ dtoolcore==3.18.2 # dtool-s3 # dtool-smb # dtool-symlink -frozenlist==1.3.3 +frozenlist==1.4.0 # via # aiohttp # aiosignal @@ -107,7 +107,7 @@ pycairo==1.24.0 # via pygobject pycparser==2.21 # via cffi -pygments==2.15.1 +pygments==2.16.1 # via dtool-info pygobject==3.44.1 # via @@ -117,7 +117,7 @@ pysmb==1.2.9.1 # via dtool-smb python-dateutil==2.8.2 # via botocore -pyyaml==6.0 +pyyaml==6.0.1 # via # -r requirements.in # dtool-lookup-api @@ -135,7 +135,7 @@ scipy==1.10.1 # via -r requirements.in six==1.16.0 # via python-dateutil -tqdm==4.65.0 +tqdm==4.66.1 # via pysmb typing-extensions==4.7.1 # via asgiref diff --git a/setup.py b/setup.py index 66107dd3..c01b8a9c 100644 --- a/setup.py +++ b/setup.py @@ -70,7 +70,7 @@ def local_scheme(version): 'dtoolcore>=3.17', 'dtool-create>=0.23.4', 'dtool-info>=0.16.2', - 'dtool-lookup-api>=0.5', + 'dtool-lookup-api>=0.7.0', 'aiohttp>=3.6', 'gbulb>=0.6', 'pyyaml>=5.3', diff --git a/test/test_app.py b/test/test_app.py index 7c3e4a64..9b7c74eb 100644 --- a/test/test_app.py +++ b/test/test_app.py @@ -5,6 +5,7 @@ from dtool_lookup_gui.views.about_dialog import AboutDialog from dtool_lookup_gui.views.settings_dialog import SettingsDialog from dtool_lookup_gui.views.log_window import LogWindow +from dtool_lookup_gui.views.login_window import LoginWindow from dtool_lookup_gui.views.main_window import MainWindow logger = logging.getLogger(__name__) @@ -34,7 +35,7 @@ async def test_app_id(app): @pytest.mark.asyncio async def test_app_window_types(app): window_types = [type(win) for win in app.get_windows()] - assert set(window_types) == set([AboutDialog, SettingsDialog, LogWindow, MainWindow]) + assert set(window_types) == set([AboutDialog, SettingsDialog, LogWindow, LoginWindow, MainWindow]) @pytest.mark.asyncio