From 937a1b26d266936dc60c36d47b64b74d4c5fd308 Mon Sep 17 00:00:00 2001 From: Sumner Evans Date: Tue, 14 Jan 2020 21:04:56 -0700 Subject: [PATCH] Added index listing to browse panel --- sublime/cache_manager.py | 79 +++++++++++++++---- sublime/ui/app_styles.css | 3 +- sublime/ui/browse.py | 156 +++++++++++++++++++++++++++++++++++++- 3 files changed, 219 insertions(+), 19 deletions(-) diff --git a/sublime/cache_manager.py b/sublime/cache_manager.py index 7b5f368..eb88ed0 100644 --- a/sublime/cache_manager.py +++ b/sublime/cache_manager.py @@ -538,22 +538,18 @@ class CacheManager(metaclass=Singleton): self, before_download: Callable[[], None] = lambda: None, force: bool = False, - ) -> 'CacheManager.Result[List[Union[Artist, ArtistID3]]]': - # TODO: no need to id3ify I think. + ) -> 'CacheManager.Result[List[ArtistID3]]': + # This will always end up being artists_id3, but do this for + # consistency. cache_name = self.id3ify('artists') if self.cache.get(cache_name) and not force: return CacheManager.Result.from_data(self.cache[cache_name]) - def download_fn(): - raw_artists = ( - self.server.get_artists - if self.browse_by_tags else self.server.get_indexes)() - - artists: List[Union[Artist, ArtistID3]] = [] - for index in raw_artists.index: + def download_fn() -> List[ArtistID3]: + artists: List[ArtistID3] = [] + for index in self.server.get_artists().index: artists.extend(index.artist) - return artists def after_download(artists): @@ -573,24 +569,75 @@ class CacheManager(metaclass=Singleton): before_download: Callable[[], None] = lambda: None, force: bool = False, ) -> 'CacheManager.Result[Union[ArtistWithAlbumsID3, Child]]': - # TODO: no need to id3ify I think. + # This will always end up being artist_details_id3, but do this for + # consistency. cache_name = self.id3ify('artist_details') if artist_id in self.cache.get(cache_name, {}) and not force: return CacheManager.Result.from_data( self.cache[cache_name][artist_id]) - server_fn = ( - self.server.get_artist - if self.browse_by_tags else self.server.get_music_directory) - def after_download(artist): with self.cache_lock: self.cache[cache_name][artist_id] = artist self.save_cache_info() return CacheManager.Result.from_server( - lambda: server_fn(artist_id), + lambda: self.server.get_artist(artist_id), + before_download=before_download, + after_download=after_download, + ) + + def get_indexes( + self, + before_download: Callable[[], None] = lambda: None, + force: bool = False, + ) -> 'CacheManager.Result[List[Artist]]': + # This will always end up being artists, but do this for + # consistency. + cache_name = self.id3ify('artists') + + if self.cache.get(cache_name) and not force: + return CacheManager.Result.from_data(self.cache[cache_name]) + + def download_fn() -> List[Artist]: + artists: List[Artist] = [] + for index in self.server.get_indexes().index: + artists.extend(index.artist) + return artists + + def after_download(artists): + with self.cache_lock: + self.cache[cache_name] = artists + self.save_cache_info() + + return CacheManager.Result.from_server( + download_fn, + before_download=before_download, + after_download=after_download, + ) + + def get_music_directory( + self, + id, + before_download: Callable[[], None] = lambda: None, + force: bool = False, + ) -> 'CacheManager.Result[Child]': + # This will always end up being artist_details, but do this for + # consistency. + cache_name = self.id3ify('artist_details') + + if id in self.cache.get(cache_name, {}) and not force: + return CacheManager.Result.from_data( + self.cache[cache_name][id]) + + def after_download(artist): + with self.cache_lock: + self.cache[cache_name][id] = artist + self.save_cache_info() + + return CacheManager.Result.from_server( + lambda: self.server.get_music_directory(id), before_download=before_download, after_download=after_download, ) diff --git a/sublime/ui/app_styles.css b/sublime/ui/app_styles.css index 6d911df..dc582a8 100644 --- a/sublime/ui/app_styles.css +++ b/sublime/ui/app_styles.css @@ -21,7 +21,8 @@ } #playlist-list-spinner:checked, -#artist-list-spinner:checked { +#artist-list-spinner:checked, +#directory-list-spinner:checked { margin: 10px; padding: 0px; } diff --git a/sublime/ui/browse.py b/sublime/ui/browse.py index e63fce9..c76fb04 100644 --- a/sublime/ui/browse.py +++ b/sublime/ui/browse.py @@ -3,8 +3,13 @@ import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk, GObject, Pango, GLib, Gio +from sublime.state_manager import ApplicationState +from sublime.cache_manager import CacheManager +from sublime.ui import util +from sublime.ui.common import IconButton -class BrowsePanel(Gtk.Label): + +class BrowsePanel(Gtk.ScrolledWindow): """Defines the arist panel.""" __gsignals__ = { @@ -21,4 +26,151 @@ class BrowsePanel(Gtk.Label): } def __init__(self): - super().__init__(label='ohea') + super().__init__() + + self.root_directory_listing = DirectoryListAndDrilldown(is_root=True) + + self.add(self.root_directory_listing) + + def update(self, state: ApplicationState, force=False): + self.root_directory_listing.update(state=state, force=force) + + +class DirectoryListAndDrilldown(Gtk.Paned): + __gsignals__ = { + 'song-clicked': ( + GObject.SignalFlags.RUN_FIRST, + GObject.TYPE_NONE, + (int, object, object), + ), + 'refresh-window': ( + GObject.SignalFlags.RUN_FIRST, + GObject.TYPE_NONE, + (object, bool), + ), + } + + def __init__(self, is_root=False): + Gtk.Paned.__init__(self, orientation=Gtk.Orientation.HORIZONTAL) + + self.directory_listing = DirectoryList() + self.pack1(self.directory_listing, False, False) + + self.listing_drilldown_panel = Gtk.Box() + self.pack2(self.listing_drilldown_panel, True, False) + + def update(self, state: ApplicationState, force=False): + print('directory list and drilldown update') + if self.is_root: + self.directory_listing.update_root(state=state, force=force) + else: + self.directory_listing.update_not_root(state=state, force=force) + + +class DirectoryList(Gtk.Box): + class SubelementModel(GObject.GObject): + id = GObject.Property(type=str) + name = GObject.Property(type=str) + + def __init__(self, id, name): + GObject.GObject.__init__(self) + self.id = id + self.name = name + + def __init__(self): + Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL) + + list_actions = Gtk.ActionBar() + + refresh = IconButton('view-refresh-symbolic') + refresh.connect('clicked', lambda *a: self.update(force=True)) + list_actions.pack_end(refresh) + + self.add(list_actions) + + self.loading_indicator = Gtk.ListBox() + spinner_row = Gtk.ListBoxRow() + spinner = Gtk.Spinner( + name='directory-list-spinner', + active=True, + ) + spinner_row.add(spinner) + self.loading_indicator.add(spinner_row) + self.pack_start(self.loading_indicator, False, False, 0) + + list_scroll_window = Gtk.ScrolledWindow(min_content_width=250) + + def create_row(model: DirectoryList.SubelementModel): + return Gtk.Label( + label=f'{util.esc(model.name)}', + use_markup=True, + margin=8, + halign=Gtk.Align.START, + ellipsize=Pango.EllipsizeMode.END, + max_width_chars=30, + ) + + self.directory_list_store = Gio.ListStore() + self.list = Gtk.ListBox(name='directory-list') + self.list.bind_model(self.directory_list_store, create_row) + list_scroll_window.add(self.list) + + self.pack_start(list_scroll_window, True, True, 0) + + @util.async_callback( + lambda *a, **k: CacheManager.get_indexes(*a, **k), + before_download=lambda self: self.loading_indicator.show_all(), + on_failure=lambda self, e: self.loading_indicator.hide(), + ) + def update_root( + self, + artists, + state: ApplicationState = None, + force=False, + ): + new_store = [] + selected_idx = None + for i, artist in enumerate(artists): + # if state and state.selected_artist_id == artist.id: + # selected_idx = i + + new_store.append( + DirectoryList.SubelementModel(artist.id, artist.name)) + + util.diff_model_store(self.directory_list_store, new_store) + + # Preserve selection + if selected_idx is not None: + row = self.list.get_row_at_index(selected_idx) + self.list.select_row(row) + + self.loading_indicator.hide() + + @util.async_callback( + lambda *a, **k: CacheManager.get_music_directory(*a, **k), + before_download=lambda self: self.loading_indicator.show_all(), + on_failure=lambda self, e: self.loading_indicator.hide(), + ) + def update_not_root( + self, + artists, + state: ApplicationState = None, + force=False, + ): + new_store = [] + selected_idx = None + for i, artist in enumerate(artists): + # if state and state.selected_artist_id == artist.id: + # selected_idx = i + + new_store.append( + DirectoryList.SubelementModel(artist.id, artist.name)) + + util.diff_model_store(self.directory_list_store, new_store) + + # Preserve selection + if selected_idx is not None: + row = self.list.get_row_at_index(selected_idx) + self.list.select_row(row) + + self.loading_indicator.hide()