Merge branch '218-python3.8-flatpak' into master

This commit is contained in:
Sumner Evans
2020-09-22 13:08:49 -06:00
118 changed files with 1962 additions and 1396 deletions

25
.builds/build-flatpak.yml Normal file
View File

@@ -0,0 +1,25 @@
image: archlinux
packages:
- dbus
- flatpak
- flatpak-builder
- gobject-introspection
- gtk3
- mpv
- python-cairo
- python-gobject
- python-pip
- python-poetry
- xorg-server-xvfb
sources:
- https://git.sr.ht/~sumner/sublime-music
environment:
REPO_NAME: sublime-music
# triggers:
# - action: email
# condition: failure
# to: ~sumner/sublime-music-devel@lists.sr.ht
tasks:
- build-flatpak: |
cd ${REPO_NAME}/flatpak
sudo REPO=repo ./flatpak_build.sh

48
.builds/build.yml Normal file
View File

@@ -0,0 +1,48 @@
image: archlinux
packages:
- dbus
- gobject-introspection
- gtk3
- mpv
- python-cairo
- python-gobject
- python-poetry
- xorg-server-xvfb
sources:
- https://git.sr.ht/~sumner/sublime-music
environment:
REPO_NAME: sublime-music
triggers:
- action: email
condition: failure
to: ~sumner/sublime-music-devel@lists.sr.ht
tasks:
- setup: |
cd ${REPO_NAME}
poetry install
echo "cd ${REPO_NAME}" >> ~/.buildenv
echo "source $(poetry env info -p)/bin/activate" >> ~/.buildenv
- lint: |
python setup.py check -mrs
black --check .
flake8
mypy sublime_music tests/**/*.py
cicd/custom_style_check.py
- test: |
Xvfb :119 -screen 0 1024x768x16 &
export DISPLAY=:119
pytest
- build: |
python setup.py sdist
- deploy-pypi: |
./cicd/run_if_tagged_with_version \
"twine upload -r testpypi dist/*" \
"twine upload dist/*"
- verify-pypi: |
./cicd/run_if_tagged_with_version \
"pip install ${REPO_NAME}"

33
.builds/readme.yml Normal file
View File

@@ -0,0 +1,33 @@
image: alpine/edge
packages:
- curl
- git
- openssh
- py3-docutils
sources:
- https://git.sr.ht/~sumner/sublime-music
secrets:
# README Personal Access Token
- 2fb5fd72-fa96-46c6-ab90-6b7cabebba16
environment:
REPO_NAME: sublime-music
triggers:
- action: email
condition: failure
to: ~sumner/sublime-music-devel@lists.sr.ht
tasks:
- setup: |
echo "cd ${REPO_NAME}" >> ~/.buildenv
# If we are on the master branch, compile the README.rst to HTML and set it
# as the README for the repo.
- readme: |
set +x
git branch --contains | grep master &&
rst2html5 --no-doc-title README.rst | \
curl -H "Content-Type: text/html" \
-H "Authorization: Bearer $(cat ~/.readme-token)" \
-XPUT \
--data-binary @- \
"https://git.sr.ht/api/repos/${REPO_NAME}/readme" &&
echo "README set" || echo "Skipping README set because not on master"

7
.envrc Normal file
View File

@@ -0,0 +1,7 @@
# Run poetry install and activate the virtualenv
poetry install
source .venv/bin/activate
watch_file pyproject.toml
watch_file poetry.lock
watch_file setup.py

1
.gitignore vendored
View File

@@ -1,5 +1,6 @@
flatpak/flatpak_build_dir/
flatpak/sublime-music.flatpak
flatpak/repo
# Created by https://www.gitignore.io/api/python
# Edit at https://www.gitignore.io/?templates=python

View File

@@ -22,11 +22,11 @@ lint:
before_script:
- ./cicd/install-project-deps.sh
script:
- pipenv run python setup.py check -mrs
- pipenv run black --check .
- pipenv run flake8
- pipenv run mypy sublime tests/**/*.py
- pipenv run cicd/custom_style_check.py
- poetry run python setup.py check -mrs
- poetry run black --check .
- poetry run flake8
- poetry run mypy sublime_music tests/**/*.py
- poetry run cicd/custom_style_check.py
test:
stage: test
@@ -37,7 +37,7 @@ test:
- Xvfb :119 -screen 0 1024x768x16 &
- export DISPLAY=:119
script:
- pipenv run ./cicd/pytest.sh
- poetry run ./cicd/pytest.sh
artifacts:
paths:
- htmlcov
@@ -47,7 +47,7 @@ build:
before_script:
- ./cicd/install-project-deps.sh
script:
- pipenv run python setup.py sdist
- poetry run python setup.py sdist
artifacts:
paths:
- dist/*
@@ -55,7 +55,6 @@ build:
build_flatpak:
image: registry.gitlab.com/sublime-music/sublime-music/flatpak-build:latest
allow_failure: true
stage: build
script:
- cd flatpak
@@ -116,7 +115,7 @@ publish_release:
before_script:
- ./cicd/install-project-deps.sh
script:
- pipenv run ./cicd/publish-gitlab-release.sh
- poetry run ./cicd/publish-gitlab-release.sh
# Scheduled Jobs
# =============================================================================

View File

@@ -1,10 +1,23 @@
v0.11.9
=======
**The wait is over!** Thanks to help from jlanda_, the **Flatpak** is back! The
Flatpak requires ``org.gnome.SDK//3.38`` and ``org.gnome.Platform//3.38``.
.. _jlanda: https://gitlab.com/jlanda
**Bug Fixes**
* Fixed regressions with Chromecast playback.
**Infrastructure**
* Switched from Pipenv to Poetry because Poetry is so much faster.
* Added a ``.envrc`` file for direnv users.
* Started migrating from GitLab to sr.ht due to usability regressions in GitLab.
* **Package name change:** The package name is now ``sublime_music`` instead of
``sublime``.
v0.11.8
=======

View File

@@ -83,8 +83,8 @@ Building the flatpak
--------------------
- A flatpak-builder environment must be setup on the build machine to do a
flatpak build. This includes ``org.gnome.SDK//3.34`` and
``org.gnome.Platform//3.34``.
flatpak build. This includes ``org.gnome.SDK//3.36`` and
``org.gnome.Platform//3.36``.
- The ``flatpak`` folder contains the required files to build a flatpak package.
- The script ``flatpak_build.sh`` will run the required commands to grab the
remaining dependencies and build the flatpak.
@@ -117,6 +117,7 @@ before knowing if your code is the correct style.
* ``flake8-importorder`` (with the ``edited`` import style): enforce ordering
of import statements.
* ``flake8-pep3101``: no ``%`` string formatting.
* ``flake8-print``: to prevent using the ``print`` function.
* `mypy`_ is used for type checking. All type errors must be resolved.

35
Pipfile
View File

@@ -1,35 +0,0 @@
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true
[dev-packages]
black = "*"
docutils = "*"
flake8 = "*"
flake8-annotations = "*"
flake8-bugbear = "*"
flake8-comprehensions = "*"
flake8-import-order = "*"
flake8-pep3101 = "*"
flake8-print = "*"
graphviz = "*"
lxml = "*"
mypy = "*"
pycodestyle = "*"
pytest = "*"
pytest-cov = "*"
rope = "*"
rst2html5 = "*"
sphinx = "*"
sphinx-rtd-theme = "*"
termcolor = "*"
[packages]
sublime-music = {editable = true, extras = ["chromecast", "keyring", "server"], path = "."}
[requires]
python_version = "3.8"
[pipenv]
allow_prereleases = true

980
Pipfile.lock generated
View File

@@ -1,980 +0,0 @@
{
"_meta": {
"hash": {
"sha256": "f018bc2d21d6dc296af872daca484440360496dc7fd3746880da2fe2996ed0ce"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.8"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"bleach": {
"hashes": [
"sha256:52b5919b81842b1854196eaae5ca29679a2f2e378905c346d3ca8227c2c66080",
"sha256:9f8ccbeb6183c6e6cddea37592dfb0167485c1e3b13b3363bc325aa8bda3adbd"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==3.2.1"
},
"bottle": {
"hashes": [
"sha256:0819b74b145a7def225c0e83b16a4d5711fde751cd92bae467a69efce720f69e",
"sha256:43157254e88f32c6be16f8d9eb1f1d1472396a4e174ebd2bf62544854ecf37e7"
],
"version": "==0.12.18"
},
"casttube": {
"hashes": [
"sha256:36f118007f9eead3959cf30de03c1640b53a263569ff2a3971c0521826c835b2",
"sha256:54d2af8c7949aa9c5db87fb11ef0a478a5d3e7ac6d2d2ac8dd1711e3a516fc82"
],
"version": "==0.2.1"
},
"certifi": {
"hashes": [
"sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3",
"sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41"
],
"version": "==2020.6.20"
},
"cffi": {
"hashes": [
"sha256:0da50dcbccd7cb7e6c741ab7912b2eff48e85af217d72b57f80ebc616257125e",
"sha256:12a453e03124069b6896107ee133ae3ab04c624bb10683e1ed1c1663df17c13c",
"sha256:15419020b0e812b40d96ec9d369b2bc8109cc3295eac6e013d3261343580cc7e",
"sha256:15a5f59a4808f82d8ec7364cbace851df591c2d43bc76bcbe5c4543a7ddd1bf1",
"sha256:23e44937d7695c27c66a54d793dd4b45889a81b35c0751ba91040fe825ec59c4",
"sha256:29c4688ace466a365b85a51dcc5e3c853c1d283f293dfcc12f7a77e498f160d2",
"sha256:57214fa5430399dffd54f4be37b56fe22cedb2b98862550d43cc085fb698dc2c",
"sha256:577791f948d34d569acb2d1add5831731c59d5a0c50a6d9f629ae1cefd9ca4a0",
"sha256:6539314d84c4d36f28d73adc1b45e9f4ee2a89cdc7e5d2b0a6dbacba31906798",
"sha256:65867d63f0fd1b500fa343d7798fa64e9e681b594e0a07dc934c13e76ee28fb1",
"sha256:672b539db20fef6b03d6f7a14b5825d57c98e4026401fce838849f8de73fe4d4",
"sha256:6843db0343e12e3f52cc58430ad559d850a53684f5b352540ca3f1bc56df0731",
"sha256:7057613efefd36cacabbdbcef010e0a9c20a88fc07eb3e616019ea1692fa5df4",
"sha256:76ada88d62eb24de7051c5157a1a78fd853cca9b91c0713c2e973e4196271d0c",
"sha256:837398c2ec00228679513802e3744d1e8e3cb1204aa6ad408b6aff081e99a487",
"sha256:8662aabfeab00cea149a3d1c2999b0731e70c6b5bac596d95d13f643e76d3d4e",
"sha256:95e9094162fa712f18b4f60896e34b621df99147c2cee216cfa8f022294e8e9f",
"sha256:99cc66b33c418cd579c0f03b77b94263c305c389cb0c6972dac420f24b3bf123",
"sha256:9b219511d8b64d3fa14261963933be34028ea0e57455baf6781fe399c2c3206c",
"sha256:ae8f34d50af2c2154035984b8b5fc5d9ed63f32fe615646ab435b05b132ca91b",
"sha256:b9aa9d8818c2e917fa2c105ad538e222a5bce59777133840b93134022a7ce650",
"sha256:bf44a9a0141a082e89c90e8d785b212a872db793a0080c20f6ae6e2a0ebf82ad",
"sha256:c0b48b98d79cf795b0916c57bebbc6d16bb43b9fc9b8c9f57f4cf05881904c75",
"sha256:da9d3c506f43e220336433dffe643fbfa40096d408cb9b7f2477892f369d5f82",
"sha256:e4082d832e36e7f9b2278bc774886ca8207346b99f278e54c9de4834f17232f7",
"sha256:e4b9b7af398c32e408c00eb4e0d33ced2f9121fd9fb978e6c1b57edd014a7d15",
"sha256:e613514a82539fc48291d01933951a13ae93b6b444a88782480be32245ed4afa",
"sha256:f5033952def24172e60493b68717792e3aebb387a8d186c43c020d9363ee7281"
],
"version": "==1.14.2"
},
"chardet": {
"hashes": [
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
],
"version": "==3.0.4"
},
"cryptography": {
"hashes": [
"sha256:10c9775a3f31610cf6b694d1fe598f2183441de81cedcf1814451ae53d71b13a",
"sha256:180c9f855a8ea280e72a5d61cf05681b230c2dce804c48e9b2983f491ecc44ed",
"sha256:247df238bc05c7d2e934a761243bfdc67db03f339948b1e2e80c75d41fc7cc36",
"sha256:26409a473cc6278e4c90f782cd5968ebad04d3911ed1c402fc86908c17633e08",
"sha256:2a27615c965173c4c88f2961cf18115c08fedfb8bdc121347f26e8458dc6d237",
"sha256:2e26223ac636ca216e855748e7d435a1bf846809ed12ed898179587d0cf74618",
"sha256:321761d55fb7cb256b771ee4ed78e69486a7336be9143b90c52be59d7657f50f",
"sha256:4005b38cd86fc51c955db40b0f0e52ff65340874495af72efabb1bb8ca881695",
"sha256:4b9e96543d0784acebb70991ebc2dbd99aa287f6217546bb993df22dd361d41c",
"sha256:548b0818e88792318dc137d8b1ec82a0ab0af96c7f0603a00bb94f896fbf5e10",
"sha256:725875681afe50b41aee7fdd629cedbc4720bab350142b12c55c0a4d17c7416c",
"sha256:7a63e97355f3cd77c94bd98c59cb85fe0efd76ea7ef904c9b0316b5bbfde6ed1",
"sha256:94191501e4b4009642be21dde2a78bd3c2701a81ee57d3d3d02f1d99f8b64a9e",
"sha256:969ae512a250f869c1738ca63be843488ff5cc031987d302c1f59c7dbe1b225f",
"sha256:9f734423eb9c2ea85000aa2476e0d7a58e021bc34f0a373ac52a5454cd52f791",
"sha256:b45ab1c6ece7c471f01c56f5d19818ca797c34541f0b2351635a5c9fe09ac2e0",
"sha256:cc6096c86ec0de26e2263c228fb25ee01c3ff1346d3cfc219d67d49f303585af",
"sha256:dc3f437ca6353979aace181f1b790f0fc79e446235b14306241633ab7d61b8f8",
"sha256:e7563eb7bc5c7e75a213281715155248cceba88b11cb4b22957ad45b85903761",
"sha256:e7dad66a9e5684a40f270bd4aee1906878193ae50a4831922e454a2a457f1716",
"sha256:eb80a288e3cfc08f679f95da72d2ef90cb74f6d8a8ba69d2f215c5e110b2ca32",
"sha256:fa7fbcc40e2210aca26c7ac8a39467eae444d90a2c346cbcffd9133a166bcc67"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==3.1"
},
"dataclasses-json": {
"hashes": [
"sha256:56ec931959ede74b5dedf65cf20772e6a79764d20c404794cce0111c88c085ff",
"sha256:b746c48d9d8e884e2a0ffa59c6220a1b21f94d4f9f12c839da0a8a0efd36dc19"
],
"markers": "python_version >= '3.6'",
"version": "==0.5.2"
},
"deepdiff": {
"hashes": [
"sha256:273b18d32bb9b956548290b2e3ddf79c515def2dd5738965f4348ae813e710c5",
"sha256:e2b74af4da0ef9cd338bb6e8c97242c1ec9d81fcb28298d7bb24acdc19ea79d7"
],
"markers": "python_version >= '3.5'",
"version": "==5.0.2"
},
"fuzzywuzzy": {
"hashes": [
"sha256:45016e92264780e58972dca1b3d939ac864b78437422beecebb3095f8efd00e8",
"sha256:928244b28db720d1e0ee7587acf660ea49d7e4c632569cad4f1cd7e68a5f0993"
],
"version": "==0.18.0"
},
"idna": {
"hashes": [
"sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
"sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.10"
},
"ifaddr": {
"hashes": [
"sha256:1f9e8a6ca6f16db5a37d3356f07b6e52344f6f9f7e806d618537731669eb1a94",
"sha256:d1f603952f0a71c9ab4e705754511e4e03b02565bc4cec7188ad6415ff534cd3"
],
"version": "==0.1.7"
},
"jeepney": {
"hashes": [
"sha256:3479b861cc2b6407de5188695fa1a8d57e5072d7059322469b62628869b8e36e",
"sha256:d6c6b49683446d2407d2fe3acb7a368a77ff063f9182fe427da15d622adc24cf"
],
"markers": "sys_platform == 'linux'",
"version": "==0.4.3"
},
"keyring": {
"hashes": [
"sha256:4e34ea2fdec90c1c43d6610b5a5fafa1b9097db1802948e90caf5763974b8f8d",
"sha256:9aeadd006a852b78f4b4ef7c7556c2774d2432bbef8ee538a3e9089ac8b11466"
],
"markers": "python_version >= '3.6'",
"version": "==21.4.0"
},
"marshmallow": {
"hashes": [
"sha256:2272273505f1644580fbc66c6b220cc78f893eb31f1ecde2af98ad28011e9811",
"sha256:47911dd7c641a27160f0df5fd0fe94667160ffe97f70a42c3cc18388d86098cc"
],
"markers": "python_version >= '3.5'",
"version": "==3.8.0"
},
"marshmallow-enum": {
"hashes": [
"sha256:38e697e11f45a8e64b4a1e664000897c659b60aa57bfa18d44e226a9920b6e58",
"sha256:57161ab3dbfde4f57adeb12090f39592e992b9c86d206d02f6bd03ebec60f072"
],
"version": "==1.5.1"
},
"mypy-extensions": {
"hashes": [
"sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d",
"sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"
],
"version": "==0.4.3"
},
"ordered-set": {
"hashes": [
"sha256:ba93b2df055bca202116ec44b9bead3df33ea63a7d5827ff8e16738b97f33a95"
],
"markers": "python_version >= '3.5'",
"version": "==4.0.2"
},
"packaging": {
"hashes": [
"sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8",
"sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==20.4"
},
"peewee": {
"hashes": [
"sha256:1269a9736865512bd4056298003aab190957afe07d2616cf22eaf56cb6398369"
],
"version": "==3.13.3"
},
"protobuf": {
"hashes": [
"sha256:0bba42f439bf45c0f600c3c5993666fcb88e8441d011fad80a11df6f324eef33",
"sha256:1e834076dfef9e585815757a2c7e4560c7ccc5962b9d09f831214c693a91b463",
"sha256:339c3a003e3c797bc84499fa32e0aac83c768e67b3de4a5d7a5a9aa3b0da634c",
"sha256:361acd76f0ad38c6e38f14d08775514fbd241316cce08deb2ce914c7dfa1184a",
"sha256:3dee442884a18c16d023e52e32dd34a8930a889e511af493f6dc7d4d9bf12e4f",
"sha256:4d1174c9ed303070ad59553f435846a2f877598f59f9afc1b89757bdf846f2a7",
"sha256:5db9d3e12b6ede5e601b8d8684a7f9d90581882925c96acf8495957b4f1b204b",
"sha256:6a82e0c8bb2bf58f606040cc5814e07715b2094caeba281e2e7d0b0e2e397db5",
"sha256:8c35bcbed1c0d29b127c886790e9d37e845ffc2725cc1db4bd06d70f4e8359f4",
"sha256:91c2d897da84c62816e2f473ece60ebfeab024a16c1751aaf31100127ccd93ec",
"sha256:9c2e63c1743cba12737169c447374fab3dfeb18111a460a8c1a000e35836b18c",
"sha256:9edfdc679a3669988ec55a989ff62449f670dfa7018df6ad7f04e8dbacb10630",
"sha256:c0c5ab9c4b1eac0a9b838f1e46038c3175a95b0f2d944385884af72876bd6bc7",
"sha256:c8abd7605185836f6f11f97b21200f8a864f9cb078a193fe3c9e235711d3ff1e",
"sha256:d69697acac76d9f250ab745b46c725edf3e98ac24763990b24d58c16c642947a",
"sha256:df3932e1834a64b46ebc262e951cd82c3cf0fa936a154f0a42231140d8237060",
"sha256:e7662437ca1e0c51b93cadb988f9b353fa6b8013c0385d63a70c8a77d84da5f9",
"sha256:f68eb9d03c7d84bd01c790948320b768de8559761897763731294e3bc316decb"
],
"version": "==3.13.0"
},
"pycairo": {
"hashes": [
"sha256:2c143183280feb67f5beb4e543fd49990c28e7df427301ede04fc550d3562e84"
],
"markers": "python_version >= '3.5' and python_version < '4'",
"version": "==1.19.1"
},
"pychromecast": {
"hashes": [
"sha256:182ee414f7de227f2e55d6444bd59d31282bac4d1d63f0f3ca1f19725c5ba4bf",
"sha256:f594231efb34b86eeb463611662bca21a6962793885d3ad68195286940f7473d"
],
"version": "==7.3.0"
},
"pycparser": {
"hashes": [
"sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0",
"sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.20"
},
"pygobject": {
"hashes": [
"sha256:051b950f509f2e9f125add96c1493bde987c527f7a0c15a1f7b69d6d1c3cd8e6"
],
"markers": "python_version >= '3.5' and python_version < '4'",
"version": "==3.38.0"
},
"pyparsing": {
"hashes": [
"sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
"sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.4.7"
},
"python-dateutil": {
"hashes": [
"sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c",
"sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.8.1"
},
"python-levenshtein": {
"hashes": [
"sha256:033a11de5e3d19ea25c9302d11224e1a1898fe5abd23c61c7c360c25195e3eb1"
],
"version": "==0.12.0"
},
"python-mpv": {
"hashes": [
"sha256:10c7ae61eff441602c7188595108391cdede153c15454772d8811c2bcb9e6823"
],
"version": "==0.5.2"
},
"requests": {
"hashes": [
"sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b",
"sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==2.24.0"
},
"secretstorage": {
"hashes": [
"sha256:15da8a989b65498e29be338b3b279965f1b8f09b9668bd8010da183024c8bff6",
"sha256:b5ec909dde94d4ae2fa26af7c089036997030f0cf0a5cb372b4cccabd81c143b"
],
"markers": "sys_platform == 'linux'",
"version": "==3.1.2"
},
"semver": {
"hashes": [
"sha256:21e80ca738975ed513cba859db0a0d2faca2380aef1962f48272ebf9a8a44bd4",
"sha256:c0a4a9d1e45557297a722ee9bac3de2ec2ea79016b6ffcaca609b0bc62cf4276"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.10.2"
},
"six": {
"hashes": [
"sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
"sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.15.0"
},
"stringcase": {
"hashes": [
"sha256:48a06980661908efe8d9d34eab2b6c13aefa2163b3ced26972902e3bdfd87008"
],
"version": "==1.2.0"
},
"sublime-music": {
"editable": true,
"extras": [
"chromecast",
"keyring",
"server"
],
"path": "."
},
"typing-extensions": {
"hashes": [
"sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918",
"sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c",
"sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"
],
"version": "==3.7.4.3"
},
"typing-inspect": {
"hashes": [
"sha256:3b98390df4d999a28cf5b35d8b333425af5da2ece8a4ea9e98f71e7591347b4f",
"sha256:8f1b1dd25908dbfd81d3bebc218011531e7ab614ba6e5bf7826d887c834afab7",
"sha256:de08f50a22955ddec353876df7b2545994d6df08a2f45d54ac8c05e530372ca0"
],
"version": "==0.6.0"
},
"urllib3": {
"editable": true,
"extras": [
"chromecast",
"keyring",
"server"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
"path": "."
},
"webencodings": {
"hashes": [
"sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78",
"sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"
],
"version": "==0.5.1"
},
"zeroconf": {
"hashes": [
"sha256:c08dbb90c116626cb6c5f19ebd14cd4846cffe7151f338c19215e6938d334980",
"sha256:f69cc7f9cc3b2a85c7f2cdafe2bb7fb00cf01604bc3df392f7ed5e3c303d7705"
],
"version": "==0.28.5"
}
},
"develop": {
"alabaster": {
"hashes": [
"sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359",
"sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"
],
"version": "==0.7.12"
},
"appdirs": {
"hashes": [
"sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41",
"sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"
],
"version": "==1.4.4"
},
"attrs": {
"hashes": [
"sha256:26b54ddbbb9ee1d34d5d3668dd37d6cf74990ab23c828c2888dccdceee395594",
"sha256:fce7fc47dfc976152e82d53ff92fa0407700c21acd20886a13777a0d20e655dc"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==20.2.0"
},
"babel": {
"hashes": [
"sha256:1aac2ae2d0d8ea368fa90906567f5c08463d98ade155c0c4bfedd6a0f7160e38",
"sha256:d670ea0b10f8b723672d3a6abeb87b565b244da220d76b4dba1b66269ec152d4"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.8.0"
},
"black": {
"hashes": [
"sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"
],
"index": "pypi",
"version": "==20.8b1"
},
"certifi": {
"hashes": [
"sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3",
"sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41"
],
"version": "==2020.6.20"
},
"chardet": {
"hashes": [
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
],
"version": "==3.0.4"
},
"click": {
"hashes": [
"sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a",
"sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==7.1.2"
},
"coverage": {
"hashes": [
"sha256:0203acd33d2298e19b57451ebb0bed0ab0c602e5cf5a818591b4918b1f97d516",
"sha256:0f313707cdecd5cd3e217fc68c78a960b616604b559e9ea60cc16795c4304259",
"sha256:1c6703094c81fa55b816f5ae542c6ffc625fec769f22b053adb42ad712d086c9",
"sha256:1d44bb3a652fed01f1f2c10d5477956116e9b391320c94d36c6bf13b088a1097",
"sha256:280baa8ec489c4f542f8940f9c4c2181f0306a8ee1a54eceba071a449fb870a0",
"sha256:29a6272fec10623fcbe158fdf9abc7a5fa032048ac1d8631f14b50fbfc10d17f",
"sha256:2b31f46bf7b31e6aa690d4c7a3d51bb262438c6dcb0d528adde446531d0d3bb7",
"sha256:2d43af2be93ffbad25dd959899b5b809618a496926146ce98ee0b23683f8c51c",
"sha256:381ead10b9b9af5f64646cd27107fb27b614ee7040bb1226f9c07ba96625cbb5",
"sha256:47a11bdbd8ada9b7ee628596f9d97fbd3851bd9999d398e9436bd67376dbece7",
"sha256:4d6a42744139a7fa5b46a264874a781e8694bb32f1d76d8137b68138686f1729",
"sha256:50691e744714856f03a86df3e2bff847c2acede4c191f9a1da38f088df342978",
"sha256:530cc8aaf11cc2ac7430f3614b04645662ef20c348dce4167c22d99bec3480e9",
"sha256:582ddfbe712025448206a5bc45855d16c2e491c2dd102ee9a2841418ac1c629f",
"sha256:63808c30b41f3bbf65e29f7280bf793c79f54fb807057de7e5238ffc7cc4d7b9",
"sha256:71b69bd716698fa62cd97137d6f2fdf49f534decb23a2c6fc80813e8b7be6822",
"sha256:7858847f2d84bf6e64c7f66498e851c54de8ea06a6f96a32a1d192d846734418",
"sha256:78e93cc3571fd928a39c0b26767c986188a4118edc67bc0695bc7a284da22e82",
"sha256:7f43286f13d91a34fadf61ae252a51a130223c52bfefb50310d5b2deb062cf0f",
"sha256:86e9f8cd4b0cdd57b4ae71a9c186717daa4c5a99f3238a8723f416256e0b064d",
"sha256:8f264ba2701b8c9f815b272ad568d555ef98dfe1576802ab3149c3629a9f2221",
"sha256:9342dd70a1e151684727c9c91ea003b2fb33523bf19385d4554f7897ca0141d4",
"sha256:9361de40701666b034c59ad9e317bae95c973b9ff92513dd0eced11c6adf2e21",
"sha256:9669179786254a2e7e57f0ecf224e978471491d660aaca833f845b72a2df3709",
"sha256:aac1ba0a253e17889550ddb1b60a2063f7474155465577caa2a3b131224cfd54",
"sha256:aef72eae10b5e3116bac6957de1df4d75909fc76d1499a53fb6387434b6bcd8d",
"sha256:bd3166bb3b111e76a4f8e2980fa1addf2920a4ca9b2b8ca36a3bc3dedc618270",
"sha256:c1b78fb9700fc961f53386ad2fd86d87091e06ede5d118b8a50dea285a071c24",
"sha256:c3888a051226e676e383de03bf49eb633cd39fc829516e5334e69b8d81aae751",
"sha256:c5f17ad25d2c1286436761b462e22b5020d83316f8e8fcb5deb2b3151f8f1d3a",
"sha256:c851b35fc078389bc16b915a0a7c1d5923e12e2c5aeec58c52f4aa8085ac8237",
"sha256:cb7df71de0af56000115eafd000b867d1261f786b5eebd88a0ca6360cccfaca7",
"sha256:cedb2f9e1f990918ea061f28a0f0077a07702e3819602d3507e2ff98c8d20636",
"sha256:e8caf961e1b1a945db76f1b5fa9c91498d15f545ac0ababbe575cfab185d3bd8"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
"version": "==5.3"
},
"docutils": {
"hashes": [
"sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af",
"sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"
],
"index": "pypi",
"version": "==0.16"
},
"flake8": {
"hashes": [
"sha256:15e351d19611c887e482fb960eae4d44845013cc142d42896e9862f775d8cf5c",
"sha256:f04b9fcbac03b0a3e58c0ab3a0ecc462e023a9faf046d57794184028123aa208"
],
"index": "pypi",
"version": "==3.8.3"
},
"flake8-annotations": {
"hashes": [
"sha256:09fe1aa3f40cb8fef632a0ab3614050a7584bb884b6134e70cf1fc9eeee642fa",
"sha256:5bda552f074fd6e34276c7761756fa07d824ffac91ce9c0a8555eb2bc5b92d7a"
],
"index": "pypi",
"version": "==2.4.0"
},
"flake8-bugbear": {
"hashes": [
"sha256:a3ddc03ec28ba2296fc6f89444d1c946a6b76460f859795b35b77d4920a51b63",
"sha256:bd02e4b009fb153fe6072c31c52aeab5b133d508095befb2ffcf3b41c4823162"
],
"index": "pypi",
"version": "==20.1.4"
},
"flake8-comprehensions": {
"hashes": [
"sha256:44eaae9894aa15f86e0c86df1e218e7917494fab6f96d28f96a029c460f17d92",
"sha256:d5751acc0f7364794c71d06f113f4686d6e2e26146a50fa93130b9f200fe160d"
],
"index": "pypi",
"version": "==3.2.3"
},
"flake8-import-order": {
"hashes": [
"sha256:90a80e46886259b9c396b578d75c749801a41ee969a235e163cfe1be7afd2543",
"sha256:a28dc39545ea4606c1ac3c24e9d05c849c6e5444a50fb7e9cdd430fc94de6e92"
],
"index": "pypi",
"version": "==0.18.1"
},
"flake8-pep3101": {
"hashes": [
"sha256:86e3eb4e42de8326dcd98ebdeaf9a3c6854203a48f34aeb3e7e8ed948107f512",
"sha256:a5dae1caca1243b2b40108dce926d97cf5a9f52515c4a4cbb1ffe1ca0c54e343"
],
"index": "pypi",
"version": "==1.3.0"
},
"flake8-print": {
"hashes": [
"sha256:324f9e59a522518daa2461bacd7f82da3c34eb26a4314c2a54bd493f8b394a68"
],
"index": "pypi",
"version": "==3.1.4"
},
"genshi": {
"hashes": [
"sha256:5e92e278ca1ea395349a451d54fc81dc3c1b543c48939a15bd36b7b3335e1560",
"sha256:7933c95151d7dd2124a2b4c8dd85bb6aec881ca17c0556da0b40e56434b313a0"
],
"version": "==0.7.3"
},
"graphviz": {
"hashes": [
"sha256:088562ef6e3dad5e8dde9389caf4a84f768e65dcaa08238cfcdf36d2b30ccf61",
"sha256:f5aad52a652c06825dcc5ee018d920fca26aef339386866094597fb3f2f222ce"
],
"index": "pypi",
"version": "==0.14.1"
},
"idna": {
"hashes": [
"sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
"sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.10"
},
"imagesize": {
"hashes": [
"sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1",
"sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.2.0"
},
"iniconfig": {
"hashes": [
"sha256:80cf40c597eb564e86346103f609d74efce0f6b4d4f30ec8ce9e2c26411ba437",
"sha256:e5f92f89355a67de0595932a6c6c02ab4afddc6fcdc0bfc5becd0d60884d3f69"
],
"version": "==1.0.1"
},
"jinja2": {
"hashes": [
"sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0",
"sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==2.11.2"
},
"lxml": {
"hashes": [
"sha256:05a444b207901a68a6526948c7cc8f9fe6d6f24c70781488e32fd74ff5996e3f",
"sha256:08fc93257dcfe9542c0a6883a25ba4971d78297f63d7a5a26ffa34861ca78730",
"sha256:107781b213cf7201ec3806555657ccda67b1fccc4261fb889ef7fc56976db81f",
"sha256:121b665b04083a1e85ff1f5243d4a93aa1aaba281bc12ea334d5a187278ceaf1",
"sha256:1fa21263c3aba2b76fd7c45713d4428dbcc7644d73dcf0650e9d344e433741b3",
"sha256:2b30aa2bcff8e958cd85d907d5109820b01ac511eae5b460803430a7404e34d7",
"sha256:4b4a111bcf4b9c948e020fd207f915c24a6de3f1adc7682a2d92660eb4e84f1a",
"sha256:5591c4164755778e29e69b86e425880f852464a21c7bb53c7ea453bbe2633bbe",
"sha256:59daa84aef650b11bccd18f99f64bfe44b9f14a08a28259959d33676554065a1",
"sha256:5a9c8d11aa2c8f8b6043d845927a51eb9102eb558e3f936df494e96393f5fd3e",
"sha256:5dd20538a60c4cc9a077d3b715bb42307239fcd25ef1ca7286775f95e9e9a46d",
"sha256:74f48ec98430e06c1fa8949b49ebdd8d27ceb9df8d3d1c92e1fdc2773f003f20",
"sha256:786aad2aa20de3dbff21aab86b2fb6a7be68064cbbc0219bde414d3a30aa47ae",
"sha256:7ad7906e098ccd30d8f7068030a0b16668ab8aa5cda6fcd5146d8d20cbaa71b5",
"sha256:80a38b188d20c0524fe8959c8ce770a8fdf0e617c6912d23fc97c68301bb9aba",
"sha256:8f0ec6b9b3832e0bd1d57af41f9238ea7709bbd7271f639024f2fc9d3bb01293",
"sha256:92282c83547a9add85ad658143c76a64a8d339028926d7dc1998ca029c88ea6a",
"sha256:94150231f1e90c9595ccc80d7d2006c61f90a5995db82bccbca7944fd457f0f6",
"sha256:9dc9006dcc47e00a8a6a029eb035c8f696ad38e40a27d073a003d7d1443f5d88",
"sha256:a76979f728dd845655026ab991df25d26379a1a8fc1e9e68e25c7eda43004bed",
"sha256:aa8eba3db3d8761db161003e2d0586608092e217151d7458206e243be5a43843",
"sha256:bea760a63ce9bba566c23f726d72b3c0250e2fa2569909e2d83cda1534c79443",
"sha256:c3f511a3c58676147c277eff0224c061dd5a6a8e1373572ac817ac6324f1b1e0",
"sha256:c9d317efde4bafbc1561509bfa8a23c5cab66c44d49ab5b63ff690f5159b2304",
"sha256:cc411ad324a4486b142c41d9b2b6a722c534096963688d879ea6fa8a35028258",
"sha256:cdc13a1682b2a6241080745b1953719e7fe0850b40a5c71ca574f090a1391df6",
"sha256:cfd7c5dd3c35c19cec59c63df9571c67c6d6e5c92e0fe63517920e97f61106d1",
"sha256:e1cacf4796b20865789083252186ce9dc6cc59eca0c2e79cca332bdff24ac481",
"sha256:e70d4e467e243455492f5de463b72151cc400710ac03a0678206a5f27e79ddef",
"sha256:ecc930ae559ea8a43377e8b60ca6f8d61ac532fc57efb915d899de4a67928efd",
"sha256:f161af26f596131b63b236372e4ce40f3167c1b5b5d459b29d2514bd8c9dc9ee"
],
"index": "pypi",
"version": "==4.5.2"
},
"markupsafe": {
"hashes": [
"sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473",
"sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161",
"sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235",
"sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5",
"sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42",
"sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff",
"sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b",
"sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1",
"sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e",
"sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183",
"sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66",
"sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b",
"sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1",
"sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15",
"sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1",
"sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e",
"sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b",
"sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905",
"sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735",
"sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d",
"sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e",
"sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d",
"sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c",
"sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21",
"sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2",
"sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5",
"sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b",
"sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6",
"sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f",
"sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f",
"sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2",
"sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7",
"sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.1.1"
},
"mccabe": {
"hashes": [
"sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
"sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
],
"version": "==0.6.1"
},
"more-itertools": {
"hashes": [
"sha256:6f83822ae94818eae2612063a5101a7311e68ae8002005b5e05f03fd74a86a20",
"sha256:9b30f12df9393f0d28af9210ff8efe48d10c94f73e5daf886f10c4b0b0b4f03c"
],
"markers": "python_version >= '3.5'",
"version": "==8.5.0"
},
"mypy": {
"hashes": [
"sha256:2c6cde8aa3426c1682d35190b59b71f661237d74b053822ea3d748e2c9578a7c",
"sha256:3fdda71c067d3ddfb21da4b80e2686b71e9e5c72cca65fa216d207a358827f86",
"sha256:5dd13ff1f2a97f94540fd37a49e5d255950ebcdf446fb597463a40d0df3fac8b",
"sha256:6731603dfe0ce4352c555c6284c6db0dc935b685e9ce2e4cf220abe1e14386fd",
"sha256:6bb93479caa6619d21d6e7160c552c1193f6952f0668cdda2f851156e85186fc",
"sha256:81c7908b94239c4010e16642c9102bfc958ab14e36048fa77d0be3289dda76ea",
"sha256:9c7a9a7ceb2871ba4bac1cf7217a7dd9ccd44c27c2950edbc6dc08530f32ad4e",
"sha256:a4a2cbcfc4cbf45cd126f531dedda8485671545b43107ded25ce952aac6fb308",
"sha256:b7fbfabdbcc78c4f6fc4712544b9b0d6bf171069c6e0e3cb82440dd10ced3406",
"sha256:c05b9e4fb1d8a41d41dec8786c94f3b95d3c5f528298d769eb8e73d293abc48d",
"sha256:d7df6eddb6054d21ca4d3c6249cae5578cb4602951fd2b6ee2f5510ffb098707",
"sha256:e0b61738ab504e656d1fe4ff0c0601387a5489ca122d55390ade31f9ca0e252d",
"sha256:eff7d4a85e9eea55afa34888dfeaccde99e7520b51f867ac28a48492c0b1130c",
"sha256:f05644db6779387ccdb468cc47a44b4356fc2ffa9287135d05b70a98dc83b89a"
],
"index": "pypi",
"version": "==0.782"
},
"mypy-extensions": {
"hashes": [
"sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d",
"sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"
],
"version": "==0.4.3"
},
"packaging": {
"hashes": [
"sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8",
"sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==20.4"
},
"pathspec": {
"hashes": [
"sha256:7d91249d21749788d07a2d0f94147accd8f845507400749ea19c1ec9054a12b0",
"sha256:da45173eb3a6f2a5a487efba21f050af2b41948be6ab52b6a1e3ff22bb8b7061"
],
"version": "==0.8.0"
},
"pluggy": {
"hashes": [
"sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0",
"sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.13.1"
},
"py": {
"hashes": [
"sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2",
"sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.9.0"
},
"pycodestyle": {
"hashes": [
"sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367",
"sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"
],
"index": "pypi",
"version": "==2.6.0"
},
"pyflakes": {
"hashes": [
"sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92",
"sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.2.0"
},
"pygments": {
"hashes": [
"sha256:307543fe65c0947b126e83dd5a61bd8acbd84abec11f43caebaf5534cbc17998",
"sha256:926c3f319eda178d1bd90851e4317e6d8cdb5e292a3386aac9bd75eca29cf9c7"
],
"markers": "python_version >= '3.5'",
"version": "==2.7.1"
},
"pyparsing": {
"hashes": [
"sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
"sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.4.7"
},
"pytest": {
"hashes": [
"sha256:0e37f61339c4578776e090c3b8f6b16ce4db333889d65d0efb305243ec544b40",
"sha256:c8f57c2a30983f469bf03e68cdfa74dc474ce56b8f280ddcb080dfd91df01043"
],
"index": "pypi",
"version": "==6.0.2"
},
"pytest-cov": {
"hashes": [
"sha256:45ec2d5182f89a81fc3eb29e3d1ed3113b9e9a873bcddb2a71faaab066110191",
"sha256:47bd0ce14056fdd79f93e1713f88fad7bdcc583dcd7783da86ef2f085a0bb88e"
],
"index": "pypi",
"version": "==2.10.1"
},
"pytz": {
"hashes": [
"sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed",
"sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048"
],
"version": "==2020.1"
},
"regex": {
"hashes": [
"sha256:0dc64ee3f33cd7899f79a8d788abfbec168410be356ed9bd30bbd3f0a23a7204",
"sha256:1269fef3167bb52631ad4fa7dd27bf635d5a0790b8e6222065d42e91bede4162",
"sha256:14a53646369157baa0499513f96091eb70382eb50b2c82393d17d7ec81b7b85f",
"sha256:3a3af27a8d23143c49a3420efe5b3f8cf1a48c6fc8bc6856b03f638abc1833bb",
"sha256:46bac5ca10fb748d6c55843a931855e2727a7a22584f302dd9bb1506e69f83f6",
"sha256:4c037fd14c5f4e308b8370b447b469ca10e69427966527edcab07f52d88388f7",
"sha256:51178c738d559a2d1071ce0b0f56e57eb315bcf8f7d4cf127674b533e3101f88",
"sha256:5ea81ea3dbd6767873c611687141ec7b06ed8bab43f68fad5b7be184a920dc99",
"sha256:6961548bba529cac7c07af2fd4d527c5b91bb8fe18995fed6044ac22b3d14644",
"sha256:75aaa27aa521a182824d89e5ab0a1d16ca207318a6b65042b046053cfc8ed07a",
"sha256:7a2dd66d2d4df34fa82c9dc85657c5e019b87932019947faece7983f2089a840",
"sha256:8a51f2c6d1f884e98846a0a9021ff6861bdb98457879f412fdc2b42d14494067",
"sha256:9c568495e35599625f7b999774e29e8d6b01a6fb684d77dee1f56d41b11b40cd",
"sha256:9eddaafb3c48e0900690c1727fba226c4804b8e6127ea409689c3bb492d06de4",
"sha256:bbb332d45b32df41200380fff14712cb6093b61bd142272a10b16778c418e98e",
"sha256:bc3d98f621898b4a9bc7fecc00513eec8f40b5b83913d74ccb445f037d58cd89",
"sha256:c11d6033115dc4887c456565303f540c44197f4fc1a2bfb192224a301534888e",
"sha256:c50a724d136ec10d920661f1442e4a8b010a4fe5aebd65e0c2241ea41dbe93dc",
"sha256:d0a5095d52b90ff38592bbdc2644f17c6d495762edf47d876049cfd2968fbccf",
"sha256:d6cff2276e502b86a25fd10c2a96973fdb45c7a977dca2138d661417f3728341",
"sha256:e46d13f38cfcbb79bfdb2964b0fe12561fe633caf964a77a5f8d4e45fe5d2ef7"
],
"version": "==2020.7.14"
},
"requests": {
"hashes": [
"sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b",
"sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==2.24.0"
},
"rope": {
"hashes": [
"sha256:658ad6705f43dcf3d6df379da9486529cf30e02d9ea14c5682aa80eb33b649e1"
],
"index": "pypi",
"version": "==0.17.0"
},
"rst2html5": {
"hashes": [
"sha256:8e64e80c52d0383aa04f2701a7627c2f2a09d59c61a3dc3555d845333bc31648"
],
"index": "pypi",
"version": "==1.10.6"
},
"six": {
"hashes": [
"sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
"sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.15.0"
},
"snowballstemmer": {
"hashes": [
"sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0",
"sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"
],
"version": "==2.0.0"
},
"sphinx": {
"hashes": [
"sha256:321d6d9b16fa381a5306e5a0b76cd48ffbc588e6340059a729c6fdd66087e0e8",
"sha256:ce6fd7ff5b215af39e2fcd44d4a321f6694b4530b6f2b2109b64d120773faea0"
],
"index": "pypi",
"version": "==3.2.1"
},
"sphinx-rtd-theme": {
"hashes": [
"sha256:22c795ba2832a169ca301cd0a083f7a434e09c538c70beb42782c073651b707d",
"sha256:373413d0f82425aaa28fb288009bf0d0964711d347763af2f1b65cafcb028c82"
],
"index": "pypi",
"version": "==0.5.0"
},
"sphinxcontrib-applehelp": {
"hashes": [
"sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a",
"sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"
],
"markers": "python_version >= '3.5'",
"version": "==1.0.2"
},
"sphinxcontrib-devhelp": {
"hashes": [
"sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e",
"sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"
],
"markers": "python_version >= '3.5'",
"version": "==1.0.2"
},
"sphinxcontrib-htmlhelp": {
"hashes": [
"sha256:3c0bc24a2c41e340ac37c85ced6dafc879ab485c095b1d65d2461ac2f7cca86f",
"sha256:e8f5bb7e31b2dbb25b9cc435c8ab7a79787ebf7f906155729338f3156d93659b"
],
"markers": "python_version >= '3.5'",
"version": "==1.0.3"
},
"sphinxcontrib-jsmath": {
"hashes": [
"sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178",
"sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"
],
"markers": "python_version >= '3.5'",
"version": "==1.0.1"
},
"sphinxcontrib-qthelp": {
"hashes": [
"sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72",
"sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"
],
"markers": "python_version >= '3.5'",
"version": "==1.0.3"
},
"sphinxcontrib-serializinghtml": {
"hashes": [
"sha256:eaa0eccc86e982a9b939b2b82d12cc5d013385ba5eadcc7e4fed23f4405f77bc",
"sha256:f242a81d423f59617a8e5cf16f5d4d74e28ee9a66f9e5b637a18082991db5a9a"
],
"markers": "python_version >= '3.5'",
"version": "==1.1.4"
},
"termcolor": {
"hashes": [
"sha256:1d6d69ce66211143803fbc56652b41d73b4a400a2891d7bf7a1cdf4c02de613b"
],
"index": "pypi",
"version": "==1.1.0"
},
"toml": {
"hashes": [
"sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f",
"sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88"
],
"version": "==0.10.1"
},
"typed-ast": {
"hashes": [
"sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355",
"sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919",
"sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa",
"sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652",
"sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75",
"sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01",
"sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d",
"sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1",
"sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907",
"sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c",
"sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3",
"sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b",
"sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614",
"sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb",
"sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b",
"sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41",
"sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6",
"sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34",
"sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe",
"sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4",
"sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"
],
"version": "==1.4.1"
},
"typing-extensions": {
"hashes": [
"sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918",
"sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c",
"sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"
],
"version": "==3.7.4.3"
},
"urllib3": {
"editable": true,
"extras": [
"chromecast",
"keyring",
"server"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
"path": "."
}
}
}

View File

@@ -47,7 +47,7 @@ def check_file(path: Path) -> bool:
valid = True
for path in Path("sublime").glob("**/*.py"):
for path in Path("sublime_music").glob("**/*.py"):
valid &= check_file(path)
for path in Path("tests").glob("**/*.py"):
@@ -56,7 +56,7 @@ for path in Path("tests").glob("**/*.py"):
"""
Checks that the version in the CHANGELOG is the same as the version in ``__init__.py``.
"""
with open(Path("sublime/__init__.py")) as f:
with open(Path("sublime_music/__init__.py")) as f:
for line in f:
if line.startswith("__version__"):
version = eval(line.split()[-1])

View File

@@ -11,5 +11,7 @@ RUN dnf -y update && \
RUN flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
RUN flatpak install -y org.gnome.Platform//3.34 && \
flatpak install -y org.gnome.Sdk//3.34
RUN flatpak install -y org.gnome.Platform//3.38 && \
flatpak install -y org.gnome.Sdk//3.38
RUN pip3 install requirements-parser

View File

@@ -3,5 +3,10 @@ export PYENV_ROOT="${HOME}/.pyenv"
export PATH="${PYENV_ROOT}/bin:$PATH"
eval "$(pyenv init -)"
export PIPENV_VENV_IN_PROJECT=1
pipenv install --dev
apt-get install -y python3-venv
pip3 install poetry
mkdir -p ~/.config/pypoetry/
echo "[virtualenvs]" > ~/.config/pypoetry/config.toml
echo "in-project = true" >> ~/.config/pypoetry/config.toml
poetry install

View File

@@ -3,7 +3,7 @@
set -xe
pushd docs
pipenv run make html
poetry run make html
popd
mv docs/_build/html public

View File

@@ -40,7 +40,7 @@ RUN apt update && \
RUN cd /usr/local/src && \
curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash
RUN pip3 install pipenv
RUN pip3 install poetry
# Install the correct Python version with pyenv
COPY install-python.sh /tmp/install-python.sh

32
cicd/run_if_tagged_with_version Executable file
View File

@@ -0,0 +1,32 @@
#! /usr/bin/env python3
import re
import subprocess
import sys
version_tag_re = re.compile(r"v\d+\.\d+\.\d+")
tags = (
subprocess.run(["git", "tag", "--contains", "HEAD"], capture_output=True)
.stdout.decode()
.strip()
.split()
)
# If one of the tags is a version tag, then run the commands specified in the
# parameters.
for tag in tags:
if match := version_tag_re.match(tag):
print(f"VERSION TAG {tag} FOUND")
# Execute the associated commands, raising an exception if the command
# returns a non-zero value.
for arg in sys.argv[1:]:
print(f"+ {' '.join(arg.split())}")
subprocess.run(arg.split()).check_returncode()
# Don't run the else statement of the for loop.
break
else:
print("No version tag found for this commit. Skipping")

View File

@@ -11,7 +11,7 @@ BUILDDIR = _build
SPHINXDOCBUILD = sphinx-apidoc
SPHINXDOCOPTS = -f -e -T
DOCSSOURCE = ./api/
PROJECTDIR = ../sublime/
PROJECTDIR = ../sublime_music/
# Put it first so that "make" without argument is like "make help".
help:

View File

@@ -8,8 +8,8 @@ server, data on the local filesystem, or even an entirely different service.
This document is designed to help you understand the Adapter API so that you can
create your own custom adapters. This document is best read in conjunction with
the :class:`sublime.adapters.Adapter` documentation. This document is meant as a
guide to tell you a general order in which to implement things.
the :class:`sublime_music.adapters.Adapter` documentation. This document is
meant as a guide to tell you a general order in which to implement things.
Terms
=====
@@ -35,8 +35,8 @@ Creating Your Adapter Class
An adapter is composed of a single Python module. The adapter module can have
arbitrary code, and as many files/classes/functions/etc. as necessary, however
there must be one and only one class in the module which inherits from the
:class:`sublime.adapters.Adapter` class. Normally, a single file with a single
class should be enough to implement the entire adapter.
:class:`sublime_music.adapters.Adapter` class. Normally, a single file with a
single class should be enough to implement the entire adapter.
.. warning::
@@ -49,16 +49,16 @@ class should be enough to implement the entire adapter.
After you've created the class, you will want to implement the following
functions and properties first:
* ``get_ui_info``: Returns a :class:`sublime.adapters.UIInfo` with the info for
the adapter.
* ``get_ui_info``: Returns a :class:`sublime_music.adapters.UIInfo` with the
info for the adapter.
* ``__init__``: Used to initialize your adapter. See the
:class:`sublime.adapters.Adapter.__init__` documentation for the function
signature of the ``__init__`` function.
:class:`sublime_music.adapters.Adapter.__init__` documentation for the
function signature of the ``__init__`` function.
* ``ping_status``: Assuming that your adapter requires connection to the
internet, this property needs to be implemented. (If your adapter doesn't
require connection to the internet, set
:class:`sublime.adapters.Adapter.is_networked` to ``False`` and ignore the
rest of this bullet point.)
:class:`sublime_music.adapters.Adapter.is_networked` to ``False`` and ignore
the rest of this bullet point.)
This property will tell the UI whether or not the underlying server can be
pinged.
@@ -80,12 +80,12 @@ functions and properties first:
If you don't want to implement all of the GTK logic yourself, and just want a
simple form, then you can use the
:class:`sublime.adapters.ConfigureServerForm` class to generate a form in a
declarative manner.
:class:`sublime_music.adapters.ConfigureServerForm` class to generate a form
in a declarative manner.
.. note::
The :class:`sublime.adapters.Adapter` class is an `Abstract Base Class
The :class:`sublime_music.adapters.Adapter` class is an `Abstract Base Class
<abc_>`_ and all required functions are annotated with the
``@abstractmethod`` decorator. This means that your adapter will fail to
instantiate if the abstract methods are not implemented.
@@ -102,21 +102,21 @@ must do the following:
name must be unique within your adapter.
2. Add a new entry to the return value of your
:class:`sublime.adapters.Adapter.get_config_parameters` function with the key
being the name from (1), and the value being a
:class:`sublime.adapters.ConfigParamDescriptor`. The order of the keys in the
dictionary matters, since the UI uses that to determine the order in which
the configuration parameters will be shown in the UI.
:class:`sublime_music.adapters.Adapter.get_config_parameters` function with
the key being the name from (1), and the value being a
:class:`sublime_music.adapters.ConfigParamDescriptor`. The order of the keys
in the dictionary matters, since the UI uses that to determine the order in
which the configuration parameters will be shown in the UI.
3. Add any verifications that are necessary for your configuration parameter in
your :class:`sublime.adapters.Adapter.verify_configuration` function. If you
parameter descriptor has ``required = True``, then that parameter is
your :class:`sublime_music.adapters.Adapter.verify_configuration` function.
If you parameter descriptor has ``required = True``, then that parameter is
guaranteed to appear in the configuration.
4. The configuration parameter will be passed into your
:class:`sublime.adapters.Adapter.init` function. It is guaranteed that the
``verify_configuration`` will have been called first, so there is no need to
re-verify the config that is passed.
:class:`sublime_music.adapters.Adapter.init` function. It is guaranteed that
the ``verify_configuration`` will have been called first, so there is no need
to re-verify the config that is passed.
Implementing Data Retrieval Methods
-----------------------------------

View File

@@ -26,13 +26,13 @@ author = "Sumner Evans"
gitlab_url = "https://gitlab.com/sublime-music/sublime-music/"
# Get the version from the package.
module_name = "sublime"
init_file = Path(__file__).parent.parent.joinpath("sublime/__init__.py").resolve()
module_name = "sublime_music"
init_file = Path(__file__).parent.parent.joinpath("sublime_music/__init__.py").resolve()
spec = importlib.util.spec_from_file_location(module_name, str(init_file))
sublime = importlib.util.module_from_spec(spec)
spec.loader.exec_module(sublime)
sublime_music = importlib.util.module_from_spec(spec)
spec.loader.exec_module(sublime_music)
version = release = f"v{sublime.__version__}"
version = release = f"v{sublime_music.__version__}"
# -- General configuration ---------------------------------------------------

View File

@@ -1,7 +1,7 @@
{
"app-id": "app.sublimemusic.SublimeMusic",
"runtime": "org.gnome.Platform",
"runtime-version": "3.34",
"runtime-version": "3.38",
"sdk": "org.gnome.Sdk",
"command": "sublime-music",
"rename-icon": "sublime-music",
@@ -20,11 +20,12 @@
"--socket=wayland",
"--share=network",
"--socket=pulseaudio",
"--socket=session-bus",
"--talk-name=org.freedesktop.Notifications",
"--own-name=org.mpris.MediaPlayer2.SublimeMusic",
"--filesystem=xdg-config/sublime-music",
"--filesystem=xdg-cache/sublime-music",
"--filesystem=xdg-data/sublime-music"
"--filesystem=xdg-data/sublime-music",
"--env=TMPDIR=/tmp"
],
"modules": [
{
@@ -66,6 +67,9 @@
"build-commands": [
"pip3 install --prefix=/app ."
],
"build-options": {
"build-args": [ "--share=network" ]
},
"sources": [
{
"type": "dir",
@@ -101,51 +105,6 @@
}
],
"modules": [
{
"name": "python-3.8",
"sources": [
{
"type": "archive",
"url": "https://www.python.org/ftp/python/3.8.3/Python-3.8.3.tar.xz",
"sha256": "dfab5ec723c218082fe3d5d7ae17ecbdebffa9a1aea4d64aa3a2ecdd2e795864"
}
],
"config-opts": [
"--enable-shared",
"--with-ensurepip=yes",
"--with-system-expat",
"--with-system-ffi",
"--enable-loadable-sqlite-extensions",
"--with-dbmliborder=gdbm",
"--enable-unicode=ucs4"
],
"post-install": [
"chmod 644 $FLATPAK_DEST/lib/libpython3.8.so.1.0"
],
"cleanup": [
"/bin/2to3*",
"/bin/easy_install*",
"/bin/idle*",
"/bin/pydoc*",
"/bin/python*-config",
"/bin/pyvenv*",
"/include",
"/lib/pkgconfig",
"/lib/python*/config",
"/share",
"/lib/python*/test",
"/lib/python*/*/test",
"/lib/python*/*/tests",
"/lib/python*/lib-tk/test",
"/lib/python*/lib-dynload/_*_test.*.so",
"/lib/python*/lib-dynload/_test*.*.so",
"/lib/python*/idlelib",
"/lib/python*/tkinter*",
"/lib/python*/turtle*",
"/lib/python*/lib2to3*",
"/lib/python3.8/config/libpython3.8.a"
]
},
{
"name": "luajit",
"no-autogen": true,

View File

@@ -1,25 +1,32 @@
bleach==3.2.1
bottle==0.12.18
casttube==0.2.1
certifi==2020.4.5.1
certifi==2020.6.20
chardet==3.0.4
dataclasses-json==0.4.4
deepdiff==4.3.2
dataclasses-json==0.5.
deepdiff==5.0.2
fuzzywuzzy==0.18.0
idna==2.9
ifaddr==0.1.6
idna==2.10
ifaddr==0.1.7
jeepney==0.4.3
marshmallow-enum==1.5.1
marshmallow==3.6.0
marshmallow==3.8.0
mypy-extensions==0.4.3
ordered-set==4.0.1
ordered-set==4.0.2
packaging==20.4
peewee==3.13.3
protobuf==3.12.2
pychromecast==5.3.0
protobuf==3.13.0
pycairo==1.19.1
pychromecast==7.3.0
pycparser==2.20
pyparsing==2.4.7
python-dateutil==2.8.1
python-levenshtein==0.12.0
python-mpv==0.4.6
requests==2.23.0
python-mpv==0.5.2
requests==2.24.0
semver==2.10.2
six==1.15.0
stringcase==1.2.0
typing-extensions==3.7.4.2
typing-inspect==0.6.0
urllib3==1.25.9
urllib3==1.25.10
zeroconf==0.28.5

View File

@@ -5,6 +5,11 @@ set -xe
REPO=${REPO:-/repo}
APPID=app.sublimemusic.SublimeMusic
# TODO move these to the Docker container
pip3 install requirements-parser
flatpak install -y org.gnome.Platform//3.38
flatpak install -y org.gnome.Sdk//3.38
rm -rf flatpak-builder-tools
git clone https://github.com/flatpak/flatpak-builder-tools.git

1214
poetry.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +1,73 @@
[tool.poetry]
name = "sublime_music"
version = "0.11.9"
description = "A native GTK *sonic client."
license = "GPL-3.0-or-later"
authors = ["Sumner Evans <inquiries@sumnerevans.com>"]
readme = "README.rst"
homepage = "https://sublimemusic.app"
repository = "https://sr.ht/~sumner/sublime-music"
documentation = "https://sublime-music.gitlab.io/sublime-music/"
keywords = ["airsonic", "music", "chromecast", "subsonic"]
classifiers = [
# 3 - Alpha
# 4 - Beta
# 5 - Production/Stable
"Development Status :: 3 - Alpha",
"Intended Audience :: End Users/Desktop",
"Operating System :: POSIX",
]
# TODO
exclude = ["tests"]
[tool.poetry.urls]
"Bug Tracker" = "https://todo.sr.ht/~sumner/sublime-music"
[tool.poetry.scripts]
sublime-music = 'sublime_music.__main__:main'
[tool.poetry.dependencies]
python = "^3.8"
bleach = "^3.2.1"
dataclasses-json = "^0.5.2"
deepdiff = "^5.0.2"
fuzzywuzzy = "^0.18.0"
peewee = "^3.13.3"
PyGObject = "^3.38.0"
python-dateutil = "^2.8.1"
python-Levenshtein = "^0.12.0"
python-mpv = "^0.5.2"
requests = "^2.24.0"
semver = "^2.10.2"
bottle = {version = "^0.12.18", optional = true}
keyring = {version = "^21.4.0", optional = true}
pychromecast = {version = "^7.3.0", optional = true}
[tool.poetry.dev-dependencies]
black = "^20.8b1"
docutils = "^0.16"
flake8 = "^3.8.3"
flake8-annotations = "^2.4.0"
flake8-bugbear = "^20.1.4"
flake8-comprehensions = "^3.2.3"
flake8-import-order = "^0.18.1"
flake8-pep3101 = "^1.3.0"
flake8-print = "^3.1.4"
mypy = "^0.782"
pytest-cov = "^2.10.1"
termcolor = "^1.1.0"
requirements-parser = "^0.2.0"
[tool.poetry.extras]
chromecast = ["pychromecast"]
keyring = ["keyring"]
server = ["bottle"]
[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"
[tool.black]
exclude = '''
(

View File

@@ -4,7 +4,7 @@ exclude = .git,__pycache__,build,dist,flatpak,.venv
max-line-length = 88
suppress-none-returning = True
suppress-dummy-args = True
application-import-names = sublime
application-import-names = sublime_music
import-order-style = edited
[mypy-bottle]
@@ -58,7 +58,7 @@ addopts =
--doctest-modules
--ignore-glob='flatpak'
--ignore-glob='cicd'
--cov=sublime
--cov=sublime_music
--cov-report html
--cov-report term
--no-cov-on-fail

View File

@@ -8,19 +8,19 @@ with open(here.joinpath("README.rst"), encoding="utf-8") as f:
long_description = f.read()
# Find the version
with open(here.joinpath("sublime", "__init__.py")) as f:
with open(here.joinpath("sublime_music", "__init__.py")) as f:
for line in f:
if line.startswith("__version__"):
version = eval(line.split()[-1])
break
package_data_dirs = [
here.joinpath("sublime", "adapters", "icons"),
here.joinpath("sublime", "adapters", "images"),
here.joinpath("sublime", "adapters", "subsonic", "icons"),
here.joinpath("sublime", "dbus", "mpris_specs"),
here.joinpath("sublime", "ui", "icons"),
here.joinpath("sublime", "ui", "images"),
here.joinpath("sublime_music", "adapters", "icons"),
here.joinpath("sublime_music", "adapters", "images"),
here.joinpath("sublime_music", "adapters", "subsonic", "icons"),
here.joinpath("sublime_music", "dbus", "mpris_specs"),
here.joinpath("sublime_music", "ui", "icons"),
here.joinpath("sublime_music", "ui", "images"),
]
package_data_files = []
for data_dir in package_data_dirs:
@@ -28,7 +28,7 @@ for data_dir in package_data_dirs:
package_data_files.append(str(file))
setup(
name="sublime-music",
name="sublime_music",
version=version,
url="https://gitlab.com/sublime-music/sublime-music",
description="A native GTK *sonic client.",
@@ -51,7 +51,7 @@ setup(
],
keywords="airsonic subsonic libresonic gonic music",
packages=find_packages(exclude=["tests"]),
package_data={"sublime": ["ui/app_styles.css", *package_data_files]},
package_data={"sublime_music": ["ui/app_styles.css", *package_data_files]},
install_requires=[
"bleach",
"dataclasses-json",
@@ -75,5 +75,5 @@ setup(
# "scripts" keyword. Entry points provide cross-platform support and
# allow pip to create the appropriate form of executable for the target
# platform.
entry_points={"console_scripts": ["sublime-music=sublime.__main__:main"]},
entry_points={"console_scripts": ["sublime-music=sublime_music.__main__:main"]},
)

View File

@@ -89,7 +89,8 @@
<update_contact>me_AT_sumnerevans.com</update_contact>
<releases>
<release version="0.10.2" date="2020-06-07"></release>
<release version="0.11.9" date="2020-09-22"></release>
<release version="0.10.3" date="2020-06-07"></release>
<release version="0.10.2" date="2020-06-07"></release>
</releases>
</component>

View File

@@ -9,8 +9,9 @@ import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk # noqa: F401
import sublime
from sublime.app import SublimeMusicApp
import sublime_music
from .app import SublimeMusicApp
def main():
@@ -33,7 +34,7 @@ def main():
args, unknown_args = parser.parse_known_args()
if args.version:
print(f"Sublime Music v{sublime.__version__}") # noqa: T001
print(f"Sublime Music v{sublime_music.__version__}") # noqa: T001
return
min_log_level = getattr(logging, args.loglevel.upper(), None)

View File

@@ -569,33 +569,34 @@ class Adapter(abc.ABC):
"""
Get a list of all of the playlists known by the adapter.
:returns: A list of all of the :class:`sublime.adapter.api_objects.Playlist`
objects known to the adapter.
:returns: A list of all of the
:class:`sublime_music.adapter.api_objects.Playlist` objects known to the
adapter.
"""
raise self._check_can_error("get_playlists")
def get_playlist_details(self, playlist_id: str,) -> Playlist:
def get_playlist_details(self, playlist_id: str) -> Playlist:
"""
Get the details for the given ``playlist_id``. If the playlist_id does not
exist, then this function should throw an exception.
:param playlist_id: The ID of the playlist to retrieve.
:returns: A :class:`sublime.adapter.api_objects.Play` object for the given
:returns: A :class:`sublime_music.adapter.api_objects.Play` object for the given
playlist.
"""
raise self._check_can_error("get_playlist_details")
def create_playlist(
self, name: str, songs: Sequence[Song] = None,
self, name: str, songs: Sequence[Song] = None
) -> Optional[Playlist]:
"""
Creates a playlist of the given name with the given songs.
:param name: The human-readable name of the playlist.
:param songs: A list of songs that should be included in the playlist.
:returns: A :class:`sublime.adapter.api_objects.Playlist` object for the created
playlist. If getting this information will incurr network overhead, then
just return ``None``.
:returns: A :class:`sublime_music.adapter.api_objects.Playlist` object for the
created playlist. If getting this information will incurr network overhead,
then just return ``None``.
"""
raise self._check_can_error("create_playlist")
@@ -618,8 +619,8 @@ class Adapter(abc.ABC):
shared/public vs. not shared/private playlists concept, setting this to
``True`` will make the playlist shared/public.
:param song_ids: A list of song IDs that should be included in the playlist.
:returns: A :class:`sublime.adapter.api_objects.Playlist` object for the updated
playlist.
:returns: A :class:`sublime_music.adapter.api_objects.Playlist` object for the
updated playlist.
"""
raise self._check_can_error("update_playlist")
@@ -671,7 +672,7 @@ class Adapter(abc.ABC):
Get the details for a given song ID.
:param song_id: The ID of the song to get the details for.
:returns: The :class:`sublime.adapters.api_objects.Song`.
:returns: The :class:`sublime_music.adapters.api_objects.Song`.
"""
raise self._check_can_error("get_song_details")
@@ -679,7 +680,7 @@ class Adapter(abc.ABC):
"""
Scrobble the given song.
:params song: The :class:`sublime.adapters.api_objects.Song` to scrobble.
:params song: The :class:`sublime_music.adapters.api_objects.Song` to scrobble.
"""
raise self._check_can_error("scrobble_song")
@@ -687,7 +688,7 @@ class Adapter(abc.ABC):
"""
Get a list of all of the artists known to the adapter.
:returns: A list of all of the :class:`sublime.adapter.api_objects.Artist`
:returns: A list of all of the :class:`sublime_music.adapter.api_objects.Artist`
objects known to the adapter.
"""
raise self._check_can_error("get_artists")
@@ -697,7 +698,7 @@ class Adapter(abc.ABC):
Get the details for the given artist ID.
:param artist_id: The ID of the artist to get the details for.
:returns: The :classs`sublime.adapters.api_objects.Artist`
:returns: The :classs`sublime_music.adapters.api_objects.Artist`
"""
raise self._check_can_error("get_artist")
@@ -723,7 +724,7 @@ class Adapter(abc.ABC):
:param query: An :class:`AlbumSearchQuery` object representing the types of
albums to return.
:returns: A list of all of the :class:`sublime.adapter.api_objects.Album`
:returns: A list of all of the :class:`sublime_music.adapter.api_objects.Album`
objects known to the adapter that match the query.
"""
raise self._check_can_error("get_albums")
@@ -733,7 +734,7 @@ class Adapter(abc.ABC):
Get the details for the given album ID.
:param album_id: The ID of the album to get the details for.
:returns: The :classs`sublime.adapters.api_objects.Album`
:returns: The :classs`sublime_music.adapters.api_objects.Album`
"""
raise self._check_can_error("get_album")
@@ -747,8 +748,9 @@ class Adapter(abc.ABC):
:param directory_id: The directory to retrieve. If the special value ``"root"``
is given, the adapter should list all of the directories at the root of the
filesystem tree.
:returns: A list of the :class:`sublime.adapter.api_objects.Directory` and
:class:`sublime.adapter.api_objects.Song` objects in the given directory.
:returns: A list of the :class:`sublime_music.adapter.api_objects.Directory` and
:class:`sublime_music.adapter.api_objects.Song` objects in the given
directory.
"""
raise self._check_can_error("get_directory")
@@ -756,7 +758,7 @@ class Adapter(abc.ABC):
"""
Get a list of the genres known to the adapter.
:returns: A list of all of the :classs`sublime.adapter.api_objects.Genre`
:returns: A list of all of the :classs`sublime_music.adapter.api_objects.Genre`
objects known to the adapter.
"""
raise self._check_can_error("get_genres")
@@ -767,7 +769,7 @@ class Adapter(abc.ABC):
the play queue from the cloud.
:returns: The cloud-saved play queue as a
:class:`sublime.adapter.api_objects.PlayQueue` object.
:class:`sublime_music.adapter.api_objects.PlayQueue` object.
"""
raise self._check_can_error("get_play_queue")
@@ -791,7 +793,7 @@ class Adapter(abc.ABC):
Return search results fro the given query.
:param query: The query string.
:returns: A :class:`sublime.adapters.api_objects.SearchResult` object
:returns: A :class:`sublime_music.adapters.api_objects.SearchResult` object
representing the results of the search.
"""
raise self._check_can_error("search")

View File

@@ -178,7 +178,9 @@ class SearchResult:
_S = TypeVar("_S")
def _to_result(
self, it: Dict[str, _S], transform: Callable[[_S], Tuple[Optional[str], ...]],
self,
it: Dict[str, _S],
transform: Callable[[_S], Tuple[Optional[str], ...]],
) -> List[_S]:
assert self.query
all_results = []

View File

@@ -111,7 +111,10 @@ class ConfigureServerForm(Gtk.Box):
self.is_networked = is_networked
content_grid = Gtk.Grid(
column_spacing=10, row_spacing=5, margin_left=10, margin_right=10,
column_spacing=10,
row_spacing=5,
margin_left=10,
margin_right=10,
)
advanced_grid = Gtk.Grid(column_spacing=10, row_spacing=10)
@@ -175,7 +178,8 @@ class ConfigureServerForm(Gtk.Box):
if cpd.helptext:
help_icon = Gtk.Image.new_from_icon_name(
"help-about", Gtk.IconSize.BUTTON,
"help-about",
Gtk.IconSize.BUTTON,
)
help_icon.get_style_context().add_class("configure-form-help-icon")
help_icon.set_tooltip_markup(cpd.helptext)
@@ -248,7 +252,7 @@ class ConfigureServerForm(Gtk.Box):
def _set_verification_status(
self, verifying: bool, is_valid: bool = False, error_text: str = None
):
from sublime.ui import util
from sublime_music.ui import util
if verifying:
if not self.verifying_in_progress:
@@ -300,7 +304,7 @@ class ConfigureServerForm(Gtk.Box):
def _verify_config(self, ratchet: int):
self.emit("config-valid-changed", False)
from sublime.adapters import Result
from sublime_music.adapters import Result
if self.required_config_parameter_keys.issubset(set(self.config_store.keys())):
if self._verification_status_ratchet != ratchet:

View File

@@ -9,7 +9,7 @@ from typing import Any, cast, Dict, Iterable, Optional, Sequence, Set, Tuple
from gi.repository import Gtk
from peewee import fn, prefetch
from sublime.adapters import api_objects as API
from sublime_music.adapters import api_objects as API
from . import models
from .. import (
@@ -60,9 +60,7 @@ class FilesystemAdapter(CachingAdapter):
def migrate_configuration(config_store: ConfigurationStore):
pass
def __init__(
self, config: dict, data_directory: Path, is_cache: bool = False,
):
def __init__(self, config: dict, data_directory: Path, is_cache: bool = False):
self.data_directory = data_directory
self.cover_art_dir = self.data_directory.joinpath("cover_art")
self.music_dir = self.data_directory.joinpath("music")
@@ -311,7 +309,9 @@ class FilesystemAdapter(CachingAdapter):
def get_song_details(self, song_id: str) -> models.Song:
return self._get_object_details(
models.Song, song_id, CachingAdapter.CachedDataKey.SONG,
models.Song,
song_id,
CachingAdapter.CachedDataKey.SONG,
)
def get_artists(self, ignore_cache_miss: bool = False) -> Sequence[API.Artist]:
@@ -429,7 +429,8 @@ class FilesystemAdapter(CachingAdapter):
),
)
search_result.add_results(
"playlists", self.get_playlists(ignore_cache_miss=True),
"playlists",
self.get_playlists(ignore_cache_miss=True),
)
return search_result
@@ -439,7 +440,10 @@ class FilesystemAdapter(CachingAdapter):
return hashlib.sha1(bytes(string, "utf8")).hexdigest()
def ingest_new_data(
self, data_key: CachingAdapter.CachedDataKey, param: Optional[str], data: Any,
self,
data_key: CachingAdapter.CachedDataKey,
param: Optional[str],
data: Any,
):
assert self.is_cache, "FilesystemAdapter is not in cache mode!"
@@ -809,7 +813,9 @@ class FilesystemAdapter(CachingAdapter):
)
song_data["_cover_art"] = (
self._do_ingest_new_data(
KEYS.COVER_ART_FILE, api_song.cover_art, data=None,
KEYS.COVER_ART_FILE,
api_song.cover_art,
data=None,
)
if api_song.cover_art
else None
@@ -863,7 +869,9 @@ class FilesystemAdapter(CachingAdapter):
return return_val if return_val is not None else cache_info
def _do_invalidate_data(
self, data_key: CachingAdapter.CachedDataKey, param: Optional[str],
self,
data_key: CachingAdapter.CachedDataKey,
param: Optional[str],
):
logging.debug(f"_do_invalidate_data param={param} data_key={data_key}")
models.CacheInfo.update({"valid": False}).where(
@@ -899,7 +907,8 @@ class FilesystemAdapter(CachingAdapter):
):
logging.debug(f"_do_delete_data param={param} data_key={data_key}")
cache_info = models.CacheInfo.get_or_none(
models.CacheInfo.cache_key == data_key, models.CacheInfo.parameter == param,
models.CacheInfo.cache_key == data_key,
models.CacheInfo.parameter == param,
)
if data_key == KEYS.COVER_ART_FILE:

View File

@@ -14,7 +14,7 @@ from peewee import (
TextField,
)
from sublime.adapters.adapter_base import CachingAdapter
from sublime_music.adapters.adapter_base import CachingAdapter
# Custom Fields
@@ -70,7 +70,10 @@ class SortedManyToManyQuery(ManyToManyQuery):
class SortedManyToManyFieldAccessor(ManyToManyFieldAccessor):
def __get__(
self, instance: Model, instance_type: Any = None, force_query: bool = False,
self,
instance: Model,
instance_type: Any = None,
force_query: bool = False,
):
if instance is not None:
if not force_query and self.src_fk.backref != "+":

View File

Before

Width:  |  Height:  |  Size: 348 B

After

Width:  |  Height:  |  Size: 348 B

View File

Before

Width:  |  Height:  |  Size: 299 B

After

Width:  |  Height:  |  Size: 299 B

View File

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -30,7 +30,7 @@ from typing import (
import requests
from sublime.config import ProviderConfiguration
from sublime_music.config import ProviderConfiguration
from .adapter_base import (
Adapter,
@@ -273,7 +273,7 @@ class AdapterManager:
config: Any,
on_song_download_progress: Callable[[Any, str, DownloadProgress], None],
):
from sublime.config import AppConfiguration
from sublime_music.config import AppConfiguration
assert isinstance(config, AppConfiguration)
@@ -493,7 +493,8 @@ class AdapterManager:
# Everything succeeded.
if expected_size_exists:
AdapterManager._instance.song_download_progress(
id, DownloadProgress(DownloadProgress.Type.DONE),
id,
DownloadProgress(DownloadProgress.Type.DONE),
)
except Exception as e:
if expected_size_exists and not download_cancelled:
@@ -872,7 +873,9 @@ class AdapterManager:
# Create a download result.
future = AdapterManager._create_download_result(
AdapterManager._instance.ground_truth_adapter.get_cover_art_uri(
cover_art_id, AdapterManager._get_networked_scheme(), size=size,
cover_art_id,
AdapterManager._get_networked_scheme(),
size=size,
),
cover_art_id,
before_download,
@@ -963,7 +966,8 @@ class AdapterManager:
):
AdapterManager._instance.download_limiter_semaphore.release()
AdapterManager._instance.song_download_progress(
song_id, DownloadProgress(DownloadProgress.Type.CANCELLED),
song_id,
DownloadProgress(DownloadProgress.Type.CANCELLED),
)
return Result("", is_download=True)
@@ -977,7 +981,8 @@ class AdapterManager:
)
AdapterManager._instance.download_limiter_semaphore.release()
AdapterManager._instance.song_download_progress(
song_id, DownloadProgress(DownloadProgress.Type.DONE),
song_id,
DownloadProgress(DownloadProgress.Type.DONE),
)
return Result("", is_download=True)
except CacheMissError:
@@ -1033,7 +1038,8 @@ class AdapterManager:
for song_id in song_ids:
# Everything succeeded.
AdapterManager._instance.song_download_progress(
song_id, DownloadProgress(DownloadProgress.Type.QUEUED),
song_id,
DownloadProgress(DownloadProgress.Type.QUEUED),
)
for song_id in song_ids:
@@ -1057,7 +1063,8 @@ class AdapterManager:
# Alert the UI that the downloads are cancelled.
for song_id in song_ids:
AdapterManager._instance.song_download_progress(
song_id, DownloadProgress(DownloadProgress.Type.CANCELLED),
song_id,
DownloadProgress(DownloadProgress.Type.CANCELLED),
)
return Result(do_batch_download_songs, is_download=True, on_cancel=on_cancel)
@@ -1070,7 +1077,8 @@ class AdapterManager:
)
for song_id in song_ids:
AdapterManager._instance.song_download_progress(
song_id, DownloadProgress(DownloadProgress.Type.CANCELLED),
song_id,
DownloadProgress(DownloadProgress.Type.CANCELLED),
)
if AdapterManager._song_download_jobs.get(song_id):
AdapterManager._song_download_jobs[song_id].cancel()
@@ -1360,8 +1368,10 @@ class AdapterManager:
return True
try:
ground_truth_search_results = AdapterManager._instance.ground_truth_adapter.search( # noqa: E501
query
ground_truth_search_results = (
AdapterManager._instance.ground_truth_adapter.search( # noqa: E501
query
)
)
search_result.update(ground_truth_search_results)
search_callback(search_result)

View File

@@ -27,7 +27,7 @@ import requests
import semver
from gi.repository import Gtk
from sublime.util import resolve_path
from sublime_music.util import resolve_path
from .api_objects import Directory, Response
from .. import (
@@ -261,7 +261,9 @@ class SubsonicAdapter(Adapter):
# Try to ping the server.
self._get_json(
self._make_url("ping"), timeout=timeout, is_exponential_backoff_ping=True,
self._make_url("ping"),
timeout=timeout,
is_exponential_backoff_ping=True,
)
def on_offline_mode_change(self, offline_mode: bool):
@@ -388,7 +390,10 @@ class SubsonicAdapter(Adapter):
result = self._get_mock_data()
else:
result = requests.get(
url, params=params, verify=self.verify_cert, timeout=timeout,
url,
params=params,
verify=self.verify_cert,
timeout=timeout,
)
if result.status_code != 200:
@@ -469,10 +474,10 @@ class SubsonicAdapter(Adapter):
raise data
if hasattr(data, "__next__"):
if d := next(data):
logging.info("MOCK DATA", d)
logging.info("MOCK DATA: %s", d)
return MockResult(d)
logging.info("MOCK DATA", data)
logging.info("MOCK DATA: %s", data)
return MockResult(data)
self._get_mock_data = get_mock_data
@@ -490,7 +495,7 @@ class SubsonicAdapter(Adapter):
return result
def create_playlist(
self, name: str, songs: Sequence[API.Song] = None,
self, name: str, songs: Sequence[API.Song] = None
) -> Optional[API.Playlist]:
return self._get_json(
self._make_url("createPlaylist"),

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -107,7 +107,8 @@ class SublimeMusicApp(Gtk.Application):
add_action("go-online", self.on_go_online)
add_action("refresh-devices", self.on_refresh_devices)
add_action(
"refresh-window", lambda *a: self.on_refresh_window(None, {}, True),
"refresh-window",
lambda *a: self.on_refresh_window(None, {}, True),
)
add_action("mute-toggle", self.on_mute_toggle)
add_action(
@@ -413,7 +414,8 @@ class SublimeMusicApp(Gtk.Application):
# repeat song IDs.
metadatas: Iterable[Any] = [
self.dbus_manager.get_mpris_metadata(
i, self.app_config.state.play_queue,
i,
self.app_config.state.play_queue,
)
for i in range(len(self.app_config.state.play_queue))
]
@@ -457,7 +459,10 @@ class SublimeMusicApp(Gtk.Application):
)
def get_playlists(
index: int, max_count: int, order: str, reverse_order: bool,
index: int,
max_count: int,
order: str,
reverse_order: bool,
) -> GLib.Variant:
playlists_result = AdapterManager.get_playlists()
if not playlists_result.data_is_available:
@@ -473,12 +478,15 @@ class SublimeMusicApp(Gtk.Application):
"Modified": lambda p: p.changed,
}
playlists.sort(
key=sorters.get(order, lambda p: p), reverse=reverse_order,
key=sorters.get(order, lambda p: p),
reverse=reverse_order,
)
def make_playlist_tuple(p: Playlist) -> GLib.Variant:
cover_art_filename = AdapterManager.get_cover_art_uri(
p.cover_art, "file", allow_download=False,
p.cover_art,
"file",
allow_download=False,
).result()
return (f"/playlist/{p.id}", p.name, cover_art_filename or "")
@@ -570,9 +578,7 @@ class SublimeMusicApp(Gtk.Application):
# ########## ACTION HANDLERS ########## #
@dbus_propagate()
def on_refresh_window(
self, _, state_updates: Dict[str, Any], force: bool = False,
):
def on_refresh_window(self, _, state_updates: Dict[str, Any], force: bool = False):
if settings := state_updates.get("__settings__"):
for k, v in settings.items():
setattr(self.app_config, k, v)
@@ -879,9 +885,7 @@ class SublimeMusicApp(Gtk.Application):
return
self.app_config.state.current_song_index -= len(before_current)
self.play_song(
self.app_config.state.current_song_index, reset=True,
)
self.play_song(self.app_config.state.current_song_index, reset=True)
else:
self.app_config.state.current_song_index -= len(before_current)
self.update_window()
@@ -1001,7 +1005,8 @@ class SublimeMusicApp(Gtk.Application):
# ########## HELPER METHODS ########## #
def show_configure_servers_dialog(
self, provider_config: Optional[ProviderConfiguration] = None,
self,
provider_config: Optional[ProviderConfiguration] = None,
):
"""Show the Connect to Server dialog."""
dialog = ConfigureProviderDialog(self.window, provider_config)
@@ -1192,7 +1197,8 @@ class SublimeMusicApp(Gtk.Application):
if artist := song.artist:
notification_lines.append(bleach.clean(artist.name))
song_notification = Notify.Notification.new(
song.title, "\n".join(notification_lines),
song.title,
"\n".join(notification_lines),
)
song_notification.add_action(
"clicked",

View File

@@ -8,8 +8,8 @@ from typing import Any, cast, Dict, Optional, Tuple, Type, Union
import dataclasses_json
from dataclasses_json import config, DataClassJsonMixin
from sublime.adapters import ConfigurationStore
from sublime.ui.state import UIState
from .adapters import ConfigurationStore
from .ui.state import UIState
# JSON decoder and encoder translations
@@ -18,9 +18,7 @@ def encode_path(path: Path) -> str:
dataclasses_json.cfg.global_config.decoders[Path] = Path
dataclasses_json.cfg.global_config.decoders[
Optional[Path] # type: ignore
] = (
dataclasses_json.cfg.global_config.decoders[Optional[Path]] = ( # type: ignore
lambda p: Path(p) if p else None
)
@@ -87,7 +85,7 @@ def encode_providers(
def decode_providers(
providers_dict: Dict[str, Dict[str, Any]]
) -> Dict[str, ProviderConfiguration]:
from sublime.adapters import AdapterManager
from sublime_music.adapters import AdapterManager
def find_adapter_type(type_name: str) -> Type:
for available_adapter in AdapterManager.available_adapters:

View File

@@ -137,7 +137,13 @@ class DBusManager:
return
self.do_on_method_call(
connection, sender, path, interface, method, params, invocation,
connection,
sender,
path,
interface,
method,
params,
invocation,
)
@staticmethod
@@ -153,7 +159,8 @@ class DBusManager:
if type(value) == dict:
return GLib.Variant(
"a{sv}", {k: DBusManager.to_variant(v) for k, v in value.items()},
"a{sv}",
{k: DBusManager.to_variant(v) for k, v in value.items()},
)
variant_type = {list: "as", str: "s", int: "i", float: "d", bool: "b"}.get(
@@ -230,7 +237,8 @@ class DBusManager:
"Rate": 1.0,
"Shuffle": state.shuffle_on,
"Metadata": self.get_mpris_metadata(
state.current_song_index, state.play_queue,
state.current_song_index,
state.play_queue,
)
if state.current_song
else {},

View File

@@ -4,7 +4,7 @@ from datetime import timedelta
from enum import Enum
from typing import Callable, Dict, Optional, Set, Tuple, Type, Union
from sublime.adapters.api_objects import Song
from ..adapters.api_objects import Song
@dataclass

View File

@@ -12,10 +12,9 @@ from uuid import UUID
from gi.repository import GLib
from sublime.adapters import AdapterManager
from sublime.adapters.api_objects import Song
from .base import Player, PlayerDeviceEvent, PlayerEvent
from ..adapters import AdapterManager
from ..adapters.api_objects import Song
try:
import pychromecast

View File

@@ -2,11 +2,10 @@ import logging
from datetime import timedelta
from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Type, Union
from sublime.adapters.api_objects import Song
from .base import PlayerDeviceEvent, PlayerEvent
from .chromecast import ChromecastPlayer # noqa: F401
from .mpv import MPVPlayer # noqa: F401
from ..adapters.api_objects import Song
class PlayerManager:
@@ -19,7 +18,8 @@ class PlayerManager:
]:
"""
:returns: Dictionary of the name of the player -> option configs (see
:class:`sublime.players.base.Player.get_configuration_options` for details).
:class:`sublime_music.players.base.Player.get_configuration_options` for
details).
"""
return {
p.name: p.get_configuration_options()
@@ -67,7 +67,8 @@ class PlayerManager:
}
def change_settings(
self, config: Dict[str, Dict[str, Union[Type, Tuple[str, ...]]]],
self,
config: Dict[str, Dict[str, Union[Type, Tuple[str, ...]]]],
):
self.config = config
for player_type, player in self.players.items():

View File

@@ -4,9 +4,8 @@ from typing import Callable, cast, Dict, Optional, Tuple, Type, Union
import mpv
from sublime.adapters.api_objects import Song
from .base import Player, PlayerDeviceEvent, PlayerEvent
from ..adapters.api_objects import Song
REPLAY_GAIN_KEY = "Replay Gain"

View File

@@ -6,16 +6,16 @@ from typing import Any, Callable, cast, Iterable, List, Optional, Tuple
from gi.repository import Gdk, Gio, GLib, GObject, Gtk, Pango
from sublime.adapters import (
from ..adapters import (
AdapterManager,
AlbumSearchQuery,
api_objects as API,
CacheMissError,
Result,
)
from sublime.config import AppConfiguration
from sublime.ui import util
from sublime.ui.common import AlbumWithSongs, IconButton, LoadError, SpinnerImage
from ..config import AppConfiguration
from ..ui import util
from ..ui.common import AlbumWithSongs, IconButton, LoadError, SpinnerImage
def _to_type(query_type: AlbumSearchQuery.Type) -> str:
@@ -164,10 +164,12 @@ class AlbumsPanel(Gtk.Box):
scrolled_window = Gtk.ScrolledWindow()
self.grid = AlbumsGrid()
self.grid.connect(
"song-clicked", lambda _, *args: self.emit("song-clicked", *args),
"song-clicked",
lambda _, *args: self.emit("song-clicked", *args),
)
self.grid.connect(
"refresh-window", lambda _, *args: self.emit("refresh-window", *args),
"refresh-window",
lambda _, *args: self.emit("refresh-window", *args),
)
self.grid.connect("cover-clicked", self.on_grid_cover_clicked)
self.grid.connect("num-pages-changed", self.on_grid_num_pages_changed)
@@ -195,7 +197,9 @@ class AlbumsPanel(Gtk.Box):
return combo, store
def populate_genre_combo(
self, app_config: AppConfiguration = None, force: bool = False,
self,
app_config: AppConfiguration = None,
force: bool = False,
):
if not AdapterManager.can_get_genres():
self.updating_query = False
@@ -464,13 +468,17 @@ class AlbumsPanel(Gtk.Box):
def on_grid_cover_clicked(self, grid: Any, id: str):
self.emit(
"refresh-window", {"selected_album_id": id}, False,
"refresh-window",
{"selected_album_id": id},
False,
)
def on_show_count_dropdown_change(self, combo: Gtk.ComboBox):
show_count = int(self.get_id(combo) or 30)
self.emit(
"refresh-window", {"album_page_size": show_count, "album_page": 0}, False,
"refresh-window",
{"album_page_size": show_count, "album_page": 0},
False,
)
def emit_if_not_updating(self, *args):
@@ -483,7 +491,7 @@ class AlbumsGrid(Gtk.Overlay):
"""Defines the albums panel."""
__gsignals__ = {
"cover-clicked": (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (object,),),
"cover-clicked": (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (object,)),
"refresh-window": (
GObject.SignalFlags.RUN_FIRST,
GObject.TYPE_NONE,
@@ -789,9 +797,7 @@ class AlbumsGrid(Gtk.Overlay):
new_items_per_row = min((rect.width // 230), 7)
if new_items_per_row != self.items_per_row:
self.items_per_row = new_items_per_row
self.detail_box_inner.set_size_request(
self.items_per_row * 230 - 10, -1,
)
self.detail_box_inner.set_size_request(self.items_per_row * 230 - 10, -1)
self.reflow_grids(
force_reload_from_master=True,
@@ -899,10 +905,14 @@ class AlbumsGrid(Gtk.Overlay):
# Just remove everything and re-add all of the items. It's not worth trying
# to diff in this case.
self.list_store_top.splice(
0, len(self.list_store_top), window[:entries_before_fold],
0,
len(self.list_store_top),
window[:entries_before_fold],
)
self.list_store_bottom.splice(
0, len(self.list_store_bottom), window[entries_before_fold:],
0,
len(self.list_store_bottom),
window[entries_before_fold:],
)
elif selected_index or entries_before_fold != self.page_size:
# This case handles when the selection changes and the entries need to be
@@ -940,7 +950,8 @@ class AlbumsGrid(Gtk.Overlay):
model = self.list_store_top[relative_selected_index]
detail_element = AlbumWithSongs(model.album, cover_art_size=300)
detail_element.connect(
"song-clicked", lambda _, *args: self.emit("song-clicked", *args),
"song-clicked",
lambda _, *args: self.emit("song-clicked", *args),
)
detail_element.connect("song-selected", lambda *a: None)

View File

@@ -5,15 +5,15 @@ from typing import cast, List, Sequence
from gi.repository import Gio, GLib, GObject, Gtk, Pango
from sublime.adapters import (
from ..adapters import (
AdapterManager,
api_objects as API,
CacheMissError,
SongCacheStatus,
)
from sublime.config import AppConfiguration
from sublime.ui import util
from sublime.ui.common import AlbumWithSongs, IconButton, LoadError, SpinnerImage
from ..config import AppConfiguration
from ..ui import util
from ..ui.common import AlbumWithSongs, IconButton, LoadError, SpinnerImage
class ArtistsPanel(Gtk.Paned):
@@ -40,10 +40,12 @@ class ArtistsPanel(Gtk.Paned):
self.artist_detail_panel = ArtistDetailPanel()
self.artist_detail_panel.connect(
"song-clicked", lambda _, *args: self.emit("song-clicked", *args),
"song-clicked",
lambda _, *args: self.emit("song-clicked", *args),
)
self.artist_detail_panel.connect(
"refresh-window", lambda _, *args: self.emit("refresh-window", *args),
"refresh-window",
lambda _, *args: self.emit("refresh-window", *args),
)
self.pack2(self.artist_detail_panel, True, False)
@@ -315,7 +317,8 @@ class ArtistDetailPanel(Gtk.Box):
self.album_list_scrolledwindow = Gtk.ScrolledWindow()
self.albums_list = AlbumsListWithSongs()
self.albums_list.connect(
"song-clicked", lambda _, *args: self.emit("song-clicked", *args),
"song-clicked",
lambda _, *args: self.emit("song-clicked", *args),
)
self.album_list_scrolledwindow.add(self.albums_list)
self.pack_start(self.album_list_scrolledwindow, True, True, 0)
@@ -408,7 +411,9 @@ class ArtistDetailPanel(Gtk.Box):
self.play_shuffle_buttons.show_all()
self.update_artist_artwork(
artist.artist_image_url, force=force, order_token=order_token,
artist.artist_image_url,
force=force,
order_token=order_token,
)
for c in self.error_container.get_children():
@@ -489,24 +494,31 @@ class ArtistDetailPanel(Gtk.Box):
# =========================================================================
def on_view_refresh_click(self, *args):
self.update_artist_view(
self.artist_id, force=True, order_token=self.update_order_token,
self.artist_id,
force=True,
order_token=self.update_order_token,
)
def on_download_all_click(self, _):
AdapterManager.batch_download_songs(
self.get_artist_song_ids(),
before_download=lambda _: self.update_artist_view(
self.artist_id, order_token=self.update_order_token,
self.artist_id,
order_token=self.update_order_token,
),
on_song_download_complete=lambda _: self.update_artist_view(
self.artist_id, order_token=self.update_order_token,
self.artist_id,
order_token=self.update_order_token,
),
)
def on_play_all_clicked(self, _):
songs = self.get_artist_song_ids()
self.emit(
"song-clicked", 0, songs, {"force_shuffle_state": False},
"song-clicked",
0,
songs,
{"force_shuffle_state": False},
)
def on_shuffle_all_button(self, _):
@@ -633,7 +645,8 @@ class AlbumsListWithSongs(Gtk.Overlay):
for album in self.albums:
album_with_songs = AlbumWithSongs(album, show_artist_name=False)
album_with_songs.connect(
"song-clicked", lambda _, *args: self.emit("song-clicked", *args),
"song-clicked",
lambda _, *args: self.emit("song-clicked", *args),
)
album_with_songs.connect("song-selected", self.on_song_selected)
album_with_songs.show_all()

View File

@@ -3,10 +3,10 @@ from typing import Any, cast, List, Optional, Tuple
from gi.repository import Gdk, Gio, GLib, GObject, Gtk, Pango
from sublime.adapters import AdapterManager, api_objects as API, CacheMissError, Result
from sublime.config import AppConfiguration
from sublime.ui import util
from sublime.ui.common import IconButton, LoadError, SongListColumn
from ..adapters import AdapterManager, api_objects as API, CacheMissError, Result
from ..config import AppConfiguration
from ..ui import util
from ..ui.common import IconButton, LoadError, SongListColumn
class BrowsePanel(Gtk.Overlay):
@@ -37,10 +37,12 @@ class BrowsePanel(Gtk.Overlay):
self.root_directory_listing = ListAndDrilldown()
self.root_directory_listing.connect(
"song-clicked", lambda _, *args: self.emit("song-clicked", *args),
"song-clicked",
lambda _, *args: self.emit("song-clicked", *args),
)
self.root_directory_listing.connect(
"refresh-window", lambda _, *args: self.emit("refresh-window", *args),
"refresh-window",
lambda _, *args: self.emit("refresh-window", *args),
)
window_box.add(self.root_directory_listing)
@@ -88,7 +90,8 @@ class BrowsePanel(Gtk.Overlay):
while current_dir_id:
try:
directory = AdapterManager.get_directory(
current_dir_id, before_download=self.spinner.show,
current_dir_id,
before_download=self.spinner.show,
).result()
except CacheMissError as e:
directory = cast(API.Directory, e.partial_data)
@@ -128,10 +131,12 @@ class ListAndDrilldown(Gtk.Paned):
self.list = MusicDirectoryList()
self.list.connect(
"song-clicked", lambda _, *args: self.emit("song-clicked", *args),
"song-clicked",
lambda _, *args: self.emit("song-clicked", *args),
)
self.list.connect(
"refresh-window", lambda _, *args: self.emit("refresh-window", *args),
"refresh-window",
lambda _, *args: self.emit("refresh-window", *args),
)
self.pack1(self.list, False, False)
@@ -163,7 +168,8 @@ class ListAndDrilldown(Gtk.Paned):
if len(children) == 0:
drilldown = ListAndDrilldown()
drilldown.connect(
"song-clicked", lambda _, *args: self.emit("song-clicked", *args),
"song-clicked",
lambda _, *args: self.emit("song-clicked", *args),
)
drilldown.connect(
"refresh-window",
@@ -277,7 +283,9 @@ class MusicDirectoryList(Gtk.Box):
self.directory_id = directory_id or self.directory_id
self.selected_id = selected_id or self.selected_id
self.update_store(
self.directory_id, force=force, order_token=self.update_order_token,
self.directory_id,
force=force,
order_token=self.update_order_token,
)
if app_config:
@@ -428,7 +436,8 @@ class MusicDirectoryList(Gtk.Box):
# ==================================================================================
def create_row(self, model: DrilldownElement) -> Gtk.ListBoxRow:
row = Gtk.ListBoxRow(
action_name="app.browse-to", action_target=GLib.Variant("s", model.id),
action_name="app.browse-to",
action_target=GLib.Variant("s", model.id),
)
rowbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
rowbox.add(
@@ -461,7 +470,7 @@ class MusicDirectoryList(Gtk.Box):
{},
)
def on_song_button_press(self, tree: Gtk.TreeView, event: Gdk.EventButton,) -> bool:
def on_song_button_press(self, tree: Gtk.TreeView, event: Gdk.EventButton) -> bool:
if event.button == 3: # Right click
clicked_path = tree.get_path_at_pos(event.x, event.y)
if not clicked_path:

View File

@@ -3,9 +3,9 @@ from typing import Any, cast, List
from gi.repository import Gdk, GLib, GObject, Gtk, Pango
from sublime.adapters import AdapterManager, api_objects as API, Result
from sublime.config import AppConfiguration
from sublime.ui import util
from sublime_music.adapters import AdapterManager, api_objects as API, Result
from sublime_music.config import AppConfiguration
from sublime_music.ui import util
from .icon_button import IconButton
from .load_error import LoadError
@@ -15,7 +15,7 @@ from .spinner_image import SpinnerImage
class AlbumWithSongs(Gtk.Box):
__gsignals__ = {
"song-selected": (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (),),
"song-selected": (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, ()),
"song-clicked": (
GObject.SignalFlags.RUN_FIRST,
GObject.TYPE_NONE,
@@ -122,7 +122,9 @@ class AlbumWithSongs(Gtk.Box):
album_details.add(
Gtk.Label(
label=util.dot_join(*stats), halign=Gtk.Align.START, margin_left=10,
label=util.dot_join(*stats),
halign=Gtk.Align.START,
margin_left=10,
)
)
@@ -235,7 +237,10 @@ class AlbumWithSongs(Gtk.Box):
def play_btn_clicked(self, btn: Any):
song_ids = [x[-1] for x in self.album_song_store]
self.emit(
"song-clicked", 0, song_ids, {"force_shuffle_state": False},
"song-clicked",
0,
song_ids,
{"force_shuffle_state": False},
)
def shuffle_btn_clicked(self, btn: Any):

View File

@@ -35,7 +35,7 @@ class SpinnerImage(Gtk.Overlay):
self.filename = filename
if self.image_size is not None and filename:
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(
filename, self.image_size, self.image_size, True,
filename, self.image_size, self.image_size, True
)
self.image.set_from_pixbuf(pixbuf)
else:

View File

@@ -4,9 +4,9 @@ from typing import Any, Optional, Type
from gi.repository import Gio, GObject, Gtk, Pango
from sublime.adapters import AdapterManager, UIInfo
from sublime.adapters.filesystem import FilesystemAdapter
from sublime.config import ConfigurationStore, ProviderConfiguration
from ..adapters import AdapterManager, UIInfo
from ..adapters.filesystem import FilesystemAdapter
from ..config import ConfigurationStore, ProviderConfiguration
class AdapterTypeModel(GObject.GObject):

View File

Before

Width:  |  Height:  |  Size: 325 B

After

Width:  |  Height:  |  Size: 325 B

View File

Before

Width:  |  Height:  |  Size: 402 B

After

Width:  |  Height:  |  Size: 402 B

View File

Before

Width:  |  Height:  |  Size: 402 B

After

Width:  |  Height:  |  Size: 402 B

View File

Before

Width:  |  Height:  |  Size: 372 B

After

Width:  |  Height:  |  Size: 372 B

View File

Before

Width:  |  Height:  |  Size: 276 B

After

Width:  |  Height:  |  Size: 276 B

View File

Before

Width:  |  Height:  |  Size: 391 B

After

Width:  |  Height:  |  Size: 391 B

View File

Before

Width:  |  Height:  |  Size: 392 B

After

Width:  |  Height:  |  Size: 392 B

View File

Before

Width:  |  Height:  |  Size: 388 B

After

Width:  |  Height:  |  Size: 388 B

Some files were not shown because too many files have changed in this diff Show More