move recently added to routines

This commit is contained in:
cwilvx
2024-11-17 20:08:04 +03:00
parent 498d0688b0
commit 333fd6603f
14 changed files with 554 additions and 213 deletions
+1 -3
View File
@@ -35,6 +35,4 @@ class HomepageItem(BaseModel):
@api.get("/") @api.get("/")
def homepage_items(query: HomepageItem): def homepage_items(query: HomepageItem):
return { return HomepageStore.get_homepage_items(limit=query.limit)
"artist_mixes": HomepageStore.get_mixes("artist_mixes", limit=query.limit),
}
+14 -1
View File
@@ -2,6 +2,8 @@ import time
import schedule import schedule
from app.crons.mixes import Mixes from app.crons.mixes import Mixes
from app.lib.recipes.recents import RecentlyAdded, RecentlyPlayed
from app.lib.recipes.topstreamed import TopArtists
from app.utils.threading import background from app.utils.threading import background
@@ -10,9 +12,20 @@ def start_cron_jobs():
""" """
This is the function that triggers the cron jobs. This is the function that triggers the cron jobs.
""" """
Mixes() # NOTE: RecentlyPlayed is not a CRON job, it's triggered here to
# populate the values for the very first time.
RecentlyPlayed()
RecentlyAdded()
# Initialized CRON jobs
# Mixes()
TopArtists()
TopArtists(duration="week")
# Trigger all CRON jobs when the app is started.
schedule.run_all() schedule.run_all()
# Run all CRON jobs on a loop.
while True: while True:
schedule.run_pending() schedule.run_pending()
time.sleep(1) time.sleep(1)
+3 -3
View File
@@ -9,10 +9,10 @@ class CronJob(ABC):
A cron job that will be run on a regular interval. A cron job that will be run on a regular interval.
""" """
def __init__(self, name: str, hours: int): name: str
self.name = name hours: int = 1
self.hours = hours
def __init__(self):
schedule.every(self.hours).hours.do(self.run) schedule.every(self.hours).hours.do(self.run)
@abstractmethod @abstractmethod
+6 -15
View File
@@ -1,7 +1,5 @@
from app.crons.cron import CronJob from app.crons.cron import CronJob
from app.lib.recipes import ArtistMixes from app.lib.recipes.artistmixes import ArtistMixes
from app.plugins.mixes import MixesPlugin
from app.store.homepage import HomepageStore
class Mixes(CronJob): class Mixes(CronJob):
@@ -9,22 +7,15 @@ class Mixes(CronJob):
This cron job creates mixes displayed on the homepage. This cron job creates mixes displayed on the homepage.
""" """
name: str = "mixes"
hours: int = 1
def __init__(self): def __init__(self):
super().__init__("mixes", 1) super().__init__()
def run(self): def run(self):
""" """
Creates the artist mixes Creates the artist mixes
""" """
print("⭐⭐⭐⭐ Mixes cron job running") print("⭐⭐⭐⭐ Mixes cron job running")
ArtistMixes().run() ArtistMixes()
# mixes = MixesPlugin()
# if not mixes.enabled:
# return
# artist_mixes = mixes.create_artist_mixes()
# if artist_mixes:
# HomepageStore.set_artist_mixes(artist_mixes)
+2 -2
View File
@@ -293,10 +293,10 @@ class ScrobbleTable(Base):
return cls.insert_one(item) return cls.insert_one(item)
@classmethod @classmethod
def get_all(cls, start: int, limit: int | None = None): def get_all(cls, start: int, limit: int | None = None, userid: int | None = None):
result = cls.execute( result = cls.execute(
select(cls) select(cls)
.where(cls.userid == get_current_userid()) .where(cls.userid == (userid if userid else get_current_userid()))
.order_by(cls.timestamp.desc()) .order_by(cls.timestamp.desc())
.offset(start) .offset(start)
.limit(limit) .limit(limit)
+53 -36
View File
@@ -59,13 +59,19 @@ def create_track(t: Track):
""" """
Creates a recently added track entry. Creates a recently added track entry.
""" """
track = serialize_track(t, to_remove={"created_date"})
track["help_text"] = "NEW TRACK"
return { return {
"type": "track", "type": "track",
"item": track, "hash": t.trackhash,
"timestamp": t.last_mod,
"help_text": "NEW TRACK",
} }
# track = serialize_track(t, to_remove={"created_date"})
# track["help_text"] = "NEW TRACK"
# return {
# "type": "track",
# "item": track,
# }
# INFO: Keys: folder, tracks, time (timestamp) # INFO: Keys: folder, tracks, time (timestamp)
@@ -94,26 +100,25 @@ def check_folder_type(group_: dict):
if entry is None: if entry is None:
return None return None
album = album_serializer( # album = album_serializer(
entry.album, # entry.album,
to_remove={ # to_remove={
"genres", # "genres",
"og_title", # "og_title",
"date", # "date",
"duration", # "duration",
"count", # "count",
"albumartists_hashes", # "albumartists_hashes",
"base_title", # "base_title",
}, # },
) # )
album["help_text"] = (
"NEW ALBUM" if albumhash in existing_album_hashes else "NEW TRACKS"
)
album["time"] = timestamp_to_time_passed(time)
return { return {
"type": "album", "type": "album",
"item": album, "hash": albumhash,
"timestamp": time,
"help_text": (
"NEW ALBUM" if albumhash in existing_album_hashes else "NEW TRACKS"
),
} }
is_artist, artisthash, trackcount = check_is_artist_folder(tracks) is_artist, artisthash, trackcount = check_is_artist_folder(tracks)
@@ -123,18 +128,27 @@ def check_folder_type(group_: dict):
if entry is None: if entry is None:
return None return None
artist = serialize_for_card(entry.artist)
artist["trackcount"] = trackcount
artist["help_text"] = (
"NEW ARTIST" if artisthash not in existing_artist_hashes else "NEW MUSIC"
)
artist["time"] = timestamp_to_time_passed(time)
return { return {
"type": "artist", "type": "artist",
"item": artist, "hash": artisthash,
"timestamp": time,
"help_text": (
"NEW ARTIST" if artisthash not in existing_artist_hashes else "NEW MUSIC"
),
} }
# artist = serialize_for_card(entry.artist)
# artist["trackcount"] = trackcount
# artist["help_text"] = (
# "NEW ARTIST" if artisthash not in existing_artist_hashes else "NEW MUSIC"
# )
# artist["time"] = timestamp_to_time_passed(time)
# return {
# "type": "artist",
# "item": artist,
# }
is_track_folder = check_is_track_folder(tracks) is_track_folder = check_is_track_folder(tracks)
return ( return (
@@ -142,12 +156,15 @@ def check_folder_type(group_: dict):
if is_track_folder if is_track_folder
else { else {
"type": "folder", "type": "folder",
"item": { "hash": key,
"path": key, "timestamp": time,
"count": len(tracks), "help_text": "NEW MUSIC",
"help_text": "NEW MUSIC", # "item": {
"time": timestamp_to_time_passed(time), # "path": key,
}, # "count": len(tracks),
# "help_text": "NEW MUSIC",
# "time": timestamp_to_time_passed(time),
# },
} }
) )
+238 -88
View File
@@ -22,7 +22,7 @@ from app.store.tracks import TrackStore
from app.store.artists import ArtistStore from app.store.artists import ArtistStore
def get_recently_played(limit=7): def get_recently_played(limit=7, userid: int | None = None):
# TODO: Paginate this # TODO: Paginate this
items = [] items = []
added = set() added = set()
@@ -43,47 +43,52 @@ def get_recently_played(limit=7):
added.add(entry.source) added.add(entry.source)
if entry.type == "album": if entry.type == "album":
album = AlbumStore.get_album_by_hash(entry.type_src) album = AlbumStore.albummap.get(entry.type_src)
if album is None: if album is None:
continue continue
album = album_serializer( # album = album_serializer(
album, # album,
to_remove={ # to_remove={
"genres", # "genres",
"date", # "date",
"count", # "count",
"duration", # "duration",
"albumartists_hashes", # "albumartists_hashes",
"og_title", # "og_title",
}, # },
) # )
album["help_text"] = "album" item = {
album["time"] = timestamp_to_time_passed(entry.timestamp) "type": "album",
"hash": entry.type_src,
"timestamp": entry.timestamp,
}
# album["help_text"] = "album"
# album["time"] = timestamp_to_time_passed(entry.timestamp)
items.append( # {
{ # "type": "album",
"type": "album", # "item": album,
"item": album, # }
} items.append(item)
)
continue continue
if entry.type == "artist": if entry.type == "artist":
artist = ArtistStore.get_artist_by_hash(entry.type_src) artist = ArtistStore.artistmap.get(entry.type_src)
if artist is None: if artist is None:
continue continue
artist = serialize_for_card(artist) # artist = serialize_for_card(artist)
artist["help_text"] = "artist" # artist["help_text"] = "artist"
artist["time"] = timestamp_to_time_passed(entry.timestamp) # artist["time"] = timestamp_to_time_passed(entry.timestamp)
items.append( items.append(
{ {
"type": "artist", "type": "artist",
"item": artist, "hash": entry.type_src,
"timestamp": entry.timestamp,
} }
) )
@@ -103,47 +108,56 @@ def get_recently_played(limit=7):
if is_home_dir: if is_home_dir:
folder = os.path.expanduser("~") folder = os.path.expanduser("~")
# print(folder) # count = FolderStore.count_tracks_containing_paths([folder])
# folder = os.path.join("/", folder, "") item = {
# print(folder) "type": "folder",
# count = len([t for t in TrackStore.tracks if t.folder == folder]) "hash": entry.type_src,
count = FolderStore.count_tracks_containing_paths([folder]) "timestamp": entry.timestamp,
items.append( }
{
"type": "folder", items.append(item)
"item": { # {
"path": folder, # "type": "folder",
"count": count[0]["trackcount"], # "item": {
"help_text": "folder", # "path": folder,
"time": timestamp_to_time_passed(entry.timestamp), # "count": count[0]["trackcount"],
}, # "help_text": "folder",
} # "time": timestamp_to_time_passed(entry.timestamp),
) # },
# }
continue continue
if entry.type == "playlist": if entry.type == "playlist":
is_custom = entry.type_src in [i["name"] for i in custom_playlists] is_custom = entry.type_src in [i["name"] for i in custom_playlists]
# is_recently_added = entry.type_src == "recentlyadded"
if is_custom: if is_custom:
playlist, _ = next( # playlist, _ = next(
i["handler"]() # i["handler"]()
for i in custom_playlists # for i in custom_playlists
if i["name"] == entry.type_src # if i["name"] == entry.type_src
) # )
playlist.images = [i["image"] for i in playlist.images] # playlist.images = [i["image"] for i in playlist.images]
playlist = serialize_playlist( # playlist = serialize_playlist(
playlist, to_remove={"settings", "duration"} # playlist, to_remove={"settings", "duration"}
) # )
playlist["help_text"] = "playlist" # playlist["help_text"] = "playlist"
playlist["time"] = timestamp_to_time_passed(entry.timestamp) # playlist["time"] = timestamp_to_time_passed(entry.timestamp)
# items.append(
# {
# "type": "playlist",
# "item": playlist,
# }
# )
items.append( items.append(
{ {
"type": "playlist", "type": "playlist",
"item": playlist, "hash": entry.type_src,
"timestamp": entry.timestamp,
"is_custom": True,
} }
) )
continue continue
@@ -152,34 +166,47 @@ def get_recently_played(limit=7):
if playlist is None: if playlist is None:
continue continue
tracks = TrackStore.get_tracks_by_trackhashes(playlist.trackhashes) item = {
playlist.clear_lists() "type": "playlist",
"hash": entry.type_src,
"timestamp": entry.timestamp,
}
if not playlist.has_image: items.append(item)
images = get_first_4_images(tracks)
images = [i["image"] for i in images]
playlist.images = images
items.append( # tracks = TrackStore.get_tracks_by_trackhashes(playlist.trackhashes)
{ # playlist.clear_lists()
"type": "playlist",
"item": { # if not playlist.has_image:
"help_text": "playlist", # images = get_first_4_images(tracks)
"time": timestamp_to_time_passed(entry.timestamp), # images = [i["image"] for i in images]
**serialize_playlist(playlist), # playlist.images = images
},
} # items.append(
) # {
# "type": "playlist",
# "item": {
# "help_text": "playlist",
# "time": timestamp_to_time_passed(entry.timestamp),
# **serialize_playlist(playlist),
# },
# }
# )
continue
if entry.type == "favorite": if entry.type == "favorite":
items.append( items.append(
# {
# "type": "favorite_tracks",
# "item": {
# "help_text": "playlist",
# "count": FavoritesTable.count(),
# "time": timestamp_to_time_passed(entry.timestamp),
# },
# }
{ {
"type": "favorite_tracks", "type": "favorite",
"item": { "timestamp": entry.timestamp,
"help_text": "playlist",
"count": FavoritesTable.count(),
"time": timestamp_to_time_passed(entry.timestamp),
},
} }
) )
continue continue
@@ -189,22 +216,23 @@ def get_recently_played(limit=7):
if t is None: if t is None:
continue continue
track = serialize_track(t.get_best()) item = {
track["help_text"] = "track" "type": "track",
track["time"] = timestamp_to_time_passed(entry.timestamp) "hash": entry.trackhash,
"timestamp": entry.timestamp,
}
items.append( # track = serialize_track(t.get_best())
{ # track["help_text"] = "track"
"type": "track", # track["time"] = timestamp_to_time_passed(entry.timestamp)
"item": track,
} items.append(item)
)
BATCH_SIZE = 200 BATCH_SIZE = 200
current_index = 0 current_index = 0
entries = ScrobbleTable.get_all(0, BATCH_SIZE) entries = ScrobbleTable.get_all(0, BATCH_SIZE)
max_iterations = 20 # Safeguard against unexpected infinite loops max_iterations = 20 # Safeguard against unexpected infinite loops
iterations = 0 iterations = 0
while len(items) < limit and iterations < max_iterations: while len(items) < limit and iterations < max_iterations:
@@ -212,7 +240,9 @@ def get_recently_played(limit=7):
current_index += BATCH_SIZE current_index += BATCH_SIZE
if len(items) < limit: if len(items) < limit:
entries = ScrobbleTable.get_all(current_index + 1, BATCH_SIZE) entries = ScrobbleTable.get_all(
start=current_index + 1, limit=BATCH_SIZE, userid=userid
)
if not entries: if not entries:
break break
@@ -226,6 +256,126 @@ def get_recently_played(limit=7):
return items return items
def recover_recently_played_items(items: list[dict]):
custom_playlists = [
{"name": "recentlyadded", "handler": get_recently_added_playlist},
{"name": "recentlyplayed", "handler": get_recently_played_playlist},
]
recovered = []
for item in items:
recovered_item = None
if item["type"] == "album":
album = AlbumStore.get_album_by_hash(item["hash"])
if album is None:
continue
album = album_serializer(
album,
to_remove={
"genres",
"date",
"count",
"duration",
"albumartists_hashes",
"og_title",
},
)
recovered_item = {
"type": "album",
"item": album,
}
elif item["type"] == "artist":
artist = ArtistStore.get_artist_by_hash(item["hash"])
if artist is None:
continue
recovered_item = {
"type": "artist",
"item": serialize_for_card(artist),
}
elif item["type"] == "folder":
count = FolderStore.count_tracks_containing_paths([item["hash"]])
recovered_item = {
"type": "folder",
"item": {
"path": item["hash"],
"count": count[0]["trackcount"],
},
}
elif item["type"] == "playlist":
if item["is_custom"]:
playlist, _ = next(
i["handler"]()
for i in custom_playlists
if i["name"] == item["hash"]
)
playlist.images = [i["image"] for i in playlist.images]
playlist = serialize_playlist(
playlist, to_remove={"settings", "duration"}
)
recovered_item = {
"type": "playlist",
"item": playlist,
}
else:
playlist = PlaylistTable.get_by_id(item["hash"])
if playlist is None:
continue
tracks = TrackStore.get_tracks_by_trackhashes(playlist.trackhashes)
playlist.clear_lists()
if not playlist.has_image:
images = get_first_4_images(tracks)
images = [i["image"] for i in images]
playlist.images = images
recovered_item = {
"type": "playlist",
"item": serialize_playlist(playlist),
}
elif item["type"] == "favorite":
recovered_item = {
"type": "favorite",
"item": {
"count": FavoritesTable.count(),
},
}
elif item["type"] == "track":
track = TrackStore.trackhashmap.get(item["hash"])
if track is None:
continue
recovered_item = {
"type": "track",
"item": serialize_track(track.get_best()),
}
if recovered_item is not None:
helptext = item.get("help_text") or item.get("type")
secondary_text = item.get("secondary_text")
if "secondary_text" in item:
secondary_text = item["secondary_text"]
elif "timestamp" in item:
secondary_text = timestamp_to_time_passed(item["timestamp"])
if helptext:
recovered_item["item"]["help_text"] = helptext
if secondary_text:
recovered_item["item"]["time"] = secondary_text
recovered.append(recovered_item)
return recovered
def get_recently_played_playlist(limit: int = 100): def get_recently_played_playlist(limit: int = 100):
playlist = Playlist( playlist = Playlist(
id="recentlyplayed", id="recentlyplayed",
+3 -40
View File
@@ -3,25 +3,13 @@ Recipes are a way to create mixes.
""" """
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from typing import Any, Dict, List from typing import Any, List
from app.db.userdata import UserTable
from app.models.mix import Mix
from app.plugins.mixes import MixesPlugin
from app.store.homepage import HomepageStore
class HomepageRoutine(ABC): class HomepageRoutine(ABC):
""" """
A routine creates a row of homepage items. A routine creates a row of homepage items.
""" """
title: str
description: str
items: List[Mix]
extra: Dict[str, Any]
@property @property
@abstractmethod @abstractmethod
def is_valid(self) -> bool: ... def is_valid(self) -> bool: ...
@@ -30,37 +18,12 @@ class HomepageRoutine(ABC):
if not self.is_valid: if not self.is_valid:
return return
self.items = self.run() self.run()
@abstractmethod @abstractmethod
def run(self) -> List[Mix]: def run(self) -> List[Any]:
""" """
Creates the homepage items and saves them to the Creates the homepage items and saves them to the
homepage store if self.is_valid is true. homepage store if self.is_valid is true.
""" """
... ...
class ArtistMixes(HomepageRoutine):
items: List[Mix] = []
extra: Dict[str, Any] = {}
store_key = "artist_mixes"
@property
def is_valid(self):
return MixesPlugin().enabled
def run(self):
users = UserTable.get_all()
for user in users:
mix = MixesPlugin()
mixes = mix.create_artist_mixes(user.id)
if not mixes:
continue
HomepageStore.set_mixes(mixes, mixkey=self.store_key, userid=user.id)
def __init__(self) -> None:
super().__init__()
+27
View File
@@ -0,0 +1,27 @@
from app.db.userdata import UserTable
from app.lib.recipes import HomepageRoutine
from app.plugins.mixes import MixesPlugin
from app.store.homepage import HomepageStore
class ArtistMixes(HomepageRoutine):
store_key = "artist_mixes"
@property
def is_valid(self):
return MixesPlugin().enabled
def run(self):
users = UserTable.get_all()
for user in users:
mix = MixesPlugin()
mixes = mix.create_artist_mixes(user.id)
if not mixes:
continue
HomepageStore.set_mixes(mixes, entrykey=self.store_key, userid=user.id)
def __init__(self) -> None:
super().__init__()
+46
View File
@@ -0,0 +1,46 @@
import pprint
from app.db.userdata import UserTable
from app.lib.home.recentlyadded import get_recently_added_items
from app.lib.home.recentlyplayed import get_recently_played
from app.lib.recipes import HomepageRoutine
from app.store.homepage import HomepageStore
class RecentlyPlayed(HomepageRoutine):
store_key = "recently_played"
def __init__(self, userid: int | None = None) -> None:
"""
The userid is provided when we are running this routine
outside a cron job. ie. when a user records a new scrobble.
"""
self.userids = [userid] if userid else [user.id for user in UserTable.get_all()]
super().__init__()
@property
def is_valid(self):
return True
def run(self):
for userid in self.userids:
items = get_recently_played(limit=15, userid=userid)
HomepageStore.entries[self.store_key].items[userid] = items
class RecentlyAdded(HomepageRoutine):
ITEM_LIMIT = 15
store_key = "recently_added"
@property
def is_valid(self):
return True
def __init__(self):
super().__init__()
def run(self):
items = get_recently_added_items(limit=self.ITEM_LIMIT)
# NOTE: Recently added is a global entry
# So we don't need a userid
HomepageStore.entries[self.store_key].items[0] = items
+84
View File
@@ -0,0 +1,84 @@
from gettext import ngettext
from os import name
import pendulum
from app.crons.cron import CronJob
from app.db.userdata import UserTable
from app.lib.recipes import HomepageRoutine
from app.store.homepage import HomepageStore
from app.utils.dates import get_date_range, seconds_to_time_string
from app.utils.stats import get_artists_in_period
class TopArtists(CronJob, HomepageRoutine):
"""
A routine to populate the top streamed artists/albums in the last week or month
"""
hours = 1
ITEM_LIMIT = 15
@property
def is_valid(self):
"""
Only valid if it's the middle or last 2 days of this month.
When the duration is "week", it's valid on Saturday and Sunday.
"""
if self.duration == "month":
now = pendulum.now()
middle_day = now.days_in_month // 2
return (
now.day in range(middle_day, middle_day + 2)
or now.day > now.days_in_month - 2
)
if self.duration == "week":
return pendulum.now().isoweekday() in (6, 7)
return False
def __init__(self, duration: str = "month") -> None:
super().__init__()
self.duration = duration
if not self.is_valid:
return
def run(self):
if not self.is_valid:
self.destroy()
return
self.userids = [user.id for user in UserTable.get_all()]
for userid in self.userids:
date_range = get_date_range(self.duration)
artists = get_artists_in_period(date_range[0], date_range[1], userid)[
: self.ITEM_LIMIT
]
artists = [
{
"type": "artist",
"hash": artist["artisthash"],
"help_text": seconds_to_time_string(artist["playduration"]),
"secondary_text": str(artist["playcount"])
+ " "
+ ngettext("play", "plays", artist["playcount"]),
}
for artist in artists
]
HomepageStore.entries[f"top_streamed_{self.duration}ly_artists"].items[
userid
] = artists
def destroy(self):
"""
Clear the top streamed entry from the homepage store.
"""
keys = [f"top_streamed_{self.duration}ly_artists"]
for key in keys:
HomepageStore.entries[key].items = {}
+6
View File
@@ -13,11 +13,17 @@ class TrackLog:
duration: int duration: int
timestamp: int timestamp: int
source: str source: str
"""
The full source string, eg. "al:123456"
"""
userid: int userid: int
extra: dict[str, Any] extra: dict[str, Any]
type = "track" type = "track"
type_src = None type_src = None
"""
The source identifier string, eg. albumhash, artisthash, etc.
"""
def __post_init__(self): def __post_init__(self):
prefix_map = { prefix_map = {
+69 -23
View File
@@ -1,11 +1,11 @@
from abc import ABC from abc import ABC
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any from typing import Any
from app.lib.home.recentlyplayed import recover_recently_played_items
from app.models.mix import Mix from app.models.mix import Mix
from app.utils.auth import get_current_userid from app.utils.auth import get_current_userid
@dataclass
class HomepageEntry(ABC): class HomepageEntry(ABC):
""" """
Base class for all homepage entries. Base class for all homepage entries.
@@ -15,20 +15,19 @@ class HomepageEntry(ABC):
title: str title: str
description: str description: str
items: dict[int, dict[str, Any]] items: dict[int, Any]
def __init__(self, title: str, description: str): def __init__(self, title: str, description: str):
self.title = title self.title = title
self.description = description self.description = description
def get_items(self, userid: int): def get_items(self, userid: int, limit: int | None = None):
""" """
Return usable items for the homepage. Return usable items for the homepage.
""" """
... ...
@dataclass
class MixHomepageEntry(HomepageEntry): class MixHomepageEntry(HomepageEntry):
""" """
A homepage entry for mixes. A homepage entry for mixes.
@@ -62,26 +61,77 @@ class MixHomepageEntry(HomepageEntry):
} }
class RecentlyPlayedHomepageEntry(HomepageEntry):
"""
A homepage entry for recently played.
"""
items: dict[int, list[dict[str, Any]]]
def __init__(self, title: str, description: str = ""):
super().__init__(title, description)
self.items = {}
def get_items(self, userid: int, limit: int | None = None):
items = self.items.get(userid, [])[:limit]
return {
"title": self.title,
"description": self.description,
"items": recover_recently_played_items(items),
}
class RecentlyAddedHomepageEntry(RecentlyPlayedHomepageEntry):
"""
A homepage entry for recently added.
"""
def get_items(self, userid: int, limit: int | None = None):
return super().get_items(0, limit)
class TopStreamedHomepageEntry(RecentlyPlayedHomepageEntry):
"""
A homepage entry for top streamed.
"""
# NOTE: This extends RecentlyPlayedHomepageEntry because
# the shape of the data is the same.
pass
class HomepageStore: class HomepageStore:
""" """
Stores the homepage items. Stores the homepage items.
""" """
entries = { entries: dict[str, HomepageEntry] = {
"recently_played": RecentlyPlayedHomepageEntry(
title="Recently played",
),
"artist_mixes": MixHomepageEntry( "artist_mixes": MixHomepageEntry(
title="Artist mixes for you", title="Artist mixes for you",
description="Based on artists you have been listening to", description="Based on artists you have been listening to",
), ),
"top_streamed_weekly_artists": TopStreamedHomepageEntry(
title="Top artists this week",
description="Your most played artists since Monday",
),
"top_streamed_monthly_artists": TopStreamedHomepageEntry(
title="Top artists this month",
description="Your most played artists since the start of the month",
),
"recently_added": RecentlyAddedHomepageEntry(
title="Recently added",
description="New music added to your library",
),
} }
@classmethod @classmethod
def set_mixes(cls, mixes: list[Mix], mixkey: str, userid: int | None = None): def set_mixes(cls, items: list[Any], entrykey: str, userid: int | None = None):
idmap = {mix.id[1:]: mix for mix in mixes} idmap = {item.id[1:]: item for item in items}
cls.entries[mixkey].items[userid or get_current_userid()] = idmap cls.entries[entrykey].items[userid or get_current_userid()] = idmap
@classmethod
def get_mixes(cls, mixkey: str, limit: int | None = 9):
return cls.entries[mixkey].get_items(get_current_userid(), limit)
@classmethod @classmethod
def get_mix(cls, mixkey: str, mixid: str): def get_mix(cls, mixkey: str, mixid: str):
@@ -89,14 +139,10 @@ class HomepageStore:
return mix.to_full_dict() if mix else None return mix.to_full_dict() if mix else None
@classmethod @classmethod
def get_mix_by_sourcehash(cls, sourcehash: str): def get_homepage_items(cls, limit: int):
return next( # return a dict of entry name to entry items
( return [
mix {entry: cls.entries[entry].get_items(get_current_userid(), limit)}
for mix in cls.entries["artist_mixes"] for entry in cls.entries.keys()
.items.get(get_current_userid(), {}) if len(cls.entries[entry].items)
.values() ]
if mix.sourcehash == sourcehash
),
None,
)
+1 -1
View File
@@ -46,7 +46,7 @@ def date_string_to_time_passed(prev_date: str) -> str:
return timestamp_to_time_passed(then) return timestamp_to_time_passed(then)
def seconds_to_time_string(seconds): def seconds_to_time_string(seconds: int):
""" """
Converts seconds to a time string. e.g. 1 hour 2 minutes, 1 hour 2 seconds, 1 hour, 1 minute 2 seconds, etc. Converts seconds to a time string. e.g. 1 hour 2 minutes, 1 hour 2 seconds, 1 hour, 1 minute 2 seconds, etc.
""" """