/*
* Overchan Android (Meta Imageboard Client)
* Copyright (C) 2014-2016 miku-nyan <https://github.com/miku-nyan>
*
* 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 nya.miku.wishmaster.containers;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.zip.Deflater;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
import nya.miku.wishmaster.api.interfaces.CancellableTask;
import nya.miku.wishmaster.common.IOUtils;
import nya.miku.wishmaster.common.Logger;
/**
* Класс инкапсулирует работу с ZIP архивом (создание/модификация).<br>
* Если файл существует, все файлы, кроме отдельного списка, будут скопированы в новый архив
* @author miku-nyan
*
*/
public class WriteableZip extends WriteableContainer {
private static final String TAG = "WriteableZip";
private Set<String> files;
private File outputFile;
private FileOutputStream outputFileStream;
private ZipOutputStream output;
private String filePath;
private boolean streamIsOpened;
private boolean objectCancelled = false;
public WriteableZip(File file) throws IOException {
filePath = file.getAbsolutePath();
files = new HashSet<String>();
outputFile = new File(filePath + ".tmp");
outputFileStream = new FileOutputStream(outputFile);
output = new ZipOutputStream(outputFileStream);
output.setMethod(ZipOutputStream.DEFLATED);
output.setLevel(Deflater.NO_COMPRESSION);
streamIsOpened = false;
}
@Override
public void transfer(String[] doNotCopy, CancellableTask task) throws IOException {
if (streamIsOpened) throw new IllegalStateException("stream is already opened");
if (doNotCopy == null) doNotCopy = new String[0];
File sourceFile = new File(filePath);
if (sourceFile.exists()) {
ZipFile source = null;
try {
source = new ZipFile(sourceFile);
for (Enumeration<? extends ZipEntry> entries = source.entries(); entries.hasMoreElements(); ) {
if (task.isCancelled()) {
cancel();
throw new InterruptedException();
}
ZipEntry current = entries.nextElement();
boolean skip = false;
for (String exclfile : doNotCopy) {
if (current.getName().equalsIgnoreCase(exclfile)) {
skip = true;
break;
}
}
if (skip || hasFile(current.getName())) continue;
files.add(current.getName());
output.putNextEntry(new ZipEntry(current.getName()));
InputStream in = IOUtils.modifyInputStream(source.getInputStream(current), null, task);
try {
IOUtils.copyStream(in, output);
} catch (IOException e) {
Logger.e(TAG, "cannot transfer file "+current.getName(), e);
} finally {
IOUtils.closeQuietly(in);
output.closeEntry();
}
}
} catch (Exception e) {
if (e instanceof InterruptedException) throw new IOException();
Logger.e(TAG, e);
if (e instanceof IOException && IOUtils.isENOSPC(e)) {
cancel();
throw (IOException) e;
}
} finally {
if (source != null) {
try {
source.close();
} catch (Exception e) {
Logger.e(TAG, e);
}
}
}
}
}
@Override
public boolean hasFile(String filename) {
return files.contains(filename);
}
@Override
public void cancel() {
try {
output.close();
outputFile.delete();
} catch (Exception e) {
Logger.e(TAG, e);
try {
outputFileStream.close();
outputFile.delete();
} catch (Exception e1) {
Logger.e(TAG, e1);
}
}
objectCancelled = true;
}
@Override
public void close() throws IOException {
if (objectCancelled) return;
try {
output.close();
} catch (Exception e) {
Logger.e(TAG, e);
outputFileStream.close();
outputFile.delete();
throw e;
}
File old = new File(filePath);
old.delete();
if (!outputFile.renameTo(old)) throw new IOException("cannot rename temp file");
}
@Override
public OutputStream openStream(String filename) throws IOException {
if (files.contains(filename)) throw new IllegalStateException("file already exists");
if (streamIsOpened) throw new IllegalStateException("stream is already opened");
files.add(filename);
output.putNextEntry(new ZipEntry(filename));
streamIsOpened = true;
return new InzipOutputStream();
}
private class InzipOutputStream extends OutputStream {
@Override
public void close() throws IOException {
streamIsOpened = false;
output.closeEntry();
}
@Override
public void flush() throws IOException {
output.flush();
}
@Override
public void write(int oneByte) throws IOException {
output.write(oneByte);
}
@Override
public void write(byte[] buffer) throws IOException {
output.write(buffer);
}
@Override
public void write(byte[] buffer, int offset, int count) throws IOException {
output.write(buffer, offset, count);
}
}
}