1
0
Fork 0
mirror of synced 2024-05-19 12:02:54 +12:00

Lgndr: Replace _installed_lock with a non-thread-local instance.

Since Python 3.11, `FileLock` is thread-local by default, which causes
numerous issues with Rare because of numerous operations running in
`QThreads` and `QRunnables`. To work around it, add a monkey LGDLFS class
that uses a non-thread-local instance of `FileLock`. Since the monkey class
exists, implement a `unlock_installed` method for code clarity

* Add decorate `LegendaryCore.egl_sync` with `unlock_installed`
* Log that Rare's monkeys are in use
* Add function signature protocols based on `typing.Protocol`
This commit is contained in:
loathingKernel 2023-12-13 15:00:36 +02:00
parent 9de5627be3
commit f361828f37
No known key found for this signature in database
GPG key ID: CE0C72D0B53821FD
7 changed files with 78 additions and 24 deletions

View file

@ -43,15 +43,16 @@ class LegendaryCLI(LegendaryCLIReal):
def unlock_installed(func):
@functools.wraps(func)
def unlock(self, *args, **kwargs):
self.logger.debug("Using unlock decorator")
if not self.core.lgd.lock_installed():
self.logger.debug("Data is locked, trying to forcufully release it")
self.logger.info("Data is locked, trying to forcufully release it")
# self.core.lgd._installed_lock.release(force=True)
try:
ret = func(self, *args, **kwargs)
except Exception as e:
raise e
finally:
self.core.lgd._installed_lock.release(force=True)
self.core.lgd.unlock_installed()
return ret
return unlock

View file

@ -17,16 +17,19 @@ from legendary.models.game import Game, InstalledGame
from legendary.models.manifest import ManifestMeta
from rare.lgndr.downloader.mp.manager import DLManager
from rare.lgndr.lfs.lgndry import LGDLFS
from rare.lgndr.glue.exception import LgndrException, LgndrLogHandler
legendary.core.DLManager = DLManager
legendary.core.LGDLFS = LGDLFS
# fmt: off
class LegendaryCore(LegendaryCoreReal):
def __init__(self, override_config=None, timeout=10.0):
super(LegendaryCore, self).__init__(override_config=override_config, timeout=timeout)
def __init__(self, *args, **kwargs):
super(LegendaryCore, self).__init__(*args, **kwargs)
self.log.info("Using Rare's LegendaryCore monkey")
self.handler = LgndrLogHandler(logging.CRITICAL)
self.log.addHandler(self.handler)
@ -34,15 +37,16 @@ class LegendaryCore(LegendaryCoreReal):
def unlock_installed(func):
@functools.wraps(func)
def unlock(self, *args, **kwargs):
self.log.debug("Using unlock decorator")
if not self.lgd.lock_installed():
self.log.debug("Data is locked, trying to forcufully release it")
self.log.info("Data is locked, trying to forcufully release it")
# self.lgd._installed_lock.release(force=True)
try:
ret = func(self, *args, **kwargs)
except Exception as e:
raise e
finally:
self.lgd._installed_lock.release(force=True)
self.lgd.unlock_installed()
return ret
return unlock
@ -149,6 +153,15 @@ class LegendaryCore(LegendaryCoreReal):
finally:
pass
@unlock_installed
def egl_sync(self, app_name=''):
try:
super(LegendaryCore, self).egl_sync(app_name)
except LgndrException as ret:
raise ret
finally:
pass
def prepare_overlay_install(self, path=None):
dlm, analysis_result, igame = super(LegendaryCore, self).prepare_overlay_install(path)
# lk: monkeypatch status_q (the queue for download stats)

View file

@ -17,6 +17,10 @@ from rare.lgndr.models.downloading import UIUpdate
# fmt: off
class DLManager(DLManagerReal):
def __init__(self, *args, **kwargs):
super(DLManager, self).__init__(*args, **kwargs)
self.log.info("Using Rare's DLManager monkey")
# Rare: prototype to avoid undefined variable in type checkers
signals_queue: MPQueue

View file

@ -1,16 +1,19 @@
from dataclasses import dataclass, field
from enum import IntEnum
from typing import Callable, List, Optional, Dict
from typing import List, Optional, Dict
from rare.lgndr.glue.monkeys import (
LgndrIndirectStatus,
GetBooleanChoiceProtocol,
SdlPromptProtocol,
VerifyStdoutProtocol,
UiUpdateProtocol,
get_boolean_choice_factory,
sdl_prompt_factory,
verify_stdout_factory,
ui_update_factory,
DLManagerSignals,
)
from rare.lgndr.models.downloading import UIUpdate
"""
@dataclass(kw_only=True)
@ -34,7 +37,7 @@ class LgndrImportGameArgs:
yes: bool = False
# Rare: Extra arguments
indirect_status: LgndrIndirectStatus = field(default_factory=LgndrIndirectStatus)
get_boolean_choice: Callable[[str, bool], bool] = get_boolean_choice_factory(True)
get_boolean_choice: GetBooleanChoiceProtocol = get_boolean_choice_factory(True)
@dataclass
@ -45,8 +48,8 @@ class LgndrUninstallGameArgs:
yes: bool = False
# Rare: Extra arguments
indirect_status: LgndrIndirectStatus = field(default_factory=LgndrIndirectStatus)
get_boolean_choice_main: Callable[[str, bool], bool] = get_boolean_choice_factory(True)
get_boolean_choice_handler: Callable[[str, bool], bool] = get_boolean_choice_factory(True)
get_boolean_choice_main: GetBooleanChoiceProtocol = get_boolean_choice_factory(True)
get_boolean_choice_handler: GetBooleanChoiceProtocol = get_boolean_choice_factory(True)
@dataclass
@ -54,7 +57,7 @@ class LgndrVerifyGameArgs:
app_name: str
# Rare: Extra arguments
indirect_status: LgndrIndirectStatus = field(default_factory=LgndrIndirectStatus)
verify_stdout: Callable[[int, int, float, float], None] = verify_stdout_factory(None)
verify_stdout: VerifyStdoutProtocol = verify_stdout_factory(None)
@dataclass
@ -95,11 +98,11 @@ class LgndrInstallGameArgs:
yes: bool = True
# Rare: Extra arguments
indirect_status: LgndrIndirectStatus = field(default_factory=LgndrIndirectStatus)
get_boolean_choice: Callable[[str, bool], bool] = get_boolean_choice_factory(True)
verify_stdout: Callable[[int, int, float, float], None] = verify_stdout_factory(None)
get_boolean_choice: GetBooleanChoiceProtocol = get_boolean_choice_factory(True)
verify_stdout: VerifyStdoutProtocol = verify_stdout_factory(None)
def __post_init__(self):
self.sdl_prompt: Callable[[Dict, str], List[str]] = sdl_prompt_factory(self.install_tag)
self.sdl_prompt: SdlPromptProtocol = sdl_prompt_factory(self.install_tag)
@dataclass
@ -117,7 +120,7 @@ class LgndrInstallGameRealArgs:
# Rare: Extra arguments
install_prereqs: bool = False
indirect_status: LgndrIndirectStatus = field(default_factory=LgndrIndirectStatus)
ui_update: Callable[[UIUpdate], None] = ui_update_factory(None)
ui_update: UiUpdateProtocol = ui_update_factory(None)
dlm_signals: DLManagerSignals = field(default_factory=DLManagerSignals)

View file

@ -1,20 +1,30 @@
import logging
from dataclasses import dataclass
from typing import Callable, List, Optional, Dict
from typing import Protocol, List, Optional, Dict
from rare.lgndr.models.downloading import UIUpdate
logger = logging.getLogger("LgndrMonkeys")
def get_boolean_choice_factory(value: bool = True) -> Callable[[str, bool], bool]:
def get_boolean_choice(prompt: str, default: bool) -> bool:
class GetBooleanChoiceProtocol(Protocol):
def __call__(self, prompt: str, default: bool = ...) -> bool:
...
def get_boolean_choice_factory(value: bool = True) -> GetBooleanChoiceProtocol:
def get_boolean_choice(prompt: str, default: bool = value) -> bool:
logger.debug("get_boolean_choice: %s, default: %s, choice: %s", prompt, default, value)
return value
return get_boolean_choice
def sdl_prompt_factory(install_tag: Optional[List[str]] = None) -> Callable[[Dict, str], List[str]]:
class SdlPromptProtocol(Protocol):
def __call__(self, sdl_data: Dict, title: str) -> List[str]:
...
def sdl_prompt_factory(install_tag: Optional[List[str]] = None) -> SdlPromptProtocol:
def sdl_prompt(sdl_data: Dict, title: str) -> List[str]:
logger.debug("sdl_prompt: %s", title)
for key in sdl_data.keys():
@ -25,9 +35,12 @@ def sdl_prompt_factory(install_tag: Optional[List[str]] = None) -> Callable[[Dic
return sdl_prompt
def verify_stdout_factory(
callback: Callable[[int, int, float, float], None] = None
) -> Callable[[int, int, float, float], None]:
class VerifyStdoutProtocol(Protocol):
def __call__(self, a0: int, a1: int, a2: float, a3: float) -> None:
...
def verify_stdout_factory(callback: VerifyStdoutProtocol = None) -> VerifyStdoutProtocol:
def verify_stdout(a0: int, a1: int, a2: float, a3: float) -> None:
if callback is not None and callable(callback):
callback(a0, a1, a2, a3)
@ -36,7 +49,12 @@ def verify_stdout_factory(
return verify_stdout
def ui_update_factory(callback: Callable[[UIUpdate], None] = None) -> Callable[[UIUpdate], None]:
class UiUpdateProtocol(Protocol):
def __call__(self, status: UIUpdate) -> None:
...
def ui_update_factory(callback: UiUpdateProtocol = None) -> UiUpdateProtocol:
def ui_update(status: UIUpdate) -> None:
if callback is not None and callable(callback):
callback(status)

View file

15
rare/lgndr/lfs/lgndry.py Normal file
View file

@ -0,0 +1,15 @@
import os
from filelock import FileLock
from legendary.lfs.lgndry import LGDLFS as LGDLFSReal
class LGDLFS(LGDLFSReal):
def __init__(self, *args, **kwargs):
super(LGDLFS, self).__init__(*args, **kwargs)
self.log.info("Using Rare's LGDLFS monkey")
# Rare: Default FileLock in Python 3.11 is thread-local, so replace it with a non-local verison
self._installed_lock = FileLock(os.path.join(self.path, 'installed.json') + '.lock', thread_local=False)
def unlock_installed(self):
self._installed_lock.release(force=True)