Merge branch “issue-36-server-panel” (close #36, #37, #38, #35)

This commit is contained in:
coderkun 2017-12-26 10:16:54 +01:00
commit 16c2ffd775
8 changed files with 1281 additions and 373 deletions

View file

@ -108,10 +108,14 @@ class Client(Base):
SIGNAL_CONNECTION = 'connection'
# Signal: status
SIGNAL_STATUS = 'status'
# Signal: stats
SIGNAL_STATS = 'stats'
# Signal: load albums
SIGNAL_LOAD_ALBUMS = 'load-albums'
# Signal: load playlist
SIGNAL_LOAD_PLAYLIST = 'load-playlist'
# Signal: load audio output devices
SIGNAL_LOAD_OUTPUT_DEVICES = 'load-output-devices'
# Signal: error
SIGNAL_ERROR = 'error'
@ -174,6 +178,25 @@ class Client(Base):
self._add_action(self._get_status)
def get_stats(self):
"""Load statistics."""
self._logger.info("get stats")
self._add_action(self._get_stats)
def get_output_devices(self):
"""Determine the list of audio output devices."""
self._logger.info("get output devices")
self._add_action(self._get_output_devices)
def enable_output_device(self, device, enabled):
"""Enable/disable an audio output device."""
self._logger.info("enable output device")
self._add_action(self._enable_output_device, device, enabled)
def load_albums(self):
self._logger.info("load albums")
self._add_action(self._load_albums)
@ -343,6 +366,9 @@ class Client(Base):
self.load_albums()
self.load_playlist()
self.get_status()
if subsystems['changed'] == 'output':
self.get_output_devices()
self.get_status()
def _noidle(self):
@ -375,10 +401,14 @@ class Client(Base):
if 'error' in status:
error = status['error']
# Album
file = None
album = None
pos = 0
song = self._parse_dict(self._call("currentsong"))
if song:
# File
if 'file' in song:
file = song['file']
# Track
track = self._extract_playlist_track(song)
if track:
@ -391,7 +421,66 @@ class Client(Base):
album = palbum
break
pos = pos - len(palbum.get_tracks())
self._callback(Client.SIGNAL_STATUS, state, album, pos, time, volume, error)
# Audio
audio = None
if 'audio' in status:
audio = status['audio']
# Bitrate
bitrate = None
if 'bitrate' in status:
bitrate = status['bitrate']
self._callback(Client.SIGNAL_STATUS, state, album, pos, time, volume, file, audio, bitrate, error)
def _get_stats(self):
"""Action: Perform the real statistics gathering."""
self._logger.info("getting statistics")
stats = self._parse_dict(self._call("stats"))
self._logger.debug("stats: %r", stats)
# Artists
artists = 0
if 'artists' in stats:
artists = int(stats['artists'])
# Albums
albums = 0
if 'albums' in stats:
albums = int(stats['albums'])
# Songs
songs = 0
if 'songs' in stats:
songs = int(stats['songs'])
# Database playtime
dbplaytime = 0
if 'db_playtime' in stats:
dbplaytime = stats['db_playtime']
# Playtime
playtime = 0
if 'playtime' in stats:
playtime = stats['playtime']
# Uptime
uptime = 0
if 'uptime' in stats:
uptime = stats['uptime']
self._callback(Client.SIGNAL_STATS, artists, albums, songs, dbplaytime, playtime, uptime)
def _get_output_devices(self):
"""Action: Perform the real loading of output devices."""
devices = []
for output in self._parse_list(self._call('outputs'), ['outputid']):
device = OutputDevice(output['outputid'], output['outputname'])
device.set_enabled(int(output['outputenabled']) == 1)
devices.append(device)
self._callback(Client.SIGNAL_LOAD_OUTPUT_DEVICES, devices)
def _enable_output_device(self, device, enabled):
"""Action: Perform the real enabling/disabling of an output device."""
if enabled:
self._call('enableoutput ', device.get_id())
else:
self._call('disableoutput ', device.get_id())
def _load_albums(self):
@ -684,6 +773,33 @@ class Client(Base):
class OutputDevice:
def __init__(self, id, name):
self._id = id
self._name = name
self._enabled = None
def get_id(self):
return self._id
def get_name(self):
return self._name
def set_enabled(self, enabled):
self._enabled = enabled
def is_enabled(self):
return self._enabled
class MCGAlbum:
DEFAULT_ALBUM = 'Various'
_FILE_NAMES = ['cover', 'folder']

View file

@ -88,7 +88,7 @@ class Window():
SETTING_SORT_ORDER = 'sort-order'
SETTING_SORT_TYPE = 'sort-type'
STOCK_ICON_DEFAULT = 'image-x-generic-symbolic'
_PANEL_INDEX_CONNECTION = 0
_PANEL_INDEX_SERVER = 0
_PANEL_INDEX_COVER = 1
_PANEL_INDEX_PLAYLIST = 2
_PANEL_INDEX_LIBRARY = 3
@ -107,8 +107,10 @@ class Window():
self._maximized = self._settings.get_boolean(Window.SETTING_WINDOW_MAXIMIZED)
self._fullscreened = False
# Login screen
self._connection_panel = ConnectionPanel(builder)
# Panels
self._panels.append(ConnectionPanel(builder))
self._panels.append(ServerPanel(builder))
self._panels.append(CoverPanel(builder))
self._panels.append(PlaylistPanel(builder))
self._panels.append(LibraryPanel(builder))
@ -117,6 +119,7 @@ class Window():
# InfoBar
self._infobar = InfoBar(builder)
# Stack
self._content_stack = builder.get_object('contentstack')
self._stack = builder.get_object('panelstack')
# Header
self._header_bar = HeaderBar(builder)
@ -125,11 +128,11 @@ class Window():
# Properties
self._header_bar.set_sensitive(False, False)
self._panels[Window._PANEL_INDEX_CONNECTION].set_host(self._settings.get_string(Window.SETTING_HOST))
self._panels[Window._PANEL_INDEX_CONNECTION].set_port(self._settings.get_int(Window.SETTING_PORT))
self._connection_panel.set_host(self._settings.get_string(Window.SETTING_HOST))
self._connection_panel.set_port(self._settings.get_int(Window.SETTING_PORT))
if use_keyring:
self._panels[Window._PANEL_INDEX_CONNECTION].set_password(keyring.get_password(ZeroconfProvider.KEYRING_SYSTEM, ZeroconfProvider.KEYRING_USERNAME))
self._panels[Window._PANEL_INDEX_CONNECTION].set_image_dir(self._settings.get_string(Window.SETTING_IMAGE_DIR))
self._connection_panel.set_password(keyring.get_password(ZeroconfProvider.KEYRING_SYSTEM, ZeroconfProvider.KEYRING_USERNAME))
self._connection_panel.set_image_dir(self._settings.get_string(Window.SETTING_IMAGE_DIR))
self._panels[Window._PANEL_INDEX_COVER].set_tracklist_size(self._settings.get_enum(Window.SETTING_TRACKLIST_SIZE))
self._panels[Window._PANEL_INDEX_PLAYLIST].set_item_size(self._settings.get_int(Window.SETTING_ITEM_SIZE))
self._panels[Window._PANEL_INDEX_LIBRARY].set_item_size(self._settings.get_int(Window.SETTING_ITEM_SIZE))
@ -141,7 +144,8 @@ class Window():
self._header_bar.connect('toolbar-connect', self.on_header_bar_connect)
self._header_bar.connect('toolbar-playpause', self.on_header_bar_playpause)
self._header_bar.connect('toolbar-set-volume', self.on_header_bar_set_volume)
self._panels[Window._PANEL_INDEX_CONNECTION].connect('connection-changed', self.on_connection_panel_connection_changed)
self._connection_panel.connect('connection-changed', self.on_connection_panel_connection_changed)
self._panels[Window._PANEL_INDEX_SERVER].connect('change-output-device', self.on_server_panel_output_device_changed)
self._panels[Window._PANEL_INDEX_COVER].connect('toggle-fullscreen', self.on_cover_panel_toggle_fullscreen)
self._panels[Window._PANEL_INDEX_COVER].connect('tracklist-size-changed', self.on_cover_panel_tracklist_size_changed)
self._panels[Window._PANEL_INDEX_COVER].connect('set-song', self.on_cover_panel_set_song)
@ -157,6 +161,8 @@ class Window():
self._panels[Window._PANEL_INDEX_LIBRARY].connect('sort-type-changed', self.on_library_panel_sort_type_changed)
self._mcg.connect_signal(client.Client.SIGNAL_CONNECTION, self.on_mcg_connect)
self._mcg.connect_signal(client.Client.SIGNAL_STATUS, self.on_mcg_status)
self._mcg.connect_signal(client.Client.SIGNAL_STATS, self.on_mcg_stats)
self._mcg.connect_signal(client.Client.SIGNAL_LOAD_OUTPUT_DEVICES, self.on_mcg_load_output_devices)
self._mcg.connect_signal(client.Client.SIGNAL_LOAD_PLAYLIST, self.on_mcg_load_playlist)
self._mcg.connect_signal(client.Client.SIGNAL_LOAD_ALBUMS, self.on_mcg_load_albums)
self._mcg.connect_signal(client.Client.SIGNAL_ERROR, self.on_mcg_error)
@ -172,7 +178,7 @@ class Window():
}
handlers.update(self._header_bar.get_signal_handlers())
handlers.update(self._infobar.get_signal_handlers())
handlers.update(self._panels[Window._PANEL_INDEX_CONNECTION].get_signal_handlers())
handlers.update(self._connection_panel.get_signal_handlers())
handlers.update(self._panels[Window._PANEL_INDEX_COVER].get_signal_handlers())
handlers.update(self._panels[Window._PANEL_INDEX_PLAYLIST].get_signal_handlers())
handlers.update(self._panels[Window._PANEL_INDEX_LIBRARY].get_signal_handlers())
@ -183,7 +189,7 @@ class Window():
if self._maximized:
self._appwindow.maximize()
self._appwindow.show_all()
self._stack.set_visible_child(self._panels[Window._PANEL_INDEX_CONNECTION].get())
self._content_stack.set_visible_child(self._connection_panel.get())
if self._settings.get_boolean(Window.SETTING_CONNECTED):
self._connect()
@ -298,6 +304,10 @@ class Window():
self._mcg.play_album_from_playlist(album)
def on_server_panel_output_device_changed(self, widget, device, enabled):
self._mcg.enable_output_device(device, enabled)
def on_cover_panel_toggle_fullscreen(self, widget):
if not self._fullscreened:
self._appwindow.fullscreen()
@ -346,6 +356,8 @@ class Window():
self._mcg.load_playlist()
self._mcg.load_albums()
self._mcg.get_status()
self._mcg.get_stats()
self._mcg.get_output_devices()
self._connect_action.set_state(GLib.Variant.new_boolean(True))
self._play_action.set_enabled(True)
self._clear_playlist_action.set_enabled(True)
@ -358,7 +370,7 @@ class Window():
self._panel_action.set_enabled(False)
def on_mcg_status(self, state, album, pos, time, volume, error):
def on_mcg_status(self, state, album, pos, time, volume, file, audio, bitrate, error):
# Album
GObject.idle_add(self._panels[Window._PANEL_INDEX_COVER].set_album, album)
if not album and self._fullscreened:
@ -374,6 +386,8 @@ class Window():
self._play_action.set_state(GLib.Variant.new_boolean(False))
# Volume
GObject.idle_add(self._header_bar.set_volume, volume)
# Status
self._panels[Window._PANEL_INDEX_SERVER].set_status(file, audio, bitrate, error)
# Error
if error is None:
self._infobar.hide()
@ -381,12 +395,20 @@ class Window():
self._show_error(error)
def on_mcg_stats(self, artists, albums, songs, dbplaytime, playtime, uptime):
self._panels[Window._PANEL_INDEX_SERVER].set_stats(artists, albums, songs, dbplaytime, playtime, uptime)
def on_mcg_load_output_devices(self, devices):
self._panels[Window._PANEL_INDEX_SERVER].set_output_devices(devices)
def on_mcg_load_playlist(self, playlist):
self._panels[self._PANEL_INDEX_PLAYLIST].set_playlist(self._panels[self._PANEL_INDEX_CONNECTION].get_host(), playlist)
self._panels[self._PANEL_INDEX_PLAYLIST].set_playlist(self._connection_panel.get_host(), playlist)
def on_mcg_load_albums(self, albums):
self._panels[self._PANEL_INDEX_LIBRARY].set_albums(self._panels[self._PANEL_INDEX_CONNECTION].get_host(), albums)
self._panels[self._PANEL_INDEX_LIBRARY].set_albums(self._connection_panel.get_host(), albums)
def on_mcg_error(self, error):
@ -424,17 +446,16 @@ class Window():
# Private methods
def _connect(self):
connection_panel = self._panels[Window._PANEL_INDEX_CONNECTION]
connection_panel.get().set_sensitive(False)
self._connection_panel.get().set_sensitive(False)
self._header_bar.set_sensitive(False, True)
if self._mcg.is_connected():
self._mcg.disconnect()
self._settings.set_boolean(Window.SETTING_CONNECTED, False)
else:
host = connection_panel.get_host()
port = connection_panel.get_port()
password = connection_panel.get_password()
image_dir = connection_panel.get_image_dir()
host = self._connection_panel.get_host()
port = self._connection_panel.get_port()
password = self._connection_panel.get_password()
image_dir = self._connection_panel.get_image_dir()
self._mcg.connect(host, port, password, image_dir)
self._settings.set_boolean(Window.SETTING_CONNECTED, True)
@ -442,6 +463,7 @@ class Window():
def _connect_connected(self):
self._header_bar.connected()
self._header_bar.set_sensitive(True, False)
self._content_stack.set_visible_child(self._stack)
self._stack.set_visible_child(self._panels[self._settings.get_int(Window.SETTING_PANEL)].get())
@ -451,8 +473,8 @@ class Window():
self._header_bar.disconnected()
self._header_bar.set_sensitive(False, False)
self._save_visible_panel()
self._stack.set_visible_child(self._panels[Window._PANEL_INDEX_CONNECTION].get())
self._panels[Window._PANEL_INDEX_CONNECTION].get().set_sensitive(True)
self._content_stack.set_visible_child(self._connection_panel.get())
self._connection_panel.get().set_sensitive(True)
def _fullscreen(self, fullscreened_new):
@ -469,8 +491,7 @@ class Window():
def _save_visible_panel(self):
panels = [panel.get() for panel in self._panels]
panel_index_selected = panels.index(self._stack.get_visible_child())
if panel_index_selected > 0:
self._settings.set_int(Window.SETTING_PANEL, panel_index_selected)
self._settings.set_int(Window.SETTING_PANEL, panel_index_selected)
def _set_menu_visible_panel(self):
@ -508,6 +529,8 @@ class HeaderBar(GObject.GObject):
# Widgets
self._header_bar = builder.get_object('headerbar')
self._title_stack = builder.get_object('headerbar-title-stack')
self._connection_label = builder.get_object('headerbar-connectionn-label')
self._stack_switcher = StackSwitcher(builder)
self._button_connect = builder.get_object('headerbar-connection')
self._button_playpause = builder.get_object('headerbar-playpause')
@ -582,6 +605,7 @@ class HeaderBar(GObject.GObject):
self._button_connect.handler_unblock_by_func(
self.on_connection_active_notify
)
self._title_stack.set_visible_child(self._stack_switcher.get())
def disconnected(self):
@ -593,6 +617,7 @@ class HeaderBar(GObject.GObject):
self._button_connect.handler_unblock_by_func(
self.on_connection_active_notify
)
self._title_stack.set_visible_child(self._connection_label)
def set_play(self):
@ -675,8 +700,7 @@ class ConnectionPanel(GObject.GObject):
self._profile = None
# Widgets
self._panel = builder.get_object('server-panel')
self._toolbar = builder.get_object('server-toolbar')
self._panel = builder.get_object('connection-panel')
# Zeroconf
self._zeroconf_list = builder.get_object('server-zeroconf-list')
self._zeroconf_list.set_model(self._services)
@ -701,10 +725,6 @@ class ConnectionPanel(GObject.GObject):
return self._panel
def get_toolbar(self):
return self._toolbar
def get_signal_handlers(self):
return {
'on_server-zeroconf-list-selection_changed': self.on_service_selected,
@ -792,6 +812,118 @@ class ConnectionPanel(GObject.GObject):
class ServerPanel(GObject.GObject):
__gsignals__ = {
'change-output-device': (GObject.SIGNAL_RUN_FIRST, None, (GObject.TYPE_PYOBJECT,bool,)),
}
def __init__(self, builder):
GObject.GObject.__init__(self)
self._none_label = ""
self._output_buttons = {}
# Widgets
self._panel = builder.get_object('server-panel')
self._toolbar = builder.get_object('server-toolbar')
self._stack = builder.get_object('server-stack')
# Status widgets
self._status_file = builder.get_object('server-status-file')
self._status_audio = builder.get_object('server-status-audio')
self._status_bitrate = builder.get_object('server-status-bitrate')
self._status_error = builder.get_object('server-status-error')
self._none_label = self._status_file.get_label()
# Stats widgets
self._stats_artists = builder.get_object('server-stats-artists')
self._stats_albums = builder.get_object('server-stats-albums')
self._stats_songs = builder.get_object('server-stats-songs')
self._stats_dbplaytime = builder.get_object('server-stats-dbplaytime')
self._stats_playtime = builder.get_object('server-stats-playtime')
self._stats_uptime = builder.get_object('server-stats-uptime')
# Audio ouptut devices widgets
self._output_devices = builder.get_object('server-output-devices')
def get(self):
return self._panel
def get_toolbar(self):
return self._toolbar
def on_output_device_toggled(self, widget, device):
self.emit('change-output-device', device, widget.get_active())
def set_status(self, file, audio, bitrate, error):
if file:
file = GObject.markup_escape_text(file)
else:
file = self._none_label
self._status_file.set_markup(file)
# Audio information
if audio:
parts = audio.split(":")
if len(parts) == 3:
audio = "{}Hz, {}bit, {}channels".format(parts[0], parts[1], parts[2])
else:
audio = self._none_label
self._status_audio.set_markup(audio)
# Bitrate
if bitrate:
bitrate = bitrate + "kb/s"
else:
bitrate = self._none_label
self._status_bitrate.set_markup(bitrate)
# Error
if error:
error = GObject.markup_escape_text(error)
else:
error = self._none_label
self._status_error.set_markup(error)
def set_stats(self, artists, albums, songs, dbplaytime, playtime, uptime):
self._stats_artists.set_text(str(artists))
self._stats_albums.set_text(str(albums))
self._stats_songs.set_text(str(songs))
self._stats_dbplaytime.set_text(str(dbplaytime))
self._stats_playtime.set_text(str(playtime))
self._stats_uptime.set_text(str(uptime))
def set_output_devices(self, devices):
device_ids = []
# Add devices
for device in devices:
device_ids.append(device.get_id())
if device.get_id() in self._output_buttons.keys():
self._output_buttons[device.get_id()].freeze_notify()
self._output_buttons[device.get_id()].set_active(device.is_enabled())
self._output_buttons[device.get_id()].thaw_notify()
else:
button = Gtk.CheckButton(device.get_name())
if device.is_enabled():
button.set_active(True)
handler = button.connect('toggled', self.on_output_device_toggled, device)
self._output_devices.insert(button, -1)
self._output_buttons[device.get_id()] = button
self._output_devices.show_all()
# Remove devices
for id in self._output_buttons.keys():
if id not in device_ids:
self._output_devices.remove(self._output_buttons[id].get_parent())
class CoverPanel(GObject.GObject):
__gsignals__ = {
'toggle-fullscreen': (GObject.SIGNAL_RUN_FIRST, None, ()),