Started laying framework for paging

This commit is contained in:
Sumner Evans
2019-10-18 00:02:20 -06:00
parent 8261f0d9c5
commit a18ee58582
3 changed files with 99 additions and 61 deletions

View File

@@ -459,35 +459,56 @@ class CacheManager(metaclass=Singleton):
return CacheManager.create_future(do_get_artist_artwork_filename)
def get_albums(
def get_albums_future_generator(
self,
type_: str,
size: int = 30,
page_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]:
"""
Note: We handle invalidating the cache manually. Never force. We
invalidate the cache ourselves (force is used when sort params
change).
"""
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 handle random.
if not self.cache.get(cache_name, {}).get(type_) or force:
before_download()
albums = server_fn(type_, size=size, **params)
# TODO make this invalidate instead of delete
if force and self.cache.get(cache_name, {}).get(type_):
with self.cache_lock:
self.cache[cache_name][type_] = albums.album
self.cache[cache_name][type_] = []
self.save_cache_info()
return self.cache[cache_name][type_]
cached_values = self.cache.get(cache_name, {}).get(type_, [])
offset = len(cached_values)
return CacheManager.create_future(do_get_albums)
# First, yield whatever's cached.
yield from cached_values
while True:
yield 'network barrier'
before_download()
page = server_fn(type_, size=page_size, offset=offset).album
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(
self,

View File

@@ -179,7 +179,7 @@ class AlbumsPanel(Gtk.Box):
self.emit(
'refresh-window',
{'current_album_sort': new_active_sort},
True,
False,
)
def on_alphabetical_type_change(self, combo):
@@ -188,7 +188,7 @@ class AlbumsPanel(Gtk.Box):
self.emit(
'refresh-window',
{'current_album_alphabetical_sort': new_active_alphabetical_sort},
True,
False,
)
def on_genre_change(self, combo):
@@ -199,7 +199,7 @@ class AlbumsPanel(Gtk.Box):
self.emit(
'refresh-window',
{'current_album_genre': new_active_genre},
True,
False,
)
def on_year_changed(self, entry):
@@ -212,17 +212,17 @@ class AlbumsPanel(Gtk.Box):
if self.to_year_entry == entry:
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:
self.grid.update_params(from_year=year)
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):
self.emit(
'refresh-window',
{'selected_album_id': id},
True,
False,
)
@@ -270,7 +270,7 @@ 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, force=False):
def get_new_model_generator(self, before_download=None, force=False):
type_ = self.type_
if self.type_ == 'alphabetical':
type_ += {
@@ -278,17 +278,13 @@ class AlbumsGrid(CoverArtGrid):
'artist': 'ByArtist',
}[self.alphabetical_type]
return CacheManager.get_albums(
yield from CacheManager.get_albums_future_generator(
type_=type_,
to_year=self.to_year,
from_year=self.from_year,
genre=self.genre,
before_download=before_download,
# We handle invalidating the cache manually. Never force. We
# invalidate the cache ourselves (force is used when sort params
# change).
force=False,
force=force,
)
def create_model_from_element(self, album):

View File

@@ -6,6 +6,7 @@ gi.require_version('Gtk', '3.0')
from gi.repository import GLib, Gtk, GObject, Gio, Pango
from sublime.state_manager import ApplicationState
from libremsonic.cache_manager import CacheManager
from .spinner_image import SpinnerImage
@@ -29,6 +30,8 @@ class CoverArtGrid(Gtk.ScrolledWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.model_list_future_generator = None
# This is the master list.
self.list_store = Gio.ListStore()
@@ -110,46 +113,56 @@ class CoverArtGrid(Gtk.ScrolledWindow):
children[0].update(force=force)
def update_grid(self, force=False, selected_id=None):
self.download_occurred = False
def start_loading():
self.download_occurred = True
self.spinner.show()
def stop_loading():
def reflow_grid(force_reload, selected_index):
selection_changed = (selected_index != self.current_selection)
self.current_selection = selected_index
self.reflow_grids(
force_reload_from_master=force_reload,
selection_changed=selection_changed,
)
self.spinner.hide()
def future_done(f):
try:
result = f.result()
except Exception as e:
print('fail', e)
return
# If we don't have a generator yet, then we need to get one.
self.model_list_future_generator = (
self.get_new_model_generator(
before_download=lambda: GLib.idle_add(self.spinner.show),
force=force,
))
def do_update():
old_len = len(self.list_store)
self.list_store.remove_all()
i = 0
selected_index = None
for i, el in enumerate(result or []):
model = self.create_model_from_element(el)
while True:
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:
selected_index = i
i += 1
self.list_store.append(model)
new_len = len(self.list_store)
self.current_selection = selected_index
# Only force if there's a length change.
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,
GLib.idle_add(
reflow_grid,
old_len != len(self.list_store),
selected_index,
)
future.add_done_callback(lambda f: GLib.idle_add(future_done, f))
do_update()
def create_widget(self, item):
widget_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
@@ -199,7 +212,11 @@ class CoverArtGrid(Gtk.ScrolledWindow):
widget_box.show_all()
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.
entries_before_fold = len(self.list_store)
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]
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.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 '
'CoverArtGrid.')
def get_model_list_future(self, before_download, force=False):
def get_new_model_generator(self, before_download, force=False):
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.')
def create_model_from_element(self, el):