Adjust receiver protocol to accommodate config

This commit is contained in:
Danielle McLean 2024-07-11 12:15:34 +10:00
parent 09fe3b3e6c
commit 04859b8c8b
Signed by: 00dani
GPG key ID: 6854781A0488421C
5 changed files with 32 additions and 21 deletions

View file

@ -10,15 +10,15 @@ from .mpd.listener import MpdStateListener
from .song_receiver import ( from .song_receiver import (
Receiver, Receiver,
choose_loop_factory, choose_loop_factory,
import_receiver, construct_receiver,
) )
async def listen( async def listen(
config: Config, listener: MpdStateListener, receiver_types: Iterable[type[Receiver]] config: Config, listener: MpdStateListener, receivers: Iterable[Receiver]
) -> None: ) -> None:
await listener.start(config.mpd) await listener.start(config.mpd)
receivers = (rec(listener, config) for rec in receiver_types) await asyncio.gather(*(rec.start(listener) for rec in receivers))
await listener.loop(receivers) await listener.loop(receivers)
@ -28,11 +28,11 @@ def main() -> None:
print(config) print(config)
listener = MpdStateListener(config.cache) listener = MpdStateListener(config.cache)
receiver_types = tuple(import_receiver(rec) for rec in config.receivers) receivers = tuple(construct_receiver(rec_config) for rec_config in config.receivers)
factory = choose_loop_factory(receivers)
factory = choose_loop_factory(receiver_types)
asyncio.run( asyncio.run(
listen(config, listener, receiver_types), listen(config, listener, receivers),
loop_factory=factory.make_loop, loop_factory=factory.make_loop,
debug=True, debug=True,
) )

View file

@ -21,7 +21,7 @@ class BaseReceiverConfig(Protocol):
@dataclass(slots=True) @dataclass(slots=True)
class CocoaReceiverConfig(BaseReceiverConfig): class CocoaReceiverConfig(BaseReceiverConfig):
kind: Literal["cocoa"] = "cocoa" kind: Literal["cocoa"] = field(default="cocoa", repr=False)
ReceiverConfig = Annotated[ ReceiverConfig = Annotated[

View file

@ -83,8 +83,7 @@ class MpdStateListener(Player):
if status["state"] == "stop": if status["state"] == "stop":
print("Nothing playing") print("Nothing playing")
for r in self.receivers: await self.update(None)
r.update(None)
return return
art = await self.art_cache.get_cached_artwork(current) art = await self.art_cache.get_cached_artwork(current)
@ -93,8 +92,10 @@ class MpdStateListener(Player):
song = mpd_current_to_song(status, current, to_artwork(art)) song = mpd_current_to_song(status, current, to_artwork(art))
rprint(song) rprint(song)
for r in self.receivers: await self.update(song)
r.update(song)
async def update(self, song: Song | None) -> None:
await asyncio.gather(*(r.update(song) for r in self.receivers))
async def get_art(self, file: str) -> bytes | None: async def get_art(self, file: str) -> bytes | None:
picture = await self.readpicture(file) picture = await self.readpicture(file)

View file

@ -37,7 +37,7 @@ from MediaPlayer import (
MPRemoteCommandHandlerStatusSuccess, MPRemoteCommandHandlerStatusSuccess,
) )
from ...config.model import Config from ...config.model import CocoaReceiverConfig
from ...player import Player from ...player import Player
from ...song import PlaybackState, Song from ...song import PlaybackState, Song
from ...song_receiver import LoopFactory, Receiver from ...song_receiver import LoopFactory, Receiver
@ -146,7 +146,10 @@ class CocoaNowPlayingReceiver(Receiver):
def loop_factory(cls) -> LoopFactory[CoreFoundationEventLoop]: def loop_factory(cls) -> LoopFactory[CoreFoundationEventLoop]:
return CocoaLoopFactory() return CocoaLoopFactory()
def __init__(self, player: Player, config: Config): def __init__(self, config: CocoaReceiverConfig):
pass
async def start(self, player: Player) -> None:
self.cmd_center = MPRemoteCommandCenter.sharedCommandCenter() self.cmd_center = MPRemoteCommandCenter.sharedCommandCenter()
self.info_center = MPNowPlayingInfoCenter.defaultCenter() self.info_center = MPNowPlayingInfoCenter.defaultCenter()
@ -184,7 +187,7 @@ class CocoaNowPlayingReceiver(Receiver):
# unpause with remote commands. # unpause with remote commands.
self.info_center.setPlaybackState_(MPMusicPlaybackStatePlaying) self.info_center.setPlaybackState_(MPMusicPlaybackStatePlaying)
def update(self, song: Song | None) -> None: async def update(self, song: Song | None) -> None:
if song: if song:
self.info_center.setNowPlayingInfo_(song_to_media_item(song)) self.info_center.setNowPlayingInfo_(song_to_media_item(song))
self.info_center.setPlaybackState_(playback_state_to_cocoa(song.state)) self.info_center.setPlaybackState_(playback_state_to_cocoa(song.state))

View file

@ -3,7 +3,7 @@ from dataclasses import dataclass
from importlib import import_module from importlib import import_module
from typing import Generic, Iterable, Literal, Protocol, TypeVar, cast from typing import Generic, Iterable, Literal, Protocol, TypeVar, cast
from .config.model import BaseReceiverConfig, Config from .config.model import BaseReceiverConfig
from .player import Player from .player import Player
from .song import Song from .song import Song
from .tools.types import not_none from .tools.types import not_none
@ -20,10 +20,12 @@ class LoopFactory(Generic[T], Protocol):
class Receiver(Protocol): class Receiver(Protocol):
def __init__(self, player: Player, config: Config) -> None: ... def __init__(self, config: BaseReceiverConfig): ...
@classmethod @classmethod
def loop_factory(cls) -> LoopFactory[AbstractEventLoop]: ... def loop_factory(cls) -> LoopFactory[AbstractEventLoop]: ...
def update(self, song: Song | None) -> None: ...
async def start(self, player: Player) -> None: ...
async def update(self, song: Song | None) -> None: ...
class ReceiverModule(Protocol): class ReceiverModule(Protocol):
@ -42,8 +44,8 @@ class DefaultLoopFactory(LoopFactory[AbstractEventLoop]):
@dataclass @dataclass
class IncompatibleReceiverError(Exception): class IncompatibleReceiverError(Exception):
a: type[Receiver] a: Receiver
b: type[Receiver] b: Receiver
def import_receiver(config: BaseReceiverConfig) -> type[Receiver]: def import_receiver(config: BaseReceiverConfig) -> type[Receiver]:
@ -53,13 +55,18 @@ def import_receiver(config: BaseReceiverConfig) -> type[Receiver]:
return mod.receiver return mod.receiver
def construct_receiver(config: BaseReceiverConfig) -> Receiver:
cls = import_receiver(config)
return cls(config)
def choose_loop_factory( def choose_loop_factory(
receivers: Iterable[type[Receiver]], receivers: Iterable[Receiver],
) -> LoopFactory[AbstractEventLoop]: ) -> LoopFactory[AbstractEventLoop]:
"""Given the desired receivers, determine which asyncio event loop implementation will support all of them. Will raise an IncompatibleReceiverError if no such implementation exists.""" """Given the desired receivers, determine which asyncio event loop implementation will support all of them. Will raise an IncompatibleReceiverError if no such implementation exists."""
chosen_fac: LoopFactory[AbstractEventLoop] = DefaultLoopFactory() chosen_fac: LoopFactory[AbstractEventLoop] = DefaultLoopFactory()
chosen_rec: type[Receiver] | None = None chosen_rec: Receiver | None = None
for rec in receivers: for rec in receivers:
fac = rec.loop_factory() fac = rec.loop_factory()