Merge pull request #968 from kingosticks/fix/session-playlist-uri-mapping

Share MPD playlist uri mapping between sessions
This commit is contained in:
Stein Magnus Jodal 2015-02-09 13:26:40 +01:00
commit 52457df525
6 changed files with 103 additions and 59 deletions

View File

@ -46,6 +46,9 @@ v0.20.0 (UNRELEASED)
- Enable browsing of artist references, in addition to albums and playlists.
(PR: :issue:`884`)
- Share a single mapping between names and URIs across all MPD sessions. (Fixes:
:issue:`934`, PR: :issue:`968`)
**Audio**

View File

@ -6,7 +6,7 @@ import pykka
from mopidy import exceptions, zeroconf
from mopidy.core import CoreListener
from mopidy.mpd import session
from mopidy.mpd import session, uri_mapper
from mopidy.utils import encoding, network, process
logger = logging.getLogger(__name__)
@ -18,6 +18,7 @@ class MpdFrontend(pykka.ThreadingActor, CoreListener):
self.hostname = network.format_hostname(config['mpd']['hostname'])
self.port = config['mpd']['port']
self.uri_map = uri_mapper.MpdUriMapper(core)
self.zeroconf_name = config['mpd']['zeroconf']
self.zeroconf_service = None
@ -29,6 +30,7 @@ class MpdFrontend(pykka.ThreadingActor, CoreListener):
protocol_kwargs={
'config': config,
'core': core,
'uri_map': self.uri_map,
},
max_connections=config['mpd']['max_connections'],
timeout=config['mpd']['connection_timeout'])

View File

@ -21,7 +21,7 @@ class MpdDispatcher(object):
_noidle = re.compile(r'^noidle$')
def __init__(self, session=None, config=None, core=None):
def __init__(self, session=None, config=None, core=None, uri_map=None):
self.config = config
self.authenticated = False
self.command_list_receiving = False
@ -29,7 +29,7 @@ class MpdDispatcher(object):
self.command_list = []
self.command_list_index = None
self.context = MpdContext(
self, session=session, config=config, core=core)
self, session=session, config=config, core=core, uri_map=uri_map)
def handle_request(self, request, current_command_list_index=None):
"""Dispatch incoming requests to the correct handler."""
@ -227,10 +227,10 @@ class MpdContext(object):
#: The subsytems that we want to be notified about in idle mode.
subscriptions = None
_invalid_browse_chars = re.compile(r'[\n\r]')
_invalid_playlist_chars = re.compile(r'[/]')
_uri_map = None
def __init__(self, dispatcher, session=None, config=None, core=None):
def __init__(self, dispatcher, session=None, config=None, core=None,
uri_map=None):
self.dispatcher = dispatcher
self.session = session
if config is not None:
@ -238,58 +238,19 @@ class MpdContext(object):
self.core = core
self.events = set()
self.subscriptions = set()
self._uri_from_name = {}
self._name_from_uri = {}
self.refresh_playlists_mapping()
def create_unique_name(self, name, uri):
stripped_name = self._invalid_browse_chars.sub(' ', name)
name = stripped_name
i = 2
while name in self._uri_from_name:
if self._uri_from_name[name] == uri:
return name
name = '%s [%d]' % (stripped_name, i)
i += 1
return name
def insert_name_uri_mapping(self, name, uri):
name = self.create_unique_name(name, uri)
self._uri_from_name[name] = uri
self._name_from_uri[uri] = name
return name
def refresh_playlists_mapping(self):
"""
Maintain map between playlists and unique playlist names to be used by
MPD
"""
if self.core is not None:
for playlist in self.core.playlists.playlists.get():
if not playlist.name:
continue
# TODO: add scheme to name perhaps 'foo (spotify)' etc.
name = self._invalid_playlist_chars.sub('|', playlist.name)
self.insert_name_uri_mapping(name, playlist.uri)
self._uri_map = uri_map
def lookup_playlist_from_name(self, name):
"""
Helper function to retrieve a playlist from its unique MPD name.
"""
if not self._uri_from_name:
self.refresh_playlists_mapping()
if name not in self._uri_from_name:
return None
uri = self._uri_from_name[name]
return self.core.playlists.lookup(uri).get()
return self._uri_map.playlist_from_name(name)
def lookup_playlist_name_from_uri(self, uri):
"""
Helper function to retrieve the unique MPD playlist name from its uri.
"""
if uri not in self._name_from_uri:
self.refresh_playlists_mapping()
return self._name_from_uri[uri]
return self._uri_map.playlist_name_from_uri(uri)
def browse(self, path, recursive=True, lookup=True):
"""
@ -313,8 +274,8 @@ class MpdContext(object):
path_parts = re.findall(r'[^/]+', path or '')
root_path = '/'.join([''] + path_parts)
if root_path not in self._uri_from_name:
uri = None
uri = self._uri_map.uri_from_name(root_path)
if uri is None:
for part in path_parts:
for ref in self.core.library.browse(uri).get():
if ref.type != ref.TRACK and ref.name == part:
@ -322,10 +283,7 @@ class MpdContext(object):
break
else:
raise exceptions.MpdNoExistError('Not found')
root_path = self.insert_name_uri_mapping(root_path, uri)
else:
uri = self._uri_from_name[root_path]
root_path = self._uri_map.insert(root_path, uri)
if recursive:
yield (root_path, None)
@ -335,7 +293,7 @@ class MpdContext(object):
base_path, future = path_and_futures.pop()
for ref in future.get():
path = '/'.join([base_path, ref.name.replace('/', '')])
path = self.insert_name_uri_mapping(path, ref.uri)
path = self._uri_map.insert(path, ref.uri)
if ref.type == ref.TRACK:
if lookup:

View File

@ -18,10 +18,10 @@ class MpdSession(network.LineProtocol):
encoding = protocol.ENCODING
delimiter = r'\r?\n'
def __init__(self, connection, config=None, core=None):
def __init__(self, connection, config=None, core=None, uri_map=None):
super(MpdSession, self).__init__(connection)
self.dispatcher = dispatcher.MpdDispatcher(
session=self, config=config, core=core)
session=self, config=config, core=core, uri_map=uri_map)
def on_start(self):
logger.info('New MPD connection from [%s]:%s', self.host, self.port)

79
mopidy/mpd/uri_mapper.py Normal file
View File

@ -0,0 +1,79 @@
from __future__ import absolute_import, unicode_literals
import re
class MpdUriMapper(object):
"""
Maintains the mappings between uniquified MPD names and URIs.
"""
#: The Mopidy core API. An instance of :class:`mopidy.core.Core`.
core = None
_invalid_browse_chars = re.compile(r'[\n\r]')
_invalid_playlist_chars = re.compile(r'[/]')
def __init__(self, core=None):
self.core = core
self._uri_from_name = {}
self._name_from_uri = {}
def _create_unique_name(self, name, uri):
stripped_name = self._invalid_browse_chars.sub(' ', name)
name = stripped_name
i = 2
while name in self._uri_from_name:
if self._uri_from_name[name] == uri:
return name
name = '%s [%d]' % (stripped_name, i)
i += 1
return name
def insert(self, name, uri):
"""
Create a unique and MPD compatible name that maps to the given uri.
"""
name = self._create_unique_name(name, uri)
self._uri_from_name[name] = uri
self._name_from_uri[uri] = name
return name
def uri_from_name(self, name):
"""
Return the uri for the given MPD name.
"""
if name in self._uri_from_name:
return self._uri_from_name[name]
def refresh_playlists_mapping(self):
"""
Maintain map between playlists and unique playlist names to be used by
MPD
"""
if self.core is not None:
for playlist in self.core.playlists.playlists.get():
if not playlist.name:
continue
# TODO: add scheme to name perhaps 'foo (spotify)' etc.
name = self._invalid_playlist_chars.sub('|', playlist.name)
self.insert(name, playlist.uri)
def playlist_from_name(self, name):
"""
Helper function to retrieve a playlist from its unique MPD name.
"""
if not self._uri_from_name:
self.refresh_playlists_mapping()
if name not in self._uri_from_name:
return None
uri = self._uri_from_name[name]
return self.core.playlists.lookup(uri).get()
def playlist_name_from_uri(self, uri):
"""
Helper function to retrieve the unique MPD playlist name from its uri.
"""
if uri not in self._name_from_uri:
self.refresh_playlists_mapping()
return self._name_from_uri[uri]

View File

@ -8,7 +8,7 @@ import pykka
from mopidy import core
from mopidy.backend import dummy
from mopidy.mpd import session
from mopidy.mpd import session, uri_mapper
class MockConnection(mock.Mock):
@ -35,9 +35,11 @@ class BaseTestCase(unittest.TestCase):
self.backend = dummy.create_dummy_backend_proxy()
self.core = core.Core.start(backends=[self.backend]).proxy()
self.uri_map = uri_mapper.MpdUriMapper(self.core)
self.connection = MockConnection()
self.session = session.MpdSession(
self.connection, config=self.get_config(), core=self.core)
self.connection, config=self.get_config(), core=self.core,
uri_map=self.uri_map)
self.dispatcher = self.session.dispatcher
self.context = self.dispatcher.context