/* * $Id$ * IzPack - Copyright 2001-2008 Julien Ponge, All Rights Reserved. * * http://izpack.org/ * http://izpack.codehaus.org/ * * Copyright 2001 Johannes Lehtinen * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.izforge.izpack.installer; import com.izforge.izpack.*; import com.izforge.izpack.event.InstallerListener; import com.izforge.izpack.util.*; import java.io.*; import java.lang.reflect.Constructor; import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.jar.Pack200; /** * Unpacker class. * * @author Julien Ponge * @author Johannes Lehtinen */ public class Unpacker extends UnpackerBase { private static final String tempSubPath = "/IzpackWebTemp"; private Pack200.Unpacker unpacker; /** * The constructor. * * @param idata The installation data. * @param handler The installation progress handler. */ public Unpacker(AutomatedInstallData idata, AbstractUIProgressHandler handler) { super(idata, handler); } /* (non-Javadoc) * @see com.izforge.izpack.installer.IUnpacker#run() */ public void run() { addToInstances(); try { // // Initialisations FileOutputStream out = null; ArrayList<ParsableFile> parsables = new ArrayList<ParsableFile>(); ArrayList<ExecutableFile> executables = new ArrayList<ExecutableFile>(); ArrayList<UpdateCheck> updatechecks = new ArrayList<UpdateCheck>(); List packs = idata.selectedPacks; int npacks = packs.size(); handler.startAction("Unpacking", npacks); udata = UninstallData.getInstance(); // Custom action listener stuff --- load listeners ---- List[] customActions = getCustomActions(); // Custom action listener stuff --- beforePacks ---- informListeners(customActions, InstallerListener.BEFORE_PACKS, idata, npacks, handler); packs = idata.selectedPacks; npacks = packs.size(); // We unpack the selected packs for (int i = 0; i < npacks; i++) { // We get the pack stream //int n = idata.allPacks.indexOf(packs.get(i)); Pack p = (Pack) packs.get(i); // evaluate condition if (p.hasCondition()) { if (rules != null) { if (!rules.isConditionTrue(p.getCondition())) { // skip pack, condition is not fullfilled. continue; } } else { // TODO: skip pack, because condition can not be checked } } // Custom action listener stuff --- beforePack ---- informListeners(customActions, InstallerListener.BEFORE_PACK, packs.get(i), npacks, handler); ObjectInputStream objIn = new ObjectInputStream(getPackAsStream(p.id, p.uninstall)); // We unpack the files int nfiles = objIn.readInt(); // We get the internationalized name of the pack final Pack pack = ((Pack) packs.get(i)); String stepname = pack.name;// the message to be passed to the // installpanel if (langpack != null && !(pack.id == null || "".equals(pack.id))) { final String name = langpack.getString(pack.id); if (name != null && !"".equals(name)) { stepname = name; } } handler.nextStep(stepname, i + 1, nfiles); for (int j = 0; j < nfiles; j++) { // We read the header PackFile pf = (PackFile) objIn.readObject(); // TODO: reaction if condition can not be checked if (pf.hasCondition() && (rules != null)) { if (!rules.isConditionTrue(pf.getCondition())) { if (!pf.isBackReference()){ // skip, condition is not fulfilled objIn.skip(pf.length()); } continue; } } if (OsConstraint.oneMatchesCurrentSystem(pf.osConstraints())) { // We translate & build the path String path = IoHelper.translatePath(pf.getTargetPath(), vs); File pathFile = new File(path); File dest = pathFile; if (!pf.isDirectory()) { dest = pathFile.getParentFile(); } if (!dest.exists()) { // If there are custom actions which would be called // at // creating a directory, create it recursively. List fileListeners = customActions[customActions.length - 1]; if (fileListeners != null && fileListeners.size() > 0) { mkDirsWithEnhancement(dest, pf, customActions); } else // Create it in on step. { if (!dest.mkdirs()) { handler.emitError("Error creating directories", "Could not create directory\n" + dest.getPath()); handler.stopAction(); this.result = false; return; } } } if (pf.isDirectory()) { continue; } // Custom action listener stuff --- beforeFile ---- informListeners(customActions, InstallerListener.BEFORE_FILE, pathFile, pf, null); // We add the path to the log, udata.addFile(path, pack.uninstall); handler.progress(j, path); // if this file exists and should not be overwritten, // check // what to do if ((pathFile.exists()) && (pf.override() != PackFile.OVERRIDE_TRUE)) { boolean overwritefile = false; // don't overwrite file if the user said so if (pf.override() != PackFile.OVERRIDE_FALSE) { if (pf.override() == PackFile.OVERRIDE_TRUE) { overwritefile = true; } else if (pf.override() == PackFile.OVERRIDE_UPDATE) { // check mtime of involved files // (this is not 100% perfect, because the // already existing file might // still be modified but the new installed // is just a bit newer; we would // need the creation time of the existing // file or record with which mtime // it was installed...) overwritefile = (pathFile.lastModified() < pf.lastModified()); } else { int def_choice = -1; if (pf.override() == PackFile.OVERRIDE_ASK_FALSE) { def_choice = AbstractUIHandler.ANSWER_NO; } if (pf.override() == PackFile.OVERRIDE_ASK_TRUE) { def_choice = AbstractUIHandler.ANSWER_YES; } int answer = handler.askQuestion(idata.langpack .getString("InstallPanel.overwrite.title") + " - " + pathFile.getName(), idata.langpack .getString("InstallPanel.overwrite.question") + pathFile.getAbsolutePath(), AbstractUIHandler.CHOICES_YES_NO, def_choice); overwritefile = (answer == AbstractUIHandler.ANSWER_YES); } } if (!overwritefile) { if (!pf.isBackReference() && !((Pack) packs.get(i)).loose) { objIn.skip(pf.length()); } continue; } } // We copy the file InputStream pis = objIn; if (pf.isBackReference()) { InputStream is = getPackAsStream(pf.previousPackId, pack.uninstall); pis = new ObjectInputStream(is); // must wrap for blockdata use by objectstream // (otherwise strange result) // skip on underlaying stream (for some reason not // possible on ObjectStream) is.skip(pf.offsetInPreviousPack - 4); // but the stream header is now already read (== 4 // bytes) } else if (((Pack) packs.get(i)).loose) { /* Old way of doing the job by using the (absolute) sourcepath. * Since this is very likely to fail and does not confirm to the documentation, * prefer using relative path's pis = new FileInputStream(pf.sourcePath); */ File resolvedFile = new File(getAbsolutInstallSource(), pf .getRelativeSourcePath()); if (!resolvedFile.exists()) { //try alternative destination - the current working directory //user.dir is likely (depends on launcher type) the current directory of the executable or jar-file... final File userDir = new File(System.getProperty("user.dir")); resolvedFile = new File(userDir, pf.getRelativeSourcePath()); } if (resolvedFile.exists()) { pis = new FileInputStream(resolvedFile); //may have a different length & last modified than we had at compiletime, therefore we have to build a new PackFile for the copy process... pf = new PackFile(resolvedFile.getParentFile(), resolvedFile, pf.getTargetPath(), pf.osConstraints(), pf.override(), pf.getAdditionals()); } else { //file not found //issue a warning (logging api pending) //since this file was loosely bundled, we continue with the installation. System.out.println("Could not find loosely bundled file: " + pf.getRelativeSourcePath()); out.close(); continue; } } if (pf.isPack200Jar()) { int key = objIn.readInt(); InputStream pack200Input = Unpacker.class.getResourceAsStream("/packs/pack200-" + key); Pack200.Unpacker unpacker = getPack200Unpacker(); java.util.jar.JarOutputStream jarOut = new java.util.jar.JarOutputStream(new FileOutputStream(pathFile)); unpacker.unpack(pack200Input, jarOut); jarOut.close(); } else { out = new FileOutputStream(pathFile); byte[] buffer = new byte[5120]; long bytesCopied = 0; while (bytesCopied < pf.length()) { if (performInterrupted()) { // Interrupt was initiated; perform it. out.close(); if (pis != objIn) { pis.close(); } return; } int maxBytes = (int) Math.min(pf.length() - bytesCopied, buffer.length); int bytesInBuffer = pis.read(buffer, 0, maxBytes); if (bytesInBuffer == -1) { throw new IOException("Unexpected end of stream (installer corrupted?)"); } out.write(buffer, 0, bytesInBuffer); bytesCopied += bytesInBuffer; } out.close(); } if (pis != objIn) { pis.close(); } // Set file modification time if specified if (pf.lastModified() >= 0) { pathFile.setLastModified(pf.lastModified()); } // Custom action listener stuff --- afterFile ---- informListeners(customActions, InstallerListener.AFTER_FILE, pathFile, pf, null); } else { if (!pf.isBackReference()) { objIn.skip(pf.length()); } } } // Load information about parsable files int numParsables = objIn.readInt(); for (int k = 0; k < numParsables; k++) { ParsableFile pf = (ParsableFile) objIn.readObject(); if (pf.hasCondition() && (rules != null)) { if (!rules.isConditionTrue(pf.getCondition())) { // skip, condition is not fulfilled continue; } } pf.path = IoHelper.translatePath(pf.path, vs); parsables.add(pf); } // Load information about executable files int numExecutables = objIn.readInt(); for (int k = 0; k < numExecutables; k++) { ExecutableFile ef = (ExecutableFile) objIn.readObject(); if (ef.hasCondition() && (rules != null)) { if (!rules.isConditionTrue(ef.getCondition())) { // skip, condition is false continue; } } ef.path = IoHelper.translatePath(ef.path, vs); if (null != ef.argList && !ef.argList.isEmpty()) { String arg = null; for (int j = 0; j < ef.argList.size(); j++) { arg = ef.argList.get(j); arg = IoHelper.translatePath(arg, vs); ef.argList.set(j, arg); } } executables.add(ef); if (ef.executionStage == ExecutableFile.UNINSTALL) { udata.addExecutable(ef); } } // Custom action listener stuff --- uninstall data ---- handleAdditionalUninstallData(udata, customActions); // Load information about updatechecks int numUpdateChecks = objIn.readInt(); for (int k = 0; k < numUpdateChecks; k++) { UpdateCheck uc = (UpdateCheck) objIn.readObject(); updatechecks.add(uc); } objIn.close(); if (performInterrupted()) { // Interrupt was initiated; perform it. return; } // Custom action listener stuff --- afterPack ---- informListeners(customActions, InstallerListener.AFTER_PACK, packs.get(i), i, handler); } // We use the scripts parser ScriptParser parser = new ScriptParser(parsables, vs); parser.parseFiles(); if (performInterrupted()) { // Interrupt was initiated; perform it. return; } // We use the file executor FileExecutor executor = new FileExecutor(executables); if (executor.executeFiles(ExecutableFile.POSTINSTALL, handler) != 0) { handler.emitError("File execution failed", "The installation was not completed"); this.result = false; } if (performInterrupted()) { // Interrupt was initiated; perform it. return; } // We put the uninstaller (it's not yet complete...) putUninstaller(); // update checks _after_ uninstaller was put, so we don't delete it performUpdateChecks(updatechecks); if (performInterrupted()) { // Interrupt was initiated; perform it. return; } // Custom action listener stuff --- afterPacks ---- informListeners(customActions, InstallerListener.AFTER_PACKS, idata, handler, null); if (performInterrupted()) { // Interrupt was initiated; perform it. return; } // write installation information writeInstallationInformation(); // The end :-) handler.stopAction(); } catch (Exception err) { // TODO: finer grained error handling with useful error messages handler.stopAction(); if ("Installation cancelled".equals(err.getMessage())) { handler.emitNotification("Installation cancelled"); } else { handler.emitError("An error occured", err.getMessage()); err.printStackTrace(); } this.result = false; Housekeeper.getInstance().shutDown(4); } finally { removeFromInstances(); } } private Pack200.Unpacker getPack200Unpacker() { if (unpacker == null) { unpacker = Pack200.newUnpacker(); } return unpacker; } /** * Returns a stream to a pack, location depending on if it's web based. * * @param uninstall true if pack must be uninstalled * @return The stream or null if it could not be found. * @throws Exception Description of the Exception */ private InputStream getPackAsStream(String packid, boolean uninstall) throws Exception { InputStream in = null; String webDirURL = idata.info.getWebDirURL(); packid = "-" + packid; if (webDirURL == null) // local { in = Unpacker.class.getResourceAsStream("/packs/pack" + packid); } else // web based { // TODO: Look first in same directory as primary jar // This may include prompting for changing of media // TODO: download and cache them all before starting copy process // See compiler.Packager#getJarOutputStream for the counterpart String baseName = idata.info.getInstallerBase(); String packURL = webDirURL + "/" + baseName + ".pack" + packid + ".jar"; String tf = IoHelper.translatePath(idata.info.getUninstallerPath()+ Unpacker.tempSubPath, vs); String tempfile; try { tempfile = WebRepositoryAccessor.getCachedUrl(packURL, tf); udata.addFile(tempfile, uninstall); } catch (Exception e) { if ("Cancelled".equals(e.getMessage())) { throw new InstallerException("Installation cancelled", e); } else { throw new InstallerException("Installation failed", e); } } URL url = new URL("jar:" + tempfile + "!/packs/pack" + packid); //URL url = new URL("jar:" + packURL + "!/packs/pack" + packid); // JarURLConnection jarConnection = (JarURLConnection) // url.openConnection(); // TODO: what happens when using an automated installer? in = new WebAccessor(null).openInputStream(url); // TODO: Fails miserably when pack jars are not found, so this is // temporary if (in == null) { throw new InstallerException(url.toString() + " not available", new FileNotFoundException(url.toString())); } } if (in != null && idata.info.getPackDecoderClassName() != null) { Class<Object> decoder = (Class<Object>) Class.forName(idata.info.getPackDecoderClassName()); Class[] paramsClasses = new Class[1]; paramsClasses[0] = Class.forName("java.io.InputStream"); Constructor<Object> constructor = decoder.getDeclaredConstructor(paramsClasses); // Our first used decoder input stream (bzip2) reads byte for byte from // the source. Therefore we put a buffering stream between it and the // source. InputStream buffer = new BufferedInputStream(in); Object[] params = {buffer}; Object instance = null; instance = constructor.newInstance(params); if (!InputStream.class.isInstance(instance)) { throw new InstallerException("'" + idata.info.getPackDecoderClassName() + "' must be derived from " + InputStream.class.toString()); } in = (InputStream) instance; } return in; } }