# Copyright 2012-2023 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2

import threading

from portage import os
from _emerge.AbstractPollTask import AbstractPollTask


class PipeReaderBlockingIO(AbstractPollTask):
    """
    Reads output from one or more files and saves it in memory, for
    retrieval via the getvalue() method. This is driven by a thread
    for each input file, in order to support blocking IO.  This is
    historically useful for using threads to handle blocking IO with
    Jython, since Jython lacks the fcntl module which is needed for
    non-blocking IO (see http://bugs.jython.org/issue1074).

    Portage does not currently support Jython, but re-introducing
    support in The Future (TM) may be possible.
    """

    __slots__ = ("input_files", "_read_data", "_terminate", "_threads", "_thread_rlock")

    def _start(self):
        self._terminate = threading.Event()
        self._threads = {}
        self._read_data = []

        self._registered = True
        self._thread_rlock = threading.RLock()
        with self._thread_rlock:
            for f in self.input_files.values():
                t = threading.Thread(target=self._reader_thread, args=(f,))
                t.daemon = True
                t.start()
                self._threads[f] = t

    def _reader_thread(self, f):
        terminated = self._terminate.is_set
        bufsize = self._bufsize
        while not terminated():
            buf = f.read(bufsize)
            with self._thread_rlock:
                if terminated():
                    break
                elif buf:
                    self._read_data.append(buf)
                else:
                    del self._threads[f]
                    if not self._threads:
                        # Thread-safe callback to EventLoop
                        self.scheduler.call_soon_threadsafe(self._eof)
                    break
        f.close()

    def _eof(self):
        self._registered = False
        if self.returncode is None:
            self.returncode = os.EX_OK
        self._async_wait()

    def _cancel(self):
        self._terminate.set()
        self._registered = False
        if self.returncode is None:
            self.returncode = self._cancelled_returncode
        self._async_wait()

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

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