001: /*
002: * $Id: Unpacker.java 2062 2008-02-25 20:22:45Z jponge $
003: * IzPack - Copyright 2001-2008 Julien Ponge, All Rights Reserved.
004: *
005: * http://izpack.org/
006: * http://izpack.codehaus.org/
007: *
008: * Copyright 2001 Johannes Lehtinen
009: *
010: * Licensed under the Apache License, Version 2.0 (the "License");
011: * you may not use this file except in compliance with the License.
012: * You may obtain a copy of the License at
013: *
014: * http://www.apache.org/licenses/LICENSE-2.0
015: *
016: * Unless required by applicable law or agreed to in writing, software
017: * distributed under the License is distributed on an "AS IS" BASIS,
018: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
019: * See the License for the specific language governing permissions and
020: * limitations under the License.
021: */
022:
023: package com.izforge.izpack.installer;
024:
025: import java.io.BufferedInputStream;
026: import java.io.File;
027: import java.io.FileInputStream;
028: import java.io.FileNotFoundException;
029: import java.io.FileOutputStream;
030: import java.io.IOException;
031: import java.io.InputStream;
032: import java.io.ObjectInputStream;
033: import java.lang.reflect.Constructor;
034: import java.net.URL;
035: import java.util.ArrayList;
036: import java.util.List;
037:
038: import com.izforge.izpack.ExecutableFile;
039: import com.izforge.izpack.Pack;
040: import com.izforge.izpack.PackFile;
041: import com.izforge.izpack.ParsableFile;
042: import com.izforge.izpack.UpdateCheck;
043: import com.izforge.izpack.event.InstallerListener;
044: import com.izforge.izpack.util.AbstractUIHandler;
045: import com.izforge.izpack.util.AbstractUIProgressHandler;
046: import com.izforge.izpack.util.FileExecutor;
047: import com.izforge.izpack.util.IoHelper;
048: import com.izforge.izpack.util.OsConstraint;
049:
050: /**
051: * Unpacker class.
052: *
053: * @author Julien Ponge
054: * @author Johannes Lehtinen
055: */
056: public class Unpacker extends UnpackerBase {
057: private static final String tempPath = "$INSTALL_PATH/Uninstaller/IzpackWebTemp";
058:
059: /**
060: * The constructor.
061: *
062: * @param idata The installation data.
063: * @param handler The installation progress handler.
064: */
065: public Unpacker(AutomatedInstallData idata,
066: AbstractUIProgressHandler handler) {
067: super (idata, handler);
068: }
069:
070: /* (non-Javadoc)
071: * @see com.izforge.izpack.installer.IUnpacker#run()
072: */
073: public void run() {
074: addToInstances();
075: try {
076: //
077: // Initialisations
078: FileOutputStream out = null;
079: ArrayList<ParsableFile> parsables = new ArrayList<ParsableFile>();
080: ArrayList<ExecutableFile> executables = new ArrayList<ExecutableFile>();
081: ArrayList<UpdateCheck> updatechecks = new ArrayList<UpdateCheck>();
082: List packs = idata.selectedPacks;
083: int npacks = packs.size();
084: handler.startAction("Unpacking", npacks);
085: udata = UninstallData.getInstance();
086: // Custom action listener stuff --- load listeners ----
087: List[] customActions = getCustomActions();
088: // Custom action listener stuff --- beforePacks ----
089: informListeners(customActions,
090: InstallerListener.BEFORE_PACKS, idata, npacks,
091: handler);
092: packs = idata.selectedPacks;
093: npacks = packs.size();
094:
095: // We unpack the selected packs
096: for (int i = 0; i < npacks; i++) {
097: // We get the pack stream
098: //int n = idata.allPacks.indexOf(packs.get(i));
099: Pack p = (Pack) packs.get(i);
100:
101: // evaluate condition
102: if (p.hasCondition()) {
103: if (rules != null) {
104: if (!rules.isConditionTrue(p.getCondition())) {
105: // skip pack, condition is not fullfilled.
106: continue;
107: }
108: } else {
109: // TODO: skip pack, because condition can not be checked
110: }
111: }
112:
113: // Custom action listener stuff --- beforePack ----
114: informListeners(customActions,
115: InstallerListener.BEFORE_PACK, packs.get(i),
116: npacks, handler);
117: ObjectInputStream objIn = new ObjectInputStream(
118: getPackAsStream(p.id, p.uninstall));
119:
120: // We unpack the files
121: int nfiles = objIn.readInt();
122:
123: // We get the internationalized name of the pack
124: final Pack pack = ((Pack) packs.get(i));
125: String stepname = pack.name;// the message to be passed to the
126: // installpanel
127: if (langpack != null
128: && !(pack.id == null || "".equals(pack.id))) {
129:
130: final String name = langpack.getString(pack.id);
131: if (name != null && !"".equals(name)) {
132: stepname = name;
133: }
134: }
135: handler.nextStep(stepname, i + 1, nfiles);
136: for (int j = 0; j < nfiles; j++) {
137: // We read the header
138: PackFile pf = (PackFile) objIn.readObject();
139: // TODO: reaction if condition can not be checked
140: if (pf.hasCondition() && (rules != null)) {
141: if (!rules.isConditionTrue(pf.getCondition())) {
142: // skip, condition is not fulfilled
143: objIn.skip(pf.length());
144: continue;
145: }
146: }
147: if (OsConstraint.oneMatchesCurrentSystem(pf
148: .osConstraints())) {
149: // We translate & build the path
150: String path = IoHelper.translatePath(pf
151: .getTargetPath(), vs);
152: File pathFile = new File(path);
153: File dest = pathFile;
154: if (!pf.isDirectory())
155: dest = pathFile.getParentFile();
156:
157: if (!dest.exists()) {
158: // If there are custom actions which would be called
159: // at
160: // creating a directory, create it recursively.
161: List fileListeners = customActions[customActions.length - 1];
162: if (fileListeners != null
163: && fileListeners.size() > 0)
164: mkDirsWithEnhancement(dest, pf,
165: customActions);
166: else
167: // Create it in on step.
168: {
169: if (!dest.mkdirs()) {
170: handler
171: .emitError(
172: "Error creating directories",
173: "Could not create directory\n"
174: + dest
175: .getPath());
176: handler.stopAction();
177: this .result = false;
178: return;
179: }
180: }
181: }
182:
183: if (pf.isDirectory())
184: continue;
185:
186: // Custom action listener stuff --- beforeFile ----
187: informListeners(customActions,
188: InstallerListener.BEFORE_FILE,
189: pathFile, pf, null);
190: // We add the path to the log,
191: udata.addFile(path, pack.uninstall);
192:
193: handler.progress(j, path);
194:
195: // if this file exists and should not be overwritten,
196: // check
197: // what to do
198: if ((pathFile.exists())
199: && (pf.override() != PackFile.OVERRIDE_TRUE)) {
200: boolean overwritefile = false;
201:
202: // don't overwrite file if the user said so
203: if (pf.override() != PackFile.OVERRIDE_FALSE) {
204: if (pf.override() == PackFile.OVERRIDE_TRUE) {
205: overwritefile = true;
206: } else if (pf.override() == PackFile.OVERRIDE_UPDATE) {
207: // check mtime of involved files
208: // (this is not 100% perfect, because the
209: // already existing file might
210: // still be modified but the new installed
211: // is just a bit newer; we would
212: // need the creation time of the existing
213: // file or record with which mtime
214: // it was installed...)
215: overwritefile = (pathFile
216: .lastModified() < pf
217: .lastModified());
218: } else {
219: int def_choice = -1;
220:
221: if (pf.override() == PackFile.OVERRIDE_ASK_FALSE)
222: def_choice = AbstractUIHandler.ANSWER_NO;
223: if (pf.override() == PackFile.OVERRIDE_ASK_TRUE)
224: def_choice = AbstractUIHandler.ANSWER_YES;
225:
226: int answer = handler
227: .askQuestion(
228: idata.langpack
229: .getString("InstallPanel.overwrite.title")
230: + " - "
231: + pathFile
232: .getName(),
233: idata.langpack
234: .getString("InstallPanel.overwrite.question")
235: + pathFile
236: .getAbsolutePath(),
237: AbstractUIHandler.CHOICES_YES_NO,
238: def_choice);
239:
240: overwritefile = (answer == AbstractUIHandler.ANSWER_YES);
241: }
242:
243: }
244:
245: if (!overwritefile) {
246: if (!pf.isBackReference()
247: && !((Pack) packs.get(i)).loose)
248: objIn.skip(pf.length());
249: continue;
250: }
251:
252: }
253:
254: // We copy the file
255: InputStream pis = objIn;
256: if (pf.isBackReference()) {
257: InputStream is = getPackAsStream(
258: pf.previousPackId, pack.uninstall);
259: pis = new ObjectInputStream(is);
260: // must wrap for blockdata use by objectstream
261: // (otherwise strange result)
262: // skip on underlaying stream (for some reason not
263: // possible on ObjectStream)
264: is.skip(pf.offsetInPreviousPack - 4);
265: // but the stream header is now already read (== 4
266: // bytes)
267: } else if (((Pack) packs.get(i)).loose) {
268: /* Old way of doing the job by using the (absolute) sourcepath.
269: * Since this is very likely to fail and does not confirm to the documentation,
270: * prefer using relative path's
271: pis = new FileInputStream(pf.sourcePath);
272: */
273:
274: //take the relative path and search for the file
275: //1. look at the location where the "info"-file is loaded from (jar)
276: //2. look into the current working directory
277: //maybe look into other other locations after that (configurable ?)
278: //find directory of jar file
279: URL url = getClass().getResource("/info");
280: String urlPath = url.getPath();
281: int pos = urlPath.indexOf('!');
282: if (pos >= 0
283: && urlPath.startsWith("file:/")) {
284: //remove jar-specific part
285: urlPath = urlPath.substring("file:/"
286: .length(), pos);
287: }
288: File installerDir = new File(urlPath);
289: if (!installerDir.isDirectory()) {
290: installerDir = installerDir
291: .getParentFile();
292: }
293:
294: File resolvedFile = new File(installerDir,
295: pf.getRelativeSourcePath());
296: if (!resolvedFile.exists()) {
297: //try alternative destination - the current working directory
298: //user.dir is likely (depends on launcher type) the current directory of the executable or jar-file...
299: final File userDir = new File(System
300: .getProperty("user.dir"));
301: resolvedFile = new File(userDir, pf
302: .getRelativeSourcePath());
303: }
304: if (resolvedFile.exists()) {
305: pis = new FileInputStream(resolvedFile);
306: //may have a different length & last modified than we had at compiletime, therefore we have to build a new PackFile for the copy process...
307: pf = new PackFile(resolvedFile
308: .getParentFile(), resolvedFile,
309: pf.getTargetPath(), pf
310: .osConstraints(), pf
311: .override(), pf
312: .getAdditionals());
313: } else {
314: //file not found
315: //issue a warning (logging api pending)
316: //since this file was loosely bundled, we continue with the installation.
317: System.out
318: .println("Could not find loosely bundled file: "
319: + pf
320: .getRelativeSourcePath());
321: out.close();
322: continue;
323: }
324: }
325:
326: out = new FileOutputStream(pathFile);
327: byte[] buffer = new byte[5120];
328: long bytesCopied = 0;
329: while (bytesCopied < pf.length()) {
330: if (performInterrupted()) { // Interrupt was initiated; perform it.
331: out.close();
332: if (pis != objIn)
333: pis.close();
334: return;
335: }
336: int maxBytes = (int) Math.min(pf.length()
337: - bytesCopied, buffer.length);
338: int bytesInBuffer = pis.read(buffer, 0,
339: maxBytes);
340: if (bytesInBuffer == -1)
341: throw new IOException(
342: "Unexpected end of stream (installer corrupted?)");
343:
344: out.write(buffer, 0, bytesInBuffer);
345:
346: bytesCopied += bytesInBuffer;
347: }
348: // Cleanings
349: out.close();
350: if (pis != objIn)
351: pis.close();
352:
353: // Set file modification time if specified
354: if (pf.lastModified() >= 0)
355: pathFile.setLastModified(pf.lastModified());
356: // Custom action listener stuff --- afterFile ----
357: informListeners(customActions,
358: InstallerListener.AFTER_FILE, pathFile,
359: pf, null);
360:
361: } else {
362: if (!pf.isBackReference())
363: objIn.skip(pf.length());
364: }
365: }
366:
367: // Load information about parsable files
368: int numParsables = objIn.readInt();
369: for (int k = 0; k < numParsables; k++) {
370: ParsableFile pf = (ParsableFile) objIn.readObject();
371: if (pf.hasCondition() && (rules != null)) {
372: if (!rules.isConditionTrue(pf.getCondition())) {
373: // skip, condition is not fulfilled
374: continue;
375: }
376: }
377: pf.path = IoHelper.translatePath(pf.path, vs);
378: parsables.add(pf);
379: }
380:
381: // Load information about executable files
382: int numExecutables = objIn.readInt();
383: for (int k = 0; k < numExecutables; k++) {
384: ExecutableFile ef = (ExecutableFile) objIn
385: .readObject();
386: if (ef.hasCondition() && (rules != null)) {
387: if (!rules.isConditionTrue(ef.getCondition())) {
388: // skip, condition is false
389: continue;
390: }
391: }
392: ef.path = IoHelper.translatePath(ef.path, vs);
393: if (null != ef.argList && !ef.argList.isEmpty()) {
394: String arg = null;
395: for (int j = 0; j < ef.argList.size(); j++) {
396: arg = ef.argList.get(j);
397: arg = IoHelper.translatePath(arg, vs);
398: ef.argList.set(j, arg);
399: }
400: }
401: executables.add(ef);
402: if (ef.executionStage == ExecutableFile.UNINSTALL) {
403: udata.addExecutable(ef);
404: }
405: }
406: // Custom action listener stuff --- uninstall data ----
407: handleAdditionalUninstallData(udata, customActions);
408:
409: // Load information about updatechecks
410: int numUpdateChecks = objIn.readInt();
411:
412: for (int k = 0; k < numUpdateChecks; k++) {
413: UpdateCheck uc = (UpdateCheck) objIn.readObject();
414:
415: updatechecks.add(uc);
416: }
417:
418: objIn.close();
419:
420: if (performInterrupted()) { // Interrupt was initiated; perform it.
421: return;
422: }
423:
424: // Custom action listener stuff --- afterPack ----
425: informListeners(customActions,
426: InstallerListener.AFTER_PACK, packs.get(i), i,
427: handler);
428: }
429:
430: // We use the scripts parser
431: ScriptParser parser = new ScriptParser(parsables, vs);
432: parser.parseFiles();
433: if (performInterrupted()) { // Interrupt was initiated; perform it.
434: return;
435: }
436:
437: // We use the file executor
438: FileExecutor executor = new FileExecutor(executables);
439: if (executor.executeFiles(ExecutableFile.POSTINSTALL,
440: handler) != 0) {
441: handler.emitError("File execution failed",
442: "The installation was not completed");
443: this .result = false;
444: }
445:
446: if (performInterrupted()) { // Interrupt was initiated; perform it.
447: return;
448: }
449:
450: // We put the uninstaller (it's not yet complete...)
451: putUninstaller();
452:
453: // update checks _after_ uninstaller was put, so we don't delete it
454: performUpdateChecks(updatechecks);
455:
456: if (performInterrupted()) { // Interrupt was initiated; perform it.
457: return;
458: }
459:
460: // Custom action listener stuff --- afterPacks ----
461: informListeners(customActions,
462: InstallerListener.AFTER_PACKS, idata, handler, null);
463: if (performInterrupted()) { // Interrupt was initiated; perform it.
464: return;
465: }
466:
467: // write installation information
468: writeInstallationInformation();
469:
470: // The end :-)
471: handler.stopAction();
472: } catch (Exception err) {
473: // TODO: finer grained error handling with useful error messages
474: handler.stopAction();
475: if ("Installation cancelled".equals(err.getMessage())) {
476: handler.emitNotification("Installation cancelled");
477: } else {
478: handler.emitError("An error occured", err.getMessage());
479: err.printStackTrace();
480: }
481: this .result = false;
482: System.exit(4);
483: } finally {
484: removeFromInstances();
485: }
486: }
487:
488: /**
489: * Returns a stream to a pack, location depending on if it's web based.
490: *
491: * @param uninstall true if pack must be uninstalled
492: * @return The stream or null if it could not be found.
493: * @exception Exception Description of the Exception
494: */
495: private InputStream getPackAsStream(String packid, boolean uninstall)
496: throws Exception {
497: InputStream in = null;
498:
499: String webDirURL = idata.info.getWebDirURL();
500:
501: packid = "-" + packid;
502:
503: if (webDirURL == null) // local
504: {
505: in = Unpacker.class.getResourceAsStream("/packs/pack"
506: + packid);
507: } else
508: // web based
509: {
510: // TODO: Look first in same directory as primary jar
511: // This may include prompting for changing of media
512: // TODO: download and cache them all before starting copy process
513:
514: // See compiler.Packager#getJarOutputStream for the counterpart
515: String baseName = idata.info.getInstallerBase();
516: String packURL = webDirURL + "/" + baseName + ".pack"
517: + packid + ".jar";
518: String tf = IoHelper.translatePath(Unpacker.tempPath, vs);
519: String tempfile;
520: try {
521: tempfile = WebRepositoryAccessor.getCachedUrl(packURL,
522: tf);
523: udata.addFile(tempfile, uninstall);
524: } catch (Exception e) {
525: if ("Cancelled".equals(e.getMessage()))
526: throw new InstallerException(
527: "Installation cancelled", e);
528: else
529: throw new InstallerException("Installation failed",
530: e);
531: }
532: URL url = new URL("jar:" + tempfile + "!/packs/pack"
533: + packid);
534:
535: //URL url = new URL("jar:" + packURL + "!/packs/pack" + packid);
536: // JarURLConnection jarConnection = (JarURLConnection)
537: // url.openConnection();
538: // TODO: what happens when using an automated installer?
539: in = new WebAccessor(null).openInputStream(url);
540: // TODO: Fails miserably when pack jars are not found, so this is
541: // temporary
542: if (in == null)
543: throw new InstallerException(url.toString()
544: + " not available", new FileNotFoundException(
545: url.toString()));
546: }
547: if (in != null && idata.info.getPackDecoderClassName() != null) {
548: Class<Object> decoder = (Class<Object>) Class
549: .forName(idata.info.getPackDecoderClassName());
550: Class[] paramsClasses = new Class[1];
551: paramsClasses[0] = Class.forName("java.io.InputStream");
552: Constructor<Object> constructor = decoder
553: .getDeclaredConstructor(paramsClasses);
554: // Our first used decoder input stream (bzip2) reads byte for byte from
555: // the source. Therefore we put a buffering stream between it and the
556: // source.
557: InputStream buffer = new BufferedInputStream(in);
558: Object[] params = { buffer };
559: Object instance = null;
560: instance = constructor.newInstance(params);
561: if (!InputStream.class.isInstance(instance))
562: throw new InstallerException("'"
563: + idata.info.getPackDecoderClassName()
564: + "' must be derived from "
565: + InputStream.class.toString());
566: in = (InputStream) instance;
567:
568: }
569: return in;
570: }
571: }
|