mirror of
https://github.com/KeksPirates/SoftwareManager.git
synced 2026-08-03 17:39:42 +02:00
Merge pull request #88 from KeksPirates/refactor/stability
fix/refactor/Improve Stability
This commit is contained in:
@@ -143,6 +143,7 @@ venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
.python-version
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
from utils.logging.logs import consoleLog
|
||||
import requests
|
||||
|
||||
def scrape_buzzheavier(url):
|
||||
|
||||
response = requests.get(url)
|
||||
try:
|
||||
response = requests.get(url, timeout=15)
|
||||
response.raise_for_status()
|
||||
except requests.RequestException as e:
|
||||
consoleLog(f"Buzzheavier: Failed to fetch page - {e}")
|
||||
return None
|
||||
|
||||
download_url = url + '/download'
|
||||
|
||||
@@ -13,6 +17,10 @@ def scrape_buzzheavier(url):
|
||||
'referer': url
|
||||
}
|
||||
|
||||
head_response = requests.head(download_url, headers=headers, allow_redirects=False)
|
||||
try:
|
||||
head_response = requests.head(download_url, headers=headers, allow_redirects=False, timeout=15)
|
||||
hx_redirect = head_response.headers.get('hx-redirect')
|
||||
return hx_redirect
|
||||
except requests.RequestException as e:
|
||||
consoleLog(f"Buzzheavier: Failed to fetch download URL - {e}")
|
||||
return None
|
||||
@@ -23,9 +23,9 @@ def scrape_gofile(url):
|
||||
|
||||
content_id = match.group(1)
|
||||
session = requests.Session()
|
||||
|
||||
try:
|
||||
# Create a guest account
|
||||
r = session.post(f"{_API_BASE}/accounts", headers={"User-Agent": _USER_AGENT})
|
||||
r = session.post(f"{_API_BASE}/accounts", headers={"User-Agent": _USER_AGENT}, timeout=15)
|
||||
data = r.json()
|
||||
if data.get("status") != "ok":
|
||||
consoleLog("GoFile: Failed to create guest account")
|
||||
@@ -44,7 +44,7 @@ def scrape_gofile(url):
|
||||
}
|
||||
|
||||
# Fetch folder contents
|
||||
r = session.get(f"{_API_BASE}/contents/{content_id}", headers=headers)
|
||||
r = session.get(f"{_API_BASE}/contents/{content_id}", headers=headers, timeout=15)
|
||||
data = r.json()
|
||||
if data.get("status") != "ok":
|
||||
consoleLog(f"GoFile: API error - {data.get('status')}")
|
||||
@@ -62,3 +62,5 @@ def scrape_gofile(url):
|
||||
return None
|
||||
|
||||
return link, headers
|
||||
finally:
|
||||
session.close()
|
||||
@@ -2,6 +2,7 @@ from utils.network.jsonhandler import split_data, format_data
|
||||
from utils.data.tracker import get_magnet_link
|
||||
from utils.logging.logs import consoleLog
|
||||
from utils.data.state import state
|
||||
from urllib.parse import quote
|
||||
from typing import Dict
|
||||
import requests
|
||||
|
||||
@@ -11,10 +12,18 @@ class RutrackerScraper:
|
||||
is_magnet = True
|
||||
|
||||
def search(self, query):
|
||||
search = requests.get(f"{state.api_url}/search?q={query}", timeout=15)
|
||||
try:
|
||||
search = requests.get(f"{state.api_url}/search?q={quote(query)}", timeout=15)
|
||||
except requests.RequestException as e:
|
||||
consoleLog(f"Failed to reach server: {e}")
|
||||
return []
|
||||
consoleLog("Sent request to server")
|
||||
if search:
|
||||
try:
|
||||
_, data, _, success, cached = split_data(search.text)
|
||||
except (KeyError, ValueError) as e:
|
||||
consoleLog(f"Failed to parse server response: {e}")
|
||||
return []
|
||||
if cached:
|
||||
consoleLog("Server response cached")
|
||||
if success:
|
||||
|
||||
@@ -27,7 +27,7 @@ class SteamripScraper:
|
||||
|
||||
try:
|
||||
url = f"https://steamrip.com/games-list-page/"
|
||||
response = requests.get(url)
|
||||
response = requests.get(url, timeout=15)
|
||||
response.raise_for_status()
|
||||
text = response.text
|
||||
except requests.RequestException as e:
|
||||
@@ -61,7 +61,7 @@ class SteamripScraper:
|
||||
|
||||
def scrape_steamrip_game_downloads(self, url):
|
||||
try:
|
||||
response = requests.get(url)
|
||||
response = requests.get(url, timeout=15)
|
||||
response.raise_for_status()
|
||||
except requests.RequestException as e:
|
||||
consoleLog(f"SteamRip: Failed to fetch game page - {e}")
|
||||
|
||||
@@ -19,7 +19,7 @@ class UztrackerScraper:
|
||||
posts = []
|
||||
|
||||
try:
|
||||
response = requests.get(search_url)
|
||||
response = requests.get(search_url, timeout=15)
|
||||
response.raise_for_status()
|
||||
soup = BeautifulSoup(response.text, 'html.parser')
|
||||
links = soup.find_all('tr', class_="tCenter hl-tr", id=lambda x: x and x.startswith('tor_'))
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from PySide6.QtCore import Qt, QPoint, Signal, QThread
|
||||
from network.direct_download.handle import DirectDownloadHandle
|
||||
from utils.logging.logs import consoleLog, remove_download_log
|
||||
from utils.network.download import download_selected
|
||||
from utils.data.tracker import get_magnet_link
|
||||
@@ -121,7 +122,12 @@ class ContextMenu_Downloads:
|
||||
|
||||
remove_download_log(magnet_link)
|
||||
|
||||
if state.dl_session:
|
||||
if isinstance(magnetdl, DirectDownloadHandle):
|
||||
try:
|
||||
magnetdl.stop()
|
||||
except Exception as e:
|
||||
consoleLog(f"Exception while stopping direct download: {e}")
|
||||
elif state.dl_session:
|
||||
try:
|
||||
state.dl_session.remove_torrent(magnetdl)
|
||||
except Exception as e:
|
||||
@@ -179,29 +185,24 @@ class ContextMenu_Downloads:
|
||||
|
||||
remove_download_log(magnet_link)
|
||||
|
||||
if state.dl_session:
|
||||
if isinstance(magnetdl, DirectDownloadHandle):
|
||||
try:
|
||||
magnetdl.stop()
|
||||
except Exception as e:
|
||||
consoleLog(f"Exception while stopping direct download: {e}")
|
||||
elif state.dl_session:
|
||||
try:
|
||||
state.dl_session.remove_torrent(magnetdl)
|
||||
time.sleep(0.5)
|
||||
except Exception as e:
|
||||
consoleLog(f"Exception while removing download from LibTorrent: {e}")
|
||||
|
||||
if download_path and os.path.exists(download_path):
|
||||
last_error = None
|
||||
for attempt in range(3):
|
||||
try:
|
||||
if os.path.isfile(download_path) or os.path.isdir(download_path):
|
||||
send2trash(download_path)
|
||||
consoleLog(f"Deleted files for: {torrent_name}", True)
|
||||
last_error = None
|
||||
break
|
||||
except Exception as e:
|
||||
last_error = e
|
||||
if attempt < 2:
|
||||
time.sleep(0.5)
|
||||
|
||||
if last_error:
|
||||
consoleLog(f"Error deleting files: {last_error}", True)
|
||||
consoleLog(f"Error deleting files: {e}", True)
|
||||
else:
|
||||
consoleLog(f"Removed entry (files not found): {torrent_name}", True)
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ from interface.utils.searchhelper import run_search
|
||||
from interface.utils.tabhelper import create_tab
|
||||
from utils.general.shutdown import closehelper
|
||||
from utils.general.wrappers import run_thread
|
||||
from utils.logging.logs import log_emitter
|
||||
from interface.dialogs.image import Image
|
||||
from utils.data.state import state
|
||||
|
||||
@@ -68,14 +69,12 @@ class CenteredDelegate(QStyledItemDelegate):
|
||||
|
||||
class MainWindow(QtWidgets.QMainWindow, QWidget):
|
||||
eventFilter = eventFilter
|
||||
log_signal = Signal(str)
|
||||
search_results_signal = Signal(list)
|
||||
_instance = None
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
MainWindow._instance = self
|
||||
self.log_signal.connect(self._on_log_signal)
|
||||
self.search_results_signal.connect(self._on_search_results)
|
||||
|
||||
pixmap = QPixmap()
|
||||
@@ -132,6 +131,7 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
|
||||
self.consoleLog = QTextEdit()
|
||||
self.statusbar = QStatusBar()
|
||||
|
||||
log_emitter.new_log.connect(self._on_log_signal)
|
||||
flush_log_buffer()
|
||||
|
||||
self._tracker_elided_delegate = ElidedItemDelegate(lambda: self._tracker_hovered_row, self)
|
||||
@@ -307,6 +307,10 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
|
||||
table.setItemDelegateForColumn(i, self._tracker_hover_delegate)
|
||||
|
||||
def _start_search(self):
|
||||
self.searchbar.setEnabled(False)
|
||||
state.trackertable.setRowCount(0)
|
||||
self.show_empty_results(False)
|
||||
|
||||
def _search_thread():
|
||||
try:
|
||||
run_search(self)
|
||||
@@ -315,13 +319,6 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
|
||||
self.search_results_signal.emit([])
|
||||
run_thread(threading.Thread(target=_search_thread))
|
||||
|
||||
@staticmethod
|
||||
def add_log(text):
|
||||
if hasattr(MainWindow, "_instance") and MainWindow._instance:
|
||||
MainWindow._instance.log_signal.emit(text)
|
||||
return True
|
||||
return False
|
||||
|
||||
def _on_log_signal(self, text):
|
||||
if hasattr(self, 'consoleLog'):
|
||||
self.consoleLog.append(text)
|
||||
|
||||
@@ -6,9 +6,6 @@ for scraper in SCRAPERS:
|
||||
state.trackers[scraper.name] = scraper
|
||||
|
||||
def run_search(self) -> None:
|
||||
state.trackertable.setRowCount(0)
|
||||
self.show_empty_results(False)
|
||||
|
||||
search_text = self.searchbar.text()
|
||||
# Check for empty search
|
||||
if search_text == "":
|
||||
@@ -16,7 +13,6 @@ def run_search(self) -> None:
|
||||
return
|
||||
|
||||
state.posts = []
|
||||
self.searchbar.setEnabled(False)
|
||||
consoleLog(f"User searched for: {search_text}")
|
||||
|
||||
# Get current tracker and call its search function
|
||||
|
||||
+8
-6
@@ -1,4 +1,3 @@
|
||||
from network.libtorrent_misc import send_notification, update_log, check_deleted_files
|
||||
from utils.logging.loghandler import split_data, check_completed, check_downloads
|
||||
from network.interface import list_interfaces, init_interfaces
|
||||
from PySide6.QtNetwork import QLocalServer, QLocalSocket
|
||||
@@ -9,12 +8,13 @@ from utils.general.shutdown import closehelper
|
||||
from utils.general.wrappers import run_thread
|
||||
from interface.assets.base64_icons import logo_base64
|
||||
from PySide6.QtWidgets import QSystemTrayIcon, QMenu
|
||||
from network.libtorrent_misc import AppDaemons
|
||||
from utils.general.shutdown import force_exit
|
||||
from utils.config.config import read_config
|
||||
from PySide6.QtGui import QAction, QPixmap
|
||||
from utils.logging.logs import consoleLog
|
||||
from interface.gui import MainWindow
|
||||
from PySide6.QtCore import Qt, QObject
|
||||
from PySide6.QtCore import Qt, QObject, QThread
|
||||
from utils.data.state import state
|
||||
from PySide6 import QtWidgets
|
||||
import qdarktheme
|
||||
@@ -45,7 +45,6 @@ class SingleInstance(QObject):
|
||||
self.server.listen(SERVER_NAME)
|
||||
self.server.newConnection.connect(self.handle_connection)
|
||||
self.is_running = False
|
||||
self.is_running = False
|
||||
|
||||
def handle_connection(self):
|
||||
socket = self.server.nextPendingConnection()
|
||||
@@ -143,9 +142,12 @@ def main():
|
||||
init_interfaces()
|
||||
consoleLog(f"Current Bound: {state.bound_interface}")
|
||||
# Start background daemon threads
|
||||
run_thread(threading.Thread(target=send_notification, args=(state.shutdown_event,), daemon=True))
|
||||
run_thread(threading.Thread(target=update_log, args=(state.shutdown_event,), daemon=True))
|
||||
run_thread(threading.Thread(target=check_deleted_files, args=(state.shutdown_event,), daemon=True))
|
||||
app._daemon_thread = QThread()
|
||||
app._daemons = AppDaemons()
|
||||
app._daemons.moveToThread(app._daemon_thread)
|
||||
app._daemon_thread.started.connect(app._daemons.start_all)
|
||||
app.aboutToQuit.connect(app._daemon_thread.quit)
|
||||
app._daemon_thread.start()
|
||||
# Start background non-daemon threads
|
||||
run_thread(threading.Thread(target=check_completed, args=(downloads, state.autoresume)))
|
||||
run_thread(threading.Thread(target=check_downloads, args=(downloads,)))
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from utils.logging.logs import update_download_completed_by_hash
|
||||
from utils.logging.logs import consoleLog
|
||||
from PySide6.QtCore import QObject, QTimer
|
||||
from utils.data.state import state
|
||||
from plyer import notification
|
||||
import libtorrent as lt
|
||||
@@ -19,55 +20,28 @@ def cleanup_session():
|
||||
with state.downloads_lock:
|
||||
state.active_downloads.clear()
|
||||
|
||||
def send_notification(shutdown_event):
|
||||
notified = set()
|
||||
while not shutdown_event.is_set():
|
||||
try:
|
||||
with state.downloads_lock:
|
||||
items = list(state.active_downloads.items())
|
||||
for magnet_uri, magnetdl in items:
|
||||
if isinstance(magnetdl, dict):
|
||||
continue
|
||||
class AppDaemons(QObject):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
status = magnetdl.status()
|
||||
self.notified_magnets = set()
|
||||
self.updated_magnets = set()
|
||||
|
||||
if status.state == lt.torrent_status.seeding and magnet_uri not in notified:
|
||||
notification.notify(
|
||||
title="Download finished",
|
||||
message=f"{status.name} has finished downloading.",
|
||||
timeout=4
|
||||
)
|
||||
notified.add(magnet_uri)
|
||||
except Exception as e:
|
||||
consoleLog(f"Exception while sending notification: {e}")
|
||||
time.sleep(5)
|
||||
# Create Timers
|
||||
self.deleted_files_timer = QTimer(self)
|
||||
self.completed_downloads_timer = QTimer(self)
|
||||
|
||||
def update_log(shutdown_event):
|
||||
updated = set()
|
||||
while not shutdown_event.is_set():
|
||||
try:
|
||||
with state.downloads_lock:
|
||||
items = list(state.active_downloads.items())
|
||||
for magnet_uri, magnetdl in items:
|
||||
if isinstance(magnetdl, dict):
|
||||
continue
|
||||
# Connect Timer Signals
|
||||
self.deleted_files_timer.timeout.connect(self.check_deleted_files)
|
||||
self.completed_downloads_timer.timeout.connect(self.check_completed_downloads)
|
||||
|
||||
status = magnetdl.status()
|
||||
def start_all(self):
|
||||
consoleLog("Starting Timer: deleted_files")
|
||||
self.deleted_files_timer.start(2000)
|
||||
consoleLog("Starting Timer: completed_downloads")
|
||||
self.completed_downloads_timer.start(5000)
|
||||
|
||||
if status.state == lt.torrent_status.seeding and magnet_uri not in updated and magnet_uri not in state.seeded_magnets:
|
||||
consoleLog(f"Marking {status.name} as completed")
|
||||
if hasattr(status, 'info_hashes'):
|
||||
info_hash = str(status.info_hashes.v1)
|
||||
else:
|
||||
info_hash = str(status.info_hash)
|
||||
update_download_completed_by_hash(info_hash, True)
|
||||
updated.add(magnet_uri)
|
||||
except Exception as e:
|
||||
consoleLog(f"Exception while updating log file: {e}")
|
||||
time.sleep(5)
|
||||
|
||||
def check_deleted_files(shutdown_event):
|
||||
while not shutdown_event.is_set():
|
||||
def check_deleted_files(self):
|
||||
try:
|
||||
with state.downloads_lock:
|
||||
items = list(state.active_downloads.items())
|
||||
@@ -87,4 +61,36 @@ def check_deleted_files(shutdown_event):
|
||||
del state.active_downloads[magnet_uri]
|
||||
except Exception as e:
|
||||
consoleLog(f"Exception while checking for file deletions: {e}")
|
||||
time.sleep(5)
|
||||
|
||||
def check_completed_downloads(self):
|
||||
try:
|
||||
with state.downloads_lock:
|
||||
items = list(state.active_downloads.items())
|
||||
seeded = set(state.seeded_magnets)
|
||||
for magnet_uri, magnetdl in items:
|
||||
if isinstance(magnetdl, dict):
|
||||
continue
|
||||
|
||||
status = magnetdl.status()
|
||||
|
||||
if status.state == lt.torrent_status.seeding:
|
||||
|
||||
if magnet_uri not in self.updated_magnets and magnet_uri not in seeded:
|
||||
consoleLog(f"Marking {status.name} as completed")
|
||||
if hasattr(status, 'info_hashes'):
|
||||
info_hash = str(status.info_hashes.v1)
|
||||
else:
|
||||
info_hash = str(status.info_hash)
|
||||
update_download_completed_by_hash(info_hash, True)
|
||||
self.updated_magnets.add(magnet_uri)
|
||||
|
||||
if magnet_uri not in self.notified_magnets:
|
||||
notification.notify(
|
||||
title="Download finished",
|
||||
message=f"{status.name} has finished downloading.",
|
||||
timeout=4
|
||||
)
|
||||
self.notified_magnets.add(magnet_uri)
|
||||
|
||||
except Exception as e:
|
||||
consoleLog(f"Exception while checking downloads: {e}")
|
||||
@@ -61,6 +61,7 @@ class AppState(QObject):
|
||||
self.max_connections: int = 200
|
||||
self.max_downloads: int = 10
|
||||
self.downloads_lock = threading.RLock()
|
||||
self._log_lock = threading.Lock()
|
||||
|
||||
@property
|
||||
def image_path(self) -> str:
|
||||
|
||||
+14
-14
@@ -1,4 +1,5 @@
|
||||
from utils.data.models import Download, DownloadList
|
||||
from PySide6.QtCore import QObject, Signal
|
||||
from utils.data.state import state
|
||||
from dataclasses import asdict
|
||||
from datetime import datetime
|
||||
@@ -7,6 +8,10 @@ import time
|
||||
import os
|
||||
import re
|
||||
|
||||
class LogSignal(QObject):
|
||||
new_log = Signal(str)
|
||||
|
||||
log_emitter = LogSignal()
|
||||
|
||||
def _downloads_file_path() -> str:
|
||||
return os.path.join(state.settings_path, "downloads.json")
|
||||
@@ -187,27 +192,22 @@ def _update_download_completed_by_hash_inner(info_hash, completed) -> DownloadLi
|
||||
def set_main_window(window):
|
||||
state.main_window = window
|
||||
|
||||
def flush_log_buffer(): # credits to claude
|
||||
if state.log_buffer:
|
||||
try:
|
||||
from interface.gui import MainWindow
|
||||
for log_entry in state.log_buffer:
|
||||
MainWindow.add_log(log_entry)
|
||||
state.log_buffer = []
|
||||
except Exception as e:
|
||||
consoleLog(f"Exception while flushing log buffer: {e}")
|
||||
def flush_log_buffer():
|
||||
with state._log_lock:
|
||||
for text in state.log_buffer:
|
||||
log_emitter.new_log.emit(text)
|
||||
|
||||
state.log_buffer.clear()
|
||||
|
||||
def consoleLog(text, printAnyways = False):
|
||||
now = datetime.now()
|
||||
current_time = now.strftime("%H:%M:%S")
|
||||
formatted_text = f"[{current_time}] {text}"
|
||||
|
||||
try:
|
||||
from interface.gui import MainWindow
|
||||
if not MainWindow.add_log(formatted_text):
|
||||
state.log_buffer.append(formatted_text)
|
||||
except Exception:
|
||||
with state._log_lock:
|
||||
state.log_buffer.append(formatted_text)
|
||||
|
||||
log_emitter.new_log.emit(formatted_text)
|
||||
|
||||
if state.debug or printAnyways:
|
||||
print(formatted_text)
|
||||
|
||||
@@ -2,13 +2,12 @@ from network.direct_download.handle import DirectDownloadHandle
|
||||
from utils.general.shutdown import closehelper
|
||||
from utils.logging.logs import consoleLog
|
||||
from utils.data.state import state
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtCore import Qt, QTimer, QEventLoop
|
||||
from PySide6 import QtWidgets
|
||||
import libtorrent as lt
|
||||
import subprocess
|
||||
import tempfile
|
||||
import hashlib
|
||||
import time
|
||||
import sys
|
||||
import os
|
||||
|
||||
@@ -42,7 +41,6 @@ def download_update(assets: list):
|
||||
progress = QtWidgets.QProgressDialog("Downloading update... (0.0 MB/s)", None, 0, 100)
|
||||
progress.setWindowTitle("Updating")
|
||||
progress.setWindowModality(Qt.WindowModality.ApplicationModal)
|
||||
progress.setCancelButton(None)
|
||||
progress.setMinimumDuration(0)
|
||||
progress.setAutoClose(False)
|
||||
progress.setAutoReset(False)
|
||||
@@ -55,14 +53,19 @@ def download_update(assets: list):
|
||||
handle = DirectDownloadHandle(url, filename, tempfile.gettempdir())
|
||||
handle.start()
|
||||
|
||||
while True:
|
||||
QtWidgets.QApplication.processEvents()
|
||||
loop = QEventLoop()
|
||||
timer = QTimer()
|
||||
timer.setInterval(100)
|
||||
|
||||
def poll_progress():
|
||||
status = handle.status()
|
||||
|
||||
if status.error:
|
||||
state.down_speed_limit = original_limit
|
||||
progress.close()
|
||||
consoleLog(f"Update download failed: {status.error}")
|
||||
timer.stop()
|
||||
loop.quit()
|
||||
return
|
||||
|
||||
if status.total_wanted > 0:
|
||||
@@ -72,9 +75,25 @@ def download_update(assets: list):
|
||||
progress.setLabelText(f"Downloading update... ({speed_mb:.1f} MB/s)")
|
||||
|
||||
if status.state == lt.torrent_status.seeding:
|
||||
break
|
||||
timer.stop()
|
||||
loop.quit()
|
||||
|
||||
time.sleep(0.1)
|
||||
if progress.wasCanceled():
|
||||
state.down_speed_limit = original_limit
|
||||
consoleLog("Update download cancelled by user.")
|
||||
timer.stop()
|
||||
loop.quit()
|
||||
return
|
||||
|
||||
timer.timeout.connect(poll_progress)
|
||||
timer.start()
|
||||
loop.exec()
|
||||
|
||||
if progress.wasCanceled():
|
||||
return
|
||||
|
||||
if handle.status().error:
|
||||
return
|
||||
|
||||
state.down_speed_limit = original_limit
|
||||
|
||||
@@ -84,6 +103,11 @@ def download_update(assets: list):
|
||||
else:
|
||||
progress.close()
|
||||
consoleLog("Error: Invalid file hash, file may be corrupted")
|
||||
QtWidgets.QMessageBox.critical(
|
||||
None,
|
||||
"Update Failed",
|
||||
"Error: Invalid file hash, file may be corrupted"
|
||||
)
|
||||
sys.exit(0)
|
||||
else:
|
||||
consoleLog("Skipping hash verification (no hash found for release)")
|
||||
@@ -94,5 +118,4 @@ def download_update(assets: list):
|
||||
|
||||
closehelper()
|
||||
subprocess.Popen([installer_path, "/VERYSILENT", "/SUPPRESSMSGBOXES", "/SP-", "/CLOSEAPPLICATIONS"])
|
||||
time.sleep(1)
|
||||
os._exit(0)
|
||||
Reference in New Issue
Block a user