[cli/core/utils/lfs] Add automatic alias generation

This commit is contained in:
derrod 2021-10-02 21:10:25 +02:00
parent d70d5a6521
commit 0d8b74a9e0
4 changed files with 155 additions and 2 deletions

View file

@ -51,9 +51,15 @@ class LegendaryCLI:
ql.start()
return ql
def _resolve_aliases(self, app_name):
def _resolve_aliases(self, name):
# make sure aliases exist if not yet created
self.core.update_aliases(force=False)
name = name.strip()
# resolve alias (if any) to real app name
return self.core.lgd.config.get('Legendary.aliases', app_name, fallback=app_name)
return self.core.lgd.config.get(
section='Legendary.aliases', option=name,
fallback=self.core.lgd.aliases.get(name.lower(), name)
)
def auth(self, args):
if args.auth_delete:

View file

@ -273,6 +273,11 @@ class LegendaryCore:
# return data if available
return sdl_data
def update_aliases(self, force=False):
_aliases_enabled = not self.lgd.config.getboolean('Legendary', 'disable_auto_aliasing', fallback=False)
if _aliases_enabled and (force or not self.lgd.aliases):
self.lgd.generate_aliases()
def get_assets(self, update_assets=False, platform_override=None) -> List[GameAsset]:
# do not save and always fetch list when platform is overridden
if platform_override:
@ -311,6 +316,7 @@ class LegendaryCore:
force_refresh=False, skip_ue=True) -> (List[Game], Dict[str, List[Game]]):
_ret = []
_dlc = defaultdict(list)
meta_updated = False
for ga in self.get_assets(update_assets=update_assets,
platform_override=platform_override):
@ -328,6 +334,7 @@ class LegendaryCore:
app_title=eg_meta['title'], asset_info=ga, metadata=eg_meta)
if not platform_override:
meta_updated = True
self.lgd.set_game_meta(game.app_name, game)
# replace asset info with the platform specific one if override is used
@ -340,6 +347,9 @@ class LegendaryCore:
elif not any(i['path'] == 'mods' for i in game.metadata.get('categories', [])):
_ret.append(game)
if not platform_override:
self.update_aliases(force=meta_updated)
return _ret, _dlc
def get_non_asset_library_items(self, force_refresh=False,

View file

@ -4,10 +4,12 @@ import json
import os
import logging
from collections import defaultdict
from pathlib import Path
from time import time
from legendary.models.game import *
from legendary.utils.aliasing import generate_aliases
from legendary.utils.config import LGDConf
from legendary.utils.env import is_windows_or_pyi
from legendary.utils.lfs import clean_filename
@ -112,6 +114,17 @@ class LGDLFS:
except Exception as e:
self.log.debug(f'Loading game meta file "{gm_file}" failed: {e!r}')
# load auto-aliases if enabled
self.aliases = dict()
if not self.config.getboolean('Legendary', 'disable_auto_aliasing', fallback=False):
try:
_j = json.load(open(os.path.join(self.path, 'aliases.json')))
for app_name, aliases in _j.items():
for alias in aliases:
self.aliases[alias] = app_name
except Exception as e:
self.log.debug(f'Loading aliases failed with {e!r}')
@property
def userdata(self):
if self._user_data is not None:
@ -339,3 +352,38 @@ class LGDLFS:
json.dump(dict(version=sdl_version, data=sdl_data),
open(os.path.join(self.path, 'tmp', f'{app_name}.json'), 'w'),
indent=2, sort_keys=True)
def generate_aliases(self):
self.log.debug('Generating list of aliases...')
aliases = set()
collisions = set()
alias_map = defaultdict(set)
for app_name in self._game_metadata.keys():
game = self.get_game_meta(app_name)
if game.is_dlc:
continue
game_folder = game.metadata.get('customAttributes', {}).get('FolderName', {}).get('value', None)
_aliases = generate_aliases(game.app_title, game_folder)
for alias in _aliases:
if alias not in aliases:
aliases.add(alias)
alias_map[game.app_name].add(alias)
else:
collisions.add(alias)
# remove colliding aliases from map and add aliases to lookup table
for app_name, aliases in alias_map.items():
alias_map[app_name] -= collisions
for alias in alias_map[app_name]:
self.aliases[alias] = app_name
def serialise_sets(obj):
"""Turn sets into sorted lists for storage"""
if isinstance(obj, set):
return sorted(obj)
return obj
json.dump(alias_map, open(os.path.join(self.path, 'aliases.json'), 'w', newline='\n'),
indent=2, sort_keys=True, default=serialise_sets)

View file

@ -0,0 +1,89 @@
from string import ascii_lowercase, digits
# Aliases generated:
# - name lowercase (without TM etc.)
# - same, but without spaces
# - same, but roman numerals are replaced
# if name has >= 3 parts:
# - initials
# - initials, but roman numerals are intact
# - initials, but roman numerals are replaced with number
# if ':' in name:
# - run previous recursively with everything before ":"
# if single 'f' in long word:
# - split word (this is mainly for cases like Battlfront -> BF)
allowed_characters = ascii_lowercase+digits
roman = {
# 'i': '1',
'ii': '2',
'iii': '3',
'iv': '4',
'v': '5',
'vi': '6',
'vii': '7',
'viii': '8',
'ix': '9',
'x': '10',
'xi': '11',
'xii': '12',
'xiii': '13',
'xiv': '14',
'xv': '15',
'xvi': '16',
'xvii': '17',
'xviii': '18',
'xix': '19',
'xx': '20'
}
def _filter(input):
return ''.join(l for l in input if l in allowed_characters)
def generate_aliases(game_name, game_folder=None, split_words=True):
# normalise and split name, then filter for legal characters
game_parts = [_filter(p) for p in game_name.lower().split()]
# filter out empty parts
game_parts = [p for p in game_parts if p]
_aliases = [
' '.join(game_parts),
''.join(game_parts),
''.join(roman.get(p, p) for p in game_parts),
]
# single word abbreviation
first_word = next(i for i in game_parts if i not in ('for', 'the'))
if len(first_word) > 1:
_aliases.append(first_word)
# remove subtitle from game
if ':' in game_name:
_aliases.extend(generate_aliases(game_name.partition(':')[0]))
# include folder name for alternative short forms
if game_folder:
_aliases.extend(generate_aliases(game_folder, split_words=False))
# include initialisms
if len(game_parts) > 2:
_aliases.append(''.join(p[0] for p in game_parts))
_aliases.append(''.join(p[0] if p not in roman else p for p in game_parts))
_aliases.append(''.join(roman.get(p, p[0]) for p in game_parts))
# Attempt to address cases like "Battlefront" being shortened to "BF"
if split_words:
new_game_parts = []
for word in game_parts:
if len(word) >= 8 and word[3:-3].count('f') == 1:
word_middle = word[3:-3]
word_split = ' f'.join(word_middle.split('f'))
word = word[0:3] + word_split + word[-3:]
new_game_parts.extend(word.split())
else:
new_game_parts.append(word)
_aliases.append(''.join(p[0] for p in new_game_parts))
_aliases.append(''.join(p[0] if p not in roman else p for p in new_game_parts))
_aliases.append(''.join(roman.get(p, p[0]) for p in new_game_parts))
# return sorted unqiues
return sorted(set(_aliases))