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

View File

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

View File

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