diff --git a/dtool_lookup_gui/models/datasets.py b/dtool_lookup_gui/models/datasets.py
index 27469b4f..80e2cf5e 100644
--- a/dtool_lookup_gui/models/datasets.py
+++ b/dtool_lookup_gui/models/datasets.py
@@ -268,25 +268,22 @@ def __init__(self, uri=None, dataset_info=None):
raise ValueError('Please provide either `uri` or `dateset_info`.')
@classmethod
- async def search(cls, keyword):
+ async def search(cls, keyword, page_number, page_size, pagination={}):
async with ConfigurationBasedLookupClient() as lookup:
- datasets = await lookup.search(keyword)
-
+ datasets = await lookup.search(keyword, 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 query(cls, query_text):
+ async def query(cls, query_text, *args, **kwargs):
async with ConfigurationBasedLookupClient() as lookup:
- datasets = await lookup.by_query(query_text)
-
+ datasets = await lookup.by_query(query_text, *args, **kwargs)
return [await cls.from_lookup(lookup_dict) for lookup_dict in datasets]
@classmethod
- async def query_all(cls):
- """Query all datasets from lookup server."""
+ async def query_all(cls, page_number=1, page_size=8, pagination={}):
+ """Query all datasets from the lookup server."""
async with ConfigurationBasedLookupClient() as lookup:
- datasets = await lookup.all()
-
+ 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]
def __str__(self):
diff --git a/dtool_lookup_gui/views/main_window.py b/dtool_lookup_gui/views/main_window.py
index 5b27c8d7..52c2a660 100644
--- a/dtool_lookup_gui/views/main_window.py
+++ b/dtool_lookup_gui/views/main_window.py
@@ -38,6 +38,7 @@
import dtool_lookup_api.core.config
from dtool_lookup_api.core.LookupClient import ConfigurationBasedLookupClient
+
# As of dtool-lookup-api 0.5.0, the following line still is a necessity to
# disable prompting for credentials on the command line. This behavior
# will change in future versions.
@@ -101,7 +102,7 @@ class MainWindow(Gtk.ApplicationWindow):
search_entry = Gtk.Template.Child()
- #copy_dataset_spinner = Gtk.Template.Child()
+ # copy_dataset_spinner = Gtk.Template.Child()
base_uri_list_box = Gtk.Template.Child()
dataset_list_box = Gtk.Template.Child()
@@ -155,6 +156,20 @@ class MainWindow(Gtk.ApplicationWindow):
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()
+ next_page_advancer_button = Gtk.Template.Child()
+ page_advancer_button = Gtk.Template.Child()
+ nextoption_page_button = Gtk.Template.Child()
+ last_page_button = Gtk.Template.Child()
+
+ main_statusbar = Gtk.Template.Child()
+
+
+
+
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -241,6 +256,10 @@ def __init__(self, *args, **kwargs):
_logger.debug(f"Constructed main window for app '{self.application.get_application_id()}'")
+ self.pagination = {}
+
+
+
# utility methods
def refresh(self):
"""Refresh view."""
@@ -291,18 +310,29 @@ async def _refresh_base_uri_list_box(self):
def _update_search_summary(self, datasets):
row = self.base_uri_list_box.search_results_row
total_size = sum([0 if dataset.size_int is None else dataset.size_int for dataset in datasets])
- row.info_label.set_text(f'{len(datasets)} datasets, {sizeof_fmt(total_size).strip()}')
+ total_value = self.pagination['total']
+ row.info_label.set_text(f'{total_value} datasets, {sizeof_fmt(total_size).strip()}')
- async def _fetch_search_results(self, keyword, on_show=None):
+ 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}")
+
+ async def _fetch_search_results(self, keyword, on_show=None, page_number=1, page_size=8):
row = self.base_uri_list_box.search_results_row
row.start_spinner()
+ self.pagination = {} # Add pagination dictionary
+
+
+
try:
- # datasets = await DatasetModel.search(keyword)
if keyword:
if is_valid_query(keyword):
_logger.debug("Valid query specified.")
- datasets = await DatasetModel.query(keyword)
+ datasets = await DatasetModel.query(keyword, page_number=page_number, page_size=page_size,
+ pagination=self.pagination) # Pass pagination dictionary and
+ # page_number, page_size
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
@@ -312,10 +342,13 @@ async def _fetch_search_results(self, keyword, on_show=None):
# 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)
+ datasets = await DatasetModel.search(keyword, page_number=page_number, page_size=page_size,
+ pagination=self.pagination) # Pass pagination dictionary and
+ # page_number, page_size
else:
_logger.debug("No keyword specified, list all datasets.")
- datasets = await DatasetModel.query_all()
+ datasets = await DatasetModel.query_all(page_number=page_number, page_size=page_size,pagination=self.pagination) # Pass pagination dictionary and
+
if len(datasets) > self._max_nb_datasets:
_logger.warning(
@@ -324,6 +357,7 @@ async def _fetch_search_results(self, keyword, on_show=None):
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()
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)
@@ -335,6 +369,8 @@ async def _fetch_search_results(self, keyword, on_show=None):
self.main_stack.set_visible_child(self.main_paned)
row.stop_spinner()
+
+
def _search_by_uuid(self, uuid):
search_text = dump_single_line_query_text({"uuid": uuid})
self._search_by_search_text(search_text)
@@ -489,7 +525,7 @@ def do_get_item(self, action, value):
dataset = self.dataset_list_box.get_selected_row().dataset
items = self._get_selected_items()
- if len(items) !=1:
+ if len(items) != 1:
raise ValueError("Can only get one item at a time.")
item_name, item_uuid = items[0]
@@ -504,6 +540,8 @@ 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()
@@ -517,6 +555,7 @@ def on_settings_clicked(self, widget):
def on_logging_clicked(self, widget):
self.log_window.show()
+
@Gtk.Template.Callback()
def on_about_clicked(self, widget):
self.about_dialog.show()
@@ -687,6 +726,7 @@ 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.
@@ -696,7 +736,7 @@ def on_manifest_row_activated(self, tree_view, path, column):
is pressed. (https://www.gnu.org/software/guile-gnome/docs/gtk/html/GtkTreeView.html)"""
items = self._get_selected_items()
- if len(items) !=1:
+ if len(items) != 1:
raise ValueError("Can only get one item at a time.")
item_name, item_uuid = items[0]
self._show_get_item_dialog(item_name, item_uuid)
@@ -750,6 +790,77 @@ def on_error_bar_response(self, widget, response_id):
if response_id == Gtk.ResponseType.CLOSE:
self.error_bar.set_revealed(False)
+ @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))
+
+
+ @Gtk.Template.Callback()
+ def on_nextoption_page_button_clicked(self, widget):
+ if 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))
+ asyncio.create_task(
+ self._fetch_search_results(keyword=None, on_show=None, page_number=page_number))
+
+ @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))
+ asyncio.create_task(
+ self._fetch_search_results(keyword=None, on_show=None, page_number=page_number))
+
+ @Gtk.Template.Callback()
+ def on_prev_page_button_clicked(self, widget):
+ if 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))
+ asyncio.create_task(
+ self._fetch_search_results(keyword=None, on_show=None, page_number=page_number))
+
+ @Gtk.Template.Callback()
+ def curr_page_button_clicked(self, widget):
+ page_number = self.pagination['page']
+ 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))
+
+ @Gtk.Template.Callback()
+ def page_advancer_button_clicked(self, widget):
+ page_number = self.pagination['page']
+ if page_number > 1:
+ page_number -= 1
+ 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))
+
+ @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
+ 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))
+
# @Gtk.Template.Callback(), not in .ui
def on_copy_clicked(self, widget):
async def _copy():
@@ -802,7 +913,6 @@ def _show_get_item_dialog(self, item_name, item_uuid):
dest_file = os.path.join(default_dir, item_name)
self.activate_action('get-item', GLib.Variant.new_string(dest_file))
-
# TODO: move to the model
def _add_item(self, fpath):
handle = os.path.basename(fpath)
@@ -895,4 +1005,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())
\ No newline at end of file
+ _logger.error(traceback.format_exc())
diff --git a/dtool_lookup_gui/views/main_window.ui b/dtool_lookup_gui/views/main_window.ui
index 8452b661..47aa6c07 100644
--- a/dtool_lookup_gui/views/main_window.ui
+++ b/dtool_lookup_gui/views/main_window.ui
@@ -233,12 +233,127 @@
@@ -980,16 +1095,17 @@ or search for a dataset
True
False
-
+
True
False
- 10
- 10
10
10
6
6
2
+
page0
diff --git a/requirements.txt b/requirements.txt
index 2877a3c8..cb1e84bc 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,36 +1,36 @@
#
-# This file is autogenerated by pip-compile with python 3.8
-# To update, run:
+# This file is autogenerated by pip-compile with Python 3.8
+# by the following command:
#
# pip-compile requirements.in
#
-aiohttp==3.8.1
+aiohttp==3.8.4
# via
# -r requirements.in
# dtool-lookup-api
-aiosignal==1.2.0
+aiosignal==1.3.1
# via aiohttp
-asgiref==3.5.0
+asgiref==3.7.2
# via dtool-lookup-api
async-timeout==4.0.2
# via aiohttp
-attrs==21.4.0
+attrs==23.1.0
# via aiohttp
-boto3==1.21.22
+boto3==1.28.2
# via dtool-s3
-botocore==1.24.22
+botocore==1.31.2
# via
# boto3
# s3transfer
-certifi==2021.10.8
+certifi==2023.5.7
# via requests
-cffi==1.15.0
+cffi==1.15.1
# via cryptography
-charset-normalizer==2.0.12
+charset-normalizer==3.2.0
# via
# aiohttp
# requests
-click==8.0.4
+click==8.1.4
# via
# click-plugins
# dtool-cli
@@ -39,7 +39,7 @@ click==8.0.4
# dtool-s3
click-plugins==1.1.1
# via dtool-cli
-cryptography==36.0.2
+cryptography==41.0.2
# 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.5.0
+dtool-lookup-api==0.6.0
# via -r requirements.in
dtool-s3==0.14.1
# via -r requirements.in
@@ -71,19 +71,19 @@ dtoolcore==3.18.2
# dtool-s3
# dtool-smb
# dtool-symlink
-frozenlist==1.3.0
+frozenlist==1.3.3
# via
# aiohttp
# aiosignal
-gbulb==0.6.3
+gbulb==0.6.4
# via -r requirements.in
-idna==3.3
+idna==3.4
# via
# requests
# yarl
-jinja2==3.0.3
+jinja2==3.1.2
# via dtool-info
-jmespath==1.0.0
+jmespath==1.0.1
# via
# boto3
# botocore
@@ -93,29 +93,27 @@ markupsafe==2.0.1
# via
# -r requirements.in
# jinja2
-multidict==6.0.2
+multidict==6.0.4
# via
# aiohttp
# yarl
-numpy==1.22.3
+numpy==1.24.4
# via scipy
-packaging==21.3
+packaging==23.1
# via dtool-s3
-pyasn1==0.4.8
+pyasn1==0.5.0
# via pysmb
-pycairo==1.21.0
+pycairo==1.24.0
# via pygobject
pycparser==2.21
# via cffi
-pygments==2.11.2
+pygments==2.15.1
# via dtool-info
-pygobject==3.42.0
+pygobject==3.44.1
# via
# -r requirements.in
# gbulb
-pyparsing==3.0.7
- # via packaging
-pysmb==1.2.7
+pysmb==1.2.9.1
# via dtool-smb
python-dateutil==2.8.2
# via botocore
@@ -123,23 +121,27 @@ pyyaml==6.0
# via
# -r requirements.in
# dtool-lookup-api
-requests==2.27.1
+requests==2.31.0
# via dtool-http
-ruamel-yaml==0.17.21
+ruamel-yaml==0.17.32
# via
# -r requirements.in
# dtool-create
-ruamel-yaml-clib==0.2.6
+ruamel-yaml-clib==0.2.7
# via ruamel-yaml
-s3transfer==0.5.2
+s3transfer==0.6.1
# via boto3
-scipy==1.8.0
+scipy==1.10.1
# via -r requirements.in
six==1.16.0
# via python-dateutil
-urllib3==1.26.9
+tqdm==4.65.0
+ # via pysmb
+typing-extensions==4.7.1
+ # via asgiref
+urllib3==1.26.16
# via
# botocore
# requests
-yarl==1.7.2
+yarl==1.9.2
# via aiohttp