# moosic_factory.py - Factory functions for creating Moosic server proxies.
#
# Copyright (C) 2001-2003 Daniel Pearson <daniel.pearson@umbc.edu>
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

"""Factory functions for creating Moosic server proxies.

This module contains functions that make it very quick and easy to create a
proxy object that exposes all the methods of a Moosic server.  These proxy
objects are instances of xmlrpclib.ServerProxy, so you should refer to the
documentation for the xmlrpclib module for more detailed information about
ServerProxy objects.  These factory functions are not necessary for creating
proper Moosic server proxies, but they are a very convenient way of doing so.

This module also contains a subclass of xmlrpclib.Transport that adapts the
XML-RPC client implementation for use with Unix sockets.

It is safe to "import *" from this module.
"""

import xmlrpclib, urllib, httplib, socket, os, os.path, popen2, sys

# Define the True and False constants if they don't already exist.
try: True
except NameError: True = 1
try: False
except NameError: False = 0


__all__ = ('UnixStreamTransport', 'LocalMoosicProxy',
           'UnixMoosicProxy', 'InetMoosicProxy', 'startServer')


class UnixStreamTransport(xmlrpclib.Transport):
    '''An adapter for speaking HTTP over a Unix socket instead of a TCP/IP socket.
    '''
    def make_connection(self, host):
        class HTTPConnection(httplib.HTTPConnection):
            def connect(self):
                host = urllib.unquote(self.host)
                try:
                    self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
                    if self.debuglevel > 0:
                        print "connect: (%s)" % host
                    self.sock.connect(host)
                except socket.error, msg:
                    if self.debuglevel > 0:
                        print 'connect fail:', host
                    if self.sock:
                        self.sock.close()
                    self.sock = None
                    raise
        class HTTP(httplib.HTTP):
            _connection_class = HTTPConnection
        return HTTP(host)


def LocalMoosicProxy(filename=None):
    '''Creates a proxy to a Moosic server that is listening to a local (Unix
    address family) socket.
    
    The optional "filename" argument is the location of the socket file that the
    Moosic server is using as its address.
    '''

    if not filename:
        filename = os.path.join(os.getenv('HOME', '/tmp'), '.moosic',
                                'socket-' + socket.gethostname())
    server_address = 'http://%s/' % urllib.quote(filename, safe='')
    return xmlrpclib.ServerProxy(uri=server_address,
                                 transport=UnixStreamTransport(),
                                 verbose=False)


def UnixMoosicProxy(filename=None):
    '''An alias for the LocalMoosicProxy function.
    '''
    return LocalMoosicProxy(filename)


def InetMoosicProxy(host, port):
    '''Creates a proxy to a Moosic server that is listening to a TCP/IP socket.
    
    The first argument, "host", is the hostname or IP address where the server
    is located. The second argument, "port", is the TCP port that the server is
    listening to.
    '''
    return xmlrpclib.ServerProxy(uri='http://%s:%d/' % (host, port),
                                 verbose=False)


def startServer(config_dir):
    '''Attempts to run the "moosicd" program.
    
    The location of the program is first sought in the MOOSICD environment
    variable (which should contain the full pathname of the program), and then
    sought in the directories listed in the PATH environment variable.
    
    The "config_dir" parameter is the path name of the directory where moosicd
    should locate the files it uses at runtime. This is usually set to the
    ".moosic" directory in the user's home directory.
    
    If moosicd can't be started, a string describing why is returned.
    Otherwise, None is returned.
    '''
    # Use the MOOSICD environment variable if it is set.
    moosicd = os.getenv('MOOSICD', '')
    if moosicd and not (os.access(moosicd, os.R_OK | os.X_OK) and os.path.isfile(moosicd)):
        print >>sys.stderr, 'Warning:', moosicd, 'is not an executable file.'
        print >>sys.stderr, 'Ignoring the value of $MOOSICD and searching $PATH instead.'
        moosicd = None
    # Check whether moosicd is in the execution path.
    if not moosicd:
        for dir in os.getenv('PATH', ':/bin:/usr/bin').split(':'):
            f = os.path.join(dir, 'moosicd')
            if os.access(f, os.R_OK | os.X_OK) and os.path.isfile(f):
                moosicd = f
                break
    # Search for a program named "MoosicDaemon" if a program named "moosicd"
    # wasn't found.
    if not moosicd:
        for dir in os.getenv('PATH', ':/bin:/usr/bin').split(':'):
            f = os.path.join(dir, 'MoosicDaemon')
            if os.access(f, os.R_OK | os.X_OK) and os.path.isfile(f):
                moosicd = f
                break
    if not moosicd:
        return 'The "moosicd" program could not be found.'
    # Execute moosicd.
    import commands
    status, output = commands.getstatusoutput("%s -c %s" % (moosicd, config_dir))
    if os.WIFEXITED(status):
        status = os.WEXITSTATUS(status)
    else:
        return "moosicd did something rather abnormal."
    if status == 0:
        return
    else:
        return output + "\nExit status: %d" % status

