From bcd35842cd19a62d7519b115acc3642a3e2ffafb Mon Sep 17 00:00:00 2001 From: Colin Powell Date: Sun, 26 Mar 2023 12:30:06 -0400 Subject: [PATCH] Add new fields to page to use it better --- .../books/migrations/0012_page_end_time.py | 18 +++++ .../apps/books/migrations/0013_page_user.py | 26 +++++++ vrobbler/apps/books/models.py | 74 +++++++++++++++++++ 3 files changed, 118 insertions(+) create mode 100644 vrobbler/apps/books/migrations/0012_page_end_time.py create mode 100644 vrobbler/apps/books/migrations/0013_page_user.py diff --git a/vrobbler/apps/books/migrations/0012_page_end_time.py b/vrobbler/apps/books/migrations/0012_page_end_time.py new file mode 100644 index 0000000..0190602 --- /dev/null +++ b/vrobbler/apps/books/migrations/0012_page_end_time.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1.7 on 2023-03-26 02:34 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("books", "0011_book_genre"), + ] + + operations = [ + migrations.AddField( + model_name="page", + name="end_time", + field=models.DateTimeField(blank=True, null=True), + ), + ] diff --git a/vrobbler/apps/books/migrations/0013_page_user.py b/vrobbler/apps/books/migrations/0013_page_user.py new file mode 100644 index 0000000..b00c642 --- /dev/null +++ b/vrobbler/apps/books/migrations/0013_page_user.py @@ -0,0 +1,26 @@ +# Generated by Django 4.1.7 on 2023-03-26 05:31 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("books", "0012_page_end_time"), + ] + + operations = [ + migrations.AddField( + model_name="page", + name="user", + field=models.ForeignKey( + default=1, + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + preserve_default=False, + ), + ] diff --git a/vrobbler/apps/books/models.py b/vrobbler/apps/books/models.py index 893f27b..21c1195 100644 --- a/vrobbler/apps/books/models.py +++ b/vrobbler/apps/books/models.py @@ -1,4 +1,5 @@ import logging +from datetime import timedelta from uuid import uuid4 import requests @@ -182,9 +183,11 @@ class Book(LongPlayScrobblableMixin): class Page(TimeStampedModel): + user = models.ForeignKey(User, on_delete=models.CASCADE) book = models.ForeignKey(Book, on_delete=models.CASCADE) number = models.IntegerField() start_time = models.DateTimeField(**BNULL) + end_time = models.DateTimeField(**BNULL) duration_seconds = models.IntegerField(**BNULL) class Meta: @@ -195,3 +198,74 @@ class Page(TimeStampedModel): def __str__(self): return f"Page {self.number} of {self.book.pages} in {self.book.title}" + + def save(self, *args, **kwargs): + if not self.end_time and self.duration_seconds: + self._set_end_time() + + return super(Page, self).save(*args, **kwargs) + + @property + def next(self): + page = self.book.page_set.filter(number=self.number + 1).first() + if not page: + page = ( + self.book.page_set.filter(created__gt=self.created) + .order_by("created") + .first() + ) + return page + + @property + def previous(self): + page = self.book.page_set.filter(number=self.number - 1).first() + if not page: + page = ( + self.book.page_set.filter(created__lt=self.created) + .order_by("-created") + .first() + ) + return page + + @property + def seconds_to_next_page(self) -> int: + seconds = 0 + if not self.end_time: + self._set_end_time() + if self.next: + seconds = (self.next.start_time - self.end_time).seconds + return seconds + + @property + def is_scrobblable(self) -> bool: + """A page defines the start of a scrobble if the seconds to next page + are greater than an hour, or 3600 seconds, and it's not a single page, + so the next seconds to next_page is less than an hour as well. + + As a special case, the first recorded page is a scrobble, so we establish + when the book was started. + + """ + is_scrobblable = False + over_an_hour_since_last_page = False + if not self.previous: + is_scrobblable = True + + if self.previous: + over_an_hour_since_last_page = ( + self.previous.seconds_to_next_page >= 3600 + ) + blip = self.seconds_to_next_page >= 3600 + + if over_an_hour_since_last_page and not blip: + is_scrobblable = True + return is_scrobblable + + def _set_end_time(self) -> None: + if self.end_time: + return self.end_time + + self.end_time = self.start_time + timedelta( + seconds=self.duration_seconds + ) + self.save(update_fields=["end_time"])