tg-prepare 2.2.2__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.
- tg_prepare-2.2.2.dist-info/METADATA +20 -0
- tg_prepare-2.2.2.dist-info/RECORD +82 -0
- tg_prepare-2.2.2.dist-info/WHEEL +5 -0
- tg_prepare-2.2.2.dist-info/entry_points.txt +4 -0
- tg_prepare-2.2.2.dist-info/licenses/LICENSE +202 -0
- tg_prepare-2.2.2.dist-info/projects/.secret_key +1 -0
- tg_prepare-2.2.2.dist-info/top_level.txt +2 -0
- tgp_backend/__init__.py +17 -0
- tgp_backend/auth.py +71 -0
- tgp_backend/cli.py +95 -0
- tgp_backend/config.py +35 -0
- tgp_backend/directories.py +79 -0
- tgp_backend/interfaces.py +3 -0
- tgp_backend/nextcloud.py +146 -0
- tgp_backend/project.py +468 -0
- tgp_backend/session_manager.py +47 -0
- tgp_backend/tgclient.py +187 -0
- tgp_backend/user.py +137 -0
- tgp_backend/util.py +131 -0
- tgp_ui/__init__.py +0 -0
- tgp_ui/app.py +150 -0
- tgp_ui/routes/__init__.py +0 -0
- tgp_ui/routes/auth.py +72 -0
- tgp_ui/routes/collection.py +319 -0
- tgp_ui/routes/data.py +229 -0
- tgp_ui/routes/project.py +103 -0
- tgp_ui/routes/publication.py +229 -0
- tgp_ui/routes/tabs.py +34 -0
- tgp_ui/routes/views.py +66 -0
- tgp_ui/static/css/bootstrap-icons.min.css +5 -0
- tgp_ui/static/css/bootstrap.min.css +6 -0
- tgp_ui/static/css/bootstrap.min.css.map +1 -0
- tgp_ui/static/css/main.css +141 -0
- tgp_ui/static/css/navbar.css +92 -0
- tgp_ui/static/css/simpleXML.css +67 -0
- tgp_ui/static/img/favicon.ico +0 -0
- tgp_ui/static/img/textgrid-logo.svg +1 -0
- tgp_ui/static/js/bootstrap.bundle.min.js +7 -0
- tgp_ui/static/js/collectionManager.js +60 -0
- tgp_ui/static/js/fileManager.js +153 -0
- tgp_ui/static/js/jquery.min.js +2 -0
- tgp_ui/static/js/main.js +36 -0
- tgp_ui/static/js/modalManager.js +105 -0
- tgp_ui/static/js/navbarManager.js +151 -0
- tgp_ui/static/js/projectManager.js +60 -0
- tgp_ui/static/js/require.js +5 -0
- tgp_ui/static/js/sidebarManager.js +44 -0
- tgp_ui/static/js/simpleXML.js +193 -0
- tgp_ui/static/js/tabManager.js +83 -0
- tgp_ui/templates/auth/login.html +42 -0
- tgp_ui/templates/details/empty_container.html +16 -0
- tgp_ui/templates/details/manage_collection.html +209 -0
- tgp_ui/templates/file_tree.html +39 -0
- tgp_ui/templates/includes/get_sessionid.html +29 -0
- tgp_ui/templates/includes/publish_form.html +55 -0
- tgp_ui/templates/includes/set_sessionid.html +26 -0
- tgp_ui/templates/includes/upload_form.html +258 -0
- tgp_ui/templates/layout.html +74 -0
- tgp_ui/templates/macros.html +101 -0
- tgp_ui/templates/modal/delete_project.html +25 -0
- tgp_ui/templates/modal/empty_container.html +9 -0
- tgp_ui/templates/modal/file_explorer_content.html +34 -0
- tgp_ui/templates/modal/file_explorer_main.html +22 -0
- tgp_ui/templates/modal/file_explorer_nextcloud.html +27 -0
- tgp_ui/templates/modal/github_modal.html +29 -0
- tgp_ui/templates/modal/nextcloud_login.html +48 -0
- tgp_ui/templates/modal/tei_explorer.html +58 -0
- tgp_ui/templates/modal/xpath_parser.html +52 -0
- tgp_ui/templates/nxc_login.html +25 -0
- tgp_ui/templates/nxc_tab.html +15 -0
- tgp_ui/templates/project_main.html +36 -0
- tgp_ui/templates/project_navbar.html +81 -0
- tgp_ui/templates/projects_main.html +58 -0
- tgp_ui/templates/tabs/check_upload.html +87 -0
- tgp_ui/templates/tabs/edit_project.html +113 -0
- tgp_ui/templates/tabs/empty_container.html +12 -0
- tgp_ui/templates/tabs/import_data.html +122 -0
- tgp_ui/templates/tabs/manage_collections.html +107 -0
- tgp_ui/templates/tabs/publication.html +42 -0
- tgp_ui/templates/tabs/select_directories.html +68 -0
- tgp_ui/templates/tabs/upload.html +42 -0
- tgp_ui/templates/tabs/validate_metadata.html +227 -0
tgp_backend/nextcloud.py
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Copyright (C) 2023-2025 TU-Dresden (ZIH)
|
|
3
|
+
# ralf.klammer@tu-dresden.de
|
|
4
|
+
# moritz.wilhelm@tu-dresden.de
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
import nextcloud
|
|
8
|
+
import os
|
|
9
|
+
|
|
10
|
+
log = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Nextcloud:
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
url=None,
|
|
17
|
+
username=None,
|
|
18
|
+
password=None,
|
|
19
|
+
folder=None,
|
|
20
|
+
**kwargs,
|
|
21
|
+
):
|
|
22
|
+
self._nxc = None
|
|
23
|
+
self._root = None
|
|
24
|
+
self.url = url
|
|
25
|
+
self.username = username
|
|
26
|
+
self.password = password
|
|
27
|
+
self.root_dir = folder
|
|
28
|
+
|
|
29
|
+
def test_connection(self):
|
|
30
|
+
if all([self.url, self.username, self.password]):
|
|
31
|
+
return str(self.nxc.list_folders().status_code).startswith("2")
|
|
32
|
+
return False
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def nxc(self):
|
|
36
|
+
if self._nxc is None:
|
|
37
|
+
self._nxc = nextcloud.NextCloud(
|
|
38
|
+
endpoint=self.url,
|
|
39
|
+
user=self.username,
|
|
40
|
+
password=self.password,
|
|
41
|
+
)
|
|
42
|
+
return self._nxc
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def root(self):
|
|
46
|
+
if self._root is None:
|
|
47
|
+
log.debug("start get_folder")
|
|
48
|
+
self._root = self.nxc.get_folder(self.root_dir)
|
|
49
|
+
if self._root is None:
|
|
50
|
+
raise "Could not find the Root Directory"
|
|
51
|
+
return self._root
|
|
52
|
+
|
|
53
|
+
def nxc_list_files_and_folders(self, get_selectable=False):
|
|
54
|
+
|
|
55
|
+
selectable_folders = []
|
|
56
|
+
|
|
57
|
+
def recursive_list(item, depth=0):
|
|
58
|
+
items = []
|
|
59
|
+
for d in item.list():
|
|
60
|
+
if d.isdir():
|
|
61
|
+
children = recursive_list(d, depth=depth + 1)
|
|
62
|
+
contains_xml = any(
|
|
63
|
+
f.get("name", "").endswith(".xml") for f in children
|
|
64
|
+
)
|
|
65
|
+
item = {
|
|
66
|
+
"type": "folder",
|
|
67
|
+
"name": d.basename(),
|
|
68
|
+
"depth": depth,
|
|
69
|
+
"path": d.get_relative_path(),
|
|
70
|
+
"contains_xml": contains_xml,
|
|
71
|
+
"children": {
|
|
72
|
+
"count": len(children),
|
|
73
|
+
"list": children,
|
|
74
|
+
},
|
|
75
|
+
}
|
|
76
|
+
items.append(item)
|
|
77
|
+
if contains_xml:
|
|
78
|
+
selectable_folders.append(item)
|
|
79
|
+
else:
|
|
80
|
+
items.append(
|
|
81
|
+
{
|
|
82
|
+
"type": "file",
|
|
83
|
+
"name": d.basename(),
|
|
84
|
+
"depth": depth,
|
|
85
|
+
}
|
|
86
|
+
)
|
|
87
|
+
return items
|
|
88
|
+
|
|
89
|
+
result = recursive_list(self.root)
|
|
90
|
+
if get_selectable:
|
|
91
|
+
return selectable_folders
|
|
92
|
+
else:
|
|
93
|
+
return result
|
|
94
|
+
|
|
95
|
+
def get_selectable_folders(self):
|
|
96
|
+
|
|
97
|
+
return self.nxc_list_files_and_folders(get_selectable=True)
|
|
98
|
+
|
|
99
|
+
def download_nxc_files(self, file_paths, projectname):
|
|
100
|
+
for path in file_paths:
|
|
101
|
+
# Retrieve element (file or folder) from the server
|
|
102
|
+
try:
|
|
103
|
+
element = self.nxc.get_folder(path)
|
|
104
|
+
log.debug(f"Processing {path} in project {projectname}")
|
|
105
|
+
|
|
106
|
+
# Determine destination folder
|
|
107
|
+
destination_folder = (
|
|
108
|
+
f"projects/{projectname}/data/{element.basename()}"
|
|
109
|
+
)
|
|
110
|
+
log.debug(f"Destination folder: {destination_folder}")
|
|
111
|
+
|
|
112
|
+
if not os.path.exists(destination_folder):
|
|
113
|
+
os.makedirs(destination_folder)
|
|
114
|
+
|
|
115
|
+
# Recursive function to download files and folders
|
|
116
|
+
def download_recursive(
|
|
117
|
+
current_element, current_path, current_dest
|
|
118
|
+
):
|
|
119
|
+
for item in current_element.list():
|
|
120
|
+
item_path = os.path.join(current_path, item.basename())
|
|
121
|
+
if item.isdir():
|
|
122
|
+
# If it's a folder, create subfolder and continue
|
|
123
|
+
# recursively
|
|
124
|
+
new_dest = os.path.join(
|
|
125
|
+
current_dest, item.basename()
|
|
126
|
+
)
|
|
127
|
+
if not os.path.exists(new_dest):
|
|
128
|
+
os.makedirs(new_dest)
|
|
129
|
+
subfolder = self.nxc.get_folder(item_path)
|
|
130
|
+
download_recursive(subfolder, item_path, new_dest)
|
|
131
|
+
else:
|
|
132
|
+
# If it's a file, download it
|
|
133
|
+
remote_file = self.nxc.get_file(item_path)
|
|
134
|
+
target_path = os.path.join(
|
|
135
|
+
current_dest, item.basename()
|
|
136
|
+
)
|
|
137
|
+
remote_file.download(
|
|
138
|
+
target=target_path, overwrite=True
|
|
139
|
+
)
|
|
140
|
+
log.debug(f"Downloaded: {target_path}")
|
|
141
|
+
|
|
142
|
+
# Start the recursive download
|
|
143
|
+
download_recursive(element, path, destination_folder)
|
|
144
|
+
|
|
145
|
+
except Exception as e:
|
|
146
|
+
log.error(f"Error processing {path}: {str(e)}")
|
tgp_backend/project.py
ADDED
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Copyright (C) 2023-2025 TU-Dresden (ZIH)
|
|
3
|
+
# ralf.klammer@tu-dresden.de
|
|
4
|
+
# moritz.wilhelm@tu-dresden.de
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import subprocess
|
|
10
|
+
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
|
|
13
|
+
from tg_model.collection import CollectionModeler # type: ignore
|
|
14
|
+
from tg_model.project import Project as ProjectHandler # type: ignore
|
|
15
|
+
from tg_model.tei import TEIParser # type: ignore
|
|
16
|
+
|
|
17
|
+
from tg_model.yaml import ( # type: ignore
|
|
18
|
+
CollectionConfig,
|
|
19
|
+
CollectionConfigTemplate,
|
|
20
|
+
ProjectConfigTemplate,
|
|
21
|
+
ProjectConfig,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
from tgp_backend.config import MAIN_PATH
|
|
25
|
+
from tgp_backend.tgclient import TGclient
|
|
26
|
+
from tgp_backend.util import (
|
|
27
|
+
list_files_and_folders,
|
|
28
|
+
get_selectable_folders,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
log = logging.getLogger(__name__)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Project(object):
|
|
35
|
+
def __init__(self, path, username):
|
|
36
|
+
self.path = path
|
|
37
|
+
self.username = username
|
|
38
|
+
self._project_config = None
|
|
39
|
+
self._collections = None
|
|
40
|
+
self._nextcloud = None
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def name(self):
|
|
44
|
+
log.warning("Project.name is deprecated, use Project.path instead")
|
|
45
|
+
return self.path
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def fullpath(self):
|
|
49
|
+
return os.path.join(MAIN_PATH, self.username, self.path)
|
|
50
|
+
# return f"{MAIN_PATH}/{self.path}"
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def title(self):
|
|
54
|
+
return self.project_config.title
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def description(self):
|
|
58
|
+
return self.project_config.description
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def collectors(self):
|
|
62
|
+
return self.project_config.collectors
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def path_data(self):
|
|
66
|
+
return os.path.join(self.fullpath, "data")
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def path_metadata(self):
|
|
70
|
+
return os.path.join(self.fullpath, "metadata")
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def path_otherfiles(self):
|
|
74
|
+
return os.path.join(self.path_metadata, "other_files")
|
|
75
|
+
|
|
76
|
+
def create(self):
|
|
77
|
+
if not os.path.exists(self.fullpath):
|
|
78
|
+
os.makedirs(self.fullpath)
|
|
79
|
+
os.makedirs(self.path_data)
|
|
80
|
+
os.makedirs(self.path_metadata)
|
|
81
|
+
else:
|
|
82
|
+
log.warning("Project already exists!")
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def avatar(self):
|
|
86
|
+
if self.project_config.avatar:
|
|
87
|
+
return os.path.join(
|
|
88
|
+
self.path_otherfiles, self.project_config.avatar
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def xslt(self):
|
|
93
|
+
if self.project_config.xslt:
|
|
94
|
+
return os.path.join(self.path_otherfiles, self.project_config.xslt)
|
|
95
|
+
|
|
96
|
+
def save_avatar(self, file):
|
|
97
|
+
if file.filename and file.filename != "":
|
|
98
|
+
file.save(os.path.join(self.path_otherfiles, file.filename))
|
|
99
|
+
self.project_config.avatar = file.filename
|
|
100
|
+
|
|
101
|
+
def save_xslt(self, file):
|
|
102
|
+
if file.filename and file.filename != "":
|
|
103
|
+
file.save(os.path.join(self.path_otherfiles, file.filename))
|
|
104
|
+
self.project_config.xslt = file.filename
|
|
105
|
+
|
|
106
|
+
def clear_xslt(self):
|
|
107
|
+
self.project_config.xslt = None
|
|
108
|
+
self.project_config.save()
|
|
109
|
+
|
|
110
|
+
def update(self, title, description, collectors, avatar, xslt):
|
|
111
|
+
self.project_config.title = title
|
|
112
|
+
self.project_config.description = description
|
|
113
|
+
self.project_config.collectors = collectors
|
|
114
|
+
|
|
115
|
+
if avatar:
|
|
116
|
+
self.save_avatar(avatar)
|
|
117
|
+
self.save_xslt(xslt)
|
|
118
|
+
|
|
119
|
+
self.project_config.save()
|
|
120
|
+
|
|
121
|
+
def clone_git_project(self, url):
|
|
122
|
+
repo_name = url.split("/")[-1].replace(".git", "")
|
|
123
|
+
repo_path = os.path.join(self.path_data, repo_name)
|
|
124
|
+
|
|
125
|
+
if not os.path.exists(repo_path):
|
|
126
|
+
subprocess.run(["git", "clone", url, repo_path])
|
|
127
|
+
else:
|
|
128
|
+
log.info("Repository already exists!")
|
|
129
|
+
|
|
130
|
+
def file_upload(self, files):
|
|
131
|
+
for file in files:
|
|
132
|
+
if file:
|
|
133
|
+
filename = file.filename
|
|
134
|
+
filepath = os.path.join(self.path_data, filename)
|
|
135
|
+
directory = os.path.dirname(filepath)
|
|
136
|
+
if not os.path.exists(directory):
|
|
137
|
+
os.makedirs(directory)
|
|
138
|
+
file.save(filepath)
|
|
139
|
+
|
|
140
|
+
def list_files_and_folders(self):
|
|
141
|
+
return list_files_and_folders(self.path_data)
|
|
142
|
+
|
|
143
|
+
def get_selectable_folders(self):
|
|
144
|
+
selectable_folders = get_selectable_folders(self.path_data)
|
|
145
|
+
for folder in selectable_folders:
|
|
146
|
+
folder["relative_path"] = folder["path"].replace(
|
|
147
|
+
self.path_data, ""
|
|
148
|
+
)
|
|
149
|
+
return selectable_folders
|
|
150
|
+
|
|
151
|
+
@property
|
|
152
|
+
def main_config(self):
|
|
153
|
+
log.warning(
|
|
154
|
+
"Project.main_config is deprecated, use Project.project_config instead"
|
|
155
|
+
)
|
|
156
|
+
return self.project_config
|
|
157
|
+
|
|
158
|
+
@property
|
|
159
|
+
def project_config(self):
|
|
160
|
+
if not self._project_config:
|
|
161
|
+
self._project_config = ProjectConfig(
|
|
162
|
+
projectpath=self.path_metadata
|
|
163
|
+
)
|
|
164
|
+
if not self._project_config.exists():
|
|
165
|
+
self._project_config = ProjectConfigTemplate(
|
|
166
|
+
self.path_metadata
|
|
167
|
+
).render()
|
|
168
|
+
self._project_config.title = self.path
|
|
169
|
+
self._project_config.save()
|
|
170
|
+
|
|
171
|
+
return self._project_config
|
|
172
|
+
|
|
173
|
+
@property
|
|
174
|
+
def project_handler(self):
|
|
175
|
+
return ProjectHandler(self.path_metadata)
|
|
176
|
+
|
|
177
|
+
def get_subproject_inpath(self, name):
|
|
178
|
+
for subproject in self.project_config.content["subprojects"]:
|
|
179
|
+
if subproject["name"] == name:
|
|
180
|
+
return subproject["inpath"]
|
|
181
|
+
return None
|
|
182
|
+
|
|
183
|
+
def _set_collections(self):
|
|
184
|
+
self._collections = {}
|
|
185
|
+
if self.project_config.exists():
|
|
186
|
+
for subproject in self.project_config.content["subprojects"]:
|
|
187
|
+
collection_config = CollectionConfig(subproject["basepath"])
|
|
188
|
+
if not collection_config.exists():
|
|
189
|
+
collection_config = CollectionConfigTemplate(
|
|
190
|
+
projectpath=self.path_metadata,
|
|
191
|
+
subproject=subproject,
|
|
192
|
+
files=[
|
|
193
|
+
TEIParser(fullpath=file)
|
|
194
|
+
for file in subproject["files"]
|
|
195
|
+
],
|
|
196
|
+
).render(overwrite=False)
|
|
197
|
+
|
|
198
|
+
c_modeler = CollectionModeler(subproject, self.path_metadata)
|
|
199
|
+
self._collections[subproject["name"]] = {
|
|
200
|
+
"config": collection_config,
|
|
201
|
+
"paths": subproject,
|
|
202
|
+
"parser": CollectionParser(collection_config),
|
|
203
|
+
"modeler": c_modeler,
|
|
204
|
+
"path": c_modeler.get_collection_path(),
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
@property
|
|
208
|
+
def collections(self):
|
|
209
|
+
if self._collections is None:
|
|
210
|
+
self._set_collections()
|
|
211
|
+
return self._collections
|
|
212
|
+
|
|
213
|
+
def get_collection_paths(self):
|
|
214
|
+
return [c["path"] for c in self.collections.values()]
|
|
215
|
+
|
|
216
|
+
def get_collection(self, title):
|
|
217
|
+
return self.collections.get(title)
|
|
218
|
+
|
|
219
|
+
def get_collection_parser(self, collection):
|
|
220
|
+
return CollectionParser(collection)
|
|
221
|
+
|
|
222
|
+
def get_tgp(self, instance):
|
|
223
|
+
return TGProject(self, instance)
|
|
224
|
+
|
|
225
|
+
def _save_validation_results(self, results, filepath):
|
|
226
|
+
"""
|
|
227
|
+
Saves the validation results to a JSON file.
|
|
228
|
+
Adds the date of the last execution.
|
|
229
|
+
"""
|
|
230
|
+
results["last_run"] = datetime.now().strftime("%Y-%m-%d %H:%M")
|
|
231
|
+
|
|
232
|
+
with open(filepath, "w", encoding="utf-8") as f:
|
|
233
|
+
json.dump(results, f, indent=4, ensure_ascii=False)
|
|
234
|
+
|
|
235
|
+
log.info(f"Validation results saved to {filepath}")
|
|
236
|
+
|
|
237
|
+
def _run_validation(self, file_attributes=False):
|
|
238
|
+
validation_results = self.project_handler.validate(
|
|
239
|
+
file_attributes=file_attributes
|
|
240
|
+
)
|
|
241
|
+
self._save_validation_results(validation_results, self.validation_file)
|
|
242
|
+
return validation_results
|
|
243
|
+
|
|
244
|
+
@property
|
|
245
|
+
def validation_file(self):
|
|
246
|
+
"""
|
|
247
|
+
Returns the path to the validation results file.
|
|
248
|
+
"""
|
|
249
|
+
return os.path.join(self.path_metadata, "validation_results.json")
|
|
250
|
+
|
|
251
|
+
def get_validation_results(self, refresh=False, file_attributes=False):
|
|
252
|
+
"""
|
|
253
|
+
Returns stored validation results.
|
|
254
|
+
If no results are saved or `refresh=True`, a new validation will
|
|
255
|
+
be performed.
|
|
256
|
+
"""
|
|
257
|
+
# If `refresh` is set or file doesn't exist, perform new validation
|
|
258
|
+
if refresh or not os.path.exists(self.validation_file):
|
|
259
|
+
validation_results = self._run_validation(
|
|
260
|
+
file_attributes=file_attributes
|
|
261
|
+
)
|
|
262
|
+
else:
|
|
263
|
+
# Load results from file
|
|
264
|
+
with open(self.validation_file, "r", encoding="utf-8") as f:
|
|
265
|
+
validation_results = json.load(f)
|
|
266
|
+
|
|
267
|
+
if "ready_for_publication" not in validation_results:
|
|
268
|
+
validation_results = self._run_validation(
|
|
269
|
+
file_attributes=file_attributes
|
|
270
|
+
)
|
|
271
|
+
return validation_results
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
class TGProject(object):
|
|
275
|
+
|
|
276
|
+
def __init__(self, project, instance, username=None):
|
|
277
|
+
if isinstance(project, Project):
|
|
278
|
+
self.project = project
|
|
279
|
+
elif username:
|
|
280
|
+
self.project = Project(project, username)
|
|
281
|
+
else:
|
|
282
|
+
raise ValueError(
|
|
283
|
+
"Project must be an instance of Project or a project path \
|
|
284
|
+
with a username."
|
|
285
|
+
)
|
|
286
|
+
self.instance = instance
|
|
287
|
+
self.project_config = self.project.project_config
|
|
288
|
+
self.collections = self.project.collections
|
|
289
|
+
self._tg_client = None
|
|
290
|
+
self._tg_session_id = None
|
|
291
|
+
self._tg_project_id = None
|
|
292
|
+
|
|
293
|
+
def check_session(self):
|
|
294
|
+
return self.tg_client.check_session()
|
|
295
|
+
|
|
296
|
+
@property
|
|
297
|
+
def tg_session_id(self):
|
|
298
|
+
if not self._tg_session_id:
|
|
299
|
+
self._tg_session_id = self.project_config.get_tg_session_id(
|
|
300
|
+
self.instance
|
|
301
|
+
)
|
|
302
|
+
return self._tg_session_id
|
|
303
|
+
|
|
304
|
+
@tg_session_id.setter
|
|
305
|
+
def tg_session_id(self, session_id):
|
|
306
|
+
self._tg_session_id = session_id
|
|
307
|
+
self.project_config.set_tg_session_id(session_id, self.instance)
|
|
308
|
+
|
|
309
|
+
@property
|
|
310
|
+
def imex_file(self):
|
|
311
|
+
return os.path.join(self.project_config.projectpath, "config.imex")
|
|
312
|
+
|
|
313
|
+
@property
|
|
314
|
+
def tg_project_id(self):
|
|
315
|
+
if not self._tg_project_id:
|
|
316
|
+
self._tg_project_id = self.project_config.get_tg_project_id(
|
|
317
|
+
self.instance
|
|
318
|
+
)
|
|
319
|
+
return self._tg_project_id
|
|
320
|
+
|
|
321
|
+
@tg_project_id.setter
|
|
322
|
+
def tg_project_id(self, project_id):
|
|
323
|
+
self._tg_project_id = project_id
|
|
324
|
+
self.project_config.set_tg_project_id(project_id, self.instance)
|
|
325
|
+
|
|
326
|
+
@property
|
|
327
|
+
def tg_client(self):
|
|
328
|
+
if not self._tg_client:
|
|
329
|
+
self._tg_client = TGclient(self.tg_session_id, self.instance)
|
|
330
|
+
return self._tg_client
|
|
331
|
+
|
|
332
|
+
def create_tg_project(self, name, description=""):
|
|
333
|
+
# def create_tg_project(self, name, instance="test", description=""):
|
|
334
|
+
if not self.tg_session_id:
|
|
335
|
+
return []
|
|
336
|
+
tg_project_id = self.tg_client.create_project(name, description)
|
|
337
|
+
if tg_project_id:
|
|
338
|
+
self.tg_project_id = tg_project_id
|
|
339
|
+
|
|
340
|
+
def clear_tg_project(self, tg_project_id):
|
|
341
|
+
# def clear_tg_project(self, tg_project_id, instance="test"):
|
|
342
|
+
if not self.tg_session_id:
|
|
343
|
+
return False
|
|
344
|
+
# clear contents of tg-project at textgrid server
|
|
345
|
+
self.tg_client.clear_project(tg_project_id)
|
|
346
|
+
|
|
347
|
+
# remove imex files locally
|
|
348
|
+
for c in self.project.collections.values():
|
|
349
|
+
c["modeler"].remove_imex_file()
|
|
350
|
+
self.project_config.other_files.remove_imex_file()
|
|
351
|
+
|
|
352
|
+
return True
|
|
353
|
+
|
|
354
|
+
def delete_tg_project(self, tg_project_id):
|
|
355
|
+
# def delete_tg_project(self, tg_project_id, instance="test"):
|
|
356
|
+
if not self.tg_session_id:
|
|
357
|
+
return False
|
|
358
|
+
# delete tg-project at textgrid server
|
|
359
|
+
res = self.tg_client.delete_project(tg_project_id)
|
|
360
|
+
# delete tg-project at local config if successful AND
|
|
361
|
+
# is the currently defined project_id
|
|
362
|
+
if res and tg_project_id == self.tg_project_id:
|
|
363
|
+
self.tg_project_id = None
|
|
364
|
+
return True
|
|
365
|
+
|
|
366
|
+
def get_tg_projects(self):
|
|
367
|
+
# def get_tg_projects(self, instance="test"):
|
|
368
|
+
if not self.tg_session_id:
|
|
369
|
+
return []
|
|
370
|
+
return self.tg_client.get_assigned_projects()
|
|
371
|
+
|
|
372
|
+
def get_tg_project(self, project_id):
|
|
373
|
+
if self.tg_session_id:
|
|
374
|
+
return self.tg_client.get_project_description(project_id)
|
|
375
|
+
|
|
376
|
+
def upload_tg_project(self):
|
|
377
|
+
# def upload_tg_project(self, instance="test"):
|
|
378
|
+
if not self.tg_session_id:
|
|
379
|
+
return False
|
|
380
|
+
|
|
381
|
+
for c_name in self.collections:
|
|
382
|
+
collection = self.collections[c_name]
|
|
383
|
+
# step 1: create required metadata
|
|
384
|
+
collection["modeler"].render_collection()
|
|
385
|
+
# step 2: upload collection to textgrid server
|
|
386
|
+
collection["modeler"].upload(
|
|
387
|
+
tg_session_id=self.tg_session_id,
|
|
388
|
+
tg_project_id=self.tg_project_id,
|
|
389
|
+
tg_server=self.instance,
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
def upload_collection(self, collection_name):
|
|
393
|
+
# def upload_collection(self, collection_name, instance="test"):
|
|
394
|
+
if not self.tg_session_id:
|
|
395
|
+
return False
|
|
396
|
+
log.info("Uploading collection (%s)..." % collection_name)
|
|
397
|
+
collection = self.project.get_collection(collection_name)
|
|
398
|
+
# step 1: create required metadata
|
|
399
|
+
collection["modeler"].render_collection()
|
|
400
|
+
# step 2: upload collection to textgrid server
|
|
401
|
+
collection["modeler"].upload(
|
|
402
|
+
tg_session_id=self.tg_session_id,
|
|
403
|
+
tg_project_id=self.tg_project_id,
|
|
404
|
+
tg_server=self.instance,
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
def upload_other_files(self):
|
|
408
|
+
# def upload_other_files(self, instance="test"):
|
|
409
|
+
if not self.tg_session_id:
|
|
410
|
+
return False
|
|
411
|
+
log.info("Uploading other files...")
|
|
412
|
+
self.project_config.other_files.upload(
|
|
413
|
+
tg_session_id=self.tg_session_id,
|
|
414
|
+
tg_project_id=self.tg_project_id,
|
|
415
|
+
tg_server=self.instance,
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
def get_project_contents(self):
|
|
419
|
+
return self.tg_client.get_project_contents(self.tg_project_id)
|
|
420
|
+
|
|
421
|
+
def publish_project(self):
|
|
422
|
+
# def publish_project(self, instance="test"):
|
|
423
|
+
if not self.tg_session_id:
|
|
424
|
+
return False
|
|
425
|
+
log.info("Publishing project...")
|
|
426
|
+
self.tg_client.publish(self.tg_project_id)
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
class CollectionParser(object):
|
|
430
|
+
|
|
431
|
+
def __init__(self, collection):
|
|
432
|
+
self.collection = collection
|
|
433
|
+
self._elements = None
|
|
434
|
+
|
|
435
|
+
@property
|
|
436
|
+
def elements(self):
|
|
437
|
+
if self._elements is None:
|
|
438
|
+
self._elements = []
|
|
439
|
+
for file in self.collection.elements:
|
|
440
|
+
self._elements.append(
|
|
441
|
+
{
|
|
442
|
+
"file": file,
|
|
443
|
+
"tei_parser": TEIParser(fullpath=file["fullpath"]),
|
|
444
|
+
}
|
|
445
|
+
)
|
|
446
|
+
return self._elements
|
|
447
|
+
|
|
448
|
+
def test_xpath(self, xpath):
|
|
449
|
+
results = []
|
|
450
|
+
for element in self.elements:
|
|
451
|
+
result = element["tei_parser"].find(xpath)
|
|
452
|
+
if result is not None:
|
|
453
|
+
results.append(
|
|
454
|
+
{
|
|
455
|
+
"filename": element["file"]["filename"],
|
|
456
|
+
"result": result,
|
|
457
|
+
}
|
|
458
|
+
)
|
|
459
|
+
return {
|
|
460
|
+
"results": results,
|
|
461
|
+
"count": {
|
|
462
|
+
"total": len(self.collection.elements),
|
|
463
|
+
"found": len(results),
|
|
464
|
+
"percentage": round(
|
|
465
|
+
len(results) / len(self.collection.elements) * 100
|
|
466
|
+
),
|
|
467
|
+
},
|
|
468
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Copyright (C) 2023-2025 TU-Dresden (ZIH)
|
|
3
|
+
# ralf.klammer@tu-dresden.de
|
|
4
|
+
# moritz.wilhelm@tu-dresden.de
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
|
|
8
|
+
log = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SessionManager:
|
|
12
|
+
def __init__(self, session):
|
|
13
|
+
self.session = session
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SessionManagerNextcloud(SessionManager):
|
|
17
|
+
|
|
18
|
+
def save_credentials(self, form_data):
|
|
19
|
+
log.info("Saving credentials to session")
|
|
20
|
+
self.session["nextcloud"] = {
|
|
21
|
+
"username": form_data.get("nextcloud_user"),
|
|
22
|
+
"password": form_data.get("nextcloud_password"),
|
|
23
|
+
"url": form_data.get("nextcloud_url"),
|
|
24
|
+
"folder": form_data.get("nextcloud_folder", ""),
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
def get_credentials(self):
|
|
28
|
+
log.info("Retrieving credentials from session")
|
|
29
|
+
return self.session.get("nextcloud", {})
|
|
30
|
+
|
|
31
|
+
def delete_nextcloud_credentials(self):
|
|
32
|
+
log.info("Deleting credentials from session")
|
|
33
|
+
self.session.pop("nextcloud", None)
|
|
34
|
+
|
|
35
|
+
def request_has_valid_credentials(self, request):
|
|
36
|
+
"""
|
|
37
|
+
Check if the request contains valid Nextcloud credentials.
|
|
38
|
+
"""
|
|
39
|
+
relevant_form_keys = [
|
|
40
|
+
"nextcloud_url",
|
|
41
|
+
"nextcloud_user",
|
|
42
|
+
"nextcloud_password",
|
|
43
|
+
]
|
|
44
|
+
relevant_form_values = [
|
|
45
|
+
request.form.get(key) for key in relevant_form_keys
|
|
46
|
+
]
|
|
47
|
+
return all(relevant_form_values)
|