diff --git a/docs/changes.rst b/docs/changes.rst index 43b930b8..27b8731b 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -63,6 +63,16 @@ v0.8 (in development) - Support tracks with only release year, and not a full release date, like e.g. Spotify tracks. +- Default value of ``LOCAL_MUSIC_PATH`` has been updated to be + ``$XDG_MUSIC_DIR``, which on most systems this is set to ``$HOME``. Users of + local backend that relied on the old default ``~/music`` need to update their + settings. Note that the code responsible for finding this music now also + ignores UNIX hidden files and folders. + +- File and path settings now support ``$XDG_CACHE_DIR``, ``$XDG_DATA_DIR`` and + ``$XDG_MUSIC_DIR`` substitution. Defaults for such settings have been updated + to use this instead of hidden away defaults. + **Bug fixes** - :issue:`72`: Created a Spotify track proxy that will switch to using loaded @@ -80,6 +90,12 @@ v0.8 (in development) - Fixed crash on lookup of unknown path when using local backend. +- :issue:`189` ``LOCAL_MUSIC_PATH`` and path handling in rest of settings has + been updated so all of the code now uses the correct value. + +- Fixed incorrect track URIs generated by ``parse_m3u`` code, generated tracks + are now relative to ``LOCAL_MUSIC_PATH``. + v0.7.3 (2012-08-11) =================== diff --git a/mopidy/backends/local/__init__.py b/mopidy/backends/local/__init__.py index c7126824..db86e56f 100644 --- a/mopidy/backends/local/__init__.py +++ b/mopidy/backends/local/__init__.py @@ -7,7 +7,7 @@ import shutil from pykka.actor import ThreadingActor from pykka.registry import ActorRegistry -from mopidy import audio, core, settings, DATA_PATH +from mopidy import audio, core, settings from mopidy.backends import base from mopidy.models import Playlist, Track, Album @@ -15,13 +15,6 @@ from .translator import parse_m3u, parse_mpd_tag_cache logger = logging.getLogger(u'mopidy.backends.local') -DEFAULT_PLAYLIST_PATH = os.path.join(DATA_PATH, 'playlists') -DEFAULT_TAG_CACHE_FILE = os.path.join(DATA_PATH, 'tag_cache') -DEFAULT_MUSIC_PATH = str(glib.get_user_special_dir(glib.USER_DIRECTORY_MUSIC)) - -if not DEFAULT_MUSIC_PATH or DEFAULT_MUSIC_PATH == os.path.expanduser(u'~'): - DEFAULT_MUSIC_PATH = os.path.expanduser(u'~/music') - class LocalBackend(ThreadingActor, base.Backend): """ @@ -81,7 +74,7 @@ class LocalPlaybackController(core.PlaybackController): class LocalStoredPlaylistsProvider(base.BaseStoredPlaylistsProvider): def __init__(self, *args, **kwargs): super(LocalStoredPlaylistsProvider, self).__init__(*args, **kwargs) - self._folder = settings.LOCAL_PLAYLIST_PATH or DEFAULT_PLAYLIST_PATH + self._folder = settings.LOCAL_PLAYLIST_PATH self.refresh() def lookup(self, uri): @@ -95,7 +88,7 @@ class LocalStoredPlaylistsProvider(base.BaseStoredPlaylistsProvider): for m3u in glob.glob(os.path.join(self._folder, '*.m3u')): name = os.path.basename(m3u)[:-len('.m3u')] tracks = [] - for uri in parse_m3u(m3u): + for uri in parse_m3u(m3u, settings.LOCAL_MUSIC_PATH): try: tracks.append(self.backend.library.lookup(uri)) except LookupError, e: @@ -158,12 +151,11 @@ class LocalLibraryProvider(base.BaseLibraryProvider): self.refresh() def refresh(self, uri=None): - tag_cache = settings.LOCAL_TAG_CACHE_FILE or DEFAULT_TAG_CACHE_FILE - music_folder = settings.LOCAL_MUSIC_PATH or DEFAULT_MUSIC_PATH + tracks = parse_mpd_tag_cache(settings.LOCAL_TAG_CACHE_FILE, + settings.LOCAL_MUSIC_PATH) - tracks = parse_mpd_tag_cache(tag_cache, music_folder) - - logger.info('Loading tracks in %s from %s', music_folder, tag_cache) + logger.info('Loading tracks in %s from %s', settings.LOCAL_MUSIC_PATH, + settings.LOCAL_TAG_CACHE_FILE) for track in tracks: self._uri_mapping[track.uri] = track diff --git a/mopidy/backends/local/translator.py b/mopidy/backends/local/translator.py index 3b610a94..1fea555c 100644 --- a/mopidy/backends/local/translator.py +++ b/mopidy/backends/local/translator.py @@ -7,7 +7,7 @@ from mopidy.models import Track, Artist, Album from mopidy.utils import locale_decode from mopidy.utils.path import path_to_uri -def parse_m3u(file_path): +def parse_m3u(file_path, music_folder): """ Convert M3U file list of uris @@ -29,8 +29,6 @@ def parse_m3u(file_path): """ uris = [] - folder = os.path.dirname(file_path) - try: with open(file_path) as m3u: contents = m3u.readlines() @@ -48,7 +46,7 @@ def parse_m3u(file_path): if line.startswith('file://'): uris.append(line) else: - path = path_to_uri(folder, line) + path = path_to_uri(music_folder, line) uris.append(path) return uris diff --git a/mopidy/backends/spotify/session_manager.py b/mopidy/backends/spotify/session_manager.py index aa3734ae..856257f1 100644 --- a/mopidy/backends/spotify/session_manager.py +++ b/mopidy/backends/spotify/session_manager.py @@ -6,7 +6,7 @@ from spotify.manager import SpotifySessionManager as PyspotifySessionManager from pykka.registry import ActorRegistry -from mopidy import audio, get_version, settings, CACHE_PATH +from mopidy import audio, get_version, settings from mopidy.backends.base import Backend from mopidy.backends.spotify import BITRATES from mopidy.backends.spotify.container_manager import SpotifyContainerManager @@ -22,8 +22,7 @@ logger = logging.getLogger('mopidy.backends.spotify.session_manager') class SpotifySessionManager(BaseThread, PyspotifySessionManager): - cache_location = (settings.SPOTIFY_CACHE_PATH - or os.path.join(CACHE_PATH, 'spotify')) + cache_location = settings.SPOTIFY_CACHE_PATH settings_location = cache_location appkey_file = os.path.join(os.path.dirname(__file__), 'spotify_appkey.key') user_agent = 'Mopidy %s' % get_version() diff --git a/mopidy/scanner.py b/mopidy/scanner.py index 3bcf03d9..29511c80 100644 --- a/mopidy/scanner.py +++ b/mopidy/scanner.py @@ -52,7 +52,7 @@ def translator(data): class Scanner(object): def __init__(self, folder, data_callback, error_callback=None): - self.uris = [path_to_uri(f) for f in find_files(folder)] + self.files = find_files(folder) self.data_callback = data_callback self.error_callback = error_callback self.loop = gobject.MainLoop() @@ -114,18 +114,19 @@ class Scanner(object): return None def next_uri(self): - if not self.uris: - return self.stop() - + try: + uri = path_to_uri(self.files.next()) + except StopIteration: + self.stop() + return False self.pipe.set_state(gst.STATE_NULL) - self.uribin.set_property('uri', self.uris.pop()) + self.uribin.set_property('uri', uri) self.pipe.set_state(gst.STATE_PAUSED) + return True def start(self): - if not self.uris: - return - self.next_uri() - self.loop.run() + if self.next_uri(): + self.loop.run() def stop(self): self.pipe.set_state(gst.STATE_NULL) diff --git a/mopidy/settings.py b/mopidy/settings.py index 0612fc24..a2270707 100644 --- a/mopidy/settings.py +++ b/mopidy/settings.py @@ -85,9 +85,8 @@ LASTFM_PASSWORD = u'' #: #: Default:: #: -#: # Defaults to asking glib where music is stored, fallback is ~/music -#: LOCAL_MUSIC_PATH = None -LOCAL_MUSIC_PATH = None +#: LOCAL_MUSIC_PATH = u'$XDG_MUSIC_DIR' +LOCAL_MUSIC_PATH = u'$XDG_MUSIC_DIR' #: Path to playlist folder with m3u files for local music. #: @@ -95,8 +94,8 @@ LOCAL_MUSIC_PATH = None #: #: Default:: #: -#: LOCAL_PLAYLIST_PATH = None # Implies $XDG_DATA_DIR/mopidy/playlists -LOCAL_PLAYLIST_PATH = None +#: LOCAL_PLAYLIST_PATH = u'$XDG_DATA_DIR/mopidy/playlists' +LOCAL_PLAYLIST_PATH = u'$XDG_DATA_DIR/mopidy/playlists' #: Path to tag cache for local music. #: @@ -104,8 +103,8 @@ LOCAL_PLAYLIST_PATH = None #: #: Default:: #: -#: LOCAL_TAG_CACHE_FILE = None # Implies $XDG_DATA_DIR/mopidy/tag_cache -LOCAL_TAG_CACHE_FILE = None +#: LOCAL_TAG_CACHE_FILE = u'$XDG_DATA_DIR/mopidy/tag_cache' +LOCAL_TAG_CACHE_FILE = u'$XDG_DATA_DIR/mopidy/tag_cache' #: Sound mixer to use. #: @@ -177,7 +176,11 @@ OUTPUT = u'autoaudiosink' #: Path to the Spotify cache. #: #: Used by :mod:`mopidy.backends.spotify`. -SPOTIFY_CACHE_PATH = None +#: +#: Default:: +#: +#: SPOTIFY_CACHE_PATH = u'$XDG_CACHE_DIR/mopidy/spotify' +SPOTIFY_CACHE_PATH = u'$XDG_CACHE_DIR/mopidy/spotify' #: Your Spotify Premium username. #: @@ -194,7 +197,7 @@ SPOTIFY_PASSWORD = u'' #: Available values are 96, 160, and 320. #: #: Used by :mod:`mopidy.backends.spotify`. -# +#: #: Default:: #: #: SPOTIFY_BITRATE = 160 diff --git a/mopidy/utils/path.py b/mopidy/utils/path.py index 5d99ac12..7f1b9233 100644 --- a/mopidy/utils/path.py +++ b/mopidy/utils/path.py @@ -1,11 +1,20 @@ +import glib import logging import os -import sys import re +import string +import sys import urllib logger = logging.getLogger('mopidy.utils.path') +XDG_DIRS = { + 'XDG_CACHE_DIR': glib.get_user_cache_dir(), + 'XDG_DATA_DIR': glib.get_user_data_dir(), + 'XDG_MUSIC_DIR': glib.get_user_special_dir(glib.USER_DIRECTORY_MUSIC), +} + + def get_or_create_folder(folder): folder = os.path.expanduser(folder) if os.path.isfile(folder): @@ -16,6 +25,7 @@ def get_or_create_folder(folder): os.makedirs(folder, 0755) return folder + def get_or_create_file(filename): filename = os.path.expanduser(filename) if not os.path.isfile(filename): @@ -23,6 +33,7 @@ def get_or_create_file(filename): open(filename, 'w') return filename + def path_to_uri(*paths): path = os.path.join(*paths) path = path.encode('utf-8') @@ -30,6 +41,7 @@ def path_to_uri(*paths): return 'file:' + urllib.pathname2url(path) return 'file://' + urllib.pathname2url(path) + def uri_to_path(uri): if sys.platform == 'win32': path = urllib.url2pathname(re.sub('^file:', '', uri)) @@ -37,6 +49,7 @@ def uri_to_path(uri): path = urllib.url2pathname(re.sub('^file://', '', uri)) return path.encode('latin1').decode('utf-8') # Undo double encoding + def split_path(path): parts = [] while True: @@ -47,21 +60,40 @@ def split_path(path): break return parts -# pylint: disable = W0612 -# Unused variable 'dirnames' + +def expand_path(path): + path = string.Template(path).safe_substitute(XDG_DIRS) + path = os.path.expanduser(path) + path = os.path.abspath(path) + return path + + def find_files(path): if os.path.isfile(path): if not isinstance(path, unicode): path = path.decode('utf-8') - yield path + if not os.path.basename(path).startswith('.'): + yield path else: for dirpath, dirnames, filenames in os.walk(path): + # Filter out hidden folders by modifying dirnames in place. + for dirname in dirnames: + if dirname.startswith('.'): + dirnames.remove(dirname) + for filename in filenames: + # Skip hidden files. + if filename.startswith('.'): + continue + filename = os.path.join(dirpath, filename) if not isinstance(filename, unicode): - filename = filename.decode('utf-8') + try: + filename = filename.decode('utf-8') + except UnicodeDecodeError: + filename = filename.decode('latin1') yield filename -# pylint: enable = W0612 + # FIXME replace with mock usage in tests. class Mtime(object): diff --git a/mopidy/utils/settings.py b/mopidy/utils/settings.py index 726917c6..e6c35ce1 100644 --- a/mopidy/utils/settings.py +++ b/mopidy/utils/settings.py @@ -1,14 +1,16 @@ # Absolute import needed to import ~/.mopidy/settings.py and not ourselves from __future__ import absolute_import -from copy import copy + +import copy import getpass import logging import os -from pprint import pformat +import pprint import sys from mopidy import SettingsError, SETTINGS_PATH, SETTINGS_FILE -from mopidy.utils.log import indent +from mopidy.utils import log +from mopidy.utils import path logger = logging.getLogger('mopidy.utils.settings') @@ -39,7 +41,7 @@ class SettingsProxy(object): @property def current(self): - current = copy(self.default) + current = copy.copy(self.default) current.update(self.local) current.update(self.runtime) return current @@ -47,16 +49,18 @@ class SettingsProxy(object): def __getattr__(self, attr): if not self._is_setting(attr): return - if attr not in self.current: + + current = self.current # bind locally to avoid copying+updates + if attr not in current: raise SettingsError(u'Setting "%s" is not set.' % attr) - value = self.current[attr] + + value = current[attr] if isinstance(value, basestring) and len(value) == 0: raise SettingsError(u'Setting "%s" is empty.' % attr) if not value: return value if attr.endswith('_PATH') or attr.endswith('_FILE'): - value = os.path.expanduser(value) - value = os.path.abspath(value) + value = path.expand_path(value) return value def __setattr__(self, attr, value): @@ -70,7 +74,7 @@ class SettingsProxy(object): self._read_missing_settings_from_stdin(self.current, self.runtime) if self.get_errors(): logger.error(u'Settings validation errors: %s', - indent(self.get_errors_as_string())) + log.indent(self.get_errors_as_string())) raise SettingsError(u'Settings validation failed.') def _read_missing_settings_from_stdin(self, current, runtime): @@ -194,10 +198,11 @@ def format_settings_list(settings): for (key, value) in sorted(settings.current.iteritems()): default_value = settings.default.get(key) masked_value = mask_value_if_secret(key, value) - lines.append(u'%s: %s' % (key, indent(pformat(masked_value), places=2))) + lines.append(u'%s: %s' % (key, log.indent( + pprint.pformat(masked_value), places=2))) if value != default_value and default_value is not None: lines.append(u' Default: %s' % - indent(pformat(default_value), places=4)) + log.indent(pformat(default_value), places=4)) if errors.get(key) is not None: lines.append(u' Error: %s' % errors[key]) return '\n'.join(lines) diff --git a/tests/backends/local/translator_test.py b/tests/backends/local/translator_test.py index 1dceb737..08f29c1b 100644 --- a/tests/backends/local/translator_test.py +++ b/tests/backends/local/translator_test.py @@ -9,6 +9,7 @@ from mopidy.models import Track, Artist, Album from tests import unittest, path_to_data_dir +data_dir = path_to_data_dir('') song1_path = path_to_data_dir('song1.mp3') song2_path = path_to_data_dir('song2.mp3') encoded_path = path_to_data_dir(u'æøå.mp3') @@ -21,22 +22,32 @@ encoded_uri = path_to_uri(encoded_path) class M3UToUriTest(unittest.TestCase): def test_empty_file(self): - uris = parse_m3u(path_to_data_dir('empty.m3u')) + uris = parse_m3u(path_to_data_dir('empty.m3u'), data_dir) self.assertEqual([], uris) def test_basic_file(self): - uris = parse_m3u(path_to_data_dir('one.m3u')) + uris = parse_m3u(path_to_data_dir('one.m3u'), data_dir) self.assertEqual([song1_uri], uris) def test_file_with_comment(self): - uris = parse_m3u(path_to_data_dir('comment.m3u')) + uris = parse_m3u(path_to_data_dir('comment.m3u'), data_dir) self.assertEqual([song1_uri], uris) + def test_file_is_relative_to_correct_folder(self): + with tempfile.NamedTemporaryFile(delete=False) as tmp: + tmp.write('song1.mp3') + try: + uris = parse_m3u(tmp.name, data_dir) + self.assertEqual([song1_uri], uris) + finally: + if os.path.exists(tmp.name): + os.remove(tmp.name) + def test_file_with_absolute_files(self): with tempfile.NamedTemporaryFile(delete=False) as tmp: tmp.write(song1_path) try: - uris = parse_m3u(tmp.name) + uris = parse_m3u(tmp.name, data_dir) self.assertEqual([song1_uri], uris) finally: if os.path.exists(tmp.name): @@ -48,29 +59,28 @@ class M3UToUriTest(unittest.TestCase): tmp.write('# comment \n') tmp.write(song2_path) try: - uris = parse_m3u(tmp.name) + uris = parse_m3u(tmp.name, data_dir) 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(delete=False) as tmp: tmp.write(song1_uri) try: - uris = parse_m3u(tmp.name) + uris = parse_m3u(tmp.name, data_dir) 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(path_to_data_dir('encoding.m3u')) + uris = parse_m3u(path_to_data_dir('encoding.m3u'), data_dir) self.assertEqual([encoded_uri], uris) def test_open_missing_file(self): - uris = parse_m3u(path_to_data_dir('non-existant.m3u')) + uris = parse_m3u(path_to_data_dir('non-existant.m3u'), data_dir) self.assertEqual([], uris) diff --git a/tests/data/.blank.mp3 b/tests/data/.blank.mp3 new file mode 100644 index 00000000..ef159a70 Binary files /dev/null and b/tests/data/.blank.mp3 differ diff --git a/tests/data/.hidden/.gitignore b/tests/data/.hidden/.gitignore new file mode 100644 index 00000000..e69de29b diff --git a/tests/utils/path_test.py b/tests/utils/path_test.py index 19bae375..d782aa15 100644 --- a/tests/utils/path_test.py +++ b/tests/utils/path_test.py @@ -1,12 +1,12 @@ # encoding: utf-8 +import glib import os import shutil import sys import tempfile -from mopidy.utils.path import (get_or_create_folder, mtime, - path_to_uri, uri_to_path, split_path, find_files) +from mopidy.utils import path from tests import unittest, path_to_data_dir @@ -23,7 +23,7 @@ class GetOrCreateFolderTest(unittest.TestCase): 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) + created = path.get_or_create_folder(folder) self.assert_(os.path.exists(folder)) self.assert_(os.path.isdir(folder)) self.assertEqual(created, folder) @@ -35,7 +35,7 @@ class GetOrCreateFolderTest(unittest.TestCase): self.assert_(not os.path.isdir(level2_folder)) self.assert_(not os.path.exists(level3_folder)) self.assert_(not os.path.isdir(level3_folder)) - created = get_or_create_folder(level3_folder) + created = path.get_or_create_folder(level3_folder) self.assert_(os.path.exists(level2_folder)) self.assert_(os.path.isdir(level2_folder)) self.assert_(os.path.exists(level3_folder)) @@ -43,7 +43,7 @@ class GetOrCreateFolderTest(unittest.TestCase): self.assertEqual(created, level3_folder) def test_creating_existing_folder(self): - created = get_or_create_folder(self.parent) + created = path.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) @@ -52,92 +52,116 @@ class GetOrCreateFolderTest(unittest.TestCase): conflicting_file = os.path.join(self.parent, 'test') open(conflicting_file, 'w').close() folder = os.path.join(self.parent, 'test') - self.assertRaises(OSError, get_or_create_folder, folder) + self.assertRaises(OSError, path.get_or_create_folder, folder) class PathToFileURITest(unittest.TestCase): def test_simple_path(self): if sys.platform == 'win32': - result = path_to_uri(u'C:/WINDOWS/clock.avi') + result = path.path_to_uri(u'C:/WINDOWS/clock.avi') self.assertEqual(result, 'file:///C://WINDOWS/clock.avi') else: - result = path_to_uri(u'/etc/fstab') + result = path.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') + result = path.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') + result = path.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') + result = path.path_to_uri(u'C:/test this') self.assertEqual(result, 'file:///C://test%20this') else: - result = path_to_uri(u'/tmp/test this') + result = path.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:/æøå') + result = path.path_to_uri(u'C:/æøå') self.assertEqual(result, 'file:///C://%C3%A6%C3%B8%C3%A5') else: - result = path_to_uri(u'/tmp/æøå') + result = path.path_to_uri(u'/tmp/æøå') self.assertEqual(result, u'file:///tmp/%C3%A6%C3%B8%C3%A5') class UriToPathTest(unittest.TestCase): def test_simple_uri(self): if sys.platform == 'win32': - result = uri_to_path('file:///C://WINDOWS/clock.avi') + result = path.uri_to_path('file:///C://WINDOWS/clock.avi') self.assertEqual(result, u'C:/WINDOWS/clock.avi') else: - result = uri_to_path('file:///etc/fstab') + result = path.uri_to_path('file:///etc/fstab') self.assertEqual(result, u'/etc/fstab') def test_space_in_uri(self): if sys.platform == 'win32': - result = uri_to_path('file:///C://test%20this') + result = path.uri_to_path('file:///C://test%20this') self.assertEqual(result, u'C:/test this') else: - result = uri_to_path(u'file:///tmp/test%20this') + result = path.uri_to_path(u'file:///tmp/test%20this') self.assertEqual(result, u'/tmp/test this') def test_unicode_in_uri(self): if sys.platform == 'win32': - result = uri_to_path( 'file:///C://%C3%A6%C3%B8%C3%A5') + result = path.uri_to_path( 'file:///C://%C3%A6%C3%B8%C3%A5') self.assertEqual(result, u'C:/æøå') else: - result = uri_to_path(u'file:///tmp/%C3%A6%C3%B8%C3%A5') + result = path.uri_to_path(u'file:///tmp/%C3%A6%C3%B8%C3%A5') self.assertEqual(result, u'/tmp/æøå') class SplitPathTest(unittest.TestCase): def test_empty_path(self): - self.assertEqual([], split_path('')) + self.assertEqual([], path.split_path('')) def test_single_folder(self): - self.assertEqual(['foo'], split_path('foo')) + self.assertEqual(['foo'], path.split_path('foo')) def test_folders(self): - self.assertEqual(['foo', 'bar', 'baz'], split_path('foo/bar/baz')) + self.assertEqual(['foo', 'bar', 'baz'], path.split_path('foo/bar/baz')) def test_folders(self): - self.assertEqual(['foo', 'bar', 'baz'], split_path('foo/bar/baz')) + self.assertEqual(['foo', 'bar', 'baz'], path.split_path('foo/bar/baz')) def test_initial_slash_is_ignored(self): - self.assertEqual(['foo', 'bar', 'baz'], split_path('/foo/bar/baz')) + self.assertEqual(['foo', 'bar', 'baz'], path.split_path('/foo/bar/baz')) def test_only_slash(self): - self.assertEqual([], split_path('/')) + self.assertEqual([], path.split_path('/')) + + +class ExpandPathTest(unittest.TestCase): + # TODO: test via mocks? + + def test_empty_path(self): + self.assertEqual(os.path.abspath('.'), path.expand_path('')) + + def test_absolute_path(self): + self.assertEqual('/tmp/foo', path.expand_path('/tmp/foo')) + + def test_home_dir_expansion(self): + self.assertEqual(os.path.expanduser('~/foo'), path.expand_path('~/foo')) + + def test_abspath(self): + self.assertEqual(os.path.abspath('./foo'), path.expand_path('./foo')) + + def test_xdg_subsititution(self): + self.assertEqual(glib.get_user_data_dir() + '/foo', + path.expand_path('$XDG_DATA_DIR/foo')) + + def test_xdg_subsititution_unknown(self): + self.assertEqual('/tmp/$XDG_INVALID_DIR/foo', + path.expand_path('/tmp/$XDG_INVALID_DIR/foo')) class FindFilesTest(unittest.TestCase): - def find(self, path): - return list(find_files(path_to_data_dir(path))) + def find(self, value): + return list(path.find_files(path_to_data_dir(value))) def test_basic_folder(self): self.assert_(self.find('')) @@ -156,15 +180,21 @@ class FindFilesTest(unittest.TestCase): self.assert_(is_unicode(name), '%s is not unicode object' % repr(name)) + def test_ignores_hidden_folders(self): + self.assertEqual(self.find('.hidden'), []) + + def test_ignores_hidden_files(self): + self.assertEqual(self.find('.blank.mp3'), []) + class MtimeTest(unittest.TestCase): def tearDown(self): - mtime.undo_fake() + path.mtime.undo_fake() def test_mtime_of_current_dir(self): mtime_dir = int(os.stat('.').st_mtime) - self.assertEqual(mtime_dir, mtime('.')) + self.assertEqual(mtime_dir, path.mtime('.')) def test_fake_time_is_returned(self): - mtime.set_fake_time(123456) - self.assertEqual(mtime('.'), 123456) + path.mtime.set_fake_time(123456) + self.assertEqual(path.mtime('.'), 123456)