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/browser.py
ADDED
|
@@ -0,0 +1,625 @@
|
|
|
1
|
+
"""Browser tab."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import irods.exception
|
|
8
|
+
import PyQt6.QtCore
|
|
9
|
+
import PyQt6.QtGui
|
|
10
|
+
import PyQt6.QtWidgets
|
|
11
|
+
import PyQt6.uic
|
|
12
|
+
from ibridges import IrodsPath, download, upload
|
|
13
|
+
from ibridges.meta import MetaData
|
|
14
|
+
from ibridges.permissions import Permissions
|
|
15
|
+
from ibridges.util import obj_replicas
|
|
16
|
+
|
|
17
|
+
from ibridgesgui.gui_utils import (
|
|
18
|
+
UI_FILE_DIR,
|
|
19
|
+
get_coll_dict,
|
|
20
|
+
get_downloads_dir,
|
|
21
|
+
get_irods_item,
|
|
22
|
+
populate_table,
|
|
23
|
+
populate_textfield,
|
|
24
|
+
)
|
|
25
|
+
from ibridgesgui.popup_widgets import CreateCollection, Rename
|
|
26
|
+
from ibridgesgui.ui_files.tabBrowser import Ui_tabBrowser
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Browser(PyQt6.QtWidgets.QWidget, Ui_tabBrowser):
|
|
30
|
+
"""Browser view for iRODS session."""
|
|
31
|
+
|
|
32
|
+
def __init__(self, session, app_name):
|
|
33
|
+
"""Initialize an iRODS browser view."""
|
|
34
|
+
super().__init__()
|
|
35
|
+
if getattr(sys, "frozen", False):
|
|
36
|
+
super().setupUi(self)
|
|
37
|
+
else:
|
|
38
|
+
PyQt6.uic.loadUi(UI_FILE_DIR / "tabBrowser.ui", self)
|
|
39
|
+
|
|
40
|
+
self.logger = logging.getLogger(app_name)
|
|
41
|
+
self.session = session
|
|
42
|
+
self.info_tabs.setCurrentIndex(0)
|
|
43
|
+
# iRODS default home
|
|
44
|
+
if self.session.home is not None:
|
|
45
|
+
root_path = IrodsPath(self.session).absolute()
|
|
46
|
+
else:
|
|
47
|
+
root_path = IrodsPath(
|
|
48
|
+
self.session, f"/{self.session.zone}/home/{self.session.username}"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
if root_path.collection_exists():
|
|
52
|
+
self.root_coll = IrodsPath(self.session, root_path).collection
|
|
53
|
+
elif IrodsPath(self.session, f"/{self.session.zone}/home").collection_exists():
|
|
54
|
+
self.root_coll = IrodsPath(self.session, f"/{self.session.zone}/home").collection
|
|
55
|
+
else:
|
|
56
|
+
self.error_label.setText(
|
|
57
|
+
'Cannot set root collection. Set "irods_home" in your environment.json'
|
|
58
|
+
)
|
|
59
|
+
self.reset_path()
|
|
60
|
+
self.browse()
|
|
61
|
+
|
|
62
|
+
def browse(self):
|
|
63
|
+
"""Initialize browser view GUI elements. Define the signals and slots."""
|
|
64
|
+
# Main navigation elements
|
|
65
|
+
self.path_input.returnPressed.connect(self.load_browser_table)
|
|
66
|
+
self.refresh_button.clicked.connect(self.load_browser_table)
|
|
67
|
+
self.refresh_button.setToolTip("Refresh")
|
|
68
|
+
self.home_button.clicked.connect(self.reset_path)
|
|
69
|
+
self.home_button.setToolTip("Home")
|
|
70
|
+
self.parent_button.clicked.connect(self.set_parent)
|
|
71
|
+
self.parent_button.setToolTip("Parent Coll")
|
|
72
|
+
|
|
73
|
+
# Main manipulation buttons Upload/Download create collection
|
|
74
|
+
self.upload_file_button.clicked.connect(self.file_upload)
|
|
75
|
+
self.upload_dir_button.clicked.connect(self.folder_upload)
|
|
76
|
+
self.download_button.clicked.connect(self.download)
|
|
77
|
+
self.create_coll_button.clicked.connect(self.create_collection)
|
|
78
|
+
self.rename_button.clicked.connect(self.rename_item)
|
|
79
|
+
|
|
80
|
+
# Browser table behaviour
|
|
81
|
+
self.browser_table.doubleClicked.connect(self.update_path)
|
|
82
|
+
self.browser_table.clicked.connect(self.fill_info)
|
|
83
|
+
|
|
84
|
+
# Bottom tab view buttons
|
|
85
|
+
# Metadata
|
|
86
|
+
self.meta_table.clicked.connect(self.edit_metadata)
|
|
87
|
+
self.add_meta_button.clicked.connect(self.add_icat_meta)
|
|
88
|
+
self.update_meta_button.clicked.connect(self.set_icat_meta)
|
|
89
|
+
self.delete_meta_button.clicked.connect(self.delete_icat_meta)
|
|
90
|
+
# ACLs
|
|
91
|
+
self.acl_table.clicked.connect(self.edit_acl)
|
|
92
|
+
self.add_acl_button.clicked.connect(self.update_icat_acl)
|
|
93
|
+
# Delete
|
|
94
|
+
self.confirm_button.clicked.connect(self.delete_data)
|
|
95
|
+
self.load_selection_button.clicked.connect(self.load_selection)
|
|
96
|
+
|
|
97
|
+
def reset_path(self):
|
|
98
|
+
"""Reset browser table to root path."""
|
|
99
|
+
self.path_input.setText(self.root_coll.path)
|
|
100
|
+
self.load_browser_table()
|
|
101
|
+
|
|
102
|
+
def set_parent(self):
|
|
103
|
+
"""Set browser path to parent of current collection and update browser table."""
|
|
104
|
+
current_path = IrodsPath(self.session, self.path_input.text())
|
|
105
|
+
self.path_input.setText(str(current_path.parent))
|
|
106
|
+
self.load_browser_table()
|
|
107
|
+
|
|
108
|
+
def update_path(self, index):
|
|
109
|
+
"""Take path from path_input and loads browser table."""
|
|
110
|
+
self.error_label.clear()
|
|
111
|
+
row = index.row()
|
|
112
|
+
irods_path = self._get_item_path(row)
|
|
113
|
+
if irods_path.collection_exists():
|
|
114
|
+
self.path_input.setText(str(irods_path))
|
|
115
|
+
self.load_browser_table()
|
|
116
|
+
|
|
117
|
+
def create_collection(self):
|
|
118
|
+
"""Create a new collection in current collection."""
|
|
119
|
+
self.error_label.clear()
|
|
120
|
+
parent = IrodsPath(self.session, "/" + self.path_input.text().strip("/"))
|
|
121
|
+
coll_widget = CreateCollection(parent, self.logger)
|
|
122
|
+
coll_widget.exec()
|
|
123
|
+
self.load_browser_table()
|
|
124
|
+
|
|
125
|
+
def rename_item(self):
|
|
126
|
+
"""Rename/move a collection or data object."""
|
|
127
|
+
self.error_label.clear()
|
|
128
|
+
if self.browser_table.currentRow() == -1:
|
|
129
|
+
self.error_label.setText("Please select a row from the table first!")
|
|
130
|
+
return
|
|
131
|
+
item_name = self.browser_table.item(self.browser_table.currentRow(), 1).text()
|
|
132
|
+
irods_path = IrodsPath(self.session, "/" + self.path_input.text().strip("/")).joinpath(
|
|
133
|
+
item_name
|
|
134
|
+
)
|
|
135
|
+
rename_widget = Rename(irods_path, self.logger)
|
|
136
|
+
rename_widget.exec()
|
|
137
|
+
self.load_browser_table()
|
|
138
|
+
|
|
139
|
+
def folder_upload(self):
|
|
140
|
+
"""Select a folder and upload."""
|
|
141
|
+
self.error_label.clear()
|
|
142
|
+
select_dir = PyQt6.QtWidgets.QFileDialog.getExistingDirectory(self, "Select Directory")
|
|
143
|
+
path = self._fs_select(select_dir)
|
|
144
|
+
if path is not None:
|
|
145
|
+
self._upload(path)
|
|
146
|
+
|
|
147
|
+
def file_upload(self):
|
|
148
|
+
"""Select a file and upload."""
|
|
149
|
+
self.error_label.clear()
|
|
150
|
+
select_file = PyQt6.QtWidgets.QFileDialog.getOpenFileName(self, "Open Filie")
|
|
151
|
+
path = self._fs_select(select_file)
|
|
152
|
+
if path is not None:
|
|
153
|
+
self._upload(path)
|
|
154
|
+
|
|
155
|
+
def download(self):
|
|
156
|
+
"""Download collection or data object."""
|
|
157
|
+
self.error_label.clear()
|
|
158
|
+
if self.browser_table.currentRow() == -1:
|
|
159
|
+
self.error_label.setText("Please select a row from the table first!")
|
|
160
|
+
return
|
|
161
|
+
|
|
162
|
+
if self.browser_table.item(self.browser_table.currentRow(), 1) is not None:
|
|
163
|
+
item_name = self.browser_table.item(self.browser_table.currentRow(), 1).text()
|
|
164
|
+
path = IrodsPath(self.session, "/", *self.path_input.text().split("/"), item_name)
|
|
165
|
+
overwrite = self.overwrite.isChecked()
|
|
166
|
+
download_dir = get_downloads_dir()
|
|
167
|
+
if overwrite:
|
|
168
|
+
write = "All data will be updated."
|
|
169
|
+
else:
|
|
170
|
+
write = "Only new data will be added."
|
|
171
|
+
info = f"Download data:\n{path}\n\nto\n\n{download_dir}\n\n{write}"
|
|
172
|
+
|
|
173
|
+
try:
|
|
174
|
+
if path.exists():
|
|
175
|
+
button_reply = PyQt6.QtWidgets.QMessageBox.question(self, "", info)
|
|
176
|
+
if button_reply == PyQt6.QtWidgets.QMessageBox.StandardButton.Yes:
|
|
177
|
+
if Path(download_dir).joinpath(item_name).exists() and not overwrite:
|
|
178
|
+
raise FileExistsError
|
|
179
|
+
self.setCursor(PyQt6.QtGui.QCursor(PyQt6.QtCore.Qt.CursorShape.BusyCursor))
|
|
180
|
+
self.logger.info(
|
|
181
|
+
"Downloading %s to %s, overwrite %s", path, download_dir, str(overwrite)
|
|
182
|
+
)
|
|
183
|
+
download(self.session, path, download_dir, overwrite=overwrite)
|
|
184
|
+
self.setCursor(PyQt6.QtGui.QCursor(PyQt6.QtCore.Qt.CursorShape.ArrowCursor))
|
|
185
|
+
self.error_label.setText("Data downloaded to: " + str(download_dir))
|
|
186
|
+
else:
|
|
187
|
+
self.error_label.setText(f"Data {path.parent} does not exist.")
|
|
188
|
+
except FileExistsError:
|
|
189
|
+
self.error_label.setText(
|
|
190
|
+
f"Data already exists in {download_dir}."
|
|
191
|
+
+ ' Check "overwrite" to overwrite the data.'
|
|
192
|
+
)
|
|
193
|
+
except Exception as err:
|
|
194
|
+
self.logger.exception("Downloading %s failed: %s", path, err)
|
|
195
|
+
self.error_label.setText(f"Could not download {path}. Consult the logs.")
|
|
196
|
+
|
|
197
|
+
def load_browser_table(self):
|
|
198
|
+
"""Load main browser table."""
|
|
199
|
+
self.error_label.clear()
|
|
200
|
+
self._clear_info_tabs()
|
|
201
|
+
obj_path = IrodsPath(self.session, self.path_input.text())
|
|
202
|
+
if obj_path.collection_exists():
|
|
203
|
+
try:
|
|
204
|
+
coll = obj_path.collection
|
|
205
|
+
|
|
206
|
+
coll_data = [
|
|
207
|
+
(
|
|
208
|
+
"C-",
|
|
209
|
+
subcoll.name,
|
|
210
|
+
"",
|
|
211
|
+
"",
|
|
212
|
+
subcoll.create_time.strftime("%d-%m-%Y"),
|
|
213
|
+
subcoll.modify_time.strftime("%d-%m-%Y %H:%m"),
|
|
214
|
+
)
|
|
215
|
+
for subcoll in coll.subcollections
|
|
216
|
+
]
|
|
217
|
+
obj_data = [
|
|
218
|
+
(
|
|
219
|
+
max(repl[4] for repl in obj_replicas(obj)),
|
|
220
|
+
obj.name,
|
|
221
|
+
str(obj.size),
|
|
222
|
+
obj.checksum,
|
|
223
|
+
obj.create_time.strftime("%d-%m-%Y"),
|
|
224
|
+
obj.modify_time.strftime("%d-%m-%Y %H:%m"),
|
|
225
|
+
)
|
|
226
|
+
for obj in coll.data_objects
|
|
227
|
+
]
|
|
228
|
+
|
|
229
|
+
populate_table(
|
|
230
|
+
self.browser_table,
|
|
231
|
+
len(coll.data_objects) + len(coll.subcollections),
|
|
232
|
+
coll_data + obj_data,
|
|
233
|
+
)
|
|
234
|
+
self.browser_table.resizeColumnsToContents()
|
|
235
|
+
except Exception:
|
|
236
|
+
self.browser_table.setRowCount(0)
|
|
237
|
+
self.logger.exception("Cannot load browser.")
|
|
238
|
+
self.error_label.setText("Cannot load browser table. Consult the logs.")
|
|
239
|
+
else:
|
|
240
|
+
self.browser_table.setRowCount(0)
|
|
241
|
+
self.error_label.setText("Collection does not exist.")
|
|
242
|
+
|
|
243
|
+
def fill_info(self):
|
|
244
|
+
"""Fill lower tabs with info."""
|
|
245
|
+
self.error_label.clear()
|
|
246
|
+
self._clear_info_tabs()
|
|
247
|
+
self.delete_browser.clear()
|
|
248
|
+
self.meta_table.setRowCount(0)
|
|
249
|
+
self.acl_table.setRowCount(0)
|
|
250
|
+
self.replica_table.setRowCount(0)
|
|
251
|
+
irods_path = self._get_item_path(self.browser_table.currentRow())
|
|
252
|
+
self._clear_info_tabs()
|
|
253
|
+
try:
|
|
254
|
+
self._fill_preview_tab(irods_path)
|
|
255
|
+
self._fill_metadata_tab(irods_path)
|
|
256
|
+
self._fill_acls_tab(irods_path)
|
|
257
|
+
self._fill_replicas_tab(irods_path)
|
|
258
|
+
except Exception:
|
|
259
|
+
self.logger.exception("Cannot load info tabs.")
|
|
260
|
+
self.error_label.setText("Cannot load info tabs. Consult the logs.")
|
|
261
|
+
|
|
262
|
+
def set_icat_meta(self):
|
|
263
|
+
"""Button metadata set."""
|
|
264
|
+
try:
|
|
265
|
+
self._metadata_edits("set")
|
|
266
|
+
except Exception as error:
|
|
267
|
+
self.error_label.setText(repr(error))
|
|
268
|
+
|
|
269
|
+
def add_icat_meta(self):
|
|
270
|
+
"""Button metadata add."""
|
|
271
|
+
try:
|
|
272
|
+
self._metadata_edits("add")
|
|
273
|
+
except Exception as error:
|
|
274
|
+
self.error_label.setText(repr(error))
|
|
275
|
+
|
|
276
|
+
def delete_icat_meta(self):
|
|
277
|
+
"""Button metadata delete."""
|
|
278
|
+
try:
|
|
279
|
+
self._metadata_edits("delete")
|
|
280
|
+
except Exception as error:
|
|
281
|
+
self.error_label.setText(repr(error))
|
|
282
|
+
|
|
283
|
+
# @PyQt6.QtCore.pyqtSlot(PyQt6.QtCore.QModelIndex)
|
|
284
|
+
def edit_metadata(self, index):
|
|
285
|
+
"""Load selected metadata into edit fields."""
|
|
286
|
+
self.error_label.clear()
|
|
287
|
+
self.meta_key_field.clear()
|
|
288
|
+
self.meta_value_field.clear()
|
|
289
|
+
self.meta_units_field.clear()
|
|
290
|
+
row = index.row()
|
|
291
|
+
key = self.meta_table.item(row, 0).text()
|
|
292
|
+
value = self.meta_table.item(row, 1).text()
|
|
293
|
+
if self.meta_table.item(row, 2):
|
|
294
|
+
units = self.meta_table.item(row, 2).text()
|
|
295
|
+
else:
|
|
296
|
+
units = ""
|
|
297
|
+
self.meta_key_field.setText(key)
|
|
298
|
+
self.meta_value_field.setText(value)
|
|
299
|
+
self.meta_units_field.setText(units)
|
|
300
|
+
|
|
301
|
+
# @PyQt6.QtCore.pyqtSlot(PyQt6.QtCore.QModelIndex)
|
|
302
|
+
def edit_acl(self, index):
|
|
303
|
+
"""Load selected acl into editing fields."""
|
|
304
|
+
self.error_label.clear()
|
|
305
|
+
self.acl_user_field.clear()
|
|
306
|
+
self.acl_zone_field.clear()
|
|
307
|
+
self.acl_box.setCurrentText("")
|
|
308
|
+
row = index.row()
|
|
309
|
+
user_name = self.acl_table.item(row, 0).text()
|
|
310
|
+
user_zone = self.acl_table.item(row, 1).text()
|
|
311
|
+
acc_name = self.acl_table.item(row, 2).text()
|
|
312
|
+
self.acl_user_field.setText(user_name)
|
|
313
|
+
self.acl_zone_field.setText(user_zone)
|
|
314
|
+
self.acl_box.setCurrentText(acc_name)
|
|
315
|
+
|
|
316
|
+
def update_icat_acl(self):
|
|
317
|
+
"""Send acls to iRODS server."""
|
|
318
|
+
self.error_label.clear()
|
|
319
|
+
if self.browser_table.currentRow() == -1:
|
|
320
|
+
self.error_label.setText("Please select a row from the table first!")
|
|
321
|
+
return
|
|
322
|
+
irods_path = self._get_item_path(self.browser_table.currentRow())
|
|
323
|
+
user_name = self.acl_user_field.text()
|
|
324
|
+
user_zone = self.acl_zone_field.text()
|
|
325
|
+
acc_name = self.acl_box.currentText()
|
|
326
|
+
|
|
327
|
+
if acc_name in ("inherit", "noinherit"):
|
|
328
|
+
if irods_path.dataobject_exists():
|
|
329
|
+
self.error_label.setText("WARNING: (no)inherit is not applicable to data objects")
|
|
330
|
+
return
|
|
331
|
+
elif user_name == "":
|
|
332
|
+
self.error_label.setText("Please provide a user.")
|
|
333
|
+
return
|
|
334
|
+
elif acc_name == "":
|
|
335
|
+
self.error_label.setText("Please provide an access level from the menu.")
|
|
336
|
+
return
|
|
337
|
+
recursive = self.recurisive_box.currentText() == "True"
|
|
338
|
+
try:
|
|
339
|
+
item = get_irods_item(irods_path)
|
|
340
|
+
perm = Permissions(self.session, item)
|
|
341
|
+
perm.set(perm=acc_name, user=user_name, zone=user_zone, recursive=recursive)
|
|
342
|
+
if acc_name == "null":
|
|
343
|
+
self.logger.info(
|
|
344
|
+
"Delete access (%s, %s, %s, %s) for %s",
|
|
345
|
+
acc_name,
|
|
346
|
+
user_name,
|
|
347
|
+
user_zone,
|
|
348
|
+
str(recursive),
|
|
349
|
+
item.path,
|
|
350
|
+
)
|
|
351
|
+
else:
|
|
352
|
+
self.logger.info(
|
|
353
|
+
"Add/change access of %s to (%s, %s, %s, %s)",
|
|
354
|
+
item.path,
|
|
355
|
+
acc_name,
|
|
356
|
+
user_name,
|
|
357
|
+
user_zone,
|
|
358
|
+
str(recursive),
|
|
359
|
+
)
|
|
360
|
+
self._fill_acls_tab(irods_path)
|
|
361
|
+
except irods.exception.CAT_INVALID_USER:
|
|
362
|
+
self.error_label.setText(f"Cannot update ACLs. {user_name}#{user_zone} not known.")
|
|
363
|
+
except Exception:
|
|
364
|
+
self.logger.exception("Cannot update ACLs.")
|
|
365
|
+
self.error_label.setText("Cannot update ACLs. Consult the logs.")
|
|
366
|
+
|
|
367
|
+
def load_selection(self):
|
|
368
|
+
"""Load selection from main table into delete tab."""
|
|
369
|
+
self.setCursor(PyQt6.QtGui.QCursor(PyQt6.QtCore.Qt.CursorShape.WaitCursor))
|
|
370
|
+
self.delete_browser.clear()
|
|
371
|
+
row = self.browser_table.currentRow()
|
|
372
|
+
if row == -1:
|
|
373
|
+
self.error_label.setText("Please select a row from the table first.")
|
|
374
|
+
self.setCursor(PyQt6.QtGui.QCursor(PyQt6.QtCore.Qt.CursorShape.ArrowCursor))
|
|
375
|
+
return
|
|
376
|
+
content = []
|
|
377
|
+
item_path = self._get_item_path(row)
|
|
378
|
+
if item_path.exists():
|
|
379
|
+
item = get_irods_item(item_path)
|
|
380
|
+
if item_path.collection_exists():
|
|
381
|
+
data_dict = get_coll_dict(item)
|
|
382
|
+
for key in list(data_dict.keys())[:20]:
|
|
383
|
+
content.append(key)
|
|
384
|
+
if len(data_dict[key]) > 0:
|
|
385
|
+
for item in data_dict[key]:
|
|
386
|
+
content.append("\t" + item)
|
|
387
|
+
content.append("...")
|
|
388
|
+
else:
|
|
389
|
+
content.append(str(item_path))
|
|
390
|
+
populate_textfield(self.delete_browser, content)
|
|
391
|
+
self.setCursor(PyQt6.QtGui.QCursor(PyQt6.QtCore.Qt.CursorShape.ArrowCursor))
|
|
392
|
+
|
|
393
|
+
def delete_data(self):
|
|
394
|
+
"""Delete all data in the delete_browser."""
|
|
395
|
+
self.error_label.clear()
|
|
396
|
+
data = self.delete_browser.toPlainText().split("\n")
|
|
397
|
+
if data[0] != "":
|
|
398
|
+
item = data[0].strip()
|
|
399
|
+
quit_msg = "Delete all data in \n\n" + item + "\n"
|
|
400
|
+
reply = PyQt6.QtWidgets.QMessageBox.question(
|
|
401
|
+
self,
|
|
402
|
+
"Message",
|
|
403
|
+
quit_msg,
|
|
404
|
+
PyQt6.QtWidgets.QMessageBox.StandardButton.Yes,
|
|
405
|
+
PyQt6.QtWidgets.QMessageBox.StandardButton.No,
|
|
406
|
+
)
|
|
407
|
+
if reply == PyQt6.QtWidgets.QMessageBox.StandardButton.Yes:
|
|
408
|
+
try:
|
|
409
|
+
IrodsPath(self.session, item).remove()
|
|
410
|
+
self.logger.info("Delete data %s", item)
|
|
411
|
+
self.delete_browser.clear()
|
|
412
|
+
self.load_browser_table()
|
|
413
|
+
self.error_label.clear()
|
|
414
|
+
except (irods.exception.CAT_NO_ACCESS_PERMISSION, PermissionError):
|
|
415
|
+
self.error_label.setText(f"No permissions to delete {item}")
|
|
416
|
+
except Exception:
|
|
417
|
+
self.logger.exception("FAILED: Delete data %s", item)
|
|
418
|
+
self.error_label.setText(f"FAILED: Delete data {item}. Consult the logs.")
|
|
419
|
+
|
|
420
|
+
# Internal functions
|
|
421
|
+
def _clear_info_tabs(self):
|
|
422
|
+
"""Clear the tabs view."""
|
|
423
|
+
self.acl_table.setRowCount(0)
|
|
424
|
+
self.meta_table.setRowCount(0)
|
|
425
|
+
self.replica_table.setRowCount(0)
|
|
426
|
+
self.preview_browser.clear()
|
|
427
|
+
|
|
428
|
+
def _fill_replicas_tab(self, irods_path):
|
|
429
|
+
"""Populate the table in the Replicas tab.
|
|
430
|
+
|
|
431
|
+
Parameters
|
|
432
|
+
----------
|
|
433
|
+
irods_path : str
|
|
434
|
+
Path of iRODS collection or data object selected.
|
|
435
|
+
|
|
436
|
+
"""
|
|
437
|
+
self.replica_table.setRowCount(0)
|
|
438
|
+
if irods_path.dataobject_exists():
|
|
439
|
+
obj = irods_path.dataobject
|
|
440
|
+
populate_table(self.replica_table, len(obj_replicas(obj)), obj_replicas(obj))
|
|
441
|
+
self.replica_table.setRowCount(len(obj.replicas))
|
|
442
|
+
self.replica_table.resizeColumnsToContents()
|
|
443
|
+
|
|
444
|
+
def _fill_acls_tab(self, irods_path):
|
|
445
|
+
"""Populate the table in the ACLs tab.
|
|
446
|
+
|
|
447
|
+
Parameters
|
|
448
|
+
----------
|
|
449
|
+
irods_path : str
|
|
450
|
+
Path of iRODS collection or data object selected.
|
|
451
|
+
|
|
452
|
+
"""
|
|
453
|
+
self.acl_table.setRowCount(0)
|
|
454
|
+
self.acl_user_field.clear()
|
|
455
|
+
self.acl_zone_field.clear()
|
|
456
|
+
self.acl_box.setCurrentText("")
|
|
457
|
+
obj = None
|
|
458
|
+
if irods_path.collection_exists():
|
|
459
|
+
obj = irods_path.collection
|
|
460
|
+
inheritance = f"{obj.inheritance}"
|
|
461
|
+
elif irods_path.dataobject_exists():
|
|
462
|
+
obj = irods_path.dataobject
|
|
463
|
+
inheritance = ""
|
|
464
|
+
if obj is not None:
|
|
465
|
+
acls = Permissions(self.session, obj)
|
|
466
|
+
acl_data = [(p.user_name, p.user_zone, p.access_name, inheritance) for p in acls]
|
|
467
|
+
populate_table(self.acl_table, len(list(acls)), acl_data)
|
|
468
|
+
self.acl_table.resizeColumnsToContents()
|
|
469
|
+
self.owner_label.setText(f"{obj.owner_name}")
|
|
470
|
+
|
|
471
|
+
def _fill_metadata_tab(self, irods_path):
|
|
472
|
+
"""Populate the table in the metadata tab.
|
|
473
|
+
|
|
474
|
+
Parameters
|
|
475
|
+
----------
|
|
476
|
+
irods_path : str
|
|
477
|
+
Full name of iRODS collection or data object selected.
|
|
478
|
+
|
|
479
|
+
"""
|
|
480
|
+
self.meta_key_field.clear()
|
|
481
|
+
self.meta_value_field.clear()
|
|
482
|
+
self.meta_units_field.clear()
|
|
483
|
+
item = None
|
|
484
|
+
if irods_path.collection_exists():
|
|
485
|
+
item = irods_path.collection
|
|
486
|
+
elif irods_path.dataobject_exists():
|
|
487
|
+
item = irods_path.dataobject
|
|
488
|
+
if item is not None:
|
|
489
|
+
meta = MetaData(item)
|
|
490
|
+
populate_table(self.meta_table, len(list(meta)), meta)
|
|
491
|
+
self.meta_table.resizeColumnsToContents()
|
|
492
|
+
|
|
493
|
+
def _fill_preview_tab(self, irods_path):
|
|
494
|
+
"""Populate the table in the metadata tab.
|
|
495
|
+
|
|
496
|
+
Parameters
|
|
497
|
+
----------
|
|
498
|
+
irods_path : str
|
|
499
|
+
Full name of iRODS collection or data object selected.
|
|
500
|
+
|
|
501
|
+
"""
|
|
502
|
+
if irods_path.collection_exists():
|
|
503
|
+
obj = irods_path.collection
|
|
504
|
+
content = ["Collections:", "-----------------"]
|
|
505
|
+
content.extend([sc.name for sc in obj.subcollections])
|
|
506
|
+
content.extend(["\n", "DataObjects:", "-----------------"])
|
|
507
|
+
content.extend([do.name for do in obj.data_objects])
|
|
508
|
+
elif irods_path.dataobject_exists():
|
|
509
|
+
file_type = ""
|
|
510
|
+
obj = irods_path.dataobject
|
|
511
|
+
if "." in irods_path.parts[-1]:
|
|
512
|
+
file_type = irods_path.parts[-1].split(".")[1]
|
|
513
|
+
if file_type in ["txt", "json", "csv"]:
|
|
514
|
+
try:
|
|
515
|
+
with obj.open("r") as objfd:
|
|
516
|
+
content = [objfd.read(1024).decode("utf-8")]
|
|
517
|
+
# self.preview_browser.append(preview_string)
|
|
518
|
+
except Exception as error:
|
|
519
|
+
content = [
|
|
520
|
+
f"No Preview for: {irods_path}",
|
|
521
|
+
repr(error),
|
|
522
|
+
"Storage resource might be down.",
|
|
523
|
+
]
|
|
524
|
+
else:
|
|
525
|
+
content = [f"No Preview for: {irods_path}"]
|
|
526
|
+
populate_textfield(self.preview_browser, content)
|
|
527
|
+
|
|
528
|
+
def _get_item_path(self, row):
|
|
529
|
+
item_name = self.browser_table.item(row, 1).text()
|
|
530
|
+
return IrodsPath(self.session, "/", *self.path_input.text().split("/"), item_name)
|
|
531
|
+
|
|
532
|
+
def _metadata_edits(self, operation):
|
|
533
|
+
self.error_label.clear()
|
|
534
|
+
if self.browser_table.currentRow() == -1:
|
|
535
|
+
self.error_label.setText("Please select an object first!")
|
|
536
|
+
else:
|
|
537
|
+
irods_path = self._get_item_path(self.browser_table.currentRow())
|
|
538
|
+
new_key = self.meta_key_field.text()
|
|
539
|
+
new_val = self.meta_value_field.text()
|
|
540
|
+
new_units = self.meta_units_field.text()
|
|
541
|
+
if new_key != "" and new_val != "":
|
|
542
|
+
irods_path = self._get_item_path(self.browser_table.currentRow())
|
|
543
|
+
item = get_irods_item(irods_path)
|
|
544
|
+
meta = MetaData(item)
|
|
545
|
+
if operation == "add":
|
|
546
|
+
meta.add(new_key, new_val, new_units)
|
|
547
|
+
self.logger.info(
|
|
548
|
+
"Add metadata (%s, %s, %s) to %s", new_key, new_val, new_units, irods_path
|
|
549
|
+
)
|
|
550
|
+
elif operation == "set":
|
|
551
|
+
meta.set(new_key, new_val, new_units)
|
|
552
|
+
self.logger.info(
|
|
553
|
+
"Set all metadata with key %s to (%s, %s, %s) for %s",
|
|
554
|
+
new_key,
|
|
555
|
+
new_key,
|
|
556
|
+
new_val,
|
|
557
|
+
new_units,
|
|
558
|
+
irods_path,
|
|
559
|
+
)
|
|
560
|
+
elif operation == "delete":
|
|
561
|
+
meta.delete(new_key, new_val, new_units)
|
|
562
|
+
self.logger.info(
|
|
563
|
+
"Delete metadata (%s, %s, %s) from %s",
|
|
564
|
+
new_key,
|
|
565
|
+
new_val,
|
|
566
|
+
new_units,
|
|
567
|
+
irods_path,
|
|
568
|
+
)
|
|
569
|
+
self._fill_metadata_tab(irods_path)
|
|
570
|
+
|
|
571
|
+
def _fs_select(self, path_select):
|
|
572
|
+
"""Retrieve the path (file or folder) from a QFileDialog.
|
|
573
|
+
|
|
574
|
+
Parameters
|
|
575
|
+
----------
|
|
576
|
+
path_select: PyQt6.QtWidgets.QFileDialog.getExistingDirectory
|
|
577
|
+
PyQt6.QtWidgets.QFileDialog.getOpenFileName
|
|
578
|
+
|
|
579
|
+
"""
|
|
580
|
+
yes_button = PyQt6.QtWidgets.QMessageBox.StandardButton.Yes
|
|
581
|
+
if isinstance(path_select, tuple):
|
|
582
|
+
path = path_select[0]
|
|
583
|
+
else:
|
|
584
|
+
path = path_select
|
|
585
|
+
|
|
586
|
+
if path != "":
|
|
587
|
+
if self.overwrite.isChecked():
|
|
588
|
+
write = "All data will be updated."
|
|
589
|
+
else:
|
|
590
|
+
write = "Only new data will be added."
|
|
591
|
+
info = f"Upload data:\n{path}\n\nto\n{self.path_input.text()}\n\n{write}"
|
|
592
|
+
reply = PyQt6.QtWidgets.QMessageBox.question(self, "", info)
|
|
593
|
+
if reply == yes_button:
|
|
594
|
+
return Path(path)
|
|
595
|
+
return None
|
|
596
|
+
|
|
597
|
+
def _upload(self, source):
|
|
598
|
+
"""Upload data to path in path_input."""
|
|
599
|
+
overwrite = self.overwrite.isChecked()
|
|
600
|
+
parent_path = IrodsPath(self.session, "/", *self.path_input.text().split("/"))
|
|
601
|
+
|
|
602
|
+
try:
|
|
603
|
+
if parent_path.joinpath(source.name).exists() and not overwrite:
|
|
604
|
+
raise FileExistsError
|
|
605
|
+
self.logger.info(
|
|
606
|
+
"Uploading %s to %s, overwrite %s", source, parent_path, str(overwrite)
|
|
607
|
+
)
|
|
608
|
+
upload(self.session, source, parent_path, overwrite=overwrite)
|
|
609
|
+
self.load_browser_table()
|
|
610
|
+
except FileExistsError:
|
|
611
|
+
self.error_label.setText(
|
|
612
|
+
f"Data already exists in {parent_path}."
|
|
613
|
+
+ ' Check "overwrite" to overwrite the data.'
|
|
614
|
+
)
|
|
615
|
+
except irods.exception.CAT_NO_ACCESS_PERMISSION:
|
|
616
|
+
self.error_label.setText(f"No permission to upload data to {parent_path}.")
|
|
617
|
+
self.logger.info(
|
|
618
|
+
"Uploading %s to %s, overwrite %s failed. No permissions.",
|
|
619
|
+
source,
|
|
620
|
+
parent_path,
|
|
621
|
+
str(overwrite),
|
|
622
|
+
)
|
|
623
|
+
except Exception as err:
|
|
624
|
+
self.logger.exception("Failed to upload %s to %s: %s", source, parent_path, err)
|
|
625
|
+
self.error_label.setText(f"Failed to upload {source}. Consult the logs.")
|