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/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)
|
ibridgesgui/gui_utils.py
ADDED
|
@@ -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)
|