#!/usr/bin/python
#
#*****************************************************************************/
# 
# Filename: INSTALL
#
# Copyright (c) 2010 Autodesk Canada Inc.
# All rights reserved.
# 
# This computer source code and related instructions and comments are the
# unpublished confidential and proprietary information of Autodesk, Inc.
# and are protected under applicable copyright and trade secret law.
# They may not be disclosed to, copied or used by any third party without
# the prior written consent of Autodesk, Inc.
#*****************************************************************************/

import distutils.dir_util
import logging, logging.handlers
import optparse
import os
import platform
import pygtk
import re
import stat
import sys

NAME = 0
FILE = 1
ARCH = 2

AUTODESK_LOG_DIR = "/var/log/autodesk"
PRODUCT_HOMEDIR = "DEF_PRODUCT_HOMEDIR"

logger = None
##
# Create a logger to log script's output to the shell and in a log file.
# The stream handler output informations on stdout.
# The rotating file handler output with DEBUG level.
def createLogger():
   global logger
   
   # Create the log directory
   if not os.path.exists(AUTODESK_LOG_DIR):
      os.makedirs(AUTODESK_LOG_DIR)
   os.chmod(AUTODESK_LOG_DIR, 0777)

   # Create the logger
   scriptName = os.path.basename(sys.argv[0])
   logger = logging.getLogger(scriptName)

   logFile = os.path.join(AUTODESK_LOG_DIR, "%s.log" % scriptName)
   logFileExists = os.path.exists(logFile)

   # File handler
   # Keep 10 log files in the folder.
   fhand = logging.handlers.RotatingFileHandler(logFile, 'a', 0, 10)
   fhand.setLevel(logging.DEBUG)
   formatt = logging.Formatter('[%(levelname)s] line %(lineno)d: %(message)s')
   fhand.setFormatter(formatt)
   # Check if we need to rotate the log file
   if logFileExists:
      fhand.doRollover()
   os.chmod(logFile, 0666)
   logger.addHandler(fhand)

   # Stream handler
   shand = logging.StreamHandler(sys.stdout)
   if os.getenv("DL_INSTALL_DEBUG"):
      shand.setLevel(logging.DEBUG)
   else:
      shand.setLevel(logging.INFO)
   formatt = logging.Formatter('%(message)s')
   shand.setFormatter(formatt)
   logger.addHandler(shand)

   logger.setLevel(logging.DEBUG)

# Define architecture.  Used by package lists below...
knownArchitectures = { "i386":"32", "i486":"32", "i586":"32", "i686":"32", "x86_64":"64" }
supportedArchitectures = [ 'i686', 'x86_64' ]
supportedModels = [ 'all' ]
machineArch = None
machineBits = None

machineArch = platform.machine()
  
if knownArchitectures.has_key(machineArch):
   machineBits = knownArchitectures[machineArch]
   if machineBits == "32":
      machineBits32 = machineBits
else:
   print( "Error: %s is an unknown machine architecture" % machineArch )
   sys.exit(1)

# Directories containing packages.
#
# Start with current dir then check for:
#   - "dist" subdirectory (new "single" DVD layout for all IFFFS products)
#   - kernel specific 
#   - extra/other RPM packages
#
dirList = []

# Extract the current directory
currentDir = os.path.dirname(sys.argv[0])
if not os.path.isabs(currentDir):
   currentDir = os.path.join( os.getcwd(), currentDir )
currentDir = os.path.abspath(currentDir)

# Dist sub-dir? (new layout for single DVD)
dirDist = os.path.join(currentDir, "dist")
if not os.path.exists(dirDist):
   # Old layout, RPMs inside current dir
   dirDist = currentDir
dirList.append(dirDist)
    
# Kernel sub-dir
kernelVersion = platform.release()
dirKernel = os.path.join(dirDist, kernelVersion)
if os.path.exists(dirKernel):
   dirList.append(dirKernel)

# Extra/other RPM packages
dirOtherRPMs = os.path.join(dirDist, "Packages", "binaries")
if os.path.exists(dirOtherRPMs):
   dirList.append(dirOtherRPMs)


#
# packagesToUpgrade: means these package will ALWAYS be upgrade.
# Only ONE installed version is allowed.
#
# oneOfAKindPackages: means many versions can co-exist, as long as the version is different.
# Different stamps of same version will be upgraded.
#
# interestingPackages: is the list of packages to consider.  
# Packages NOT in this list will completely be ignored by the installer.
#
# optionnalPackages: list of optionnal packages.
# No warning/error will be reported if they are missing.
# Mostly useful for debugging, internal and automation tools.
#


packagesToUpgrade = [
]

oneOfAKindPackages = [
    "conform.doc.help", \
]

interestingPackages = [
    "conform.doc.help", \
]

optionnalPackages = [
]

packagesFunctions = {
}

installed = []

useGfx = True
if os.environ.has_key("DL_UNATTENDED_INSTALL"):
   useGfx = int(os.environ["DL_UNATTENDED_INSTALL"]) == 0
try:
   pygtk.require('2.0')
except AssertionError:
   vers = pygtk._get_available_versions().keys()
   vers.sort()
   for i in xrange(len(vers)):
      curr = float(vers[len(vers) - i - 1])
      if  curr > 2.0:
         pygtk.require("%2.1f" % curr)
         break
   if i == (len(vers) - 1):
      print "No valid version of GTK found, cannot continue with installation"
      print "This script is only supported for gtk v2.0 and higher"
      useGfx = False
try:
   import gtk
except RuntimeError:
   useGfx = False

def executeCommand(command):
   logger.debug("\tExecuting: %s" % command)
   return os.popen4(command)[1].readlines()


def stripExtensions(fileName):
   index = fileName.rfind(".rpm")

   if index != -1:
      fileName = fileName[0:index]

   for arch in knownArchitectures:
      index = fileName.rfind(".%s" % arch)
      if index != -1:
         fileName = fileName[0:index]
         break

   return fileName

preReleaseTags = { "prealpha":"0", "alpha":"1", "beta":"2" }
ignoreReleaseTags = [ "SP" ]

def splitPackageName(package):
   packageVersion = ""

   parts = package.split("-")
   packageName = parts[0]

   if len(parts) > 1:
      finishedName = False

      for partnumber in xrange(1, len(parts)):
         part = parts[partnumber]

         if not finishedName:
            if len(part) > 0:
               if not part[0].isdigit():
                  packageName += "-" + part
               else:
                  packageVersion = part
                  finishedName = True
            else:
               packageName += "-"
         else:
            packageVersion += "-" + part

   # Remove architecture from version
   packageVersion = packageVersion.replace(".x86_64", "")
   packageVersion = packageVersion.replace(".i686", "")
   packageVersion = packageVersion.replace(".i386", "")
   
   return (packageName, packageVersion)

def splitVersion(versionString, removePreReleaseTag = False):
   parts = versionString.split("-")
   version = parts[0]
   release = ""

   if len(parts) > 1:
      release = "-".join(parts[1:])

   # replace alpha/beta strings by a number
   # move alpha/beta strings to the "release" section
   if removePreReleaseTag:
      for tag in preReleaseTags.iterkeys():
         pattern = re.compile(tag + "[0-9]*")
         match = pattern.search(version)
         if match:            
            release += "-" + version[match.start():]
            version = version[0:match.start()] #+ preReleaseTags[tag]
            
   # strip off SP string, while keeping the actual SP version number.
   # 2007.0.SP2 will become 2007.0.2
   for tag in ignoreReleaseTags:
      pattern = re.compile(tag + "[0-9]*")
      match = pattern.search(version)
      if match:
         version = version[0:match.start()] + version[match.start()+len(tag):]

   # remove any unknown release tag
   if removePreReleaseTag:
      match = re.compile("([0-9\.]*).*").search(version)
      if match:
         version = match.group(1)

   return (version, release)

def splitSubVersion(versionString):
   versions = versionString.split(".")
   major = versions[0]

   minor = 0
   if len(versions) > 1 and versions[1] != '':
      minor = versions[1]

   patch = 0
   if len(versions) > 2:
      if len(versions[2]) > 0:
         patch = versions[2]

   build = 0
   if len(versions) > 3:
      if len(versions[3]) > 0:
         build = versions[3]

   return (int(major), int(minor), int(patch), int(build))

def splitSubRelease(releaseString):
   (releaseNum, preReleaseInfo) = re.compile("([0-9]+)(.*)").search(releaseString).groups()

   preReleaseString = ""
   preReleaseNum = 0

   if (preReleaseInfo != ""):
      match = re.compile("(.*?)([0-9]+)").search(preReleaseInfo)
      if match:
         (preReleaseString, preReleaseNum) = match.groups()
      else:
         preReleaseString = preReleaseInfo

   return (int(releaseNum), preReleaseString, int(preReleaseNum))

def samePackage(firstPackage, secondPackage):
   return splitPackageName(firstPackage)[0] == splitPackageName(secondPackage)[0]

def similarPackage(firstPackage, secondPackage):
   (firstName, firstVersion) = splitPackageName(firstPackage)
   (secondName, secondVersion) = splitPackageName(secondPackage)

   pattern = re.compile("[0-9]*")

   firstName = pattern.sub("", firstName.lower())
   secondName = pattern.sub("", secondName.lower())

   return firstName == secondName

def sameVersion(firstPackage, secondPackage):
   if samePackage(firstPackage, secondPackage):
      (firstVersion, firstRelease) = splitVersion(splitPackageName(firstPackage)[1])
      (secondVersion, secondRelease) = splitVersion(splitPackageName(secondPackage)[1])

      if firstVersion == secondVersion:
         return True

   return False

def sameMajorVersion(firstPackage, secondPackage):
   if samePackage(firstPackage, secondPackage):
      (firstVersion, firstRelease) = splitVersion(splitPackageName(firstPackage)[1])
      (secondVersion, secondRelease) = splitVersion(splitPackageName(secondPackage)[1])

      (fMajor, fMinor, fPatch, fBuild) = splitSubVersion(firstVersion)
      (sMajor, sMinor, sPatch, sBuild) = splitSubVersion(secondVersion)
 
      if fMajor == sMajor:
         return True

   return False

def sameMinorVersion(firstPackage, secondPackage):
   if samePackage(firstPackage, secondPackage):
      (firstVersion, firstRelease) = splitVersion(splitPackageName(firstPackage)[1])
      (secondVersion, secondRelease) = splitVersion(splitPackageName(secondPackage)[1])

      (fMajor, fMinor, fPatch, fBuild) = splitSubVersion(firstVersion)
      (sMajor, sMinor, sPatch, sBuild) = splitSubVersion(secondVersion)
 
      if fMinor == sMinor:
         return True

   return False

def sameRelease(firstPackage, secondPackage):
   if samePackage(firstPackage, secondPackage):
      (firstVersion, firstRelease) = splitVersion(splitPackageName(firstPackage)[1])
      (secondVersion, secondRelease) = splitVersion(splitPackageName(secondPackage)[1])

      if firstRelease == secondRelease:
         return True

   return False

def isPreRelease(package):
   for tag in preReleaseTags:
      pattern = re.compile(tag + "[0-9]*")
      match = pattern.search(package)
      if match:
         return True

   return False

def getMostRecentVersion(firstVersion, secondVersion):
   (fVer, fRel) = splitVersion(firstVersion, removePreReleaseTag=True)
   (sVer, sRel) = splitVersion(secondVersion,  removePreReleaseTag=True)

   (fMajor, fMinor, fPatch, fBuild ) = splitSubVersion(fVer)
   (sMajor, sMinor, sPatch, sBuild ) = splitSubVersion(sVer)
   
   if fMajor < sMajor:
      return secondVersion
   elif sMajor < fMajor:
      return firstVersion
   else:
      if fMinor < sMinor:
         return secondVersion
      elif sMinor < fMinor:
         return firstVersion
      else:
         if fPatch < sPatch:
            return secondVersion
         elif sPatch < fPatch:
            return firstVersion
         else:
            if fBuild < sBuild:
               return secondVersion
            elif sBuild < fBuild:
               return firstVersion
            else:
               (fReleaseNum, fPreRelString, fPreRelNum) = splitSubRelease(fRel)
               (sReleaseNum, sPreRelString, sPreRelNum) = splitSubRelease(sRel)

               if fReleaseNum < sReleaseNum:
                  return secondVersion
               elif sReleaseNum < fReleaseNum:
                  return firstVersion
               else:
                  if (fPreRelString in preReleaseTags) and (sPreRelString in preReleaseTags):
                     if preReleaseTags[fPreRelString] > preReleaseTags[sPreRelString]:
                        return firstVersion
                     else:
                        return secondVersion
                  elif fPreRelString < sPreRelString:
                     return secondVersion
                  elif sPreRelString < fPreRelString:
                     return firstVersion
                  else:
                     if fPreRelNum < sPreRelNum:
                        return secondVersion
                     elif sPreRelNum < fPreRelNum:
                        return firstVersion

   return secondVersion

def getMostRecentRelease(firstVersion, secondVersion):
   (fVer, fRel) = splitVersion(firstVersion, removePreReleaseTag=True)
   (sVer, sRel) = splitVersion(secondVersion, removePreReleaseTag=True)

   (fReleaseNum, fPreRelString, fPreRelNum) = splitSubRelease(fRel)
   (sReleaseNum, sPreRelString, sPreRelNum) = splitSubRelease(sRel)

   if fReleaseNum < sReleaseNum:
      return secondVersion
   elif sReleaseNum < fReleaseNum:
      return firstVersion
   else:
      if (fPreRelString in preReleaseTags) and (sPreRelString in preReleaseTags):
         if preReleaseTags[fPreRelString] > preReleaseTags[sPreRelString]:
            return firstVersion
         else:
            return secondVersion
      elif fPreRelString < sPreRelString:
         return secondVersion
      elif sPreRelString < fPreRelString:
         return firstVersion
      else:
         if fPreRelNum < sPreRelNum:
            return secondVersion
         elif sPreRelNum < fPreRelNum:
            return firstVersion
            
   return secondVersion

def getMostRecentPackage(firstPackage, secondPackage, checkReleaseOnly=False):
   firstVersion = splitPackageName(firstPackage)[1]
   secondVersion = splitPackageName(secondPackage)[1]

   logger.debug("firstPackage=%s, secondPackage=%s" % (firstPackage, secondPackage))
   logger.debug("firstVersion=%s, secondVersion=%s" % (firstVersion, secondVersion))
   logger.debug("")

   if checkReleaseOnly:
      # Only consider release number
      if getMostRecentRelease(firstVersion, secondVersion) == firstVersion:
         return firstPackage
   else:
      # Only consider both version and release numbers
      if getMostRecentVersion(firstVersion, secondVersion) == firstVersion:
         return firstPackage

   return secondPackage

def isPackageInstalled(packageName, matchArchitecture):
   
   if matchArchitecture == "":
      result = executeCommand( "/bin/rpm -q --nosignature --queryformat " \
                               "'%%{NAME}-%%{VERSION}-%%{RELEASE}\n' %s" % \
                               packageName )
   else:
      result = executeCommand( "/bin/rpm -q --nosignature --queryformat " \
                               "'%%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}\n' %s" % \
                               packageName )

   logger.debug("isPackageInstalled() packageName=%s matchArchitecture=%s result=%s" % (packageName, matchArchitecture, result))

   if result[0].find(" is not installed") == -1:
      fixedResult = []
      for line in result:
         if matchArchitecture == "":
            fixedResult.append(line.strip())
         else:
            if line.find(matchArchitecture) >= 0:
               fixedResult.append(line.strip())

      logger.debug("isPackageInstalled() packageName=%s matchArchitecture=%s fixedResult=%s" % (packageName, matchArchitecture, fixedResult))

      return fixedResult
   else:
      return None

def checkUninstall(packageName, architecture = ""):
   logger.info( "\t\tTesting uninstall of %s" % packageName )
   ok = True
   if architecture == "":
      result = executeCommand("/bin/rpm -e --allmatches --test " + packageName)
   elif architecture in packageName:
      # If the architecture is already in the package name
      result = executeCommand("/bin/rpm -e --test %s" % packageName)
   else:
      result = executeCommand("/bin/rpm -e --test " + packageName + "." + architecture)
   if len(result):
      ok = False
      for line in result:
         logger.info( "\t\t> %s" % line.strip() )
   return ok


def uninstallPackage(packageName, architecture = "", otherOptions = ""):
   if architecture == "":
      result = executeCommand("/bin/rpm -e --allmatches " + otherOptions + " " + packageName)
   elif architecture in packageName:
      # If the architecture is already in the package name
      result = executeCommand("/bin/rpm -e %s %s" % (otherOptions, packageName))
   else:
      result = executeCommand("/bin/rpm -e " + otherOptions + " " + packageName + "." + architecture)
   for line in result:
      if line.find("is not installed") == -1:
         logger.info( "> %s" % line.strip() )
      else:
         # Package was alread removed.  Ignore error.
         logger.debug( "\tPackage was already removed: %s" % packageName)
         
def checkUpgrade(fileName, otherOptions = ""):
   logger.info( "\tTesting upgrade" )
   ok = True
   result = executeCommand("/bin/rpm -U --test --nosignature " + otherOptions + fileName)
   if len(result):
      ok = False
      for line in result:
         logger.info( "\t> %s" % line.strip() )
   return ok


def upgradePackage(fileName, otherOptions = ""):
   logger.info( "\tUpgrading ..." )
   os.system("/bin/rpm -U --nosignature " + otherOptions + " " + fileName)

conflictPattern = re.compile(" conflicts with file from package ")

def checkForceRequired(fileName):
   forceRequired = False
   result = executeCommand("/bin/rpm -i --test --nosignature " + fileName)
   if len(result):
      newerPattern = re.compile("package (.+) \(which is newer than (.+)\) is already installed")
      for message in result:
         if not newerPattern.search(message):
            break
      else:
         forceRequired = True
   return forceRequired
   

def checkInstall(fileName, otherOptions = ""):
   logger.info( "\tTesting install of %s" % fileName )
   ok = True
   result = executeCommand("/bin/rpm -i " + otherOptions + " --test --nosignature " + fileName)
   if len(result):
      ok = False
      for line in result:
         logger.info( "\t> %s" % line.strip() )
         if line.find("Stale NFS") >= 0:
            logger.error( 'ERROR: The rpm tool has trouble evaluating the disk space for some mount points.' )
            logger.error( 'ERROR: Try to manually unmount the faulty directory mentionned above,' )
            logger.error( 'ERROR: using the "umount" command, and re-launch this script.' )
            sys.exit(1)
   return ok


def getInstallConflicts(fileName):
   conflicts = []

   result = executeCommand("/bin/rpm -i --test --nosignature " + fileName)
   for line in result:
      line = line.strip()
      match = conflictPattern.search(line)
      if match:
         conflicts.append( line[match.end():] )

   dict = {}
   for conf in conflicts:
      dict[conf] = ""
   return dict.keys()


def installPackage(fileName, otherOptions = ""):
   logger.info( "\tInstalling ..." )
   os.system("/bin/rpm -i --nosignature " + otherOptions + " " + fileName)

def conflictsOkToUninstall(toInstall, conflicts):

   for conflict in conflicts:
      if not similarPackage(toInstall, conflict):
         return False

      if getMostRecentPackage(toInstall, conflict) != toInstall:
         return False
   return True


def proceedPackageList(packageListToInstall, force):
   success = True
   toCancel = []

   for packageInfo in packageListToInstall:
      package = packageInfo[NAME]
      packageFile = packageInfo[FILE]
      packageArch = packageInfo[ARCH]
      rpmExtraOption = ""

      if package in toCancel:
         # Skip this one.
         continue

      # Check if pacakge need to be excluded
      if package in packagesFunctions:
         if ( packagesFunctions[ package ]() ):
            logger.info( "%s not required" % package )
            continue

      # Special case for BB
      if package == "backburner_server.sw.base" and isPackageInstalled("backburner_server.sw.base", ""):
         logger.info( "Stopping Backburner Server" )
         executeCommand("/usr/discreet/backburner/backburner_server_stop")

      # Special case for wine
      if package.startswith("wine"):
         rpmExtraOption += " --nodeps "

      # lustrepremium_common_ has MD5 errors, because it repackages some RHEL4 system RPMs (nasty!).
      if package.startswith("lustrepremium_common"):
         rpmExtraOption += " --nomd5 "

      # GROUP BEGIN -- Need to group Some packages together (Stonewire)
      packageGroup = []
      packageGroupNames = []
      packageGroupFiles = []
      packageGroupArchs = []
      if package.startswith("autodesk.stonewire.") or \
         package.startswith("Stonewire."):
         for pkgGrpElement in packageListToInstall:
            if pkgGrpElement[NAME].startswith("autodesk.stonewire.") or \
               pkgGrpElement[NAME].startswith("Stonewire."):
               packageGroup.append((pkgGrpElement[NAME],pkgGrpElement[FILE],pkgGrpElement[ARCH]))
               toCancel.append(pkgGrpElement[NAME])
         
      if packageGroup:
         for pkgGrpElement in packageGroup:
            packageGroupNames.append(pkgGrpElement[NAME])
            packageGroupFiles.append(pkgGrpElement[FILE])
            packageGroupArchs.append(pkgGrpElement[ARCH])
      # GROUP END -- End of package groupping

      # Upgrade?
      if package in packagesToUpgrade:
         if packageGroup:
            # Need to group, do them in one single RPM command
            packageFileToUpgrade = " ".join(packageGroupFiles)
            packageNameToUpgrade = packageGroupNames
            packageArchToUpgrade = packageGroupArchs
         else:
            # Regular single package
            packageFileToUpgrade = packageFile
            packageNameToUpgrade = package
            packageArchToUpgrade = packageArch

         logger.info( "Upgrading %s with %s" % (package, packageFileToUpgrade) )

         if checkUpgrade(packageFileToUpgrade, rpmExtraOption):
            upgradePackage(packageFileToUpgrade, rpmExtraOption)
         elif ( packageArchToUpgrade == "x86_64" and isPackageInstalled(packageNameToUpgrade, "i386") ) \
            or ( packageArchToUpgrade == "i386" and isPackageInstalled(packageNameToUpgrade, "x86_64") ) :
            uninstallPackage(packageNameToUpgrade)
            installPackage(packageFileToUpgrade, rpmExtraOption)
         elif force:
            logger.info( "Force install specified, continuing upgrade..." )
            upgradePackage(packageFileToUpgrade, "--nodeps --force")
         else:
            logger.info( "Force install not specified, stopping install..." )
            success = False
            break

         for pkgGrpName in packageGroupNames:
            installed.append(pkgGrpName)

      else:
         # Install?
         versionToInstall = stripExtensions(os.path.basename(packageFile))

         logger.info( "Installing %s" % packageFile )

         conflicts = getInstallConflicts(packageFile)

         if package in oneOfAKindPackages:
            installedVersions = isPackageInstalled(package, packageArch)
            if installedVersions:
               for installedVersion in installedVersions:
                  if ( sameVersion(installedVersion, versionToInstall) \
                   and not sameRelease(installedVersion, versionToInstall) \
                   and getMostRecentPackage(installedVersion, versionToInstall, checkReleaseOnly=True) == versionToInstall ):
                     if not installedVersion in conflicts:
                        conflicts.append(installedVersion)

         if conflicts:
            if conflictsOkToUninstall(versionToInstall, conflicts):
               # special case, conflicts with older versions of the same package
               logger.warning( "\tWarning: package %s conflicts with older version:" % packageFile )
               for conflict in conflicts:
                  logger.warning( "\t\t %s" % conflict )

               logger.info( "\tWill replace older versions" )
               
               for conflict in conflicts:
                  uninstallPackage(conflict, packageArch, "--nodeps")
            else:
               logger.warning( "\tWarning: %s conflicts with:" % packageFile )
               for conflict in conflicts:
                  logger.info( "\t\t%s" % conflict )

               if force:
                  logger.info( "\tForce install specified, removing conflicts..." )
                  for conflict in conflicts:
                     uninstallPackage(conflict, packageArch, "--nodeps")
               else:
                  if getMostRecentPackage(versionToInstall, conflict) == conflict:
                     logger.info( "\tNewer version already installed, skipping %s" % versionToInstall )
                     continue
                  else:
                     logger.warning( "\nForce install not specified, stopping install...\n" )
                     success = False
                     break

         if checkForceRequired(packageFile):
            rpmExtraOption += " --force "

         if checkInstall(packageFile, rpmExtraOption):
            installPackage(packageFile, rpmExtraOption)
            installed.append(package)
         elif force:
            logger.info( "\tForce install specified, continuing..." )
            installPackage(packageFile, "--force --nodeps")
            installed.append(package)
         else:
            logger.info( "Force install not specified, stopping install..." )
            success = False
            break
   return success

def parseArguments():
   parser = optparse.OptionParser(usage="%prog [options]")
   
   parser.add_option("--install", dest="install",
                     action="store_true", default=True,
                     help="deprecated option")
   
   parser.add_option("--uninstall", dest="install",
                     action="store_false", default=True,
                     help="deprecated option. Use rmsoft command instead")
   
   parser.add_option("--force", dest="force",
                     action="store_true", default=False,
                     help="ignore file conflicts between packages and "
                          "reinstall if the package is already present")
   
   parser.add_option("--sparkdev", "--edu", dest="eduMode",
                     action="store_true", default=False)
   
   parser.add_option("--uid",
                     action="store", dest="uid",
                     help="override user id of product's account")
   
   parser.add_option("--gid",
                     action="store", dest="gid",
                     help="override group id of product's account")
   
   (options, args) = parser.parse_args()
   if len(args)!=0:
      parser.error("no argument needed")
   
   if not options.install:
      parser.error("The --uninstall option is deprecated.\nPlease use the \"rmsoft\" command instead.")
   
   if options.uid and (not options.uid.isdigit() or int(options.uid)<0):
      parser.error("Account UID must be an integer greater or equal to 0.")
   
   if options.gid and (not options.gid.isdigit() or int(options.gid)<0):
      parser.error("Account GID must be an integer greater or equal to 0.")
   
   return options

##
# Ask a question the the user with some possible answers
class QuestionPopup(object):
   ##
   # Create the question window
   # @param header Title of the window
   # @param message Question for the user
   # @param 2-tuple list with possible values containing string answer a value.
   #        e.g. [("Yes",True),("No",False)]
   def __init__(self, header, message, answers):
      self.__header = header
      self.__message = message
      self.__answerList = answers
      self.__answerDict = {}
      # Set the default answer to the first button
      self.__answer = answers[0][1]
   
   def answer(self, widget, data=None):
      self.__answer = self.__answerDict[widget.get_label()]
      data.destroy()
      gtk.main_quit()
   
   def close(self, widget, data=None):
      widget.destroy()
      gtk.main_quit()
   
   def __drawMsg(self):
      # Create window
      window = gtk.Window(gtk.WINDOW_TOPLEVEL)
      window.set_position(gtk.WIN_POS_CENTER)
      window.set_title(self.__header)
      window.set_size_request(400, 100)
      window.connect('delete_event', self.close)
      
      # Create vBox
      vBox = gtk.VBox(False, 0)
      window.add(vBox)
      
      # Create Message
      Text = gtk.Label(self.__message)
      vBox.pack_start(Text, True, True, 0)
      
      # Create buttons
      
      self.__answerDict = {}
      for ans in self.__answerList:
         self.__answerDict[ans[0]] = ans[1]
         button = gtk.Button(ans[0])
         button.connect('clicked', self.answer, window)
         vBox.pack_start(button, False, True, 0)
      
      window.show_all()
      gtk.main()
   
   ##
   # Display the message to the user and return it's answer
   # @return User answer's value
   def ask(self):
      self.__drawMsg()
      return self.__answer


def adlmAskQuestion():
   try:
      address = ""
      while len(address) == 0:
         address = raw_input("Enter the license server name or address (localhost for a local server): ")
         address = address.strip()
         if len(address) == 0:
            print("Please provide a valid host name or address")

      macAddress = "0"
   
      adlmServConfig = os.path.join(PRODUCT_HOMEDIR, "bin", "res", "adlm", "adlmLicServConfig")
      if os.system("%s --network %s %s" % (adlmServConfig, address, macAddress)) != 0:
         print("Problem during the license server configuration.")
         print("Please configure your license server manually.")
         print("Refer to the product's documentation for more instructions.")
   except KeyboardInterrupt:
      return

####################
#
# MAIN starts here
#
####################

#This code will attempt to import pygtk v2, but if
#pygtk v2 is not found on the system, it will import the most recent version
#This is necessary because the most recent version is not always the default version
#rather, the default version is the latest version installed
def main():
   if os.geteuid():
      print( "You must have root privileges to execute this script." )
      sys.exit(1)
   
   createLogger()
      
   # unset LD_ASSUME_KERNEL since adding/removing rpm packages with
   # this environment variable set to 2.4.19 is likely to corrupt the rpm
   # database 
   os.unsetenv("LD_ASSUME_KERNEL")
   
   # Let sub-processes know they are called from the install script.
   os.putenv("DL_INSTALL_SCRIPT_RUNNING", os.path.basename(sys.argv[0]))
   
   # We look for specific output messages from systems commands.
   # Enforce english, otherwise et won't recognize them!
   os.putenv("LANG", "en_US.UTF-8")
   
   # Parse arguments
   options = parseArguments()
   
   # Validate architecture
   supported = False
   if options.eduMode:
      supported = True
   for arch in supportedArchitectures:
      if machineArch == arch or arch == "all":
         supported = True
         break
   if not supported:
      logger.error( "Error: this software distribution is NOT supported on %s" % machineArch )
      sys.exit(1)
   
   # Validate model
   machineModel = os.popen4("/usr/sbin/dmidecode | grep -i 'System Information' --after-context=5 | grep -i 'Product Name'")[1].read().strip()
   index = machineModel.find(":")
   if index != -1:
      machineModel = machineModel[index:]
   else:
      retString = os.popen4("/usr/sbin/dmidecode | grep -i 'SMBIOS'")[1].read().strip()
      testIdx = retString.find("SMBIOS")
      if testIdx == -1:
         logger.error( "\nError: can't found BIOS data. Please reboot your workstation and run this script again.\n" )
      sys.exit(1)   
   
   supported = False
   if options.eduMode:
      supported = True
   for model in supportedModels:
      if machineModel.find(model) != -1:
         supported = True
         break
      if model == "all":
         supported = True
         break
   if not supported:
      logger.error( "Error: this software distribution is NOT supported on %s" % machineModel )
      sys.exit(1)
  
# Stonefs will only work on the following kernels

   stonefsKernels = [
     "2.6.9-22.0.1.EL.ADSK.1smp",
     "2.6.9-22.0.1.EL.ADSKsmp",
     "2.6.9-34.0.1.EL.ADSK.1smp",
     "2.6.9-34.0.1.EL.ADSKsmp" ]
 
   # OK, here's the meat...
      
   logger.info( "Finding packages..." )

   packagesToInstall = []

   for package in interestingPackages:
      found = False
      for dir in dirList:
         dirContent = os.listdir(dir)
   
         for file in dirContent:
            if file.find(package) == 0:
 
               found = True

               # There's no point in installting the stonefs package
               # if the OS doesn't support it
               if ( package.startswith( "autodesk.stonewire.stonefs" )
                    and platform.release() not in stonefsKernels ):
                  continue
                  
               filePath = os.path.join(dir, file)
               arch = executeCommand( "rpm -qp " +  filePath +
                                      " --nosignature --queryformat '%{ARCH}\n'" )[ 0 ]
               logger.info( "\tUsing %s" % filePath )
               packagesToInstall.append( ( package, filePath, arch.strip() ) )

      if not found:
         if not package in optionnalPackages:
            logger.warning( "\tWarning: Can't find %s" % package )
 
   # Hack for Burn
   # Pre 2012 Burn versions have a (needless) depedency on sw_dbd
   # So install the servers if there is an older stone+wire version installed
   # to make sure we don't pull the rug out from under older Burn versions
   # If stonewire.servers is already going to be installed, skip this entirely
   package = "autodesk.stonewire.servers"
   if ( isPackageInstalled("Stonewire.base.sw", arch.strip())
        and package not in interestingPackages ):
      for dir in dirList:
         for file in os.listdir(dir):
            if file.startswith(package):
               filePath =  os.path.join(dir, file)
               arch = executeCommand( "rpm -qp " +  filePath +
                                   " --nosignature --queryformat '%{ARCH}\n'" )[ 0 ]
               logger.info( "\tUsing %s" % filePath )
               packagesToInstall.append((package, filePath, arch))

   logger.info( "Checking installed versions..." )

   toRemove = []

   for packageInfo in packagesToInstall:
      package = packageInfo[NAME]
      packageFile = packageInfo[FILE]
      packageArch = packageInfo[ARCH]
      versionToInstall = stripExtensions(os.path.basename(packageFile))
      skipThisOne = False
      
      # Special case for FlamePremium
      if package.startswith("flamepremium.sw.ogl"):
         flameVersion = (splitPackageName(os.path.basename(packageFile))[1]).split("-")[0]
         
         # Tell smoke and Lustre we are installing a FlamePremium
         os.environ["DL_INSTALL_FLAME_PREMIUM"] = "1"
         os.environ["DL_INSTALL_FLAME_PREMIUM_V"] = "%s" % flameVersion

      # Mesa GLU HACK for burn.
      # If Mesa GLU is only installed as 32bits, we need both.
      # to simplify things, we will remove the i386 version here and 
      # re-install both i386 and x86_64 along with the other packages.
      if package == "xorg-x11-Mesa-libGLU" and \
         isPackageInstalled("xorg-x11-Mesa-libGLU", "i386") and \
         not isPackageInstalled("xorg-x11-Mesa-libGLU", "x86_64"):
         logger.info( "Uninstalling xorg-x11-Mesa-libGLU (will be re-installed later)" )
         uninstallPackage("xorg-x11-Mesa-libGLU", "i386", "--nodeps")
         skipThisOne = True
      #
      # mesa package name changed between RHEL4 and RHEL5, skip installing 
      # the RHEL4 verion if the RHEL5 one is already installed
      #
      elif package == "xorg-x11-Mesa-libGLU" and \
          isPackageInstalled("mesa-libGLU", "x86_64" ):
          toRemove.append((package,packageFile,packageArch))
          skipThisOne = True
      elif package == "BURN_GPU_PACKAGE_NAME":
         nvidiaCard = "/proc/driver/nvidia/cards/0"
         if not os.path.exists(nvidiaCard):
            toRemove.append((package,packageFile,packageArch))
            skipThisOne = True

      if not skipThisOne:
         # Regular case, check if we must install or upgrade.
         if isPackageInstalled(versionToInstall, packageArch):
            logger.info( "\t %s.%s is already installed" %
                         ( versionToInstall, packageArch ) )
            toRemove.append((package,packageFile,packageArch))

         elif package in packagesToUpgrade:
            installedVersions = isPackageInstalled(package, packageArch)
            if installedVersions:
               for version in installedVersions:
                  if getMostRecentPackage(versionToInstall, version) == version:
                     logger.info( "\tInstalled package %s is more recent" % version )
                     toRemove.append((package,packageFile,packageArch))
                     break

   for packageInfo in toRemove:
      packagesToInstall.remove((packageInfo[NAME],packageInfo[FILE],packageInfo[ARCH]))

   logger.info( "Performing installs..." )

   # Stop stone+wire if is to be installed
   if "autodesk.stonewire.base" in packagesToInstall or \
      "Stonewire.base.sw" in packagesToInstall:
      os.system("/etc/init.d/stone+wire stop >/dev/null 2>&1 </dev/null")

   # Remove obsolete or renamed packages
   dlmpdRemoved = False
   for packageInfo in packagesToInstall:
      package = packageInfo[NAME]
      packageFile = packageInfo[FILE]
      packageArch = packageInfo[ARCH]

      # If we are upgrading, and we previously installed a stonefs package
      # when we did not need it, we will have rpm dependency problems, so
      # uninstall them
      if platform.release() not in stonefsKernels:
         if isPackageInstalled("Stonewire.filesystem.sw", ""):
            logger.info( "Uninstalling Stonewire.filesystem.sw")
            uninstallPackage("Stonewire.filesystem.sw")

         if isPackageInstalled("Stonewire.filesystem.driver", ""):
            logger.info( "Uninstalling Stonewire.filesystem.driver")
            uninstallPackage("Stonewire.filesystem.driver")

         if isPackageInstalled("autodesk.stonewire.stonefs", ""):
            logger.info( "Uninstalling autodesk.stonewire.stonefs")
            uninstallPackage("autodesk.stonewire.stonefs")

      # Don't install switchable storage if it was NOT installed previously.
      if package == "Stonewire.switchablestorage.sw" and not isPackageInstalled("Stonewire.switchablestorage.sw", ""):
         logger.info( "Skipping Stonewire.switchablestorage.sw" )
         packagesToInstall.remove((package,packageFile,packageArch))

      if package == "autodesk.stonewire.base" and isPackageInstalled("Stonewire.base.sw", ""):
         logger.info( " Stonewire.base.sw" )
         uninstallPackage( "Stonewire.eoe.sw",
                           otherOptions = "--nodeps" )

      if package == "autodesk.stonewire.stonefs" and isPackageInstalled("Stonewire.switchablestorage.sw", ""):
         logger.info( "Uninstalling Stonewire.switchablestorage.sw" )
         uninstallPackage("Stonewire.switchablestorage.sw")

      if package == "backburner.sw.base" and isPackageInstalled("backburner_utils.sw.base", ""):
         logger.info( "Uninstalling backburner_utils.sw.base" )
         uninstallPackage("backburner_utils.sw.base")

      if package == "backburner.sw.base" and isPackageInstalled("backburner_server.sw.base", ""):
         logger.info( "Uninstalling backburner_server.sw.base" )
         uninstallPackage("backburner_server.sw.base")

      if package == "backburner.sw.base" and isPackageInstalled("backburner_manager.sw.base", ""):
         logger.info( "Uninstalling backburner_manager.sw.base" )
         uninstallPackage("backburner_manager.sw.base")

      if package == "dlfonts.sw.fonts" and isPackageInstalled("DLfonts.sw.fonts", ""):
         logger.info( "Uninstalling DLfonts.sw.fonts" )
         uninstallPackage("DLfonts.sw.fonts")

      if package == "dlfonts.sw.fonts" and isPackageInstalled("dlfonts.sw.fonts", ""):
         logger.info( "Uninstalling dlfonts.sw.fonts to upgrade" )
         uninstallPackage("dlfonts.sw.fonts",
                           otherOptions = "--nodeps")

      if package == "autodesk.wiretapcentral.server" and isPackageInstalled("wiretapcentral.sw.server", ""):
         logger.info( "Uninstalling wiretapcentral.sw.server to upgrade" )
         uninstallPackage("wiretapcentral.sw.server")
  
      if package == "linuxtools.sw.base" and isPackageInstalled("linuxtools.sw.base", ""):
         logger.info( "Uninstalling linuxtools.sw.base to upgrade" )
         uninstallPackage("linuxtools.sw.base", otherOptions = "--nodeps")

      # dlmpd used to package the 32 bits AND 64 bits in the same RPM!
      # Now, it only contains 64 bits RPMs, but when upgrading from an
      # older version, it will complain about dependencies to the old 32 bits
      # DSOs. So if there is any 32 bit stuff (from dlmpd) installed on the
      # system, simply remove the package altogether and it will be
      # re-installed 
      #
      if package == "dlmpd_core.sw.base" and isPackageInstalled("dlmpd_core.sw.base", ""):
         dlmpdFileList = executeCommand("rpm -ql %s" % package) 
         if len(dlmpdFileList) and not dlmpdRemoved:
            for dlmpdFile in dlmpdFileList:
               if dlmpdFile.find("lib32") >= 0:
                  logger.info( "Uninstalling dlmpd to resolve lib32 conflicts" )
                  uninstallPackage("dlmpd_admin.sw.base", "", "--nodeps")
                  uninstallPackage("dlmpd_sys.sw.base", "", "--nodeps")
                  uninstallPackage("dlmpd_core.sw.base", "", "--nodeps")
                  dlmpdRemoved = True                  

   # Seting environment variable to override default UID and GID
   if options.uid != None:
      os.environ["DL_EXITOP_UID"] = options.uid
   
   if options.gid != None:
      # Check if group exists
      import grp
      try:
         check = grp.getgrgid(int(options.gid))
      except KeyError:
         logger.error("ERROR: Group ID \"%s\" not found." % options.gid)
         logger.error("ERROR: The group must already exist.")
         logger.error("ERROR: If you need to create a new group, please use the command \"groupadd\"")
         sys.exit(1)
      os.environ["DL_EXITOP_GID"] = options.gid

   # Do it!
   success = proceedPackageList(packagesToInstall, options.force)

   # Post install: MIO / Afterburner script
   if "wine" in installed:
      executeCommand("wine echo")
   
   # Post install: restart S+W
   if "Stonewire.base.sw" in installed:
      os.system("/etc/init.d/stone+wire start >/dev/null 2>&1 </dev/null")

   # Install documentation in the application's folder.
   # Used to be inside the package, now outside so the
   # PDFs can be browsed directly from the CD and/or from a laptop.
   # Caveat: needs to figure out manually the location of application
   #
   # DISABLED FOR 2012:
   #  beta1 (and maybe beta2): doc help will be in a RPM package (smoke.doc)
   #  beta3 (and release): doc will be online (web based)
   #  A downloadable Help installer will be available (smoke.doc.help)
   #  Welcome screen will remain local (smoke.doc.welcome)
   # 
   #for packageInfo in packagesToInstall:
   for packageInfo in []:
      packageName = packageInfo[NAME]
      packageFile = packageInfo[FILE]
      packageArch = packageInfo[ARCH]
      # kludge, the ifffs packages have ".sw.ogl" in their name.
      index = packageName.find(".sw.ogl")
      if index == -1:
         # burn now has a different name.
         index = packageName.find("burn.sw.base")
      if index != -1:
         fileList = executeCommand("rpm -qlp " +  packageFile + " | head -2 | tail -1") 
         if len(fileList):
            appDir = os.path.dirname(fileList[0])
            if os.path.exists(appDir):
               dstDocDir = os.path.join(appDir, "documentation")
               srcDocDir = "documentation"
               if os.path.exists(srcDocDir):
                  # When the documentation for more than one product is provided,
                  # Ask which one to install.
                  if os.path.exists(os.path.join(srcDocDir, "inferno")) and \
                     os.path.exists(os.path.join(srcDocDir, "flame")):
                     if useGfx:
                        questionBox = QuestionPopup("Documentation", "Which documentation do you want to install?", [("Flame with Flare", "flame"), ("Inferno with Flare", "inferno")])
                        srcDocDir = os.path.join(srcDocDir, questionBox.ask())
                        
                  logger.info( "Installing documentation in %s" % dstDocDir )
                  distutils.dir_util.copy_tree(srcDocDir, dstDocDir)

                  # Change ownership of doc dir to be the same as the app dir.
                  uid = os.stat(appDir)[stat.ST_UID]
                  gid = os.stat(appDir)[stat.ST_GID]
                  if uid > 0:
                     os.chown(dstDocDir, uid, gid)

   

   if not success:
      sys.exit(2)

if __name__ == "__main__":
   main()

