ibridgesgui 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. ibridgesgui/__init__.py +1 -0
  2. ibridgesgui/__main__.py +164 -0
  3. ibridgesgui/_version.py +16 -0
  4. ibridgesgui/browser.py +625 -0
  5. ibridgesgui/config.py +250 -0
  6. ibridgesgui/gui_utils.py +118 -0
  7. ibridgesgui/icons/arrow-left.png +0 -0
  8. ibridgesgui/icons/arrow-right.png +0 -0
  9. ibridgesgui/icons/arrow-up.png +0 -0
  10. ibridgesgui/icons/home.png +0 -0
  11. ibridgesgui/icons/iBridges.ico +0 -0
  12. ibridgesgui/icons/loading_circle.gif +0 -0
  13. ibridgesgui/icons/nosync.png +0 -0
  14. ibridgesgui/icons/refresh.png +0 -0
  15. ibridgesgui/icons/syncing.png +0 -0
  16. ibridgesgui/info.py +53 -0
  17. ibridgesgui/irods_tree_model.py +158 -0
  18. ibridgesgui/login.py +101 -0
  19. ibridgesgui/popup_widgets.py +237 -0
  20. ibridgesgui/search.py +285 -0
  21. ibridgesgui/sync.py +289 -0
  22. ibridgesgui/threads.py +251 -0
  23. ibridgesgui/ui_files/.py +118 -0
  24. ibridgesgui/ui_files/ExampleTab.py +87 -0
  25. ibridgesgui/ui_files/ExampleTab.ui +93 -0
  26. ibridgesgui/ui_files/MainMenu.py +121 -0
  27. ibridgesgui/ui_files/MainMenu.ui +171 -0
  28. ibridgesgui/ui_files/__init__.py +1 -0
  29. ibridgesgui/ui_files/configCheck.py +96 -0
  30. ibridgesgui/ui_files/configCheck.ui +142 -0
  31. ibridgesgui/ui_files/createCollection.py +82 -0
  32. ibridgesgui/ui_files/createCollection.ui +153 -0
  33. ibridgesgui/ui_files/dataTransferState.py +114 -0
  34. ibridgesgui/ui_files/dataTransferState.ui +187 -0
  35. ibridgesgui/ui_files/irodsLogin.py +134 -0
  36. ibridgesgui/ui_files/irodsLogin.ui +237 -0
  37. ibridgesgui/ui_files/renameItem.py +77 -0
  38. ibridgesgui/ui_files/renameItem.ui +151 -0
  39. ibridgesgui/ui_files/tabBrowser.py +525 -0
  40. ibridgesgui/ui_files/tabBrowser.ui +829 -0
  41. ibridgesgui/ui_files/tabInfo.py +207 -0
  42. ibridgesgui/ui_files/tabInfo.ui +356 -0
  43. ibridgesgui/ui_files/tabSearch.py +233 -0
  44. ibridgesgui/ui_files/tabSearch.ui +400 -0
  45. ibridgesgui/ui_files/tabSync.py +223 -0
  46. ibridgesgui/ui_files/tabSync.ui +481 -0
  47. ibridgesgui/ui_files/tabUpDownload.py +303 -0
  48. ibridgesgui/ui_files/tabUpDownload.ui +460 -0
  49. ibridgesgui-0.2.0.dist-info/LICENSE +674 -0
  50. ibridgesgui-0.2.0.dist-info/METADATA +787 -0
  51. ibridgesgui-0.2.0.dist-info/RECORD +54 -0
  52. ibridgesgui-0.2.0.dist-info/WHEEL +5 -0
  53. ibridgesgui-0.2.0.dist-info/entry_points.txt +2 -0
  54. ibridgesgui-0.2.0.dist-info/top_level.txt +1 -0
ibridgesgui/config.py ADDED
@@ -0,0 +1,250 @@
1
+ """Setting up logger and configuration files."""
2
+
3
+ import datetime
4
+ import json
5
+ import logging
6
+ import logging.handlers
7
+ import sys
8
+ from json import JSONDecodeError
9
+ from pathlib import Path
10
+ from typing import Union
11
+
12
+ from ibridges.session import Session
13
+ from irods.connection import PlainTextPAMPasswordError
14
+ from irods.exception import (
15
+ CAT_INVALID_AUTHENTICATION,
16
+ CAT_INVALID_USER,
17
+ PAM_AUTH_PASSWORD_FAILED,
18
+ NetworkException,
19
+ )
20
+ from irods.session import iRODSSession
21
+
22
+ LOG_LEVEL = {
23
+ "fulldebug": logging.DEBUG - 5,
24
+ "debug": logging.DEBUG,
25
+ "info": logging.INFO,
26
+ "warn": logging.WARNING,
27
+ "error": logging.ERROR,
28
+ "critical": logging.CRITICAL,
29
+ }
30
+
31
+ CONFIG_DIR = Path("~", ".ibridges").expanduser()
32
+ CONFIG_FILE = CONFIG_DIR.joinpath("ibridges_gui.json")
33
+
34
+
35
+ def ensure_log_config_location():
36
+ """Ensure the location for logs and config files."""
37
+ CONFIG_DIR.mkdir(parents=True, exist_ok=True)
38
+
39
+
40
+ # logging functions
41
+ def init_logger(app_name: str, log_level: str) -> logging.Logger:
42
+ """Create a logger for the app.
43
+
44
+ app_name : str
45
+ Name of the app, will be used as file name
46
+ log_level : str
47
+ String that will be mapped to python's log levels, default is info
48
+ """
49
+ logger = logging.getLogger(app_name)
50
+ logfile = CONFIG_DIR.joinpath(f"{app_name}.log")
51
+
52
+ # Direct logging to logfile
53
+ formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
54
+ file_handler = logging.handlers.RotatingFileHandler(logfile, "a", 100000, 1)
55
+ file_handler.setFormatter(formatter)
56
+ logger.addHandler(file_handler)
57
+ logger.setLevel(LOG_LEVEL.get(log_level, LOG_LEVEL["info"]))
58
+
59
+ # Logger greeting when app is started
60
+ with open(logfile, "a", encoding="utf-8") as logfd:
61
+ logfd.write("\n\n")
62
+ underscores = f"{'_' * 50}\n"
63
+ logfd.write(underscores * 2)
64
+ logfd.write(f"\t Starting iBridges-GUI \n\t{datetime.datetime.now().isoformat()}\n")
65
+ logfd.write(underscores * 2)
66
+
67
+ return logger
68
+
69
+
70
+ # ibridges config functions
71
+ def get_last_ienv_path() -> Union[None, str]:
72
+ """Retrieve last used environment path from the config file."""
73
+ config = _get_config()
74
+ if config is not None:
75
+ return config.get("gui_last_env")
76
+ return None
77
+
78
+
79
+ def set_last_ienv_path(ienv_path: Path):
80
+ """Save the last used environment path to the config file.
81
+
82
+ ienv_path : Path
83
+ Path to last environment
84
+ """
85
+ config = _get_config()
86
+ if config is not None:
87
+ config["gui_last_env"] = ienv_path
88
+ else:
89
+ config = {"gui_last_env": ienv_path}
90
+ _save_config(config)
91
+
92
+
93
+ def get_log_level() -> Union[None, str]:
94
+ """Retrieve log level from config."""
95
+ config = _get_config()
96
+ if config is not None:
97
+ return config.get("log_level")
98
+ return None
99
+
100
+
101
+ def set_log_level(level: str):
102
+ """Save log level to config."""
103
+ config = _get_config()
104
+ if config is not None:
105
+ config["log_level"] = level
106
+ else:
107
+ config = {"log_level": level}
108
+ _save_config(config)
109
+
110
+
111
+ def _save_config(conf: dict):
112
+ ensure_log_config_location()
113
+ _write_json(CONFIG_FILE, conf)
114
+
115
+
116
+ def _get_config() -> Union[None, dict]:
117
+ try:
118
+ return _read_json(CONFIG_FILE)
119
+ except FileNotFoundError:
120
+ return None
121
+ except JSONDecodeError as err:
122
+ # empty file
123
+ if err.msg == "Expecting value":
124
+ return None
125
+ print(f"CANNOT START APP: {CONFIG_FILE} incorrectly formatted.")
126
+ sys.exit(1)
127
+
128
+
129
+ # irods config functions
130
+
131
+
132
+ def is_session_from_config(session: Session) -> Union[Session, None]:
133
+ """Create a new session from the given session.
134
+
135
+ For the QThreads we need an own session to avoid hickups.
136
+ We will verify that the given session was instantiated by the
137
+ parameters saved in the ibridges configuration.
138
+ """
139
+ ienv_path = Path("~").expanduser().joinpath(".irods", get_last_ienv_path())
140
+ try:
141
+ env = _read_json(ienv_path)
142
+ except Exception:
143
+ return False
144
+
145
+ if (
146
+ session.host == env.get("irods_host", -1)
147
+ and session.port == env.get("irods_port", -1)
148
+ and session.zone == env.get("irods_zone_name", -1)
149
+ and session.username == env.get("irods_user_name")
150
+ and session.home == env.get("irods_home", -1)
151
+ and session.default_resc == env.get("irods_default_resource", -1)
152
+ ):
153
+ return True
154
+ return False
155
+
156
+
157
+ def check_irods_config(ienv: Union[Path, dict]) -> str:
158
+ """Check whether an iRODS configuration file is correct.
159
+
160
+ Parameters
161
+ ----------
162
+ ienv : Path or dict
163
+ Path to the irods_environment.json or the dictionary conatining the json.
164
+
165
+ Returns
166
+ -------
167
+ str :
168
+ Error message why login with the settings would fail.
169
+ "All checks passed successfully" in case all seems to be fine.
170
+
171
+ """
172
+ if isinstance(ienv, Path):
173
+ try:
174
+ env = _read_json(ienv)
175
+ except FileNotFoundError:
176
+ return f"{ienv} not found."
177
+ except JSONDecodeError as err:
178
+ print(repr(err))
179
+ return f"{ienv} not well formatted.\n {err.msg} at position {err.pos}."
180
+ else:
181
+ env = ienv
182
+ # check host and port and connectivity
183
+ if "irods_host" not in env:
184
+ return '"irods_host" is missing in environment.'
185
+ if "irods_port" not in env:
186
+ return '"irods_port" is missing in environment.'
187
+ if "irods_home" not in env:
188
+ return 'Please set an "irods_home".'
189
+ if "irods_default_resource" not in env:
190
+ return 'Please set an "irods_default_resource".'
191
+ if not isinstance(env["irods_port"], int):
192
+ return '"irods_port" needs to be an integer, remove quotes.'
193
+ if not Session.network_check(env["irods_host"], env["irods_port"]):
194
+ return f'No connection: Network might be down or\n \
195
+ server name {env["irods_host"]} is incorrect or\n \
196
+ port {env["irods_port"]} is incorrect.'
197
+ # check authentication scheme
198
+ try:
199
+ sess = iRODSSession(password="bogus", **env)
200
+ _ = sess.server_version
201
+ except TypeError as err:
202
+ return repr(err)
203
+ except NetworkException as err:
204
+ return repr(err)
205
+ except AttributeError as err:
206
+ return repr(err)
207
+
208
+ except PlainTextPAMPasswordError:
209
+ return (
210
+ 'Value of "irods_client_server_negotiation" needs to be'
211
+ + ' "request_server_negotiation".'
212
+ )
213
+
214
+ except CAT_INVALID_AUTHENTICATION:
215
+ return 'Wrong "irods_authentication_scheme".'
216
+ except ValueError as err:
217
+ if "scheme" in err.args[0]:
218
+ return 'Value of "irods_authentication_scheme" not recognised.'
219
+ return f"{err.args}"
220
+
221
+ # password incorrect but rest is fine
222
+ except (CAT_INVALID_USER, PAM_AUTH_PASSWORD_FAILED):
223
+ return "All checks passed successfully."
224
+ # all tests passed
225
+ return "All checks passed successfully."
226
+
227
+
228
+ def save_irods_config(env_path: Union[Path, str], conf: dict):
229
+ """Save an irods environment as json in ~/.irods.
230
+
231
+ Parmeters
232
+ ---------
233
+ env_name : str
234
+ file name
235
+ """
236
+ env_path = Path(env_path)
237
+ if env_path.suffix == ".json":
238
+ _write_json(env_path, conf)
239
+ else:
240
+ raise ValueError("Filetype needs to be '.json'.")
241
+
242
+
243
+ def _read_json(file_path: Path) -> dict:
244
+ with open(file_path, "r", encoding="utf-8") as handle:
245
+ return json.load(handle)
246
+
247
+
248
+ def _write_json(file_path: Path, content: dict):
249
+ with open(file_path, "w", encoding="utf-8") as handle:
250
+ json.dump(content, handle)
@@ -0,0 +1,118 @@
1
+ """Handy and reusable functions for the GUI."""
2
+
3
+ import pathlib
4
+ from typing import Union
5
+
6
+ import irods
7
+ import PyQt6
8
+ from ibridges import IrodsPath, download
9
+
10
+ from ibridgesgui.config import get_last_ienv_path, is_session_from_config
11
+
12
+ try:
13
+ from importlib.resources import files
14
+ except ImportError:
15
+ from importlib_resources import files
16
+
17
+
18
+ UI_FILE_DIR = files(__package__) / "ui_files"
19
+
20
+
21
+ # Widget utils
22
+ def populate_table(table_widget, rows: int, data_by_row: list):
23
+ """Populate a table-like pyqt widget with data."""
24
+ table_widget.setRowCount(rows)
25
+ for row, data in enumerate(data_by_row):
26
+ for col, item in enumerate(data):
27
+ table_widget.setItem(row, col, PyQt6.QtWidgets.QTableWidgetItem(str(item)))
28
+ table_widget.resizeColumnsToContents()
29
+
30
+
31
+ def populate_textfield(text_widget, text_by_row: Union[str, list]):
32
+ """Populate a text viewer or editor with text."""
33
+ text_widget.clear()
34
+ if isinstance(text_by_row, str):
35
+ text_widget.append(text_by_row)
36
+ else:
37
+ for row in text_by_row:
38
+ text_widget.append(row)
39
+
40
+
41
+ # iBridges/iRODS utils
42
+ def get_irods_item(irods_path: IrodsPath):
43
+ """Get the item behind an iRODS path."""
44
+ if irods_path.collection_exists():
45
+ item = irods_path.collection
46
+ else:
47
+ item = irods_path.dataobject
48
+ return item
49
+
50
+
51
+ def get_coll_dict(root_coll: irods.collection.iRODSCollection) -> dict:
52
+ """Create a recursive metadata dictionary for `coll`.
53
+
54
+ Parameters
55
+ ----------
56
+ root_coll : irods.collection.iRODSCollection
57
+ Root collection for the metadata gathering.
58
+
59
+ Returns
60
+ -------
61
+ dict
62
+ Keys of logical paths, values
63
+
64
+ """
65
+ return {
66
+ this_coll.path: [data_obj.name for data_obj in data_objs]
67
+ for this_coll, _, data_objs in root_coll.walk()
68
+ }
69
+
70
+
71
+ def prep_session_for_copy(session, error_label) -> pathlib.Path:
72
+ """Either return a save path to create a new session from or sets message in error label."""
73
+ if is_session_from_config(session):
74
+ return pathlib.Path.home().joinpath(".irods", get_last_ienv_path())
75
+
76
+ text = "The ibridges config changed during the session."
77
+ text += "Please reset or restart the session."
78
+ error_label.setText(text)
79
+ return None
80
+
81
+
82
+ def combine_diffs(session, sources: list, destination: Union[IrodsPath, pathlib.Path]) -> dict:
83
+ """Combine the diffs of several upload or download dry-runs."""
84
+ combined_diffs = {
85
+ "create_dir": set(),
86
+ "create_collection": set(),
87
+ "upload": [],
88
+ "download": [],
89
+ "resc_name": "",
90
+ "options": None,
91
+ }
92
+ if isinstance(destination, pathlib.Path):
93
+ for ipath in sources:
94
+ diff = download(session, ipath, destination, dry_run=True, overwrite=True)
95
+ combined_diffs["download"].extend(diff["download"])
96
+ combined_diffs["create_dir"] = combined_diffs["create_dir"].union(diff["create_dir"])
97
+ elif isinstance(destination, IrodsPath):
98
+ print("not implemented yet")
99
+ return combined_diffs
100
+
101
+
102
+ # OS utils
103
+ def get_downloads_dir() -> pathlib.Path:
104
+ """Find the platform-dependent 'Downloads' directory.
105
+
106
+ Returns
107
+ -------
108
+ pathlib.Path
109
+ Absolute path to 'Downloads' directory.
110
+
111
+ """
112
+ # Linux and Mac Download folders
113
+ if pathlib.Path.home().joinpath("Downloads").is_dir():
114
+ return pathlib.Path.home().joinpath("Downloads")
115
+
116
+ # Try to create Downloads
117
+ pathlib.Path.home().joinpath("Downloads").mkdir(parents=True)
118
+ return pathlib.Path.home().joinpath("Downloads")
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
ibridgesgui/info.py ADDED
@@ -0,0 +1,53 @@
1
+ """Provide the GUI with iRODS information."""
2
+
3
+ import sys
4
+
5
+ import PyQt6
6
+ import PyQt6.QtWidgets
7
+ import PyQt6.uic
8
+ from ibridges.resources import Resources
9
+
10
+ from ibridgesgui.config import CONFIG_DIR
11
+ from ibridgesgui.gui_utils import UI_FILE_DIR, populate_table, populate_textfield
12
+ from ibridgesgui.ui_files.tabInfo import Ui_tabInfo
13
+
14
+
15
+ class Info(PyQt6.QtWidgets.QWidget, Ui_tabInfo):
16
+ """Set iRODS information in the GUI."""
17
+
18
+ def __init__(self, session):
19
+ """Initialise the tab."""
20
+ super().__init__()
21
+ if getattr(sys, "frozen", False):
22
+ super().setupUi(self)
23
+ else:
24
+ PyQt6.uic.loadUi(UI_FILE_DIR / "tabInfo.ui", self)
25
+ self.session = session
26
+
27
+ self.refresh_button.clicked.connect(self.refresh_info)
28
+ self.refresh_info()
29
+
30
+ def refresh_info(self):
31
+ """Find and set the information of the connected iRODS system."""
32
+ self.resc_table.setRowCount(0)
33
+ self.setCursor(PyQt6.QtGui.QCursor(PyQt6.QtCore.Qt.CursorShape.WaitCursor))
34
+ # irods Zone
35
+ self.zone_label.setText(self.session.zone)
36
+ # irods user
37
+ self.user_label.setText(self.session.username)
38
+ # irods user type and groups
39
+ user_type, user_groups = self.session.get_user_info()
40
+ self.type_label.setText(user_type)
41
+ populate_textfield(self.groups_browser, user_groups)
42
+ # ibridges log location
43
+ self.log_label.setText(str(CONFIG_DIR))
44
+ # default resource
45
+ self.resc_label.setText(self.session.default_resc)
46
+ # irods server and version
47
+ self.server_label.setText(self.session.host)
48
+ self.version_label.setText(".".join((str(num) for num in self.session.server_version)))
49
+ # irods resources
50
+ resc_info = Resources(self.session).root_resources
51
+ populate_table(self.resc_table, len(resc_info[0]), resc_info)
52
+ self.resc_table.resizeColumnsToContents()
53
+ self.setCursor(PyQt6.QtGui.QCursor(PyQt6.QtCore.Qt.CursorShape.ArrowCursor))
@@ -0,0 +1,158 @@
1
+ """Tree model for IRODS collections.
2
+
3
+ The IRODS database is huge and retrieving a complete tree of all files
4
+ can take ages. To improve the loading time the tree is only grown as
5
+ far as it displays.
6
+ """
7
+
8
+ import irods
9
+ import irods.exception
10
+ import PyQt6
11
+ import PyQt6.QtCore
12
+ import PyQt6.QtGui
13
+ import PyQt6.QtWidgets
14
+ from ibridges import IrodsPath
15
+
16
+
17
+ class IrodsTreeModel(PyQt6.QtGui.QStandardItemModel):
18
+ """Model for an iRODS tree view."""
19
+
20
+ def __init__(self, tree_view, irods_root_path: IrodsPath):
21
+ """Initialise the tree view with the root node and first level.
22
+
23
+ Class variables 'user_groups' and 'base_path' _must_ be
24
+ populated with a list of group names and the path name of the
25
+ iRODS top-level collection (i.e., /<zone name>/home),
26
+ respectively.
27
+
28
+ Parameters
29
+ ----------
30
+ tree_view : PyQt6.QtWidgets
31
+ Defined iRODS tree view UI element.
32
+ session : ibridges.Session
33
+ A valid session to iRODS
34
+ irods_root_path : IrodsPath
35
+ The root collection for the tree
36
+
37
+ """
38
+ super().__init__()
39
+ self.tree_view = tree_view
40
+ self.session = irods_root_path.session
41
+ self.irods_root_path = irods_root_path
42
+ # Empty tree
43
+ self.clear()
44
+
45
+ def _tree_row_from_irods_item(self, item, parent_id, level, display_path=False):
46
+ icon_provider = PyQt6.QtWidgets.QFileIconProvider()
47
+ if display_path:
48
+ display = PyQt6.QtGui.QStandardItem(item.path)
49
+ else:
50
+ display = PyQt6.QtGui.QStandardItem(item.name)
51
+ if isinstance(item, irods.collection.iRODSCollection):
52
+ display.setIcon(icon_provider.icon(PyQt6.QtWidgets.QFileIconProvider.IconType.Folder))
53
+ datatype = "C"
54
+ else:
55
+ display.setIcon(icon_provider.icon(PyQt6.QtWidgets.QFileIconProvider.IconType.File))
56
+ datatype = "d"
57
+ row = [
58
+ display, # display name
59
+ PyQt6.QtGui.QStandardItem(str(level + 1)), # item level in the tree
60
+ PyQt6.QtGui.QStandardItem(str(item.id)), # id in iRODS
61
+ PyQt6.QtGui.QStandardItem(str(parent_id)), # parent id
62
+ PyQt6.QtGui.QStandardItem(datatype), # C or d
63
+ PyQt6.QtGui.QStandardItem(item.path), # absolute irods path
64
+ ]
65
+ return row
66
+
67
+ def init_tree(self):
68
+ """Draw the first levels of an iRODS filesystem as a tree."""
69
+ self.setRowCount(0)
70
+ root = self.invisibleRootItem()
71
+
72
+ # Start the tree, add the highest level to the invisible root
73
+ root_coll = IrodsPath(self.session, self.irods_root_path).collection
74
+ root_row = self._tree_row_from_irods_item(root_coll, -1, -1, True)
75
+ root.appendRow(root_row)
76
+
77
+ new_node = root.child(root.rowCount() - 1)
78
+
79
+ # insert a dummy child to get the link to open the collection
80
+ new_node.appendRow(None)
81
+
82
+ def delete_subtree(self, tree_item):
83
+ """Delete subtree.
84
+
85
+ Parameters
86
+ ----------
87
+ tree_item : QStandardItem
88
+ Item in the QTreeView
89
+
90
+ """
91
+ # Remove all children from tree_item
92
+ tree_item.removeRows(0, tree_item.rowCount())
93
+
94
+ def add_subtree(self, tree_item, tree_item_data: list):
95
+ """Grow tree_view from tree_item.
96
+
97
+ Parameters
98
+ ----------
99
+ tree_item : PyQt6.QtGui.QStandardItem
100
+ The root of the subtree in the tree view
101
+ tree_item_data : list
102
+ [display_name, level, id, parent_id, 'C/d', absolute iRODS Path]
103
+
104
+ """
105
+ _, level, _, _, _, abs_irods_path = tree_item_data
106
+ parent_coll = IrodsPath(self.session, abs_irods_path).collection
107
+
108
+ # we assume that tree_item has no children yet.
109
+ new_nodes = {}
110
+ for item in parent_coll.subcollections + parent_coll.data_objects:
111
+ row = self._tree_row_from_irods_item(item, parent_coll.id, int(level))
112
+ tree_item.appendRow(row)
113
+ new_nodes[item.id] = tree_item.child(tree_item.rowCount() - 1)
114
+ if isinstance(item, irods.collection.iRODSCollection):
115
+ # insert a dummy child to get the link to open the collection
116
+ new_nodes[item.id].appendRow(None)
117
+
118
+ def refresh_subtree(self, position):
119
+ """Refresh the tree view.
120
+
121
+ Parameters
122
+ ----------
123
+ position : PyQt6.QtCore.QModelIndex
124
+ Location in tree
125
+
126
+ """
127
+ model_index = position
128
+ tree_item = self.itemFromIndex(model_index) # clicked item
129
+ parent = tree_item.parent()
130
+ if parent is None:
131
+ parent = self.invisibleRootItem()
132
+ row = tree_item.row()
133
+
134
+ # retrieve information of clicked item, the information is stored in its parent
135
+ tree_item_data = [parent.child(row, col).data(0) for col in range(parent.columnCount())]
136
+ irods_path = IrodsPath(self.session, tree_item_data[-1])
137
+ if irods_path.collection_exists():
138
+ # Delete subtree in irodsFsdata and the tree_view.
139
+ self.delete_subtree(tree_item)
140
+ self.add_subtree(tree_item, tree_item_data)
141
+
142
+ def irods_path_from_tree_index(self, model_index):
143
+ """Convert a tree index to iRODS path.
144
+
145
+ Parameters
146
+ ----------
147
+ model_index : PyQt6.QtCore.QModelIndex
148
+ Selected row in tree view
149
+
150
+ """
151
+ tree_item = self.itemFromIndex(model_index) # clicked item
152
+ row = tree_item.row()
153
+
154
+ parent = tree_item.parent() # contains the data of tree_item
155
+ if parent is None:
156
+ parent = self.invisibleRootItem()
157
+ irods_path = [parent.child(row, col).data(0) for col in range(parent.columnCount())][-1]
158
+ return IrodsPath(self.session, irods_path)