mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-11-06 03:08:55 -05:00
[V3 Downloader] Make shared libraries work and make repo handling smarter (#1313)
* Make stuff stateless * Update shared lib stuff
This commit is contained in:
parent
d2e841f681
commit
249756e0d2
@ -15,7 +15,7 @@ class InstalledCog(commands.Converter):
|
||||
if downloader is None:
|
||||
raise commands.CommandError("Downloader not loaded.")
|
||||
|
||||
cog = discord.utils.get(downloader.installed_cogs, name=arg)
|
||||
cog = discord.utils.get(await downloader.installed_cogs(), name=arg)
|
||||
if cog is None:
|
||||
raise commands.BadArgument(
|
||||
"That cog is not installed"
|
||||
|
||||
@ -7,6 +7,7 @@ from typing import Tuple, Union
|
||||
import discord
|
||||
from redbot.core import Config
|
||||
from redbot.core import checks
|
||||
from redbot.core.data_manager import cog_data_path
|
||||
from redbot.core.i18n import CogI18n
|
||||
from redbot.core.utils.chat_formatting import box, pagify
|
||||
from discord.ext import commands
|
||||
@ -30,13 +31,12 @@ class Downloader:
|
||||
force_registration=True)
|
||||
|
||||
self.conf.register_global(
|
||||
repos={},
|
||||
installed=[]
|
||||
)
|
||||
|
||||
self.already_agreed = False
|
||||
|
||||
self.LIB_PATH = self.bot.main_dir / "lib"
|
||||
self.LIB_PATH = cog_data_path(self) / "lib"
|
||||
self.SHAREDLIB_PATH = self.LIB_PATH / "cog_shared"
|
||||
self.SHAREDLIB_INIT = self.SHAREDLIB_PATH / "__init__.py"
|
||||
|
||||
@ -73,7 +73,7 @@ class Downloader:
|
||||
"""
|
||||
installed = await self.conf.installed()
|
||||
# noinspection PyTypeChecker
|
||||
return tuple(Installable.from_json(v) for v in installed)
|
||||
return tuple(Installable.from_json(v, self._repo_manager) for v in installed)
|
||||
|
||||
async def _add_to_installed(self, cog: Installable):
|
||||
"""Mark a cog as installed.
|
||||
@ -297,11 +297,19 @@ class Downloader:
|
||||
"""
|
||||
Updates all cogs or one of your choosing.
|
||||
"""
|
||||
installed_cogs = set(await self.installed_cogs())
|
||||
|
||||
if cog_name is None:
|
||||
updated = await self._repo_manager.update_all_repos()
|
||||
installed_cogs = set(await self.installed_cogs())
|
||||
updated_cogs = set(cog for repo in updated.keys() for cog in repo.available_cogs)
|
||||
|
||||
else:
|
||||
try:
|
||||
updated = await self._repo_manager.update_repo(cog_name.repo_name)
|
||||
except KeyError:
|
||||
# Thrown if the repo no longer exists
|
||||
updated = {}
|
||||
|
||||
updated_cogs = set(cog for repo in updated.keys() for cog in repo.available_cogs)
|
||||
installed_and_updated = updated_cogs & installed_cogs
|
||||
|
||||
# noinspection PyTypeChecker
|
||||
|
||||
@ -3,12 +3,15 @@ import distutils.dir_util
|
||||
import shutil
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
from typing import Union, MutableMapping, Any
|
||||
from typing import MutableMapping, Any
|
||||
|
||||
from redbot.core import data_manager
|
||||
from redbot.core.utils import TYPE_CHECKING
|
||||
from .log import log
|
||||
from .json_mixins import RepoJSONMixin
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .repo_manager import RepoManager
|
||||
|
||||
|
||||
class InstallableType(Enum):
|
||||
UNKNOWN = 0
|
||||
@ -50,11 +53,6 @@ class Installable(RepoJSONMixin):
|
||||
:class:`InstallationType`.
|
||||
|
||||
"""
|
||||
|
||||
INFO_FILE_DESCRIPTION = """
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, location: Path):
|
||||
"""Base installable initializer.
|
||||
|
||||
@ -192,13 +190,22 @@ class Installable(RepoJSONMixin):
|
||||
return info
|
||||
|
||||
def to_json(self):
|
||||
data_path = data_manager.cog_data_path()
|
||||
return {
|
||||
"location": self._location.relative_to(data_path).parts
|
||||
"repo_name": self.repo_name,
|
||||
"cog_name": self.name
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, data: dict):
|
||||
data_path = data_manager.cog_data_path()
|
||||
location = data_path / Path(*data["location"])
|
||||
def from_json(cls, data: dict, repo_mgr: "RepoManager"):
|
||||
repo_name = data['repo_name']
|
||||
cog_name = data['cog_name']
|
||||
|
||||
repo = repo_mgr.get_repo(repo_name)
|
||||
if repo is not None:
|
||||
repo_folder = repo.folder_path
|
||||
else:
|
||||
repo_folder = repo_mgr.repos_folder / "MISSING_REPO"
|
||||
|
||||
location = repo_folder / cog_name
|
||||
|
||||
return cls(location=location)
|
||||
|
||||
@ -30,6 +30,7 @@ class Repo(RepoJSONMixin):
|
||||
" {old_hash} {new_hash}")
|
||||
GIT_LOG = ("git -C {path} log --relative-date --reverse {old_hash}.."
|
||||
" {relative_file_path}")
|
||||
GIT_DISCOVER_REMOTE_URL = "git -C {path} config --get remote.origin.url"
|
||||
|
||||
PIP_INSTALL = "{python} -m pip install -U -t {target_dir} {reqs}"
|
||||
|
||||
@ -266,6 +267,39 @@ class Repo(RepoJSONMixin):
|
||||
|
||||
return p.stdout.decode().strip()
|
||||
|
||||
async def current_url(self, folder: Path=None) -> str:
|
||||
"""
|
||||
Discovers the FETCH URL for a Git repo.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
folder : pathlib.Path
|
||||
The folder to search for a URL.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
The FETCH URL.
|
||||
|
||||
Raises
|
||||
------
|
||||
RuntimeError
|
||||
When the folder does not contain a git repo with a FETCH URL.
|
||||
"""
|
||||
if folder is None:
|
||||
folder = self.folder_path
|
||||
|
||||
p = await self._run(
|
||||
Repo.GIT_DISCOVER_REMOTE_URL.format(
|
||||
path=folder
|
||||
).split()
|
||||
)
|
||||
|
||||
if p.returncode != 0:
|
||||
raise RuntimeError("Unable to discover a repo URL.")
|
||||
|
||||
return p.stdout.decode().strip()
|
||||
|
||||
async def hard_reset(self, branch: str=None) -> None:
|
||||
"""Perform a hard reset on the current repo.
|
||||
|
||||
@ -373,14 +407,17 @@ class Repo(RepoJSONMixin):
|
||||
The success of the installation.
|
||||
|
||||
"""
|
||||
if libraries:
|
||||
if len(libraries) > 0:
|
||||
if not all([i in self.available_libraries for i in libraries]):
|
||||
raise ValueError("Some given libraries are not available in this repo.")
|
||||
else:
|
||||
libraries = self.available_libraries
|
||||
|
||||
if libraries:
|
||||
return all([lib.copy_to(target_dir=target_dir) for lib in libraries])
|
||||
if len(libraries) > 0:
|
||||
ret = True
|
||||
for lib in libraries:
|
||||
ret = ret and await lib.copy_to(target_dir=target_dir)
|
||||
return ret
|
||||
return True
|
||||
|
||||
async def install_requirements(self, cog: Installable, target_dir: Path) -> bool:
|
||||
@ -467,23 +504,13 @@ class Repo(RepoJSONMixin):
|
||||
if m.type == InstallableType.SHARED_LIBRARY]
|
||||
)
|
||||
|
||||
def to_json(self):
|
||||
data_path = data_manager.cog_data_path()
|
||||
return {
|
||||
"url": self.url,
|
||||
"name": self.name,
|
||||
"branch": self.branch,
|
||||
"folder_path": self.folder_path.relative_to(data_path).parts,
|
||||
"available_modules": [m.to_json() for m in self.available_modules]
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, data):
|
||||
data_path = data_manager.cog_data_path()
|
||||
# noinspection PyTypeChecker
|
||||
return Repo(data['name'], data['url'], data['branch'],
|
||||
data_path / Path(*data['folder_path']),
|
||||
tuple([Installable.from_json(m) for m in data['available_modules']]))
|
||||
async def from_folder(cls, folder: Path):
|
||||
repo = cls(name=folder.stem, branch="", url="", folder_path=folder)
|
||||
repo.branch = await repo.current_branch()
|
||||
repo.url = await repo.current_url()
|
||||
repo._update_available_modules()
|
||||
return repo
|
||||
|
||||
|
||||
class RepoManager:
|
||||
@ -540,7 +567,6 @@ class RepoManager:
|
||||
await r.clone()
|
||||
|
||||
self._repos[name] = r
|
||||
await self._save_repos()
|
||||
|
||||
return r
|
||||
|
||||
@ -596,7 +622,10 @@ class RepoManager:
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
await self._save_repos()
|
||||
async def update_repo(self, repo_name: str) -> MutableMapping[Repo, Tuple[str, str]]:
|
||||
repo = self._repos[repo_name]
|
||||
old, new = await repo.update()
|
||||
return {repo: (old, new)}
|
||||
|
||||
async def update_all_repos(self) -> MutableMapping[Repo, Tuple[str, str]]:
|
||||
"""Call `Repo.update` on all repositories.
|
||||
@ -609,23 +638,23 @@ class RepoManager:
|
||||
|
||||
"""
|
||||
ret = {}
|
||||
for _, repo in self._repos.items():
|
||||
old, new = await repo.update()
|
||||
for repo_name, _ in self._repos.items():
|
||||
repo, (old, new) = await self.update_repo(repo_name)
|
||||
if old != new:
|
||||
ret[repo] = (old, new)
|
||||
|
||||
await self._save_repos()
|
||||
return ret
|
||||
|
||||
async def _load_repos(self, set=False) -> MutableMapping[str, Repo]:
|
||||
ret = {
|
||||
name: Repo.from_json(data) for name, data in
|
||||
(await self.downloader_config.repos()).items()
|
||||
}
|
||||
ret = {}
|
||||
for folder in self.repos_folder.iterdir():
|
||||
if not folder.is_dir():
|
||||
continue
|
||||
try:
|
||||
ret[folder.stem] = await Repo.from_folder(folder)
|
||||
except RuntimeError:
|
||||
# Thrown when there's no findable git remote URL
|
||||
pass
|
||||
|
||||
if set:
|
||||
self._repos = ret
|
||||
return ret
|
||||
|
||||
async def _save_repos(self):
|
||||
repo_json_info = {name: r.to_json() for name, r in self._repos.items()}
|
||||
await self.downloader_config.repos.set(repo_json_info)
|
||||
|
||||
@ -66,6 +66,6 @@ def test_repo_name(installable):
|
||||
|
||||
def test_serialization(installable):
|
||||
data = installable.to_json()
|
||||
location = data["location"]
|
||||
cog_name = data["cog_name"]
|
||||
|
||||
assert location[-1] == "test_cog"
|
||||
assert cog_name == "test_cog"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user