unit3dprep 1.0.4__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.
- unit3dprep/__init__.py +0 -0
- unit3dprep/cli.py +256 -0
- unit3dprep/core.py +556 -0
- unit3dprep/i18n.py +151 -0
- unit3dprep/media.py +278 -0
- unit3dprep/upload.py +146 -0
- unit3dprep/web/__init__.py +0 -0
- unit3dprep/web/_env.py +40 -0
- unit3dprep/web/api/__init__.py +1 -0
- unit3dprep/web/api/auth.py +36 -0
- unit3dprep/web/api/fs.py +64 -0
- unit3dprep/web/api/library.py +503 -0
- unit3dprep/web/api/logs.py +42 -0
- unit3dprep/web/api/queue.py +54 -0
- unit3dprep/web/api/quickupload.py +153 -0
- unit3dprep/web/api/search.py +40 -0
- unit3dprep/web/api/settings.py +77 -0
- unit3dprep/web/api/tmdb.py +77 -0
- unit3dprep/web/api/trackers.py +30 -0
- unit3dprep/web/api/uploaded.py +26 -0
- unit3dprep/web/api/version.py +754 -0
- unit3dprep/web/api/webup.py +59 -0
- unit3dprep/web/api/wizard.py +512 -0
- unit3dprep/web/app.py +201 -0
- unit3dprep/web/auth.py +41 -0
- unit3dprep/web/clients.py +167 -0
- unit3dprep/web/config.py +932 -0
- unit3dprep/web/db.py +184 -0
- unit3dprep/web/dist/assets/JetBrainsMono-Italic-VariableFont_wght-CZO9PUqx.ttf +0 -0
- unit3dprep/web/dist/assets/JetBrainsMono-VariableFont_wght-BrlcHZ7m.ttf +0 -0
- unit3dprep/web/dist/assets/SpaceGrotesk-VariableFont_wght-DIScfSlK.ttf +0 -0
- unit3dprep/web/dist/assets/index-BizNr_oP.js +255 -0
- unit3dprep/web/dist/assets/index-DChRHChM.css +1 -0
- unit3dprep/web/dist/index.html +14 -0
- unit3dprep/web/duplicate_check.py +92 -0
- unit3dprep/web/lang_cache.py +118 -0
- unit3dprep/web/logbuf.py +164 -0
- unit3dprep/web/tmdb_cache.py +116 -0
- unit3dprep/web/trackers.py +177 -0
- unit3dprep/web/webup_client.py +190 -0
- unit3dprep/web/webup_job_fix.py +231 -0
- unit3dprep/web/webup_logclass.py +107 -0
- unit3dprep/web/webup_orchestrator.py +796 -0
- unit3dprep/web/webup_ws.py +141 -0
- unit3dprep-1.0.4.dist-info/METADATA +819 -0
- unit3dprep-1.0.4.dist-info/RECORD +50 -0
- unit3dprep-1.0.4.dist-info/WHEEL +5 -0
- unit3dprep-1.0.4.dist-info/entry_points.txt +3 -0
- unit3dprep-1.0.4.dist-info/licenses/LICENSE +674 -0
- unit3dprep-1.0.4.dist-info/top_level.txt +1 -0
unit3dprep/web/db.py
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"""Upload history — JSON file store. No sqlite3 dependency."""
|
|
2
|
+
import asyncio
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import threading
|
|
6
|
+
import time
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
_lock = threading.Lock()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _db_path() -> Path:
|
|
13
|
+
"""Re-resolve on every call so UI edits take effect without restart."""
|
|
14
|
+
default = str(Path.home() / ".unit3dprep_db.json")
|
|
15
|
+
try:
|
|
16
|
+
from . import config
|
|
17
|
+
return Path(config.runtime_setting("U3DP_DB_PATH", default=default))
|
|
18
|
+
except Exception:
|
|
19
|
+
from ._env import env as _env
|
|
20
|
+
return Path(_env("U3DP_DB_PATH", "ITA_DB_PATH", default) or default)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# Back-compat alias — resolved at import time (tests, external callers).
|
|
24
|
+
DB_PATH = _db_path()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _load() -> list[dict]:
|
|
28
|
+
path = _db_path()
|
|
29
|
+
if not path.exists():
|
|
30
|
+
return []
|
|
31
|
+
try:
|
|
32
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
33
|
+
return json.load(f)
|
|
34
|
+
except (json.JSONDecodeError, OSError):
|
|
35
|
+
return []
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _save(records: list[dict]):
|
|
39
|
+
path = _db_path()
|
|
40
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
41
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
42
|
+
json.dump(records, f, indent=2, ensure_ascii=False)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _now_iso() -> str:
|
|
46
|
+
return time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime())
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _init_db_sync():
|
|
50
|
+
with _lock:
|
|
51
|
+
if not _db_path().exists():
|
|
52
|
+
_save([])
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _record_upload_sync(
|
|
56
|
+
category, kind, source_path, seeding_path,
|
|
57
|
+
tmdb_id, title, year, final_name, exit_code, hardlink_only=False,
|
|
58
|
+
duplicate_skipped=False, duplicate_info=None,
|
|
59
|
+
):
|
|
60
|
+
with _lock:
|
|
61
|
+
records = _load()
|
|
62
|
+
# upsert by seeding_path
|
|
63
|
+
for r in records:
|
|
64
|
+
if r["seeding_path"] == seeding_path:
|
|
65
|
+
r["unit3dup_exit_code"] = exit_code
|
|
66
|
+
r["uploaded_at"] = _now_iso()
|
|
67
|
+
r["hardlink_only"] = hardlink_only
|
|
68
|
+
r["duplicate_skipped"] = duplicate_skipped
|
|
69
|
+
if duplicate_info is not None:
|
|
70
|
+
r["duplicate_info"] = duplicate_info
|
|
71
|
+
if source_path:
|
|
72
|
+
r["source_path"] = source_path
|
|
73
|
+
if tmdb_id:
|
|
74
|
+
r["tmdb_id"] = tmdb_id
|
|
75
|
+
if title:
|
|
76
|
+
r["title"] = title
|
|
77
|
+
if year:
|
|
78
|
+
r["year"] = year
|
|
79
|
+
if final_name:
|
|
80
|
+
r["final_name"] = final_name
|
|
81
|
+
_save(records)
|
|
82
|
+
return
|
|
83
|
+
next_id = max((r.get("id", 0) for r in records), default=0) + 1
|
|
84
|
+
records.append({
|
|
85
|
+
"id": next_id,
|
|
86
|
+
"category": category,
|
|
87
|
+
"kind": kind,
|
|
88
|
+
"source_path": source_path,
|
|
89
|
+
"seeding_path": seeding_path,
|
|
90
|
+
"tmdb_id": tmdb_id or "",
|
|
91
|
+
"title": title or "",
|
|
92
|
+
"year": year or "",
|
|
93
|
+
"final_name": final_name or "",
|
|
94
|
+
"uploaded_at": _now_iso(),
|
|
95
|
+
"unit3dup_exit_code": exit_code,
|
|
96
|
+
"hardlink_only": hardlink_only,
|
|
97
|
+
"duplicate_skipped": duplicate_skipped,
|
|
98
|
+
"duplicate_info": duplicate_info,
|
|
99
|
+
})
|
|
100
|
+
_save(records)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _update_exit_code_sync(seeding_path: str, exit_code: int):
|
|
104
|
+
with _lock:
|
|
105
|
+
records = _load()
|
|
106
|
+
for r in records:
|
|
107
|
+
if r["seeding_path"] == seeding_path:
|
|
108
|
+
r["unit3dup_exit_code"] = exit_code
|
|
109
|
+
_save(records)
|
|
110
|
+
return
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _list_uploads_sync() -> list[dict]:
|
|
114
|
+
with _lock:
|
|
115
|
+
records = _load()
|
|
116
|
+
return sorted(records, key=lambda r: r.get("uploaded_at", ""), reverse=True)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _get_by_seeding_sync(seeding_path: str) -> dict | None:
|
|
120
|
+
with _lock:
|
|
121
|
+
records = _load()
|
|
122
|
+
for r in records:
|
|
123
|
+
if r["seeding_path"] == seeding_path:
|
|
124
|
+
return r
|
|
125
|
+
return None
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _delete_record_sync(seeding_path: str):
|
|
129
|
+
with _lock:
|
|
130
|
+
records = _load()
|
|
131
|
+
records = [r for r in records if r["seeding_path"] != seeding_path]
|
|
132
|
+
_save(records)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
# ---------------------------------------------------------------------------
|
|
136
|
+
# Async wrappers
|
|
137
|
+
# ---------------------------------------------------------------------------
|
|
138
|
+
|
|
139
|
+
async def _run(fn, *args):
|
|
140
|
+
loop = asyncio.get_event_loop()
|
|
141
|
+
return await loop.run_in_executor(None, fn, *args)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
async def init_db():
|
|
145
|
+
await _run(_init_db_sync)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
async def record_upload(
|
|
149
|
+
*,
|
|
150
|
+
category: str,
|
|
151
|
+
kind: str,
|
|
152
|
+
source_path: str,
|
|
153
|
+
seeding_path: str,
|
|
154
|
+
tmdb_id: str = "",
|
|
155
|
+
title: str = "",
|
|
156
|
+
year: str = "",
|
|
157
|
+
final_name: str = "",
|
|
158
|
+
exit_code: int | None = None,
|
|
159
|
+
hardlink_only: bool = False,
|
|
160
|
+
duplicate_skipped: bool = False,
|
|
161
|
+
duplicate_info: dict | None = None,
|
|
162
|
+
):
|
|
163
|
+
await _run(
|
|
164
|
+
_record_upload_sync,
|
|
165
|
+
category, kind, source_path, seeding_path,
|
|
166
|
+
tmdb_id, title, year, final_name, exit_code, hardlink_only,
|
|
167
|
+
duplicate_skipped, duplicate_info,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
async def update_exit_code(seeding_path: str, exit_code: int):
|
|
172
|
+
await _run(_update_exit_code_sync, seeding_path, exit_code)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
async def list_uploads() -> list[dict]:
|
|
176
|
+
return await _run(_list_uploads_sync)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
async def get_upload_by_seeding_path(seeding_path: str) -> dict | None:
|
|
180
|
+
return await _run(_get_by_seeding_sync, seeding_path)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
async def delete_record(seeding_path: str):
|
|
184
|
+
await _run(_delete_record_sync, seeding_path)
|
|
Binary file
|
|
Binary file
|