Fix linter errors

This commit is contained in:
Sumner Evans
2020-09-19 00:01:36 -06:00
parent 5367ed082f
commit 6265281955
29 changed files with 346 additions and 162 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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
View 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()

View File

@@ -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

View File

@@ -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.

View File

@@ -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 = []

View File

@@ -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)

View File

@@ -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:

View 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 != "+":

View File

@@ -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)

View File

@@ -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"),

View File

@@ -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",

View File

@@ -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
) )

View File

@@ -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 {},

View File

@@ -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():

View File

@@ -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)

View File

@@ -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()

View File

@@ -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:

View File

@@ -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):

View File

@@ -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:

View File

@@ -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):

View File

@@ -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)

View File

@@ -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(

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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