Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
31797cdabb | ||
|
|
771c438257 | ||
|
|
5379be6c11 | ||
|
|
c6fe895334 |
@@ -42,12 +42,18 @@ jobs:
|
|||||||
path: Playtube-${{ github.ref_name }}-win64.zip
|
path: Playtube-${{ github.ref_name }}-win64.zip
|
||||||
|
|
||||||
- name: Patch-Paket packen (nur Playtube.exe, fuer schnelle Updates)
|
- name: Patch-Paket packen (nur Playtube.exe, fuer schnelle Updates)
|
||||||
run: Compress-Archive -Path dist\Playtube\Playtube.exe -DestinationPath Playtube-${{ github.ref_name }}-win64-patch.zip
|
# .play statt .zip: eigene Dateiendung, die Playtube nach dem ersten Start als
|
||||||
|
# Windows-Dateizuordnung registriert (siehe shortcuts.py) - ein manuell
|
||||||
|
# heruntergeladenes Patch laesst sich so auch per Doppelklick installieren.
|
||||||
|
# Technisch bleibt es ein ganz normales ZIP-Archiv.
|
||||||
|
run: |
|
||||||
|
Compress-Archive -Path dist\Playtube\Playtube.exe -DestinationPath Playtube-${{ github.ref_name }}-win64-patch.zip
|
||||||
|
Rename-Item -Path Playtube-${{ github.ref_name }}-win64-patch.zip -NewName Playtube-${{ github.ref_name }}-win64-patch.play
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: windows-patch
|
name: windows-patch
|
||||||
path: Playtube-${{ github.ref_name }}-win64-patch.zip
|
path: Playtube-${{ github.ref_name }}-win64-patch.play
|
||||||
|
|
||||||
build-linux:
|
build-linux:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
@@ -102,6 +102,16 @@ zum neuen Code passen (fruehere Ursache fuer fehlende Icons). Der Login bleibt d
|
|||||||
unberuehrt, da Cookies/LocalStorage in einem komplett getrennten Ordner
|
unberuehrt, da Cookies/LocalStorage in einem komplett getrennten Ordner
|
||||||
(`webprofile/storage`) liegen.
|
(`webprofile/storage`) liegen.
|
||||||
|
|
||||||
|
### Patch-Dateien manuell installieren (`.play`)
|
||||||
|
|
||||||
|
Windows-Patch-Pakete tragen die eigene Dateiendung `.play` statt `.zip` (technisch
|
||||||
|
weiterhin ein ganz normales ZIP-Archiv). Playtube registriert `.play` beim ersten Start
|
||||||
|
automatisch als Windows-Dateizuordnung - eine manuell heruntergeladene
|
||||||
|
`Playtube-vX.Y.Z-win64-patch.play` (z.B. von der
|
||||||
|
[Releases-Seite](https://github.com/fojadrachi/Playtube/releases)) laesst sich also
|
||||||
|
einfach per Doppelklick installieren, ohne dass Playtube selbst etwas herunterladen
|
||||||
|
muss.
|
||||||
|
|
||||||
### Eine neue Version veroeffentlichen
|
### Eine neue Version veroeffentlichen
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
|
|||||||
@@ -12,7 +12,10 @@ from pathlib import Path
|
|||||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||||
from playtube import __version__, app_id # noqa: E402
|
from playtube import __version__, app_id # noqa: E402
|
||||||
from playtube.config import APP_NAME, clear_cache_on_update, load_config # noqa: E402
|
from playtube.config import APP_NAME, clear_cache_on_update, load_config # noqa: E402
|
||||||
from playtube.shortcuts import ensure_start_menu_shortcut # noqa: E402
|
from playtube.shortcuts import ( # noqa: E402
|
||||||
|
ensure_play_file_association,
|
||||||
|
ensure_start_menu_shortcut,
|
||||||
|
)
|
||||||
|
|
||||||
app_id.set_app_user_model_id()
|
app_id.set_app_user_model_id()
|
||||||
app_id.configure_webengine_process_path()
|
app_id.configure_webengine_process_path()
|
||||||
@@ -33,6 +36,15 @@ from PySide6.QtWidgets import QApplication # noqa: E402
|
|||||||
from playtube.mainwindow import MainWindow # noqa: E402
|
from playtube.mainwindow import MainWindow # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
|
def _pending_local_patch() -> str | None:
|
||||||
|
"""Falls Playtube per Doppelklick auf eine ".play"-Patchdatei gestartet wurde
|
||||||
|
(siehe shortcuts.ensure_play_file_association), liefert den Pfad dazu."""
|
||||||
|
for arg in sys.argv[1:]:
|
||||||
|
if arg.lower().endswith(".play") and Path(arg).is_file():
|
||||||
|
return str(Path(arg).resolve())
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
QApplication.setAttribute(Qt.ApplicationAttribute.AA_ShareOpenGLContexts, True)
|
QApplication.setAttribute(Qt.ApplicationAttribute.AA_ShareOpenGLContexts, True)
|
||||||
app = QApplication(sys.argv)
|
app = QApplication(sys.argv)
|
||||||
@@ -51,10 +63,17 @@ def main() -> int:
|
|||||||
# komplett, da Playtube als portables ZIP ohne Installer ausgeliefert wird.
|
# komplett, da Playtube als portables ZIP ohne Installer ausgeliefert wird.
|
||||||
clear_cache_on_update(__version__)
|
clear_cache_on_update(__version__)
|
||||||
ensure_start_menu_shortcut(APP_NAME)
|
ensure_start_menu_shortcut(APP_NAME)
|
||||||
|
ensure_play_file_association(APP_NAME)
|
||||||
|
|
||||||
window = MainWindow(config)
|
window = MainWindow(config)
|
||||||
window.show()
|
window.show()
|
||||||
|
|
||||||
|
# Playtube wurde per Doppelklick auf eine heruntergeladene .play-Patchdatei
|
||||||
|
# gestartet -> direkt installieren statt selbst etwas herunterzuladen.
|
||||||
|
local_patch = _pending_local_patch()
|
||||||
|
if local_patch:
|
||||||
|
window.install_local_patch(local_patch)
|
||||||
|
|
||||||
return app.exec()
|
return app.exec()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"""Playtube - ein eigenstaendiger YouTube- & YouTube-Music-Player mit Discord Rich Presence."""
|
"""Playtube - ein eigenstaendiger YouTube- & YouTube-Music-Player mit Discord Rich Presence."""
|
||||||
|
|
||||||
__app_name__ = "Playtube"
|
__app_name__ = "Playtube"
|
||||||
__version__ = "2.2.0"
|
__version__ = "2.3.0"
|
||||||
|
|||||||
@@ -259,9 +259,10 @@ class DiscordRPCWorker(QThread):
|
|||||||
payload = build_presence_payload(item, self._session_start, start_ts, end_ts)
|
payload = build_presence_payload(item, self._session_start, start_ts, end_ts)
|
||||||
self._presence.update(**payload)
|
self._presence.update(**payload)
|
||||||
log_line(
|
log_line(
|
||||||
"[send] title=%r thumbnail=%r large_image=%r start=%s end=%s"
|
"[send] title=%r url=%r thumbnail=%r large_image=%r start=%s end=%s"
|
||||||
% (
|
% (
|
||||||
item.get("title"),
|
item.get("title"),
|
||||||
|
item.get("url"),
|
||||||
item.get("thumbnail"),
|
item.get("thumbnail"),
|
||||||
payload.get("large_image"),
|
payload.get("large_image"),
|
||||||
start_ts,
|
start_ts,
|
||||||
|
|||||||
+18
-5
@@ -311,20 +311,33 @@ class MainWindow(QMainWindow):
|
|||||||
if answer == QMessageBox.StandardButton.Yes:
|
if answer == QMessageBox.StandardButton.Yes:
|
||||||
self._start_update_install(download_url)
|
self._start_update_install(download_url)
|
||||||
|
|
||||||
def _start_update_install(self, download_url: str) -> None:
|
def _start_update_install(self, download_url: str = "", local_path: str | None = None) -> None:
|
||||||
if not download_url:
|
if not download_url and not local_path:
|
||||||
return
|
return
|
||||||
self._settings_tab.hide_install_button()
|
self._settings_tab.hide_install_button()
|
||||||
self._settings_tab.set_update_status("Lade Update herunter …")
|
if local_path:
|
||||||
self._settings_tab.set_download_progress(0)
|
self._settings_tab.set_update_status("Installiere lokale Patch-Datei …")
|
||||||
|
self._settings_tab.set_download_progress(-1)
|
||||||
|
else:
|
||||||
|
self._settings_tab.set_update_status("Lade Update herunter …")
|
||||||
|
self._settings_tab.set_download_progress(0)
|
||||||
self._tray.setToolTip(f"{APP_NAME} – Update wird installiert …")
|
self._tray.setToolTip(f"{APP_NAME} – Update wird installiert …")
|
||||||
self._update_installer = UpdateInstaller(download_url, self)
|
self._update_installer = UpdateInstaller(
|
||||||
|
download_url, local_archive_path=local_path, parent=self
|
||||||
|
)
|
||||||
self._update_installer.progress.connect(self._on_install_progress_text)
|
self._update_installer.progress.connect(self._on_install_progress_text)
|
||||||
self._update_installer.progress_percent.connect(self._settings_tab.set_download_progress)
|
self._update_installer.progress_percent.connect(self._settings_tab.set_download_progress)
|
||||||
self._update_installer.finished_ok.connect(self._on_update_finished)
|
self._update_installer.finished_ok.connect(self._on_update_finished)
|
||||||
self._update_installer.failed.connect(self._on_update_failed)
|
self._update_installer.failed.connect(self._on_update_failed)
|
||||||
self._update_installer.start()
|
self._update_installer.start()
|
||||||
|
|
||||||
|
def install_local_patch(self, path: str) -> None:
|
||||||
|
"""Wird von main.py aufgerufen, wenn Playtube per Doppelklick auf eine
|
||||||
|
heruntergeladene .play-Patchdatei gestartet wurde (siehe
|
||||||
|
shortcuts.ensure_play_file_association)."""
|
||||||
|
self._tabs.setCurrentWidget(self._settings_tab)
|
||||||
|
self._start_update_install(local_path=path)
|
||||||
|
|
||||||
def _on_install_progress_text(self, msg: str) -> None:
|
def _on_install_progress_text(self, msg: str) -> None:
|
||||||
self._tray.setToolTip(f"{APP_NAME} – {msg}")
|
self._tray.setToolTip(f"{APP_NAME} – {msg}")
|
||||||
self._settings_tab.set_update_status(msg)
|
self._settings_tab.set_update_status(msg)
|
||||||
|
|||||||
+10
-2
@@ -82,10 +82,18 @@ MEDIA_PROBE_JS = r"""
|
|||||||
var videoId = videoIdFromUrl(location.href);
|
var videoId = videoIdFromUrl(location.href);
|
||||||
var title = null, subtitle = null, thumbnail = null;
|
var title = null, subtitle = null, thumbnail = null;
|
||||||
|
|
||||||
|
// WICHTIG: die Video-ID aus der URL ist die zuverlaessigste Thumbnail-Quelle - sie
|
||||||
|
// wechselt garantiert synchron mit dem Titel. Das <img> in der YT-Music-Playerleiste
|
||||||
|
// (fruehere Praeferenz) wird von YouTube per Crossfade/Shadow-DOM animiert und
|
||||||
|
// aktualisiert sein src-Attribut dabei nachweislich NICHT zuverlaessig pro Titel -
|
||||||
|
// empirisch bestaetigt: dieselbe Thumbnail-URL blieb ueber mehrere komplett
|
||||||
|
// unterschiedliche Songs hinweg stehen, obwohl Titel/Zeit schon laengst gewechselt
|
||||||
|
// hatten. Das DOM-<img> dient nur noch als Fallback, falls keine Video-ID in der
|
||||||
|
// URL steckt.
|
||||||
if (isMusic) {
|
if (isMusic) {
|
||||||
title = txt('.title.ytmusic-player-bar') || txt('ytmusic-player-bar .title');
|
title = txt('.title.ytmusic-player-bar') || txt('ytmusic-player-bar .title');
|
||||||
subtitle = txt('.byline.ytmusic-player-bar') || txt('ytmusic-player-bar .byline');
|
subtitle = txt('.byline.ytmusic-player-bar') || txt('ytmusic-player-bar .byline');
|
||||||
thumbnail = imgSrc('ytmusic-player-bar img, .image.ytmusic-player-bar img') || ytThumbnailUrl(videoId);
|
thumbnail = ytThumbnailUrl(videoId) || imgSrc('ytmusic-player-bar img, .image.ytmusic-player-bar img');
|
||||||
} else {
|
} else {
|
||||||
var t = document.title.replace(/ - YouTube$/, '');
|
var t = document.title.replace(/ - YouTube$/, '');
|
||||||
title = t || null;
|
title = t || null;
|
||||||
@@ -94,7 +102,7 @@ MEDIA_PROBE_JS = r"""
|
|||||||
|| txt('#channel-name a')
|
|| txt('#channel-name a')
|
||||||
|| txt('ytd-channel-name#channel-name a');
|
|| txt('ytd-channel-name#channel-name a');
|
||||||
var imgY = document.querySelector('link[rel="image_src"]');
|
var imgY = document.querySelector('link[rel="image_src"]');
|
||||||
thumbnail = (imgY ? imgY.href : null) || ytThumbnailUrl(videoId);
|
thumbnail = ytThumbnailUrl(videoId) || (imgY ? imgY.href : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
var timeInfo = isMusic ? musicTimeInfo() : youtubeTimeInfo();
|
var timeInfo = isMusic ? musicTimeInfo() : youtubeTimeInfo();
|
||||||
|
|||||||
+43
-1
@@ -1,4 +1,5 @@
|
|||||||
"""Erstellt bei Bedarf eine Windows-Startmenue-Verknuepfung.
|
"""Erstellt bei Bedarf eine Windows-Startmenue-Verknuepfung sowie die Dateizuordnung
|
||||||
|
fuer die eigene ".play"-Patchdateiendung.
|
||||||
|
|
||||||
Playtube wird als portables ZIP ausgeliefert (kein MSI/Installer) - ohne das gaebe es
|
Playtube wird als portables ZIP ausgeliefert (kein MSI/Installer) - ohne das gaebe es
|
||||||
also nie einen Eintrag im Windows-Startmenue, wie man ihn von "richtig installierten"
|
also nie einen Eintrag im Windows-Startmenue, wie man ihn von "richtig installierten"
|
||||||
@@ -50,3 +51,44 @@ def ensure_start_menu_shortcut(app_name: str) -> None:
|
|||||||
except Exception:
|
except Exception:
|
||||||
# Kein Startmenue-Eintrag ist kein Grund, den App-Start scheitern zu lassen.
|
# Kein Startmenue-Eintrag ist kein Grund, den App-Start scheitern zu lassen.
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_play_file_association(app_name: str) -> None:
|
||||||
|
"""Registriert ".play" (unsere eigene Endung fuer Patch-Pakete, siehe updater.py)
|
||||||
|
als Windows-Dateizuordnung fuer Playtube - ein manuell heruntergeladenes Patch kann
|
||||||
|
danach per Doppelklick installiert werden (Playtube startet dann mit dem Dateipfad
|
||||||
|
als Kommandozeilenargument, siehe main.py). Nur unter HKEY_CURRENT_USER, damit keine
|
||||||
|
Admin-Rechte noetig sind. Wird bei jedem Start erneut geschrieben (billig, idempotent
|
||||||
|
und heilt sich selbst, falls die .exe z.B. nach einem Update an einem neuen Pfad
|
||||||
|
liegt)."""
|
||||||
|
if sys.platform != "win32" or not getattr(sys, "frozen", False):
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
import winreg
|
||||||
|
|
||||||
|
exe_path = str(Path(sys.executable).resolve())
|
||||||
|
prog_id = f"{app_name}.PatchFile"
|
||||||
|
icon_path = _find_icon(Path(exe_path).parent) or Path(exe_path)
|
||||||
|
|
||||||
|
with winreg.CreateKey(winreg.HKEY_CURRENT_USER, r"Software\Classes\.play") as key:
|
||||||
|
winreg.SetValueEx(key, "", 0, winreg.REG_SZ, prog_id)
|
||||||
|
with winreg.CreateKey(winreg.HKEY_CURRENT_USER, rf"Software\Classes\{prog_id}") as key:
|
||||||
|
winreg.SetValueEx(key, "", 0, winreg.REG_SZ, f"{app_name}-Patchdatei")
|
||||||
|
with winreg.CreateKey(
|
||||||
|
winreg.HKEY_CURRENT_USER, rf"Software\Classes\{prog_id}\DefaultIcon"
|
||||||
|
) as key:
|
||||||
|
winreg.SetValueEx(key, "", 0, winreg.REG_SZ, str(icon_path))
|
||||||
|
with winreg.CreateKey(
|
||||||
|
winreg.HKEY_CURRENT_USER, rf"Software\Classes\{prog_id}\shell\open\command"
|
||||||
|
) as key:
|
||||||
|
winreg.SetValueEx(key, "", 0, winreg.REG_SZ, f'"{exe_path}" "%1"')
|
||||||
|
|
||||||
|
# Explorer informieren, damit die neue Zuordnung sofort (ohne Neustart) greift.
|
||||||
|
import ctypes
|
||||||
|
|
||||||
|
SHCNE_ASSOCCHANGED = 0x08000000
|
||||||
|
SHCNF_IDLIST = 0x0000
|
||||||
|
ctypes.windll.shell32.SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, None, None)
|
||||||
|
except Exception:
|
||||||
|
# Keine Dateizuordnung ist kein Grund, den App-Start scheitern zu lassen.
|
||||||
|
pass
|
||||||
|
|||||||
+68
-33
@@ -17,6 +17,14 @@ Ablauf:
|
|||||||
PowerShell unter Windows, ein Shell-Skript unter Linux.
|
PowerShell unter Windows, ein Shell-Skript unter Linux.
|
||||||
- Entwicklungsmodus (python main.py): fuehrt 'git pull' + 'pip install -r
|
- Entwicklungsmodus (python main.py): fuehrt 'git pull' + 'pip install -r
|
||||||
requirements.txt' aus, die App startet sich danach selbst neu (os.execv).
|
requirements.txt' aus, die App startet sich danach selbst neu (os.execv).
|
||||||
|
|
||||||
|
Windows-Patch-Pakete tragen die eigene Dateiendung ".play" statt ".zip" (technisch
|
||||||
|
weiterhin ein ganz normales ZIP-Archiv - Windows/Python schauen beim Entpacken auf die
|
||||||
|
Magic Bytes, nicht auf die Endung). shortcuts.py registriert ".play" beim ersten Start
|
||||||
|
als Windows-Dateizuordnung fuer Playtube - ein manuell heruntergeladenes Patch kann so
|
||||||
|
auch per Doppelklick installiert werden, ohne dass Playtube selbst etwas herunterladen
|
||||||
|
muss (siehe main.py: wird eine .play-Datei als Kommandozeilenargument uebergeben,
|
||||||
|
installiert MainWindow.install_local_patch() sie direkt ueber UpdateInstaller).
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
@@ -72,16 +80,21 @@ def fetch_latest_release() -> dict[str, Any] | None:
|
|||||||
def _find_platform_asset(release: dict[str, Any]) -> dict[str, Any] | None:
|
def _find_platform_asset(release: dict[str, Any]) -> dict[str, Any] | None:
|
||||||
"""Sucht das zur laufenden Plattform passende Release-Paket. Bevorzugt das kleine
|
"""Sucht das zur laufenden Plattform passende Release-Paket. Bevorzugt das kleine
|
||||||
"-patch"-Paket (nur die .exe/Binary) gegenueber dem vollen Release-Paket - siehe
|
"-patch"-Paket (nur die .exe/Binary) gegenueber dem vollen Release-Paket - siehe
|
||||||
Moduldoku. Windows -> *.zip mit "win" im Namen, Linux -> *.tar.gz mit "linux"."""
|
Moduldoku. Windows: volles Paket -> *.zip, Patch -> *.play (unsere eigene
|
||||||
|
Dateiendung, technisch ein ganz normales .zip - siehe Moduldoku), beide mit "win" im
|
||||||
|
Namen. Linux -> *.tar.gz mit "linux"."""
|
||||||
assets = release.get("assets", [])
|
assets = release.get("assets", [])
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
hints, exts = ("win",), (".zip",)
|
hints = ("win",)
|
||||||
|
patch_exts, full_exts = (".play",), (".zip",)
|
||||||
elif sys.platform.startswith("linux"):
|
elif sys.platform.startswith("linux"):
|
||||||
hints, exts = ("linux",), (".tar.gz", ".tgz")
|
hints = ("linux",)
|
||||||
|
patch_exts = full_exts = (".tar.gz", ".tgz")
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def find(want_patch: bool, require_hint: bool) -> dict[str, Any] | None:
|
def find(want_patch: bool, require_hint: bool) -> dict[str, Any] | None:
|
||||||
|
exts = patch_exts if want_patch else full_exts
|
||||||
for asset in assets:
|
for asset in assets:
|
||||||
name = asset.get("name", "").lower()
|
name = asset.get("name", "").lower()
|
||||||
if not name.endswith(exts):
|
if not name.endswith(exts):
|
||||||
@@ -133,9 +146,18 @@ class UpdateInstaller(QThread):
|
|||||||
finished_ok = Signal()
|
finished_ok = Signal()
|
||||||
failed = Signal(str)
|
failed = Signal(str)
|
||||||
|
|
||||||
def __init__(self, download_url: str, parent=None):
|
def __init__(
|
||||||
|
self,
|
||||||
|
download_url: str = "",
|
||||||
|
local_archive_path: str | None = None,
|
||||||
|
parent=None,
|
||||||
|
):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self._download_url = download_url
|
self._download_url = download_url
|
||||||
|
# Gesetzt, wenn der Nutzer eine bereits heruntergeladene .play-Patchdatei per
|
||||||
|
# Doppelklick geoeffnet hat (siehe shortcuts.py-Dateiverknuepfung) - dann wird
|
||||||
|
# nichts heruntergeladen, sondern direkt diese lokale Datei installiert.
|
||||||
|
self._local_archive_path = local_archive_path
|
||||||
|
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
try:
|
try:
|
||||||
@@ -149,39 +171,49 @@ class UpdateInstaller(QThread):
|
|||||||
# ---------------------------------------------------------- gepackter Modus
|
# ---------------------------------------------------------- gepackter Modus
|
||||||
|
|
||||||
def _run_packaged_update(self) -> None:
|
def _run_packaged_update(self) -> None:
|
||||||
if not self._download_url:
|
|
||||||
self.failed.emit(
|
|
||||||
"Kein passendes Release-Paket fuer dieses Betriebssystem gefunden."
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
self.progress.emit("Lade Update herunter …")
|
|
||||||
self.progress_percent.emit(0)
|
|
||||||
install_dir = Path(sys.executable).resolve().parent
|
install_dir = Path(sys.executable).resolve().parent
|
||||||
staging = Path(tempfile.mkdtemp(prefix="playtube_update_"))
|
staging = Path(tempfile.mkdtemp(prefix="playtube_update_"))
|
||||||
archive_name = self._download_url.rsplit("/", 1)[-1]
|
|
||||||
archive_path = staging / archive_name
|
|
||||||
extract_dir = staging / "extracted"
|
extract_dir = staging / "extracted"
|
||||||
|
|
||||||
req = urllib.request.Request(self._download_url, headers={"User-Agent": _USER_AGENT})
|
if self._local_archive_path:
|
||||||
with urllib.request.urlopen(req, timeout=120) as resp:
|
archive_path = Path(self._local_archive_path)
|
||||||
total = int(resp.headers.get("Content-Length") or 0)
|
if not archive_path.exists():
|
||||||
downloaded = 0
|
self.failed.emit(f"Patch-Datei nicht gefunden: {archive_path}")
|
||||||
last_emitted = -1
|
return
|
||||||
with open(archive_path, "wb") as out:
|
archive_name = archive_path.name
|
||||||
while True:
|
self.progress.emit(f"Verwende lokale Patch-Datei {archive_name} …")
|
||||||
chunk = resp.read(256 * 1024)
|
self.progress_percent.emit(-1)
|
||||||
if not chunk:
|
else:
|
||||||
break
|
if not self._download_url:
|
||||||
out.write(chunk)
|
self.failed.emit(
|
||||||
downloaded += len(chunk)
|
"Kein passendes Release-Paket fuer dieses Betriebssystem gefunden."
|
||||||
if total:
|
)
|
||||||
percent = int(downloaded * 100 / total)
|
return
|
||||||
if percent != last_emitted:
|
|
||||||
self.progress_percent.emit(percent)
|
self.progress.emit("Lade Update herunter …")
|
||||||
last_emitted = percent
|
self.progress_percent.emit(0)
|
||||||
|
archive_name = self._download_url.rsplit("/", 1)[-1]
|
||||||
|
archive_path = staging / archive_name
|
||||||
|
|
||||||
|
req = urllib.request.Request(self._download_url, headers={"User-Agent": _USER_AGENT})
|
||||||
|
with urllib.request.urlopen(req, timeout=120) as resp:
|
||||||
|
total = int(resp.headers.get("Content-Length") or 0)
|
||||||
|
downloaded = 0
|
||||||
|
last_emitted = -1
|
||||||
|
with open(archive_path, "wb") as out:
|
||||||
|
while True:
|
||||||
|
chunk = resp.read(256 * 1024)
|
||||||
|
if not chunk:
|
||||||
|
break
|
||||||
|
out.write(chunk)
|
||||||
|
downloaded += len(chunk)
|
||||||
|
if total:
|
||||||
|
percent = int(downloaded * 100 / total)
|
||||||
|
if percent != last_emitted:
|
||||||
|
self.progress_percent.emit(percent)
|
||||||
|
last_emitted = percent
|
||||||
|
self.progress_percent.emit(100)
|
||||||
|
|
||||||
self.progress_percent.emit(100)
|
|
||||||
self.progress.emit("Entpacke Update …")
|
self.progress.emit("Entpacke Update …")
|
||||||
# Unbestimmter Fortschritt waehrend Entpacken/Installieren - die UI zeigt
|
# Unbestimmter Fortschritt waehrend Entpacken/Installieren - die UI zeigt
|
||||||
# dafuer einen "laufenden" Balken statt einer Prozentzahl.
|
# dafuer einen "laufenden" Balken statt einer Prozentzahl.
|
||||||
@@ -190,10 +222,13 @@ class UpdateInstaller(QThread):
|
|||||||
with tarfile.open(archive_path) as tf:
|
with tarfile.open(archive_path) as tf:
|
||||||
tf.extractall(extract_dir)
|
tf.extractall(extract_dir)
|
||||||
else:
|
else:
|
||||||
|
# .zip UND .play (unsere eigene Dateiendung fuer per Doppelklick startbare
|
||||||
|
# Patchdateien - technisch ein ganz normales .zip, siehe Moduldoku) werden
|
||||||
|
# beide als ZIP entpackt.
|
||||||
with zipfile.ZipFile(archive_path) as zf:
|
with zipfile.ZipFile(archive_path) as zf:
|
||||||
zf.extractall(extract_dir)
|
zf.extractall(extract_dir)
|
||||||
|
|
||||||
is_patch = "patch" in archive_name.lower()
|
is_patch = "patch" in archive_name.lower() or archive_name.lower().endswith(".play")
|
||||||
self.progress.emit("Bereite Installation vor …")
|
self.progress.emit("Bereite Installation vor …")
|
||||||
|
|
||||||
if is_patch:
|
if is_patch:
|
||||||
|
|||||||
Reference in New Issue
Block a user