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 import shutil
from dataclasses import dataclass, field from dataclasses import dataclass, field
from pathlib import Path from pathlib import Path
from typing import Iterable, List from typing import Iterable, List, Dict
from . import config, providers from . import config, providers
@ -17,7 +17,7 @@ logger = logging.getLogger(__name__)
class Addon: class Addon:
name: str = None name: str = None
url: str = None url: str = None
dirs: List[Path] = field(default_factory=list) dirs: Dict[str, Path] = field(default_factory=dict)
provider: str = None provider: str = None
provider_data: dict = field(default_factory=dict) provider_data: dict = field(default_factory=dict)
@ -26,7 +26,7 @@ class Addon:
return { return {
"name": self.name, "name": self.name,
"url": self.url, "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": self.provider,
"provider_data": self.provider_data "provider_data": self.provider_data
} }
@ -36,7 +36,7 @@ class Addon:
return cls( return cls(
name=data["name"], name=data["name"],
url=data["url"], 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["provider"],
provider_data=data["provider_data"] provider_data=data["provider_data"]
) )
@ -51,7 +51,7 @@ class Addon:
self.download_dir.mkdir(parents=True, exist_ok=True) self.download_dir.mkdir(parents=True, exist_ok=True)
def is_cached(self) -> bool: 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: def get_provider(self) -> providers.Provider:
if self.provider is not None: if self.provider is not None:
@ -74,36 +74,41 @@ class Addon:
def install(self) -> None: def install(self) -> None:
logger.info("Installing %s", self) logger.info("Installing %s", self)
self.dirs = find_addon_dirs(self.download_dir) self.dirs = find_addon_dirs(self.download_dir)
for dir in self.dirs: for dir_name, path in self.dirs.items():
shutil.copytree(str(dir), config.ADDONS_DIR.joinpath(dir.name)) shutil.copytree(str(path), config.ADDONS_DIR.joinpath(dir_name))
if self.name is None: # didn't get AddOn name from provider if self.name is None: # didn't get AddOn name from provider
if len(self.dirs) == 1: if len(self.dirs) == 1:
self.name = self.dirs[0].name self.name = next(iter(self.dirs.keys()))
else: else:
print(f"Unable to identify AddOn name for {self.url}") print(f"Unable to identify AddOn name for {self.url}")
self.name = input("AddOn name: ") self.name = input("AddOn name: ")
def uninstall(self) -> None: def uninstall(self) -> None:
logger.info("Uninstalling %s", self) logger.info("Uninstalling %s", self)
for dir in self.dirs: for dir_name in self.dirs.keys():
shutil.rmtree(config.ADDONS_DIR.joinpath(dir.name), ignore_errors=True) 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. 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
Each WoW AddOn is identified by a .toc-file whose filename matches its containing folder. The algorithm traverses directory/.toc-pairs. This ensures that we, as an example, find both AddOns 'Foo' and 'Foo_Config'.
the path in a breadth-first fashion, returning all found AddOns in the first level which contains any AddOns. Occasionally, the downloaded directory will contain no directory/.toc-pairs which conform to the Blizzard AddOn
This ensures we find AddOns 'Foo' and 'Foo_Config'. 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] level = [path]
while level: while level:
addon_dirs = [] addon_dirs = {}
for p in level: for p in level:
if any(toc.stem == p.name for toc in p.glob("*.toc")): tocs = list(p.glob("*.toc"))
addon_dirs.append(p) 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: if addon_dirs:
logger.debug("Found AddOn dirs: %s in '%s'", addon_dirs, path) logger.debug("Found AddOn dirs: %s in '%s'", addon_dirs, path)
return addon_dirs 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) logger.debug("Loading list of installed AddOns from %s", config.CONFIG_FILE)
try: try:
with config.CONFIG_FILE.open() as cf: 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: except FileNotFoundError:
return [] return []
@ -134,4 +139,4 @@ def load_installed_addons() -> List[Addon]:
def save_installed_addons(addons: Iterable[Addon]) -> None: def save_installed_addons(addons: Iterable[Addon]) -> None:
logger.debug("Saving list of installed AddOns to %s", config.CONFIG_FILE) logger.debug("Saving list of installed AddOns to %s", config.CONFIG_FILE)
with config.CONFIG_FILE.open("w") as cf: 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, "Name": addon.name,
"Provider": addon.get_provider().__name__, "Provider": addon.get_provider().__name__,
"URL": addon.url, "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 from tabulate import tabulate
print(tabulate(table, headers="keys", tablefmt="orgtbl")) print(tabulate(table, headers="keys", tablefmt="orgtbl"))