Remove legacy Python artifacts from repository
This commit is contained in:
parent
4fc0ce26f3
commit
2ae3cb5fa5
@ -80,4 +80,4 @@ npm run self-check
|
|||||||
- `rd_session_state.json`
|
- `rd_session_state.json`
|
||||||
- `rd_downloader.log`
|
- `rd_downloader.log`
|
||||||
|
|
||||||
- Die bisherige Python-Datei bleibt vorerst als Legacy-Referenz im Repo, die aktive App ist jetzt Node/Electron.
|
- Das Repository enthält jetzt nur noch die aktive Node/Electron-Codebasis.
|
||||||
|
|||||||
@ -1,16 +0,0 @@
|
|||||||
param(
|
|
||||||
[string]$Version = ""
|
|
||||||
)
|
|
||||||
|
|
||||||
python -m pip install --upgrade pip
|
|
||||||
pip install -r requirements.txt
|
|
||||||
pip install pyinstaller pillow
|
|
||||||
|
|
||||||
if ($Version -ne "") {
|
|
||||||
python scripts/set_version.py $Version
|
|
||||||
}
|
|
||||||
|
|
||||||
python scripts/prepare_icon.py
|
|
||||||
pyinstaller --noconfirm --windowed --onedir --name "Real-Debrid-Downloader" --icon "assets/app_icon.ico" real_debrid_downloader_gui.py
|
|
||||||
|
|
||||||
Write-Host "Build fertig: dist/Real-Debrid-Downloader/Real-Debrid-Downloader.exe"
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,5 +0,0 @@
|
|||||||
requests>=2.31.0
|
|
||||||
pyzipper>=0.3.6
|
|
||||||
send2trash>=1.8.2
|
|
||||||
keyring>=25.6.0
|
|
||||||
tkinterdnd2>=0.4.2
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
from pathlib import Path
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
|
||||||
project_root = Path(__file__).resolve().parents[1]
|
|
||||||
png_path = project_root / "assets" / "app_icon.png"
|
|
||||||
ico_path = project_root / "assets" / "app_icon.ico"
|
|
||||||
|
|
||||||
if not png_path.exists():
|
|
||||||
print(f"Icon PNG not found: {png_path}")
|
|
||||||
return 1
|
|
||||||
|
|
||||||
try:
|
|
||||||
from PIL import Image
|
|
||||||
except ImportError:
|
|
||||||
print("Pillow missing. Install with: pip install pillow")
|
|
||||||
return 1
|
|
||||||
|
|
||||||
with Image.open(png_path) as image:
|
|
||||||
image = image.convert("RGBA")
|
|
||||||
sizes = [(16, 16), (24, 24), (32, 32), (48, 48), (64, 64), (128, 128), (256, 256)]
|
|
||||||
image.save(ico_path, format="ICO", sizes=sizes)
|
|
||||||
|
|
||||||
print(f"Wrote icon: {ico_path}")
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
raise SystemExit(main())
|
|
||||||
@ -1,305 +0,0 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import json
|
|
||||||
import sys
|
|
||||||
import tempfile
|
|
||||||
import threading
|
|
||||||
import time
|
|
||||||
import zipfile
|
|
||||||
from pathlib import Path
|
|
||||||
from tkinter import messagebox
|
|
||||||
|
|
||||||
ROOT = Path(__file__).resolve().parents[1]
|
|
||||||
if str(ROOT) not in sys.path:
|
|
||||||
sys.path.insert(0, str(ROOT))
|
|
||||||
|
|
||||||
import real_debrid_downloader_gui as appmod
|
|
||||||
|
|
||||||
|
|
||||||
def assert_true(condition: bool, message: str) -> None:
|
|
||||||
if not condition:
|
|
||||||
raise AssertionError(message)
|
|
||||||
|
|
||||||
|
|
||||||
def run() -> None:
|
|
||||||
temp_root = Path(tempfile.mkdtemp(prefix="rd_self_check_"))
|
|
||||||
|
|
||||||
original_config = appmod.CONFIG_FILE
|
|
||||||
original_manifest = appmod.MANIFEST_FILE
|
|
||||||
appmod.CONFIG_FILE = temp_root / "rd_downloader_config.json"
|
|
||||||
appmod.MANIFEST_FILE = temp_root / "rd_download_manifest.json"
|
|
||||||
|
|
||||||
message_calls: list[tuple[str, str, str]] = []
|
|
||||||
original_showerror = messagebox.showerror
|
|
||||||
original_showwarning = messagebox.showwarning
|
|
||||||
original_showinfo = messagebox.showinfo
|
|
||||||
original_askyesno = messagebox.askyesno
|
|
||||||
|
|
||||||
def fake_message(kind: str):
|
|
||||||
def _inner(title: str, text: str):
|
|
||||||
message_calls.append((kind, str(title), str(text)))
|
|
||||||
return None
|
|
||||||
|
|
||||||
return _inner
|
|
||||||
|
|
||||||
messagebox.showerror = fake_message("error")
|
|
||||||
messagebox.showwarning = fake_message("warning")
|
|
||||||
messagebox.showinfo = fake_message("info")
|
|
||||||
|
|
||||||
app = appmod.DownloaderApp()
|
|
||||||
app.withdraw()
|
|
||||||
|
|
||||||
try:
|
|
||||||
app.token_var.set("demo-token")
|
|
||||||
app.output_dir_var.set(str(temp_root / "downloads"))
|
|
||||||
app.links_text.delete("1.0", "end")
|
|
||||||
app.links_text.insert("1.0", "not_a_link")
|
|
||||||
app.start_downloads()
|
|
||||||
assert_true(
|
|
||||||
any("Ungültige Links" in text for kind, _, text in message_calls if kind == "error"),
|
|
||||||
"Link-Validierung hat ungültige Eingabe nicht blockiert",
|
|
||||||
)
|
|
||||||
|
|
||||||
app.cleanup_mode_var.set("delete")
|
|
||||||
app.extract_conflict_mode_var.set("rename")
|
|
||||||
app.remove_link_files_after_extract_var.set(True)
|
|
||||||
app.remove_samples_var.set(True)
|
|
||||||
app.remember_token_var.set(True)
|
|
||||||
app.token_var.set("token-123")
|
|
||||||
|
|
||||||
original_can_secure = app._can_store_token_securely
|
|
||||||
original_store_keyring = app._store_token_in_keyring
|
|
||||||
app._can_store_token_securely = lambda: True
|
|
||||||
app._store_token_in_keyring = lambda token: False
|
|
||||||
app._save_config()
|
|
||||||
|
|
||||||
config_data = json.loads(appmod.CONFIG_FILE.read_text(encoding="utf-8"))
|
|
||||||
assert_true(config_data.get("token") == "token-123", "Token-Fallback in Config bei Keyring-Fehler fehlt")
|
|
||||||
|
|
||||||
app.cleanup_mode_var.set("none")
|
|
||||||
app.extract_conflict_mode_var.set("overwrite")
|
|
||||||
app.remove_link_files_after_extract_var.set(False)
|
|
||||||
app.remove_samples_var.set(False)
|
|
||||||
app.token_var.set("")
|
|
||||||
app._load_config()
|
|
||||||
assert_true(app.cleanup_mode_var.get() == "delete", "cleanup_mode wurde nicht aus Config geladen")
|
|
||||||
assert_true(app.extract_conflict_mode_var.get() == "rename", "extract_conflict_mode wurde nicht geladen")
|
|
||||||
assert_true(app.remove_link_files_after_extract_var.get() is True, "remove_link_files_after_extract fehlt")
|
|
||||||
assert_true(app.remove_samples_var.get() is True, "remove_samples_after_extract fehlt")
|
|
||||||
|
|
||||||
app._can_store_token_securely = original_can_secure
|
|
||||||
app._store_token_in_keyring = original_store_keyring
|
|
||||||
|
|
||||||
class DummyWorker:
|
|
||||||
@staticmethod
|
|
||||||
def is_alive() -> bool:
|
|
||||||
return True
|
|
||||||
|
|
||||||
app.worker_thread = DummyWorker()
|
|
||||||
app.pause_event.clear()
|
|
||||||
app.toggle_pause_downloads()
|
|
||||||
assert_true(app.pause_event.is_set(), "Pause wurde nicht aktiviert")
|
|
||||||
app.toggle_pause_downloads()
|
|
||||||
assert_true(not app.pause_event.is_set(), "Resume wurde nicht aktiviert")
|
|
||||||
|
|
||||||
app.pause_event.set()
|
|
||||||
started = time.monotonic()
|
|
||||||
|
|
||||||
def _unpause() -> None:
|
|
||||||
time.sleep(0.25)
|
|
||||||
app.pause_event.clear()
|
|
||||||
|
|
||||||
threading.Thread(target=_unpause, daemon=True).start()
|
|
||||||
app._wait_if_paused()
|
|
||||||
waited = time.monotonic() - started
|
|
||||||
assert_true(waited >= 0.2, "Pause-Wait hat nicht geblockt")
|
|
||||||
|
|
||||||
messagebox.askyesno = lambda *args, **kwargs: True
|
|
||||||
cancel_package_dir = temp_root / "cancel_pkg"
|
|
||||||
cancel_package_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
(cancel_package_dir / "release.part1.rar").write_bytes(b"x")
|
|
||||||
(cancel_package_dir / "keep_movie.mkv").write_bytes(b"x")
|
|
||||||
|
|
||||||
cancel_row = "package-cancel"
|
|
||||||
child_row = "package-cancel-link-1"
|
|
||||||
app.table.insert("", "end", iid=cancel_row, text="cancelpkg", values=("-", "Wartet", "0/1", "0 B/s", "0"), open=True)
|
|
||||||
app.table.insert(cancel_row, "end", iid=child_row, text="https://example.com/cancel", values=("-", "Wartet", "0%", "0 B/s", "0"))
|
|
||||||
app.links_text.delete("1.0", "end")
|
|
||||||
app.links_text.insert("1.0", "https://example.com/cancel\n")
|
|
||||||
app.package_contexts = [
|
|
||||||
{
|
|
||||||
"package_row_id": cancel_row,
|
|
||||||
"row_map": {1: child_row},
|
|
||||||
"job": {
|
|
||||||
"name": "cancelpkg",
|
|
||||||
"links": ["https://example.com/cancel"],
|
|
||||||
"package_dir": cancel_package_dir,
|
|
||||||
"extract_target_dir": None,
|
|
||||||
"completed_indices": [],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
]
|
|
||||||
app.worker_thread = DummyWorker()
|
|
||||||
app.table.selection_set(child_row)
|
|
||||||
app._remove_selected_progress_rows()
|
|
||||||
assert_true(app._is_package_cancelled(cancel_row), "Paket-Abbruch wurde nicht markiert")
|
|
||||||
assert_true(not app.table.exists(cancel_row), "Paketzeile wurde nicht entfernt")
|
|
||||||
remaining_links = app.links_text.get("1.0", "end").strip()
|
|
||||||
assert_true(not remaining_links, "Link wurde bei Paketentfernung nicht aus Liste entfernt")
|
|
||||||
|
|
||||||
removed_cancel_files = app._cleanup_cancelled_package_artifacts(cancel_package_dir)
|
|
||||||
assert_true(removed_cancel_files >= 1, "Archiv-Cleanup bei Paketabbruch hat nichts gelöscht")
|
|
||||||
assert_true(not (cancel_package_dir / "release.part1.rar").exists(), "RAR-Teil wurde nicht entfernt")
|
|
||||||
assert_true((cancel_package_dir / "keep_movie.mkv").exists(), "Nicht-Archivdatei wurde fälschlich gelöscht")
|
|
||||||
|
|
||||||
status_events: list[tuple[float, str]] = []
|
|
||||||
extract_times: dict[str, float] = {}
|
|
||||||
download_starts: dict[str, float] = {}
|
|
||||||
|
|
||||||
original_queue_status = app._queue_status
|
|
||||||
original_download_single = app._download_single_link
|
|
||||||
original_extract_archive = app._extract_archive
|
|
||||||
|
|
||||||
def fake_queue_status(message: str) -> None:
|
|
||||||
status_events.append((time.monotonic(), message))
|
|
||||||
original_queue_status(message)
|
|
||||||
|
|
||||||
def fake_download_single(
|
|
||||||
token: str,
|
|
||||||
package_dir: Path,
|
|
||||||
index: int,
|
|
||||||
link: str,
|
|
||||||
package_row_id: str | None = None,
|
|
||||||
) -> appmod.DownloadResult:
|
|
||||||
package_name = package_dir.name
|
|
||||||
download_starts.setdefault(package_name, time.monotonic())
|
|
||||||
archive_path = package_dir / f"{package_name}_{index}.zip"
|
|
||||||
archive_path.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
with zipfile.ZipFile(archive_path, "w") as archive:
|
|
||||||
archive.writestr("movie.mkv", b"movie-data")
|
|
||||||
archive.writestr(f"Samples/{package_name}-sample.mkv", b"sample-data")
|
|
||||||
archive.writestr("download_links.txt", "https://example.com/file")
|
|
||||||
time.sleep(0.18)
|
|
||||||
return appmod.DownloadResult(path=archive_path, bytes_written=archive_path.stat().st_size)
|
|
||||||
|
|
||||||
def fake_extract_archive(archive_path: Path, extract_target_dir: Path, conflict_mode: str):
|
|
||||||
package_name = archive_path.parent.name
|
|
||||||
if package_name == "pkg1":
|
|
||||||
extract_times["pkg1_start"] = time.monotonic()
|
|
||||||
time.sleep(0.8)
|
|
||||||
else:
|
|
||||||
time.sleep(0.25)
|
|
||||||
with zipfile.ZipFile(archive_path) as archive:
|
|
||||||
archive.extractall(extract_target_dir)
|
|
||||||
if package_name == "pkg1":
|
|
||||||
extract_times["pkg1_end"] = time.monotonic()
|
|
||||||
return None
|
|
||||||
|
|
||||||
app._queue_status = fake_queue_status
|
|
||||||
app._download_single_link = fake_download_single
|
|
||||||
app._extract_archive = fake_extract_archive
|
|
||||||
|
|
||||||
app.table.delete(*app.table.get_children())
|
|
||||||
app.package_contexts = []
|
|
||||||
|
|
||||||
package_specs: list[tuple[str, Path, Path]] = []
|
|
||||||
for idx in (1, 2):
|
|
||||||
package_name = f"pkg{idx}"
|
|
||||||
package_dir = temp_root / package_name
|
|
||||||
extract_dir = temp_root / f"extract_{package_name}"
|
|
||||||
package_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
extract_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
package_row_id = f"package-{idx}"
|
|
||||||
app.table.insert("", "end", iid=package_row_id, text=package_name, values=("-", "Wartet", "0/1", "0 B/s", "0"), open=True)
|
|
||||||
row_id = f"{package_row_id}-link-1"
|
|
||||||
app.table.insert(package_row_id, "end", iid=row_id, text="https://example.com/file", values=("-", "Wartet", "0%", "0 B/s", "0"))
|
|
||||||
|
|
||||||
app.package_contexts.append(
|
|
||||||
{
|
|
||||||
"package_row_id": package_row_id,
|
|
||||||
"row_map": {1: row_id},
|
|
||||||
"job": {
|
|
||||||
"name": package_name,
|
|
||||||
"links": ["https://example.com/file"],
|
|
||||||
"package_dir": package_dir,
|
|
||||||
"extract_target_dir": extract_dir,
|
|
||||||
"completed_indices": [],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
package_specs.append((package_name, package_dir, extract_dir))
|
|
||||||
|
|
||||||
app.run_started_at = time.monotonic()
|
|
||||||
app.total_downloaded_bytes = 0
|
|
||||||
app.stop_event.clear()
|
|
||||||
app.pause_event.clear()
|
|
||||||
app._set_manifest_for_run(
|
|
||||||
[
|
|
||||||
{"name": name, "links": ["https://example.com/file"]}
|
|
||||||
for name, _package_dir, _extract_dir in package_specs
|
|
||||||
],
|
|
||||||
temp_root / "downloads",
|
|
||||||
"self-check-signature",
|
|
||||||
resume_map={},
|
|
||||||
)
|
|
||||||
|
|
||||||
app._download_queue_worker(
|
|
||||||
token="demo-token",
|
|
||||||
max_parallel=1,
|
|
||||||
hybrid_extract=True,
|
|
||||||
cleanup_mode="none",
|
|
||||||
extract_conflict_mode="overwrite",
|
|
||||||
overall_total_links=2,
|
|
||||||
remove_link_files_after_extract=True,
|
|
||||||
remove_samples_after_extract=True,
|
|
||||||
)
|
|
||||||
app._process_ui_queue()
|
|
||||||
|
|
||||||
pkg1_extract_dir = temp_root / "extract_pkg1"
|
|
||||||
pkg2_extract_dir = temp_root / "extract_pkg2"
|
|
||||||
assert_true((pkg1_extract_dir / "movie.mkv").exists(), "Entpacken pkg1 fehlgeschlagen")
|
|
||||||
assert_true((pkg2_extract_dir / "movie.mkv").exists(), "Entpacken pkg2 fehlgeschlagen")
|
|
||||||
assert_true(not (pkg1_extract_dir / "download_links.txt").exists(), "Link-Artefakte wurden nicht entfernt")
|
|
||||||
assert_true(not (pkg2_extract_dir / "download_links.txt").exists(), "Link-Artefakte pkg2 wurden nicht entfernt")
|
|
||||||
assert_true(not (pkg1_extract_dir / "Samples").exists(), "Sample-Ordner pkg1 wurde nicht entfernt")
|
|
||||||
assert_true(not (pkg2_extract_dir / "Samples").exists(), "Sample-Ordner pkg2 wurde nicht entfernt")
|
|
||||||
|
|
||||||
assert_true("pkg1_start" in extract_times and "pkg1_end" in extract_times, "Entpack-Zeiten für pkg1 fehlen")
|
|
||||||
assert_true("pkg2" in download_starts, "Downloadstart für pkg2 fehlt")
|
|
||||||
assert_true(
|
|
||||||
download_starts["pkg2"] < extract_times["pkg1_end"],
|
|
||||||
"Paket 2 startete nicht parallel zum Entpacken von Paket 1",
|
|
||||||
)
|
|
||||||
|
|
||||||
manifest_data = json.loads(appmod.MANIFEST_FILE.read_text(encoding="utf-8"))
|
|
||||||
assert_true(bool(manifest_data.get("finished")), "Manifest wurde nach Lauf nicht abgeschlossen")
|
|
||||||
|
|
||||||
with app.path_lock:
|
|
||||||
app.reserved_target_keys.add("dummy-key")
|
|
||||||
app.ui_queue.put(("controls", False))
|
|
||||||
app._process_ui_queue()
|
|
||||||
with app.path_lock:
|
|
||||||
assert_true(len(app.reserved_target_keys) == 0, "reserved_target_keys wurden nicht bereinigt")
|
|
||||||
|
|
||||||
app._queue_status = original_queue_status
|
|
||||||
app._download_single_link = original_download_single
|
|
||||||
app._extract_archive = original_extract_archive
|
|
||||||
|
|
||||||
assert_true(any("Entpacken läuft parallel" in text for _, text in status_events), "Kein Parallel-Entpacken-Status geloggt")
|
|
||||||
print("Self-check erfolgreich")
|
|
||||||
finally:
|
|
||||||
try:
|
|
||||||
app.destroy()
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
messagebox.showerror = original_showerror
|
|
||||||
messagebox.showwarning = original_showwarning
|
|
||||||
messagebox.showinfo = original_showinfo
|
|
||||||
messagebox.askyesno = original_askyesno
|
|
||||||
appmod.CONFIG_FILE = original_config
|
|
||||||
appmod.MANIFEST_FILE = original_manifest
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
run()
|
|
||||||
@ -1,35 +0,0 @@
|
|||||||
import re
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
|
||||||
if len(sys.argv) < 2:
|
|
||||||
print("Usage: python scripts/set_version.py <version>")
|
|
||||||
return 1
|
|
||||||
|
|
||||||
version = sys.argv[1].strip().lstrip("v")
|
|
||||||
if not re.fullmatch(r"\d+(?:\.\d+){1,3}", version):
|
|
||||||
print(f"Invalid version: {version}")
|
|
||||||
return 1
|
|
||||||
|
|
||||||
target = Path(__file__).resolve().parents[1] / "real_debrid_downloader_gui.py"
|
|
||||||
content = target.read_text(encoding="utf-8")
|
|
||||||
updated, count = re.subn(
|
|
||||||
r'^APP_VERSION\s*=\s*"[^"]+"\s*$',
|
|
||||||
f'APP_VERSION = "{version}"',
|
|
||||||
content,
|
|
||||||
count=1,
|
|
||||||
flags=re.MULTILINE,
|
|
||||||
)
|
|
||||||
if count != 1:
|
|
||||||
print("APP_VERSION marker not found")
|
|
||||||
return 1
|
|
||||||
|
|
||||||
target.write_text(updated, encoding="utf-8")
|
|
||||||
print(f"Set APP_VERSION to {version}")
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
raise SystemExit(main())
|
|
||||||
Loading…
Reference in New Issue
Block a user