mirror of
https://github.com/beetbox/beets.git
synced 2026-02-26 17:21:24 +01:00
Merge pull request #2389 from irskep/pr/web-features
A new 'beet web' endpoint and configuration option, with docs
This commit is contained in:
commit
23e05fe0b1
4 changed files with 76 additions and 4 deletions
|
|
@ -37,7 +37,10 @@ def _rep(obj, expand=False):
|
|||
out = dict(obj)
|
||||
|
||||
if isinstance(obj, beets.library.Item):
|
||||
del out['path']
|
||||
if app.config.get('INCLUDE_PATHS', False):
|
||||
out['path'] = util.displayable_path(out['path'])
|
||||
else:
|
||||
del out['path']
|
||||
|
||||
# Get the size (in bytes) of the backing file. This is useful
|
||||
# for the Tomahawk resolver API.
|
||||
|
|
@ -173,11 +176,16 @@ class QueryConverter(PathConverter):
|
|||
return ','.join(value)
|
||||
|
||||
|
||||
class EverythingConverter(PathConverter):
|
||||
regex = '.*?'
|
||||
|
||||
|
||||
# Flask setup.
|
||||
|
||||
app = flask.Flask(__name__)
|
||||
app.url_map.converters['idlist'] = IdListConverter
|
||||
app.url_map.converters['query'] = QueryConverter
|
||||
app.url_map.converters['everything'] = EverythingConverter
|
||||
|
||||
|
||||
@app.before_request
|
||||
|
|
@ -218,6 +226,16 @@ def item_query(queries):
|
|||
return g.lib.items(queries)
|
||||
|
||||
|
||||
@app.route('/item/path/<everything:path>')
|
||||
def item_at_path(path):
|
||||
query = beets.library.PathQuery('path', path.encode('utf-8'))
|
||||
item = g.lib.items(query).get()
|
||||
if item:
|
||||
return flask.jsonify(_rep(item))
|
||||
else:
|
||||
return flask.abort(404)
|
||||
|
||||
|
||||
@app.route('/item/values/<string:key>')
|
||||
def item_unique_field_values(key):
|
||||
sort_key = flask.request.args.get('sort_key', key)
|
||||
|
|
@ -309,6 +327,7 @@ class WebPlugin(BeetsPlugin):
|
|||
'host': u'127.0.0.1',
|
||||
'port': 8337,
|
||||
'cors': '',
|
||||
'include_paths': False,
|
||||
})
|
||||
|
||||
def commands(self):
|
||||
|
|
@ -327,6 +346,8 @@ class WebPlugin(BeetsPlugin):
|
|||
# Normalizes json output
|
||||
app.config['JSONIFY_PRETTYPRINT_REGULAR'] = False
|
||||
|
||||
app.config['INCLUDE_PATHS'] = self.config['include_paths']
|
||||
|
||||
# Enable CORS if required.
|
||||
if self.config['cors']:
|
||||
self._log.info(u'Enabling CORS with origin: {0}',
|
||||
|
|
|
|||
|
|
@ -13,6 +13,10 @@ New features:
|
|||
non-numeric track index data. For example, some vinyl or tape media will
|
||||
report the side of the record using a letter instead of a number in that
|
||||
field. :bug:`1831` :bug:`2363`
|
||||
* The :doc:`/plugins/web` has a new endpoint, ``/item/path/foo``, which will
|
||||
return the item info for the file at the given path, or 404.
|
||||
* The :doc:`/plugins/web` also has a new config option, ``include_paths``,
|
||||
which will cause paths to be included in item API responses if set to true.
|
||||
|
||||
Fixes:
|
||||
|
||||
|
|
|
|||
|
|
@ -63,6 +63,8 @@ configuration file. The available options are:
|
|||
Default: 8337.
|
||||
- **cors**: The CORS allowed origin (see :ref:`web-cors`, below).
|
||||
Default: CORS is disabled.
|
||||
- **include_paths**: If true, includes paths in item objects.
|
||||
Default: false.
|
||||
|
||||
Implementation
|
||||
--------------
|
||||
|
|
@ -160,6 +162,16 @@ response includes all the items requested. If a track is not found it is silentl
|
|||
dropped from the response.
|
||||
|
||||
|
||||
``GET /item/path/...``
|
||||
++++++++++++++++++++++
|
||||
|
||||
Look for an item at the given absolute path on the server. If it corresponds to
|
||||
a track, return the track in the same format as ``/item/*``.
|
||||
|
||||
If the server runs UNIX, you'll need to include an extra leading slash:
|
||||
``http://localhost:8337/item/path//Users/beets/Music/Foo/Bar/Baz.mp3``
|
||||
|
||||
|
||||
``GET /item/query/querystring``
|
||||
+++++++++++++++++++++++++++++++
|
||||
|
||||
|
|
|
|||
|
|
@ -4,11 +4,12 @@
|
|||
|
||||
from __future__ import division, absolute_import, print_function
|
||||
|
||||
import json
|
||||
import unittest
|
||||
import os.path
|
||||
from six import assertCountEqual
|
||||
|
||||
from test import _common
|
||||
import json
|
||||
from beets.library import Item, Album
|
||||
from beetsplug import web
|
||||
|
||||
|
|
@ -21,15 +22,32 @@ class WebPluginTest(_common.LibTestCase):
|
|||
# Add fixtures
|
||||
for track in self.lib.items():
|
||||
track.remove()
|
||||
self.lib.add(Item(title=u'title', path='', id=1))
|
||||
self.lib.add(Item(title=u'another title', path='', id=2))
|
||||
self.lib.add(Item(title=u'title', path='/path_1', id=1))
|
||||
self.lib.add(Item(title=u'another title', path='/path_2', id=2))
|
||||
self.lib.add(Album(album=u'album', id=3))
|
||||
self.lib.add(Album(album=u'another album', id=4))
|
||||
|
||||
web.app.config['TESTING'] = True
|
||||
web.app.config['lib'] = self.lib
|
||||
web.app.config['INCLUDE_PATHS'] = False
|
||||
self.client = web.app.test_client()
|
||||
|
||||
def test_config_include_paths_true(self):
|
||||
web.app.config['INCLUDE_PATHS'] = True
|
||||
response = self.client.get('/item/1')
|
||||
response.json = json.loads(response.data.decode('utf-8'))
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.json['path'], u'/path_1')
|
||||
|
||||
def test_config_include_paths_false(self):
|
||||
web.app.config['INCLUDE_PATHS'] = False
|
||||
response = self.client.get('/item/1')
|
||||
response.json = json.loads(response.data.decode('utf-8'))
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertNotIn('path', response.json)
|
||||
|
||||
def test_get_all_items(self):
|
||||
response = self.client.get('/item/')
|
||||
response.json = json.loads(response.data.decode('utf-8'))
|
||||
|
|
@ -58,6 +76,23 @@ class WebPluginTest(_common.LibTestCase):
|
|||
response = self.client.get('/item/3')
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
def test_get_single_item_by_path(self):
|
||||
data_path = os.path.join(_common.RSRC, b'full.mp3')
|
||||
self.lib.add(Item.from_path(data_path))
|
||||
response = self.client.get('/item/path/' + data_path.decode('utf-8'))
|
||||
response.json = json.loads(response.data.decode('utf-8'))
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.json['title'], u'full')
|
||||
|
||||
def test_get_single_item_by_path_not_found_if_not_in_library(self):
|
||||
data_path = os.path.join(_common.RSRC, b'full.mp3')
|
||||
# data_path points to a valid file, but we have not added the file
|
||||
# to the library.
|
||||
response = self.client.get('/item/path/' + data_path.decode('utf-8'))
|
||||
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
def test_get_item_empty_query(self):
|
||||
response = self.client.get('/item/query/')
|
||||
response.json = json.loads(response.data.decode('utf-8'))
|
||||
|
|
|
|||
Loading…
Reference in a new issue