Skip to content

Commit

Permalink
Release 0.5.4 (#62)
Browse files Browse the repository at this point in the history
- Subsonic caching implemented
  • Loading branch information
GioF71 authored Dec 28, 2024
1 parent dd85341 commit dbc3f3c
Show file tree
Hide file tree
Showing 10 changed files with 781 additions and 16 deletions.
7 changes: 5 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
{
"flake8.args": [
"--max-line-length=140",
"--ignore=E128,E701,E704,W504"
]
"--ignore=E128,E701,E704,W504,W718,C0116,C0321"
],
"pylint.args": [
"--max-line-length=140",
"--disable=C0111,W0718,C0321"]
}
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,10 @@ In order to avoid issues with password, which might contain special characters,

## Releases

### Release 0.5.4

- Introduced caching against subsonic servers

### Release 0.5.3

- Build using setuptools
Expand Down
15 changes: 8 additions & 7 deletions mpd_subsonic_scrobbler/mpd_util.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from enum import Enum

from context import Context
from context_key import ContextKey
from enum import Enum
from mpd_status_key import MPDStatusKey
from mpd_instance_config import MpdInstanceConfig
from scrobbler_config import ScrobblerConfig
Expand Down Expand Up @@ -76,21 +77,21 @@ def get_mpd_status(context: Context, mpd_index: int = 0) -> dict[str, str]:


def get_mpd_current_song_artist(context: Context, index: int) -> str:
return __get_mpd_current_song_property(context=context, index=index, property=MPDStatusKey.ARTIST.get_key())
return __get_mpd_current_song_property(context=context, index=index, property_key=MPDStatusKey.ARTIST.get_key())


def get_mpd_current_song_title(context: Context, index: int) -> str:
return __get_mpd_current_song_property(context=context, index=index, property=MPDStatusKey.TITLE.get_key())
return __get_mpd_current_song_property(context=context, index=index, property_key=MPDStatusKey.TITLE.get_key())


def get_mpd_current_song_file(context: Context, index: int) -> str:
return __get_mpd_current_song_property(context=context, index=index, property=MPDStatusKey.FILE.get_key())
return __get_mpd_current_song_property(context=context, index=index, property_key=MPDStatusKey.FILE.get_key())


def get_mpd_current_song_time(context: Context, index: int) -> str:
return __get_mpd_current_song_property(context=context, index=index, property=MPDStatusKey.TIME.get_key())
return __get_mpd_current_song_property(context=context, index=index, property_key=MPDStatusKey.TIME.get_key())


def __get_mpd_current_song_property(context: Context, index: int, property: str) -> str:
def __get_mpd_current_song_property(context: Context, index: int, property_key: str) -> str:
current_song: dict[str, str] = context.get(context_key=ContextKey.CURRENT_MPD_SONG, index=index)
return current_song[property] if current_song and property in current_song else None
return current_song[property_key] if current_song and property_key in current_song else None
7 changes: 5 additions & 2 deletions mpd_subsonic_scrobbler/scrobbler.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import mpd_util
import subsonic_util
import scrobbler_util
import song_cache

from mpd_status_key import MPDStatusKey
from scrobbler_config import ScrobblerConfig
Expand Down Expand Up @@ -100,7 +101,7 @@ def handle_playback(context: Context, index: int):
print(f"Song [{changed}], loading TrackId:[{subsonic_track_id}] "
f"from subsonic server [{current_config.get_friendly_name()}] ...")
get_ss_start: float = time.time()
song = subsonic_util.get_song(current_config, subsonic_track_id)
song = song_cache.get_song(context, current_config, subsonic_track_id)
get_ss_elapsed: float = time.time() - get_ss_start
context.set(context_key=ContextKey.ELAPSED_SS_GET_SONG_INFO, index=index, context_value=get_ss_elapsed)
context.set(context_key=ContextKey.CURRENT_SUBSONIC_SONG_OBJECT, index=index, context_value=song)
Expand Down Expand Up @@ -260,7 +261,9 @@ def main():
except Exception as e:
mpd_get_status_elapsed: float = time.time() - mpd_get_status_start
context.set(context_key=ContextKey.ELAPSED_MPD_STATE, index=mpd_index, context_value=mpd_get_status_elapsed)
if ((isinstance(e, OSError) and (e.errno == 113 or e.errno == 111)) or
is_oserror: bool = isinstance(e, OSError)
maybe_os_error: OSError = e if is_oserror else None
if ((is_oserror and (maybe_os_error.errno in [113, 111])) or
isinstance(e, socket.timeout)):
# no route to host, impose sleep on player
sleep_iteration_count: int = context.get_config().get_mpd_imposed_sleep_iteration_count()
Expand Down
11 changes: 10 additions & 1 deletion mpd_subsonic_scrobbler/scrobbler_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,17 @@ def __init__(self):
self.__redact_credentials: bool = True if int(self.__read_env(ConfigKey.REDACT_CREDENTIALS)) == 1 else False
self.__max_subsonic_servers: int = int(self.__read_env(ConfigKey.MAX_SUBSONIC_SERVERS))
self.__max_mpd_instances: int = int(self.__read_env(ConfigKey.MAX_MPD_INSTANCES))
self.__server_list: list[SubsonicServerConfig] = scrobbler_util.get_subsonic_server_config_list(
tmp_server_list: list[SubsonicServerConfig] = scrobbler_util.get_subsonic_server_config_list(
max_servers=self.__max_subsonic_servers)
# cannot allow duplicate friendly names.
current_server_config: SubsonicServerConfig
fn_set: set[str] = set()
for current_server_config in tmp_server_list:
if current_server_config.get_friendly_name() in fn_set:
raise Exception(f"Duplicate friendly name [{current_server_config.get_friendly_name()}]")
else:
fn_set.add(current_server_config.get_friendly_name())
self.__server_list: list[SubsonicServerConfig] = tmp_server_list
self.__mpd_list: list[MpdInstanceConfig] = scrobbler_util.get_mpd_instances_list(self.__max_mpd_instances)
mpd_client_timeout_sec_str = self.__read_env(ConfigKey.MPD_CLIENT_TIMEOUT_SEC)
self.__mpd_client_timout_sec: float = (float(mpd_client_timeout_sec_str)
Expand Down
90 changes: 90 additions & 0 deletions mpd_subsonic_scrobbler/song_cache.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
"""Module providing a way to avoid to query the subsonic servers too many times."""

import time
from enum import Enum

from subsonic_connector.song import Song
from subsonic_server_config import SubsonicServerConfig
import subsonic_util
from context import Context


class SongCacheDefaults(Enum):

MAX_AGE_SEC = float(600)


class SongCacheEntry:

def __init__(self, song: Song):
self.__missing: bool = song is None
self.__song: Song = song
self.__creation_time: float = time.time()

@property
def missing(self) -> bool:
return self.__missing

@property
def song(self) -> Song:
return self.__song

@property
def creation_time(self) -> time:
return self.__creation_time


__song_cache: dict[str, SongCacheEntry] = {}


def __too_old(entry: SongCacheEntry) -> bool:
now: float = time.time()
diff: float = now - entry.creation_time
if diff > SongCacheDefaults.MAX_AGE_SEC.value:
print(f"Entry is too old, diff is [{diff}], purging")
return True
return False


def __purge_old():
to_purge_list: list[str] = list()
k: str
v: Song
for k, v in __song_cache.items():
# too old? add to purge list
if __too_old(v):
to_purge_list.append(k)
to_purge: str
for to_purge in to_purge_list:
del __song_cache[to_purge]


def __try_get_song(context: Context, current_config: SubsonicServerConfig, song_id: str) -> Song:
try:
song: Song = subsonic_util.get_song(current_config, song_id)
return song
except Exception as e:
if context.get_config().get_verbose():
print(f"Cannot load song [{song_id}] from [{current_config.get_friendly_name()}] "
f"due to [{type(e)}] [{e}]")
return None


def get_song(context: Context, current_config: SubsonicServerConfig, song_id: str) -> Song:
"""Get song from configured subsonic servers, maybe caching it for a while."""
song_key: str = f"{current_config.get_friendly_name()}-{song_id}"
__purge_old()
if context.get_config().get_verbose():
print(f"get_song with track_id [{song_id}] on [{current_config.get_friendly_name()}] ...")
song_cache_entry: SongCacheEntry = __song_cache[song_key] if song_key in __song_cache else None
cache_hit: bool = song_cache_entry is not None
if song_cache_entry is None:
# get song.
loaded: Song = __try_get_song(context, current_config, song_id)
# add to cache even if it's empty so we keep track of that.
song_cache_entry: SongCacheEntry = SongCacheEntry(song=loaded)
__song_cache[song_key] = song_cache_entry
result: Song = song_cache_entry.song
if context.get_config().get_verbose():
print(f"Getting cached song for [{song_id}] key [{song_key}]: [{result is not None}] Hit [{cache_hit}] ...")
return result
3 changes: 2 additions & 1 deletion mpd_subsonic_scrobbler/subsonic_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from urllib.error import URLError

import os
import song_cache


def __get_connector(subsonic_server_config: SubsonicServerConfig) -> Connector:
Expand Down Expand Up @@ -75,7 +76,7 @@ def __get_subsonic_track_id_for_config(
# must check if track belongs to server in this case
song: Song = None
try:
song = get_song(current_config=subsonic_server_config, song_id=right)
song = song_cache.get_song(context=context, current_config=subsonic_server_config, song_id=right)
except DataNotFoundError:
# Song does not belong to current server
pass
Expand Down
Loading

0 comments on commit dbc3f3c

Please sign in to comment.