Update vendored beets to 1.6.0

Updates colorama to 0.4.6
Adds confuse version 1.7.0
Updates jellyfish to 0.9.0
Adds mediafile 0.10.1
Updates munkres to 1.1.4
Updates musicbrainzngs to 0.7.1
Updates mutagen to 1.46.0
Updates pyyaml to 6.0
Updates unidecode to 1.3.6
This commit is contained in:
Labrys of Knossos 2022-11-28 18:02:40 -05:00
commit 56c6773c6b
385 changed files with 25143 additions and 18080 deletions

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# This file is part of beets.
# Copyright 2016, Adrian Sampson.
#
@ -14,14 +13,13 @@
# included in all copies or substantial portions of the Software.
"""A Web interface to beets."""
from __future__ import division, absolute_import, print_function
from beets.plugins import BeetsPlugin
from beets import ui
from beets import util
import beets.library
import flask
from flask import g
from flask import g, jsonify
from werkzeug.routing import BaseConverter, PathConverter
import os
from unidecode import unidecode
@ -59,7 +57,10 @@ def _rep(obj, expand=False):
return out
elif isinstance(obj, beets.library.Album):
del out['artpath']
if app.config.get('INCLUDE_PATHS', False):
out['artpath'] = util.displayable_path(out['artpath'])
else:
del out['artpath']
if expand:
out['items'] = [_rep(item) for item in obj.items()]
return out
@ -91,7 +92,20 @@ def is_expand():
return flask.request.args.get('expand') is not None
def resource(name):
def is_delete():
"""Returns whether the current delete request should remove the selected
files.
"""
return flask.request.args.get('delete') is not None
def get_method():
"""Returns the HTTP method of the current request."""
return flask.request.method
def resource(name, patchable=False):
"""Decorates a function to handle RESTful HTTP requests for a resource.
"""
def make_responder(retriever):
@ -99,34 +113,98 @@ def resource(name):
entities = [retriever(id) for id in ids]
entities = [entity for entity in entities if entity]
if len(entities) == 1:
return flask.jsonify(_rep(entities[0], expand=is_expand()))
elif entities:
return app.response_class(
json_generator(entities, root=name),
mimetype='application/json'
)
if get_method() == "DELETE":
if app.config.get('READONLY', True):
return flask.abort(405)
for entity in entities:
entity.remove(delete=is_delete())
return flask.make_response(jsonify({'deleted': True}), 200)
elif get_method() == "PATCH" and patchable:
if app.config.get('READONLY', True):
return flask.abort(405)
for entity in entities:
entity.update(flask.request.get_json())
entity.try_sync(True, False) # write, don't move
if len(entities) == 1:
return flask.jsonify(_rep(entities[0], expand=is_expand()))
elif entities:
return app.response_class(
json_generator(entities, root=name),
mimetype='application/json'
)
elif get_method() == "GET":
if len(entities) == 1:
return flask.jsonify(_rep(entities[0], expand=is_expand()))
elif entities:
return app.response_class(
json_generator(entities, root=name),
mimetype='application/json'
)
else:
return flask.abort(404)
else:
return flask.abort(404)
responder.__name__ = 'get_{0}'.format(name)
return flask.abort(405)
responder.__name__ = f'get_{name}'
return responder
return make_responder
def resource_query(name):
def resource_query(name, patchable=False):
"""Decorates a function to handle RESTful HTTP queries for resources.
"""
def make_responder(query_func):
def responder(queries):
return app.response_class(
json_generator(
query_func(queries),
root='results', expand=is_expand()
),
mimetype='application/json'
)
responder.__name__ = 'query_{0}'.format(name)
entities = query_func(queries)
if get_method() == "DELETE":
if app.config.get('READONLY', True):
return flask.abort(405)
for entity in entities:
entity.remove(delete=is_delete())
return flask.make_response(jsonify({'deleted': True}), 200)
elif get_method() == "PATCH" and patchable:
if app.config.get('READONLY', True):
return flask.abort(405)
for entity in entities:
entity.update(flask.request.get_json())
entity.try_sync(True, False) # write, don't move
return app.response_class(
json_generator(entities, root=name),
mimetype='application/json'
)
elif get_method() == "GET":
return app.response_class(
json_generator(
entities,
root='results', expand=is_expand()
),
mimetype='application/json'
)
else:
return flask.abort(405)
responder.__name__ = f'query_{name}'
return responder
return make_responder
@ -140,7 +218,7 @@ def resource_list(name):
json_generator(list_all(), root=name, expand=is_expand()),
mimetype='application/json'
)
responder.__name__ = 'all_{0}'.format(name)
responder.__name__ = f'all_{name}'
return responder
return make_responder
@ -150,7 +228,7 @@ def _get_unique_table_field_values(model, field, sort_field):
if field not in model.all_keys() or sort_field not in model.all_keys():
raise KeyError
with g.lib.transaction() as tx:
rows = tx.query('SELECT DISTINCT "{0}" FROM "{1}" ORDER BY "{2}"'
rows = tx.query('SELECT DISTINCT "{}" FROM "{}" ORDER BY "{}"'
.format(field, model._table, sort_field))
return [row[0] for row in rows]
@ -169,7 +247,7 @@ class IdListConverter(BaseConverter):
return ids
def to_url(self, value):
return ','.join(value)
return ','.join(str(v) for v in value)
class QueryConverter(PathConverter):
@ -177,10 +255,13 @@ class QueryConverter(PathConverter):
"""
def to_python(self, value):
return value.split('/')
queries = value.split('/')
"""Do not do path substitution on regex value tests"""
return [query if '::' in query else query.replace('\\', os.sep)
for query in queries]
def to_url(self, value):
return ','.join(value)
return ','.join([v.replace(os.sep, '\\') for v in value])
class EverythingConverter(PathConverter):
@ -202,8 +283,8 @@ def before_request():
# Items.
@app.route('/item/<idlist:ids>')
@resource('items')
@app.route('/item/<idlist:ids>', methods=["GET", "DELETE", "PATCH"])
@resource('items', patchable=True)
def get_item(id):
return g.lib.get_item(id)
@ -249,8 +330,8 @@ def item_file(item_id):
return response
@app.route('/item/query/<query:queries>')
@resource_query('items')
@app.route('/item/query/<query:queries>', methods=["GET", "DELETE", "PATCH"])
@resource_query('items', patchable=True)
def item_query(queries):
return g.lib.items(queries)
@ -278,7 +359,7 @@ def item_unique_field_values(key):
# Albums.
@app.route('/album/<idlist:ids>')
@app.route('/album/<idlist:ids>', methods=["GET", "DELETE"])
@resource('albums')
def get_album(id):
return g.lib.get_album(id)
@ -291,7 +372,7 @@ def all_albums():
return g.lib.albums()
@app.route('/album/query/<query:queries>')
@app.route('/album/query/<query:queries>', methods=["GET", "DELETE"])
@resource_query('albums')
def album_query(queries):
return g.lib.albums(queries)
@ -351,20 +432,21 @@ def home():
class WebPlugin(BeetsPlugin):
def __init__(self):
super(WebPlugin, self).__init__()
super().__init__()
self.config.add({
'host': u'127.0.0.1',
'host': '127.0.0.1',
'port': 8337,
'cors': '',
'cors_supports_credentials': False,
'reverse_proxy': False,
'include_paths': False,
'readonly': True,
})
def commands(self):
cmd = ui.Subcommand('web', help=u'start a Web interface')
cmd.parser.add_option(u'-d', u'--debug', action='store_true',
default=False, help=u'debug mode')
cmd = ui.Subcommand('web', help='start a Web interface')
cmd.parser.add_option('-d', '--debug', action='store_true',
default=False, help='debug mode')
def func(lib, opts, args):
args = ui.decargs(args)
@ -378,12 +460,13 @@ class WebPlugin(BeetsPlugin):
app.config['JSONIFY_PRETTYPRINT_REGULAR'] = False
app.config['INCLUDE_PATHS'] = self.config['include_paths']
app.config['READONLY'] = self.config['readonly']
# Enable CORS if required.
if self.config['cors']:
self._log.info(u'Enabling CORS with origin: {0}',
self._log.info('Enabling CORS with origin: {0}',
self.config['cors'])
from flask.ext.cors import CORS
from flask_cors import CORS
app.config['CORS_ALLOW_HEADERS'] = "Content-Type"
app.config['CORS_RESOURCES'] = {
r"/*": {"origins": self.config['cors'].get(str)}
@ -407,7 +490,7 @@ class WebPlugin(BeetsPlugin):
return [cmd]
class ReverseProxied(object):
class ReverseProxied:
'''Wrap the application in this middleware and configure the
front-end server to add these headers, to let you quietly bind
this to a URL other than / and to an HTTP scheme that is

View file

@ -129,7 +129,7 @@ $.fn.player = function(debug) {
// Simple selection disable for jQuery.
// Cut-and-paste from:
// http://stackoverflow.com/questions/2700000
// https://stackoverflow.com/questions/2700000
$.fn.disableSelection = function() {
$(this).attr('unselectable', 'on')
.css('-moz-user-select', 'none')