caching indicators, scrubbing, next, prev
This commit is contained in:
@@ -12,6 +12,7 @@ from .ui.configure_servers import ConfigureServersDialog
|
||||
|
||||
from .state_manager import ApplicationState
|
||||
from .cache_manager import CacheManager
|
||||
from .server.api_objects import Child
|
||||
|
||||
|
||||
class LibremsonicApp(Gtk.Application):
|
||||
@@ -38,6 +39,18 @@ class LibremsonicApp(Gtk.Application):
|
||||
self.window.player_controls.update_scrubber(
|
||||
value, self.state.current_song.duration)
|
||||
|
||||
@self.player.property_observer('eof-reached')
|
||||
def file_end(_, value):
|
||||
print('eof', value)
|
||||
return
|
||||
if value is None:
|
||||
# TODO handle repeat
|
||||
current_idx = self.state.play_queue.index(
|
||||
self.state.current_song)
|
||||
has_next_song = current_idx < len(self.state.play_queue) - 1
|
||||
if has_next_song:
|
||||
self.on_next_track(None, None)
|
||||
|
||||
# Handle command line option parsing.
|
||||
def do_command_line(self, command_line):
|
||||
options = command_line.get_options_dict()
|
||||
@@ -99,6 +112,7 @@ class LibremsonicApp(Gtk.Application):
|
||||
self.window.stack.connect('notify::visible-child',
|
||||
self.on_stack_change)
|
||||
self.window.connect('song-clicked', self.on_song_clicked)
|
||||
self.window.player_controls.connect('song-scrub', self.on_song_scrub)
|
||||
|
||||
# Display the window.
|
||||
self.window.show_all()
|
||||
@@ -122,16 +136,18 @@ class LibremsonicApp(Gtk.Application):
|
||||
self.show_configure_servers_dialog()
|
||||
|
||||
def on_play_pause(self, action, param):
|
||||
self.player.command('cycle', 'pause')
|
||||
self.player.cycle('pause')
|
||||
self.state.playing = not self.state.playing
|
||||
|
||||
self.update_window()
|
||||
|
||||
def on_next_track(self, action, params):
|
||||
self.player.playlist_next()
|
||||
current_idx = self.state.play_queue.index(self.state.current_song)
|
||||
self.play_song(self.state.play_queue[current_idx + 1])
|
||||
|
||||
def on_prev_track(self, action, params):
|
||||
self.player.playlist_prev()
|
||||
current_idx = self.state.play_queue.index(self.state.current_song)
|
||||
self.play_song(self.state.play_queue[current_idx - 1])
|
||||
|
||||
def on_repeat_press(self, action, params):
|
||||
print('repeat press')
|
||||
@@ -163,12 +179,16 @@ class LibremsonicApp(Gtk.Application):
|
||||
def on_song_clicked(self, win, song_id, song_queue):
|
||||
CacheManager.save_play_queue(id=song_queue, current=song_id)
|
||||
song = CacheManager.get_song(song_id)
|
||||
self.state.current_song = song
|
||||
self.state.play_queue = [CacheManager.get_song(s) for s in song_queue]
|
||||
self.state.playing = True
|
||||
# print(CacheManager.get_play_queue())
|
||||
song_file = CacheManager.get_song_filename(song)
|
||||
self.player.loadfile(song_file)
|
||||
self.update_window()
|
||||
self.play_song(song)
|
||||
|
||||
def on_song_scrub(self, _, scrub_value):
|
||||
if not hasattr(self.state, 'current_song'):
|
||||
return
|
||||
|
||||
new_time = self.state.current_song.duration * (scrub_value / 100)
|
||||
self.player.command('seek', str(new_time), 'absolute')
|
||||
|
||||
# ########## HELPER METHODS ########## #
|
||||
def show_configure_servers_dialog(self):
|
||||
@@ -182,3 +202,10 @@ class LibremsonicApp(Gtk.Application):
|
||||
|
||||
def update_window(self):
|
||||
self.window.update(self.state)
|
||||
|
||||
def play_song(self, song: Child):
|
||||
self.state.current_song = song
|
||||
song_file = CacheManager.get_song_filename(song)
|
||||
self.player.loadfile(song_file, 'replace')
|
||||
self.player.pause = False
|
||||
self.update_window()
|
||||
|
@@ -1,11 +1,11 @@
|
||||
import os
|
||||
import json
|
||||
|
||||
from enum import EnumMeta
|
||||
from enum import EnumMeta, Enum
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from collections import defaultdict
|
||||
from typing import DefaultDict, Dict, List, Optional, Tuple
|
||||
from typing import Dict, List, Optional, Union, Callable, Set
|
||||
|
||||
from libremsonic.config import AppConfiguration, ServerConfiguration
|
||||
from libremsonic.server import Server
|
||||
@@ -27,6 +27,13 @@ class Singleton(type):
|
||||
return None
|
||||
|
||||
|
||||
class SongCacheStatus(Enum):
|
||||
NOT_CACHED = 0
|
||||
CACHED = 1
|
||||
PERMANENTLY_CACHED = 2
|
||||
DOWNLOADING = 3
|
||||
|
||||
|
||||
class CacheManager(metaclass=Singleton):
|
||||
class CacheEncoder(json.JSONEncoder):
|
||||
def default(self, obj):
|
||||
@@ -43,16 +50,9 @@ class CacheManager(metaclass=Singleton):
|
||||
server: Server
|
||||
playlists: Optional[List[Playlist]] = None
|
||||
playlist_details: Dict[int, PlaylistWithSongs] = {}
|
||||
|
||||
# {id -> {size -> file_location}}
|
||||
cover_art: DefaultDict[str, Dict[str, str]] = defaultdict(dict)
|
||||
|
||||
# {id -> Child}
|
||||
permanently_cached_paths: Set[str] = set()
|
||||
song_details: Dict[int, Child] = {}
|
||||
|
||||
# { (artist, album, title) -> file_location }
|
||||
song_cache: Dict[str, str] = {}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
app_config: AppConfiguration,
|
||||
@@ -69,8 +69,7 @@ class CacheManager(metaclass=Singleton):
|
||||
self.load_cache_info()
|
||||
|
||||
def load_cache_info(self):
|
||||
cache_location = Path(self.app_config.cache_location)
|
||||
cache_meta_file = cache_location.joinpath('.cache_meta')
|
||||
cache_meta_file = self.calculate_abs_path('.cache_meta')
|
||||
|
||||
if not cache_meta_file.exists():
|
||||
return
|
||||
@@ -88,42 +87,51 @@ class CacheManager(metaclass=Singleton):
|
||||
id: PlaylistWithSongs.from_json(v)
|
||||
for id, v in meta_json.get('playlist_details', {}).items()
|
||||
}
|
||||
self.cover_art = defaultdict(dict,
|
||||
**meta_json.get('cover_art', {}))
|
||||
self.song_details = {
|
||||
id: Child.from_json(v)
|
||||
for id, v in meta_json.get('song_details', {}).items()
|
||||
}
|
||||
self.song_cache = dict(**meta_json.get('song_cache', {}))
|
||||
self.permanently_cached_paths = set(
|
||||
meta_json.get('permanently_cached_paths', []))
|
||||
|
||||
def save_cache_info(self):
|
||||
cache_location = Path(self.app_config.cache_location)
|
||||
os.makedirs(cache_location, exist_ok=True)
|
||||
os.makedirs(self.app_config.cache_location, exist_ok=True)
|
||||
|
||||
cache_meta_file = cache_location.joinpath('.cache_meta')
|
||||
cache_meta_file = self.calculate_abs_path('.cache_meta')
|
||||
with open(cache_meta_file, 'w+') as f:
|
||||
cache_info = dict(
|
||||
playlists=self.playlists,
|
||||
playlist_details=self.playlist_details,
|
||||
song_details=self.song_details,
|
||||
permanently_cached_paths=list(
|
||||
self.permanently_cached_paths),
|
||||
)
|
||||
f.write(
|
||||
json.dumps(
|
||||
dict(
|
||||
playlists=self.playlists,
|
||||
playlist_details=self.playlist_details,
|
||||
cover_art=self.cover_art,
|
||||
song_details=self.song_details,
|
||||
song_cache=self.song_cache,
|
||||
),
|
||||
indent=2,
|
||||
cls=CacheManager.CacheEncoder,
|
||||
))
|
||||
|
||||
def save_file(self, relative_path: str, data: bytes) -> Path:
|
||||
cache_location = Path(self.app_config.cache_location)
|
||||
absolute_path = cache_location.joinpath(relative_path)
|
||||
json.dumps(cache_info,
|
||||
indent=2,
|
||||
cls=CacheManager.CacheEncoder))
|
||||
|
||||
def save_file(self, absolute_path: Path, data: bytes):
|
||||
# Make the necessary directories and write to file.
|
||||
os.makedirs(absolute_path.parent, exist_ok=True)
|
||||
with open(absolute_path, 'wb+') as f:
|
||||
f.write(data)
|
||||
return absolute_path
|
||||
|
||||
def calculate_abs_path(self, relative_path):
|
||||
return Path(self.app_config.cache_location).joinpath(relative_path)
|
||||
|
||||
def return_cache_or_download(
|
||||
self,
|
||||
relative_path: Union[Path, str],
|
||||
download_fn: Callable[[], bytes],
|
||||
force: bool = False,
|
||||
):
|
||||
abs_path = self.calculate_abs_path(relative_path)
|
||||
if not abs_path.exists() or force:
|
||||
print(abs_path, 'not found. Downloading...')
|
||||
self.save_file(abs_path, download_fn())
|
||||
|
||||
return str(abs_path)
|
||||
|
||||
def get_playlists(self, force: bool = False) -> List[Playlist]:
|
||||
if not self.playlists or force:
|
||||
@@ -147,35 +155,38 @@ class CacheManager(metaclass=Singleton):
|
||||
def get_cover_art_filename(
|
||||
self,
|
||||
id: str,
|
||||
size: str = '200',
|
||||
size: Union[str, int] = 200,
|
||||
force: bool = False,
|
||||
) -> str:
|
||||
if not self.cover_art[id].get(size):
|
||||
raw_cover = self.server.get_cover_art(id, size)
|
||||
abs_path = self.save_file(f'cover_art/{id}_{size}', raw_cover)
|
||||
self.cover_art[id][size] = str(abs_path)
|
||||
self.save_cache_info()
|
||||
print('cover art cache not hit')
|
||||
return self.return_cache_or_download(
|
||||
f'cover_art/{id}_{size}',
|
||||
lambda: self.server.get_cover_art(id, str(size)),
|
||||
force=force,
|
||||
)
|
||||
|
||||
return self.cover_art[id][size]
|
||||
|
||||
def get_song(self, song_id: int, force: bool = False):
|
||||
def get_song(self, song_id: int, force: bool = False) -> Child:
|
||||
if not self.song_details.get(song_id) or force:
|
||||
self.song_details[song_id] = self.server.get_song(song_id)
|
||||
self.save_cache_info()
|
||||
print('song info cache not hit')
|
||||
|
||||
return self.song_details[song_id]
|
||||
|
||||
def get_song_filename(self, song: Child, force: bool = False):
|
||||
if not self.song_cache.get(song.id) or force:
|
||||
raw_song = self.server.download(song.id)
|
||||
abs_path = self.save_file(song.path, raw_song)
|
||||
self.song_cache[song.id] = str(abs_path)
|
||||
self.save_cache_info()
|
||||
print('song file cache not hit')
|
||||
def get_song_filename(self, song: Child, force: bool = False) -> str:
|
||||
return self.return_cache_or_download(
|
||||
song.path,
|
||||
lambda: self.server.download(song.id),
|
||||
force=force,
|
||||
)
|
||||
|
||||
return self.song_cache[song.id]
|
||||
def get_cached_status(self, song: Child) -> SongCacheStatus:
|
||||
path = self.calculate_abs_path(song.path)
|
||||
if path.exists():
|
||||
if path in self.permanently_cached_paths:
|
||||
return SongCacheStatus.PERMANENTLY_CACHED
|
||||
else:
|
||||
return SongCacheStatus.CACHED
|
||||
|
||||
return SongCacheStatus.NOT_CACHED
|
||||
|
||||
_instance: Optional[__CacheManagerInternal] = None
|
||||
|
||||
|
@@ -7,11 +7,11 @@ from .server.api_objects import Child
|
||||
|
||||
class ApplicationState:
|
||||
config: AppConfiguration = AppConfiguration.get_default_configuration()
|
||||
current_song: Child # TODO fix
|
||||
current_song: Child
|
||||
config_file: str
|
||||
playing: bool = False
|
||||
song_progress: float = 0.0
|
||||
up_next: List[Any] # TODO should be song
|
||||
play_queue: List[Child]
|
||||
volume: int = 100
|
||||
|
||||
def load_config(self):
|
||||
|
@@ -1,8 +1,7 @@
|
||||
import gi
|
||||
import sys
|
||||
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gio, Gtk, GObject
|
||||
from gi.repository import Gtk, GObject
|
||||
|
||||
from libremsonic.state_manager import ApplicationState
|
||||
|
||||
|
@@ -21,7 +21,7 @@ class MainWindow(Gtk.ApplicationWindow):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.set_default_size(1024, 768)
|
||||
self.set_default_size(1100, 768)
|
||||
|
||||
self.panels = {
|
||||
'Albums': albums.AlbumsPanel(),
|
||||
@@ -45,7 +45,6 @@ class MainWindow(Gtk.ApplicationWindow):
|
||||
flowbox.pack_start(self.player_controls, False, True, 0)
|
||||
self.add(flowbox)
|
||||
|
||||
# TODO the song should eventually be an API object...
|
||||
def update(self, state: ApplicationState):
|
||||
# Update the Connected to label on the popup menu.
|
||||
if state.config.current_server >= 0:
|
||||
|
@@ -3,7 +3,7 @@ import math
|
||||
import gi
|
||||
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gio, Gtk, Gtk
|
||||
from gi.repository import Gtk, Pango, GObject
|
||||
|
||||
from libremsonic.state_manager import ApplicationState
|
||||
from libremsonic.cache_manager import CacheManager
|
||||
@@ -14,6 +14,14 @@ class PlayerControls(Gtk.ActionBar):
|
||||
"""
|
||||
Defines the player controls panel that appears at the bottom of the window.
|
||||
"""
|
||||
__gsignals__ = {
|
||||
'song-scrub': (
|
||||
GObject.SIGNAL_RUN_FIRST,
|
||||
GObject.TYPE_NONE,
|
||||
(float, ),
|
||||
),
|
||||
}
|
||||
editing: bool = False
|
||||
|
||||
def __init__(self):
|
||||
Gtk.ActionBar.__init__(self)
|
||||
@@ -32,13 +40,28 @@ class PlayerControls(Gtk.ActionBar):
|
||||
self.play_button.get_child().set_from_icon_name(
|
||||
f"media-playback-{icon}-symbolic", Gtk.IconSize.LARGE_TOOLBAR)
|
||||
|
||||
if not hasattr(state, 'current_song'):
|
||||
has_current_song = hasattr(state, 'current_song')
|
||||
has_prev_song, has_next_song = False, False
|
||||
if has_current_song:
|
||||
# TODO will need to change when repeat is implemented
|
||||
has_prev_song = state.play_queue.index(state.current_song) > 0
|
||||
has_next_song = state.play_queue.index(state.current_song) < len(
|
||||
state.play_queue) - 1
|
||||
|
||||
self.song_scrubber.set_sensitive(has_current_song)
|
||||
self.repeat_button.set_sensitive(has_current_song)
|
||||
self.prev_button.set_sensitive(has_current_song and has_prev_song)
|
||||
self.play_button.set_sensitive(has_current_song)
|
||||
self.next_button.set_sensitive(has_current_song and has_next_song)
|
||||
self.shuffle_button.set_sensitive(has_current_song)
|
||||
|
||||
if not has_current_song:
|
||||
return
|
||||
|
||||
self.album_art.set_from_file(
|
||||
CacheManager.get_cover_art_filename(
|
||||
state.current_song.coverArt,
|
||||
size=70,
|
||||
size='70',
|
||||
))
|
||||
|
||||
def esc(string):
|
||||
@@ -48,6 +71,25 @@ class PlayerControls(Gtk.ActionBar):
|
||||
self.album_name.set_markup(f'<i>{esc(state.current_song.album)}</i>')
|
||||
self.artist_name.set_markup(f'{esc(state.current_song.artist)}')
|
||||
|
||||
def update_scrubber(self, current, duration):
|
||||
current = current or 0
|
||||
percent_complete = current / duration * 100
|
||||
|
||||
if not self.editing:
|
||||
self.song_scrubber.set_value(percent_complete)
|
||||
self.song_duration_label.set_text(util.format_song_duration(duration))
|
||||
self.song_progress_label.set_text(
|
||||
util.format_song_duration(math.floor(current)))
|
||||
|
||||
def on_scrub_state_change(self, scrubber_container, eventbutton):
|
||||
self.editing = not self.editing
|
||||
|
||||
if not self.editing:
|
||||
self.emit('song-scrub', self.song_scrubber.get_value())
|
||||
|
||||
def on_scrub_edit(self, box, event):
|
||||
print(event.x)
|
||||
|
||||
def create_song_display(self):
|
||||
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
|
||||
|
||||
@@ -57,8 +99,13 @@ class PlayerControls(Gtk.ActionBar):
|
||||
details_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
||||
details_box.pack_start(Gtk.Box(), True, True, 0)
|
||||
|
||||
def make_label(n):
|
||||
return Gtk.Label(name=n, halign=Gtk.Align.START, use_markup=True)
|
||||
def make_label(name):
|
||||
return Gtk.Label(
|
||||
name=name,
|
||||
halign=Gtk.Align.START,
|
||||
use_markup=True,
|
||||
ellipsize=Pango.EllipsizeMode.END,
|
||||
)
|
||||
|
||||
self.song_title = make_label('song-title')
|
||||
details_box.add(self.song_title)
|
||||
@@ -70,7 +117,7 @@ class PlayerControls(Gtk.ActionBar):
|
||||
details_box.add(self.artist_name)
|
||||
|
||||
details_box.pack_start(Gtk.Box(), True, True, 0)
|
||||
box.pack_start(details_box, True, True, 5)
|
||||
box.pack_start(details_box, False, False, 5)
|
||||
|
||||
return box
|
||||
|
||||
@@ -87,6 +134,10 @@ class PlayerControls(Gtk.ActionBar):
|
||||
orientation=Gtk.Orientation.HORIZONTAL, min=0, max=100, step=5)
|
||||
self.song_scrubber.set_name('song-scrubber')
|
||||
self.song_scrubber.set_draw_value(False)
|
||||
self.song_scrubber.connect('button-press-event',
|
||||
self.on_scrub_state_change)
|
||||
self.song_scrubber.connect('button-release-event',
|
||||
self.on_scrub_state_change)
|
||||
scrubber_box.pack_start(self.song_scrubber, True, True, 0)
|
||||
|
||||
self.song_duration_label = Gtk.Label('-:--')
|
||||
@@ -104,11 +155,11 @@ class PlayerControls(Gtk.ActionBar):
|
||||
buttons.pack_start(self.repeat_button, False, False, 5)
|
||||
|
||||
# Previous button
|
||||
previous_button = util.button_with_icon(
|
||||
self.prev_button = util.button_with_icon(
|
||||
'media-skip-backward-symbolic',
|
||||
icon_size=Gtk.IconSize.LARGE_TOOLBAR)
|
||||
previous_button.set_action_name('app.prev-track')
|
||||
buttons.pack_start(previous_button, False, False, 5)
|
||||
self.prev_button.set_action_name('app.prev-track')
|
||||
buttons.pack_start(self.prev_button, False, False, 5)
|
||||
|
||||
# Play button
|
||||
self.play_button = util.button_with_icon(
|
||||
@@ -120,11 +171,11 @@ class PlayerControls(Gtk.ActionBar):
|
||||
buttons.pack_start(self.play_button, False, False, 0)
|
||||
|
||||
# Next button
|
||||
next_button = util.button_with_icon(
|
||||
self.next_button = util.button_with_icon(
|
||||
'media-skip-forward-symbolic',
|
||||
icon_size=Gtk.IconSize.LARGE_TOOLBAR)
|
||||
next_button.set_action_name('app.next-track')
|
||||
buttons.pack_start(next_button, False, False, 5)
|
||||
self.next_button.set_action_name('app.next-track')
|
||||
buttons.pack_start(self.next_button, False, False, 5)
|
||||
|
||||
# Shuffle button
|
||||
self.shuffle_button = util.button_with_icon(
|
||||
@@ -165,11 +216,3 @@ class PlayerControls(Gtk.ActionBar):
|
||||
vbox.pack_start(box, False, True, 0)
|
||||
vbox.pack_start(Gtk.Box(), True, True, 0)
|
||||
return vbox
|
||||
|
||||
def update_scrubber(self, current, duration):
|
||||
current = current or 0
|
||||
percent_complete = current / duration * 100
|
||||
self.song_scrubber.set_value(percent_complete)
|
||||
self.song_duration_label.set_text(util.format_song_duration(duration))
|
||||
self.song_progress_label.set_text(
|
||||
util.format_song_duration(math.floor(current)))
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import gi
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
gi.require_version('Gtk', '3.0')
|
||||
@@ -8,7 +7,7 @@ from gi.repository import Gio, Gtk, Pango, GObject
|
||||
|
||||
from libremsonic.server.api_objects import Child, PlaylistWithSongs
|
||||
from libremsonic.state_manager import ApplicationState
|
||||
from libremsonic.cache_manager import CacheManager
|
||||
from libremsonic.cache_manager import CacheManager, SongCacheStatus
|
||||
from libremsonic.ui import util
|
||||
|
||||
|
||||
@@ -111,15 +110,31 @@ class PlaylistsPanel(Gtk.Paned):
|
||||
column.set_expand(not width)
|
||||
return column
|
||||
|
||||
self.playlist_song_model = Gtk.ListStore(str, str, str, str, str)
|
||||
self.playlist_song_model = Gtk.ListStore(
|
||||
str, # cache status
|
||||
str, # title
|
||||
str, # album
|
||||
str, # artist
|
||||
str, # duration
|
||||
str, # song ID
|
||||
)
|
||||
|
||||
self.playlist_songs = Gtk.TreeView(model=self.playlist_song_model,
|
||||
margin_top=15)
|
||||
self.playlist_songs.append_column(create_column('TITLE', 0, bold=True))
|
||||
self.playlist_songs.append_column(create_column('ALBUM', 1))
|
||||
self.playlist_songs.append_column(create_column('ARTIST', 2))
|
||||
|
||||
# Song status column.
|
||||
renderer = Gtk.CellRendererPixbuf()
|
||||
renderer.set_fixed_size(30, 35)
|
||||
|
||||
column = Gtk.TreeViewColumn('', renderer, icon_name=0)
|
||||
column.set_resizable(True)
|
||||
self.playlist_songs.append_column(column)
|
||||
|
||||
self.playlist_songs.append_column(create_column('TITLE', 1, bold=True))
|
||||
self.playlist_songs.append_column(create_column('ALBUM', 2))
|
||||
self.playlist_songs.append_column(create_column('ARTIST', 3))
|
||||
self.playlist_songs.append_column(
|
||||
create_column('DURATION', 3, align=1, width=40))
|
||||
create_column('DURATION', 4, align=1, width=40))
|
||||
|
||||
self.playlist_songs.connect('row-activated', self.on_song_double_click)
|
||||
|
||||
@@ -139,13 +154,24 @@ class PlaylistsPanel(Gtk.Paned):
|
||||
CacheManager.get_cover_art_filename(playlist.coverArt))
|
||||
self.playlist_indicator.set_markup('PLAYLIST')
|
||||
self.playlist_name.set_markup(f'<b>{playlist.name}</b>')
|
||||
self.playlist_comment.set_text(playlist.comment or '')
|
||||
if playlist.comment:
|
||||
self.playlist_comment.set_text(playlist.comment)
|
||||
self.playlist_comment.show()
|
||||
else:
|
||||
self.playlist_comment.hide()
|
||||
self.playlist_stats.set_markup(self.format_stats(playlist))
|
||||
|
||||
# Update the song list model
|
||||
self.playlist_song_model.clear()
|
||||
for song in (playlist.entry or []):
|
||||
cache_icon = {
|
||||
SongCacheStatus.NOT_CACHED: '',
|
||||
SongCacheStatus.CACHED: 'folder-download-symbolic',
|
||||
SongCacheStatus.PERMANENTLY_CACHED: 'view-pin-symbolic',
|
||||
SongCacheStatus.DOWNLOADING: 'folder-download-symbolic',
|
||||
}
|
||||
self.playlist_song_model.append([
|
||||
cache_icon[CacheManager.get_cached_status(song)],
|
||||
song.title,
|
||||
song.album,
|
||||
song.artist,
|
||||
@@ -159,9 +185,10 @@ class PlaylistsPanel(Gtk.Paned):
|
||||
self.update_playlist_list(force=True)
|
||||
|
||||
def on_song_double_click(self, treeview, idx, column):
|
||||
song_id = self.playlist_song_model[idx][4]
|
||||
# The song ID is in the last column of the model.
|
||||
song_id = self.playlist_song_model[idx][-1]
|
||||
self.emit('song-clicked', song_id,
|
||||
[m[4] for m in self.playlist_song_model])
|
||||
[m[-1] for m in self.playlist_song_model])
|
||||
|
||||
# Helper Methods
|
||||
# =========================================================================
|
||||
@@ -172,13 +199,16 @@ class PlaylistsPanel(Gtk.Paned):
|
||||
self.update_playlist_list()
|
||||
|
||||
def update_playlist_list(self, force=False):
|
||||
self.playlist_ids = []
|
||||
for c in self.playlist_list.get_children():
|
||||
self.playlist_list.remove(c)
|
||||
|
||||
not_seen = set(self.playlist_ids)
|
||||
for playlist in CacheManager.get_playlists(force=force):
|
||||
self.playlist_ids.append(playlist.id)
|
||||
self.playlist_list.add(self.create_playlist_label(playlist))
|
||||
if playlist.id not in self.playlist_ids:
|
||||
self.playlist_ids.append(playlist.id)
|
||||
self.playlist_list.add(self.create_playlist_label(playlist))
|
||||
else:
|
||||
not_seen.remove(playlist.id)
|
||||
|
||||
for playlist_id in not_seen:
|
||||
print(playlist_id)
|
||||
|
||||
self.playlist_list.show_all()
|
||||
|
||||
|
Reference in New Issue
Block a user