diff --git a/mopidy/__main__.py b/mopidy/__main__.py index c5e4991a..12d27ed3 100644 --- a/mopidy/__main__.py +++ b/mopidy/__main__.py @@ -10,14 +10,14 @@ sys.path.insert(0, from mopidy import get_version, settings, SettingsError from mopidy.process import CoreProcess -from mopidy.utils import get_class, get_or_create_dotdir +from mopidy.utils import get_class, get_or_create_folder logger = logging.getLogger('mopidy.main') def main(): options = _parse_options() _setup_logging(options.verbosity_level) - get_or_create_dotdir('~/.mopidy/') + get_or_create_folder('~/.mopidy/') core_queue = multiprocessing.Queue() get_class(settings.SERVER)(core_queue) core = CoreProcess(core_queue) diff --git a/mopidy/backends/gstreamer.py b/mopidy/backends/gstreamer.py index baa3c221..826a3669 100644 --- a/mopidy/backends/gstreamer.py +++ b/mopidy/backends/gstreamer.py @@ -188,7 +188,7 @@ class GStreamerLibraryController(BaseLibraryController): self.refresh() def refresh(self, uri=None): - tracks, artists, albums = parse_mpd_tag_cache(settings.TAG_CACHE, + tracks = parse_mpd_tag_cache(settings.TAG_CACHE, settings.MUSIC_FOLDER) for track in tracks: @@ -223,6 +223,7 @@ class GStreamerLibraryController(BaseLibraryController): q = query.strip().lower() library_tracks = self._uri_mapping.values() + # FIXME this is bound to be slow for large libraries track_filter = lambda t: q in t.name.lower() album_filter = lambda t: q in getattr(t, 'album', Album()).name.lower() artist_filter = lambda t: filter(lambda a: q in a.name.lower(), diff --git a/mopidy/utils.py b/mopidy/utils.py index af962b49..8ddca4c8 100644 --- a/mopidy/utils.py +++ b/mopidy/utils.py @@ -2,6 +2,7 @@ import logging from multiprocessing.reduction import reduce_connection import os import pickle +import sys import urllib logger = logging.getLogger('mopidy.utils') @@ -25,12 +26,19 @@ def get_class(name): class_object = getattr(module, class_name) return class_object -def get_or_create_dotdir(dotdir): - dotdir = os.path.expanduser(dotdir) - if not os.path.isdir(dotdir): - logger.info(u'Creating %s', dotdir) - os.mkdir(dotdir, 0755) - return dotdir +def get_or_create_folder(folder): + folder = os.path.expanduser(folder) + if not os.path.isdir(folder): + logger.info(u'Creating %s', folder) + os.mkdir(folder, 0755) + return folder + +def path_to_uri(*paths): + path = os.path.join(*paths) + path = path.encode('utf-8') + if sys.platform == 'win32': + return 'file:' + urllib.pathname2url(path) + return 'file://' + urllib.pathname2url(path) def indent(string, places=4, linebreak='\n'): lines = string.split(linebreak) @@ -136,9 +144,8 @@ def parse_m3u(file_path): if line.startswith('file://'): uris.append(line) else: - path = os.path.join(folder, line) - path = urllib.pathname2url(path.encode('utf-8')) - uris.append('file://' + path) + path = path_to_uri(folder, line) + uris.append(path) return uris @@ -147,15 +154,13 @@ def parse_mpd_tag_cache(tag_cache, music_dir=''): Converts a MPD tag_cache into a lists of tracks, artists and albums. """ tracks = set() - artists = set() - albums = set() try: with open(tag_cache) as library: contents = library.read() except IOError, e: logger.error('Could not open tag cache: %s', e) - return tracks, artists, albums + return tracks current = {} state = None @@ -173,16 +178,16 @@ def parse_mpd_tag_cache(tag_cache, music_dir=''): key, value = line.split(': ', 1) if key == 'key': - _convert_mpd_data(current, tracks, artists, albums, music_dir) + _convert_mpd_data(current, tracks, music_dir) current.clear() current[key.lower()] = value - _convert_mpd_data(current, tracks, artists, albums, music_dir) + _convert_mpd_data(current, tracks, music_dir) - return tracks, artists, albums + return tracks -def _convert_mpd_data(data, tracks, artists, albums, music_dir): +def _convert_mpd_data(data, tracks, music_dir): if not data: return @@ -195,14 +200,12 @@ def _convert_mpd_data(data, tracks, artists, albums, music_dir): if 'artist' in data: artist = Artist(name=data['artist']) - artists.add(artist) track_kwargs['artists'] = [artist] album_kwargs['artists'] = [artist] if 'album' in data: album_kwargs['name'] = data['album'] album = Album(**album_kwargs) - albums.add(album) track_kwargs['album'] = album if 'title' in data: @@ -213,7 +216,7 @@ def _convert_mpd_data(data, tracks, artists, albums, music_dir): else: path = os.path.join(music_dir, data['file']) - track_kwargs['uri'] = 'file://' + urllib.pathname2url(path) + track_kwargs['uri'] = path_to_uri(path) track_kwargs['length'] = int(data.get('time', 0)) * 1000 track = Track(**track_kwargs) diff --git a/tests/backends/base.py b/tests/backends/base.py index 518f44ee..2974b316 100644 --- a/tests/backends/base.py +++ b/tests/backends/base.py @@ -77,7 +77,8 @@ class BaseCurrentPlaylistControllerTest(object): @populate_playlist def test_get_by_uri_raises_error_for_invalid_id(self): - self.assertRaises(LookupError, lambda: self.controller.get(uri='foobar')) + test = lambda: self.controller.get(uri='foobar') + self.assertRaises(LookupError, test) @populate_playlist def test_clear(self): @@ -232,7 +233,6 @@ class BaseCurrentPlaylistControllerTest(object): @populate_playlist def test_move_group_invalid_group(self): - tracks = len(self.controller.playlist.tracks) test = lambda: self.controller.move(2, 1, 0) self.assertRaises(AssertionError, test) @@ -606,11 +606,11 @@ class BasePlaybackControllerTest(object): self.assertEqual(self.playback.current_playlist_position, None) def test_new_playlist_loaded_callback_gets_called(self): - new_playlist_loaded_callback = self.playback.new_playlist_loaded_callback + callback = self.playback.new_playlist_loaded_callback def wrapper(): wrapper.called = True - return new_playlist_loaded_callback() + return callback() wrapper.called = False self.playback.new_playlist_loaded_callback = wrapper @@ -628,7 +628,7 @@ class BasePlaybackControllerTest(object): event.set() return result - self.playback.end_of_track_callback= wrapper + self.playback.end_of_track_callback = wrapper self.playback.play() self.playback.seek(self.tracks[0].length - 10) @@ -978,6 +978,7 @@ class BaseStoredPlaylistsControllerTest(object): def test_create_in_playlists(self): playlist = self.stored.create('test') self.assert_(self.stored.playlists) + self.assert_(playlist in self.stored.playlists) def test_playlists_empty_to_start_with(self): self.assert_(not self.stored.playlists) @@ -991,7 +992,7 @@ class BaseStoredPlaylistsControllerTest(object): self.assert_(not self.stored.playlists) def test_get_without_criteria(self): - test = lambda: self.stored.get() + test = self.stored.get self.assertRaises(LookupError, test) def test_get_with_wrong_cirteria(self): @@ -1065,15 +1066,15 @@ class BaseStoredPlaylistsControllerTest(object): class BaseLibraryControllerTest(object): - tracks = [] - artists = [Artist(name='artist1'), Artist(name='artist2'), Artist()] albums = [Album(name='album1', artists=artists[:1]), - Album(name='album2', artists=artists[1:2]), - Album()] - tracks = [Track(name='track1', length=4000, artists=artists[:1], album=albums[0], uri='file://' + data_folder('uri1')), - Track(name='track2', length=4000, artists=artists[1:2], album=albums[1], uri='file://' + data_folder('uri2')), - Track()] + Album(name='album2', artists=artists[1:2]), + Album()] + tracks = [Track(name='track1', length=4000, artists=artists[:1], + album=albums[0], uri='file://' + data_folder('uri1')), + Track(name='track2', length=4000, artists=artists[1:2], + album=albums[1], uri='file://' + data_folder('uri2')), + Track()] def setUp(self): self.backend = self.backend_class(mixer=DummyMixer()) diff --git a/tests/backends/gstreamer_test.py b/tests/backends/gstreamer_test.py index 48679cd8..7e682d36 100644 --- a/tests/backends/gstreamer_test.py +++ b/tests/backends/gstreamer_test.py @@ -1,31 +1,35 @@ import unittest import os -import urllib from mopidy import settings from mopidy.backends.gstreamer import GStreamerBackend from mopidy.mixers.dummy import DummyMixer from mopidy.models import Playlist, Track +from mopidy.utils import path_to_uri from tests.backends.base import * from tests import SkipTest, data_folder song = data_folder('song%s.wav') -generate_song = lambda i: 'file://' + urllib.pathname2url(song % i) +generate_song = lambda i: path_to_uri(song % i) # FIXME can be switched to generic test -class GStreamerCurrentPlaylistHandlerTest(BaseCurrentPlaylistControllerTest, unittest.TestCase): - tracks = [Track(uri=generate_song(i), id=i, length=4464) for i in range(1, 4)] +class GStreamerCurrentPlaylistControllerTest(BaseCurrentPlaylistControllerTest, + unittest.TestCase): + tracks = [Track(uri=generate_song(i), id=i, length=4464) + for i in range(1, 4)] backend_class = GStreamerBackend -class GStreamerPlaybackControllerTest(BasePlaybackControllerTest, unittest.TestCase): - tracks = [Track(uri=generate_song(i), id=i, length=4464) for i in range(1, 4)] +class GStreamerPlaybackControllerTest(BasePlaybackControllerTest, + unittest.TestCase): + tracks = [Track(uri=generate_song(i), id=i, length=4464) + for i in range(1, 4)] backend_class = GStreamerBackend - def add_track(self, file): - uri = 'file://' + urllib.pathname2url(data_folder(file)) + def add_track(self, path): + uri = path_to_uri(data_folder(path)) track = Track(uri=uri, id=1, length=4464) self.backend.current_playlist.add(track) @@ -48,7 +52,7 @@ class GStreamerPlaybackControllerTest(BasePlaybackControllerTest, unittest.TestC self.assertEqual(self.playback.state, self.playback.PLAYING) -class GStreamerBackendStoredPlaylistsControllerTest(BaseStoredPlaylistsControllerTest, +class GStreamerStoredPlaylistsControllerTest(BaseStoredPlaylistsControllerTest, unittest.TestCase): backend_class = GStreamerBackend @@ -68,8 +72,8 @@ class GStreamerBackendStoredPlaylistsControllerTest(BaseStoredPlaylistsControlle def test_deleted_playlist_get_removed(self): playlist = self.stored.create('test') self.stored.delete(playlist) - file = os.path.join(settings.PLAYLIST_FOLDER, 'test.m3u') - self.assert_(not os.path.exists(file)) + path = os.path.join(settings.PLAYLIST_FOLDER, 'test.m3u') + self.assert_(not os.path.exists(path)) def test_renamed_playlist_gets_moved(self): playlist = self.stored.create('test') @@ -84,20 +88,18 @@ class GStreamerBackendStoredPlaylistsControllerTest(BaseStoredPlaylistsControlle track = Track(uri=generate_song(1)) uri = track.uri[len('file://'):] playlist = Playlist(tracks=[track], name='test') - file_path = os.path.join(settings.PLAYLIST_FOLDER, 'test.m3u') + path = os.path.join(settings.PLAYLIST_FOLDER, 'test.m3u') self.stored.save(playlist) - with open(file_path) as file: - contents = file.read() + with open(path) as playlist_file: + contents = playlist_file.read() self.assertEqual(uri, contents.strip()) def test_playlists_are_loaded_at_startup(self): track = Track(uri=generate_song(1)) - uri = track.uri[len('file://'):] playlist = Playlist(tracks=[track], name='test') - file_path = os.path.join(settings.PLAYLIST_FOLDER, 'test.m3u') self.stored.save(playlist) @@ -122,7 +124,7 @@ class GStreamerBackendStoredPlaylistsControllerTest(BaseStoredPlaylistsControlle raise SkipTest -class GStreamerBackendLibraryControllerTest(BaseLibraryControllerTest, +class GStreamerLibraryControllerTest(BaseLibraryControllerTest, unittest.TestCase): backend_class = GStreamerBackend @@ -132,12 +134,12 @@ class GStreamerBackendLibraryControllerTest(BaseLibraryControllerTest, self.original_music_folder = settings.MUSIC_FOLDER settings.TAG_CACHE = data_folder('library_tag_cache') settings.MUSIC_FOLDER = data_folder('') - super(GStreamerBackendLibraryControllerTest, self).setUp() + super(GStreamerLibraryControllerTest, self).setUp() def tearDown(self): settings.TAG_CACHE = self.original_tag_cache - settings.MUSIC_FOLDER= self.original_music_folder - super(GStreamerBackendLibraryControllerTest, self).tearDown() + settings.MUSIC_FOLDER = self.original_music_folder + super(GStreamerLibraryControllerTest, self).tearDown() if __name__ == '__main__': unittest.main() diff --git a/tests/models_test.py b/tests/models_test.py index 9d385363..1bafd792 100644 --- a/tests/models_test.py +++ b/tests/models_test.py @@ -165,8 +165,10 @@ class AlbumTest(unittest.TestCase): self.assertNotEqual(hash(album1), hash(album2)) def test_ne(self): - album1 = Album(name=u'name1', uri=u'uri1', artists=[Artist(name=u'name1')], num_tracks=1) - album2 = Album(name=u'name2', uri=u'uri2', artists=[Artist(name=u'name2')], num_tracks=2) + album1 = Album(name=u'name1', uri=u'uri1', + artists=[Artist(name=u'name1')], num_tracks=1) + album2 = Album(name=u'name2', uri=u'uri2', + artists=[Artist(name=u'name2')], num_tracks=2) self.assertNotEqual(album1, album2) self.assertNotEqual(hash(album1), hash(album2)) @@ -221,9 +223,9 @@ class TrackTest(unittest.TestCase): self.assertRaises(AttributeError, setattr, track, 'bitrate', None) def test_id(self): - id = 17 - track = Track(id=id) - self.assertEqual(track.id, id) + track_id = 17 + track = Track(id=track_id) + self.assertEqual(track.id, track_id) self.assertRaises(AttributeError, setattr, track, 'id', None) def test_mpd_format_for_empty_track(self): diff --git a/tests/utils_test.py b/tests/utils_test.py index 63e48201..5ee8797b 100644 --- a/tests/utils_test.py +++ b/tests/utils_test.py @@ -1,21 +1,84 @@ #encoding: utf-8 import os +import sys +import shutil import tempfile import unittest import urllib -from mopidy.utils import parse_m3u, parse_mpd_tag_cache +from mopidy.utils import * from mopidy.models import Track, Artist, Album from tests import SkipTest, data_folder +class GetOrCreateFolderTest(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_folder(self): + folder = os.path.join(self.parent, 'test') + self.assert_(not os.path.exists(folder)) + self.assert_(not os.path.isdir(folder)) + created = get_or_create_folder(folder) + self.assert_(os.path.exists(folder)) + self.assert_(os.path.isdir(folder)) + self.assertEqual(created, folder) + + def test_creating_existing_folder(self): + created = get_or_create_folder(self.parent) + self.assert_(os.path.exists(self.parent)) + self.assert_(os.path.isdir(self.parent)) + self.assertEqual(created, self.parent) + + def test_that_userfolder_is_expanded(self): + raise SkipTest # Not sure how to safely test this + + +class PathToFileURITest(unittest.TestCase): + def test_simple_path(self): + if sys.platform == 'win32': + result = path_to_uri(u'C:/WINDOWS/clock.avi') + self.assertEqual(result, 'file:///C://WINDOWS/clock.avi') + else: + result = path_to_uri(u'/etc/fstab') + self.assertEqual(result, 'file:///etc/fstab') + + def test_folder_and_path(self): + if sys.platform == 'win32': + result = path_to_uri(u'C:/WINDOWS/', u'clock.avi') + self.assertEqual(result, 'file:///C://WINDOWS/clock.avi') + else: + result = path_to_uri(u'/etc', u'fstab') + self.assertEqual(result, u'file:///etc/fstab') + + def test_space_in_path(self): + if sys.platform == 'win32': + result = path_to_uri(u'C:/test this') + self.assertEqual(result, 'file:///C://test%20this') + else: + result = path_to_uri(u'/tmp/test this') + self.assertEqual(result, u'file:///tmp/test%20this') + + def test_unicode_in_path(self): + if sys.platform == 'win32': + result = path_to_uri(u'C:/æøå') + self.assertEqual(result, 'file:///C://%C3%A6%C3%B8%C3%A5') + else: + result = path_to_uri(u'/tmp/æøå') + self.assertEqual(result, u'file:///tmp/%C3%A6%C3%B8%C3%A5') + + song1_path = data_folder('song1.mp3') song2_path = data_folder('song2.mp3') encoded_path = data_folder(u'æøå.mp3') -song1_uri = 'file://' + urllib.pathname2url(song1_path) -song2_uri = 'file://' + urllib.pathname2url(song2_path) -encoded_uri = 'file://' + urllib.pathname2url(encoded_path.encode('utf-8')) +song1_uri = path_to_uri(song1_path) +song2_uri = path_to_uri(song2_path) +encoded_uri = path_to_uri(encoded_path) class M3UToUriTest(unittest.TestCase): @@ -32,27 +95,37 @@ class M3UToUriTest(unittest.TestCase): self.assertEqual([song1_uri], uris) def test_file_with_absolute_files(self): - with tempfile.NamedTemporaryFile() as file: - file.write(song1_path) - file.flush() - uris = parse_m3u(file.name) - self.assertEqual([song1_uri], uris) + with tempfile.NamedTemporaryFile(delete=False) as tmp: + tmp.write(song1_path) + try: + uris = parse_m3u(tmp.name) + self.assertEqual([song1_uri], uris) + finally: + if os.path.exists(tmp.name): + os.remove(tmp.name) def test_file_with_multiple_absolute_files(self): - with tempfile.NamedTemporaryFile() as file: - file.write(song1_path+'\n') - file.write('# comment \n') - file.write(song2_path) - file.flush() - uris = parse_m3u(file.name) - self.assertEqual([song1_uri, song2_uri], uris) + with tempfile.NamedTemporaryFile(delete=False) as tmp: + tmp.write(song1_path+'\n') + tmp.write('# comment \n') + tmp.write(song2_path) + try: + uris = parse_m3u(tmp.name) + self.assertEqual([song1_uri, song2_uri], uris) + finally: + if os.path.exists(tmp.name): + os.remove(tmp.name) + def test_file_with_uri(self): - with tempfile.NamedTemporaryFile() as file: - file.write(song1_uri) - file.flush() - uris = parse_m3u(file.name) - self.assertEqual([song1_uri], uris) + with tempfile.NamedTemporaryFile(delete=False) as tmp: + tmp.write(song1_uri) + try: + uris = parse_m3u(tmp.name) + self.assertEqual([song1_uri], uris) + finally: + if os.path.exists(tmp.name): + os.remove(tmp.name) def test_encoding_is_latin1(self): uris = parse_m3u(data_folder('encoding.m3u')) @@ -67,11 +140,12 @@ class URItoM3UTest(unittest.TestCase): pass expected_artists = [Artist(name='name')] -expected_albums = [Album(name='albumname', artists=expected_artists, num_tracks=2)] +expected_albums = [Album(name='albumname', artists=expected_artists, + num_tracks=2)] expected_tracks = [] def generate_track(path): - uri = 'file://' + urllib.pathname2url(data_folder(path)) + uri = path_to_uri(data_folder(path)) track = Track(name='trackname', artists=expected_artists, track_no=1, album=expected_albums[0], length=4000, uri=uri) expected_tracks.append(track) @@ -88,27 +162,19 @@ generate_track('subdir1/subsubdir/song9.mp3') class MPDTagCacheToTracksTest(unittest.TestCase): def test_emtpy_cache(self): - tracks, artists, albums = parse_mpd_tag_cache(data_folder('empty_tag_cache'), + tracks = parse_mpd_tag_cache(data_folder('empty_tag_cache'), data_folder('')) self.assertEqual(set(), tracks) - self.assertEqual(set(), artists) - self.assertEqual(set(), albums) def test_simple_cache(self): - tracks, artists, albums = parse_mpd_tag_cache(data_folder('simple_tag_cache'), + tracks = parse_mpd_tag_cache(data_folder('simple_tag_cache'), data_folder('')) - self.assertEqual(expected_tracks[0], list(tracks)[0]) - self.assertEqual(set(expected_artists), artists) - self.assertEqual(set(expected_albums), albums) def test_advanced_cache(self): - tracks, artists, albums = parse_mpd_tag_cache(data_folder('advanced_tag_cache'), + tracks = parse_mpd_tag_cache(data_folder('advanced_tag_cache'), data_folder('')) - self.assertEqual(set(expected_tracks), tracks) - self.assertEqual(set(expected_artists), artists) - self.assertEqual(set(expected_albums), albums) def test_unicode_cache(self): raise SkipTest @@ -118,12 +184,7 @@ class MPDTagCacheToTracksTest(unittest.TestCase): raise SkipTest def test_cache_with_blank_track_info(self): - tracks, artists, albums = parse_mpd_tag_cache(data_folder('blank_tag_cache'), + tracks = parse_mpd_tag_cache(data_folder('blank_tag_cache'), data_folder('')) - - uri = 'file://' + urllib.pathname2url(data_folder('song1.mp3')) - + uri = path_to_uri(data_folder('song1.mp3')) self.assertEqual(set([Track(uri=uri, length=4000)]), tracks) - self.assertEqual(set(), artists) - self.assertEqual(set(), albums) -