Compare commits

...

3 commits

3 changed files with 39 additions and 39 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

@ -98,7 +98,7 @@ class CLI:
args.func(args)
def install(self, args) -> None:
already_installed_urls = {a.url for a in self.installed_addons}
already_installed_urls = {addon.url for addon in self.installed_addons}
for url in set(args.urls): # set removes duplicates
if url in already_installed_urls:
print(f"{url} is already installed")
@ -108,21 +108,16 @@ class CLI:
addon.download()
addon.install()
self.installed_addons.append(addon)
addons.save_installed_addons(self.installed_addons)
addons.save_installed_addons(self.installed_addons)
def remove(self, args) -> None:
if args.all:
remove = self.installed_addons.copy()
else:
remove = [addon
for addon in self.installed_addons
if addon.name in args.addons]
for addon in remove:
print(f"Removing {addon.name}")
addon.uninstall()
shutil.rmtree(addon.download_dir, ignore_errors=True)
self.installed_addons.remove(addon)
addons.save_installed_addons(self.installed_addons)
for addon in self.installed_addons:
if args.all or addon.name in args.addons:
print(f"Removing {addon.name}")
addon.uninstall()
shutil.rmtree(addon.download_dir, ignore_errors=True)
self.installed_addons.remove(addon)
addons.save_installed_addons(self.installed_addons)
def update(self, args) -> None:
for addon in self.installed_addons:
@ -132,16 +127,16 @@ class CLI:
addon.uninstall()
addon.install()
print("Done" if changed else "Already up to date")
addons.save_installed_addons(self.installed_addons)
addons.save_installed_addons(self.installed_addons)
def list(self, args) -> None:
table = []
for addon in self.installed_addons:
for addon in sorted(self.installed_addons, key=lambda a: a.name.lower()):
table.append({
"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"))

View file

@ -28,7 +28,7 @@ class GitHub(Web):
latest_release = http.open(
url=f"{cls.api_url}/repos/{repo_owner}/{repo_name}/releases/latest"
).json()
for asset in latest_release["assets"]:
for asset in sorted(latest_release["assets"], key=lambda a: "classic" in a["name"].lower(), reverse=True):
if Path(asset["name"]).suffix == ".zip":
return super().download(addon, asset["browser_download_url"])
raise FileNotFoundError("No zip file found for latest release")