[books] Fix list page_data for old book scrobbles
All checks were successful
build / test (push) Successful in 2m2s
All checks were successful
build / test (push) Successful in 2m2s
This commit is contained in:
22
PROJECT.org
22
PROJECT.org
@ -88,7 +88,7 @@ fetching and simple saving.
|
|||||||
*** Metadata sources
|
*** Metadata sources
|
||||||
**** Scraper
|
**** Scraper
|
||||||
|
|
||||||
* Backlog [1/16] :vrobbler:project:personal:
|
* Backlog [2/17] :vrobbler:project:personal:
|
||||||
** TODO [#C] Create small utility to clean up tracks scrobbled with wonky playback times :vrobbler:personal:bug:music:scrobbles:
|
** TODO [#C] Create small utility to clean up tracks scrobbled with wonky playback times :vrobbler:personal:bug:music:scrobbles:
|
||||||
:PROPERTIES:
|
:PROPERTIES:
|
||||||
:ID: 702462cf-d54b-48c6-8a7c-78b8de751deb
|
:ID: 702462cf-d54b-48c6-8a7c-78b8de751deb
|
||||||
@ -544,6 +544,26 @@ log a warning and move on.
|
|||||||
We should have a global view `/favorites/` that shows the logged in users's
|
We should have a global view `/favorites/` that shows the logged in users's
|
||||||
favorited media objects.
|
favorited media objects.
|
||||||
|
|
||||||
|
** DONE [#A] Fix book scrobbles where page_data is a list :bug:books:scrobbles:
|
||||||
|
:PROPERTIES:
|
||||||
|
:ID: 35b323fa-ccc0-4009-b227-8a0f12bbd469
|
||||||
|
:END:
|
||||||
|
|
||||||
|
*** Description
|
||||||
|
|
||||||
|
Comic scrobbling is currently kind of janky. Most of the problems boil down to them
|
||||||
|
storing saved page data in a list of dicts rather than a dict keyed off of the page number that
|
||||||
|
was read.
|
||||||
|
|
||||||
|
We need to adjust comic scrobbling to use a dict of pages keyed off the page number, and also
|
||||||
|
write a migration script that runs as a data migration to update any book scrobbles that may
|
||||||
|
have page_data as a list.
|
||||||
|
|
||||||
|
*** Example data
|
||||||
|
#+begin_src python
|
||||||
|
{"notes": null, "page_end": 27, "page_data": [{"notes": null, "end_ts": 1771815895, "duration": 14, "start_ts": 1771815881, "description": null, "page_number": "1"}, {"notes": null, "end_ts": 1771815908, "duration": 13, "start_ts": 1771815895, "description": null, "page_number": "1"}, {"notes": null, "end_ts": 1771815913, "duration": 5, "start_ts": 1771815908, "description": null, "page_number": "2"}, {"notes": null, "end_ts": 1771815933, "duration": 20, "start_ts": 1771815913, "description": null, "page_number": "3"}, {"notes": null, "end_ts": 1771815945, "duration": 12, "start_ts": 1771815933, "description": null, "page_number": "4"}, {"notes": null, "end_ts": 1771815983, "duration": 38, "start_ts": 1771815945, "description": null, "page_number": "5"}, {"notes": null, "end_ts": 1771816007, "duration": 24, "start_ts": 1771815983, "description": null, "page_number": "6"}, {"notes": null, "end_ts": 1771816011, "duration": 4, "start_ts": 1771816007, "description": null, "page_number": "7"}, {"notes": null, "end_ts": 1771816013, "duration": 2, "start_ts": 1771816011, "description": null, "page_number": "8"}, {"notes": null, "end_ts": 1771816052, "duration": 39, "start_ts": 1771816013, "description": null, "page_number": "7"}, {"notes": null, "end_ts": 1771816127, "duration": 75, "start_ts": 1771816052, "description": null, "page_number": "8"}, {"notes": null, "end_ts": 1771816134, "duration": 7, "start_ts": 1771816127, "description": null, "page_number": "9"}, {"notes": null, "end_ts": 1771816196, "duration": 62, "start_ts": 1771816134, "description": null, "page_number": "10"}, {"notes": null, "end_ts": 1771816262, "duration": 66, "start_ts": 1771816196, "description": null, "page_number": "11"}, {"notes": null, "end_ts": 1771816293, "duration": 31, "start_ts": 1771816262, "description": null, "page_number": "12"}, {"notes": null, "end_ts": 1771816322, "duration": 29, "start_ts": 1771816293, "description": null, "page_number": "13"}, {"notes": null, "end_ts": 1771816330, "duration": 8, "start_ts": 1771816322, "description": null, "page_number": "14"}, {"notes": null, "end_ts": 1771816368, "duration": 38, "start_ts": 1771816330, "description": null, "page_number": "15"}, {"notes": null, "end_ts": 1771816388, "duration": 20, "start_ts": 1771816368, "description": null, "page_number": "16"}, {"notes": null, "end_ts": 1771816482, "duration": 94, "start_ts": 1771816388, "description": null, "page_number": "17"}, {"notes": null, "end_ts": 1771816550, "duration": 68, "start_ts": 1771816482, "description": null, "page_number": "18"}, {"notes": null, "end_ts": 1771816567, "duration": 17, "start_ts": 1771816550, "description": null, "page_number": "19"}, {"notes": null, "end_ts": 1771816586, "duration": 19, "start_ts": 1771816567, "description": null, "page_number": "20"}, {"notes": null, "end_ts": 1771816597, "duration": 11, "start_ts": 1771816586, "description": null, "page_number": "21"}, {"notes": null, "end_ts": 1771816616, "duration": 19, "start_ts": 1771816597, "description": null, "page_number": "22"}, {"notes": null, "end_ts": 1771816640, "duration": 24, "start_ts": 1771816616, "description": null, "page_number": "23"}, {"notes": null, "end_ts": 1771816690, "duration": 50, "start_ts": 1771816640, "description": null, "page_number": "24"}, {"notes": null, "end_ts": 1771816702, "duration": 12, "start_ts": 1771816690, "description": null, "page_number": "25"}, {"notes": null, "end_ts": 1771816823, "duration": 121, "start_ts": 1771816702, "description": null, "page_number": "26"}, {"notes": null, "end_ts": null, "duration": null, "start_ts": 1771816823, "description": null, "page_number": "27"}], "page_start": 1, "pages_read": 27, "resume_url": null, "description": null, "koreader_hash": null, "long_play_complete": false}
|
||||||
|
|
||||||
|
#+end_src
|
||||||
** DONE [#A] Lichess imports do not set default visbility :boardgames:bug:importers:lichess:
|
** DONE [#A] Lichess imports do not set default visbility :boardgames:bug:importers:lichess:
|
||||||
:PROPERTIES:
|
:PROPERTIES:
|
||||||
:ID: a78f7c72-a20a-8db2-cde0-d92a731d4fba
|
:ID: a78f7c72-a20a-8db2-cde0-d92a731d4fba
|
||||||
|
|||||||
@ -0,0 +1,35 @@
|
|||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
def convert_page_data_to_dict(apps, schema_editor):
|
||||||
|
Scrobble = apps.get_model("scrobbles", "Scrobble")
|
||||||
|
for scrobble in Scrobble.objects.filter(media_type="Book").exclude(
|
||||||
|
log__page_data=None
|
||||||
|
):
|
||||||
|
page_data = scrobble.log.get("page_data")
|
||||||
|
if isinstance(page_data, list):
|
||||||
|
new_page_data = {}
|
||||||
|
for entry in page_data:
|
||||||
|
page_num = entry.get("page_number")
|
||||||
|
if page_num is not None:
|
||||||
|
try:
|
||||||
|
page_num = int(page_num)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
continue
|
||||||
|
new_page_data[page_num] = entry
|
||||||
|
scrobble.log["page_data"] = new_page_data
|
||||||
|
scrobble.save(update_fields=["log"])
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("scrobbles", "0095_backfill_null_visibility"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(
|
||||||
|
convert_page_data_to_dict,
|
||||||
|
reverse_code=migrations.RunPython.noop,
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -1412,20 +1412,18 @@ class Scrobble(TimeStampedModel):
|
|||||||
or scrobble_data["playback_status"] == "stopped"
|
or scrobble_data["playback_status"] == "stopped"
|
||||||
):
|
):
|
||||||
if read_log_page:
|
if read_log_page:
|
||||||
page_list = scrobble.log.get("page_data", [])
|
page_data = scrobble.log.get("page_data", {})
|
||||||
if page_list:
|
if page_data:
|
||||||
for page in page_list:
|
for page_num, page in page_data.items():
|
||||||
if not page.get("end_ts", None):
|
if not page.get("end_ts", None):
|
||||||
page["end_ts"] = int(timezone.now().timestamp())
|
page["end_ts"] = int(timezone.now().timestamp())
|
||||||
page["duration"] = page["end_ts"] - page.get("start_ts")
|
page["duration"] = page["end_ts"] - page.get("start_ts")
|
||||||
|
|
||||||
page_list.append(
|
page_data[read_log_page] = BookPageLogData(
|
||||||
BookPageLogData(
|
page_number=read_log_page,
|
||||||
page_number=read_log_page,
|
start_ts=int(timezone.now().timestamp()),
|
||||||
start_ts=int(timezone.now().timestamp()),
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
scrobble.log["page_data"] = page_list
|
scrobble.log["page_data"] = page_data
|
||||||
scrobble.save(update_fields=["log"])
|
scrobble.save(update_fields=["log"])
|
||||||
elif "log" in scrobble_data.keys() and scrobble.log:
|
elif "log" in scrobble_data.keys() and scrobble.log:
|
||||||
scrobble_data["log"] = scrobble.log | scrobble_data["log"]
|
scrobble_data["log"] = scrobble.log | scrobble_data["log"]
|
||||||
@ -1436,12 +1434,12 @@ class Scrobble(TimeStampedModel):
|
|||||||
|
|
||||||
if read_log_page:
|
if read_log_page:
|
||||||
scrobble_data["log"] = BookLogData(
|
scrobble_data["log"] = BookLogData(
|
||||||
page_data=[
|
page_data={
|
||||||
BookPageLogData(
|
read_log_page: BookPageLogData(
|
||||||
page_number=read_log_page,
|
page_number=read_log_page,
|
||||||
start_ts=int(timezone.now().timestamp()),
|
start_ts=int(timezone.now().timestamp()),
|
||||||
)
|
)
|
||||||
]
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
@ -1783,30 +1781,24 @@ class Scrobble(TimeStampedModel):
|
|||||||
page_data = self.log.get("page_data")
|
page_data = self.log.get("page_data")
|
||||||
|
|
||||||
if page_data:
|
if page_data:
|
||||||
# --- Sort safely by numeric page_number ---
|
|
||||||
def safe_page_number(entry):
|
|
||||||
try:
|
|
||||||
return int(getattr("page_number", entry), 0)
|
|
||||||
except (ValueError, TypeError):
|
|
||||||
return float("inf")
|
|
||||||
|
|
||||||
if isinstance(page_data, dict):
|
if isinstance(page_data, dict):
|
||||||
logger.warning("Page data is dict, migrate koreader data")
|
valid_pages = sorted(int(k) for k in page_data.keys())
|
||||||
return
|
if valid_pages:
|
||||||
|
self.log["page_start"] = min(valid_pages)
|
||||||
|
self.log["page_end"] = max(valid_pages)
|
||||||
|
self.log["pages_read"] = len(set(valid_pages))
|
||||||
|
elif isinstance(page_data, list):
|
||||||
|
valid_pages = []
|
||||||
|
for page in page_data:
|
||||||
|
try:
|
||||||
|
valid_pages.append(int(page["page_number"]))
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
continue
|
||||||
|
|
||||||
page_data.sort(key=safe_page_number)
|
if valid_pages:
|
||||||
|
self.log["page_start"] = min(valid_pages)
|
||||||
valid_pages = []
|
self.log["page_end"] = max(valid_pages)
|
||||||
for page in page_data:
|
self.log["pages_read"] = len(set(valid_pages))
|
||||||
try:
|
|
||||||
valid_pages.append(int(page["page_number"]))
|
|
||||||
except (ValueError, TypeError):
|
|
||||||
continue
|
|
||||||
|
|
||||||
if valid_pages:
|
|
||||||
self.log["page_start"] = min(valid_pages)
|
|
||||||
self.log["page_end"] = max(valid_pages)
|
|
||||||
self.log["pages_read"] = len(set(valid_pages))
|
|
||||||
else:
|
else:
|
||||||
page_start = self.log.get("page_start")
|
page_start = self.log.get("page_start")
|
||||||
page_end = self.log.get("page_end")
|
page_end = self.log.get("page_end")
|
||||||
|
|||||||
Reference in New Issue
Block a user