Comments added and methods of class MCGClient reordered
This commit is contained in:
parent
c2eb40c317
commit
cd711efa4a
2 changed files with 145 additions and 98 deletions
235
mcg.py
235
mcg.py
|
@ -1,6 +1,9 @@
|
||||||
#!/usr/bin/python
|
#!/usr/bin/python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Author: coderkun <olli@coderkun.de>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import mpd
|
import mpd
|
||||||
|
@ -8,17 +11,26 @@ import os
|
||||||
from hashlib import md5
|
from hashlib import md5
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class MCGClient:
|
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_CONNECT = 'connect'
|
||||||
|
# Signal: general idle event
|
||||||
SIGNAL_IDLE = 'idle'
|
SIGNAL_IDLE = 'idle'
|
||||||
|
# Signal: player idle event
|
||||||
SIGNAL_IDLE_PLAYER = 'idle_player'
|
SIGNAL_IDLE_PLAYER = 'idle_player'
|
||||||
|
# Signal: update event
|
||||||
SIGNAL_UPDATE = 'update'
|
SIGNAL_UPDATE = 'update'
|
||||||
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
"""Sets class variables and instantiates the MPDClient.
|
||||||
|
"""
|
||||||
self._connected = False
|
self._connected = False
|
||||||
self._albums = {}
|
self._albums = {}
|
||||||
self._callbacks = {}
|
self._callbacks = {}
|
||||||
|
@ -29,6 +41,9 @@ class MCGClient:
|
||||||
|
|
||||||
|
|
||||||
def connect(self, host="localhost", port="6600", password=None):
|
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
|
# TODO als Parameter an _add_action() übergeben, nicht speichern
|
||||||
self._host = host
|
self._host = host
|
||||||
self._port = port
|
self._port = port
|
||||||
|
@ -36,42 +51,22 @@ class MCGClient:
|
||||||
self._add_action(self._connect)
|
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):
|
def is_connected(self):
|
||||||
|
"""Returns the connection status.
|
||||||
|
"""
|
||||||
return self._connected
|
return self._connected
|
||||||
|
|
||||||
|
|
||||||
def disconnect(self):
|
def disconnect(self):
|
||||||
|
"""Disconnects from the connected MPD.
|
||||||
|
"""
|
||||||
self._add_action(self._disconnect)
|
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):
|
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():
|
if not self.is_connected():
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
|
@ -83,32 +78,81 @@ class MCGClient:
|
||||||
|
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
|
"""Updates the album list.
|
||||||
|
"""
|
||||||
self._add_action(self._update)
|
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):
|
def play(self, album):
|
||||||
|
"""Plays the given album.
|
||||||
|
"""
|
||||||
# TODO play()
|
# TODO play()
|
||||||
|
# mpd-Befehle: add, play
|
||||||
|
# https://github.com/Mic92/python-mpd2/blob/master/mpd.py
|
||||||
print("play: ", self._albums[album].get_title())
|
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):
|
def _idle(self, modules):
|
||||||
|
"""Reacts to idle events from MPD.
|
||||||
|
"""
|
||||||
if not modules:
|
if not modules:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -125,11 +169,59 @@ class MCGClient:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def idle_player(self):
|
def _connect(self):
|
||||||
self._add_action(self._idle_player)
|
"""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):
|
def _idle_player(self):
|
||||||
|
"""Reacts on the player idle event.
|
||||||
|
"""
|
||||||
if not self._has_callback(self.SIGNAL_IDLE_PLAYER):
|
if not self._has_callback(self.SIGNAL_IDLE_PLAYER):
|
||||||
return
|
return
|
||||||
status = self._client.status()
|
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:
|
class MCGAlbum:
|
||||||
_file_names = ['folder', 'cover']
|
_file_names = ['folder', 'cover']
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
#!/usr/bin/python
|
#!/usr/bin/python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Author: coderkun <olli@coderkun.de>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
from gi.repository import Gtk, Gdk, GdkPixbuf, GObject
|
from gi.repository import Gtk, Gdk, GdkPixbuf, GObject
|
||||||
import mcg
|
import mcg
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class MCGGtk(Gtk.Window):
|
class MCGGtk(Gtk.Window):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -292,7 +292,6 @@ class ConnectionPanel(Gtk.Box):
|
||||||
|
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
|
||||||
|
|
||||||
class CoverPanel(Gtk.HPaned):
|
class CoverPanel(Gtk.HPaned):
|
||||||
SIGNAL_UPDATE_START = 'update-start'
|
SIGNAL_UPDATE_START = 'update-start'
|
||||||
SIGNAL_UPDATE_END = 'update-end'
|
SIGNAL_UPDATE_END = 'update-end'
|
||||||
|
@ -443,6 +442,7 @@ class CoverPanel(Gtk.HPaned):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import configparser
|
import configparser
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue