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/login.py ADDED
@@ -0,0 +1,101 @@
1
+ """Pop up Widget for Login."""
2
+
3
+ import logging
4
+ import sys
5
+ from pathlib import Path
6
+
7
+ from ibridges import Session
8
+ from ibridges.session import LoginError, PasswordError
9
+ from PyQt6.QtWidgets import QDialog, QLineEdit
10
+ from PyQt6.uic import loadUi
11
+
12
+ from ibridgesgui.config import get_last_ienv_path, set_last_ienv_path
13
+ from ibridgesgui.gui_utils import UI_FILE_DIR
14
+ from ibridgesgui.ui_files.irodsLogin import Ui_irodsLogin
15
+
16
+
17
+ class Login(QDialog, Ui_irodsLogin):
18
+ """Definition and initialization of the iRODS login window."""
19
+
20
+ def __init__(self, session_dict, app_name):
21
+ """Initialise tab."""
22
+ super().__init__()
23
+ if getattr(sys, "frozen", False):
24
+ super().setupUi(self)
25
+ else:
26
+ loadUi(UI_FILE_DIR / "irodsLogin.ui", self)
27
+
28
+ self.logger = logging.getLogger(app_name)
29
+ self.irods_config_dir = Path("~", ".irods").expanduser()
30
+
31
+ self.session_dict = session_dict
32
+ self._init_envbox()
33
+ self.cached_pw = self._init_password()
34
+ self._load_gui()
35
+ self.setWindowTitle("Connect to iRODS server")
36
+
37
+ def _load_gui(self):
38
+ self.connect_button.clicked.connect(self.login_function)
39
+ self.cancel_button.clicked.connect(self.close)
40
+ self.password_field.setEchoMode(QLineEdit.EchoMode.Password)
41
+
42
+ def _init_envbox(self):
43
+ env_jsons = [path.name for path in self.irods_config_dir.glob("irods_environment*json")]
44
+ if len(env_jsons) == 0:
45
+ self.envError.setText(
46
+ f"ERROR: no irods_environment*json files found in {self.irods_config_dir}"
47
+ )
48
+
49
+ self.envbox.clear()
50
+ self.envbox.addItems(env_jsons)
51
+ last_env = get_last_ienv_path()
52
+ if last_env is not None and last_env in env_jsons:
53
+ self.envbox.setCurrentIndex(env_jsons.index(last_env))
54
+ else:
55
+ self.envbox.setCurrentIndex(0)
56
+
57
+ def _init_password(self):
58
+ # Check if there is a cached password
59
+ passwd_file = self.irods_config_dir.joinpath(".irodsA")
60
+ if passwd_file.is_file():
61
+ self.password_field.setText("***********")
62
+ return True
63
+ return False
64
+
65
+ def close(self):
66
+ """Abort login."""
67
+ self.done(0)
68
+
69
+ def login_function(self):
70
+ """Connect to iRODS server with gathered info."""
71
+ self.error_label.clear()
72
+ env_file = self.irods_config_dir.joinpath(self.envbox.currentText())
73
+ try:
74
+ if self.cached_pw is True and self.password_field.text() == "***********":
75
+ self.logger.debug("Login with %s and cached password.", env_file)
76
+ session = Session(irods_env=env_file)
77
+ else:
78
+ session = Session(irods_env=env_file, password=self.password_field.text())
79
+ self.logger.debug("Login with %s and password from prompt.", env_file)
80
+ self.session_dict["session"] = session
81
+ self.logger.info(
82
+ "Logged in as %s to %s; working coll %s",
83
+ session.username,
84
+ session.host,
85
+ session.home,
86
+ )
87
+ session.write_pam_password()
88
+ set_last_ienv_path(env_file.name)
89
+ self.close()
90
+ except LoginError:
91
+ self.error_label.setText("irods_environment.json not setup correctly.")
92
+ except PasswordError:
93
+ self.error_label.setText("Wrong password!")
94
+ except ConnectionError:
95
+ self.error_label.setText(
96
+ "Cannot connect to server. Check Internet, host name and port."
97
+ )
98
+ except Exception as err:
99
+ log_path = Path("~/.ibridges")
100
+ self.logger.exception("Failed to login: %s", repr(err))
101
+ self.error_label.setText(f"Login failed, consult the log file(s) in {log_path}")
@@ -0,0 +1,237 @@
1
+ """Pop-up widget definitions."""
2
+
3
+ import json
4
+ import os
5
+ import sys
6
+
7
+ import irods
8
+ from ibridges import IrodsPath
9
+ from PyQt6 import QtCore
10
+ from PyQt6.QtWidgets import QDialog, QFileDialog
11
+ from PyQt6.uic import loadUi
12
+
13
+ from ibridgesgui.config import _read_json, check_irods_config, save_irods_config
14
+ from ibridgesgui.gui_utils import UI_FILE_DIR, populate_textfield
15
+ from ibridgesgui.ui_files.configCheck import Ui_configCheck
16
+ from ibridgesgui.ui_files.createCollection import Ui_createCollection
17
+ from ibridgesgui.ui_files.renameItem import Ui_renameItem
18
+
19
+
20
+ class CreateCollection(QDialog, Ui_createCollection):
21
+ """Popup window to create a new collection."""
22
+
23
+ def __init__(self, parent, logger):
24
+ """Initialise window."""
25
+ super().__init__()
26
+ if getattr(sys, "frozen", False):
27
+ super().setupUi(self)
28
+ else:
29
+ loadUi(UI_FILE_DIR / "createCollection.ui", self)
30
+
31
+ self.logger = logger
32
+ self.setWindowTitle("Create iRODS collection")
33
+ self.setWindowFlags(QtCore.Qt.WindowType.WindowStaysOnTopHint)
34
+ self.parent = parent
35
+ self.label.setText(str(self.parent) + "/")
36
+ self.buttonBox.accepted.connect(self.accept)
37
+
38
+ def accept(self):
39
+ """Create new collection."""
40
+ if self.coll_path_input.text() != "":
41
+ new_coll_path = IrodsPath(self.parent.session, self.parent, self.coll_path_input.text())
42
+ if new_coll_path.exists():
43
+ self.error_label.setText(f"{new_coll_path} already exists.")
44
+ else:
45
+ try:
46
+ IrodsPath.create_collection(new_coll_path.session, new_coll_path)
47
+ self.logger.info(f"Created collection {new_coll_path}")
48
+ self.done(0)
49
+ except irods.exception.CAT_NO_ACCESS_PERMISSION:
50
+ self.error_label.setText(
51
+ f"No access rights to {new_coll_path.parent}."
52
+ + f" Cannot create {self.coll_path_input.text()}."
53
+ )
54
+ except Exception as err:
55
+ self.logger.exception(f"Could not create {new_coll_path}: {err}")
56
+ self.error_label.setText(f"Could not create {new_coll_path}, consult the logs.")
57
+
58
+
59
+ class CreateDirectory(QDialog, Ui_createCollection):
60
+ """Popup window to create a new directory."""
61
+
62
+ def __init__(self, parent):
63
+ """Initialise window."""
64
+ super().__init__()
65
+ if getattr(sys, "frozen", False):
66
+ super().setupUi(self)
67
+ else:
68
+ loadUi(UI_FILE_DIR / "createCollection.ui", self)
69
+ self.setWindowTitle("Create Directory")
70
+ self.setWindowFlags(QtCore.Qt.WindowType.WindowStaysOnTopHint)
71
+ self.parent = parent
72
+ self.label.setText(self.parent + os.sep)
73
+ self.buttonBox.accepted.connect(self.accept)
74
+
75
+ def accept(self):
76
+ """Create folder."""
77
+ if self.coll_path_input.text() != "":
78
+ new_dir_path = self.parent + os.sep + self.coll_path_input.text()
79
+ try:
80
+ os.makedirs(new_dir_path)
81
+ self.done(1)
82
+ except Exception as error:
83
+ if hasattr(error, "message"):
84
+ self.error_label.setText(error.message)
85
+ else:
86
+ self.error_label.setText("ERROR: insufficient rights.")
87
+
88
+
89
+ class Rename(QDialog, Ui_renameItem):
90
+ """Popup window to rename and move a collection or data object."""
91
+
92
+ def __init__(self, irods_path: IrodsPath, logger):
93
+ """Initialise window."""
94
+ super().__init__()
95
+ if getattr(sys, "frozen", False):
96
+ super().setupUi(self)
97
+ else:
98
+ loadUi(UI_FILE_DIR / "renameItem.ui", self)
99
+
100
+ self.logger = logger
101
+ self.setWindowTitle("Create iRODS collection")
102
+ self.setWindowFlags(QtCore.Qt.WindowType.WindowStaysOnTopHint)
103
+ self.irods_path = irods_path
104
+ self.item_path_label.setText(str(irods_path))
105
+ self.item_path_input.setText(str(irods_path))
106
+ self.buttonBox.accepted.connect(self.accept)
107
+
108
+ def accept(self):
109
+ """Create new collection."""
110
+ if self.item_path_input.text() != "":
111
+ new_path = IrodsPath(self.irods_path.session, self.item_path_input.text())
112
+ if new_path.exists():
113
+ self.error_label.setText(f"{new_path} already exists.")
114
+ else:
115
+ try:
116
+ new_irods_path = self.irods_path.rename(new_path)
117
+ self.logger.info(f"Rename/Move {self.irods_path} --> {new_irods_path}")
118
+ self.done(0)
119
+ except irods.exception.CAT_NO_ACCESS_PERMISSION:
120
+ self.error_label.setText(f"No access rights to {new_path}.")
121
+ except Exception as err:
122
+ self.logger.exception(f"Could not create {new_path}: {err}")
123
+ self.error_label.setText(f"Could not create {new_path}, consult the logs.")
124
+
125
+
126
+ class CheckConfig(QDialog, Ui_configCheck):
127
+ """Popup window to edit, create and check an environment.json."""
128
+
129
+ def __init__(self, logger, env_path):
130
+ """Initialise window."""
131
+ super().__init__()
132
+ if getattr(sys, "frozen", False):
133
+ super().setupUi(self)
134
+ else:
135
+ loadUi(UI_FILE_DIR / "configCheck.ui", self)
136
+
137
+ self.logger = logger
138
+ self.env_path = env_path
139
+ self.setWindowTitle("Create, edit and inspect iRODS environment")
140
+ self._init_env_box()
141
+
142
+ self.envbox.activated.connect(self.load_env)
143
+ self.new_button.clicked.connect(self.create_env)
144
+ self.check_button.clicked.connect(self.check_env)
145
+ self.save_button.clicked.connect(self.save_env)
146
+ self.save_as_button.clicked.connect(self.save_env_as)
147
+ self.close_button.clicked.connect(self.close)
148
+
149
+ def _init_env_box(self):
150
+ self.envbox.clear()
151
+ env_jsons = [""] + [path.name for path in self.env_path.glob("irods_environment*json")]
152
+ if len(env_jsons) != 0:
153
+ self.envbox.addItems(env_jsons)
154
+ self.envbox.setCurrentIndex(0)
155
+
156
+ def load_env(self):
157
+ """Load json into text field."""
158
+ self.error_label.clear()
159
+ env_file = self.env_path.joinpath(self.envbox.currentText())
160
+ try:
161
+ content = json.dumps(
162
+ _read_json(env_file), sort_keys=True, indent=4, separators=(",", ": ")
163
+ )
164
+ populate_textfield(self.env_field, content)
165
+ except IsADirectoryError:
166
+ self.error_label.setText("Choose and environment or create a new one.")
167
+ except FileNotFoundError:
168
+ self.error_label.setText(f"File does not exist {env_file}")
169
+ except Exception as err:
170
+ self.error_label.setText(f"{repr(err)}")
171
+
172
+ def create_env(self):
173
+ """Load standard environment into text field."""
174
+ self.error_label.clear()
175
+ self.envbox.setCurrentIndex(0)
176
+ env = {
177
+ "irods_host": "<THE SERVER NAME OR IP ADDRESS>",
178
+ "irods_port": 1247,
179
+ "irods_home": "<A DEFAULT LOCATION ON THE IRODS SERVER AS YOUR HOME>",
180
+ "irods_default_resource": "<A DEFAULT IRODS RESOURCE NAME>",
181
+ "irods_user_name": "<YOUR IRODS USERNAME>",
182
+ "irods_zone_name": "<THE IRODS ZONE NAME>",
183
+ "irods_authentication_scheme": "pam",
184
+ "irods_encryption_algorithm": "AES-256-CBC",
185
+ "irods_encryption_key_size": 32,
186
+ "irods_encryption_num_hash_rounds": 16,
187
+ "irods_encryption_salt_size": 8,
188
+ "irods_client_server_policy": "CS_NEG_REQUIRE",
189
+ "irods_client_server_negotiation": "request_server_negotiation",
190
+ }
191
+ populate_textfield(
192
+ self.env_field, json.dumps(env, sort_keys=True, indent=4, separators=(",", ": "))
193
+ )
194
+
195
+ def check_env(self):
196
+ """Check formatting, parameters and connectivity of information in text field."""
197
+ self.error_label.clear()
198
+ try:
199
+ msg = check_irods_config(json.loads(self.env_field.toPlainText()))
200
+ except json.decoder.JSONDecodeError as err:
201
+ msg = f"JSON decoding error: {err.msg} at position {err.pos}."
202
+ self.error_label.setText(msg)
203
+
204
+ def save_env(self):
205
+ """Overwrite file from combobox with information from text field."""
206
+ self.error_label.clear()
207
+ env_file = self.env_path.joinpath(self.envbox.currentText())
208
+ if env_file.is_file():
209
+ try:
210
+ save_irods_config(env_file, json.loads(self.env_field.toPlainText()))
211
+ self.error_label.setText(f"Configuration saved as {env_file}")
212
+ except json.decoder.JSONDecodeError:
213
+ self.error_label.setText(
214
+ "Incorrectly formatted. Click 'Check' for more information."
215
+ )
216
+ else:
217
+ self.error_label.setText("Choose 'Save as' to save")
218
+
219
+ def save_env_as(self):
220
+ """Choose file to save text field as json."""
221
+ self.error_label.clear()
222
+ dialog = QFileDialog(self)
223
+ dialog.setFileMode(QFileDialog.FileMode.AnyFile)
224
+ dialog.setNameFilter("(*.json)")
225
+ create_file = QFileDialog.getSaveFileName(
226
+ self, "Save as File", str(self.env_path), "(*.json)"
227
+ )
228
+ if create_file[0] != "":
229
+ try:
230
+ save_irods_config(create_file[0], json.loads(self.env_field.toPlainText()))
231
+ self.error_label.setText(f"Configuration saved as {create_file[0]}")
232
+ except json.decoder.JSONDecodeError:
233
+ self.error_label.setText(
234
+ "Incorrectly formatted. Click 'Check' for more information."
235
+ )
236
+ except TypeError:
237
+ self.error_label.setText("File type needs to be .json")
ibridgesgui/search.py ADDED
@@ -0,0 +1,285 @@
1
+ """Search tab."""
2
+
3
+ import logging
4
+ import sys
5
+ from pathlib import Path
6
+
7
+ import PyQt6.uic
8
+ from ibridges import IrodsPath
9
+ from PyQt6 import QtCore, QtGui, QtWidgets
10
+ from PyQt6.QtWidgets import QMessageBox
11
+
12
+ from ibridgesgui.config import get_last_ienv_path, is_session_from_config
13
+ from ibridgesgui.gui_utils import UI_FILE_DIR, combine_diffs, populate_table
14
+ from ibridgesgui.threads import SearchThread, TransferDataThread
15
+ from ibridgesgui.ui_files.tabSearch import Ui_tabSearch
16
+
17
+
18
+ class Search(PyQt6.QtWidgets.QWidget, Ui_tabSearch):
19
+ """Search view."""
20
+
21
+ def __init__(self, session, app_name, browser):
22
+ """Initialize an iRODS search view.
23
+
24
+ This tab is linked to the browser tab. When double-clicking one of
25
+ the results the path will be loaded into the browser of the browser tab.
26
+
27
+ Parameters
28
+ ----------
29
+ session : ibridges.Session
30
+ The iRODS session object
31
+ app_name : str
32
+ The name of the app and corresponding logger
33
+ browser : PyQt6.QtWidgets.QWidget
34
+ The browser widget
35
+
36
+ """
37
+ super().__init__()
38
+ if getattr(sys, "frozen", False):
39
+ super().setupUi(self)
40
+ else:
41
+ PyQt6.uic.loadUi(UI_FILE_DIR / "tabSearch.ui", self)
42
+
43
+ self.logger = logging.getLogger(app_name)
44
+ self.session = session
45
+ self.browser = browser
46
+ self.search_thread = None
47
+ self.download_thread = None
48
+
49
+ self.hide_result_elements()
50
+ self.search_button.clicked.connect(self.search)
51
+ self.clear_button.clicked.connect(self.hide_result_elements)
52
+ self.download_button.clicked.connect(self.download)
53
+
54
+ # group textfields for gathering key, value, unit
55
+ self.keys = [self.key1, self.key2, self.key3, self.key4]
56
+ self.vals = [self.val1, self.val2, self.val3, self.val4]
57
+
58
+ self.search_table.doubleClicked.connect(self.send_to_browser)
59
+
60
+ def hide_result_elements(self):
61
+ """Hide the GUI elemnts that show and manipulate search results."""
62
+ self.error_label.clear()
63
+ self.search_table.hide()
64
+ self.download_button.hide()
65
+ self.clear_button.hide()
66
+ self.info_label.hide()
67
+ self.search_table.setRowCount(0)
68
+
69
+ def show_result_elements(self):
70
+ """Show the GUI elemnts that show and manipulate search results."""
71
+ self.search_table.show()
72
+ self.download_button.show()
73
+ self.clear_button.show()
74
+ self.info_label.show()
75
+
76
+ def search(self):
77
+ """Validate search parameters and start search."""
78
+ self.hide_result_elements()
79
+ self.setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.WaitCursor))
80
+ self.error_label.clear()
81
+
82
+ msg, key_vals, path, checksum = self._validate_search_params()
83
+ if msg is not None:
84
+ self.error_label.setText(msg)
85
+ self.setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.ArrowCursor))
86
+ return
87
+
88
+ if key_vals is None and path is None and checksum is None:
89
+ self.error_label.setText("No search critera given.")
90
+ self.setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.ArrowCursor))
91
+ return
92
+
93
+ self._start_search(key_vals, path, checksum)
94
+
95
+ def load_results(self, results):
96
+ """Load seach results into the table view."""
97
+ self.error_label.clear()
98
+ table_data = [] # (Path, Name, Size, Checksum, created, modified)
99
+ for result in results:
100
+ if "DATA_NAME" in result:
101
+ obj = IrodsPath(self.session, result["COLL_NAME"], result["DATA_NAME"]).dataobject
102
+
103
+ table_data.append(
104
+ (
105
+ "-d",
106
+ obj.path,
107
+ obj.size,
108
+ obj.create_time.strftime("%d-%m-%Y"),
109
+ obj.modify_time.strftime("%d-%m-%Y"),
110
+ )
111
+ )
112
+ else:
113
+ coll = IrodsPath(self.session, result["COLL_NAME"]).collection
114
+ table_data.append(
115
+ (
116
+ "-C",
117
+ coll.path,
118
+ "",
119
+ coll.create_time.strftime("%d-%m-%Y"),
120
+ coll.modify_time.strftime("%d-%m-%Y"),
121
+ )
122
+ )
123
+ populate_table(self.search_table, len(table_data), table_data)
124
+
125
+ def download(self):
126
+ """Determine iRODS paths, select destination and start download."""
127
+ self.setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.WaitCursor))
128
+ self.error_label.clear()
129
+ irods_paths = self._retrieve_selected_paths()
130
+ if len(irods_paths) == 0:
131
+ self.error_label.setText("No data selected.")
132
+ self.setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.ArrowCursor))
133
+ return
134
+ select_dir = Path(
135
+ QtWidgets.QFileDialog.getExistingDirectory(
136
+ self, "Select Directory", directory=str(Path("~").expanduser())
137
+ )
138
+ )
139
+ if str(select_dir) == "" or str(select_dir) == ".":
140
+ self.setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.ArrowCursor))
141
+ return
142
+ info = f"Download to: {select_dir}\n"
143
+
144
+ data_exists = [(ipath, select_dir.joinpath(ipath.name).exists()) for ipath in irods_paths]
145
+ overwrite = False
146
+
147
+ if any(exists for _, exists in data_exists):
148
+ button_reply = QMessageBox.question(
149
+ self,
150
+ "Message Box",
151
+ info + f"Some data already exists in {select_dir}." + "\nOverwrite data?",
152
+ )
153
+ else:
154
+ button_reply = QMessageBox.question(self, "Message Box", info)
155
+
156
+ if button_reply == QMessageBox.StandardButton.Yes:
157
+ overwrite = True
158
+ self._start_download(irods_paths, select_dir, overwrite)
159
+ else:
160
+ self.setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.ArrowCursor))
161
+ return
162
+
163
+ def send_to_browser(self):
164
+ """Set browser path_input to collection or parent of object."""
165
+ row = self.search_table.currentIndex().row()
166
+ irods_path = IrodsPath(self.session, self.search_table.item(row, 1).text())
167
+ if irods_path.collection_exists():
168
+ self.browser.path_input.setText(str(irods_path))
169
+ self.error_label.setText(f"Browser tab switched to {irods_path}")
170
+ else:
171
+ self.browser.path_input.setText(str(irods_path.parent))
172
+ self.error_label.setText(f"Browser tab switched to {irods_path.parent}")
173
+ self.browser.load_browser_table()
174
+
175
+ def _validate_search_params(self) -> tuple[str, dict, str, str]:
176
+ # All metadata values need a specific key
177
+ if any(key.text() == "" and val.text() != "" for key, val in zip(self.keys, self.vals)):
178
+ return "There are metadata values without keys. Stop search.", None, None, None
179
+ if all(key.text() == "" for key in self.keys):
180
+ key_vals = None
181
+ else:
182
+ # Replace empty values with the wild card, turn into search key_vals
183
+ key_vals = {
184
+ key.text(): "%" if val.text() == "" else val.text()
185
+ for key, val in zip(self.keys, self.vals)
186
+ }
187
+ del key_vals[""]
188
+
189
+ path = self.path_field.text() if self.path_field.text() != "" else None
190
+ checksum = self.checksum_field.text() if self.checksum_field.text() != "" else None
191
+ return None, key_vals, path, checksum
192
+
193
+ def _retrieve_selected_paths(self) -> list[IrodsPath]:
194
+ """Retrieve paths from all selected rows in search results table."""
195
+ irods_paths = []
196
+ rows = set(idx.row() for idx in self.search_table.selectedIndexes())
197
+ for row in rows:
198
+ irods_paths.append(IrodsPath(self.session, self.search_table.item(row, 1).text()))
199
+ return irods_paths
200
+
201
+ def _start_download(self, irods_paths, folder, overwrite):
202
+ self.download_button.setEnabled(False)
203
+ self.clear_button.setEnabled(False)
204
+ self.search_button.setEnabled(False)
205
+ # check if session comes from env file in ibridges config
206
+ if is_session_from_config(self.session):
207
+ env_path = Path("~").expanduser().joinpath(".irods", get_last_ienv_path())
208
+ else:
209
+ text = "No download possible: The ibridges config changed during the session."
210
+ text += " Please reset or restart the session."
211
+ self.error_label.setText(text)
212
+ return
213
+
214
+ # get diff dictionary
215
+ diffs = combine_diffs(self.session, irods_paths, folder)
216
+
217
+ self.error_label.setText(f"Downloading to {folder} ....")
218
+ try:
219
+ self.download_thread = TransferDataThread(
220
+ env_path, self.logger, diffs, overwrite=overwrite
221
+ )
222
+ except Exception:
223
+ self.error_label.setText(
224
+ "Could not instantiate a new session from{env_path}.Check configuration"
225
+ )
226
+ self.setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.ArrowCursor))
227
+ return
228
+ self.download_thread.succeeded.connect(self._download_end)
229
+ self.download_thread.finished.connect(self._finish_download)
230
+ self.download_thread.current_progress.connect(self._download_status)
231
+ self.download_thread.start()
232
+
233
+ def _finish_download(self):
234
+ self.download_button.setEnabled(True)
235
+ self.clear_button.setEnabled(True)
236
+ self.search_button.setEnabled(True)
237
+ self.setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.ArrowCursor))
238
+ del self.download_thread
239
+
240
+ def _download_status(self, state):
241
+ self.error_label.setText(state)
242
+
243
+ def _download_end(self, thread_output: dict):
244
+ if thread_output["error"] == "":
245
+ self.error_label.setText("Download finished.")
246
+ else:
247
+ self.error_label.setText("Errors occurred during download. Consult the logs.")
248
+ self.setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.ArrowCursor))
249
+
250
+ def _start_search(self, key_vals, path, checksum):
251
+ self.search_button.setEnabled(False)
252
+ # check if session comes from env file in ibridges config
253
+ if is_session_from_config(self.session):
254
+ env_path = Path("~").expanduser().joinpath(".irods", get_last_ienv_path())
255
+ else:
256
+ text = "No search possible: The ibridges config changed during the session."
257
+ text += " Please reset or restart the session."
258
+ self.error_label.setText(text)
259
+ return
260
+ self.error_label.setText("Searching ...")
261
+ try:
262
+ self.search_thread = SearchThread(self.logger, env_path, path, checksum, key_vals)
263
+ except Exception:
264
+ self.error_label.setText(
265
+ "Could not instantiate a new session from{env_path}.Check configuration"
266
+ )
267
+ return
268
+ self.search_thread.succeeded.connect(self._fetch_results)
269
+ self.search_thread.finished.connect(self._finish_search)
270
+ self.search_thread.start()
271
+
272
+ def _finish_search(self):
273
+ self.search_button.setEnabled(True)
274
+ self.setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.ArrowCursor))
275
+ del self.search_thread
276
+
277
+ def _fetch_results(self, therad_output: dict):
278
+ if "error" in therad_output:
279
+ self.error_label.setText(therad_output["error"])
280
+ elif len(therad_output["results"]) == 0:
281
+ self.error_label.setText("No objects or collections found.")
282
+ else:
283
+ self.show_result_elements()
284
+ self.load_results(therad_output["results"])
285
+ self.setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.ArrowCursor))