diff --git a/mopidy/__main__.py b/mopidy/__main__.py index 6a691cce..7e1677ef 100644 --- a/mopidy/__main__.py +++ b/mopidy/__main__.py @@ -236,8 +236,10 @@ def filter_enabled_extensions(raw_config, extensions): else: disabled_names.append(extension.ext_name) - logging.info('Enabled extensions: %s', ', '.join(enabled_names)) - logging.info('Disabled extensions: %s', ', '.join(disabled_names)) + logging.info( + 'Enabled extensions: %s', ', '.join(enabled_names) or 'none') + logging.info( + 'Disabled extensions: %s', ', '.join(disabled_names) or 'none') return enabled_extensions @@ -310,7 +312,6 @@ def validate_config(raw_config, schemas, extensions=None): def create_file_structures(): path.get_or_create_dir('$XDG_DATA_DIR/mopidy') - path.get_or_create_dir('$XDG_CONFIG_DIR/mopidy') path.get_or_create_file('$XDG_CONFIG_DIR/mopidy/mopidy.conf') @@ -325,12 +326,19 @@ def stop_audio(): def setup_backends(config, extensions, audio): - logger.info('Starting Mopidy backends') - backends = [] + backend_classes = [] for extension in extensions: - for backend_class in extension.get_backend_classes(): - backend = backend_class.start(config=config, audio=audio).proxy() - backends.append(backend) + backend_classes.extend(extension.get_backend_classes()) + + logger.info( + 'Starting Mopidy backends: %s', + ', '.join(b.__name__ for b in backend_classes) or 'none') + + backends = [] + for backend_class in backend_classes: + backend = backend_class.start(config=config, audio=audio).proxy() + backends.append(backend) + return backends @@ -352,10 +360,16 @@ def stop_core(): def setup_frontends(config, extensions, core): - logger.info('Starting Mopidy frontends') + frontend_classes = [] for extension in extensions: - for frontend_class in extension.get_frontend_classes(): - frontend_class.start(config=config, core=core) + frontend_classes.extend(extension.get_frontend_classes()) + + logger.info( + 'Starting Mopidy frontends: %s', + ', '.join(f.__name__ for f in frontend_classes) or 'none') + + for frontend_class in frontend_classes: + frontend_class.start(config=config, core=core) def stop_frontends(extensions): diff --git a/mopidy/backends/local/__init__.py b/mopidy/backends/local/__init__.py index ffcf5869..0367cf15 100644 --- a/mopidy/backends/local/__init__.py +++ b/mopidy/backends/local/__init__.py @@ -9,8 +9,8 @@ default_config = """ [local] enabled = true media_dir = $XDG_MUSIC_DIR -playlists_dir = $XDG_DATA_DIR/mopidy/playlists -tag_cache_file = $XDG_DATA_DIR/mopidy/tag_cache +playlists_dir = $XDG_DATA_DIR/mopidy/local/playlists +tag_cache_file = $XDG_DATA_DIR/mopidy/local/tag_cache """ __doc__ = """A backend for playing music from a local music archive. diff --git a/mopidy/backends/local/actor.py b/mopidy/backends/local/actor.py index a1655dd9..1817e133 100644 --- a/mopidy/backends/local/actor.py +++ b/mopidy/backends/local/actor.py @@ -5,6 +5,7 @@ import logging import pykka from mopidy.backends import base +from mopidy.utils import encoding, path from .library import LocalLibraryProvider from .playlists import LocalPlaylistsProvider @@ -18,8 +19,32 @@ class LocalBackend(pykka.ThreadingActor, base.Backend): self.config = config + self.create_dirs_and_files() + self.library = LocalLibraryProvider(backend=self) self.playback = base.BasePlaybackProvider(audio=audio, backend=self) self.playlists = LocalPlaylistsProvider(backend=self) self.uri_schemes = ['file'] + + def create_dirs_and_files(self): + try: + path.get_or_create_dir(self.config['local']['media_dir']) + except EnvironmentError as error: + logger.warning( + 'Could not create local media dir: %s', + encoding.locale_decode(error)) + + try: + path.get_or_create_dir(self.config['local']['playlists_dir']) + except EnvironmentError as error: + logger.warning( + 'Could not create local playlists dir: %s', + encoding.locale_decode(error)) + + try: + path.get_or_create_file(self.config['local']['tag_cache_file']) + except EnvironmentError as error: + logger.warning( + 'Could not create empty tag cache file: %s', + encoding.locale_decode(error)) diff --git a/mopidy/backends/local/library.py b/mopidy/backends/local/library.py index a76ce594..669e72d7 100644 --- a/mopidy/backends/local/library.py +++ b/mopidy/backends/local/library.py @@ -1,7 +1,6 @@ from __future__ import unicode_literals import logging - from mopidy.backends import base from mopidy.models import Album, SearchResult @@ -19,15 +18,19 @@ class LocalLibraryProvider(base.BaseLibraryProvider): self.refresh() def refresh(self, uri=None): - tracks = parse_mpd_tag_cache(self._tag_cache_file, self._media_dir) - - logger.info( - 'Loading tracks from %s using %s', + logger.debug( + 'Loading local tracks from %s using %s', self._media_dir, self._tag_cache_file) + tracks = parse_mpd_tag_cache(self._tag_cache_file, self._media_dir) + for track in tracks: self._uri_mapping[track.uri] = track + logger.info( + 'Loaded %d local tracks from %s using %s', + len(tracks), self._media_dir, self._tag_cache_file) + def lookup(self, uri): try: return [self._uri_mapping[uri]] diff --git a/mopidy/backends/local/playlists.py b/mopidy/backends/local/playlists.py index 3b9e1d73..cd370eaa 100644 --- a/mopidy/backends/local/playlists.py +++ b/mopidy/backends/local/playlists.py @@ -42,8 +42,6 @@ class LocalPlaylistsProvider(base.BasePlaylistsProvider): return playlist def refresh(self): - logger.info('Loading playlists from %s', self._playlists_dir) - playlists = [] for m3u in glob.glob(os.path.join(self._playlists_dir, '*.m3u')): @@ -65,6 +63,10 @@ class LocalPlaylistsProvider(base.BasePlaylistsProvider): self.playlists = playlists listener.BackendListener.send('playlists_loaded') + logger.info( + 'Loaded %d local playlists from %s', + len(playlists), self._playlists_dir) + def save(self, playlist): assert playlist.uri, 'Cannot save playlist without URI' diff --git a/mopidy/backends/spotify/session_manager.py b/mopidy/backends/spotify/session_manager.py index a830138f..c0592ea7 100644 --- a/mopidy/backends/spotify/session_manager.py +++ b/mopidy/backends/spotify/session_manager.py @@ -182,7 +182,7 @@ class SpotifySessionManager(process.BaseThread, PyspotifySessionManager): bitrate=self.bitrate, username=self.username)) playlists = filter(None, playlists) self.backend.playlists.playlists = playlists - logger.info('Loaded %d Spotify playlist(s)', len(playlists)) + logger.info('Loaded %d Spotify playlists', len(playlists)) BackendListener.send('playlists_loaded') def logout(self): diff --git a/mopidy/frontends/mpris/objects.py b/mopidy/frontends/mpris/objects.py index 696e39bd..a7f049d2 100644 --- a/mopidy/frontends/mpris/objects.py +++ b/mopidy/frontends/mpris/objects.py @@ -93,7 +93,7 @@ class MprisObject(dbus.service.Object): mainloop = dbus.mainloop.glib.DBusGMainLoop() bus_name = dbus.service.BusName( BUS_NAME, dbus.SessionBus(mainloop=mainloop)) - logger.info('Connected to D-Bus') + logger.info('MPRIS server connected to D-Bus') return bus_name def get_playlist_id(self, playlist_uri): diff --git a/mopidy/frontends/scrobbler/actor.py b/mopidy/frontends/scrobbler/actor.py index 1809661a..74a11f82 100644 --- a/mopidy/frontends/scrobbler/actor.py +++ b/mopidy/frontends/scrobbler/actor.py @@ -32,7 +32,7 @@ class ScrobblerFrontend(pykka.ThreadingActor, CoreListener): api_key=API_KEY, api_secret=API_SECRET, username=self.config['scrobbler']['username'], password_hash=pylast.md5(self.config['scrobbler']['password'])) - logger.info('Connected to Last.fm') + logger.info('Scrobbler connected to Last.fm') except (pylast.NetworkError, pylast.MalformedResponseError, pylast.WSError) as e: logger.error('Error during Last.fm setup: %s', e) diff --git a/mopidy/utils/path.py b/mopidy/utils/path.py index 699b361f..2ad51368 100644 --- a/mopidy/utils/path.py +++ b/mopidy/utils/path.py @@ -39,12 +39,13 @@ def get_or_create_dir(dir_path): return dir_path -def get_or_create_file(filename): - filename = expand_path(filename) - if not os.path.isfile(filename): - logger.info('Creating file %s', filename) - open(filename, 'w') - return filename +def get_or_create_file(file_path): + file_path = expand_path(file_path) + get_or_create_dir(os.path.dirname(file_path)) + if not os.path.isfile(file_path): + logger.info('Creating file %s', file_path) + open(file_path, 'w').close() + return file_path def path_to_uri(*paths): diff --git a/tests/utils/path_test.py b/tests/utils/path_test.py index a73cb8e4..9d1c16d3 100644 --- a/tests/utils/path_test.py +++ b/tests/utils/path_test.py @@ -24,7 +24,6 @@ class GetOrCreateDirTest(unittest.TestCase): def test_creating_dir(self): dir_path = os.path.join(self.parent, 'test') self.assert_(not os.path.exists(dir_path)) - self.assert_(not os.path.isdir(dir_path)) created = path.get_or_create_dir(dir_path) self.assert_(os.path.exists(dir_path)) self.assert_(os.path.isdir(dir_path)) @@ -34,9 +33,7 @@ class GetOrCreateDirTest(unittest.TestCase): level2_dir = os.path.join(self.parent, 'test') level3_dir = os.path.join(self.parent, 'test', 'test') self.assert_(not os.path.exists(level2_dir)) - self.assert_(not os.path.isdir(level2_dir)) self.assert_(not os.path.exists(level3_dir)) - self.assert_(not os.path.isdir(level3_dir)) created = path.get_or_create_dir(level3_dir) self.assert_(os.path.exists(level2_dir)) self.assert_(os.path.isdir(level2_dir)) @@ -57,6 +54,47 @@ class GetOrCreateDirTest(unittest.TestCase): self.assertRaises(OSError, path.get_or_create_dir, dir_path) +class GetOrCreateFileTest(unittest.TestCase): + def setUp(self): + self.parent = tempfile.mkdtemp() + + def tearDown(self): + if os.path.isdir(self.parent): + shutil.rmtree(self.parent) + + def test_creating_file(self): + file_path = os.path.join(self.parent, 'test') + self.assert_(not os.path.exists(file_path)) + created = path.get_or_create_file(file_path) + self.assert_(os.path.exists(file_path)) + self.assert_(os.path.isfile(file_path)) + self.assertEqual(created, file_path) + + def test_creating_nested_file(self): + level2_dir = os.path.join(self.parent, 'test') + file_path = os.path.join(self.parent, 'test', 'test') + self.assert_(not os.path.exists(level2_dir)) + self.assert_(not os.path.exists(file_path)) + created = path.get_or_create_file(file_path) + self.assert_(os.path.exists(level2_dir)) + self.assert_(os.path.isdir(level2_dir)) + self.assert_(os.path.exists(file_path)) + self.assert_(os.path.isfile(file_path)) + self.assertEqual(created, file_path) + + def test_creating_existing_file(self): + file_path = os.path.join(self.parent, 'test') + path.get_or_create_file(file_path) + created = path.get_or_create_file(file_path) + self.assert_(os.path.exists(file_path)) + self.assert_(os.path.isfile(file_path)) + self.assertEqual(created, file_path) + + def test_create_file_with_name_of_existing_dir_throws_ioerror(self): + conflicting_dir = os.path.join(self.parent) + self.assertRaises(IOError, path.get_or_create_file, conflicting_dir) + + class PathToFileURITest(unittest.TestCase): def test_simple_path(self): if sys.platform == 'win32':