[api/cli/core/lfs] Add Legendary update check

This commit is contained in:
derrod 2021-09-02 19:29:50 +02:00
parent 69eeccec21
commit a465966954
4 changed files with 109 additions and 0 deletions

22
legendary/api/lgd.py Normal file
View file

@ -0,0 +1,22 @@
# !/usr/bin/env python
# coding: utf-8
import legendary
import requests
import logging
class LGDAPI:
_user_agent = f'Legendary/{legendary.__version__}'
_api_host = 'legendary.rodney.io'
def __init__(self):
self.session = requests.session()
self.log = logging.getLogger('LGDAPI')
self.session.headers['User-Agent'] = self._user_agent
def get_version_information(self):
r = self.session.get(f'https://{self._api_host}/version.json',
timeout=10.0)
r.raise_for_status()
return r.json()

View file

@ -1065,6 +1065,8 @@ class LegendaryCLI:
exit(1) exit(1)
except ValueError: except ValueError:
pass pass
# if automatic checks are off force an update here
self.core.check_for_updates(force=True)
if not self.core.lgd.userdata: if not self.core.lgd.userdata:
user_name = '<not logged in>' user_name = '<not logged in>'
@ -1089,6 +1091,16 @@ class LegendaryCLI:
print(f'Games installed: {games_installed}') print(f'Games installed: {games_installed}')
print(f'EGL Sync enabled: {self.core.egl_sync_enabled}') print(f'EGL Sync enabled: {self.core.egl_sync_enabled}')
print(f'Config directory: {self.core.lgd.path}') print(f'Config directory: {self.core.lgd.path}')
print(f'\nLegendary version: {__version__} - "{__codename__}"')
print(f'Update available: {"yes" if self.core.update_available else "no"}')
if self.core.update_available:
if update_info := self.core.get_update_info():
print(f'- New version: {update_info["version"]} - "{update_info["name"]}"')
print(f'- Release summary:\n{update_info["summary"]}\n- Release URL: {update_info["gh_url"]}')
if update_info['critical']:
print('! This update is recommended as it fixes major issues.')
# prevent update message on close
self.core.update_available = False
def cleanup(self, args): def cleanup(self, args):
before = self.core.lgd.get_dir_size() before = self.core.lgd.get_dir_size()
@ -1413,6 +1425,15 @@ def main():
except KeyboardInterrupt: except KeyboardInterrupt:
logger.info('Command was aborted via KeyboardInterrupt, cleaning up...') logger.info('Command was aborted via KeyboardInterrupt, cleaning up...')
# show note if update is available
if cli.core.update_available:
if update_info := cli.core.get_update_info():
print(f'\nLegendary update available!')
print(f'- New version: {update_info["version"]} - "{update_info["name"]}"')
print(f'- Release summary:\n{update_info["summary"]}\n- Release URL: {update_info["gh_url"]}')
if update_info['critical']:
print('! This update is recommended as it fixes major issues.')
cli.core.exit() cli.core.exit()
ql.stop() ql.stop()
exit(0) exit(0)

View file

@ -16,7 +16,9 @@ from requests.exceptions import HTTPError
from typing import List, Dict from typing import List, Dict
from uuid import uuid4 from uuid import uuid4
from legendary import __version__
from legendary.api.egs import EPCAPI from legendary.api.egs import EPCAPI
from legendary.api.lgd import LGDAPI
from legendary.downloader.mp.manager import DLManager from legendary.downloader.mp.manager import DLManager
from legendary.lfs.egl import EPCLFS from legendary.lfs.egl import EPCLFS
from legendary.lfs.lgndry import LGDLFS from legendary.lfs.lgndry import LGDLFS
@ -50,6 +52,7 @@ class LegendaryCore:
self.egs = EPCAPI() self.egs = EPCAPI()
self.lgd = LGDLFS() self.lgd = LGDLFS()
self.egl = EPCLFS() self.egl = EPCLFS()
self.lgdapi = LGDAPI()
# on non-Windows load the programdata path from config # on non-Windows load the programdata path from config
if os.name != 'nt': if os.name != 'nt':
@ -75,6 +78,8 @@ class LegendaryCore:
else: else:
self.log.warning(f'Could not determine locale, falling back to en-US') self.log.warning(f'Could not determine locale, falling back to en-US')
self.update_available = False
def auth(self, username, password): def auth(self, username, password):
""" """
Attempts direct non-web login, raises CaptchaError if manual login is required Attempts direct non-web login, raises CaptchaError if manual login is required
@ -153,6 +158,15 @@ class LegendaryCore:
if not self.lgd.userdata: if not self.lgd.userdata:
raise ValueError('No saved credentials') raise ValueError('No saved credentials')
# run update check
if self.update_check_enabled():
try:
self.check_for_updates()
except Exception as e:
self.log.warning(f'Checking for Legendary updates failed: {e!r}')
else:
self.apply_lgd_config()
if self.lgd.userdata['expires_at']: if self.lgd.userdata['expires_at']:
dt_exp = datetime.fromisoformat(self.lgd.userdata['expires_at'][:-1]) dt_exp = datetime.fromisoformat(self.lgd.userdata['expires_at'][:-1])
dt_now = datetime.utcnow() dt_now = datetime.utcnow()
@ -185,6 +199,40 @@ class LegendaryCore:
self.lgd.userdata = userdata self.lgd.userdata = userdata
return True return True
def update_check_enabled(self):
return self.lgd.config.getboolean('Legendary', 'enable_update_check',
fallback=os.name == 'nt')
def check_for_updates(self, force=False):
def version_tuple(v):
return tuple(map(int, (v.split('.'))))
cached = self.lgd.get_cached_version()
version_info = cached['data']
if force or not version_info or (datetime.now().timestamp() - cached['last_update']) > 24*3600:
version_info = self.lgdapi.get_version_information()
self.lgd.set_cached_version(version_info)
web_version = version_info['release_info']['version']
self.update_available = version_tuple(web_version) > version_tuple(__version__)
self.apply_lgd_config(version_info)
def apply_lgd_config(self, version_info=None):
"""Applies configuration options returned by update API"""
if not version_info:
version_info = self.lgd.get_cached_version()['data']
# if cached data is invalid
if not version_info:
self.log.debug('No cached legendary config to apply.')
return
if 'egl_config' in version_info:
self.egs.update_egs_params(version_info['egl_config'])
# todo update sid auth/downloader UA and game overrides
def get_update_info(self):
return self.lgd.get_cached_version()['data'].get('release_info')
def get_assets(self, update_assets=False, platform_override=None) -> List[GameAsset]: def get_assets(self, update_assets=False, platform_override=None) -> List[GameAsset]:
# do not save and always fetch list when platform is overridden # do not save and always fetch list when platform is overridden
if platform_override: if platform_override:

View file

@ -5,6 +5,7 @@ import os
import logging import logging
from pathlib import Path from pathlib import Path
from time import time
from legendary.models.game import * from legendary.models.game import *
from legendary.utils.config import LGDConf from legendary.utils.config import LGDConf
@ -28,6 +29,8 @@ class LGDLFS:
self._assets = None self._assets = None
# EGS metadata # EGS metadata
self._game_metadata = dict() self._game_metadata = dict()
# Legendary update check info
self._update_info = None
# Config with game specific settings (e.g. start parameters, env variables) # Config with game specific settings (e.g. start parameters, env variables)
self.config = LGDConf(comment_prefixes='/', allow_no_value=True) self.config = LGDConf(comment_prefixes='/', allow_no_value=True)
self.config.optionxform = str self.config.optionxform = str
@ -285,3 +288,18 @@ class LGDLFS:
def get_dir_size(self): def get_dir_size(self):
return sum(f.stat().st_size for f in Path(self.path).glob('**/*') if f.is_file()) return sum(f.stat().st_size for f in Path(self.path).glob('**/*') if f.is_file())
def get_cached_version(self):
try:
self._update_info = json.load(open(os.path.join(self.path, 'version.json')))
return self._update_info
except Exception as e:
self.log.debug(f'Failed to load cached update data: {e!r}')
return dict(last_update=0, data=None)
def set_cached_version(self, version_data):
if not version_data:
return
self._update_info = dict(last_update=time(), data=version_data)
json.dump(self._update_info, open(os.path.join(self.path, 'version.json'), 'w'),
indent=2, sort_keys=True)