2021-04-05 20:57:11 +12:00
|
|
|
import configparser
|
2021-03-18 02:16:33 +13:00
|
|
|
import logging
|
2021-03-19 00:45:59 +13:00
|
|
|
import os
|
2021-12-12 11:41:55 +13:00
|
|
|
import platform
|
2022-02-06 04:03:21 +13:00
|
|
|
import shutil
|
2021-03-25 05:01:12 +13:00
|
|
|
import sys
|
2021-04-07 21:50:35 +12:00
|
|
|
import time
|
2021-09-11 08:41:30 +12:00
|
|
|
import traceback
|
2022-02-26 06:43:27 +13:00
|
|
|
from argparse import Namespace
|
2022-03-28 08:52:32 +13:00
|
|
|
from datetime import datetime
|
2022-06-19 06:45:36 +12:00
|
|
|
from typing import Optional
|
2021-03-18 02:16:33 +13:00
|
|
|
|
2022-06-19 06:45:36 +12:00
|
|
|
import legendary
|
2022-04-13 11:07:48 +12:00
|
|
|
import requests.exceptions
|
2022-06-30 07:24:45 +12:00
|
|
|
from PyQt5.QtCore import QThreadPool, QTimer, QT_VERSION_STR, PYQT_VERSION_STR
|
2021-11-13 02:17:06 +13:00
|
|
|
from PyQt5.QtWidgets import QApplication, QSystemTrayIcon, QMessageBox
|
2021-10-15 10:05:00 +13:00
|
|
|
from requests import HTTPError
|
2021-03-18 02:16:33 +13:00
|
|
|
|
2022-06-24 07:26:08 +12:00
|
|
|
import rare
|
2021-04-08 08:39:23 +12:00
|
|
|
from rare.components.dialogs.launch_dialog import LaunchDialog
|
|
|
|
from rare.components.main_window import MainWindow
|
|
|
|
from rare.components.tray_icon import TrayIcon
|
2022-08-01 23:31:07 +12:00
|
|
|
from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton, ArgumentsSingleton
|
2022-06-19 06:45:36 +12:00
|
|
|
from rare.shared.image_manager import ImageManagerSingleton
|
2022-02-06 02:48:50 +13:00
|
|
|
from rare.utils import legendary_utils, config_helper
|
2022-06-24 07:26:08 +12:00
|
|
|
from rare.utils.paths import cache_dir, tmp_dir
|
|
|
|
from rare.widgets.rare_app import RareApp
|
2021-03-18 02:16:33 +13:00
|
|
|
|
2021-12-24 22:09:50 +13:00
|
|
|
start_time = time.strftime("%y-%m-%d--%H-%M") # year-month-day-hour-minute
|
2021-12-12 11:41:55 +13:00
|
|
|
file_name = os.path.join(cache_dir, "logs", f"Rare_{start_time}.log")
|
2021-04-07 21:50:35 +12:00
|
|
|
if not os.path.exists(os.path.dirname(file_name)):
|
|
|
|
os.makedirs(os.path.dirname(file_name))
|
2021-09-11 08:41:30 +12:00
|
|
|
|
2021-03-18 02:16:33 +13:00
|
|
|
logger = logging.getLogger("Rare")
|
|
|
|
|
|
|
|
|
2021-09-11 08:41:30 +12:00
|
|
|
def excepthook(exc_type, exc_value, exc_tb):
|
|
|
|
tb = "".join(traceback.format_exception(exc_type, exc_value, exc_tb))
|
|
|
|
print("Error")
|
2021-10-15 10:05:00 +13:00
|
|
|
if exc_tb == HTTPError:
|
|
|
|
try:
|
2022-02-26 06:43:27 +13:00
|
|
|
if LegendaryCoreSingleton().login():
|
2021-10-15 10:05:00 +13:00
|
|
|
return
|
|
|
|
else:
|
|
|
|
raise ValueError
|
|
|
|
except Exception as e:
|
|
|
|
logger.fatal(str(e))
|
|
|
|
QMessageBox.warning(None, "Error", QApplication.tr("Failed to login"))
|
|
|
|
QApplication.exit(1)
|
|
|
|
return
|
2021-09-11 08:41:30 +12:00
|
|
|
logger.fatal(tb)
|
|
|
|
QMessageBox.warning(None, "Error", tb)
|
|
|
|
QApplication.exit(1)
|
|
|
|
|
|
|
|
|
2022-06-24 07:26:08 +12:00
|
|
|
class App(RareApp):
|
2022-06-19 06:45:36 +12:00
|
|
|
mainwindow: Optional[MainWindow] = None
|
|
|
|
tray_icon: Optional[QSystemTrayIcon] = None
|
2021-12-23 09:21:49 +13:00
|
|
|
|
2022-02-26 06:43:27 +13:00
|
|
|
def __init__(self, args: Namespace):
|
2022-06-24 07:26:08 +12:00
|
|
|
super(App, self).__init__()
|
2022-02-26 06:43:27 +13:00
|
|
|
self.args = ArgumentsSingleton(args) # add some options
|
2021-12-23 09:21:49 +13:00
|
|
|
self.window_launched = False
|
2022-02-26 07:05:20 +13:00
|
|
|
|
2021-03-27 06:23:22 +13:00
|
|
|
# init Legendary
|
2021-04-05 20:57:11 +12:00
|
|
|
try:
|
2022-08-01 23:31:07 +12:00
|
|
|
self.core = LegendaryCoreSingleton(init=True)
|
2021-04-05 20:57:11 +12:00
|
|
|
except configparser.MissingSectionHeaderError as e:
|
|
|
|
logger.warning(f"Config is corrupt: {e}")
|
2021-12-24 22:09:50 +13:00
|
|
|
if config_path := os.environ.get("XDG_CONFIG_HOME"):
|
|
|
|
path = os.path.join(config_path, "legendary")
|
2021-04-05 20:57:11 +12:00
|
|
|
else:
|
2021-12-24 22:09:50 +13:00
|
|
|
path = os.path.expanduser("~/.config/legendary")
|
2021-04-05 20:57:11 +12:00
|
|
|
with open(os.path.join(path, "config.ini"), "w") as config_file:
|
|
|
|
config_file.write("[Legendary]")
|
2022-08-01 23:31:07 +12:00
|
|
|
self.core = LegendaryCoreSingleton(init=True)
|
2021-04-08 08:39:23 +12:00
|
|
|
if "Legendary" not in self.core.lgd.config.sections():
|
2021-03-27 06:23:22 +13:00
|
|
|
self.core.lgd.config.add_section("Legendary")
|
|
|
|
self.core.lgd.save_config()
|
|
|
|
|
2022-06-24 07:26:08 +12:00
|
|
|
lang = self.settings.value("language", self.core.language_code, type=str)
|
|
|
|
self.load_translator(lang)
|
|
|
|
|
2022-02-06 02:48:50 +13:00
|
|
|
config_helper.init_config_handler(self.core)
|
|
|
|
|
2021-06-08 08:12:26 +12:00
|
|
|
# workaround if egl sync enabled, but no programdata_path
|
|
|
|
# programdata_path might be unset if logging in through the browser
|
|
|
|
if self.core.egl_sync_enabled:
|
|
|
|
if self.core.egl.programdata_path is None:
|
|
|
|
self.core.lgd.config.remove_option("Legendary", "egl_sync")
|
|
|
|
self.core.lgd.save_config()
|
|
|
|
else:
|
|
|
|
if not os.path.exists(self.core.egl.programdata_path):
|
|
|
|
self.core.lgd.config.remove_option("Legendary", "egl_sync")
|
|
|
|
self.core.lgd.save_config()
|
2021-05-21 09:00:38 +12:00
|
|
|
|
2021-04-05 20:57:11 +12:00
|
|
|
# set Application name for settings
|
2021-06-08 08:12:26 +12:00
|
|
|
self.launch_dialog = None
|
2021-09-30 10:22:47 +13:00
|
|
|
|
2022-05-05 08:11:41 +12:00
|
|
|
self.signals = GlobalSignalsSingleton(init=True)
|
2022-06-19 06:45:36 +12:00
|
|
|
self.image_manager = ImageManagerSingleton(init=True)
|
2021-10-15 10:05:00 +13:00
|
|
|
|
2021-12-23 09:21:49 +13:00
|
|
|
self.signals.exit_app.connect(self.exit_app)
|
2021-10-15 10:05:00 +13:00
|
|
|
self.signals.send_notification.connect(
|
2021-12-24 22:09:50 +13:00
|
|
|
lambda title: self.tray_icon.showMessage(
|
2021-10-15 10:05:00 +13:00
|
|
|
self.tr("Download finished"),
|
|
|
|
self.tr("Download finished. {} is playable now").format(title),
|
2021-12-24 22:09:50 +13:00
|
|
|
QSystemTrayIcon.Information,
|
|
|
|
4000,
|
|
|
|
)
|
|
|
|
if self.settings.value("notification", True, bool)
|
|
|
|
else None
|
|
|
|
)
|
2021-05-21 23:09:26 +12:00
|
|
|
|
2021-04-05 20:57:11 +12:00
|
|
|
# launch app
|
2021-10-08 07:19:24 +13:00
|
|
|
self.launch_dialog = LaunchDialog()
|
2021-06-08 08:12:26 +12:00
|
|
|
self.launch_dialog.quit_app.connect(self.launch_dialog.close)
|
|
|
|
self.launch_dialog.quit_app.connect(lambda ec: exit(ec))
|
2021-03-25 05:01:12 +13:00
|
|
|
self.launch_dialog.start_app.connect(self.start_app)
|
2021-06-08 08:12:26 +12:00
|
|
|
self.launch_dialog.start_app.connect(self.launch_dialog.close)
|
2021-04-20 01:44:28 +12:00
|
|
|
|
2021-12-10 08:03:34 +13:00
|
|
|
self.launch_dialog.login()
|
2021-03-25 05:01:12 +13:00
|
|
|
|
2022-03-28 08:52:32 +13:00
|
|
|
dt_exp = datetime.fromisoformat(self.core.lgd.userdata['expires_at'][:-1])
|
|
|
|
dt_now = datetime.utcnow()
|
|
|
|
td = abs(dt_exp - dt_now)
|
|
|
|
self.timer = QTimer()
|
|
|
|
self.timer.timeout.connect(self.re_login)
|
2022-04-13 11:07:48 +12:00
|
|
|
self.timer.start(int(td.total_seconds() - 60) * 1000)
|
2022-03-28 08:52:32 +13:00
|
|
|
|
|
|
|
def re_login(self):
|
|
|
|
logger.info("Session expires shortly. Renew session")
|
2022-04-13 11:07:48 +12:00
|
|
|
try:
|
|
|
|
self.core.login()
|
|
|
|
except requests.exceptions.ConnectionError:
|
|
|
|
self.timer.start(3000) # try again if no connection
|
|
|
|
return
|
|
|
|
dt_exp = datetime.fromisoformat(self.core.lgd.userdata['expires_at'][:-1])
|
|
|
|
dt_now = datetime.utcnow()
|
|
|
|
td = abs(dt_exp - dt_now)
|
|
|
|
self.timer.start(int(td.total_seconds() - 60) * 1000)
|
2022-03-28 08:52:32 +13:00
|
|
|
|
2021-12-23 09:21:49 +13:00
|
|
|
def show_mainwindow(self):
|
|
|
|
if self.window_launched:
|
|
|
|
self.mainwindow.show()
|
|
|
|
else:
|
2022-02-26 06:43:27 +13:00
|
|
|
self.mainwindow.show_window_centered()
|
2021-12-23 09:21:49 +13:00
|
|
|
|
2021-10-08 07:19:24 +13:00
|
|
|
def start_app(self):
|
2022-01-14 08:11:20 +13:00
|
|
|
for igame in self.core.get_installed_list():
|
2022-01-16 04:40:31 +13:00
|
|
|
if not os.path.exists(igame.install_path):
|
2022-07-16 06:16:12 +12:00
|
|
|
# lk; since install_path is lost anyway, set keep_files to True
|
|
|
|
# lk: to avoid spamming the log with "file not found" errors
|
|
|
|
legendary_utils.uninstall_game(self.core, igame.app_name, keep_files=True)
|
2022-01-16 04:40:31 +13:00
|
|
|
logger.info(f"Uninstalled {igame.title}, because no game files exist")
|
|
|
|
continue
|
2022-08-03 11:33:50 +12:00
|
|
|
# lk: games that don't have an override and can't find their executable due to case sensitivity
|
|
|
|
# lk: will still erroneously require verification. This might need to be removed completely
|
|
|
|
# lk: or be decoupled from the verification requirement
|
|
|
|
if override_exe := self.core.lgd.config.get(igame.app_name, "override_exe", fallback=""):
|
|
|
|
igame_executable = override_exe
|
|
|
|
else:
|
|
|
|
igame_executable = igame.executable
|
|
|
|
if not os.path.exists(os.path.join(igame.install_path, igame_executable.replace("\\", "/").lstrip("/"))):
|
2022-01-14 08:11:20 +13:00
|
|
|
igame.needs_verification = True
|
|
|
|
self.core.lgd.set_installed_game(igame.app_name, igame)
|
2022-01-16 04:40:31 +13:00
|
|
|
logger.info(f"{igame.title} needs verification")
|
2022-01-14 08:11:20 +13:00
|
|
|
|
2021-10-08 07:19:24 +13:00
|
|
|
self.mainwindow = MainWindow()
|
2022-08-14 03:53:00 +12:00
|
|
|
self.tray_icon: TrayIcon = TrayIcon(self)
|
2021-06-08 08:12:26 +12:00
|
|
|
self.tray_icon.exit_action.triggered.connect(self.exit_app)
|
2021-12-23 09:21:49 +13:00
|
|
|
self.tray_icon.start_rare.triggered.connect(self.show_mainwindow)
|
2021-12-24 22:09:50 +13:00
|
|
|
self.tray_icon.activated.connect(
|
|
|
|
lambda r: self.show_mainwindow()
|
|
|
|
if r == QSystemTrayIcon.DoubleClick
|
|
|
|
else None
|
|
|
|
)
|
2021-03-18 02:16:33 +13:00
|
|
|
|
2021-04-18 06:00:15 +12:00
|
|
|
if not self.args.silent:
|
2022-02-26 06:43:27 +13:00
|
|
|
self.mainwindow.show_window_centered()
|
2021-12-23 09:21:49 +13:00
|
|
|
self.window_launched = True
|
2021-04-18 04:58:18 +12:00
|
|
|
|
2022-02-26 06:43:27 +13:00
|
|
|
if self.args.subparser == "launch":
|
|
|
|
if self.args.app_name in [
|
2021-12-24 22:09:50 +13:00
|
|
|
i.app_name for i in self.core.get_installed_list()
|
|
|
|
]:
|
|
|
|
logger.info(
|
2022-02-26 06:43:27 +13:00
|
|
|
f"Launching {self.core.get_installed_game(self.args.app_name).title}"
|
2021-12-24 22:09:50 +13:00
|
|
|
)
|
|
|
|
self.mainwindow.tab_widget.games_tab.game_utils.prepare_launch(
|
2022-02-26 06:43:27 +13:00
|
|
|
self.args.app_name
|
2021-12-24 22:09:50 +13:00
|
|
|
)
|
2021-12-10 08:03:34 +13:00
|
|
|
else:
|
|
|
|
logger.error(
|
2022-02-26 06:43:27 +13:00
|
|
|
f"Could not find {self.args.app_name} in Games or it is not installed"
|
2021-12-24 22:09:50 +13:00
|
|
|
)
|
|
|
|
QMessageBox.warning(
|
|
|
|
self.mainwindow,
|
|
|
|
"Warning",
|
|
|
|
self.tr(
|
|
|
|
"Could not find {} in installed games. Did you modify the shortcut? "
|
2022-02-26 06:43:27 +13:00
|
|
|
).format(self.args.app_name),
|
2021-12-24 22:09:50 +13:00
|
|
|
)
|
2021-12-10 08:03:34 +13:00
|
|
|
|
2022-02-26 06:43:27 +13:00
|
|
|
if self.args.test_start:
|
2021-12-11 12:18:57 +13:00
|
|
|
self.exit_app(0)
|
|
|
|
|
2021-04-08 00:46:27 +12:00
|
|
|
def tray(self, reason):
|
|
|
|
if reason == QSystemTrayIcon.DoubleClick:
|
|
|
|
self.mainwindow.show()
|
|
|
|
logger.info("Show App")
|
|
|
|
|
2021-06-08 08:12:26 +12:00
|
|
|
def exit_app(self, exit_code=0):
|
2021-11-23 10:35:37 +13:00
|
|
|
# FIXME: Fix this with the downlaod tab redesign
|
|
|
|
if self.mainwindow is not None:
|
2022-02-26 06:43:27 +13:00
|
|
|
if not self.args.offline and self.mainwindow.tab_widget.downloadTab.is_download_active:
|
2021-11-23 10:35:37 +13:00
|
|
|
question = QMessageBox.question(
|
|
|
|
self.mainwindow,
|
|
|
|
self.tr("Close"),
|
2021-12-24 22:09:50 +13:00
|
|
|
self.tr(
|
|
|
|
"There is a download active. Do you really want to exit app?"
|
|
|
|
),
|
|
|
|
QMessageBox.Yes,
|
|
|
|
QMessageBox.No,
|
|
|
|
)
|
2021-11-23 10:35:37 +13:00
|
|
|
if question == QMessageBox.No:
|
|
|
|
return
|
|
|
|
else:
|
2021-12-23 09:21:49 +13:00
|
|
|
# clear queue
|
|
|
|
self.mainwindow.tab_widget.downloadTab.queue_widget.update_queue([])
|
2021-11-23 10:35:37 +13:00
|
|
|
self.mainwindow.tab_widget.downloadTab.stop_download()
|
|
|
|
# FIXME: End of FIXME
|
2021-12-23 09:21:49 +13:00
|
|
|
self.mainwindow.timer.stop()
|
2021-10-21 06:19:03 +13:00
|
|
|
self.mainwindow.hide()
|
|
|
|
threadpool = QThreadPool.globalInstance()
|
|
|
|
threadpool.waitForDone()
|
2022-07-16 06:16:12 +12:00
|
|
|
self.core.exit()
|
2021-06-08 08:12:26 +12:00
|
|
|
if self.mainwindow is not None:
|
|
|
|
self.mainwindow.close()
|
2021-10-21 06:19:03 +13:00
|
|
|
if self.tray_icon is not None:
|
|
|
|
self.tray_icon.deleteLater()
|
2021-06-08 08:12:26 +12:00
|
|
|
self.processEvents()
|
2022-02-06 04:03:21 +13:00
|
|
|
shutil.rmtree(tmp_dir)
|
|
|
|
os.makedirs(tmp_dir)
|
|
|
|
|
2021-06-08 08:12:26 +12:00
|
|
|
self.exit(exit_code)
|
|
|
|
|
2021-03-25 05:01:12 +13:00
|
|
|
|
2021-04-09 21:36:27 +12:00
|
|
|
def start(args):
|
2021-09-11 08:41:30 +12:00
|
|
|
# set excepthook to show dialog with exception
|
|
|
|
sys.excepthook = excepthook
|
|
|
|
|
|
|
|
# configure logging
|
|
|
|
if args.debug:
|
2021-12-24 22:09:50 +13:00
|
|
|
logging.basicConfig(
|
|
|
|
format="[%(name)s] %(levelname)s: %(message)s", level=logging.DEBUG
|
|
|
|
)
|
2021-09-11 08:41:30 +12:00
|
|
|
logging.getLogger().setLevel(level=logging.DEBUG)
|
2021-09-13 07:19:51 +12:00
|
|
|
# keep requests, asyncio and pillow quiet
|
2021-12-24 22:09:50 +13:00
|
|
|
logging.getLogger("requests").setLevel(logging.WARNING)
|
|
|
|
logging.getLogger("urllib3").setLevel(logging.WARNING)
|
2021-09-13 07:19:51 +12:00
|
|
|
logging.getLogger("asyncio").setLevel(logging.WARNING)
|
2021-12-24 22:09:50 +13:00
|
|
|
logger.info(
|
|
|
|
f"Launching Rare version {rare.__version__} Codename: {rare.code_name}\n"
|
2022-06-30 07:24:45 +12:00
|
|
|
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}"
|
2021-12-24 22:09:50 +13:00
|
|
|
)
|
2021-09-11 08:41:30 +12:00
|
|
|
else:
|
|
|
|
logging.basicConfig(
|
2021-12-24 22:09:50 +13:00
|
|
|
format="[%(name)s] %(levelname)s: %(message)s",
|
2021-09-11 08:41:30 +12:00
|
|
|
level=logging.INFO,
|
|
|
|
filename=file_name,
|
|
|
|
)
|
2022-04-13 10:57:17 +12:00
|
|
|
logger.info(f"Launching Rare version {rare.__version__}")
|
|
|
|
logger.info(f"Operating System: {platform.system()}")
|
2021-09-11 08:41:30 +12:00
|
|
|
|
2021-04-07 21:50:35 +12:00
|
|
|
while True:
|
2022-02-26 06:43:27 +13:00
|
|
|
app = App(args)
|
2021-04-07 21:50:35 +12:00
|
|
|
exit_code = app.exec_()
|
|
|
|
# if not restart
|
|
|
|
# restart app
|
2021-09-13 09:28:54 +12:00
|
|
|
del app
|
2021-06-08 08:12:26 +12:00
|
|
|
if exit_code != -133742:
|
|
|
|
break
|