Improve AddOn detection by allowing renaming of top-level directory according to containing .toc.

This commit is contained in:
Casper V. Kristensen 2021-03-24 11:37:34 +01:00
parent 1445f7d26e
commit bedea2f983
Signed by: caspervk
GPG key ID: 289CA03790535054
2 changed files with 27 additions and 22 deletions

View file

@ -6,7 +6,7 @@ import re
import shutil
from dataclasses import dataclass, field
from pathlib import Path
from typing import Iterable, List
from typing import Iterable, List, Dict
from . import config, providers
@ -17,7 +17,7 @@ logger = logging.getLogger(__name__)
class Addon:
name: str = None
url: str = None
dirs: List[Path] = field(default_factory=list)
dirs: Dict[str, Path] = field(default_factory=dict)
provider: str = None
provider_data: dict = field(default_factory=dict)
@ -26,7 +26,7 @@ class Addon:
return {
"name": self.name,
"url": self.url,
"dirs": [str(d) for d in self.dirs],
"dirs": {name: str(path) for name, path in self.dirs.items()},
"provider": self.provider,
"provider_data": self.provider_data
}
@ -36,7 +36,7 @@ class Addon:
return cls(
name=data["name"],
url=data["url"],
dirs=[Path(d) for d in data["dirs"]],
dirs={name: Path(path) for name, path in data["dirs"].items()},
provider=data["provider"],
provider_data=data["provider_data"]
)
@ -51,7 +51,7 @@ class Addon:
self.download_dir.mkdir(parents=True, exist_ok=True)
def is_cached(self) -> bool:
return self.dirs and all(d.is_dir() for d in self.dirs)
return self.dirs and all(d.is_dir() for d in self.dirs.values())
def get_provider(self) -> providers.Provider:
if self.provider is not None:
@ -74,36 +74,41 @@ class Addon:
def install(self) -> None:
logger.info("Installing %s", self)
self.dirs = find_addon_dirs(self.download_dir)
for dir in self.dirs:
shutil.copytree(str(dir), config.ADDONS_DIR.joinpath(dir.name))
for dir_name, path in self.dirs.items():
shutil.copytree(str(path), config.ADDONS_DIR.joinpath(dir_name))
if self.name is None: # didn't get AddOn name from provider
if len(self.dirs) == 1:
self.name = self.dirs[0].name
self.name = next(iter(self.dirs.keys()))
else:
print(f"Unable to identify AddOn name for {self.url}")
self.name = input("AddOn name: ")
def uninstall(self) -> None:
logger.info("Uninstalling %s", self)
for dir in self.dirs:
shutil.rmtree(config.ADDONS_DIR.joinpath(dir.name), ignore_errors=True)
for dir_name in self.dirs.keys():
shutil.rmtree(config.ADDONS_DIR.joinpath(dir_name), ignore_errors=True)
def find_addon_dirs(path: Path) -> List[Path]:
def find_addon_dirs(path: Path) -> Dict[str, Path]:
"""
Find and return AddOns in the given path.
Each WoW AddOn is identified by a .toc-file whose filename matches its containing folder. The algorithm traverses
the path in a breadth-first fashion, returning all found AddOns in the first level which contains any AddOns.
This ensures we find AddOns 'Foo' and 'Foo_Config'.
WoW AddOns are identified by a .toc file whose filename matches its containing folder. The algorithm traverses the
given directory path in a breadth-first fashion, returning all AddOns in the first level which contains any such
directory/.toc-pairs. This ensures that we, as an example, find both AddOns 'Foo' and 'Foo_Config'.
Occasionally, the downloaded directory will contain no directory/.toc-pairs which conform to the Blizzard AddOn
standard; e.g. in the case of 'foo-master' from git with 'Foo.toc' in the root. Therefore, as a special case, if the
first level in the given path contains a single .toc file, this path will be returned along with its proper name, so
it can be renamed on install to Interface/AddOns.
"""
level = [path]
while level:
addon_dirs = []
addon_dirs = {}
for p in level:
if any(toc.stem == p.name for toc in p.glob("*.toc")):
addon_dirs.append(p)
tocs = list(p.glob("*.toc"))
if any(toc.stem == p.name for toc in tocs):
addon_dirs[p.name] = p
elif len(level) == 1 and len(tocs) == 1:
addon_dirs[tocs[0].stem] = p
if addon_dirs:
logger.debug("Found AddOn dirs: %s in '%s'", addon_dirs, path)
return addon_dirs
@ -126,7 +131,7 @@ def load_installed_addons() -> List[Addon]:
logger.debug("Loading list of installed AddOns from %s", config.CONFIG_FILE)
try:
with config.CONFIG_FILE.open() as cf:
return [Addon.from_json(a) for a in json.load(cf)["installed_addons"]]
return [Addon.from_json(addon) for addon in json.load(cf)["installed_addons"]]
except FileNotFoundError:
return []
@ -134,4 +139,4 @@ def load_installed_addons() -> List[Addon]:
def save_installed_addons(addons: Iterable[Addon]) -> None:
logger.debug("Saving list of installed AddOns to %s", config.CONFIG_FILE)
with config.CONFIG_FILE.open("w") as cf:
json.dump(dict(installed_addons=[a.to_json for a in addons]), cf)
json.dump(dict(installed_addons=[addon.to_json for addon in addons]), cf)

View file

@ -141,7 +141,7 @@ class CLI:
"Name": addon.name,
"Provider": addon.get_provider().__name__,
"URL": addon.url,
"Directories": textwrap.fill(", ".join(d.name for d in addon.dirs), width=40)
"Directories": textwrap.fill(", ".join(addon.dirs.keys()), width=40)
})
from tabulate import tabulate
print(tabulate(table, headers="keys", tablefmt="orgtbl"))