Parcourir la source

Add ability to cancel and finish manual scrobbles

Colin Powell il y a 2 ans
Parent
commit
646c7ab99c

+ 35 - 0
vrobbler/apps/scrobbles/migrations/0009_scrobble_uuid.py

@@ -0,0 +1,35 @@
+# Generated by Django 4.1.5 on 2023-01-20 18:40
+
+from uuid import uuid4
+from django.db import migrations, models
+
+
+def generate_uuids(apps, schema_editor):
+    """Force uuid generation for old scrobbles"""
+    Scrobble = apps.get_model('scrobbles', 'Scrobble')
+    for scrobble in Scrobble.objects.all():
+        if not scrobble.uuid:
+            scrobble.uuid = uuid4()
+            scrobble.save(update_fields=['uuid'])
+
+
+def reverse_generate_uuids(apps, schema_editor):
+    pass
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('scrobbles', '0008_scrobble_sport_event'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='scrobble',
+            name='uuid',
+            field=models.UUIDField(blank=True, editable=False, null=True),
+        ),
+        migrations.RunPython(
+            code=generate_uuids, reverse_code=reverse_generate_uuids
+        ),
+    ]

+ 25 - 3
vrobbler/apps/scrobbles/models.py

@@ -1,5 +1,6 @@
 import logging
 from datetime import timedelta
+from uuid import uuid4
 
 from django.contrib.auth import get_user_model
 from django.db import models
@@ -8,8 +9,8 @@ from django_extensions.db.models import TimeStampedModel
 from music.models import Track
 from podcasts.models import Episode
 from scrobbles.utils import check_scrobble_for_finish
-from videos.models import Video
 from sports.models import SportEvent
+from videos.models import Video
 
 logger = logging.getLogger(__name__)
 User = get_user_model()
@@ -17,6 +18,7 @@ BNULL = {"blank": True, "null": True}
 
 
 class Scrobble(TimeStampedModel):
+    uuid = models.UUIDField(editable=False, **BNULL)
     video = models.ForeignKey(Video, on_delete=models.DO_NOTHING, **BNULL)
     track = models.ForeignKey(Track, on_delete=models.DO_NOTHING, **BNULL)
     podcast_episode = models.ForeignKey(
@@ -38,6 +40,22 @@ class Scrobble(TimeStampedModel):
     in_progress = models.BooleanField(default=True)
     scrobble_log = models.TextField(**BNULL)
 
+    def save(self, *args, **kwargs):
+        if not self.uuid:
+            self.uuid = uuid4()
+
+        return super(Scrobble, self).save(*args, **kwargs)
+
+    @property
+    def status(self) -> str:
+        if self.is_paused:
+            return 'paused'
+        if self.played_to_completion:
+            return 'finished'
+        if self.in_progress:
+            return 'in-progress'
+        return 'zombie'
+
     @property
     def percent_played(self) -> int:
         if not self.media_obj.run_time_ticks:
@@ -231,13 +249,13 @@ class Scrobble(TimeStampedModel):
         )
         return scrobble
 
-    def stop(self) -> None:
+    def stop(self, force_finish=False) -> None:
         if not self.in_progress:
             logger.warning("Scrobble already stopped")
             return
         self.in_progress = False
         self.save(update_fields=['in_progress'])
-        check_scrobble_for_finish(self)
+        check_scrobble_for_finish(self, force_finish)
 
     def pause(self) -> None:
         if self.is_paused:
@@ -253,6 +271,10 @@ class Scrobble(TimeStampedModel):
             self.in_progress = True
             return self.save(update_fields=["is_paused", "in_progress"])
 
+    def cancel(self) -> None:
+        check_scrobble_for_finish(self, force_finish=True)
+        self.delete()
+
     def update_ticks(self, data) -> None:
         self.playback_position_ticks = data.get("playback_position_ticks")
         self.playback_position = data.get("playback_position")

+ 3 - 1
vrobbler/apps/scrobbles/urls.py

@@ -4,7 +4,9 @@ from scrobbles import views
 app_name = 'scrobbles'
 
 urlpatterns = [
-    path('', views.scrobble_endpoint, name='scrobble-list'),
+    path('', views.scrobble_endpoint, name='api-list'),
+    path('finish/<slug:uuid>', views.scrobble_finish, name='finish'),
+    path('cancel/<slug:uuid>', views.scrobble_cancel, name='cancel'),
     path('jellyfin/', views.jellyfin_websocket, name='jellyfin-websocket'),
     path('mopidy/', views.mopidy_websocket, name='mopidy-websocket'),
 ]

+ 4 - 2
vrobbler/apps/scrobbles/utils.py

@@ -66,10 +66,12 @@ def parse_mopidy_uri(uri: str) -> dict:
     }
 
 
-def check_scrobble_for_finish(scrobble: "Scrobble") -> None:
+def check_scrobble_for_finish(
+    scrobble: "Scrobble", force_finish=False
+) -> None:
     completion_percent = scrobble.media_obj.COMPLETION_PERCENT
 
-    if scrobble.percent_played >= completion_percent:
+    if scrobble.percent_played >= completion_percent or force_finish:
         logger.debug(f"Completion percent {completion_percent} met, finishing")
 
         scrobble.in_progress = False

+ 33 - 0
vrobbler/apps/scrobbles/views.py

@@ -177,3 +177,36 @@ def mopidy_websocket(request):
         return Response({}, status=status.HTTP_400_BAD_REQUEST)
 
     return Response({'scrobble_id': scrobble.id}, status=status.HTTP_200_OK)
+
+
+@csrf_exempt
+@api_view(['GET'])
+def scrobble_finish(request, uuid):
+    user = request.user
+    if not user.is_authenticated:
+        return Response({}, status=status.HTTP_403_FORBIDDEN)
+
+    scrobble = Scrobble.objects.filter(user=user, uuid=uuid).first()
+    if not scrobble:
+        return Response({}, status=status.HTTP_404_NOT_FOUND)
+    scrobble.stop(force_finish=True)
+    return Response(
+        {'id': scrobble.id, 'status': scrobble.status},
+        status=status.HTTP_200_OK,
+    )
+
+
+@csrf_exempt
+@api_view(['GET'])
+def scrobble_cancel(request, uuid):
+    user = request.user
+    if not user.is_authenticated:
+        return Response({}, status=status.HTTP_403_FORBIDDEN)
+
+    scrobble = Scrobble.objects.filter(user=user, uuid=uuid).first()
+    if not scrobble:
+        return Response({}, status=status.HTTP_404_NOT_FOUND)
+    scrobble.cancel()
+    return Response(
+        {'id': scrobble.id, 'status': 'cancelled'}, status=status.HTTP_200_OK
+    )

+ 2 - 0
vrobbler/templates/base.html

@@ -212,6 +212,8 @@
                                 <div class="progress-bar" style="margin-right:5px;">
                                     <span class="progress-bar-fill" style="width: {{scrobble.percent_played}}%;"></span>
                                 </div>
+                                <a href="{% url "scrobbles:cancel" scrobble.uuid %}">Cancel</a>
+                                <a href="{% url "scrobbles:finish" scrobble.uuid %}">Finish</a>
                             </div>
                             <hr/>
                             {% endfor %}

+ 2 - 3
vrobbler/urls.py

@@ -1,12 +1,11 @@
+import scrobbles.views as scrobbles_views
 from django.conf import settings
 from django.conf.urls.static import static
 from django.contrib import admin
 from django.urls import include, path
 from rest_framework import routers
-import scrobbles.views as scrobbles_views
-from videos import urls as video_urls
-
 from scrobbles import urls as scrobble_urls
+from videos import urls as video_urls
 
 urlpatterns = [
     path("admin/", admin.site.urls),