mirror of
https://github.com/beetbox/beets.git
synced 2025-12-16 05:34:47 +01:00
changed location of commands to Server object; implemented a few commands
--HG-- extra : convert_revision : svn%3A41726ec3-264d-0410-9c23-a9f1637257cc/trunk%4093
This commit is contained in:
parent
45a777fc46
commit
2cf92e0541
1 changed files with 87 additions and 27 deletions
114
beets/bpd.py
114
beets/bpd.py
|
|
@ -8,11 +8,14 @@ use of the wide range of MPD clients.
|
|||
import eventlet.api
|
||||
import re
|
||||
from string import Template
|
||||
from beets import Library
|
||||
import sys
|
||||
|
||||
|
||||
|
||||
DEFAULT_PORT = 6600
|
||||
PROTOCOL_VERSION = '0.12.2'
|
||||
BUFSIZE = 1024
|
||||
|
||||
HELLO = 'OK MPD %s' % PROTOCOL_VERSION
|
||||
CLIST_BEGIN = 'command_list_begin'
|
||||
|
|
@ -39,8 +42,21 @@ ERROR_EXIST = 56
|
|||
|
||||
|
||||
|
||||
def debug(msg):
|
||||
print >>sys.stderr, msg
|
||||
|
||||
|
||||
|
||||
class Server(object):
|
||||
"""A MPD-compatible music player server.
|
||||
|
||||
The functions with the `cmd_` prefix are invoked in response to
|
||||
client commands. For instance, if the client says `status`,
|
||||
`cmd_status` will be invoked. The arguments to the client's commands
|
||||
are used as function arguments (*args). The functions should return
|
||||
a `Response` object.
|
||||
|
||||
This is a generic superclass and doesn't support many commands.
|
||||
"""
|
||||
|
||||
def __init__(self, host, port=DEFAULT_PORT):
|
||||
|
|
@ -59,17 +75,45 @@ class Server(object):
|
|||
sock, address = self.listener.accept()
|
||||
except KeyboardInterrupt:
|
||||
break # ^C ends the server.
|
||||
eventlet.api.spawn(Connection.handle, sock)
|
||||
eventlet.api.spawn(Connection.handle, sock, self)
|
||||
|
||||
def cmd_ping(self):
|
||||
return SuccessResponse()
|
||||
|
||||
def cmd_commands(self):
|
||||
"""Just lists the commands available to the user. For the time
|
||||
being, lists all commands because no authentication is present.
|
||||
"""
|
||||
out = []
|
||||
for key in dir(self):
|
||||
if key.startswith('cmd_'):
|
||||
out.append('command: ' + key[4:])
|
||||
return SuccessResponse(out)
|
||||
|
||||
def cmd_notcommands(self):
|
||||
"""Lists all unavailable commands. Because there's currently no
|
||||
authentication, returns no commands.
|
||||
"""
|
||||
return SuccessResponse()
|
||||
|
||||
class BGServer(Server):
|
||||
"""A `Server` using GStreamer to play audio and beets to store its
|
||||
library.
|
||||
"""
|
||||
|
||||
def __init__(self, host, port=DEFAULT_PORT, libpath='library.blb'):
|
||||
super(BGServer, self).__init__(host, port)
|
||||
self.library = Library(libpath)
|
||||
|
||||
class Connection(object):
|
||||
"""A connection between a client and the server. Handles input and
|
||||
output from and to the client.
|
||||
"""
|
||||
|
||||
def __init__(self, client):
|
||||
def __init__(self, client, server):
|
||||
"""Create a new connection for the accepted socket `client`.
|
||||
"""
|
||||
self.fp = client.makefile()
|
||||
self.client, self.server = client, server
|
||||
|
||||
def send(self, data):
|
||||
"""Send data, which is either a string or an iterable
|
||||
|
|
@ -82,7 +126,27 @@ class Connection(object):
|
|||
lines = data
|
||||
|
||||
for line in lines:
|
||||
self.fp.write(line + NEWLINE)
|
||||
debug(line)
|
||||
self.client.sendall(line + NEWLINE)
|
||||
|
||||
line_re = re.compile(r'([^\r\n]*)(?:\r\n|\n\r|\n|\r)')
|
||||
def lines(self):
|
||||
"""A generator yielding lines (delimited by some usual newline
|
||||
code) as they arrive from the client.
|
||||
"""
|
||||
buf = ''
|
||||
while True:
|
||||
# Dump new data on the buffer.
|
||||
chunk = self.client.recv(BUFSIZE)
|
||||
if not chunk: break # EOF.
|
||||
buf += chunk
|
||||
|
||||
# Clear out and yield any lines in the buffer.
|
||||
while True:
|
||||
match = self.line_re.match(buf)
|
||||
if not match: break # No lines remain.
|
||||
yield match.group(1)
|
||||
buf = buf[match.end():] # Remove line from buffer.
|
||||
|
||||
def run(self):
|
||||
"""Send a greeting to the client and begin processing commands
|
||||
|
|
@ -91,41 +155,36 @@ class Connection(object):
|
|||
self.send(HELLO)
|
||||
|
||||
clist = None # Initially, no command list is being constructed.
|
||||
for line in self.fp:
|
||||
line = line.rstrip()
|
||||
|
||||
for line in self.lines():
|
||||
debug(line)
|
||||
|
||||
if clist is not None:
|
||||
# Command list already opened.
|
||||
if line == CLIST_END:
|
||||
self.send(clist.run())
|
||||
self.send(clist.run(self.server))
|
||||
clist = None
|
||||
else:
|
||||
clist.append(Command(line))
|
||||
|
||||
elif line == CLIST_BEGIN or line == CLIST_VERBOSE_BEGIN:
|
||||
# Begin a command list.
|
||||
clist = CommandList(line == CLIST_VERBOSE_BEGIN)
|
||||
clist = CommandList([], line == CLIST_VERBOSE_BEGIN)
|
||||
|
||||
else:
|
||||
# Ordinary command.
|
||||
self.send(Command(line).run())
|
||||
self.send(Command(line).run(self.server))
|
||||
|
||||
@classmethod
|
||||
def handle(cls, client):
|
||||
"""Creates a new `Connection` for `client` and runs it.
|
||||
def handle(cls, client, server):
|
||||
"""Creates a new `Connection` for `client` and `server` and runs
|
||||
it.
|
||||
"""
|
||||
cls(client).run()
|
||||
cls(client, server).run()
|
||||
|
||||
|
||||
|
||||
class Command(object):
|
||||
"""A command issued by the client for processing by the server.
|
||||
|
||||
The functions with the `cmd_` prefix are invoked in response to
|
||||
client commands. For instance, if the client says `status`,
|
||||
`cmd_status` will be invoked. The arguments to the client's commands
|
||||
are used as function arguments (*args). The functions should return
|
||||
a `Response` object.
|
||||
"""
|
||||
|
||||
command_re = re.compile(r'^([^ \t]+)[ \t]*')
|
||||
|
|
@ -140,12 +199,13 @@ class Command(object):
|
|||
arg_matches = self.arg_re.findall(s[command_match.end():])
|
||||
self.args = [m[0] or m[1] for m in arg_matches]
|
||||
|
||||
def run(self):
|
||||
"""Executes the command, returning a `Response` object.
|
||||
def run(self, server):
|
||||
"""Executes the command on the given `Sever`, returning a
|
||||
`Response` object.
|
||||
"""
|
||||
func_name = 'cmd_' + self.name
|
||||
if hasattr(self, func_name):
|
||||
return getattr(self, func_name)(*self.args)
|
||||
if hasattr(server, func_name):
|
||||
return getattr(server, func_name)(*self.args)
|
||||
else:
|
||||
return ErrorResponse(ERROR_UNKNOWN, self.name, 'unknown command')
|
||||
|
||||
|
|
@ -163,19 +223,19 @@ class CommandList(list):
|
|||
self.append(item)
|
||||
self.verbose = verbose
|
||||
|
||||
def run(self):
|
||||
def run(self, server):
|
||||
"""Execute all the commands in this list, returning a list of
|
||||
strings to be sent as a response.
|
||||
"""
|
||||
out = []
|
||||
|
||||
for i, command in enumerate(self):
|
||||
resp = command.run()
|
||||
resp = command.run(server)
|
||||
out.extend(resp.items)
|
||||
|
||||
# If the command failed, stop executing and send the completion
|
||||
# code for this command.
|
||||
if isinstance(self, ErrorResponse):
|
||||
if isinstance(resp, ErrorResponse):
|
||||
resp.index = i # Give the error the correct index.
|
||||
break
|
||||
|
||||
|
|
@ -240,4 +300,4 @@ class SuccessResponse(Response):
|
|||
|
||||
|
||||
if __name__ == '__main__':
|
||||
Server('0.0.0.0').run()
|
||||
BGServer('0.0.0.0', 6600, 'library.blb').run()
|
||||
Loading…
Reference in a new issue