#!/usr/bin/python
"""\
@file public_fetch_tarballs.py
@author Rob Lanphier
@date 2009-05-30
@brief Fetch + extract tarballs and zipfiles listed in doc/asset_urls.txt

$LicenseInfo:firstyear=2009&license=viewergpl$
Copyright (c) 2009, Linden Research, Inc.

Second Life Viewer Source Code
The source code in this file ("Source Code") is provided by Linden Lab
to you under the terms of the GNU General Public License, version 2.0
("GPL"), unless you have obtained a separate licensing agreement
("Other License"), formally executed by you and Linden Lab.  Terms of
the GPL can be found in doc/GPL-license.txt in this distribution, or
online at http://secondlifegrid.net/programs/open_source/licensing/gplv2

There are special exceptions to the terms and conditions of the GPL as
it is applied to this Source Code. View the full text of the exception
in the file doc/FLOSS-exception.txt in this software distribution, or
online at
http://secondlifegrid.net/programs/open_source/licensing/flossexception

By copying, modifying or distributing this software, you acknowledge
that you have read and understood your obligations described above,
and agree to abide by those obligations.

ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
COMPLETENESS OR PERFORMANCE.
$/LicenseInfo$
"""

import sys
import os.path

# Look for indra/lib/python in all possible parent directories ...
# This is an improvement over the setup-path.py method used previously:
#  * the script may blocated anywhere inside the source tree
#  * it doesn't depend on the current directory
#  * it doesn't depend on another file being present.

def add_indra_lib_path():
    root = os.path.realpath(__file__)
    # always insert the directory of the script in the search path
    dir = os.path.dirname(root)
    if dir not in sys.path:
        sys.path.insert(0, dir)

    # Now go look for indra/lib/python in the parent dies
    while root != os.path.sep:
        root = os.path.dirname(root)
        dir = os.path.join(root, 'indra', 'lib', 'python')
        if os.path.isdir(dir):
            if dir not in sys.path:
                sys.path.insert(0, dir)
            return root
    else:
        print >>sys.stderr, "This script is not inside a valid installation."
        sys.exit(1)

base_dir = add_indra_lib_path()
print base_dir

import os
import sys
import re
import urllib
import zipfile
import tarfile
import optparse
import tempfile

import indra.util.helpformatter

# load + parse doc/asset_urls.txt
def get_asset_urls():
    asset_urls={}
    f = open(os.path.join(base_dir,"doc", "asset_urls.txt"))
    for line in f:
        line=line.strip()
        (name, value)=re.split("=", line, 1)
        asset_urls[name]=value
    return asset_urls

# Filename from a URL
def get_asset_filename_from_url(asseturl, targetdir):
    i = asseturl.rfind('/')
    filename = os.path.join(targetdir, asseturl[i+1:])
    return filename


# Extract .zip file to targetdir.  Called by extract_archive_sans_linden.
def extract_zipfile_sans_linden(filename, targetdir):
    archive = zipfile.ZipFile(filename, 'r')
    names = archive.namelist()
    for path in names:
        if(path=="linden/"):
            pass
        target = os.path.join(targetdir, re.sub("linden/", "", path))
        subdir = os.path.dirname(target)
        if not os.path.exists(subdir):
            os.makedirs(subdir)
        if not os.path.exists(target):
            fp = open(target, 'wb')
            fp.write(archive.read(path))
            fp.close()
    archive.close()


# Extract .tar.gz file to targetdir.  Called by extract_archive_sans_linden.
def extract_tarball_sans_linden(filename, targetdir):
    archive = tarfile.TarFile.open(filename, 'r')
    # get a series of TarInfo objects
    tarentries=archive.getmembers()
    for tarentry in tarentries:
        target = re.sub(r'^(\./)?(linden/?)?', "", tarentry.name)
        if(target==""):
            continue
        fulltarget=os.path.join(targetdir, target)
        subdir = os.path.dirname(fulltarget)
        if not os.path.exists(subdir):
            os.makedirs(subdir)
        if not os.path.exists(fulltarget):
            # Reset the name property on the TarInfo object, so it writes the
            # file exactly where we want it.  It's hard telling for sure if this
            # property is intended to be written to, but it works for now.
            tarentry.name=fulltarget
            # Calling TarFile.extract with the "path" parameter doesn't work as
            # we might hope, because the path components in the tarball get
            # appended to the "path" parameter.  Hence the reason for passing in
            # the TarInfo object with the munged name property
            archive.extract(tarentry)
    archive.close()

# Extract either .tar.gz file or .zip file to targetdir, stripping off the 
# leading "linden" directory, but leaving the directory structure otherwise 
# intact.
def extract_archive_sans_linden(filename, targetdir):
    if(filename.endswith('.tar.gz')):
        extract_tarball_sans_linden(filename, targetdir)
    elif(filename.endswith('.zip')):
        extract_zipfile_sans_linden(filename, targetdir)
    else:
        raise Exception, "Unhandled archive type" 

def get_assetnames_by_platform(platform):
    assetnames=['SLASSET_ART']
    if(platform=='linux' or platform=='all'):
        assetnames.append('SLASSET_LIBS_LINUXI386')
    if(platform=='darwin' or platform=='all'):
        assetnames.append('SLASSET_LIBS_DARWIN')
    if(platform=='windows' or platform=='all'):
        assetnames.append('SLASSET_LIBS_WIN32')
    return assetnames

# adapted from install.py
def _get_platform():
    "Return appropriate platform packages for the environment."
    platform_map = {
        'darwin': 'darwin',
        'linux2': 'linux',
        'win32' : 'windows',
        'cygwin' : 'windows',
        'solaris' : 'solaris'
        }
    this_platform = platform_map[sys.platform]
    return this_platform

# copied from install.py
def _default_installable_cache():
    """In general, the installable files do not change much, so find a 
    host/user specific location to cache files."""
    user = _getuser()
    cache_dir = "/var/tmp/%s/install.cache" % user
    if _get_platform() == 'windows':
        cache_dir = os.path.join(tempfile.gettempdir(), \
                                 'install.cache.%s' % user)
    return cache_dir

# For status messages (e.g. "Loading...").  May come in handy if
# we implement a "quiet" mode.
def _report(string):
    print string


# copied from install.py
def _getuser():
    "Get the user"
    try:
        # Unix-only.
        import getpass
        return getpass.getuser()
    except ImportError:
        import win32api
        return win32api.GetUserName()

# adapted from install.py
def _parse_args():
    parser = optparse.OptionParser(
        usage="usage: %prog [options]",
        formatter = indra.util.helpformatter.Formatter(),
        description="""This script fetches and installs tarballs and \
zipfiles ("asset bundles") listed in doc/asset_urls.txt

If no asset bundles are specified on the command line, then the default \
behavior is to install all known asset bundles appropriate for the platform \
specified.  You can specify more than one asset bundle on the command line.

Example:
  %prog SLASSET_ART

  This looks for the "SLASSET_ART" entry in doc/asset_urls.txt, and extracts
  the corresponding asset bundle into your source tree.
""")
    parser.add_option(
        '-p', '--platform', 
        type='choice',
        default=_get_platform(),
        dest='platform',
        choices=['windows', 'darwin', 'linux', 'solaris', 'all'],
        help="""Override the automatically determined platform. \
You can specify 'all' to get assets for all platforms.  Choices: windows, \
darwin, linux, solaris, or all.  Default: autodetected (%s)""" % \
             _get_platform())
    parser.add_option(
        '--cache-dir', 
        type='string',
        default=_default_installable_cache(),
        dest='cache_dir',
        help='Where to download files. Default: %s'% \
             (_default_installable_cache()))
    parser.add_option(
        '--install-dir', 
        type='string',
        default=base_dir,
        dest='install_dir',
        help='Where to unpack the installed files.  Default: %s' % base_dir)

    return parser.parse_args()

def main(argv):
    options, args = _parse_args()
    # 1. prepare cache dir
    if not os.path.exists(options.cache_dir):
        os.makedirs(options.cache_dir)

    # 2. read doc/asset_urls.txt
    asseturls=get_asset_urls()

    # 3. figure out which asset bundles we'll be downloading
    if len(args)>0:
        assetnames=args
    else:
        assetnames=get_assetnames_by_platform(options.platform)

    # 4. download and extract each asset bundle
    for asset in assetnames:
        # 4a. get the URL for the asset bundle
        try:
            asseturl=asseturls[asset]
        except:
            print "No asset in doc/asset_urls.txt named %s" % asset
            sys.exit(2)
        # 4b. figure out where to put the downloaded asset bundle
        filename=get_asset_filename_from_url(asseturl, options.cache_dir)

        # 4c. see if we have it, and if not, get it
        if os.path.exists(filename):
            _report("Using already downloaded "+filename+" ...")
        else:
            _report("Downloading "+filename+" ...")
            urllib.urlretrieve(asseturl, filename)
        # 4d. extract it into the tree
        extract_archive_sans_linden(filename, options.install_dir)

# execute main() only if invoked directly:
if __name__ == "__main__":
    sys.exit(main(sys.argv))

