#!/usr/bin/env python
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# Authors: Erik Karlsson <pilo@ayeon.org>, Jean-Philippe Braun <eon@patapon.info>
# Some bits taken from quodlibet mpris plugin by <christoph.reiter@gmx.at>

import os
import sys
import re
import shlex
import signal
import socket
import getopt
import types
import mpd
import gobject
import dbus
import dbus.service
from dbus.mainloop.glib import DBusGMainLoop
import ConfigParser
import logging
import gettext

gettext.bindtextdomain('mpDris2', '/usr/local/share/locale')
gettext.textdomain('mpDris2')
_ = gettext.gettext

identity = "Music Player Daemon"
params = {
    'host': 'localhost',
    'port': 6600,
    'password': None,
    'progname': sys.argv[0],
    'music_dir': '',
    'mmkeys': True,
    'notify': True,
}

try:
    import pynotify
except:
    params['notify'] = False


notification = None

# MPRIS allowed metadata tags
allowed_tags = {
    'mpris:trackid': dbus.ObjectPath,
    'mpris:length': long,
    'mpris:artUrl': str,
    'xesam:album': str,
    'xesam:albumArtist': list,
    'xesam:artist': list,
    'xesam:asText': str,
    'xesam:audioBPM': int,
    'xesam:comment': list,
    'xesam:composer': list,
    'xesam:contentCreated': str,
    'xesam:discNumber': int,
    'xesam:firstUsed': str,
    'xesam:genre': list,
    'xesam:lastUsed': str,
    'xesam:lyricist': str,
    'xesam:title': str,
    'xesam:trackNumber': int,
    'xesam:url': str,
    'xesam:useCount': int,
    'xesam:userRating': float,
}

# python dbus bindings don't include annotations and properties
MPRIS2_INTROSPECTION = \
"""<node name="/org/mpris/MediaPlayer2">
  <interface name="org.freedesktop.DBus.Introspectable">
    <method name="Introspect">
      <arg direction="out" name="xml_data" type="s"/>
    </method>
  </interface>
  <interface name="org.freedesktop.DBus.Properties">
    <method name="Get">
      <arg direction="in" name="interface_name" type="s"/>
      <arg direction="in" name="property_name" type="s"/>
      <arg direction="out" name="value" type="v"/>
    </method>
    <method name="GetAll">
      <arg direction="in" name="interface_name" type="s"/>
      <arg direction="out" name="properties" type="a{sv}"/>
    </method>
    <method name="Set">
      <arg direction="in" name="interface_name" type="s"/>
      <arg direction="in" name="property_name" type="s"/>
      <arg direction="in" name="value" type="v"/>
    </method>
    <signal name="PropertiesChanged">
      <arg name="interface_name" type="s"/>
      <arg name="changed_properties" type="a{sv}"/>
      <arg name="invalidated_properties" type="as"/>
    </signal>
  </interface>
  <interface name="org.mpris.MediaPlayer2">
    <method name="Raise"/>
    <method name="Quit"/>
    <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
    <property name="CanQuit" type="b" access="read"/>
    <property name="CanRaise" type="b" access="read"/>
    <property name="HasTrackList" type="b" access="read"/>
    <property name="Identity" type="s" access="read"/>
    <property name="DesktopEntry" type="s" access="read"/>
    <property name="SupportedUriSchemes" type="as" access="read"/>
    <property name="SupportedMimeTypes" type="as" access="read"/>
  </interface>
  <interface name="org.mpris.MediaPlayer2.Player">
    <method name="Next"/>
    <method name="Previous"/>
    <method name="Pause"/>
    <method name="PlayPause"/>
    <method name="Stop"/>
    <method name="Play"/>
    <method name="Seek">
      <arg direction="in" name="Offset" type="x"/>
    </method>
    <method name="SetPosition">
      <arg direction="in" name="TrackId" type="o"/>
      <arg direction="in" name="Position" type="x"/>
    </method>
    <method name="OpenUri">
      <arg direction="in" name="Uri" type="s"/>
    </method>
    <signal name="Seeked">
      <arg name="Position" type="x"/>
    </signal>
    <property name="PlaybackStatus" type="s" access="read">
      <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
    </property>
    <property name="LoopStatus" type="s" access="readwrite">
      <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
    </property>
    <property name="Rate" type="d" access="readwrite">
      <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
    </property>
    <property name="Shuffle" type="b" access="readwrite">
      <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
    </property>
    <property name="Metadata" type="a{sv}" access="read">
      <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
    </property>
    <property name="Volume" type="d" access="readwrite">
      <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
    </property>
    <property name="Position" type="x" access="read">
      <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
    </property>
    <property name="MinimumRate" type="d" access="read">
      <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
    </property>
    <property name="MaximumRate" type="d" access="read">
      <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
    </property>
    <property name="CanGoNext" type="b" access="read">
      <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
    </property>
    <property name="CanGoPrevious" type="b" access="read">
      <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
    </property>
    <property name="CanPlay" type="b" access="read">
      <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
    </property>
    <property name="CanPause" type="b" access="read">
      <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
    </property>
    <property name="CanSeek" type="b" access="read">
      <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
    </property>
    <property name="CanControl" type="b" access="read">
      <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
    </property>
  </interface>
</node>"""


# Default url handlers if MPD doesn't support 'urlhandlers' command
urlhandlers = [ 'http://' ]
downloaded_covers = [ '.covers/%s-%s.jpg' ]
local_covers = [ 'cover.jpg', 'cover.png', 'album.jpg', 'front.jpg',
    'folder.jpg', '.folder.jpg', '.folder.png', 'AlbumArt.jpg',
    'AlbumArtSmall.jpg' ]

# Wrapper to handle socket errors and similar
class MPDWrapper(mpd.MPDClient):

    def __init__(self, params):
        mpd.MPDClient.__init__(self)
        self._dbus = dbus
        self._params = params
        self._monitor = False
        self._status = False
        self._position = 0
        self._dbus_service = False
        self._errors = 0
        # init mmkeys, if configured
        if self._params['mmkeys']:
            try:
                gsd_object = dbus.SessionBus().get_object('org.gnome.SettingsDaemon',
                    '/org/gnome/SettingsDaemon/MediaKeys')
                # this is what gives us the multi media keys.
                gsd_object.GrabMediaPlayerKeys('mpDris2', 0,
                    dbus_interface='org.gnome.SettingsDaemon.MediaKeys')
                # connect_to_signal registers our callback function.
                gsd_object.connect_to_signal('MediaPlayerKeyPressed', self.on_mediakey)
            except:
                logger.error('Failed to connect to GNOME Settings Daemon.' \
                    ' Disabling multimedia key support')
                params['mmkeys'] = False

    def run(self):
        """ Try to connect to MPD """
        if self.my_connect():
            # If connection fail retry in 5 seconds
            gobject.timeout_add(5000, self.my_connect)

    def my_connect(self):
        """ Init MPD connection """
        try:
            # Connect to host
            self.connect(self._params['host'], self._params['port'])
            if params['password']:
                self.password(self._params['password'])
            # Get URL handlers supported by MPD
            global urlhandlers
            if 'urlhandlers' in self.commands():
                urlhandlers = self.urlhandlers()
            if self._errors > 0:
                notification.rnotify(identity, _('Reconnected'))
                logger.debug('Reconnected to MPD server.')
            else:
                logger.debug('Connected to MPD server.')
            # Make the socket non blocking to detect deconnections
            self._sock.settimeout(5.0)
            # Export our DBUS service
            if not self._dbus_service:
                self._dbus_service = MPRISInterface(self._params['music_dir'])
            else:
                # Add our service to the session bus
                self._dbus_service.add_to_connection(dbus.SessionBus(),
                    '/org/mpris/MediaPlayer2')
                self._dbus_service.aquire_name()
            # Init internal state to throw events at start
            self.init_state()
            # Add periodic status check for sending MPRIS events
            self._monitor = gobject.timeout_add(1000, self.monitor)
            # Reset error counter
            self._errors = 0
            # Return False to stop trying to connect
            return False
        except socket.error as e:
            self._errors += 1
            if self._errors < 6:
                logger.error('Could not connect to MPD: %s' % e)
            if self._errors == 6:
                logger.info('Continue to connect but going silent')
            return True
        except mpd.CommandError as e:
            logger.error('MPD command error: %s' % e)
            return True

    def reconnect(self):
        logger.warning("Disconnected")
        notification.rnotify(identity, _('Disconnected'), 'error')
        # Release the Dbus name
        self._dbus_service.release_name();
        # Disconnect service from the session bus
        self._dbus_service.remove_from_connection();
        # Stop monitoring
        gobject.source_remove(self._monitor)
        # Clean mpd client state
        self.disconnect()
        # Try to reconnect
        self.run()

    def init_state(self):
        # Get current state
        self._status = self.status()
        # Invalid some fields to throw events at start
        self._status['state'] = 'invalid'
        self._status['songid'] = '-1'
        self._position = 0

    def monitor(self):
        old_status = self._status
        old_position = self._position

        self._status = self.status()

        if type(self._status) == types.DictType:

            if 'time' in self._status:
                self._position = int(self._status['time'].split(':')[0])
            else:
                self._position = 0

            if self._status['state'] != 'stop':
                if not 'songid' in old_status or old_status['songid'] != self._status['songid']:
                    metadata = self._dbus_service.update_property('org.mpris.MediaPlayer2.Player', 'Metadata')
                    if self._params['notify']:
                        uri = 'sound'
                        if 'mpris:artUrl' in metadata:
                            uri = metadata['mpris:artUrl']
                        title = 'Unknown Title'
                        if 'xesam:title' in metadata:
                            title = metadata['xesam:title']
                        artist = 'Unknown Artist'
                        if 'xesam:artist' in metadata:
                            artist = metadata['xesam:artist'][0]
                        notification.notify(title, _('by %s') % artist, uri)

                if (abs(self._position - old_position) > 2 and \
                   old_status['songid'] == self._status['songid']) or \
                   not 'songid' in old_status or \
                   old_status['songid'] == '-1':
                    self._dbus_service.Seeked(self._position * 1000000)


            if old_status['volume'] != self._status['volume']:
                self._dbus_service.update_property('org.mpris.MediaPlayer2.Player', 'Volume')

            if old_status['state'] != self._status['state']:
                self._dbus_service.update_property('org.mpris.MediaPlayer2.Player', 'PlaybackStatus')

            if old_status['random'] != self._status['random']:
                self._dbus_service.update_property('org.mpris.MediaPlayer2.Player', 'Shuffle')

            if old_status['repeat'] != self._status['repeat']:
                self._dbus_service.update_property('org.mpris.MediaPlayer2.Player', 'LoopStatus')

        # Return True to continue polling
        return True

    def on_mediakey(self, appname, key):
        """ GNOME media key handler """
        logger.debug('Got GNOME mmkey "%s" for "%s"' % (key, appname))
        if key == 'Play':
            if self._status['state'] == 'play':
                notification.rnotify(identity, _('Paused'))
                self.pause()
            else:
                notification.rnotify(identity, _('Playing'))
                self.play()
        elif key == 'Next':
            self.next()
        elif key == 'Previous':
            self.previous()
        elif key == 'Stop':
            notification.rnotify(identity, _('Stopped'))
            self.stop()

    # Catch connection errors when calling mpd client methods
    def _execute(self, command, args):
        try:
            return mpd.MPDClient._execute(self, command, args)
        except (socket.error, mpd.MPDError, socket.timeout):
            self.reconnect()
            return False

class Notify:
    def __init__(self, params):
        """ Init the notification system """
        if params['notify']:
            if pynotify.init(identity):
                self._notification = pynotify.Notification("", "", "")
            else:
                logger.error('Failed to init libnotify; disabling' \
                    'notifications')
                self._notification = False
        else:
            self._notification = False

    def notify(self, title, body, uri = ''):
        """ Issue a new notification """
        if self._notification:
            self._notification = pynotify.Notification(title, body, uri)
            self._notification.show()

    def rnotify(self, title, body, uri = ''):
        """ Replace current notification """
        if self._notification:
            self._notification.update(title, body, uri)
            self._notification.show()


class MPRISInterface(dbus.service.Object):
    ''' The base object of an MPRIS player '''

    __name = "org.mpris.MediaPlayer2.mpd"
    __path = "/org/mpris/MediaPlayer2"
    __introspect_interface = "org.freedesktop.DBus.Introspectable"
    __prop_interface = dbus.PROPERTIES_IFACE

    def __init__(self, bus, path = ""):
        dbus.service.Object.__init__(self, dbus.SessionBus(),
            MPRISInterface.__path)
        self.path = path
        self.aquire_name()

    def aquire_name(self):
        self._bus_name = dbus.service.BusName(MPRISInterface.__name,
            bus=dbus.SessionBus())

    def release_name(self):
        del self._bus_name

    __root_interface = "org.mpris.MediaPlayer2"
    __root_props = {
        "CanQuit": (False, None),
        "CanRaise": (False, None),
        "DesktopEntry": ("mpDris2", None),
        "HasTrackList": (False, None),
        "Identity": (identity, None),
        "SupportedUriSchemes": (dbus.Array(signature="s"), None),
        "SupportedMimeTypes": (dbus.Array(signature="s"), None)
    }

    def __get_playback_status():
        status = mpd_wrapper.status()
        return {'play': 'Playing', 'pause': 'Paused', 'stop': 'Stopped'}[status['state']]

    def __set_loop_status(value):
        if str(value) == "Playlist":
            mpd_wrapper.repeat(1)
        elif str(value) == "None":
            mpd_wrapper.repeat(0)
        else:
            raise dbus.exceptions.DBusException("Loop mode not supported")
        return

    def __get_loop_status():
        status = mpd_wrapper.status()
        return ("None", "Playlist")[int(status['repeat'])]

    def __set_shuffle(value):
        mpd_wrapper.random(value)
        return

    def __get_shuffle():
        if mpd_wrapper.status()['random'] == '1':
            return True
        else:
            return False

    def __get_metadata():
        return format_metadata(mpd_wrapper.currentsong())

    def __get_volume():
        vol = float(mpd_wrapper.status()['volume'])
        if vol > 0:
            return vol / 100.0
        else:
            return 0.0

    def __set_volume(value):
        if value >= 0 and value <= 1:
            mpd_wrapper.setvol(int(value * 100))
        return

    def __get_position():
        status = mpd_wrapper.status()
        if 'time' in status:
            current, end = status['time'].split(':')
            return dbus.Int64((int(current) * 1000000))
        else:
            return dbus.Int64(0)

    def __can_next():
        if 'nextsongid' in mpd_wrapper.status():
            return True
        else:
            return False

    __player_interface = "org.mpris.MediaPlayer2.Player"
    __player_props = {
        "PlaybackStatus": (__get_playback_status, None),
        "LoopStatus": (__get_loop_status, __set_loop_status),
        "Rate": (1.0, None),
        "Shuffle": (__get_shuffle, __set_shuffle),
        "Metadata": (__get_metadata, None),
        "Volume": (__get_volume, __set_volume),
        "Position": (__get_position, None),
        "MinimumRate": (1.0, None),
        "MaximumRate": (1.0, None),
        "CanGoNext": (__can_next, None),
        "CanGoPrevious": (True, None),
        "CanPlay": (True, None),
        "CanPause": (True, None),
        "CanSeek": (True, None),
        "CanControl": (True, None),
    }

    __tracklist_interface = "org.mpris.MediaPlayer2.TrackList"

    __prop_mapping = {
        __player_interface: __player_props,
        __root_interface: __root_props}

    @dbus.service.method(__introspect_interface)
    def Introspect(self):
        return MPRIS2_INTROSPECTION

    @dbus.service.signal(__prop_interface, signature="sa{sv}as")
    def PropertiesChanged(self, interface, changed_properties,
        invalidated_properties):
        pass

    @dbus.service.method(__prop_interface,
                         in_signature="ss", out_signature="v")
    def Get(self, interface, prop):
        getter, setter = self.__prop_mapping[interface][prop]
        if callable(getter):
            return getter()
        return getter

    @dbus.service.method(__prop_interface,
                         in_signature="ssv", out_signature="")
    def Set(self, interface, prop, value):
        getter, setter = self.__prop_mapping[interface][prop]
        if setter is not None:
            setter(value)

    @dbus.service.method(__prop_interface,
                         in_signature="s", out_signature="a{sv}")
    def GetAll(self, interface):
        read_props = {}
        props = self.__prop_mapping[interface]
        for key, (getter, setter) in props.iteritems():
            if callable(getter): getter = getter()
            read_props[key] = getter
        return read_props

    def update_property(self, interface, prop):
        getter, setter = self.__prop_mapping[interface][prop]
        if callable(getter):
            value = getter()
        else:
            value = getter
        logger.debug('Updated property: %s = %s' % (prop, value))
        self.PropertiesChanged(interface, {prop: value}, [])
        return value

    # Root methods
    @dbus.service.method(__root_interface, in_signature='', out_signature='')
    def Raise(self):
        return

    @dbus.service.method(__root_interface, in_signature='', out_signature='')
    def Quit(self):
        return

    # Player methods
    @dbus.service.method(__player_interface, in_signature='', out_signature='')
    def Next(self):
        mpd_wrapper.next()
        return

    @dbus.service.method(__player_interface, in_signature='', out_signature='')
    def Previous(self):
        mpd_wrapper.previous()
        return

    @dbus.service.method(__player_interface, in_signature='', out_signature='')
    def Pause(self):
        mpd_wrapper.pause()
        notification.rnotify(identity, _('Paused'))
        return

    @dbus.service.method(__player_interface, in_signature='', out_signature='')
    def PlayPause(self):
        status = mpd_wrapper.status()
        if status['state'] == 'play':
            mpd_wrapper.pause()
            notification.rnotify(identity, _('Paused'))
        else:
            mpd_wrapper.play()
            notification.rnotify(identity, _('Playing'))
        return

    @dbus.service.method(__player_interface, in_signature='', out_signature='')
    def Stop(self):
        mpd_wrapper.stop()
        notification.rnotify(identity, _('Stopped'))
        return

    @dbus.service.method(__player_interface, in_signature='', out_signature='')
    def Play(self):
        mpd_wrapper.play()
        notification.notify(identity, _('Playing'))
        return

    @dbus.service.method(__player_interface, in_signature='x', out_signature='')
    def Seek(self, offset):
        status = mpd_wrapper.status()
        current, end = status['time'].split(':')
        current = int(current)
        end = int(end)
        offset = int(offset) / 1000000
        if current + offset <= end:
            position = current + offset
            if position < 0: position = 0
            mpd_wrapper.seekid(int(status['songid']), position)
            self.Seeked(position * 1000000)
        return

    @dbus.service.method(__player_interface, in_signature='ox', out_signature='')
    def SetPosition(self, trackid, position):
        song = mpd_wrapper.currentsong()
        # FIXME: use real dbus objects
        if str(trackid) != '/org/mpris/MediaPlayer2/Track/%s' % song['id']: return
        # Convert position to seconds
        position = int(position) / 1000000
        if position <= int(song['time']):
            mpd_wrapper.seekid(int(song['id']), position)
            self.Seeked(position * 1000000)
        return

    @dbus.service.signal(__player_interface, signature='x')
    def Seeked(self, position):
        logger.debug("Seeked to %i" % position)
        return float(position)

    @dbus.service.method(__player_interface, in_signature='', out_signature='')
    def OpenUri(self):
        # TODO
        return

# Handle signals more gracefully
def handle_sigint(signum, frame):
    logger.debug('Caught SIGINT, exiting.')
    loop.quit()

def format_metadata(metadata):
    """http://xmms2.org/wiki/MPRIS_Metadata"""

    if 'id' in metadata:
        metadata['mpris:trackid'] = "/org/mpris/MediaPlayer2/Track/%s" % metadata['id']

    if 'time' in metadata:
        metadata['mpris:length'] = int(metadata['time']) * 1000000

    if 'date' in metadata:
        if len(metadata['date']) is 4:
            metadata['xesam:contentCreated'] = metadata['date']
        else:
            metadata['xesam:contentCreated'] = metadata['date'][0:4]

    if 'track' in metadata:
        if type(metadata['track']) == list and len(metadata['track']) > 0:
            track = str(metadata['track'][0])
        else:
            track = str(metadata['track'])
        if re.match('^([0-9]+).*', track):
            metadata['xesam:trackNumber'] = int(re.match('^([0-9]+).*', track).group(1))
            # Ensure the integer is signed 32bit
            if metadata['xesam:trackNumber'] & 0x80000000:
                metadata['xesam:trackNumber'] += -0x100000000
        else:
            metadata['xesam:trackNumber'] = 0

    if 'disc' in metadata:
        if type(metadata['disc']) == list and len(metadata['disc']) > 0:
            disc = str(metadata['disc'][0])
        else:
            disc = str(metadata['disc'])
        if re.match('^([0-9]+).*', disc):
            metadata['xesam:discNumber'] = int(re.match('^([0-9]+).*', disc).group(1))

    if 'artist' in metadata:
        metadata['xesam:artist'] = [metadata['artist'],]

    if 'composer' in metadata:
        metadata['xesam:composer'] = [metadata['composer'],]

    mpd_tags = ('album', 'title')
    for tag in mpd_tags:
        if tag in metadata:
            metadata['xesam:%s' % tag] = metadata[tag]

    if 'file' in metadata:
        file = metadata['file']
        if len([ x for x in urlhandlers if file.startswith(x) ]) == 0:
            file = os.path.join(params['music_dir'], file)
        metadata['xesam:url'] = file
        if file.startswith('file://'):
            basedirpath = os.path.dirname(file).replace('file://', '')
            cover = False
            if os.path.exists(basedirpath):
                for f in os.listdir(basedirpath):
                    if f in local_covers:
                        cover = 'file://' + os.path.join(basedirpath, f)
                if not cover and 'artist' in metadata and 'album' in metadata:
                    for f in downloaded_covers:
                        f = os.path.expanduser(os.path.join('~', f % \
                            (metadata['artist'], metadata['album'])))
                        if os.path.exists(f):
                            cover = 'file://' + f
                if cover:
                    metadata['mpris:artUrl'] = cover

    # Stream: populate some missings tags with stream's name
    if 'name' in metadata:
        if 'xesam:title' not in metadata:
            metadata['xesam:title'] = metadata['name']
        elif 'xesam:album' not in metadata:
            metadata['xesam:album'] = metadata['name']

    surplus_tags = set(metadata.keys()).difference(set(allowed_tags.keys()))
    # Remove surplus tags
    for tag in surplus_tags:
        del metadata[tag]

    # Cast metadata to the correct type, or discard it
    for key, value in metadata.items():
        try:
            metadata[key] = allowed_tags[key](value)
        except ValueError:
            del metadata[key]
            logger.error("Can't cast value %s to %s" % \
                (value, allowed_tags[key]))

    return dbus.Dictionary(metadata, signature='sv')

def find_music_dir():
    if 'XDG_MUSIC_DIR' in os.environ:
        return os.environ['XDG_MUSIC_DIR']

    conf = os.path.expanduser('~/.config/user-dirs.dirs')
    try:
        for line in open(conf, 'r'):
            if not line.startswith('XDG_MUSIC_DIR='):
                continue
            # use shlex to handle "shell escaping"
            path = shlex.split(line)[0][14:]
            if path.startswith('$HOME/'):
                return os.path.expanduser('~' + path[5:])
            elif path.startswith('/'):
                return path
            else:
                continue # other forms are not supported
    except IOError:
        pass

    paths = '~/Music', '~/music'
    for path in map(os.path.expanduser, paths):
        if os.path.isdir(path):
            return path

    return None

def usage(params):
    print """\
Usage: %(progname)s [OPTION]... [MPD_HOST] [MPD_PORT]

Note: Environment variables MPD_HOST and MPD_PORT can be used instead of above
      arguments.

     -p, --path=PATH        Sets the library path of MPD to PATH
     -d, --debug            Run in debug mode

Default: MPD_HOST: %(host)s, MPD_PORT: %(port)s

Report bugs to https://github.com/eonpatapon/mpDris2/issues""" % params

if __name__ == '__main__':
    DBusGMainLoop(set_as_default=True)

    path = find_music_dir()

    config = ConfigParser.SafeConfigParser()
    config.read(['/etc/mpDris2.conf', os.path.expanduser('~/.config/mpDris2/mpDris2.conf')])

    if config.has_option('Connection', 'host'):
        params['host'] = config.get('Connection', 'host')
    if config.has_option('Connection', 'port'):
        params['port'] = config.get('Connection', 'port')
    if config.has_option('Connection', 'password'):
        params['password'] = config.get('Connection', 'password')
    if config.has_option('Connection', 'music_dir'):
        path = config.get('Connection', 'music_dir')

    if 'MPD_HOST' in os.environ:
        params['host'] = os.environ['MPD_HOST']
    if 'MPD_PORT' in os.environ:
        params['port'] = os.environ['MPD_PORT']

    try:
        (opts, args) = getopt.getopt(sys.argv[1:], 'hdp:', ['help', 'debug', 'path='])
    except getopt.GetoptError, (msg, opt):
        print sys.argv[0] + ': ' + msg
        print
        usage(params)
        sys.exit(2)

    log_format = '%(asctime)s %(module)s %(levelname)s: %(message)s'
    log_level = logging.INFO

    for (opt, arg) in opts:
        if opt in ['-h', '--help']:
            usage(params)
            sys.exit()
        elif opt in ['-p', '--path']:
            path = arg
        elif opt in ['-d', '--debug']:
            log_level = logging.DEBUG

    logging.basicConfig(format=log_format, level=log_level)
    logger = logging.getLogger('mpDris2')

    if len(args) > 2:
        usage(params)
        sys.exit()

    for arg in args[:2]:
        if arg.isdigit():
            params['port'] = arg
        else:
            params['host'] = arg

    if '@' in params['host']:
        params['password'], params['host'] = params['host'].split('@', 1)

    if path and os.path.exists(path):
        logger.info('Using %s as music library path' % path)
        if not path.startswith('file://'):
            params['music_dir'] = 'file://' + path
    else:
        logger.warning('By not supplying a path for the music library ' \
            'this program will break the MPRIS specification!')

    for bling in ['mmkeys','notify']:
        if config.has_option('Bling', bling):
            params[bling] = config.getboolean('Bling', bling)

    loop = gobject.MainLoop()
    signal.signal(signal.SIGINT, handle_sigint)

    # Wrapper to send notifications
    notification = Notify(params)

    # Create wrapper to handle connection failures with MPD more gracefully
    mpd_wrapper = MPDWrapper(params)
    mpd_wrapper.run()

    # Run idle loop
    loop.run()

    # Clean up
    try:
        mpd_wrapper.close()
        mpd_wrapper.disconnect()
        logger.debug('Exiting')
    except mpd.ConnectionError:
        logger.error('Failed to disconnect properly')
