mirror of
https://github.com/derrod/legendary.git
synced 2024-12-22 17:55:27 +00:00
[cli/core/utils/lfs] Add automatic alias generation
This commit is contained in:
parent
d70d5a6521
commit
0d8b74a9e0
|
@ -51,9 +51,15 @@ class LegendaryCLI:
|
||||||
ql.start()
|
ql.start()
|
||||||
return ql
|
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
|
# 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):
|
def auth(self, args):
|
||||||
if args.auth_delete:
|
if args.auth_delete:
|
||||||
|
|
|
@ -273,6 +273,11 @@ class LegendaryCore:
|
||||||
# return data if available
|
# return data if available
|
||||||
return sdl_data
|
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]:
|
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:
|
||||||
|
@ -311,6 +316,7 @@ class LegendaryCore:
|
||||||
force_refresh=False, skip_ue=True) -> (List[Game], Dict[str, List[Game]]):
|
force_refresh=False, skip_ue=True) -> (List[Game], Dict[str, List[Game]]):
|
||||||
_ret = []
|
_ret = []
|
||||||
_dlc = defaultdict(list)
|
_dlc = defaultdict(list)
|
||||||
|
meta_updated = False
|
||||||
|
|
||||||
for ga in self.get_assets(update_assets=update_assets,
|
for ga in self.get_assets(update_assets=update_assets,
|
||||||
platform_override=platform_override):
|
platform_override=platform_override):
|
||||||
|
@ -328,6 +334,7 @@ class LegendaryCore:
|
||||||
app_title=eg_meta['title'], asset_info=ga, metadata=eg_meta)
|
app_title=eg_meta['title'], asset_info=ga, metadata=eg_meta)
|
||||||
|
|
||||||
if not platform_override:
|
if not platform_override:
|
||||||
|
meta_updated = True
|
||||||
self.lgd.set_game_meta(game.app_name, game)
|
self.lgd.set_game_meta(game.app_name, game)
|
||||||
|
|
||||||
# replace asset info with the platform specific one if override is used
|
# 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', [])):
|
elif not any(i['path'] == 'mods' for i in game.metadata.get('categories', [])):
|
||||||
_ret.append(game)
|
_ret.append(game)
|
||||||
|
|
||||||
|
if not platform_override:
|
||||||
|
self.update_aliases(force=meta_updated)
|
||||||
|
|
||||||
return _ret, _dlc
|
return _ret, _dlc
|
||||||
|
|
||||||
def get_non_asset_library_items(self, force_refresh=False,
|
def get_non_asset_library_items(self, force_refresh=False,
|
||||||
|
|
|
@ -4,10 +4,12 @@ import json
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from collections import defaultdict
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from time import time
|
from time import time
|
||||||
|
|
||||||
from legendary.models.game import *
|
from legendary.models.game import *
|
||||||
|
from legendary.utils.aliasing import generate_aliases
|
||||||
from legendary.utils.config import LGDConf
|
from legendary.utils.config import LGDConf
|
||||||
from legendary.utils.env import is_windows_or_pyi
|
from legendary.utils.env import is_windows_or_pyi
|
||||||
from legendary.utils.lfs import clean_filename
|
from legendary.utils.lfs import clean_filename
|
||||||
|
@ -112,6 +114,17 @@ class LGDLFS:
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.log.debug(f'Loading game meta file "{gm_file}" failed: {e!r}')
|
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
|
@property
|
||||||
def userdata(self):
|
def userdata(self):
|
||||||
if self._user_data is not None:
|
if self._user_data is not None:
|
||||||
|
@ -339,3 +352,38 @@ class LGDLFS:
|
||||||
json.dump(dict(version=sdl_version, data=sdl_data),
|
json.dump(dict(version=sdl_version, data=sdl_data),
|
||||||
open(os.path.join(self.path, 'tmp', f'{app_name}.json'), 'w'),
|
open(os.path.join(self.path, 'tmp', f'{app_name}.json'), 'w'),
|
||||||
indent=2, sort_keys=True)
|
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)
|
||||||
|
|
89
legendary/utils/aliasing.py
Normal file
89
legendary/utils/aliasing.py
Normal 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))
|
Loading…
Reference in a new issue