Skip to content

Commit

Permalink
Merge pull request #197 from chStaiger/develop
Browse files Browse the repository at this point in the history
Merge to main
  • Loading branch information
chStaiger authored Jun 7, 2024
2 parents cfa8e7c + 277e31a commit 7f213c0
Show file tree
Hide file tree
Showing 17 changed files with 828 additions and 347 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
The git repository contains a generic *iRODS* graphical user interface. The iRODS functionality is based on [ibridges](https://github.com/UtrechtUniversity/iBridges) and works with any *iRODS* instance.


![](docs/screenshots/metadata.png)

## Highlights

Expand Down
58 changes: 36 additions & 22 deletions ibridgesgui/browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
import PyQt6.QtGui
import PyQt6.QtWidgets
import PyQt6.uic
from ibridges import IrodsPath, download, get_collection, get_dataobject, upload
from ibridges.data_operations import obj_replicas
from ibridges import IrodsPath, download, upload
from ibridges.meta import MetaData
from ibridges.permissions import Permissions
from ibridges.util import obj_replicas

from ibridgesgui.gui_utils import (
UI_FILE_DIR,
Expand All @@ -22,7 +22,7 @@
populate_table,
populate_textfield,
)
from ibridgesgui.popup_widgets import CreateCollection
from ibridgesgui.popup_widgets import CreateCollection, Rename
from ibridgesgui.ui_files.tabBrowser import Ui_tabBrowser


Expand All @@ -42,20 +42,20 @@ def __init__(self, session, app_name):
self.info_tabs.setCurrentIndex(0)
# iRODS default home
if self.session.home is not None:
root_path = self.session.home
root_path = IrodsPath(self.session).absolute()
else:
root_path = IrodsPath(
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:
root_path = f"/{self.session.zone}/home/{self.session.username}"
try:
self.root_coll = get_collection(self.session, root_path)
except irods.exception.CollectionDoesNotExist:
self.root_coll = get_collection(self.session, f"/{self.session.zone}/home")
except irods.exception.NetworkException:
self.error_label.setText("iRODS NETWORK ERROR: No Connection, please check network")
except Exception as err:
self.error_label.setText(
'Cannot set root collection. Set "irods_home" in your environment.json'
)
self.logger.exception("Failed to set iRODS home: %s", err)
self.reset_path()
self.browse()

Expand All @@ -75,6 +75,7 @@ def browse(self):
self.upload_dir_button.clicked.connect(self.folder_upload)
self.download_button.clicked.connect(self.download)
self.create_coll_button.clicked.connect(self.create_collection)
self.rename_button.clicked.connect(self.rename_item)

# Browser table behaviour
self.browser_table.doubleClicked.connect(self.update_path)
Expand Down Expand Up @@ -104,7 +105,6 @@ def set_parent(self):
self.path_input.setText(str(current_path.parent))
self.load_browser_table()

# @PyQt6.QtCore.pyqtSlot(PyQt6.QtCore.QModelIndex)
def update_path(self, index):
"""Take path from path_input and loads browser table."""
self.error_label.clear()
Expand All @@ -122,6 +122,20 @@ def create_collection(self):
coll_widget.exec()
self.load_browser_table()

def rename_item(self):
"""Rename/move a collection or data object."""
self.error_label.clear()
if self.browser_table.currentRow() == -1:
self.error_label.setText("Please select a row from the table first!")
return
item_name = self.browser_table.item(self.browser_table.currentRow(), 1).text()
irods_path = IrodsPath(self.session, "/" + self.path_input.text().strip("/")).joinpath(
item_name
)
rename_widget = Rename(irods_path, self.logger)
rename_widget.exec()
self.load_browser_table()

def folder_upload(self):
"""Select a folder and upload."""
self.error_label.clear()
Expand Down Expand Up @@ -187,7 +201,7 @@ def load_browser_table(self):
obj_path = IrodsPath(self.session, self.path_input.text())
if obj_path.collection_exists():
try:
coll = get_collection(self.session, obj_path)
coll = obj_path.collection

coll_data = [
(
Expand Down Expand Up @@ -422,7 +436,7 @@ def _fill_replicas_tab(self, irods_path):
"""
self.replica_table.setRowCount(0)
if irods_path.dataobject_exists():
obj = get_dataobject(self.session, irods_path)
obj = irods_path.dataobject
populate_table(self.replica_table, len(obj_replicas(obj)), obj_replicas(obj))
self.replica_table.setRowCount(len(obj.replicas))
self.replica_table.resizeColumnsToContents()
Expand All @@ -442,10 +456,10 @@ def _fill_acls_tab(self, irods_path):
self.acl_box.setCurrentText("")
obj = None
if irods_path.collection_exists():
obj = get_collection(self.session, irods_path)
obj = irods_path.collection
inheritance = f"{obj.inheritance}"
elif irods_path.dataobject_exists():
obj = get_dataobject(self.session, irods_path)
obj = irods_path.dataobject
inheritance = ""
if obj is not None:
acls = Permissions(self.session, obj)
Expand All @@ -468,9 +482,9 @@ def _fill_metadata_tab(self, irods_path):
self.meta_units_field.clear()
item = None
if irods_path.collection_exists():
item = get_collection(self.session, irods_path)
item = irods_path.collection
elif irods_path.dataobject_exists():
item = get_dataobject(self.session, irods_path)
item = irods_path.dataobject
if item is not None:
meta = MetaData(item)
populate_table(self.meta_table, len(list(meta)), meta)
Expand All @@ -486,14 +500,14 @@ def _fill_preview_tab(self, irods_path):
"""
if irods_path.collection_exists():
obj = get_collection(self.session, irods_path)
obj = irods_path.collection
content = ["Collections:", "-----------------"]
content.extend([sc.name for sc in obj.subcollections])
content.extend(["\n", "DataObjects:", "-----------------"])
content.extend([do.name for do in obj.data_objects])
elif irods_path.dataobject_exists():
file_type = ""
obj = get_dataobject(self.session, irods_path)
obj = irods_path.dataobject
if "." in irods_path.parts[-1]:
file_type = irods_path.parts[-1].split(".")[1]
if file_type in ["txt", "json", "csv"]:
Expand Down
28 changes: 23 additions & 5 deletions ibridgesgui/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@
from typing import Union

from ibridges.session import Session
from irods.exception import CAT_INVALID_USER, PAM_AUTH_PASSWORD_FAILED, NetworkException
from irods.connection import PlainTextPAMPasswordError
from irods.exception import (
CAT_INVALID_AUTHENTICATION,
CAT_INVALID_USER,
PAM_AUTH_PASSWORD_FAILED,
NetworkException,
)
from irods.session import iRODSSession

LOG_LEVEL = {
Expand Down Expand Up @@ -133,7 +139,6 @@ def is_session_from_config(session: Session) -> Union[Session, None]:
ienv_path = Path("~").expanduser().joinpath(".irods", get_last_ienv_path())
try:
env = _read_json(ienv_path)
print(env)
except Exception:
return False

Expand Down Expand Up @@ -170,7 +175,8 @@ def check_irods_config(ienv: Union[Path, dict]) -> str:
except FileNotFoundError:
return f"{ienv} not found."
except JSONDecodeError as err:
return f"{ienv} not well formatted.\n{err.msg}"
print(repr(err))
return f"{ienv} not well formatted.\n {err.msg} at position {err.pos}."
else:
env = ienv
# check host and port and connectivity
Expand Down Expand Up @@ -199,10 +205,22 @@ def check_irods_config(ienv: Union[Path, dict]) -> str:
except AttributeError as err:
return repr(err)

except PlainTextPAMPasswordError:
return (
'Value of "irods_client_server_negotiation" needs to be'
+ ' "request_server_negotiation".'
)

except CAT_INVALID_AUTHENTICATION:
return 'Wrong "irods_authentication_scheme".'
except ValueError as err:
if "scheme" in err.args[0]:
return 'Value of "irods_authentication_scheme" not recognised.'
return f"{err.args}"

# password incorrect but rest is fine
except (ValueError, CAT_INVALID_USER, PAM_AUTH_PASSWORD_FAILED):
except (CAT_INVALID_USER, PAM_AUTH_PASSWORD_FAILED):
return "All checks passed successfully."

# all tests passed
return "All checks passed successfully."

Expand Down
59 changes: 48 additions & 11 deletions ibridgesgui/gui_utils.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
"""Handy and reusable functions for the GUI."""

import pathlib
from importlib.resources import files
from typing import Union

import irods
import PyQt6
from ibridges import get_collection, get_dataobject
from ibridges.path import IrodsPath
from ibridges import IrodsPath, download

from ibridgesgui.config import get_last_ienv_path, is_session_from_config

try:
from importlib.resources import files
except ImportError:
from importlib_resources import files


UI_FILE_DIR = files(__package__) / "ui_files"

Expand Down Expand Up @@ -35,10 +41,10 @@ def populate_textfield(text_widget, text_by_row: Union[str, list]):
# iBridges/iRODS utils
def get_irods_item(irods_path: IrodsPath):
"""Get the item behind an iRODS path."""
try:
item = get_collection(irods_path.session, irods_path)
except ValueError:
item = get_dataobject(irods_path.session, irods_path)
if irods_path.collection_exists():
item = irods_path.collection
else:
item = irods_path.dataobject
return item


Expand All @@ -62,6 +68,37 @@ def get_coll_dict(root_coll: irods.collection.iRODSCollection) -> dict:
}


def prep_session_for_copy(session, error_label) -> pathlib.Path:
"""Either return a save path to create a new session from or sets message in error label."""
if is_session_from_config(session):
return pathlib.Path.home().joinpath(".irods", get_last_ienv_path())

text = "The ibridges config changed during the session."
text += "Please reset or restart the session."
error_label.setText(text)
return None


def combine_diffs(session, sources: list, destination: Union[IrodsPath, pathlib.Path]) -> dict:
"""Combine the diffs of several upload or download dry-runs."""
combined_diffs = {
"create_dir": set(),
"create_collection": set(),
"upload": [],
"download": [],
"resc_name": "",
"options": None,
}
if isinstance(destination, pathlib.Path):
for ipath in sources:
diff = download(session, ipath, destination, dry_run=True, overwrite=True)
combined_diffs["download"].extend(diff["download"])
combined_diffs["create_dir"] = combined_diffs["create_dir"].union(diff["create_dir"])
elif isinstance(destination, IrodsPath):
print("not implemented yet")
return combined_diffs


# OS utils
def get_downloads_dir() -> pathlib.Path:
"""Find the platform-dependent 'Downloads' directory.
Expand All @@ -73,9 +110,9 @@ def get_downloads_dir() -> pathlib.Path:
"""
# Linux and Mac Download folders
if pathlib.Path("~", "Downloads").expanduser().is_dir():
return pathlib.Path("~", "Downloads").expanduser()
if pathlib.Path.home().joinpath("Downloads").is_dir():
return pathlib.Path.home().joinpath("Downloads")

# Try to create Downloads
pathlib.Path("~", "Downloads").expanduser().mkdir(parents=True)
return pathlib.Path("~", "Downloads").expanduser()
pathlib.Path.home().joinpath("Downloads").mkdir(parents=True)
return pathlib.Path.home().joinpath("Downloads")
15 changes: 6 additions & 9 deletions ibridgesgui/irods_tree_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import PyQt6.QtCore
import PyQt6.QtGui
import PyQt6.QtWidgets
from ibridges import IrodsPath, get_collection
from ibridges import IrodsPath


class IrodsTreeModel(PyQt6.QtGui.QStandardItemModel):
Expand Down Expand Up @@ -70,15 +70,14 @@ def init_tree(self):
root = self.invisibleRootItem()

# Start the tree, add the highest level to the invisible root
root_coll = get_collection(self.session, self.irods_root_path)
root_coll = IrodsPath(self.session, self.irods_root_path).collection
root_row = self._tree_row_from_irods_item(root_coll, -1, -1, True)
root.appendRow(root_row)

new_node = root.child(root.rowCount() - 1)

# insert a dummy child to get the link to open the collection
if len(root_coll.subcollections + root_coll.data_objects) > 0:
new_node.appendRow(None)
new_node.appendRow(None)

def delete_subtree(self, tree_item):
"""Delete subtree.
Expand All @@ -104,8 +103,7 @@ def add_subtree(self, tree_item, tree_item_data: list):
"""
_, level, _, _, _, abs_irods_path = tree_item_data
# _tree_row_from_irods_item(self, item, parent_id, level, display_path = False)
parent_coll = get_collection(self.session, abs_irods_path)
parent_coll = IrodsPath(self.session, abs_irods_path).collection

# we assume that tree_item has no children yet.
new_nodes = {}
Expand All @@ -114,9 +112,8 @@ def add_subtree(self, tree_item, tree_item_data: list):
tree_item.appendRow(row)
new_nodes[item.id] = tree_item.child(tree_item.rowCount() - 1)
if isinstance(item, irods.collection.iRODSCollection):
if len(item.subcollections + item.data_objects) > 0:
# insert a dummy child to get the link to open the collection
new_nodes[item.id].appendRow(None)
# insert a dummy child to get the link to open the collection
new_nodes[item.id].appendRow(None)

def refresh_subtree(self, position):
"""Refresh the tree view.
Expand Down
Loading

0 comments on commit 7f213c0

Please sign in to comment.