Merge pull request #452 from adamcik/feature/mtime
Add last modified time to tracks and make scanner mtime aware.
This commit is contained in:
commit
f91e621c40
@ -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']
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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])
|
||||
|
||||
@ -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):
|
||||
|
||||
Loading…
Reference in New Issue
Block a user