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:
parent
9de5627be3
commit
f361828f37
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
0
rare/lgndr/lfs/__init__.py
Normal file
0
rare/lgndr/lfs/__init__.py
Normal file
15
rare/lgndr/lfs/lgndry.py
Normal file
15
rare/lgndr/lfs/lgndry.py
Normal 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)
|
Loading…
Reference in a new issue