Files
MarietjeDjango/marietje/api/views.py
2017-11-02 18:45:33 +01:00

301 lines
9.4 KiB
Python

import time
from functools import wraps
import django.middleware.csrf as csrf
from django.contrib.auth import authenticate, login
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.db import transaction
from django.db.models import Q, Sum, Value
from django.db.models.functions import Coalesce
from django.http import JsonResponse, HttpResponseForbidden
from django.shortcuts import get_object_or_404
from django.views.decorators.http import require_http_methods
from django.conf import settings
from mutagen import File
from marietje.utils import song_to_dict, playlist_song_to_dict, send_to_bertha
from queues.models import PlaylistSong, QueueCommand
from songs.models import Song
def api_auth_required(view_func):
@wraps(view_func)
def _wrapped_view(request, *args, **kwargs):
if request.user.is_authenticated and request.user.is_active:
return view_func(request, *args, **kwargs)
response = JsonResponse({
'error': 'User not authenticated or activated.'
})
response.status_code = 401
return response
return _wrapped_view
def login_user(request):
data = {'error': 'Method not allowed'}
status = 405
if request.method == "POST":
username = request.POST.get('username', '').strip()
password = request.POST.get('password', '').strip()
data = {'error': 'Please enter a correct username and password. '
'Note that both fields may be case-sensitive.'}
status = 401
if username and password:
user = authenticate(username=username, password=password)
if user is not None:
if user.is_active:
login(request, user)
data = {}
status = 200
else:
data = {'error': 'User is not active'}
status = 401
else:
csrf.get_token(request)
response = JsonResponse(data)
response.status_code = status
return response
@api_auth_required
def permissions(request):
return JsonResponse({
'can_move': request.user.has_perm('queues.can_move'),
'can_skip': request.user.has_perm('queues.can_skip'),
'can_cancel': request.user.has_perm('queues.can_cancel'),
'can_control_volume': request.user.has_perm('queues.can_control_volume')
})
@api_auth_required
def songs(request):
try:
pagesize = int(request.POST.get('pagesize'))
except:
pagesize = 10
if not pagesize or pagesize < 10:
pagesize = 10
try:
page = int(request.POST.get('page'))
except:
page = 1
queries = [Q(deleted=False)]
queries.extend([Q(Q(artist__icontains=word) | Q(title__icontains=word)) for word in request.POST.get('all', '').split()])
queries.extend([Q(user__name__icontains=word) for word in request.POST.get('uploader', '').split()])
filter_query = queries.pop()
for query in queries:
filter_query &= query
songs_query = Song.objects.filter(filter_query).order_by('artist', 'title').select_related('user')
paginator = Paginator(songs_query, pagesize)
try:
songs = paginator.page(page)
except PageNotAnInteger:
songs = paginator.page(1)
except EmptyPage:
songs = paginator.page(paginator.num_pages)
songs_dict = [song_to_dict(song, user=True) for song in songs.object_list]
return JsonResponse({
'per_page': pagesize,
'current_page': page,
'last_page': paginator.num_pages,
'data': songs_dict
})
@api_auth_required
def managesongs(request):
try:
pagesize = int(request.POST.get('pagesize'))
except:
pagesize = 10
if not pagesize or pagesize < 10:
pagesize = 10
try:
page = int(request.POST.get('page'))
except:
page = 1
songs_query = Song.objects.filter(
user=request.user, deleted=False,
artist__icontains=request.POST.get('artist', ''),
title__icontains=request.POST.get('title', '')
).order_by('artist', 'title')
total = songs_query.count()
paginator = Paginator(songs_query, pagesize)
try:
songs = paginator.page(page)
except PageNotAnInteger:
songs = paginator.page(1)
except EmptyPage:
songs = paginator.page(paginator.num_pages)
songs_dict = [song_to_dict(song) for song in songs.object_list]
return JsonResponse({
'total': total,
'per_page': pagesize,
'current_page': page,
'last_page': paginator.num_pages,
'data': songs_dict
})
@api_auth_required
def queue(request):
queue = request.user.queue
return JsonResponse({
'current_song': playlist_song_to_dict(queue.current_song()),
'queue': [playlist_song_to_dict(playlist_song, user=request.user) for playlist_song in queue.queue()],
'started_at': 0 if queue.started_at is None else int(queue.started_at.timestamp()),
'current_time': int(time.time())
})
@api_auth_required
def skip(request):
playlist_song = request.user.queue.current_song()
if playlist_song.user != request.user and not request.user.has_perm('queues.can_skip'):
return HttpResponseForbidden()
playlist_song.state = 2
playlist_song.save()
return JsonResponse({})
@require_http_methods(["POST"])
@api_auth_required
def move_up(request):
if not request.user.has_perm('queues.can_move'):
return HttpResponseForbidden()
playlist_song = get_object_or_404(PlaylistSong, id=request.POST.get('id'))
playlist_song.move_up()
return JsonResponse({})
@require_http_methods(["POST"])
@api_auth_required
def move_down(request):
playlist_song = get_object_or_404(PlaylistSong, id=request.POST.get('id'))
if playlist_song.user != request.user and not request.user.has_perm('queues.can_move'):
return HttpResponseForbidden()
playlist_song.move_down()
return JsonResponse({})
@require_http_methods(["POST"])
@api_auth_required
def cancel(request):
playlist_song = get_object_or_404(PlaylistSong, id=request.POST.get('id'))
if playlist_song.user != request.user and not request.user.has_perm('queues.can_cancel'):
return HttpResponseForbidden()
playlist_song.state = 3
playlist_song.save()
return JsonResponse({})
@require_http_methods(["POST"])
@api_auth_required
def request(request):
queue = request.user.queue
song = get_object_or_404(Song, id=request.POST.get('id'), deleted=False)
if queue.request(song, request.user):
return JsonResponse({
'success': True
})
return JsonResponse({
'success': False,
'message': 'You cannot request more than ' + str(settings.MAX_MINUTES_IN_A_ROW) + ' minutes in a row.'
})
@require_http_methods(["POST"])
@api_auth_required
def upload(request):
files = request.FILES.getlist('file[]')
artists = request.POST.getlist('artist[]')
titles = request.POST.getlist('title[]')
for artist in artists:
if not artist:
return JsonResponse({'success': False, 'errorMessage': 'Please enter artists which are not empty.'})
for title in titles:
if not title:
return JsonResponse({'success': False, 'errorMessage': 'Please enter titles which are not empty.'})
# Allow upload if the user has a good reputation
# Score function:
# - U = duration * songs uploaded
# - Q = duration * songs queued
# - If 3*U < Q: allow upload (otherwise don't)
stats = upload_stats(request.user)
ratio = stats['minutes_queued'] / (3.0 * stats['minutes_upload'])
if not request.user.is_superuser and ratio < 1.0:
msg = 'Queue-to-upload ratio too high. Please queue some more before uploading. ({:.2f})'
return JsonResponse({'success': False, 'errorMessage': msg.format(ratio)})
for i, file in enumerate(files):
duration = File(file).info.length
hash = send_to_bertha(file)
if not hash:
return JsonResponse({'success': False, 'errorMessage': 'Files not uploaded correctly.'})
song = Song(user=request.user, artist=artists[i], title=titles[i], hash=hash, duration=duration)
song.save()
return JsonResponse({'success': True})
@require_http_methods(["POST"])
@api_auth_required
def volume_down(request):
if not request.user.has_perm('queues.can_control_volume'):
return HttpResponseForbidden()
command = QueueCommand(queue=request.user.queue, command='volume_down')
command.save()
return JsonResponse({})
@require_http_methods(["POST"])
@api_auth_required
def volume_up(request):
if not request.user.has_perm('queues.can_control_volume'):
return HttpResponseForbidden()
command = QueueCommand(queue=request.user.queue, command='volume_up')
command.save()
return JsonResponse({})
@require_http_methods(["POST"])
@api_auth_required
def mute(request):
if not request.user.has_perm('queues.can_control_volume'):
return HttpResponseForbidden()
command = QueueCommand(queue=request.user.queue, command='mute')
command.save()
return JsonResponse({})
@transaction.atomic
def upload_stats(user):
q = PlaylistSong.objects.filter(user=user, song__deleted=False).aggregate(
minutes_queued=Coalesce(Sum('song__duration'), Value(0)))
q.update(Song.objects.filter(user=user, deleted=False).aggregate(
minutes_upload=Coalesce(Sum('duration'), Value(0))))
return q