Started laying framework for paging
This commit is contained in:
@@ -459,35 +459,56 @@ class CacheManager(metaclass=Singleton):
|
|||||||
|
|
||||||
return CacheManager.create_future(do_get_artist_artwork_filename)
|
return CacheManager.create_future(do_get_artist_artwork_filename)
|
||||||
|
|
||||||
def get_albums(
|
def get_albums_future_generator(
|
||||||
self,
|
self,
|
||||||
type_: str,
|
type_: str,
|
||||||
size: int = 30,
|
page_size: int = 30,
|
||||||
before_download: Callable[[], None] = lambda: None,
|
before_download: Callable[[], None] = lambda: None,
|
||||||
force: bool = False,
|
force: bool = False,
|
||||||
# Look at documentation for get_album_list in server.py:
|
# Look at documentation for get_album_list in server.py:
|
||||||
**params,
|
**params,
|
||||||
) -> Future:
|
) -> Future:
|
||||||
def do_get_albums() -> List[Child]:
|
"""
|
||||||
cache_name = self.id3ify('albums')
|
Note: We handle invalidating the cache manually. Never force. We
|
||||||
server_fn = (
|
invalidate the cache ourselves (force is used when sort params
|
||||||
self.server.get_album_list2
|
change).
|
||||||
if self.browse_by_tags else self.server.get_album_list)
|
"""
|
||||||
|
cache_name = self.id3ify('albums')
|
||||||
|
server_fn = (
|
||||||
|
self.server.get_album_list2
|
||||||
|
if self.browse_by_tags else self.server.get_album_list)
|
||||||
|
|
||||||
# TODO handle other parameters
|
# TODO make this invalidate instead of delete
|
||||||
# TODO handle random.
|
if force and self.cache.get(cache_name, {}).get(type_):
|
||||||
if not self.cache.get(cache_name, {}).get(type_) or force:
|
with self.cache_lock:
|
||||||
before_download()
|
self.cache[cache_name][type_] = []
|
||||||
albums = server_fn(type_, size=size, **params)
|
self.save_cache_info()
|
||||||
|
|
||||||
with self.cache_lock:
|
cached_values = self.cache.get(cache_name, {}).get(type_, [])
|
||||||
self.cache[cache_name][type_] = albums.album
|
offset = len(cached_values)
|
||||||
|
|
||||||
self.save_cache_info()
|
# First, yield whatever's cached.
|
||||||
|
yield from cached_values
|
||||||
|
|
||||||
return self.cache[cache_name][type_]
|
while True:
|
||||||
|
yield 'network barrier'
|
||||||
|
before_download()
|
||||||
|
page = server_fn(type_, size=page_size, offset=offset).album
|
||||||
|
|
||||||
return CacheManager.create_future(do_get_albums)
|
with self.cache_lock:
|
||||||
|
if not self.cache[cache_name].get(type_):
|
||||||
|
self.cache[cache_name][type_] = []
|
||||||
|
self.cache[cache_name][type_].extend(page)
|
||||||
|
self.save_cache_info()
|
||||||
|
|
||||||
|
yield from page
|
||||||
|
|
||||||
|
# If the length of the page is less than the page size, then it
|
||||||
|
# means that we reached the end of the list somewhere in this
|
||||||
|
# page.
|
||||||
|
if len(page) < page_size:
|
||||||
|
break
|
||||||
|
offset += page_size
|
||||||
|
|
||||||
def get_album(
|
def get_album(
|
||||||
self,
|
self,
|
||||||
|
@@ -179,7 +179,7 @@ class AlbumsPanel(Gtk.Box):
|
|||||||
self.emit(
|
self.emit(
|
||||||
'refresh-window',
|
'refresh-window',
|
||||||
{'current_album_sort': new_active_sort},
|
{'current_album_sort': new_active_sort},
|
||||||
True,
|
False,
|
||||||
)
|
)
|
||||||
|
|
||||||
def on_alphabetical_type_change(self, combo):
|
def on_alphabetical_type_change(self, combo):
|
||||||
@@ -188,7 +188,7 @@ class AlbumsPanel(Gtk.Box):
|
|||||||
self.emit(
|
self.emit(
|
||||||
'refresh-window',
|
'refresh-window',
|
||||||
{'current_album_alphabetical_sort': new_active_alphabetical_sort},
|
{'current_album_alphabetical_sort': new_active_alphabetical_sort},
|
||||||
True,
|
False,
|
||||||
)
|
)
|
||||||
|
|
||||||
def on_genre_change(self, combo):
|
def on_genre_change(self, combo):
|
||||||
@@ -199,7 +199,7 @@ class AlbumsPanel(Gtk.Box):
|
|||||||
self.emit(
|
self.emit(
|
||||||
'refresh-window',
|
'refresh-window',
|
||||||
{'current_album_genre': new_active_genre},
|
{'current_album_genre': new_active_genre},
|
||||||
True,
|
False,
|
||||||
)
|
)
|
||||||
|
|
||||||
def on_year_changed(self, entry):
|
def on_year_changed(self, entry):
|
||||||
@@ -212,17 +212,17 @@ class AlbumsPanel(Gtk.Box):
|
|||||||
|
|
||||||
if self.to_year_entry == entry:
|
if self.to_year_entry == entry:
|
||||||
self.grid.update_params(to_year=year)
|
self.grid.update_params(to_year=year)
|
||||||
self.emit('refresh-window', {'current_album_to_year': year}, True)
|
self.emit('refresh-window', {'current_album_to_year': year}, False)
|
||||||
else:
|
else:
|
||||||
self.grid.update_params(from_year=year)
|
self.grid.update_params(from_year=year)
|
||||||
self.emit(
|
self.emit(
|
||||||
'refresh-window', {'current_album_from_year': year}, True)
|
'refresh-window', {'current_album_from_year': year}, False)
|
||||||
|
|
||||||
def on_grid_cover_clicked(self, grid, id):
|
def on_grid_cover_clicked(self, grid, id):
|
||||||
self.emit(
|
self.emit(
|
||||||
'refresh-window',
|
'refresh-window',
|
||||||
{'selected_album_id': id},
|
{'selected_album_id': id},
|
||||||
True,
|
False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -270,7 +270,7 @@ class AlbumsGrid(CoverArtGrid):
|
|||||||
def get_info_text(self, item: AlbumModel) -> Optional[str]:
|
def get_info_text(self, item: AlbumModel) -> Optional[str]:
|
||||||
return util.dot_join(item.album.artist, item.album.year)
|
return util.dot_join(item.album.artist, item.album.year)
|
||||||
|
|
||||||
def get_model_list_future(self, before_download, force=False):
|
def get_new_model_generator(self, before_download=None, force=False):
|
||||||
type_ = self.type_
|
type_ = self.type_
|
||||||
if self.type_ == 'alphabetical':
|
if self.type_ == 'alphabetical':
|
||||||
type_ += {
|
type_ += {
|
||||||
@@ -278,17 +278,13 @@ class AlbumsGrid(CoverArtGrid):
|
|||||||
'artist': 'ByArtist',
|
'artist': 'ByArtist',
|
||||||
}[self.alphabetical_type]
|
}[self.alphabetical_type]
|
||||||
|
|
||||||
return CacheManager.get_albums(
|
yield from CacheManager.get_albums_future_generator(
|
||||||
type_=type_,
|
type_=type_,
|
||||||
to_year=self.to_year,
|
to_year=self.to_year,
|
||||||
from_year=self.from_year,
|
from_year=self.from_year,
|
||||||
genre=self.genre,
|
genre=self.genre,
|
||||||
before_download=before_download,
|
before_download=before_download,
|
||||||
|
force=force,
|
||||||
# We handle invalidating the cache manually. Never force. We
|
|
||||||
# invalidate the cache ourselves (force is used when sort params
|
|
||||||
# change).
|
|
||||||
force=False,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def create_model_from_element(self, album):
|
def create_model_from_element(self, album):
|
||||||
|
@@ -6,6 +6,7 @@ gi.require_version('Gtk', '3.0')
|
|||||||
from gi.repository import GLib, Gtk, GObject, Gio, Pango
|
from gi.repository import GLib, Gtk, GObject, Gio, Pango
|
||||||
|
|
||||||
from sublime.state_manager import ApplicationState
|
from sublime.state_manager import ApplicationState
|
||||||
|
from libremsonic.cache_manager import CacheManager
|
||||||
from .spinner_image import SpinnerImage
|
from .spinner_image import SpinnerImage
|
||||||
|
|
||||||
|
|
||||||
@@ -29,6 +30,8 @@ class CoverArtGrid(Gtk.ScrolledWindow):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
self.model_list_future_generator = None
|
||||||
|
|
||||||
# This is the master list.
|
# This is the master list.
|
||||||
self.list_store = Gio.ListStore()
|
self.list_store = Gio.ListStore()
|
||||||
|
|
||||||
@@ -110,46 +113,56 @@ class CoverArtGrid(Gtk.ScrolledWindow):
|
|||||||
children[0].update(force=force)
|
children[0].update(force=force)
|
||||||
|
|
||||||
def update_grid(self, force=False, selected_id=None):
|
def update_grid(self, force=False, selected_id=None):
|
||||||
self.download_occurred = False
|
def reflow_grid(force_reload, selected_index):
|
||||||
|
selection_changed = (selected_index != self.current_selection)
|
||||||
def start_loading():
|
self.current_selection = selected_index
|
||||||
self.download_occurred = True
|
self.reflow_grids(
|
||||||
self.spinner.show()
|
force_reload_from_master=force_reload,
|
||||||
|
selection_changed=selection_changed,
|
||||||
def stop_loading():
|
)
|
||||||
self.spinner.hide()
|
self.spinner.hide()
|
||||||
|
|
||||||
def future_done(f):
|
# If we don't have a generator yet, then we need to get one.
|
||||||
try:
|
self.model_list_future_generator = (
|
||||||
result = f.result()
|
self.get_new_model_generator(
|
||||||
except Exception as e:
|
before_download=lambda: GLib.idle_add(self.spinner.show),
|
||||||
print('fail', e)
|
force=force,
|
||||||
return
|
))
|
||||||
|
|
||||||
|
def do_update():
|
||||||
old_len = len(self.list_store)
|
old_len = len(self.list_store)
|
||||||
self.list_store.remove_all()
|
self.list_store.remove_all()
|
||||||
|
|
||||||
|
i = 0
|
||||||
selected_index = None
|
selected_index = None
|
||||||
for i, el in enumerate(result or []):
|
while True:
|
||||||
model = self.create_model_from_element(el)
|
try:
|
||||||
|
next_el = next(self.model_list_future_generator)
|
||||||
|
except StopIteration:
|
||||||
|
break
|
||||||
|
|
||||||
|
# Stop once we hit a network barrier (unless the list hasn't
|
||||||
|
# been loaded).
|
||||||
|
if next_el == 'network barrier':
|
||||||
|
if len(self.list_store) == 0:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
model = self.create_model_from_element(next_el)
|
||||||
if model.id == selected_id:
|
if model.id == selected_id:
|
||||||
selected_index = i
|
selected_index = i
|
||||||
|
i += 1
|
||||||
|
|
||||||
self.list_store.append(model)
|
self.list_store.append(model)
|
||||||
new_len = len(self.list_store)
|
|
||||||
|
|
||||||
self.current_selection = selected_index
|
GLib.idle_add(
|
||||||
|
reflow_grid,
|
||||||
|
old_len != len(self.list_store),
|
||||||
|
selected_index,
|
||||||
|
)
|
||||||
|
|
||||||
# Only force if there's a length change.
|
do_update()
|
||||||
self.reflow_grids(
|
|
||||||
force_reload_from_master=(
|
|
||||||
old_len != new_len or self.download_occurred))
|
|
||||||
stop_loading()
|
|
||||||
|
|
||||||
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):
|
def create_widget(self, item):
|
||||||
widget_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
widget_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
||||||
@@ -199,7 +212,11 @@ class CoverArtGrid(Gtk.ScrolledWindow):
|
|||||||
widget_box.show_all()
|
widget_box.show_all()
|
||||||
return widget_box
|
return widget_box
|
||||||
|
|
||||||
def reflow_grids(self, force_reload_from_master=False):
|
def reflow_grids(
|
||||||
|
self,
|
||||||
|
force_reload_from_master=False,
|
||||||
|
selection_changed=False,
|
||||||
|
):
|
||||||
# Determine where the cuttoff is between the top and bottom grids.
|
# Determine where the cuttoff is between the top and bottom grids.
|
||||||
entries_before_fold = len(self.list_store)
|
entries_before_fold = len(self.list_store)
|
||||||
if self.current_selection is not None and self.items_per_row:
|
if self.current_selection is not None and self.items_per_row:
|
||||||
@@ -236,6 +253,10 @@ class CoverArtGrid(Gtk.ScrolledWindow):
|
|||||||
del self.list_store_top[-1]
|
del self.list_store_top[-1]
|
||||||
|
|
||||||
if self.current_selection is not None:
|
if self.current_selection is not None:
|
||||||
|
if not selection_changed:
|
||||||
|
return
|
||||||
|
|
||||||
|
# TODO: only do this if the selection actually changed.
|
||||||
self.grid_top.select_child(
|
self.grid_top.select_child(
|
||||||
self.grid_top.get_child_at_index(self.current_selection))
|
self.grid_top.get_child_at_index(self.current_selection))
|
||||||
|
|
||||||
@@ -272,9 +293,9 @@ class CoverArtGrid(Gtk.ScrolledWindow):
|
|||||||
'get_info_text must be implemented by the inheritor of '
|
'get_info_text must be implemented by the inheritor of '
|
||||||
'CoverArtGrid.')
|
'CoverArtGrid.')
|
||||||
|
|
||||||
def get_model_list_future(self, before_download, force=False):
|
def get_new_model_generator(self, before_download, force=False):
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
'get_model_list_future must be implemented by the inheritor of '
|
'get_new_model_generator must be implemented by the inheritor of '
|
||||||
'CoverArtGrid.')
|
'CoverArtGrid.')
|
||||||
|
|
||||||
def create_model_from_element(self, el):
|
def create_model_from_element(self, el):
|
||||||
|
Reference in New Issue
Block a user