Cache songs on get playlist details
This commit is contained in:
@@ -39,6 +39,8 @@ class FilesystemAdapter(CachingAdapter):
|
||||
database_filename = data_directory.joinpath('cache.db')
|
||||
models.database.init(database_filename)
|
||||
models.database.connect()
|
||||
|
||||
with models.database.atomic():
|
||||
models.database.create_tables(models.ALL_TABLES)
|
||||
|
||||
def shutdown(self):
|
||||
@@ -86,6 +88,7 @@ class FilesystemAdapter(CachingAdapter):
|
||||
|
||||
# Data Ingestion Methods
|
||||
# =========================================================================
|
||||
@models.database.atomic()
|
||||
def ingest_new_data(
|
||||
self,
|
||||
function: 'CachingAdapter.FunctionNames',
|
||||
@@ -105,7 +108,22 @@ class FilesystemAdapter(CachingAdapter):
|
||||
asdict, data)).on_conflict_replace().execute()
|
||||
elif function == CachingAdapter.FunctionNames.GET_PLAYLIST_DETAILS:
|
||||
playlist_data = asdict(data)
|
||||
# TODO deal with the songs
|
||||
del playlist_data['songs']
|
||||
models.Playlist.insert(
|
||||
playlist_data).on_conflict_replace().execute()
|
||||
playlist, created = models.Playlist.get_or_create(
|
||||
id=playlist_data['id'],
|
||||
defaults=playlist_data,
|
||||
)
|
||||
|
||||
# Handle the songs.
|
||||
f = ('id', 'title', 'duration')
|
||||
playlist.songs = [
|
||||
models.Song.create(
|
||||
**dict(filter(lambda kv: kv[0] in f, s.items())))
|
||||
for s in playlist_data['songs']
|
||||
]
|
||||
|
||||
# Update the values if the playlist already existed.
|
||||
if not created:
|
||||
for k, v in playlist_data.items():
|
||||
setattr(playlist, k, v)
|
||||
|
||||
playlist.save()
|
||||
|
@@ -24,10 +24,10 @@ database = SqliteDatabase(None)
|
||||
# =============================================================================
|
||||
class DurationField(IntegerField):
|
||||
def db_value(self, value: timedelta) -> Optional[int]:
|
||||
return value.microseconds if value else None
|
||||
return value.total_seconds() if value else None
|
||||
|
||||
def python_value(self, value: Optional[int]) -> Optional[timedelta]:
|
||||
return timedelta(microseconds=value) if value else None
|
||||
return timedelta(seconds=value) if value else None
|
||||
|
||||
|
||||
class CacheConstantsField(TextField):
|
||||
@@ -53,6 +53,37 @@ class CoverArt(BaseModel):
|
||||
|
||||
class Song(BaseModel):
|
||||
id = TextField(unique=True, primary_key=True)
|
||||
title = TextField()
|
||||
duration = DurationField()
|
||||
|
||||
# parent: Optional[str] = None
|
||||
# album: Optional[str] = None
|
||||
# artist: Optional[str] = None
|
||||
# track: Optional[int] = None
|
||||
# year: Optional[int] = None
|
||||
# genre: Optional[str] = None
|
||||
# cover_art: Optional[str] = None
|
||||
# size: Optional[int] = None
|
||||
# content_type: Optional[str] = None
|
||||
# suffix: Optional[str] = None
|
||||
# transcoded_content_type: Optional[str] = None
|
||||
# transcoded_suffix: Optional[str] = None
|
||||
# duration: Optional[int] = None
|
||||
# bit_rate: Optional[int] = None
|
||||
# path: Optional[str] = None
|
||||
# is_video: Optional[bool] = None
|
||||
# user_rating: Optional[int] = None
|
||||
# average_rating: Optional[float] = None
|
||||
# play_count: Optional[int] = None
|
||||
# disc_number: Optional[int] = None
|
||||
# created: Optional[datetime] = None
|
||||
# starred: Optional[datetime] = None
|
||||
# album_id: Optional[str] = None
|
||||
# artist_id: Optional[str] = None
|
||||
# type: Optional[SublimeAPI.MediaType] = None
|
||||
# bookmark_position: Optional[int] = None
|
||||
# original_width: Optional[int] = None
|
||||
# original_height: Optional[int] = None
|
||||
|
||||
|
||||
class CacheInfo(BaseModel):
|
||||
|
@@ -187,5 +187,6 @@ class SubsonicAdapter(Adapter):
|
||||
self._make_url('getPlaylist'),
|
||||
id=playlist_id,
|
||||
).playlist
|
||||
# TODO better error
|
||||
assert result, f'Error getting playlist {playlist_id}'
|
||||
return result
|
||||
|
@@ -5,6 +5,8 @@ These are the API objects that are returned by Subsonic.
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime, timedelta
|
||||
from typing import List, Optional
|
||||
import operator
|
||||
from functools import reduce
|
||||
|
||||
import dataclasses_json
|
||||
from dataclasses_json import (
|
||||
@@ -34,7 +36,6 @@ for type_, translation_function in extra_translation_map.items():
|
||||
class Child(SublimeAPI.Song):
|
||||
id: str
|
||||
title: str
|
||||
value: Optional[str] = None
|
||||
parent: Optional[str] = None
|
||||
album: Optional[str] = None
|
||||
artist: Optional[str] = None
|
||||
@@ -47,7 +48,7 @@ class Child(SublimeAPI.Song):
|
||||
suffix: Optional[str] = None
|
||||
transcoded_content_type: Optional[str] = None
|
||||
transcoded_suffix: Optional[str] = None
|
||||
duration: Optional[int] = None
|
||||
duration: Optional[timedelta] = None
|
||||
bit_rate: Optional[int] = None
|
||||
path: Optional[str] = None
|
||||
is_video: Optional[bool] = None
|
||||
@@ -99,7 +100,7 @@ class PlaylistWithSongs(SublimeAPI.PlaylistDetails):
|
||||
def __post_init__(self):
|
||||
self.song_count = self.song_count or len(self.songs)
|
||||
self.duration = self.duration or timedelta(
|
||||
seconds=sum(s.duration for s in self.songs))
|
||||
seconds=sum(s.duration.total_seconds() if s.duration else 0 for s in self.songs))
|
||||
|
||||
|
||||
@dataclass
|
||||
|
@@ -98,12 +98,27 @@ def test_caching_get_playlist_details(
|
||||
|
||||
# Ingest an empty list (for example, no playlists added yet to server).
|
||||
cache_adapter.ingest_new_data(
|
||||
FilesystemAdapter.FunctionNames.GET_PLAYLIST_DETAILS, ('1', ),
|
||||
SubsonicAPI.PlaylistWithSongs('1', 'test1', songs=[]))
|
||||
FilesystemAdapter.FunctionNames.GET_PLAYLIST_DETAILS,
|
||||
('1', ),
|
||||
SubsonicAPI.PlaylistWithSongs(
|
||||
'1',
|
||||
'test1',
|
||||
songs=[
|
||||
SubsonicAPI.Child(
|
||||
'1', 'Song 1', duration=timedelta(seconds=10.2)),
|
||||
SubsonicAPI.Child(
|
||||
'2', 'Song 2', duration=timedelta(seconds=20.8)),
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
playlist = cache_adapter.get_playlist_details('1')
|
||||
assert playlist.id == '1'
|
||||
assert playlist.name == 'test1'
|
||||
assert playlist.song_count == 2
|
||||
assert playlist.duration == timedelta(seconds=31)
|
||||
assert (playlist.songs[0].id, playlist.songs[0].title) == ('1', 'Song 1')
|
||||
assert (playlist.songs[1].id, playlist.songs[1].title) == ('2', 'Song 2')
|
||||
|
||||
with pytest.raises(CacheMissError):
|
||||
cache_adapter.get_playlist_details('2')
|
||||
|
@@ -159,7 +159,7 @@ def test_get_playlist_details(adapter: SubsonicAdapter):
|
||||
suffix="m4a",
|
||||
transcoded_content_type="audio/mpeg",
|
||||
transcoded_suffix="mp3",
|
||||
duration=238,
|
||||
duration=timedelta(seconds=238),
|
||||
bit_rate=256,
|
||||
path='/'.join(
|
||||
(
|
||||
|
Reference in New Issue
Block a user