[Pkg-mpd-commits] [python-mpd] 07/91: Add twisted based async client

Simon McVittie smcv at debian.org
Sat Feb 24 14:55:26 UTC 2018


This is an automated email from the git hooks/post-receive script.

smcv pushed a commit to branch debian/master
in repository python-mpd.

commit a8f1b244432f002724eb15bad6c3c67a7deb35b0
Author: Robert Niederreiter <office at squarewave.at>
Date:   Thu Aug 25 09:23:46 2016 +0200

    Add twisted based async client
---
 mpd/__init__.py |   4 ++
 mpd/twisted.py  | 212 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 216 insertions(+)

diff --git a/mpd/__init__.py b/mpd/__init__.py
index d9a1b53..d95cfcc 100644
--- a/mpd/__init__.py
+++ b/mpd/__init__.py
@@ -27,3 +27,7 @@ from mpd.base import PendingCommandError
 from mpd.base import ProtocolError
 from mpd.base import VERSION
 
+try:
+    from mpd.twisted import MPDProtocol
+except ImportError:
+    pass
diff --git a/mpd/twisted.py b/mpd/twisted.py
new file mode 100644
index 0000000..b2b6df5
--- /dev/null
+++ b/mpd/twisted.py
@@ -0,0 +1,212 @@
+# python-mpd2: Python MPD client library
+#
+# Copyright (C) 2008-2010  J. Alexander Treuman <jat at spatialrift.net>
+# Copyright (C) 2010  Jasper St. Pierre <jstpierre at mecheye.net>
+# Copyright (C) 2010-2011  Oliver Mader <b52 at reaktor42.de>
+# Copyright (C) 2016  Robert Niederreiter <rnix at squarewave.at>
+#
+# python-mpd2 is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# python-mpd2 is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with python-mpd2.  If not, see <http://www.gnu.org/licenses/>.
+#
+# THIS MODULE IS EXPERIMENTAL. AS SOON AS IT IS CONSIDERED STABLE THIS NOTE
+# WILL BE REMOVED. PLEASE REPORT INCONSISTENCIES, BUGS AND IMPROVEMENTS AT
+# https://github.com/Mic92/python-mpd2/issues
+
+from __future__ import absolute_import
+from mpd.base import CommandError
+from mpd.base import CommandListError
+from mpd.base import ERROR_PREFIX
+from mpd.base import HELLO_PREFIX
+from mpd.base import MPDClientBase
+from mpd.base import NEXT
+from mpd.base import SUCCESS
+from mpd.base import logger
+from mpd.base import mpd_command_provider
+from mpd.base import mpd_commands
+from twisted.internet import defer
+from twisted.protocols import basic
+from types import GeneratorType
+
+
+def _create_command(wrapper, name, callback):
+    def mpd_command(self, *args):
+        def bound_callback(lines):
+            return callback(self, lines)
+        return wrapper(self, name, args, bound_callback)
+    return mpd_command
+
+
+ at mpd_command_provider
+class MPDProtocol(basic.LineReceiver, MPDClientBase):
+    delimiter = "\n"
+
+    def __init__(self, default_idle=True, idle_result=None, use_unicode=False):
+        super(MPDProtocol, self).__init__(use_unicode=use_unicode)
+        # flag whether client should idle by default
+        self._default_idle = default_idle
+        self._idle_result = idle_result
+        self._reset()
+
+    def _reset(self):
+        super(MPDProtocol, self)._reset()
+        self.mpd_version = None
+        self._command_list = False
+        self._command_list_results = []
+        self._rcvd_lines = []
+        self._state = []
+        self._idle = False
+
+    @classmethod
+    def add_command(cls, name, callback):
+        # ignore commands which are implemented on class directly 
+        if getattr(cls, name, None) is not None:
+            return
+        # create command and hook it on class
+        func = _create_command(cls._execute, name, callback)
+        escaped_name = name.replace(" ", "_")
+        setattr(cls, escaped_name, func)
+
+    def lineReceived(self, line):
+        line = line.decode('utf-8')
+        command_list = self._state and isinstance(self._state[0], list)
+        state_list = self._state[0] if command_list else self._state
+        if line.startswith(HELLO_PREFIX):
+            self.mpd_version = line[len(HELLO_PREFIX):].strip()
+            # default state idle, enter idle
+            if self._default_idle:
+                self.idle().addCallback(self._dispatch_idle_result)
+        elif line.startswith(ERROR_PREFIX):
+            error = line[len(ERROR_PREFIX):].strip()
+            if command_list:
+                state_list[0].errback(CommandError(error))
+                for state in state_list[1:-1]:
+                    state.errback(
+                        CommandListError("An earlier command failed."))
+                state_list[-1].errback(CommandListError(error))
+                del self._state[0]
+                del self._command_list_results[0]
+            else:
+                state_list.pop(0).errback(CommandError(error))
+            # XXX: reset received lines here? 
+            self._continue_idle()
+        elif line == SUCCESS or (command_list and line == NEXT):
+            state_list.pop(0).callback(self._rcvd_lines[:])
+            self._rcvd_lines = []
+            if command_list and line == SUCCESS:
+                del self._state[0]
+            self._continue_idle()
+        else:
+            self._rcvd_lines.append(line)
+
+    def _execute(self, command, args, parser):
+        # close or kill command in command list not allowed
+        if self._command_list and not callable(parser):
+            msg = "{} not allowed in command list".format(command)
+            raise CommandListError(msg)
+        # default state idle and currently in idle state, trigger noidle
+        if self._default_idle and self._idle and command != "idle":
+            self.noidle().addCallback(self._dispatch_noidle_result)
+        # write command to MPD
+        self._write_command(command, args)
+        # create command related deferred
+        deferred = defer.Deferred()
+        # extend pending result queue
+        state = self._state[-1] if self._command_list else self._state
+        state.append(deferred)
+        # NOOP is for close and kill commands
+        if parser is not self.NOOP:
+            # attach command related result parser
+            deferred.addCallback(parser)
+            # command list, attach handler for collecting command list results
+            if self._command_list:
+                deferred.addCallback(self._parse_command_list_item)
+        return deferred
+
+    def _write_command(self, command, args=[]):
+        parts = [command]
+        parts += ['"{}"'.format(escape(arg.encode('utf-8')) \
+            if isinstance(arg, unicode) else str(arg)) for arg in args]
+        cmd = " ".join(parts)
+        self.sendLine(cmd)
+
+    def _parse_command_list_item(self, result):
+        # TODO: find a better way to do this
+        if type(result) == GeneratorType:
+            result = list(result)
+        self._command_list_results[0].append(result)
+        return result
+
+    def _parse_command_list_end(self, lines):
+        return self._command_list_results.pop(0)
+
+    @mpd_commands(
+        *MPDClientBase._parse_nothing.mpd_commands)
+    def _parse_nothing(self, lines):
+        return None
+
+    def _continue_idle(self):
+        if self._default_idle and not self._idle and not self._state:
+            self.idle().addCallback(self._dispatch_idle_result)
+
+    def _do_dispatch(self, result):
+        if self._idle_result:
+            self._idle_result(result)
+        else:
+            res = list(result)
+            msg = 'MPDProtocol: no idle callback defined: {}'.format(res)
+            logger.warning(msg)
+
+    def _dispatch_noidle_result(self, result):
+        self._do_dispatch(result)
+
+    def _dispatch_idle_result(self, result):
+        self._idle = False
+        self._do_dispatch(result)
+        self._continue_idle()
+
+    def idle(self):
+        if self._idle:
+            raise CommandError("Already in idle state")
+        self._idle = True
+        return self._execute('idle', [], self._parse_list)
+
+    def noidle(self):
+        if not self._idle:
+            raise CommandError("Not in idle state")
+        # delete first pending deferred, idle returns nothing when
+        # noidle gets called
+        self._state.pop(0)
+        self._idle = False
+        return self._execute('noidle', [], self._parse_list)
+
+    def command_list_ok_begin(self):
+        if self._command_list:
+            raise CommandListError("Already in command list")
+        if self._default_idle and self._idle:
+            self.noidle().addCallback(self._dispatch_noidle_result)
+        self._write_command("command_list_ok_begin")
+        self._command_list = True
+        self._command_list_results.append([])
+        self._state.append([])
+
+    def command_list_end(self):
+        if not self._command_list:
+            raise CommandListError("Not in command list")
+        self._write_command("command_list_end")
+        deferred = defer.Deferred()
+        deferred.addCallback(self._parse_command_list_end)
+        self._state[-1].append(deferred)
+        self._command_list = False
+        return deferred
+
+# vim: set expandtab shiftwidth=4 softtabstop=4 textwidth=79:

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-mpd/python-mpd.git



More information about the Pkg-mpd-commits mailing list