#!/usr/bin/env python

import sys, time, os, socket, traceback
import xml.dom.minidom

# This part is copied from common.py

bartender_uri = 'http://www.nordugrid.org/schemas/bartender'
# True and False values used in the XML representation
true = '1'
false = '0'

def parse_url(url):
    import urlparse
    (proto, host_port, path, _, _, _) = urlparse.urlparse(url)
    if ':' in host_port:
        host, port = host_port.split(':')
    else:
        host = host_port
        port = 80
    return proto, host, int(port), path

supported_protocols = ['http', 'byteio', 'gridftp', 'srm']
CHUNKSIZE = 2**20

def upload_to_turl(turl, protocol, fobj, size = None, ssl_config = {}, verbose = False):
    """docstring for upload_to_turl"""
    if protocol not in supported_protocols:
        raise Exception, 'Unsupported protocol'
    if protocol == 'byteio':
        from storage.client import ByteIOClient
        return ByteIOClient(turl, ssl_config = ssl_config).write(fobj)
    elif protocol == 'gridftp':
        try:
            import arc
            from arcom import datapoint_from_url
            use_hed = True
        except:
            use_hed = False
        if use_hed:
            src = datapoint_from_url(fobj.name)
            dst = datapoint_from_url(turl, ssl_config)
            mover = arc.DataMover()
            mover.verbose(verbose)
            status = mover.Transfer(src, dst, arc.FileCache(), arc.URLMap())
            if status != status.Success:
                raise Exception, "Failed to upload file. Please delete the file and try again!"
            return str(status)
        else:
            return 'cannot upload file, import arc failed'
    elif protocol == 'http':
        try:
            import arc
            from arcom import datapoint_from_url
            use_hed = True
        except:
            use_hed = False
        if use_hed:
            src = datapoint_from_url(fobj.name)
            dst = datapoint_from_url(turl, ssl_config)
            mover = arc.DataMover()
            mover.verbose(verbose)
            mover.retry(False)
            status = mover.Transfer(src, dst, arc.FileCache(), arc.URLMap())
            if status != status.Success:
                raise Exception, "Failed to upload file. Please delete the file and try again!"
            return str(status)
        else:
            proto, host, port, path = parse_url(turl)
            import httplib
            if proto == 'https':
                h = httplib.HTTPSConnection(host, port, key_file = ssl_config.get('key_file', None), cert_file = ssl_config.get('cert_file', None))
            else:
                h = httplib.HTTPConnection(host, port)
            if not size:
                size = os.path.getsize(fobj.name)
            if verbose:
                print 'Uploading %s bytes...' % size,
            h.putrequest('PUT', path)
            h.putheader('Content-Length', size)
            h.endheaders()
            chunk = fobj.read(CHUNKSIZE)
            uploaded = 0
            if verbose:
                print '00%',
            t = time.time()
            while chunk:
                sys.stdout.flush()
                h.send(chunk)
                uploaded = uploaded + len(chunk)
                if time.time() - t > 2:
                    t = time.time()
                    if verbose:
                        print '\b\b\b\b%02d%%' % (uploaded*100/size),
                chunk = fobj.read(CHUNKSIZE)
            if verbose:
                print '\b\b\b\bdata sent, waiting...',
            sys.stdout.flush()
            r = h.getresponse()
            if verbose:
                print 'done.'
            #resp = r.read()
            return r.status

def download_from_turl(turl, protocol, fobj, ssl_config = {}, verbose = False):
    """docstring for download_from_turl"""
    if protocol not in supported_protocols:
        raise Exception, 'Unsupported protocol'
    if protocol == 'byteio':
        from storage.client import ByteIOClient
        ByteIOClient(turl, ssl_config = ssl_config).read(file = f)
    elif protocol == 'gridftp':
        try:
            import arc
            from arcom import datapoint_from_url
            use_hed = True
        except:
            use_hed = False
        if use_hed:
            src = datapoint_from_url(turl, ssl_config)
            dst = datapoint_from_url(fobj.name)
            mover = arc.DataMover()
            mover.verbose(verbose)
            mover.retry(False)
            #mover.passive(True)
            status = mover.Transfer(src, dst, arc.FileCache(), arc.URLMap())
            if status != status.Success:
                raise Exception, "Failed to download file. Try again!"
            return str(status)
        else:
            return 'cannot download file, import arc filed'
    elif protocol == 'http':
        try:
            import arc
            from arcom import datapoint_from_url
            use_hed = True
        except:
            use_hed = False
        if use_hed:
            src = datapoint_from_url(turl, ssl_config)
            dst = datapoint_from_url(fobj.name)
            mover = arc.DataMover()
            mover.verbose(verbose)
            mover.retry(False)
            status = mover.Transfer(src, dst, arc.FileCache(), arc.URLMap())
            if status != status.Success:
                raise Exception, "Failed to download file. Try again!"
            return str(status)
        else:
            proto, host, port, path = parse_url(turl)
            import httplib
            if proto == 'https':
                h = httplib.HTTPSConnection(host, port, key_file = ssl_config.get('key_file', None), cert_file = ssl_config.get('cert_file', None))
            else:
                h = httplib.HTTPConnection(host, port)
            h.request('GET', path)
            r = h.getresponse()
            size = int(r.getheader('Content-Length', CHUNKSIZE))
            if verbose:
                print 'Downloading %s bytes...' % size,
            chunk = r.read(CHUNKSIZE)
            downloaded = 0
            if verbose:
                print '00%',
            t = time.time()
            while chunk:
                sys.stdout.flush()
                fobj.write(chunk)
                downloaded = downloaded + len(chunk)
                if time.time() - t > 2:
                    t = time.time()
                    if verbose: 
                        print '\b\b\b\b%02d%%' % (downloaded*100/size),
                chunk = r.read(CHUNKSIZE)
            if verbose:
                print '\b\b\b\bdone.'
            return r.status

def splitLN(LN):
    """  Split a Logical Name to a 3-tuple: GUID, dirname, basename.

    splitLN(LN)

    The part before the first slash is the GUID, even if it is empty.
    The part after the last slash is the basename, could be empty.
    Between them is the dirname, could be empty or could contain several more slashes.

    Examples

        splitLN('/dir') returns ('', '', 'dir')
        splitLN('guid/dir') returns ('guid', '', 'dir')
        splitLN('guid/dir/file') returns ('guid', 'dir', 'file')
        splitLN('guid/dir/dir2/file') returns ('guid', 'dir/dir2', 'file')
        splitLN('/dir/dir2/file') returns ('', 'dir/dir2', 'file')

    Trailing slash:
        splitLN('guid/dir/dir2/') returns ('guid', 'dir/dir2', '')

        splitLN('') returns ('', '', '')
        splitLN('/') returns ('', '', '')
        splitLN('//') returns ('', '', '')
        splitLN('///') returns ('', '/', '')
        splitLN('///') returns ('', '//', '')

        splitLN('0') returns ('0', '', '')
        splitLN('0/') returns ('0', '', '')
        splitLN('something') returns ('something', '', '')
        splitLN('something/') returns ('something', '', '')
    """
    # split it along the slashes
    parts = LN.split('/')
    # get the first part (before the first slash)
    rootguid = parts.pop(0)
    # try to get the last part, if there are more parts
    try:
        basename = parts.pop()
    except:
        basename = ''
    # put the slashes back between the rest items
    dirname = '/'.join(parts)
    return rootguid, dirname, basename


def create_checksum(file, type):
    """ Create checksum of a file.

    create_checksum(file, type)

    file is an object with a 'read' method
    type is a string indicating the type of the checksum, currently only md5 is supported

    Returns the checksum as a string.
    """
    if type == 'md5':
        return _md5sum(file)
    else:
        raise Exception, 'Unsupported checksum type'

try:
    import hashlib as md5
    md5.new_new = md5.new
    md5.new = lambda: md5.new_new('md5')
except:
    import md5

def _md5sum(file):
    """ Create md5 checksum of a file.

    _md5sum(file)

    file is an object with a 'read' method

    Returns the checksum as a string.
    """
    m = md5.new()
    while True:
        # read the file in chunks
        t = file.read(1024)
        if len(t) == 0: break # end of file
        # put the file content into the md5 object
        m.update(t)
    # get the checksum from the md5 object
    return m.hexdigest()


def create_metadata(metadata, prefix = ''):
    """ Create an XMLTree structure from a dictionary with metadata.

    create_metadata(metadata, prefix = '')

    metadata is a dictionary with ('section','property') as keys and 'value' as values
    prefix is a string for the namespace prefix of the XML tag, empty by default.

    Example:

    input:

        {('entry','type') : 'file', ('states', 'size') : '812314'}

    output:

        [('metadata', [('section', 'entry'), ('property', 'type'), ('value', 'file')]),
         ('metadata', [('section', 'states'), ('property', 'size'), ('value', '812314')])]

    this output can be used as an XMLTree object, and can be put into an XMLNode, which will look like this:

            <metadata>
                <section>entry</section>
                <property>type</property>
                <value>file</value>
            </metadata>
            <metadata>
                <section>states</section>
                <property>size</property>
                <value>812314</value>
            </metadata>
    """
    # if it is an empty dictionary, just return an empty list
    if not metadata:
        return []
    # if a prefix is specified
    if prefix:
        # for each item in the metadata list get the section, property and value and put it into XMLTree form
        # the 'metadata', 'section', 'property' and 'value' strings are prefixed with the given prefix and a ':'
        return [
            (prefix + ':metadata', [
                (prefix + ':section', section),
                (prefix + ':property', property),
                (prefix + ':value', value)
            ]) for ((section, property), value) in metadata.items()
        ]
    else:
        # if no prefix is specified do not put a colon before the names
        return [
            ('metadata', [
                ('section', section),
                ('property', property),
                ('value', value)
            ]) for ((section, property), value) in metadata.items()
        ]


def parse_node(node, names, single = False, string = True):
    """ Call node_to_data() for each child of the given node.

    parse_node(node, names, single = False, string = True)

    node is the XMLNode whose children we want to convert
    names is a list of tag names which will be returned in the specified order
    single indicates that we need only one value beside the key, do not put it into a list
    string indicates that we need the string data of the nodes, not the nodes itself.

    Example:

        xml = XMLNode('''
            <statRequestList>
                <statRequestElement>
                    <requestID>0</requestID>
                    <LN>/</LN>
                </statRequestElement>
                <statRequestElement>
                    <requestID>1</requestID>
                    <LN>/testfile</LN>
                </statRequestElement>
            </statRequestList>
        ''')

    parse_node(xml, ['requestID','LN']) returns:

        {'0': ['/'], '1': ['/testfile']}


    parse_node(xml, ['requestID','LN'], single = True) returns:

        {'0': '/', '1': '/testfile'}


    parse_node(xml, ['LN','requestID'], True) returns:

        {'/': '0', '/testfile': '1'}


    parse_node(xml, ['requestID','LN','LN']) returns:

        {'0': ['/', '/'], '1': ['/testfile', '/testfile']}


    """
    return dict([
        node_to_data(n, names, single, string)
            for n in get_child_nodes(node)
    ])

def get_data_node(node):
    try:
        return node.Get('Body').Child().Child()
    except:
        try:
            fault = str(node.Get('Body').Get('Fault'))
        except:
            raise Exception, 'Not valid SOAP message (%s)' % node.GetXML()
        raise Exception, 'Fault in SOAP message (%s)' % fault

def get_child_nodes(node):
    """ Get all the children nodes of an XMLNode

    get_child_nodes(node)

    node is the XMLNode whose children we need
    """
    # the node.Size() method returns the number of children
    return [node.Child(i) for i in range(node.Size())]

def get_child_values_by_name(node, name):
    return [str(child) for child in get_child_nodes(node) if child.Name() == name]


def node_to_data(node, names, single = False, string = True):
    """ Get some children of an XMLNode and return them in a list in the specified order using the first one as a key.

    node_to_data(node, names, single = False, string = True)

    node is an XMLNode which has some children
    names is a list of strings, the names of the children we want to extract, the first name always will be a key
    single is a boolean indicating if we want only a single value thus do not put it in a list
    string is a boolean indicating if we want the string values of the nodes or the nodes itself

    Example:

        node:
            <changeRequest>
                <changeID>0</changeID>
                <ID>123</ID>
                <section>states</section>
                <property>neededReplicas</property>
                <value>3</value>
                <somethingElse>not interesting</somethingElse>
                <changeType>set</changeType>
            </changeRequest>

        names: ['changeID', 'ID', 'changeType', 'section', 'property', 'value']

        here changeID will be the key, and all the other names will be in a list in the specified order

        so it returns ('0', ['123', 'set', 'states', 'neededReplicas', '3'])

            ('somethingElse' is not returned)

    Example:

        node:
            <getRequest>
                <GUID>11</GUID>
                <requestID>99</requestID>
            </getRequest>

        names: ['requestID', 'GUID']
        single: True

        here requestID will be the key, and GUID is the single value which won't be in a list

        so it returns ('99', '11')

            (instead of '99', ['11'])
    """
    if string:
        # if we need the strings
        # for each name get the string data of the child with that name,
        data = [str(node.Get(name)) for name in names]
    else:
        # for each name get the child node itself
        data = [node.Get(name) for name in names]
    if single:
        # return the first item as a key and the second item as a single value
        return data[0], data[1]
    else:
        # return the first item as a key, and all the rest items as a list
        return data[0], data[1:]

def parse_metadata(metadatalist_node):
    """ Return the metadata which was put into an XML representation.

    parse_metadata(metadatalist_node)

    metadatalist_node is an XMLNode which has zero or more children,
        and each child should have three children called 'section', 'property', 'value')

    The method get the string value from each tuple of section-property-value,
    then creates a dictionary where the (section, property) pairs are the keys,
    and the (value) is the value.

    Example:

    input:

        <metadataList>
            <metadata>
                <section>entry</section>
                <property>type</property>
                <value>file</value>
            </metadata>
            <metadata>
                <section>states</section>
                <property>size</property>
                <value>812314</value>
            </metadata>
        </metadataList>

    output:

        {('entry','type') : 'file', ('states', 'size') : '812314'}
    """
    # get the number of children
    metadatalist_number = metadatalist_node.Size()
    # initialize the list
    metadata = []
    # for each child
    for j in range(metadatalist_number):
        # get the child node
        metadata_node = metadatalist_node.Child(j)
        # get the 'section', 'property' and 'value' children's string value
        # and append them to the list as (('section','property'),'value')
        metadata.append((
            (str(metadata_node.Get('section')),str(metadata_node.Get('property'))),
                str(metadata_node.Get('value'))))
    # create a dictionary from the list
    return dict(metadata)



# This part is copied from fakexmlnode.py

class FakeXMLNode(object):
    def __init__(self, from_string = '', from_node = None, doc_node = None):
        if from_node:
            self.node = from_node
            self.doc = doc_node
        else:
            self.doc = xml.dom.minidom.parseString(from_string)
            self.node = self.doc.childNodes[0]

    def __str__(self):
        """docstring for __str__"""
        text = ''.join([child.nodeValue for child in self.node.childNodes if child.nodeType == child.TEXT_NODE])
        return text

    def _get_real_children(self):
        """docstring for _get_real_children"""
        return [child for child in self.node.childNodes if child.nodeType != child.TEXT_NODE]

    def Size(self):
        """docstring for Size"""
        return len(self._get_real_children())

    def Child(self, no = 0):
        """docstring for Child"""
        return FakeXMLNode(from_node = self._get_real_children()[no], doc_node = self.doc)

    def FullName(self):
        """docstring for FullName"""
        return self.node.nodeName

    def Name(self):
        """docstring for Name"""
        return self.node.localName

    def Prefix(self):
        """docstring for Prefix"""
        return self.node.prefix

    def GetXML(self):
        """docstring for GetXML"""
        return self.node.toxml()

    def Get(self, tagname):
        """docstring for Get"""
        return [FakeXMLNode(from_node = child, doc_node = self.doc) for child in self.node.childNodes if child.nodeName == tagname or child.localName == tagname][0]

    def NewChild(self, tagname):
        """docstring for NewChild"""
        return FakeXMLNode(from_node = self.node.appendChild(self.doc.createElement(tagname)), doc_node = self.doc)

    def Set(self, text_data):
        """docstring for Set"""
        self.node.appendChild(self.doc.createTextNode(text_data))

storage_actions = ['read', 'addEntry', 'removeEntry', 'delete', 'modifyPolicy', 'modifyStates', 'modifyMetadata']

# This part is copied form xmltree.py and the 'xmlnode_class = arc.XMLNode' is changed

class XMLTree:
    def __init__(self, from_node = None, from_string = '', from_tree = None, rewrite = {}, forget_namespace = False, xmlnode_class = None):
        """ Constructor of the XMLTree class

        XMLTree(from_node = None, from_string = '', from_tree = None, rewrite = {}, forget_namespace = False)

        'from_tree' could be tree structure or an XMLTree object
        'from_string' could be an XML string
        'from_node' could be an XMLNode
        'rewrite' is a dictionary, if an XML node has a name which is a key in this dictionary,
            then it will be renamed as the value of that key
        'forget_namespace' is a boolean, if it is true, the XMLTree will not contain the namespace prefixes

        'from_tree' has the highest priority, if it is not None,
            then the other two is ignored.
        If 'from_tree' is None but from_string is given, then from_node is ignored.
        If only 'from_node' is given, then it will be the choosen one.
        In this case you may simply use:
            tree = XMLTree(node)
        """
        if from_tree:
            # if a tree structure is given, set the internal variable with it
            # if this is an XMLTree object, get just the data from it
            if isinstance(from_tree,XMLTree):
                self._data = from_tree._data
            else:
                self._data = from_tree
        else:
            if from_node:
                # if no from_tree is given, and we have an XMLNode, just save it
                x = from_node
            else:
                # if no from_tree and from_node is given, try to parse the string
                if not xmlnode_class:
                    xmlnode_class = FakeXMLNode
                x = xmlnode_class(from_string)
            # set the internal tree structure to (<name of the root node>, <rest of the document>)
            # where <rest of the document> is a list of the child nodes of the root node
            self._data = (self._getname(x, rewrite, forget_namespace), self._dump(x, rewrite, forget_namespace))

    def _getname(self, node, rewrite = {}, forget_namespace = False):
        # gets the name of an XMLNode, with namespace prefix if it has one
        if not forget_namespace and node.Prefix():
            name = node.FullName()
        else: # and without namespace prefix if it has no prefix
            name = node.Name()
        return rewrite.get(name,name)

    def _dump(self, node, rewrite = {}, forget_namespace = False):
        # recursive method for converting an XMLNode to XMLTree structure
        size = node.Size() # get the number of children of the node
        if size == 0: # if it has no child, get the string
            return str(node)
        children = [] # collect the children
        for i in range(size):
            children.append(node.Child(i))
        # call itself recursively for each children
        return [(self._getname(n, rewrite, forget_namespace), self._dump(n, rewrite, forget_namespace)) for n in children ]

    def add_to_node(self, node, path = None):
        """ Adding a tree structure to an XMLNode.

        add_to_node(node, path = None)

        'node' is the XMLNode we want to add to
        'path' selects the part of the XMLTree we want to add
        """
        # selects the part we want
        data = self.get(path)
        # call the recursive helping method
        self._add_to_node(data, node)

    def _add_to_node(self, data, node):
        # recursively add the tree structure to the node
        for element in data:
            # we want to avoid empty tags in XML
            if element[0]:
                # for each child in the tree create a child in the XMLNode
                child_node = node.NewChild(element[0])
                # if the node has children:
                if isinstance(element[1],list):
                    self._add_to_node(element[1], child_node)
                else: # if it has no child, create a string from it
                    child_node.Set(str(element[1]))

    def pretty_xml(self, indent = ' ', path = None, prefix = ''):
        data = self.get(path)
        return self._pretty_xml(data, indent, level = 0, prefix = prefix)

    def _pretty_xml(self, data, indent, level, prefix ):
        out = []
        for element in data:
            if element[0]:
                if isinstance(element[1], list):
                    out.append(
                        prefix + indent * level + '<%s>\n' % element[0] +
                            self._pretty_xml(element[1], indent, level+1, prefix) + '\n' +
                        prefix + indent * level +'</%s>' % element[0]
                    )
                else:
                    out.append(prefix + indent * level + '<%s>%s</%s>' % (element[0], element[1], element[0]))
        return '\n'.join(out)


    def __str__(self):
        return str(self._data)

    def _traverse(self, path, data):
        # helping function for recursively traverse the tree
        # 'path' is a list of the node names, e.g. ['root','key1']
        # 'data' is the data of a tree-node,
        # e.g. ('root', [('key1', 'value'), ('key2', 'value')])
        # if the first element of the path and the name of the node is equal
        #   or if the element of the path is empty, it matches all node names
        # if not, then we have no match here, return an empty list
        if path[0] != data[0] and path[0] != '':
            return []
        # if there are no more path-elements, then we are done
        # we've just found what we looking for
        if len(path) == 1:
            return [data]
        # if there are more path-elements, but this is a string node
        # then no luck, we cannot proceed, return an empty list
        if isinstance(data[1],str):
            return []
        # if there are more path-elements, and this node has children
        ret = []
        for d in data[1]:
            # let's recurively ask all child if they have any matches
            # and collect the matches
            ret.extend( self._traverse(path[1:], d) )
        # return the matches
        return ret

    def get(self, path = None):
        """ Returns the parts of the XMLTree which match the path.

        get(path = None)

        if 'path' is not given, it defaults to the root node
        """
        if path: # if path is given
            # if it is not starts with a slash
            if not path.startswith('/'):
                raise Exception, 'invalid path (%s)' % path
            # remove the starting slash
            path = path[1:]
            # split the path to a list of strings
            path = path.split('/')
        else: # if path is not given
            # set it to the root node
            path = [self._data[0]]
        # gets the parts which are selected by this path
        return self._traverse(path, self._data)

    def get_trees(self, path = None):
        """ Returns XMLTree object for each subtree which match the path.

        get_tress(path = None)
        """
        # get the parts match the path and convert them to XMLTree
        return [XMLTree(from_tree = t) for t in self.get(path)]

    def get_value(self, path = None, *args):
        """ Returns the value of the selected part.

        get_value(path = None, [default])

        Returns the value of the node first matched the path.
        This is one level deeper than the value returned by the 'get' method.
        If there is no such node, and a default is given,
        it will return the default.
        """
        try:
            # use the get method then get the value of the first result
            return self.get(path)[0][1]
        except:
            # there was an error
            if args: # if any more argumentum is given
                # the first will be the default
                return args[0]
            raise

    def add_tree(self, tree, path = None):
        """ Add a new subtree to a path.

        add_tree(tree, path = None)
        """
        # if this is a real XMLTree object, get just the data from it
        if isinstance(tree,XMLTree):
            tree = tree._data
        # get the first node selected by the path and append the new subtree to it
        self.get(path)[0][1].append(tree)

    def get_values(self, path = None):
        """ Get all the values selected by a path.

        get_values(path = None)

        Like get_value but gets all values not just the first
        This has no default value.
        """
        try:
            # get just the value of each node
            return [d[1] for d in self.get(path)]
        except:
            return []

    def _dict(self, value, keys):
        # helper method for changing keys
        if keys:
            # if keys is given use only the keys which is in it
            # and translete them to new keys (the values of the 'keys' dictionary)
            return dict([(keys[k],v) for (k,v) in value if k in keys.keys()])
        else: # if keys is empty, use all the data
            return dict(value)

    def get_dict(self, path = None, keys = {}):
        """ Returns a dictionary from the first node the path matches.

        get_dict(path, keys = {})

        'keys' is a dictionary which filters and translate the keys
            e.g. if keys is {'hash:line':'line'}, it will only return
            the 'hash:line' nodes, and will call them 'line'
        """
        return self._dict(self.get_value(path,[]),keys)

    def get_dicts(self, path = None, keys = {}):
        """ Returns a list of dictionaries from all the nodes the path matches.

        get_dicts(path, keys = {})

        'keys' is a dictionary which filters and translate the keys
            e.g. if keys is {'hash:line':'line'}, it will only return
            the 'hash:line' nodes, and will call them 'line'
        """
        return [self._dict(v,keys) for v in self.get_values(path)]

import base64

# This part is only in this file

class FakeNS(object):
    """docstring for FakeNS"""
    def __init__(self, NS):
        self.NS = NS

    def getNamespaces(self):
        """docstring for getNamespaces"""
        return ''.join(['xmlns:%s="%s"' % (prefix, urn) for prefix, urn in self.NS.items()])

# This part is from the client.py file, but is changed

class Client:
    """ Base Client class for sending SOAP messages to services """

    NS_class = FakeNS

    def __init__(self, url, ns, print_xml = False, ssl_config = {}):
        """ The constructor of the Client class.

        Client(url, ns, print_xml = false)

        url is the URL of the service
        ns contains the namespaces we want to use with each message
        print_xml is for debugging, prints all the SOAP messages to the screen
        """
        import urlparse
        (proto, host_port, path, _, _, _) = urlparse.urlparse(url)
        self.ssl_config = ssl_config
        self.https = (proto == 'https')
        if ':' in host_port:
            host, port = host_port.split(':')
        else:
            host = host_port
            port = 80
        self.host = host
        self.port = int(port)
        self.path = path
        self.ns = ns
        self.url = url
        self.print_xml = print_xml
        self.xmlnode_class = FakeXMLNode
        try:
            import arc
            self.call_raw = self.call_raw_hed
            self.cfg = arc.MCCConfig()
            if self.https:
                if ssl_config.has_key('proxy_file'):
                    self.cfg.AddProxy(self.ssl_config['proxy_file'])
                else:
                    try:
                        self.cfg.AddCertificate(self.ssl_config['cert_file'])
                        self.cfg.AddPrivateKey(self.ssl_config['key_file'])
                    except:
                        raise Exception, 'no key file or cert file found!'
                if ssl_config.has_key('ca_file'):
                    self.cfg.AddCAFile(self.ssl_config['ca_file'])
                elif ssl_config.has_key('ca_dir'):
                    self.cfg.AddCADir(self.ssl_config['ca_dir'])
                else:
                    raise Exception, 'no CA file or CA dir found!'
            self.url = arc.URL(url)
        except ImportError:
            self.call_raw = self.call_raw_standalone
            if self.https:
                if ssl_config.has_key('proxy_file'):
                    raise Exception, 'No ARC HED installation found: proxy certificates not supported!'
                if not ssl_config.has_key('cert_file') or not ssl_config.has_key('key_file'):
                    raise Exception, 'no key file or cert file found!'
                if ssl_config.has_key('ca_file') or ssl_config.has_key('ca_dir'):
                    print '- No ARC HED installation found: the validity of the CA will not be checked!'

    def create_soap_envelope(self, ns):
        """docstring for create_soap_envelope"""
        try:
            import arc
            out = arc.PayloadSOAP(arc.NS(ns.NS))
            return out, out
        except:
            namespaces = ns.getNamespaces()
            xml_text = '<soap-env:Envelope %s xmlns:soap-enc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><soap-env:Body/></soap-env:Envelope>' % namespaces
            out = FakeXMLNode(from_string = xml_text)
            return out, out.Child()

    def call(self, tree, return_tree_only = False):
        """ Create a SOAP message from an XMLTree and send it to the service.

        call(tree, return_tree_only = False)

        tree is an XMLTree object containing the content of the request
        return_tree_only indicates that we only need to put the response into an XMLTree
        """
        # create a new PayloadSOAP object with the given namespace
        out, body = self.create_soap_envelope(self.ns)
        # add the content of the XMLTree to the XMLNode of the SOAP object
        tree.add_to_node(body)
        if self.print_xml:
            msg = out.GetXML()
            print 'Request:'
            print XMLTree(out).pretty_xml(indent = '    ', prefix = '')
            print
        # call the service and get back the response, and the HTTP status
        resp = self.call_raw(out)
        if self.print_xml:
            print 'Response:'
            try:
                print XMLTree(from_string = resp).pretty_xml(indent = '    ', prefix = '')
            except:
                print resp
            print
        if return_tree_only:
            # wrap the response into an XMLTree and return only the tree
            return XMLTree(from_string = resp, forget_namespace = True).get_trees('///')[0]
        else:
            return resp

    def call_raw_standalone(self, outpayload):
        """ Send a POST request with the SOAP XML message.

        call_raw(outpayload)

        outpayload is an XMLNode with the SOAP message
        """
        import httplib
        if self.https:
            h = httplib.HTTPSConnection(self.host, self.port, key_file = self.ssl_config.get('key_file', None), cert_file = self.ssl_config.get('cert_file', None))
        else:
            # create an HTTP connection
            h = httplib.HTTPConnection(self.host, self.port)
        # get the XML from outpayload, and send it as POST
        h.request('POST', self.path, outpayload.GetXML())
        # get the response object
        try:
            r = h.getresponse()
        except httplib.BadStatusLine:
            raise socket.error, (-1, 'Bad status line from the server. (Maybe wrong URL: "%s%s:%s%s"?)' %
                (self.https and 'https://' or 'http://', self.host, self.port, self.path))
        # read the response data
        resp = r.read()
        if not resp:
            raise socket.error, (-1, 'Server returned empty answer. (Maybe wrong URL: "%s%s:%s%s"?)' %
                (self.https and 'https://' or 'http://',self.host, self.port, self.path))
        if 'text/xml' not in r.getheader('content-type'):
            raise socket.error, (-1, 'Server did not return SOAP answer. (Maybe wrong URL: "%s%s:%s%s"?)' %
                (self.https and 'https://' or 'http://',self.host, self.port, self.path))
        # return the data and the status
        return resp


    def call_raw_hed(self, outpayload):
        """ Send a POST request with the SOAP XML message.

        call_raw(outpayload)

        outpayload is an XMLNode with the SOAP message
        """
        try:
            import arc
            #x = arc.XMLNode('<dummy/>')
            #self.cfg.MakeConfig(x)
            #print x.GetXML()
            s = arc.ClientSOAP(self.cfg, self.url)
            resp, status = s.process(outpayload)
        except:
            raise socket.error, (-1, "Wrong status from server")
        if not status.isOk():
            raise Exception, status
        if resp.IsFault():
            raise Exception, resp.Fault().Reason()
        return resp.GetXML()




# This part is copied from client.py

class ISISClient(Client):

    def __init__(self, url, print_xml = False, ssl_config = {}):
        ns = self.NS_class({'isis':'http://www.nordugrid.org/schemas/isis/2007/06'})
        Client.__init__(self, url, ns, print_xml, ssl_config = ssl_config)

    def getServiceURLs(self, service_type):
        query = XMLTree(from_tree =
            ('Query', [
                ('QueryString', "/RegEntry/SrcAdv[Type = '%s']" % service_type)
            ])
        )
        QueryResponse = self.call(query, True)
        return QueryResponse.get_values('/QueryResponse/RegEntry/SrcAdv/EPR/Address')

class BartenderClient(Client):
    """ Client for the Bartender service. """

    def __init__(self, url, print_xml = False, ssl_config = {}):
        """ Constructior of the client.

        BartenderClient(url, print_xml = False)

        url is the URL of the Bartender service
        if print_xml is true this will print the SOAP messages
        """
        # sets the namespace
        ns = self.NS_class({'bar':bartender_uri})
        # calls the superclass' constructor
        Client.__init__(self, url, ns, print_xml, ssl_config = ssl_config)

    def stat(self, requests):
        """ Get metadata of a file or collection

        stat(requests)

        requests is a dictionary where requestIDs are the keys, and Logical Names are the values.
        this method returns a dictionary for each requestID which contains all the metadata with (section, dictionary) as the key

        e.g.

        In: {'frodo':'/', 'sam':'/testfile'}
        Out:
            {'frodo': {('entry', 'type'): 'collection',
                       ('entries', 'testdir'): '4cabc8cb-599d-488c-a253-165f71d4e180',
                       ('entries', 'testfile'): 'cf05727b-73f3-4318-8454-16eaf10f302c',
                       ('states', 'closed'): 'no'},
             'sam': {('entry', 'type'): 'file',
                     ('locations', 'http://localhost:60000/Shepherd 00d6388c-42df-441c-8d9f-bd78c2c51667'): 'alive',
                     ('locations', 'http://localhost:60000/Shepherd 51c9b49a-1472-4389-90d2-0f18b960fe29'): 'alive',
                     ('states', 'checksum'): '0927c28a393e8834aa7b838ad8a69400',
                     ('states', 'checksumType'): 'md5',
                     ('states', 'neededReplicas'): '5',
                     ('states', 'size'): '11'}}
        """
        tree = XMLTree(from_tree =
            ('bar:stat', [
                ('bar:statRequestList', [
                    ('bar:statRequestElement', [
                        ('bar:requestID', requestID),
                        ('bar:LN', LN)
                    ]) for requestID, LN in requests.items()
                ])
            ])
        )
        msg = self.call(tree)
        xml = self.xmlnode_class(msg)
        elements = parse_node(get_data_node(xml),
            ['requestID', 'metadataList'], single = True, string = False)
        return dict([(str(requestID), parse_metadata(metadataList))
            for requestID, metadataList in elements.items()])

    def getFile(self, requests):
        """ Initiate download of a file.

        getFile(requests)

        requests is a dicitonary with requestID as key and (LN, protocols) as value,
            where LN is the Logical Name
            protocols is a list of strings: the supported transfer protocols by the client
        returns a dictionary with requestID as key and [success, TURL, protocol] as value, where
            success is the status of the request
            TURL is the Transfer URL
            protocol is the name of the choosen protocol

        Example:
        In: {'1':['/', ['byteio']], 'a':['/testfile', ['ftp', 'byteio']]}
        Out:
            {'1': ['is not a file', '', ''],
             'a': ['done',
                   'http://localhost:60000/byteio/29563f36-e9cb-47eb-8186-0d720adcbfca',
                   'byteio']}
        """
        tree = XMLTree(from_tree =
            ('bar:getFile', [
                ('bar:getFileRequestList', [
                    ('bar:getFileRequestElement', [
                        ('bar:requestID', rID),
                        ('bar:LN', LN)
                    ] + [
                        ('bar:protocol', protocol) for protocol in protocols
                    ]) for rID, (LN, protocols) in requests.items()
                ])
            ])
        )
        msg = self.call(tree)
        xml = self.xmlnode_class(msg)
        return parse_node(get_data_node(xml), ['requestID', 'success', 'TURL', 'protocol'])

    def putFile(self, requests):
        """ Initiate uploading a file.

        putFile(requests)

        requests is a dictionary with requestID as key, and (LN, metadata, protocols) as value, where
            LN is the Logical Name
            metadata is a dictionary with (section,property) as key, it contains the metadata of the new file
            protocols is a list of protocols supported by the client
        returns a dictionary with requestID as key, and (success, TURL, protocol) as value, where
            success is the state of the request
            TURL is the transfer URL
            protocol is the name of the choosen protocol

        Example:
        In: {'qwe': ['/newfile',
                    {('states', 'size') : 1055, ('states', 'checksum') : 'none', ('states', 'checksumType') : 'none'},
                    ['byteio']]}
        Out:
            {'qwe': ['done',
                     'http://localhost:60000/byteio/d42f0993-79a8-4bba-bd86-84324367c65f',
                     'byteio']}
        """
        tree = XMLTree(from_tree =
            ('bar:putFile', [
                ('bar:putFileRequestList', [
                    ('bar:putFileRequestElement', [
                        ('bar:requestID', rID),
                        ('bar:LN', LN),
                        ('bar:metadataList', create_metadata(metadata, 'bar')),
                    ] + [
                        ('bar:protocol', protocol) for protocol in protocols
                    ]) for rID, (LN, metadata, protocols) in requests.items()
                ])
            ])
        )
        msg = self.call(tree)
        xml = self.xmlnode_class(msg)
        return parse_node(get_data_node(xml), ['requestID', 'success', 'TURL', 'protocol'])

    def delFile(self, requests):
        """ Initiate deleting a file.

        delFile(requests)

        requests is a dictionary with requestID as key, and (LN) as value, where
        LN is the Logical Name of the file to be deleted

        returns a dictionary with requestID as key, and (success) as value,
        where success is the state of the request
        (one of 'deleted', 'noSuchLN', 'denied)

        Example:
        In: {'fish': ['/cod']}
        Out: {'fish': ['deleted']}
        """
        tree = XMLTree(from_tree =
            ('bar:delFile', [
                ('bar:delFileRequestList', [
                    ('bar:delFileRequestElement', [
                        ('bar:requestID', requestID),
                        ('bar:LN', LN)
                    ]) for requestID, LN in requests.items()
                ])
            ])
        )
        msg = self.call(tree)
        xml = self.xmlnode_class(msg)
        return parse_node(get_data_node(xml), ['requestID', 'success'], single = True)


    def addReplica(self, requests, protocols):
        """ Add a new replica to an existing file.

        addReplica(requests, protocols)

        requests is a dictionary with requestID as key and GUID as value.
        protocols is a list of protocols supported by the client

        returns a dictionary with requestID as key and (success, TURL, protocol) as value, where
            success is the status of the request,
            TURL is the transfer URL
            protocol is the choosen protocol

        Example:

        In: requests = {'001':'c9c82371-4773-41e4-aef3-caf7c7eaf6f8'}, protocols = ['http','byteio']
        Out:
            {'001': ['done',
                     'http://localhost:60000/byteio/c94a77a1-347c-430c-ae1b-02d83786fb2d',
                    'byteio']}
        """
        tree = XMLTree(from_tree =
            ('bar:addReplica', [
                ('bar:addReplicaRequestList', [
                    ('bar:putReplicaRequestElement', [
                        ('bar:requestID', rID),
                        ('bar:GUID', GUID),
                    ]) for rID, GUID in requests.items()
                ])
            ] + [
                ('bar:protocol', protocol) for protocol in protocols
            ])
        )
        msg = self.call(tree)
        xml = self.xmlnode_class(msg)
        return parse_node(get_data_node(xml), ['requestID', 'success', 'TURL', 'protocol'])

    def unlink(self, requests):
        """docstring for unlink"""
        tree = XMLTree(from_tree =
            ('bar:unlink', [
                ('bar:unlinkRequestList', [
                    ('bar:unlinkRequestElement', [
                        ('bar:requestID', rID),
                        ('bar:LN', LN),
                    ]) for rID, LN in requests.items()
                ])
            ])
        )
        msg = self.call(tree)
        xml = self.xmlnode_class(msg)
        return parse_node(get_data_node(xml), ['requestID', 'success'], single = True)

    def unmakeCollection(self, requests):
        """docstring for unmakeCollection"""
        tree = XMLTree(from_tree =
            ('bar:unmakeCollection', [
                ('bar:unmakeCollectionRequestList', [
                    ('bar:unmakeCollectionRequestElement', [
                        ('bar:requestID', rID),
                        ('bar:LN', LN),
                    ]) for rID, LN in requests.items()
                ])
            ])
        )
        msg = self.call(tree)
        xml = self.xmlnode_class(msg)
        return parse_node(get_data_node(xml), ['requestID', 'success'], single = True)

    def makeCollection(self, requests):
        """ Create a new collection.

        makeCollection(requests)

        requests is a dictionary with requestID as key and (LN, metadata) as value, where
            LN is the Logical Name
            metadata is the metadata of the new collection, a dictionary with (section, property) as key
        returns a dictionary with requestID as key and the state of the request as value

        In: {'b5':['/coloredcollection', {('metadata','color') : 'light blue'}]}
        Out: {'b5': 'done'}
        """
        tree = XMLTree(from_tree =
            ('bar:makeCollection', [
                ('bar:makeCollectionRequestList', [
                    ('bar:makeCollectionRequestElement', [
                        ('bar:requestID', rID),
                        ('bar:LN', LN),
                        ('bar:metadataList', create_metadata(metadata, 'bar'))
                    ]) for rID, (LN, metadata) in requests.items()
                ])
            ])
        )
        msg = self.call(tree)
        xml = self.xmlnode_class(msg)
        return parse_node(get_data_node(xml), ['requestID', 'success'], single = True)

        ### Created by Salman Toor ###
    def makeMountpoint(self, requests):
        """ Create a new Mountpoint.

        makeMountpoint(requests)

        requests is a dictionary with requestID as key and (LN, metadata) as value, where
            LN is the Logical Name
            metadata is the metadata of the new collection, a dictionary with (section, property) as key
        returns a dictionary with requestID as key and the state of the request as value

        In: {'b5':['/coloredcollection', {('metadata','color') : 'light blue'}]}
        Out: {'b5': 'done'}
        """
        tree = XMLTree(from_tree =
            ('bar:makeMountpoint', [
                ('bar:makeMountpointRequestList', [
                    ('bar:makeMountpointRequestElement', [
                        ('bar:requestID', rID),
                        ('bar:LN', LN),
                        ('bar:URL', URL),
                        ('bar:metadataList', create_metadata(metadata, 'bar'))
                    ]) for rID, (LN,metadata,URL) in requests.items()
                ])
            ])
        )

        msg = self.call(tree)
        xml = self.xmlnode_class(msg)
        return parse_node(get_data_node(xml), ['requestID', 'success'], single = True)

    def unmakeMountpoint(self, requests):
        """docstring for unmakeMountpoint"""
        tree = XMLTree(from_tree =
            ('bar:unmakeMountpoint', [
                ('bar:unmakeMountpointRequestList', [
                    ('bar:unmakeMountpointRequestElement', [
                        ('bar:requestID', rID),
                        ('bar:LN', LN),
                    ]) for rID, LN in requests.items()
                ])
            ])
        )
        msg = self.call(tree)
        xml = self.xmlnode_class(msg)
        return parse_node(get_data_node(xml), ['requestID', 'success'], single = True)
        ###    ###

    def list(self, requests, neededMetadata = []):
        """ List the contents of a collection.

        list(requests, neededMetadata = [])

        requests is a dictionary with requestID as key and Logical Name as value
        neededMetadata is a list of (section, property) pairs
            if neededMetadata is empty, list will return all metadata for each collection-entry
            otherwise just those values will be returnd which has a listed (section, property)
            if a property is empty means that all properties will be listed from that section
        returns a dictionary with requestID as key and (entries, status) as value, where
            entries is a dictionary with the entry name as key and (GUID, metadata) as value
            status is the status of the request

        Example:
        In: requests = {'jim': '/', 'kirk' : '/testfile'}, neededMetadata = [('states','size'),('entry','type')]
        Out:
            {'jim': ({'coloredcollection': ('cab8d235-4afa-4e33-a85f-fde47b0240d1', {('entry', 'type'): 'collection'}),
                      'newdir': ('3b200a34-0d63-4d15-9b01-3693685928bc', {('entry', 'type'): 'collection'}),
                      'newfile': ('c9c82371-4773-41e4-aef3-caf7c7eaf6f8', {('entry', 'type'): 'file', ('states', 'size'): '1055'}),
                      'testdir': ('4cabc8cb-599d-488c-a253-165f71d4e180', {('entry', 'type'): 'collection'}),
                      'testfile': ('cf05727b-73f3-4318-8454-16eaf10f302c', {('entry', 'type'): 'file', ('states', 'size'): '11'})},
                     'found'),
             'kirk': ({}, 'is a file')}
        """
        tree = XMLTree(from_tree =
            ('bar:list', [
                ('bar:listRequestList', [
                    ('bar:listRequestElement', [
                        ('bar:requestID', requestID),
                        ('bar:LN', LN)
                    ]) for requestID, LN in requests.items()
                ]),
                ('bar:neededMetadataList', [
                    ('bar:neededMetadataElement', [
                        ('bar:section', section),
                        ('bar:property', property)
                    ]) for section, property in neededMetadata
                ])
            ])
        )

        msg = self.call(tree)
        xml = self.xmlnode_class(msg)
        elements = parse_node(get_data_node(xml),
            ['requestID', 'entries', 'status'], string = False)
        return dict([
            (   str(requestID),
                (dict([(str(name), (str(GUID), parse_metadata(metadataList))) for name, (GUID, metadataList) in
                    parse_node(entries, ['name', 'GUID', 'metadataList'], string = False).items()]),
                str(status))
            ) for requestID, (entries, status) in elements.items()
        ])

    def move(self, requests):
        """ Move a file or collection within the global namespace.

        move(requests)

        requests is a dictionary with requestID as key, and (sourceLN, targetLN, preserveOriginal) as value, where
            sourceLN is the source Logical Name
            targetLN is the target Logical Name
            preserverOriginal is True if we want to create a hardlink
        returns a dictionary with requestID as key and status as value
        Example:
        In: {'shoo':['/testfile','/newname',False]}
        Out: {'shoo': ['moved']}
        """
        tree = XMLTree(from_tree =
            ('bar:move', [
                ('bar:moveRequestList', [
                    ('bar:moveRequestElement', [
                        ('bar:requestID', requestID),
                        ('bar:sourceLN', sourceLN),
                        ('bar:targetLN', targetLN),
                        ('bar:preserveOriginal', preserveOriginal and true or false)
                    ]) for requestID, (sourceLN, targetLN, preserveOriginal) in requests.items()
                ])
            ])
        )
        msg = self.call(tree)
        xml = self.xmlnode_class(msg)
        return parse_node(get_data_node(xml), ['requestID', 'status'], single = False)

    def modify(self, requests):
        tree = XMLTree(from_tree =
            ('bar:modify', [
                ('bar:modifyRequestList', [
                    ('bar:modifyRequestElement', [
                        ('bar:changeID', changeID),
                        ('bar:LN', LN),
                        ('bar:changeType', changeType),
                        ('bar:section', section),
                        ('bar:property', property),
                        ('bar:value', value)
                    ]) for changeID, (LN, changeType, section, property, value) in requests.items()
                ])
            ])
        )
        response = self.call(tree)
        node = self.xmlnode_class(response)
        return parse_node(get_data_node(node), ['changeID', 'success'], True)

    def credentialsDelegation(self,request):
        #print "Inside delegateCredentials"
        import arc
        url = arc.URL("https://localhost:60000/Bartender")
        #print "Protocol: "+str(url.Protocol())
        self.X509DelegationClient = arc.ClientX509Delegation(self.cfg,self.url)
        delegID = ""
        statusAndID = self.X509DelegationClient.createDelegation(arc.DELEG_ARC,delegID)
        #print statusAndID
        if statusAndID[0]:
            return statusAndID[1]
        else:
            return 0

    def removeCredentials(self, request):
        #print "will removed the delegated Credentials -- "
        tree = XMLTree(from_tree = ('bar:removeCredentials', 'removeCredentials'))
        #print tree
        msg = self.call(tree)
        xml = self.xmlnode_class(msg)
        return parse_node(get_data_node(xml), ['message', 'status'], single = True)

# This part is copied from bartender_client.py but then changed

args = sys.argv[1:]

verbose = False
print_xml = False
configfilename = ''
bartender_url_from_argument = ''
allowed_to_run_without_arc = False

while (len(args) > 0) and (args[0].startswith('-')):
    flag = args.pop(0)[1:]
    if flag in ['x']:
        print_xml = True
    if flag in ['v']:
       verbose = True
    if flag in ['z']:
        configfilename = args.pop(0)
        if not os.path.exists(configfilename):
            if verbose:
                print '- Config file does not exist:', configfilename, '- using default.'
            configfilename = ''
    if flag in ['b']:
        bartender_url_from_argument = args.pop(0)
    if flag in ['w']:
        allowed_to_run_without_arc = True

if verbose:
    try:
        import arc
        root_logger = arc.Logger_getRootLogger()
        root_logger.addDestination(arc.LogStream(sys.stdout))
        root_logger.setThreshold(arc.ERROR)
    except:
        pass

if verbose:
    if configfilename:
        print '- Config file specified:', configfilename
    else:
        print '- Using default user config.'


def call_it(method_name, *args):
    if verbose:
        print '- Calling the Bartender\'s %s method...' % method_name
    start = time.time()
    exceptions = []
    for bartender_url in bartender_urls:
        if verbose:
            print '- Trying', bartender_url + '...'
        try:
            bartender = BartenderClient(bartender_url, print_xml, ssl_config = ssl_config)
            response = getattr(bartender, method_name)(*args)
            if verbose:
                print '- done in %0.2f seconds.' % (time.time()-start)
            return response
        except Exception, e:
            exceptions.append(e)
    for e in exceptions:
        try:
            error = str(e.args[1])
        except:
            error = str(e)
        print 'ERROR: Cannot get an answer from the Bartender, reason:'
        if verbose:
            print '|', error.replace('\n', '\n| ')
        else:
            spl = error.split('\n')
            first_line = spl.pop(0)
            print '   ', first_line, spl and '[...]' or ''
    if "STATUS_UNDEFINED" in error:
        if os.environ.get("PYTHONPATH", None):
            print "Please check if PYTHONPATH is set properly."
        else:
            print "PYTHONPATH is not set. Please set it!"

    if not verbose:
        print "(use the '-v' flag for more details, e.g. 'chelonia -v list /')"
    sys.exit(-1)

if len(args) == 0 or args[0][0:3] not in ['sta', 'mak', 'mkd', 'unm', 'rmd', 'lis', 'ls', 'mov', 'mv', 'put', 'get', 'del', 'rm', 'mod', 'pol', 'unl', 'cre', 'rem']:
    print 'Usage:'
    print '  chelonia [-v] [-x] [-z <path>] [-b <URL>] [-w] <method> [<arguments>]'
    print ''
    print 'Supported methods:'
    print '  stat - get detailed information about an entry'
    print '  makeCollection | make | mkdir - create a collection'
    print '  unmakeCollection | unmake | rmdir - remove an empty collection'
    print '  list | ls - list the content of a collection'
    print '  move | mv - move entries within the namespace'
    print '  putFile | put - upload a file'
    print '  getFile | get - download a file'
    print '  delFile | del | rm - remove a file'
    print '  modify | mod - modify metadata'
    print '  policy | pol - modify access policy rules'
    print '  unlink - remove a link to an entry from a collection without removing the entry itself'
    print '  credentialsDelegation | cre - delegate credentials for using gateway'
    print '  removeCredentials | rem - remove previously delegated credentials'
    print '  makeMountPoint | makemount - create a mount point'
    print '  unmakeMountPoint | unmount - remove a mount point'
    print ''
    print 'Without arguments, each method prints its own help.'
    print ''
    print 'Options:'
    print '  -v    verbose mode'
    print '  -x    print the SOAP XML messages'
    print '  -z    path to the user config'
    print '  -b    URL of the Bartender to connect'
    print '  -w    allow to run without the ARC python client libraries (with limited functionality)'
else:
    if not allowed_to_run_without_arc:
        try:
            import arc
        except:
            print 'ERROR: ARC python client library not found (maybe PYTHONPATH is not set properly?)'
            print '       If you want to run without the ARC client libraries, use the \'-w\' flag'
            sys.exit(-1)
    
    problem_with_userconfig = None
    try:
        import arc
        user_config = arc.UserConfig(configfilename)
        key_file = user_config.KeyPath()
        cert_file = user_config.CertificatePath()
        proxy_file = user_config.ProxyPath()
        ca_file = user_config.CACertificatePath()
        ca_dir = user_config.CACertificatesDirectory()
        bartender_urls = [url.fullstr() for url in user_config.Bartender()]
        index_services = user_config.GetSelectedServices(arc.INDEX)
        if index_services.has_key('ARC1'):
            isis_urls = [url.fullstr() for url in index_services['ARC1']]
        else:
            isis_urls = []
    except:
        problem_with_userconfig = traceback.format_exc()
        key_file = None
        cert_file = None
        proxy_file = None
        ca_file = None
        ca_dir = None
        bartender_urls = []
        isis_urls = []

    ssl_config = {}
    key_file = os.environ.get('ARC_KEY_FILE', key_file)
    cert_file = os.environ.get('ARC_CERT_FILE', cert_file)
    proxy_file = os.environ.get('ARC_PROXY_FILE', proxy_file)
    ca_file = os.environ.get('ARC_CA_FILE', ca_file)
    ca_dir = os.environ.get('ARC_CA_DIR', ca_dir)
    if proxy_file:
        ssl_config['proxy_file'] = proxy_file
        if verbose:
            print '- The proxy certificate file:', ssl_config['proxy_file']
    else:
        if key_file and cert_file:
            ssl_config['key_file'] = key_file
            ssl_config['cert_file'] = cert_file
            if verbose:
                print '- The key file:', ssl_config['key_file']
                print '- The cert file:', ssl_config['cert_file']
    if ca_file:
        ssl_config['ca_file'] = ca_file
        if verbose:
            print '- The CA file:', ssl_config['ca_file']
    elif ca_dir:
        ssl_config['ca_dir'] = ca_dir
        if verbose:
            print '- The CA dir:', ssl_config['ca_dir']

    no_bartender_message = "No Bartender URL found."

    bartender_env_url = os.environ.get('ARC_BARTENDER_URL', '')
    if bartender_url_from_argument:
        bartender_urls = [bartender_url_from_argument]
        if verbose:
            print '- Bartender URL specified as an argument, will use only this:', bartender_urls[0]
    elif bartender_env_url:
        bartender_urls = [bartender_env_url]
        if verbose:
            print '- Bartender URL found in the ARC_BARTENDER_URL environment variable, will use only this:', bartender_urls[0]
    if not bartender_urls:
        if verbose:
            print '- No Bartender URL found in the config or in the environment, try to get one from ISIS:'
        isis_env_url = os.environ.get('ARC_ISIS_URL', '')
        if isis_env_url:
            isis_urls = [isis_env_url]
            if verbose:
                print '  - ISIS URL found in the ARC_ISIS_URL environment variable:', isis_urls[0]
        if not isis_urls:
            if verbose:
                print '  - No ISIS URL found in the config or in the environment, giving up.'
            no_bartender_message = 'No Bartender URL and no ISIS URL found - cannot figure out where to connect.'
        else:
            bartender_found = False
            isis_connected = False
            while not bartender_found and len(isis_urls) > 0:
                isis_url = isis_urls.pop()
                isis = ISISClient(isis_url, print_xml, ssl_config = ssl_config)
                try:
                    bartender_urls = isis.getServiceURLs('org.nordugrid.storage.bartender')
                    isis_connected = True
                    if not bartender_urls:
                        if verbose:
                            print '  -', isis_url, '- This ISIS did not return any Bartender URL.'
                    else:
                        bartender_found = True
                        if verbose:
                            print '  -', isis_url, '- Got Bartender URL from ISIS:', ', '.join(bartender_urls)
                except:
                    if verbose:
                        print '  -', isis_url, '- Failed to connect to ISIS.'
            if not bartender_found:
                if isis_connected:
                    no_bartender_message = 'Cannot get Bartender URL from the ISIS. Try again later.'
                else:
                    no_bartender_message = 'Cannot connect to any ISIS to get Bartender URL.'
    if bartender_urls:
        if verbose:
            print '- The URL of the Bartender(s):', ', '.join(bartender_urls)        
    else:
        if problem_with_userconfig and verbose:
            print '- There was a problem reading the user\'s config. Reason:', str(problem_with_userconfig)
        # bartender_urls = ['http://localhost:60000/Bartender']
        # print '- The URL of the Bartender is not given, using', bartender_urls[0]
        print 'ERROR:', no_bartender_message
        if not verbose:
            print "(use the '-v' flag for more details, e.g. 'chelonia -v list /')"
        sys.exit(-1)
    try:
        needed_replicas = os.environ['ARC_NEEDED_REPLICAS']
    except:
        needed_replicas = 3

    command_long = args.pop(0).lower()
    command = command_long[0:3]
    if command in ['sta']:
        if len(args) < 1:
            print 'Usage: stat <LN> [<LN> ...]'
        else:
            request = dict([(i, args[i]) for i in range(len(args))])
            #print 'stat', request
            stat = call_it('stat', request)
            #print stat
            for i,s in stat.items():
                print "'%s':" % args[int(i)],
                if s:
                    print 'found'
                    c = {}
                    for k,v in s.items():
                        sect, prop = k
                        c[sect] = c.get(sect,[])
                        c[sect].append((prop, v))
                    for k, vs in c.items():
                        print ' ', k
                        for p, v in vs:
                            print ' ', '  %s: %s' % (p, v)
                else:
                    print 'not found'
    # For Credential Delegation.
    elif command in ['cre']:
        #print command
        request = {}
        response = call_it('credentialsDelegation', request)
        if response:
            print 'Successful delegation. Proxy ID:', response
        else:
            print 'ERROR: failed to delegate credentials.'
    elif command in ['rem']:
        #print command
        request = {}
        response = call_it('removeCredentials', request)
        print response['message']
    elif command in ['del', 'rm']:
        if len(args) < 1:
            print 'Usage: delFile <LN> [<LN> ...]'
        else:
            request = dict([(str(i), args[i]) for i in range(len(args))])
            #print 'delFile', request
            response = call_it('delFile', request)
            #print response
            for i in request.keys():
                print '%s: %s' % (request[i], response[i])
    elif command in ['get']:
        if len(args) < 1:
            print 'Usage: getFile <source LN> [<target filename>]'
        else:
            LN = args[0]
            try:
                filename = args[1]
                if os.path.isdir(filename):
                    _, _, LN_name = splitLN(LN)
                    filename = os.path.join(filename, LN_name)
            except:
                filename = '.'
            if filename == '.':
                _, _, filename = splitLN(LN)
            if filename == '':
                filename, _, _ = splitLN(LN)
            if os.path.exists(filename):
                print "ERROR: local path '%s'" % filename, 'already exists!'
            else:
                request = {'0' : (LN, supported_protocols)}
                #print 'getFile', request
                response = call_it('getFile', request)
                #print response
                success, turl, protocol = response['0']
                if success == 'done':
                    f = file(filename, 'wb')
                    if verbose:
                        print '- Got transfer URL:', turl
                        print "- Downloading from '%s' to '%s' with %s..." % (turl, filename, protocol)
                    start = time.time()
                    try:
                        download_from_turl(turl, protocol, f, ssl_config = ssl_config, verbose = verbose)
                        f.close()
                        size = os.path.getsize(filename)
                        if verbose:
                            print '- done in %.4f seconds.' % (time.time()-start)
                        print "'%s' (%s bytes) downloaded as '%s'." % (LN, size, filename)
                    except Exception, e:
                        print "ERROR:", str(e)
                        sys.exit(-1)
                else:
                    print '%s: %s' % (LN, success)
    elif command in ['put']:
        if len(args) < 2:
            print 'Usage: putFile <source filename> <target LN> [<number of replicas needed>]'
        else:
            filename = args[0]
            try:
                size = os.path.getsize(filename)
            except:
                print 'ERROR: local file', "'%s'" % filename, 'does not exist!'
                sys.exit(-1)
            if verbose:
                print '- The size of the file is', size, 'bytes'
            f = file(filename,'rb')
            if verbose:
                print '- Calculating md5 checksum...'
            start = time.time()
            checksum = create_checksum(f, 'md5')
            if verbose:
                print '- done in %.4f seconds, md5 checksum is %s' % ((time.time()-start), checksum)
            LN = args[1]
            if LN.endswith('/'):
                LN = LN + filename.split('/')[-1]
            needed_replicas_for_this = needed_replicas
            if len(args) > 2:
                try:
                    needed_replicas_for_this = int(args[2])
                except:
                    needed_replicas_for_this = needed_replicas
            metadata = {('states', 'size') : size, ('states', 'checksum') : checksum,
                    ('states', 'checksumType') : 'md5', ('states', 'neededReplicas') : needed_replicas_for_this}
            request = {'0': (LN, metadata, supported_protocols)}
            #print 'putFile', request
            response = call_it('putFile', request)
            #print response
            success, turl, protocol = response['0']
            if success == 'done':
                if verbose:
                    print '- Got transfer URL:', turl
                f = file(filename,'rb')
                if verbose:
                    print "- Uploading from '%s' to '%s' with %s..." % (filename, turl, protocol)
                start = time.time()
                try:
                    upload_to_turl(turl, protocol, f, ssl_config = ssl_config, verbose = verbose)
                    if verbose:
                        print '- done in %.4f seconds.' % (time.time()-start)
                    print "'%s' (%s bytes) uploaded as '%s'." % (filename, size, LN)
                except Exception, e:
                    print "ERROR:", str(e)
                    sys.exit(-1)
            else:
                print '%s: %s' % (LN, success)
    elif command in ['unl']:
        if len(args) < 1:
            print 'Usage: unlink <LN>'
        else:
            request = {'0': (args[0])}
            response = call_it('unlink', request)
            print "Unlinking '%s': %s" % (args[0], response['0'])
    elif command in ['unm', 'rmd']:
        if 'mount' in command_long:
            if len(args) < 1:
                print 'Usage: unmakeMountpoint <LN>'
            else:
                request = {'0': (args[0])}
                response = call_it('unmakeMountpoint', request)
                print "Removing mountpoint '%s': %s" % (args[0], response['0'])
        else:
            if len(args) < 1:
                print 'Usage: unmakeCollection <LN>'
            else:
                request = {'0': (args[0])}
                #print 'unmakeCollection', request
                response = call_it('unmakeCollection', request)
                #print response
                print "Removing collection '%s': %s" % (args[0], response['0'])
    elif command in ['mak','mkd']:
        if 'mount' in command_long:
            if len(args) < 2:
                print 'Usage: makeMountpoint <LN> <URL>'
            else:
                request = {'0': (args[0], {('states', 'closed') : 'no'}, args[1])}
                response = call_it('makeMountpoint', request)
                print "Creating mountpoint '%s': %s" % (args[0], response['0'])
        else:
            if len(args) < 1:
                print 'Usage: makeCollection <LN>'
            else:
                request = {'0': (args[0], {('states', 'closed') : 'no'})}
                #print 'Make collection:',request
                response = call_it('makeCollection', request)
                #print response
                print "Creating collection '%s': %s" % (args[0], response['0'])
    elif command in ['lis', 'ls']:
        if len(args) < 1:
            print 'Usage: list <LN> [<LN> ...]'
        else:
            request = dict([(str(i), args[i]) for i in range(len(args))])
            #print 'list', request
            response = call_it('list', request, [('entry','')])
            #print response

            for rID, (entries, status) in response.items():
                if status == 'found':
                    print "'%s':" % request[rID], 'collection'
                    if entries:
                        longest = 0
                        for name in entries.keys():
                            if len(name) > longest:
                                longest = len(name)
                        for name, (GUID, metadata) in entries.items():
                            print '  %s%s<%s>' % (name, ' ' + ' '*(longest-len(name)), metadata.get(('entry', 'type'),'unknown'))
                    else:
                        print '    empty.'
                else:
                    print "'%s': %s" % (request[rID], status)
    elif command in ['mov', 'mv']:
        if len(args) < 2:
            print 'Usage: move <sourceLN> <targetLN>'
        else:
            sourceLN = args.pop(0)
            targetLN = args.pop(0)
            preserveOriginal = False
            if len(args) > 0:
                if args[0] == 'preserve':
                    preserveOriginal = True
            request = {'0' : (sourceLN, targetLN, preserveOriginal)}
            #print 'move', request
            response = call_it('move', request)
            #print response
            print "Moving '%s' to '%s': %s" % (sourceLN, targetLN, response['0'][0])
    elif command == 'mod':
        if len(args) < 4 or (args[1] not in ['set', 'unset', 'add']) or (args[1] in ['set', 'add'] and len(args) == 4):
            print 'Usage: modify <LN> <changeType> <section> <property> <value>'
            print "  <changeType> could be 'set', 'unset', 'add'"
            print "    'set' : sets the property to value within the given section"
            print "    'unset' : removes the property from the given section"
            print "              (the 'value' does not matter)"
            print "    'add' : sets the property to value within the given section"
            print "            only if it does not exists yet"
            print ""
            print "  To change the number of needed replicas for a file:"
            print "    modify <LN> set states neededReplicas <number of needed replicas>"
            print ""
            print "  To close a collection:"
            print "    modify <LN> set states closed yes"
            print ""
            print "  To change metadata key-value pairs:"
            print "    modify <LN> set|unset|add metadata <key> <value>"
        else:
            if len(args) == 4:
                args.append('')
            LN, changeType, section, property, value = args
            if section == 'states':
                if property not in ['neededReplicas', 'closed']:
                    print "ERROR: in the 'states' section only the 'neededReplicas' and 'closed' can be changed."
                    sys.exit(-1)
                else:
                    if property == 'neededReplicas':
                        try:
                            v = int(value)
                            if v < 1:
                                print "ERROR: the number of needed replicas should be at least 1"
                                sys.exit(-1)
                        except Exception:
                            print "ERROR: the number of needed replicas should be an integer"
                            sys.exit(-1)
                    if property == 'closed':
                        if value != 'yes':
                            print "ERROR: you can set the 'closed' state only to 'yes'"
                            sys.exit(-1)
            request = {'0' : args}
            #print 'modify', request
            mod_response = call_it('modify', request)['0']
            print "Modifying '%s', %s in section '%s' property '%s' value '%s': %s." % (LN, changeType, section, property, value, mod_response)
    elif command == 'pol':
        if len(args) < 3:
            print "Usage: policy <LN> <changeType> <identity> <action list>"
            print "  <changeType> could be 'set', 'change' or 'clear'"
            print "    'set': sets the action list to the given user overwriting the old one"
            print "    'change': modify the current action list with adding and removing actions"
            print "    'clear': clear the action list of the given user"
            print "  <identity> could be a '<UserDN>' or a 'VOMS:<VO name>'"
            print "  <action list> is a list actions prefixed with '+' or '-'"
            print "    e.g. '+read +addEntry -delete'"
            print "    possible actions: ", ' '.join(storage_actions)
        else:
            LN = args.pop(0)
            changeType = args.pop(0)
            # this is an ugly hack for handling DNs with spaces in them
            # TODO: figure out how to do this the proper way
            identity = ''
            try:
                while args[0][0] not in ['+', '-']:
                    identity += args.pop(0) + ' '
            except:
                pass
            if identity:
                identity = identity[:-1]
            if changeType not in ['set', 'change', 'clear']:
                print 'wrong change type! possible change types: set, change, clear'
            else:
                wrong_args = [arg[1:] for arg in args if arg[1:] not in storage_actions]
                if wrong_args:
                    print 'wrong action names:', ' '.join(wrong_args)
                else:
                    if changeType == 'set':
                        action_list = ' '.join(args)
                    elif changeType == 'change':
                        stat = call_it('stat', {'policy' : LN})
                        old_list = stat['policy'].get(('policy',identity),'')
                        #print "old action list for the user %s was '%s'" % (identity, old_list)
                        action_dict = dict([(paction[1:], (paction[0] == '+')) for paction in old_list.split()])
                        for paction in args:
                            action_dict[paction[1:]] = (paction[0] == '+')
                        action_list = ' '.join([(permit and '+' or '-') + action for action, permit in action_dict.items()])
                    elif changeType == 'clear':
                        action_list = ''
                    print "Setting action list of '%s' for user %s to %s:" % (LN, identity, action_list),
                    mod_response = call_it('modify', {'policy' : (LN, 'set', 'policy', identity, action_list)})
                    print mod_response['policy'] + '.'

