Merge branch 'release/0.6.1'
This commit is contained in:
commit
6bf73384d2
1
.env.dev
1
.env.dev
|
@ -1,4 +1,3 @@
|
||||||
API_AUTHENTICATION_REQUIRED=True
|
API_AUTHENTICATION_REQUIRED=True
|
||||||
CACHALOT_ENABLED=False
|
|
||||||
RAVEN_ENABLED=false
|
RAVEN_ENABLED=false
|
||||||
RAVEN_DSN=https://44332e9fdd3d42879c7d35bf8562c6a4:0062dc16a22b41679cd5765e5342f716@sentry.eliotberriot.com/5
|
RAVEN_DSN=https://44332e9fdd3d42879c7d35bf8562c6a4:0062dc16a22b41679cd5765e5342f716@sentry.eliotberriot.com/5
|
||||||
|
|
|
@ -84,3 +84,5 @@ front/test/unit/coverage
|
||||||
front/test/e2e/reports
|
front/test/e2e/reports
|
||||||
front/selenium-debug.log
|
front/selenium-debug.log
|
||||||
docs/_build
|
docs/_build
|
||||||
|
|
||||||
|
data/
|
||||||
|
|
21
CHANGELOG
21
CHANGELOG
|
@ -3,6 +3,27 @@ Changelog
|
||||||
|
|
||||||
.. towncrier
|
.. towncrier
|
||||||
|
|
||||||
|
0.6.1 (unreleased)
|
||||||
|
------------------
|
||||||
|
|
||||||
|
Features:
|
||||||
|
|
||||||
|
- Can now skip acoustid on file import with the --no-acoustid flag (#111)
|
||||||
|
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
|
||||||
|
- Added missing batch id in output during import (#112)
|
||||||
|
- Added some feedback on the play button (#100)
|
||||||
|
- Smarter pagination which takes a fixed size (#84)
|
||||||
|
|
||||||
|
|
||||||
|
Other:
|
||||||
|
|
||||||
|
- Completely removed django-cachalot from the codebase (#110). You can safely
|
||||||
|
remove the CACHALOT_ENABLED setting from your .env file
|
||||||
|
|
||||||
|
|
||||||
0.6 (2018-03-04)
|
0.6 (2018-03-04)
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
#!/bin/bash -eux
|
#!/bin/bash -eux
|
||||||
python /app/manage.py collectstatic --noinput
|
python /app/manage.py collectstatic --noinput
|
||||||
/usr/local/bin/daphne --root-path=/app -b 0.0.0.0 -p 5000 config.asgi:application
|
/usr/local/bin/daphne -b 0.0.0.0 -p 5000 config.asgi:application
|
||||||
|
|
|
@ -4,16 +4,19 @@ set -e
|
||||||
# Since docker-compose relies heavily on environment variables itself for configuration, we'd have to define multiple
|
# Since docker-compose relies heavily on environment variables itself for configuration, we'd have to define multiple
|
||||||
# environment variables just to support cookiecutter out of the box. That makes no sense, so this little entrypoint
|
# environment variables just to support cookiecutter out of the box. That makes no sense, so this little entrypoint
|
||||||
# does all this for us.
|
# does all this for us.
|
||||||
export CACHE_URL=redis://redis:6379/0
|
export CACHE_URL=${CACHE_URL:="redis://redis:6379/0"}
|
||||||
|
|
||||||
|
if [ -z "$DATABASE_URL" ]; then
|
||||||
# the official postgres image uses 'postgres' as default user if not set explictly.
|
# the official postgres image uses 'postgres' as default user if not set explictly.
|
||||||
if [ -z "$POSTGRES_ENV_POSTGRES_USER" ]; then
|
if [ -z "$POSTGRES_ENV_POSTGRES_USER" ]; then
|
||||||
export POSTGRES_ENV_POSTGRES_USER=postgres
|
export POSTGRES_ENV_POSTGRES_USER=postgres
|
||||||
fi
|
fi
|
||||||
|
|
||||||
export DATABASE_URL=postgres://$POSTGRES_ENV_POSTGRES_USER:$POSTGRES_ENV_POSTGRES_PASSWORD@postgres:5432/$POSTGRES_ENV_POSTGRES_USER
|
export DATABASE_URL=postgres://$POSTGRES_ENV_POSTGRES_USER:$POSTGRES_ENV_POSTGRES_PASSWORD@postgres:5432/$POSTGRES_ENV_POSTGRES_USER
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$CELERY_BROKER_URL" ]; then
|
||||||
export CELERY_BROKER_URL=$CACHE_URL
|
export CELERY_BROKER_URL=$CACHE_URL
|
||||||
|
fi
|
||||||
|
|
||||||
# we copy the frontend files, if any so we can serve them from the outside
|
# we copy the frontend files, if any so we can serve them from the outside
|
||||||
if [ -d "frontend" ]; then
|
if [ -d "frontend" ]; then
|
||||||
|
|
|
@ -55,7 +55,6 @@ THIRD_PARTY_APPS = (
|
||||||
'rest_framework',
|
'rest_framework',
|
||||||
'rest_framework.authtoken',
|
'rest_framework.authtoken',
|
||||||
'taggit',
|
'taggit',
|
||||||
'cachalot',
|
|
||||||
'rest_auth',
|
'rest_auth',
|
||||||
'rest_auth.registration',
|
'rest_auth.registration',
|
||||||
'mptt',
|
'mptt',
|
||||||
|
@ -310,7 +309,7 @@ CELERY_BROKER_URL = env(
|
||||||
"CELERY_BROKER_URL", default=env('CACHE_URL', default=CACHE_DEFAULT))
|
"CELERY_BROKER_URL", default=env('CACHE_URL', default=CACHE_DEFAULT))
|
||||||
########## END CELERY
|
########## END CELERY
|
||||||
# Location of root django.contrib.admin URL, use {% url 'admin:index' %}
|
# Location of root django.contrib.admin URL, use {% url 'admin:index' %}
|
||||||
ADMIN_URL = r'^admin/'
|
|
||||||
# Your common stuff: Below this line define 3rd party library settings
|
# Your common stuff: Below this line define 3rd party library settings
|
||||||
CELERY_TASK_DEFAULT_RATE_LIMIT = 1
|
CELERY_TASK_DEFAULT_RATE_LIMIT = 1
|
||||||
CELERY_TASK_TIME_LIMIT = 300
|
CELERY_TASK_TIME_LIMIT = 300
|
||||||
|
@ -371,9 +370,6 @@ MUSICBRAINZ_CACHE_DURATION = env.int(
|
||||||
default=300
|
default=300
|
||||||
)
|
)
|
||||||
|
|
||||||
CACHALOT_ENABLED = env.bool('CACHALOT_ENABLED', default=True)
|
|
||||||
|
|
||||||
|
|
||||||
# Custom Admin URL, use {% url 'admin:index' %}
|
# Custom Admin URL, use {% url 'admin:index' %}
|
||||||
ADMIN_URL = env('DJANGO_ADMIN_URL', default='^api/admin/')
|
ADMIN_URL = env('DJANGO_ADMIN_URL', default='^api/admin/')
|
||||||
CSRF_USE_SESSIONS = True
|
CSRF_USE_SESSIONS = True
|
||||||
|
|
|
@ -6,8 +6,8 @@ python manage.py migrate --noinput
|
||||||
|
|
||||||
echo "Creating demo user..."
|
echo "Creating demo user..."
|
||||||
|
|
||||||
cat demo/demo-user.py | python manage.py shell --plain
|
cat demo/demo-user.py | python manage.py shell -i python
|
||||||
|
|
||||||
echo "Importing demo tracks..."
|
echo "Importing demo tracks..."
|
||||||
|
|
||||||
python manage.py import_files "/music/**/*.ogg" --recursive --noinput
|
python manage.py import_files "/music/**/*.ogg" --recursive --noinput --username demo
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
__version__ = '0.6'
|
__version__ = '0.6.1'
|
||||||
__version_info__ = tuple([int(num) if num.isdigit() else num for num in __version__.replace('-', '.', 1).split('.')])
|
__version_info__ = tuple([int(num) if num.isdigit() else num for num in __version__.replace('-', '.', 1).split('.')])
|
||||||
|
|
|
@ -9,6 +9,7 @@ from rest_framework import exceptions
|
||||||
from rest_framework_jwt.settings import api_settings
|
from rest_framework_jwt.settings import api_settings
|
||||||
from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication
|
from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication
|
||||||
|
|
||||||
|
from funkwhale_api.users.models import User
|
||||||
|
|
||||||
|
|
||||||
class TokenHeaderAuth(BaseJSONWebTokenAuthentication):
|
class TokenHeaderAuth(BaseJSONWebTokenAuthentication):
|
||||||
|
@ -40,7 +41,7 @@ class TokenAuthMiddleware:
|
||||||
auth = TokenHeaderAuth()
|
auth = TokenHeaderAuth()
|
||||||
try:
|
try:
|
||||||
user, token = auth.authenticate(scope)
|
user, token = auth.authenticate(scope)
|
||||||
except exceptions.AuthenticationFailed:
|
except (User.DoesNotExist, exceptions.AuthenticationFailed):
|
||||||
user = AnonymousUser()
|
user = AnonymousUser()
|
||||||
|
|
||||||
scope['user'] = user
|
scope['user'] = user
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
from django.core.files.base import ContentFile
|
from django.core.files.base import ContentFile
|
||||||
|
|
||||||
|
from dynamic_preferences.registries import global_preferences_registry
|
||||||
|
|
||||||
from funkwhale_api.taskapp import celery
|
from funkwhale_api.taskapp import celery
|
||||||
from funkwhale_api.providers.acoustid import get_acoustid_client
|
from funkwhale_api.providers.acoustid import get_acoustid_client
|
||||||
from funkwhale_api.providers.audiofile.tasks import import_track_data_from_path
|
from funkwhale_api.providers.audiofile.tasks import import_track_data_from_path
|
||||||
|
@ -23,18 +25,19 @@ def set_acoustid_on_track_file(track_file):
|
||||||
return update(result['id'])
|
return update(result['id'])
|
||||||
|
|
||||||
|
|
||||||
def _do_import(import_job, replace):
|
def _do_import(import_job, replace, use_acoustid=True):
|
||||||
from_file = bool(import_job.audio_file)
|
from_file = bool(import_job.audio_file)
|
||||||
mbid = import_job.mbid
|
mbid = import_job.mbid
|
||||||
acoustid_track_id = None
|
acoustid_track_id = None
|
||||||
duration = None
|
duration = None
|
||||||
track = None
|
track = None
|
||||||
if not mbid and from_file:
|
manager = global_preferences_registry.manager()
|
||||||
|
use_acoustid = use_acoustid and manager['providers_acoustid__api_key']
|
||||||
|
if not mbid and use_acoustid and from_file:
|
||||||
# we try to deduce mbid from acoustid
|
# we try to deduce mbid from acoustid
|
||||||
client = get_acoustid_client()
|
client = get_acoustid_client()
|
||||||
match = client.get_best_match(import_job.audio_file.path)
|
match = client.get_best_match(import_job.audio_file.path)
|
||||||
if not match:
|
if match:
|
||||||
raise ValueError('Cannot get match')
|
|
||||||
duration = match['recordings'][0]['duration']
|
duration = match['recordings'][0]['duration']
|
||||||
mbid = match['recordings'][0]['id']
|
mbid = match['recordings'][0]['id']
|
||||||
acoustid_track_id = match['id']
|
acoustid_track_id = match['id']
|
||||||
|
@ -77,13 +80,13 @@ def _do_import(import_job, replace):
|
||||||
models.ImportJob.objects.filter(
|
models.ImportJob.objects.filter(
|
||||||
status__in=['pending', 'errored']),
|
status__in=['pending', 'errored']),
|
||||||
'import_job')
|
'import_job')
|
||||||
def import_job_run(self, import_job, replace=False):
|
def import_job_run(self, import_job, replace=False, use_acoustid=True):
|
||||||
def mark_errored():
|
def mark_errored():
|
||||||
import_job.status = 'errored'
|
import_job.status = 'errored'
|
||||||
import_job.save()
|
import_job.save(update_fields=['status'])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return _do_import(import_job, replace)
|
return _do_import(import_job, replace, use_acoustid=use_acoustid)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
if not settings.DEBUG:
|
if not settings.DEBUG:
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -34,6 +34,13 @@ class Command(BaseCommand):
|
||||||
default=False,
|
default=False,
|
||||||
help='Will launch celery tasks for each file to import instead of doing it synchronously and block the CLI',
|
help='Will launch celery tasks for each file to import instead of doing it synchronously and block the CLI',
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--no-acoustid',
|
||||||
|
action='store_true',
|
||||||
|
dest='no_acoustid',
|
||||||
|
default=False,
|
||||||
|
help='Use this flag to completely bypass acoustid completely',
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--noinput', '--no-input', action='store_false', dest='interactive',
|
'--noinput', '--no-input', action='store_false', dest='interactive',
|
||||||
help="Do NOT prompt the user for input of any kind.",
|
help="Do NOT prompt the user for input of any kind.",
|
||||||
|
@ -81,13 +88,12 @@ class Command(BaseCommand):
|
||||||
raise CommandError("Import cancelled.")
|
raise CommandError("Import cancelled.")
|
||||||
|
|
||||||
batch = self.do_import(matching, user=user, options=options)
|
batch = self.do_import(matching, user=user, options=options)
|
||||||
|
|
||||||
message = 'Successfully imported {} tracks'
|
message = 'Successfully imported {} tracks'
|
||||||
if options['async']:
|
if options['async']:
|
||||||
message = 'Successfully launched import for {} tracks'
|
message = 'Successfully launched import for {} tracks'
|
||||||
self.stdout.write(message.format(len(matching)))
|
self.stdout.write(message.format(len(matching)))
|
||||||
self.stdout.write(
|
self.stdout.write(
|
||||||
"For details, please refer to import batch #".format(batch.pk))
|
"For details, please refer to import batch #{}".format(batch.pk))
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def do_import(self, matching, user, options):
|
def do_import(self, matching, user, options):
|
||||||
|
@ -109,7 +115,10 @@ class Command(BaseCommand):
|
||||||
|
|
||||||
job.save()
|
job.save()
|
||||||
try:
|
try:
|
||||||
utils.on_commit(import_handler, import_job_id=job.pk)
|
utils.on_commit(
|
||||||
|
import_handler,
|
||||||
|
import_job_id=job.pk,
|
||||||
|
use_acoustid=not options['no_acoustid'])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.stdout.write('Error: {}'.format(e))
|
self.stdout.write('Error: {}'.format(e))
|
||||||
|
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% block title %}Page Not found{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h1>Page Not found</h1>
|
|
||||||
|
|
||||||
<p>This is not the page you were looking for.</p>
|
|
||||||
{% endblock content %}
|
|
|
@ -1,13 +0,0 @@
|
||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% block title %}Server Error{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h1>Ooops!!! 500</h1>
|
|
||||||
|
|
||||||
<h3>Looks like something went wrong!</h3>
|
|
||||||
|
|
||||||
<p>We track these errors automatically, but if the problem persists feel free to contact us. In the meantime, try refreshing.</p>
|
|
||||||
{% endblock content %}
|
|
||||||
|
|
||||||
|
|
|
@ -1,107 +0,0 @@
|
||||||
{% load staticfiles i18n %}<!DOCTYPE html>
|
|
||||||
<html lang="en" ng-app>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
|
||||||
<title>{% block title %}funkwhale_api{% endblock title %}</title>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<meta name="description" content="">
|
|
||||||
<meta name="author" content="">
|
|
||||||
|
|
||||||
<!-- HTML5 shim, for IE6-8 support of HTML5 elements -->
|
|
||||||
<!--[if lt IE 9]>
|
|
||||||
<script src="https://html5shim.googlecode.com/svn/trunk/html5.js"></script>
|
|
||||||
<![endif]-->
|
|
||||||
|
|
||||||
{% block css %}
|
|
||||||
<!-- Latest compiled and minified CSS -->
|
|
||||||
<link rel="stylesheet" href="https://cdn.rawgit.com/twbs/bootstrap/v4-dev/dist/css/bootstrap.css">
|
|
||||||
|
|
||||||
<!-- Your stuff: Third-party css libraries go here -->
|
|
||||||
|
|
||||||
<!-- This file store project specific CSS -->
|
|
||||||
<link href="{% static 'css/project.css' %}" rel="stylesheet">
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block angular %}
|
|
||||||
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<div class="m-b">
|
|
||||||
<nav class="navbar navbar-dark navbar-static-top bg-inverse">
|
|
||||||
<div class="container">
|
|
||||||
<a class="navbar-brand" href="/">funkwhale_api</a>
|
|
||||||
<button type="button" class="navbar-toggler hidden-sm-up pull-right" data-toggle="collapse" data-target="#bs-navbar-collapse-1">
|
|
||||||
☰
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- Collect the nav links, forms, and other content for toggling -->
|
|
||||||
<div class="collapse navbar-toggleable-xs" id="bs-navbar-collapse-1">
|
|
||||||
<ul class="nav navbar-nav">
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="">Home</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="">About</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<ul class="nav navbar-nav pull-right">
|
|
||||||
{% if request.user.is_authenticated %}
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="{% url 'users:detail' request.user.username %}">{% trans "My Profile" %}</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="{% url 'account_logout' %}">{% trans "Logout" %}</a>
|
|
||||||
</li>
|
|
||||||
{% else %}
|
|
||||||
<li class="nav-item">
|
|
||||||
<a id="sign-up-link" class="nav-link" href="{% url 'account_signup' %}">{% trans "Sign Up" %}</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a id="log-in-link" class="nav-link" href="{% url 'account_login' %}">{% trans "Log In" %}</a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="container">
|
|
||||||
|
|
||||||
{% if messages %}
|
|
||||||
{% for message in messages %}
|
|
||||||
<div class="alert {% if message.tags %}alert-{{ message.tags }}{% endif %}">{{ message }}</div>
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<p>Use this document as a way to quick start any new project.</p>
|
|
||||||
{% endblock content %}
|
|
||||||
|
|
||||||
</div> <!-- /container -->
|
|
||||||
|
|
||||||
{% block modal %}{% endblock modal %}
|
|
||||||
|
|
||||||
<!-- Le javascript
|
|
||||||
================================================== -->
|
|
||||||
<!-- Placed at the end of the document so the pages load faster -->
|
|
||||||
{% block javascript %}
|
|
||||||
<!-- Latest JQuery -->
|
|
||||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
|
|
||||||
|
|
||||||
<!-- Latest compiled and minified JavaScript -->
|
|
||||||
<script src="https://cdn.rawgit.com/twbs/bootstrap/v4-dev/dist/js/bootstrap.js"></script>
|
|
||||||
|
|
||||||
<!-- Your stuff: Third-party javascript libraries go here -->
|
|
||||||
|
|
||||||
<!-- place project specific Javascript in this file -->
|
|
||||||
<script src="{% static 'js/project.js' %}"></script>
|
|
||||||
{% endblock javascript %}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1 +0,0 @@
|
||||||
{% extends "base.html" %}
|
|
|
@ -1 +0,0 @@
|
||||||
{% extends "base.html" %}
|
|
|
@ -50,9 +50,6 @@ mutagen>=1.39,<1.40
|
||||||
django-taggit>=0.22,<0.23
|
django-taggit>=0.22,<0.23
|
||||||
# Until this is merged
|
# Until this is merged
|
||||||
git+https://github.com/EliotBerriot/PyMemoize.git@django
|
git+https://github.com/EliotBerriot/PyMemoize.git@django
|
||||||
# Until this is merged
|
|
||||||
#django-cachalot==1.5.0
|
|
||||||
git+https://github.com/EliotBerriot/django-cachalot.git@django-2
|
|
||||||
|
|
||||||
django-dynamic-preferences>=1.5,<1.6
|
django-dynamic-preferences>=1.5,<1.6
|
||||||
pyacoustid>=1.1.5,<1.2
|
pyacoustid>=1.1.5,<1.2
|
||||||
|
|
|
@ -9,7 +9,8 @@ from . import data as api_data
|
||||||
DATA_DIR = os.path.dirname(os.path.abspath(__file__))
|
DATA_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
|
||||||
def test_set_acoustid_on_track_file(factories, mocker):
|
def test_set_acoustid_on_track_file(factories, mocker, preferences):
|
||||||
|
preferences['providers_acoustid__api_key'] = 'test'
|
||||||
track_file = factories['music.TrackFile'](acoustid_track_id=None)
|
track_file = factories['music.TrackFile'](acoustid_track_id=None)
|
||||||
id = 'e475bf79-c1ce-4441-bed7-1e33f226c0a2'
|
id = 'e475bf79-c1ce-4441-bed7-1e33f226c0a2'
|
||||||
payload = {
|
payload = {
|
||||||
|
@ -31,7 +32,7 @@ def test_set_acoustid_on_track_file(factories, mocker):
|
||||||
|
|
||||||
assert str(track_file.acoustid_track_id) == id
|
assert str(track_file.acoustid_track_id) == id
|
||||||
assert r == id
|
assert r == id
|
||||||
m.assert_called_once_with('', track_file.audio_file.path, parse=False)
|
m.assert_called_once_with('test', track_file.audio_file.path, parse=False)
|
||||||
|
|
||||||
|
|
||||||
def test_set_acoustid_on_track_file_required_high_score(factories, mocker):
|
def test_set_acoustid_on_track_file_required_high_score(factories, mocker):
|
||||||
|
@ -48,7 +49,9 @@ def test_set_acoustid_on_track_file_required_high_score(factories, mocker):
|
||||||
assert track_file.acoustid_track_id is None
|
assert track_file.acoustid_track_id is None
|
||||||
|
|
||||||
|
|
||||||
def test_import_job_can_run_with_file_and_acoustid(factories, mocker):
|
def test_import_job_can_run_with_file_and_acoustid(
|
||||||
|
preferences, factories, mocker):
|
||||||
|
preferences['providers_acoustid__api_key'] = 'test'
|
||||||
path = os.path.join(DATA_DIR, 'test.ogg')
|
path = os.path.join(DATA_DIR, 'test.ogg')
|
||||||
mbid = '9968a9d6-8d92-4051-8f76-674e157b6eed'
|
mbid = '9968a9d6-8d92-4051-8f76-674e157b6eed'
|
||||||
acoustid_payload = {
|
acoustid_payload = {
|
||||||
|
@ -88,7 +91,46 @@ def test_import_job_can_run_with_file_and_acoustid(factories, mocker):
|
||||||
assert job.source == 'file://'
|
assert job.source == 'file://'
|
||||||
|
|
||||||
|
|
||||||
def test_import_job_can_be_skipped(factories, mocker):
|
def test_run_import_skipping_accoustid(factories, mocker):
|
||||||
|
m = mocker.patch('funkwhale_api.music.tasks._do_import')
|
||||||
|
path = os.path.join(DATA_DIR, 'test.ogg')
|
||||||
|
job = factories['music.FileImportJob'](audio_file__path=path)
|
||||||
|
tasks.import_job_run(import_job_id=job.pk, use_acoustid=False)
|
||||||
|
m.assert_called_once_with(job, False, use_acoustid=False)
|
||||||
|
|
||||||
|
|
||||||
|
def test__do_import_skipping_accoustid(factories, mocker):
|
||||||
|
t = factories['music.Track']()
|
||||||
|
m = mocker.patch(
|
||||||
|
'funkwhale_api.music.tasks.import_track_data_from_path',
|
||||||
|
return_value=t)
|
||||||
|
path = os.path.join(DATA_DIR, 'test.ogg')
|
||||||
|
job = factories['music.FileImportJob'](
|
||||||
|
mbid=None,
|
||||||
|
audio_file__path=path)
|
||||||
|
p = job.audio_file.path
|
||||||
|
tasks._do_import(job, replace=False, use_acoustid=False)
|
||||||
|
m.assert_called_once_with(p)
|
||||||
|
|
||||||
|
|
||||||
|
def test__do_import_skipping_accoustid_if_no_key(
|
||||||
|
factories, mocker, preferences):
|
||||||
|
preferences['providers_acoustid__api_key'] = ''
|
||||||
|
t = factories['music.Track']()
|
||||||
|
m = mocker.patch(
|
||||||
|
'funkwhale_api.music.tasks.import_track_data_from_path',
|
||||||
|
return_value=t)
|
||||||
|
path = os.path.join(DATA_DIR, 'test.ogg')
|
||||||
|
job = factories['music.FileImportJob'](
|
||||||
|
mbid=None,
|
||||||
|
audio_file__path=path)
|
||||||
|
p = job.audio_file.path
|
||||||
|
tasks._do_import(job, replace=False, use_acoustid=False)
|
||||||
|
m.assert_called_once_with(p)
|
||||||
|
|
||||||
|
|
||||||
|
def test_import_job_can_be_skipped(factories, mocker, preferences):
|
||||||
|
preferences['providers_acoustid__api_key'] = 'test'
|
||||||
path = os.path.join(DATA_DIR, 'test.ogg')
|
path = os.path.join(DATA_DIR, 'test.ogg')
|
||||||
mbid = '9968a9d6-8d92-4051-8f76-674e157b6eed'
|
mbid = '9968a9d6-8d92-4051-8f76-674e157b6eed'
|
||||||
track_file = factories['music.TrackFile'](track__mbid=mbid)
|
track_file = factories['music.TrackFile'](track__mbid=mbid)
|
||||||
|
@ -124,7 +166,8 @@ def test_import_job_can_be_skipped(factories, mocker):
|
||||||
assert job.status == 'skipped'
|
assert job.status == 'skipped'
|
||||||
|
|
||||||
|
|
||||||
def test_import_job_can_be_errored(factories, mocker):
|
def test_import_job_can_be_errored(factories, mocker, preferences):
|
||||||
|
preferences['providers_acoustid__api_key'] = 'test'
|
||||||
path = os.path.join(DATA_DIR, 'test.ogg')
|
path = os.path.join(DATA_DIR, 'test.ogg')
|
||||||
mbid = '9968a9d6-8d92-4051-8f76-674e157b6eed'
|
mbid = '9968a9d6-8d92-4051-8f76-674e157b6eed'
|
||||||
track_file = factories['music.TrackFile'](track__mbid=mbid)
|
track_file = factories['music.TrackFile'](track__mbid=mbid)
|
||||||
|
|
|
@ -54,7 +54,7 @@ def test_management_command_requires_a_valid_username(factories, mocker):
|
||||||
|
|
||||||
|
|
||||||
def test_import_files_creates_a_batch_and_job(factories, mocker):
|
def test_import_files_creates_a_batch_and_job(factories, mocker):
|
||||||
m = m = mocker.patch('funkwhale_api.common.utils.on_commit')
|
m = mocker.patch('funkwhale_api.common.utils.on_commit')
|
||||||
user = factories['users.User'](username='me')
|
user = factories['users.User'](username='me')
|
||||||
path = os.path.join(DATA_DIR, 'dummy_file.ogg')
|
path = os.path.join(DATA_DIR, 'dummy_file.ogg')
|
||||||
call_command(
|
call_command(
|
||||||
|
@ -77,4 +77,24 @@ def test_import_files_creates_a_batch_and_job(factories, mocker):
|
||||||
assert job.source == 'file://' + path
|
assert job.source == 'file://' + path
|
||||||
m.assert_called_once_with(
|
m.assert_called_once_with(
|
||||||
music_tasks.import_job_run.delay,
|
music_tasks.import_job_run.delay,
|
||||||
import_job_id=job.pk)
|
import_job_id=job.pk,
|
||||||
|
use_acoustid=True)
|
||||||
|
|
||||||
|
|
||||||
|
def test_import_files_skip_acoustid(factories, mocker):
|
||||||
|
m = mocker.patch('funkwhale_api.common.utils.on_commit')
|
||||||
|
user = factories['users.User'](username='me')
|
||||||
|
path = os.path.join(DATA_DIR, 'dummy_file.ogg')
|
||||||
|
call_command(
|
||||||
|
'import_files',
|
||||||
|
path,
|
||||||
|
username='me',
|
||||||
|
async=True,
|
||||||
|
no_acoustid=True,
|
||||||
|
interactive=False)
|
||||||
|
batch = user.imports.latest('id')
|
||||||
|
job = batch.jobs.first()
|
||||||
|
m.assert_called_once_with(
|
||||||
|
music_tasks.import_job_run.delay,
|
||||||
|
import_job_id=job.pk,
|
||||||
|
use_acoustid=False)
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
#!/bin/bash -eux
|
||||||
|
version="develop"
|
||||||
|
music_path="/usr/share/music"
|
||||||
|
demo_path="/srv/funkwhale-demo/demo"
|
||||||
|
|
||||||
|
echo 'Cleaning everything...'
|
||||||
|
cd $demo_path
|
||||||
|
docker-compose down -v || echo 'Nothing to stop'
|
||||||
|
rm -rf /srv/funkwhale-demo/demo/*
|
||||||
|
mkdir -p $demo_path
|
||||||
|
echo 'Downloading demo files...'
|
||||||
|
curl -L -o docker-compose.yml "https://code.eliotberriot.com/funkwhale/funkwhale/raw/$version/deploy/docker-compose.yml"
|
||||||
|
curl -L -o .env "https://code.eliotberriot.com/funkwhale/funkwhale/raw/$version/deploy/env.prod.sample"
|
||||||
|
|
||||||
|
mkdir data/
|
||||||
|
cp -r $music_path data/music
|
||||||
|
|
||||||
|
curl -L -o front.zip "https://code.eliotberriot.com/funkwhale/funkwhale/-/jobs/artifacts/$version/download?job=build_front"
|
||||||
|
unzip front.zip
|
||||||
|
|
||||||
|
echo "FUNKWHALE_URL=https://demo.funkwhale.audio/" >> .env
|
||||||
|
echo "DJANGO_SECRET_KEY=demo" >> .env
|
||||||
|
echo "DJANGO_ALLOWED_HOSTS=demo.funkwhale.audio" >> .env
|
||||||
|
echo "FUNKWHALE_VERSION=$version" >> .env
|
||||||
|
echo "FUNKWHALE_API_PORT=5001" >> .env
|
||||||
|
|
||||||
|
docker-compose pull
|
||||||
|
docker-compose up -d postgres redis
|
||||||
|
sleep 5
|
||||||
|
docker-compose run --rm api demo/load-demo-data.sh
|
||||||
|
docker-compose up -d
|
|
@ -20,7 +20,7 @@ services:
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
image: funkwhale/funkwhale:${FUNKWHALE_VERSION:-latest}
|
image: funkwhale/funkwhale:${FUNKWHALE_VERSION:-latest}
|
||||||
env_file: .env
|
env_file: .env
|
||||||
command: python manage.py celery worker
|
command: celery -A funkwhale_api.taskapp worker -l INFO
|
||||||
links:
|
links:
|
||||||
- postgres
|
- postgres
|
||||||
- redis
|
- redis
|
||||||
|
|
|
@ -31,7 +31,7 @@ FUNKWHALE_API_PORT=5000
|
||||||
|
|
||||||
# Replace this by the definitive, public domain you will use for
|
# Replace this by the definitive, public domain you will use for
|
||||||
# your instance
|
# your instance
|
||||||
FUNKWHALE_URL=https.//yourdomain.funwhale
|
FUNKWHALE_URL=https://yourdomain.funwhale
|
||||||
|
|
||||||
# API/Django configuration
|
# API/Django configuration
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ User=funkwhale
|
||||||
# adapt this depending on the path of your funkwhale installation
|
# adapt this depending on the path of your funkwhale installation
|
||||||
WorkingDirectory=/srv/funkwhale/api
|
WorkingDirectory=/srv/funkwhale/api
|
||||||
EnvironmentFile=/srv/funkwhale/config/.env
|
EnvironmentFile=/srv/funkwhale/config/.env
|
||||||
ExecStart=/usr/local/bin/daphne -b ${FUNKWHALE_API_IP} -p ${FUNKWHALE_API_PORT} config.asgi:application
|
ExecStart=/srv/funkwhale/virtualenv/bin/daphne -b ${FUNKWHALE_API_IP} -p ${FUNKWHALE_API_PORT} config.asgi:application
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
|
|
@ -8,7 +8,7 @@ User=funkwhale
|
||||||
# adapt this depending on the path of your funkwhale installation
|
# adapt this depending on the path of your funkwhale installation
|
||||||
WorkingDirectory=/srv/funkwhale/api
|
WorkingDirectory=/srv/funkwhale/api
|
||||||
EnvironmentFile=/srv/funkwhale/config/.env
|
EnvironmentFile=/srv/funkwhale/config/.env
|
||||||
ExecStart=/srv/funkwhale/virtualenv/bin/python manage.py celery worker
|
ExecStart=/srv/funkwhale/virtualenv/bin/celery -A funkwhale_api.taskapp worker -l INFO
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
# global proxy conf
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-Forwarded-Host $host:$server_port;
|
||||||
|
proxy_set_header X-Forwarded-Port $server_port;
|
||||||
|
proxy_redirect off;
|
||||||
|
|
||||||
|
# websocket support
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection $connection_upgrade;
|
|
@ -48,20 +48,6 @@ server {
|
||||||
|
|
||||||
root /srv/funkwhale/front/dist;
|
root /srv/funkwhale/front/dist;
|
||||||
|
|
||||||
# global proxy conf
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
proxy_set_header X-Forwarded-Host $host:$server_port;
|
|
||||||
proxy_set_header X-Forwarded-Port $server_port;
|
|
||||||
proxy_redirect off;
|
|
||||||
|
|
||||||
# websocket support
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
|
||||||
proxy_set_header Connection $connection_upgrade;
|
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
try_files $uri $uri/ @rewrites;
|
try_files $uri $uri/ @rewrites;
|
||||||
}
|
}
|
||||||
|
@ -70,6 +56,7 @@ server {
|
||||||
rewrite ^(.+)$ /index.html last;
|
rewrite ^(.+)$ /index.html last;
|
||||||
}
|
}
|
||||||
location /api/ {
|
location /api/ {
|
||||||
|
include /etc/nginx/funkwhale_proxy.conf;
|
||||||
# this is needed if you have file import via upload enabled
|
# this is needed if you have file import via upload enabled
|
||||||
client_max_body_size 30M;
|
client_max_body_size 30M;
|
||||||
proxy_pass http://funkwhale-api/api/;
|
proxy_pass http://funkwhale-api/api/;
|
||||||
|
@ -89,6 +76,7 @@ server {
|
||||||
|
|
||||||
# Transcoding logic and caching
|
# Transcoding logic and caching
|
||||||
location = /transcode-auth {
|
location = /transcode-auth {
|
||||||
|
include /etc/nginx/funkwhale_proxy.conf;
|
||||||
# needed so we can authenticate transcode requests, but still
|
# needed so we can authenticate transcode requests, but still
|
||||||
# cache the result
|
# cache the result
|
||||||
internal;
|
internal;
|
||||||
|
@ -97,14 +85,13 @@ server {
|
||||||
if ($request_uri ~* "[^\?]+\?(.*)$") {
|
if ($request_uri ~* "[^\?]+\?(.*)$") {
|
||||||
set $query $1;
|
set $query $1;
|
||||||
}
|
}
|
||||||
proxy_set_header X-Forwarded-Host $host:$server_port;
|
|
||||||
proxy_set_header X-Forwarded-Port $server_port;
|
|
||||||
proxy_pass http://funkwhale-api/api/v1/trackfiles/viewable/?$query;
|
proxy_pass http://funkwhale-api/api/v1/trackfiles/viewable/?$query;
|
||||||
proxy_pass_request_body off;
|
proxy_pass_request_body off;
|
||||||
proxy_set_header Content-Length "";
|
proxy_set_header Content-Length "";
|
||||||
}
|
}
|
||||||
|
|
||||||
location /api/v1/trackfiles/transcode/ {
|
location /api/v1/trackfiles/transcode/ {
|
||||||
|
include /etc/nginx/funkwhale_proxy.conf;
|
||||||
# this block deals with authenticating and caching transcoding
|
# this block deals with authenticating and caching transcoding
|
||||||
# requests. Caching is heavily recommended as transcoding
|
# requests. Caching is heavily recommended as transcoding
|
||||||
# is a CPU intensive process.
|
# is a CPU intensive process.
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
Instance configuration
|
||||||
|
======================
|
||||||
|
|
||||||
|
General configuration is achieved using two type of settings.
|
||||||
|
|
||||||
|
Environment variables
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
Those are located in your ``.env`` file, which you should have created
|
||||||
|
during installation.
|
||||||
|
|
||||||
|
Options from this file are heavily commented, and usually target lower level
|
||||||
|
and technical aspects of your instance, such as database credentials.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
You should restart all funwhale processes when you change the values
|
||||||
|
on environment variables.
|
||||||
|
|
||||||
|
|
||||||
|
Instance settings
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
Those settings are stored in database and do not require a restart of your
|
||||||
|
instance after modification. They typically relate to higher level configuration,
|
||||||
|
such your instance description, signup policy and so on.
|
||||||
|
|
||||||
|
There is no polished interface for those settings, yet, but you can view update
|
||||||
|
them using the administration interface provided by Django (the framework funkwhale is built on).
|
||||||
|
|
||||||
|
The URL should be ``/api/admin/dynamic_preferences/globalpreferencemodel/`` (prepend your domain in front of it, of course).
|
||||||
|
|
||||||
|
If you plan to use acoustid and external imports
|
||||||
|
(e.g. with the youtube backends), you should edit the corresponding
|
||||||
|
settings in this interface.
|
|
@ -25,6 +25,10 @@ to the ``/music`` directory on the container:
|
||||||
For the best results, we recommand tagging your music collection through
|
For the best results, we recommand tagging your music collection through
|
||||||
`Picard <http://picard.musicbrainz.org/>`_ in order to have the best quality metadata.
|
`Picard <http://picard.musicbrainz.org/>`_ in order to have the best quality metadata.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Autotagging using acoustid is experimental now and can yield unexpected
|
||||||
|
result. You can disable acoustid by passing the --no-acoustid flag.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ Funkwhale is a self-hosted, modern free and open-source music server, heavily in
|
||||||
|
|
||||||
features
|
features
|
||||||
installation/index
|
installation/index
|
||||||
|
configuration
|
||||||
importing-music
|
importing-music
|
||||||
changelog
|
changelog
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,15 @@ you should now be able to open a postgresql shell:
|
||||||
|
|
||||||
sudo -u funkwhale -H psql
|
sudo -u funkwhale -H psql
|
||||||
|
|
||||||
|
Unless you give a superuser access to the database user, you should also
|
||||||
|
enable some extensions on your database server, as those are required
|
||||||
|
for funkwhale to work properly:
|
||||||
|
|
||||||
|
.. code-block:: shell
|
||||||
|
|
||||||
|
sudo -u postgres psql -c 'CREATE EXTENSION "unaccent";''
|
||||||
|
|
||||||
|
|
||||||
Cache setup (Redis)
|
Cache setup (Redis)
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
|
|
|
@ -59,10 +59,11 @@ Ensure you have a recent version of nginx on your server. On debian-like system,
|
||||||
apt-get update
|
apt-get update
|
||||||
apt-get install nginx
|
apt-get install nginx
|
||||||
|
|
||||||
Then, download our sample virtualhost file:
|
Then, download our sample virtualhost file and proxy conf:
|
||||||
|
|
||||||
.. parsed-literal::
|
.. parsed-literal::
|
||||||
|
|
||||||
|
curl -L -o /etc/nginx/funkwhale_proxy.conf "https://code.eliotberriot.com/funkwhale/funkwhale/raw/|version|/deploy/funkwhale_proxy.conf"
|
||||||
curl -L -o /etc/nginx/sites-enabled/funkwhale.conf "https://code.eliotberriot.com/funkwhale/funkwhale/raw/|version|/deploy/nginx.conf"
|
curl -L -o /etc/nginx/sites-enabled/funkwhale.conf "https://code.eliotberriot.com/funkwhale/funkwhale/raw/|version|/deploy/nginx.conf"
|
||||||
|
|
||||||
Ensure static assets and proxy pass match your configuration, and check the configuration is valid with ``nginx -t``. If everything is fine, you can restart your nginx server with ``service nginx restart``.
|
Ensure static assets and proxy pass match your configuration, and check the configuration is valid with ``nginx -t``. If everything is fine, you can restart your nginx server with ``service nginx restart``.
|
||||||
|
|
|
@ -1,23 +1,23 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="ui pagination borderless menu">
|
<div class="ui pagination borderless menu">
|
||||||
<a
|
|
||||||
@click="selectPage(1)"
|
|
||||||
:class="[{'disabled': current === 1}, 'item']"><i class="angle double left icon"></i></a>
|
|
||||||
<a
|
<a
|
||||||
@click="selectPage(current - 1)"
|
@click="selectPage(current - 1)"
|
||||||
:class="[{'disabled': current - 1 < 1}, 'item']"><i class="angle left icon"></i></a>
|
:class="[{'disabled': current - 1 < 1}, 'item']"><i class="angle left icon"></i></a>
|
||||||
|
<template>
|
||||||
<a
|
<a
|
||||||
|
v-if="page !== 'skip'"
|
||||||
v-for="page in pages"
|
v-for="page in pages"
|
||||||
@click="selectPage(page)"
|
@click="selectPage(page)"
|
||||||
:class="[{'active': page === current}, 'item']">
|
:class="[{'active': page === current}, 'item']">
|
||||||
{{ page }}
|
{{ page }}
|
||||||
</a>
|
</a>
|
||||||
|
<a v-else class="disabled item">
|
||||||
|
...
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
<a
|
<a
|
||||||
@click="selectPage(current + 1)"
|
@click="selectPage(current + 1)"
|
||||||
:class="[{'disabled': current + 1 > maxPage}, 'item']"><i class="angle right icon"></i></a>
|
:class="[{'disabled': current + 1 > maxPage}, 'item']"><i class="angle right icon"></i></a>
|
||||||
<a
|
|
||||||
@click="selectPage(maxPage)"
|
|
||||||
:class="[{'disabled': current === maxPage}, 'item']"><i class="angle double right icon"></i></a>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -32,7 +32,38 @@ export default {
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
pages: function () {
|
pages: function () {
|
||||||
return _.range(1, this.maxPage + 1)
|
let range = 2
|
||||||
|
let current = this.current
|
||||||
|
let beginning = _.range(1, Math.min(this.maxPage, 1 + range))
|
||||||
|
let middle = _.range(Math.max(1, current - range + 1), Math.min(this.maxPage, current + range))
|
||||||
|
let end = _.range(this.maxPage, Math.max(1, this.maxPage - range))
|
||||||
|
let allowed = beginning.concat(middle, end)
|
||||||
|
allowed = _.uniq(allowed)
|
||||||
|
allowed = _.sortBy(allowed, [(e) => { return e }])
|
||||||
|
let final = []
|
||||||
|
allowed.forEach(p => {
|
||||||
|
let last = final.slice(-1)[0]
|
||||||
|
let consecutive = true
|
||||||
|
if (last === 'skip') {
|
||||||
|
consecutive = false
|
||||||
|
} else {
|
||||||
|
if (!last) {
|
||||||
|
consecutive = true
|
||||||
|
} else {
|
||||||
|
consecutive = last + 1 === p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (consecutive) {
|
||||||
|
final.push(p)
|
||||||
|
} else {
|
||||||
|
if (p !== 'skip') {
|
||||||
|
final.push('skip')
|
||||||
|
final.push(p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
console.log(final)
|
||||||
|
return final
|
||||||
},
|
},
|
||||||
maxPage: function () {
|
maxPage: function () {
|
||||||
return Math.ceil(this.total / this.paginateBy)
|
return Math.ceil(this.total / this.paginateBy)
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
<template>
|
<template>
|
||||||
<div :class="['ui', {'tiny': discrete}, 'buttons']">
|
<div :class="['ui', {'tiny': discrete}, 'buttons']">
|
||||||
<button title="Add to current queue" @click="add" :class="['ui', {'mini': discrete}, {disabled: playableTracks.length === 0}, 'button']">
|
<button
|
||||||
|
title="Add to current queue"
|
||||||
|
@click="add"
|
||||||
|
:class="['ui', {loading: isLoading}, {'mini': discrete}, {disabled: playableTracks.length === 0}, 'button']">
|
||||||
<i class="ui play icon"></i>
|
<i class="ui play icon"></i>
|
||||||
<template v-if="!discrete"><slot>Play</slot></template>
|
<template v-if="!discrete"><slot>Play</slot></template>
|
||||||
</button>
|
</button>
|
||||||
|
@ -26,6 +29,11 @@ export default {
|
||||||
track: {type: Object, required: false},
|
track: {type: Object, required: false},
|
||||||
discrete: {type: Boolean, default: false}
|
discrete: {type: Boolean, default: false}
|
||||||
},
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
isLoading: false
|
||||||
|
}
|
||||||
|
},
|
||||||
created () {
|
created () {
|
||||||
if (!this.track & !this.tracks) {
|
if (!this.track & !this.tracks) {
|
||||||
logger.default.error('You have to provide either a track or tracks property')
|
logger.default.error('You have to provide either a track or tracks property')
|
||||||
|
@ -50,10 +58,19 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
triggerLoad () {
|
||||||
|
let self = this
|
||||||
|
this.isLoading = true
|
||||||
|
setTimeout(() => {
|
||||||
|
self.isLoading = false
|
||||||
|
}, 500)
|
||||||
|
},
|
||||||
add () {
|
add () {
|
||||||
|
this.triggerLoad()
|
||||||
this.$store.dispatch('queue/appendMany', {tracks: this.playableTracks})
|
this.$store.dispatch('queue/appendMany', {tracks: this.playableTracks})
|
||||||
},
|
},
|
||||||
addNext (next) {
|
addNext (next) {
|
||||||
|
this.triggerLoad()
|
||||||
this.$store.dispatch('queue/appendMany', {tracks: this.playableTracks, index: this.$store.state.queue.currentIndex + 1})
|
this.$store.dispatch('queue/appendMany', {tracks: this.playableTracks, index: this.$store.state.queue.currentIndex + 1})
|
||||||
if (next) {
|
if (next) {
|
||||||
this.$store.dispatch('queue/next')
|
this.$store.dispatch('queue/next')
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 434 KiB After Width: | Height: | Size: 957 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
Loading…
Reference in New Issue