Fix linter errors
This commit is contained in:
@@ -12,10 +12,8 @@ environment:
|
|||||||
# to: ~sumner/sublime-music-devel@lists.sr.ht
|
# to: ~sumner/sublime-music-devel@lists.sr.ht
|
||||||
tasks:
|
tasks:
|
||||||
- setup: |
|
- setup: |
|
||||||
cd ${REPO_NAME}
|
pip install requirements-parser
|
||||||
poetry install
|
|
||||||
echo "cd ${REPO_NAME}" >> ~/.buildenv
|
|
||||||
|
|
||||||
- build-flatpak: |
|
- build-flatpak: |
|
||||||
cd flatpak
|
cd ${REPO_NAME}/flatpak
|
||||||
./flatpak_build.sh
|
./flatpak_build.sh
|
||||||
|
@@ -1,8 +1,9 @@
|
|||||||
image: archlinux
|
image: alpine/edge
|
||||||
packages:
|
packages:
|
||||||
- curl
|
- curl
|
||||||
|
- git
|
||||||
- openssh
|
- openssh
|
||||||
- python-docutils
|
- py3-docutils
|
||||||
sources:
|
sources:
|
||||||
- https://git.sr.ht/~sumner/offlinemsmtp
|
- https://git.sr.ht/~sumner/offlinemsmtp
|
||||||
secrets:
|
secrets:
|
||||||
@@ -21,7 +22,6 @@ tasks:
|
|||||||
cd ${REPO_NAME}
|
cd ${REPO_NAME}
|
||||||
echo "cd ${REPO_NAME}" >> ~/.buildenv
|
echo "cd ${REPO_NAME}" >> ~/.buildenv
|
||||||
|
|
||||||
# TODO
|
|
||||||
# - gitlab-mirror: |
|
# - gitlab-mirror: |
|
||||||
# ssh-keyscan gitlab.com >> ~/.ssh/known_hosts
|
# ssh-keyscan gitlab.com >> ~/.ssh/known_hosts
|
||||||
# git push --quiet --mirror git@gitlab.com:sublime-music/sublime-music.git
|
# git push --quiet --mirror git@gitlab.com:sublime-music/sublime-music.git
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
image: archlinux
|
image: archlinux
|
||||||
packages:
|
packages:
|
||||||
- python-poetry
|
- python
|
||||||
- xorg-server-xvfb
|
- xorg-server-xvfb
|
||||||
sources:
|
sources:
|
||||||
- https://git.sr.ht/~sumner/offlinemsmtp
|
- https://git.sr.ht/~sumner/offlinemsmtp
|
||||||
@@ -13,15 +13,16 @@ environment:
|
|||||||
tasks:
|
tasks:
|
||||||
- setup: |
|
- setup: |
|
||||||
cd ${REPO_NAME}
|
cd ${REPO_NAME}
|
||||||
poetry install
|
|
||||||
echo "cd ${REPO_NAME}" >> ~/.buildenv
|
echo "cd ${REPO_NAME}" >> ~/.buildenv
|
||||||
echo "source $(poetry env info -p)/bin/activate" >> ~/.buildenv
|
|
||||||
|
|
||||||
- build: |
|
- build: |
|
||||||
python setup.py sdist
|
python setup.py sdist
|
||||||
|
|
||||||
- deploy_pypi: |
|
- deploy-pypi: |
|
||||||
echo "deploy"
|
./cicd/run_if_tagged_with_version \
|
||||||
|
"twine upload -r testpypi dist/*" \
|
||||||
|
"twine upload dist/*"
|
||||||
|
|
||||||
- verify_pypi: |
|
- verify-pypi: |
|
||||||
echo "deploy"
|
./cicd/run_if_tagged_with_version \
|
||||||
|
"pip install ${REPO_NAME}"
|
||||||
|
27
cicd/run_if_tagged_with_version
Executable file
27
cicd/run_if_tagged_with_version
Executable file
@@ -0,0 +1,27 @@
|
|||||||
|
#! /usr/bin/env python
|
||||||
|
|
||||||
|
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()
|
@@ -10,6 +10,9 @@ import-order-style = edited
|
|||||||
[mypy-bottle]
|
[mypy-bottle]
|
||||||
ignore_missing_imports = True
|
ignore_missing_imports = True
|
||||||
|
|
||||||
|
[mypy-dataclasses_json]
|
||||||
|
ignore_missing_imports = True
|
||||||
|
|
||||||
[mypy-deepdiff]
|
[mypy-deepdiff]
|
||||||
ignore_missing_imports = True
|
ignore_missing_imports = True
|
||||||
|
|
||||||
|
@@ -574,7 +574,7 @@ class Adapter(abc.ABC):
|
|||||||
"""
|
"""
|
||||||
raise self._check_can_error("get_playlists")
|
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
|
Get the details for the given ``playlist_id``. If the playlist_id does not
|
||||||
exist, then this function should throw an exception.
|
exist, then this function should throw an exception.
|
||||||
@@ -586,7 +586,7 @@ class Adapter(abc.ABC):
|
|||||||
raise self._check_can_error("get_playlist_details")
|
raise self._check_can_error("get_playlist_details")
|
||||||
|
|
||||||
def create_playlist(
|
def create_playlist(
|
||||||
self, name: str, songs: Sequence[Song] = None,
|
self, name: str, songs: Sequence[Song] = None
|
||||||
) -> Optional[Playlist]:
|
) -> Optional[Playlist]:
|
||||||
"""
|
"""
|
||||||
Creates a playlist of the given name with the given songs.
|
Creates a playlist of the given name with the given songs.
|
||||||
|
@@ -178,7 +178,9 @@ class SearchResult:
|
|||||||
_S = TypeVar("_S")
|
_S = TypeVar("_S")
|
||||||
|
|
||||||
def _to_result(
|
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]:
|
) -> List[_S]:
|
||||||
assert self.query
|
assert self.query
|
||||||
all_results = []
|
all_results = []
|
||||||
|
@@ -111,7 +111,10 @@ class ConfigureServerForm(Gtk.Box):
|
|||||||
self.is_networked = is_networked
|
self.is_networked = is_networked
|
||||||
|
|
||||||
content_grid = Gtk.Grid(
|
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)
|
advanced_grid = Gtk.Grid(column_spacing=10, row_spacing=10)
|
||||||
|
|
||||||
@@ -175,7 +178,8 @@ class ConfigureServerForm(Gtk.Box):
|
|||||||
|
|
||||||
if cpd.helptext:
|
if cpd.helptext:
|
||||||
help_icon = Gtk.Image.new_from_icon_name(
|
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.get_style_context().add_class("configure-form-help-icon")
|
||||||
help_icon.set_tooltip_markup(cpd.helptext)
|
help_icon.set_tooltip_markup(cpd.helptext)
|
||||||
|
@@ -60,9 +60,7 @@ class FilesystemAdapter(CachingAdapter):
|
|||||||
def migrate_configuration(config_store: ConfigurationStore):
|
def migrate_configuration(config_store: ConfigurationStore):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def __init__(
|
def __init__(self, config: dict, data_directory: Path, is_cache: bool = False):
|
||||||
self, config: dict, data_directory: Path, is_cache: bool = False,
|
|
||||||
):
|
|
||||||
self.data_directory = data_directory
|
self.data_directory = data_directory
|
||||||
self.cover_art_dir = self.data_directory.joinpath("cover_art")
|
self.cover_art_dir = self.data_directory.joinpath("cover_art")
|
||||||
self.music_dir = self.data_directory.joinpath("music")
|
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:
|
def get_song_details(self, song_id: str) -> models.Song:
|
||||||
return self._get_object_details(
|
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]:
|
def get_artists(self, ignore_cache_miss: bool = False) -> Sequence[API.Artist]:
|
||||||
@@ -429,7 +429,8 @@ class FilesystemAdapter(CachingAdapter):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
search_result.add_results(
|
search_result.add_results(
|
||||||
"playlists", self.get_playlists(ignore_cache_miss=True),
|
"playlists",
|
||||||
|
self.get_playlists(ignore_cache_miss=True),
|
||||||
)
|
)
|
||||||
return search_result
|
return search_result
|
||||||
|
|
||||||
@@ -439,7 +440,10 @@ class FilesystemAdapter(CachingAdapter):
|
|||||||
return hashlib.sha1(bytes(string, "utf8")).hexdigest()
|
return hashlib.sha1(bytes(string, "utf8")).hexdigest()
|
||||||
|
|
||||||
def ingest_new_data(
|
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!"
|
assert self.is_cache, "FilesystemAdapter is not in cache mode!"
|
||||||
|
|
||||||
@@ -809,7 +813,9 @@ class FilesystemAdapter(CachingAdapter):
|
|||||||
)
|
)
|
||||||
song_data["_cover_art"] = (
|
song_data["_cover_art"] = (
|
||||||
self._do_ingest_new_data(
|
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
|
if api_song.cover_art
|
||||||
else None
|
else None
|
||||||
@@ -863,7 +869,9 @@ class FilesystemAdapter(CachingAdapter):
|
|||||||
return return_val if return_val is not None else cache_info
|
return return_val if return_val is not None else cache_info
|
||||||
|
|
||||||
def _do_invalidate_data(
|
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}")
|
logging.debug(f"_do_invalidate_data param={param} data_key={data_key}")
|
||||||
models.CacheInfo.update({"valid": False}).where(
|
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}")
|
logging.debug(f"_do_delete_data param={param} data_key={data_key}")
|
||||||
cache_info = models.CacheInfo.get_or_none(
|
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:
|
if data_key == KEYS.COVER_ART_FILE:
|
||||||
|
@@ -70,7 +70,10 @@ class SortedManyToManyQuery(ManyToManyQuery):
|
|||||||
|
|
||||||
class SortedManyToManyFieldAccessor(ManyToManyFieldAccessor):
|
class SortedManyToManyFieldAccessor(ManyToManyFieldAccessor):
|
||||||
def __get__(
|
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 instance is not None:
|
||||||
if not force_query and self.src_fk.backref != "+":
|
if not force_query and self.src_fk.backref != "+":
|
||||||
|
@@ -493,7 +493,8 @@ class AdapterManager:
|
|||||||
# Everything succeeded.
|
# Everything succeeded.
|
||||||
if expected_size_exists:
|
if expected_size_exists:
|
||||||
AdapterManager._instance.song_download_progress(
|
AdapterManager._instance.song_download_progress(
|
||||||
id, DownloadProgress(DownloadProgress.Type.DONE),
|
id,
|
||||||
|
DownloadProgress(DownloadProgress.Type.DONE),
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if expected_size_exists and not download_cancelled:
|
if expected_size_exists and not download_cancelled:
|
||||||
@@ -872,7 +873,9 @@ class AdapterManager:
|
|||||||
# Create a download result.
|
# Create a download result.
|
||||||
future = AdapterManager._create_download_result(
|
future = AdapterManager._create_download_result(
|
||||||
AdapterManager._instance.ground_truth_adapter.get_cover_art_uri(
|
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,
|
cover_art_id,
|
||||||
before_download,
|
before_download,
|
||||||
@@ -963,7 +966,8 @@ class AdapterManager:
|
|||||||
):
|
):
|
||||||
AdapterManager._instance.download_limiter_semaphore.release()
|
AdapterManager._instance.download_limiter_semaphore.release()
|
||||||
AdapterManager._instance.song_download_progress(
|
AdapterManager._instance.song_download_progress(
|
||||||
song_id, DownloadProgress(DownloadProgress.Type.CANCELLED),
|
song_id,
|
||||||
|
DownloadProgress(DownloadProgress.Type.CANCELLED),
|
||||||
)
|
)
|
||||||
return Result("", is_download=True)
|
return Result("", is_download=True)
|
||||||
|
|
||||||
@@ -977,7 +981,8 @@ class AdapterManager:
|
|||||||
)
|
)
|
||||||
AdapterManager._instance.download_limiter_semaphore.release()
|
AdapterManager._instance.download_limiter_semaphore.release()
|
||||||
AdapterManager._instance.song_download_progress(
|
AdapterManager._instance.song_download_progress(
|
||||||
song_id, DownloadProgress(DownloadProgress.Type.DONE),
|
song_id,
|
||||||
|
DownloadProgress(DownloadProgress.Type.DONE),
|
||||||
)
|
)
|
||||||
return Result("", is_download=True)
|
return Result("", is_download=True)
|
||||||
except CacheMissError:
|
except CacheMissError:
|
||||||
@@ -1033,7 +1038,8 @@ class AdapterManager:
|
|||||||
for song_id in song_ids:
|
for song_id in song_ids:
|
||||||
# Everything succeeded.
|
# Everything succeeded.
|
||||||
AdapterManager._instance.song_download_progress(
|
AdapterManager._instance.song_download_progress(
|
||||||
song_id, DownloadProgress(DownloadProgress.Type.QUEUED),
|
song_id,
|
||||||
|
DownloadProgress(DownloadProgress.Type.QUEUED),
|
||||||
)
|
)
|
||||||
|
|
||||||
for song_id in song_ids:
|
for song_id in song_ids:
|
||||||
@@ -1057,7 +1063,8 @@ class AdapterManager:
|
|||||||
# Alert the UI that the downloads are cancelled.
|
# Alert the UI that the downloads are cancelled.
|
||||||
for song_id in song_ids:
|
for song_id in song_ids:
|
||||||
AdapterManager._instance.song_download_progress(
|
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)
|
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:
|
for song_id in song_ids:
|
||||||
AdapterManager._instance.song_download_progress(
|
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):
|
if AdapterManager._song_download_jobs.get(song_id):
|
||||||
AdapterManager._song_download_jobs[song_id].cancel()
|
AdapterManager._song_download_jobs[song_id].cancel()
|
||||||
@@ -1360,8 +1368,10 @@ class AdapterManager:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ground_truth_search_results = AdapterManager._instance.ground_truth_adapter.search( # noqa: E501
|
ground_truth_search_results = (
|
||||||
query
|
AdapterManager._instance.ground_truth_adapter.search( # noqa: E501
|
||||||
|
query
|
||||||
|
)
|
||||||
)
|
)
|
||||||
search_result.update(ground_truth_search_results)
|
search_result.update(ground_truth_search_results)
|
||||||
search_callback(search_result)
|
search_callback(search_result)
|
||||||
|
@@ -261,7 +261,9 @@ class SubsonicAdapter(Adapter):
|
|||||||
|
|
||||||
# Try to ping the server.
|
# Try to ping the server.
|
||||||
self._get_json(
|
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):
|
def on_offline_mode_change(self, offline_mode: bool):
|
||||||
@@ -388,7 +390,10 @@ class SubsonicAdapter(Adapter):
|
|||||||
result = self._get_mock_data()
|
result = self._get_mock_data()
|
||||||
else:
|
else:
|
||||||
result = requests.get(
|
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:
|
if result.status_code != 200:
|
||||||
@@ -490,7 +495,7 @@ class SubsonicAdapter(Adapter):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
def create_playlist(
|
def create_playlist(
|
||||||
self, name: str, songs: Sequence[API.Song] = None,
|
self, name: str, songs: Sequence[API.Song] = None
|
||||||
) -> Optional[API.Playlist]:
|
) -> Optional[API.Playlist]:
|
||||||
return self._get_json(
|
return self._get_json(
|
||||||
self._make_url("createPlaylist"),
|
self._make_url("createPlaylist"),
|
||||||
|
@@ -107,7 +107,8 @@ class SublimeMusicApp(Gtk.Application):
|
|||||||
add_action("go-online", self.on_go_online)
|
add_action("go-online", self.on_go_online)
|
||||||
add_action("refresh-devices", self.on_refresh_devices)
|
add_action("refresh-devices", self.on_refresh_devices)
|
||||||
add_action(
|
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("mute-toggle", self.on_mute_toggle)
|
||||||
add_action(
|
add_action(
|
||||||
@@ -413,7 +414,8 @@ class SublimeMusicApp(Gtk.Application):
|
|||||||
# repeat song IDs.
|
# repeat song IDs.
|
||||||
metadatas: Iterable[Any] = [
|
metadatas: Iterable[Any] = [
|
||||||
self.dbus_manager.get_mpris_metadata(
|
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))
|
for i in range(len(self.app_config.state.play_queue))
|
||||||
]
|
]
|
||||||
@@ -457,7 +459,10 @@ class SublimeMusicApp(Gtk.Application):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def get_playlists(
|
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:
|
) -> GLib.Variant:
|
||||||
playlists_result = AdapterManager.get_playlists()
|
playlists_result = AdapterManager.get_playlists()
|
||||||
if not playlists_result.data_is_available:
|
if not playlists_result.data_is_available:
|
||||||
@@ -473,12 +478,15 @@ class SublimeMusicApp(Gtk.Application):
|
|||||||
"Modified": lambda p: p.changed,
|
"Modified": lambda p: p.changed,
|
||||||
}
|
}
|
||||||
playlists.sort(
|
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:
|
def make_playlist_tuple(p: Playlist) -> GLib.Variant:
|
||||||
cover_art_filename = AdapterManager.get_cover_art_uri(
|
cover_art_filename = AdapterManager.get_cover_art_uri(
|
||||||
p.cover_art, "file", allow_download=False,
|
p.cover_art,
|
||||||
|
"file",
|
||||||
|
allow_download=False,
|
||||||
).result()
|
).result()
|
||||||
return (f"/playlist/{p.id}", p.name, cover_art_filename or "")
|
return (f"/playlist/{p.id}", p.name, cover_art_filename or "")
|
||||||
|
|
||||||
@@ -570,9 +578,7 @@ class SublimeMusicApp(Gtk.Application):
|
|||||||
|
|
||||||
# ########## ACTION HANDLERS ########## #
|
# ########## ACTION HANDLERS ########## #
|
||||||
@dbus_propagate()
|
@dbus_propagate()
|
||||||
def on_refresh_window(
|
def on_refresh_window(self, _, state_updates: Dict[str, Any], force: bool = False):
|
||||||
self, _, state_updates: Dict[str, Any], force: bool = False,
|
|
||||||
):
|
|
||||||
if settings := state_updates.get("__settings__"):
|
if settings := state_updates.get("__settings__"):
|
||||||
for k, v in settings.items():
|
for k, v in settings.items():
|
||||||
setattr(self.app_config, k, v)
|
setattr(self.app_config, k, v)
|
||||||
@@ -879,9 +885,7 @@ class SublimeMusicApp(Gtk.Application):
|
|||||||
return
|
return
|
||||||
|
|
||||||
self.app_config.state.current_song_index -= len(before_current)
|
self.app_config.state.current_song_index -= len(before_current)
|
||||||
self.play_song(
|
self.play_song(self.app_config.state.current_song_index, reset=True)
|
||||||
self.app_config.state.current_song_index, reset=True,
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
self.app_config.state.current_song_index -= len(before_current)
|
self.app_config.state.current_song_index -= len(before_current)
|
||||||
self.update_window()
|
self.update_window()
|
||||||
@@ -1001,7 +1005,8 @@ class SublimeMusicApp(Gtk.Application):
|
|||||||
|
|
||||||
# ########## HELPER METHODS ########## #
|
# ########## HELPER METHODS ########## #
|
||||||
def show_configure_servers_dialog(
|
def show_configure_servers_dialog(
|
||||||
self, provider_config: Optional[ProviderConfiguration] = None,
|
self,
|
||||||
|
provider_config: Optional[ProviderConfiguration] = None,
|
||||||
):
|
):
|
||||||
"""Show the Connect to Server dialog."""
|
"""Show the Connect to Server dialog."""
|
||||||
dialog = ConfigureProviderDialog(self.window, provider_config)
|
dialog = ConfigureProviderDialog(self.window, provider_config)
|
||||||
@@ -1192,7 +1197,8 @@ class SublimeMusicApp(Gtk.Application):
|
|||||||
if artist := song.artist:
|
if artist := song.artist:
|
||||||
notification_lines.append(bleach.clean(artist.name))
|
notification_lines.append(bleach.clean(artist.name))
|
||||||
song_notification = Notify.Notification.new(
|
song_notification = Notify.Notification.new(
|
||||||
song.title, "\n".join(notification_lines),
|
song.title,
|
||||||
|
"\n".join(notification_lines),
|
||||||
)
|
)
|
||||||
song_notification.add_action(
|
song_notification.add_action(
|
||||||
"clicked",
|
"clicked",
|
||||||
|
@@ -18,9 +18,7 @@ def encode_path(path: Path) -> str:
|
|||||||
|
|
||||||
|
|
||||||
dataclasses_json.cfg.global_config.decoders[Path] = Path
|
dataclasses_json.cfg.global_config.decoders[Path] = Path
|
||||||
dataclasses_json.cfg.global_config.decoders[
|
dataclasses_json.cfg.global_config.decoders[Optional[Path]] = ( # type: ignore
|
||||||
Optional[Path] # type: ignore
|
|
||||||
] = (
|
|
||||||
lambda p: Path(p) if p else None
|
lambda p: Path(p) if p else None
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@@ -137,7 +137,13 @@ class DBusManager:
|
|||||||
|
|
||||||
return
|
return
|
||||||
self.do_on_method_call(
|
self.do_on_method_call(
|
||||||
connection, sender, path, interface, method, params, invocation,
|
connection,
|
||||||
|
sender,
|
||||||
|
path,
|
||||||
|
interface,
|
||||||
|
method,
|
||||||
|
params,
|
||||||
|
invocation,
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -153,7 +159,8 @@ class DBusManager:
|
|||||||
|
|
||||||
if type(value) == dict:
|
if type(value) == dict:
|
||||||
return GLib.Variant(
|
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(
|
variant_type = {list: "as", str: "s", int: "i", float: "d", bool: "b"}.get(
|
||||||
@@ -230,7 +237,8 @@ class DBusManager:
|
|||||||
"Rate": 1.0,
|
"Rate": 1.0,
|
||||||
"Shuffle": state.shuffle_on,
|
"Shuffle": state.shuffle_on,
|
||||||
"Metadata": self.get_mpris_metadata(
|
"Metadata": self.get_mpris_metadata(
|
||||||
state.current_song_index, state.play_queue,
|
state.current_song_index,
|
||||||
|
state.play_queue,
|
||||||
)
|
)
|
||||||
if state.current_song
|
if state.current_song
|
||||||
else {},
|
else {},
|
||||||
|
@@ -67,7 +67,8 @@ class PlayerManager:
|
|||||||
}
|
}
|
||||||
|
|
||||||
def change_settings(
|
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
|
self.config = config
|
||||||
for player_type, player in self.players.items():
|
for player_type, player in self.players.items():
|
||||||
|
@@ -164,10 +164,12 @@ class AlbumsPanel(Gtk.Box):
|
|||||||
scrolled_window = Gtk.ScrolledWindow()
|
scrolled_window = Gtk.ScrolledWindow()
|
||||||
self.grid = AlbumsGrid()
|
self.grid = AlbumsGrid()
|
||||||
self.grid.connect(
|
self.grid.connect(
|
||||||
"song-clicked", lambda _, *args: self.emit("song-clicked", *args),
|
"song-clicked",
|
||||||
|
lambda _, *args: self.emit("song-clicked", *args),
|
||||||
)
|
)
|
||||||
self.grid.connect(
|
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("cover-clicked", self.on_grid_cover_clicked)
|
||||||
self.grid.connect("num-pages-changed", self.on_grid_num_pages_changed)
|
self.grid.connect("num-pages-changed", self.on_grid_num_pages_changed)
|
||||||
@@ -195,7 +197,9 @@ class AlbumsPanel(Gtk.Box):
|
|||||||
return combo, store
|
return combo, store
|
||||||
|
|
||||||
def populate_genre_combo(
|
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():
|
if not AdapterManager.can_get_genres():
|
||||||
self.updating_query = False
|
self.updating_query = False
|
||||||
@@ -464,13 +468,17 @@ class AlbumsPanel(Gtk.Box):
|
|||||||
|
|
||||||
def on_grid_cover_clicked(self, grid: Any, id: str):
|
def on_grid_cover_clicked(self, grid: Any, id: str):
|
||||||
self.emit(
|
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):
|
def on_show_count_dropdown_change(self, combo: Gtk.ComboBox):
|
||||||
show_count = int(self.get_id(combo) or 30)
|
show_count = int(self.get_id(combo) or 30)
|
||||||
self.emit(
|
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):
|
def emit_if_not_updating(self, *args):
|
||||||
@@ -483,7 +491,7 @@ class AlbumsGrid(Gtk.Overlay):
|
|||||||
"""Defines the albums panel."""
|
"""Defines the albums panel."""
|
||||||
|
|
||||||
__gsignals__ = {
|
__gsignals__ = {
|
||||||
"cover-clicked": (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (object,),),
|
"cover-clicked": (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (object,)),
|
||||||
"refresh-window": (
|
"refresh-window": (
|
||||||
GObject.SignalFlags.RUN_FIRST,
|
GObject.SignalFlags.RUN_FIRST,
|
||||||
GObject.TYPE_NONE,
|
GObject.TYPE_NONE,
|
||||||
@@ -789,9 +797,7 @@ class AlbumsGrid(Gtk.Overlay):
|
|||||||
new_items_per_row = min((rect.width // 230), 7)
|
new_items_per_row = min((rect.width // 230), 7)
|
||||||
if new_items_per_row != self.items_per_row:
|
if new_items_per_row != self.items_per_row:
|
||||||
self.items_per_row = new_items_per_row
|
self.items_per_row = new_items_per_row
|
||||||
self.detail_box_inner.set_size_request(
|
self.detail_box_inner.set_size_request(self.items_per_row * 230 - 10, -1)
|
||||||
self.items_per_row * 230 - 10, -1,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.reflow_grids(
|
self.reflow_grids(
|
||||||
force_reload_from_master=True,
|
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
|
# Just remove everything and re-add all of the items. It's not worth trying
|
||||||
# to diff in this case.
|
# to diff in this case.
|
||||||
self.list_store_top.splice(
|
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(
|
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:
|
elif selected_index or entries_before_fold != self.page_size:
|
||||||
# This case handles when the selection changes and the entries need to be
|
# 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]
|
model = self.list_store_top[relative_selected_index]
|
||||||
detail_element = AlbumWithSongs(model.album, cover_art_size=300)
|
detail_element = AlbumWithSongs(model.album, cover_art_size=300)
|
||||||
detail_element.connect(
|
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)
|
detail_element.connect("song-selected", lambda *a: None)
|
||||||
|
|
||||||
|
@@ -40,10 +40,12 @@ class ArtistsPanel(Gtk.Paned):
|
|||||||
|
|
||||||
self.artist_detail_panel = ArtistDetailPanel()
|
self.artist_detail_panel = ArtistDetailPanel()
|
||||||
self.artist_detail_panel.connect(
|
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(
|
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)
|
self.pack2(self.artist_detail_panel, True, False)
|
||||||
|
|
||||||
@@ -315,7 +317,8 @@ class ArtistDetailPanel(Gtk.Box):
|
|||||||
self.album_list_scrolledwindow = Gtk.ScrolledWindow()
|
self.album_list_scrolledwindow = Gtk.ScrolledWindow()
|
||||||
self.albums_list = AlbumsListWithSongs()
|
self.albums_list = AlbumsListWithSongs()
|
||||||
self.albums_list.connect(
|
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.album_list_scrolledwindow.add(self.albums_list)
|
||||||
self.pack_start(self.album_list_scrolledwindow, True, True, 0)
|
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.play_shuffle_buttons.show_all()
|
||||||
|
|
||||||
self.update_artist_artwork(
|
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():
|
for c in self.error_container.get_children():
|
||||||
@@ -489,24 +494,31 @@ class ArtistDetailPanel(Gtk.Box):
|
|||||||
# =========================================================================
|
# =========================================================================
|
||||||
def on_view_refresh_click(self, *args):
|
def on_view_refresh_click(self, *args):
|
||||||
self.update_artist_view(
|
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, _):
|
def on_download_all_click(self, _):
|
||||||
AdapterManager.batch_download_songs(
|
AdapterManager.batch_download_songs(
|
||||||
self.get_artist_song_ids(),
|
self.get_artist_song_ids(),
|
||||||
before_download=lambda _: self.update_artist_view(
|
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(
|
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, _):
|
def on_play_all_clicked(self, _):
|
||||||
songs = self.get_artist_song_ids()
|
songs = self.get_artist_song_ids()
|
||||||
self.emit(
|
self.emit(
|
||||||
"song-clicked", 0, songs, {"force_shuffle_state": False},
|
"song-clicked",
|
||||||
|
0,
|
||||||
|
songs,
|
||||||
|
{"force_shuffle_state": False},
|
||||||
)
|
)
|
||||||
|
|
||||||
def on_shuffle_all_button(self, _):
|
def on_shuffle_all_button(self, _):
|
||||||
@@ -633,7 +645,8 @@ class AlbumsListWithSongs(Gtk.Overlay):
|
|||||||
for album in self.albums:
|
for album in self.albums:
|
||||||
album_with_songs = AlbumWithSongs(album, show_artist_name=False)
|
album_with_songs = AlbumWithSongs(album, show_artist_name=False)
|
||||||
album_with_songs.connect(
|
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.connect("song-selected", self.on_song_selected)
|
||||||
album_with_songs.show_all()
|
album_with_songs.show_all()
|
||||||
|
@@ -37,10 +37,12 @@ class BrowsePanel(Gtk.Overlay):
|
|||||||
|
|
||||||
self.root_directory_listing = ListAndDrilldown()
|
self.root_directory_listing = ListAndDrilldown()
|
||||||
self.root_directory_listing.connect(
|
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(
|
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)
|
window_box.add(self.root_directory_listing)
|
||||||
|
|
||||||
@@ -88,7 +90,8 @@ class BrowsePanel(Gtk.Overlay):
|
|||||||
while current_dir_id:
|
while current_dir_id:
|
||||||
try:
|
try:
|
||||||
directory = AdapterManager.get_directory(
|
directory = AdapterManager.get_directory(
|
||||||
current_dir_id, before_download=self.spinner.show,
|
current_dir_id,
|
||||||
|
before_download=self.spinner.show,
|
||||||
).result()
|
).result()
|
||||||
except CacheMissError as e:
|
except CacheMissError as e:
|
||||||
directory = cast(API.Directory, e.partial_data)
|
directory = cast(API.Directory, e.partial_data)
|
||||||
@@ -128,10 +131,12 @@ class ListAndDrilldown(Gtk.Paned):
|
|||||||
|
|
||||||
self.list = MusicDirectoryList()
|
self.list = MusicDirectoryList()
|
||||||
self.list.connect(
|
self.list.connect(
|
||||||
"song-clicked", lambda _, *args: self.emit("song-clicked", *args),
|
"song-clicked",
|
||||||
|
lambda _, *args: self.emit("song-clicked", *args),
|
||||||
)
|
)
|
||||||
self.list.connect(
|
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)
|
self.pack1(self.list, False, False)
|
||||||
|
|
||||||
@@ -163,7 +168,8 @@ class ListAndDrilldown(Gtk.Paned):
|
|||||||
if len(children) == 0:
|
if len(children) == 0:
|
||||||
drilldown = ListAndDrilldown()
|
drilldown = ListAndDrilldown()
|
||||||
drilldown.connect(
|
drilldown.connect(
|
||||||
"song-clicked", lambda _, *args: self.emit("song-clicked", *args),
|
"song-clicked",
|
||||||
|
lambda _, *args: self.emit("song-clicked", *args),
|
||||||
)
|
)
|
||||||
drilldown.connect(
|
drilldown.connect(
|
||||||
"refresh-window",
|
"refresh-window",
|
||||||
@@ -277,7 +283,9 @@ class MusicDirectoryList(Gtk.Box):
|
|||||||
self.directory_id = directory_id or self.directory_id
|
self.directory_id = directory_id or self.directory_id
|
||||||
self.selected_id = selected_id or self.selected_id
|
self.selected_id = selected_id or self.selected_id
|
||||||
self.update_store(
|
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:
|
if app_config:
|
||||||
@@ -428,7 +436,8 @@ class MusicDirectoryList(Gtk.Box):
|
|||||||
# ==================================================================================
|
# ==================================================================================
|
||||||
def create_row(self, model: DrilldownElement) -> Gtk.ListBoxRow:
|
def create_row(self, model: DrilldownElement) -> Gtk.ListBoxRow:
|
||||||
row = 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 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
|
||||||
rowbox.add(
|
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
|
if event.button == 3: # Right click
|
||||||
clicked_path = tree.get_path_at_pos(event.x, event.y)
|
clicked_path = tree.get_path_at_pos(event.x, event.y)
|
||||||
if not clicked_path:
|
if not clicked_path:
|
||||||
|
@@ -15,7 +15,7 @@ from .spinner_image import SpinnerImage
|
|||||||
|
|
||||||
class AlbumWithSongs(Gtk.Box):
|
class AlbumWithSongs(Gtk.Box):
|
||||||
__gsignals__ = {
|
__gsignals__ = {
|
||||||
"song-selected": (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (),),
|
"song-selected": (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, ()),
|
||||||
"song-clicked": (
|
"song-clicked": (
|
||||||
GObject.SignalFlags.RUN_FIRST,
|
GObject.SignalFlags.RUN_FIRST,
|
||||||
GObject.TYPE_NONE,
|
GObject.TYPE_NONE,
|
||||||
@@ -122,7 +122,9 @@ class AlbumWithSongs(Gtk.Box):
|
|||||||
|
|
||||||
album_details.add(
|
album_details.add(
|
||||||
Gtk.Label(
|
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):
|
def play_btn_clicked(self, btn: Any):
|
||||||
song_ids = [x[-1] for x in self.album_song_store]
|
song_ids = [x[-1] for x in self.album_song_store]
|
||||||
self.emit(
|
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):
|
def shuffle_btn_clicked(self, btn: Any):
|
||||||
|
@@ -35,7 +35,7 @@ class SpinnerImage(Gtk.Overlay):
|
|||||||
self.filename = filename
|
self.filename = filename
|
||||||
if self.image_size is not None and filename:
|
if self.image_size is not None and filename:
|
||||||
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(
|
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)
|
self.image.set_from_pixbuf(pixbuf)
|
||||||
else:
|
else:
|
||||||
|
@@ -24,14 +24,14 @@ class MainWindow(Gtk.ApplicationWindow):
|
|||||||
GObject.TYPE_NONE,
|
GObject.TYPE_NONE,
|
||||||
(int, object, object),
|
(int, object, object),
|
||||||
),
|
),
|
||||||
"songs-removed": (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (object,),),
|
"songs-removed": (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (object,)),
|
||||||
"refresh-window": (
|
"refresh-window": (
|
||||||
GObject.SignalFlags.RUN_FIRST,
|
GObject.SignalFlags.RUN_FIRST,
|
||||||
GObject.TYPE_NONE,
|
GObject.TYPE_NONE,
|
||||||
(object, bool),
|
(object, bool),
|
||||||
),
|
),
|
||||||
"notification-closed": (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (),),
|
"notification-closed": (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, ()),
|
||||||
"go-to": (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (str, str),),
|
"go-to": (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (str, str)),
|
||||||
}
|
}
|
||||||
|
|
||||||
_updating_settings: bool = False
|
_updating_settings: bool = False
|
||||||
@@ -100,7 +100,8 @@ class MainWindow(Gtk.ApplicationWindow):
|
|||||||
"songs-removed", lambda _, *a: self.emit("songs-removed", *a)
|
"songs-removed", lambda _, *a: self.emit("songs-removed", *a)
|
||||||
)
|
)
|
||||||
self.player_controls.connect(
|
self.player_controls.connect(
|
||||||
"refresh-window", lambda _, *args: self.emit("refresh-window", *args),
|
"refresh-window",
|
||||||
|
lambda _, *args: self.emit("refresh-window", *args),
|
||||||
)
|
)
|
||||||
flowbox.pack_start(self.player_controls, False, True, 0)
|
flowbox.pack_start(self.player_controls, False, True, 0)
|
||||||
|
|
||||||
@@ -167,7 +168,8 @@ class MainWindow(Gtk.ApplicationWindow):
|
|||||||
f"{icon_basename}-{icon_status}-symbolic"
|
f"{icon_basename}-{icon_status}-symbolic"
|
||||||
)
|
)
|
||||||
self.connection_status_icon.set_from_icon_name(
|
self.connection_status_icon.set_from_icon_name(
|
||||||
f"server-{icon_status}-symbolic", Gtk.IconSize.BUTTON,
|
f"server-{icon_status}-symbolic",
|
||||||
|
Gtk.IconSize.BUTTON,
|
||||||
)
|
)
|
||||||
self.connection_status_label.set_text(status_label)
|
self.connection_status_label.set_text(status_label)
|
||||||
self.connected_status_box.show_all()
|
self.connected_status_box.show_all()
|
||||||
@@ -194,7 +196,10 @@ class MainWindow(Gtk.ApplicationWindow):
|
|||||||
|
|
||||||
for provider in sorted(other_providers, key=lambda p: p.name.lower()):
|
for provider in sorted(other_providers, key=lambda p: p.name.lower()):
|
||||||
self.provider_options_box.pack_start(
|
self.provider_options_box.pack_start(
|
||||||
self._create_switch_provider_button(provider), False, True, 0,
|
self._create_switch_provider_button(provider),
|
||||||
|
False,
|
||||||
|
True,
|
||||||
|
0,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.provider_options_box.show_all()
|
self.provider_options_box.show_all()
|
||||||
@@ -489,17 +494,21 @@ class MainWindow(Gtk.ApplicationWindow):
|
|||||||
|
|
||||||
def _on_retry_all_clicked(self, _):
|
def _on_retry_all_clicked(self, _):
|
||||||
AdapterManager.batch_download_songs(
|
AdapterManager.batch_download_songs(
|
||||||
self._failed_downloads, lambda _: None, lambda _: None,
|
self._failed_downloads,
|
||||||
|
lambda _: None,
|
||||||
|
lambda _: None,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _create_stack(self, **kwargs: Gtk.Widget) -> Gtk.Stack:
|
def _create_stack(self, **kwargs: Gtk.Widget) -> Gtk.Stack:
|
||||||
stack = Gtk.Stack()
|
stack = Gtk.Stack()
|
||||||
for name, child in kwargs.items():
|
for name, child in kwargs.items():
|
||||||
child.connect(
|
child.connect(
|
||||||
"song-clicked", lambda _, *args: self.emit("song-clicked", *args),
|
"song-clicked",
|
||||||
|
lambda _, *args: self.emit("song-clicked", *args),
|
||||||
)
|
)
|
||||||
child.connect(
|
child.connect(
|
||||||
"refresh-window", lambda _, *args: self.emit("refresh-window", *args),
|
"refresh-window",
|
||||||
|
lambda _, *args: self.emit("refresh-window", *args),
|
||||||
)
|
)
|
||||||
stack.add_titled(child, name.lower(), name)
|
stack.add_titled(child, name.lower(), name)
|
||||||
return stack
|
return stack
|
||||||
@@ -671,7 +680,8 @@ class MainWindow(Gtk.ApplicationWindow):
|
|||||||
current_downloads_header = Gtk.Box()
|
current_downloads_header = Gtk.Box()
|
||||||
current_downloads_header.add(
|
current_downloads_header.add(
|
||||||
current_downloads_label := Gtk.Label(
|
current_downloads_label := Gtk.Label(
|
||||||
label="Current Downloads", name="menu-header",
|
label="Current Downloads",
|
||||||
|
name="menu-header",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
current_downloads_label.get_style_context().add_class("menu-label")
|
current_downloads_label.get_style_context().add_class("menu-label")
|
||||||
@@ -767,7 +777,8 @@ class MainWindow(Gtk.ApplicationWindow):
|
|||||||
vbox.add(Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL))
|
vbox.add(Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL))
|
||||||
|
|
||||||
music_provider_button = self._create_model_button(
|
music_provider_button = self._create_model_button(
|
||||||
"Switch Music Provider", menu_name="switch-provider",
|
"Switch Music Provider",
|
||||||
|
menu_name="switch-provider",
|
||||||
)
|
)
|
||||||
vbox.add(music_provider_button)
|
vbox.add(music_provider_button)
|
||||||
|
|
||||||
@@ -858,7 +869,8 @@ class MainWindow(Gtk.ApplicationWindow):
|
|||||||
self.search_popup = Gtk.PopoverMenu(modal=False)
|
self.search_popup = Gtk.PopoverMenu(modal=False)
|
||||||
|
|
||||||
results_scrollbox = Gtk.ScrolledWindow(
|
results_scrollbox = Gtk.ScrolledWindow(
|
||||||
min_content_width=500, min_content_height=700,
|
min_content_width=500,
|
||||||
|
min_content_height=700,
|
||||||
)
|
)
|
||||||
|
|
||||||
def make_search_result_header(text: str) -> Gtk.Label:
|
def make_search_result_header(text: str) -> Gtk.Label:
|
||||||
@@ -867,7 +879,8 @@ class MainWindow(Gtk.ApplicationWindow):
|
|||||||
return label
|
return label
|
||||||
|
|
||||||
search_results_box = Gtk.Box(
|
search_results_box = Gtk.Box(
|
||||||
orientation=Gtk.Orientation.VERTICAL, name="search-results",
|
orientation=Gtk.Orientation.VERTICAL,
|
||||||
|
name="search-results",
|
||||||
)
|
)
|
||||||
self.search_results_loading = Gtk.Spinner(active=False, name="search-spinner")
|
self.search_results_loading = Gtk.Spinner(active=False, name="search-spinner")
|
||||||
search_results_box.add(self.search_results_loading)
|
search_results_box.add(self.search_results_loading)
|
||||||
@@ -1155,8 +1168,8 @@ class MainWindow(Gtk.ApplicationWindow):
|
|||||||
|
|
||||||
class DownloadStatusBox(Gtk.Box):
|
class DownloadStatusBox(Gtk.Box):
|
||||||
__gsignals__ = {
|
__gsignals__ = {
|
||||||
"cancel-clicked": (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (str,),),
|
"cancel-clicked": (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (str,)),
|
||||||
"retry-clicked": (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (str,),),
|
"retry-clicked": (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (str,)),
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, song_id: str):
|
def __init__(self, song_id: str):
|
||||||
|
@@ -21,15 +21,15 @@ class PlayerControls(Gtk.ActionBar):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
__gsignals__ = {
|
__gsignals__ = {
|
||||||
"song-scrub": (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (float,),),
|
"song-scrub": (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (float,)),
|
||||||
"volume-change": (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (float,),),
|
"volume-change": (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (float,)),
|
||||||
"device-update": (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (str,),),
|
"device-update": (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (str,)),
|
||||||
"song-clicked": (
|
"song-clicked": (
|
||||||
GObject.SignalFlags.RUN_FIRST,
|
GObject.SignalFlags.RUN_FIRST,
|
||||||
GObject.TYPE_NONE,
|
GObject.TYPE_NONE,
|
||||||
(int, object, object),
|
(int, object, object),
|
||||||
),
|
),
|
||||||
"songs-removed": (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (object,),),
|
"songs-removed": (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (object,)),
|
||||||
"refresh-window": (
|
"refresh-window": (
|
||||||
GObject.SignalFlags.RUN_FIRST,
|
GObject.SignalFlags.RUN_FIRST,
|
||||||
GObject.TYPE_NONE,
|
GObject.TYPE_NONE,
|
||||||
@@ -242,12 +242,16 @@ class PlayerControls(Gtk.ActionBar):
|
|||||||
return f"<b>{title}</b>\n{util.dot_join(album, artist)}"
|
return f"<b>{title}</b>\n{util.dot_join(album, artist)}"
|
||||||
|
|
||||||
def make_idle_index_capturing_function(
|
def make_idle_index_capturing_function(
|
||||||
idx: int, order_tok: int, fn: Callable[[int, int, Any], None],
|
idx: int,
|
||||||
|
order_tok: int,
|
||||||
|
fn: Callable[[int, int, Any], None],
|
||||||
) -> Callable[[Result], None]:
|
) -> Callable[[Result], None]:
|
||||||
return lambda f: GLib.idle_add(fn, idx, order_tok, f.result())
|
return lambda f: GLib.idle_add(fn, idx, order_tok, f.result())
|
||||||
|
|
||||||
def on_cover_art_future_done(
|
def on_cover_art_future_done(
|
||||||
idx: int, order_token: int, cover_art_filename: str,
|
idx: int,
|
||||||
|
order_token: int,
|
||||||
|
cover_art_filename: str,
|
||||||
):
|
):
|
||||||
if order_token != self.play_queue_update_order_token:
|
if order_token != self.play_queue_update_order_token:
|
||||||
return
|
return
|
||||||
@@ -269,9 +273,7 @@ class PlayerControls(Gtk.ActionBar):
|
|||||||
# The cover art is already cached.
|
# The cover art is already cached.
|
||||||
return cover_art_result.result()
|
return cover_art_result.result()
|
||||||
|
|
||||||
def on_song_details_future_done(
|
def on_song_details_future_done(idx: int, order_token: int, song_details: Song):
|
||||||
idx: int, order_token: int, song_details: Song,
|
|
||||||
):
|
|
||||||
if order_token != self.play_queue_update_order_token:
|
if order_token != self.play_queue_update_order_token:
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -559,7 +561,8 @@ class PlayerControls(Gtk.ActionBar):
|
|||||||
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
|
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
|
||||||
|
|
||||||
self.album_art = SpinnerImage(
|
self.album_art = SpinnerImage(
|
||||||
image_name="player-controls-album-artwork", image_size=70,
|
image_name="player-controls-album-artwork",
|
||||||
|
image_size=70,
|
||||||
)
|
)
|
||||||
box.pack_start(self.album_art, False, False, 0)
|
box.pack_start(self.album_art, False, False, 0)
|
||||||
|
|
||||||
@@ -691,12 +694,16 @@ class PlayerControls(Gtk.ActionBar):
|
|||||||
self.device_popover.set_relative_to(self.device_button)
|
self.device_popover.set_relative_to(self.device_button)
|
||||||
|
|
||||||
device_popover_box = Gtk.Box(
|
device_popover_box = Gtk.Box(
|
||||||
orientation=Gtk.Orientation.VERTICAL, name="device-popover-box",
|
orientation=Gtk.Orientation.VERTICAL,
|
||||||
|
name="device-popover-box",
|
||||||
)
|
)
|
||||||
device_popover_header = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
|
device_popover_header = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
|
||||||
|
|
||||||
self.popover_label = Gtk.Label(
|
self.popover_label = Gtk.Label(
|
||||||
label="<b>Devices</b>", use_markup=True, halign=Gtk.Align.START, margin=5,
|
label="<b>Devices</b>",
|
||||||
|
use_markup=True,
|
||||||
|
halign=Gtk.Align.START,
|
||||||
|
margin=5,
|
||||||
)
|
)
|
||||||
device_popover_header.add(self.popover_label)
|
device_popover_header.add(self.popover_label)
|
||||||
|
|
||||||
@@ -748,7 +755,8 @@ class PlayerControls(Gtk.ActionBar):
|
|||||||
|
|
||||||
play_queue_loading_overlay = Gtk.Overlay()
|
play_queue_loading_overlay = Gtk.Overlay()
|
||||||
play_queue_scrollbox = Gtk.ScrolledWindow(
|
play_queue_scrollbox = Gtk.ScrolledWindow(
|
||||||
min_content_height=600, min_content_width=400,
|
min_content_height=600,
|
||||||
|
min_content_width=400,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.play_queue_store = Gtk.ListStore(
|
self.play_queue_store = Gtk.ListStore(
|
||||||
@@ -759,7 +767,9 @@ class PlayerControls(Gtk.ActionBar):
|
|||||||
str, # song ID
|
str, # song ID
|
||||||
)
|
)
|
||||||
self.play_queue_list = Gtk.TreeView(
|
self.play_queue_list = Gtk.TreeView(
|
||||||
model=self.play_queue_store, reorderable=True, headers_visible=False,
|
model=self.play_queue_store,
|
||||||
|
reorderable=True,
|
||||||
|
headers_visible=False,
|
||||||
)
|
)
|
||||||
selection = self.play_queue_list.get_selection()
|
selection = self.play_queue_list.get_selection()
|
||||||
selection.set_mode(Gtk.SelectionMode.MULTIPLE)
|
selection.set_mode(Gtk.SelectionMode.MULTIPLE)
|
||||||
|
@@ -104,10 +104,12 @@ class PlaylistsPanel(Gtk.Paned):
|
|||||||
|
|
||||||
self.playlist_detail_panel = PlaylistDetailPanel()
|
self.playlist_detail_panel = PlaylistDetailPanel()
|
||||||
self.playlist_detail_panel.connect(
|
self.playlist_detail_panel.connect(
|
||||||
"song-clicked", lambda _, *args: self.emit("song-clicked", *args),
|
"song-clicked",
|
||||||
|
lambda _, *args: self.emit("song-clicked", *args),
|
||||||
)
|
)
|
||||||
self.playlist_detail_panel.connect(
|
self.playlist_detail_panel.connect(
|
||||||
"refresh-window", lambda _, *args: self.emit("refresh-window", *args),
|
"refresh-window",
|
||||||
|
lambda _, *args: self.emit("refresh-window", *args),
|
||||||
)
|
)
|
||||||
self.pack2(self.playlist_detail_panel, True, False)
|
self.pack2(self.playlist_detail_panel, True, False)
|
||||||
|
|
||||||
@@ -158,7 +160,7 @@ class PlaylistList(Gtk.Box):
|
|||||||
|
|
||||||
loading_new_playlist = Gtk.ListBox()
|
loading_new_playlist = Gtk.ListBox()
|
||||||
|
|
||||||
self.loading_indicator = Gtk.ListBoxRow(activatable=False, selectable=False,)
|
self.loading_indicator = Gtk.ListBoxRow(activatable=False, selectable=False)
|
||||||
loading_spinner = Gtk.Spinner(name="playlist-list-spinner", active=True)
|
loading_spinner = Gtk.Spinner(name="playlist-list-spinner", active=True)
|
||||||
self.loading_indicator.add(loading_spinner)
|
self.loading_indicator.add(loading_spinner)
|
||||||
loading_new_playlist.add(self.loading_indicator)
|
loading_new_playlist.add(self.loading_indicator)
|
||||||
@@ -364,13 +366,17 @@ class PlaylistDetailPanel(Gtk.Overlay):
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.play_all_button = IconButton(
|
self.play_all_button = IconButton(
|
||||||
"media-playback-start-symbolic", label="Play All", relief=True,
|
"media-playback-start-symbolic",
|
||||||
|
label="Play All",
|
||||||
|
relief=True,
|
||||||
)
|
)
|
||||||
self.play_all_button.connect("clicked", self.on_play_all_clicked)
|
self.play_all_button.connect("clicked", self.on_play_all_clicked)
|
||||||
self.play_shuffle_buttons.pack_start(self.play_all_button, False, False, 0)
|
self.play_shuffle_buttons.pack_start(self.play_all_button, False, False, 0)
|
||||||
|
|
||||||
self.shuffle_all_button = IconButton(
|
self.shuffle_all_button = IconButton(
|
||||||
"media-playlist-shuffle-symbolic", label="Shuffle All", relief=True,
|
"media-playlist-shuffle-symbolic",
|
||||||
|
label="Shuffle All",
|
||||||
|
relief=True,
|
||||||
)
|
)
|
||||||
self.shuffle_all_button.connect("clicked", self.on_shuffle_all_button)
|
self.shuffle_all_button.connect("clicked", self.on_shuffle_all_button)
|
||||||
self.play_shuffle_buttons.pack_start(self.shuffle_all_button, False, False, 5)
|
self.play_shuffle_buttons.pack_start(self.shuffle_all_button, False, False, 5)
|
||||||
@@ -908,7 +914,7 @@ class PlaylistDetailPanel(Gtk.Overlay):
|
|||||||
self.playlist_artwork.set_loading(False)
|
self.playlist_artwork.set_loading(False)
|
||||||
self.playlist_view_loading_box.hide()
|
self.playlist_view_loading_box.hide()
|
||||||
|
|
||||||
def make_label(self, text: str = None, name: str = None, **params,) -> Gtk.Label:
|
def make_label(self, text: str = None, name: str = None, **params) -> Gtk.Label:
|
||||||
return Gtk.Label(
|
return Gtk.Label(
|
||||||
label=text,
|
label=text,
|
||||||
name=name,
|
name=name,
|
||||||
@@ -919,7 +925,10 @@ class PlaylistDetailPanel(Gtk.Overlay):
|
|||||||
|
|
||||||
@util.async_callback(AdapterManager.get_playlist_details)
|
@util.async_callback(AdapterManager.get_playlist_details)
|
||||||
def _update_playlist_order(
|
def _update_playlist_order(
|
||||||
self, playlist: API.Playlist, app_config: AppConfiguration, **kwargs,
|
self,
|
||||||
|
playlist: API.Playlist,
|
||||||
|
app_config: AppConfiguration,
|
||||||
|
**kwargs,
|
||||||
):
|
):
|
||||||
self.playlist_view_loading_box.show_all()
|
self.playlist_view_loading_box.show_all()
|
||||||
update_playlist_future = AdapterManager.update_playlist(
|
update_playlist_future = AdapterManager.update_playlist(
|
||||||
|
@@ -87,7 +87,9 @@ class UIState:
|
|||||||
self.name = "Rock"
|
self.name = "Rock"
|
||||||
|
|
||||||
current_album_search_query: AlbumSearchQuery = AlbumSearchQuery(
|
current_album_search_query: AlbumSearchQuery = AlbumSearchQuery(
|
||||||
AlbumSearchQuery.Type.RANDOM, genre=_DefaultGenre(), year_range=(2010, 2020),
|
AlbumSearchQuery.Type.RANDOM,
|
||||||
|
genre=_DefaultGenre(),
|
||||||
|
year_range=(2010, 2020),
|
||||||
)
|
)
|
||||||
|
|
||||||
active_playlist_id: Optional[str] = None
|
active_playlist_id: Optional[str] = None
|
||||||
|
@@ -45,7 +45,7 @@ def format_song_duration(duration_secs: Union[int, timedelta, None]) -> str:
|
|||||||
return f"{duration_secs // 60}:{duration_secs % 60:02}"
|
return f"{duration_secs // 60}:{duration_secs % 60:02}"
|
||||||
|
|
||||||
|
|
||||||
def pluralize(string: str, number: int, pluralized_form: str = None,) -> str:
|
def pluralize(string: str, number: int, pluralized_form: str = None) -> str:
|
||||||
"""
|
"""
|
||||||
Pluralize the given string given the count as a number.
|
Pluralize the given string given the count as a number.
|
||||||
|
|
||||||
@@ -205,7 +205,8 @@ def show_song_popover(
|
|||||||
def do_on_remove_downloads_click(_: Any):
|
def do_on_remove_downloads_click(_: Any):
|
||||||
AdapterManager.cancel_download_songs(song_ids)
|
AdapterManager.cancel_download_songs(song_ids)
|
||||||
AdapterManager.batch_delete_cached_songs(
|
AdapterManager.batch_delete_cached_songs(
|
||||||
song_ids, on_song_delete=on_download_state_change,
|
song_ids,
|
||||||
|
on_song_delete=on_download_state_change,
|
||||||
)
|
)
|
||||||
on_remove_downloads_click()
|
on_remove_downloads_click()
|
||||||
|
|
||||||
@@ -438,7 +439,10 @@ def async_callback(
|
|||||||
GLib.idle_add(fn)
|
GLib.idle_add(fn)
|
||||||
|
|
||||||
result: Result = future_fn(
|
result: Result = future_fn(
|
||||||
*args, before_download=on_before_download, force=force, **kwargs,
|
*args,
|
||||||
|
before_download=on_before_download,
|
||||||
|
force=force,
|
||||||
|
**kwargs,
|
||||||
)
|
)
|
||||||
result.add_done_callback(
|
result.add_done_callback(
|
||||||
functools.partial(future_callback, result.data_is_available)
|
functools.partial(future_callback, result.data_is_available)
|
||||||
|
@@ -170,7 +170,8 @@ def test_search_result_update():
|
|||||||
|
|
||||||
search_results2 = SearchResult(query="foo")
|
search_results2 = SearchResult(query="foo")
|
||||||
search_results2.add_results(
|
search_results2.add_results(
|
||||||
"artists", [SubsonicAPI.ArtistAndArtistInfo(id="3", name="foo2")],
|
"artists",
|
||||||
|
[SubsonicAPI.ArtistAndArtistInfo(id="3", name="foo2")],
|
||||||
)
|
)
|
||||||
|
|
||||||
search_results1.update(search_results2)
|
search_results1.update(search_results2)
|
||||||
|
@@ -90,7 +90,8 @@ def cache_adapter(tmp_path: Path):
|
|||||||
|
|
||||||
|
|
||||||
def mock_data_files(
|
def mock_data_files(
|
||||||
request_name: str, mode: str = "r",
|
request_name: str,
|
||||||
|
mode: str = "r",
|
||||||
) -> Generator[Tuple[Path, Any], None, None]:
|
) -> Generator[Tuple[Path, Any], None, None]:
|
||||||
"""
|
"""
|
||||||
Yields all of the files in the mock_data directory that start with ``request_name``.
|
Yields all of the files in the mock_data directory that start with ``request_name``.
|
||||||
@@ -272,7 +273,9 @@ def test_caching_get_playlist_then_details(cache_adapter: FilesystemAdapter):
|
|||||||
|
|
||||||
# Simulate getting playlist details for id=1, then id=2
|
# Simulate getting playlist details for id=1, then id=2
|
||||||
cache_adapter.ingest_new_data(
|
cache_adapter.ingest_new_data(
|
||||||
KEYS.PLAYLIST_DETAILS, "1", SubsonicAPI.Playlist("1", "test1"),
|
KEYS.PLAYLIST_DETAILS,
|
||||||
|
"1",
|
||||||
|
SubsonicAPI.Playlist("1", "test1"),
|
||||||
)
|
)
|
||||||
|
|
||||||
cache_adapter.ingest_new_data(
|
cache_adapter.ingest_new_data(
|
||||||
@@ -308,7 +311,9 @@ def test_invalidate_playlist(cache_adapter: FilesystemAdapter):
|
|||||||
[SubsonicAPI.Playlist("1", "test1"), SubsonicAPI.Playlist("2", "test2")],
|
[SubsonicAPI.Playlist("1", "test1"), SubsonicAPI.Playlist("2", "test2")],
|
||||||
)
|
)
|
||||||
cache_adapter.ingest_new_data(
|
cache_adapter.ingest_new_data(
|
||||||
KEYS.COVER_ART_FILE, "pl_test1", MOCK_ALBUM_ART,
|
KEYS.COVER_ART_FILE,
|
||||||
|
"pl_test1",
|
||||||
|
MOCK_ALBUM_ART,
|
||||||
)
|
)
|
||||||
cache_adapter.ingest_new_data(
|
cache_adapter.ingest_new_data(
|
||||||
KEYS.PLAYLIST_DETAILS,
|
KEYS.PLAYLIST_DETAILS,
|
||||||
@@ -316,7 +321,9 @@ def test_invalidate_playlist(cache_adapter: FilesystemAdapter):
|
|||||||
SubsonicAPI.Playlist("2", "test2", cover_art="pl_2", songs=[]),
|
SubsonicAPI.Playlist("2", "test2", cover_art="pl_2", songs=[]),
|
||||||
)
|
)
|
||||||
cache_adapter.ingest_new_data(
|
cache_adapter.ingest_new_data(
|
||||||
KEYS.COVER_ART_FILE, "pl_2", MOCK_ALBUM_ART2,
|
KEYS.COVER_ART_FILE,
|
||||||
|
"pl_2",
|
||||||
|
MOCK_ALBUM_ART2,
|
||||||
)
|
)
|
||||||
|
|
||||||
stale_uri_1 = cache_adapter.get_cover_art_uri("pl_test1", "file", size=300)
|
stale_uri_1 = cache_adapter.get_cover_art_uri("pl_test1", "file", size=300)
|
||||||
@@ -362,7 +369,9 @@ def test_invalidate_song_file(cache_adapter: FilesystemAdapter):
|
|||||||
cache_adapter.ingest_new_data(KEYS.SONG, "2", MOCK_SUBSONIC_SONGS[0])
|
cache_adapter.ingest_new_data(KEYS.SONG, "2", MOCK_SUBSONIC_SONGS[0])
|
||||||
cache_adapter.ingest_new_data(KEYS.SONG, "1", MOCK_SUBSONIC_SONGS[1])
|
cache_adapter.ingest_new_data(KEYS.SONG, "1", MOCK_SUBSONIC_SONGS[1])
|
||||||
cache_adapter.ingest_new_data(
|
cache_adapter.ingest_new_data(
|
||||||
KEYS.COVER_ART_FILE, "s1", MOCK_ALBUM_ART,
|
KEYS.COVER_ART_FILE,
|
||||||
|
"s1",
|
||||||
|
MOCK_ALBUM_ART,
|
||||||
)
|
)
|
||||||
cache_adapter.ingest_new_data(KEYS.SONG_FILE, "1", (None, MOCK_SONG_FILE, None))
|
cache_adapter.ingest_new_data(KEYS.SONG_FILE, "1", (None, MOCK_SONG_FILE, None))
|
||||||
cache_adapter.ingest_new_data(KEYS.SONG_FILE, "2", (None, MOCK_SONG_FILE2, None))
|
cache_adapter.ingest_new_data(KEYS.SONG_FILE, "2", (None, MOCK_SONG_FILE2, None))
|
||||||
@@ -430,7 +439,9 @@ def test_delete_playlists(cache_adapter: FilesystemAdapter):
|
|||||||
SubsonicAPI.Playlist("2", "test1", cover_art="pl_2", songs=[]),
|
SubsonicAPI.Playlist("2", "test1", cover_art="pl_2", songs=[]),
|
||||||
)
|
)
|
||||||
cache_adapter.ingest_new_data(
|
cache_adapter.ingest_new_data(
|
||||||
KEYS.COVER_ART_FILE, "pl_1", MOCK_ALBUM_ART,
|
KEYS.COVER_ART_FILE,
|
||||||
|
"pl_1",
|
||||||
|
MOCK_ALBUM_ART,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Deleting a playlist should get rid of it entirely.
|
# Deleting a playlist should get rid of it entirely.
|
||||||
@@ -451,7 +462,8 @@ def test_delete_playlists(cache_adapter: FilesystemAdapter):
|
|||||||
|
|
||||||
# Even if the cover art failed to be deleted, it should cache miss.
|
# Even if the cover art failed to be deleted, it should cache miss.
|
||||||
shutil.copy(
|
shutil.copy(
|
||||||
MOCK_ALBUM_ART, str(cache_adapter.cover_art_dir.joinpath(MOCK_ALBUM_ART_HASH)),
|
MOCK_ALBUM_ART,
|
||||||
|
str(cache_adapter.cover_art_dir.joinpath(MOCK_ALBUM_ART_HASH)),
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
cache_adapter.get_cover_art_uri("pl_1", "file", size=300)
|
cache_adapter.get_cover_art_uri("pl_1", "file", size=300)
|
||||||
@@ -464,7 +476,9 @@ def test_delete_song_data(cache_adapter: FilesystemAdapter):
|
|||||||
cache_adapter.ingest_new_data(KEYS.SONG, "1", MOCK_SUBSONIC_SONGS[1])
|
cache_adapter.ingest_new_data(KEYS.SONG, "1", MOCK_SUBSONIC_SONGS[1])
|
||||||
cache_adapter.ingest_new_data(KEYS.SONG_FILE, "1", (None, MOCK_SONG_FILE, None))
|
cache_adapter.ingest_new_data(KEYS.SONG_FILE, "1", (None, MOCK_SONG_FILE, None))
|
||||||
cache_adapter.ingest_new_data(
|
cache_adapter.ingest_new_data(
|
||||||
KEYS.COVER_ART_FILE, "s1", MOCK_ALBUM_ART,
|
KEYS.COVER_ART_FILE,
|
||||||
|
"s1",
|
||||||
|
MOCK_ALBUM_ART,
|
||||||
)
|
)
|
||||||
|
|
||||||
music_file_path = cache_adapter.get_song_file_uri("1", "file")
|
music_file_path = cache_adapter.get_song_file_uri("1", "file")
|
||||||
@@ -747,14 +761,18 @@ def test_caching_get_artist(cache_adapter: FilesystemAdapter):
|
|||||||
)
|
)
|
||||||
|
|
||||||
artist = cache_adapter.get_artist("1")
|
artist = cache_adapter.get_artist("1")
|
||||||
assert artist.artist_image_url and (
|
assert (
|
||||||
artist.id,
|
artist.artist_image_url
|
||||||
artist.name,
|
and (
|
||||||
artist.album_count,
|
artist.id,
|
||||||
artist.artist_image_url,
|
artist.name,
|
||||||
artist.biography,
|
artist.album_count,
|
||||||
artist.music_brainz_id,
|
artist.artist_image_url,
|
||||||
) == ("1", "Bar", 1, "image", "this is a bio", "mbid")
|
artist.biography,
|
||||||
|
artist.music_brainz_id,
|
||||||
|
)
|
||||||
|
== ("1", "Bar", 1, "image", "this is a bio", "mbid")
|
||||||
|
)
|
||||||
assert artist.similar_artists == [
|
assert artist.similar_artists == [
|
||||||
SubsonicAPI.ArtistAndArtistInfo(id="A", name="B"),
|
SubsonicAPI.ArtistAndArtistInfo(id="A", name="B"),
|
||||||
SubsonicAPI.ArtistAndArtistInfo(id="C", name="D"),
|
SubsonicAPI.ArtistAndArtistInfo(id="C", name="D"),
|
||||||
@@ -787,14 +805,18 @@ def test_caching_get_artist(cache_adapter: FilesystemAdapter):
|
|||||||
)
|
)
|
||||||
|
|
||||||
artist = cache_adapter.get_artist("1")
|
artist = cache_adapter.get_artist("1")
|
||||||
assert artist.artist_image_url and (
|
assert (
|
||||||
artist.id,
|
artist.artist_image_url
|
||||||
artist.name,
|
and (
|
||||||
artist.album_count,
|
artist.id,
|
||||||
artist.artist_image_url,
|
artist.name,
|
||||||
artist.biography,
|
artist.album_count,
|
||||||
artist.music_brainz_id,
|
artist.artist_image_url,
|
||||||
) == ("1", "Foo", 2, "image2", "this is a bio2", "mbid2")
|
artist.biography,
|
||||||
|
artist.music_brainz_id,
|
||||||
|
)
|
||||||
|
== ("1", "Foo", 2, "image2", "this is a bio2", "mbid2")
|
||||||
|
)
|
||||||
assert artist.similar_artists == [
|
assert artist.similar_artists == [
|
||||||
SubsonicAPI.ArtistAndArtistInfo(id="A", name="B"),
|
SubsonicAPI.ArtistAndArtistInfo(id="A", name="B"),
|
||||||
SubsonicAPI.ArtistAndArtistInfo(id="E", name="F"),
|
SubsonicAPI.ArtistAndArtistInfo(id="E", name="F"),
|
||||||
@@ -837,7 +859,14 @@ def test_caching_get_album(cache_adapter: FilesystemAdapter):
|
|||||||
album.song_count,
|
album.song_count,
|
||||||
album.year,
|
album.year,
|
||||||
album.play_count,
|
album.play_count,
|
||||||
) == ("a1", "foo", "c", 2, 2020, 20,)
|
) == (
|
||||||
|
"a1",
|
||||||
|
"foo",
|
||||||
|
"c",
|
||||||
|
2,
|
||||||
|
2020,
|
||||||
|
20,
|
||||||
|
)
|
||||||
assert album.artist
|
assert album.artist
|
||||||
assert (album.artist.id, album.artist.name) == ("art1", "cool")
|
assert (album.artist.id, album.artist.name) == ("art1", "cool")
|
||||||
assert album.songs
|
assert album.songs
|
||||||
|
@@ -41,7 +41,10 @@ def test_song_list_column():
|
|||||||
def test_spinner_image():
|
def test_spinner_image():
|
||||||
initial_size = 300
|
initial_size = 300
|
||||||
image = common.SpinnerImage(
|
image = common.SpinnerImage(
|
||||||
loading=False, image_name="test", spinner_name="ohea", image_size=initial_size,
|
loading=False,
|
||||||
|
image_name="test",
|
||||||
|
spinner_name="ohea",
|
||||||
|
image_size=initial_size,
|
||||||
)
|
)
|
||||||
image.set_from_file(None)
|
image.set_from_file(None)
|
||||||
assert image.image.get_pixbuf() is None
|
assert image.image.get_pixbuf() is None
|
||||||
|
Reference in New Issue
Block a user