diff --git a/mcg.py b/mcg.py index 094b81e..3e4bd61 100644 --- a/mcg.py +++ b/mcg.py @@ -1,6 +1,9 @@ #!/usr/bin/python # -*- coding: utf-8 -*- +# Author: coderkun + + import mpd @@ -8,17 +11,26 @@ import os from hashlib import md5 from threading import Thread - - - class MCGClient: + """Client library for handling the connection to the Music Player Daemon. + + This class implements an album-based MPD client. + It offers a non-blocking threaded worker model for use in graphical + environments and is based on python-mpd2. + """ + # Signal: connect/disconnect event SIGNAL_CONNECT = 'connect' + # Signal: general idle event SIGNAL_IDLE = 'idle' + # Signal: player idle event SIGNAL_IDLE_PLAYER = 'idle_player' + # Signal: update event SIGNAL_UPDATE = 'update' def __init__(self): + """Sets class variables and instantiates the MPDClient. + """ self._connected = False self._albums = {} self._callbacks = {} @@ -29,6 +41,9 @@ class MCGClient: def connect(self, host="localhost", port="6600", password=None): + """Connects to MPD with the given host, port and password or + with standard values. + """ # TODO als Parameter an _add_action() übergeben, nicht speichern self._host = host self._port = port @@ -36,42 +51,22 @@ class MCGClient: self._add_action(self._connect) - def _connect(self): - try: - self._client.connect(self._host, self._port) - if self._password: - self._client.password(self._password) - # TODO Verbindung testen - self._connected = True - self._callback(self.SIGNAL_CONNECT, self._connected, None) - self.update() - self.idle_player() - except IOError as e: - self._connected = False - self._callback(self.SIGNAL_CONNECT, self._connected, e) - - def is_connected(self): + """Returns the connection status. + """ return self._connected def disconnect(self): + """Disconnects from the connected MPD. + """ self._add_action(self._disconnect) - def _disconnect(self): - if not self.is_connected(): - return - try: - #self._client.close() - self._client.disconnect() - except: - self._client = mpd.MPDClient() - self._connected = False - self._callback(self.SIGNAL_CONNECT, self._connected, None) - - def close(self): + """Closes the connection and stops properly the worker thread. + This method is to stop the whole appliction. + """ if not self.is_connected(): return try: @@ -83,32 +78,81 @@ class MCGClient: def update(self): + """Updates the album list. + """ self._add_action(self._update) - def _update(self): - for song in self._client.listallinfo(): - try: - if song['album'] not in self._albums: - album = MCGAlbum(song['artist'], song['album'], song['date'], os.path.dirname(song['file'])) - self._albums[album.get_hash()] = album - else: - album = self._albums[MCGAlbum.hash(song['artist'], song['album'])] - - track = MCGTrack(song['title'], song['track'], song['time'], song['file']) - album.add_track(track) - except KeyError: - pass - # TODO Alben sortieren - self._callback(self.SIGNAL_UPDATE, self._albums) - - def play(self, album): + """Plays the given album. + """ # TODO play() + # mpd-Befehle: add, play + # https://github.com/Mic92/python-mpd2/blob/master/mpd.py print("play: ", self._albums[album].get_title()) + def connect_signal(self, signal, callback): + """Connects a callback function to a signal (event). + """ + self._callbacks[signal] = callback + + + def _has_callback(self, signal): + """Checks if there is a registered callback function for a + signal. + """ + return signal in self._callbacks + + + def _callback(self, signal, *args): + """Calls the callback function for a signal. + """ + if self._has_callback(signal): + callback = self._callbacks[signal] + callback(*args) + + + def _add_action(self, method): + """Adds an action to the action list. + """ + self._actions.append(method) + self._start_worker() + + + def _start_worker(self): + """Starts the worker thread which waits for action to be + performed. + """ + if self._worker is None or not self._worker.is_alive(): + self._worker = Thread(target=self._work, name='worker', args=()) + self._worker.start() + else: + try: + self._client.noidle() + except TypeError as e: + pass + + + def _work(self): + """Performs the next action or waits for an idle event. + """ + while True: + if self._actions: + action = self._actions.pop(0) + action() + else: + if not self.is_connected(): + break + modules = self._client.idle() + if not self._go: + break + self._idle(modules) + + def _idle(self, modules): + """Reacts to idle events from MPD. + """ if not modules: return @@ -125,11 +169,59 @@ class MCGClient: pass - def idle_player(self): - self._add_action(self._idle_player) + def _connect(self): + """Action: Performs the real connect to MPD. + """ + try: + self._client.connect(self._host, self._port) + if self._password: + self._client.password(self._password) + # TODO Verbindung testen + self._connected = True + self._callback(self.SIGNAL_CONNECT, self._connected, None) + self.update() + self._add_action(self._idle_player) + except IOError as e: + self._connected = False + self._callback(self.SIGNAL_CONNECT, self._connected, e) + + + def _disconnect(self): + """Action: Performs the real disconnect from MPD. + """ + if not self.is_connected(): + return + try: + #self._client.close() + self._client.disconnect() + except: + self._client = mpd.MPDClient() + self._connected = False + self._callback(self.SIGNAL_CONNECT, self._connected, None) + + + def _update(self): + """Action: Performs the real update. + """ + for song in self._client.listallinfo(): + try: + if song['album'] not in self._albums: + album = MCGAlbum(song['artist'], song['album'], song['date'], os.path.dirname(song['file'])) + self._albums[album.get_hash()] = album + else: + album = self._albums[MCGAlbum.hash(song['artist'], song['album'])] + + track = MCGTrack(song['title'], song['track'], song['time'], song['file']) + album.add_track(track) + except KeyError: + pass + # TODO Alben sortieren + self._callback(self.SIGNAL_UPDATE, self._albums) def _idle_player(self): + """Reacts on the player idle event. + """ if not self._has_callback(self.SIGNAL_IDLE_PLAYER): return status = self._client.status() @@ -140,51 +232,6 @@ class MCGClient: - def connect_signal(self, signal, callback): - self._callbacks[signal] = callback - - - def _has_callback(self, signal): - return signal in self._callbacks - - - def _callback(self, signal, *args): - if self._has_callback(signal): - callback = self._callbacks[signal] - callback(*args) - - - def _add_action(self, method): - self._actions.append(method) - self._start_worker() - - - def _start_worker(self): - if self._worker is None or not self._worker.is_alive(): - self._worker = Thread(target=self._work, name='worker', args=()) - self._worker.start() - else: - try: - self._client.noidle() - except TypeError as e: - pass - - - def _work(self): - while True: - if self._actions: - action = self._actions.pop(0) - action() - else: - if not self.is_connected(): - break - modules = self._client.idle() - if not self._go: - break - self._idle(modules) - - - class MCGAlbum: _file_names = ['folder', 'cover'] diff --git a/mcgGtk.py b/mcgGtk.py index 258cdeb..2566f47 100755 --- a/mcgGtk.py +++ b/mcgGtk.py @@ -1,14 +1,14 @@ #!/usr/bin/python # -*- coding: utf-8 -*- +# Author: coderkun + + from gi.repository import Gtk, Gdk, GdkPixbuf, GObject import mcg - - - class MCGGtk(Gtk.Window): def __init__(self): @@ -292,7 +292,6 @@ class ConnectionPanel(Gtk.Box): from threading import Thread - class CoverPanel(Gtk.HPaned): SIGNAL_UPDATE_START = 'update-start' SIGNAL_UPDATE_END = 'update-end' @@ -443,6 +442,7 @@ class CoverPanel(Gtk.HPaned): + import os import configparser