Current File : /home/inlingua/miniconda3/lib/python3.1/site-packages/conda_anaconda_tos/path.py
# Copyright (C) 2024 Anaconda, Inc
# SPDX-License-Identifier: BSD-3-Clause
"""Low-level path helpers."""

from __future__ import annotations

import hashlib
import os
from functools import cache
from pathlib import Path
from typing import TYPE_CHECKING

from conda.common.compat import on_win
from conda.common.configuration import custom_expandvars
from conda.models.channel import Channel
from platformdirs import user_cache_dir

from . import APP_NAME

if TYPE_CHECKING:
    from collections.abc import Iterable, Iterator
    from datetime import datetime
    from typing import Final

#: Site metadata directory. This is the highest priority location.
SITE_TOS_ROOT: Final = "C:/ProgramData/conda/tos" if on_win else "/etc/conda/tos"

#: System metadata directory. Located in the conda installation.
SYSTEM_TOS_ROOT: Final = "$CONDA_ROOT/conda-meta/tos"

#: User metadata directory. Located in the user home directory.
USER_TOS_ROOT: Final = "~/.conda/tos"

#: Environment metadata directory. Located in the current conda environment.
ENV_TOS_ROOT: Final = "$CONDA_PREFIX/conda-meta/tos"

#: Search path for metadata directories.
SEARCH_PATH: Final = tuple(
    filter(
        None,
        (
            SITE_TOS_ROOT,
            "/var/lib/conda/tos" if not on_win else None,
            SYSTEM_TOS_ROOT,
            "$XDG_CONFIG_HOME/conda/tos",
            "~/.config/conda/tos",
            USER_TOS_ROOT,
            ENV_TOS_ROOT,
            # mirrors $CONDARC
            "$CONDATOS",
        ),
    ),
)

#: Metadata file glob pattern.
TOS_GLOB: Final = "*.json"

#: OS and user specific metadata cache directory.
CACHE_DIR: Final = Path(user_cache_dir(APP_NAME, appauthor=APP_NAME))


@cache
def hash_channel(channel: str | Channel) -> str:
    """Hash the channel to remove problematic characters (e.g. /)."""
    channel = Channel(channel)
    if not channel.base_url:
        raise ValueError(
            "`channel` must have a base URL. "
            "(hint: `conda.models.channel.MultiChannel` cannot be hashed)"
        )

    hasher = hashlib.new("sha256")
    hasher.update(channel.channel_location.encode("utf-8"))
    hasher.update(channel.channel_name.encode("utf-8"))
    return hasher.hexdigest()


def get_path(path: str | os.PathLike[str] | Path) -> Path:
    """Expand environment variables and user home in the path."""
    if isinstance(path, str):
        path = custom_expandvars(path, os.environ)
    elif not isinstance(path, Path):
        raise TypeError("`path` must be a string or `pathlib.Path`.")
    return Path(path).expanduser()


def get_search_path(
    extend_search_path: Iterable[str | os.PathLike[str] | Path] | None = None,
) -> Iterator[Path]:
    """Get all root metadata paths ordered from highest to lowest priority."""
    seen: set[Path] = set()
    for tos_root in (*SEARCH_PATH, *(extend_search_path or ())):
        if (path := get_path(tos_root)).is_dir() and path not in seen:
            yield path
            seen.add(path)


def get_tos_dir(
    tos_root: str | os.PathLike[str] | Path,
    channel: str | Channel,
) -> Path:
    """Get the metadata directory for the given channel."""
    return get_path(tos_root) / hash_channel(channel)


def get_metadata_path(
    tos_root: str | os.PathLike[str] | Path,
    channel: str | Channel,
    version: datetime,
) -> Path:
    """Get the metadata file path for the given channel and version."""
    return get_tos_dir(tos_root, channel) / f"{version.timestamp()}.json"


def get_all_channel_paths(
    extend_search_path: Iterable[str | os.PathLike[str] | Path] | None = None,
) -> Iterator[Path]:
    """Get all local metadata file paths."""
    for path in get_search_path(extend_search_path):
        yield from sorted(get_path(path).glob(f"*/{TOS_GLOB}"))


def get_channel_paths(
    channel: str | Channel,
    *,
    extend_search_path: Iterable[str | os.PathLike[str] | Path] | None = None,
) -> Iterator[Path]:
    """Get all local metadata file paths for the given channel."""
    for path in get_search_path(extend_search_path):
        yield from sorted(get_tos_dir(path, channel).glob(TOS_GLOB))


def get_cache_path(channel: str | Channel) -> Path:
    """Get the metadata cache file path for the given channel."""
    return CACHE_DIR / f"{hash_channel(channel)}.cache"


def get_cache_paths() -> Iterator[Path]:
    """Get all local metadata cache file paths."""
    yield from sorted(CACHE_DIR.glob("*.cache"))