Initial commit - SoundWave v1.0
- Full PWA support with offline capabilities - Comprehensive search across songs, playlists, and channels - Offline playlist manager with download tracking - Pre-built frontend for zero-build deployment - Docker-based deployment with docker compose - Material-UI dark theme interface - YouTube audio download and management - Multi-user authentication support
This commit is contained in:
commit
51679d1943
254 changed files with 37281 additions and 0 deletions
207
backend/playlist/views_download.py
Normal file
207
backend/playlist/views_download.py
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
"""Views for playlist downloads"""
|
||||
|
||||
from rest_framework import viewsets, status
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
from playlist.models import Playlist
|
||||
from playlist.models_download import PlaylistDownload, PlaylistDownloadItem
|
||||
from playlist.serializers_download import (
|
||||
PlaylistDownloadSerializer,
|
||||
PlaylistDownloadCreateSerializer,
|
||||
PlaylistDownloadItemSerializer,
|
||||
)
|
||||
from playlist.tasks_download import (
|
||||
download_playlist_task,
|
||||
pause_playlist_download,
|
||||
resume_playlist_download,
|
||||
cancel_playlist_download,
|
||||
retry_failed_items,
|
||||
)
|
||||
from common.permissions import IsOwnerOrAdmin
|
||||
|
||||
|
||||
class PlaylistDownloadViewSet(viewsets.ModelViewSet):
|
||||
"""ViewSet for managing playlist downloads"""
|
||||
permission_classes = [IsAuthenticated, IsOwnerOrAdmin]
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.action == 'create':
|
||||
return PlaylistDownloadCreateSerializer
|
||||
return PlaylistDownloadSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
"""Filter by user"""
|
||||
queryset = PlaylistDownload.objects.select_related(
|
||||
'playlist', 'user'
|
||||
).prefetch_related('items')
|
||||
|
||||
# Regular users see only their downloads
|
||||
if not (self.request.user.is_admin or self.request.user.is_superuser):
|
||||
queryset = queryset.filter(user=self.request.user)
|
||||
|
||||
# Filter by status
|
||||
status_filter = self.request.query_params.get('status')
|
||||
if status_filter:
|
||||
queryset = queryset.filter(status=status_filter)
|
||||
|
||||
# Filter by playlist
|
||||
playlist_id = self.request.query_params.get('playlist_id')
|
||||
if playlist_id:
|
||||
queryset = queryset.filter(playlist_id=playlist_id)
|
||||
|
||||
return queryset.order_by('-created_at')
|
||||
|
||||
def perform_create(self, serializer):
|
||||
"""Create download and trigger task"""
|
||||
download = serializer.save(user=self.request.user)
|
||||
|
||||
# Trigger download task
|
||||
download_playlist_task.apply_async(args=[download.id])
|
||||
|
||||
return download
|
||||
|
||||
@action(detail=True, methods=['post'])
|
||||
def pause(self, request, pk=None):
|
||||
"""Pause playlist download"""
|
||||
download = self.get_object()
|
||||
|
||||
if download.status != 'downloading':
|
||||
return Response(
|
||||
{'error': 'Can only pause downloading playlists'},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
result = pause_playlist_download.apply_async(args=[download.id])
|
||||
|
||||
return Response({
|
||||
'message': 'Playlist download paused',
|
||||
'task_id': result.id
|
||||
})
|
||||
|
||||
@action(detail=True, methods=['post'])
|
||||
def resume(self, request, pk=None):
|
||||
"""Resume paused playlist download"""
|
||||
download = self.get_object()
|
||||
|
||||
if not download.can_resume:
|
||||
return Response(
|
||||
{'error': 'Download cannot be resumed'},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
result = resume_playlist_download.apply_async(args=[download.id])
|
||||
|
||||
return Response({
|
||||
'message': 'Playlist download resumed',
|
||||
'task_id': result.id
|
||||
})
|
||||
|
||||
@action(detail=True, methods=['post'])
|
||||
def cancel(self, request, pk=None):
|
||||
"""Cancel playlist download"""
|
||||
download = self.get_object()
|
||||
|
||||
if download.status in ['completed', 'failed']:
|
||||
return Response(
|
||||
{'error': 'Cannot cancel completed or failed download'},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
result = cancel_playlist_download.apply_async(args=[download.id])
|
||||
|
||||
return Response({
|
||||
'message': 'Playlist download cancelled',
|
||||
'task_id': result.id
|
||||
})
|
||||
|
||||
@action(detail=True, methods=['post'])
|
||||
def retry_failed(self, request, pk=None):
|
||||
"""Retry failed items"""
|
||||
download = self.get_object()
|
||||
|
||||
if download.failed_items == 0:
|
||||
return Response(
|
||||
{'error': 'No failed items to retry'},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
result = retry_failed_items.apply_async(args=[download.id])
|
||||
|
||||
return Response({
|
||||
'message': f'Retrying {download.failed_items} failed items',
|
||||
'task_id': result.id
|
||||
})
|
||||
|
||||
@action(detail=True, methods=['get'])
|
||||
def items(self, request, pk=None):
|
||||
"""Get download items with status"""
|
||||
download = self.get_object()
|
||||
items = download.items.select_related('audio').order_by('position')
|
||||
|
||||
serializer = PlaylistDownloadItemSerializer(items, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
@action(detail=False, methods=['get'])
|
||||
def active(self, request):
|
||||
"""Get active downloads (pending or downloading)"""
|
||||
downloads = self.get_queryset().filter(
|
||||
status__in=['pending', 'downloading']
|
||||
)
|
||||
|
||||
serializer = self.get_serializer(downloads, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
@action(detail=False, methods=['get'])
|
||||
def completed(self, request):
|
||||
"""Get completed downloads"""
|
||||
downloads = self.get_queryset().filter(status='completed')
|
||||
|
||||
serializer = self.get_serializer(downloads, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
@action(detail=False, methods=['post'])
|
||||
def download_playlist(self, request):
|
||||
"""Quick action to download a playlist"""
|
||||
playlist_id = request.data.get('playlist_id')
|
||||
quality = request.data.get('quality', 'medium')
|
||||
|
||||
if not playlist_id:
|
||||
return Response(
|
||||
{'error': 'playlist_id is required'},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
# Get playlist
|
||||
playlist = get_object_or_404(Playlist, id=playlist_id, owner=request.user)
|
||||
|
||||
# Check if already downloading
|
||||
existing = PlaylistDownload.objects.filter(
|
||||
playlist=playlist,
|
||||
user=request.user,
|
||||
status__in=['pending', 'downloading']
|
||||
).first()
|
||||
|
||||
if existing:
|
||||
return Response(
|
||||
{
|
||||
'error': 'Playlist is already being downloaded',
|
||||
'download': PlaylistDownloadSerializer(existing).data
|
||||
},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
# Create download
|
||||
download = PlaylistDownload.objects.create(
|
||||
playlist=playlist,
|
||||
user=request.user,
|
||||
quality=quality
|
||||
)
|
||||
|
||||
# Trigger task
|
||||
download_playlist_task.apply_async(args=[download.id])
|
||||
|
||||
serializer = PlaylistDownloadSerializer(download)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
Loading…
Add table
Add a link
Reference in a new issue