diff --git a/picard/util/pipe.py b/picard/util/pipe.py index 7f3e1dce7..7a95a588d 100644 --- a/picard/util/pipe.py +++ b/picard/util/pipe.py @@ -203,6 +203,10 @@ class Pipe: __pool = concurrent.futures.ThreadPoolExecutor() + # we're sending only filepaths, so it's safe to append newline + # this newline helps with handling collisions of messages + message += "\n" + if self.__is_win: sender = __pool.submit(self.__win_sender, message) else: @@ -217,7 +221,7 @@ class Pipe: return False - def read_from_pipe(self, timeout_secs: Optional[float] = None) -> str: + def read_from_pipe(self, timeout_secs: Optional[float] = None) -> list[str]: if timeout_secs is None: timeout_secs = self.TIMEOUT_SECS @@ -228,16 +232,27 @@ class Pipe: else: reader = __pool.submit(self.__unix_reader) + out = [] + try: - if reader.result(timeout=timeout_secs): - res: str = reader.result() - if res != self.MESSAGE_TO_IGNORE: - return res + res = reader.result(timeout=timeout_secs) + if res: + res = res.split("\n") + for r in res: + if res == self.MESSAGE_TO_IGNORE: + out = [] + break + elif r: + out.append(r) + except concurrent.futures._base.TimeoutError: # hacky way to kill the file-opening loop self.send_to_pipe(self.MESSAGE_TO_IGNORE) - return Pipe.NO_RESPONSE_MESSAGE + if out: + return out + + return [Pipe.NO_RESPONSE_MESSAGE] def __win_reader(self) -> str: response = "" # type: ignore diff --git a/test/test_util_pipe.py b/test/test_util_pipe.py index 4cc995c84..c07bc123f 100644 --- a/test/test_util_pipe.py +++ b/test/test_util_pipe.py @@ -18,6 +18,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +import concurrent.futures from test.picardtestcase import PicardTestCase @@ -28,6 +29,26 @@ from picard import ( from picard.util import pipe +def pipe_listener(pipe_handler, end_of_sequence): + received = [] + messages = [] + while end_of_sequence not in messages: + messages = pipe_handler.read_from_pipe() + for message in messages: + if message not in (pipe.Pipe.MESSAGE_TO_IGNORE, pipe.Pipe.NO_RESPONSE_MESSAGE, "", end_of_sequence): + received.append(message) + + return received + + +def pipe_writer(pipe_handler, to_send, end_of_sequence): + for m in to_send: + while not pipe_handler.send_to_pipe(m): + pass + while not pipe_handler.send_to_pipe(end_of_sequence): + pass + + class TestPipe(PicardTestCase): def test_invalid_args(self): @@ -44,6 +65,16 @@ class TestPipe(PicardTestCase): pass def test_pipe_protocol(self): - # TODO concurrent.futures like in util/pipe.py, one with listener, one with sender - # test if the data is sent correctly - pass + END_OF_SEQUENCE = "stop" + to_send = [["it", "tests", "picard", "pipe"], + ["test", "number", "two"], + ["my_music_file.mp3"]] + + pipe_listener_handler = pipe.Pipe(PICARD_APP_NAME, PICARD_FANCY_VERSION_STR) + pipe_writer_handler = pipe.Pipe(PICARD_APP_NAME, PICARD_FANCY_VERSION_STR) + + for i in range(len(to_send)): + __pool = concurrent.futures.ThreadPoolExecutor() + plistener = __pool.submit(pipe_listener, pipe_listener_handler, END_OF_SEQUENCE) + __pool.submit(pipe_writer, pipe_writer_handler, to_send[i], END_OF_SEQUENCE) + self.assertEqual(plistener.result(), to_send[i])