Merge pull request #452 from adamcik/feature/mtime

Add last modified time to tracks and make scanner mtime aware.
This commit is contained in:
Stein Magnus Jodal 2013-05-20 06:57:53 -07:00
commit f91e621c40
5 changed files with 68 additions and 18 deletions

View File

@ -112,6 +112,9 @@ def _convert_mpd_data(data, tracks, music_dir):
else:
track_kwargs['track_no'] = int(data['track'])
if 'mtime' in data:
track_kwargs['last_modified'] = int(data['mtime'])
if 'artist' in data:
artist_kwargs['name'] = data['artist']
albumartist_kwargs['name'] = data['artist']

View File

@ -231,6 +231,8 @@ class Track(ImmutableObject):
:type bitrate: integer
:param musicbrainz_id: MusicBrainz ID
:type musicbrainz_id: string
:param last_modified: Represents last modification time
:type last_modified: integer
"""
#: The track URI. Read-only.
@ -263,6 +265,11 @@ class Track(ImmutableObject):
#: The MusicBrainz ID of the track. Read-only.
musicbrainz_id = None
#: Integer representing when the track was last modified, exact meaning
#: depends on source of track. For local files this is the mtime, for other
#: backends it could be a timestamp or simply a version counter.
last_modified = 0
def __init__(self, *args, **kwargs):
self.__dict__['artists'] = frozenset(kwargs.pop('artists', []))
super(Track, self).__init__(*args, **kwargs)

View File

@ -27,6 +27,7 @@ pygst.require('0.10')
import gst
from mopidy import config as config_lib, ext
from mopidy.audio import dummy as dummy_audio
from mopidy.frontends.mpd import translator as mpd_translator
from mopidy.models import Track, Artist, Album
from mopidy.utils import log, path, versioning
@ -45,9 +46,9 @@ def main():
log.setup_root_logger()
log.setup_console_logging(logging_config, args.verbosity_level)
extensions = ext.load_extensions()
extensions = dict((e.ext_name, e) for e in ext.load_extensions())
config, errors = config_lib.load(
config_files, extensions, config_overrides)
config_files, extensions.values(), config_overrides)
log.setup_log_levels(config)
if not config['local']['media_dir']:
@ -56,20 +57,48 @@ def main():
# TODO: missing error checking and other default setup code.
tracks = []
audio = dummy_audio.DummyAudio()
local_backend_classes = extensions['local'].get_backend_classes()
local_backend = local_backend_classes[0](config, audio)
tracks = {} # Current lib.
update = [] # Paths to rescan for updates/adds.
remove = [] # Paths to delete from lib.
for track in local_backend.library.search().tracks:
tracks[track.uri] = track
logging.info('Checking %d files from library.', len(tracks))
for track in tracks.itervalues():
try:
stat = os.stat(path.uri_to_path(track.uri))
if int(stat.st_mtime) > track.last_modified:
update.append(track.uri)
except OSError:
remove.append(track.uri)
logging.info('Removing %d files from library.', len(remove))
for uri in remove:
del tracks[uri]
logging.info('Checking %s for changes.', config['local']['media_dir'])
for p in path.find_files(config['local']['media_dir']):
uri = path.path_to_uri(p)
if uri not in tracks:
update.append(uri)
def store(data):
track = translator(data)
tracks.append(track)
tracks[track.uri] = track
logging.debug('Added %s', track.uri)
def debug(uri, error, debug):
logging.warning('Failed %s: %s', uri, error)
logging.debug('Debug info for %s: %s', uri, debug)
logging.info('Scanning %s', config['local']['media_dir'])
scanner = Scanner(config['local']['media_dir'], store, debug)
logging.info('Scanning %d new/changed files.', len(update))
scanner = Scanner(update, store, debug)
try:
scanner.start()
except KeyboardInterrupt:
@ -78,7 +107,7 @@ def main():
logging.info('Done scanning; writing tag cache...')
for row in mpd_translator.tracks_to_tag_cache_format(
tracks, config['local']['media_dir']):
tracks.values(), config['local']['media_dir']):
if len(row) == 1:
print ('%s' % row).encode('utf-8')
else:
@ -141,6 +170,7 @@ def translator(data):
album_kwargs['artists'] = [Artist(**albumartist_kwargs)]
track_kwargs['uri'] = data['uri']
track_kwargs['last_modified'] = int(data['mtime'])
track_kwargs['length'] = data[gst.TAG_DURATION]
track_kwargs['album'] = Album(**album_kwargs)
track_kwargs['artists'] = [Artist(**artist_kwargs)]
@ -149,9 +179,9 @@ def translator(data):
class Scanner(object):
def __init__(self, base_dir, data_callback, error_callback=None):
def __init__(self, uris, data_callback, error_callback=None):
self.data = {}
self.files = path.find_files(base_dir)
self.uris = iter(uris)
self.data_callback = data_callback
self.error_callback = error_callback
self.loop = gobject.MainLoop()
@ -195,7 +225,9 @@ class Scanner(object):
if message.structure.get_name() != 'handoff':
return
self.data['uri'] = unicode(self.uribin.get_property('uri'))
uri = unicode(self.uribin.get_property('uri'))
self.data['uri'] = uri
self.data['mtime'] = os.path.getmtime(path.uri_to_path(uri))
self.data[gst.TAG_DURATION] = self.get_duration()
try:
@ -234,7 +266,7 @@ class Scanner(object):
def next_uri(self):
self.data = {}
try:
uri = path.path_to_uri(self.files.next())
uri = next(self.uris)
except StopIteration:
self.stop()
return False

View File

@ -101,7 +101,8 @@ def generate_track(path, ident):
uri = path_to_uri(path_to_data_dir(path))
track = Track(
uri=uri, name='trackname', artists=expected_artists,
album=expected_albums[0], track_no=1, date='2006', length=4000)
album=expected_albums[0], track_no=1, date='2006', length=4000,
last_modified=1272319626)
expected_tracks.append(track)
@ -128,7 +129,8 @@ class MPDTagCacheToTracksTest(unittest.TestCase):
uri = path_to_uri(path_to_data_dir('song1.mp3'))
track = Track(
uri=uri, name='trackname', artists=expected_artists, track_no=1,
album=expected_albums[0], date='2006', length=4000)
album=expected_albums[0], date='2006', length=4000,
last_modified=1272319626)
self.assertEqual(set([track]), tracks)
def test_advanced_cache(self):
@ -144,7 +146,8 @@ class MPDTagCacheToTracksTest(unittest.TestCase):
artists = [Artist(name='æøå')]
album = Album(name='æøå', artists=artists)
track = Track(
uri=uri, name='æøå', artists=artists, album=album, length=4000)
uri=uri, name='æøå', artists=artists, album=album, length=4000,
last_modified=1272319626)
self.assertEqual(track, list(tracks)[0])
@ -157,7 +160,8 @@ class MPDTagCacheToTracksTest(unittest.TestCase):
tracks = parse_mpd_tag_cache(
path_to_data_dir('blank_tag_cache'), path_to_data_dir(''))
uri = path_to_uri(path_to_data_dir('song1.mp3'))
self.assertEqual(set([Track(uri=uri, length=4000)]), tracks)
expected = Track(uri=uri, length=4000, last_modified=1272319626)
self.assertEqual(set([expected]), tracks)
def test_musicbrainz_tagcache(self):
tracks = parse_mpd_tag_cache(
@ -184,5 +188,5 @@ class MPDTagCacheToTracksTest(unittest.TestCase):
album = expected_albums[0].copy(artists=[artist])
track = Track(
uri=uri, name='trackname', artists=expected_artists, track_no=1,
album=album, date='2006', length=4000)
album=album, date='2006', length=4000, last_modified=1272319626)
self.assertEqual(track, list(tracks)[0])

View File

@ -4,6 +4,7 @@ import unittest
from mopidy.scanner import Scanner, translator
from mopidy.models import Track, Artist, Album
from mopidy.utils import path as path_lib
from tests import path_to_data_dir
@ -32,6 +33,7 @@ class TranslatorTest(unittest.TestCase):
'musicbrainz-albumid': 'mbalbumid',
'musicbrainz-artistid': 'mbartistid',
'musicbrainz-albumartistid': 'mbalbumartistid',
'mtime': 1234,
}
self.album = {
@ -57,6 +59,7 @@ class TranslatorTest(unittest.TestCase):
'track_no': 1,
'length': 4531,
'musicbrainz_id': 'mbtrackid',
'last_modified': 1234,
}
def build_track(self):
@ -141,8 +144,9 @@ class ScannerTest(unittest.TestCase):
self.data = {}
def scan(self, path):
scanner = Scanner(
path_to_data_dir(path), self.data_callback, self.error_callback)
paths = path_lib.find_files(path_to_data_dir(path))
uris = (path_lib.path_to_uri(p) for p in paths)
scanner = Scanner(uris, self.data_callback, self.error_callback)
scanner.start()
def check(self, name, key, value):