diff --git a/docs/changelog.rst b/docs/changelog.rst index 6d7da9d4..c5dc8150 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -35,6 +35,17 @@ Bug fix release. - Configuration: :option:`mopidy --config` now supports directories. +- Network: Fix a race condition where two threads could try to free the same + data simultaneously. (Fixes: :issue:`781`) + +- Backend API: Update :meth:`mopidy.backend.LibraryProvider.browse` signature + and docs to match how the core use the backend's browse method. (Fixes: + :issue:`833`) + +- Local library API: Add :attr:`mopidy.local.ROOT_DIRECTORY_URI` constant for + use by implementors of :method:`mopidy.local.Library.browse`. (Related to: + :issue:`833`) + v0.19.3 (2014-08-03) ==================== diff --git a/mopidy/backend/__init__.py b/mopidy/backend/__init__.py index 845eac11..712bfafe 100644 --- a/mopidy/backend/__init__.py +++ b/mopidy/backend/__init__.py @@ -81,12 +81,12 @@ class LibraryProvider(object): def __init__(self, backend): self.backend = backend - def browse(self, path): + def browse(self, uri): """ See :meth:`mopidy.core.LibraryController.browse`. If you implement this method, make sure to also set - :attr:`root_directory_name`. + :attr:`root_directory`. *MAY be implemented by subclass.* """ diff --git a/mopidy/local/__init__.py b/mopidy/local/__init__.py index 8b4a8b1f..7f4cd03f 100644 --- a/mopidy/local/__init__.py +++ b/mopidy/local/__init__.py @@ -46,6 +46,15 @@ class Extension(ext.Extension): return LocalCommand() +ROOT_DIRECTORY_URI = 'local:directory' +""" +URI of the local backend's root directory. + +This constant should be used by libraries implementing the +:meth:`Library.browse` method. +""" + + class Library(object): """ Local library interface. @@ -64,11 +73,14 @@ class Library(object): def __init__(self, config): self._config = config - def browse(self, path): + def browse(self, uri): """ - Browse directories and tracks at the given path. + Browse directories and tracks at the given URI. - :param string path: path to browse or None for root. + The URI for the root directory is a constant available at + :attr:`ROOT_DIRECTORY_URI`. + + :param string path: URI to browse. :rtype: List of :class:`~mopidy.models.Ref` tracks and directories. """ raise NotImplementedError diff --git a/mopidy/local/json.py b/mopidy/local/json.py index 927f8898..1bb55389 100644 --- a/mopidy/local/json.py +++ b/mopidy/local/json.py @@ -61,8 +61,7 @@ class _BrowseCache(object): splitpath_re = re.compile(r'([^/]+)') def __init__(self, uris): - # TODO: local.ROOT_DIRECTORY_URI - self._cache = {'local:directory': collections.OrderedDict()} + self._cache = {local.ROOT_DIRECTORY_URI: collections.OrderedDict()} for track_uri in uris: path = translator.local_track_uri_to_path(track_uri, b'/') @@ -97,10 +96,10 @@ class _BrowseCache(object): else: # Loop completed, so final child needs to be added to root. if child: - self._cache['local:directory'][child.uri] = child + self._cache[local.ROOT_DIRECTORY_URI][child.uri] = child # If no parent was set we belong in the root. if not parent_uri: - parent_uri = 'local:directory' + parent_uri = local.ROOT_DIRECTORY_URI self._cache[parent_uri][track_uri] = track_ref diff --git a/mopidy/local/library.py b/mopidy/local/library.py index a626f566..cf312883 100644 --- a/mopidy/local/library.py +++ b/mopidy/local/library.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals import logging -from mopidy import backend, models +from mopidy import backend, local, models logger = logging.getLogger(__name__) @@ -10,18 +10,18 @@ logger = logging.getLogger(__name__) class LocalLibraryProvider(backend.LibraryProvider): """Proxy library that delegates work to our active local library.""" - root_directory = models.Ref.directory(uri=b'local:directory', - name='Local media') + root_directory = models.Ref.directory( + uri=local.ROOT_DIRECTORY_URI, name='Local media') def __init__(self, backend, library): super(LocalLibraryProvider, self).__init__(backend) self._library = library self.refresh() - def browse(self, path): + def browse(self, uri): if not self._library: return [] - return self._library.browse(path) + return self._library.browse(uri) def refresh(self, uri=None): if not self._library: diff --git a/mopidy/utils/network.py b/mopidy/utils/network.py index 11469b47..4ea25026 100644 --- a/mopidy/utils/network.py +++ b/mopidy/utils/network.py @@ -268,8 +268,8 @@ class Connection(object): return True if not data: - self.actor_ref.tell({'close': True}) self.disable_recv() + self.actor_ref.tell({'close': True}) return True try: diff --git a/tests/utils/network/test_connection.py b/tests/utils/network/test_connection.py index 7a25643f..c3200689 100644 --- a/tests/utils/network/test_connection.py +++ b/tests/utils/network/test_connection.py @@ -7,7 +7,7 @@ import unittest import gobject -from mock import Mock, patch, sentinel +from mock import Mock, call, patch, sentinel import pykka @@ -418,8 +418,11 @@ class ConnectionTest(unittest.TestCase): self.assertTrue(network.Connection.recv_callback( self.mock, sentinel.fd, gobject.IO_IN)) - self.mock.actor_ref.tell.assert_called_once_with({'close': True}) - self.mock.disable_recv.assert_called_once_with() + self.assertEqual(self.mock.mock_calls, [ + call.sock.recv(any_int), + call.disable_recv(), + call.actor_ref.tell({'close': True}), + ]) def test_recv_callback_recoverable_error(self): self.mock.sock = Mock(spec=socket.SocketType)