/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * The contents of this file are subject to the Netscape Public License
 * Version 1.0 (the "NPL"); you may not use this file except in
 * compliance with the NPL.  You may obtain a copy of the NPL at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the NPL is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
 * for the specific language governing rights and limitations under the
 * NPL.
 *
 * The Initial Developer of this code under the NPL is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
 * Reserved.
 */
/*
 * SoftwareUpdate.java    1.0    96/12/19
 *
 */
package netscape.softupdate;

import java.net.*;
import java.util.Vector;
import java.util.Hashtable;
import java.lang.Thread;

import netscape.util.*;
import netscape.applet.AppletClassLoader;
import netscape.security.Principal;
import netscape.security.Target;
import netscape.security.UserTarget;
import netscape.security.PrivilegeManager;
import netscape.security.AppletSecurity;
import netscape.security.UserDialogHelper;
import netscape.security.ForbiddenTargetException;
import netscape.javascript.JSObject;

final public class SoftwareUpdate {

/* Security strategy
 * Each routine that does something unsafe checks the permissions there
 */
    // leave the numeric constants for backward compatibility!
    public static final int LIMITED_INSTALL= 0;
    public static final int FULL_INSTALL = 1;
    public static final int SILENT_INSTALL = 2;

    protected static final String IMPERSONATOR = "Impersonator";
    public static final String INSTALL_PRIV = "SoftwareInstall";
    public static final String SILENT_PRIV = "SilentInstall";
	public static final String FOLDER_FILE_URL = "File URL";

    /* Errors -200 to -300 */
    public static final int BAD_PACKAGE_NAME = -200;
    public static final int UNEXPECTED_ERROR = -201;
    public static final int ACCESS_DENIED = -202;
    public static final int TOO_MANY_CERTIFICATES = -203;  // Installer file must have 1 certificate
    public static final int NO_INSTALLER_CERTIFICATE = -204; // Installer file must have a certificate
    public static final int NO_CERTIFICATE =  -205; // Extracted file is not signed
    public static final int NO_MATCHING_CERTIFICATE = -206; // Extracted file does not match installer certificate
    public static final int UNKNOWN_JAR_FILE = -207;   // JAR file has not been opened
    public static final int INVALID_ARGUMENTS = -208;  // Bad arguments to a function
    public static final int ILLEGAL_RELATIVE_PATH = -209;  // Illegal relative path
    public static final int USER_CANCELLED = -210;     // User cancelled
    public static final int INSTALL_NOT_STARTED = -211;
    public static final int SILENT_MODE_DENIED = -212;
    public static final int NO_SUCH_COMPONENT = -213;  // no such component in the registry.
	public static final int FILE_DOES_NOT_EXIST = -214; //File cannot be deleted as it does not exist
	public static final int FILE_READ_ONLY = -215;	// File cannot be deleted as it is read only.
	public static final int FILE_IS_DIRECTORY = -216;	// File cannot be deleted as it is a directory
	public static final int NOT_IN_REGISTRY = -217;	// Component not found in registry
    public static final int APPLE_SINGLE_ERR = -218;   // error in AppleSingle unpacking

    public static final int SUCCESS = 0;
    public static final int REBOOT_NEEDED = 999;

	protected String packageName;	// Name	of the package we are installing
	protected String userPackageName;	// User-readable package name
	protected Vector installedFiles;	// List	of files/processes to be installed
    protected Hashtable patchList;      // files that have been patched (orig name is key)
	protected VersionInfo versionInfo;	// Component version info
    private FolderSpec packageFolder;   // default folder for package
	private ProgressMediator progress;  // Progress feedback
    private int userChoice;             // User feedback: -1 unknown, 0 is cancel, 1 is OK
    private boolean silent;        // Silent upgrade?
    private boolean force;              // Force install?
    private int lastError;              // the most recent non-zero error
    

	/**
	 * @param env   JavaScript environment (this inside the installer). Contains installer directives
	 * @param inUserPackageName Name of tha package installed. This name is displayed to the user
	 */
	public
	SoftwareUpdate( JSObject env, String inUserPackageName)
	{
		userPackageName	= inUserPackageName;
		installPrincipal	= null;
		packageName	= null;
		packageFolder = null;
		progress = null;
		patchList = new java.util.Hashtable();
		zigPtr = 0;
		userChoice = -1;
        lastError = 0;
		/* Need to verify that this is a SoftUpdate JavaScript object */
		VerifyJSObject(env);
		jarName = (String) env.getMember("src");
 		try
		{
    		silent = ((Boolean) env.getMember("silent")).booleanValue();
			force  = ((Boolean) env.getMember("force")).booleanValue();
	    }
	    catch (Throwable t)
	    {
    	    System.out.println("Unexpected throw getting silent/force" );
	        silent = false;
			force = false;
	    }
	}

	protected void
	finalize() throws Throwable
	{
		CleanUp();
		super.finalize();
	}

	/* VerifyJSObject
	 * Make sure that JSObject is of type SoftUpdate.
	 * Since SoftUpdate JSObject can only be created by C code
	 * we cannot be spoofed
	 */
	private	native void
	VerifyJSObject(JSObject obj);
    /*
     * Reads in the installer certificate, and creates its principal
     */
    private void
    InitializeInstallerCertificate() throws SoftUpdateException
    {
        Object[] certArray = null;

        certArray = getCertificates( zigPtr, installerJarName );

		if ( certArray == null || certArray.length == 0 )
			throw new SoftUpdateException( Strings.error_NoCertificate(), NO_INSTALLER_CERTIFICATE) ;

        if ( certArray.length > 1 )
            throw new SoftUpdateException( Strings.error_TooManyCertificates(), TOO_MANY_CERTIFICATES);

        installPrincipal = new netscape.security.Principal( Principal.CERT_KEY,
                                (byte[])certArray[0] );
    }

    /*
     * checks if our principal has privileges for silent install
     */
    private void
    CheckSilentPrivileges()
    {
        if (silent == false)
            return;
        try
        {
            /* Request impersonation privileges */
            netscape.security.PrivilegeManager privMgr;
            Target impersonation, target;

            privMgr = AppletSecurity.getPrivilegeManager();
            impersonation = Target.findTarget( IMPERSONATOR );
            privMgr.enablePrivilege( impersonation );

            /* check the security permissions */
            target = Target.findTarget( SILENT_PRIV );

            privMgr.checkPrivilegeGranted( target, GetPrincipal(), null );
        }
        catch(Throwable t)
        {
            System.out.println(Strings.error_SilentModeDenied());
            silent = false;
        }
    }

    /* Request the security privileges, so that the security dialogs
     * pop up
     */
    private void
    RequestSecurityPrivileges()
    {
        netscape.security.PrivilegeManager privMgr;
        Target impersonation, target;

        privMgr = AppletSecurity.getPrivilegeManager();
        impersonation = Target.findTarget( IMPERSONATOR );
        privMgr.enablePrivilege( impersonation );

        /* check the security permissions */
        target = Target.findTarget( INSTALL_PRIV );
        privMgr.enablePrivilege( target, GetPrincipal() );
    }

    protected final netscape.security.Principal 
    GetPrincipal()  {
        return installPrincipal;
    }

    protected final String
    GetUserPackageName()    {
        return userPackageName;
    }
    
    protected final String
    GetRegPackageName()    {
         return packageName;
    }

    protected final boolean
    GetSilent() {
        return silent;
    }
    
    /**
     * @return enumeration of InstallObjects
     */
    protected final java.util.Enumeration
    GetInstallQueue()   {
        if (installedFiles != null)
            return installedFiles.elements();
        else
            return null;
    }

    /**
     * @return  the most recent non-zero error code
     * @see ResetError
     */
    public int GetLastError()
    {
        return lastError;
    }

    /**
     * resets remembered error code to zero
     * @see GetLastError
     */
    public void ResetError()
    {
        lastError = 0;
    }

    /**
     * saves non-zero error codes so they can be seen by GetLastError()
     */
    private int saveError(int errcode)
    {
        if ( errcode != SUCCESS ) 
            lastError = errcode;
        return errcode;
    }



	/**
	 * @return the folder object suitable to be passed into AddSubcomponent
	 * @param folderID One of the predefined folder names
	 * @see AddSubcomponent
	 */
	public FolderSpec
	GetFolder(String folderID)
	{
		FolderSpec spec	= null;
		try
		{
			int	errCode	= 0;

			if ((folderID.compareTo("Installed") != 0) && (folderID.compareTo(FOLDER_FILE_URL) != 0))
			{
				spec = new FolderSpec(folderID,	packageName, userPackageName);
				if (folderID ==	"User Pick")	 //	Force the prompt
				{
					String ignore =	spec.GetDirectoryPath();
				}
			}
		}
		catch (Throwable e)
		{
			return null;
		}
		return spec;
	}


	/**
  	 * @return the full path to a component as it is known to the
	 *          Version Registry
	 * @param  component Version Registry component name
	 */
	public FolderSpec GetComponentFolder( String component )
	{
        String     dir;
        FolderSpec spec = null;

        dir = VersionRegistry.getDefaultDirectory( component );

        if ( dir == null )
        {
            dir = VersionRegistry.componentPath( component );
            if ( dir != null )
            {
                int i;

                if ((i = dir.lastIndexOf(java.lang.System.getProperty("file.separator"))) > 0)
                {
                        char c[] = new char[i];
                        dir.getChars(0, i, c, 0);
                        dir = new String( c );
                }
            }
        }

        if ( dir != null ) /* We have a directory */
        {
            spec = new FolderSpec("Installed", dir, userPackageName);
        }
        return spec;
	}

	public FolderSpec GetComponentFolder( String component, String subdir )
	{
        return GetFolder( GetComponentFolder( component ), subdir );
    }



    /**
     * sets the default folder for the package
     * @param folder a folder object obtained through GetFolder()
     */
    public void SetPackageFolder( FolderSpec folder )
    {
        packageFolder = folder;
    }


    /**
     * Returns a Windows Profile object if you're on windows,
     * null if you're not or if there's a security error
     */
    public Object
    GetWinProfile(FolderSpec folder, String file)
    {
        WinProfile  profile = null;
        String      osName = System.getProperty("os.name");

        try {
            if ( packageName == null ) {
                throw new SoftUpdateException(
                    Strings.error_WinProfileMustCallStart(), 
                    INSTALL_NOT_STARTED );
            }

            if ( osName.indexOf("Windows") >= 0 || osName.equals("Win32s") || 
                osName.equals("OS/2") ) 
            {
                profile = new WinProfile(this, folder, file);
            }
        }
        catch (Throwable e) {
            profile = null;
            e.printStackTrace();
        }

        return profile;
    }

    /**
     * @return an object for manipulating the Windows Registry.
     *          Null if you're not on windows
     */
    public Object GetWinRegistry()
    {
        WinReg      registry = null;
        String      osName = System.getProperty("os.name");

        try {
            if ( packageName == null )
                throw new SoftUpdateException(
                    Strings.error_WinProfileMustCallStart(),
                    INSTALL_NOT_STARTED);

            if ( osName.indexOf("Windows") >= 0 || osName.equals("Win32s")) {
                registry = new WinReg(this);
            }
        }
        catch (Throwable e) {
            registry = null;
            e.printStackTrace();
        }

        return registry;
    }

    /* JAR file manipulation */

    private String installerJarName;    // Name of the installer file
    private String jarName;             // Name of the JAR file
    private int zigPtr;                 // Stores the pointer to ZIG *
    private netscape.security.Principal installPrincipal; /* principal with the signature from the JAR file */


    /* Open/close the jar file
     */
    private native void OpenJARFile() throws SoftUpdateException;
    private native void CloseJARFile();

    /* getCertificates
     * native encapsulation that calls AppletClassLoader.getCertificates
     * we cannot call this method from Java because it is private.
     * The method cannot be made public because it is a security risk
     */
    private native Object[] getCertificates(int zigPtr, String inJarLocation);

    /**
     * extract the file out of the JAR directory and places it into temporary
     * directory.
     * two security checks:
     * - the certificate of the extracted file must match the installer certificate
     * - must have privileges to extract the jar file
     * @param inJarLocation  file name inside the JAR file
     */
    protected String
    ExtractJARFile( String inJarLocation, String finalFile ) throws SoftUpdateException,
                                                   netscape.security.AppletSecurityException
    {
        if (zigPtr == 0)
            throw new SoftUpdateException("JAR file has not been opened", UNKNOWN_JAR_FILE);

        /* Security checks */
        Target target;
        PrivilegeManager privMgr;
        privMgr = AppletSecurity.getPrivilegeManager();
        target = Target.findTarget( INSTALL_PRIV );

        privMgr.checkPrivilegeEnabled(target);

        /* Make sure that the certificate of the extracted file matches
            the installer certificate */
        /* XXX this needs to be optimized, so that we do not create a principal
            for every certificate */
        {
            int i;
            boolean haveMatch = false;
            Object[] certArray  = null;
            certArray = getCertificates( zigPtr, inJarLocation );
            if (certArray == null || certArray.length == 0)
            {
                throw new SoftUpdateException("Missing certificate for "
                    + inJarLocation, NO_CERTIFICATE);
            }

            for (i=0; i < certArray.length; i++)
            {
                netscape.security.Principal prin = new netscape.security.Principal( Principal.CERT_KEY,
                                                        (byte[])certArray[i] );
                if (installPrincipal.equals( prin ))
                {
                    haveMatch = true;
                    break;
                }
            }
            if (haveMatch == false)
                throw new SoftUpdateException(Strings.error_MismatchedCertificate() + inJarLocation,
                                            NO_MATCHING_CERTIFICATE);
        }

        /* Extract the file */
//      System.out.println("Extracting " + inJarLocation);
        String outExtractLocation;
        outExtractLocation = NativeExtractJARFile(inJarLocation, finalFile);
//      System.out.println("Extracted to " + outExtractLocation);
        return outExtractLocation;
   }

    private native String
    NativeExtractJARFile( String inJarLocation, String finalFile ) throws   SoftUpdateException;

    /**
     * Call this to initialize the update
     * Opens the jar file and gets the certificate of the installer
     * Opens up the gui, and asks for proper security privileges
     * @param vrPackageName     Full qualified  version registry name of the package
     *                          (ex: "/Plugins/Adobe/Acrobat")
     *                          null or empty package names are errors
     * @param inVInfo           version of the package installed.
     *                          Can be null, in which case package is installed
     *                          without a version. Having a null version, this package is
     *                          automatically updated in the future (ie. no version check is performed).
     * @param securityLevel     ignored (was LIMITED_INSTALL or FULL_INSTALL)
     */
    public int
    StartInstall(String vrPackageName,
                 VersionInfo inVInfo,
                 int securityLevel)
	{
        // ignore securityLevel
        return StartInstall( vrPackageName, inVInfo );
    }


    /**
     * An new form that doesn't require the security level
     */
    public int
    StartInstall(String vrPackageName, VersionInfo inVInfo )
    {
        int errcode = SUCCESS;
        ResetError();

		try
		{
			if ( (vrPackageName	== null) )
				throw new SoftUpdateException( Strings.error_BadPackageName() ,	INVALID_ARGUMENTS );

			packageName	= vrPackageName;
			while ( packageName.endsWith("/")) // Make sure that package name does not end with '/'
			{
				char c[] = new char[packageName.length() - 1];
				packageName.getChars(0,	packageName.length() -1, c,	0);
				packageName	= new String( c	);
                // System.out.println("Truncated package name is " + packageName);

			}
			versionInfo	= inVInfo;
			installedFiles = new java.util.Vector();

			/* JAR initalization */
			/* open	the	file, create a principal out of	installer file certificate */
			OpenJARFile();
            InitializeInstallerCertificate();
            CheckSilentPrivileges();
            RequestSecurityPrivileges();
            progress = new ProgressMediator(this);
            progress.StartInstall();

            // set up default package folder, if any
            String path = VersionRegistry.getDefaultDirectory( packageName );
            if ( path != null )
            {
                packageFolder = new FolderSpec("Installed", path, userPackageName);
            }
        }
		catch(SoftUpdateException e)
		{
			errcode = e.GetError();
		}
        catch(ForbiddenTargetException e)
        {
            errcode = ACCESS_DENIED;
        }
		catch(Exception	e)
		{
			e.printStackTrace();
			errcode = UNEXPECTED_ERROR;
		}

        saveError( errcode );
        return errcode;
	}

    /**
     * another StartInstall() simplification -- version as string
     */
    public int
    StartInstall(String vrPackageName, String inVer )
    {
        return StartInstall( vrPackageName, new VersionInfo( inVer ) );
    }




    /*
     * UI feedback
     */
    protected void
    UserCancelled()
    {
        userChoice = 0;
        AbortInstall();
    }

    protected void
    UserApproved()
    {
        userChoice = 1;
    }

    /**
     * Proper completion of the install
     * Copies all the files to the right place
     * returns 0 on success, <0 error code on failure
     */
    public int
    FinalizeInstall()
    {
        boolean rebootNeeded = false;
		int result = SUCCESS;
		try
		{
			if (packageName == null) //	probably didn't	call StartInstall()
            {
                throw new SoftUpdateException(
                    Strings.error_WinProfileMustCallStart(), 
                    INSTALL_NOT_STARTED );
            }

            // Wait for user approval
            progress.ConfirmWithUser();
            while ( userChoice == -1 )
                Thread.sleep(10);

            if (userChoice != 1)
            {
                AbortInstall();
                lastError = USER_CANCELLED;
                return USER_CANCELLED;
            }

            // Register default package folder if set
            if ( packageFolder != null )
            {
                VersionRegistry.setDefaultDirectory( packageName, 
                    packageFolder.toString() );
            }
            
            /* call Complete() on all the elements */
            /* If an error occurs in the middle, call Abort() on the rest */
            java.util.Enumeration   ve = GetInstallQueue();
            InstallObject   ie = null;
            
            try     // Main loop
            {
                VersionRegistry.uninstallCreate(packageName, userPackageName);
                while ( ve.hasMoreElements() )
                {
                    ie = (InstallObject)ve.nextElement();
                    try {
                        ie.Complete();
                    } 
                    catch (SoftUpdateException e) {
				        if (e.GetError() == REBOOT_NEEDED)
                            rebootNeeded = true;
                        else
                            throw e;
                    }
                }
            }
            catch (Throwable e)
            {   
			    if (ie != null)
                    ie.Abort();
 
                while (ve.hasMoreElements())
    			{
    			    try 
   				    {
   				        ie = (InstallObject)ve.nextElement();
   				        ie.Abort();
    				}
    				catch(Throwable t)
    				{
    				    // t.printStackTrace();
					}
                }
			    throw(e);   // Rethrow out of main loop
            }
            
            // add overall version for package
            if ( versionInfo != null )
            {
                result = VersionRegistry.installComponent(
                                    packageName, null, versionInfo );
            }
		}
		catch (SoftUpdateException e)
		{
			result = e.GetError();
		}
		catch (ForbiddenTargetException e)
		{
			result = ACCESS_DENIED;
		}
		catch (Throwable e)
		{
			e.printStackTrace();
			System.out.println( Strings.error_Unexpected() + " FinalizeInstall");
			result = UNEXPECTED_ERROR;
		}
		finally
		{
		    if ( installedFiles != null )
                installedFiles.removeAllElements();
            CleanUp();
        }

        if (result == SUCCESS && rebootNeeded)
            result = REBOOT_NEEDED;

        saveError( result );
        return result;
}

	/**
	 * Aborts the install :), cleans up all the installed files
	 */
	public synchronized void AbortInstall()
	{
		try
		{
			if (installedFiles != null)
			{
	   			java.util.Enumeration	ve = GetInstallQueue();
    			while (	ve.hasMoreElements() )
    			{
    		    	InstallObject	ie;
    				ie = (InstallObject)ve.nextElement();
    				try // We just want to loop through all of them if possible
    				{
						ie.Abort();
    			    }
    			    catch (Throwable t)
    			    {
				    }
    			}	    
				installedFiles.removeAllElements();
				installedFiles = null;
			}
			CloseJARFile();
    	}
		catch (Throwable e)
		{
			System.out.println( Strings.error_Unexpected() + " AbortInstall");
		    e.printStackTrace();
		}
		finally
		{
		    try {
	    	    CleanUp();
	        }
	        catch (Throwable e) {
	            e.printStackTrace();
	        }
		}
	}

	/*
	 * CleanUp
	 * call	it when	done with the install
	 *
	 */
	private	synchronized void CleanUp()
	{
        if (progress != null)
            progress.Complete();
        
        CloseJARFile();
        progress = null;
        zigPtr = 0;
        if ( installedFiles != null )
        {
//            if (installedFiles.size() != 0)
//                System.err.println("Installed files are not empty!");
            java.util.Enumeration   ve = GetInstallQueue();
            while ( ve.hasMoreElements() )
            {
                InstallObject   ie;
                ie = (InstallObject)ve.nextElement();
                ie.Abort();
            }       
            installedFiles.removeAllElements();
        }
        installedFiles = null;
        packageName = null;  // used to see if StartInstall() has been called
    }

    /**
     * ScheduleForInstall
     * call this to put an InstallObject on the install queue
     * Do not call installedFiles.addElement directly, because this routine also handles
     * progress messages
     */
    protected void ScheduleForInstall(InstallObject ob) throws SoftUpdateException
    {
        // flash current item
        if (progress != null)
            progress.SetStatus( ob );

        // do any unpacking or other set-up
        ob.Prepare();


        // Add to installation list if we haven't thrown out
        installedFiles.addElement( ob );
        if (progress != null)
            progress.ScheduleForInstall( ob );
    }

    /**
     * GetQualifiedRegName
     *
     * This routine converts a package-relative component registry name
     * into a full name that can be used in calls to the version registry.
     */
    private String GetQualifiedRegName( String name )
    {
        String packagePrefix = (packageName.length() == 0) ? "" : packageName+"/" ;

        if (name.toUpperCase().startsWith("=COMM=/"))
            // bug 66403: JRI_GetStringUTFChars() returns bad string in native
            // code after a substring() method call (str looks OK in java!)
            // name = name.substring(7);
            name = (name.substring(7)+"===").substring(0,name.length()-7);
        else if (name.charAt(0) != '/')   // Relative package path
            name = packagePrefix + name;

        return name;
    }

    /**
     * Extract  a file from JAR archive to the disk, and update the
     * version registry. Actually, keep a record of elements to be installed. FinalizeInstall()
     * does the real installation. Install elements are accepted if they meet one of the
     * following criteria:
     *  1) There is no entry for this subcomponnet in the registry
     *  2) The subcomponent version info is newer than the one installed
     *  3) The subcomponent version info is null
     *
     * @param name      path of the package in the registry. Can be:
     *                    absolute: "/Plugins/Adobe/Acrobat/Drawer.exe"
     *                    relative: "Drawer.exe". Relative paths are relative to main package name
     *                    null:   if null jarLocation is assumed to be the relative path
     * @param version   version of the subcomponent. Can be null
     * @param jarSource location of the file to be installed inside JAR
     * @param folderSpec one of the predefined folder   locations
     * @see GetFolder
     * @param relativePath  where the file should be copied to on the local disk.
     *                      Relative to folder
     *                      if null, use the path to the JAR source.
	 * @param forceInstall  if true, file is always replaced
	 */
	public int AddSubcomponent(String name,
					VersionInfo	version,
					String jarSource,
					FolderSpec	folderSpec,
					String relativePath,
					boolean forceInstall)
	{
		InstallFile	ie;
		int	  result = SUCCESS;
		try
		{
            if ( jarSource == null || jarSource.length() == 0 || folderSpec == null )
                throw (new SoftUpdateException( "", INVALID_ARGUMENTS ));

			if (packageName == null) {
                // probably didn't call StartInstall()
                throw new SoftUpdateException( Strings.error_BadPackageNameAS(),
                                               BAD_PACKAGE_NAME);
            }

            if ((name == null) || (name.length() == 0)) {
                // Default subName = location in jar file
                name = GetQualifiedRegName( jarSource );
            }
            else
                name = GetQualifiedRegName( name );

			if ( (relativePath == null)	|| (relativePath.length() == 0))
				relativePath = jarSource;

		/* Check for existence of the newer	version	*/

			boolean	versionNewer = false;
			if ( (forceInstall == false ) &&
			     (version !=  null)	&&
			     ( VersionRegistry.validateComponent( name ) == 0 ) )
			{
				VersionInfo	oldVersion = VersionRegistry.componentVersion(name);
				if ( version.compareTo( oldVersion ) > 0 )
					versionNewer = true;
			}
			else
				versionNewer = true;

		   if (versionNewer)
		   {
			    ie = new InstallFile( this, name, version, jarSource,
                        folderSpec, relativePath, forceInstall );
				ScheduleForInstall( ie );
			}
		}
		catch (SoftUpdateException e)
		{
			result = e.GetError();
		}
		catch (ForbiddenTargetException e)
		{
			result = ACCESS_DENIED;
		}
		catch (Throwable e)
		{
			System.out.println(Strings.error_Unexpected() + "AddSubcomponent");
			e.printStackTrace();
			result = UNEXPECTED_ERROR;
		}

        saveError( result );
		return result;
	}

	
    /** 
     * Various alternate flavors of AddSubcomponent()
     */

    public int AddSubcomponent( String jarSource )
    {
        return AddSubcomponent( "", versionInfo, jarSource, packageFolder, "", force );
    }
    
    public int AddSubcomponent( String name, String jarSource )
    {
        return AddSubcomponent( name, versionInfo,  jarSource, packageFolder, "", force );
    }
    
    public int AddSubcomponent( String regname, String jarSource, boolean forceInstall )
    {
        return AddSubcomponent( regname, versionInfo, jarSource, packageFolder, "", forceInstall );
    }
    
    public int AddSubcomponent( String regname, VersionInfo version, String jarSource )
    {
        return AddSubcomponent( regname, version, jarSource, packageFolder, "", force );
    }
    
    public int AddSubcomponent( String regname, VersionInfo version, String jarSource, boolean forceInstall )
    {
        return AddSubcomponent( regname, version, jarSource, packageFolder, "", forceInstall );
    }
   
    public int AddSubcomponent( String regname, String jarSource, String relativePath )
    {
        return AddSubcomponent( regname, versionInfo, jarSource, packageFolder, relativePath, force );
    }
 
    public int AddSubcomponent( String regname, String jarSource, String relativePath, boolean forceInstall )
    {
        return AddSubcomponent( regname, versionInfo, jarSource, packageFolder, relativePath, forceInstall );
    }
 
    public int AddSubcomponent( String regname, String jarSource, FolderSpec folderSpec, String relativePath )
    {
        return AddSubcomponent( regname, versionInfo, jarSource, folderSpec, relativePath, force );
    }
    
    public int AddSubcomponent( String regname, String jarSource, FolderSpec folderSpec, String relativePath, 
                                boolean forceInstall )
    {
        return AddSubcomponent( regname, versionInfo,  jarSource, folderSpec, relativePath, forceInstall );
    }
    
    public int AddSubcomponent( String regname, VersionInfo version, String jarSource, FolderSpec folderSpec, 
                                String relativePath )
    {
        return AddSubcomponent( regname, version, jarSource, folderSpec, relativePath, force );
    }

	/**
     * executes the file
     * @param jarSource     name of the file to execute inside JAR archive
     * @param args          command-line argument string (Win/Unix only)
     */
    public int
    Execute(String jarSource)
    {
        return Execute( jarSource, null );
    }

    	public int
	Execute(String jarSource, String args)
	{
        int errcode = SUCCESS;

		try
		{
			InstallExecute ie =	new	InstallExecute(	this, jarSource, args);
			ScheduleForInstall( ie );
		}
		catch (SoftUpdateException e)
		{
			errcode = e.GetError();
		}
		catch (ForbiddenTargetException e)
		{
			errcode = ACCESS_DENIED;
		}
		catch (Throwable e)
		{
			e.printStackTrace();
			System.out.println(Strings.error_Unexpected() + "Execute");
			errcode = UNEXPECTED_ERROR;
		}

        saveError( errcode );
		return errcode;
	}

   /**
     * Mac-only, simulates Mac toolbox Gestalt function
     * OSErr Gestalt(String selector, long * response)
     * @param selector      4-character string, 
     * @return      2 item array. 1st item corresponds to OSErr, 
     *                       2nd item corresponds to response
     */
    public int[] Gestalt(String selector)
    {
        int a[] = new int[2];
        a[0] = 0;
        a[1] = 0;
        try
        {
            a[1] = NativeGestalt(selector);
        }
        catch (SoftUpdateException s)
        {
            a[0] = s.GetError();
        }
        catch (Throwable t)
        {
            a[0] = -1;
            System.out.println(Strings.error_Unexpected() + "Gestalt");
        }
        return a;
    }
    
	private native int
    NativeGestalt(String selector) throws SoftUpdateException;



    /**
     * Patch
     *
     */

    public int Patch( String regName, VersionInfo version, String patchname )
    {
        int errcode = SUCCESS;

        if ( regName == null || patchname == null )
            return saveError( INVALID_ARGUMENTS );

        String rname = GetQualifiedRegName( regName );

        try {
            InstallPatch ip = new InstallPatch(this, rname, version, patchname);
            ScheduleForInstall( ip );
        }
        catch (SoftUpdateException e) {
            errcode = e.GetError();
        }
        catch (Throwable e)
        {
            System.out.println(Strings.error_Unexpected() + "Patch");
            e.printStackTrace();
            errcode = UNEXPECTED_ERROR;
        }
        saveError( errcode );
        return errcode;
    }

    public int Patch( String regName, String patchname )
    {
        return Patch( regName, versionInfo, patchname );
    }

    public int
    Patch( String regName, VersionInfo version, String patchname,
            FolderSpec folder, String filename )
    {
        if ( folder == null || regName == null || regName.length() == 0 || patchname == null )
            return saveError( INVALID_ARGUMENTS );

        int errcode = SUCCESS;

        try {
            String rname = GetQualifiedRegName( regName );

            InstallPatch ip = new InstallPatch( this, rname, version,
                                                patchname, folder, filename );
            ScheduleForInstall( ip );
        }
        catch (SoftUpdateException e) {
            errcode = e.GetError();
        }
        catch (Throwable e) {
            System.out.println(Strings.error_Unexpected() + "Patch");
            e.printStackTrace();
            errcode = UNEXPECTED_ERROR;
        }
        saveError( errcode );
        return errcode;
    }

    public int
    Patch( String regName, String patchname, FolderSpec folder, String file )
    {
        return Patch( regName, versionInfo, patchname, folder, file );
    }
    
	private native int NativeMakeDirectory (String path);

	/**
	 * This method deletes the specified file from the disk. It does not look
	 * for the file in the registry and is guaranteed to muddle any uninstall 
	 * reference counting.  Its main purpose is to delete files installed or 
	 * created outside of SmartUpdate.
     */
	public int DeleteFile(FolderSpec folder, String relativeFileName)
	{
        int errcode = SUCCESS;

		try
		{
			InstallDelete id =	new	InstallDelete(	this, folder, relativeFileName	);
			ScheduleForInstall( id );
		}
		catch (SoftUpdateException e)
		{
			errcode = e.GetError();
			if (errcode == FILE_DOES_NOT_EXIST) {
				errcode = SUCCESS;
			}
		}
		catch (netscape.security.AppletSecurityException e)
		{
			errcode = ACCESS_DENIED;
		}
		catch (Throwable e)
		{
			e.printStackTrace();
			errcode = UNEXPECTED_ERROR;
		}

        saveError( errcode );
		return errcode;
	}


	/**
	 * This method finds named registry component and deletes both the file and the 
	 * entry in the Client VR. registryName is the name of the component in the registry.
     * Returns usual errors codes + code to indicate item doesn't exist in registry, registry 
     * item wasn't a file item, or the related file doesn't exist. If the file is in use we will
     * store the name and to try to delete it on subsequent start-ups until we're successful.
     */
	public int DeleteComponent(String registryName)
    {
		int errcode = SUCCESS;

		try
		{
			InstallDelete id =	new	InstallDelete(	this, registryName	);
			ScheduleForInstall( id );
		}
		catch (SoftUpdateException e)
		{
			errcode = e.GetError();
			if (errcode == FILE_DOES_NOT_EXIST) {
				errcode = SUCCESS;
			}
		}
		catch (netscape.security.AppletSecurityException e)
		{
			errcode = ACCESS_DENIED;
		}
		catch (Throwable e)
		{
			e.printStackTrace();
			errcode = UNEXPECTED_ERROR;
		}
		if (errcode == FILE_DOES_NOT_EXIST)
		{
			errcode = SUCCESS;
		}
        saveError( errcode );
		return errcode;
    }
    
	    
    public FolderSpec GetFolder( String targetFolder, String subdirectory )
    {
		if (targetFolder.compareTo(FOLDER_FILE_URL) == 0)
		{
			String newPath;
		    String path = NativeFileURLToNative("/"+subdirectory );
            if ( path != null )
			{
				if (path.endsWith(System.getProperty("file.separator")))
				{
					return new FolderSpec("Installed", path, userPackageName);
				} 
				else
				{
					newPath = path + System.getProperty("file.separator");
					return new FolderSpec("Installed", newPath, userPackageName);
				}
			}
            else 
                return null;
        }
        else 
            return GetFolder( GetFolder(targetFolder), subdirectory );
    }
    
	private native long NativeDiskSpaceAvailable (String path);
	private native String NativeFileURLToNative (String path);
    private native String[] ExtractDirEntries(String Dir);

    public FolderSpec GetFolder( FolderSpec folder, String subdir )
    {
        FolderSpec spec = null;
        String     path;
        String     newPath;

        if ( subdir == null || subdir.length() == 0 )
        {
            // no subdir, return what we were passed
            spec = folder;
        }
        else if ( folder != null )
        {
            try { 
                path = folder.MakeFullPath( subdir );
            } catch (Exception e ) {
                path = null;
            }

            if (path != null)
            {
				if (path.endsWith(System.getProperty("file.separator")))
				{
					spec = new FolderSpec("Installed", path, userPackageName);
				}
				else
				{
					newPath = path + System.getProperty("file.separator");
					spec = new FolderSpec("Installed", newPath, userPackageName);
				}
            }
        }
        return spec;
    }

   
	
    /**
	 * This method returns true if there is enough free diskspace, false if there isn't.
     * The drive containg the folder is checked for # of free bytes.
     */
	public long DiskSpaceAvailable( FolderSpec folder )
    {
        String path = folder.toString();

        if (path == null)
			return 0;

		return NativeDiskSpaceAvailable (path);
    }


    /**
     * This method is used to install an entire subdirectory of the JAR. 
     * Any files so installed are individually entered into the registry so they
     * can be uninstalled later. Like AddSubcomponent the directory is installed
     * only if the version specified is greater than that already registered,
     * or if the force parameter is true.The final version presented is the complete
     * form, the others are for convenience and assume values for the missing
     * arguments.
     */
        
        
    public int AddDirectory(  String name, 
                                VersionInfo version, 
                                String jarSource,
                                FolderSpec folderSpec, 
                                String subdir, 
                                boolean forceInstall)
    
    {
        InstallFile ie;
        int   result = SUCCESS;
        try
        {
            if ( jarSource == null || jarSource.length() == 0 || folderSpec == null )
                throw (new SoftUpdateException( "", INVALID_ARGUMENTS ));

            if (packageName == null) {
                // probably didn't call StartInstall()
                throw (new SoftUpdateException( Strings.error_BadPackageNameAS(), 
                    BAD_PACKAGE_NAME));
            }
    
            if ((name == null) || (name.length() == 0)) {
                // Default subName = location in jar file
                name = GetQualifiedRegName( jarSource );
            }
            else 
                name = GetQualifiedRegName( name );

            if ( subdir == null ) {
                subdir = "";
            }
            else if ( subdir.length() != 0 ) {
                subdir += "/";
            }


            System.out.println("AddDirectory " + name 
                + " from " + jarSource + " into " + 
                folderSpec.MakeFullPath(subdir));
         

            String[] matchingFiles = ExtractDirEntries( jarSource );
            int i;
            boolean bInstall;
            VersionInfo oldVer;
             
            for(i=0; i< matchingFiles.length;i++)
            {
                String fullRegName = name + "/" + matchingFiles[i];

                if ( (forceInstall == false) && (version != null) &&
                    (VersionRegistry.validateComponent(fullRegName) == 0) )
                {
                    // Only install if newer
                    oldVer = VersionRegistry.componentVersion(fullRegName);
                    bInstall = ( version.compareTo(oldVer) > 0 );
                }
                else {
                    // file doesn't exist or "forced" install
                    bInstall = true;
                }

                if ( bInstall ) {
                    ie = new InstallFile( this, 
                                fullRegName, 
                                version,
                                jarSource + "/" + matchingFiles[i], 
                                (FolderSpec) folderSpec, 
                                subdir + matchingFiles[i], 
                                forceInstall );

                    ScheduleForInstall( ie );
                }
             }        
      }
      catch (SoftUpdateException e)
      {
          result = e.GetError();
      }
      catch (ForbiddenTargetException e)
      {
          result = ACCESS_DENIED;
      }
      catch (Throwable e)
      {
          System.out.println(Strings.error_Unexpected() + "AddDirectory");
          result = UNEXPECTED_ERROR;
      }
      
      saveError( result );
      return (result);
    }

    // Alternate forms of AddDirectory()

    public int AddDirectory( String jarSource )
    {
        return AddDirectory( "", versionInfo, jarSource, packageFolder, 
                    "", force );
    }

    public int AddDirectory( String regName, String jarSource )
    {
        return AddDirectory( regName, versionInfo, jarSource, packageFolder,
                    "", force );
    }

    public int AddDirectory( String regName, String jarSource, boolean forceMode )
    {
        return AddDirectory( regName, versionInfo, jarSource, packageFolder,
                    "", forceMode );
    }

    public int AddDirectory( String regName, VersionInfo version, String jarSource)
    {
        return AddDirectory( regName, version, jarSource, packageFolder,
                    "", force );
    }

    public int AddDirectory( String regName, VersionInfo version, String jarSource,
                             boolean forceMode )
    {
        return AddDirectory( regName, version, jarSource, packageFolder, 
                    "", forceMode );
    }

    public int AddDirectory( String regName, String jarSource, 
                             FolderSpec folder, String subdir )
    {
        return AddDirectory( regName, versionInfo, jarSource, folder, subdir, force );
    }

    public int AddDirectory( String regName, String jarSource,
                             FolderSpec folder, String subdir, boolean forceMode )
    {
        return AddDirectory( regName, versionInfo, jarSource, folder, subdir, forceMode );
    }

    public int AddDirectory( String regName, VersionInfo version, String jarSource,
                             FolderSpec folder, String subdir )
    {
        return AddDirectory( regName, version, jarSource, folder, subdir, force );
    }


    // Uninstall

    public int Uninstall(String packageName)
    {
        return NativeUninstall(packageName);
    }

    private native int NativeUninstall (String packageName);

}

