package mil.nga.dice.report;
import android.content.Context;
import android.os.AsyncTask;
import android.util.Log;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
/**
* Extract report content from its Zip {@link mil.nga.dice.report.Report#getSourceFile() source file}. Also update the report properties if the Zip
* contains a metadata.json file. Progress notifications go to the given {@link mil.nga.dice.report.ReportImportCallbacks callbacks} object
* on the main thread using the {@link android.os.AsyncTask} API.
*/
public class UnzipReportSourceFile extends AsyncTask<Void, Integer, Void> {
private static final String TAG = UnzipReportSourceFile.class.getSimpleName();
private static final int BUFFER_SIZE = 1 << 16;
private final Report report;
private final File destDir;
private Context context;
private ReportImportCallbacks callbacks;
private long entryBytesRead = 0;
private long totalEntryBytes = -1;
private int percentComplete = 0;
private Exception error = null;
public UnzipReportSourceFile(Report report, File destDir, Context context, ReportImportCallbacks callbacks) {
this.report = report;
this.destDir = destDir;
this.context = context;
this.callbacks = callbacks;
if (report.getSourceFileSize() > 0) {
totalEntryBytes = report.getSourceFileSize();
}
}
/**
* The bits that will be run on a thread
*/
@Override
public Void doInBackground(Void... params) {
try {
Log.i(TAG, "unzipping report package " + report.getSourceFile());
unzip();
}
catch (Exception e) {
error = e;
Log.e(TAG, "error unzipping report file: " + report.getSourceFile(), e);
}
return null;
}
@Override
protected void onProgressUpdate(Integer... values) {
callbacks.importProgressPercentage(report, values[0]);
}
@Override
protected void onPostExecute(Void nothing) {
if (error == null) {
callbacks.importComplete(report);
}
else {
report.setError(error.getMessage());
callbacks.importError(report);
}
context = null;
callbacks = null;
}
private void unzip() throws IOException {
byte[] entryBuffer = new byte[BUFFER_SIZE];
ZipInputStream zipIn = new ZipInputStream(
new CompressedByteReportingInputStream(
context.getContentResolver().openInputStream(report.getSourceFile())));
try {
ZipEntry entry = zipIn.getNextEntry();
if (entry == null) {
// sometimes the zip stream just has no entries when one might think
// it would throw an exception, like when unzipping a text file - weird
throw new IOException("zip file has no entries: " + report.getSourceFile());
}
while (entry != null) {
File entryFile = new File(destDir, entry.getName());
if (entry.isDirectory()) {
entryFile.mkdir();
}
else {
extractEntryFile(zipIn, entryFile, entryBuffer);
}
zipIn.closeEntry();
entry = zipIn.getNextEntry();
}
}
finally {
zipIn.close();
}
}
private void extractEntryFile(ZipInputStream zipIn, File toFile, byte[] entryBuffer) throws IOException {
OutputStream entryOut = new FileOutputStream(toFile);
int read;
while ((read = zipIn.read(entryBuffer)) != -1) {
entryOut.write(entryBuffer, 0, read);
int roundedPercent = (int) Math.round((double) entryBytesRead / totalEntryBytes * 100);
if (roundedPercent > percentComplete) {
percentComplete = roundedPercent;
publishProgress(percentComplete);
}
}
entryOut.close();
}
private class CompressedByteReportingInputStream extends FilterInputStream {
/**
* Constructs a new {@code FilterInputStream} with the specified input
* stream as source.
* <p/>
* <p><strong>Warning:</strong> passing a null source creates an invalid
* {@code FilterInputStream}, that fails on every method that is not
* overridden. Subclasses should check for null in their constructors.
*
* @param zipFileStream the input stream to filter reads on.
*/
private CompressedByteReportingInputStream(InputStream zipFileStream) {
super(zipFileStream);
}
@Override
public int read() throws IOException {
entryBytesRead++;
return super.read();
}
@Override
public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
int readCount = super.read(buffer, byteOffset, byteCount);
entryBytesRead += readCount;
return readCount;
}
@Override
public int read(byte[] buffer) throws IOException {
int readCount = super.read(buffer);
entryBytesRead += readCount;
return readCount;
}
@Override
public long skip(long byteCount) throws IOException {
long skipCount = super.skip(byteCount);
entryBytesRead += skipCount;
return skipCount;
}
}
}