/**
* Simple class to download and write zip files to disk.
*/
package ecologylab.io;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLConnection;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import ecologylab.appframework.StatusReporter;
import ecologylab.concurrent.BasicSite;
import ecologylab.concurrent.Downloadable;
import ecologylab.concurrent.DownloadableLogRecord;
import ecologylab.generic.Continuation;
import ecologylab.generic.Debug;
import ecologylab.net.NetTools;
import ecologylab.net.ParsedURL;
/**
* Class implementing DownloadLoadable to allow for the downloading and writing to disk of zip
* files. Additionally files can be extracted after downloaded.
*
* @author Blake Dworaczyk
*/
public class ZipDownload extends Debug implements Downloadable, Continuation
{
static DownloadProcessor<ZipDownload> downloadProcessor;
ParsedURL zipSource;
File zipTarget;
StatusReporter status;
boolean keepStatus = false;
boolean downloadDone = false;
boolean downloadStarted = false;
boolean aborted = false;
boolean extractWhenComplete = false;
int fileSize = -1;
InputStream inputStream = null;
private static final int BUFFER_SIZE = 8192;
public ZipDownload(ParsedURL zipSource, File zipTarget, StatusReporter status)
{
super();
this.zipSource = zipSource;
this.zipTarget = zipTarget;
this.status = status;
if (status != null)
keepStatus = true;
}
public ZipDownload(ParsedURL zipSource, File zipTarget)
{
this(zipSource, zipTarget, null);
}
/**
* Initiate the download and writing of the zip file. This is called by outsiders.
*
* @throws IOException
*/
public void downloadAndWrite(boolean extractWhenComplete) throws IOException
{
this.extractWhenComplete = extractWhenComplete;
debug("downloadAndWrite() calling downloadMonitor");
if (downloadProcessor == null)
throw new RuntimeException("Can't download cause downloadProcessor = null.");
downloadProcessor.download(this, this);
}
public static void stopDownloadProcessor()
{
if (downloadProcessor != null)
downloadProcessor.stop();
}
public void downloadAndWrite() throws IOException
{
downloadAndWrite(false);
}
/**
* ONLY called by <code>DownloadProcessor</code>s to actually download the zip file! Not called by
* outsiders!
*
* @throws IOException
*/
@Override
public void performDownload() throws IOException
{
// debug("performDOwnload() top");
if (downloadStarted)
return;
downloadStarted = true;
// this gets the stream and sets the member field 'fileSize'
// inputStream = getInputStream(zipSource);
inputStream = zipSource.url().openStream();
debug("performDownload() got InputStream");
// actually read and write the zip
// if zipTarget already exists, delete it
if (zipTarget.exists())
{
boolean deleted = zipTarget.delete();
debug("ZipTarget exists, so deleting = " + deleted);
}
OutputStream outputStream = new BufferedOutputStream(new FileOutputStream(zipTarget));
debug("performDownload() got outputStream from " + zipTarget);
byte zipBytes[] = new byte[BUFFER_SIZE];
// Read data from the source file and write it out to the zip file
int count = 0;
int lastTenth = 1;
int incrementSize = fileSize / 10;
// int i=0;
while ((count = inputStream.read(zipBytes, 0, BUFFER_SIZE)) != -1)
{
if (status != null)
{
// Our status will be in 10% increments
if (count >= incrementSize * (lastTenth))
status.display("Downloading zip file " + zipTarget.getName(), 1, count / 10,
incrementSize);
// can't just increment because we maybe skip/hit 1/10ths due to
// faster/slower transfer rates, traffic, etc.
lastTenth = (int) Math.floor(((double) count / fileSize) * 10);
}
outputStream.write(zipBytes, 0, count);
}
outputStream.close();
inputStream.close();
if (extractWhenComplete)
extractZipFile(zipTarget);
synchronized (this)
{
downloadDone = true;
}
}
public BufferedInputStream getInputStream(File zipTarget) throws Exception
{
URLConnection urlConnection;
if (zipTarget.isDirectory())
{
throw new Exception("ZipDownload: write exception! Can't write to directory!");
}
if (zipSource.isFile())
{
inputStream = new FileInputStream(zipSource.file());
}
// In case the purl was constructed with a local file used as a URL
// TODO find the source of these bad constructions! (only applicabale when cF is launched as an
// application)
else if (zipSource.toString().startsWith("file://"))
{
try
{
File fileInput = new File(zipSource.toString().substring(7, zipSource.toString().length()));
inputStream = new FileInputStream(fileInput);
}
catch (Exception e)
{
System.out.println("ZipDownload: error reading local file");
e.printStackTrace();
throw new Exception("ZipDownload: IOException");
}
}
else
{
urlConnection = zipSource.url().openConnection();
if ((urlConnection == null) || aborted)
{
System.err.println("Cant open URLConnection for " + aborted + " " + zipSource);
throw new Exception("ZipDownload: IOException");
}
try
{
urlConnection.connect();
fileSize = urlConnection.getContentLength();
inputStream = urlConnection.getInputStream();
}
catch (FileNotFoundException e)
{
System.err.println("FileNotFound!");
throw new Exception("ZipDownload: IOException");
}
}
if ((inputStream == null) || aborted)
{
System.err.println("Cant open InputStream for " + aborted + " " + zipSource);
throw new Exception("ZipDownload: IOException");
}
return new BufferedInputStream(inputStream);
}
public boolean isDownloadDone()
{
return downloadDone;
}
@Override
public void handleIoError(Throwable e)
{
aborted = true;
NetTools.close(inputStream);
}
@Override
public String toString()
{
return "ZipDownload() " + zipSource + " -> " + zipTarget;
}
@Override
public void callback(Object o)
{
System.out.println("ZipDownload delivered: " + (o));
}
/**
* Convenience function to allow downloading and uncompressing of a zip file from a source to a
* target location with minimal effort.
*
* @param sourceZip
* The location of the zip file to download and uncompress.
* @param targetDir
* The location where the zip file should be uncompressed. This directory structure will
* be created if it doesn't exist.
* @param status
* The StatusReporter object that provides a source of state change visiblity; can be
* null.
* @param unCompress
* true if the file is a zip that needs to be uncompressed after download.
* @return TODO
*/
public static ZipDownload downloadAndPerhapsUncompress(ParsedURL sourceZip, File targetDir,
StatusReporter status, boolean unCompress)
{
// Create the target parent directory.
if (!targetDir.exists())
targetDir.mkdirs();
println("downloading from zip URL: " + sourceZip + "\n\t to " + targetDir);
try
{
// FIXME make this use a (fixed?!) version of ParsedURL.isFile() ...
// if (sourceZip.toString().startsWith("file://"))
if (sourceZip.isFile())
{
// copy zip file from assets source to cache
File sourceZipFile = sourceZip.file();
String fileName = sourceZipFile.getName();
File destFile = Files.newFile(targetDir, fileName);
File destFileDir = Files.newFile(targetDir,
destFile.toString().substring(0, destFile.toString().length() - 4));
println("Checking if dir exists: " + destFileDir.toString());
// Delete the previous directory if it exists.
if (destFileDir.exists())
Files.deleteDirectory(destFileDir);
StreamUtils.copyFile(sourceZipFile, destFile);
extractZipFile(sourceZipFile, targetDir);
return null;
}
else
{
String fileName = sourceZip.getName();
ZipDownload zipDownload = new ZipDownload(sourceZip, Files.newFile(targetDir, fileName),
status);
zipDownload.downloadAndWrite(unCompress);
return zipDownload;
}
}
catch (IOException e)
{
System.err.println("Error, zip file not found on the server!");
// hiding stack trace -- it's annoying.
// e.printStackTrace();
return null;
}
}
/**
* Extracts a zip file into the directory where it resides
*
* @param zipSourcePath
* The path to the source zip file to extract.
*/
public static void extractZipFile(String zipSourcePath) throws IOException
{
extractZipFile(new File(zipSourcePath));
}
public static void extractZipFile(File zipSource) throws IOException
{
extractZipFile(zipSource, zipSource.getParentFile());
}
/**
* Extracts a zip file into the directory where it resides
*
* @param zipSourcePath
* The source zip file to extract
*/
public static void extractZipFile(String zipSourcePath, File unzipPath) throws IOException
{
extractZipFile(new File(zipSourcePath), unzipPath);
}
/**
* Extracts a zip file into the directory where it resides
*
* @param zipSource
* The source zip file to extract
*/
public static void extractZipFile(File zipSource, File unzipPath) throws IOException
{
ZipFile zipFile = new ZipFile(zipSource);
System.out.println("Extracting zip file: " + zipSource);
Enumeration entries = zipFile.entries();
while (entries.hasMoreElements())
{
ZipEntry entry = (ZipEntry) entries.nextElement();
String entryName = entry.getName();
if (entry.isDirectory())
{
File entryDir = new File(unzipPath, entryName);
if (!entryDir.exists())
entryDir.mkdirs();
continue;
}
// else (if !entry.ex)
File outFile = new File(unzipPath, entryName);
String dirPath = outFile.getParent();
File dir = new File(dirPath);
if (!dir.exists())
dir.mkdirs();
StreamUtils.copyInputStream(zipFile.getInputStream(entry), new BufferedOutputStream(
new FileOutputStream(outFile)));
}
zipFile.close();
System.out.println("Finished extracting Zip file to " + unzipPath);
}
/**
* Call to notify the object that its download is completed;
*
*/
public synchronized void downloadDone()
{
notifyAll();
}
public void waitForDownload()
{
synchronized (this)
{
if (!downloadDone && !aborted)
{
try
{
wait();
}
catch (InterruptedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public static void setDownloadProcessor(DownloadProcessor downloadProcessor)
{
ZipDownload.downloadProcessor = downloadProcessor;
}
@Override
public boolean isRecycled()
{
// TODO Auto-generated method stub
return false;
}
@Override
public BasicSite getSite()
{
// TODO Auto-generated method stub
return null;
}
/**
*
* @return What to tell the user about what is being downloaded.
*/
@Override
public String message()
{
return "zip archive " + zipSource.toString();
}
@Override
public ParsedURL location()
{
// TODO Auto-generated method stub
return null;
}
@Override
public void recycle()
{
if (inputStream != null)
try
{
inputStream.close();
}
catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* Default empty implementation; will be ignored for this type.
*/
@Override
public boolean isImage()
{
return false;
}
@Override
public BasicSite getDownloadSite()
{
return null;
}
@Override
public ParsedURL getDownloadLocation()
{
return location();
}
@Override
public boolean isCached()
{
// TODO Auto-generated method stub
return false;
}
@Override
public DownloadableLogRecord getLogRecord()
{
// TODO Auto-generated method stub
return null;
}
}