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.
- ibridgesgui/__init__.py +1 -0
- ibridgesgui/__main__.py +164 -0
- ibridgesgui/_version.py +16 -0
- ibridgesgui/browser.py +625 -0
- ibridgesgui/config.py +250 -0
- ibridgesgui/gui_utils.py +118 -0
- ibridgesgui/icons/arrow-left.png +0 -0
- ibridgesgui/icons/arrow-right.png +0 -0
- ibridgesgui/icons/arrow-up.png +0 -0
- ibridgesgui/icons/home.png +0 -0
- ibridgesgui/icons/iBridges.ico +0 -0
- ibridgesgui/icons/loading_circle.gif +0 -0
- ibridgesgui/icons/nosync.png +0 -0
- ibridgesgui/icons/refresh.png +0 -0
- ibridgesgui/icons/syncing.png +0 -0
- ibridgesgui/info.py +53 -0
- ibridgesgui/irods_tree_model.py +158 -0
- ibridgesgui/login.py +101 -0
- ibridgesgui/popup_widgets.py +237 -0
- ibridgesgui/search.py +285 -0
- ibridgesgui/sync.py +289 -0
- ibridgesgui/threads.py +251 -0
- ibridgesgui/ui_files/.py +118 -0
- ibridgesgui/ui_files/ExampleTab.py +87 -0
- ibridgesgui/ui_files/ExampleTab.ui +93 -0
- ibridgesgui/ui_files/MainMenu.py +121 -0
- ibridgesgui/ui_files/MainMenu.ui +171 -0
- ibridgesgui/ui_files/__init__.py +1 -0
- ibridgesgui/ui_files/configCheck.py +96 -0
- ibridgesgui/ui_files/configCheck.ui +142 -0
- ibridgesgui/ui_files/createCollection.py +82 -0
- ibridgesgui/ui_files/createCollection.ui +153 -0
- ibridgesgui/ui_files/dataTransferState.py +114 -0
- ibridgesgui/ui_files/dataTransferState.ui +187 -0
- ibridgesgui/ui_files/irodsLogin.py +134 -0
- ibridgesgui/ui_files/irodsLogin.ui +237 -0
- ibridgesgui/ui_files/renameItem.py +77 -0
- ibridgesgui/ui_files/renameItem.ui +151 -0
- ibridgesgui/ui_files/tabBrowser.py +525 -0
- ibridgesgui/ui_files/tabBrowser.ui +829 -0
- ibridgesgui/ui_files/tabInfo.py +207 -0
- ibridgesgui/ui_files/tabInfo.ui +356 -0
- ibridgesgui/ui_files/tabSearch.py +233 -0
- ibridgesgui/ui_files/tabSearch.ui +400 -0
- ibridgesgui/ui_files/tabSync.py +223 -0
- ibridgesgui/ui_files/tabSync.ui +481 -0
- ibridgesgui/ui_files/tabUpDownload.py +303 -0
- ibridgesgui/ui_files/tabUpDownload.ui +460 -0
- ibridgesgui-0.2.0.dist-info/LICENSE +674 -0
- ibridgesgui-0.2.0.dist-info/METADATA +787 -0
- ibridgesgui-0.2.0.dist-info/RECORD +54 -0
- ibridgesgui-0.2.0.dist-info/WHEEL +5 -0
- ibridgesgui-0.2.0.dist-info/entry_points.txt +2 -0
- 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))
|