Getting the playlist processing logic organized and implemented.

Signed-off-by: Cliff Hill <xlorep@darkhelm.org>
This commit is contained in:
2024-04-24 08:04:31 -04:00
parent d54a43ba5b
commit c91e7339fa
4 changed files with 394 additions and 36 deletions

View File

@@ -1,11 +1,15 @@
"""Contains all SQL code here."""
from collections import Counter
from datetime import datetime
from typing import Any
from typing import AsyncGenerator
import env
import models
from aiologger import Logger
from playlist import enums
from playlist import env
from playlist import models
from sqlalchemy import func
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.ext.asyncio import create_async_engine
@@ -110,9 +114,7 @@ async def update_statistics_timestamps(operation: str) -> None:
async with async_session_maker() as session:
try:
async with session.begin():
stats = await session.execute(
select(models.Statistics).order_by(models.Statistics.id)
)
stats = await session.get(models.Statistics, 1)
stats_obj = stats.scalars().first()
if operation == "start":
@@ -214,32 +216,6 @@ async def remove_holiday_by_id(session: AsyncSession, holiday_id: int) -> None:
await session.rollback()
async def update_average_track_length(new_average: float) -> None:
"""Update the average track length in the statistics model with the geometric mean.
Args:
new_average (float): The new average track length to be set.
"""
async with async_session_maker() as session:
try:
async with session.begin():
# Assuming there's only one statistics record, or you might need to handle this differently
stats = await session.get(
models.Statistics, 1
) # Adjust based on how you access the statistics record
if stats:
stats.average_track_length = new_average
await session.commit()
else:
# If no statistics entry exists, create one
new_stats = models.Statistics(average_track_length=new_average)
session.add(new_stats)
await session.commit()
except Exception as e:
print(f"Failed to update the average track length: {e}")
await session.rollback()
async def update_statistics(
new_average_duration: float,
new_podcast_length: float,
@@ -283,9 +259,7 @@ async def update_statistics(
try:
async with session.begin():
# Assuming there's only one statistics record, or you might need to handle this differently
stats = await session.get(
models.Statistics, 1
) # Adjust based on how you access the statistics record
stats = await session.get(models.Statistics, 1)
if stats:
stats.average_track_length = new_average_duration
stats.total_podcast_length = new_podcast_length
@@ -328,3 +302,173 @@ async def update_statistics(
except Exception as e:
print(f"Failed to update the statistics model: {e}")
await session.rollback()
async def fetch_tracks_for_sublist(
session: AsyncSession,
is_category: bool,
type_id: int,
is_favorite: bool,
sublist: enums.Sublist,
limit: int,
playlist_entries: set[int],
) -> AsyncGenerator[models.Track, None]:
"""Fetch tracks for a given sublist type within a category.
Args:
session (AsyncSession): The database session.
is_category (bool): True if a category, false if a holiday.
type_id (int): Category or holiday ID for track filtering.
is_favorite (bool): Flag indicating if only favorite tracks should be fetched.
sublist (enums.Sublist): Type of sublist to fetch.
limit (int): Number of tracks to fetch.
playlist_entries (set[int]): The set of track IDs already in the playlist.
Returns:
AsyncGenerator[models.Track, None]: Generates the tracks fitting the criteria.
"""
try:
query = select(models.Track)
if is_category:
query = query.where(models.Track.category_id == type_id)
else:
query = query.join(models.Track.holidays).where(
models.Holiday.id == type_id
)
query = query.filter(models.Track.id.not_in(playlist_entries))
if is_favorite:
query = query.filter(models.Track.rating == 5)
match sublist:
case enums.Sublist.LEAST_RECENTLY_PLAYED:
query = query.order_by(models.Track.last_played.asc())
case enums.Sublist.LEAST_OFTEN_PLAYED:
query = query.order_by(models.Track.play_count.asc())
case enums.Sublist.LEAST_RECENTLY_ADDED:
query = query.order_by(models.Track.date_added.asc())
case enums.Sublist.RANDOM:
query = query.order_by(func.random())
case _:
raise ValueError(f"Unknown sublist type: {sublist}")
query = query.limit(limit)
result = await session.execute(query)
for track in result.scalars():
yield track
except SQLAlchemyError as e:
await logger.error(f"Database error occurred while fetching tracks: {e}")
raise
except Exception as e:
await logger.error(f"Unexpected error occurred while fetching tracks: {e}")
raise
async def insert_into_playlist( # noqa: C901
session: AsyncSession,
is_category: bool,
type_id: int,
track_id: int,
sublist: enums.Sublist,
is_favorite: bool,
) -> None:
"""Insert a track into the playlist.
Args:
session (AsyncSession): The database session.
is_category (bool): True is a category, false is a holiday.
type_id (int): Category or holiday ID associated with the playlist.
track_id (int): Track ID to be added to the playlist.
sublist (enums.Sublist): Sublist type under which the track is added.
is_favorite (bool): Indicates if the track is added as a favorite.
"""
try:
if is_category:
playlist_track = models.PlaylistTrack(
category_id=type_id,
track_id=track_id,
sublist_type=sublist,
is_favorite=is_favorite,
)
else:
playlist_track = models.PlaylistTrack(
holiday_id=type_id,
track_id=track_id,
sublist_type=sublist,
is_favorite=is_favorite,
)
session.add(playlist_track)
await session.commit()
except SQLAlchemyError as e:
await logger.error(
f"Database error occurred while inserting into playlist: {e}"
)
await session.rollback()
raise
except Exception as e:
await logger.error(
f"Unexpected error occurred while inserting into playlist: {e}"
)
await session.rollback()
raise
async def gen_all_category_ids() -> AsyncGenerator[int, None, None]:
"""Generate the current category ids from the database.
Yields:
int: The category id.
"""
async with async_session_maker() as session:
query = select(models.Category)
result = session.execute(query)
for row in result.scalars():
yield row.id
async def gen_active_holiday_ids() -> AsyncGenerator[int, None, None]:
"""Generate the current active holiday ids from the database.
Yields:
int: The holiday id.
"""
async with async_session_maker() as session:
query = select(models.Holiday).where(models.Holiday.is_active is True)
result = session.execute(query)
for row in result.scalars():
yield row.id
async def get_existing_playlist_track_info() -> (
tuple[set[int], dict[tuple[bool, int, bool, enums.Sublist], int]]
):
"""Get the existing playlist track information.
Returns:
tuple[set[int], dict[tuple[bool, int, bool, enums.Sublist], int]]: Two pieces:
First is a set of the ids of all of the tracks in the playlist. Second is a
dict with the key being the combination of is_category, type_id, is_favorite,
and sublist; the value is the count of that unique key combination.
"""
async with async_session_maker() as session:
# TODO: Set up user-specific query here.
query = select(models.PlaylistTrack).where(
models.PlaylistTrack.episode_id is None
)
result = session.execute(query)
ids = set()
data = []
for row in result.scalars():
if row.episode_id is not None:
continue
ids.append(row.track_id)
is_category = row.category_id is not None
type_id = row.category_id if is_category else row.holiday_id
is_favorite = row.is_favorite
sublist = row.sublist
data.append((is_category, type_id, is_favorite, sublist))
data_counts = Counter(data)
return ids, data_counts
async def get_statistics() -> models.Statistics:
stats = await session.get(models.Statistics, 1)
stats_obj = stats.scalars().first()
return stats_obj