Current File : /home/inlingua/miniconda3/lib/python3.1/site-packages/conda/gateways/repodata/jlap/interface.py
# Copyright (C) 2012 Anaconda, Inc
# SPDX-License-Identifier: BSD-3-Clause
"""JLAP interface for repodata."""

from __future__ import annotations

import logging
import os
from typing import TYPE_CHECKING

from ....base.context import context
from ...connection.download import disable_ssl_verify_warning
from ...connection.session import get_session
from .. import (
    CACHE_CONTROL_KEY,
    ETAG_KEY,
    LAST_MODIFIED_KEY,
    URL_KEY,
    RepodataOnDisk,
    RepodataState,
    RepoInterface,
    Response304ContentUnchanged,
    conda_http_errors,
)
from . import fetch

if TYPE_CHECKING:
    from .. import RepodataCache

log = logging.getLogger(__name__)


class JlapRepoInterface(RepoInterface):
    def __init__(
        self,
        url: str,
        repodata_fn: str | None,
        *,
        cache: RepodataCache,
        **kwargs,
    ) -> None:
        log.debug("Using %s", self.__class__.__name__)

        self._cache = cache

        self._url = url
        self._repodata_fn = repodata_fn

        self._log = logging.getLogger(__name__)
        self._stderrlog = logging.getLogger("conda.stderrlog")

    def repodata(self, state: dict | RepodataState) -> str | None:
        """
        Fetch newest repodata if necessary.

        Always writes to ``cache_path_json``.
        """
        self.repodata_parsed(state)
        raise RepodataOnDisk()

    def repodata_parsed(self, state: dict | RepodataState) -> dict | None:
        """
        JLAP has to parse the JSON anyway.

        Use this to avoid a redundant parse when repodata is updated.

        When repodata is not updated, it doesn't matter whether this function or
        the caller reads from a file.
        """
        session = get_session(self._url)

        if not context.ssl_verify:
            disable_ssl_verify_warning()

        repodata_url = f"{self._url}/{self._repodata_fn}"

        # XXX won't modify caller's state dict
        state_ = self._repodata_state_copy(state)

        # at this point, self._cache.state == state == state_

        temp_path = (
            self._cache.cache_dir / f"{self._cache.name}.{os.urandom(2).hex()}.tmp"
        )
        try:
            with conda_http_errors(self._url, self._repodata_fn):
                repodata_json_or_none = fetch.request_url_jlap_state(
                    repodata_url,
                    state_,
                    session=session,
                    cache=self._cache,
                    temp_path=temp_path,
                )

                # update caller's state dict-or-RepodataState. Do this before
                # the self._cache.replace() call which also writes state, then
                # signal not to write state to caller.
                state.update(state_)

                state[URL_KEY] = self._url
                headers = state.get("jlap", {}).get(
                    "headers"
                )  # XXX overwrite headers in jlapper.request_url_jlap_state
                if headers:
                    state[ETAG_KEY] = headers.get("etag")
                    state[LAST_MODIFIED_KEY] = headers.get("last-modified")
                    state[CACHE_CONTROL_KEY] = headers.get("cache-control")

                self._cache.state.update(state)

            if temp_path.exists():
                self._cache.replace(temp_path)
        except fetch.Jlap304NotModified:
            raise Response304ContentUnchanged()
        finally:
            # Clean up the temporary file. In the successful case it raises
            # OSError as self._cache_replace() removed temp_file.
            try:
                temp_path.unlink()
            except OSError:
                pass

        if repodata_json_or_none is None:  # common
            # Indicate that subdir_data mustn't rewrite cache_path_json
            raise RepodataOnDisk()
        else:
            return repodata_json_or_none

    def _repodata_state_copy(self, state: dict | RepodataState):
        return RepodataState(dict=state)


class RepodataStateSkipFormat(RepodataState):
    skip_formats: set[str]

    def __init__(self, *args, skip_formats=set(), **kwargs):
        super().__init__(*args, **kwargs)
        self.skip_formats = set(skip_formats)

    def should_check_format(self, format):
        if format in self.skip_formats:
            return False
        return super().should_check_format(format)


class ZstdRepoInterface(JlapRepoInterface):
    """
    Support repodata.json.zst (if available) without checking .jlap
    """

    def _repodata_state_copy(self, state: dict | RepodataState):
        return RepodataStateSkipFormat(dict=state, skip_formats=["jlap"])