krest-utils 1.0.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.
- krest/__init__.py +0 -0
- krest/__main__.py +9 -0
- krest/krest_backend.py +404 -0
- krest/krest_logging.py +76 -0
- krest/krest_qt.py +62 -0
- krest/krest_tui.py +1293 -0
- krest/krest_tui.tcss +142 -0
- krest/translations.py +9158 -0
- krest_utils-1.0.0.data/data/share/applications/krest_tui.tcss +142 -0
- krest_utils-1.0.0.dist-info/METADATA +32 -0
- krest_utils-1.0.0.dist-info/RECORD +15 -0
- krest_utils-1.0.0.dist-info/WHEEL +5 -0
- krest_utils-1.0.0.dist-info/entry_points.txt +5 -0
- krest_utils-1.0.0.dist-info/licenses/LICENSE.txt +338 -0
- krest_utils-1.0.0.dist-info/top_level.txt +1 -0
krest/__init__.py
ADDED
|
File without changes
|
krest/__main__.py
ADDED
krest/krest_backend.py
ADDED
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import json
|
|
5
|
+
import shutil
|
|
6
|
+
# import time
|
|
7
|
+
import base64
|
|
8
|
+
from typing import Callable, List, Dict, Optional, Any, Self
|
|
9
|
+
from enum import Enum
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from importlib.metadata import version
|
|
12
|
+
import secrets
|
|
13
|
+
import httpx
|
|
14
|
+
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
|
15
|
+
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
|
16
|
+
from cryptography.hazmat.primitives import hashes
|
|
17
|
+
from krest.translations import t
|
|
18
|
+
|
|
19
|
+
print(t(t.REPLY_NAME, name=t(t.K_USER))) # testing i18n solution
|
|
20
|
+
|
|
21
|
+
def developer_log(message):
|
|
22
|
+
with open("/home/antototh/Downloads/krest_debug_log_developer.log", "a") as f:
|
|
23
|
+
f.write(message + "\n")
|
|
24
|
+
|
|
25
|
+
class CredentialType(Enum):
|
|
26
|
+
BASIC = ("Basic", "Basic Auth (" + t(t.BASICAUTH_DESC) + ")")
|
|
27
|
+
DIGEST = ("DigestAuth", "Digest Auth (" + t(t.DIGESTAUTH_DESC) + ")")
|
|
28
|
+
OAUTH2 = ("OAuth2", "OAuth 2.0 (" + t(t.OAUTH_DESC) + ")")
|
|
29
|
+
# HEADER_KEY = ("HeaderKey", "Header Key")
|
|
30
|
+
|
|
31
|
+
def __new__(cls: type["CredentialType"], value: str, display: str) -> "CredentialType":
|
|
32
|
+
obj = object.__new__(cls)
|
|
33
|
+
obj._value_ = value
|
|
34
|
+
obj.display = display
|
|
35
|
+
return obj
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Method(Enum):
|
|
39
|
+
GET = "GET"
|
|
40
|
+
POST = "POST"
|
|
41
|
+
PUT = "PUT"
|
|
42
|
+
DELETE = "DELETE"
|
|
43
|
+
HEAD = "HEAD"
|
|
44
|
+
OPTIONS = "OPTIONS"
|
|
45
|
+
PATCH = "PATCH"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def empty_endpoint() -> Dict[str, Any]:
|
|
49
|
+
return {
|
|
50
|
+
"id": None,
|
|
51
|
+
"name": "",
|
|
52
|
+
"desc": "",
|
|
53
|
+
"labels": [],
|
|
54
|
+
"credential_id": None,
|
|
55
|
+
"method": Method.GET.value,
|
|
56
|
+
"timeout": 150,
|
|
57
|
+
"url": "",
|
|
58
|
+
"headers": {"Accept": "text/html", "Accept-Charset": "utf-8", "Connection": "close", "Content-Type": "application/json"},
|
|
59
|
+
"body": None,
|
|
60
|
+
"params": {},
|
|
61
|
+
"follow_redirects": True,
|
|
62
|
+
"created_at": None,
|
|
63
|
+
"updated_at": None,
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
def empty_credential(credential_type: str) -> Dict[str, Any]:
|
|
67
|
+
empty_credential = {}
|
|
68
|
+
|
|
69
|
+
if credential_type == CredentialType.BASIC.value:
|
|
70
|
+
empty_credential = {
|
|
71
|
+
"id": None,
|
|
72
|
+
"name": "",
|
|
73
|
+
"desc": "",
|
|
74
|
+
"labels": [],
|
|
75
|
+
"credential_type": credential_type,
|
|
76
|
+
"username": "",
|
|
77
|
+
"password": "",
|
|
78
|
+
"created_at": None,
|
|
79
|
+
"updated_at": None,
|
|
80
|
+
}
|
|
81
|
+
elif credential_type == CredentialType.DIGEST.value:
|
|
82
|
+
empty_credential = {
|
|
83
|
+
"id": None,
|
|
84
|
+
"name": "",
|
|
85
|
+
"desc": "",
|
|
86
|
+
"labels": [],
|
|
87
|
+
"credential_type": credential_type,
|
|
88
|
+
"username": "",
|
|
89
|
+
"password": "",
|
|
90
|
+
"created_at": None,
|
|
91
|
+
"updated_at": None,
|
|
92
|
+
}
|
|
93
|
+
elif credential_type == CredentialType.OAUTH2.value:
|
|
94
|
+
empty_credential = {
|
|
95
|
+
"id": None,
|
|
96
|
+
"name": "",
|
|
97
|
+
"desc": "",
|
|
98
|
+
"labels": [],
|
|
99
|
+
"credential_type": credential_type,
|
|
100
|
+
"token_url": "",
|
|
101
|
+
"scope": None, # e.g. "urn:opc:idm:__myscopes__"
|
|
102
|
+
"client_id": "",
|
|
103
|
+
"client_secret": "",
|
|
104
|
+
"created_at": None,
|
|
105
|
+
"updated_at": None,
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return empty_credential
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
MAGIC_BYTES = b"KREST01"
|
|
112
|
+
FILE_EXTENSION = "krest"
|
|
113
|
+
CREDENTIAL_TYPES = [credential.value for credential in CredentialType]
|
|
114
|
+
METHODS = [method.value for method in Method]
|
|
115
|
+
EMPTY_FILE_DATA = {
|
|
116
|
+
"application": "krest",
|
|
117
|
+
"version": version("krest"),
|
|
118
|
+
"settings": {"is_backup_enabled": True},
|
|
119
|
+
"max_endpoint_id": -1,
|
|
120
|
+
"max_credential_id": -1,
|
|
121
|
+
"max_call_history_id": -1,
|
|
122
|
+
"credentials": [],
|
|
123
|
+
"endpoints": [],
|
|
124
|
+
"call_history": [],
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class KrestBackend:
|
|
129
|
+
"""Backend for the Krest application, handling file operations, business functions."""
|
|
130
|
+
|
|
131
|
+
__slots__ = ("file_path", "file_password", "file_data", "dirty_bit")
|
|
132
|
+
|
|
133
|
+
@staticmethod
|
|
134
|
+
def derive_key(password: str, salt: bytes, length=32, iterations=600_000) -> bytes:
|
|
135
|
+
"""Derive a key from a password and salt."""
|
|
136
|
+
|
|
137
|
+
kdf = PBKDF2HMAC(
|
|
138
|
+
algorithm=hashes.SHA256(),
|
|
139
|
+
length=length,
|
|
140
|
+
salt=salt,
|
|
141
|
+
iterations=iterations,
|
|
142
|
+
)
|
|
143
|
+
return kdf.derive(password.encode())
|
|
144
|
+
|
|
145
|
+
def __init__(self):
|
|
146
|
+
"""Constructor to initialize the backend."""
|
|
147
|
+
self.file_path: str|None = None
|
|
148
|
+
self.file_password: str|None = None
|
|
149
|
+
self.dirty_bit: int|None = None
|
|
150
|
+
self.file_data: Dict[str, Any]|None = EMPTY_FILE_DATA.copy()
|
|
151
|
+
|
|
152
|
+
def delete_endpoint(self, id: int) -> None:
|
|
153
|
+
"""Delete an endpoint from the file data."""
|
|
154
|
+
|
|
155
|
+
to_remove = next((d for d in self.file_data["endpoints"] if d["id"] == id), None)
|
|
156
|
+
|
|
157
|
+
if to_remove:
|
|
158
|
+
self.file_data["endpoints"].remove(to_remove)
|
|
159
|
+
self.dirty_bit = True
|
|
160
|
+
|
|
161
|
+
def save_endpoint(self, endpoint: Dict[str, Any]) -> int|None:
|
|
162
|
+
"""Add an endpoint to the file data."""
|
|
163
|
+
|
|
164
|
+
self.dirty_bit = True
|
|
165
|
+
|
|
166
|
+
if endpoint.get("id") is None:
|
|
167
|
+
next_id = self.file_data["max_endpoint_id"] + 1
|
|
168
|
+
new_endpoint = endpoint.copy()
|
|
169
|
+
new_endpoint["created_at"] = datetime.now().isoformat()
|
|
170
|
+
new_endpoint["id"] = next_id
|
|
171
|
+
self.file_data["endpoints"].append(new_endpoint)
|
|
172
|
+
self.file_data["max_endpoint_id"] = next_id
|
|
173
|
+
return next_id
|
|
174
|
+
else:
|
|
175
|
+
for i, act_endpoint in enumerate(self.file_data["endpoints"]):
|
|
176
|
+
if act_endpoint.get("id") == endpoint.get("id"):
|
|
177
|
+
endpoint["updated_at"] = datetime.now().isoformat()
|
|
178
|
+
self.file_data["endpoints"][i].update(endpoint)
|
|
179
|
+
break
|
|
180
|
+
return endpoint.get("id")
|
|
181
|
+
|
|
182
|
+
def save_credential(self, credential: Dict[str, Any]) -> int|None:
|
|
183
|
+
"""Add a credential to the file data."""
|
|
184
|
+
|
|
185
|
+
self.dirty_bit = True
|
|
186
|
+
|
|
187
|
+
if credential.get("id") is None:
|
|
188
|
+
next_id = self.file_data["max_credential_id"] + 1
|
|
189
|
+
new_credential = credential.copy()
|
|
190
|
+
new_credential["created_at"] = datetime.now().isoformat()
|
|
191
|
+
new_credential["id"] = next_id
|
|
192
|
+
self.file_data["credentials"].append(new_credential)
|
|
193
|
+
self.file_data["max_credential_id"] = next_id
|
|
194
|
+
return next_id
|
|
195
|
+
else:
|
|
196
|
+
for i, act_credential in enumerate(self.file_data["credentials"]):
|
|
197
|
+
if act_credential.get("id") == credential.get("id"):
|
|
198
|
+
credential["updated_at"] = datetime.now().isoformat()
|
|
199
|
+
self.file_data["credentials"][i].update(credential)
|
|
200
|
+
break
|
|
201
|
+
return credential.get("id")
|
|
202
|
+
|
|
203
|
+
def get_endpoint(self, id: int) -> Dict[str, Any]|None:
|
|
204
|
+
"""Get an endpoint by its ID."""
|
|
205
|
+
result = [endpoint for endpoint in self.file_data["endpoints"] if endpoint.get("id") == id]
|
|
206
|
+
return result[0] if result else None
|
|
207
|
+
|
|
208
|
+
def get_credential(self, id: int) -> Dict[str, Any]|None:
|
|
209
|
+
"""Get a credential by its ID."""
|
|
210
|
+
result = [cred for cred in self.file_data["credentials"] if cred["id"] == id]
|
|
211
|
+
return result[0] if result else None
|
|
212
|
+
|
|
213
|
+
def select_file(self, file_path: str|None = None, file_password: str|None = None):
|
|
214
|
+
"""Set the file path and password for the backend."""
|
|
215
|
+
if file_path is None:
|
|
216
|
+
raise ValueError(t(t.ERR_MISSING_FILEPATH))
|
|
217
|
+
self.file_path = file_path
|
|
218
|
+
self.file_password = file_password
|
|
219
|
+
|
|
220
|
+
def create_file(self, file_path: str|None = None, file_password: str|None = None):
|
|
221
|
+
"""Create an Empty File; """
|
|
222
|
+
self.file_data = EMPTY_FILE_DATA.copy()
|
|
223
|
+
self.select_file(file_path, file_password)
|
|
224
|
+
self.save_file()
|
|
225
|
+
|
|
226
|
+
def save_file(self):
|
|
227
|
+
"""Save the file data to the specified file path with encryption."""
|
|
228
|
+
# Check if file_path and file_password are set
|
|
229
|
+
# Check if file_data is not empty
|
|
230
|
+
# Create directory if it does not exist
|
|
231
|
+
# Check if backup file sign is enabled and create a backup if the file exists
|
|
232
|
+
|
|
233
|
+
if not self.file_path:
|
|
234
|
+
raise ValueError(t(t.ERR_MISSING_FILEPATH))
|
|
235
|
+
if not self.file_data:
|
|
236
|
+
raise ValueError(t(t.ERR_NODATA))
|
|
237
|
+
|
|
238
|
+
os.makedirs(os.path.dirname(self.file_path), exist_ok=True)
|
|
239
|
+
|
|
240
|
+
salt = secrets.token_bytes(16)
|
|
241
|
+
key = self.derive_key(self.file_password, salt)
|
|
242
|
+
aesgcm = AESGCM(key)
|
|
243
|
+
iv = secrets.token_bytes(12)
|
|
244
|
+
json_bytes = json.dumps(self.file_data).encode()
|
|
245
|
+
ciphertext = aesgcm.encrypt(iv, json_bytes, None)
|
|
246
|
+
encrypted_data = MAGIC_BYTES + salt + iv + ciphertext
|
|
247
|
+
|
|
248
|
+
with open(self.file_path + '.tmp', "wb") as f:
|
|
249
|
+
f.write(encrypted_data)
|
|
250
|
+
|
|
251
|
+
if self.file_data["settings"]["is_backup_enabled"] and os.path.exists(self.file_path):
|
|
252
|
+
backup_file_path = self.file_path + "." + str(datetime.now().strftime('%Y%m%dT%H%M%S')) + ".bak"
|
|
253
|
+
shutil.copy2(self.file_path, backup_file_path)
|
|
254
|
+
|
|
255
|
+
shutil.move(self.file_path + '.tmp', self.file_path)
|
|
256
|
+
|
|
257
|
+
self.dirty_bit = None
|
|
258
|
+
|
|
259
|
+
def load_file(self):
|
|
260
|
+
"""Load the file data from the specified file path with decryption."""
|
|
261
|
+
|
|
262
|
+
with open(self.file_path, "rb") as f:
|
|
263
|
+
encrypted_data = f.read()
|
|
264
|
+
magic_bytes, salt, iv, ciphertext = encrypted_data[:7], encrypted_data[7:23], encrypted_data[23:35], encrypted_data[35:]
|
|
265
|
+
if magic_bytes != MAGIC_BYTES:
|
|
266
|
+
# developer_log(f"Expected magic bytes: {MAGIC_BYTES}, found: {magic_bytes}")
|
|
267
|
+
raise ValueError(t(t.ERR_MAGICBYTES))
|
|
268
|
+
key = self.derive_key(self.file_password, salt)
|
|
269
|
+
aesgcm = AESGCM(key)
|
|
270
|
+
try:
|
|
271
|
+
json_bytes = aesgcm.decrypt(iv, ciphertext, None)
|
|
272
|
+
except Exception:
|
|
273
|
+
raise ValueError(t(t.ERR_INVALID_PASSWD))
|
|
274
|
+
self.file_data = json.loads(json_bytes)
|
|
275
|
+
|
|
276
|
+
self.dirty_bit = None
|
|
277
|
+
|
|
278
|
+
def log_response(self, call_type: str, endpoint_id: int, response: httpx.Response, endpoint: dict|None = None, credential: dict|None = None):
|
|
279
|
+
next_id = self.file_data["max_call_history_id"] + 1
|
|
280
|
+
|
|
281
|
+
# try:
|
|
282
|
+
# content = response.json()
|
|
283
|
+
# except ValueError:
|
|
284
|
+
# content = response.text
|
|
285
|
+
|
|
286
|
+
cookie_list = [vars(c) for c in response.cookies.jar]
|
|
287
|
+
|
|
288
|
+
self.file_data["call_history"].append({
|
|
289
|
+
"id": next_id,
|
|
290
|
+
"call_type": call_type,
|
|
291
|
+
"endpoint_id": endpoint_id,
|
|
292
|
+
"run_date": datetime.now().isoformat(),
|
|
293
|
+
"endpoint": (endpoint or {}).copy(),
|
|
294
|
+
"credential": (credential or {}).copy(),
|
|
295
|
+
"elapsed_time": response.elapsed.total_seconds() if response.elapsed else 0,
|
|
296
|
+
|
|
297
|
+
# "request_url": str(response.request.url),
|
|
298
|
+
# "request_headers": dict(response.request.headers), - endpoint should include
|
|
299
|
+
# "request_method": response.request.method, - endpoint should include
|
|
300
|
+
# "request_body": response.request.body if response.request.body else "", - endpoint should include
|
|
301
|
+
|
|
302
|
+
#"response_url": str(response.url),
|
|
303
|
+
"response_status_code": response.status_code,
|
|
304
|
+
"response_reason_phrase": response.reason_phrase,
|
|
305
|
+
|
|
306
|
+
"response_content_length": len(response.content) if response.content else 0,
|
|
307
|
+
"response_encoding": response.encoding,
|
|
308
|
+
"response_charset_encoding": response.charset_encoding,
|
|
309
|
+
# "response_ssl_version": response.raw.version if hasattr(response.raw, "version") else "",
|
|
310
|
+
"response_headers": dict(response.headers),
|
|
311
|
+
"cookies": cookie_list,
|
|
312
|
+
"response_body": response.text,
|
|
313
|
+
"response_content_base64": base64.b64encode(response.content).decode("ascii"), # Original byte Data: base64.b64decode(response_content_base64)
|
|
314
|
+
# "response_json": response.json(),
|
|
315
|
+
"response_http_version": response.http_version,
|
|
316
|
+
"response_links": response.links.copy(),
|
|
317
|
+
"response_has_redirect_location": response.has_redirect_location,
|
|
318
|
+
})
|
|
319
|
+
self.file_data["max_call_history_id"] = next_id
|
|
320
|
+
return next_id
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def run_endpoint(self, endpoint_id):
|
|
324
|
+
|
|
325
|
+
endpoint = self.get_endpoint(endpoint_id)
|
|
326
|
+
if endpoint == None:
|
|
327
|
+
raise ValueError(t(t.ERR_INVALID_ENDPOINT_ID, endpoint_id=endpoint_id))
|
|
328
|
+
|
|
329
|
+
def log_event(name):
|
|
330
|
+
return lambda *args, **kwargs: None #developer_log(f"{time.time():.2f} - {name}")
|
|
331
|
+
hooks = {
|
|
332
|
+
'request': [log_event('Request Started')],
|
|
333
|
+
'response': [log_event('Response Received')]
|
|
334
|
+
}
|
|
335
|
+
transport = httpx.HTTPTransport(local_address="0.0.0.0") # Force IPv4 transport
|
|
336
|
+
|
|
337
|
+
auth = None
|
|
338
|
+
request_headers = endpoint.get("headers", {})
|
|
339
|
+
request_data = endpoint.get("body")
|
|
340
|
+
credential_id = endpoint.get("credential_id", None)
|
|
341
|
+
credential = self.get_credential(credential_id)
|
|
342
|
+
credential_type = None
|
|
343
|
+
if credential:
|
|
344
|
+
credential_type = credential.get("credential_type", None)
|
|
345
|
+
|
|
346
|
+
if credential_type == CredentialType.BASIC.value:
|
|
347
|
+
auth = httpx.BasicAuth(credential.get("username", ""), credential.get("password", ""))
|
|
348
|
+
|
|
349
|
+
if credential_type == CredentialType.DIGEST.value:
|
|
350
|
+
auth = httpx.DigestAuth(username=credential.get("username", ""), password=credential.get("password", ""))
|
|
351
|
+
|
|
352
|
+
# if credential_type == CredentialType.HEADER_KEY.value:
|
|
353
|
+
# auth = None
|
|
354
|
+
# headers[credential.get("username")] = credential.get("password")
|
|
355
|
+
|
|
356
|
+
if credential_type == CredentialType.OAUTH2.value:
|
|
357
|
+
|
|
358
|
+
token_body = "grant_type=client_credentials"
|
|
359
|
+
if credential.get("scope", None):
|
|
360
|
+
token_body += "&scope=" + credential["scope"]
|
|
361
|
+
|
|
362
|
+
token_response = None
|
|
363
|
+
with httpx.Client(event_hooks=hooks, transport=transport) as client:
|
|
364
|
+
token_response = client.request(
|
|
365
|
+
method="POST",
|
|
366
|
+
url=credential.get("token_url"),
|
|
367
|
+
auth=httpx.BasicAuth(credential.get("client_id"), credential.get("client_secret")),
|
|
368
|
+
timeout=endpoint.get("timeout"),
|
|
369
|
+
headers={"Accept": "application/json", "Content-Type": "application/x-www-form-urlencoded"},
|
|
370
|
+
data=token_body,
|
|
371
|
+
follow_redirects=endpoint.get("follow_redirects")
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
self.log_response(t(t.TOKEN_REQUEST), endpoint_id, token_response, endpoint, credential)
|
|
375
|
+
token_response.raise_for_status()
|
|
376
|
+
|
|
377
|
+
token_json = token_response.json()
|
|
378
|
+
access_token = token_json["access_token"]
|
|
379
|
+
|
|
380
|
+
if access_token is None or access_token=="":
|
|
381
|
+
# developer_log(token_response.text)
|
|
382
|
+
raise ValueError(t(t.ERR_ACCESS_TOKEN))
|
|
383
|
+
|
|
384
|
+
auth = None
|
|
385
|
+
request_headers["Authorization"] = f"Bearer {access_token}"
|
|
386
|
+
|
|
387
|
+
response = None
|
|
388
|
+
with httpx.Client(event_hooks=hooks, transport=transport) as client:
|
|
389
|
+
response = client.request(
|
|
390
|
+
method=endpoint.get("method"),
|
|
391
|
+
url=endpoint.get("url"),
|
|
392
|
+
auth=auth,
|
|
393
|
+
timeout=endpoint.get("timeout"),
|
|
394
|
+
headers=request_headers,
|
|
395
|
+
data=request_data,
|
|
396
|
+
params=endpoint.get("params"),
|
|
397
|
+
follow_redirects=endpoint.get("follow_redirects")
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
self.log_response(t(t.SERVICE_REQUEST), endpoint_id, response, endpoint, credential)
|
|
401
|
+
self.dirty_bit = True
|
|
402
|
+
response.raise_for_status()
|
|
403
|
+
|
|
404
|
+
return response
|
krest/krest_logging.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
pass
|
|
4
|
+
|
|
5
|
+
# import os
|
|
6
|
+
# import sys
|
|
7
|
+
# import logging
|
|
8
|
+
#
|
|
9
|
+
# def user_log_dir(appname: str | None = None) -> str:
|
|
10
|
+
# # better solution: https://pypi.org/project/platformdirs/
|
|
11
|
+
# # from platformdirs import user_log_dir
|
|
12
|
+
#
|
|
13
|
+
# if sys.platform == "win32":
|
|
14
|
+
# # Windows
|
|
15
|
+
# path = os.environ.get("LOCALAPPDATA")
|
|
16
|
+
# if not path:
|
|
17
|
+
# path = os.path.expanduser("~\\AppData\\Local")
|
|
18
|
+
# if appname:
|
|
19
|
+
# path = os.path.join(path, appname, "Logs")
|
|
20
|
+
# else:
|
|
21
|
+
# path = os.path.join(path, "Logs")
|
|
22
|
+
#
|
|
23
|
+
# elif sys.platform == "darwin":
|
|
24
|
+
# # macOS
|
|
25
|
+
# path = os.path.expanduser("~/Library/Logs")
|
|
26
|
+
# if appname:
|
|
27
|
+
# path = os.path.join(path, appname)
|
|
28
|
+
#
|
|
29
|
+
# elif (os.getenv("ANDROID_DATA") == "/data" and os.getenv("ANDROID_ROOT") == "/system"):
|
|
30
|
+
# # Android
|
|
31
|
+
# base = None
|
|
32
|
+
# try:
|
|
33
|
+
# from android import mActivity # type: ignore
|
|
34
|
+
# context = mActivity.getApplicationContext()
|
|
35
|
+
# base = context.getCacheDir().getAbsolutePath()
|
|
36
|
+
# except Exception:
|
|
37
|
+
# try:
|
|
38
|
+
# from jnius import autoclass # type: ignore
|
|
39
|
+
# context = autoclass("android.content.Context")
|
|
40
|
+
# base = context.getCacheDir().getAbsolutePath()
|
|
41
|
+
# except Exception:
|
|
42
|
+
# import re
|
|
43
|
+
# pattern = re.compile(r"/data/(data|user/\d+)/(.+)/files")
|
|
44
|
+
# for p in sys.path:
|
|
45
|
+
# match = pattern.match(p)
|
|
46
|
+
# if match:
|
|
47
|
+
# base = p.split("/files")[0] + "/cache"
|
|
48
|
+
# if base is None:
|
|
49
|
+
# base = "/data/data/unknown/cache"
|
|
50
|
+
#
|
|
51
|
+
# if appname:
|
|
52
|
+
# path = os.path.join(base, appname, "log")
|
|
53
|
+
# else:
|
|
54
|
+
# path = os.path.join(base, "log")
|
|
55
|
+
# else:
|
|
56
|
+
# # Linux/Unix
|
|
57
|
+
# state_home = os.environ.get("XDG_STATE_HOME")
|
|
58
|
+
# if not state_home or not state_home.strip():
|
|
59
|
+
# state_home = os.path.expanduser("~/.local/state")
|
|
60
|
+
#
|
|
61
|
+
# if appname:
|
|
62
|
+
# path = os.path.join(state_home, appname, "log")
|
|
63
|
+
# else:
|
|
64
|
+
# path = os.path.join(state_home, "log")
|
|
65
|
+
#
|
|
66
|
+
# return path
|
|
67
|
+
#
|
|
68
|
+
# def setup_logging():
|
|
69
|
+
# log_dir = user_log_dir("Krest")
|
|
70
|
+
# os.makedirs(log_dir, exist_ok=True)
|
|
71
|
+
# log_file = os.path.join(log_dir, 'krest.log')
|
|
72
|
+
# print("[I] Log File:", log_file)
|
|
73
|
+
# logging.basicConfig(filename=log_file, level=logging.DEBUG, force=True)
|
|
74
|
+
#
|
|
75
|
+
# setup_logging()
|
|
76
|
+
#
|
krest/krest_qt.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
# import os
|
|
4
|
+
# import gc
|
|
5
|
+
import locale
|
|
6
|
+
|
|
7
|
+
# import sys
|
|
8
|
+
# import signal
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
|
|
12
|
+
# from importlib.resources import files
|
|
13
|
+
|
|
14
|
+
# from PySide6.QtGui import QGuiApplication
|
|
15
|
+
# from PySide6.QtCore import QUrl
|
|
16
|
+
# from PySide6.QtQml import QQmlApplicationEngine
|
|
17
|
+
|
|
18
|
+
from krest.krest_backend import setup_logging, KrestBackend
|
|
19
|
+
# from krest.translations import t
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
lang, encoding = locale.getdefaultlocale()
|
|
23
|
+
|
|
24
|
+
def qt():
|
|
25
|
+
# TODO: QT/Kirigami UI
|
|
26
|
+
|
|
27
|
+
return "This feature is yet not implemented"
|
|
28
|
+
|
|
29
|
+
logging.info("connct backend...")
|
|
30
|
+
backend = KrestBackend()
|
|
31
|
+
print("File version: " + backend.file_data["version"])
|
|
32
|
+
|
|
33
|
+
app = QGuiApplication(sys.argv)
|
|
34
|
+
engine = QQmlApplicationEngine()
|
|
35
|
+
|
|
36
|
+
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
|
37
|
+
|
|
38
|
+
if not os.environ.get("QT_QUICK_CONTROLS_STYLE"):
|
|
39
|
+
os.environ["QT_QUICK_CONTROLS_STYLE"] = "org.kde.desktop"
|
|
40
|
+
|
|
41
|
+
base_path = files('krest').joinpath('qml', 'Main.qml')
|
|
42
|
+
url = QUrl(f"{base_path}")
|
|
43
|
+
|
|
44
|
+
engine.load(url)
|
|
45
|
+
|
|
46
|
+
if len(engine.rootObjects()) == 0:
|
|
47
|
+
quit()
|
|
48
|
+
|
|
49
|
+
app.exec()
|
|
50
|
+
|
|
51
|
+
def main():
|
|
52
|
+
setup_logging()
|
|
53
|
+
logging.info("Start main()...")
|
|
54
|
+
|
|
55
|
+
logging.info("start QT...")
|
|
56
|
+
qt()
|
|
57
|
+
|
|
58
|
+
logging.info("End main().")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
if __name__ == "__main__":
|
|
62
|
+
main()
|