1
0
Fork 0
mirror of synced 2024-06-26 18:20:50 +12:00
Rare/rare/utils/misc.py
loathingKernel 4063195b4d DownloadsTab: Refactor downloads tab
When updates are queued, they are removed from the update's list. An exceptions is made
when the queued item comes from repairing (without updating), in which case the update is
disabled for the runtime.

A queued item can be either removed (if it is an update it will be added back to the
updates groups) or forced to be updated now. If a queued item is forced, the currently
running item will be added to the front of the queue. Downloads will be queued if
there is no active download but there is a queue already.

The download thread is now responsible for emitting the progress to `RareGame`

InstallDialog: Pass `RareGame` and `InstallOptionsModel` only as arguments.
The `update`, `repair` and `silent` arguments are already part of `InstallOptionsModel`
`RareGame` is used to query information about the game.

InstallInfoWorker: Pass only `InstallOptionsModel` as argument
Emit `InstallQueueItemModel` as result, to re-use the worker when queuing stopped games

RareGame: Query and store metadata property about entitlement grant date
RareGame: Add `RareEosOverlay` class that imitates `RareGame` to handle the overlay

LibraryWidgetController: Remove dead signal routing code, these signals are handled by `RareGame`
Directly parent library widgets instead of reparenting them

GameWidgets: Remove unused signals

EOSGroup: Set install location based on preferences and use EOSOverlayApp from legendary

GamesTab: Connect the `progress` signals of dlcs to the base game's signals
GamesTab: Remove dead code

GlobalSignals: Remove `ProgresSignals`

RareCore: Mangle internal signleton's names

Signed-off-by: loathingKernel <142770+loathingKernel@users.noreply.github.com>
2023-02-04 17:38:07 +02:00

356 lines
11 KiB
Python

import os
import platform
import shlex
import sys
from logging import getLogger
from typing import List, Union
import qtawesome
import requests
from PyQt5.QtCore import (
pyqtSignal,
QObject,
QRunnable,
QSettings,
QStandardPaths,
QFile,
QDir,
)
from PyQt5.QtGui import QPalette, QColor, QImage
from PyQt5.QtWidgets import qApp, QStyleFactory, QWidget
from legendary.core import LegendaryCore
from legendary.models.game import Game
from requests.exceptions import HTTPError
from rare.models.apiresults import ApiResults
from rare.utils.paths import image_dir, resources_path
if platform.system() == "Windows":
# noinspection PyUnresolvedReferences
from win32com.client import Dispatch # pylint: disable=E0401
logger = getLogger("Utils")
settings = QSettings("Rare", "Rare")
color_role_map = {
0: "WindowText",
1: "Button",
2: "Light",
3: "Midlight",
4: "Dark",
5: "Mid",
6: "Text",
7: "BrightText",
8: "ButtonText",
9: "Base",
10: "Window",
11: "Shadow",
12: "Highlight",
13: "HighlightedText",
14: "Link",
15: "LinkVisited",
16: "AlternateBase",
# 17: "NoRole",
18: "ToolTipBase",
19: "ToolTipText",
20: "PlaceholderText",
# 21: "NColorRoles",
}
color_group_map = {
0: "Active",
1: "Disabled",
2: "Inactive",
}
def load_color_scheme(path: str) -> QPalette:
palette = QPalette()
scheme = QSettings(path, QSettings.IniFormat)
try:
scheme.beginGroup("ColorScheme")
for g in color_group_map:
scheme.beginGroup(color_group_map[g])
group = QPalette.ColorGroup(g)
for r in color_role_map:
role = QPalette.ColorRole(r)
color = scheme.value(color_role_map[r], None)
if color is not None:
palette.setColor(group, role, QColor(color))
else:
palette.setColor(group, role, palette.color(QPalette.Active, role))
scheme.endGroup()
scheme.endGroup()
except:
palette = None
return palette
def set_color_pallete(color_scheme: str):
if not color_scheme:
qApp.setStyle(QStyleFactory.create(qApp.property("rareDefaultQtStyle")))
qApp.setStyleSheet("")
qApp.setPalette(qApp.style().standardPalette())
return
qApp.setStyle(QStyleFactory.create("Fusion"))
custom_palette = load_color_scheme(f":/schemes/{color_scheme}")
if custom_palette is not None:
qApp.setPalette(custom_palette)
icon_color = qApp.palette().color(QPalette.Foreground).name()
qtawesome.set_defaults(color=icon_color)
def get_color_schemes() -> List[str]:
colors = []
for file in QDir(":/schemes"):
colors.append(file)
return colors
def set_style_sheet(style_sheet: str):
if not style_sheet:
qApp.setStyle(QStyleFactory.create(qApp.property("rareDefaultQtStyle")))
qApp.setStyleSheet("")
return
qApp.setStyle(QStyleFactory.create("Fusion"))
file = QFile(f":/stylesheets/{style_sheet}/stylesheet.qss")
file.open(QFile.ReadOnly)
stylesheet = file.readAll().data().decode("utf-8")
qApp.setStyleSheet(stylesheet)
icon_color = qApp.palette().color(QPalette.Text).name()
qtawesome.set_defaults(color="#eeeeee")
def get_style_sheets() -> List[str]:
styles = []
for file in QDir(":/stylesheets/"):
styles.append(file)
return styles
def get_translations():
langs = ["en"]
for i in os.listdir(os.path.join(resources_path, "languages")):
if i.endswith(".qm") and not i.startswith("qt_"):
langs.append(i.split(".")[0])
return langs
def get_latest_version():
try:
resp = requests.get(
"https://api.github.com/repos/Dummerle/Rare/releases/latest"
)
tag = resp.json()["tag_name"]
return tag
except requests.exceptions.ConnectionError:
return "0.0.0"
def get_size(b: Union[int, float]) -> str:
for i in ["", "K", "M", "G", "T", "P", "E"]:
if b < 1024:
return f"{b:.2f}{i}B"
b /= 1024
def get_rare_executable() -> List[str]:
# lk: detect if nuitka
if "__compiled__" in globals():
executable = [sys.executable]
elif platform.system() == "Linux" or platform.system() == "Darwin":
if p := os.environ.get("APPIMAGE"):
executable = [p]
else:
if sys.executable == os.path.abspath(sys.argv[0]):
executable = [sys.executable]
else:
executable = [sys.executable, os.path.abspath(sys.argv[0])]
elif platform.system() == "Windows":
executable = [sys.executable]
if sys.executable != os.path.abspath(sys.argv[0]):
executable.append(os.path.abspath(sys.argv[0]))
if executable[0].endswith("python.exe"):
# be sure to start consoleless then
executable[0] = executable[0].replace("python.exe", "pythonw.exe")
if executable[1].endswith("rare"):
executable[1] = executable[1] + ".exe"
else:
executable = [sys.executable]
executable[0] = os.path.abspath(executable[0])
return executable
def create_desktop_link(app_name=None, core: LegendaryCore = None, type_of_link="desktop",
for_rare: bool = False) -> bool:
if not for_rare:
igame = core.get_installed_game(app_name)
icon = os.path.join(os.path.join(image_dir(), igame.app_name, "installed.png"))
icon = icon.replace(".png", "")
if platform.system() == "Linux":
if type_of_link == "desktop":
path = QStandardPaths.writableLocation(QStandardPaths.DesktopLocation)
elif type_of_link == "start_menu":
path = QStandardPaths.writableLocation(QStandardPaths.ApplicationsLocation)
else:
return False
if not os.path.exists(path):
return False
executable = get_rare_executable()
executable = shlex.join(executable)
if for_rare:
with open(os.path.join(path, "Rare.desktop"), "w") as desktop_file:
desktop_file.write(
"[Desktop Entry]\n"
f"Name=Rare\n"
f"Type=Application\n"
f"Categories=Game;\n"
f"Icon={os.path.join(resources_path, 'images', 'Rare.png')}\n"
f"Exec={executable}\n"
"Terminal=false\n"
"StartupWMClass=Rare\n"
)
else:
with open(os.path.join(path, f"{igame.title}.desktop"), "w") as desktop_file:
desktop_file.write(
"[Desktop Entry]\n"
f"Name={igame.title}\n"
f"Type=Application\n"
f"Categories=Game;\n"
f"Icon={icon}.png\n"
f"Exec={executable} launch {app_name}\n"
"Terminal=false\n"
"StartupWMClass=Rare\n"
)
os.chmod(os.path.join(path, f"{igame.title}.desktop"), 0o755)
return True
elif platform.system() == "Windows":
# Target of shortcut
if type_of_link == "desktop":
target_folder = QStandardPaths.writableLocation(QStandardPaths.DesktopLocation)
elif type_of_link == "start_menu":
target_folder = os.path.join(
QStandardPaths.writableLocation(QStandardPaths.ApplicationsLocation),
".."
)
else:
logger.warning("No valid type of link")
return False
if not os.path.exists(target_folder):
return False
if for_rare:
linkName = "Rare.lnk"
else:
linkName = igame.title
# TODO: this conversion is not applied everywhere (see base_installed_widget), should it?
for c in r'<>?":|\/*':
linkName = linkName.replace(c, "")
linkName = f"{linkName.strip()}.lnk"
# Path to location of link file
pathLink = os.path.join(target_folder, linkName)
# Add shortcut
shell = Dispatch("WScript.Shell")
shortcut = shell.CreateShortCut(pathLink)
executable = get_rare_executable()
arguments = []
if len(executable) > 1:
arguments.extend(executable[1:])
executable = executable[0]
if not for_rare:
arguments.extend(["launch", app_name])
shortcut.Targetpath = executable
# Maybe there is a better solution, but windows does not accept single quotes (Windows is weird)
shortcut.Arguments = shlex.join(arguments).replace("'", '"')
if for_rare:
shortcut.WorkingDirectory = QStandardPaths.writableLocation(QStandardPaths.HomeLocation)
# Icon
if for_rare:
icon_location = os.path.join(resources_path, "images", "Rare.ico")
else:
if not os.path.exists(f"{icon}.ico"):
img = QImage()
img.load(f"{icon}.png")
img.save(f"{icon}.ico")
logger.info("Created ico file")
icon_location = f"{icon}.ico"
shortcut.IconLocation = os.path.abspath(icon_location)
shortcut.save()
return True
# mac OS is based on Darwin
elif platform.system() == "Darwin":
return False
class CloudWorker(QRunnable):
class Signals(QObject):
# List[SaveGameFile]
result_ready = pyqtSignal(list)
def __init__(self, core: LegendaryCore):
super(CloudWorker, self).__init__()
self.core = core
self.signals = CloudWorker.Signals()
self.setAutoDelete(True)
def run(self) -> None:
try:
result = self.core.get_save_games()
except HTTPError:
result = None
self.signals.result_ready.emit(result)
def get_raw_save_path(game: Game):
if game.supports_cloud_saves:
return (
game.metadata.get("customAttributes", {})
.get("CloudSaveFolder", {})
.get("value")
)
def get_default_platform(app_name, api_results: ApiResults):
if platform.system() != "Darwin" or app_name not in api_results.mac_games:
return "Windows"
else:
return "Mac"
def icon(icn_str: str, fallback: str = None, **kwargs):
try:
return qtawesome.icon(icn_str, **kwargs)
except Exception as e:
if not fallback:
logger.warning(f"{e} {icn_str}")
if fallback:
try:
return qtawesome.icon(fallback, **kwargs)
except Exception as e:
logger.error(str(e))
if kwargs.get("color"):
kwargs["color"] = "red"
return qtawesome.icon("ei.error", **kwargs)
def widget_object_name(widget: QWidget, app_name: str) -> str:
return f"{type(widget).__name__}_{app_name}"