Cache songs on get playlist details

This commit is contained in:
Sumner Evans
2020-04-22 01:51:29 -06:00
parent c90b52f493
commit 934eff06c5
6 changed files with 79 additions and 13 deletions

View File

@@ -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()

View File

@@ -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):

View File

@@ -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

View File

@@ -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

View File

@@ -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')

View File

@@ -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(
(