From 13b6dae8b29b6a939062d4d4c67d120e543be5c0 Mon Sep 17 00:00:00 2001 From: alexdelorenzo Date: Wed, 23 Jun 2021 23:52:28 -0400 Subject: [PATCH] Fix seeking, enable icon caching, ensure that user data dirs are created if they don't exist before trying to create the user data --- cast_control/__init__.py | 2 +- cast_control/base.py | 46 ++++++++++++++---------- cast_control/wrapper.py | 76 ++++++++++++++++++++++++++++++++++------ pyproject.toml | 4 +-- requirements.txt | 2 +- 5 files changed, 98 insertions(+), 32 deletions(-) diff --git a/cast_control/__init__.py b/cast_control/__init__.py index 9499342..1108a27 100644 --- a/cast_control/__init__.py +++ b/cast_control/__init__.py @@ -8,7 +8,7 @@ __license__: Final[str] = 'AGPL-3.0' __copyright__: Final[str] = \ f'Copyright 2021 {__author__}. Licensed under terms of the {__license__}.' -__version__: Final[str] = '0.11.2' +__version__: Final[str] = '0.11.3' NAME: Final[str] = 'cast_control' SHORT_NAME: Final[str] = 'castctl' diff --git a/cast_control/base.py b/cast_control/base.py index 5fbaa29..73eb287 100644 --- a/cast_control/base.py +++ b/cast_control/base.py @@ -1,10 +1,10 @@ from __future__ import annotations -from typing import Optional, Union, NamedTuple +from typing import Optional, Union, NamedTuple, Callable from pathlib import Path from uuid import UUID from enum import auto from os import stat_result -from functools import lru_cache +from functools import lru_cache, wraps from asyncio import gather, run from weakref import finalize import logging @@ -129,6 +129,31 @@ def set_log_level( ) +async def _create_user_dirs(): + paths = map(AsyncPath, USER_DIRS) + + coros = ( + path.mkdir(parents=True, exist_ok=True) + for path in paths + ) + + await gather(*coros) + + +@lru_cache(LRU_MAX_SIZE) +def create_user_dirs(): + run(_create_user_dirs()) + + +def ensure_user_dirs_exist(func: Callable) -> Callable: + @wraps(func) + def new_func(*args, **kwargs): + create_user_dirs() + return func(*args, **kwargs) + + return new_func + + def get_stat(file: Path) -> stat_result: return file.stat() @@ -174,6 +199,7 @@ def new_file_from_template(file: Path, icon_path: Path): @lru_cache(LRU_MAX_SIZE) +@ensure_user_dirs_exist def create_desktop_file(light_icon: bool = True) -> Path: icon, file = get_paths(light_icon) @@ -183,22 +209,6 @@ def create_desktop_file(light_icon: bool = True) -> Path: return file -async def _create_user_dirs(): - paths = map(AsyncPath, USER_DIRS) - - coros = ( - path.mkdir(parents=True, exist_ok=True) - for path in paths - ) - - await gather(*coros) - - -@lru_cache(LRU_MAX_SIZE) -def create_user_dirs(): - run(_create_user_dirs()) - - def get_device_via_host( host: str, retry_wait: Optional[float] = DEFAULT_RETRY_WAIT, diff --git a/cast_control/wrapper.py b/cast_control/wrapper.py index ee8f211..8a3eb6b 100644 --- a/cast_control/wrapper.py +++ b/cast_control/wrapper.py @@ -30,7 +30,7 @@ from .base import DEFAULT_THUMB, LIGHT_THUMB, NO_DURATION, NO_DELTA, \ US_IN_SEC, DEFAULT_DISC_NO, MediaType, NO_DESKTOP_FILE, LRU_MAX_SIZE, \ NAME, create_desktop_file, DEFAULT_ICON, create_user_dirs, \ - Device + Device, ensure_user_dirs_exist RESOLUTION: Final[int] = 1 @@ -56,8 +56,8 @@ class Titles(NamedTuple): class Controllers(NamedTuple): - yt: YouTubeController - spotify: SpotifyController + yt: YouTubeController = None + spotify: SpotifyController = None # dash: DashCastController # bbc_ip: BbcIplayerController # bbc_sound: BbcSoundsController @@ -73,6 +73,7 @@ class Controllers(NamedTuple): class Wrapper(Protocol): dev: Device ctls: Controllers + cached_icon: Optional[CachedIcon] = None light_icon: bool = DEFAULT_ICON @property @@ -131,7 +132,7 @@ def __init__(self): def _setup_controllers(self): self.ctls = Controllers( YouTubeController(), - SpotifyController(), + # SpotifyController(), # DashCastController(), # BbcIplayerController(), # BbcSoundsController(), @@ -144,6 +145,9 @@ def _setup_controllers(self): ) for ctl in self.ctls: + if not ctl: + continue + self._register(ctl) def _register(self, controller: BaseController): @@ -213,6 +217,12 @@ def titles(self) -> Titles: if title: titles.append(title) + if self.media_status: + series_title = self.media_status.series_title + + if series_title: + titles.append(series_title) + subtitle = self.get_subtitle() if subtitle: @@ -324,24 +334,70 @@ def set_rate(self, val: RateDecimal): pass +class CachedIcon(NamedTuple): + url: str + app_id: str + title: str + + class IconsMixin(Wrapper): def set_icon(self, lighter: bool = False): self.light_icon: bool = lighter - def get_art_url(self, track: Optional[int] = None) -> str: - thumb = self.media_controller.thumbnail + def _set_cached_icon(self, url: Optional[str] = None): + if not url: + self.cached_icon = None + return + + app_id = self.dev.app_id + title, *_ = self.titles + self.cached_icon = CachedIcon(url, app_id, title) + + def _can_use_cache(self) -> bool: + cache = self.cached_icon - if thumb: - return thumb + if not cache or not cache.url: + return False + + app_id = self.dev.app_id + title, *_ = self.titles - icon: Optional[str] = None + if cache.app_id != app_id or cache.title != title: + return False + + return True + + def _get_icon_from_device(self) -> Optional[str]: + images = self.media_status.images + + if images: + first, *_ = images + url, *_ = first + + self._set_cached_icon(url) + return url + + url: Optional[str] = None if self.cast_status: - icon = self.cast_status.icon_url + url = self.cast_status.icon_url + + if url: + self._set_cached_icon(url) + return url + + if not self._can_use_cache(): + return None + + return self.cached_icon.url + + def get_art_url(self, track: Optional[int] = None) -> str: + icon = self._get_icon_from_device() if icon: return icon + # use default icon files if there's no thumbnails create_user_dirs() if self.light_icon: diff --git a/pyproject.toml b/pyproject.toml index 7f610c0..0213d15 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ [project] name = "cast_control" - version = "0.11.2" + version = "0.11.3" description = "📺 Control Chromecasts from Linux and D-Bus" license = "AGPL-3.0" homepage = "https://github.com/alexdelorenzo/cast_control" @@ -20,7 +20,7 @@ aiopath = ">=0.5.6" click = "==8.0.1" daemons = "==1.3.2" - mpris-server = ">=0.3.3, <0.4.0" + mpris-server = ">=0.3.4, <0.4.0" pychromecast = "==9.2.0" pydbus = ">=0.6.0" pygobject = ">=3.34.0" diff --git a/requirements.txt b/requirements.txt index 943fe25..30b5b7e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,4 +8,4 @@ typing-extensions>=3.10.0.0; python_version < '3.10' aiopath>=0.5.8; python_version <= '3.9' aiopath>=0.6.8; python_version >= '3.10' -mpris_server>=0.3.3, <0.4.0 +mpris_server>=0.3.4, <0.4.0