001/* 002 * $Source: /cvsroot2/open/projects/WebARTS/ca/bc/webarts/tools/Zipper.java,v $ 003 * Zipper.java - A zip compression tool. 004 * 005 * $Revision: 563 $ 006 * $Date: 2012-11-03 19:28:37 -0700 (Sat, 03 Nov 2012) $ 007 * $Locker: $ 008 * 009 * 010 * Written by Tom Gutwin - WebARTS Design. 011 * Copyright (C) 2001 WebARTS Design, North Vancouver Canada 012 * http://www.webarts.bc.ca 013 * 014 * This program is free software; you can redistribute it and/or modify 015 * it under the terms of the GNU General Public License as published by 016 * the Free Software Foundation; either version 2 of the License, or 017 * (at your option) any later version. 018 * 019 * This program is distributed in the hope that it will be useful, 020 * but WITHOUT ANY WARRANTY; without even the implied warranty of 021 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 022 * GNU General Public License for more details. 023 * 024 * You should have received a copy of the GNU General Public License 025 * along with this program; if not, write to the Free Software 026 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 027 */ 028package ca.bc.webarts.tools; 029 030import ca.bc.webarts.widgets.Util; 031 032import java.io.File; 033import java.io.FileNotFoundException; 034import java.io.FileInputStream; 035import java.io.FileOutputStream; 036import java.io.IOException; 037import java.io.OutputStream; 038import java.io.PrintStream; 039 040import java.util.Date; 041import java.util.Vector; 042 043import java.util.zip.DataFormatException; 044import java.util.zip.DeflaterOutputStream; 045import java.util.zip.ZipEntry; 046import java.util.zip.ZipException; 047import java.util.zip.ZipOutputStream; 048 049/** 050 * This class creates a zip file for specified directories or 051 * files. The max size for the zip file can be specified.<P>Usage details:<pre> 052 * Zipper <destination file with .zip extension> <source directory(s)/file(s)> 053 * </pre> 054 * 055 * @author Tom Gutwin tgutwin@webarts.bc.ca 056 * @created May 19, 2002 057 */ 058public class Zipper 059{ 060 061 /** Output stream for destination zip file. * */ 062 protected ZipOutputStream zos_ = null; 063 protected File zipfile_ = null; 064 /** the Out Stream for any info **/ 065 protected PrintStream printOutStream_ = System.out; 066 protected OutputStream infoOutStream_ = System.out; 067 protected boolean quiet_ = true; 068 protected boolean reallyQuiet_ = false; 069 private Vector zipWatchers_ = new Vector(); 070 private String oldName_ = ""; 071 int zipfilenameCounter_ = 0; 072 int compressionLevel_ = 9; 073 long maxZipFileSize_ = 630L * 1000L * 1024L; //650 MB (630 to be safe for CDRW overhead issues) 074 String [] defaultExcludes_ = {"~","SWAPPER.DAT","#","OldHPFS","OldFatDisk","tmp","Trash"}; 075 Vector excludes_ = new Vector(); 076 long startTime_ = 0L; 077 078 079 /** Constructor for the Zipper object */ 080 public Zipper() 081 { 082 for (int i=0;i < defaultExcludes_.length;i++) 083 excludes_.add(defaultExcludes_[i]); 084 } 085 086 087 /** 088 * Constructor for the Zipper object 089 * 090 * @param src An array of files to include in the zip. 091 * @param des Description of the Parameter 092 */ 093 Zipper(File[] src, File des) 094 { 095 for (int i=0;i < defaultExcludes_.length;i++) 096 excludes_.add(defaultExcludes_[i]); 097 zipFiles(src, des); 098 } 099 100 101 /** 102 * Constructor for the Zipper object 103 * 104 * @param src Description of the Parameter 105 * @param des Description of the Parameter 106 */ 107 Zipper(File src, File des) 108 { 109 File[] srcArray = {src}; 110 for (int i=0;i < defaultExcludes_.length;i++) 111 excludes_.add(defaultExcludes_[i]); 112 zipFiles(srcArray, des); 113 } 114 115 116 /** 117 * Main Entry point to run this class as an application usage: <code> 118 * Zipper <destination file with .zip extension> <source directorys / files> 119 *</code> 120 * 121 * @param args 1st arg is the destination file, 2nd arg is the source files 122 */ 123 public static void main(String args[]) 124 { 125 if (args.length >= 2) 126 { 127 try 128 { 129 File des = new File(args[0]); 130 if (!args[0].toLowerCase().substring(args[0].length() - 4, 131 args[0].length()).equalsIgnoreCase(".zip")) 132 { 133 throw new ArrayIndexOutOfBoundsException(); 134 } 135 136 File[] src = new File[args.length - 1]; 137 for (int i = 1; i < args.length; i++) 138 { 139 src[i - 1] = new File(args[i]); 140 } 141 142 new Zipper(src, des); 143 } 144 catch (ArrayIndexOutOfBoundsException aioobe) 145 { 146 System.out.println("Usage: java Zipper DestZipfile.zip SourceFile " + 147 "SourceFile2 ..."); 148 System.out.println(" OR"); 149 System.out.println("Usage: java Zipper SourceFile"); 150 System.out.println(" ...zips the SourceFile to SourceFile.zip"); 151 } 152 } 153 else if (args.length == 1) 154 { 155 try 156 { 157 File src = new File(args[0]); 158 File des = new File(System.getProperty("user.dir") + 159 File.separator + args[0] + ".zip"); 160 new Zipper(src, des); 161 } 162 catch (ArrayIndexOutOfBoundsException aioobe) 163 { 164 System.out.println("Usage: java Zipper DestZipfile.zip " + 165 "SourceFile SourceFile2 ..."); 166 System.out.println(" OR"); 167 System.out.println("Usage: java Zipper SourceFile"); 168 System.out.println(" ...zips the SourceFile to SourceFile.zip"); 169 } 170 } 171 else 172 { 173 System.out.println("Usage: java Zipper DestZipfile.zip SourceFile " + 174 "SourceFile2 ..."); 175 System.out.println(" OR"); 176 System.out.println("Usage: java Zipper SourceFile"); 177 System.out.println(" ...zips the SourceFile to SourceFile.zip"); 178 } 179 180 } 181 182 183 /** 184 * Gets the quiet_ field. Which specifies how much user feedback is provided 185 **/ 186 public boolean getQuiet_() 187 { 188 return quiet_; 189 } 190 191 192 /** 193 * Sets the quiet_ field. Which specifies how much user feedback is provided 194 **/ 195 public void setQuiet_(boolean quiet_) 196 { 197 this.quiet_ = quiet_; 198 } 199 200 201 /** 202 * Gets the infoOutStream_ field,Which specifies where to send feedback. 203 **/ 204 public OutputStream getInfoOutStream() 205 { 206 return infoOutStream_; 207 } 208 209 210 /** 211 * Sets the infoOutStream_ field, Which specifies where to send feedback. 212 **/ 213 public void setInfoOutStream(OutputStream ios) 214 { 215 infoOutStream_ = ios; 216 printOutStream_ = new PrintStream(ios); 217 } 218 219 220 /** 221 * Adds a String to the excludes Vecotr that gets checked to exclude files. 222 **/ 223 public void addToExcludes(String ex) 224 { 225 if (ex!= null && !ex.equals("")) 226 excludes_.add(ex); 227 } 228 229 230 /** 231 * Sets the maxZipFileSize_ attribute of the Zipper object 232 * 233 * @param maxZipFileSize_ The new maxZipFileSize_ value 234 */ 235 public void setMaxZipFileSize_(long maxZipFileSize) 236 { 237 this.maxZipFileSize_ = maxZipFileSize *1024L *1000L; 238 } 239 240 241 /** 242 * Gets the maxZipFileSize_ attribute of the Zipper object 243 * 244 * @return The maxZipFileSize_ value 245 */ 246 public long getMaxZipFileSize_() 247 { 248 return maxZipFileSize_; 249 } 250 251 252 /** 253 * Sets the compressionLevel_ attribute of the Zipper object 254 * 255 * @param compressionLevel_ The new compressionLevel_ value 256 */ 257 public void setCompressionLevel_(int compressionLevel_) 258 { 259 this.compressionLevel_ = compressionLevel_; 260 } 261 262 263 protected boolean excludedFile(File fileToCheck) 264 { 265 boolean retVal = false; 266 267 String filename = fileToCheck.getAbsolutePath(); 268 for (int i=0; i< excludes_.size(); i++) 269 if (filename.endsWith((String)excludes_.get(i))) 270 { 271 retVal = true; 272 break; 273 } 274 275 return retVal; 276 } 277 278 279 protected boolean excludedFile(String fileToCheck) 280 { 281 boolean retVal = false; 282 283 for (int i=0; i< excludes_.size(); i++) 284 if (fileToCheck.endsWith((String)excludes_.get(i))) 285 { 286 retVal = true; 287 break; 288 } 289 290 return retVal; 291 } 292 293 294 /** 295 * Gets the compressionLevel_ attribute of the Zipper object 296 * 297 * @return The compressionLevel_ value 298 */ 299 public int getCompressionLevel_() 300 { 301 return compressionLevel_; 302 } 303 304 305 protected void registerAsZipWatcher(ZipWatcher watcherClass) 306 { 307 zipWatchers_.add(watcherClass); 308 } 309 310 311 protected void notifyZipWatchers(String filenameJustCompleted, boolean done) 312 { 313 ZipWatcher currentWatcher = null; 314 for (int i=0; i< zipWatchers_.size(); i++) 315 { 316 currentWatcher = (ZipWatcher)zipWatchers_.get(i); 317 currentWatcher.nextZipDone(filenameJustCompleted, done); 318 } 319 } 320 321 322 323 protected ZipOutputStream createZipOutputStream(FileOutputStream fos) 324 { 325 return new ZipOutputStream(fos); 326 } 327 328 329 /** 330 * Inits the Zip Output stream with the latest increment counter. 331 * 332 * @param destZipFile Description of the Parameter 333 * @return Description of the Return Value 334 */ 335 protected boolean initZipOutputStream(File destZipFile) 336 { 337 boolean retVal = (destZipFile != null); 338 // avoid nullPEx 339 340 if (retVal) 341 { 342 String newName = destZipFile.getPath().trim(); 343 oldName_ = newName; 344 int endOffset = 6; 345 346 if (zipfilenameCounter_ > 0) 347 { 348 if (zipfilenameCounter_ == 1) 349 { 350 endOffset = 4; 351 } 352 else if (zipfilenameCounter_ > 10) 353 { 354 endOffset = 7; 355 } 356 else if (zipfilenameCounter_ > 100) 357 { 358 endOffset = 8; 359 } 360 newName = newName.substring(0, newName.length() - endOffset) + "_" + 361 zipfilenameCounter_ + ".zip"; 362 try 363 { 364 if (zos_ != null) 365 { 366 zos_.close(); 367 notifyZipWatchers(zipfile_.getAbsolutePath(), false); 368 } 369 } 370 catch (java.io.IOException ioe) 371 { 372 printOutStream_.println("Input/Output problem attempting to close the " + 373 "zipfile " + destZipFile.getPath().trim() + 374 ". \nDetails :\n" + ioe.getMessage()); 375 retVal = false; 376 } 377 } 378 379 // Attempt to open the stream 380 try 381 { 382 zipfilenameCounter_++; 383 zipfile_ = new File(newName); 384 zos_ = createZipOutputStream(new FileOutputStream(zipfile_)); 385 zos_.setLevel(compressionLevel_); 386 } 387 catch (java.lang.NullPointerException npe) 388 { 389 printOutStream_.println("Internal Zipper Error. Details :\n" + npe.getMessage()); 390 retVal = false; 391 } 392 catch (java.io.FileNotFoundException fnfe) 393 { 394 printOutStream_.println("Cannot init File:" + newName + ". \nDetails :\n" + 395 fnfe.getMessage()); 396 retVal = false; 397 } 398 } 399 400 return retVal; 401 } 402 403 404 /** 405 * Encapsulates the zipping of file(s) into a zip archive. 406 * 407 * @param source The files to include in the zipped file. 408 * @param destination The resultant zipped archive. 409 */ 410 public void zipFiles(File[] source, File destination) 411 { 412 boolean initOkay = false; 413 try 414 { 415 startTime_ = (new Date()).getTime(); 416 printOutStream_.println("\nInitializing the ZipOutput at: "+ 417 Util.createCurrentDateTimeStamp()); 418 initOkay = initZipOutputStream(destination); 419 // String srcFileNames[] = {source.getName()}; 420 //File arrFile[] = {source}; 421 if (initOkay && source.length > 0) 422 { 423 for (int i = 0; i < source.length; i++) 424 { 425 if (!reallyQuiet_ && source[i]!= null) 426 { 427 printOutStream_.println("Recursing "+source[i].getAbsolutePath()); 428 //printOutStream_.print(" ..." ); 429 //printOutStream_.println( 430 // Util.countFilesInDir(source[i].getAbsolutePath(),true)+" files."); 431 } 432 recursiveZip(source[i]); 433 } 434 zos_.close(); 435 notifyZipWatchers(zipfile_.getAbsolutePath(), true); 436 437 long duration = ((new Date()).getTime() - startTime_)/1000L; 438 long minutes = duration / 60L; 439 long seconds = duration - (60L*minutes); 440 if (!reallyQuiet_ && !quiet_) 441 { 442 printOutStream_.println(destination.getName() + 443 " file created successfully."); 444 printOutStream_.print("Time to complete: "); 445 printOutStream_.println(""+minutes+":"+seconds); 446 } 447 } 448 else 449 { 450 if (initOkay) 451 printOutStream_.println("There is no file in the specified source."); 452 else 453 printOutStream_.println("Could NOT Init Zip Output: " + destination); 454 } 455 } 456 catch (java.util.zip.ZipException ze) 457 { 458 printOutStream_.println("Exception occured in zipping file. Details :\n" + ze.getMessage()); 459 } 460 catch (java.io.FileNotFoundException fnfe) 461 { 462 printOutStream_.println("File is missing. Details :" + fnfe.getMessage()); 463 } 464 catch (java.io.IOException ioe) 465 { 466 printOutStream_.println("Input / Output problem. Details :\n" + ioe.getMessage()); 467 } 468 } 469 470 471 /** 472 * Compresses and saves the passed file parameter into a zip stream. This 473 * is a convienience class that wraps the srcFilename into an array and 474 * calls the 475 * <pre>writeToZipStream(String[] srcFilenameArray, ZipOutputStream zos, boolean recurseSubDirs)</pre> 476 * method. 477 * 478 * @param srcFilename The File to zip up - it can be a dir. 479 * @param zos The ZipOutputStream to comptress into. 480 * @param recurseSubDirs Flags if this method should decend into subdirs. 481 * @exception DataFormatException Description of the Exception 482 * @exception ZipException Description of the Exception 483 * @exception FileNotFoundException Description of the Exception 484 * @exception IOException Description of the Exception 485 */ 486 public void writeToZipStream(String srcFilename, ZipOutputStream zos, boolean recurseSubDirs) 487 throws DataFormatException, ZipException, FileNotFoundException, IOException 488 { 489 String [] tmpStr = {srcFilename}; 490 writeToZipStream(tmpStr, zos, recurseSubDirs); 491 } 492 493 494 /** 495 * Compresses and saves the passed files into a zip stream. 496 * 497 * @param srcFilename The Files to zip up - they can be a dir names. 498 * @param zos The ZipOutputStream to comptress into. 499 * @param recurseSubDirs Flags if this method should decend into subdirs. 500 * @exception DataFormatException Description of the Exception 501 * @exception ZipException Description of the Exception 502 * @exception FileNotFoundException Description of the Exception 503 * @exception IOException Description of the Exception 504 */ 505 public void writeToZipStream(String[] srcFilenameArray, ZipOutputStream zos, boolean recurseSubDirs) 506 throws DataFormatException, ZipException, FileNotFoundException, IOException 507 { 508 boolean debugOutput = true; 509 if (zos !=null) 510 { 511 String srcFilename = ""; 512 for (int arrayCounter = 0; 513 arrayCounter< srcFilenameArray.length; 514 arrayCounter++) 515 { 516 srcFilename = srcFilenameArray[arrayCounter]; 517 // This function write s the data in to Zipoutput Stream. 518 byte fileBytes[] = new byte[2048]; 519 File fileToGet = new File(srcFilename); 520 // clean off the drive letter and '/' 521 String entry = srcFilename.toString().trim(); 522 if (entry.startsWith(":"+File.separator,1)) 523 entry = srcFilename.toString().substring(3); 524 else if (entry.startsWith(File.separator)) 525 entry = srcFilename.toString().substring(1); 526 527 // Make sure dirs hava a trailing separator char so it is represented 528 // in the Zip correctly 529 if (fileToGet.isDirectory() && !entry.endsWith(File.separator)) 530 entry += File.separator; 531 532 entry = Util.tokenReplace(entry,"\\","/"); 533 ZipEntry ze = null; 534 535 if (!fileToGet.isDirectory()) 536 { 537 int len = 0; 538 ze = new ZipEntry(entry); 539 zos.putNextEntry(ze); 540 FileInputStream fileInStream = new FileInputStream(srcFilename); 541 len = fileInStream.read(fileBytes); 542 if (debugOutput) 543 System.out.println("Zipping on "+srcFilename+File.separator+srcFilename); 544 while (len != -1) 545 { 546 if (debugOutput) 547 System.out.print("."); 548 zos.write(fileBytes); 549 len = fileInStream.read(fileBytes); 550 } 551 if (debugOutput) 552 System.out.println(""); 553 zos.closeEntry(); 554 } 555 else // it is a dir 556 { 557 ze = new ZipEntry(entry); 558 zos.putNextEntry(ze); 559 zos.closeEntry(); 560 String srcFileNames[] = fileToGet.list(); 561 File tmpFile = null; 562 for (int i=0; i< srcFileNames.length; i++) 563 { 564 tmpFile = new File(srcFilename+File.separator+srcFileNames[i]); 565 if (tmpFile != null && tmpFile.isDirectory() && recurseSubDirs) 566 { 567 if (debugOutput) 568 System.out.println("Recursing on "+srcFilename+File.separator+srcFileNames[i]); 569 writeToZipStream(srcFilename+File.separator+srcFileNames[i], 570 zos, recurseSubDirs); 571 } 572 else if (tmpFile != null && !tmpFile.isDirectory()) 573 { 574 if (debugOutput) 575 System.out.println("Recursing on "+srcFilename+File.separator+srcFileNames[i]); 576 writeToZipStream(srcFilename+File.separator+srcFileNames[i], 577 zos, recurseSubDirs); 578 } 579 } 580 } 581 } 582 } 583 } 584 585 586 /** 587 * Compresses and saves the passed file parameter to a zip file. 588 * 589 * @param src The File to zip up - it can be a dir. 590 * @exception DataFormatException Description of the Exception 591 * @exception ZipException Description of the Exception 592 * @exception FileNotFoundException Description of the Exception 593 * @exception IOException Description of the Exception 594 */ 595 public void writeToZip(File src) 596 throws DataFormatException, ZipException, FileNotFoundException, IOException 597 { 598 // This function write s the data in to Zipoutput Stream. 599 byte b[] = new byte[512]; 600 // clean off the drive letter and '/' 601 String entry = src.toString().trim(); 602 if (entry.startsWith(":"+File.separator,1)) 603 entry = src.toString().substring(3); 604 else if (entry.startsWith(File.separator)) 605 entry = src.toString().substring(1); 606 607 // Make sure dirs hava a trailing separator char so it is represented 608 // in the Zip correctly 609 if (src.isDirectory() && !entry.endsWith(File.separator)) 610 entry += File.separator; 611 612 entry = Util.tokenReplace(entry,"\\","/"); 613 ZipEntry ze = new ZipEntry(entry); 614 zos_.putNextEntry(ze); 615 if (!src.isDirectory()) 616 { 617 int len = 0; 618 FileInputStream is = new FileInputStream(src); 619 while ((len = is.read(b)) != -1) 620 { 621 zos_.write(b, 0, len); 622 } 623 is = null; 624 } 625 626 // Provide some user feedback 627 if (!reallyQuiet_ && !quiet_) 628 if (!src.isDirectory()) 629 printOutStream_.println(" Adding " + entry); 630 else 631 printOutStream_.println(" Added directory " + entry); 632 zos_.closeEntry(); 633 } 634 635 636 /** 637 * Wrapper method to accept a dir or individual file in the passed in File 638 * object AND then calls the method that writes the passed file/dir to this 639 * Zipper instances zip output stream. This is a recursive function. If the 640 * passed File object is a file then it calls the function to write to zip 641 * output stream. If it is a directory it gets the list of file objects in 642 * the child directory and recurses on them. 643 * 644 * @param fo The File object to zip up (can be an actual file or dir). 645 */ 646 public void recursiveZip(File fo) 647 { 648 try 649 { 650 if (!fo.isDirectory()) 651 { 652 //printOutStream_.println("Adding "+fo.getName()); 653 if (zipfile_.length() + fo.length() > maxZipFileSize_) 654 { 655 printOutStream_.print("Max Zip File Size Reached! "); 656 printOutStream_.println("Incrementing ZipFile Name."); 657 System.out.print("Max Zip File Size Reached! "); 658 System.out.println("Incrementing ZipFile Name."); 659 initZipOutputStream(zipfile_); 660 } 661 try 662 { 663 if (!excludedFile(fo)) 664 writeToZip(fo); 665 else if(!reallyQuiet_) 666 printOutStream_.println("Excluding File:"+fo.getAbsolutePath()); 667 } 668 catch (java.io.FileNotFoundException fnfe) 669 { 670 if(fnfe.getMessage().endsWith("(Too many open files)")) 671 { 672 //wait a bit and try again 673 ca.bc.webarts.widgets.Util.sleep(2000); 674 recursiveZip(fo); 675 } 676 else if (!reallyQuiet_) 677 printOutStream_.println("File is missing (Zipping will continue):\n" + 678 fnfe.getMessage()); 679 } 680 } 681 else 682 { // it IS a Directory 683 try 684 { 685 if (excludedFile(fo)) 686 { 687 if(!reallyQuiet_) 688 { 689 printOutStream_.println("Excluding Directory:"+fo.getAbsolutePath()); 690 } 691 } 692 else 693 { 694 writeToZip(fo); 695 if (!reallyQuiet_) 696 printOutStream_.println("Recursing " + fo.getPath()); 697 String srcFileNames[] = fo.list(); 698 if (srcFileNames != null) 699 { 700 for (int i = 0; i < srcFileNames.length; i++) 701 { 702 if (!excludedFile(srcFileNames[i])) 703 { 704 recursiveZip(new File(fo.getPath() + File.separator + 705 srcFileNames[i])); 706 } 707 } 708 } 709 } 710 } 711 catch (java.io.FileNotFoundException fnfe) 712 { 713 if(!reallyQuiet_) 714 printOutStream_.println("Directory is missing (Zipping will continue):\n" + 715 fnfe.getMessage()); 716 } 717 } 718 } 719 catch (java.util.zip.DataFormatException dfe) 720 { 721 printOutStream_.println("Input data is not in proper format. Details :\n" + 722 dfe.getMessage()); 723 } 724 catch (java.util.zip.ZipException ze) 725 { 726 printOutStream_.println("Exception occured in zipping file. Details :\n" + 727 ze.getMessage()); 728 } 729 /*catch (java.io.FileNotFoundException fnfe) 730 { 731 printOutStream_.println("File is missing. Details :\n" + fnfe.getMessage()); 732 }*/ 733 catch (java.io.IOException ioe) 734 { 735 printOutStream_.println("Input / Output problem. \nDetails : " + 736 ioe.getMessage()); 737 if ("no space left on device".equals( 738 ioe.getMessage().trim().toLowerCase())) 739 { 740 try 741 { 742 // close up zip and stop 743 zos_.closeEntry(); 744 zos_.close(); 745 notifyZipWatchers(zipfile_.getAbsolutePath(), true); 746 long duration = ((new Date()).getTime() - startTime_)/1000L; 747 long minutes = duration / 60L; 748 long seconds = duration - (60L*minutes); 749 if (!reallyQuiet_ && !quiet_) 750 { 751 printOutStream_.println(zipfile_.getAbsolutePath() + 752 " file created successfully."); 753 printOutStream_.print("Time to complete: "); 754 printOutStream_.println(""+minutes+":"+seconds); 755 } 756 } 757 catch (java.io.IOException ioe2) 758 { 759 printOutStream_.println("Cannot Close zip output file. Details: \n" + 760 ioe2.getMessage()); 761 } 762 } 763 } 764 } 765}