Skip to content

Commit

Permalink
Merge pull request #214 from chStaiger/develop
Browse files Browse the repository at this point in the history
v0.2.2
  • Loading branch information
chStaiger authored Jun 26, 2024
2 parents 81d1b50 + 441d92a commit 0913fbf
Show file tree
Hide file tree
Showing 18 changed files with 279 additions and 35 deletions.
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,18 @@ The git repository contains a generic *iRODS* graphical user interface. The iRO
- Safe default options when working with your data.

## Installation
- As python package
- The python package

```bash
pip install ibridgesgui
```

- A specific branch of the git repository (testers, developers)

```bash
pip install git+https://github.com/chStaiger/iBridges-Gui.git@branch-name
```

- Locally from code (for developers)

```bash
Expand Down
3 changes: 1 addition & 2 deletions docs/getting-started.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
## Install iBridges-gui

```
pip install ibridges==0.1.6
pip install pip install git+https://github.com/chStaiger/iBridges-Gui.git@main
pip install ibridgesgui
```

## Start the program
Expand Down
28 changes: 21 additions & 7 deletions ibridgesgui/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@

from ibridgesgui.browser import Browser
from ibridgesgui.config import get_log_level, init_logger, set_log_level
from ibridgesgui.gui_utils import UI_FILE_DIR
from ibridgesgui.info import Info
from ibridgesgui.login import Login
from ibridgesgui.logviewer import LogViewer
from ibridgesgui.popup_widgets import CheckConfig
from ibridgesgui.search import Search
from ibridgesgui.sync import Sync
Expand All @@ -34,10 +34,9 @@ class MainMenu(PyQt6.QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self, app_name):
"""Initialise the main window."""
super().__init__()
if getattr(sys, "frozen", False):
super().setupUi(self)
else:
PyQt6.uic.loadUi(UI_FILE_DIR / "MainMenu.ui", self)
super().setupUi(self)

app.aboutToQuit.connect(self.close_event)

self.logger = logging.getLogger(app_name)

Expand All @@ -49,6 +48,7 @@ def __init__(self, app_name):
"tabSync": self.init_sync_tab,
"tabSearch": self.init_search_tab,
"tabInfo": self.init_info_tab,
"tabLog": self.init_log_tab
}

self.session = None
Expand All @@ -61,6 +61,7 @@ def __init__(self, app_name):
self.action_check_configuration.triggered.connect(self.inspect_env_file)
self.tab_widget.setCurrentIndex(0)


def disconnect(self):
"""Close iRODS session."""
self.error_label.clear()
Expand All @@ -82,13 +83,16 @@ def connect(self):
login_window = Login(self.session_dict, self.app_name)
login_window.exec()
# Trick to get the session object from the QDialog

if "session" in self.session_dict:
self.session = self.session_dict["session"]
try:
self.setup_tabs()
except:
self.session = None
raise
else:
self.logger.exception("No session created. %s", self.session_dict)

def exit(self):
"""Quit program."""
Expand All @@ -106,6 +110,11 @@ def exit(self):
else:
pass

def close_event(self):
"""Close program properly if main window is closed."""
self.disconnect()
sys.exit()

def setup_tabs(self):
"""Init tab view."""
for tab_fun in self.ui_tabs_lookup.values():
Expand All @@ -122,6 +131,11 @@ def init_info_tab(self):
irods_info = Info(self.session)
self.tab_widget.addTab(irods_info, "Info")

def init_log_tab(self):
"""Create log tab."""
ibridges_log = LogViewer(self.logger)
self.tab_widget.addTab(ibridges_log, "Logs")

def init_browser_tab(self):
"""Create browser."""
self.irods_browser = Browser(self.session, self.app_name)
Expand Down Expand Up @@ -156,8 +170,8 @@ def main():
if log_level is not None:
init_logger(THIS_APPLICATION, log_level)
else:
set_log_level("error")
init_logger(THIS_APPLICATION, "error")
set_log_level("debug")
init_logger(THIS_APPLICATION, "debug")
main_widget = PyQt6.QtWidgets.QStackedWidget()
main_app = MainMenu(THIS_APPLICATION)
main_widget.addWidget(main_app)
Expand Down
9 changes: 1 addition & 8 deletions ibridgesgui/browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,7 @@ def __init__(self, session, app_name):
self.session, f"/{self.session.zone}/home/{self.session.username}"
)

if root_path.collection_exists():
self.root_coll = IrodsPath(self.session, root_path).collection
elif IrodsPath(self.session, f"/{self.session.zone}/home").collection_exists():
self.root_coll = IrodsPath(self.session, f"/{self.session.zone}/home").collection
else:
self.error_label.setText(
'Cannot set root collection. Set "irods_home" in your environment.json'
)
self.root_coll = IrodsPath(self.session, root_path).collection
self.reset_path()
self.browse()

Expand Down
1 change: 1 addition & 0 deletions ibridgesgui/gui_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
UI_FILE_DIR = files(__package__) / "ui_files"
LOGO_DIR = files(__package__) / "icons"


# Widget utils
def populate_table(table_widget, rows: int, data_by_row: list):
"""Populate a table-like pyqt widget with data."""
Expand Down
5 changes: 4 additions & 1 deletion ibridgesgui/irods_tree_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,12 @@ def add_subtree(self, tree_item, tree_item_data: list):
_, level, _, _, _, abs_irods_path = tree_item_data
parent_coll = IrodsPath(self.session, abs_irods_path).collection

# the irods root also contains the irods root as subcollection
subcolls = [c for c in parent_coll.subcollections if c.path != "/"]
dataobjs = parent_coll.data_objects
# we assume that tree_item has no children yet.
new_nodes = {}
for item in parent_coll.subcollections + parent_coll.data_objects:
for item in subcolls + dataobjs:
row = self._tree_row_from_irods_item(item, parent_coll.id, int(level))
tree_item.appendRow(row)
new_nodes[item.id] = tree_item.child(tree_item.rowCount() - 1)
Expand Down
38 changes: 35 additions & 3 deletions ibridgesgui/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
import sys
from pathlib import Path

from ibridges import Session
from ibridges import IrodsPath, Session
from ibridges.resources import Resources
from ibridges.session import LoginError, PasswordError
from irods.exception import ResourceDoesNotExist
from PyQt6.QtWidgets import QDialog, QLineEdit
from PyQt6.uic import loadUi

Expand Down Expand Up @@ -77,25 +79,55 @@ def login_function(self):
else:
session = Session(irods_env=env_file, password=self.password_field.text())
self.logger.debug("Login with %s and password from prompt.", env_file)
self.session_dict["session"] = session
self.logger.info(
"Logged in as %s to %s; working coll %s",
session.username,
session.host,
session.home,
)
session.write_pam_password()
self.session_dict["session"] = session
set_last_ienv_path(env_file.name)
self.close()
except LoginError:
self.error_label.setText("irods_environment.json not setup correctly.")
self.logger.error("irods_environment.json not setup correctly.")
except PasswordError:
self.error_label.setText("Wrong password!")
self.logger.error("Wrong password provided.")
except ConnectionError:
self.error_label.setText(
"Cannot connect to server. Check Internet, host name and port."
)
self.logger.exception("Network error.")
except Exception as err:
log_path = Path("~/.ibridges")
self.logger.exception("Failed to login: %s", repr(err))
self.error_label.setText(f"Login failed, consult the log file(s) in {log_path}")

#check irods_home
fail_home = True
if not IrodsPath(self.session_dict["session"]).collection_exists():
self.error_label.setText(f'"irods_home": "{session.home}" does not exist.')
self.logger.error("irods_home does not exist.")
else:
fail_home = False

#check existance of default resource
fail_resc = True
try:
resc = Resources(self.session_dict["session"]).get_resource(session.default_resc)
if resc.parent is None:
fail_resc = False
else:
self.error_label.setText(f'"default_resource": "{session.default_resc}" not valid.')
except ResourceDoesNotExist:
self.error_label.setText(
f'"default_resource": "{session.default_resc}" does not exist.')
self.logger.error("Default resource does not exist.")
except AttributeError:
self.error_label.setText(f'"default_resource": "{session.default_resc}" not valid.')

if fail_resc or fail_home:
del self.session_dict["session"]
else:
self.close()
50 changes: 50 additions & 0 deletions ibridgesgui/logviewer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"""Logging tab."""

import logging
import sys

import PyQt6

from ibridgesgui.config import CONFIG_DIR
from ibridgesgui.gui_utils import UI_FILE_DIR
from ibridgesgui.ui_files.tabLogging import Ui_tabLogging


class QTextEditLogger(logging.Handler, PyQt6.QtCore.QObject):
"""Logging in a Qt text browser."""

append_plain_text = PyQt6.QtCore.pyqtSignal(str)

def __init__(self, text_browser):
"""Initialise."""
super().__init__()
PyQt6.QtCore.QObject.__init__(self)
self.widget = text_browser
self.widget.setReadOnly(True)
self.append_plain_text.connect(self.widget.insertPlainText)

def emit(self, record):
"""Emit when new logging accurs."""
msg = self.format(record)+"\n"
self.append_plain_text.emit(msg)


class LogViewer(PyQt6.QtWidgets.QWidget, Ui_tabLogging):
"""Set iBridges logging in GUI."""

def __init__(self, logger):
"""Initialise the tab."""
super().__init__()
if getattr(sys, "frozen", False):
super().setupUi(self)
else:
PyQt6.uic.loadUi(UI_FILE_DIR / "tabLogging.ui", self)

self.logger = logger
self.log_label.setText(str(CONFIG_DIR))
self.log_text = QTextEditLogger(self.log_browser)
self.log_text.setFormatter(
logging.Formatter(
'%(asctime)s %(levelname)s %(module)s %(funcName)s %(message)s'))
self.logger.addHandler(self.log_text)
self.logger.setLevel(logging.DEBUG)
27 changes: 27 additions & 0 deletions ibridgesgui/popup_widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import irods
from ibridges import IrodsPath
from ibridges.util import find_environment_provider, get_environment_providers
from PyQt6 import QtCore
from PyQt6.QtWidgets import QDialog, QFileDialog
from PyQt6.uic import loadUi
Expand Down Expand Up @@ -140,8 +141,10 @@ def __init__(self, logger, env_path):
self.env_path = env_path
self.setWindowTitle("Create, edit and inspect iRODS environment")
self._init_env_box()
self._init_template_box()

self.envbox.activated.connect(self.load_env)
self.template_box.activated.connect(self.load_template)
self.new_button.clicked.connect(self.create_env)
self.check_button.clicked.connect(self.check_env)
self.save_button.clicked.connect(self.save_env)
Expand All @@ -155,6 +158,30 @@ def _init_env_box(self):
self.envbox.addItems(env_jsons)
self.envbox.setCurrentIndex(0)

def _init_template_box(self):
self.template_box.clear()
providers = get_environment_providers()

if len(providers) == 0:
self.template_box.hide()
return

templates = [key+": "+descr for p in providers
for key, descr in p.descriptions.items()]
if len(templates) > 0:
self.template_box.addItems(templates)
self.template_box.setCurrentIndex(0)

def load_template(self):
"""Load environment template into text field."""
self.error_label.clear()
template = self.template_box.currentText()
key = template.split(": ")[0]
env_json = find_environment_provider(get_environment_providers(), key).\
environment_json(key, "USERNAME").split("\n")
populate_textfield(self.env_field, env_json)
self.error_label.setText("Please fill in your user name.")

def load_env(self):
"""Load json into text field."""
self.error_label.clear()
Expand Down
3 changes: 2 additions & 1 deletion ibridgesgui/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,8 @@ def _validate_search_params(self) -> tuple[str, dict, str, str]:
key.text(): "%" if val.text() == "" else val.text()
for key, val in zip(self.keys, self.vals)
}
del key_vals[""]
if "" in key_vals:
del key_vals[""]

path = self.path_field.text() if self.path_field.text() != "" else None
checksum = self.checksum_field.text() if self.checksum_field.text() != "" else None
Expand Down
12 changes: 7 additions & 5 deletions ibridgesgui/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def _init_irods_tree(self):
def irods_root(self):
"""Retrieve lowest visible level in the iRODS tree for the user."""
lowest = IrodsPath(self.session).absolute()
while lowest.parent.exists():
while lowest.parent.exists() and str(lowest) != "/":
lowest = lowest.parent
return lowest

Expand Down Expand Up @@ -276,10 +276,12 @@ def _sync_diff_end(self, thread_output: dict):
return

self.error_label.clear()
table_data = [(ipath, lpath, ipath.size)
for ipath, lpath in thread_output["result"]["download"]] + \
[(lpath, ipath, lpath.stat().st_size)
for lpath, ipath in thread_output["result"]["upload"]]
table_data = [
(ipath, lpath, ipath.size) for ipath, lpath in thread_output["result"]["download"]
] + [
(lpath, ipath, lpath.stat().st_size)
for lpath, ipath in thread_output["result"]["upload"]
]
populate_table(self.diff_table, len(table_data), table_data)
if len(table_data) == 0:
self.error_label.setText("Data is already synchronised.")
Expand Down
Loading

0 comments on commit 0913fbf

Please sign in to comment.