# Copyright 1999-2020 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2

import fcntl

from portage import os
from _emerge.AbstractPollTask import AbstractPollTask


class PipeReader(AbstractPollTask):
    """
    Reads output from one or more files and saves it in memory,
    for retrieval via the getvalue() method. This is driven by
    the scheduler's poll() loop, so it runs entirely within the
    current process.
    """

    __slots__ = ("input_files",) + ("_read_data", "_use_array")

    def _start(self):
        self._read_data = []

        for f in self.input_files.values():
            fd = f if isinstance(f, int) else f.fileno()
            fcntl.fcntl(
                fd, fcntl.F_SETFL, fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK
            )

            if self._use_array:
                self.scheduler.add_reader(fd, self._array_output_handler, f)
            else:
                self.scheduler.add_reader(fd, self._output_handler, fd)

        self._registered = True

    def _cancel(self):
        self._unregister()
        if self.returncode is None:
            self.returncode = self._cancelled_returncode

    def getvalue(self):
        """Retrieve the entire contents"""
        return b"".join(self._read_data)

    def close(self):
        """Free the memory buffer."""
        self._read_data = None

    def _output_handler(self, fd):
        while True:
            data = self._read_buf(fd)
            if data is None:
                break
            if data:
                self._read_data.append(data)
            else:
                self._unregister()
                self.returncode = self.returncode or os.EX_OK
                self._async_wait()
                break

    def _array_output_handler(self, f):
        while True:
            data = self._read_array(f)
            if data is None:
                break
            if data:
                self._read_data.append(data)
            else:
                self._unregister()
                self.returncode = self.returncode or os.EX_OK
                self._async_wait()
                break

        return True

    def _unregister(self):
        """
        Unregister from the scheduler and close open files.
        """

        self._registered = False

        if self.input_files is not None:
            for f in self.input_files.values():
                if isinstance(f, int):
                    self.scheduler.remove_reader(f)
                    os.close(f)
                else:
                    self.scheduler.remove_reader(f.fileno())
                    f.close()
            self.input_files = None
