Support a TOML configuration file
The new config file currently only configures the same options that were already available through environment variables. However I have ideas for additional features that would be much nicer to support using a structured configuration format like TOML rather than environment variables, so config files exist now! The previous environment variables are still supported and will be used if you don't have a config file. I plan to keep supporting the MPD_HOST and MPD_PORT environment variables forever since they're shared with other MPD clients such as mpc, but I may eventually drop the environment variables specific to mpd-now-playable in a future release.
This commit is contained in:
parent
796e3df87d
commit
dc037a0a4b
12 changed files with 305 additions and 33 deletions
112
pdm.lock
112
pdm.lock
|
@ -5,7 +5,7 @@
|
||||||
groups = ["default", "all", "dev"]
|
groups = ["default", "all", "dev"]
|
||||||
strategy = ["cross_platform"]
|
strategy = ["cross_platform"]
|
||||||
lock_version = "4.4.1"
|
lock_version = "4.4.1"
|
||||||
content_hash = "sha256:2b4fba8ca6883f5a1de28a420870d566efa031d62f9057c59095a25a0e51d36e"
|
content_hash = "sha256:828f8051de2c2a04cede8130abf86764edb38f0428cd1d52dd797bff43452afe"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aiocache"
|
name = "aiocache"
|
||||||
|
@ -54,6 +54,22 @@ files = [
|
||||||
{file = "aiomcache-0.8.2.tar.gz", hash = "sha256:43b220d7f499a32a71871c4f457116eb23460fa216e69c1d32b81e3209e51359"},
|
{file = "aiomcache-0.8.2.tar.gz", hash = "sha256:43b220d7f499a32a71871c4f457116eb23460fa216e69c1d32b81e3209e51359"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "apischema"
|
||||||
|
version = "0.18.1"
|
||||||
|
requires_python = ">=3.7"
|
||||||
|
summary = "JSON (de)serialization, GraphQL and JSON schema generation using Python typing."
|
||||||
|
files = [
|
||||||
|
{file = "apischema-0.18.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:220aa56974f765dc100e875c66c688ff57bad3ae48d0aeaee4fb1ec90c5cd0fd"},
|
||||||
|
{file = "apischema-0.18.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:653492010d22acdcbe2240f0ceb3ca59de1b6d34640895e03fda15f944e216d8"},
|
||||||
|
{file = "apischema-0.18.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:56a8acc8da59cf7a1052b5aec71d2f8ed6137b54aa1492709acb2c47f0547107"},
|
||||||
|
{file = "apischema-0.18.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:d466ccc31cbc95b381037ed5b9e82e034f921eb28f8057263aefc2817678036f"},
|
||||||
|
{file = "apischema-0.18.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b005d5a4baba32eccec5bb0fbd616f9fd26b6a5fb4f15e9a8bb53a948adade0"},
|
||||||
|
{file = "apischema-0.18.1-cp312-cp312-win32.whl", hash = "sha256:bd06fc6a52d461bd6540409cb25c1d51aae23b22fcd10b1fb002a3f7f1f15d0f"},
|
||||||
|
{file = "apischema-0.18.1-cp312-cp312-win_amd64.whl", hash = "sha256:429f13d9e35379bf8187c41a3c05562f8149358128382a90e415c63db528d6a2"},
|
||||||
|
{file = "apischema-0.18.1.tar.gz", hash = "sha256:355dc4dea7389f5b25f5326c26f06eebee8107efda7e82db8f09ee122cdf0c98"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "attrs"
|
name = "attrs"
|
||||||
version = "23.2.0"
|
version = "23.2.0"
|
||||||
|
@ -64,6 +80,41 @@ files = [
|
||||||
{file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"},
|
{file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "idna"
|
||||||
|
version = "3.7"
|
||||||
|
requires_python = ">=3.5"
|
||||||
|
summary = "Internationalized Domain Names in Applications (IDNA)"
|
||||||
|
files = [
|
||||||
|
{file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"},
|
||||||
|
{file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "multidict"
|
||||||
|
version = "6.0.5"
|
||||||
|
requires_python = ">=3.7"
|
||||||
|
summary = "multidict implementation"
|
||||||
|
files = [
|
||||||
|
{file = "multidict-6.0.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:51d035609b86722963404f711db441cf7134f1889107fb171a970c9701f92e1e"},
|
||||||
|
{file = "multidict-6.0.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cbebcd5bcaf1eaf302617c114aa67569dd3f090dd0ce8ba9e35e9985b41ac35b"},
|
||||||
|
{file = "multidict-6.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ffc42c922dbfddb4a4c3b438eb056828719f07608af27d163191cb3e3aa6cc5"},
|
||||||
|
{file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ceb3b7e6a0135e092de86110c5a74e46bda4bd4fbfeeb3a3bcec79c0f861e450"},
|
||||||
|
{file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79660376075cfd4b2c80f295528aa6beb2058fd289f4c9252f986751a4cd0496"},
|
||||||
|
{file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4428b29611e989719874670fd152b6625500ad6c686d464e99f5aaeeaca175a"},
|
||||||
|
{file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d84a5c3a5f7ce6db1f999fb9438f686bc2e09d38143f2d93d8406ed2dd6b9226"},
|
||||||
|
{file = "multidict-6.0.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76c0de87358b192de7ea9649beb392f107dcad9ad27276324c24c91774ca5271"},
|
||||||
|
{file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:79a6d2ba910adb2cbafc95dad936f8b9386e77c84c35bc0add315b856d7c3abb"},
|
||||||
|
{file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:92d16a3e275e38293623ebf639c471d3e03bb20b8ebb845237e0d3664914caef"},
|
||||||
|
{file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:fb616be3538599e797a2017cccca78e354c767165e8858ab5116813146041a24"},
|
||||||
|
{file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:14c2976aa9038c2629efa2c148022ed5eb4cb939e15ec7aace7ca932f48f9ba6"},
|
||||||
|
{file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:435a0984199d81ca178b9ae2c26ec3d49692d20ee29bc4c11a2a8d4514c67eda"},
|
||||||
|
{file = "multidict-6.0.5-cp312-cp312-win32.whl", hash = "sha256:9fe7b0653ba3d9d65cbe7698cca585bf0f8c83dbbcc710db9c90f478e175f2d5"},
|
||||||
|
{file = "multidict-6.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:01265f5e40f5a17f8241d52656ed27192be03bfa8764d88e8220141d1e4b3556"},
|
||||||
|
{file = "multidict-6.0.5-py3-none-any.whl", hash = "sha256:0d63c74e3d7ab26de115c49bffc92cc77ed23395303d496eae515d4204a625e7"},
|
||||||
|
{file = "multidict-6.0.5.tar.gz", hash = "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mypy"
|
name = "mypy"
|
||||||
version = "1.10.0"
|
version = "1.10.0"
|
||||||
|
@ -215,6 +266,26 @@ files = [
|
||||||
{file = "python_mpd2-3.1.1-py2.py3-none-any.whl", hash = "sha256:86bf1100a0b135959d74a9a7a58cf0515bf30bb54eb25ae6fb8e175e50300fc3"},
|
{file = "python_mpd2-3.1.1-py2.py3-none-any.whl", hash = "sha256:86bf1100a0b135959d74a9a7a58cf0515bf30bb54eb25ae6fb8e175e50300fc3"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pytomlpp"
|
||||||
|
version = "1.0.13"
|
||||||
|
summary = "A python wrapper for toml++"
|
||||||
|
files = [
|
||||||
|
{file = "pytomlpp-1.0.13-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4710c72456c10a90e58084174312abef8f9652b0f91c240c008903c1bd99814d"},
|
||||||
|
{file = "pytomlpp-1.0.13-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b59acc12339a992404289ab7294f28ba06c7df3c2562e81d316a0e744ab4103b"},
|
||||||
|
{file = "pytomlpp-1.0.13-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:252e31a5e013a74b898784f4ffb8aa8068e136b910ad11f2af1ee8a5700e6e1e"},
|
||||||
|
{file = "pytomlpp-1.0.13-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:09e716c0f462d15f2334cecc736957777dd30f8a5bfa5cf8150679da7577d2fd"},
|
||||||
|
{file = "pytomlpp-1.0.13-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:19dbded2995370e802105fa6dce54ed60f79e58b4eb35fee7ef33f1fb5958f6c"},
|
||||||
|
{file = "pytomlpp-1.0.13-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9f87f6c958309e4c2358b778902c80bd33611d1c392f1abe2c226e3a62909ca4"},
|
||||||
|
{file = "pytomlpp-1.0.13-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e285aca948b419301fdda1927723287ef28482752782c44c9ee8c57eae7a1dc8"},
|
||||||
|
{file = "pytomlpp-1.0.13-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:aad6ae19c056ea62a43fec82427ad4675b5c773dc255c4bdcf6da659cd7edff6"},
|
||||||
|
{file = "pytomlpp-1.0.13-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0e0b34b7a132856567714342e9a622f7be0b4c9bac561a6252f0f85626c1aa4b"},
|
||||||
|
{file = "pytomlpp-1.0.13-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ac06ca7683f5a2737b3888ea1e38d6968abb24fab703bc7ceccbe589d5420e0c"},
|
||||||
|
{file = "pytomlpp-1.0.13-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35225c1d9d674df87b4682f04af97856049351c38822455b78258248d9309363"},
|
||||||
|
{file = "pytomlpp-1.0.13-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:dbc9208ac58ea2a9d5ebb77e69d54d146744007f4a704a3f4e56d9881d41ee1c"},
|
||||||
|
{file = "pytomlpp-1.0.13.tar.gz", hash = "sha256:a0bd639a8f624d1bdf5b3ea94363ca23dbfef38ab7b5b9348881a84afab434ad"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redis"
|
name = "redis"
|
||||||
version = "5.0.4"
|
version = "5.0.4"
|
||||||
|
@ -259,3 +330,42 @@ files = [
|
||||||
{file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"},
|
{file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"},
|
||||||
{file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"},
|
{file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "xdg-base-dirs"
|
||||||
|
version = "6.0.1"
|
||||||
|
requires_python = ">=3.10,<4.0"
|
||||||
|
summary = "Variables defined by the XDG Base Directory Specification"
|
||||||
|
files = [
|
||||||
|
{file = "xdg_base_dirs-6.0.1-py3-none-any.whl", hash = "sha256:63f6ebc1721ced2e86c340856e004ef829501a30a37e17079c52cfaf0e1741b9"},
|
||||||
|
{file = "xdg_base_dirs-6.0.1.tar.gz", hash = "sha256:b4c8f4ba72d1286018b25eea374ec6fbf4fddda3d4137edf50de95de53e195a6"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "yarl"
|
||||||
|
version = "1.9.4"
|
||||||
|
requires_python = ">=3.7"
|
||||||
|
summary = "Yet another URL library"
|
||||||
|
dependencies = [
|
||||||
|
"idna>=2.0",
|
||||||
|
"multidict>=4.0",
|
||||||
|
]
|
||||||
|
files = [
|
||||||
|
{file = "yarl-1.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81"},
|
||||||
|
{file = "yarl-1.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142"},
|
||||||
|
{file = "yarl-1.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074"},
|
||||||
|
{file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129"},
|
||||||
|
{file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2"},
|
||||||
|
{file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78"},
|
||||||
|
{file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4"},
|
||||||
|
{file = "yarl-1.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0"},
|
||||||
|
{file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51"},
|
||||||
|
{file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff"},
|
||||||
|
{file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7"},
|
||||||
|
{file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc"},
|
||||||
|
{file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10"},
|
||||||
|
{file = "yarl-1.9.4-cp312-cp312-win32.whl", hash = "sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7"},
|
||||||
|
{file = "yarl-1.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984"},
|
||||||
|
{file = "yarl-1.9.4-py3-none-any.whl", hash = "sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad"},
|
||||||
|
{file = "yarl-1.9.4.tar.gz", hash = "sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf"},
|
||||||
|
]
|
||||||
|
|
|
@ -10,6 +10,10 @@ dependencies = [
|
||||||
"attrs>=23.1.0",
|
"attrs>=23.1.0",
|
||||||
"pyobjc-framework-MediaPlayer>=10.0",
|
"pyobjc-framework-MediaPlayer>=10.0",
|
||||||
"python-mpd2>=3.1.0",
|
"python-mpd2>=3.1.0",
|
||||||
|
"xdg-base-dirs>=6.0.1",
|
||||||
|
"pytomlpp>=1.0.13",
|
||||||
|
"apischema>=0.18.1",
|
||||||
|
"yarl>=1.9.4",
|
||||||
]
|
]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|
||||||
|
|
|
@ -2,10 +2,10 @@ from __future__ import annotations
|
||||||
|
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from typing import Any, Optional, TypeVar
|
from typing import Any, Optional, TypeVar
|
||||||
from urllib.parse import parse_qsl, urlparse
|
|
||||||
|
|
||||||
from aiocache import Cache
|
from aiocache import Cache
|
||||||
from aiocache.serializers import BaseSerializer, PickleSerializer
|
from aiocache.serializers import BaseSerializer, PickleSerializer
|
||||||
|
from yarl import URL
|
||||||
|
|
||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
@ -28,24 +28,23 @@ class OrmsgpackSerializer(BaseSerializer):
|
||||||
return ormsgpack.unpackb(value)
|
return ormsgpack.unpackb(value)
|
||||||
|
|
||||||
|
|
||||||
def make_cache(url: str, namespace: str = "") -> Cache[T]:
|
def make_cache(url: URL, namespace: str = "") -> Cache[T]:
|
||||||
parsed_url = urlparse(url)
|
backend = Cache.get_scheme_class(url.scheme)
|
||||||
backend = Cache.get_scheme_class(parsed_url.scheme)
|
|
||||||
if backend == Cache.MEMORY:
|
if backend == Cache.MEMORY:
|
||||||
return Cache(backend)
|
return Cache(backend)
|
||||||
kwargs: dict[str, Any] = dict(parse_qsl(parsed_url.query))
|
kwargs: dict[str, Any] = dict(url.query)
|
||||||
|
|
||||||
if parsed_url.path:
|
if url.path:
|
||||||
kwargs.update(backend.parse_uri_path(parsed_url.path))
|
kwargs.update(backend.parse_uri_path(url.path))
|
||||||
|
|
||||||
if parsed_url.hostname:
|
if url.host:
|
||||||
kwargs["endpoint"] = parsed_url.hostname
|
kwargs["endpoint"] = url.host
|
||||||
|
|
||||||
if parsed_url.port:
|
if url.port:
|
||||||
kwargs["port"] = parsed_url.port
|
kwargs["port"] = url.port
|
||||||
|
|
||||||
if parsed_url.password:
|
if url.password:
|
||||||
kwargs["password"] = parsed_url.password
|
kwargs["password"] = url.password
|
||||||
|
|
||||||
namespace = ":".join(s for s in [kwargs.pop("namespace", ""), namespace] if s)
|
namespace = ":".join(s for s in [kwargs.pop("namespace", ""), namespace] if s)
|
||||||
|
|
||||||
|
|
|
@ -1,25 +1,19 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
from os import environ
|
|
||||||
|
|
||||||
from corefoundationasyncio import CoreFoundationEventLoop
|
from corefoundationasyncio import CoreFoundationEventLoop
|
||||||
|
|
||||||
from .__version__ import __version__
|
from .__version__ import __version__
|
||||||
from .cocoa.now_playing import CocoaNowPlaying
|
from .cocoa.now_playing import CocoaNowPlaying
|
||||||
|
from .config.load import loadConfig
|
||||||
from .mpd.listener import MpdStateListener
|
from .mpd.listener import MpdStateListener
|
||||||
|
|
||||||
|
|
||||||
async def listen() -> None:
|
async def listen() -> None:
|
||||||
port = int(environ.get("MPD_PORT", "6600"))
|
|
||||||
host = environ.get("MPD_HOST", "localhost")
|
|
||||||
password = environ.get("MPD_PASSWORD")
|
|
||||||
cache = environ.get("MPD_NOW_PLAYABLE_CACHE")
|
|
||||||
if password is None and "@" in host:
|
|
||||||
password, host = host.split("@", maxsplit=1)
|
|
||||||
|
|
||||||
print(f"mpd-now-playable v{__version__}")
|
print(f"mpd-now-playable v{__version__}")
|
||||||
listener = MpdStateListener(cache)
|
config = loadConfig()
|
||||||
|
listener = MpdStateListener(config.cache)
|
||||||
now_playing = CocoaNowPlaying(listener)
|
now_playing = CocoaNowPlaying(listener)
|
||||||
await listener.start(host=host, port=port, password=password)
|
await listener.start(config.mpd)
|
||||||
await listener.loop(now_playing)
|
await listener.loop(now_playing)
|
||||||
|
|
||||||
|
|
||||||
|
|
0
src/mpd_now_playable/config/__init__.py
Normal file
0
src/mpd_now_playable/config/__init__.py
Normal file
44
src/mpd_now_playable/config/config-v1.json
Normal file
44
src/mpd_now_playable/config/config-v1.json
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
{
|
||||||
|
"$id": "https://cdn.00dani.me/m/schemata/mpd-now-playable/config-v1.json",
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"definitions": {
|
||||||
|
"URL": {
|
||||||
|
"format": "uri",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"$schema": {
|
||||||
|
"$ref": "#/definitions/URL"
|
||||||
|
},
|
||||||
|
"cache": {
|
||||||
|
"$ref": "#/definitions/URL"
|
||||||
|
},
|
||||||
|
"mpd": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"default": {
|
||||||
|
"host": "127.0.0.1",
|
||||||
|
"port": 6600
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"host": {
|
||||||
|
"default": "127.0.0.1",
|
||||||
|
"format": "hostname",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"port": {
|
||||||
|
"default": 6600,
|
||||||
|
"maximum": 65535,
|
||||||
|
"minimum": 1,
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
}
|
28
src/mpd_now_playable/config/fields.py
Normal file
28
src/mpd_now_playable/config/fields.py
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
from dataclasses import field
|
||||||
|
from typing import NewType, Optional, TypeVar
|
||||||
|
|
||||||
|
from apischema import schema
|
||||||
|
from apischema.conversions import deserializer
|
||||||
|
from apischema.metadata import none_as_undefined
|
||||||
|
from yarl import URL
|
||||||
|
|
||||||
|
__all__ = ("Host", "Port", "optional")
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
Host = NewType("Host", str)
|
||||||
|
schema(format="hostname")(Host)
|
||||||
|
|
||||||
|
Port = NewType("Port", int)
|
||||||
|
schema(min=1, max=65535)(Port)
|
||||||
|
|
||||||
|
schema(format="uri")(URL)
|
||||||
|
|
||||||
|
|
||||||
|
def optional() -> Optional[T]:
|
||||||
|
return field(default=None, metadata=none_as_undefined)
|
||||||
|
|
||||||
|
|
||||||
|
@deserializer
|
||||||
|
def from_yarl(url: str) -> URL:
|
||||||
|
return URL(url)
|
34
src/mpd_now_playable/config/load.py
Normal file
34
src/mpd_now_playable/config/load.py
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
from os import environ
|
||||||
|
|
||||||
|
from apischema import deserialize
|
||||||
|
from pytomlpp import load
|
||||||
|
from xdg_base_dirs import xdg_config_home
|
||||||
|
|
||||||
|
from .model import Config
|
||||||
|
|
||||||
|
__all__ = "loadConfig"
|
||||||
|
|
||||||
|
|
||||||
|
def loadConfigFromFile() -> Config:
|
||||||
|
path = xdg_config_home() / "mpd-now-playable" / "config.toml"
|
||||||
|
data = load(path)
|
||||||
|
print(f"Loaded your configuration from {path}")
|
||||||
|
return deserialize(Config, data)
|
||||||
|
|
||||||
|
|
||||||
|
def loadConfigFromEnv() -> Config:
|
||||||
|
port = int(environ["MPD_PORT"]) if "MPD_PORT" in environ else None
|
||||||
|
host = environ.get("MPD_HOST")
|
||||||
|
password = environ.get("MPD_PASSWORD")
|
||||||
|
cache = environ.get("MPD_NOW_PLAYABLE_CACHE")
|
||||||
|
if password is None and host is not None and "@" in host:
|
||||||
|
password, host = host.split("@", maxsplit=1)
|
||||||
|
data = {"cache": cache, "mpd": {"port": port, "host": host, "password": password}}
|
||||||
|
return deserialize(Config, data)
|
||||||
|
|
||||||
|
|
||||||
|
def loadConfig() -> Config:
|
||||||
|
try:
|
||||||
|
return loadConfigFromFile()
|
||||||
|
except FileNotFoundError:
|
||||||
|
return loadConfigFromEnv()
|
26
src/mpd_now_playable/config/model.py
Normal file
26
src/mpd_now_playable/config/model.py
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from apischema import alias
|
||||||
|
from yarl import URL
|
||||||
|
|
||||||
|
from .fields import Host, Port, optional
|
||||||
|
|
||||||
|
__all__ = ("MpdConfig", "Config")
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class MpdConfig:
|
||||||
|
password: Optional[str] = optional()
|
||||||
|
host: Host = Host("127.0.0.1")
|
||||||
|
port: Port = Port(6600)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class Config:
|
||||||
|
schema: URL = field(
|
||||||
|
default=URL("https://cdn.00dani.me/m/schemata/mpd-now-playable/config-v1.json"),
|
||||||
|
metadata=alias("$schema"),
|
||||||
|
)
|
||||||
|
cache: Optional[URL] = optional()
|
||||||
|
mpd: MpdConfig = field(default_factory=MpdConfig)
|
28
src/mpd_now_playable/config/schema.py
Normal file
28
src/mpd_now_playable/config/schema.py
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
from json import dump
|
||||||
|
from pathlib import Path
|
||||||
|
from pprint import pp
|
||||||
|
from typing import Any, Mapping
|
||||||
|
|
||||||
|
from apischema.json_schema import JsonSchemaVersion, deserialization_schema
|
||||||
|
|
||||||
|
from .model import Config
|
||||||
|
|
||||||
|
|
||||||
|
def generate() -> Mapping[str, Any]:
|
||||||
|
return deserialization_schema(Config, version=JsonSchemaVersion.DRAFT_7)
|
||||||
|
|
||||||
|
|
||||||
|
def write() -> None:
|
||||||
|
schema = dict(generate())
|
||||||
|
schema["$id"] = Config.schema.human_repr()
|
||||||
|
|
||||||
|
schema_file = Path(__file__).parent / Config.schema.name
|
||||||
|
print(f"Writing this schema to {schema_file}")
|
||||||
|
pp(schema)
|
||||||
|
with open(schema_file, "w") as fp:
|
||||||
|
dump(schema, fp, indent="\t", sort_keys=True)
|
||||||
|
fp.write("\n")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
write()
|
|
@ -2,6 +2,8 @@ from __future__ import annotations
|
||||||
|
|
||||||
from typing import TypedDict
|
from typing import TypedDict
|
||||||
|
|
||||||
|
from yarl import URL
|
||||||
|
|
||||||
from ..async_tools import run_background_task
|
from ..async_tools import run_background_task
|
||||||
from ..cache import Cache, make_cache
|
from ..cache import Cache, make_cache
|
||||||
from .types import CurrentSongResponse, MpdStateHandler
|
from .types import CurrentSongResponse, MpdStateHandler
|
||||||
|
@ -23,12 +25,15 @@ def calc_track_key(song: CurrentSongResponse) -> str:
|
||||||
return song["file"]
|
return song["file"]
|
||||||
|
|
||||||
|
|
||||||
|
MEMORY = URL("memory://")
|
||||||
|
|
||||||
|
|
||||||
class MpdArtworkCache:
|
class MpdArtworkCache:
|
||||||
mpd: MpdStateHandler
|
mpd: MpdStateHandler
|
||||||
album_cache: Cache[ArtCacheEntry]
|
album_cache: Cache[ArtCacheEntry]
|
||||||
track_cache: Cache[ArtCacheEntry]
|
track_cache: Cache[ArtCacheEntry]
|
||||||
|
|
||||||
def __init__(self, mpd: MpdStateHandler, cache_url: str = "memory://"):
|
def __init__(self, mpd: MpdStateHandler, cache_url: URL = MEMORY):
|
||||||
self.mpd = mpd
|
self.mpd = mpd
|
||||||
self.album_cache = make_cache(cache_url, "album")
|
self.album_cache = make_cache(cache_url, "album")
|
||||||
self.track_cache = make_cache(cache_url, "track")
|
self.track_cache = make_cache(cache_url, "track")
|
||||||
|
|
|
@ -4,7 +4,9 @@ from uuid import UUID
|
||||||
|
|
||||||
from mpd.asyncio import MPDClient
|
from mpd.asyncio import MPDClient
|
||||||
from mpd.base import CommandError
|
from mpd.base import CommandError
|
||||||
|
from yarl import URL
|
||||||
|
|
||||||
|
from ..config.model import MpdConfig
|
||||||
from ..player import Player
|
from ..player import Player
|
||||||
from ..song import PlaybackState, Song, SongListener
|
from ..song import PlaybackState, Song, SongListener
|
||||||
from ..type_tools import convert_if_exists
|
from ..type_tools import convert_if_exists
|
||||||
|
@ -44,20 +46,18 @@ class MpdStateListener(Player):
|
||||||
art_cache: MpdArtworkCache
|
art_cache: MpdArtworkCache
|
||||||
idle_count = 0
|
idle_count = 0
|
||||||
|
|
||||||
def __init__(self, cache: str | None = None) -> None:
|
def __init__(self, cache: URL | None = None) -> None:
|
||||||
self.client = MPDClient()
|
self.client = MPDClient()
|
||||||
self.art_cache = (
|
self.art_cache = (
|
||||||
MpdArtworkCache(self, cache) if cache else MpdArtworkCache(self)
|
MpdArtworkCache(self, cache) if cache else MpdArtworkCache(self)
|
||||||
)
|
)
|
||||||
|
|
||||||
async def start(
|
async def start(self, conf: MpdConfig) -> None:
|
||||||
self, host: str = "localhost", port: int = 6600, password: str | None = None
|
print(f"Connecting to MPD server {conf.host}:{conf.port}...")
|
||||||
) -> None:
|
await self.client.connect(conf.host, conf.port)
|
||||||
print(f"Connecting to MPD server {host}:{port}...")
|
if conf.password is not None:
|
||||||
await self.client.connect(host, port)
|
|
||||||
if password is not None:
|
|
||||||
print("Authorising to MPD with your password...")
|
print("Authorising to MPD with your password...")
|
||||||
await self.client.password(password)
|
await self.client.password(conf.password)
|
||||||
print(f"Connected to MPD v{self.client.mpd_version}")
|
print(f"Connected to MPD v{self.client.mpd_version}")
|
||||||
|
|
||||||
async def refresh(self) -> None:
|
async def refresh(self) -> None:
|
||||||
|
|
Loading…
Reference in a new issue