Exemplo n.º 1
0
 def f(self, *args):
     result = command_class(name, args, partial(callback, self))
     if self.__run_task is None:
         raise ConnectionError("Can not send command to disconnected client")
     self.__commandqueue.put_nowait(result)
     self._nudge_idle()
     return result
Exemplo n.º 2
0
            def f(self, *args):
                result = command_class(name, args, partial(callback, self))
                if self.__run_task is None:
                    raise ConnectionError(
                        "Can not send command to disconnected client")

                try:
                    self.__command_queue.put_nowait(result)
                except asyncio.QueueFull as e:
                    e.args = (
                        "Command queue overflowing; this indicates the"
                        " application sending commands in an uncontrolled"
                        " fashion without awaiting them, and typically"
                        " indicates a memory leak.", )
                    # While we *could* indicate to the queued result that it has
                    # yet to send its request, that'd practically create a queue of
                    # awaited items in the user application that's growing
                    # unlimitedly, eliminating any chance of timely responses.
                    # Furthermore, the author sees no practical use case that's not
                    # violating MPD's guidance of "Do not manage a client-side copy
                    # of MPD's database". If a use case *does* come up, any change
                    # would need to maintain the property of providing backpressure
                    # information. That would require an API change.
                    raise

                self._end_idle()
                # Careful: There can't be any await points between the queue
                # appending and the write
                try:
                    self._write_command(result._command, result._args)
                except BaseException as e:
                    self.disconnect()
                    result.set_exception(e)
                return result
Exemplo n.º 3
0
    async def connect(self, host, port=6600, loop=None):
        self.__loop = loop

        if "/" in host:
            r, w = await asyncio.open_unix_connection(host, loop=loop)
        else:
            r, w = await asyncio.open_connection(host, port, loop=loop)
        self.__rfile, self.__wfile = r, w

        self.__commandqueue = asyncio.Queue(loop=loop)
        self.__idle_results = asyncio.Queue(
            loop=loop)  #: a queue of CommandResult("idle") futures
        self.__idle_consumers = [
        ]  #: list of (subsystem-list, callbacks) tuples

        try:
            helloline = await asyncio.wait_for(self.__readline(), timeout=5)
        except asyncio.TimeoutError:
            self.disconnect()
            raise ConnectionError(
                "No response from server while reading MPD hello")
        # FIXME should be reusable w/o reaching in
        SyncMPDClient._hello(self, helloline)

        self.__run_task = asyncio.Task(self.__run())
        self.__idle_task = asyncio.Task(self.__distribute_idle_results())
Exemplo n.º 4
0
    async def idle(self, subsystems=()):
        if self.__idle_consumers is None:
            raise ConnectionError(
                "Can not start idle on a disconnected client")

        interests_before = self._get_idle_interests()
        # A queue accepting either a list of things that changed in a single
        # idle cycle, or an exception to be raised
        changes = asyncio.Queue()
        try:
            entry = (subsystems, changes.put_nowait)
            self.__idle_consumers.append(entry)
            if self._get_idle_interests != interests_before:
                # Technically this does not enter idle *immediately* but rather
                # only after any commands after IMMEDIATE_COMMAND_TIMEOUT;
                # practically that should be a good thing.
                self._end_idle()
            while True:
                item = await changes.get()
                if isinstance(item, Exception):
                    raise item
                yield item
        finally:
            if self.__idle_consumers is not None:
                self.__idle_consumers.remove(entry)
Exemplo n.º 5
0
 async def _read_line(self):
     line = await self.__readline()
     if not line.endswith("\n"):
         raise ConnectionError("Connection lost while reading line")
     line = line.rstrip("\n")
     if line.startswith(ERROR_PREFIX):
         error = line[len(ERROR_PREFIX):].strip()
         raise CommandError(error)
     if line == SUCCESS:
         return None
     return line
Exemplo n.º 6
0
 def disconnect(self):
     if (self.__run_task is not None
         ):  # is None eg. when connection fails in .connect()
         self.__run_task.cancel()
     if self.__wfile is not None:
         self.__wfile.close()
     self.__rfile = self.__wfile = None
     self.__run_task = None
     self.__command_queue = None
     if self.__idle_consumers is not None:
         # copying the list as each raising callback will remove itself from __idle_consumers
         for subsystems, callback in list(self.__idle_consumers):
             callback(ConnectionError())
     self.__idle_consumers = None
Exemplo n.º 7
0
    async def connect(self, host, port=6600, loop=None):
        if "/" in host:
            r, w = await asyncio.open_unix_connection(host, loop=loop)
        else:
            r, w = await asyncio.open_connection(host, port, loop=loop)
        self.__rfile, self.__wfile = r, w

        self.__command_queue = asyncio.Queue(maxsize=self.COMMAND_QUEUE_LENGTH)
        self.__idle_consumers = []  #: list of (subsystem-list, callbacks) tuples

        try:
            helloline = await asyncio.wait_for(self.__readline(), timeout=5)
        except asyncio.TimeoutError:
            self.disconnect()
            raise ConnectionError("No response from server while reading MPD hello")
        # FIXME should be reusable w/o reaching in
        SyncMPDClient._hello(self, helloline)

        self.__run_task = asyncio.Task(self.__run())
Exemplo n.º 8
0
    async def idle(self, subsystems=()):
        if self.__idle_consumers is None:
            raise ConnectionError(
                "Can not start idle on a disconnected client")

        interests_before = self._get_idle_interests()
        changes = asyncio.Queue()
        try:
            entry = (subsystems, changes.put_nowait)
            self.__idle_consumers.append(entry)
            if self._get_idle_interests != interests_before:
                # Technically this does not enter idle *immediately* but rather
                # only after any commands after IMMEDIATE_COMMAND_TIMEOUT;
                # practically that should be a good thing.
                self._end_idle()
            while True:
                yield await changes.get()
        finally:
            if self.__idle_consumers is not None:
                self.__idle_consumers.remove(entry)
Exemplo n.º 9
0
    async def _read_binary(self):
        obj = {}

        while True:
            line = await self._read_line()
            if line is None:
                break

            key, value = self._parse_pair(line, ": ")

            if key == "binary":
                chunk_size = int(value)
                value = await self._read_chunk(chunk_size)

                if await self.__rfile.readexactly(1) != b"\n":
                    # newline after binary content
                    self.disconnect()
                    raise ConnectionError("Connection lost while reading line")

            obj[key] = value
        return obj
Exemplo n.º 10
0
 async def _read_chunk(self, length):
     try:
         return await self.__rfile.readexactly(length)
     except asyncio.IncompleteReadError:
         raise ConnectionError("Connection lost while reading binary")