mirror of
https://github.com/beetbox/beets.git
synced 2025-12-06 16:42:42 +01:00
back to functions, conn.send everywhere
--HG-- extra : convert_revision : svn%3A41726ec3-264d-0410-9c23-a9f1637257cc/trunk%40175
This commit is contained in:
parent
efddffb094
commit
2a4153a411
1 changed files with 70 additions and 84 deletions
|
|
@ -164,13 +164,12 @@ def path_to_list(path):
|
||||||
class BaseServer(object):
|
class BaseServer(object):
|
||||||
"""A MPD-compatible music player server.
|
"""A MPD-compatible music player server.
|
||||||
|
|
||||||
The generators with the `cmd_` prefix are invoked in response to
|
The functions with the `cmd_` prefix are invoked in response to
|
||||||
client commands. For instance, if the client says `status`,
|
client commands. For instance, if the client says `status`,
|
||||||
`cmd_status` will be invoked. The arguments to the client's commands
|
`cmd_status` will be invoked. The arguments to the client's commands
|
||||||
are used as function arguments following the connection issuing the
|
are used as function arguments following the connection issuing the
|
||||||
command. The generators should yield strings or sequences of strings
|
command. The functions may send data on the connection. They may
|
||||||
as many times as necessary. They may also raise BPDError exceptions
|
also raise BPDError exceptions to report errors.
|
||||||
to report errors.
|
|
||||||
|
|
||||||
This is a generic superclass and doesn't support many commands.
|
This is a generic superclass and doesn't support many commands.
|
||||||
"""
|
"""
|
||||||
|
|
@ -254,13 +253,13 @@ class BaseServer(object):
|
||||||
if self.password and not conn.authenticated:
|
if self.password and not conn.authenticated:
|
||||||
# Not authenticated. Show limited list of commands.
|
# Not authenticated. Show limited list of commands.
|
||||||
for cmd in SAFE_COMMANDS:
|
for cmd in SAFE_COMMANDS:
|
||||||
yield 'command: ' + cmd
|
conn.send('command: ' + cmd)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Authenticated. Show all commands.
|
# Authenticated. Show all commands.
|
||||||
for func in dir(self):
|
for func in dir(self):
|
||||||
if func.startswith('cmd_'):
|
if func.startswith('cmd_'):
|
||||||
yield 'command: ' + func[4:]
|
conn.send('command: ' + func[4:])
|
||||||
|
|
||||||
def cmd_notcommands(self, conn):
|
def cmd_notcommands(self, conn):
|
||||||
"""Lists all unavailable commands."""
|
"""Lists all unavailable commands."""
|
||||||
|
|
@ -270,7 +269,7 @@ class BaseServer(object):
|
||||||
if func.startswith('cmd_'):
|
if func.startswith('cmd_'):
|
||||||
cmd = func[4:]
|
cmd = func[4:]
|
||||||
if cmd not in SAFE_COMMANDS:
|
if cmd not in SAFE_COMMANDS:
|
||||||
yield 'command: ' + cmd
|
conn.send('command: ' + cmd)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Authenticated. No commands are unavailable.
|
# Authenticated. No commands are unavailable.
|
||||||
|
|
@ -283,7 +282,7 @@ class BaseServer(object):
|
||||||
Gives a list of response-lines for: volume, repeat, random,
|
Gives a list of response-lines for: volume, repeat, random,
|
||||||
playlist, playlistlength, and xfade.
|
playlist, playlistlength, and xfade.
|
||||||
"""
|
"""
|
||||||
yield ('volume: ' + str(self.volume),
|
conn.send('volume: ' + str(self.volume),
|
||||||
'repeat: ' + str(int(self.repeat)),
|
'repeat: ' + str(int(self.repeat)),
|
||||||
'random: ' + str(int(self.random)),
|
'random: ' + str(int(self.random)),
|
||||||
'playlist: ' + str(self.playlist_version),
|
'playlist: ' + str(self.playlist_version),
|
||||||
|
|
@ -297,15 +296,15 @@ class BaseServer(object):
|
||||||
state = 'pause'
|
state = 'pause'
|
||||||
else:
|
else:
|
||||||
state = 'play'
|
state = 'play'
|
||||||
yield 'state: ' + state
|
conn.send('state: ' + state)
|
||||||
|
|
||||||
if self.current_index != -1: # i.e., paused or playing
|
if self.current_index != -1: # i.e., paused or playing
|
||||||
current_id = self._item_id(self.playlist[self.current_index])
|
current_id = self._item_id(self.playlist[self.current_index])
|
||||||
yield 'song: ' + str(self.current_index)
|
conn.send('song: ' + str(self.current_index))
|
||||||
yield 'songid: ' + str(current_id)
|
conn.send('songid: ' + str(current_id))
|
||||||
|
|
||||||
if self.error:
|
if self.error:
|
||||||
yield 'error: ' + self.error
|
conn.send('error: ' + self.error)
|
||||||
|
|
||||||
def cmd_clearerror(self, conn):
|
def cmd_clearerror(self, conn):
|
||||||
"""Removes the persistent error state of the server. This
|
"""Removes the persistent error state of the server. This
|
||||||
|
|
@ -370,7 +369,7 @@ class BaseServer(object):
|
||||||
raise ArgumentIndexError()
|
raise ArgumentIndexError()
|
||||||
def cmd_moveid(self, conn, id_from, idx_to):
|
def cmd_moveid(self, conn, id_from, idx_to):
|
||||||
idx_from = self._id_to_index(idx_from)
|
idx_from = self._id_to_index(idx_from)
|
||||||
for l in self.cmd_move(conn, idx_from, idx_to): yield l
|
self.cmd_move(conn, idx_from, idx_to)
|
||||||
|
|
||||||
def cmd_swap(self, conn, i, j):
|
def cmd_swap(self, conn, i, j):
|
||||||
"""Swaps two tracks in the playlist."""
|
"""Swaps two tracks in the playlist."""
|
||||||
|
|
@ -386,7 +385,7 @@ class BaseServer(object):
|
||||||
def cmd_swapid(self, conn, i_id, j_id):
|
def cmd_swapid(self, conn, i_id, j_id):
|
||||||
i = self._id_to_index(i_id)
|
i = self._id_to_index(i_id)
|
||||||
j = self._id_to_index(j_id)
|
j = self._id_to_index(j_id)
|
||||||
for l in self.cmd_swap(conn, i, j): yield l
|
self.cmd_swap(conn, i, j)
|
||||||
|
|
||||||
def cmd_urlhandlers(self, conn):
|
def cmd_urlhandlers(self, conn):
|
||||||
"""Indicates supported URL schemes. None by default."""
|
"""Indicates supported URL schemes. None by default."""
|
||||||
|
|
@ -399,42 +398,41 @@ class BaseServer(object):
|
||||||
index = cast_arg(int, index)
|
index = cast_arg(int, index)
|
||||||
if index == -1:
|
if index == -1:
|
||||||
for track in self.playlist:
|
for track in self.playlist:
|
||||||
yield self._item_info(track)
|
conn.send(*self._item_info(track))
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
track = self.playlist[index]
|
track = self.playlist[index]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
raise ArgumentIndexError()
|
raise ArgumentIndexError()
|
||||||
yield self._item_info(track)
|
conn.send(*self._item_info(track))
|
||||||
def cmd_playlistid(self, conn, track_id=-1):
|
def cmd_playlistid(self, conn, track_id=-1):
|
||||||
for l in self.cmd_playlistinfo(conn, self._id_to_index(track_id)):
|
self.cmd_playlistinfo(conn, self._id_to_index(track_id))
|
||||||
yield l
|
|
||||||
|
|
||||||
def cmd_plchanges(self, conn, version):
|
def cmd_plchanges(self, conn, version):
|
||||||
"""Yields playlist changes since the given version.
|
"""Sends playlist changes since the given version.
|
||||||
|
|
||||||
This is a "fake" implementation that ignores the version and
|
This is a "fake" implementation that ignores the version and
|
||||||
just returns the entire playlist (rather like version=0). This
|
just returns the entire playlist (rather like version=0). This
|
||||||
seems to satisfy many clients.
|
seems to satisfy many clients.
|
||||||
"""
|
"""
|
||||||
for l in self.cmd_playlistinfo(conn): yield l
|
self.cmd_playlistinfo(conn)
|
||||||
|
|
||||||
def cmd_plchangesposid(self, conn, version):
|
def cmd_plchangesposid(self, conn, version):
|
||||||
"""Like plchanges, but only yields position and id.
|
"""Like plchanges, but only sends position and id.
|
||||||
|
|
||||||
Also a dummy implementation.
|
Also a dummy implementation.
|
||||||
"""
|
"""
|
||||||
for idx, track in enumerate(self.playlist):
|
for idx, track in enumerate(self.playlist):
|
||||||
yield ('Pos: ' + str(idx),
|
conn.send('Pos: ' + str(idx),
|
||||||
'Id: ' + str(track.id),
|
'Id: ' + str(track.id),
|
||||||
)
|
)
|
||||||
|
|
||||||
def cmd_currentsong(self, conn):
|
def cmd_currentsong(self, conn):
|
||||||
"""Yields information about the currently-playing song.
|
"""Sends information about the currently-playing song.
|
||||||
"""
|
"""
|
||||||
if self.current_index != -1: # -1 means stopped.
|
if self.current_index != -1: # -1 means stopped.
|
||||||
track = self.playlist[self.current_index]
|
track = self.playlist[self.current_index]
|
||||||
yield self._item_info(track)
|
conn.send(*self._item_info(track))
|
||||||
|
|
||||||
def cmd_next(self, conn):
|
def cmd_next(self, conn):
|
||||||
"""Advance to the next song in the playlist."""
|
"""Advance to the next song in the playlist."""
|
||||||
|
|
@ -500,7 +498,7 @@ class BaseServer(object):
|
||||||
self.current_index = index
|
self.current_index = index
|
||||||
def cmd_seekid(self, track_id, pos):
|
def cmd_seekid(self, track_id, pos):
|
||||||
index = self._id_to_index(track_id)
|
index = self._id_to_index(track_id)
|
||||||
for l in self.cmd_seek(conn, index, pos): yield l
|
self.cmd_seek(conn, index, pos)
|
||||||
|
|
||||||
class Connection(object):
|
class Connection(object):
|
||||||
"""A connection between a client and the server. Handles input and
|
"""A connection between a client and the server. Handles input and
|
||||||
|
|
@ -513,20 +511,12 @@ class Connection(object):
|
||||||
self.client, self.server = client, server
|
self.client, self.server = client, server
|
||||||
self.authenticated = False
|
self.authenticated = False
|
||||||
|
|
||||||
def send(self, data=None):
|
def send(self, *lines):
|
||||||
"""Send data, which is either a string or an iterable
|
"""Send lines, which are strings, to the client. A newline is
|
||||||
consisting of strings, to the client. A newline is added after
|
added after every string. `data` may be None, in which case
|
||||||
every string. `data` may be None, in which case nothing is
|
nothing is sent.
|
||||||
sent.
|
|
||||||
"""
|
"""
|
||||||
if data is None:
|
out = NEWLINE.join(lines) + NEWLINE
|
||||||
return
|
|
||||||
|
|
||||||
if isinstance(data, basestring): # Passed a single string.
|
|
||||||
out = data + NEWLINE
|
|
||||||
else: # Passed an iterable of strings (for instance, a Response).
|
|
||||||
out = NEWLINE.join(data) + NEWLINE
|
|
||||||
|
|
||||||
log.debug(out[:-1]) # Don't log trailing newline.
|
log.debug(out[:-1]) # Don't log trailing newline.
|
||||||
self.client.sendall(out.encode('utf-8'))
|
self.client.sendall(out.encode('utf-8'))
|
||||||
|
|
||||||
|
|
@ -632,11 +622,7 @@ class Command(object):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
args = [conn] + self.args
|
args = [conn] + self.args
|
||||||
responses = func(*args)
|
func(*args)
|
||||||
if responses is not None:
|
|
||||||
# Yielding nothing is considered success.
|
|
||||||
for response in responses:
|
|
||||||
conn.send(response)
|
|
||||||
|
|
||||||
except BPDError, e:
|
except BPDError, e:
|
||||||
# An exposed error. Set the command name and then let
|
# An exposed error. Set the command name and then let
|
||||||
|
|
@ -766,22 +752,22 @@ class Server(BaseServer):
|
||||||
return artist, album, track
|
return artist, album, track
|
||||||
|
|
||||||
def cmd_lsinfo(self, conn, path="/"):
|
def cmd_lsinfo(self, conn, path="/"):
|
||||||
"""Yields info on all the items in the path."""
|
"""Sends info on all the items in the path."""
|
||||||
artist, album, track = self._parse_path(path)
|
artist, album, track = self._parse_path(path)
|
||||||
|
|
||||||
if not artist: # List all artists.
|
if not artist: # List all artists.
|
||||||
for artist in self.lib.artists():
|
for artist in self.lib.artists():
|
||||||
yield 'directory: ' + artist
|
conn.send('directory: ' + artist)
|
||||||
elif not album: # List all albums for an artist.
|
elif not album: # List all albums for an artist.
|
||||||
for album in self.lib.albums(artist):
|
for album in self.lib.albums(artist):
|
||||||
yield 'directory: ' + seq_to_path(album)
|
conn.send('directory: ' + seq_to_path(album))
|
||||||
elif not track: # List all tracks on an album.
|
elif not track: # List all tracks on an album.
|
||||||
for item in self.lib.items(artist, album):
|
for item in self.lib.items(artist, album):
|
||||||
yield self._item_info(item)
|
conn.send(*self._item_info(item))
|
||||||
else: # List a track. This isn't a directory.
|
else: # List a track. This isn't a directory.
|
||||||
raise BPDError(ERROR_ARG, 'this is not a directory')
|
raise BPDError(ERROR_ARG, 'this is not a directory')
|
||||||
|
|
||||||
def _listall(self, path="/", info=False):
|
def _listall(self, conn, path="/", info=False):
|
||||||
"""Helper function for recursive listing. If info, show
|
"""Helper function for recursive listing. If info, show
|
||||||
tracks' complete info; otherwise, just show items' paths.
|
tracks' complete info; otherwise, just show items' paths.
|
||||||
"""
|
"""
|
||||||
|
|
@ -790,28 +776,28 @@ class Server(BaseServer):
|
||||||
# artists
|
# artists
|
||||||
if not artist:
|
if not artist:
|
||||||
for a in self.lib.artists():
|
for a in self.lib.artists():
|
||||||
yield 'directory: ' + a
|
conn.send('directory: ' + a)
|
||||||
|
|
||||||
# albums
|
# albums
|
||||||
if not album:
|
if not album:
|
||||||
for a in self.lib.albums(artist or None):
|
for a in self.lib.albums(artist or None):
|
||||||
yield 'directory: ' + seq_to_path(a)
|
conn.send('directory: ' + seq_to_path(a))
|
||||||
|
|
||||||
# tracks
|
# tracks
|
||||||
items = self.lib.items(artist or None, album or None)
|
items = self.lib.items(artist or None, album or None)
|
||||||
if info:
|
if info:
|
||||||
for item in items:
|
for item in items:
|
||||||
yield self._item_info(item)
|
conn.send(*self._item_info(item))
|
||||||
else:
|
else:
|
||||||
for item in items:
|
for item in items:
|
||||||
yield 'file: ' + self._item_path(i)
|
conn.send('file: ' + self._item_path(i))
|
||||||
|
|
||||||
def cmd_listall(self, conn, path="/"):
|
def cmd_listall(self, conn, path="/"):
|
||||||
"""Return the paths all items in the directory, recursively."""
|
"""Send the paths all items in the directory, recursively."""
|
||||||
for l in self._listall(path, False): yield l
|
self._listall(conn, path, False)
|
||||||
def cmd_listallinfo(self, conn, path="/"):
|
def cmd_listallinfo(self, conn, path="/"):
|
||||||
"""Return info on all the items in the directory, recursively."""
|
"""Send info on all the items in the directory, recursively."""
|
||||||
for l in self._listall(path, True): yield l
|
self._listall(conn, path, True)
|
||||||
|
|
||||||
|
|
||||||
# Playlist manipulation.
|
# Playlist manipulation.
|
||||||
|
|
@ -829,25 +815,25 @@ class Server(BaseServer):
|
||||||
self.playlist.append(self._get_by_path(path))
|
self.playlist.append(self._get_by_path(path))
|
||||||
self.playlist_version += 1
|
self.playlist_version += 1
|
||||||
def cmd_addid(self, conn, path):
|
def cmd_addid(self, conn, path):
|
||||||
"""Same as cmd_add but yields an id."""
|
"""Same as cmd_add but sends an id."""
|
||||||
track = self._get_by_path(path)
|
track = self._get_by_path(path)
|
||||||
self.playlist.append(track)
|
self.playlist.append(track)
|
||||||
self.playlist_version += 1
|
self.playlist_version += 1
|
||||||
yield 'Id: ' + str(track.id)
|
conn.send('Id: ' + str(track.id))
|
||||||
|
|
||||||
|
|
||||||
# Server info.
|
# Server info.
|
||||||
|
|
||||||
def cmd_status(self, conn):
|
def cmd_status(self, conn):
|
||||||
for l in super(Server, self).cmd_status(conn): yield l
|
super(Server, self).cmd_status(conn)
|
||||||
if self.current_index > -1:
|
if self.current_index > -1:
|
||||||
item = self.playlist[self.current_index]
|
item = self.playlist[self.current_index]
|
||||||
|
|
||||||
yield 'bitrate: ' + str(item.bitrate/1000)
|
conn.send('bitrate: ' + str(item.bitrate/1000))
|
||||||
#fixme: missing 'audio'
|
#fixme: missing 'audio'
|
||||||
|
|
||||||
(pos, total) = self.player.time()
|
(pos, total) = self.player.time()
|
||||||
yield 'time: ' + str(pos) + ':' + str(total)
|
conn.send('time: ' + str(pos) + ':' + str(total))
|
||||||
|
|
||||||
#fixme: also missing 'updating_db'
|
#fixme: also missing 'updating_db'
|
||||||
|
|
||||||
|
|
@ -855,7 +841,7 @@ class Server(BaseServer):
|
||||||
def cmd_stats(self, conn):
|
def cmd_stats(self, conn):
|
||||||
# The first three items need to be done more efficiently. The
|
# The first three items need to be done more efficiently. The
|
||||||
# last three need to be implemented.
|
# last three need to be implemented.
|
||||||
yield ('artists: ' + str(len(self.lib.artists())),
|
conn.send('artists: ' + str(len(self.lib.artists())),
|
||||||
'albums: ' + str(len(self.lib.albums())),
|
'albums: ' + str(len(self.lib.albums())),
|
||||||
'songs: ' + str(len(list(self.lib.items()))),
|
'songs: ' + str(len(list(self.lib.items()))),
|
||||||
'uptime: ' + str(int(time.time() - self.startup_time)),
|
'uptime: ' + str(int(time.time() - self.startup_time)),
|
||||||
|
|
@ -885,7 +871,7 @@ class Server(BaseServer):
|
||||||
searching.
|
searching.
|
||||||
"""
|
"""
|
||||||
for tag in self.tagtype_map:
|
for tag in self.tagtype_map:
|
||||||
yield 'tagtype: ' + tag
|
conn.send('tagtype: ' + tag)
|
||||||
|
|
||||||
def cmd_search(self, conn, key, value):
|
def cmd_search(self, conn, key, value):
|
||||||
"""Perform a substring match in a specific column."""
|
"""Perform a substring match in a specific column."""
|
||||||
|
|
@ -893,7 +879,7 @@ class Server(BaseServer):
|
||||||
key = 'path'
|
key = 'path'
|
||||||
query = beets.library.SubstringQuery(key, value)
|
query = beets.library.SubstringQuery(key, value)
|
||||||
for item in self.lib.get(query):
|
for item in self.lib.get(query):
|
||||||
yield self._item_info(item)
|
conn.send(*self._item_info(item))
|
||||||
|
|
||||||
def cmd_find(self, conn, key, value):
|
def cmd_find(self, conn, key, value):
|
||||||
"""Perform an exact match in a specific column."""
|
"""Perform an exact match in a specific column."""
|
||||||
|
|
@ -901,7 +887,7 @@ class Server(BaseServer):
|
||||||
key = 'path'
|
key = 'path'
|
||||||
query = beets.library.MatchQuery(key, value)
|
query = beets.library.MatchQuery(key, value)
|
||||||
for item in self.lib.get(query):
|
for item in self.lib.get(query):
|
||||||
yield self._item_info(item)
|
conn.send(*self._item_info(item))
|
||||||
|
|
||||||
|
|
||||||
# "Outputs." Just a dummy implementation because we don't control
|
# "Outputs." Just a dummy implementation because we don't control
|
||||||
|
|
@ -909,7 +895,7 @@ class Server(BaseServer):
|
||||||
|
|
||||||
def cmd_outputs(self, conn):
|
def cmd_outputs(self, conn):
|
||||||
"""List the available outputs."""
|
"""List the available outputs."""
|
||||||
yield ('outputid: 0',
|
conn.send('outputid: 0',
|
||||||
'outputname: gstreamer',
|
'outputname: gstreamer',
|
||||||
'outputenabled: 1',
|
'outputenabled: 1',
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue