pyupdator 0.1.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.
- examples/basic_usage.py +66 -0
- pyupdator/__init__.py +410 -0
- pyupdator/core/__init__.py +74 -0
- pyupdator/core/backend_manager.py +264 -0
- pyupdator/core/downloaders/__init__.py +26 -0
- pyupdator/core/downloaders/aria2_dl.py +281 -0
- pyupdator/core/downloaders/base.py +108 -0
- pyupdator/core/downloaders/http_dl.py +832 -0
- pyupdator/core/downloaders/http_zip_delta.py +2070 -0
- pyupdator/core/downloaders/libtorrent_dl.py +383 -0
- pyupdator/core/downloaders/manual_dl.py +141 -0
- pyupdator/core/downloaders/qbittorrent_dl.py +935 -0
- pyupdator/core/downloaders/transmission_dl.py +154 -0
- pyupdator/core/downloaders/utorrent_dl.py +245 -0
- pyupdator/core/torrent.py +1103 -0
- pyupdator/core/zip_engine.py +630 -0
- pyupdator/utils/__init__.py +38 -0
- pyupdator/utils/helpers.py +186 -0
- pyupdator-0.1.0.dist-info/METADATA +165 -0
- pyupdator-0.1.0.dist-info/RECORD +42 -0
- pyupdator-0.1.0.dist-info/WHEEL +5 -0
- pyupdator-0.1.0.dist-info/entry_points.txt +2 -0
- pyupdator-0.1.0.dist-info/top_level.txt +4 -0
- pyupdator_gui/__init__.py +52 -0
- pyupdator_gui/main_window.py +1072 -0
- tests/cdc_test_server.py +211 -0
- tests/test_cdc_delta.py +405 -0
- tests/test_comprehensive.py +240 -0
- tests/test_final_qbittorrent_fix.py +171 -0
- tests/test_html_manifest_fallback.py +474 -0
- tests/test_libtorrent_virtual.py +292 -0
- tests/test_qbittorrent.py +60 -0
- tests/test_qbittorrent_api.py +73 -0
- tests/test_qbittorrent_endpoints.py +63 -0
- tests/test_qbittorrent_fix.py +184 -0
- tests/test_qbittorrent_fix_fixed.py +184 -0
- tests/test_qbittorrent_info.py +39 -0
- tests/test_qbittorrent_new_logic.py +168 -0
- tests/test_qbittorrent_pause.py +48 -0
- tests/test_zip_block_delta.py +368 -0
- tests/test_zip_delta.py +329 -0
- tests/test_zip_root_detect.py +257 -0
examples/basic_usage.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""
|
|
2
|
+
examples/basic_usage.py — GUI-free example showing how to use pyupdator.
|
|
3
|
+
|
|
4
|
+
This is the canonical example for a game launcher or other host project.
|
|
5
|
+
|
|
6
|
+
pip install pyupdator
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
python examples/basic_usage.py
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import logging
|
|
15
|
+
import sys
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
# Ensure the local pyupdator package is importable when running from the repo.
|
|
19
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
20
|
+
|
|
21
|
+
from pyupdator import Updater, UpdaterError, human_bytes
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def progress_callback(done: int, total: int, message: str = "") -> None:
|
|
25
|
+
"""Host-provided progress callback."""
|
|
26
|
+
if total:
|
|
27
|
+
pct = done / total * 100
|
|
28
|
+
print(f"[{pct:6.2f}%] {message} ({human_bytes(done)} / {human_bytes(total)})")
|
|
29
|
+
else:
|
|
30
|
+
print(f"{message} ({human_bytes(done)})")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def main() -> int:
|
|
34
|
+
logging.basicConfig(
|
|
35
|
+
level=logging.INFO,
|
|
36
|
+
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# The host launcher decides install_dir and source.
|
|
40
|
+
install_dir = Path("C:/Oyun") # <-- change to your game folder
|
|
41
|
+
source = "oyun.torrent" # <-- change to your .torrent or HTTP URL
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
updater = Updater(install_dir=str(install_dir), source=source)
|
|
45
|
+
|
|
46
|
+
# 1) Analyze what needs to be downloaded / verified.
|
|
47
|
+
plan = updater.analyze()
|
|
48
|
+
print("Analiz tamamlandı. Plan:", plan)
|
|
49
|
+
|
|
50
|
+
# 2) Apply the update, reporting progress back to the host.
|
|
51
|
+
updater.update(progress_callback=progress_callback)
|
|
52
|
+
print("Güncelleme tamamlandı.")
|
|
53
|
+
|
|
54
|
+
# 3) (Optional) verify the installed files against the torrent.
|
|
55
|
+
result = updater.verify()
|
|
56
|
+
print("Doğrulama sonucu:", result)
|
|
57
|
+
|
|
58
|
+
except UpdaterError as exc:
|
|
59
|
+
print(f"Hata: {exc}", file=sys.stderr)
|
|
60
|
+
return 1
|
|
61
|
+
|
|
62
|
+
return 0
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
if __name__ == "__main__":
|
|
66
|
+
raise SystemExit(main())
|
pyupdator/__init__.py
ADDED
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
"""
|
|
2
|
+
pyupdator — Delta updater for games (Torrent and HTTP Zip support).
|
|
3
|
+
|
|
4
|
+
Public API:
|
|
5
|
+
|
|
6
|
+
from pyupdator import Updater
|
|
7
|
+
|
|
8
|
+
updater = Updater(install_dir="C:/Oyun", source="oyun.torrent")
|
|
9
|
+
plan = updater.analyze()
|
|
10
|
+
updater.update(progress_callback=my_func)
|
|
11
|
+
|
|
12
|
+
The ``pyupdator`` package contains NO tkinter / customtkinter / gui imports.
|
|
13
|
+
GUI code lives in the separate ``pyupdator_gui`` package.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import logging
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Any, Callable, Dict, Optional, Union
|
|
21
|
+
|
|
22
|
+
from .core import (
|
|
23
|
+
TorrentParser,
|
|
24
|
+
PieceMap,
|
|
25
|
+
DeltaPlan,
|
|
26
|
+
DeltaAnalyzer,
|
|
27
|
+
DeltaApplier,
|
|
28
|
+
Verifier,
|
|
29
|
+
TorrentError,
|
|
30
|
+
NeedDownloadError,
|
|
31
|
+
ZipDeltaEngine,
|
|
32
|
+
ZipEntry,
|
|
33
|
+
ZipIndex,
|
|
34
|
+
ZipDeltaError,
|
|
35
|
+
HttpRangeClient,
|
|
36
|
+
chunk_file,
|
|
37
|
+
generate_manifest,
|
|
38
|
+
manifest_stats,
|
|
39
|
+
BackendManager,
|
|
40
|
+
BackendInfo,
|
|
41
|
+
BackendScanResult,
|
|
42
|
+
BackendError,
|
|
43
|
+
PIECE_LEVEL,
|
|
44
|
+
FILE_LEVEL,
|
|
45
|
+
CHUNK_LEVEL,
|
|
46
|
+
AUTO_ORDER,
|
|
47
|
+
scan_backends,
|
|
48
|
+
get_backend_class,
|
|
49
|
+
)
|
|
50
|
+
from .utils import (
|
|
51
|
+
human_bytes,
|
|
52
|
+
sha1_hex,
|
|
53
|
+
sha256_file,
|
|
54
|
+
safe_join,
|
|
55
|
+
list_files,
|
|
56
|
+
read_json,
|
|
57
|
+
write_json,
|
|
58
|
+
load_config,
|
|
59
|
+
save_config,
|
|
60
|
+
UpdaterError,
|
|
61
|
+
DEFAULT_CONFIG,
|
|
62
|
+
CONFIG_PATH,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
__version__ = "0.1.0"
|
|
66
|
+
|
|
67
|
+
__all__ = [
|
|
68
|
+
"Updater",
|
|
69
|
+
"UpdaterError",
|
|
70
|
+
"TorrentError",
|
|
71
|
+
"NeedDownloadError",
|
|
72
|
+
"ZipDeltaError",
|
|
73
|
+
"BackendError",
|
|
74
|
+
# core classes
|
|
75
|
+
"TorrentParser",
|
|
76
|
+
"PieceMap",
|
|
77
|
+
"DeltaPlan",
|
|
78
|
+
"DeltaAnalyzer",
|
|
79
|
+
"DeltaApplier",
|
|
80
|
+
"Verifier",
|
|
81
|
+
"ZipDeltaEngine",
|
|
82
|
+
"ZipEntry",
|
|
83
|
+
"ZipIndex",
|
|
84
|
+
"HttpRangeClient",
|
|
85
|
+
"BackendManager",
|
|
86
|
+
"BackendInfo",
|
|
87
|
+
"BackendScanResult",
|
|
88
|
+
# constants
|
|
89
|
+
"PIECE_LEVEL",
|
|
90
|
+
"FILE_LEVEL",
|
|
91
|
+
"CHUNK_LEVEL",
|
|
92
|
+
"AUTO_ORDER",
|
|
93
|
+
# functions
|
|
94
|
+
"chunk_file",
|
|
95
|
+
"generate_manifest",
|
|
96
|
+
"manifest_stats",
|
|
97
|
+
"scan_backends",
|
|
98
|
+
"get_backend_class",
|
|
99
|
+
"human_bytes",
|
|
100
|
+
"sha1_hex",
|
|
101
|
+
"sha256_file",
|
|
102
|
+
"safe_join",
|
|
103
|
+
"list_files",
|
|
104
|
+
"read_json",
|
|
105
|
+
"write_json",
|
|
106
|
+
"load_config",
|
|
107
|
+
"save_config",
|
|
108
|
+
# version
|
|
109
|
+
"__version__",
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
# ---------------------------------------------------------------------------
|
|
113
|
+
# Paket düzeyinde logger; host launcher kendi handler'ını ekleyebilir.
|
|
114
|
+
# ---------------------------------------------------------------------------
|
|
115
|
+
|
|
116
|
+
logger = logging.getLogger("pyupdator")
|
|
117
|
+
_logger_initialized = False
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _ensure_logging() -> None:
|
|
121
|
+
"""Varsayılan logging yapılandırmasını tek seferlik kurar."""
|
|
122
|
+
global _logger_initialized
|
|
123
|
+
if _logger_initialized:
|
|
124
|
+
return
|
|
125
|
+
_logger_initialized = True
|
|
126
|
+
logging.basicConfig(
|
|
127
|
+
level=logging.INFO,
|
|
128
|
+
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def set_log_level(level: Union[int, str]) -> None:
|
|
133
|
+
"""Kütüphane genel log seviyesini ayarlar."""
|
|
134
|
+
_ensure_logging()
|
|
135
|
+
logger.setLevel(level)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def add_log_handler(handler: logging.Handler) -> None:
|
|
139
|
+
"""Host launcher'ın kendi log handler'ını ekler (ör. GUI konsolu)."""
|
|
140
|
+
_ensure_logging()
|
|
141
|
+
logger.addHandler(handler)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def remove_log_handler(handler: logging.Handler) -> None:
|
|
145
|
+
"""Önceden eklenen handler'ı çıkarır."""
|
|
146
|
+
logger.removeHandler(handler)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
# ---------------------------------------------------------------------------
|
|
150
|
+
# Updater — Ana API sınıfı
|
|
151
|
+
# ---------------------------------------------------------------------------
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class Updater:
|
|
155
|
+
"""
|
|
156
|
+
Ana güncelleme motoru.
|
|
157
|
+
|
|
158
|
+
Sözleşme:
|
|
159
|
+
|
|
160
|
+
from pyupdator import Updater
|
|
161
|
+
updater = Updater(install_dir="C:/Oyun", source="oyun.torrent")
|
|
162
|
+
plan = updater.analyze()
|
|
163
|
+
updater.update(progress_callback=my_func)
|
|
164
|
+
|
|
165
|
+
``source`` hem .torrent dosya yolu hem de http/https URL'i olabilir.
|
|
166
|
+
"""
|
|
167
|
+
|
|
168
|
+
def __init__(
|
|
169
|
+
self,
|
|
170
|
+
install_dir: Union[str, Path],
|
|
171
|
+
source: Union[str, Path],
|
|
172
|
+
torrent_root: Optional[Union[str, Path]] = None,
|
|
173
|
+
source_dir: Optional[Union[str, Path]] = None,
|
|
174
|
+
allow_delete: bool = False,
|
|
175
|
+
do_backup: bool = True,
|
|
176
|
+
logger_cb: Optional[Callable[[str], None]] = None,
|
|
177
|
+
config: Optional[Dict[str, Any]] = None,
|
|
178
|
+
):
|
|
179
|
+
self.install_dir = Path(install_dir).resolve()
|
|
180
|
+
self.source = str(source)
|
|
181
|
+
self.torrent_root = Path(torrent_root).resolve() if torrent_root else None
|
|
182
|
+
self.source_dir = Path(source_dir).resolve() if source_dir else None
|
|
183
|
+
self.allow_delete = allow_delete
|
|
184
|
+
self.do_backup = do_backup
|
|
185
|
+
self.logger_cb = logger_cb or (lambda msg: logger.info(msg))
|
|
186
|
+
|
|
187
|
+
self.config = config or load_config()
|
|
188
|
+
|
|
189
|
+
# Durum
|
|
190
|
+
self._plan: Optional[DeltaPlan] = None
|
|
191
|
+
_ensure_logging()
|
|
192
|
+
|
|
193
|
+
# Kaynak türünü belirle (http/https ise ZIP delta / CDC, değilse torrent)
|
|
194
|
+
self._is_http = self.source.startswith(("http://", "https://"))
|
|
195
|
+
|
|
196
|
+
# ------------------------------------------------------------------
|
|
197
|
+
# Günlükleme
|
|
198
|
+
# ------------------------------------------------------------------
|
|
199
|
+
|
|
200
|
+
def _log(self, msg: str) -> None:
|
|
201
|
+
self.logger_cb(msg)
|
|
202
|
+
logger.info(msg)
|
|
203
|
+
|
|
204
|
+
# ------------------------------------------------------------------
|
|
205
|
+
# Analyze
|
|
206
|
+
# ------------------------------------------------------------------
|
|
207
|
+
|
|
208
|
+
def analyze(self) -> Dict[str, Any]:
|
|
209
|
+
"""
|
|
210
|
+
Kurulu klasörü kaynakla karşılaştırır ve güncelleme planı üretir.
|
|
211
|
+
|
|
212
|
+
Döner: plan sözlüğü (DeltaPlan.to_dict()).
|
|
213
|
+
"""
|
|
214
|
+
if self._is_http:
|
|
215
|
+
return self._analyze_http()
|
|
216
|
+
return self._analyze_torrent()
|
|
217
|
+
|
|
218
|
+
def _analyze_torrent(self) -> Dict[str, Any]:
|
|
219
|
+
analyzer = DeltaAnalyzer(
|
|
220
|
+
installed_dir=self.install_dir,
|
|
221
|
+
torrent_path=self.source,
|
|
222
|
+
torrent_root=self.torrent_root,
|
|
223
|
+
logger=self._log,
|
|
224
|
+
)
|
|
225
|
+
plan = analyzer.analyze()
|
|
226
|
+
self._plan = plan
|
|
227
|
+
return plan.to_dict()
|
|
228
|
+
|
|
229
|
+
def _analyze_http(self) -> Dict[str, Any]:
|
|
230
|
+
"""
|
|
231
|
+
HTTP kaynağı (ZIP) için analiz.
|
|
232
|
+
|
|
233
|
+
ZIP delta motoru kullanılarak uzak ZIP'in Central Directory'si
|
|
234
|
+
okunur ve yerel klasörle karşılaştırılır.
|
|
235
|
+
"""
|
|
236
|
+
engine = ZipDeltaEngine(
|
|
237
|
+
url=self.source,
|
|
238
|
+
local_dir=self.install_dir,
|
|
239
|
+
logger_cb=self._log,
|
|
240
|
+
)
|
|
241
|
+
index = engine.fetch_index()
|
|
242
|
+
changed = engine.compare(index)
|
|
243
|
+
|
|
244
|
+
plan: Dict[str, Any] = {
|
|
245
|
+
"mode": "ZIP_DELTA",
|
|
246
|
+
"url": self.source,
|
|
247
|
+
"install_dir": str(self.install_dir),
|
|
248
|
+
"total_entries": len(index.entries),
|
|
249
|
+
"to_download": [e.to_dict() for e in changed],
|
|
250
|
+
"unchanged": [
|
|
251
|
+
e.to_dict() for e in index.entries
|
|
252
|
+
if not e.is_dir and e not in changed
|
|
253
|
+
],
|
|
254
|
+
"stats": {
|
|
255
|
+
"total_compressed": index.total_compressed,
|
|
256
|
+
"changed_count": len(changed),
|
|
257
|
+
},
|
|
258
|
+
}
|
|
259
|
+
self._plan = plan # type: ignore[assignment]
|
|
260
|
+
self._log(
|
|
261
|
+
f"HTTP ZIP analiz tamam: {len(changed)} dosya değişecek / "
|
|
262
|
+
f"{index.total_entries} toplam giriş"
|
|
263
|
+
)
|
|
264
|
+
return plan
|
|
265
|
+
|
|
266
|
+
# ------------------------------------------------------------------
|
|
267
|
+
# Update
|
|
268
|
+
# ------------------------------------------------------------------
|
|
269
|
+
|
|
270
|
+
def update(
|
|
271
|
+
self,
|
|
272
|
+
progress_callback: Optional[Callable[[int, int, str], None]] = None,
|
|
273
|
+
allow_delete: Optional[bool] = None,
|
|
274
|
+
do_backup: Optional[bool] = None,
|
|
275
|
+
dry_run: bool = False,
|
|
276
|
+
) -> Dict[str, Any]:
|
|
277
|
+
"""
|
|
278
|
+
Mevcut planı uygular.
|
|
279
|
+
|
|
280
|
+
Eğer ``analyze()`` henüz çağrılmadıysa otomatik olarak çağrılır.
|
|
281
|
+
|
|
282
|
+
Döner: uygulama özeti sözlüğü.
|
|
283
|
+
"""
|
|
284
|
+
if self._plan is None:
|
|
285
|
+
self.analyze()
|
|
286
|
+
|
|
287
|
+
if self._is_http:
|
|
288
|
+
return self._update_http(progress_callback=progress_callback)
|
|
289
|
+
|
|
290
|
+
return self._update_torrent(
|
|
291
|
+
progress_callback=progress_callback,
|
|
292
|
+
allow_delete=allow_delete if allow_delete is not None else self.allow_delete,
|
|
293
|
+
do_backup=do_backup if do_backup is not None else self.do_backup,
|
|
294
|
+
dry_run=dry_run,
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
def _update_torrent(
|
|
298
|
+
self,
|
|
299
|
+
progress_callback: Optional[Callable[[int, int, str], None]] = None,
|
|
300
|
+
allow_delete: bool = False,
|
|
301
|
+
do_backup: bool = True,
|
|
302
|
+
dry_run: bool = False,
|
|
303
|
+
) -> Dict[str, Any]:
|
|
304
|
+
plan = self._plan
|
|
305
|
+
assert plan is not None
|
|
306
|
+
|
|
307
|
+
def _cb(done: int, total: int, msg: str) -> None:
|
|
308
|
+
if progress_callback:
|
|
309
|
+
progress_callback(done, total, msg)
|
|
310
|
+
self._log(f"{msg} ({done}/{total})")
|
|
311
|
+
|
|
312
|
+
applier = DeltaApplier(
|
|
313
|
+
installed_dir=self.install_dir,
|
|
314
|
+
torrent_path=self.source,
|
|
315
|
+
source_dir=self.source_dir,
|
|
316
|
+
logger=self._log,
|
|
317
|
+
)
|
|
318
|
+
result = applier.apply_plan(
|
|
319
|
+
plan=plan,
|
|
320
|
+
allow_delete=allow_delete,
|
|
321
|
+
do_backup=do_backup,
|
|
322
|
+
dry_run=dry_run,
|
|
323
|
+
)
|
|
324
|
+
_cb(1, 1, "Güncelleme tamamlandı")
|
|
325
|
+
return result
|
|
326
|
+
|
|
327
|
+
def _update_http(
|
|
328
|
+
self,
|
|
329
|
+
progress_callback: Optional[Callable[[int, int, str], None]] = None,
|
|
330
|
+
) -> Dict[str, Any]:
|
|
331
|
+
plan = self._plan
|
|
332
|
+
assert plan is not None and plan.get("mode") == "ZIP_DELTA"
|
|
333
|
+
|
|
334
|
+
engine = ZipDeltaEngine(
|
|
335
|
+
url=self.source,
|
|
336
|
+
local_dir=self.install_dir,
|
|
337
|
+
logger_cb=self._log,
|
|
338
|
+
)
|
|
339
|
+
index = engine.fetch_index()
|
|
340
|
+
changed_entries = [
|
|
341
|
+
ZipEntry(
|
|
342
|
+
name=e["name"],
|
|
343
|
+
compress_size=e.get("compress_size", 0),
|
|
344
|
+
file_size=e.get("file_size", 0),
|
|
345
|
+
crc32=int(e.get("crc32", 0), 16) if isinstance(e.get("crc32"), str) else int(e.get("crc32", 0)),
|
|
346
|
+
compress_type=e.get("compress_type", 0),
|
|
347
|
+
flag_bits=e.get("flag_bits", 0),
|
|
348
|
+
local_header_offset=e.get("local_header_offset", 0),
|
|
349
|
+
)
|
|
350
|
+
for e in plan.get("to_download", [])
|
|
351
|
+
]
|
|
352
|
+
|
|
353
|
+
def _cb(done: int, total: int, msg: str) -> None:
|
|
354
|
+
if progress_callback:
|
|
355
|
+
progress_callback(done, total, msg)
|
|
356
|
+
self._log(f"{msg} ({done}/{total})")
|
|
357
|
+
|
|
358
|
+
result = engine.apply(
|
|
359
|
+
zip_index=index,
|
|
360
|
+
changed_entries=changed_entries,
|
|
361
|
+
progress_cb=_cb,
|
|
362
|
+
)
|
|
363
|
+
_cb(1, 1, "HTTP ZIP güncelleme tamamlandı")
|
|
364
|
+
return result
|
|
365
|
+
|
|
366
|
+
# ------------------------------------------------------------------
|
|
367
|
+
# Verify
|
|
368
|
+
# ------------------------------------------------------------------
|
|
369
|
+
|
|
370
|
+
def verify(
|
|
371
|
+
self,
|
|
372
|
+
against_dir: Optional[Union[str, Path]] = None,
|
|
373
|
+
backup_manifest: Optional[Dict[str, Any]] = None,
|
|
374
|
+
root_override: Optional[Union[str, Path]] = None,
|
|
375
|
+
) -> Dict[str, Any]:
|
|
376
|
+
"""
|
|
377
|
+
Kurulu klasörü .torrent ile doğrular.
|
|
378
|
+
|
|
379
|
+
Döner: {"ok": bool, "errors": [...], ...}
|
|
380
|
+
"""
|
|
381
|
+
if self._is_http:
|
|
382
|
+
raise UpdaterError("Doğrulama sadece .torrent kaynağı için desteklenir.")
|
|
383
|
+
|
|
384
|
+
verifier = Verifier(
|
|
385
|
+
installed_dir=self.install_dir,
|
|
386
|
+
torrent_path=self.source,
|
|
387
|
+
logger=self._log,
|
|
388
|
+
)
|
|
389
|
+
return verifier.verify(
|
|
390
|
+
against_dir=against_dir,
|
|
391
|
+
backup_manifest=backup_manifest,
|
|
392
|
+
root_override=root_override,
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
# ------------------------------------------------------------------
|
|
396
|
+
# Plan accessors
|
|
397
|
+
# ------------------------------------------------------------------
|
|
398
|
+
|
|
399
|
+
@property
|
|
400
|
+
def plan(self) -> Optional[Dict[str, Any]]:
|
|
401
|
+
"""Son analyze() sonucu (dict) veya None."""
|
|
402
|
+
if self._plan is None:
|
|
403
|
+
return None
|
|
404
|
+
if isinstance(self._plan, DeltaPlan):
|
|
405
|
+
return self._plan.to_dict()
|
|
406
|
+
return self._plan
|
|
407
|
+
|
|
408
|
+
@property
|
|
409
|
+
def is_http(self) -> bool:
|
|
410
|
+
return self._is_http
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""
|
|
2
|
+
pyupdator.core — Güncelleme motoru.
|
|
3
|
+
|
|
4
|
+
Paket içerisinde:
|
|
5
|
+
torrent.py : .torrent parse, recheck, delta planı, in-place uygulama,
|
|
6
|
+
doğrulama
|
|
7
|
+
zip_engine.py : HTTP ZIP delta + CDC chunker
|
|
8
|
+
backend_manager.py: indirme backend tespiti ve seçimi
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from .torrent import (
|
|
12
|
+
TorrentParser,
|
|
13
|
+
PieceMap,
|
|
14
|
+
DeltaPlan,
|
|
15
|
+
DeltaAnalyzer,
|
|
16
|
+
DeltaApplier,
|
|
17
|
+
Verifier,
|
|
18
|
+
TorrentError,
|
|
19
|
+
NeedDownloadError,
|
|
20
|
+
)
|
|
21
|
+
from .zip_engine import (
|
|
22
|
+
ZipDeltaEngine,
|
|
23
|
+
ZipEntry,
|
|
24
|
+
ZipIndex,
|
|
25
|
+
ZipDeltaError,
|
|
26
|
+
HttpRangeClient,
|
|
27
|
+
chunk_file,
|
|
28
|
+
generate_manifest,
|
|
29
|
+
manifest_stats,
|
|
30
|
+
)
|
|
31
|
+
from .backend_manager import (
|
|
32
|
+
BackendManager,
|
|
33
|
+
BackendInfo,
|
|
34
|
+
BackendScanResult,
|
|
35
|
+
BackendError,
|
|
36
|
+
PIECE_LEVEL,
|
|
37
|
+
FILE_LEVEL,
|
|
38
|
+
CHUNK_LEVEL,
|
|
39
|
+
AUTO_ORDER,
|
|
40
|
+
scan_backends,
|
|
41
|
+
get_backend_class,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
__all__ = [
|
|
45
|
+
# torrent
|
|
46
|
+
"TorrentParser",
|
|
47
|
+
"PieceMap",
|
|
48
|
+
"DeltaPlan",
|
|
49
|
+
"DeltaAnalyzer",
|
|
50
|
+
"DeltaApplier",
|
|
51
|
+
"Verifier",
|
|
52
|
+
"TorrentError",
|
|
53
|
+
"NeedDownloadError",
|
|
54
|
+
# zip_engine
|
|
55
|
+
"ZipDeltaEngine",
|
|
56
|
+
"ZipEntry",
|
|
57
|
+
"ZipIndex",
|
|
58
|
+
"ZipDeltaError",
|
|
59
|
+
"HttpRangeClient",
|
|
60
|
+
"chunk_file",
|
|
61
|
+
"generate_manifest",
|
|
62
|
+
"manifest_stats",
|
|
63
|
+
# backend_manager
|
|
64
|
+
"BackendManager",
|
|
65
|
+
"BackendInfo",
|
|
66
|
+
"BackendScanResult",
|
|
67
|
+
"BackendError",
|
|
68
|
+
"PIECE_LEVEL",
|
|
69
|
+
"FILE_LEVEL",
|
|
70
|
+
"CHUNK_LEVEL",
|
|
71
|
+
"AUTO_ORDER",
|
|
72
|
+
"scan_backends",
|
|
73
|
+
"get_backend_class",
|
|
74
|
+
]
|