WineResolver: Move common wine operations to rare/utils/wine
`rare.utils.misc.get_raw_save_path` has been removed in favor of the equivalent `RareGame` property.
This commit is contained in:
parent
3adabda1ba
commit
f6189772d0
|
@ -1,6 +1,5 @@
|
|||
import os
|
||||
import platform
|
||||
import subprocess
|
||||
import time
|
||||
from argparse import Namespace
|
||||
from configparser import ConfigParser
|
||||
|
@ -9,10 +8,11 @@ from typing import Union, Iterator
|
|||
|
||||
from PyQt5.QtCore import pyqtSignal, QObject
|
||||
|
||||
import rare.utils.wine as wine
|
||||
from rare.lgndr.core import LegendaryCore
|
||||
from rare.models.game import RareGame
|
||||
from rare.models.pathspec import PathSpec
|
||||
from rare.utils.misc import read_registry, path_size, format_size
|
||||
from rare.utils.misc import path_size, format_size
|
||||
from .fetch import FetchWorker
|
||||
from .worker import Worker
|
||||
|
||||
|
@ -31,17 +31,8 @@ class WineResolver(Worker):
|
|||
def __init__(self, core: LegendaryCore, path: str, app_name: str):
|
||||
super(WineResolver, self).__init__()
|
||||
self.signals = WineResolver.Signals()
|
||||
self.wine_env = os.environ.copy()
|
||||
self.wine_env.update(core.get_app_environment(app_name))
|
||||
self.wine_env["WINEDLLOVERRIDES"] = "winemenubuilder=d;mscoree=d;mshtml=d;"
|
||||
self.wine_env["DISPLAY"] = ""
|
||||
|
||||
self.wine_binary = core.lgd.config.get(
|
||||
app_name, "wine_executable", fallback=core.lgd.config.get(
|
||||
"default", "wine_executable", fallback="wine"
|
||||
)
|
||||
)
|
||||
self.winepath_binary = os.path.join(os.path.dirname(self.wine_binary), "winepath")
|
||||
self.wine_env = wine.environ(core, app_name)
|
||||
self.wine_exec = wine.wine(core, app_name)
|
||||
self.path = PathSpec(core, app_name).cook(path)
|
||||
|
||||
def run_real(self):
|
||||
|
@ -49,36 +40,13 @@ class WineResolver(Worker):
|
|||
# pylint: disable=E1136
|
||||
self.signals.result_ready[str].emit("")
|
||||
return
|
||||
if not os.path.exists(self.wine_binary) or not os.path.exists(self.winepath_binary):
|
||||
if not os.path.exists(self.wine_exec) or not os.path.exists(wine.winepath(self.wine_exec)):
|
||||
# pylint: disable=E1136
|
||||
self.signals.result_ready[str].emit("")
|
||||
return
|
||||
path = self.path.strip().replace("/", "\\")
|
||||
# lk: if path does not exist form
|
||||
cmd = [self.wine_binary, "cmd", "/c", "echo", path]
|
||||
# lk: if path exists and needs a case-sensitive interpretation form
|
||||
# cmd = [self.wine_binary, 'cmd', '/c', f'cd {path} & cd']
|
||||
proc = subprocess.Popen(
|
||||
cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
env=self.wine_env,
|
||||
shell=False,
|
||||
text=True,
|
||||
)
|
||||
out, err = proc.communicate()
|
||||
path = wine.resolve_path(self.wine_exec, self.wine_env, self.path)
|
||||
# Clean wine output
|
||||
out = out.strip().strip('"')
|
||||
proc = subprocess.Popen(
|
||||
[self.winepath_binary, "-u", out],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
env=self.wine_env,
|
||||
shell=False,
|
||||
text=True,
|
||||
)
|
||||
out, err = proc.communicate()
|
||||
real_path = os.path.realpath(out.strip())
|
||||
real_path = wine.convert_to_unix_path(self.wine_exec, self.wine_env, path)
|
||||
# pylint: disable=E1136
|
||||
self.signals.result_ready[str].emit(real_path)
|
||||
return
|
||||
|
@ -104,46 +72,35 @@ class OriginWineWorker(FetchWorker):
|
|||
if not reg_path:
|
||||
continue
|
||||
|
||||
reg_key: str = rgame.game.metadata \
|
||||
.get("customAttributes", {}) \
|
||||
.get("RegistryKey", {}).get("value", None)
|
||||
|
||||
if platform.system() == "Windows":
|
||||
install_dir = windows_helpers.query_registry_value(winreg.HKEY_LOCAL_MACHINE, reg_path, "Install Dir")
|
||||
install_dir = windows_helpers.query_registry_value(winreg.HKEY_LOCAL_MACHINE, reg_path, reg_key)
|
||||
else:
|
||||
wine_prefix = self.core.lgd.config.get(
|
||||
rgame.app_name, "wine_prefix", fallback=self.core.lgd.config.get(
|
||||
"default", "wine_prefix", fallback=os.path.expanduser("~/.wine")
|
||||
)
|
||||
)
|
||||
reg = self.__cache.get(wine_prefix, None) or read_registry("system.reg", wine_prefix)
|
||||
wine_env = wine.environ(self.core, rgame.app_name)
|
||||
wine_exec = wine.wine(self.core, rgame.app_name)
|
||||
|
||||
# lk: this is the original way of gettijng the path by parsing "system.reg"
|
||||
wine_prefix = wine.prefix(self.core, rgame.app_name)
|
||||
reg = self.__cache.get(wine_prefix, None) or wine.read_registry("system.reg", wine_prefix)
|
||||
self.__cache[wine_prefix] = reg
|
||||
|
||||
reg_path = reg_path.replace("SOFTWARE", "Software").replace("WOW6432Node", "Wow6432Node")
|
||||
# lk: split and rejoin the registry path to avoid slash expansion
|
||||
reg_path = "\\\\".join([x for x in reg_path.split("\\") if bool(x)])
|
||||
|
||||
install_dir = reg.get(reg_path, '"Install Dir"', fallback=None)
|
||||
install_dir = reg.get(reg_path, f'"{reg_key}"', fallback=None)
|
||||
|
||||
# lk: this is the alternative way of getting the path by using wine itself
|
||||
# install_dir = wine.query_reg_key(wine_exec, wine_env, f"HKLM\\{reg_path}", reg_key)
|
||||
|
||||
if install_dir:
|
||||
wine = self.core.lgd.config.get(
|
||||
rgame.app_name, "wine_executable", fallback=self.core.lgd.config.get(
|
||||
"default", "wine_executable", fallback="wine"
|
||||
)
|
||||
)
|
||||
winepath = os.path.join(os.path.dirname(wine), "winepath")
|
||||
wine_env = os.environ.copy()
|
||||
wine_env.update(self.core.get_app_environment(rgame.app_name))
|
||||
wine_env["WINEDLLOVERRIDES"] = "winemenubuilder=d;mscoree=d;mshtml=d;"
|
||||
wine_env["DISPLAY"] = ""
|
||||
install_dir = install_dir.strip().strip('"')
|
||||
proc = subprocess.Popen(
|
||||
[winepath, "-u", install_dir],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
env=wine_env,
|
||||
shell=False,
|
||||
text=True,
|
||||
)
|
||||
out, err = proc.communicate()
|
||||
install_dir = os.path.realpath(out.strip())
|
||||
install_dir = wine.convert_to_unix_path(wine_exec, wine_env, install_dir)
|
||||
|
||||
if install_dir:
|
||||
if os.path.exists(install_dir):
|
||||
if os.path.isdir(install_dir):
|
||||
size = path_size(install_dir)
|
||||
rgame.set_origin_attributes(install_dir, size)
|
||||
logger.debug(f"Found Origin game {rgame.title} ({install_dir}, {format_size(size)})")
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import os
|
||||
from configparser import ConfigParser
|
||||
from logging import getLogger
|
||||
from typing import List, Union, Type
|
||||
|
||||
|
@ -18,7 +17,6 @@ from PyQt5.QtGui import QPalette, QColor, QFontMetrics
|
|||
from PyQt5.QtWidgets import qApp, QStyleFactory, QLabel
|
||||
from PyQt5.sip import wrappertype
|
||||
from legendary.core import LegendaryCore
|
||||
from legendary.models.game import Game
|
||||
from requests.exceptions import HTTPError
|
||||
|
||||
from rare.utils.paths import resources_path
|
||||
|
@ -162,7 +160,7 @@ def path_size(path: Union[str, os.PathLike]) -> int:
|
|||
return sum(
|
||||
os.stat(os.path.join(dp, f)).st_size
|
||||
for dp, dn, filenames in os.walk(path)
|
||||
for f in filenames
|
||||
for f in filenames if os.path.isfile(f)
|
||||
)
|
||||
|
||||
|
||||
|
@ -173,18 +171,6 @@ def format_size(b: Union[int, float]) -> str:
|
|||
b /= 1024
|
||||
|
||||
|
||||
# this is a copied function from legendary.utils.wine_helpers, but registry file can be specified
|
||||
def read_registry(registry: str, wine_pfx: str) -> ConfigParser:
|
||||
accepted = ["system.reg", "user.reg"]
|
||||
if registry not in accepted:
|
||||
raise RuntimeError(f'Unknown target "{registry}" not in {accepted}')
|
||||
reg = ConfigParser(comment_prefixes=(';', '#', '/', 'WINE'), allow_no_value=True,
|
||||
strict=False)
|
||||
reg.optionxform = str
|
||||
reg.read(os.path.join(wine_pfx, 'system.reg'))
|
||||
return reg
|
||||
|
||||
|
||||
class CloudWorker(QRunnable):
|
||||
class Signals(QObject):
|
||||
# List[SaveGameFile]
|
||||
|
@ -218,14 +204,6 @@ class CloudWorker(QRunnable):
|
|||
|
||||
self.signals.result_ready.emit(latest_saves)
|
||||
|
||||
def get_raw_save_path(game: Game):
|
||||
if game.supports_cloud_saves:
|
||||
return (
|
||||
game.metadata.get("customAttributes", {})
|
||||
.get("CloudSaveFolder", {})
|
||||
.get("value")
|
||||
)
|
||||
|
||||
|
||||
def icon(icn_str: str, fallback: str = None, **kwargs):
|
||||
try:
|
||||
|
|
101
rare/utils/wine.py
Normal file
101
rare/utils/wine.py
Normal file
|
@ -0,0 +1,101 @@
|
|||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
from configparser import ConfigParser
|
||||
from typing import Mapping, Dict, List, Tuple
|
||||
|
||||
from rare.lgndr.core import LegendaryCore
|
||||
|
||||
|
||||
# this is a copied function from legendary.utils.wine_helpers, but registry file can be specified
|
||||
def read_registry(registry: str, wine_pfx: str) -> ConfigParser:
|
||||
accepted = ["system.reg", "user.reg"]
|
||||
if registry not in accepted:
|
||||
raise RuntimeError(f'Unknown target "{registry}" not in {accepted}')
|
||||
reg = ConfigParser(comment_prefixes=(';', '#', '/', 'WINE'), allow_no_value=True,
|
||||
strict=False)
|
||||
reg.optionxform = str
|
||||
reg.read(os.path.join(wine_pfx, 'system.reg'))
|
||||
return reg
|
||||
|
||||
|
||||
def execute(cmd: List, wine_env: Mapping) -> Tuple[str, str]:
|
||||
proc = subprocess.Popen(
|
||||
cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
env=wine_env,
|
||||
shell=False,
|
||||
text=True,
|
||||
)
|
||||
return proc.communicate()
|
||||
|
||||
|
||||
def resolve_path(wine_exec: str, wine_env: Mapping, path: str) -> str:
|
||||
path = path.strip().replace("/", "\\")
|
||||
# lk: if path does not exist form
|
||||
cmd = [wine_exec, "cmd", "/c", "echo", path]
|
||||
# lk: if path exists and needs a case-sensitive interpretation form
|
||||
# cmd = [wine_cmd, 'cmd', '/c', f'cd {path} & cd']
|
||||
out, err = execute(cmd, wine_env)
|
||||
return out.strip().strip('"')
|
||||
|
||||
|
||||
def query_reg_path(wine_exec: str, wine_env: Mapping, reg_path: str):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def query_reg_key(wine_exec: str, wine_env: Mapping, reg_path: str, reg_key) -> str:
|
||||
cmd = [wine_exec, "reg", "query", reg_path, "/v", reg_key]
|
||||
out, err = execute(cmd, wine_env)
|
||||
lines = out.split("\n")
|
||||
keys: Dict = {}
|
||||
for line in lines:
|
||||
if line.startswith(" "*4):
|
||||
key = [x for x in line.split(" "*4, 3) if bool(x)]
|
||||
keys.update({key[0]: key[2]})
|
||||
return keys.get(reg_key, "")
|
||||
|
||||
|
||||
def convert_to_windows_path(wine_exec: str, wine_env: Mapping, path: str) -> str:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def convert_to_unix_path(wine_exec: str, wine_env: Mapping, path: str) -> str:
|
||||
path = path.strip().strip('"')
|
||||
cmd = [winepath(wine_exec), "-u", path]
|
||||
out, err = execute(cmd, wine_env)
|
||||
return os.path.realpath(out.strip())
|
||||
|
||||
|
||||
def winepath(wine_exec: str) -> str:
|
||||
_winepath = os.path.join(os.path.dirname(wine_exec), "winepath")
|
||||
if not os.path.isfile(_winepath):
|
||||
return ""
|
||||
return _winepath
|
||||
|
||||
|
||||
def wine(core: LegendaryCore, app_name: str = "default") -> str:
|
||||
_wine = core.lgd.config.get(
|
||||
app_name, "wine_executable", fallback=core.lgd.config.get(
|
||||
"default", "wine_executable", fallback=shutil.which("wine")
|
||||
)
|
||||
)
|
||||
return _wine
|
||||
|
||||
|
||||
def environ(core: LegendaryCore, app_name: str = "default") -> Dict:
|
||||
_environ = os.environ.copy()
|
||||
_environ.update(core.get_app_environment(app_name))
|
||||
_environ["WINEDLLOVERRIDES"] = "winemenubuilder=d;mscoree=d;mshtml=d;"
|
||||
_environ["DISPLAY"] = ""
|
||||
return _environ
|
||||
|
||||
|
||||
def prefix(core: LegendaryCore, app_name: str = "default") -> str:
|
||||
_prefix = core.lgd.config.get(
|
||||
app_name, "wine_prefix", fallback=core.lgd.config.get(
|
||||
"default", "wine_prefix", fallback=os.path.expanduser("~/.wine")
|
||||
)
|
||||
)
|
||||
return _prefix if os.path.isdir(_prefix) else ""
|
Loading…
Reference in a new issue