001/* 002 * $Id: Unpacker.java,v 1.49 2005/08/26 06:48:51 bartzkau Exp $ 003 * IzPack - Copyright 2001-2005 Julien Ponge, All Rights Reserved. 004 * 005 * http://www.izforge.com/izpack/ 006 * http://developer.berlios.de/projects/izpack/ 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 023package com.izforge.izpack.installer; 024 025import java.io.BufferedInputStream; 026import java.io.BufferedOutputStream; 027import java.io.File; 028import java.io.FileInputStream; 029import java.io.FileNotFoundException; 030import java.io.FileOutputStream; 031import java.io.IOException; 032import java.io.InputStream; 033import java.io.ObjectInputStream; 034import java.lang.reflect.Constructor; 035import java.net.URL; 036import java.util.ArrayList; 037import java.util.HashMap; 038import java.util.HashSet; 039import java.util.Iterator; 040import java.util.List; 041import java.util.Stack; 042import java.util.TreeSet; 043import java.util.zip.ZipEntry; 044import java.util.zip.ZipInputStream; 045import java.util.zip.ZipOutputStream; 046 047import org.apache.regexp.RE; 048import org.apache.regexp.RECompiler; 049import org.apache.regexp.RESyntaxException; 050 051import com.izforge.izpack.ExecutableFile; 052import com.izforge.izpack.LocaleDatabase; 053import com.izforge.izpack.Pack; 054import com.izforge.izpack.PackFile; 055import com.izforge.izpack.ParsableFile; 056import com.izforge.izpack.UpdateCheck; 057import com.izforge.izpack.event.InstallerListener; 058import com.izforge.izpack.util.AbstractUIHandler; 059import com.izforge.izpack.util.AbstractUIProgressHandler; 060import com.izforge.izpack.util.FileExecutor; 061import com.izforge.izpack.util.IoHelper; 062import com.izforge.izpack.util.OsConstraint; 063import com.izforge.izpack.util.VariableSubstitutor; 064 065/** 066 * Unpacker class. 067 * 068 * @author Julien Ponge 069 * @author Johannes Lehtinen 070 */ 071public class Unpacker extends Thread 072{ 073 074 /** The installdata. */ 075 private AutomatedInstallData idata; 076 077 /** The installer listener. */ 078 private AbstractUIProgressHandler handler; 079 080 /** The uninstallation data. */ 081 private UninstallData udata; 082 083 /** The variables substitutor. */ 084 private VariableSubstitutor vs; 085 086 /** The instances of the unpacker objects. */ 087 private static HashMap instances = new HashMap(); 088 089 /** The absolute path of the installation. (NOT the canonical!) */ 090 private File absolute_installpath; 091 092 /** The packs locale database. */ 093 private LocaleDatabase langpack = null; 094 095 /** Interrupt flag if global interrupt is desired. */ 096 private static boolean interruptDesired = false; 097 098 /** Do not perform a interrupt call. */ 099 private static boolean discardInterrupt = false; 100 101 /** The name of the XML file that specifies the panel langpack */ 102 private static final String LANG_FILE_NAME = "packsLang.xml"; 103 104 public static final String ALIVE = "alive"; 105 106 public static final String INTERRUPT = "doInterrupt"; 107 108 public static final String INTERRUPTED = "interruppted"; 109 110 /** 111 * The constructor. 112 * 113 * @param idata The installation data. 114 * @param handler The installation progress handler. 115 */ 116 public Unpacker(AutomatedInstallData idata, AbstractUIProgressHandler handler) 117 { 118 super("IzPack - Unpacker thread"); 119 try 120 { 121 String resource = LANG_FILE_NAME + "_" + idata.localeISO3; 122 this.langpack = new LocaleDatabase(ResourceManager.getInstance().getInputStream( 123 resource)); 124 } 125 catch (Throwable exception) 126 {} 127 128 this.idata = idata; 129 this.handler = handler; 130 131 // Initialize the variable substitutor 132 vs = new VariableSubstitutor(idata.getVariables()); 133 } 134 135 /** 136 * Returns a copy of the active unpacker instances. 137 * 138 * @return a copy of active unpacker instances 139 */ 140 public static HashMap getRunningInstances() 141 { 142 synchronized (instances) 143 { // Return a shallow copy to prevent a 144 // ConcurrentModificationException. 145 return (HashMap) (instances.clone()); 146 } 147 } 148 149 /** 150 * Adds this to the map of all existent instances of Unpacker. 151 */ 152 private void addToInstances() 153 { 154 synchronized (instances) 155 { 156 instances.put(this, ALIVE); 157 } 158 } 159 160 /** 161 * Removes this from the map of all existent instances of Unpacker. 162 */ 163 private void removeFromInstances() 164 { 165 synchronized (instances) 166 { 167 instances.remove(this); 168 } 169 } 170 171 /** 172 * Initiate interrupt of all alive Unpacker. This method does not interrupt the Unpacker objects 173 * else it sets only the interrupt flag for the Unpacker objects. The dispatching of interrupt 174 * will be performed by the Unpacker objects self. 175 */ 176 private static void setInterruptAll() 177 { 178 synchronized (instances) 179 { 180 Iterator iter = instances.keySet().iterator(); 181 while (iter.hasNext()) 182 { 183 Object key = iter.next(); 184 if (instances.get(key).equals(ALIVE)) 185 { 186 instances.put(key, INTERRUPT); 187 } 188 } 189 // Set global flag to allow detection of it in other classes. 190 // Do not set it to thread because an exec will then be stoped. 191 setInterruptDesired(true); 192 } 193 } 194 195 /** 196 * Initiate interrupt of all alive Unpacker and waits until all Unpacker are interrupted or the 197 * wait time has arrived. If the doNotInterrupt flag in InstallerListener is set to true, the 198 * interrupt will be discarded. 199 * 200 * @param waitTime wait time in millisecounds 201 * @return true if the interrupt will be performed, false if the interrupt will be discarded 202 */ 203 public static boolean interruptAll(long waitTime) 204 { 205 long t0 = System.currentTimeMillis(); 206 if (isDiscardInterrupt()) return (false); 207 setInterruptAll(); 208 while (!isInterruptReady()) 209 { 210 if (System.currentTimeMillis() - t0 > waitTime) return (true); 211 try 212 { 213 Thread.sleep(100); 214 } 215 catch (InterruptedException e) 216 {} 217 } 218 return (true); 219 } 220 221 private static boolean isInterruptReady() 222 { 223 synchronized (instances) 224 { 225 Iterator iter = instances.keySet().iterator(); 226 while (iter.hasNext()) 227 { 228 Object key = iter.next(); 229 if (!instances.get(key).equals(INTERRUPTED)) return (false); 230 } 231 return (true); 232 } 233 234 } 235 236 /** 237 * Sets the interrupt flag for this Unpacker to INTERRUPTED if the previos state was INTERRUPT 238 * or INTERRUPTED and returns whether interrupt was initiate or not. 239 * 240 * @return whether interrupt was initiate or not 241 */ 242 private boolean performInterrupted() 243 { 244 synchronized (instances) 245 { 246 Object doIt = instances.get(this); 247 if (doIt != null && (doIt.equals(INTERRUPT) || doIt.equals(INTERRUPTED))) 248 { 249 instances.put(this, INTERRUPTED); 250 return (true); 251 } 252 return (false); 253 } 254 } 255 256 /** 257 * Returns whether interrupt was initiate or not for this Unpacker. 258 * 259 * @return whether interrupt was initiate or not 260 */ 261 private boolean shouldInterrupt() 262 { 263 synchronized (instances) 264 { 265 Object doIt = instances.get(this); 266 if (doIt != null && (doIt.equals(INTERRUPT) || doIt.equals(INTERRUPTED))) { return (true); } 267 return (false); 268 } 269 270 } 271 272 /** The run method. */ 273 public void run() 274 { 275 addToInstances(); 276 try 277 { 278 // 279 // Initialisations 280 FileOutputStream out = null; 281 ArrayList parsables = new ArrayList(); 282 ArrayList executables = new ArrayList(); 283 ArrayList updatechecks = new ArrayList(); 284 List packs = idata.selectedPacks; 285 int npacks = packs.size(); 286 handler.startAction("Unpacking", npacks); 287 udata = UninstallData.getInstance(); 288 // Custom action listener stuff --- load listeners ---- 289 List[] customActions = getCustomActions(); 290 // Custom action listener stuff --- beforePacks ---- 291 informListeners(customActions, InstallerListener.BEFORE_PACKS, idata, new Integer( 292 npacks), handler); 293 294 // We unpack the selected packs 295 for (int i = 0; i < npacks; i++) 296 { 297 // We get the pack stream 298 int n = idata.allPacks.indexOf(packs.get(i)); 299 300 // Custom action listener stuff --- beforePack ---- 301 informListeners(customActions, InstallerListener.BEFORE_PACK, packs.get(i), 302 new Integer(npacks), handler); 303 ObjectInputStream objIn = new ObjectInputStream(getPackAsStream(n)); 304 305 // We unpack the files 306 int nfiles = objIn.readInt(); 307 308 // We get the internationalized name of the pack 309 final Pack pack = ((Pack) packs.get(i)); 310 String stepname = pack.name;// the message to be passed to the 311 // installpanel 312 if (langpack != null && !(pack.id == null || pack.id.equals(""))) 313 { 314 315 final String name = langpack.getString(pack.id); 316 if (name != null && !name.equals("")) 317 { 318 stepname = name; 319 } 320 } 321 handler.nextStep(stepname, i + 1, nfiles); 322 for (int j = 0; j < nfiles; j++) 323 { 324 // We read the header 325 PackFile pf = (PackFile) objIn.readObject(); 326 327 if (OsConstraint.oneMatchesCurrentSystem(pf.osConstraints())) 328 { 329 // We translate & build the path 330 String path = IoHelper.translatePath(pf.getTargetPath(), vs); 331 File pathFile = new File(path); 332 File dest = pathFile; 333 if (!pf.isDirectory()) dest = pathFile.getParentFile(); 334 335 if (!dest.exists()) 336 { 337 // If there are custom actions which would be called 338 // at 339 // creating a directory, create it recursively. 340 List fileListeners = customActions[customActions.length - 1]; 341 if (fileListeners != null && fileListeners.size() > 0) 342 mkDirsWithEnhancement(dest, pf, customActions); 343 else 344 // Create it in on step. 345 { 346 if (!dest.mkdirs()) 347 { 348 handler.emitError("Error creating directories", 349 "Could not create directory\n" + dest.getPath()); 350 handler.stopAction(); 351 return; 352 } 353 } 354 } 355 356 if (pf.isDirectory()) continue; 357 358 // Custom action listener stuff --- beforeFile ---- 359 informListeners(customActions, InstallerListener.BEFORE_FILE, pathFile, pf, 360 null); 361 // We add the path to the log, 362 udata.addFile(path); 363 364 handler.progress(j, path); 365 366 // if this file exists and should not be overwritten, 367 // check 368 // what to do 369 if ((pathFile.exists()) && (pf.override() != PackFile.OVERRIDE_TRUE)) 370 { 371 boolean overwritefile = false; 372 373 // don't overwrite file if the user said so 374 if (pf.override() != PackFile.OVERRIDE_FALSE) 375 { 376 if (pf.override() == PackFile.OVERRIDE_TRUE) 377 { 378 overwritefile = true; 379 } 380 else if (pf.override() == PackFile.OVERRIDE_UPDATE) 381 { 382 // check mtime of involved files 383 // (this is not 100% perfect, because the 384 // already existing file might 385 // still be modified but the new installed 386 // is just a bit newer; we would 387 // need the creation time of the existing 388 // file or record with which mtime 389 // it was installed...) 390 overwritefile = (pathFile.lastModified() < pf.lastModified()); 391 } 392 else 393 { 394 int def_choice = -1; 395 396 if (pf.override() == PackFile.OVERRIDE_ASK_FALSE) 397 def_choice = AbstractUIHandler.ANSWER_NO; 398 if (pf.override() == PackFile.OVERRIDE_ASK_TRUE) 399 def_choice = AbstractUIHandler.ANSWER_YES; 400 401 int answer = handler.askQuestion(idata.langpack 402 .getString("InstallPanel.overwrite.title") 403 + pathFile.getName(), idata.langpack 404 .getString("InstallPanel.overwrite.question") 405 + pathFile.getAbsolutePath(), 406 AbstractUIHandler.CHOICES_YES_NO, def_choice); 407 408 overwritefile = (answer == AbstractUIHandler.ANSWER_YES); 409 } 410 411 } 412 413 if (!overwritefile) 414 { 415 if (!pf.isBackReference() && !((Pack) packs.get(i)).loose) 416 objIn.skip(pf.length()); 417 continue; 418 } 419 420 } 421 422 // We copy the file 423 out = new FileOutputStream(pathFile); 424 byte[] buffer = new byte[5120]; 425 long bytesCopied = 0; 426 InputStream pis = objIn; 427 if (pf.isBackReference()) 428 { 429 InputStream is = getPackAsStream(pf.previousPackNumber); 430 pis = new ObjectInputStream(is); 431 // must wrap for blockdata use by objectstream 432 // (otherwise strange result) 433 // skip on underlaying stream (for some reason not 434 // possible on ObjectStream) 435 is.skip(pf.offsetInPreviousPack - 4); 436 // but the stream header is now already read (== 4 437 // bytes) 438 } 439 else if (((Pack) packs.get(i)).loose) 440 { 441 pis = new FileInputStream(pf.sourcePath); 442 } 443 while (bytesCopied < pf.length()) 444 { 445 if (performInterrupted()) 446 { // Interrupt was initiated; perform it. 447 out.close(); 448 if (pis != objIn) pis.close(); 449 return; 450 } 451 int maxBytes = (int) Math.min(pf.length() - bytesCopied, buffer.length); 452 int bytesInBuffer = pis.read(buffer, 0, maxBytes); 453 if (bytesInBuffer == -1) 454 throw new IOException("Unexpected end of stream"); 455 456 out.write(buffer, 0, bytesInBuffer); 457 458 bytesCopied += bytesInBuffer; 459 } 460 // Cleanings 461 out.close(); 462 if (pis != objIn) pis.close(); 463 464 // Set file modification time if specified 465 if (pf.lastModified() >= 0) pathFile.setLastModified(pf.lastModified()); 466 // Custom action listener stuff --- afterFile ---- 467 informListeners(customActions, InstallerListener.AFTER_FILE, pathFile, pf, 468 null); 469 470 } 471 else 472 { 473 if (!pf.isBackReference()) objIn.skip(pf.length()); 474 } 475 } 476 477 // Load information about parsable files 478 int numParsables = objIn.readInt(); 479 for (int k = 0; k < numParsables; k++) 480 { 481 ParsableFile pf = (ParsableFile) objIn.readObject(); 482 pf.path = IoHelper.translatePath(pf.path, vs); 483 parsables.add(pf); 484 } 485 486 // Load information about executable files 487 int numExecutables = objIn.readInt(); 488 for (int k = 0; k < numExecutables; k++) 489 { 490 ExecutableFile ef = (ExecutableFile) objIn.readObject(); 491 ef.path = IoHelper.translatePath(ef.path, vs); 492 if (null != ef.argList && !ef.argList.isEmpty()) 493 { 494 String arg = null; 495 for (int j = 0; j < ef.argList.size(); j++) 496 { 497 arg = (String) ef.argList.get(j); 498 arg = IoHelper.translatePath(arg, vs); 499 ef.argList.set(j, arg); 500 } 501 } 502 executables.add(ef); 503 if (ef.executionStage == ExecutableFile.UNINSTALL) 504 { 505 udata.addExecutable(ef); 506 } 507 } 508 // Custom action listener stuff --- uninstall data ---- 509 handleAdditionalUninstallData(udata, customActions); 510 511 // Load information about updatechecks 512 int numUpdateChecks = objIn.readInt(); 513 514 for (int k = 0; k < numUpdateChecks; k++) 515 { 516 UpdateCheck uc = (UpdateCheck) objIn.readObject(); 517 518 updatechecks.add(uc); 519 } 520 521 objIn.close(); 522 523 if (performInterrupted()) 524 { // Interrupt was initiated; perform it. 525 return; 526 } 527 528 // Custom action listener stuff --- afterPack ---- 529 informListeners(customActions, InstallerListener.AFTER_PACK, packs.get(i), 530 new Integer(i), handler); 531 } 532 533 // We use the scripts parser 534 ScriptParser parser = new ScriptParser(parsables, vs); 535 parser.parseFiles(); 536 if (performInterrupted()) 537 { // Interrupt was initiated; perform it. 538 return; 539 } 540 541 // We use the file executor 542 FileExecutor executor = new FileExecutor(executables); 543 if (executor.executeFiles(ExecutableFile.POSTINSTALL, handler) != 0) 544 handler.emitError("File execution failed", "The installation was not completed"); 545 546 if (performInterrupted()) 547 { // Interrupt was initiated; perform it. 548 return; 549 } 550 551 // We put the uninstaller (it's not yet complete...) 552 putUninstaller(); 553 554 // update checks _after_ uninstaller was put, so we don't delete it 555 performUpdateChecks(updatechecks); 556 557 if (performInterrupted()) 558 { // Interrupt was initiated; perform it. 559 return; 560 } 561 562 // Custom action listener stuff --- afterPacks ---- 563 informListeners(customActions, InstallerListener.AFTER_PACKS, idata, handler, null); 564 if (performInterrupted()) 565 { // Interrupt was initiated; perform it. 566 return; 567 } 568 569 // The end :-) 570 handler.stopAction(); 571 } 572 catch (Exception err) 573 { 574 // TODO: finer grained error handling with useful error messages 575 handler.stopAction(); 576 handler.emitError("An error occured", err.toString()); 577 err.printStackTrace(); 578 } 579 finally 580 { 581 removeFromInstances(); 582 } 583 } 584 585 /** 586 * @param updatechecks 587 */ 588 private void performUpdateChecks(ArrayList updatechecks) 589 { 590 ArrayList include_patterns = new ArrayList(); 591 ArrayList exclude_patterns = new ArrayList(); 592 593 RECompiler recompiler = new RECompiler(); 594 595 this.absolute_installpath = new File(idata.getInstallPath()).getAbsoluteFile(); 596 597 // at first, collect all patterns 598 for (Iterator iter = updatechecks.iterator(); iter.hasNext();) 599 { 600 UpdateCheck uc = (UpdateCheck) iter.next(); 601 602 if (uc.includesList != null) 603 include_patterns.addAll(preparePatterns(uc.includesList, recompiler)); 604 605 if (uc.excludesList != null) 606 exclude_patterns.addAll(preparePatterns(uc.excludesList, recompiler)); 607 } 608 609 // do nothing if no update checks were specified 610 if (include_patterns.size() == 0) return; 611 612 // now collect all files in the installation directory and figure 613 // out files to check for deletion 614 615 // use a treeset for fast access 616 TreeSet installed_files = new TreeSet(); 617 618 for (Iterator if_it = this.udata.getFilesList().iterator(); if_it.hasNext();) 619 { 620 String fname = (String) if_it.next(); 621 622 File f = new File(fname); 623 624 if (!f.isAbsolute()) 625 { 626 f = new File(this.absolute_installpath, fname); 627 } 628 629 installed_files.add(f.getAbsolutePath()); 630 } 631 632 // now scan installation directory (breadth first), contains Files of 633 // directories to scan 634 // (note: we'll recurse infinitely if there are circular links or 635 // similar nasty things) 636 Stack scanstack = new Stack(); 637 638 // contains File objects determined for deletion 639 ArrayList files_to_delete = new ArrayList(); 640 641 try 642 { 643 scanstack.add(absolute_installpath); 644 645 while (!scanstack.empty()) 646 { 647 File f = (File) scanstack.pop(); 648 649 File[] files = f.listFiles(); 650 651 if (files == null) { throw new IOException(f.getPath() + "is not a directory!"); } 652 653 for (int i = 0; i < files.length; i++) 654 { 655 File newf = files[i]; 656 657 String newfname = newf.getPath(); 658 659 // skip files we just installed 660 if (installed_files.contains(newfname)) continue; 661 662 if (fileMatchesOnePattern(newfname, include_patterns) 663 && (!fileMatchesOnePattern(newfname, exclude_patterns))) 664 { 665 files_to_delete.add(newf); 666 } 667 668 if (newf.isDirectory()) 669 { 670 scanstack.push(newf); 671 } 672 673 } 674 } 675 } 676 catch (IOException e) 677 { 678 this.handler.emitError("error while performing update checks", e.toString()); 679 } 680 681 for (Iterator f_it = files_to_delete.iterator(); f_it.hasNext();) 682 { 683 File f = (File) f_it.next(); 684 685 if (!f.isDirectory()) 686 // skip directories - they cannot be removed safely yet 687 { 688 this.handler.emitNotification("deleting " + f.getPath()); 689 f.delete(); 690 } 691 692 } 693 694 } 695 696 /** 697 * @param filename 698 * @param patterns 699 * 700 * @return true if the file matched one pattern, false if it did not 701 */ 702 private boolean fileMatchesOnePattern(String filename, ArrayList patterns) 703 { 704 // first check whether any include matches 705 for (Iterator inc_it = patterns.iterator(); inc_it.hasNext();) 706 { 707 RE pattern = (RE) inc_it.next(); 708 709 if (pattern.match(filename)) { return true; } 710 } 711 712 return false; 713 } 714 715 /** 716 * @param list A list of file name patterns (in ant fileset syntax) 717 * @param recompiler The regular expression compiler (used to speed up RE compiling). 718 * 719 * @return List of org.apache.regexp.RE 720 */ 721 private List preparePatterns(ArrayList list, RECompiler recompiler) 722 { 723 ArrayList result = new ArrayList(); 724 725 for (Iterator iter = list.iterator(); iter.hasNext();) 726 { 727 String element = (String) iter.next(); 728 729 if ((element != null) && (element.length() > 0)) 730 { 731 // substitute variables in the pattern 732 element = this.vs.substitute(element, "plain"); 733 734 // check whether the pattern is absolute or relative 735 File f = new File(element); 736 737 // if it is relative, make it absolute and prepend the 738 // installation path 739 // (this is a bit dangerous...) 740 if (!f.isAbsolute()) 741 { 742 element = new File(this.absolute_installpath, element).toString(); 743 } 744 745 // now parse the element and construct a regular expression from 746 // it 747 // (we have to parse it one character after the next because 748 // every 749 // character should only be processed once - it's not possible 750 // to get this 751 // correct using regular expression replacing) 752 StringBuffer element_re = new StringBuffer(); 753 754 int lookahead = -1; 755 756 int pos = 0; 757 758 while (pos < element.length()) 759 { 760 char c; 761 762 if (lookahead != -1) 763 { 764 c = (char) lookahead; 765 lookahead = -1; 766 } 767 else 768 c = element.charAt(pos++); 769 770 switch (c) 771 { 772 case '/': { 773 element_re.append(File.separator); 774 break; 775 } 776 // escape backslash and dot 777 case '\\': 778 case '.': { 779 element_re.append("\\"); 780 element_re.append(c); 781 break; 782 } 783 case '*': { 784 if (pos == element.length()) 785 { 786 element_re.append("[^" + File.separator + "]*"); 787 break; 788 } 789 790 lookahead = element.charAt(pos++); 791 792 // check for "**" 793 if (lookahead == '*') 794 { 795 element_re.append(".*"); 796 // consume second star 797 lookahead = -1; 798 } 799 else 800 { 801 element_re.append("[^" + File.separator + "]*"); 802 // lookahead stays there 803 } 804 break; 805 } 806 default: { 807 element_re.append(c); 808 break; 809 } 810 } // switch 811 812 } 813 814 // make sure that the whole expression is matched 815 element_re.append('$'); 816 817 // replace \ by \\ and create a RE from the result 818 try 819 { 820 result.add(new RE(recompiler.compile(element_re.toString()))); 821 } 822 catch (RESyntaxException e) 823 { 824 this.handler.emitNotification("internal error: pattern \"" + element 825 + "\" produced invalid RE \"" + f.getPath() + "\""); 826 } 827 828 } 829 } 830 831 return result; 832 } 833 834 /** 835 * Puts the uninstaller. 836 * 837 * @exception Exception Description of the Exception 838 */ 839 private void putUninstaller() throws Exception 840 { 841 // get the uninstaller base, returning if not found so that 842 // idata.uninstallOutJar remains null 843 InputStream[] in = new InputStream[2]; 844 in[0] = Unpacker.class.getResourceAsStream("/res/IzPack.uninstaller"); 845 if (in[0] == null) return; 846 // The uninstaller extension is facultative; it will be exist only 847 // if a native library was marked for uninstallation. 848 in[1] = Unpacker.class.getResourceAsStream("/res/IzPack.uninstaller-ext"); 849 850 // Me make the .uninstaller directory 851 String dest = IoHelper.translatePath("$INSTALL_PATH", vs) + File.separator + "Uninstaller"; 852 String jar = dest + File.separator + idata.info.getUninstallerName(); 853 File pathMaker = new File(dest); 854 pathMaker.mkdirs(); 855 856 // We log the uninstaller deletion information 857 udata.setUninstallerJarFilename(jar); 858 udata.setUninstallerPath(dest); 859 860 // We open our final jar file 861 FileOutputStream out = new FileOutputStream(jar); 862 // Intersect a buffer else byte for byte will be written to the file. 863 BufferedOutputStream bos = new BufferedOutputStream(out); 864 ZipOutputStream outJar = new ZipOutputStream(bos); 865 idata.uninstallOutJar = outJar; 866 outJar.setLevel(9); 867 udata.addFile(jar); 868 869 // We copy the uninstallers 870 HashSet doubles = new HashSet(); 871 872 for (int i = 0; i < in.length; ++i) 873 { 874 if (in[i] == null) continue; 875 ZipInputStream inRes = new ZipInputStream(in[i]); 876 ZipEntry zentry = inRes.getNextEntry(); 877 while (zentry != null) 878 { 879 // Puts a new entry, but not twice like META-INF 880 if (!doubles.contains(zentry.getName())) 881 { 882 doubles.add(zentry.getName()); 883 outJar.putNextEntry(new ZipEntry(zentry.getName())); 884 885 // Byte to byte copy 886 int unc = inRes.read(); 887 while (unc != -1) 888 { 889 outJar.write(unc); 890 unc = inRes.read(); 891 } 892 893 // Next one please 894 inRes.closeEntry(); 895 outJar.closeEntry(); 896 } 897 zentry = inRes.getNextEntry(); 898 } 899 inRes.close(); 900 } 901 902 // We put the langpack 903 InputStream in2 = Unpacker.class.getResourceAsStream("/langpacks/" + idata.localeISO3 904 + ".xml"); 905 outJar.putNextEntry(new ZipEntry("langpack.xml")); 906 int read = in2.read(); 907 while (read != -1) 908 { 909 outJar.write(read); 910 read = in2.read(); 911 } 912 outJar.closeEntry(); 913 } 914 915 /** 916 * Returns a stream to a pack, location depending on if it's web based. 917 * 918 * @param n The pack number. 919 * @return The stream or null if it could not be found. 920 * @exception Exception Description of the Exception 921 */ 922 private InputStream getPackAsStream(int n) throws Exception 923 { 924 InputStream in = null; 925 926 String webDirURL = idata.info.getWebDirURL(); 927 928 if (webDirURL == null) // local 929 { 930 in = Unpacker.class.getResourceAsStream("/packs/pack" + n); 931 } 932 else 933 // web based 934 { 935 // TODO: Look first in same directory as primary jar 936 // This may include prompting for changing of media 937 // TODO: download and cache them all before starting copy process 938 939 // See compiler.Packager#getJarOutputStream for the counterpart 940 String baseName = idata.info.getInstallerBase(); 941 String packURL = webDirURL + "/" + baseName + ".pack" + n + ".jar"; 942 URL url = new URL("jar:" + packURL + "!/packs/pack" + n); 943 // JarURLConnection jarConnection = (JarURLConnection) 944 // url.openConnection(); 945 // TODO: what happens when using an automated installer? 946 in = new WebAccessor(null).openInputStream(url); 947 // TODO: Fails miserably when pack jars are not found, so this is 948 // temporary 949 if (in == null) throw new FileNotFoundException(url.toString()); 950 } 951 if( in != null && idata.info.getPackDecoderClassName() != null ) 952 { 953 Class decoder = Class.forName(idata.info.getPackDecoderClassName()); 954 Class[] paramsClasses = new Class[1]; 955 paramsClasses[0] = Class.forName("java.io.InputStream"); 956 Constructor constructor = decoder.getDeclaredConstructor(paramsClasses); 957 // Our first used decoder input stream (bzip2) reads byte for byte from 958 // the source. Therefore we put a buffering stream between it and the 959 // source. 960 InputStream buffer = new BufferedInputStream(in); 961 Object[] params = { buffer }; 962 Object instance = null; 963 instance = constructor.newInstance( params); 964 if (!InputStream.class.isInstance(instance)) 965 throw new InstallerException( "'" + idata.info.getPackDecoderClassName() 966 + "' must be derived from " 967 + InputStream.class.toString()); 968 in = (InputStream) instance; 969 970 } 971 return in; 972 } 973 974 // CUSTOM ACTION STUFF -------------- start ----------------- 975 976 /** 977 * Informs all listeners which would be informed at the given action type. 978 * 979 * @param customActions array of lists with the custom action objects 980 * @param action identifier for which callback should be called 981 * @param firstParam first parameter for the call 982 * @param secondParam second parameter for the call 983 * @param thirdParam third parameter for the call 984 */ 985 private void informListeners(List[] customActions, int action, Object firstParam, 986 Object secondParam, Object thirdParam) throws Exception 987 { 988 List listener = null; 989 // select the right action list. 990 switch (action) 991 { 992 case InstallerListener.BEFORE_FILE: 993 case InstallerListener.AFTER_FILE: 994 case InstallerListener.BEFORE_DIR: 995 case InstallerListener.AFTER_DIR: 996 listener = customActions[customActions.length - 1]; 997 break; 998 default: 999 listener = customActions[0]; 1000 break; 1001 } 1002 if (listener == null) return; 1003 // Iterate the action list. 1004 Iterator iter = listener.iterator(); 1005 while (iter.hasNext()) 1006 { 1007 if (shouldInterrupt()) return; 1008 InstallerListener il = (InstallerListener) iter.next(); 1009 switch (action) 1010 { 1011 case InstallerListener.BEFORE_FILE: 1012 il.beforeFile((File) firstParam, (PackFile) secondParam); 1013 break; 1014 case InstallerListener.AFTER_FILE: 1015 il.afterFile((File) firstParam, (PackFile) secondParam); 1016 break; 1017 case InstallerListener.BEFORE_DIR: 1018 il.beforeDir((File) firstParam, (PackFile) secondParam); 1019 break; 1020 case InstallerListener.AFTER_DIR: 1021 il.afterDir((File) firstParam, (PackFile) secondParam); 1022 break; 1023 case InstallerListener.BEFORE_PACK: 1024 il.beforePack((Pack) firstParam, (Integer) secondParam, 1025 (AbstractUIProgressHandler) thirdParam); 1026 break; 1027 case InstallerListener.AFTER_PACK: 1028 il.afterPack((Pack) firstParam, (Integer) secondParam, 1029 (AbstractUIProgressHandler) thirdParam); 1030 break; 1031 case InstallerListener.BEFORE_PACKS: 1032 il.beforePacks((AutomatedInstallData) firstParam, (Integer) secondParam, 1033 (AbstractUIProgressHandler) thirdParam); 1034 break; 1035 case InstallerListener.AFTER_PACKS: 1036 il.afterPacks((AutomatedInstallData) firstParam, 1037 (AbstractUIProgressHandler) secondParam); 1038 break; 1039 1040 } 1041 } 1042 } 1043 1044 /** 1045 * Returns the defined custom actions split into types including a constructed type for the file 1046 * related installer listeners. 1047 * 1048 * @return array of lists of custom action data like listeners 1049 */ 1050 private List[] getCustomActions() 1051 { 1052 String[] listenerNames = AutomatedInstallData.CUSTOM_ACTION_TYPES; 1053 List[] retval = new List[listenerNames.length + 1]; 1054 int i; 1055 for (i = 0; i < listenerNames.length; ++i) 1056 { 1057 retval[i] = (List) idata.customData.get(listenerNames[i]); 1058 if (retval[i] == null) 1059 // Make a dummy list, then iterator is ever callable. 1060 retval[i] = new ArrayList(); 1061 } 1062 if (retval[AutomatedInstallData.INSTALLER_LISTENER_INDEX].size() > 0) 1063 { // Installer listeners exist 1064 // Create file related installer listener list in the last 1065 // element of custom action array. 1066 i = retval.length - 1; // Should be so, but safe is safe ... 1067 retval[i] = new ArrayList(); 1068 Iterator iter = ((List) retval[AutomatedInstallData.INSTALLER_LISTENER_INDEX]) 1069 .iterator(); 1070 while (iter.hasNext()) 1071 { 1072 // If we get a class cast exception many is wrong and 1073 // we must fix it. 1074 InstallerListener li = (InstallerListener) iter.next(); 1075 if (li.isFileListener()) retval[i].add(li); 1076 } 1077 1078 } 1079 return (retval); 1080 } 1081 1082 /** 1083 * Adds additional unistall data to the uninstall data object. 1084 * 1085 * @param udata unistall data 1086 * @param customData array of lists of custom action data like uninstaller listeners 1087 */ 1088 private void handleAdditionalUninstallData(UninstallData udata, List[] customData) 1089 { 1090 // Handle uninstall libs 1091 udata.addAdditionalData("__uninstallLibs__", 1092 customData[AutomatedInstallData.UNINSTALLER_LIBS_INDEX]); 1093 // Handle uninstaller listeners 1094 udata.addAdditionalData("uninstallerListeners", 1095 customData[AutomatedInstallData.UNINSTALLER_LISTENER_INDEX]); 1096 // Handle uninstaller jars 1097 udata.addAdditionalData("uninstallerJars", 1098 customData[AutomatedInstallData.UNINSTALLER_JARS_INDEX]); 1099 } 1100 1101 // This method is only used if a file related custom action exist. 1102 /** 1103 * Creates the given directory recursive and calls the method "afterDir" of each listener with 1104 * the current file object and the pack file object. On error an exception is raised. 1105 * 1106 * @param dest the directory which should be created 1107 * @param pf current pack file object 1108 * @param customActions all defined custom actions 1109 * @return false on error, true else 1110 * @throws Exception 1111 */ 1112 1113 private boolean mkDirsWithEnhancement(File dest, PackFile pf, List[] customActions) 1114 throws Exception 1115 { 1116 String path = "unknown"; 1117 if (dest != null) path = dest.getAbsolutePath(); 1118 if (dest != null && !dest.exists() && dest.getParentFile() != null) 1119 { 1120 if (dest.getParentFile().exists()) 1121 informListeners(customActions, InstallerListener.BEFORE_DIR, dest, pf, null); 1122 if (!dest.mkdir()) 1123 { 1124 mkDirsWithEnhancement(dest.getParentFile(), pf, customActions); 1125 if (!dest.mkdir()) dest = null; 1126 } 1127 informListeners(customActions, InstallerListener.AFTER_DIR, dest, pf, null); 1128 } 1129 if (dest == null) 1130 { 1131 handler.emitError("Error creating directories", "Could not create directory\n" + path); 1132 handler.stopAction(); 1133 return (false); 1134 } 1135 return (true); 1136 } 1137 1138 // CUSTOM ACTION STUFF -------------- end ----------------- 1139 1140 /** 1141 * Returns whether an interrupt request should be discarded or not. 1142 * 1143 * @return Returns the discard interrupt flag 1144 */ 1145 public static synchronized boolean isDiscardInterrupt() 1146 { 1147 return discardInterrupt; 1148 } 1149 1150 /** 1151 * Sets the discard interrupt flag. 1152 * 1153 * @param di the discard interrupt flag to set 1154 */ 1155 public static synchronized void setDiscardInterrupt(boolean di) 1156 { 1157 discardInterrupt = di; 1158 setInterruptDesired(false); 1159 } 1160 1161 /** 1162 * Returns the interrupt desired state. 1163 * 1164 * @return the interrupt desired state 1165 */ 1166 public static boolean isInterruptDesired() 1167 { 1168 return interruptDesired; 1169 } 1170 1171 /** 1172 * @param interruptDesired The interrupt desired flag to set 1173 */ 1174 private static void setInterruptDesired(boolean interruptDesired) 1175 { 1176 Unpacker.interruptDesired = interruptDesired; 1177 } 1178}