Tons of improvements to browsing using the Albums view
This commit is contained in:
@@ -100,6 +100,9 @@ def get_dependencies(xs_el) -> Tuple[Set[str], Dict[str, str]]:
|
||||
# <complexType>s depend on all of the types that their children have.
|
||||
for el in xs_el.getchildren():
|
||||
deps, fields = get_dependencies(el)
|
||||
|
||||
# Genres has this.
|
||||
fields['value'] = 'str'
|
||||
depends_on |= deps
|
||||
type_fields.update(fields)
|
||||
|
||||
|
@@ -31,6 +31,7 @@ from libremsonic.server.api_objects import (
|
||||
Playlist,
|
||||
PlaylistWithSongs,
|
||||
Child,
|
||||
Genre,
|
||||
|
||||
# Non-ID3 versions
|
||||
Artist,
|
||||
@@ -146,6 +147,7 @@ class CacheManager(metaclass=Singleton):
|
||||
('playlists', Playlist, list),
|
||||
('playlist_details', PlaylistWithSongs, dict),
|
||||
('song_details', Child, dict),
|
||||
('genres', Genre, list),
|
||||
|
||||
# Non-ID3 caches
|
||||
('albums', Child, list),
|
||||
@@ -425,17 +427,22 @@ class CacheManager(metaclass=Singleton):
|
||||
def get_albums(
|
||||
self,
|
||||
type_: str,
|
||||
size: int = 30,
|
||||
before_download: Callable[[], None] = lambda: None,
|
||||
force: bool = False,
|
||||
# Look at documentation for get_album_list in server.py:
|
||||
**params,
|
||||
) -> Future:
|
||||
def do_get_albums() -> List[Child]:
|
||||
cache_name = self.id3ify('albums')
|
||||
server_fn = (self.server.get_album_list2 if self.browse_by_tags
|
||||
else self.server.get_album_list)
|
||||
|
||||
# TODO cache per type.
|
||||
# TODO handle random.
|
||||
if not self.cache.get(cache_name) or force:
|
||||
before_download()
|
||||
albums = server_fn(type_)
|
||||
albums = server_fn(type_, size=size, **params)
|
||||
|
||||
with self.cache_lock:
|
||||
self.cache[cache_name] = albums.album
|
||||
@@ -578,6 +585,22 @@ class CacheManager(metaclass=Singleton):
|
||||
|
||||
return (str(abs_path), False)
|
||||
|
||||
def get_genres(self,
|
||||
before_download: Callable[[], None] = lambda: None,
|
||||
force: bool = False,
|
||||
) -> Future:
|
||||
def do_get_genres() -> List[Genre]:
|
||||
if not self.cache['genres'] or force:
|
||||
before_download()
|
||||
genres = self.server.get_genres().genre
|
||||
with self.cache_lock:
|
||||
self.cache['genres'] = genres
|
||||
self.save_cache_info()
|
||||
|
||||
return self.cache['genres']
|
||||
|
||||
return CacheManager.executor.submit(do_get_genres)
|
||||
|
||||
def get_cached_status(self, song: Child) -> SongCacheStatus:
|
||||
cache_path = self.calculate_abs_path(song.path)
|
||||
if cache_path.exists():
|
||||
|
@@ -18,6 +18,7 @@ class AlbumInfo(APIObject):
|
||||
smallImageUrl: List[str]
|
||||
mediumImageUrl: List[str]
|
||||
largeImageUrl: List[str]
|
||||
value: str
|
||||
|
||||
|
||||
class AverageRating(APIObject, float):
|
||||
@@ -37,6 +38,7 @@ class UserRating(APIObject, int):
|
||||
|
||||
class Child(APIObject):
|
||||
id: str
|
||||
value: str
|
||||
parent: str
|
||||
isDir: bool
|
||||
title: str
|
||||
@@ -71,10 +73,12 @@ class Child(APIObject):
|
||||
|
||||
class AlbumList(APIObject):
|
||||
album: List[Child]
|
||||
value: str
|
||||
|
||||
|
||||
class AlbumID3(APIObject):
|
||||
id: str
|
||||
value: str
|
||||
name: str
|
||||
artist: str
|
||||
artistId: str
|
||||
@@ -90,10 +94,12 @@ class AlbumID3(APIObject):
|
||||
|
||||
class AlbumList2(APIObject):
|
||||
album: List[AlbumID3]
|
||||
value: str
|
||||
|
||||
|
||||
class AlbumWithSongsID3(APIObject):
|
||||
song: List[Child]
|
||||
value: str
|
||||
id: str
|
||||
name: str
|
||||
artist: str
|
||||
@@ -110,6 +116,7 @@ class AlbumWithSongsID3(APIObject):
|
||||
|
||||
class Artist(APIObject):
|
||||
id: str
|
||||
value: str
|
||||
name: str
|
||||
artistImageUrl: str
|
||||
starred: datetime
|
||||
@@ -124,10 +131,12 @@ class ArtistInfoBase(APIObject):
|
||||
smallImageUrl: List[str]
|
||||
mediumImageUrl: List[str]
|
||||
largeImageUrl: List[str]
|
||||
value: str
|
||||
|
||||
|
||||
class ArtistInfo(APIObject):
|
||||
similarArtist: List[Artist]
|
||||
value: str
|
||||
biography: List[str]
|
||||
musicBrainzId: List[str]
|
||||
lastFmUrl: List[str]
|
||||
@@ -138,6 +147,7 @@ class ArtistInfo(APIObject):
|
||||
|
||||
class ArtistID3(APIObject):
|
||||
id: str
|
||||
value: str
|
||||
name: str
|
||||
coverArt: str
|
||||
artistImageUrl: str
|
||||
@@ -147,6 +157,7 @@ class ArtistID3(APIObject):
|
||||
|
||||
class ArtistInfo2(APIObject):
|
||||
similarArtist: List[ArtistID3]
|
||||
value: str
|
||||
biography: List[str]
|
||||
musicBrainzId: List[str]
|
||||
lastFmUrl: List[str]
|
||||
@@ -157,6 +168,7 @@ class ArtistInfo2(APIObject):
|
||||
|
||||
class ArtistWithAlbumsID3(APIObject):
|
||||
album: List[AlbumID3]
|
||||
value: str
|
||||
id: str
|
||||
name: str
|
||||
coverArt: str
|
||||
@@ -167,16 +179,19 @@ class ArtistWithAlbumsID3(APIObject):
|
||||
|
||||
class IndexID3(APIObject):
|
||||
artist: List[ArtistID3]
|
||||
value: str
|
||||
name: str
|
||||
|
||||
|
||||
class ArtistsID3(APIObject):
|
||||
index: List[IndexID3]
|
||||
value: str
|
||||
ignoredArticles: str
|
||||
|
||||
|
||||
class Bookmark(APIObject):
|
||||
entry: List[Child]
|
||||
value: str
|
||||
position: int
|
||||
username: str
|
||||
comment: str
|
||||
@@ -186,20 +201,24 @@ class Bookmark(APIObject):
|
||||
|
||||
class Bookmarks(APIObject):
|
||||
bookmark: List[Bookmark]
|
||||
value: str
|
||||
|
||||
|
||||
class ChatMessage(APIObject):
|
||||
username: str
|
||||
value: str
|
||||
time: int
|
||||
message: str
|
||||
|
||||
|
||||
class ChatMessages(APIObject):
|
||||
chatMessage: List[ChatMessage]
|
||||
value: str
|
||||
|
||||
|
||||
class Directory(APIObject):
|
||||
child: List[Child]
|
||||
value: str
|
||||
id: str
|
||||
parent: str
|
||||
name: str
|
||||
@@ -211,20 +230,24 @@ class Directory(APIObject):
|
||||
|
||||
class Error(APIObject):
|
||||
code: int
|
||||
value: str
|
||||
message: str
|
||||
|
||||
|
||||
class Genre(APIObject):
|
||||
songCount: int
|
||||
value: str
|
||||
albumCount: int
|
||||
|
||||
|
||||
class Genres(APIObject):
|
||||
genre: List[Genre]
|
||||
value: str
|
||||
|
||||
|
||||
class Index(APIObject):
|
||||
artist: List[Artist]
|
||||
value: str
|
||||
name: str
|
||||
|
||||
|
||||
@@ -232,12 +255,14 @@ class Indexes(APIObject):
|
||||
shortcut: List[Artist]
|
||||
index: List[Index]
|
||||
child: List[Child]
|
||||
value: str
|
||||
lastModified: int
|
||||
ignoredArticles: str
|
||||
|
||||
|
||||
class InternetRadioStation(APIObject):
|
||||
id: str
|
||||
value: str
|
||||
name: str
|
||||
streamUrl: str
|
||||
homePageUrl: str
|
||||
@@ -245,10 +270,12 @@ class InternetRadioStation(APIObject):
|
||||
|
||||
class InternetRadioStations(APIObject):
|
||||
internetRadioStation: List[InternetRadioStation]
|
||||
value: str
|
||||
|
||||
|
||||
class JukeboxStatus(APIObject):
|
||||
currentIndex: int
|
||||
value: str
|
||||
playing: bool
|
||||
gain: float
|
||||
position: int
|
||||
@@ -256,6 +283,7 @@ class JukeboxStatus(APIObject):
|
||||
|
||||
class JukeboxPlaylist(APIObject):
|
||||
entry: List[Child]
|
||||
value: str
|
||||
currentIndex: int
|
||||
playing: bool
|
||||
gain: float
|
||||
@@ -264,6 +292,7 @@ class JukeboxPlaylist(APIObject):
|
||||
|
||||
class License(APIObject):
|
||||
valid: bool
|
||||
value: str
|
||||
email: str
|
||||
licenseExpires: datetime
|
||||
trialExpires: datetime
|
||||
@@ -271,16 +300,19 @@ class License(APIObject):
|
||||
|
||||
class Lyrics(APIObject):
|
||||
artist: str
|
||||
value: str
|
||||
title: str
|
||||
|
||||
|
||||
class MusicFolder(APIObject):
|
||||
id: int
|
||||
value: str
|
||||
name: str
|
||||
|
||||
|
||||
class MusicFolders(APIObject):
|
||||
musicFolder: List[MusicFolder]
|
||||
value: str
|
||||
|
||||
|
||||
class PodcastStatus(APIObject, Enum):
|
||||
@@ -298,6 +330,7 @@ class PodcastEpisode(APIObject):
|
||||
description: str
|
||||
status: PodcastStatus
|
||||
publishDate: datetime
|
||||
value: str
|
||||
id: str
|
||||
parent: str
|
||||
isDir: bool
|
||||
@@ -333,6 +366,7 @@ class PodcastEpisode(APIObject):
|
||||
|
||||
class NewestPodcasts(APIObject):
|
||||
episode: List[PodcastEpisode]
|
||||
value: str
|
||||
|
||||
|
||||
class NowPlayingEntry(APIObject):
|
||||
@@ -340,6 +374,7 @@ class NowPlayingEntry(APIObject):
|
||||
minutesAgo: int
|
||||
playerId: int
|
||||
playerName: str
|
||||
value: str
|
||||
id: str
|
||||
parent: str
|
||||
isDir: bool
|
||||
@@ -375,10 +410,12 @@ class NowPlayingEntry(APIObject):
|
||||
|
||||
class NowPlaying(APIObject):
|
||||
entry: List[NowPlayingEntry]
|
||||
value: str
|
||||
|
||||
|
||||
class PlayQueue(APIObject):
|
||||
entry: List[Child]
|
||||
value: str
|
||||
current: int
|
||||
position: int
|
||||
username: str
|
||||
@@ -388,6 +425,7 @@ class PlayQueue(APIObject):
|
||||
|
||||
class Playlist(APIObject):
|
||||
allowedUser: List[str]
|
||||
value: str
|
||||
id: str
|
||||
name: str
|
||||
comment: str
|
||||
@@ -402,6 +440,7 @@ class Playlist(APIObject):
|
||||
|
||||
class PlaylistWithSongs(APIObject):
|
||||
entry: List[Child]
|
||||
value: str
|
||||
allowedUser: List[str]
|
||||
id: str
|
||||
name: str
|
||||
@@ -417,10 +456,12 @@ class PlaylistWithSongs(APIObject):
|
||||
|
||||
class Playlists(APIObject):
|
||||
playlist: List[Playlist]
|
||||
value: str
|
||||
|
||||
|
||||
class PodcastChannel(APIObject):
|
||||
episode: List[PodcastEpisode]
|
||||
value: str
|
||||
id: str
|
||||
url: str
|
||||
title: str
|
||||
@@ -433,6 +474,7 @@ class PodcastChannel(APIObject):
|
||||
|
||||
class Podcasts(APIObject):
|
||||
channel: List[PodcastChannel]
|
||||
value: str
|
||||
|
||||
|
||||
class ResponseStatus(APIObject, Enum):
|
||||
@@ -442,11 +484,13 @@ class ResponseStatus(APIObject, Enum):
|
||||
|
||||
class ScanStatus(APIObject):
|
||||
scanning: bool
|
||||
value: str
|
||||
count: int
|
||||
|
||||
|
||||
class SearchResult(APIObject):
|
||||
match: List[Child]
|
||||
value: str
|
||||
offset: int
|
||||
totalHits: int
|
||||
|
||||
@@ -455,16 +499,19 @@ class SearchResult2(APIObject):
|
||||
artist: List[Artist]
|
||||
album: List[Child]
|
||||
song: List[Child]
|
||||
value: str
|
||||
|
||||
|
||||
class SearchResult3(APIObject):
|
||||
artist: List[ArtistID3]
|
||||
album: List[AlbumID3]
|
||||
song: List[Child]
|
||||
value: str
|
||||
|
||||
|
||||
class Share(APIObject):
|
||||
entry: List[Child]
|
||||
value: str
|
||||
id: str
|
||||
url: str
|
||||
description: str
|
||||
@@ -477,38 +524,46 @@ class Share(APIObject):
|
||||
|
||||
class Shares(APIObject):
|
||||
share: List[Share]
|
||||
value: str
|
||||
|
||||
|
||||
class SimilarSongs(APIObject):
|
||||
song: List[Child]
|
||||
value: str
|
||||
|
||||
|
||||
class SimilarSongs2(APIObject):
|
||||
song: List[Child]
|
||||
value: str
|
||||
|
||||
|
||||
class Songs(APIObject):
|
||||
song: List[Child]
|
||||
value: str
|
||||
|
||||
|
||||
class Starred(APIObject):
|
||||
artist: List[Artist]
|
||||
album: List[Child]
|
||||
song: List[Child]
|
||||
value: str
|
||||
|
||||
|
||||
class Starred2(APIObject):
|
||||
artist: List[ArtistID3]
|
||||
album: List[AlbumID3]
|
||||
song: List[Child]
|
||||
value: str
|
||||
|
||||
|
||||
class TopSongs(APIObject):
|
||||
song: List[Child]
|
||||
value: str
|
||||
|
||||
|
||||
class User(APIObject):
|
||||
folder: List[int]
|
||||
value: str
|
||||
username: str
|
||||
email: str
|
||||
scrobblingEnabled: bool
|
||||
@@ -530,6 +585,7 @@ class User(APIObject):
|
||||
|
||||
class Users(APIObject):
|
||||
user: List[User]
|
||||
value: str
|
||||
|
||||
|
||||
class Version(APIObject, str):
|
||||
@@ -538,17 +594,20 @@ class Version(APIObject, str):
|
||||
|
||||
class AudioTrack(APIObject):
|
||||
id: str
|
||||
value: str
|
||||
name: str
|
||||
languageCode: str
|
||||
|
||||
|
||||
class Captions(APIObject):
|
||||
id: str
|
||||
value: str
|
||||
name: str
|
||||
|
||||
|
||||
class VideoConversion(APIObject):
|
||||
id: str
|
||||
value: str
|
||||
bitRate: int
|
||||
audioTrackId: int
|
||||
|
||||
@@ -557,11 +616,13 @@ class VideoInfo(APIObject):
|
||||
captions: List[Captions]
|
||||
audioTrack: List[AudioTrack]
|
||||
conversion: List[VideoConversion]
|
||||
value: str
|
||||
id: str
|
||||
|
||||
|
||||
class Videos(APIObject):
|
||||
video: List[Child]
|
||||
value: str
|
||||
|
||||
|
||||
class Response(APIObject):
|
||||
@@ -608,5 +669,6 @@ class Response(APIObject):
|
||||
topSongs: TopSongs
|
||||
scanStatus: ScanStatus
|
||||
error: Error
|
||||
value: str
|
||||
status: ResponseStatus
|
||||
version: Version
|
||||
|
@@ -2,7 +2,7 @@ import gi
|
||||
from typing import Optional, Union
|
||||
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gio, Gtk, GObject
|
||||
from gi.repository import Gtk, GObject, GLib
|
||||
|
||||
from libremsonic.state_manager import ApplicationState
|
||||
from libremsonic.cache_manager import CacheManager
|
||||
@@ -14,7 +14,7 @@ from libremsonic.server.api_objects import Child, AlbumWithSongsID3
|
||||
Album = Union[Child, AlbumWithSongsID3]
|
||||
|
||||
|
||||
class AlbumsPanel(Gtk.ScrolledWindow):
|
||||
class AlbumsPanel(Gtk.Box):
|
||||
__gsignals__ = {
|
||||
'song-clicked': (
|
||||
GObject.SignalFlags.RUN_FIRST,
|
||||
@@ -23,17 +23,172 @@ class AlbumsPanel(Gtk.ScrolledWindow):
|
||||
),
|
||||
}
|
||||
|
||||
currently_active_sort: str = 'random'
|
||||
currently_active_alphabetical_sort: str = 'name'
|
||||
currently_active_genre: str = 'Rock'
|
||||
currently_active_from_year: int = 2010
|
||||
currently_active_to_year: int = 2020
|
||||
|
||||
def __init__(self):
|
||||
Gtk.ScrolledWindow.__init__(self)
|
||||
self.child = AlbumsGrid()
|
||||
self.child.connect(
|
||||
super().__init__(orientation=Gtk.Orientation.VERTICAL)
|
||||
|
||||
actionbar = Gtk.ActionBar()
|
||||
|
||||
# Sort by
|
||||
actionbar.add(Gtk.Label(label='Sort by:'))
|
||||
self.sort_type_combo = self.make_combobox(
|
||||
(
|
||||
('random', 'Random'),
|
||||
('newest', 'Most recently added'),
|
||||
('highest', 'Highest rated'),
|
||||
('frequent', 'Most played'),
|
||||
('recent', 'Most recently played'),
|
||||
('alphabetical', 'Alphabetically'),
|
||||
('starred', 'Starred only'),
|
||||
('byYear', 'Year'),
|
||||
('byGenre', 'Genre'),
|
||||
),
|
||||
self.on_type_combo_changed,
|
||||
)
|
||||
actionbar.pack_start(self.sort_type_combo)
|
||||
|
||||
# Alphabetically how?
|
||||
self.alphabetical_type_combo = self.make_combobox(
|
||||
(
|
||||
('name', 'by album name'),
|
||||
('artist', 'by artist name'),
|
||||
),
|
||||
self.on_alphabetical_type_change,
|
||||
)
|
||||
actionbar.pack_start(self.alphabetical_type_combo)
|
||||
|
||||
# Alphabetically how?
|
||||
self.genre_combo = self.make_combobox(
|
||||
(),
|
||||
self.on_genre_change,
|
||||
)
|
||||
actionbar.pack_start(self.genre_combo)
|
||||
|
||||
self.from_year_label = Gtk.Label(label='from')
|
||||
actionbar.pack_start(self.from_year_label)
|
||||
self.from_year_entry = Gtk.Entry()
|
||||
self.from_year_entry.connect('changed', self.on_year_changed)
|
||||
actionbar.pack_start(self.from_year_entry)
|
||||
|
||||
self.to_year_label = Gtk.Label(label='to')
|
||||
actionbar.pack_start(self.to_year_label)
|
||||
self.to_year_entry = Gtk.Entry()
|
||||
self.to_year_entry.connect('changed', self.on_year_changed)
|
||||
actionbar.pack_start(self.to_year_entry)
|
||||
|
||||
refresh = util.button_with_icon('view-refresh')
|
||||
refresh.connect('clicked', lambda *a: self.update(force=True))
|
||||
actionbar.pack_end(refresh)
|
||||
|
||||
self.add(actionbar)
|
||||
|
||||
scrolled_window = Gtk.ScrolledWindow()
|
||||
self.grid = AlbumsGrid()
|
||||
self.grid.connect(
|
||||
'song-clicked',
|
||||
lambda _, song, queue: self.emit('song-clicked', song, queue),
|
||||
)
|
||||
self.add(self.child)
|
||||
scrolled_window.add(self.grid)
|
||||
self.add(scrolled_window)
|
||||
|
||||
def update(self, state: ApplicationState):
|
||||
self.child.update(state)
|
||||
def make_combobox(self, items, on_change):
|
||||
store = Gtk.ListStore(str, str)
|
||||
for item in items:
|
||||
store.append(item)
|
||||
|
||||
combo = Gtk.ComboBox.new_with_model(store)
|
||||
combo.set_id_column(0)
|
||||
combo.connect('changed', on_change)
|
||||
|
||||
renderer_text = Gtk.CellRendererText()
|
||||
combo.pack_start(renderer_text, True)
|
||||
combo.add_attribute(renderer_text, 'text', 1)
|
||||
|
||||
return combo
|
||||
|
||||
def populate_genre_combo(self):
|
||||
def get_genres_done(f):
|
||||
model = self.genre_combo.get_model()
|
||||
for genre in (f.result() or []):
|
||||
model.append((genre.value, genre.value))
|
||||
|
||||
self.genre_combo.set_active_id(self.currently_active_genre)
|
||||
|
||||
genres_future = CacheManager.get_genres()
|
||||
genres_future.add_done_callback(
|
||||
lambda f: GLib.idle_add(get_genres_done, f))
|
||||
|
||||
def update(self, state: ApplicationState = None, force: bool = False):
|
||||
# TODO store this in state
|
||||
self.sort_type_combo.set_active_id(self.currently_active_sort)
|
||||
self.alphabetical_type_combo.set_active_id(
|
||||
self.currently_active_alphabetical_sort)
|
||||
self.from_year_entry.set_text(str(self.currently_active_from_year))
|
||||
self.to_year_entry.set_text(str(self.currently_active_to_year))
|
||||
|
||||
self.populate_genre_combo()
|
||||
|
||||
# Show/hide the combo boxes.
|
||||
def show_if(sort_type, *elements):
|
||||
for element in elements:
|
||||
if self.currently_active_sort == sort_type:
|
||||
element.show()
|
||||
else:
|
||||
element.hide()
|
||||
|
||||
show_if('alphabetical', self.alphabetical_type_combo)
|
||||
show_if('byGenre', self.genre_combo)
|
||||
show_if('byYear', self.from_year_label, self.from_year_entry)
|
||||
show_if('byYear', self.to_year_label, self.to_year_entry)
|
||||
|
||||
self.grid.update(state=state, force=force)
|
||||
|
||||
def get_id(self, combo):
|
||||
tree_iter = combo.get_active_iter()
|
||||
if tree_iter is not None:
|
||||
return combo.get_model()[tree_iter][0]
|
||||
|
||||
def on_type_combo_changed(self, combo):
|
||||
self.currently_active_sort = self.get_id(combo)
|
||||
if self.grid.type_ != self.currently_active_sort:
|
||||
self.grid.update_params(type_=self.currently_active_sort)
|
||||
self.update(force=True)
|
||||
|
||||
def on_alphabetical_type_change(self, combo):
|
||||
self.currently_active_alphabetical_sort = self.get_id(combo)
|
||||
if self.grid.alphabetical_type != self.currently_active_alphabetical_sort:
|
||||
self.grid.update_params(
|
||||
alphabetical_type=self.currently_active_alphabetical_sort)
|
||||
self.update(force=True)
|
||||
|
||||
def on_genre_change(self, combo):
|
||||
self.currently_active_genre = self.get_id(combo)
|
||||
if self.grid.genre != self.currently_active_genre:
|
||||
self.grid.update_params(genre=self.currently_active_genre)
|
||||
self.update(force=True)
|
||||
|
||||
def on_year_changed(self, entry):
|
||||
try:
|
||||
year = int(entry.get_text())
|
||||
except:
|
||||
print('failed, should do something to prevent non-numeric input')
|
||||
return
|
||||
|
||||
if self.to_year_entry == entry:
|
||||
self.currently_active_to_year = year
|
||||
if self.grid.to_year != self.currently_active_to_year:
|
||||
self.grid.update_params(to_year=year)
|
||||
self.update(force=True)
|
||||
else:
|
||||
self.currently_active_from_year = year
|
||||
if self.grid.from_year != self.currently_active_from_year:
|
||||
self.grid.update_params(from_year=year)
|
||||
self.update(force=True)
|
||||
|
||||
|
||||
class AlbumModel(GObject.Object):
|
||||
@@ -47,6 +202,25 @@ class AlbumModel(GObject.Object):
|
||||
|
||||
class AlbumsGrid(CoverArtGrid):
|
||||
"""Defines the albums panel."""
|
||||
type_: str = 'random'
|
||||
alphabetical_type: str = 'name'
|
||||
from_year: int = 2010
|
||||
to_year: int = 2020
|
||||
genre: str = 'Rock'
|
||||
|
||||
def update_params(
|
||||
self,
|
||||
type_: str = None,
|
||||
alphabetical_type: str = None,
|
||||
from_year: int = None,
|
||||
to_year: int = None,
|
||||
genre: str = None,
|
||||
):
|
||||
self.type_ = type_ or self.type_
|
||||
self.alphabetical_type = alphabetical_type or self.alphabetical_type
|
||||
self.from_year = from_year or self.from_year
|
||||
self.to_year = to_year or self.to_year
|
||||
self.genre = genre or self.genre
|
||||
|
||||
# Override Methods
|
||||
# =========================================================================
|
||||
@@ -57,10 +231,21 @@ class AlbumsGrid(CoverArtGrid):
|
||||
def get_info_text(self, item: AlbumModel) -> Optional[str]:
|
||||
return util.dot_join(item.album.artist, item.album.year)
|
||||
|
||||
def get_model_list_future(self, before_download):
|
||||
def get_model_list_future(self, before_download, force=False):
|
||||
type_ = self.type_
|
||||
if self.type_ == 'alphabetical':
|
||||
type_ += {
|
||||
'name': 'ByName',
|
||||
'artist': 'ByArtist',
|
||||
}[self.alphabetical_type]
|
||||
|
||||
return CacheManager.get_albums(
|
||||
type_='random',
|
||||
type_=type_,
|
||||
to_year=self.to_year,
|
||||
from_year=self.from_year,
|
||||
genre=self.genre,
|
||||
before_download=before_download,
|
||||
force=force,
|
||||
)
|
||||
|
||||
def create_model_from_element(self, album):
|
||||
|
@@ -195,7 +195,7 @@ class AlbumWithSongs(Gtk.Box):
|
||||
def deselect_all(self):
|
||||
self.album_songs.get_selection().unselect_all()
|
||||
|
||||
def update(self):
|
||||
def update(self, force=False):
|
||||
self.update_album_songs(self.album.id)
|
||||
|
||||
@util.async_callback(
|
||||
|
@@ -90,14 +90,15 @@ class CoverArtGrid(Gtk.ScrolledWindow):
|
||||
|
||||
self.add(overlay)
|
||||
|
||||
def update(self, state: ApplicationState = None):
|
||||
self.update_grid()
|
||||
def update(self, state: ApplicationState = None, force: bool = False):
|
||||
self.update_grid(force=force)
|
||||
|
||||
# Update the detail panel.
|
||||
children = self.detail_box_inner.get_children()
|
||||
if len(children) > 0 and hasattr(children[0], 'update'):
|
||||
children[0].update()
|
||||
children[0].update(force=force)
|
||||
|
||||
def update_grid(self):
|
||||
def update_grid(self, force=False):
|
||||
def start_loading():
|
||||
self.spinner.show()
|
||||
|
||||
@@ -115,18 +116,26 @@ class CoverArtGrid(Gtk.ScrolledWindow):
|
||||
if selection:
|
||||
self.selected_list_store_index = selection[0].get_index()
|
||||
|
||||
if force:
|
||||
self.selected_list_store_index = None
|
||||
|
||||
old_len = len(self.list_store)
|
||||
self.list_store.remove_all()
|
||||
for el in result:
|
||||
for el in (result or []):
|
||||
self.list_store.append(self.create_model_from_element(el))
|
||||
new_len = len(self.list_store)
|
||||
|
||||
# Only force if there's a length change.
|
||||
# TODO, this doesn't handle when something is edited.
|
||||
self.reflow_grids(force_reload_from_master=old_len != new_len)
|
||||
self.reflow_grids(
|
||||
force_reload_from_master=(old_len != new_len or force))
|
||||
stop_loading()
|
||||
|
||||
future = self.get_model_list_future(before_download=start_loading)
|
||||
print('update grid')
|
||||
future = self.get_model_list_future(
|
||||
before_download=start_loading,
|
||||
force=force,
|
||||
)
|
||||
future.add_done_callback(lambda f: GLib.idle_add(future_done, f))
|
||||
|
||||
def create_widget(self, item):
|
||||
@@ -234,7 +243,7 @@ class CoverArtGrid(Gtk.ScrolledWindow):
|
||||
'get_info_text must be implemented by the inheritor of '
|
||||
'CoverArtGrid.')
|
||||
|
||||
def get_model_list_future(self, before_download):
|
||||
def get_model_list_future(self, before_download, force=False):
|
||||
raise NotImplementedError(
|
||||
'get_model_list_future must be implemented by the inheritor of '
|
||||
'CoverArtGrid.')
|
||||
|
Reference in New Issue
Block a user