Merge pull request #200 from adamcik/feature/local-scanner-fixes

Fix for #189 and other cleanups
This commit is contained in:
Stein Magnus Jodal 2012-09-16 14:04:06 -07:00
commit 6b01e612a3
12 changed files with 184 additions and 98 deletions

View File

@ -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)
===================

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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)

View File

@ -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

View File

@ -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):

View File

@ -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)

View File

@ -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)

BIN
tests/data/.blank.mp3 Normal file

Binary file not shown.

0
tests/data/.hidden/.gitignore vendored Normal file
View File

View File

@ -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)