Merge pull request #240 from loathingKernel/fixups
Initialize logging in RareApp and log both Rare and GameLaunchHelper
This commit is contained in:
commit
8afdabbb49
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -19,5 +19,9 @@ __pycache__
|
|||
/AppDir/
|
||||
/System Volume Information/
|
||||
/test_files/
|
||||
# Nuitka build artifacts
|
||||
/rare.build
|
||||
/rare.dist
|
||||
/rare.bin
|
||||
/rare.cmd
|
||||
/rare.exe
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
pywebview[gtk]; platform_system == "Linux"
|
||||
pywebview[cef]; platform_system == "Windows"
|
||||
pypresence
|
||||
|
|
@ -114,6 +114,7 @@ if __name__ == "__main__":
|
|||
# )
|
||||
|
||||
# insert source directory
|
||||
if "__compiled__" not in globals():
|
||||
sys.path.insert(0, str(pathlib.Path(__file__).parents[1].absolute()))
|
||||
|
||||
main()
|
||||
|
|
52
rare/app.py
52
rare/app.py
|
@ -1,21 +1,17 @@
|
|||
import logging
|
||||
import os
|
||||
import platform
|
||||
import shutil
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
from argparse import Namespace
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
import legendary
|
||||
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 requests import HTTPError
|
||||
|
||||
import rare
|
||||
from rare.components.dialogs.launch_dialog import LaunchDialog
|
||||
from rare.components.main_window import MainWindow
|
||||
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.widgets.rare_app import RareApp
|
||||
|
||||
|
||||
logger = logging.getLogger("Rare")
|
||||
|
||||
|
||||
|
@ -52,49 +47,8 @@ def excepthook(exc_type, exc_value, exc_tb):
|
|||
|
||||
class App(RareApp):
|
||||
def __init__(self, args: Namespace):
|
||||
super(App, self).__init__(args)
|
||||
|
||||
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()}")
|
||||
|
||||
log_file = "Rare_{0}.log"
|
||||
super(App, self).__init__(args, log_file)
|
||||
self.rare_core = RareCore(args=args)
|
||||
self.args = ArgumentsSingleton()
|
||||
self.signals = GlobalSignalsSingleton()
|
||||
|
|
|
@ -19,7 +19,7 @@ class MainWindow(QMainWindow):
|
|||
|
||||
def __init__(self, parent=None):
|
||||
super(MainWindow, self).__init__(parent=parent)
|
||||
self.setAttribute(Qt.WA_DeleteOnClose)
|
||||
self.setAttribute(Qt.WA_DeleteOnClose, True)
|
||||
self.core = LegendaryCoreSingleton()
|
||||
self.signals = GlobalSignalsSingleton()
|
||||
self.args = ArgumentsSingleton()
|
||||
|
|
|
@ -17,6 +17,8 @@ from .console import Console
|
|||
from .lgd_helper import get_launch_args, InitArgs, get_configured_process, LaunchArgs, GameArgsError
|
||||
from .message_models import ErrorModel, Actions, FinishedModel, BaseModel, StateChangedModel
|
||||
|
||||
logger = logging.getLogger("RareLauncher")
|
||||
|
||||
|
||||
class PreLaunchThread(QRunnable):
|
||||
class Signals(QObject):
|
||||
|
@ -65,7 +67,8 @@ class GameProcessApp(RareApp):
|
|||
success: bool = True
|
||||
|
||||
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.app_name = args.app_name
|
||||
self.logger = getLogger(self.app_name)
|
||||
|
@ -98,7 +101,7 @@ class GameProcessApp(RareApp):
|
|||
)
|
||||
)
|
||||
self.game_process.readyReadStandardError.connect(
|
||||
lambda: self.console.log(
|
||||
lambda: self.console.error(
|
||||
self.game_process.readAllStandardError().data().decode("utf-8", "ignore")
|
||||
)
|
||||
)
|
||||
|
@ -132,6 +135,8 @@ class GameProcessApp(RareApp):
|
|||
|
||||
def game_finished(self, exit_code):
|
||||
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(
|
||||
FinishedModel(
|
||||
action=Actions.finished,
|
||||
|
@ -171,6 +176,8 @@ class GameProcessApp(RareApp):
|
|||
|
||||
def error_occurred(self, error_str: 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(
|
||||
error_string=error_str, app_name=self.app_name,
|
||||
action=Actions.error)
|
||||
|
@ -206,10 +213,6 @@ class GameProcessApp(RareApp):
|
|||
|
||||
def start_game(args: Namespace):
|
||||
args = InitArgs.from_argparse(args)
|
||||
logging.basicConfig(
|
||||
format="[%(name)s] %(levelname)s: %(message)s",
|
||||
level=logging.INFO,
|
||||
)
|
||||
|
||||
app = GameProcessApp(args)
|
||||
app.setQuitOnLastWindowClosed(True)
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import platform
|
||||
from typing import Union
|
||||
|
||||
from PyQt5.QtCore import QProcessEnvironment, pyqtSignal, QSize
|
||||
from PyQt5.QtGui import QTextCursor, QFont, QCursor
|
||||
from PyQt5.QtCore import QProcessEnvironment, pyqtSignal, QSize, Qt
|
||||
from PyQt5.QtGui import QTextCursor, QFont, QCursor, QCloseEvent
|
||||
from PyQt5.QtWidgets import (
|
||||
QPlainTextEdit,
|
||||
QDialog,
|
||||
|
@ -23,6 +24,7 @@ class Console(QDialog):
|
|||
|
||||
def __init__(self, parent=None):
|
||||
super(Console, self).__init__(parent=parent)
|
||||
self.setAttribute(Qt.WA_DeleteOnClose, True)
|
||||
self.setWindowTitle("Rare - Console")
|
||||
self.setGeometry(0, 0, 640, 480)
|
||||
layout = QVBoxLayout()
|
||||
|
@ -46,15 +48,15 @@ class Console(QDialog):
|
|||
|
||||
button_layout.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Fixed))
|
||||
|
||||
self.terminate_button = QPushButton(self.tr("Terminate"))
|
||||
self.terminate_button.setVisible(platform.system() == "Windows")
|
||||
button_layout.addWidget(self.terminate_button)
|
||||
self.terminate_button.clicked.connect(lambda: self.term.emit())
|
||||
|
||||
self.kill_button = QPushButton(self.tr("Kill"))
|
||||
self.kill_button.setVisible(platform.system() == "Windows")
|
||||
button_layout.addWidget(self.kill_button)
|
||||
self.kill_button.clicked.connect(lambda: self.kill.emit())
|
||||
# self.terminate_button = QPushButton(self.tr("Terminate"))
|
||||
# self.terminate_button.setVisible(platform.system() == "Windows")
|
||||
# button_layout.addWidget(self.terminate_button)
|
||||
# self.terminate_button.clicked.connect(lambda: self.term.emit())
|
||||
#
|
||||
# self.kill_button = QPushButton(self.tr("Kill"))
|
||||
# self.kill_button.setVisible(platform.system() == "Windows")
|
||||
# button_layout.addWidget(self.kill_button)
|
||||
# self.kill_button.clicked.connect(lambda: self.kill.emit())
|
||||
|
||||
layout.addLayout(button_layout)
|
||||
|
||||
|
@ -63,6 +65,8 @@ class Console(QDialog):
|
|||
self.env_variables = ConsoleEnv(self)
|
||||
self.env_variables.hide()
|
||||
|
||||
self.accept_close = False
|
||||
|
||||
def show(self) -> None:
|
||||
super(Console, self).show()
|
||||
self.center_window()
|
||||
|
@ -113,11 +117,26 @@ class Console(QDialog):
|
|||
def error(self, text, end: str = "\n"):
|
||||
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):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(ConsoleEnv, self).__init__(parent=parent)
|
||||
self.setAttribute(Qt.WA_DeleteOnClose, False)
|
||||
self.ui = Ui_ConsoleEnv()
|
||||
self.ui.setupUi(self)
|
||||
|
||||
|
@ -133,10 +152,13 @@ class ConsoleEnv(QDialog):
|
|||
|
||||
|
||||
class ConsoleEdit(QPlainTextEdit):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(ConsoleEdit, self).__init__(parent=parent)
|
||||
self.setReadOnly(True)
|
||||
self.setFont(QFont("monospace"))
|
||||
font = QFont("Monospace")
|
||||
font.setStyleHint(QFont.Monospace)
|
||||
self.setFont(font)
|
||||
self._cursor_output = self.textCursor()
|
||||
|
||||
def log(self, text):
|
||||
|
|
|
@ -21,6 +21,7 @@ class GameArgsError(Exception):
|
|||
@dataclass
|
||||
class InitArgs:
|
||||
app_name: str
|
||||
debug: bool = False
|
||||
offline: bool = False
|
||||
skip_version_check: bool = False
|
||||
wine_prefix: str = ""
|
||||
|
@ -30,10 +31,11 @@ class InitArgs:
|
|||
def from_argparse(cls, args):
|
||||
return cls(
|
||||
app_name=args.app_name,
|
||||
debug=args.debug,
|
||||
offline=args.offline,
|
||||
skip_version_check=args.skip_update_check,
|
||||
wine_bin=args.wine_bin,
|
||||
wine_prefix=args.wine_pfx
|
||||
wine_prefix=args.wine_pfx,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from hashlib import sha1
|
||||
from multiprocessing import Queue
|
||||
|
||||
# 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:
|
||||
# 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 = '',
|
||||
status_q: Queue = None, max_shm: int = 0, max_workers: int = 0,
|
||||
force: bool = False, disable_patching: bool = False,
|
||||
|
|
|
@ -189,6 +189,7 @@ def get_rare_executable() -> List[str]:
|
|||
else:
|
||||
executable = [sys.executable]
|
||||
|
||||
executable[0] = os.path.abspath(executable[0])
|
||||
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"Exec={executable}\n"
|
||||
"Terminal=false\n"
|
||||
"StartupWMClass=rare\n"
|
||||
"StartupWMClass=Rare\n"
|
||||
)
|
||||
else:
|
||||
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"Exec={executable} launch {app_name}\n"
|
||||
"Terminal=false\n"
|
||||
"StartupWMClass=rare-game\n"
|
||||
"StartupWMClass=Rare\n"
|
||||
)
|
||||
os.chmod(os.path.join(path, f"{igame.title}.desktop"), 0o755)
|
||||
|
||||
|
|
|
@ -1,24 +1,24 @@
|
|||
import logging
|
||||
import os
|
||||
import platform
|
||||
import sys
|
||||
import time
|
||||
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.QtWidgets import QApplication
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
from legendary.core import LegendaryCore
|
||||
|
||||
import rare.resources.resources
|
||||
from rare.utils import paths
|
||||
from rare.utils.misc import set_color_pallete, set_style_sheet
|
||||
|
||||
|
||||
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)
|
||||
self.setQuitOnLastWindowClosed(False)
|
||||
if hasattr(Qt, "AA_UseHighDpiPixmaps"):
|
||||
|
@ -26,7 +26,53 @@ class RareApp(QApplication):
|
|||
|
||||
self.setApplicationName("Rare")
|
||||
self.setOrganizationName("Rare")
|
||||
|
||||
# Create directories after QStandardPaths has been initialized
|
||||
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()
|
||||
|
||||
# Translator
|
||||
|
|
10
requirements-full.txt
Normal file
10
requirements-full.txt
Normal 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
|
2
requirements-presence.txt
Normal file
2
requirements-presence.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
pypresence
|
||||
|
3
requirements-webview.txt
Normal file
3
requirements-webview.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
pywebview[qt]; platform_system == "Linux"
|
||||
pywebview[cef]; platform_system == "Windows"
|
||||
|
Loading…
Reference in a new issue