diff --git a/mopidy/backends/local/__init__.py b/mopidy/backends/local/__init__.py index 436f43d4..4031c474 100644 --- a/mopidy/backends/local/__init__.py +++ b/mopidy/backends/local/__init__.py @@ -46,7 +46,17 @@ class Extension(ext.Extension): class Library(object): - #: Name of the local library implementation. + """ + Local library interface. + + Extensions that whish to provide an alternate local library storage backend + need to sub-class this class and install and confgure it with an extension. + Both scanning and library calls will use the active local library. + + :param config: Config dictionary + """ + + #: Name of the local library implementation, must be overriden. name = None def __init__(self, config): @@ -54,20 +64,53 @@ class Library(object): def load(self): """ - Initialize whatever resources are needed for this library. - - This is where you load the tracks into memory, setup a database - conection etc. + (Re)load any tracks stored in memory, if any, otherwise just return + number of available tracks currently available. Will be called at + startup for both library and update use cases, so if you plan to store + tracks in memory this is when the should be (re)loaded. :rtype: :class:`int` representing number of tracks in library. """ return 0 - def tracks(self): + def lookup(self, uri): """ - Iterator over all tracks. + Lookup the given URI. - :rtype: :class:`mopidy.models.Track` iterator + Unlike the core APIs, local tracks uris can only be resolved to a + single track. + + :param string uri: track URI + :rtype: :class:`~mopidy.models.Track` + """ + raise NotImplementedError + + # TODO: remove uris, replacing it with support in query language. + # TODO: remove exact, replacing it with support in query language. + def search(self, query=None, limit=100, offset=0, exact=False, uris=None): + """ + Search the library for tracks where ``field`` contains ``values``. + + :param dict query: one or more queries to search for + :param int limit: maximum number of results to return + :param int offset: offset into result set to use. + :param bool exact: whether to look for exact matches + :param uris: zero or more URI roots to limit the search to + :type uris: list of strings or :class:`None` + :rtype: :class:`~mopidy.models.SearchResult` + """ + raise NotImplementedError + + # TODO: add file browsing support. + + # Remaining methods are use for the update process. + def begin(self): + """ + Prepare library for accepting updates. Exactly what this means is + highly implementation depended. This must however return an iterator + that generates all tracks in the library for efficient scanning. + + :rtype: :class:`~mopidy.models.Track` iterator """ raise NotImplementedError @@ -75,8 +118,7 @@ class Library(object): """ Add the given track to library. - :param track: Track to add to the library/ - :type track: :class:`mopidy.models.Track` + :param :class:`~mopidy.models.Track` track: Track to add to the library """ raise NotImplementedError @@ -84,47 +126,27 @@ class Library(object): """ Remove the given track from the library. - :param uri: URI to remove from the library/ - :type uri: string + :param str uri: URI to remove from the library/ """ raise NotImplementedError - def commit(self): + def flush(self): """ - Persist any changes to the library. - - This is where you write your data file to disk, commit transactions - etc. depending on the requirements of your library implementation. + Called for every n-th track indicating that work should be commited, + implementors are free to ignore these hints. """ pass - def lookup(self, uri): + def close(self): """ - Lookup the given URI. - - If the URI expands to multiple tracks, the returned list will contain - them all. - - :param uri: track URI - :type uri: string - :rtype: :class:`mopidy.models.Track` + Close any resources used for updating, commit outstanding work etc. """ - raise NotImplementedError + pass - # TODO: remove uris, replacing it with support in query language. - # TODO: remove exact, replacing it with support in query language. - def search(self, query=None, exact=False, uris=None): + def clear(self): """ - Search the library for tracks where ``field`` contains ``values``. + Clear out whatever data storage is used by this backend. - :param query: one or more queries to search for - :type query: dict - :param exact: whether to look for exact matches - :type query: boolean - :param uris: zero or more URI roots to limit the search to - :type uris: list of strings or :class:`None` - :rtype: :class:`mopidy.models.SearchResult` + :rtype: Boolean indicating if state was cleared. """ - raise NotImplementedError - - # TODO: add file browsing support. + return False diff --git a/mopidy/backends/local/commands.py b/mopidy/backends/local/commands.py index 60bc8ad4..a2098078 100644 --- a/mopidy/backends/local/commands.py +++ b/mopidy/backends/local/commands.py @@ -46,7 +46,8 @@ class ScanCommand(commands.Command): num_tracks = library.load() logger.info('Checking %d tracks from library.', num_tracks) - for track in library.tracks(): + + for track in library.begin(): uri_path_mapping[track.uri] = translator.local_track_uri_to_path( track.uri, media_dir) try: @@ -90,10 +91,12 @@ class ScanCommand(commands.Command): except exceptions.ScannerError as error: logger.warning('Failed %s: %s', uri, error) + # TODO: trigger this on batch size intervals instead and add + # flush progress.increment() logger.info('Commiting changes.') - library.commit() + library.close() return 0 diff --git a/mopidy/backends/local/json.py b/mopidy/backends/local/json.py index 20b922e6..d1cacd6d 100644 --- a/mopidy/backends/local/json.py +++ b/mopidy/backends/local/json.py @@ -56,7 +56,21 @@ class JsonLibrary(local.Library): self._tracks = dict((t.uri, t) for t in library.get('tracks', [])) return len(self._tracks) - def tracks(self): + def lookup(self, uri): + try: + return self._tracks[uri] + except KeyError: + return None + + def search(self, query=None, limit=100, offset=0, uris=None, exact=False): + tracks = self._tracks.values() + # TODO: pass limit and offset into search helpers + if exact: + return search.find_exact(tracks, query=query, uris=uris) + else: + return search.search(tracks, query=query, uris=uris) + + def begin(self): return self._tracks.itervalues() def add(self, track): @@ -65,18 +79,12 @@ class JsonLibrary(local.Library): def remove(self, uri): self._tracks.pop(uri, None) - def commit(self): + def close(self): write_library(self._json_file, {'tracks': self._tracks.values()}) - def lookup(self, uri): + def clear(self): try: - return self._tracks[uri] - except KeyError: - return None - - def search(self, query=None, uris=None, exact=False): - tracks = self._tracks.values() - if exact: - return search.find_exact(tracks, query=query, uris=uris) - else: - return search.search(tracks, query=query, uris=uris) + os.remove(self._json_file) + return True + except OSError: + return False