rework project structure for more logical filenames and code separation

This commit is contained in:
coderkun 2016-08-01 11:39:24 +02:00
commit 134d1a28a7
8 changed files with 258 additions and 228 deletions

23
mcg.py Executable file
View file

@ -0,0 +1,23 @@
#!/usr/bin/env python3
"""MPDCoverGrid is a client for the Music Player Daemon, focused on albums instead of single tracks."""
import os
import sys
from mcg.application import Application
if __name__ == "__main__":
# Set environment
srcdir = os.path.abspath(os.path.dirname(__file__))
if not os.environ.get('GSETTINGS_SCHEMA_DIR'):
os.environ['GSETTINGS_SCHEMA_DIR'] = os.path.join(srcdir, 'data')
# Start application
app = Application()
exit_status = app.run(sys.argv)
sys.exit(exit_status)

49
mcg/application.py Normal file
View file

@ -0,0 +1,49 @@
#!/usr/bin/env python3
import gi
gi.require_version('Gtk', '3.0')
import urllib
from gi.repository import Gio, Gtk, Gdk
from mcg import widgets
class Application(Gtk.Application):
TITLE = "MPDCoverGrid (Gtk)"
SETTINGS_BASE_KEY = 'de.coderkun.mcg'
def __init__(self):
Gtk.Application.__init__(self, application_id="de.coderkun.mcg-dev", flags=Gio.ApplicationFlags.FLAGS_NONE)
self._window = None
def do_startup(self):
Gtk.Application.do_startup(self)
self._settings = Gio.Settings.new(Application.SETTINGS_BASE_KEY)
self.load_css()
# Create builder to load UI
self._builder = Gtk.Builder()
self._builder.add_from_file('data/gtk.glade')
def do_activate(self):
Gtk.Application.do_activate(self)
if not self._window:
self._window = widgets.Window(self, self._builder, Application.TITLE, self._settings)
self._window.present()
def load_css(self):
styleProvider = Gtk.CssProvider()
styleProvider.load_from_file(Gio.File.new_for_path('data/mcg.css'))
Gtk.StyleContext.add_provider_for_screen(
Gdk.Screen.get_default(),
styleProvider,
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
)

View file

@ -1,9 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""MPDCoverGrid is a client for the Music Player Daemon, focused on albums instead of single tracks."""
__version__ = "0.4"
import configparser import configparser
import glob import glob

54
mcg/utils.py Normal file
View file

@ -0,0 +1,54 @@
#!/usr/bin/env python3
import gi
gi.require_version('Gtk', '3.0')
import os
import urllib
from gi.repository import GdkPixbuf
class Utils:
def load_cover(url):
if not url:
return None
if url.startswith('/'):
try:
return GdkPixbuf.Pixbuf.new_from_file(url)
except Exception as e:
print(e)
return None
else:
try:
response = urllib.request.urlopen(url)
loader = GdkPixbuf.PixbufLoader()
loader.write(response.read())
loader.close()
return loader.get_pixbuf()
except Exception as e:
print(e)
return None
def load_thumbnail(cache, album, size):
cache_url = cache.create_filename(album)
pixbuf = None
if os.path.isfile(cache_url):
try:
pixbuf = GdkPixbuf.Pixbuf.new_from_file(cache_url)
except Exception as e:
print(e)
else:
url = album.get_cover()
pixbuf = Utils.load_cover(url)
if pixbuf is not None:
pixbuf = pixbuf.scale_simple(size, size, GdkPixbuf.InterpType.HYPER)
filetype = os.path.splitext(url)[1][1:]
if filetype == 'jpg':
filetype = 'jpeg'
pixbuf.savev(cache.create_filename(album), filetype, [], [])
return pixbuf

291
mcg/mcgGtk.py → mcg/widgets.py Executable file → Normal file
View file

@ -1,13 +1,8 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""MPDCoverGrid is a client for the Music Player Daemon, focused on albums instead of single tracks."""
__version__ = "0.6"
import gi import gi
gi.require_version('Gtk', '3.0') gi.require_version('Gtk', '3.0')
gi.require_version('Avahi', '0.6')
try: try:
import keyring import keyring
use_keyring = True use_keyring = True
@ -15,22 +10,19 @@ except:
use_keyring = False use_keyring = False
import logging import logging
import math import math
import os
import sys import sys
import threading import threading
import urllib
from gi.repository import Gio, Gtk, Gdk, GObject, GdkPixbuf, GLib from gi.repository import Gtk, Gdk, GObject, GdkPixbuf, GLib
from gi.repository import Avahi
import mcg from mcg import client
from mcg.utils import Utils
from mcg.zeroconf import ZeroconfProvider
class Application(Gtk.Application): class Window():
TITLE = "MPDCoverGrid (Gtk)"
SETTINGS_BASE_KEY = 'de.coderkun.mcg'
SETTING_HOST = 'host' SETTING_HOST = 'host'
SETTING_PORT = 'port' SETTING_PORT = 'port'
SETTING_CONNECTED = 'connected' SETTING_CONNECTED = 'connected'
@ -42,87 +34,6 @@ class Application(Gtk.Application):
SETTING_ITEM_SIZE = 'item-size' SETTING_ITEM_SIZE = 'item-size'
SETTING_SORT_ORDER = 'sort-order' SETTING_SORT_ORDER = 'sort-order'
SETTING_SORT_TYPE = 'sort-type' SETTING_SORT_TYPE = 'sort-type'
KEYRING_SYSTEM = 'MPDCoverGrid (Gtk)'
KEYRING_USERNAME = 'mpd'
def __init__(self):
Gtk.Application.__init__(self, application_id="de.coderkun.mcg-dev", flags=Gio.ApplicationFlags.FLAGS_NONE)
self._window = None
def do_startup(self):
Gtk.Application.do_startup(self)
self._settings = Gio.Settings.new(Application.SETTINGS_BASE_KEY)
self.load_css()
# Create builder to load UI
self._builder = Gtk.Builder()
self._builder.add_from_file('data/gtk.glade')
def do_activate(self):
Gtk.Application.do_activate(self)
if not self._window:
self._window = Window(self, self._builder, Application.TITLE, self._settings)
self._window.present()
def load_css(self):
styleProvider = Gtk.CssProvider()
styleProvider.load_from_file(Gio.File.new_for_path('data/mcg.css'))
Gtk.StyleContext.add_provider_for_screen(
Gdk.Screen.get_default(),
styleProvider,
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
)
def load_cover(url):
if not url:
return None
if url.startswith('/'):
try:
return GdkPixbuf.Pixbuf.new_from_file(url)
except Exception as e:
print(e)
return None
else:
try:
response = urllib.request.urlopen(url)
loader = GdkPixbuf.PixbufLoader()
loader.write(response.read())
loader.close()
return loader.get_pixbuf()
except Exception as e:
print(e)
return None
def load_thumbnail(cache, album, size):
cache_url = cache.create_filename(album)
pixbuf = None
if os.path.isfile(cache_url):
try:
pixbuf = GdkPixbuf.Pixbuf.new_from_file(cache_url)
except Exception as e:
print(e)
else:
url = album.get_cover()
pixbuf = Application.load_cover(url)
if pixbuf is not None:
pixbuf = pixbuf.scale_simple(size, size, GdkPixbuf.InterpType.HYPER)
filetype = os.path.splitext(url)[1][1:]
if filetype == 'jpg':
filetype = 'jpeg'
pixbuf.savev(cache.create_filename(album), filetype, [], [])
return pixbuf
class Window():
_PANEL_INDEX_CONNECTION = 0 _PANEL_INDEX_CONNECTION = 0
_PANEL_INDEX_COVER = 1 _PANEL_INDEX_COVER = 1
_PANEL_INDEX_PLAYLIST = 2 _PANEL_INDEX_PLAYLIST = 2
@ -135,16 +46,16 @@ class Window():
self._appwindow.set_title(title) self._appwindow.set_title(title)
self._settings = settings self._settings = settings
self._panels = [] self._panels = []
self._mcg = mcg.Client() self._mcg = client.Client()
self._logger = logging.getLogger(__name__) self._logger = logging.getLogger(__name__)
self._logger.addHandler(logging.StreamHandler(stream=sys.stdout)) self._logger.addHandler(logging.StreamHandler(stream=sys.stdout))
self._logger.setLevel(logging.ERROR) self._logger.setLevel(logging.ERROR)
logging.getLogger(mcg.__name__).addHandler(logging.StreamHandler(stream=sys.stdout)) logging.getLogger(client.__name__).addHandler(logging.StreamHandler(stream=sys.stdout))
logging.getLogger(mcg.__name__).setLevel(logging.ERROR) logging.getLogger(client.__name__).setLevel(logging.ERROR)
#self._mcg.get_logger().addHandler(logging.StreamHandler(stream=sys.stdout)) #self._mcg.get_logger().addHandler(logging.StreamHandler(stream=sys.stdout))
#self._mcg.get_logger().setLevel(logging.ERROR) #self._mcg.get_logger().setLevel(logging.ERROR)
self._size = self._settings.get_value(Application.SETTING_WINDOW_SIZE) self._size = self._settings.get_value(Window.SETTING_WINDOW_SIZE)
self._maximized = self._settings.get_boolean(Application.SETTING_WINDOW_MAXIMIZED) self._maximized = self._settings.get_boolean(Window.SETTING_WINDOW_MAXIMIZED)
self._fullscreened = False self._fullscreened = False
# Panels # Panels
@ -165,16 +76,16 @@ class Window():
# Properties # Properties
self._header_bar.set_sensitive(False, False) self._header_bar.set_sensitive(False, False)
self._panels[Window._PANEL_INDEX_CONNECTION].set_host(self._settings.get_string(Application.SETTING_HOST)) 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(Application.SETTING_PORT)) self._panels[Window._PANEL_INDEX_CONNECTION].set_port(self._settings.get_int(Window.SETTING_PORT))
if use_keyring: if use_keyring:
self._panels[Window._PANEL_INDEX_CONNECTION].set_password(keyring.get_password(Application.KEYRING_SYSTEM, Application.KEYRING_USERNAME)) 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(Application.SETTING_IMAGE_DIR)) self._panels[Window._PANEL_INDEX_CONNECTION].set_image_dir(self._settings.get_string(Window.SETTING_IMAGE_DIR))
self._panels[Window._PANEL_INDEX_COVER].set_tracklist_size(self._settings.get_string(Application.SETTING_TRACKLIST_SIZE)) self._panels[Window._PANEL_INDEX_COVER].set_tracklist_size(self._settings.get_string(Window.SETTING_TRACKLIST_SIZE))
self._panels[Window._PANEL_INDEX_PLAYLIST].set_item_size(self._settings.get_int(Application.SETTING_ITEM_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(Application.SETTING_ITEM_SIZE)) self._panels[Window._PANEL_INDEX_LIBRARY].set_item_size(self._settings.get_int(Window.SETTING_ITEM_SIZE))
self._panels[Window._PANEL_INDEX_LIBRARY].set_sort_order(self._settings.get_string(Application.SETTING_SORT_ORDER)) self._panels[Window._PANEL_INDEX_LIBRARY].set_sort_order(self._settings.get_string(Window.SETTING_SORT_ORDER))
self._panels[Window._PANEL_INDEX_LIBRARY].set_sort_type(self._settings.get_boolean(Application.SETTING_SORT_TYPE)) self._panels[Window._PANEL_INDEX_LIBRARY].set_sort_type(self._settings.get_boolean(Window.SETTING_SORT_TYPE))
# Signals # Signals
self._header_bar.connect_signal(HeaderBar.SIGNAL_STACK_SWITCHED, self.on_header_bar_stack_switched) self._header_bar.connect_signal(HeaderBar.SIGNAL_STACK_SWITCHED, self.on_header_bar_stack_switched)
@ -191,16 +102,16 @@ class Window():
self._panels[Window._PANEL_INDEX_LIBRARY].connect_signal(LibraryPanel.SIGNAL_ITEM_SIZE_CHANGED, self.on_library_panel_item_size_changed) self._panels[Window._PANEL_INDEX_LIBRARY].connect_signal(LibraryPanel.SIGNAL_ITEM_SIZE_CHANGED, self.on_library_panel_item_size_changed)
self._panels[Window._PANEL_INDEX_LIBRARY].connect_signal(LibraryPanel.SIGNAL_SORT_ORDER_CHANGED, self.on_library_panel_sort_order_changed) self._panels[Window._PANEL_INDEX_LIBRARY].connect_signal(LibraryPanel.SIGNAL_SORT_ORDER_CHANGED, self.on_library_panel_sort_order_changed)
self._panels[Window._PANEL_INDEX_LIBRARY].connect_signal(LibraryPanel.SIGNAL_SORT_TYPE_CHANGED, self.on_library_panel_sort_type_changed) self._panels[Window._PANEL_INDEX_LIBRARY].connect_signal(LibraryPanel.SIGNAL_SORT_TYPE_CHANGED, self.on_library_panel_sort_type_changed)
self._mcg.connect_signal(mcg.Client.SIGNAL_CONNECTION, self.on_mcg_connect) self._mcg.connect_signal(client.Client.SIGNAL_CONNECTION, self.on_mcg_connect)
self._mcg.connect_signal(mcg.Client.SIGNAL_STATUS, self.on_mcg_status) self._mcg.connect_signal(client.Client.SIGNAL_STATUS, self.on_mcg_status)
self._mcg.connect_signal(mcg.Client.SIGNAL_LOAD_PLAYLIST, self.on_mcg_load_playlist) self._mcg.connect_signal(client.Client.SIGNAL_LOAD_PLAYLIST, self.on_mcg_load_playlist)
self._mcg.connect_signal(mcg.Client.SIGNAL_LOAD_ALBUMS, self.on_mcg_load_albums) self._mcg.connect_signal(client.Client.SIGNAL_LOAD_ALBUMS, self.on_mcg_load_albums)
self._mcg.connect_signal(mcg.Client.SIGNAL_ERROR, self.on_mcg_error) self._mcg.connect_signal(client.Client.SIGNAL_ERROR, self.on_mcg_error)
self._settings.connect('changed::'+Application.SETTING_PANEL, self.on_settings_panel_changed) self._settings.connect('changed::'+Window.SETTING_PANEL, self.on_settings_panel_changed)
self._settings.connect('changed::'+Application.SETTING_TRACKLIST_SIZE, self.on_settings_tracklist_size_changed) self._settings.connect('changed::'+Window.SETTING_TRACKLIST_SIZE, self.on_settings_tracklist_size_changed)
self._settings.connect('changed::'+Application.SETTING_ITEM_SIZE, self.on_settings_item_size_changed) self._settings.connect('changed::'+Window.SETTING_ITEM_SIZE, self.on_settings_item_size_changed)
self._settings.connect('changed::'+Application.SETTING_SORT_ORDER, self.on_settings_sort_order_changed) self._settings.connect('changed::'+Window.SETTING_SORT_ORDER, self.on_settings_sort_order_changed)
self._settings.connect('changed::'+Application.SETTING_SORT_TYPE, self.on_settings_sort_type_changed) self._settings.connect('changed::'+Window.SETTING_SORT_TYPE, self.on_settings_sort_type_changed)
handlers = { handlers = {
'on_appwindow_size_allocate': self.on_resize, 'on_appwindow_size_allocate': self.on_resize,
'on_appwindow_window_state_event': self.on_state, 'on_appwindow_window_state_event': self.on_state,
@ -220,7 +131,7 @@ class Window():
self._appwindow.maximize() self._appwindow.maximize()
self._appwindow.show_all() self._appwindow.show_all()
self._stack.set_visible_child(self._panels[Window._PANEL_INDEX_CONNECTION].get()) self._stack.set_visible_child(self._panels[Window._PANEL_INDEX_CONNECTION].get())
if self._settings.get_boolean(Application.SETTING_CONNECTED): if self._settings.get_boolean(Window.SETTING_CONNECTED):
self._connect() self._connect()
@ -237,11 +148,11 @@ class Window():
def on_state(self, widget, state): def on_state(self, widget, state):
self._maximized = (state.new_window_state & Gdk.WindowState.MAXIMIZED > 0) self._maximized = (state.new_window_state & Gdk.WindowState.MAXIMIZED > 0)
self._fullscreen((state.new_window_state & Gdk.WindowState.FULLSCREEN > 0)) self._fullscreen((state.new_window_state & Gdk.WindowState.FULLSCREEN > 0))
self._settings.set_boolean(Application.SETTING_WINDOW_MAXIMIZED, self._maximized) self._settings.set_boolean(Window.SETTING_WINDOW_MAXIMIZED, self._maximized)
def on_destroy(self, window): def on_destroy(self, window):
self._settings.set_value(Application.SETTING_WINDOW_SIZE, GLib.Variant('ai', list(self._size))) self._settings.set_value(Window.SETTING_WINDOW_SIZE, GLib.Variant('ai', list(self._size)))
# HeaderBar callbacks # HeaderBar callbacks
@ -267,15 +178,15 @@ class Window():
# Panel callbacks # Panel callbacks
def on_connection_panel_connection_changed(self, host, port, password, image_dir): def on_connection_panel_connection_changed(self, host, port, password, image_dir):
self._settings.set_string(Application.SETTING_HOST, host) self._settings.set_string(Window.SETTING_HOST, host)
self._settings.set_int(Application.SETTING_PORT, port) self._settings.set_int(Window.SETTING_PORT, port)
if use_keyring: if use_keyring:
if password: if password:
keyring.set_password(Application.KEYRING_SYSTEM, Application.KEYRING_USERNAME, password) keyring.set_password(ZeroconfProvider.KEYRING_SYSTEM, ZeroconfProvider.KEYRING_USERNAME, password)
else: else:
if keyring.get_password(Application.KEYRING_SYSTEM, Application.KEYRING_USERNAME): if keyring.get_password(ZeroconfProvider.KEYRING_SYSTEM, ZeroconfProvider.KEYRING_USERNAME):
keyring.delete_password(Application.KEYRING_SYSTEM, Application.KEYRING_USERNAME) keyring.delete_password(ZeroconfProvider.KEYRING_SYSTEM, ZeroconfProvider.KEYRING_USERNAME)
self._settings.set_string(Application.SETTING_IMAGE_DIR, image_dir) self._settings.set_string(Window.SETTING_IMAGE_DIR, image_dir)
def on_playlist_panel_clear_playlist(self): def on_playlist_panel_clear_playlist(self):
@ -290,7 +201,7 @@ class Window():
def on_cover_panel_tracklist_size_changed(self, size): def on_cover_panel_tracklist_size_changed(self, size):
self._settings.set_string(Application.SETTING_TRACKLIST_SIZE, size) self._settings.set_string(Window.SETTING_TRACKLIST_SIZE, size)
def on_cover_panel_set_song(self, pos, time): def on_cover_panel_set_song(self, pos, time):
@ -307,15 +218,15 @@ class Window():
def on_library_panel_item_size_changed(self, size): def on_library_panel_item_size_changed(self, size):
self._panels[Window._PANEL_INDEX_PLAYLIST].set_item_size(size) self._panels[Window._PANEL_INDEX_PLAYLIST].set_item_size(size)
self._settings.set_int(Application.SETTING_ITEM_SIZE, self._panels[Window._PANEL_INDEX_LIBRARY].get_item_size()) self._settings.set_int(Window.SETTING_ITEM_SIZE, self._panels[Window._PANEL_INDEX_LIBRARY].get_item_size())
def on_library_panel_sort_order_changed(self, sort_order): def on_library_panel_sort_order_changed(self, sort_order):
self._settings.set_string(Application.SETTING_SORT_ORDER, self._panels[Window._PANEL_INDEX_LIBRARY].get_sort_order()) self._settings.set_string(Window.SETTING_SORT_ORDER, self._panels[Window._PANEL_INDEX_LIBRARY].get_sort_order())
def on_library_panel_sort_type_changed(self, sort_type): def on_library_panel_sort_type_changed(self, sort_type):
self._settings.set_boolean(Application.SETTING_SORT_TYPE, self._panels[Window._PANEL_INDEX_LIBRARY].get_sort_type()) self._settings.set_boolean(Window.SETTING_SORT_TYPE, self._panels[Window._PANEL_INDEX_LIBRARY].get_sort_type())
# MCG callbacks # MCG callbacks
@ -398,20 +309,20 @@ class Window():
self._header_bar.set_sensitive(False, True) self._header_bar.set_sensitive(False, True)
if self._mcg.is_connected(): if self._mcg.is_connected():
self._mcg.disconnect() self._mcg.disconnect()
self._settings.set_boolean(Application.SETTING_CONNECTED, False) self._settings.set_boolean(Window.SETTING_CONNECTED, False)
else: else:
host = connection_panel.get_host() host = connection_panel.get_host()
port = connection_panel.get_port() port = connection_panel.get_port()
password = connection_panel.get_password() password = connection_panel.get_password()
image_dir = connection_panel.get_image_dir() image_dir = connection_panel.get_image_dir()
self._mcg.connect(host, port, password, image_dir) self._mcg.connect(host, port, password, image_dir)
self._settings.set_boolean(Application.SETTING_CONNECTED, True) self._settings.set_boolean(Window.SETTING_CONNECTED, True)
def _connect_connected(self): def _connect_connected(self):
self._header_bar.connected() self._header_bar.connected()
self._header_bar.set_sensitive(True, False) self._header_bar.set_sensitive(True, False)
self._stack.set_visible_child(self._panels[self._settings.get_int(Application.SETTING_PANEL)].get()) self._stack.set_visible_child(self._panels[self._settings.get_int(Window.SETTING_PANEL)].get())
def _connect_disconnected(self): def _connect_disconnected(self):
@ -439,7 +350,7 @@ class Window():
panels = [panel.get() for panel in self._panels] panels = [panel.get() for panel in self._panels]
panel_index_selected = panels.index(self._stack.get_visible_child()) panel_index_selected = panels.index(self._stack.get_visible_child())
if panel_index_selected > 0: if panel_index_selected > 0:
self._settings.set_int(Application.SETTING_PANEL, panel_index_selected) self._settings.set_int(Window.SETTING_PANEL, panel_index_selected)
def _set_visible_toolbar(self): def _set_visible_toolbar(self):
@ -455,7 +366,7 @@ class Window():
class HeaderBar(mcg.Base): class HeaderBar(client.Base):
SIGNAL_STACK_SWITCHED = 'stack-switched' SIGNAL_STACK_SWITCHED = 'stack-switched'
SIGNAL_CONNECT = 'on_headerbar-connection_active_notify' SIGNAL_CONNECT = 'on_headerbar-connection_active_notify'
SIGNAL_PLAYPAUSE = 'on_headerbar-playpause_toggled' SIGNAL_PLAYPAUSE = 'on_headerbar-playpause_toggled'
@ -463,7 +374,7 @@ class HeaderBar(mcg.Base):
def __init__(self, builder): def __init__(self, builder):
mcg.Base.__init__(self) client.Base.__init__(self)
self._buttons = {} self._buttons = {}
self._changing_volume = False self._changing_volume = False
@ -625,12 +536,12 @@ class InfoBar():
class ConnectionPanel(mcg.Base): class ConnectionPanel(client.Base):
SIGNAL_CONNECTION_CHANGED = 'connection-changed' SIGNAL_CONNECTION_CHANGED = 'connection-changed'
def __init__(self, builder): def __init__(self, builder):
mcg.Base.__init__(self) client.Base.__init__(self)
self._services = Gtk.ListStore(str, str, int) self._services = Gtk.ListStore(str, str, int)
self._profile = None self._profile = None
@ -752,7 +663,7 @@ class ConnectionPanel(mcg.Base):
class CoverPanel(mcg.Base): class CoverPanel(client.Base):
SIGNAL_TOGGLE_FULLSCREEN = 'toggle-fullscreen' SIGNAL_TOGGLE_FULLSCREEN = 'toggle-fullscreen'
SIGNAL_TRACKLIST_SIZE_CHANGED = 'tracklist-size-changed' SIGNAL_TRACKLIST_SIZE_CHANGED = 'tracklist-size-changed'
SIGNAL_SET_SONG = 'set-song' SIGNAL_SET_SONG = 'set-song'
@ -762,7 +673,7 @@ class CoverPanel(mcg.Base):
def __init__(self, builder): def __init__(self, builder):
mcg.Base.__init__(self) client.Base.__init__(self)
self._current_album = None self._current_album = None
self._cover_pixbuf = None self._cover_pixbuf = None
@ -937,7 +848,7 @@ class CoverPanel(mcg.Base):
url = album.get_cover() url = album.get_cover()
if url is not None and url is not "": if url is not None and url is not "":
# Load image and draw it # Load image and draw it
self._cover_pixbuf = Application.load_cover(url) self._cover_pixbuf = Utils.load_cover(url)
self._resize_image() self._resize_image()
else: else:
# Reset image # Reset image
@ -1023,12 +934,12 @@ class CoverPanel(mcg.Base):
class PlaylistPanel(mcg.Base): class PlaylistPanel(client.Base):
SIGNAL_CLEAR_PLAYLIST = 'clear-playlist' SIGNAL_CLEAR_PLAYLIST = 'clear-playlist'
def __init__(self, builder): def __init__(self, builder):
mcg.Base.__init__(self) client.Base.__init__(self)
self._host = None self._host = None
self._item_size = 150 self._item_size = 150
self._playlist = None self._playlist = None
@ -1148,12 +1059,12 @@ class PlaylistPanel(mcg.Base):
self._playlist_grid_model.clear() self._playlist_grid_model.clear()
GObject.idle_add(self._playlist_grid.set_item_padding, size / 100) GObject.idle_add(self._playlist_grid.set_item_padding, size / 100)
cache = mcg.MCGCache(host, size) cache = client.MCGCache(host, size)
for album in playlist: for album in playlist:
pixbuf = None pixbuf = None
if album.get_cover() is not None: if album.get_cover() is not None:
try: try:
pixbuf = Application.load_thumbnail(cache, album, size) pixbuf = Utils.load_thumbnail(cache, album, size)
except Exception as e: except Exception as e:
print(e) print(e)
if pixbuf is None: if pixbuf is None:
@ -1191,7 +1102,7 @@ class PlaylistPanel(mcg.Base):
url = album.get_cover() url = album.get_cover()
if url is not None and url is not "": if url is not None and url is not "":
# Load image and draw it # Load image and draw it
self._standalone_pixbuf = Application.load_cover(url) self._standalone_pixbuf = Utils.load_cover(url)
self._resize_standalone_image() self._resize_standalone_image()
else: else:
# Reset image # Reset image
@ -1234,7 +1145,7 @@ class PlaylistPanel(mcg.Base):
class LibraryPanel(mcg.Base): class LibraryPanel(client.Base):
SIGNAL_UPDATE = 'update' SIGNAL_UPDATE = 'update'
SIGNAL_PLAY = 'play' SIGNAL_PLAY = 'play'
SIGNAL_ITEM_SIZE_CHANGED = 'item-size-changed' SIGNAL_ITEM_SIZE_CHANGED = 'item-size-changed'
@ -1243,13 +1154,13 @@ class LibraryPanel(mcg.Base):
def __init__(self, builder): def __init__(self, builder):
mcg.Base.__init__(self) client.Base.__init__(self)
self._buttons = {} self._buttons = {}
self._albums = None self._albums = None
self._host = "localhost" self._host = "localhost"
self._filter_string = "" self._filter_string = ""
self._item_size = 150 self._item_size = 150
self._sort_order = mcg.MCGAlbum.SORT_BY_YEAR self._sort_order = client.MCGAlbum.SORT_BY_YEAR
self._sort_type = Gtk.SortType.DESCENDING self._sort_type = Gtk.SortType.DESCENDING
self._grid_pixbufs = {} self._grid_pixbufs = {}
self._old_ranges = {} self._old_ranges = {}
@ -1277,9 +1188,9 @@ class LibraryPanel(mcg.Base):
# Toolbar menu # Toolbar menu
self._toolbar_search_bar = builder.get_object('library-toolbar-search') self._toolbar_search_bar = builder.get_object('library-toolbar-search')
self._toolbar_sort_buttons = { self._toolbar_sort_buttons = {
mcg.MCGAlbum.SORT_BY_ARTIST: builder.get_object('library-toolbar-sort-artist'), client.MCGAlbum.SORT_BY_ARTIST: builder.get_object('library-toolbar-sort-artist'),
mcg.MCGAlbum.SORT_BY_TITLE: builder.get_object('library-toolbar-sort-title'), client.MCGAlbum.SORT_BY_TITLE: builder.get_object('library-toolbar-sort-title'),
mcg.MCGAlbum.SORT_BY_YEAR: builder.get_object('library-toolbar-sort-year') client.MCGAlbum.SORT_BY_YEAR: builder.get_object('library-toolbar-sort-year')
} }
self._toolbar_sort_order_button = builder.get_object('library-toolbar-sort-order') self._toolbar_sort_order_button = builder.get_object('library-toolbar-sort-order')
self._grid_scale = builder.get_object('library-toolbar-scale') self._grid_scale = builder.get_object('library-toolbar-scale')
@ -1482,7 +1393,7 @@ class LibraryPanel(mcg.Base):
if hash1 == "" or hash2 == "": if hash1 == "" or hash2 == "":
return return
return mcg.MCGAlbum.compare(self._albums[hash1], self._albums[hash2], criterion) return client.MCGAlbum.compare(self._albums[hash1], self._albums[hash2], criterion)
def stop_threads(self): def stop_threads(self):
@ -1508,13 +1419,13 @@ class LibraryPanel(mcg.Base):
i = 0 i = 0
n = len(albums) n = len(albums)
cache = mcg.MCGCache(host, size) cache = client.MCGCache(host, size)
self._grid_pixbufs.clear() self._grid_pixbufs.clear()
for hash in albums.keys(): for hash in albums.keys():
album = albums[hash] album = albums[hash]
pixbuf = None pixbuf = None
try: try:
pixbuf = Application.load_thumbnail(cache, album, size) pixbuf = Utils.load_thumbnail(cache, album, size)
except Exception as e: except Exception as e:
print(e) print(e)
if pixbuf is None: if pixbuf is None:
@ -1610,7 +1521,7 @@ class LibraryPanel(mcg.Base):
url = album.get_cover() url = album.get_cover()
if url is not None and url is not "": if url is not None and url is not "":
# Load image and draw it # Load image and draw it
self._standalone_pixbuf = Application.load_cover(url) self._standalone_pixbuf = Utils.load_cover(url)
self._resize_standalone_image() self._resize_standalone_image()
else: else:
# Reset image # Reset image
@ -1648,12 +1559,12 @@ class LibraryPanel(mcg.Base):
class StackSwitcher(mcg.Base): class StackSwitcher(client.Base):
SIGNAL_STACK_SWITCHED = 'stack-switched' SIGNAL_STACK_SWITCHED = 'stack-switched'
def __init__(self, builder): def __init__(self, builder):
mcg.Base.__init__(self) client.Base.__init__(self)
self._temp_button = None self._temp_button = None
self._stack_switcher = builder.get_object('header-panelswitcher') self._stack_switcher = builder.get_object('header-panelswitcher')
@ -1672,65 +1583,3 @@ class StackSwitcher(mcg.Base):
def get(self): def get(self):
return self._stack_switcher return self._stack_switcher
class ZeroconfProvider(mcg.Base):
SIGNAL_SERVICE_NEW = 'service-new'
TYPE = '_mpd._tcp'
def __init__(self):
mcg.Base.__init__(self)
self._service_resolvers = []
self._services = {}
self._logger = logging.getLogger(__name__)
# Client
self._client = Avahi.Client(flags=0,)
self._logger.info("avahi info")
self._logger.warning("avahi warning")
self._logger.error("avahi error")
try:
self._client.start()
# Browser
self._service_browser = Avahi.ServiceBrowser(domain='local', flags=0, interface=-1, protocol=Avahi.Protocol.GA_PROTOCOL_UNSPEC, type=ZeroconfProvider.TYPE)
self._service_browser.connect('new_service', self.on_new_service)
self._service_browser.attach(self._client)
except Exception as e:
self._logger.info(e)
def on_new_service(self, browser, interface, protocol, name, type, domain, flags):
#if not (flags & Avahi.LookupResultFlags.GA_LOOKUP_RESULT_LOCAL):
service_resolver = Avahi.ServiceResolver(interface=interface, protocol=protocol, name=name, type=type, domain=domain, aprotocol=Avahi.Protocol.GA_PROTOCOL_UNSPEC, flags=0,)
service_resolver.connect('found', self.on_found)
service_resolver.connect('failure', self.on_failure)
service_resolver.attach(self._client)
self._service_resolvers.append(service_resolver)
def on_found(self, resolver, interface, protocol, name, type, domain, host, date, port, *args):
if (host, port) not in self._services.keys():
service = (name,host,port)
self._services[(host,port)] = service
self._callback(ZeroconfProvider.SIGNAL_SERVICE_NEW, service)
def on_failure(self, resolver, date):
if resolver in self._service_resolvers:
self._service_resolvers.remove(resolver)
if __name__ == "__main__":
# Set environment
srcdir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
if not os.environ.get('GSETTINGS_SCHEMA_DIR'):
os.environ['GSETTINGS_SCHEMA_DIR'] = os.path.join(srcdir, 'data')
# Start application
app = Application()
exit_status = app.run(sys.argv)
sys.exit(exit_status)

57
mcg/zeroconf.py Normal file
View file

@ -0,0 +1,57 @@
#!/usr/bin/env python3
import gi
gi.require_version('Avahi', '0.6')
import logging
from gi.repository import Avahi
from mcg import client
class ZeroconfProvider(client.Base):
KEYRING_SYSTEM = 'MPDCoverGrid'
KEYRING_USERNAME = 'mpd'
SIGNAL_SERVICE_NEW = 'service-new'
TYPE = '_mpd._tcp'
def __init__(self):
client.Base.__init__(self)
self._service_resolvers = []
self._services = {}
self._logger = logging.getLogger(__name__)
# Client
self._client = Avahi.Client(flags=0,)
try:
self._client.start()
# Browser
self._service_browser = Avahi.ServiceBrowser(domain='local', flags=0, interface=-1, protocol=Avahi.Protocol.GA_PROTOCOL_UNSPEC, type=ZeroconfProvider.TYPE)
self._service_browser.connect('new_service', self.on_new_service)
self._service_browser.attach(self._client)
except Exception as e:
self._logger.info(e)
def on_new_service(self, browser, interface, protocol, name, type, domain, flags):
#if not (flags & Avahi.LookupResultFlags.GA_LOOKUP_RESULT_LOCAL):
service_resolver = Avahi.ServiceResolver(interface=interface, protocol=protocol, name=name, type=type, domain=domain, aprotocol=Avahi.Protocol.GA_PROTOCOL_UNSPEC, flags=0,)
service_resolver.connect('found', self.on_found)
service_resolver.connect('failure', self.on_failure)
service_resolver.attach(self._client)
self._service_resolvers.append(service_resolver)
def on_found(self, resolver, interface, protocol, name, type, domain, host, date, port, *args):
if (host, port) not in self._services.keys():
service = (name,host,port)
self._services[(host,port)] = service
self._callback(ZeroconfProvider.SIGNAL_SERVICE_NEW, service)
def on_failure(self, resolver, date):
if resolver in self._service_resolvers:
self._service_resolvers.remove(resolver)

View file

@ -8,8 +8,8 @@ from setuptools import setup
setup( setup(
name = "MPD Cover Grid", name = "MPDCoverGrid",
version = "0.4", version = "0.6",
description = "MPDCoverGrid is a client for the Music Player Daemon, focused on albums instead of single tracks.", description = "MPDCoverGrid is a client for the Music Player Daemon, focused on albums instead of single tracks.",
url = "http://www.coderkun.de/codes/mcg", url = "http://www.coderkun.de/codes/mcg",
author = "coderkun", author = "coderkun",
@ -27,12 +27,14 @@ setup(
}, },
entry_points = { entry_points = {
"gui_scripts": [ "gui_scripts": [
"frontend = mcgGtk:main" "frontend = mcg:main"
] ]
}, },
data_files = [ data_files = [
"data/MPDCoverGridGTK.desktop", "data/MPDCoverGridGTK.desktop",
"data/gschemas.compiled", "data/gschemas.compiled",
"data/gtk.glade",
"data/mcg.css",
"data/noise-texture.png" "data/noise-texture.png"
], ],
classifiers = [ classifiers = [