# Soundboard Engine - customizable soundboard # Copyright (C) 2026 Vidhu Kant Sharma # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import shutil import sys import zipfile from pathlib import Path import requests from PySide6.QtCore import Qt, QThread, Signal from PySide6.QtWidgets import QLabel, QPushButton, QVBoxLayout, QWidget from Soundboard import Soundboard class SoundboardUpdaterWindow(QWidget): start_soundboard = Signal() def __init__(self, soundboard: Soundboard): super().__init__() self.soundboard = soundboard if soundboard.update_url.strip() != "": self.worker = SoundboardUpdaterWorker(self.soundboard) self.worker.start() self.init_ui() else: self.start_soundboard.emit() def init_ui(self): self.setWindowTitle("Soundboard Updater") self.setFixedSize(300, 180) self.layout = QVBoxLayout(self) self.status_label = QLabel("Updater coming soon") self.layout.addWidget(self.status_label) self.btn_layout = QVBoxLayout() self.continue_btn = QPushButton("Continue Without Updating") self.continue_btn.clicked.connect(self._start_soundboard) self.btn_layout.addWidget(self.continue_btn) self.layout.addLayout(self.btn_layout) self.worker.no_update_found.connect(self._start_soundboard) self.worker.update_found.connect(self._on_update_found) self.worker.extracting.connect( lambda: self.status_label.setText("Extracting update...") ) self.worker.extracting.connect( lambda: self.status_label.setText("Copying files...") ) self.worker.update_completed.connect(self._on_update_completed) def _on_update_found(self, size): self.status_label.setText(f"Update found ({size})") self.update_btn = QPushButton("Download Update") self.update_btn.clicked.connect(self._on_download_update) self.btn_layout.addWidget(self.update_btn) def _on_download_update(self): self.update_btn.setEnabled(False) self.continue_btn.setEnabled(False) self.status_label.setText("Downloading update...") self.worker.start() def _on_update_completed(self): self.status_label.setText("Update completed! Please restart soundboard") self.update_btn.setVisible(False) self.continue_btn.setVisible(False) def _start_soundboard(self): self.start_soundboard.emit() self.deleteLater() class SoundboardUpdaterWorker(QThread): update_found = Signal(str) no_update_found = Signal() extracting = Signal() copying = Signal() update_completed = Signal() def __init__(self, soundboard: Soundboard): super().__init__() self.soundboard = soundboard self.mode = "CHECKING_UPDATE" def run(self): if self.mode == "CHECKING_UPDATE": self._check_for_update() else: self._download_and_extract() def _check_for_update(self): try: res = requests.get(self.soundboard.update_url + "SOUNDBOARD_VERSION") remote_version = res.text.strip() self.download_url = self.soundboard.update_url + remote_version + ".zip" if remote_version != self.soundboard.soundboard_version: head = requests.head(self.download_url, timeout=5) update_size = self.format_size( int(head.headers.get("content-length", 0)) ) self.mode = "FOUND_UPDATE" self.update_found.emit(update_size) else: self.no_update_found.emit() except Exception as e: print(e) sys.exit(1) def _download_and_extract(self): download_path = Path("temp_update.zip") if download_path.exists(): download_path.unlink() try: res = requests.get(self.download_url) with open(download_path, "wb") as f: f.write(res.content) self.extracting.emit() with zipfile.ZipFile(download_path, "r") as zip_ref: zip_ref.extractall(Path.cwd()) if download_path.exists(): download_path.unlink() self.copying.emit() extracted_path = Path("temp_update") shutil.copy2( extracted_path / "soundboard.json", self.soundboard.soundboard_path ) shutil.rmtree(Path.cwd() / "assets") shutil.copytree(extracted_path / "assets", Path.cwd() / "assets") if extracted_path.exists(): shutil.rmtree(extracted_path) self.update_completed.emit() except Exception as e: print(e) sys.exit(1) def format_size(self, bytes_size): if bytes_size >= 1024 * 1024: return f"{bytes_size / (1024 * 1024):.1f} MB" elif bytes_size >= 1024: return f"{bytes_size / 1024:.1f} KB" return f"{bytes_size} B"