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
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
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())
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)
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
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
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())
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)
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
async def _read_chunk(self, length): try: return await self.__rfile.readexactly(length) except asyncio.IncompleteReadError: raise ConnectionError("Connection lost while reading binary")