diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml index afb9a915..e89c02be 100644 --- a/.github/workflows/build-and-publish.yml +++ b/.github/workflows/build-and-publish.yml @@ -3,6 +3,7 @@ on: push: branches: - master + - main tags: - '*' pull_request: @@ -20,17 +21,14 @@ jobs: build-on-macos: uses: livMatS/dtool-lookup-gui/.github/workflows/build-on-macos.yml@master - publish-on-pypi: - runs-on: ubuntu-22.04 + build-python-package: + name: Build + runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Get history and tags for SCM versioning to work - run: | - git fetch --prune --unshallow - git fetch --depth=1 origin +refs/tags/*:refs/tags/* + - uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Set up Python 3.12 uses: actions/setup-python@v5 @@ -48,44 +46,89 @@ jobs: gir1.2-gtk-3.0 \ libgtksourceview-4-0 - - name: Install pythonic dependencies + - name: Install requirements run: | - pip install --upgrade pip - pip install wheel build - pip install -r requirements.txt + pip install --upgrade build + pip install --upgrade setuptools wheel setuptools-scm[toml] + pip list - name: Package distribution run: | - python -m build --sdist --wheel - ls -1 dist/ + python -m build - - name: Get master HEAD SHA - id: get_master_sha - run: | - git fetch --depth 1 origin master - echo "Commit that triggered this workflow: ${{ github.sha }}" - echo "HEAD at master: $(git rev-parse origin/master)" - echo "sha=$(git rev-parse origin/master)" >> $GITHUB_OUTPUT + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: python-package-distributions + path: dist/ + + + publish-to-testpypi: + name: Publish to TestPyPI + needs: + - build-python-package + runs-on: ubuntu-latest + + environment: + name: testpypi + url: https://test.pypi.org/p/dtool-lookup-gui + + permissions: + id-token: write # IMPORTANT: mandatory for trusted publishing - - name: Publish package - if: >- - github.event_name == 'push' && - startsWith(github.ref, 'refs/tags') && - steps.get_master_sha.outputs.sha == github.sha - uses: pypa/gh-action-pypi-publish@v1.8.14 + steps: + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + + - name: Publish distribution 📦 to TestPyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ + verbose: true + skip-existing: true + + publish-to-pypi: + name: Publish to PyPI + if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes + needs: + - build-python-package + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/dtool-lookup-gui # Replace with your PyPI project name + permissions: + id-token: write # IMPORTANT: mandatory for trusted publishing + + steps: + - name: Download artifact + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + + - name: Publish distribution to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 with: - user: __token__ - password: ${{ secrets.pypi_password }} verbose: true publish-on-github: - runs-on: ubuntu-20.04 + name: >- + Make github release + runs-on: ubuntu-latest + + permissions: + contents: write # IMPORTANT: mandatory for making GitHub Releases + id-token: write # IMPORTANT: mandatory for sigstore needs: - build-on-ubuntu - build-on-macos - build-on-windows - build-installer-on-windows + - publish-to-pypi steps: - name: Checkout @@ -136,6 +179,19 @@ jobs: run: | ls -lhv . + - name: Download artifact + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + + - name: Sign with Sigstore + uses: sigstore/gh-action-sigstore-python@v3.0.0 + with: + inputs: >- + ./dist/*.tar.gz + ./dist/*.whl + - name: Get master HEAD SHA id: get_master_sha run: | @@ -144,16 +200,27 @@ jobs: echo "HEAD at master: $(git rev-parse master)" echo "sha=$(git rev-parse origin/master)" >> $GITHUB_OUTPUT - - name: Make release - if: >- - github.event_name == 'push' && - startsWith(github.ref, 'refs/tags') && - steps.get_master_sha.outputs.sha == github.sha - uses: softprops/action-gh-release@v2 - with: - files: | - dtool-lookup-gui-${{ steps.fix_version.outputs.version }}-linux.tar.gz - dtool-lookup-gui-${{ steps.fix_version.outputs.version }}-macos.dmg - dtool-lookup-gui-${{ steps.fix_version.outputs.version }}-windows.zip - dtool-lookup-gui-${{ steps.fix_version.outputs.version }}-windows-installer.exe - draft: true + - name: Create GitHub Release + env: + GITHUB_TOKEN: ${{ github.token }} + run: >- + gh release create + '${{ github.ref_name }}' + --repo '${{ github.repository }}' + --notes "" + + - name: Upload artifact signatures to GitHub Release + env: + GITHUB_TOKEN: ${{ github.token }} + # Upload to GitHub Release using the `gh` CLI. + # `dist/` contains the built packages, and the + # sigstore-produced signatures and certificates. + run: >- + gh release upload + '${{ github.ref_name }}' \ + --repo '${{ github.repository }}' \ + dist/** \ + dtool-lookup-gui-${{ steps.fix_version.outputs.version }}-linux.tar.gz \ + dtool-lookup-gui-${{ steps.fix_version.outputs.version }}-macos.dmg \ + dtool-lookup-gui-${{ steps.fix_version.outputs.version }}-windows.zip \ + dtool-lookup-gui-${{ steps.fix_version.outputs.version }}-windows-installer.exe \ No newline at end of file diff --git a/.github/workflows/build-installer-on-windows.yml b/.github/workflows/build-installer-on-windows.yml index e67ce957..0d3ee65b 100644 --- a/.github/workflows/build-installer-on-windows.yml +++ b/.github/workflows/build-installer-on-windows.yml @@ -24,8 +24,10 @@ jobs: curl git zip + mingw-w64-x86_64-cmake mingw-w64-x86_64-cython mingw-w64-x86_64-gcc + mingw-w64-x86_64-gobject-introspection mingw-w64-x86_64-gtk3 mingw-w64-x86_64-gtksourceview4 mingw-w64-x86_64-openssl @@ -82,7 +84,7 @@ jobs: echo "### pacman -Q ###" pacman -Q | tee pacman_Q.txt echo "### pacman -Q diff to reference configuration ###" - (git diff --ignore-space-change --ignore-cr-at-eol --no-index pyinstaller/mingw64_pacman_Q.txt pacman_Q.txt || true) | tee diff_pacman_q.txt + (git diff --ignore-space-change --ignore-cr-at-eol --no-index pyinstaller/win/mingw64_pacman_Q.txt pacman_Q.txt || true) | tee diff_pacman_q.txt # 2022/01/23, although msys2/setup-msys2@v2 setup has been kept as close as possible to # a local reference build system, former diverged from latter's pyinstaller behavior @@ -101,8 +103,8 @@ jobs: python --version pip --version echo "### install pythonic dependencies ###" - pip install --upgrade pip - pip install -r pyinstaller/mingw64_requirements.txt + python -m pip install --upgrade pip + pip install -r pyinstaller/win/requirements.txt pip install PyInstaller echo "PyInstaller version: $(python -m PyInstaller --version)" @@ -130,12 +132,12 @@ jobs: echo "### pip freeze --local ###" pip freeze --local | tee pip_freeze_local.txt echo "### pip freeze --local diff to reference configuration ###" - (git diff --ignore-space-change --ignore-cr-at-eol --no-index pyinstaller/mingw64_venv_pip_freeze_local.txt pip_freeze_local.txt || true) | tee diff_pip_freeze_local.txt + (git diff --ignore-space-change --ignore-cr-at-eol --no-index pyinstaller/win/mingw64_venv_pip_freeze_local.txt pip_freeze_local.txt || true) | tee diff_pip_freeze_local.txt echo "### pip freeze ###" pip freeze | tee pip_freeze.txt echo "### pip freeze diff to reference configuration ###" - (git diff --ignore-space-change --ignore-cr-at-eol --no-index pyinstaller/mingw64_venv_pip_freeze.txt pip_freeze.txt || true) | tee diff_pip_freeze.txt + (git diff --ignore-space-change --ignore-cr-at-eol --no-index pyinstaller/win/mingw64_venv_pip_freeze.txt pip_freeze.txt || true) | tee diff_pip_freeze.txt - name: Compile gschemas run: | @@ -147,7 +149,7 @@ jobs: source venv/bin/activate # only ran into problems when calling PyInstaller or pyinstaller directly, # would never find package metadata within the venv, call as module mitigates the issue: - python -m PyInstaller -y ./pyinstaller/dtool-lookup-gui-windows.spec 2>&1 | tee pyinstaller.log + python -m PyInstaller -y ./pyinstaller/win/dtool-lookup-gui-windows.spec 2>&1 | tee pyinstaller.log - name: Build installer run: | diff --git a/.github/workflows/build-on-macos.yml b/.github/workflows/build-on-macos.yml index 040c9e5e..3112f5c0 100644 --- a/.github/workflows/build-on-macos.yml +++ b/.github/workflows/build-on-macos.yml @@ -6,7 +6,7 @@ on: jobs: build: - runs-on: macos-11 + runs-on: macos-14 steps: @@ -24,7 +24,18 @@ jobs: - name: Install system dependencies run: | - brew install python pygobject3 gtk+3 gtksourceview4 gnome-icon-theme create-dmg + brew install \ + python \ + glib \ + gobject-introspection \ + pygobject3 \ + gtk+3 \ + gtksourceview4 \ + adwaita-icon-theme \ + create-dmg \ + gfortran \ + numpy \ + scipy - name: Log system package info run: | @@ -37,13 +48,13 @@ jobs: - name: Install pythonic dependencies run: | - python3 -m venv --system-site-packages venv + python3.13 -m venv --system-site-packages venv source venv/bin/activate pip install --upgrade pip - pip install wheel setuptools setuptools_scm - pip install -r requirements.txt - pip install pyinstaller + pip install wheel setuptools_scm + pip install -r pyinstaller/macos/requirements.txt + pip install pyinstaller pyinstaller-hooks-contrib - name: Fix setuptools_scm-generated version id: fix_version @@ -70,7 +81,7 @@ jobs: - name: Package executable with pyinstaller run: | source venv/bin/activate - pyinstaller -y ./pyinstaller/dtool-lookup-gui-macos.spec 2>&1 | tee pyinstaller.log + pyinstaller -y ./pyinstaller/macos/dtool-lookup-gui-macos.spec 2>&1 | tee pyinstaller.log ls -lh dist mv dist/dtool-lookup-gui.app dtool-lookup-gui.app @@ -90,21 +101,57 @@ jobs: run: | ls -lhv release/ + # see hditutil issue https://github.com/actions/runner-images/issues/7522 - name: Create dmg + shell: bash {0} run: | - create-dmg \ - --volname "dtool-lookup-gui" \ - --eula "LICENSE.txt" \ - --volicon "data/icons/dtool_logo.icns" \ - --window-pos 200 120 \ - --window-size 600 300 \ - --icon-size 100 \ - --icon "dtool-lookup-gui.app" 175 120 \ - --hide-extension "dtool-lookup-gui.app" \ - --app-drop-link 425 120 \ - --hdiutil-verbose \ - "dtool-lookup-gui-${{ steps.fix_version.outputs.version }}-macos.dmg" \ - "release/" + max_retries=3 + retry_count=0 + + hdiutil info + + echo "Killing XProtect if running..." + sudo pkill -9 XProtect >/dev/null || true + + echo "Waiting for XProtect to stop..." + while pgrep XProtect; do sleep 3; done + + sleep 10 + + while (( retry_count < max_retries )); do + echo "Attempt $(($retry_count + 1)) to create dmg..." + create-dmg \ + --volname "dtool-lookup-gui" \ + --eula "LICENSE.txt" \ + --volicon "data/icons/dtool_logo.icns" \ + --window-pos 200 120 \ + --window-size 600 300 \ + --icon-size 100 \ + --icon "dtool-lookup-gui.app" 175 120 \ + --hide-extension "dtool-lookup-gui.app" \ + --app-drop-link 425 120 \ + --hdiutil-verbose \ + "dtool-lookup-gui-${{ steps.fix_version.outputs.version }}-macos.dmg" \ + "release/" + + # Check if create-dmg succeeded + if [[ $? -eq 0 ]]; then + echo "Disk image created successfully." + break + else + echo "Failed to create dmg. Resource might be busy." + ((retry_count++)) + echo "Retrying in 10 seconds... (Attempt $retry_count of $max_retries)" + sleep 10 + fi + done + + hdiutil info + + if (( retry_count == max_retries )); then + echo "Failed to create dmg after $max_retries attempts." + exit 1 + fi - name: Upload build artifact uses: actions/upload-artifact@v4 diff --git a/.github/workflows/build-on-ubuntu.yml b/.github/workflows/build-on-ubuntu.yml index c764e31b..ae5124db 100644 --- a/.github/workflows/build-on-ubuntu.yml +++ b/.github/workflows/build-on-ubuntu.yml @@ -53,7 +53,7 @@ jobs: run: | pip install --upgrade pip pip install wheel setuptools_scm - pip install -r requirements.txt + pip install -r pyinstaller/linux/requirements.txt pip install pyinstaller - name: Fix setuptools_scm-generated version @@ -74,7 +74,7 @@ jobs: - name: Package executable with pyinstaller run: | - pyinstaller -y ./pyinstaller/dtool-lookup-gui-linux-one-file.spec 2>&1 | tee pyinstaller.log + pyinstaller -y ./pyinstaller/linux/dtool-lookup-gui-linux-one-file.spec 2>&1 | tee pyinstaller.log mv dist/dtool-lookup-gui dtool-lookup-gui chmod +x dtool-lookup-gui diff --git a/.github/workflows/build-on-windows.yml b/.github/workflows/build-on-windows.yml index d376ace0..a677dd74 100644 --- a/.github/workflows/build-on-windows.yml +++ b/.github/workflows/build-on-windows.yml @@ -24,8 +24,10 @@ jobs: curl git zip + mingw-w64-x86_64-cmake mingw-w64-x86_64-cython mingw-w64-x86_64-gcc + mingw-w64-x86_64-gobject-introspection mingw-w64-x86_64-gtk3 mingw-w64-x86_64-gtksourceview4 mingw-w64-x86_64-openssl @@ -82,7 +84,7 @@ jobs: echo "### pacman -Q ###" pacman -Q | tee pacman_Q.txt echo "### pacman -Q diff to reference configuration ###" - (git diff --ignore-space-change --ignore-cr-at-eol --no-index pyinstaller/mingw64_pacman_Q.txt pacman_Q.txt || true) | tee diff_pacman_q.txt + (git diff --ignore-space-change --ignore-cr-at-eol --no-index pyinstaller/win/mingw64_pacman_Q.txt pacman_Q.txt || true) | tee diff_pacman_q.txt # 2022/01/23, although msys2/setup-msys2@v2 setup has been kept as close as possible to # a local reference build system, former diverged from latter's pyinstaller behavior @@ -101,8 +103,8 @@ jobs: python --version pip --version echo "### install pythonic dependencies ###" - pip install --upgrade pip - pip install -r pyinstaller/mingw64_requirements.txt + python -m pip install --upgrade pip + pip install -r pyinstaller/win/requirements.txt pip install PyInstaller echo "PyInstaller version: $(python -m PyInstaller --version)" @@ -130,12 +132,12 @@ jobs: echo "### pip freeze --local ###" pip freeze --local | tee pip_freeze_local.txt echo "### pip freeze --local diff to reference configuration ###" - (git diff --ignore-space-change --ignore-cr-at-eol --no-index pyinstaller/mingw64_venv_pip_freeze_local.txt pip_freeze_local.txt || true) | tee diff_pip_freeze_local.txt + (git diff --ignore-space-change --ignore-cr-at-eol --no-index pyinstaller/win/mingw64_venv_pip_freeze_local.txt pip_freeze_local.txt || true) | tee diff_pip_freeze_local.txt echo "### pip freeze ###" pip freeze | tee pip_freeze.txt echo "### pip freeze diff to reference configuration ###" - (git diff --ignore-space-change --ignore-cr-at-eol --no-index pyinstaller/mingw64_venv_pip_freeze.txt pip_freeze.txt || true) | tee diff_pip_freeze.txt + (git diff --ignore-space-change --ignore-cr-at-eol --no-index pyinstaller/win/mingw64_venv_pip_freeze.txt pip_freeze.txt || true) | tee diff_pip_freeze.txt - name: Compile gschemas run: | @@ -147,7 +149,7 @@ jobs: source venv/bin/activate # only ran into problems when calling PyInstaller or pyinstaller directly, # would never find package metadata within the venv, call as module mitigates the issue: - python -m PyInstaller -y ./pyinstaller/dtool-lookup-gui-windows-one-file.spec 2>&1 | tee pyinstaller.log + python -m PyInstaller -y ./pyinstaller/win/dtool-lookup-gui-windows-one-file.spec 2>&1 | tee pyinstaller.log mv dist/dtool-lookup-gui.exe dtool-lookup-gui.exe - name: Pack build assets diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 00000000..32a3bf7a --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,88 @@ +name: report and publish test coverage + +on: + push: + branches: + - main + - master + pull_request: + + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - name: checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up python 3.10 + uses: actions/setup-python@v5 + with: + python-version: "3.10" + + - name: Install system dependencies + run: | + sudo apt-get update -qy + sudo apt-get install -y \ + libgirepository1.0-dev \ + libcairo2-dev \ + pkg-config \ + python3-dev \ + gir1.2-gtk-3.0 \ + gir1.2-gtksource-4 \ + libgtksourceview-4-0 + + - name: Install app + run: | + pip install .[test] + + # run in virtual X server, see https://github.com/pygobject/pygobject-travis-ci-docker-examples + - name: Test with pytest + run: | + xvfb-run pytest --log-cli-level=DEBUG + + - name: setup pages + uses: actions/configure-pages@v5 + + - name: upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: 'htmlcov' + + deploy: + # Add a dependency to the build job + needs: build + + # Only run the deploy job on the "main" branch + if: github.ref == 'refs/heads/master' + + # Deploy to the github-pages environment + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + + runs-on: ubuntu-latest + + steps: + - name: deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b3db6a17..8502deb2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,6 +3,7 @@ on: push: branches: - master + - main tags: - '*' pull_request: @@ -51,7 +52,7 @@ jobs: - name: Install app run: | - pip install . + pip install .[test] # run in virtual X server, see https://github.com/pygobject/pygobject-travis-ci-docker-examples - name: Test with pytest diff --git a/docs/requirements.txt b/docs/requirements.txt index d002affa..439a087f 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,3 +1,3 @@ -sphinx==7.2.6 -sphinx-rtd-theme==2.0.0 -myst-parser==2.0.0 +sphinx==8.1.3 +sphinx-rtd-theme==3.0.1 +myst-parser==4.0.0 diff --git a/dtool_lookup_gui/main.py b/dtool_lookup_gui/main.py index dd85f0bd..4ada50be 100644 --- a/dtool_lookup_gui/main.py +++ b/dtool_lookup_gui/main.py @@ -30,24 +30,24 @@ import json import logging import os +import ssl import sys import dtoolcore import dtool_lookup_api.core.config -from dtool_lookup_api.core.LookupClient import authenticate +from dtool_lookup_api.core.LookupClient import ConfigurationBasedLookupClient import gi gi.require_version('Gtk', '3.0') gi.require_version('GtkSource', '4') from gi.repository import GLib, GObject, Gio, Gtk, GtkSource, GdkPixbuf +from gi.events import GLibEventLoopPolicy -import gbulb -gbulb.install(gtk=True) +asyncio.set_event_loop_policy(GLibEventLoopPolicy()) from .models.settings import settings - from .views.main_window import MainWindow from .views.login_window import LoginWindow @@ -88,6 +88,10 @@ def __init__(self, *args, loop=None, **kwargs): self.loop = loop self.args = None + def on_window_delete(self, window, event): + self.quit() + return False + def do_activate(self): logger.debug("do_activate") @@ -114,7 +118,7 @@ def do_activate(self): logger.debug("{}", icon_file_list) else: logger.warning("Could not load app icons.") - win.connect('destroy', lambda _: self.loop.stop()) + win.connect("delete-event", self.on_window_delete) self.loop.call_soon(win.refresh) # Populate widgets after event loop starts logger.debug("Present main window.") @@ -323,9 +327,11 @@ def do_renew_token(self, action, value): # Unpack the username, password, and auth_url from the tuple variant username, password, auth_url = value.unpack() + logger.debug("look for certificates in %s", ssl.get_default_verify_paths()) async def retrieve_token(auth_url, username, password): try: - token = await authenticate(auth_url, username, password) + async with ConfigurationBasedLookupClient(auth_url=auth_url, username=username, password=password) as lookup_client: + token = await lookup_client.authenticate(auth_url, username, password) except Exception as e: logger.error(str(e)) return @@ -344,7 +350,7 @@ def run_gui(): loop = asyncio.get_event_loop() app = Application(loop=loop) + logger.debug("do_startup") - # see https://github.com/beeware/gbulb#gapplicationgtkapplication-event-loop - loop.run_forever(application=app, argv=sys.argv) + app.run(sys.argv) diff --git a/dtool_lookup_gui/utils/dependency_graph.py b/dtool_lookup_gui/utils/dependency_graph.py index cf9f343d..92e4cd01 100644 --- a/dtool_lookup_gui/utils/dependency_graph.py +++ b/dtool_lookup_gui/utils/dependency_graph.py @@ -63,7 +63,7 @@ async def trace_dependencies(self, lookup, root_uuid, dependency_keys=None): logger.warning("Dependency keys not valid. Ignored.") dependency_keys = None - datasets = await lookup.graph(root_uuid, dependency_keys) + datasets = await lookup.get_graph_by_uuid(uuid=root_uuid, dependency_keys=dependency_keys) logger.debug("Server response on querying dependency graph for UUID = {}.".format(root_uuid)) _log_nested(logger.debug, datasets) diff --git a/dtool_lookup_gui/views/login_window.py b/dtool_lookup_gui/views/login_window.py index b3139104..07f7ed12 100644 --- a/dtool_lookup_gui/views/login_window.py +++ b/dtool_lookup_gui/views/login_window.py @@ -58,14 +58,18 @@ def __init__(self, follow_up_action=None, *args, **kwargs): if Config.password is not None: # Consider security implications self.password_entry.set_text(Config.password) + self.username_entry.connect("activate", self.on_username_activate) + self.password_entry.connect("activate", self.on_password_activate) + def on_username_activate(self, entry): + self.password_entry.grab_focus() + + def on_password_activate(self, entry): + self.on_login_button_clicked(None) # 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() diff --git a/dtool_lookup_gui/views/login_window.ui b/dtool_lookup_gui/views/login_window.ui index 0d91861a..8ed3150f 100644 --- a/dtool_lookup_gui/views/login_window.ui +++ b/dtool_lookup_gui/views/login_window.ui @@ -1,95 +1,115 @@ - + diff --git a/dtool_lookup_gui/views/main_window.py b/dtool_lookup_gui/views/main_window.py index 3d869f76..b41654bb 100644 --- a/dtool_lookup_gui/views/main_window.py +++ b/dtool_lookup_gui/views/main_window.py @@ -247,12 +247,24 @@ def __init__(self, *args, **kwargs): show_dataset_action.connect("activate", self.do_show_dataset_details_by_row_index) self.add_action(show_dataset_action) + # build dependency graph by row index in dataset list box action + row_index_variant = GLib.Variant.new_uint32(0) + build_dependency_graph_action = Gio.SimpleAction.new("build-dependency-graph", row_index_variant.get_type()) + build_dependency_graph_action.connect("activate", self.do_build_dependency_graph_by_row_index) + self.add_action(build_dependency_graph_action) + # show details of dataset by uri in dataset list box action uri_variant = GLib.Variant.new_string("dummy") show_dataset_by_uri_action = Gio.SimpleAction.new("show-dataset-by-uri", uri_variant.get_type()) show_dataset_by_uri_action.connect("activate", self.do_show_dataset_details_by_uri) self.add_action(show_dataset_by_uri_action) + # build dependency graph by uri in dataset list box action + uri_variant = GLib.Variant.new_string("dummy") + build_dependency_graph_by_uri_action = Gio.SimpleAction.new("build-dependency-graph-by-uri", uri_variant.get_type()) + build_dependency_graph_by_uri_action.connect("activate", self.do_build_dependency_graph_by_uri) + self.add_action(build_dependency_graph_by_uri_action) + # search, select and show first search result subsequently row_index_variant = GLib.Variant.new_string("dummy") search_select_show_action = Gio.SimpleAction.new("search-select-show", row_index_variant.get_type()) @@ -400,10 +412,10 @@ async def _fetch_search_results(self, keyword, on_show=None, page_number=1, page keyword, page_number=page_number, page_size=page_size, - sort_fields = sort_fields, - sort_order = sort_order, + sort_fields=sort_fields, + sort_order=sort_order, pagination=self.pagination, - sorting = self.sorting + sorting=self.sorting ) else: @@ -412,23 +424,29 @@ async def _fetch_search_results(self, keyword, on_show=None, page_number=1, page keyword, page_number=page_number, page_size=page_size, - sort_fields = sort_fields, - sort_order = sort_order, + sort_fields=sort_fields, + sort_order=sort_order, pagination=self.pagination, - sorting = self.sorting + sorting=self.sorting ) else: _logger.debug("No keyword specified, list all datasets.") datasets = await DatasetModel.query_all( page_number=page_number, page_size=page_size, - sort_fields = sort_fields, - sort_order = sort_order, + sort_fields=sort_fields, + sort_order=sort_order, pagination=self.pagination, - sorting = self.sorting - + sorting=self.sorting ) + if self.pagination == {}: # server did not provide pagination information + self.pagination = { + 'total': len(datasets), + 'page': 1, + 'last_page': 1 + } + if len(datasets) > self._max_nb_datasets: _logger.warning( f"{len(datasets)} search results exceed allowed displayed maximum of {self._max_nb_datasets}. " @@ -495,6 +513,9 @@ def _show_dataset_details(self, dataset): asyncio.create_task(self._update_dataset_view(dataset)) self.dataset_stack.set_visible_child(self.dataset_box) + def _build_dependency_graph(self, dataset): + asyncio.create_task(self._compute_dependencies(dataset)) + def _show_dataset_details_by_row_index(self, index): row = self.dataset_list_box.get_row_at_index(index) if row is not None: @@ -503,11 +524,25 @@ def _show_dataset_details_by_row_index(self, index): else: _logger.info(f"No dataset row with index {index} available for selection.") + def _build_dependency_graph_by_row_index(self, index): + """Build dependency graph by row index.""" + row = self.dataset_list_box.get_row_at_index(index) + if row is not None: + _logger.debug(f"{row.dataset.name} shown.") + self._build_dependency_graph(row.dataset) + else: + _logger.info(f"No dataset row with index {index} available for selection.") + def _show_dataset_details_by_uri(self, uri): """Select dataset row in dataset list box by uri.""" index = self.dataset_list_box.get_row_index_from_uri(uri) self._show_dataset_details_by_row_index(index) + def _build_dependency_graph_by_uri(self, uri): + """Build dependency graph by uri.""" + index = self.dataset_list_box.get_row_index_from_uri(uri) + self._build_dependency_graph_by_row_index(index) + def _select_and_show_by_row_index(self, index=0): self._select_dataset_row_by_row_index(index) self._show_dataset_details_by_row_index(index) @@ -577,11 +612,21 @@ def do_show_dataset_details_by_row_index(self, action, value): row_index = value.get_uint32() self._show_dataset_details_by_row_index(row_index) + def do_build_dependency_graph_by_row_index(self, action, value): + """Build the dependency graph by row index.""" + row_index = value.get_uint32() + self._build_dependency_graph_by_row_index(row_index) + def do_show_dataset_details_by_uri(self, action, value): """Show dataset details by uri.""" uri = value.get_string() self._show_dataset_details_by_uri(uri) + def do_build_dependency_graph_by_uri(self, action, value): + """Build the dependency graph by uri.""" + uri = value.get_string() + self._build_dependency_graph_by_uri(uri) + # search actions def do_search(self, action, value): """Evoke search tas for specific search text.""" @@ -641,7 +686,6 @@ def do_refresh_view(self, action, value): """Refresh view by reloading base uri list, """ self.refresh() - # signal handlers @Gtk.Template.Callback() def on_settings_clicked(self, widget): @@ -1043,14 +1087,11 @@ def enable_pagination_buttons(self): def select_and_load_first_uri(self): """ - This function is used exclusively for testing purposes. It automatically reloads the data - and selects the first URI. This function is designed to operate only when there is exactly - one URI in the list box, ensuring that tests can be automated effectively. + This function automatically reloads the data and selects the first URI. """ - if len(self.base_uri_list_box.get_children()) == 1: - first_row = self.base_uri_list_box.get_children()[0] - self.base_uri_list_box.select_row(first_row) - self.on_base_uri_selected(self.base_uri_list_box, first_row) + first_row = self.base_uri_list_box.get_children()[0] + self.base_uri_list_box.select_row(first_row) + self.on_base_uri_selected(self.base_uri_list_box, first_row) # TODO: this should be an action do_copy # if it is possible to hand to strings, e.g. source and destination to an action, then this action should @@ -1194,7 +1235,7 @@ async def _get_tags(): if dataset.type == 'lookup': self.dependency_stack.show() _logger.debug("Selected dataset is lookup result.") - asyncio.create_task(self._compute_dependencies(dataset)) + self.get_action_group("win").activate_action('build-dependency-graph-by-uri', GLib.Variant.new_string(dataset.uri)) else: _logger.debug("Selected dataset is accessed directly.") self.dependency_stack.hide() diff --git a/dtool_lookup_gui/views/main_window.ui b/dtool_lookup_gui/views/main_window.ui index 1f01181f..d3c7182e 100644 --- a/dtool_lookup_gui/views/main_window.ui +++ b/dtool_lookup_gui/views/main_window.ui @@ -1,27 +1,27 @@ - + - False + False True - False - 10 - 10 - 10 - 10 - 10 - 10 + False + 10 + 10 + 10 + 10 + 10 + 10 vertical True - True - True + True + True Settings @@ -34,8 +34,8 @@ True - True - True + True + True Logging @@ -48,8 +48,8 @@ True - True - True + True + True About dtool-lookup-gui @@ -62,8 +62,8 @@ True - True - True + True + True Connected dtool-lookup-server versions @@ -76,8 +76,8 @@ True - True - True + True + True Connected dtool-lookup-server config @@ -95,7 +95,7 @@ - False + False @@ -113,39 +113,202 @@ - False + False