/*Argus -- A Zip Installer for Circumventing Common Educational Web Blocks
Copyright (C) 2014 Matthew Crocco
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package com.mattc.argus.concurrent;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.zip.ZipException;
//Apache Commons accounts much better for LARGE Zip Archives
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipFile;
import com.mattc.argus.Notifications;
import com.mattc.argus.util.Console;
import com.mattc.argus.util.Utility;
/**
*
* Thread to manage Unzipping Zip Archives in an Appropriate Manner. <br />
* <br />
* Unzip's Archives using a Buffer to accelerate extraction speeds to far beyond <br />
* what is found on most secondary school computers. <br />
* <br />
* Threaded to Allow Multiple Extractions at Once
*
* @author Matthew Crocco
*/
public class ZipProcess implements DecompressProcess{
/**Determined Appropriate Buffer Size*/
public final int BUFFER_SIZE;
private final File zipFile; //Zip Archive to Extract
private final File destDir; //Root Destination Directory
private ZipFile zip;
private FileOutputStream fos; //Output Stream to Extract With
private final byte[] buffer; //Allocation Buffer
private final AtomicBoolean running; //Thread Monitor Value
public ZipProcess(File zipFile, String dest){
this(zipFile, new File(dest));
}
public ZipProcess(File zipFile, File destDir){
if(!zipFile.exists()) throw new IllegalArgumentException("The desired ZipFile Does Not Exist");
if(!destDir.exists()){
destDir.mkdirs();
}
//Get Buffer Size and Create Byte Buffer
BUFFER_SIZE = 8192; //Standard Cache Size
buffer = new byte[BUFFER_SIZE];
this.destDir = destDir;
this.zipFile = zipFile;
running = new AtomicBoolean(false);
}
public void run(){
running.set(true);
//Zip Archive Entry Input Stream
InputStream zis = null;
try{
zip = new ZipFile(zipFile);
ZipArchiveEntry entry;
Enumeration<ZipArchiveEntry> entries;
entries = zip.getEntriesInPhysicalOrder();
//While there are still Entries to process, iterate
while(entries.hasMoreElements()){
entry = entries.nextElement();
//Create the Empty File to Extract to and its Parent Directory
//Notify Console and ProgressLog that a File is Being Extracted
File destination = new File(destDir, entry.getName());
File dirs = new File(destination.getParent());
dirs.mkdirs();
Console.info("EXT: " + Utility.relativizePath(destination, destDir.getParentFile()));
Notifications.updateLog("Extracting " + destination.getName() + (entry.isDirectory() ? " as Directory" : " as File") +"...");
/*
* IMPORTANT
*
* Ensures that All Files have a Directory to go to.
*
* If a Folder is for some reason created as a File, this will
* detect that. It will delete the file and re-create it as a Directory
* so that installation can continue.
*
* If all else fails, print debug information and stop extracting.
* It is unlikely the installed application will work without all files
* being extracted.
*/
try{
if(!destination.getParentFile().isDirectory()){
destination.getParentFile().delete();
destination.getParentFile().mkdirs();
}
destination.createNewFile();
}catch(IOException e){
String errMsg = "Failure to Extract " + zipFile.getName();
String errPath = "PATH = " + destination.getCanonicalPath();
String errParent = "PARENT = " + destination.getParentFile().getCanonicalPath();
String errIsDir = "PARENT_IS_DIRECTORY = " + destination.getParentFile().isDirectory();
//Standard IO Error Information
e.printStackTrace(System.err);
System.err.println(errMsg);
System.err.println(errPath);
System.err.println(errParent);
System.err.println(errIsDir);
//Log File Error Information (Possibly Standard IO if DEBUG = True)
Console.exception(e);
Console.error(errMsg);
Console.error(errPath);
Console.error(errParent);
Console.error(errIsDir);
//GUI Error Information. For End User.
String msg = errMsg + "\n" + errPath + "\n" + errParent + "\n" + errIsDir;
Notifications.exception(msg, e);
//Attempt to Delete the Partially Unzipped and Non-functional install Directory
if(!Utility.deleteDirectory(destDir)){
Console.error("Although extraction failed, the erroneous directory could not be removed!");
Notifications.error("Failure to Delete Partially Unzipped Directory!");
}else
Console.info("Erroneous Directory Deleted Successfully!");
}
//Establish a Stream to output data to the destination file
zis = zip.getInputStream(entry);
fos = new FileOutputStream(destination);
//Read Zip Entry data into buffer, output buffer to installation file
for(int c = zis.read(buffer); c > 0; c = zis.read(buffer))
fos.write(buffer, 0, c);
//Close Current Entry and Destination OutputStream
zis.close();
fos.close();
}
}catch(ZipException e){
Console.exception(e);
}catch(IOException e){
Console.exception(e);
}finally{ //Ensure that All Streams Are Closed to prevent Memory Leaks
Utility.closeStream(zis);
Utility.closeStream(fos);
Utility.closeStream(zip);
running.set(false);
}
}
public boolean isRunning(){
return running.get();
}
public String getArchiveName() {
return zipFile.getName();
}
public String getDestination() {
return destDir.getAbsolutePath();
}
}