1
0
Fork 0
mirror of synced 2024-05-19 03:52:47 +12:00

Merge pull request #240 from loathingKernel/fixups

Initialize logging in RareApp and log both Rare and GameLaunchHelper
This commit is contained in:
Dummerle 2022-09-09 20:26:44 +02:00 committed by GitHub
commit 8afdabbb49
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 152 additions and 83 deletions

4
.gitignore vendored
View file

@ -19,5 +19,9 @@ __pycache__
/AppDir/ /AppDir/
/System Volume Information/ /System Volume Information/
/test_files/ /test_files/
# Nuitka build artifacts
/rare.build /rare.build
/rare.dist
/rare.bin /rare.bin
/rare.cmd
/rare.exe

View file

@ -1,4 +0,0 @@
pywebview[gtk]; platform_system == "Linux"
pywebview[cef]; platform_system == "Windows"
pypresence

View file

@ -114,6 +114,7 @@ if __name__ == "__main__":
# ) # )
# insert source directory # insert source directory
sys.path.insert(0, str(pathlib.Path(__file__).parents[1].absolute())) if "__compiled__" not in globals():
sys.path.insert(0, str(pathlib.Path(__file__).parents[1].absolute()))
main() main()

View file

@ -1,21 +1,17 @@
import logging import logging
import os import os
import platform
import shutil import shutil
import sys import sys
import time
import traceback import traceback
from argparse import Namespace from argparse import Namespace
from datetime import datetime from datetime import datetime
from typing import Optional from typing import Optional
import legendary
import requests.exceptions import requests.exceptions
from PyQt5.QtCore import QThreadPool, QTimer, QT_VERSION_STR, PYQT_VERSION_STR from PyQt5.QtCore import QThreadPool, QTimer
from PyQt5.QtWidgets import QApplication, QMessageBox from PyQt5.QtWidgets import QApplication, QMessageBox
from requests import HTTPError from requests import HTTPError
import rare
from rare.components.dialogs.launch_dialog import LaunchDialog from rare.components.dialogs.launch_dialog import LaunchDialog
from rare.components.main_window import MainWindow from rare.components.main_window import MainWindow
from rare.shared import ( from rare.shared import (
@ -27,7 +23,6 @@ from rare.shared.rare_core import RareCore
from rare.utils import legendary_utils, config_helper, paths from rare.utils import legendary_utils, config_helper, paths
from rare.widgets.rare_app import RareApp from rare.widgets.rare_app import RareApp
logger = logging.getLogger("Rare") logger = logging.getLogger("Rare")
@ -52,49 +47,8 @@ def excepthook(exc_type, exc_value, exc_tb):
class App(RareApp): class App(RareApp):
def __init__(self, args: Namespace): def __init__(self, args: Namespace):
super(App, self).__init__(args) log_file = "Rare_{0}.log"
super(App, self).__init__(args, log_file)
start_time = time.strftime("%y-%m-%d--%H-%M") # year-month-day-hour-minute
file_name = os.path.join(paths.log_dir(), f"Rare_{start_time}.log")
for handler in logging.root.handlers[:]:
logging.root.removeHandler(handler)
file_handler = logging.FileHandler(filename=file_name, encoding="utf-8")
file_handler.setFormatter(fmt=logging.Formatter("[%(name)s] %(levelname)s: %(message)s"))
# configure logging
if args.debug:
logging.basicConfig(
format="[%(name)s] %(levelname)s: %(message)s",
level=logging.DEBUG,
stream=sys.stderr,
)
file_handler.setLevel(logging.DEBUG)
logging.root.addHandler(file_handler)
logging.getLogger().setLevel(level=logging.DEBUG)
# keep requests, asyncio and pillow quiet
logging.getLogger("requests").setLevel(logging.WARNING)
logging.getLogger("urllib3").setLevel(logging.WARNING)
logging.getLogger("asyncio").setLevel(logging.WARNING)
logger.info(
f"Launching Rare version {rare.__version__} Codename: {rare.code_name}\n"
f" - Using Legendary {legendary.__version__} Codename: {legendary.__codename__} as backend\n"
f" - Operating System: {platform.system()}, Python version: {platform.python_version()}\n"
f" - Running {sys.executable} {' '.join(sys.argv)}\n"
f" - Qt version: {QT_VERSION_STR}, PyQt version: {PYQT_VERSION_STR}"
)
else:
logging.basicConfig(
format="[%(name)s] %(levelname)s: %(message)s",
level=logging.INFO,
stream=sys.stderr,
)
file_handler.setLevel(logging.INFO)
logging.root.addHandler(file_handler)
logger.info(f"Launching Rare version {rare.__version__}")
logger.info(f"Operating System: {platform.system()}")
self.rare_core = RareCore(args=args) self.rare_core = RareCore(args=args)
self.args = ArgumentsSingleton() self.args = ArgumentsSingleton()
self.signals = GlobalSignalsSingleton() self.signals = GlobalSignalsSingleton()

View file

@ -19,7 +19,7 @@ class MainWindow(QMainWindow):
def __init__(self, parent=None): def __init__(self, parent=None):
super(MainWindow, self).__init__(parent=parent) super(MainWindow, self).__init__(parent=parent)
self.setAttribute(Qt.WA_DeleteOnClose) self.setAttribute(Qt.WA_DeleteOnClose, True)
self.core = LegendaryCoreSingleton() self.core = LegendaryCoreSingleton()
self.signals = GlobalSignalsSingleton() self.signals = GlobalSignalsSingleton()
self.args = ArgumentsSingleton() self.args = ArgumentsSingleton()

View file

@ -17,6 +17,8 @@ from .console import Console
from .lgd_helper import get_launch_args, InitArgs, get_configured_process, LaunchArgs, GameArgsError from .lgd_helper import get_launch_args, InitArgs, get_configured_process, LaunchArgs, GameArgsError
from .message_models import ErrorModel, Actions, FinishedModel, BaseModel, StateChangedModel from .message_models import ErrorModel, Actions, FinishedModel, BaseModel, StateChangedModel
logger = logging.getLogger("RareLauncher")
class PreLaunchThread(QRunnable): class PreLaunchThread(QRunnable):
class Signals(QObject): class Signals(QObject):
@ -65,7 +67,8 @@ class GameProcessApp(RareApp):
success: bool = True success: bool = True
def __init__(self, args: Namespace): def __init__(self, args: Namespace):
super(GameProcessApp, self).__init__(args) log_file = f"Rare_Launcher_{args.app_name}" + "_{0}.log"
super(GameProcessApp, self).__init__(args, log_file)
self.game_process = QProcess() self.game_process = QProcess()
self.app_name = args.app_name self.app_name = args.app_name
self.logger = getLogger(self.app_name) self.logger = getLogger(self.app_name)
@ -98,7 +101,7 @@ class GameProcessApp(RareApp):
) )
) )
self.game_process.readyReadStandardError.connect( self.game_process.readyReadStandardError.connect(
lambda: self.console.log( lambda: self.console.error(
self.game_process.readAllStandardError().data().decode("utf-8", "ignore") self.game_process.readAllStandardError().data().decode("utf-8", "ignore")
) )
) )
@ -132,6 +135,8 @@ class GameProcessApp(RareApp):
def game_finished(self, exit_code): def game_finished(self, exit_code):
self.logger.info("game finished") self.logger.info("game finished")
if self.console:
self.console.on_process_exit(self.core.get_game(self.app_name).app_title, exit_code)
self.send_message( self.send_message(
FinishedModel( FinishedModel(
action=Actions.finished, action=Actions.finished,
@ -171,6 +176,8 @@ class GameProcessApp(RareApp):
def error_occurred(self, error_str: str): def error_occurred(self, error_str: str):
self.logger.warning(error_str) self.logger.warning(error_str)
if self.console:
self.console.on_process_exit(self.core.get_game(self.app_name).app_title, error_str)
self.send_message(ErrorModel( self.send_message(ErrorModel(
error_string=error_str, app_name=self.app_name, error_string=error_str, app_name=self.app_name,
action=Actions.error) action=Actions.error)
@ -206,10 +213,6 @@ class GameProcessApp(RareApp):
def start_game(args: Namespace): def start_game(args: Namespace):
args = InitArgs.from_argparse(args) args = InitArgs.from_argparse(args)
logging.basicConfig(
format="[%(name)s] %(levelname)s: %(message)s",
level=logging.INFO,
)
app = GameProcessApp(args) app = GameProcessApp(args)
app.setQuitOnLastWindowClosed(True) app.setQuitOnLastWindowClosed(True)

View file

@ -1,7 +1,8 @@
import platform import platform
from typing import Union
from PyQt5.QtCore import QProcessEnvironment, pyqtSignal, QSize from PyQt5.QtCore import QProcessEnvironment, pyqtSignal, QSize, Qt
from PyQt5.QtGui import QTextCursor, QFont, QCursor from PyQt5.QtGui import QTextCursor, QFont, QCursor, QCloseEvent
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QPlainTextEdit, QPlainTextEdit,
QDialog, QDialog,
@ -23,6 +24,7 @@ class Console(QDialog):
def __init__(self, parent=None): def __init__(self, parent=None):
super(Console, self).__init__(parent=parent) super(Console, self).__init__(parent=parent)
self.setAttribute(Qt.WA_DeleteOnClose, True)
self.setWindowTitle("Rare - Console") self.setWindowTitle("Rare - Console")
self.setGeometry(0, 0, 640, 480) self.setGeometry(0, 0, 640, 480)
layout = QVBoxLayout() layout = QVBoxLayout()
@ -46,15 +48,15 @@ class Console(QDialog):
button_layout.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Fixed)) button_layout.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Fixed))
self.terminate_button = QPushButton(self.tr("Terminate")) # self.terminate_button = QPushButton(self.tr("Terminate"))
self.terminate_button.setVisible(platform.system() == "Windows") # self.terminate_button.setVisible(platform.system() == "Windows")
button_layout.addWidget(self.terminate_button) # button_layout.addWidget(self.terminate_button)
self.terminate_button.clicked.connect(lambda: self.term.emit()) # self.terminate_button.clicked.connect(lambda: self.term.emit())
#
self.kill_button = QPushButton(self.tr("Kill")) # self.kill_button = QPushButton(self.tr("Kill"))
self.kill_button.setVisible(platform.system() == "Windows") # self.kill_button.setVisible(platform.system() == "Windows")
button_layout.addWidget(self.kill_button) # button_layout.addWidget(self.kill_button)
self.kill_button.clicked.connect(lambda: self.kill.emit()) # self.kill_button.clicked.connect(lambda: self.kill.emit())
layout.addLayout(button_layout) layout.addLayout(button_layout)
@ -63,6 +65,8 @@ class Console(QDialog):
self.env_variables = ConsoleEnv(self) self.env_variables = ConsoleEnv(self)
self.env_variables.hide() self.env_variables.hide()
self.accept_close = False
def show(self) -> None: def show(self) -> None:
super(Console, self).show() super(Console, self).show()
self.center_window() self.center_window()
@ -113,11 +117,26 @@ class Console(QDialog):
def error(self, text, end: str = "\n"): def error(self, text, end: str = "\n"):
self.console.error(text + end) self.console.error(text + end)
def on_process_exit(self, app_title: str, status: Union[int, str]):
self.error(
self.tr("Application \"{}\" finished with \"{}\"").format(app_title, status)
)
self.accept_close = True
def closeEvent(self, a0: QCloseEvent) -> None:
if self.accept_close:
super(Console, self).closeEvent(a0)
a0.accept()
else:
self.showMinimized()
a0.ignore()
class ConsoleEnv(QDialog): class ConsoleEnv(QDialog):
def __init__(self, parent=None): def __init__(self, parent=None):
super(ConsoleEnv, self).__init__(parent=parent) super(ConsoleEnv, self).__init__(parent=parent)
self.setAttribute(Qt.WA_DeleteOnClose, False)
self.ui = Ui_ConsoleEnv() self.ui = Ui_ConsoleEnv()
self.ui.setupUi(self) self.ui.setupUi(self)
@ -133,10 +152,13 @@ class ConsoleEnv(QDialog):
class ConsoleEdit(QPlainTextEdit): class ConsoleEdit(QPlainTextEdit):
def __init__(self, parent=None): def __init__(self, parent=None):
super(ConsoleEdit, self).__init__(parent=parent) super(ConsoleEdit, self).__init__(parent=parent)
self.setReadOnly(True) self.setReadOnly(True)
self.setFont(QFont("monospace")) font = QFont("Monospace")
font.setStyleHint(QFont.Monospace)
self.setFont(font)
self._cursor_output = self.textCursor() self._cursor_output = self.textCursor()
def log(self, text): def log(self, text):

View file

@ -21,6 +21,7 @@ class GameArgsError(Exception):
@dataclass @dataclass
class InitArgs: class InitArgs:
app_name: str app_name: str
debug: bool = False
offline: bool = False offline: bool = False
skip_version_check: bool = False skip_version_check: bool = False
wine_prefix: str = "" wine_prefix: str = ""
@ -30,10 +31,11 @@ class InitArgs:
def from_argparse(cls, args): def from_argparse(cls, args):
return cls( return cls(
app_name=args.app_name, app_name=args.app_name,
debug=args.debug,
offline=args.offline, offline=args.offline,
skip_version_check=args.skip_update_check, skip_version_check=args.skip_update_check,
wine_bin=args.wine_bin, wine_bin=args.wine_bin,
wine_prefix=args.wine_pfx wine_prefix=args.wine_pfx,
) )

View file

@ -1,3 +1,4 @@
from hashlib import sha1
from multiprocessing import Queue from multiprocessing import Queue
# On Windows the monkeypatching of `run_real` below doesn't work like on Linux # On Windows the monkeypatching of `run_real` below doesn't work like on Linux
@ -27,6 +28,30 @@ class LegendaryCore(LegendaryCoreReal):
# def get_installed_game(self, app_name, skip_sync=True) -> InstalledGame: # def get_installed_game(self, app_name, skip_sync=True) -> InstalledGame:
# return super(LegendaryCore, self).get_installed_game(app_name, skip_sync) # return super(LegendaryCore, self).get_installed_game(app_name, skip_sync)
# FIXME: delete this when legendary merges https://github.com/derrod/legendary/pull/477
def get_cdn_manifest(self, game, platform='Windows', disable_https=False):
manifest_urls, base_urls, manifest_hash = self.get_cdn_urls(game, platform)
if disable_https:
manifest_urls = [url.replace('https://', 'http://') for url in manifest_urls]
manifest_bytes = None
for url in manifest_urls:
self.log.debug(f'Trying to download manifest from {url} ...')
r = self.egs.unauth_session.get(url)
if r.ok:
manifest_bytes = r.content
break
else:
self.log.warning(f'Unable to download manifest from {url}, trying next one ...')
if not manifest_bytes:
raise ValueError('Unable to get manifest data from any CDN URL')
if sha1(manifest_bytes).hexdigest() != manifest_hash:
raise ValueError('Manifest sha hash mismatch!')
return manifest_bytes, base_urls
def prepare_download(self, game: Game, base_game: Game = None, base_path: str = '', def prepare_download(self, game: Game, base_game: Game = None, base_path: str = '',
status_q: Queue = None, max_shm: int = 0, max_workers: int = 0, status_q: Queue = None, max_shm: int = 0, max_workers: int = 0,
force: bool = False, disable_patching: bool = False, force: bool = False, disable_patching: bool = False,

View file

@ -189,6 +189,7 @@ def get_rare_executable() -> List[str]:
else: else:
executable = [sys.executable] executable = [sys.executable]
executable[0] = os.path.abspath(executable[0])
return executable return executable
@ -222,7 +223,7 @@ def create_desktop_link(app_name=None, core: LegendaryCore = None, type_of_link=
f"Icon={os.path.join(resources_path, 'images', 'Rare.png')}\n" f"Icon={os.path.join(resources_path, 'images', 'Rare.png')}\n"
f"Exec={executable}\n" f"Exec={executable}\n"
"Terminal=false\n" "Terminal=false\n"
"StartupWMClass=rare\n" "StartupWMClass=Rare\n"
) )
else: else:
with open(os.path.join(path, f"{igame.title}.desktop"), "w") as desktop_file: with open(os.path.join(path, f"{igame.title}.desktop"), "w") as desktop_file:
@ -234,7 +235,7 @@ def create_desktop_link(app_name=None, core: LegendaryCore = None, type_of_link=
f"Icon={icon}.png\n" f"Icon={icon}.png\n"
f"Exec={executable} launch {app_name}\n" f"Exec={executable} launch {app_name}\n"
"Terminal=false\n" "Terminal=false\n"
"StartupWMClass=rare-game\n" "StartupWMClass=Rare\n"
) )
os.chmod(os.path.join(path, f"{igame.title}.desktop"), 0o755) os.chmod(os.path.join(path, f"{igame.title}.desktop"), 0o755)

View file

@ -1,24 +1,24 @@
import logging
import os import os
import platform
import sys import sys
import time
from argparse import Namespace from argparse import Namespace
from logging import getLogger
from PyQt5.QtCore import Qt, QSettings, QTranslator import legendary
from PyQt5.QtCore import Qt, QSettings, QTranslator, QT_VERSION_STR, PYQT_VERSION_STR
from PyQt5.QtGui import QIcon from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QApplication from PyQt5.QtWidgets import QApplication
# noinspection PyUnresolvedReferences
from legendary.core import LegendaryCore
import rare.resources.resources import rare.resources.resources
from rare.utils import paths from rare.utils import paths
from rare.utils.misc import set_color_pallete, set_style_sheet from rare.utils.misc import set_color_pallete, set_style_sheet
class RareApp(QApplication): class RareApp(QApplication):
logger = getLogger("RareApp") logger = logging.getLogger("RareApp")
def __init__(self, args: Namespace): def __init__(self, args: Namespace, log_file: str):
super(RareApp, self).__init__(sys.argv) super(RareApp, self).__init__(sys.argv)
self.setQuitOnLastWindowClosed(False) self.setQuitOnLastWindowClosed(False)
if hasattr(Qt, "AA_UseHighDpiPixmaps"): if hasattr(Qt, "AA_UseHighDpiPixmaps"):
@ -26,7 +26,53 @@ class RareApp(QApplication):
self.setApplicationName("Rare") self.setApplicationName("Rare")
self.setOrganizationName("Rare") self.setOrganizationName("Rare")
# Create directories after QStandardPaths has been initialized
paths.create_dirs() paths.create_dirs()
# Clean any existing logging handlers from library imports
for handler in logging.root.handlers[:]:
logging.root.removeHandler(handler)
start_time = time.strftime("%y-%m-%d--%H-%M") # year-month-day-hour-minute
file_handler = logging.FileHandler(
filename=os.path.join(paths.log_dir(), log_file.format(start_time)),
encoding="utf-8",
)
file_handler.setFormatter(fmt=logging.Formatter("[%(name)s] %(levelname)s: %(message)s"))
# Set up common logging channel to stderr
if args.debug:
logging.basicConfig(
format="[%(name)s] %(levelname)s: %(message)s",
level=logging.DEBUG,
stream=sys.stderr,
)
file_handler.setLevel(logging.DEBUG)
logging.root.addHandler(file_handler)
logging.getLogger().setLevel(level=logging.DEBUG)
# keep requests, asyncio and pillow quiet
logging.getLogger("requests").setLevel(logging.WARNING)
logging.getLogger("urllib3").setLevel(logging.WARNING)
logging.getLogger("asyncio").setLevel(logging.WARNING)
self.logger.info(
f"Launching Rare version {rare.__version__} Codename: {rare.code_name}\n"
f" - Using Legendary {legendary.__version__} Codename: {legendary.__codename__} as backend\n"
f" - Operating System: {platform.system()}, Python version: {platform.python_version()}\n"
f" - Running {sys.executable} {' '.join(sys.argv)}\n"
f" - Qt version: {QT_VERSION_STR}, PyQt version: {PYQT_VERSION_STR}"
)
else:
logging.basicConfig(
format="[%(name)s] %(levelname)s: %(message)s",
level=logging.INFO,
stream=sys.stderr,
)
file_handler.setLevel(logging.DEBUG)
logging.root.addHandler(file_handler)
self.logger.info(f"Launching Rare version {rare.__version__}")
self.logger.info(f"Operating System: {platform.system()}")
self.settings = QSettings() self.settings = QSettings()
# Translator # Translator

10
requirements-full.txt Normal file
View file

@ -0,0 +1,10 @@
typing_extensions
requests
PyQt5
QtAwesome
setuptools
legendary-gl
pywin32; platform_system == "Windows"
pywebview[qt]; platform_system == "Linux"
pywebview[cef]; platform_system == "Windows"
pypresence

View file

@ -0,0 +1,2 @@
pypresence

3
requirements-webview.txt Normal file
View file

@ -0,0 +1,3 @@
pywebview[qt]; platform_system == "Linux"
pywebview[cef]; platform_system == "Windows"