Added artist grid
This commit is contained in:
@@ -146,6 +146,8 @@ class LibremsonicApp(Gtk.Application):
|
||||
or self.state.config.current_server < 0):
|
||||
self.show_configure_servers_dialog()
|
||||
|
||||
self.update_window()
|
||||
|
||||
# ########## ACTION HANDLERS ########## #
|
||||
def on_configure_servers(self, action, param):
|
||||
self.show_configure_servers_dialog()
|
||||
|
@@ -13,7 +13,13 @@ from typing import Dict, List, Optional, Union, Callable, Set
|
||||
from libremsonic.config import AppConfiguration, ServerConfiguration
|
||||
from libremsonic.server import Server
|
||||
from libremsonic.server.api_object import APIObject
|
||||
from libremsonic.server.api_objects import Playlist, PlaylistWithSongs, Child
|
||||
from libremsonic.server.api_objects import (
|
||||
Playlist,
|
||||
PlaylistWithSongs,
|
||||
Child,
|
||||
ArtistID3,
|
||||
ArtistWithAlbumsID3,
|
||||
)
|
||||
|
||||
|
||||
class Singleton(type):
|
||||
@@ -53,9 +59,13 @@ class CacheManager(metaclass=Singleton):
|
||||
return json.JSONEncoder.default(self, obj)
|
||||
|
||||
class __CacheManagerInternal:
|
||||
# NOTE: you need to add custom load/store logic for everything you add
|
||||
# here!
|
||||
server: Server
|
||||
playlists: Optional[List[Playlist]] = None
|
||||
artists: Optional[List[ArtistID3]] = None
|
||||
playlist_details: Dict[int, PlaylistWithSongs] = {}
|
||||
arist_details: Dict[int, ArtistWithAlbumsID3] = {}
|
||||
permanently_cached_paths: Set[str] = set()
|
||||
song_details: Dict[int, Child] = {}
|
||||
|
||||
@@ -100,10 +110,17 @@ class CacheManager(metaclass=Singleton):
|
||||
Playlist.from_json(p)
|
||||
for p in meta_json.get('playlists') or []
|
||||
]
|
||||
self.artists = [
|
||||
ArtistID3.from_json(a) for a in meta_json.get('artists') or []
|
||||
]
|
||||
self.playlist_details = {
|
||||
id: PlaylistWithSongs.from_json(v)
|
||||
for id, v in (meta_json.get('playlist_details') or {}).items()
|
||||
}
|
||||
self.artist_details = {
|
||||
id: ArtistWithAlbumsID3.from_json(a)
|
||||
for id, a in (meta_json.get('artist_details') or {}).items()
|
||||
}
|
||||
self.song_details = {
|
||||
id: Child.from_json(v)
|
||||
for id, v in (meta_json.get('song_details') or {}).items()
|
||||
@@ -118,7 +135,9 @@ class CacheManager(metaclass=Singleton):
|
||||
with open(cache_meta_file, 'w+') as f, self.cache_lock:
|
||||
cache_info = dict(
|
||||
playlists=self.playlists,
|
||||
artists=self.artists,
|
||||
playlist_details=self.playlist_details,
|
||||
artist_details=self.artist_details,
|
||||
song_details=self.song_details,
|
||||
permanently_cached_paths=list(
|
||||
self.permanently_cached_paths),
|
||||
@@ -189,8 +208,9 @@ class CacheManager(metaclass=Singleton):
|
||||
def do_get_playlists() -> List[Playlist]:
|
||||
if not self.playlists or force:
|
||||
before_download()
|
||||
playlists = self.server.get_playlists().playlist
|
||||
with self.cache_lock:
|
||||
self.playlists = self.server.get_playlists().playlist
|
||||
self.playlists = playlists
|
||||
self.save_cache_info()
|
||||
return self.playlists
|
||||
|
||||
@@ -209,8 +229,8 @@ class CacheManager(metaclass=Singleton):
|
||||
with self.cache_lock:
|
||||
self.playlist_details[playlist_id] = playlist
|
||||
|
||||
# Playlists also have song details, so save that as
|
||||
# well.
|
||||
# Playlists also have the song details, so save those
|
||||
# as well.
|
||||
for song in (playlist.entry or []):
|
||||
self.song_details[song.id] = song
|
||||
|
||||
@@ -228,6 +248,29 @@ class CacheManager(metaclass=Singleton):
|
||||
|
||||
return CacheManager.executor.submit(do_get_playlist)
|
||||
|
||||
def get_artists(
|
||||
self,
|
||||
before_download: Callable[[], None] = lambda: None,
|
||||
force: bool = False,
|
||||
) -> Future:
|
||||
def do_get_artists() -> List[ArtistID3]:
|
||||
if not self.artists or force:
|
||||
before_download()
|
||||
raw_artists = self.server.get_artists()
|
||||
|
||||
artists = []
|
||||
for index in raw_artists.index:
|
||||
artists.extend(index.artist)
|
||||
|
||||
with self.cache_lock:
|
||||
self.artists = artists
|
||||
|
||||
self.save_cache_info()
|
||||
|
||||
return self.artists
|
||||
|
||||
return CacheManager.executor.submit(do_get_artists)
|
||||
|
||||
def batch_download_songs(
|
||||
self,
|
||||
song_ids: List[int],
|
||||
@@ -281,9 +324,9 @@ class CacheManager(metaclass=Singleton):
|
||||
def do_get_song_details() -> Child:
|
||||
if not self.song_details.get(song_id) or force:
|
||||
before_download()
|
||||
song_details = self.server.get_song(song_id)
|
||||
with self.cache_lock:
|
||||
self.song_details[song_id] = self.server.get_song(
|
||||
song_id)
|
||||
self.song_details[song_id] = song_details
|
||||
self.save_cache_info()
|
||||
|
||||
return self.song_details[song_id]
|
||||
|
@@ -27,12 +27,12 @@
|
||||
margin: 5px 10px 10px 0;
|
||||
}
|
||||
|
||||
#playlist-artwork-spinner {
|
||||
#playlist-artwork-spinner, #artist-artwork-spinner {
|
||||
min-height: 35px;
|
||||
min-width: 35px;
|
||||
}
|
||||
|
||||
#playlist-album-artwork {
|
||||
#playlist-album-artwork, #artist-artwork {
|
||||
min-height: 200px;
|
||||
min-width: 200px;
|
||||
margin: 10px;
|
||||
@@ -97,6 +97,13 @@
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
/* General */
|
||||
.menu-button {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
/* ********** Artists ********** */
|
||||
.artist-box {
|
||||
min-width: 200px;
|
||||
min-height: 230px;
|
||||
}
|
||||
|
@@ -1,9 +1,15 @@
|
||||
from typing import List
|
||||
|
||||
import gi
|
||||
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk, GObject
|
||||
from gi.repository import Gtk, GObject, Gio, Pango
|
||||
|
||||
from libremsonic.state_manager import ApplicationState
|
||||
from libremsonic.cache_manager import CacheManager
|
||||
from libremsonic.ui import util
|
||||
|
||||
from libremsonic.server.api_objects import ArtistID3
|
||||
|
||||
|
||||
class ArtistsPanel(Gtk.ScrolledWindow):
|
||||
@@ -24,23 +30,91 @@ class ArtistsPanel(Gtk.ScrolledWindow):
|
||||
self.child.update(state)
|
||||
|
||||
|
||||
class ArtistModel(GObject.Object):
|
||||
def __init__(self, name, cover_art):
|
||||
self.name = name
|
||||
self.cover_art = cover_art
|
||||
super().__init__()
|
||||
|
||||
|
||||
class ArtistsGrid(Gtk.FlowBox):
|
||||
"""Defines the albums panel."""
|
||||
"""Defines the artists panel."""
|
||||
|
||||
def __init__(self):
|
||||
Gtk.FlowBox.__init__(
|
||||
self,
|
||||
vexpand=True,
|
||||
hexpand=True,
|
||||
row_spacing=12,
|
||||
column_spacing=12,
|
||||
row_spacing=10,
|
||||
column_spacing=10,
|
||||
margin_top=12,
|
||||
margin_bottom=12,
|
||||
homogeneous=True,
|
||||
valign=Gtk.Align.START,
|
||||
halign=Gtk.Align.CENTER,
|
||||
selection_mode=Gtk.SelectionMode.NONE,
|
||||
selection_mode=Gtk.SelectionMode.BROWSE,
|
||||
)
|
||||
|
||||
self.artist_model = Gio.ListStore()
|
||||
|
||||
self.bind_model(self.artist_model, self.create_artist_widget)
|
||||
|
||||
def update(self, state: ApplicationState):
|
||||
pass
|
||||
self.update_grid()
|
||||
|
||||
@util.async_callback(
|
||||
lambda *a, **k: CacheManager.get_artists(*a, **k),
|
||||
before_download=lambda self: print('ohea'),
|
||||
on_failure=lambda self, e: print('fail', e),
|
||||
)
|
||||
def update_grid(self, artists: List[ArtistID3]):
|
||||
# TODO do the diff thing eventually?
|
||||
self.artist_model.remove_all()
|
||||
for artist in artists:
|
||||
self.artist_model.append(ArtistModel(artist.name, artist.coverArt))
|
||||
|
||||
def create_artist_widget(self, item):
|
||||
artist_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
||||
|
||||
artwork_overlay = Gtk.Overlay()
|
||||
artist_artwork = Gtk.Image(name='artist-artwork')
|
||||
artwork_overlay.add(artist_artwork)
|
||||
|
||||
artwork_spinner = Gtk.Spinner(name='artist-artwork-spinner',
|
||||
active=False,
|
||||
halign=Gtk.Align.CENTER,
|
||||
valign=Gtk.Align.CENTER)
|
||||
artwork_overlay.add_overlay(artwork_spinner)
|
||||
artist_box.pack_start(artwork_overlay, False, False, 0)
|
||||
|
||||
def artwork_downloaded(f):
|
||||
filename = f.result()
|
||||
artist_artwork.set_from_file(filename)
|
||||
artwork_spinner.active = False
|
||||
|
||||
def before_download():
|
||||
artwork_spinner.active = True
|
||||
|
||||
cover_art_filename_future = CacheManager.get_cover_art_filename(
|
||||
item.cover_art, before_download=before_download)
|
||||
cover_art_filename_future.add_done_callback(artwork_downloaded)
|
||||
|
||||
name_label = Gtk.Label(
|
||||
name='artist-name-label',
|
||||
label=item.name,
|
||||
ellipsize=Pango.EllipsizeMode.END,
|
||||
max_width_chars=20,
|
||||
)
|
||||
|
||||
artist_box.pack_end(name_label, False, False, 0)
|
||||
artist_box.show_all()
|
||||
return artist_box
|
||||
|
||||
@util.async_callback(
|
||||
lambda *a, **k: CacheManager.get_cover_art_filename(*a, **k),
|
||||
before_download=lambda self: self.set_playlist_art_loading(True),
|
||||
on_failure=lambda self, e: self.set_playlist_art_loading(False),
|
||||
)
|
||||
def update_playlist_artwork(self, cover_art_filename):
|
||||
self.playlist_artwork.set_from_file(cover_art_filename)
|
||||
self.set_playlist_art_loading(False)
|
||||
|
Reference in New Issue
Block a user