diff --git a/vrobbler/apps/books/koreader.py b/vrobbler/apps/books/koreader.py index ca7d4c0..d315a37 100644 --- a/vrobbler/apps/books/koreader.py +++ b/vrobbler/apps/books/koreader.py @@ -293,6 +293,11 @@ def build_scrobbles_from_book_map( logger.info( f"Queueing scrobble for {book_id}, page {cur_page_number}" ) + log_data = { + "koreader_hash": book_dict.get("hash"), + "page_data": scrobble_page_data, + "pages_read": cur_page_number, + } scrobbles_to_create.append( Scrobble( book_id=book_id, @@ -300,11 +305,9 @@ def build_scrobbles_from_book_map( source="KOReader", media_type=Scrobble.MediaType.BOOK, timestamp=timestamp, + log=log_data, stop_timestamp=stop_timestamp, playback_position_seconds=playback_position_seconds, - book_koreader_hash=book_dict.get("hash"), - book_page_data=scrobble_page_data, - book_pages_read=cur_page_number, in_progress=False, played_to_completion=True, long_play_complete=False, @@ -338,9 +341,9 @@ def fix_long_play_stats_for_scrobbles(scrobbles: list) -> None: ) else: scrobble.long_play_seconds = scrobble.playback_position_seconds - scrobble.book_pages_read = scrobble.calc_pages_read() + scrobble.log["book_pages_read"] = scrobble.calc_pages_read() - scrobble.save(update_fields=["book_pages_read", "long_play_seconds"]) + scrobble.save(update_fields=["log", "long_play_seconds"]) def process_koreader_sqlite_file(file_path, user_id) -> list: diff --git a/vrobbler/apps/books/management/commands/migrate_koreader_data_to_log_json.py b/vrobbler/apps/books/management/commands/migrate_koreader_data_to_log_json.py new file mode 100644 index 0000000..b16a65d --- /dev/null +++ b/vrobbler/apps/books/management/commands/migrate_koreader_data_to_log_json.py @@ -0,0 +1,29 @@ +import logging +import pytz +from datetime import datetime, timedelta +from books.models import Book +from django.core.management.base import BaseCommand +from scrobbles.models import Scrobble +from vrobbler.apps.books.koreader import fix_long_play_stats_for_scrobbles +from vrobbler.apps.scrobbles.utils import timestamp_user_tz_to_utc + + +logger = logging.getLogger(__name__) + + +class Command(BaseCommand): + def handle(self, *args, **options): + total_books = Book.objects.all().count() + processed_books = 0 + + for book in Book.objects.all(): + for scrobble in book.scrobble_set.all(): + log_data = { + "koreader_hash": scrobble.book_koreader_hash, + "page_data": scrobble.book_page_data, + "pages_read": scrobble.book_pages_read, + } + scrobble.log = log_data + scrobble.save(update_fields=["log"]) + processed_books += 1 + logger.info(f"Processed book {processed_books} of {total_books}") diff --git a/vrobbler/apps/scrobbles/admin.py b/vrobbler/apps/scrobbles/admin.py index fca9e08..1b9c5a1 100644 --- a/vrobbler/apps/scrobbles/admin.py +++ b/vrobbler/apps/scrobbles/admin.py @@ -30,9 +30,6 @@ class ScrobbleInline(admin.TabularInline): exclude = ( "scrobble_log", "timezone", - "book_koreader_hash", - "book_page_data", - "book_pages_read", "videogame_save_data", "videogame_screenshot", ) diff --git a/vrobbler/apps/scrobbles/dataclasses.py b/vrobbler/apps/scrobbles/dataclasses.py index 6f355c4..b17b475 100644 --- a/vrobbler/apps/scrobbles/dataclasses.py +++ b/vrobbler/apps/scrobbles/dataclasses.py @@ -85,7 +85,7 @@ class LifeEventMetadata(JSONMetadata): participant_names: Optional[list[str]] = None location: Optional[str] = None geo_location_id: Optional[int] = None - details: Optional[str] + details: Optional[str] = None def participants(self) -> list[str]: participants = [] diff --git a/vrobbler/apps/scrobbles/migrations/0057_alter_scrobble_log.py b/vrobbler/apps/scrobbles/migrations/0057_alter_scrobble_log.py new file mode 100644 index 0000000..7cef4f0 --- /dev/null +++ b/vrobbler/apps/scrobbles/migrations/0057_alter_scrobble_log.py @@ -0,0 +1,24 @@ +# Generated by Django 4.2.13 on 2024-08-06 15:27 + +from django.db import migrations, models +import scrobbles.dataclasses + + +class Migration(migrations.Migration): + + dependencies = [ + ("scrobbles", "0056_scrobble_life_event_alter_scrobble_media_type"), + ] + + operations = [ + migrations.AlterField( + model_name="scrobble", + name="log", + field=models.JSONField( + blank=True, + decoder=scrobbles.dataclasses.ScrobbleMetadataDecoder, + encoder=scrobbles.dataclasses.ScrobbleMetadataEncoder, + null=True, + ), + ), + ] diff --git a/vrobbler/apps/scrobbles/models.py b/vrobbler/apps/scrobbles/models.py index 13d5a95..ae9d084 100644 --- a/vrobbler/apps/scrobbles/models.py +++ b/vrobbler/apps/scrobbles/models.py @@ -40,6 +40,7 @@ from videogames.models import VideoGame from videos.models import Series, Video from scrobbles.dataclasses import ( BoardGameMetadata, + BookMetadata, JSONMetadata, LifeEventMetadata, ScrobbleMetadataDecoder, @@ -541,7 +542,7 @@ class Scrobble(TimeStampedModel): ) timezone = models.CharField(max_length=50, **BNULL) - # Fields for keeping track of book data + # Fields for keeping track of book data DEPRECATED, remove after migration book_koreader_hash = models.CharField(max_length=50, **BNULL) book_pages_read = models.IntegerField(**BNULL) book_page_data = models.JSONField(**BNULL) @@ -615,6 +616,8 @@ class Scrobble(TimeStampedModel): metadata_cls = BoardGameMetadata if self.media_type == self.MediaType.VIDEO: metadata_cls = VideoMetadata + if self.media_type == self.MediaType.BOOK: + metadata_cls = BookMetadata if not metadata_cls: logger.warn( @@ -725,9 +728,9 @@ class Scrobble(TimeStampedModel): @property def session_pages_read(self) -> Optional[int]: - if not self.book_pages_read: + if not self.log.get("pages_read"): return 0 - return self.book_pages_read + return self.log.get("pages_read") @property def is_long_play(self) -> bool: