diff --git a/.gitignore b/.gitignore index c1add4727288b3b64253716a13e238836e275f20..44f64940b0346c92d46da74f43a96c33a474ea97 100644 --- a/.gitignore +++ b/.gitignore @@ -137,3 +137,5 @@ flake8.txt # junit report junit-report.xml +# General developer helper files +repod/__main__.py \ No newline at end of file diff --git a/repod/errors.py b/repod/errors.py index 08079a6f715df9b451f0eca7ad7eee896cc01891..dbbb4ba357b5ee3bd161511993a87732bc0676e1 100644 --- a/repod/errors.py +++ b/repod/errors.py @@ -12,3 +12,7 @@ class RepoManagementValidationError(RepoManagementError): class RepoManagementFileNotFoundError(RepoManagementFileError, FileNotFoundError): """An Error that is raised when a file can not be found""" + + +class PackageError(BaseException): + """A generic error that can be raised when a package contains an error""" diff --git a/repod/filehandling.py b/repod/filehandling.py new file mode 100644 index 0000000000000000000000000000000000000000..a779e235fed90035424b156b4eb0aef3a9728b7c --- /dev/null +++ b/repod/filehandling.py @@ -0,0 +1,94 @@ +import hashlib +import pathlib +import shutil +import typing + +from .errors import PackageError + + +def hashsum(file: pathlib.Path) -> bytes: + with file.open("rb") as fh: + return hashlib.sha512(fh.read()).digest() + + +def package_is_active(package: pathlib.Path) -> typing.Optional[str]: + """ + Check the databases if the given package is active or not. + If it is, return the repository that it was found in. + + Parameters + ---------- + package: pathlib.Path + A name, relative or absolute path to a package file. + + Returns + ------- + str + Returns the name of the repository in which the package was found. + """ + return None + + +async def add_package_to_pool(package: pathlib.Path, pool: pathlib.Path, skip_signature: bool = False): + if (target := pool / package.name).exists(): + if hashsum(package) != hashsum(target): + raise PackageError(f"{package.name} already exists in {pool} but with a different content.") + + if not skip_signature and pathlib.Path(f"{package}.sig").exists() is False: + raise PackageError(f"Could not locate signature for {package.name} at {package}.sig") + + if not target.parent.exists(): + target.parent.mkdir(parents=True, exist_ok=True) + + # TODO: Do we want to copy the file to a randomly generated name first, + # and then call path.rename() as suggested by https://gitlab.archlinux.org/archlinux/repod/-/issues/14#note_56232 + shutil.copy2(package, pool) + if skip_signature is False: + shutil.copy2(f"{package}.sig", pool) + + +async def remove_package_from_pool(package: pathlib.Path, pool: pathlib.Path): + if repo := package_is_active(f"{package}"): + raise PackageError(f"{package.name} is active in repo {repo}") + + (pool / package.name).unlink() + if (signature := pathlib.Path(f"{(pool / package.name)}.sig")).exists(): + signature.unlink() + + +async def add_package_to_repo( + package: pathlib.Path, repo: pathlib.Path, pool: pathlib.Path, architecture: str, skip_signature: bool = False +): + if not package.exists(): + package = pool / package.name + + if not package.exists(): + raise PackageError(f"Cannot add package to repo because the package {package} does not exist in pool.") + + if not skip_signature and (signature := pathlib.Path(f"{package}.sig")).exists() is False: + raise PackageError(f"Cannot add package to repo because signature file {signature} is missing") + + if not (repo / architecture).exists(): + (repo / architecture).mkdir(parents=True, exist_ok=True) + + if (repo / architecture / package.name).exists(): + raise PackageError(f"Cannot activate {package} as it's already in repo {repo / architecture / package.name}") + + (repo / architecture / package.name).symlink_to(package) + + if not skip_signature: + (repo / architecture / signature.name).symlink_to(signature) + + +async def remove_package_from_repo( + package: pathlib.Path, repo: pathlib.Path, pool: pathlib.Path, architecture: str, skip_signature: bool = False +): + if not package.exists(): + package = repo / architecture / package.name + + if not package.exists(): + raise PackageError(f"Cannot remove {package} as it's not in repo {repo}") + + package.unlink() + if not skip_signature and (signature := pathlib.Path(f"{package}.sig")).exists(): + signature.unlink()