Author: duncan
Date: Sun Jan  6 16:45:25 2008
New Revision: 10263

Log:
[ 1865008 ] replacement MPD plugin
New plug-in from Graham Billiau added


Added:
   branches/rel-1-7/freevo/src/audio/plugins/mpd_playlist.py   (contents, props 
changed)
   branches/rel-1-7/freevo/src/audio/plugins/mpd_status.py   (contents, props 
changed)
   branches/rel-1-7/freevo/src/audio/plugins/mpdclient2.py   (contents, props 
changed)
   branches/rel-1/freevo/src/audio/plugins/mpd_playlist.py   (contents, props 
changed)
   branches/rel-1/freevo/src/audio/plugins/mpd_status.py   (contents, props 
changed)
   branches/rel-1/freevo/src/audio/plugins/mpdclient2.py   (contents, props 
changed)
Modified:
   branches/rel-1-7/freevo/ChangeLog
   branches/rel-1/freevo/ChangeLog

Modified: branches/rel-1-7/freevo/ChangeLog
==============================================================================
--- branches/rel-1-7/freevo/ChangeLog   (original)
+++ branches/rel-1-7/freevo/ChangeLog   Sun Jan  6 16:45:25 2008
@@ -16,6 +16,7 @@
 == Release 1.7.6 (2008-??-??) ==
 --------------------------------
 
+ * New audio MPD plug-in replacement (F#1865008)
  * New DirecTV plug-in to send to DirecTV receivers (F#1858775)
  * Updated cdbackup plug-in with CD_RIP_FMT to set the encoding format 
(F#1857460)
  * Updated recordings manager plug-in to add thumbnail support (F#1857397)

Added: branches/rel-1-7/freevo/src/audio/plugins/mpd_playlist.py
==============================================================================
--- (empty file)
+++ branches/rel-1-7/freevo/src/audio/plugins/mpd_playlist.py   Sun Jan  6 
16:45:25 2008
@@ -0,0 +1,96 @@
+# This is a replacement for the previous mpd plugin
+# By Graham Billiau <[EMAIL PROTECTED]> (DrLizAu's code monkey)
+# This code is released under the GPL
+
+# There are two parts to this plugin, an addition to the audio item menu to 
queue the item in mpd's playlist and a
+# status display in the audio menu
+
+# TODO:
+#   add code to cope if the mpd server crashes
+#   add code to enqueue an entire directory & sub-directories
+#   add code to enqueue an existing playlist
+#   modify code to support localisation
+#   investigate having the mpd connection managed by another class
+
+# Advantages of this over the previous mpd plugin:
+#   The code is a lot cleaner and more robust.
+#   Faster (talks to mpd directly, rather than calling other programs).
+#   Allows you to modify the playlist within freevo.
+
+# This only works if the music to be played is part of the filesystem avalible 
to mpd and also avalible to freevo
+# so both on the same computer, or exported using samba or nfs
+
+# This code uses the following options in local_conf.py:
+#   MPD_SERVER_HOST='localhost'     # the host running the mpd server
+#   MPD_SERVER_PORT='6600'          # the port the server is listening on
+#   MPD_SERVER_PASSWORD=None        # the password to access the mpd server
+#   MPD_MUSIC_BASE_PATH='/mnt/music/'    # the local path to where the music 
is stored, must have trailing slash
+#   MPD_EXTERNAL_CLIENT='/usr/bin/pympd'    # the location of the external 
client you want to use, or None
+#   MPD_EXTERNAL_CLIENT_ARGS=''     # arguments to be passed to the external 
client, or None, obsolete
+
+# This is the mpd playlist plugin.
+# using this you can add the currently selected song to the mpd playlist
+
+import plugin
+import config
+
+import mpdclient2
+
+class PluginInterface (plugin.ItemPlugin):
+    """This plugin adds a 'enqueue in MPD' option to audio files"""
+
+
+    def __init__(self):
+        """open the connection to the mpd server and keep it alive
+        assume that the plugin is loaded once, then kept in memory"""
+        plugin.ItemPlugin.__init__(self)
+        self.conn = mpdclient2.Thread_MPD_Connection(config.MPD_SERVER_HOST, 
config.MPD_SERVER_PORT, True,
+                    config.MPD_SERVER_PASSWORD)
+        
+        # ensure there is a trailing slash on config.MPD_MUSIC_BASE_PATH
+        if not config.MPD_MUSIC_BASE_PATH.endswith('/'):
+            config.MPD_MUSIC_BASE_PATH = config.MPD_MUSIC_BASE_PATH + '/'
+
+
+    def config(self):
+        return [ ('MPD_SERVER_HOST', 'localhost', 'the host running the mpd 
server'),
+                 ('MPD_SERVER_PORT', 6600, 'the port the server is listening 
on'),
+                 ('MPD_SERVER_PASSWORD', None, 'the password to access the mpd 
server'),
+                 ('MPD_MUSIC_BASE_PATH', '/mnt/music/', 'the local path to 
where the music is stored') ]
+
+
+    def shutdown(self):
+        """close the connection to the mpd server"""
+        try:
+            # this always throws EOFError, even though there isn't really an 
error
+            self.conn.close()
+        except EOFError:
+            pass
+        return
+
+
+    def actions (self, item):
+        """add the option for all music that is in the mpd library"""
+        self.item = item
+        # check to see if item is a FileItem
+        if (item.type == 'file'):
+            # check to see if item is in mpd's library
+            if (item.filename.startswith(config.MPD_MUSIC_BASE_PATH)):
+                # can query mpd to see if the file is in it's ibrary
+                return [ (self.enqueue_file, 'Add to MPD playlist') ]
+        #elif (item.type == 'dir'):
+        #elif (item.type == 'playlist'):
+        return []
+
+
+    def enqueue_file(self, arg=None, menuw=None):
+        self.conn.add(self.item.filename[len(config.MPD_MUSIC_BASE_PATH):])
+        if menuw is not None:
+            menuw.delete_menu(arg, menuw)
+        return
+
+
+    #def enqueue_dir(self, arg=None, menuw=None):
+
+
+    #def enqueue_playlist(self, arg=None, menuw=None):
\ No newline at end of file

Added: branches/rel-1-7/freevo/src/audio/plugins/mpd_status.py
==============================================================================
--- (empty file)
+++ branches/rel-1-7/freevo/src/audio/plugins/mpd_status.py     Sun Jan  6 
16:45:25 2008
@@ -0,0 +1,242 @@
+# This is a replacement for the previous mpd plugin
+# By Graham Billiau <[EMAIL PROTECTED]> (DrLizAu's code monkey)
+# This code is released under the GPL
+
+# There are two parts to this plugin, an addition to the audio item menu to 
queue the item in mpd's playlist and a
+# status display in the audio menu
+
+# Advantages of this over the previous mpd plugin:
+#   The code is a lot cleaner and more robust.
+#   Faster (talks to mpd directly, rather than calling other programs).
+#   Allows you to modify the playlist within freevo.
+
+# TODO
+#   enable localisation
+#   investigate having the mpd connection managed by another class
+#   pretty GUI
+#   have status/everything update periodicly
+#   add a playlist browser
+#   code to handle when the mpd server is down
+#   show the currently playing song in the status view
+
+# This code uses the following options in local_conf.py:
+#   MPD_SERVER_HOST='localhost'     # the host running the mpd server
+#   MPD_SERVER_PORT='6600'          # the port the server is listening on
+#   MPD_SERVER_PASSWORD=None        # the password to access the mpd server
+#   MPD_MUSIC_BASE_PATH='/mnt/music'    # the local path to where the music is 
stored
+#   MPD_EXTERNAL_CLIENT='/usr/bin/pympd'    # the location of the external 
client you want to use, or None
+#   MPD_EXTERNAL_CLIENT_ARGS=''     # arguments to be passed to the external 
client, or None, obsolete
+
+# This is the status display.
+# using this you can see the currently playing track, play, pause, stop, skip 
forward, skip back, toggle repeat, toggle
+# shuffle, clear playlist, open external playlist editor, change volume
+
+import plugin
+import config
+from gui import PopupBox
+import menu
+import childapp
+
+import os
+import sys
+
+import mpdclient2
+
+class PluginInterface(plugin.MainMenuPlugin):
+    """This creates a new item in the audio menu
+    Which allows you to view and modify the MPD settings"""
+    
+    def __init__(self):
+        """ initilise the plugin"""
+        plugin.MainMenuPlugin.__init__(self)
+        self.show_item = menu.MenuItem('MPD status', action=self.show_menu)
+        self.show_item.type = 'audio'
+        plugin.register(self, 'audio.MPD_status')
+        # connect to the server
+        self.conn = mpdclient2.Thread_MPD_Connection(config.MPD_SERVER_HOST, 
config.MPD_SERVER_PORT, True,
+                    config.MPD_SERVER_PASSWORD)
+       
+        # items to go in the mpd menu
+        self.item_play = menu.MenuItem('play', self.mpd_play)#, parent=self)
+        self.item_status = menu.MenuItem('status', self.mpd_status)#, 
parent=self)
+        self.item_pause = menu.MenuItem('pause', self.mpd_pause)#, parent=self)
+        self.item_stop = menu.MenuItem('stop', self.mpd_stop)#, parent=self)
+        self.item_next = menu.MenuItem('next track', self.mpd_next)#, 
parent=self)
+        self.item_previous = menu.MenuItem('previous track', 
self.mpd_previous)#, parent=self)
+        self.item_clear = menu.MenuItem('clear playlist', self.mpd_clear)#, 
parent=self)
+        self.item_shuffle = menu.MenuItem('shuffle playlist', 
self.mpd_shuffle)#, parent=self)
+        self.item_random = menu.MenuItem('toggle random', 
self.mpd_toggle_random)#, parent=self)
+        self.item_repeat = menu.MenuItem('toggle repeat', 
self.mpd_toggle_repeat)#, parent=self)
+        self.item_extern = menu.MenuItem('open external mpd client', 
self.start_external)#, parent=self)
+        
+    
+    
+    def shutdown(self):
+        """close the connection to the MPD server"""
+        try:
+            self.conn.close()
+        except EOFError:
+            # this always happens
+            pass
+    
+    
+    def config(self):
+        """returns the config variables used by this plugin"""
+        return [ ('MPD_SERVER_HOST', 'localhost', 'the host running the mpd 
server'),
+                 ('MPD_SERVER_PORT', 6600, 'the port the server is listening 
on'),
+                 ('MPD_SERVER_PASSWORD', None, 'the password to access the mpd 
server'),
+                 ('MPD_EXTERNAL_CLIENT', None,'the location of the external 
client you want to use') ]
+                 #('MPD_EXTERNAL_CLIENT_ARGS', '','arguments to be passed to 
the external client') ]
+    
+    
+    def items(self, parent):
+        """returns the options to add to the main menu"""
+        return [ self.show_item ]
+    
+    
+    def actions(self):
+        """extra options for this menu"""
+        return [ ]
+    
+    
+    def create_menu(self):
+        """this creates the menu"""
+        
+        menu_items = []
+        stat = self.conn.status()
+        
+        # populate the menu
+        menu_items.append(self.item_status)
+        if (stat['state'] == 'play'):
+            menu_items.append(self.item_pause)
+            menu_items.append(self.item_stop)
+        elif (stat['state'] == 'pause'):
+            menu_items.append(self.item_play)
+            menu_items.append(self.item_stop)
+        else:
+            menu_items.append(self.item_play)
+        menu_items.append(self.item_next)
+        menu_items.append(self.item_previous)
+        menu_items.append(self.item_repeat)
+        menu_items.append(self.item_random)
+        menu_items.append(self.item_shuffle)
+        menu_items.append(self.item_clear)
+        if not (config.MPD_EXTERNAL_CLIENT is None or 
config.MPD_EXTERNAL_CLIENT == ''):
+            menu_items.append(self.item_extern)
+            
+        return menu.Menu('MPD status', menu_items, 
reload_func=self.create_menu)
+    
+    
+    def show_menu(self, arg=None, menuw=None):
+        """this displays the menu"""
+        
+        # display the menu
+        mpd_menu = self.create_menu()
+        menuw.pushmenu(mpd_menu)
+        menuw.refresh()
+    
+    
+    def mpd_play(self, arg=None, menuw=None):
+        """send the play command to the mpd server"""
+        self.conn.play()
+       # force the menu to reload
+       menuw.delete_menu()
+       self.show_menu(arg, menuw)
+    
+    
+    def mpd_next(self, arg=None, menuw=None):
+        """send the play command to the mpd server"""
+        self.conn.next()
+    
+    
+    def mpd_previous(self, arg=None, menuw=None):
+        """send the play command to the mpd server"""
+        self.conn.previous()
+    
+    
+    def mpd_stop(self, arg=None, menuw=None):
+        """send the stop command to the mpd server"""
+        self.conn.stop()
+       # force the menu to reload
+       menuw.delete_menu()
+       self.show_menu(arg, menuw)
+    
+    
+    def mpd_pause(self, arg=None, menuw=None):
+        """send the pause command to the mpd server"""
+        self.conn.pause(1)
+       # force the menu to reload
+       menuw.delete_menu()
+       self.show_menu(arg, menuw)
+    
+    
+    def mpd_shuffle(self, arg=None, menuw=None):
+        """send the shuffle command to the mpd server"""
+        self.conn.shuffle()
+    
+    
+    def mpd_toggle_random(self, arg=None, menuw=None):
+        """toggle random on/off to the mpd server"""
+        stat = self.conn.status()
+        if (stat['random'] == '0'):
+            self.conn.random(1)
+        else:
+            self.conn.random(0)
+    
+    
+    def mpd_toggle_repeat(self, arg=None, menuw=None):
+        """toggle repeat on/off to the mpd server"""
+        stat = self.conn.status()
+        if (stat['repeat'] == '0'):
+            self.conn.repeat(1)
+        else:
+            self.conn.repeat(0)
+    
+    
+    def mpd_clear(self, arg=None, menuw=None):
+        """send the clear command to the mpd server"""
+        self.conn.clear()
+    
+    
+    def start_external(self, arg=None, menuw=None):
+        """start the external browser in the config file"""
+        if (config.MPD_EXTERNAL_CLIENT is None or config.MPD_EXTERNAL_CLIENT 
== ''):
+            return
+        
+        #if (config.MPD_EXTERNAL_CLIENT_ARGS is None or 
config.MPD_EXTERNAL_CLIENT_ARGS == ''):
+            #args = None
+        #else:
+            #args = config.MPD_EXTERNAL_CLIENT_ARGS.split()
+            
+        try:
+            #os,spawnv(os.P_NOWAIT, config.MPD_EXTERNAL_CLIENT, args)
+           childapp.ChildApp(config.MPD_EXTERNAL_CLIENT)
+        except:
+            text = "Error starting external client\n%s\n%s" %sys.exc_info()[:2]
+            pop = PopupBox(text)
+            pop.show()
+    
+    
+    def mpd_status(self, arg=None, menuw=None):
+        """bring up a dialog showing mpd's current status"""
+        stat = self.conn.status()
+        
+        text = "status:     %s\n" %(stat['state'])
+        if (stat['state'] != 'stop'):
+            # how do i get the song name?
+            text += "track:      %s\\%s\n" %(int(stat['song']) + 1, 
stat['playlistlength'])
+            text += "time:       %s\n" %(stat['time'])
+        if (stat['repeat'] == '1'):
+            text += "repeat:     on\n"
+        else:
+            text += "repeat:     off\n"
+        if (stat['random'] == '1'):
+            text += "random:     on\n"
+        else:
+            text += "random:     off\n"
+        text += "volume:    %s" %(stat['volume'])
+        
+        pop = PopupBox(text)
+        pop.show()
+    
+    
\ No newline at end of file

Added: branches/rel-1-7/freevo/src/audio/plugins/mpdclient2.py
==============================================================================
--- (empty file)
+++ branches/rel-1-7/freevo/src/audio/plugins/mpdclient2.py     Sun Jan  6 
16:45:25 2008
@@ -0,0 +1,370 @@
+#!/usr/bin/env python
+
+# py-libmpdclient2 is written by Nick Welch <[EMAIL PROTECTED]>, 2005.
+#
+# This software is released to the public
+# domain and is provided AS IS, with NO WARRANTY.
+
+import socket
+from time import sleep
+import thread
+
+# a line is either:
+#
+# key:val pair
+# OK
+# ACK
+
+class socket_talker(object):
+
+    def __init__(self, host, port):
+        self.host = host
+        self.port = port
+        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        self.sock.connect((host, port))
+        self.file = self.sock.makefile("rb+")
+        self.current_line = ''
+        self.ack = ''
+        self.done = True
+
+    # this SUCKS
+
+    def get_line(self):
+        if not self.current_line:
+            self.current_line = self.file.readline().rstrip("\n")
+        if not self.current_line:
+            raise EOFError
+        if self.current_line == "OK" or self.current_line.startswith("ACK"):
+            self.done = True
+        return self.current_line
+
+    def putline(self, line):
+        self.file.write("%s\n" % line)
+        self.file.flush()
+        self.done = False
+
+    def get_pair(self):
+        line = self.get_line()
+        self.ack = ''
+
+        if self.done:
+            if line.startswith("ACK"):
+                self.ack = line.split(None, 1)[1]
+            return ()
+
+        pair = line.split(": ", 1)
+        if len(pair) != 2:
+            raise RuntimeError("bogus response: ``%s''" % line)
+
+        return pair
+
+ZERO = 0
+ONE = 1
+MANY = 2
+
+plitem_delim = ["file", "directory", "playlist"]
+
+commands = {
+    # (name, nargs): (format string, nresults, results_type_name, 
delimiter_key)
+
+    # delimiter key is for commands that return multiple results.  we use this
+    # string to detect the beginning of a new result object.
+
+    # if results_type_name is empty, the result object's .type will be set to
+    # the key of the first key/val pair in it; otherwise, it will be set to
+    # results_type_name.
+
+    ("kill",          0): ('%s', ZERO, '', []),
+    ("outputs",       0): ('%s', MANY, 'outputs', ['outputid']),
+    ("clear",         0): ('%s', ZERO, '', []),
+    ("currentsong",   0): ('%s', ONE, '', []),
+    ("shuffle",       0): ('%s', ZERO, '', []),
+    ("next",          0): ('%s', ZERO, '', []),
+    ("previous",      0): ('%s', ZERO, '', []),
+    ("stop",          0): ('%s', ZERO, '', []),
+    ("clearerror",    0): ('%s', ZERO, '', []),
+    ("close",         0): ('%s', ZERO, '', []),
+    ("commands",      0): ('%s', MANY, 'commands', ['command']),
+    ("notcommands",   0): ('%s', MANY, 'notcommands', ['command']),
+    ("ping",          0): ('%s', ZERO, '', []),
+    ("stats",         0): ('%s', ONE, 'stats', []),
+    ("status",        0): ('%s', ONE, 'status', []),
+    ("play",          0): ('%s', ZERO, '', []),
+    ("playlistinfo",  0): ('%s', MANY, '', plitem_delim),
+    ("playlistid",    0): ('%s', MANY, '', plitem_delim),
+    ("lsinfo",        0): ('%s', MANY, '', plitem_delim),
+    ("update",        0): ('%s', ZERO, '', []),
+    ("listall",       0): ('%s', MANY, '', plitem_delim),
+    ("listallinfo",   0): ('%s', MANY, '', plitem_delim),
+
+    ("disableoutput", 1): ("%s %d", ZERO, '', []),
+    ("enableoutput",  1): ("%s %d", ZERO, '', []),
+    ("delete",        1): ('%s %d', ZERO, '', []), # <int song>
+    ("deleteid",      1): ('%s %d', ZERO, '', []), # <int songid>
+    ("playlistinfo",  1): ('%s %d', MANY, '', plitem_delim), # <int song>
+    ("playlistid",    1): ('%s %d', MANY, '', plitem_delim), # <int songid>
+    ("crossfade",     1): ('%s %d', ZERO, '', []), # <int seconds>
+    ("play",          1): ('%s %d', ZERO, '', []), # <int song>
+    ("playid",        1): ('%s %d', ZERO, '', []), # <int songid>
+    ("random",        1): ('%s %d', ZERO, '', []), # <int state>
+    ("repeat",        1): ('%s %d', ZERO, '', []), # <int state>
+    ("setvol",        1): ('%s %d', ZERO, '', []), # <int vol>
+    ("plchanges",     1): ('%s %d', MANY, '', plitem_delim), # <playlist 
version>
+    ("pause",         1): ('%s %d', ZERO, '', []), # <bool pause>
+
+    ("update",      1): ('%s "%s"', ONE, 'update', []), # <string path>
+    ("listall",     1): ('%s "%s"', MANY, '', plitem_delim), # <string path>
+    ("listallinfo", 1): ('%s "%s"', MANY, '', plitem_delim), # <string path>
+    ("lsinfo",      1): ('%s "%s"', MANY, '', plitem_delim), # <string 
directory>
+    ("add",         1): ('%s "%s"', ZERO, '', []), # <string>
+    ("load",        1): ('%s "%s"', ZERO, '', []), # <string name>
+    ("rm",          1): ('%s "%s"', ZERO, '', []), # <string name>
+    ("save",        1): ('%s "%s"', ZERO, '', []), # <string playlist name>
+    ("password",    1): ('%s "%s"', ZERO, '', []), # <string password>
+
+    ("move",   2): ("%s %d %d", ZERO, '', []), # <int from> <int to>
+    ("moveid", 2): ("%s %d %d", ZERO, '', []), # <int songid from> <int to>
+    ("swap",   2): ("%s %d %d", ZERO, '', []), # <int song1> <int song2>
+    ("swapid", 2): ("%s %d %d", ZERO, '', []), # <int songid1> <int songid2>
+    ("seek",   2): ("%s %d %d", ZERO, '', []), # <int song> <int time>
+    ("seekid", 2): ("%s %d %d", ZERO, '', []), # <int songid> <int time>
+
+    # <string type> <string what>
+    ("find",   2): ('%s "%s" "%s"', MANY, '', plitem_delim),
+
+    # <string type> <string what>
+    ("search", 2): ('%s "%s" "%s"', MANY, '', plitem_delim),
+
+    # list <metadata arg1> [<metadata arg2> <search term>]
+
+    # <metadata arg1>
+    ("list", 1): ('%s "%s"', MANY, '', plitem_delim),
+
+    # <metadata arg1> <metadata arg2> <search term>
+    ("list", 3): ('%s "%s" "%s" "%s"', MANY, '', plitem_delim),
+}
+
+def is_command(cmd):
+    return cmd in [ k[0] for k in commands.keys() ]
+
+def escape(text):
+    # join/split is faster than replace
+    text = '\\\\'.join(text.split('\\')) # \ -> \\
+    text = '\\"'.join(text.split('"')) # " -> \"
+    return text
+
+def get_command(cmd, args):
+    try:
+        return commands[(cmd, len(args))]
+    except KeyError:
+        raise RuntimeError("no such command: %s (%d args)" % (cmd, len(args)))
+
+def send_command(talker, cmd, args):
+    args = list(args[:])
+    for i, arg in enumerate(args):
+        if not isinstance(arg, int):
+            args[i] = escape(str(arg))
+    format = get_command(cmd, args)[0]
+    talker.putline(format % tuple([cmd] + list(args)))
+
+class sender_n_fetcher(object):
+    def __init__(self, sender, fetcher):
+        self.sender = sender
+        self.fetcher = fetcher
+        self.iterate = False
+
+    def __getattr__(self, cmd):
+        return lambda *args: self.send_n_fetch(cmd, args)
+
+    def send_n_fetch(self, cmd, args):
+        getattr(self.sender, cmd)(*args)
+        junk, howmany, type, keywords = get_command(cmd, args)
+
+        if howmany == ZERO:
+            self.fetcher.clear()
+            return
+
+        if howmany == ONE:
+            return self.fetcher.one_object(keywords, type)
+
+        assert howmany == MANY
+        result = self.fetcher.all_objects(keywords, type)
+
+        if not self.iterate:
+            result = list(result)
+            self.fetcher.clear()
+            return result
+
+        # stupid hack because you apparently can't return non-None and yield
+        # within the same function
+        def yield_then_clear(it):
+            for x in it:
+                yield x
+            self.fetcher.clear()
+        return yield_then_clear(result)
+
+
+class command_sender(object):
+    def __init__(self, talker):
+        self.talker = talker
+    def __getattr__(self, cmd):
+        return lambda *args: send_command(self.talker, cmd, args)
+
+class response_fetcher(object):
+    def __init__(self, talker):
+        self.talker = talker
+        self.converters = {}
+
+    def clear(self):
+        while not self.talker.done:
+            self.talker.current_line = ''
+            self.talker.get_line()
+        self.talker.current_line = ''
+
+    def one_object(self, keywords, type):
+        # if type isn't empty, then the object's type is set to it.  otherwise
+        # the type is set to the key of the first key/val pair.
+
+        # keywords lists the keys that indicate a new object -- like for the
+        # 'outputs' command, keywords would be ['outputid'].
+
+        entity = dictobj()
+        if type:
+            entity['type'] = type
+
+        while not self.talker.done:
+            self.talker.get_line()
+            pair = self.talker.get_pair()
+
+            if not pair:
+                self.talker.current_line = ''
+                return entity
+
+            key, val = pair
+            key = key.lower()
+
+            if key in keywords and key in entity.keys():
+                return entity
+
+            if not type and 'type' not in entity.keys():
+                entity['type'] = key
+
+            entity[key] = self.convert(entity['type'], key, val)
+            self.talker.current_line = ''
+
+        return entity
+
+    def all_objects(self, keywords, type):
+        while 1:
+            obj = self.one_object(keywords, type)
+            if not obj:
+                raise StopIteration
+            yield obj
+            if self.talker.done:
+                raise StopIteration
+
+    def convert(self, cmd, key, val):
+        # if there's a converter, convert it, otherwise return it the same
+        return self.converters.get(cmd, {}).get(key, lambda x: x)(val)
+
+class dictobj(dict):
+    def __getattr__(self, attr):
+        return self[attr]
+    def __repr__(self):
+        # <mpdclient2.dictobj at 0x12345678 ..
+        #   {
+        #     key: val,
+        #     key2: val2
+        #   }>
+        return (object.__repr__(self).rstrip('>') + ' ..\n' +
+                '  {\n    ' +
+                ',\n    '.join([ '%s: %s' % (k, v) for k, v in self.items() ]) 
+
+                '\n  }>')
+
+class mpd_connection(object):
+    def __init__(self, host, port):
+        self.talker = socket_talker(host, port)
+        self.send = command_sender(self.talker)
+        self.fetch = response_fetcher(self.talker)
+        self.do = sender_n_fetcher(self.send, self.fetch)
+
+        self._hello()
+
+    def _hello(self):
+        line = self.talker.get_line()
+        if not line.startswith("OK MPD "):
+            raise RuntimeError("this ain't mpd")
+        self.mpd_version = line[len("OK MPD "):].strip()
+        self.talker.current_line = ''
+
+    # conn.foo() is equivalent to conn.do.foo(), but nicer
+    def __getattr__(self, attr):
+        if is_command(attr):
+            return getattr(self.do, attr)
+        raise AttributeError(attr)
+
+def parse_host(host):
+    if '@' in host:
+        return host.split('@', 1)
+    return '', host
+
+def connect(**kw):
+    import os
+
+    port = int(os.environ.get('MPD_PORT', 6600))
+    password, host = parse_host(os.environ.get('MPD_HOST', 'localhost'))
+
+    kw_port = kw.get('port', 0)
+    kw_password = kw.get('password', '')
+    kw_host = kw.get('host', '')
+
+    if kw_port:
+        port = kw_port
+    if kw_password:
+        password = kw_password
+    if kw_host:
+        host = kw_host
+
+    conn = mpd_connection(host, port)
+    if password:
+        conn.password(password)
+    return conn
+
+
+#
+# # Thread safe extenstion added by Graham Billiau <[EMAIL PROTECTED]>
+#
+
+# TODO:
+#   sometimes this hangs, probably a problem with the locking
+
+def MPD_Ping_Thread(conn):
+    """This should be run as a thread
+    It constantly loops, sending keepalive packets to the mpd server
+    it exits cleanly after the connection is closed"""
+    while True:
+        sleep(10)
+        try:
+            conn.ping()
+        except socket.error:
+            return
+
+class Thread_MPD_Connection:
+    """This is a wrapper around the mpdclient2 library to make it thread 
safe"""
+    #conn
+    def __init__ (self, host, port, keepalive=False, pword = None):
+        """create the connection and locks, 
+        if keepalive is True the connection will not time out and must be 
explcitly closed"""
+        self.conn = mpd_connection(host, port)
+        if (pword is not None):
+            self.conn.password(pword)
+        self.lock = thread.allocate_lock()
+        if (keepalive == True):
+            thread.start_new_thread(MPD_Ping_Thread, (self, ))
+
+    def __getattr__(self, attr):
+        """pass the request on to the connection object, while ensuring no 
conflicts"""
+        self.lock.acquire()
+        funct = self.conn.__getattr__(attr)
+        self.lock.release()
+        return funct

Modified: branches/rel-1/freevo/ChangeLog
==============================================================================
--- branches/rel-1/freevo/ChangeLog     (original)
+++ branches/rel-1/freevo/ChangeLog     Sun Jan  6 16:45:25 2008
@@ -19,6 +19,7 @@
 == Release 1.7.6 (2008-??-??) ==
 --------------------------------
 
+ * New audio MPD plug-in replacement (F#1865008)
  * New DirecTV plug-in to send to DirecTV receivers (F#1858775)
  * Updated cdbackup plug-in with CD_RIP_FMT to set the encoding format 
(F#1857460)
  * Updated recordings manager plug-in to add thumbnail support (F#1857397)

Added: branches/rel-1/freevo/src/audio/plugins/mpd_playlist.py
==============================================================================
--- (empty file)
+++ branches/rel-1/freevo/src/audio/plugins/mpd_playlist.py     Sun Jan  6 
16:45:25 2008
@@ -0,0 +1,96 @@
+# This is a replacement for the previous mpd plugin
+# By Graham Billiau <[EMAIL PROTECTED]> (DrLizAu's code monkey)
+# This code is released under the GPL
+
+# There are two parts to this plugin, an addition to the audio item menu to 
queue the item in mpd's playlist and a
+# status display in the audio menu
+
+# TODO:
+#   add code to cope if the mpd server crashes
+#   add code to enqueue an entire directory & sub-directories
+#   add code to enqueue an existing playlist
+#   modify code to support localisation
+#   investigate having the mpd connection managed by another class
+
+# Advantages of this over the previous mpd plugin:
+#   The code is a lot cleaner and more robust.
+#   Faster (talks to mpd directly, rather than calling other programs).
+#   Allows you to modify the playlist within freevo.
+
+# This only works if the music to be played is part of the filesystem avalible 
to mpd and also avalible to freevo
+# so both on the same computer, or exported using samba or nfs
+
+# This code uses the following options in local_conf.py:
+#   MPD_SERVER_HOST='localhost'     # the host running the mpd server
+#   MPD_SERVER_PORT='6600'          # the port the server is listening on
+#   MPD_SERVER_PASSWORD=None        # the password to access the mpd server
+#   MPD_MUSIC_BASE_PATH='/mnt/music/'    # the local path to where the music 
is stored, must have trailing slash
+#   MPD_EXTERNAL_CLIENT='/usr/bin/pympd'    # the location of the external 
client you want to use, or None
+#   MPD_EXTERNAL_CLIENT_ARGS=''     # arguments to be passed to the external 
client, or None, obsolete
+
+# This is the mpd playlist plugin.
+# using this you can add the currently selected song to the mpd playlist
+
+import plugin
+import config
+
+import mpdclient2
+
+class PluginInterface (plugin.ItemPlugin):
+    """This plugin adds a 'enqueue in MPD' option to audio files"""
+
+
+    def __init__(self):
+        """open the connection to the mpd server and keep it alive
+        assume that the plugin is loaded once, then kept in memory"""
+        plugin.ItemPlugin.__init__(self)
+        self.conn = mpdclient2.Thread_MPD_Connection(config.MPD_SERVER_HOST, 
config.MPD_SERVER_PORT, True,
+                    config.MPD_SERVER_PASSWORD)
+        
+        # ensure there is a trailing slash on config.MPD_MUSIC_BASE_PATH
+        if not config.MPD_MUSIC_BASE_PATH.endswith('/'):
+            config.MPD_MUSIC_BASE_PATH = config.MPD_MUSIC_BASE_PATH + '/'
+
+
+    def config(self):
+        return [ ('MPD_SERVER_HOST', 'localhost', 'the host running the mpd 
server'),
+                 ('MPD_SERVER_PORT', 6600, 'the port the server is listening 
on'),
+                 ('MPD_SERVER_PASSWORD', None, 'the password to access the mpd 
server'),
+                 ('MPD_MUSIC_BASE_PATH', '/mnt/music/', 'the local path to 
where the music is stored') ]
+
+
+    def shutdown(self):
+        """close the connection to the mpd server"""
+        try:
+            # this always throws EOFError, even though there isn't really an 
error
+            self.conn.close()
+        except EOFError:
+            pass
+        return
+
+
+    def actions (self, item):
+        """add the option for all music that is in the mpd library"""
+        self.item = item
+        # check to see if item is a FileItem
+        if (item.type == 'file'):
+            # check to see if item is in mpd's library
+            if (item.filename.startswith(config.MPD_MUSIC_BASE_PATH)):
+                # can query mpd to see if the file is in it's ibrary
+                return [ (self.enqueue_file, 'Add to MPD playlist') ]
+        #elif (item.type == 'dir'):
+        #elif (item.type == 'playlist'):
+        return []
+
+
+    def enqueue_file(self, arg=None, menuw=None):
+        self.conn.add(self.item.filename[len(config.MPD_MUSIC_BASE_PATH):])
+        if menuw is not None:
+            menuw.delete_menu(arg, menuw)
+        return
+
+
+    #def enqueue_dir(self, arg=None, menuw=None):
+
+
+    #def enqueue_playlist(self, arg=None, menuw=None):
\ No newline at end of file

Added: branches/rel-1/freevo/src/audio/plugins/mpd_status.py
==============================================================================
--- (empty file)
+++ branches/rel-1/freevo/src/audio/plugins/mpd_status.py       Sun Jan  6 
16:45:25 2008
@@ -0,0 +1,242 @@
+# This is a replacement for the previous mpd plugin
+# By Graham Billiau <[EMAIL PROTECTED]> (DrLizAu's code monkey)
+# This code is released under the GPL
+
+# There are two parts to this plugin, an addition to the audio item menu to 
queue the item in mpd's playlist and a
+# status display in the audio menu
+
+# Advantages of this over the previous mpd plugin:
+#   The code is a lot cleaner and more robust.
+#   Faster (talks to mpd directly, rather than calling other programs).
+#   Allows you to modify the playlist within freevo.
+
+# TODO
+#   enable localisation
+#   investigate having the mpd connection managed by another class
+#   pretty GUI
+#   have status/everything update periodicly
+#   add a playlist browser
+#   code to handle when the mpd server is down
+#   show the currently playing song in the status view
+
+# This code uses the following options in local_conf.py:
+#   MPD_SERVER_HOST='localhost'     # the host running the mpd server
+#   MPD_SERVER_PORT='6600'          # the port the server is listening on
+#   MPD_SERVER_PASSWORD=None        # the password to access the mpd server
+#   MPD_MUSIC_BASE_PATH='/mnt/music'    # the local path to where the music is 
stored
+#   MPD_EXTERNAL_CLIENT='/usr/bin/pympd'    # the location of the external 
client you want to use, or None
+#   MPD_EXTERNAL_CLIENT_ARGS=''     # arguments to be passed to the external 
client, or None, obsolete
+
+# This is the status display.
+# using this you can see the currently playing track, play, pause, stop, skip 
forward, skip back, toggle repeat, toggle
+# shuffle, clear playlist, open external playlist editor, change volume
+
+import plugin
+import config
+from gui import PopupBox
+import menu
+import childapp
+
+import os
+import sys
+
+import mpdclient2
+
+class PluginInterface(plugin.MainMenuPlugin):
+    """This creates a new item in the audio menu
+    Which allows you to view and modify the MPD settings"""
+    
+    def __init__(self):
+        """ initilise the plugin"""
+        plugin.MainMenuPlugin.__init__(self)
+        self.show_item = menu.MenuItem('MPD status', action=self.show_menu)
+        self.show_item.type = 'audio'
+        plugin.register(self, 'audio.MPD_status')
+        # connect to the server
+        self.conn = mpdclient2.Thread_MPD_Connection(config.MPD_SERVER_HOST, 
config.MPD_SERVER_PORT, True,
+                    config.MPD_SERVER_PASSWORD)
+       
+        # items to go in the mpd menu
+        self.item_play = menu.MenuItem('play', self.mpd_play)#, parent=self)
+        self.item_status = menu.MenuItem('status', self.mpd_status)#, 
parent=self)
+        self.item_pause = menu.MenuItem('pause', self.mpd_pause)#, parent=self)
+        self.item_stop = menu.MenuItem('stop', self.mpd_stop)#, parent=self)
+        self.item_next = menu.MenuItem('next track', self.mpd_next)#, 
parent=self)
+        self.item_previous = menu.MenuItem('previous track', 
self.mpd_previous)#, parent=self)
+        self.item_clear = menu.MenuItem('clear playlist', self.mpd_clear)#, 
parent=self)
+        self.item_shuffle = menu.MenuItem('shuffle playlist', 
self.mpd_shuffle)#, parent=self)
+        self.item_random = menu.MenuItem('toggle random', 
self.mpd_toggle_random)#, parent=self)
+        self.item_repeat = menu.MenuItem('toggle repeat', 
self.mpd_toggle_repeat)#, parent=self)
+        self.item_extern = menu.MenuItem('open external mpd client', 
self.start_external)#, parent=self)
+        
+    
+    
+    def shutdown(self):
+        """close the connection to the MPD server"""
+        try:
+            self.conn.close()
+        except EOFError:
+            # this always happens
+            pass
+    
+    
+    def config(self):
+        """returns the config variables used by this plugin"""
+        return [ ('MPD_SERVER_HOST', 'localhost', 'the host running the mpd 
server'),
+                 ('MPD_SERVER_PORT', 6600, 'the port the server is listening 
on'),
+                 ('MPD_SERVER_PASSWORD', None, 'the password to access the mpd 
server'),
+                 ('MPD_EXTERNAL_CLIENT', None,'the location of the external 
client you want to use') ]
+                 #('MPD_EXTERNAL_CLIENT_ARGS', '','arguments to be passed to 
the external client') ]
+    
+    
+    def items(self, parent):
+        """returns the options to add to the main menu"""
+        return [ self.show_item ]
+    
+    
+    def actions(self):
+        """extra options for this menu"""
+        return [ ]
+    
+    
+    def create_menu(self):
+        """this creates the menu"""
+        
+        menu_items = []
+        stat = self.conn.status()
+        
+        # populate the menu
+        menu_items.append(self.item_status)
+        if (stat['state'] == 'play'):
+            menu_items.append(self.item_pause)
+            menu_items.append(self.item_stop)
+        elif (stat['state'] == 'pause'):
+            menu_items.append(self.item_play)
+            menu_items.append(self.item_stop)
+        else:
+            menu_items.append(self.item_play)
+        menu_items.append(self.item_next)
+        menu_items.append(self.item_previous)
+        menu_items.append(self.item_repeat)
+        menu_items.append(self.item_random)
+        menu_items.append(self.item_shuffle)
+        menu_items.append(self.item_clear)
+        if not (config.MPD_EXTERNAL_CLIENT is None or 
config.MPD_EXTERNAL_CLIENT == ''):
+            menu_items.append(self.item_extern)
+            
+        return menu.Menu('MPD status', menu_items, 
reload_func=self.create_menu)
+    
+    
+    def show_menu(self, arg=None, menuw=None):
+        """this displays the menu"""
+        
+        # display the menu
+        mpd_menu = self.create_menu()
+        menuw.pushmenu(mpd_menu)
+        menuw.refresh()
+    
+    
+    def mpd_play(self, arg=None, menuw=None):
+        """send the play command to the mpd server"""
+        self.conn.play()
+       # force the menu to reload
+       menuw.delete_menu()
+       self.show_menu(arg, menuw)
+    
+    
+    def mpd_next(self, arg=None, menuw=None):
+        """send the play command to the mpd server"""
+        self.conn.next()
+    
+    
+    def mpd_previous(self, arg=None, menuw=None):
+        """send the play command to the mpd server"""
+        self.conn.previous()
+    
+    
+    def mpd_stop(self, arg=None, menuw=None):
+        """send the stop command to the mpd server"""
+        self.conn.stop()
+       # force the menu to reload
+       menuw.delete_menu()
+       self.show_menu(arg, menuw)
+    
+    
+    def mpd_pause(self, arg=None, menuw=None):
+        """send the pause command to the mpd server"""
+        self.conn.pause(1)
+       # force the menu to reload
+       menuw.delete_menu()
+       self.show_menu(arg, menuw)
+    
+    
+    def mpd_shuffle(self, arg=None, menuw=None):
+        """send the shuffle command to the mpd server"""
+        self.conn.shuffle()
+    
+    
+    def mpd_toggle_random(self, arg=None, menuw=None):
+        """toggle random on/off to the mpd server"""
+        stat = self.conn.status()
+        if (stat['random'] == '0'):
+            self.conn.random(1)
+        else:
+            self.conn.random(0)
+    
+    
+    def mpd_toggle_repeat(self, arg=None, menuw=None):
+        """toggle repeat on/off to the mpd server"""
+        stat = self.conn.status()
+        if (stat['repeat'] == '0'):
+            self.conn.repeat(1)
+        else:
+            self.conn.repeat(0)
+    
+    
+    def mpd_clear(self, arg=None, menuw=None):
+        """send the clear command to the mpd server"""
+        self.conn.clear()
+    
+    
+    def start_external(self, arg=None, menuw=None):
+        """start the external browser in the config file"""
+        if (config.MPD_EXTERNAL_CLIENT is None or config.MPD_EXTERNAL_CLIENT 
== ''):
+            return
+        
+        #if (config.MPD_EXTERNAL_CLIENT_ARGS is None or 
config.MPD_EXTERNAL_CLIENT_ARGS == ''):
+            #args = None
+        #else:
+            #args = config.MPD_EXTERNAL_CLIENT_ARGS.split()
+            
+        try:
+            #os,spawnv(os.P_NOWAIT, config.MPD_EXTERNAL_CLIENT, args)
+           childapp.ChildApp(config.MPD_EXTERNAL_CLIENT)
+        except:
+            text = "Error starting external client\n%s\n%s" %sys.exc_info()[:2]
+            pop = PopupBox(text)
+            pop.show()
+    
+    
+    def mpd_status(self, arg=None, menuw=None):
+        """bring up a dialog showing mpd's current status"""
+        stat = self.conn.status()
+        
+        text = "status:     %s\n" %(stat['state'])
+        if (stat['state'] != 'stop'):
+            # how do i get the song name?
+            text += "track:      %s\\%s\n" %(int(stat['song']) + 1, 
stat['playlistlength'])
+            text += "time:       %s\n" %(stat['time'])
+        if (stat['repeat'] == '1'):
+            text += "repeat:     on\n"
+        else:
+            text += "repeat:     off\n"
+        if (stat['random'] == '1'):
+            text += "random:     on\n"
+        else:
+            text += "random:     off\n"
+        text += "volume:    %s" %(stat['volume'])
+        
+        pop = PopupBox(text)
+        pop.show()
+    
+    
\ No newline at end of file

Added: branches/rel-1/freevo/src/audio/plugins/mpdclient2.py
==============================================================================
--- (empty file)
+++ branches/rel-1/freevo/src/audio/plugins/mpdclient2.py       Sun Jan  6 
16:45:25 2008
@@ -0,0 +1,370 @@
+#!/usr/bin/env python
+
+# py-libmpdclient2 is written by Nick Welch <[EMAIL PROTECTED]>, 2005.
+#
+# This software is released to the public
+# domain and is provided AS IS, with NO WARRANTY.
+
+import socket
+from time import sleep
+import thread
+
+# a line is either:
+#
+# key:val pair
+# OK
+# ACK
+
+class socket_talker(object):
+
+    def __init__(self, host, port):
+        self.host = host
+        self.port = port
+        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        self.sock.connect((host, port))
+        self.file = self.sock.makefile("rb+")
+        self.current_line = ''
+        self.ack = ''
+        self.done = True
+
+    # this SUCKS
+
+    def get_line(self):
+        if not self.current_line:
+            self.current_line = self.file.readline().rstrip("\n")
+        if not self.current_line:
+            raise EOFError
+        if self.current_line == "OK" or self.current_line.startswith("ACK"):
+            self.done = True
+        return self.current_line
+
+    def putline(self, line):
+        self.file.write("%s\n" % line)
+        self.file.flush()
+        self.done = False
+
+    def get_pair(self):
+        line = self.get_line()
+        self.ack = ''
+
+        if self.done:
+            if line.startswith("ACK"):
+                self.ack = line.split(None, 1)[1]
+            return ()
+
+        pair = line.split(": ", 1)
+        if len(pair) != 2:
+            raise RuntimeError("bogus response: ``%s''" % line)
+
+        return pair
+
+ZERO = 0
+ONE = 1
+MANY = 2
+
+plitem_delim = ["file", "directory", "playlist"]
+
+commands = {
+    # (name, nargs): (format string, nresults, results_type_name, 
delimiter_key)
+
+    # delimiter key is for commands that return multiple results.  we use this
+    # string to detect the beginning of a new result object.
+
+    # if results_type_name is empty, the result object's .type will be set to
+    # the key of the first key/val pair in it; otherwise, it will be set to
+    # results_type_name.
+
+    ("kill",          0): ('%s', ZERO, '', []),
+    ("outputs",       0): ('%s', MANY, 'outputs', ['outputid']),
+    ("clear",         0): ('%s', ZERO, '', []),
+    ("currentsong",   0): ('%s', ONE, '', []),
+    ("shuffle",       0): ('%s', ZERO, '', []),
+    ("next",          0): ('%s', ZERO, '', []),
+    ("previous",      0): ('%s', ZERO, '', []),
+    ("stop",          0): ('%s', ZERO, '', []),
+    ("clearerror",    0): ('%s', ZERO, '', []),
+    ("close",         0): ('%s', ZERO, '', []),
+    ("commands",      0): ('%s', MANY, 'commands', ['command']),
+    ("notcommands",   0): ('%s', MANY, 'notcommands', ['command']),
+    ("ping",          0): ('%s', ZERO, '', []),
+    ("stats",         0): ('%s', ONE, 'stats', []),
+    ("status",        0): ('%s', ONE, 'status', []),
+    ("play",          0): ('%s', ZERO, '', []),
+    ("playlistinfo",  0): ('%s', MANY, '', plitem_delim),
+    ("playlistid",    0): ('%s', MANY, '', plitem_delim),
+    ("lsinfo",        0): ('%s', MANY, '', plitem_delim),
+    ("update",        0): ('%s', ZERO, '', []),
+    ("listall",       0): ('%s', MANY, '', plitem_delim),
+    ("listallinfo",   0): ('%s', MANY, '', plitem_delim),
+
+    ("disableoutput", 1): ("%s %d", ZERO, '', []),
+    ("enableoutput",  1): ("%s %d", ZERO, '', []),
+    ("delete",        1): ('%s %d', ZERO, '', []), # <int song>
+    ("deleteid",      1): ('%s %d', ZERO, '', []), # <int songid>
+    ("playlistinfo",  1): ('%s %d', MANY, '', plitem_delim), # <int song>
+    ("playlistid",    1): ('%s %d', MANY, '', plitem_delim), # <int songid>
+    ("crossfade",     1): ('%s %d', ZERO, '', []), # <int seconds>
+    ("play",          1): ('%s %d', ZERO, '', []), # <int song>
+    ("playid",        1): ('%s %d', ZERO, '', []), # <int songid>
+    ("random",        1): ('%s %d', ZERO, '', []), # <int state>
+    ("repeat",        1): ('%s %d', ZERO, '', []), # <int state>
+    ("setvol",        1): ('%s %d', ZERO, '', []), # <int vol>
+    ("plchanges",     1): ('%s %d', MANY, '', plitem_delim), # <playlist 
version>
+    ("pause",         1): ('%s %d', ZERO, '', []), # <bool pause>
+
+    ("update",      1): ('%s "%s"', ONE, 'update', []), # <string path>
+    ("listall",     1): ('%s "%s"', MANY, '', plitem_delim), # <string path>
+    ("listallinfo", 1): ('%s "%s"', MANY, '', plitem_delim), # <string path>
+    ("lsinfo",      1): ('%s "%s"', MANY, '', plitem_delim), # <string 
directory>
+    ("add",         1): ('%s "%s"', ZERO, '', []), # <string>
+    ("load",        1): ('%s "%s"', ZERO, '', []), # <string name>
+    ("rm",          1): ('%s "%s"', ZERO, '', []), # <string name>
+    ("save",        1): ('%s "%s"', ZERO, '', []), # <string playlist name>
+    ("password",    1): ('%s "%s"', ZERO, '', []), # <string password>
+
+    ("move",   2): ("%s %d %d", ZERO, '', []), # <int from> <int to>
+    ("moveid", 2): ("%s %d %d", ZERO, '', []), # <int songid from> <int to>
+    ("swap",   2): ("%s %d %d", ZERO, '', []), # <int song1> <int song2>
+    ("swapid", 2): ("%s %d %d", ZERO, '', []), # <int songid1> <int songid2>
+    ("seek",   2): ("%s %d %d", ZERO, '', []), # <int song> <int time>
+    ("seekid", 2): ("%s %d %d", ZERO, '', []), # <int songid> <int time>
+
+    # <string type> <string what>
+    ("find",   2): ('%s "%s" "%s"', MANY, '', plitem_delim),
+
+    # <string type> <string what>
+    ("search", 2): ('%s "%s" "%s"', MANY, '', plitem_delim),
+
+    # list <metadata arg1> [<metadata arg2> <search term>]
+
+    # <metadata arg1>
+    ("list", 1): ('%s "%s"', MANY, '', plitem_delim),
+
+    # <metadata arg1> <metadata arg2> <search term>
+    ("list", 3): ('%s "%s" "%s" "%s"', MANY, '', plitem_delim),
+}
+
+def is_command(cmd):
+    return cmd in [ k[0] for k in commands.keys() ]
+
+def escape(text):
+    # join/split is faster than replace
+    text = '\\\\'.join(text.split('\\')) # \ -> \\
+    text = '\\"'.join(text.split('"')) # " -> \"
+    return text
+
+def get_command(cmd, args):
+    try:
+        return commands[(cmd, len(args))]
+    except KeyError:
+        raise RuntimeError("no such command: %s (%d args)" % (cmd, len(args)))
+
+def send_command(talker, cmd, args):
+    args = list(args[:])
+    for i, arg in enumerate(args):
+        if not isinstance(arg, int):
+            args[i] = escape(str(arg))
+    format = get_command(cmd, args)[0]
+    talker.putline(format % tuple([cmd] + list(args)))
+
+class sender_n_fetcher(object):
+    def __init__(self, sender, fetcher):
+        self.sender = sender
+        self.fetcher = fetcher
+        self.iterate = False
+
+    def __getattr__(self, cmd):
+        return lambda *args: self.send_n_fetch(cmd, args)
+
+    def send_n_fetch(self, cmd, args):
+        getattr(self.sender, cmd)(*args)
+        junk, howmany, type, keywords = get_command(cmd, args)
+
+        if howmany == ZERO:
+            self.fetcher.clear()
+            return
+
+        if howmany == ONE:
+            return self.fetcher.one_object(keywords, type)
+
+        assert howmany == MANY
+        result = self.fetcher.all_objects(keywords, type)
+
+        if not self.iterate:
+            result = list(result)
+            self.fetcher.clear()
+            return result
+
+        # stupid hack because you apparently can't return non-None and yield
+        # within the same function
+        def yield_then_clear(it):
+            for x in it:
+                yield x
+            self.fetcher.clear()
+        return yield_then_clear(result)
+
+
+class command_sender(object):
+    def __init__(self, talker):
+        self.talker = talker
+    def __getattr__(self, cmd):
+        return lambda *args: send_command(self.talker, cmd, args)
+
+class response_fetcher(object):
+    def __init__(self, talker):
+        self.talker = talker
+        self.converters = {}
+
+    def clear(self):
+        while not self.talker.done:
+            self.talker.current_line = ''
+            self.talker.get_line()
+        self.talker.current_line = ''
+
+    def one_object(self, keywords, type):
+        # if type isn't empty, then the object's type is set to it.  otherwise
+        # the type is set to the key of the first key/val pair.
+
+        # keywords lists the keys that indicate a new object -- like for the
+        # 'outputs' command, keywords would be ['outputid'].
+
+        entity = dictobj()
+        if type:
+            entity['type'] = type
+
+        while not self.talker.done:
+            self.talker.get_line()
+            pair = self.talker.get_pair()
+
+            if not pair:
+                self.talker.current_line = ''
+                return entity
+
+            key, val = pair
+            key = key.lower()
+
+            if key in keywords and key in entity.keys():
+                return entity
+
+            if not type and 'type' not in entity.keys():
+                entity['type'] = key
+
+            entity[key] = self.convert(entity['type'], key, val)
+            self.talker.current_line = ''
+
+        return entity
+
+    def all_objects(self, keywords, type):
+        while 1:
+            obj = self.one_object(keywords, type)
+            if not obj:
+                raise StopIteration
+            yield obj
+            if self.talker.done:
+                raise StopIteration
+
+    def convert(self, cmd, key, val):
+        # if there's a converter, convert it, otherwise return it the same
+        return self.converters.get(cmd, {}).get(key, lambda x: x)(val)
+
+class dictobj(dict):
+    def __getattr__(self, attr):
+        return self[attr]
+    def __repr__(self):
+        # <mpdclient2.dictobj at 0x12345678 ..
+        #   {
+        #     key: val,
+        #     key2: val2
+        #   }>
+        return (object.__repr__(self).rstrip('>') + ' ..\n' +
+                '  {\n    ' +
+                ',\n    '.join([ '%s: %s' % (k, v) for k, v in self.items() ]) 
+
+                '\n  }>')
+
+class mpd_connection(object):
+    def __init__(self, host, port):
+        self.talker = socket_talker(host, port)
+        self.send = command_sender(self.talker)
+        self.fetch = response_fetcher(self.talker)
+        self.do = sender_n_fetcher(self.send, self.fetch)
+
+        self._hello()
+
+    def _hello(self):
+        line = self.talker.get_line()
+        if not line.startswith("OK MPD "):
+            raise RuntimeError("this ain't mpd")
+        self.mpd_version = line[len("OK MPD "):].strip()
+        self.talker.current_line = ''
+
+    # conn.foo() is equivalent to conn.do.foo(), but nicer
+    def __getattr__(self, attr):
+        if is_command(attr):
+            return getattr(self.do, attr)
+        raise AttributeError(attr)
+
+def parse_host(host):
+    if '@' in host:
+        return host.split('@', 1)
+    return '', host
+
+def connect(**kw):
+    import os
+
+    port = int(os.environ.get('MPD_PORT', 6600))
+    password, host = parse_host(os.environ.get('MPD_HOST', 'localhost'))
+
+    kw_port = kw.get('port', 0)
+    kw_password = kw.get('password', '')
+    kw_host = kw.get('host', '')
+
+    if kw_port:
+        port = kw_port
+    if kw_password:
+        password = kw_password
+    if kw_host:
+        host = kw_host
+
+    conn = mpd_connection(host, port)
+    if password:
+        conn.password(password)
+    return conn
+
+
+#
+# # Thread safe extenstion added by Graham Billiau <[EMAIL PROTECTED]>
+#
+
+# TODO:
+#   sometimes this hangs, probably a problem with the locking
+
+def MPD_Ping_Thread(conn):
+    """This should be run as a thread
+    It constantly loops, sending keepalive packets to the mpd server
+    it exits cleanly after the connection is closed"""
+    while True:
+        sleep(10)
+        try:
+            conn.ping()
+        except socket.error:
+            return
+
+class Thread_MPD_Connection:
+    """This is a wrapper around the mpdclient2 library to make it thread 
safe"""
+    #conn
+    def __init__ (self, host, port, keepalive=False, pword = None):
+        """create the connection and locks, 
+        if keepalive is True the connection will not time out and must be 
explcitly closed"""
+        self.conn = mpd_connection(host, port)
+        if (pword is not None):
+            self.conn.password(pword)
+        self.lock = thread.allocate_lock()
+        if (keepalive == True):
+            thread.start_new_thread(MPD_Ping_Thread, (self, ))
+
+    def __getattr__(self, attr):
+        """pass the request on to the connection object, while ensuring no 
conflicts"""
+        self.lock.acquire()
+        funct = self.conn.__getattr__(attr)
+        self.lock.release()
+        return funct

-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft
Defy all challenges. Microsoft(R) Visual Studio 2005.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
_______________________________________________
Freevo-cvslog mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/freevo-cvslog

Reply via email to