1
0
Fork 0
mirror of synced 2024-05-18 11:32:50 +12:00

Rare: Move the different applications into their respective folders

This commit is contained in:
loathingKernel 2023-09-01 13:21:32 +03:00
parent 5f062830cf
commit bdbb61d3a6
No known key found for this signature in database
GPG key ID: CE0C72D0B53821FD
12 changed files with 269 additions and 262 deletions

View file

@ -1,2 +1,10 @@
__version__ = "1.10.7"
__codename__ = "Garlic Crab"
# For PyCharm profiler
if __name__ == "__main__":
import sys
from argparse import Namespace
from rare import client
status = client.start(Namespace(debug=True, silent=False, offline=False, test_start=False))
sys.exit(status)

View file

@ -1,114 +1,10 @@
#!/usr/bin/env python3
import multiprocessing
import os
import pathlib
import sys
from argparse import ArgumentParser
def main():
# fix cx_freeze
multiprocessing.freeze_support()
# insert legendary for installed via pip/setup.py submodule to path
# if not __name__ == "__main__":
# sys.path.insert(0, os.path.join(os.path.dirname(__file__), "legendary"))
# CLI Options
parser = ArgumentParser()
parser.add_argument(
"-V", "--version", action="store_true", help="Shows version and exits"
)
parser.add_argument(
"-S",
"--silent",
action="store_true",
help="Launch Rare in background. Open it from System Tray Icon",
)
parser.add_argument("--debug", action="store_true", help="Launch in debug mode")
parser.add_argument(
"--offline", action="store_true", help="Launch Rare in offline mode"
)
parser.add_argument(
"--test-start", action="store_true", help="Quit immediately after launch"
)
parser.add_argument(
"--desktop-shortcut",
action="store_true",
dest="desktop_shortcut",
help="Use this, if there is no link on desktop to start Rare",
)
parser.add_argument(
"--startmenu-shortcut",
action="store_true",
dest="startmenu_shortcut",
help="Use this, if there is no link in start menu to launch Rare",
)
subparsers = parser.add_subparsers(title="Commands", dest="subparser")
launch_minimal_parser = subparsers.add_parser("start", aliases=["launch"])
launch_minimal_parser.add_argument("app_name", help="AppName of the game to launch",
metavar="<App Name>", action="store")
launch_minimal_parser.add_argument("--dry-run", help="Print arguments and exit", action="store_true")
launch_minimal_parser.add_argument("--offline", help="Launch game offline",
action="store_true")
launch_minimal_parser.add_argument('--wine-bin', dest='wine_bin', action='store', metavar='<wine binary>',
default=os.environ.get('LGDRY_WINE_BINARY', None),
help='Set WINE binary to use to launch the app')
launch_minimal_parser.add_argument('--wine-prefix', dest='wine_pfx', action='store', metavar='<wine pfx path>',
default=os.environ.get('LGDRY_WINE_PREFIX', None),
help='Set WINE prefix to use')
launch_minimal_parser.add_argument("--ask-sync-saves", help="Ask to sync cloud saves",
action="store_true")
launch_minimal_parser.add_argument("--skip-update-check", help="Do not check for updates",
action="store_true")
args = parser.parse_args()
if args.desktop_shortcut or args.startmenu_shortcut:
from rare.utils.paths import create_desktop_link
if args.desktop_shortcut:
create_desktop_link(app_name="rare_shortcut", link_type="desktop")
if args.startmenu_shortcut:
create_desktop_link(app_name="rare_shortcut", link_type="start_menu")
print("Link created")
return
if args.version:
from rare import __version__, code_name
print(f"Rare {__version__} Codename: {code_name}")
return
if args.subparser == "start" or args.subparser == "launch":
from rare import game_launch_helper as helper
helper.start_game(args)
return
from rare.utils import singleton
try:
# this object only allows one instance per machine
me = singleton.SingleInstance()
except singleton.SingleInstanceException:
print("Rare is already running")
from rare.utils.paths import lock_file
with open(lock_file(), "w") as file:
file.write("show")
file.close()
return
from rare.app import start
start(args)
if __name__ == "__main__":
from rare.main import main
# run from source
# insert raw legendary submodule
# sys.path.insert(
@ -130,4 +26,4 @@ if __name__ == "__main__":
if sys.stderr is None:
sys.stderr = open(os.devnull, 'w')
main()
sys.exit(main())

View file

@ -1,132 +0,0 @@
import logging
import os
import shutil
import sys
import traceback
from argparse import Namespace
from datetime import datetime, timezone
from typing import Optional
import requests.exceptions
from PyQt5.QtCore import QThreadPool, QTimer, pyqtSlot, Qt
from PyQt5.QtWidgets import QApplication, QMessageBox
from requests import HTTPError
from rare.components.dialogs.launch_dialog import LaunchDialog
from rare.components.main_window import MainWindow
from rare.shared import RareCore
from rare.utils import config_helper, paths
from rare.widgets.rare_app import RareApp, RareAppException
logger = logging.getLogger("Rare")
class RareException(RareAppException):
def __init__(self, parent=None):
super(RareException, self).__init__(parent=parent)
def _handler(self, exc_type, exc_value, exc_tb) -> bool:
if exc_type == HTTPError:
try:
if RareCore.instance() is not None:
if RareCore.instance().core().login():
return True
raise ValueError
except Exception as e:
logger.fatal(str(e))
QMessageBox.warning(None, "Error", self.tr("Failed to login"))
QApplication.exit(1)
return False
class Rare(RareApp):
def __init__(self, args: Namespace):
log_file = "Rare_{0}.log"
super(Rare, self).__init__(args, log_file)
self._hook.deleteLater()
self._hook = RareException(self)
self.rcore = RareCore(args=args)
self.args = RareCore.instance().args()
self.signals = RareCore.instance().signals()
self.core = RareCore.instance().core()
config_helper.init_config_handler(self.core)
lang = self.settings.value("language", self.core.language_code, type=str)
self.load_translator(lang)
# set Application name for settings
self.main_window: Optional[MainWindow] = None
self.launch_dialog: Optional[LaunchDialog] = None
self.timer: Optional[QTimer] = None
# launch app
self.launch_dialog = LaunchDialog(parent=None)
self.launch_dialog.quit_app.connect(self.launch_dialog.close)
self.launch_dialog.quit_app.connect(lambda x: sys.exit(x))
self.launch_dialog.start_app.connect(self.start_app)
self.launch_dialog.start_app.connect(self.launch_dialog.close)
self.launch_dialog.login()
def poke_timer(self):
dt_exp = datetime.fromisoformat(self.core.lgd.userdata['expires_at'][:-1]).replace(tzinfo=timezone.utc)
dt_now = datetime.utcnow().replace(tzinfo=timezone.utc)
td = abs(dt_exp - dt_now)
self.timer.start(int(td.total_seconds() - 60) * 1000)
logger.info(f"Renewed session expires at {self.core.lgd.userdata['expires_at']}")
def re_login(self):
logger.info("Session expires shortly. Renew session")
try:
self.core.login()
except requests.exceptions.ConnectionError:
self.timer.start(3000) # try again if no connection
return
self.poke_timer()
def start_app(self):
self.timer = QTimer()
self.timer.timeout.connect(self.re_login)
self.poke_timer()
self.main_window = MainWindow()
self.main_window.exit_app.connect(self.on_exit_app)
if not self.args.silent:
self.main_window.show()
if self.args.test_start:
self.main_window.close()
self.main_window = None
self.on_exit_app(0)
@pyqtSlot()
@pyqtSlot(int)
def on_exit_app(self, exit_code=0):
threadpool = QThreadPool.globalInstance()
threadpool.waitForDone()
if self.timer is not None:
self.timer.stop()
self.timer.deleteLater()
self.timer = None
self.rcore.deleteLater()
del self.rcore
self.processEvents()
shutil.rmtree(paths.tmp_dir())
os.makedirs(paths.tmp_dir())
self.exit(exit_code)
def start(args):
while True:
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True)
app = Rare(args)
exit_code = app.exec_()
# if not restart
# restart app
del app
if exit_code != -133742:
break

View file

@ -0,0 +1,128 @@
import os
import shutil
import sys
from argparse import Namespace
from datetime import datetime, timezone
from typing import Optional
import requests.exceptions
from PyQt5.QtCore import QThreadPool, QTimer, pyqtSlot, Qt
from PyQt5.QtWidgets import QApplication, QMessageBox
from requests import HTTPError
from rare.components.dialogs.launch_dialog import LaunchDialog
from rare.components.main_window import MainWindow
from rare.shared import RareCore
from rare.utils import config_helper, paths
from rare.widgets.rare_app import RareApp, RareAppException
class RareException(RareAppException):
def __init__(self, parent=None):
super(RareException, self).__init__(parent=parent)
def _handler(self, exc_type, exc_value, exc_tb) -> bool:
if exc_type == HTTPError:
try:
if RareCore.instance() is not None:
if RareCore.instance().core().login():
return True
raise ValueError
except Exception as e:
self.logger.fatal(str(e))
QMessageBox.warning(None, "Error", self.tr("Failed to login"))
QApplication.exit(1)
return False
class Rare(RareApp):
def __init__(self, args: Namespace):
super(Rare, self).__init__(args, f"{type(self).__name__}_{{0}}.log")
self._hook.deleteLater()
self._hook = RareException(self)
self.rcore = RareCore(args=args)
self.args = RareCore.instance().args()
self.signals = RareCore.instance().signals()
self.core = RareCore.instance().core()
config_helper.init_config_handler(self.core)
lang = self.settings.value("language", self.core.language_code, type=str)
self.load_translator(lang)
# set Application name for settings
self.main_window: Optional[MainWindow] = None
self.launch_dialog: Optional[LaunchDialog] = None
self.timer: Optional[QTimer] = None
# launch app
self.launch_dialog = LaunchDialog(parent=None)
self.launch_dialog.quit_app.connect(self.launch_dialog.close)
self.launch_dialog.quit_app.connect(lambda x: sys.exit(x))
self.launch_dialog.start_app.connect(self.start_app)
self.launch_dialog.start_app.connect(self.launch_dialog.close)
self.launch_dialog.login()
def poke_timer(self):
dt_exp = datetime.fromisoformat(self.core.lgd.userdata['expires_at'][:-1]).replace(tzinfo=timezone.utc)
dt_now = datetime.utcnow().replace(tzinfo=timezone.utc)
td = abs(dt_exp - dt_now)
self.timer.start(int(td.total_seconds() - 60) * 1000)
self.logger.info(f"Renewed session expires at {self.core.lgd.userdata['expires_at']}")
def re_login(self):
self.logger.info("Session expires shortly. Renew session")
try:
self.core.login()
except requests.exceptions.ConnectionError:
self.timer.start(3000) # try again if no connection
return
self.poke_timer()
def start_app(self):
self.timer = QTimer()
self.timer.timeout.connect(self.re_login)
self.poke_timer()
self.main_window = MainWindow()
self.main_window.exit_app.connect(self.on_exit_app)
if not self.args.silent:
self.main_window.show()
if self.args.test_start:
self.main_window.close()
self.main_window = None
self.on_exit_app(0)
@pyqtSlot()
@pyqtSlot(int)
def on_exit_app(self, exit_code=0):
threadpool = QThreadPool.globalInstance()
threadpool.waitForDone()
if self.timer is not None:
self.timer.stop()
self.timer.deleteLater()
self.timer = None
self.rcore.deleteLater()
del self.rcore
self.processEvents()
shutil.rmtree(paths.tmp_dir())
os.makedirs(paths.tmp_dir())
self.exit(exit_code)
def start(args) -> int:
while True:
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True)
app = Rare(args)
exit_code = app.exec_()
# if not restart
# restart app
del app
if exit_code != -133742:
break
return exit_code

View file

@ -1,14 +1,12 @@
import json
import logging
import platform
import subprocess
import sys
import time
import traceback
from argparse import Namespace
from logging import getLogger
from signal import signal, SIGINT, SIGTERM, strsignal
from typing import Union, Optional
from typing import Optional
from PyQt5.QtCore import QObject, QProcess, pyqtSignal, QUrl, QRunnable, QThreadPool, QSettings, Qt, pyqtSlot
from PyQt5.QtGui import QDesktopServices
@ -24,8 +22,6 @@ from rare.widgets.rare_app import RareApp, RareAppException
from .console import Console
from .lgd_helper import get_launch_args, InitArgs, get_configured_process, LaunchArgs, GameArgsError
logger = logging.getLogger("RareLauncher")
DETACHED_APP_NAMES = [
"0a2d9f6403244d12969e11da6713137b"
]
@ -40,6 +36,7 @@ class PreLaunchThread(QRunnable):
def __init__(self, core: LegendaryCore, args: InitArgs, rgame: RareGameSlim, sync_action=None):
super(PreLaunchThread, self).__init__()
self.logger = getLogger(type(self).__name__)
self.core = core
self.signals = self.Signals()
self.args = args
@ -47,13 +44,13 @@ class PreLaunchThread(QRunnable):
self.sync_action = sync_action
def run(self) -> None:
logger.info(f"Sync action: {self.sync_action}")
self.logger.info(f"Sync action: {self.sync_action}")
if self.sync_action == CloudSaveDialog.UPLOAD:
self.rgame.upload_saves(False)
elif self.sync_action == CloudSaveDialog.DOWNLOAD:
self.rgame.download_saves(False)
else:
logger.info("No sync action")
self.logger.info("No sync action")
args = self.prepare_launch(self.args)
if not args:
@ -121,11 +118,9 @@ class RareLauncher(RareApp):
exit_app = pyqtSignal()
def __init__(self, args: InitArgs):
log_file = f"Rare_Launcher_{args.app_name}" + "_{0}.log"
super(RareLauncher, self).__init__(args, log_file)
super(RareLauncher, self).__init__(args, f"{type(self).__name__}_{args.app_name}_{{0}}.log")
self._hook.deleteLater()
self._hook = RareLauncherException(self, args, self)
self.logger = getLogger(f"Launcher_{args.app_name}")
self.success: bool = True
self.no_sync_on_exit = False
@ -280,7 +275,7 @@ class RareLauncher(RareApp):
self.stop()
return
if self.args.dry_run:
logger.info("Dry run activated")
self.logger.info("Dry run activated")
if self.console:
self.console.log(f"{args.executable} {' '.join(args.args)}")
self.console.log(f"Do not start {self.rgame.app_name}")
@ -338,7 +333,7 @@ class RareLauncher(RareApp):
args.offline = True
if not args.offline and self.rgame.auto_sync_saves:
logger.info("Start sync worker")
self.logger.info("Start sync worker")
worker = SyncCheckWorker(self.core, self.rgame)
worker.signals.error_occurred.connect(self.error_occurred)
worker.signals.sync_state_ready.connect(self.sync_ready)
@ -355,7 +350,7 @@ class RareLauncher(RareApp):
self.game_process.finished.disconnect()
self.game_process.errorOccurred.disconnect()
except TypeError as e:
logger.error(f"Failed to disconnect signals: {e}")
self.logger.error(f"Failed to disconnect signals: {e}")
self.logger.info("Stopping server")
try:
self.server.close()
@ -369,7 +364,7 @@ class RareLauncher(RareApp):
self.console.on_process_exit(self.rgame.app_name, 0)
def start_game(args: Namespace):
def launch(args: Namespace):
args = InitArgs.from_argparse(args)
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
@ -381,7 +376,7 @@ def start_game(args: Namespace):
# This prevents ghost QLocalSockets, which block the name, which makes it unable to start
# No handling for SIGKILL
def sighandler(s, frame):
logger.info(f"{strsignal(s)} received. Stopping")
app.logger.info(f"{strsignal(s)} received. Stopping")
app.stop()
app.exit(1)
signal(SIGINT, sighandler)
@ -392,4 +387,4 @@ def start_game(args: Namespace):
app.start(args)
# app.exit_app.connect(lambda: app.exit(0))
sys.exit(app.exec_())
return app.exec_()

103
rare/main.py Normal file
View file

@ -0,0 +1,103 @@
import multiprocessing
import os
from argparse import ArgumentParser
def main() -> int:
# fix cx_freeze
multiprocessing.freeze_support()
# insert legendary for installed via pip/setup.py submodule to path
# if not __name__ == "__main__":
# sys.path.insert(0, os.path.join(os.path.dirname(__file__), "legendary"))
# CLI Options
parser = ArgumentParser()
parser.add_argument(
"-V", "--version", action="store_true", help="Shows version and exits"
)
parser.add_argument(
"-S",
"--silent",
action="store_true",
help="Launch Rare in background. Open it from System Tray Icon",
)
parser.add_argument("--debug", action="store_true", help="Launch in debug mode")
parser.add_argument(
"--offline", action="store_true", help="Launch Rare in offline mode"
)
parser.add_argument(
"--test-start", action="store_true", help="Quit immediately after launch"
)
parser.add_argument(
"--desktop-shortcut",
action="store_true",
dest="desktop_shortcut",
help="Use this, if there is no link on desktop to start Rare",
)
parser.add_argument(
"--startmenu-shortcut",
action="store_true",
dest="startmenu_shortcut",
help="Use this, if there is no link in start menu to launch Rare",
)
subparsers = parser.add_subparsers(title="Commands", dest="subparser")
launch_minimal_parser = subparsers.add_parser("start", aliases=["launch"])
launch_minimal_parser.add_argument("app_name", help="AppName of the game to launch",
metavar="<App Name>", action="store")
launch_minimal_parser.add_argument("--dry-run", help="Print arguments and exit", action="store_true")
launch_minimal_parser.add_argument("--offline", help="Launch game offline",
action="store_true")
launch_minimal_parser.add_argument('--wine-bin', dest='wine_bin', action='store', metavar='<wine binary>',
default=os.environ.get('LGDRY_WINE_BINARY', None),
help='Set WINE binary to use to launch the app')
launch_minimal_parser.add_argument('--wine-prefix', dest='wine_pfx', action='store', metavar='<wine pfx path>',
default=os.environ.get('LGDRY_WINE_PREFIX', None),
help='Set WINE prefix to use')
launch_minimal_parser.add_argument("--ask-sync-saves", help="Ask to sync cloud saves",
action="store_true")
launch_minimal_parser.add_argument("--skip-update-check", help="Do not check for updates",
action="store_true")
args = parser.parse_args()
if args.desktop_shortcut or args.startmenu_shortcut:
from rare.utils.paths import create_desktop_link
if args.desktop_shortcut:
create_desktop_link(app_name="rare_shortcut", link_type="desktop")
if args.startmenu_shortcut:
create_desktop_link(app_name="rare_shortcut", link_type="start_menu")
print("Link created")
return 0
if args.version:
from rare import __version__, __codename__
print(f"Rare {__version__} Codename: {__codename__}")
return 0
if args.subparser == "start" or args.subparser == "launch":
from rare.launcher import launch
return launch(args)
from rare.utils import singleton
try:
# this object only allows one instance per machine
me = singleton.SingleInstance()
except singleton.SingleInstanceException:
print("Rare is already running")
from rare.utils.paths import lock_file
with open(lock_file(), "w") as file:
file.write("show")
file.close()
return -1
from rare.components import start
return start(args)

View file

@ -550,7 +550,7 @@ class RareGame(RareGameSlim):
cmd_line = get_rare_executable()
executable, args = cmd_line[0], cmd_line[1:]
args.extend(["start", self.app_name])
args.extend(["launch", self.app_name])
if offline:
args.append("--offline")
if skip_update_check:

View file

@ -129,6 +129,7 @@ def desktop_link_path(link_name: str, link_type: str) -> Path:
def get_rare_executable() -> List[str]:
logger.debug(f"Trying to find executable: {sys.executable}, {sys.argv}")
# lk: detect if nuitka
if "__compiled__" in globals():
executable = [sys.executable]
@ -141,7 +142,7 @@ def get_rare_executable() -> List[str]:
if sys.executable == os.path.abspath(sys.argv[0]):
executable = [sys.executable]
else:
executable = [os.path.abspath(sys.argv[0])]
executable = [sys.executable, os.path.abspath(sys.argv[0])]
elif platform.system() == "Windows":
executable = [sys.executable]

View file

@ -20,8 +20,8 @@ class RareAppException(QObject):
exception = pyqtSignal(object, object, object)
def __init__(self, parent=None):
self.logger = logging.getLogger(type(self).__name__)
super(RareAppException, self).__init__(parent=parent)
self.logger = logging.getLogger(type(self).__name__)
sys.excepthook = self._excepthook
self.exception.connect(self._on_exception)
@ -37,14 +37,19 @@ class RareAppException(QObject):
if self._handler(exc_type, exc_value, exc_tb):
return
self.logger.fatal(message)
QMessageBox.warning(None, exc_type.__name__, message)
QApplication.exit(1)
action = QMessageBox.warning(
None, exc_type.__name__, message,
buttons=QMessageBox.Ignore | QMessageBox.Close,
defaultButton=QMessageBox.Ignore
)
if action == QMessageBox.RejectRole:
QApplication.exit(1)
class RareApp(QApplication):
def __init__(self, args: Namespace, log_file: str):
self.logger = logging.getLogger(type(self).__name__)
super(RareApp, self).__init__(sys.argv)
self.logger = logging.getLogger(type(self).__name__)
self._hook = RareAppException(self)
self.setQuitOnLastWindowClosed(False)
self.setAttribute(Qt.AA_DontUseNativeDialogs, True)

View file

@ -45,7 +45,10 @@ setuptools.setup(
],
include_package_data=True,
python_requires=">=3.9",
entry_points=dict(console_scripts=["rare=rare.__main__:main"]),
entry_points={
# 'console_scripts': ["rare = rare.main:main"],
'gui_scripts': ["rare = rare.main:main"],
},
install_requires=requirements,
extras_require=optional_reqs,
)