# Copyright 2013 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2

from portage import os
from portage.checksum import perform_multiple_checksums
from portage.util._async.ForkProcess import ForkProcess
from _emerge.PipeReader import PipeReader


class FileDigester(ForkProcess):
    """
    Asynchronously generate file digests. Pass in file_path and
    hash_names, and after successful execution, the digests
    attribute will be a dict containing all of the requested
    digests.
    """

    __slots__ = (
        "file_path",
        "digests",
        "hash_names",
        "_digest_pipe_reader",
        "_digest_pw",
    )

    def _start(self):
        pr, pw = os.pipe()
        self.fd_pipes = {}
        self.fd_pipes[pw] = pw
        self._digest_pw = pw
        self._digest_pipe_reader = PipeReader(
            input_files={"input": pr}, scheduler=self.scheduler
        )
        self._digest_pipe_reader.addExitListener(self._digest_pipe_reader_exit)
        self._digest_pipe_reader.start()
        ForkProcess._start(self)
        os.close(pw)

    def _run(self):
        digests = perform_multiple_checksums(self.file_path, hashes=self.hash_names)

        buf = "".join("%s=%s\n" % item for item in digests.items()).encode("utf_8")

        while buf:
            buf = buf[os.write(self._digest_pw, buf) :]

        return os.EX_OK

    def _parse_digests(self, data):
        digests = {}
        for line in data.decode("utf_8").splitlines():
            parts = line.split("=", 1)
            if len(parts) == 2:
                digests[parts[0]] = parts[1]

        self.digests = digests

    def _async_waitpid(self):
        # Ignore this event, since we want to ensure that we
        # exit only after _digest_pipe_reader has reached EOF.
        if self._digest_pipe_reader is None:
            ForkProcess._async_waitpid(self)

    def _digest_pipe_reader_exit(self, pipe_reader):
        self._parse_digests(pipe_reader.getvalue())
        self._digest_pipe_reader = None
        if self.pid is None:
            self._unregister()
            self._async_wait()
        else:
            self._async_waitpid()

    def _unregister(self):
        ForkProcess._unregister(self)

        pipe_reader = self._digest_pipe_reader
        if pipe_reader is not None:
            self._digest_pipe_reader = None
            pipe_reader.removeExitListener(self._digest_pipe_reader_exit)
            pipe_reader.cancel()
