de.schlichtherle.truezip.fs.archive.tar.TarOutputShop Maven / Gradle / Ivy
Show all versions of truezip-driver-tar Show documentation
/*
* Copyright (C) 2005-2015 Schlichtherle IT Services.
* All rights reserved. Use is subject to license terms.
*/
package de.schlichtherle.truezip.fs.archive.tar;
import de.schlichtherle.truezip.entry.Entry;
import static de.schlichtherle.truezip.entry.Entry.Size.DATA;
import static de.schlichtherle.truezip.entry.Entry.UNKNOWN;
import de.schlichtherle.truezip.io.DecoratingOutputStream;
import de.schlichtherle.truezip.io.DisconnectingOutputStream;
import de.schlichtherle.truezip.io.InputException;
import de.schlichtherle.truezip.io.OutputBusyException;
import de.schlichtherle.truezip.io.SequentialIOException;
import de.schlichtherle.truezip.io.SequentialIOExceptionBuilder;
import de.schlichtherle.truezip.io.Streams;
import de.schlichtherle.truezip.socket.IOPool;
import de.schlichtherle.truezip.socket.OutputShop;
import de.schlichtherle.truezip.socket.OutputSocket;
import de.schlichtherle.truezip.util.HashMaps;
import static de.schlichtherle.truezip.util.HashMaps.initialCapacity;
import de.schlichtherle.truezip.util.JSE7;
import edu.umd.cs.findbugs.annotations.CleanupObligation;
import edu.umd.cs.findbugs.annotations.CreatesObligation;
import edu.umd.cs.findbugs.annotations.DischargesObligation;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.annotation.CheckForNull;
import javax.annotation.WillCloseWhenClosed;
import javax.annotation.concurrent.NotThreadSafe;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import static org.apache.commons.compress.archivers.tar.TarConstants.DEFAULT_BLKSIZE;
import static org.apache.commons.compress.archivers.tar.TarConstants.DEFAULT_RCDSIZE;
/**
* An output service for writing TAR files.
* This output service can only write one entry concurrently.
*
* Because the TAR file format needs to know each entry's length in advance,
* entries from an unknown source are actually written to temp files and copied
* to the underlying {@link TarArchiveOutputStream} upon a call to their
* {@link OutputStream#close} method.
* Note that this implies that the {@code close()} method may fail with
* an {@link IOException}.
*
* If the size of an entry is known in advance it's directly written to the
* underlying {@code TarArchiveOutputStream} instead.
*
* @see TarInputShop
* @author Christian Schlichtherle
*/
@NotThreadSafe
public class TarOutputShop
extends TarArchiveOutputStream
implements OutputShop {
/**
* The number of entries which can be initially accomodated by
* the internal hash map without resizing it, which is {@value}.
*
* @since TrueZIP 7.3
* @deprecated since TrueZIP 7.5.5
*/
public static final int OVERHEAD_SIZE = HashMaps.OVERHEAD_SIZE;
/** HashMaps entry names to tar entries [String -> TarDriverEntry]. */
private final Map entries
= new LinkedHashMap(
initialCapacity(HashMaps.OVERHEAD_SIZE));
private final IOPool> pool;
private boolean busy;
@CreatesObligation
public TarOutputShop(
final TarDriver driver,
final @WillCloseWhenClosed OutputStream out) {
super(out, DEFAULT_BLKSIZE, DEFAULT_RCDSIZE, driver.getEncoding());
super.setAddPaxHeadersForNonAsciiNames(true);
super.setLongFileMode(LONGFILE_POSIX);
super.setBigNumberMode(BIGNUMBER_POSIX);
this.pool = driver.getPool();
}
@Override
public int getSize() {
return entries.size();
}
@Override
public Iterator iterator() {
return Collections.unmodifiableCollection(entries.values()).iterator();
}
@Override
public @CheckForNull TarDriverEntry getEntry(String name) {
return entries.get(name);
}
@Override
public OutputSocket getOutputSocket(final TarDriverEntry local) {
if (null == local) throw new NullPointerException();
final class Output extends OutputSocket {
@Override
public TarDriverEntry getLocalTarget() {
return local;
}
@Override
public OutputStream newOutputStream() throws IOException {
if (isBusy()) throw new OutputBusyException(local.getName());
if (local.isDirectory()) {
updateProperties(local, DirectoryTemplate.INSTANCE);
return new EntryOutputStream(local);
}
updateProperties(local, getPeerTarget());
return UNKNOWN == local.getSize()
? new BufferedEntryOutputStream(local)
: new EntryOutputStream(local);
}
} // Output
return new Output();
}
void updateProperties(
final TarDriverEntry local,
final @CheckForNull Entry peer) {
if (UNKNOWN == local.getModTime().getTime())
local.setModTime(System.currentTimeMillis());
if (null != peer)
if (UNKNOWN == local.getSize())
local.setSize(peer.getSize(DATA));
}
private static final class DirectoryTemplate implements Entry {
static final DirectoryTemplate INSTANCE = new DirectoryTemplate();
@Override
public String getName() {
return "/";
}
@Override
public long getSize(Size type) {
return 0;
}
@Override
public long getTime(Access type) {
return UNKNOWN;
}
} // DirectoryTemplate
/**
* Returns whether this output archive is busy writing an archive entry
* or not.
*/
private boolean isBusy() {
return busy;
}
/**
* This entry output stream writes directly to our subclass.
* It can only be used if this output stream is not currently busy
* writing another entry and the entry holds enough information to
* write the entry header.
* These preconditions are checked by {@link #getOutputSocket(TarDriverEntry)}.
*/
@CleanupObligation
private final class EntryOutputStream extends DisconnectingOutputStream {
boolean closed;
@CreatesObligation
@edu.umd.cs.findbugs.annotations.SuppressWarnings("OBL_UNSATISFIED_OBLIGATION")
EntryOutputStream(final TarDriverEntry local)
throws IOException {
super(TarOutputShop.this);
putArchiveEntry(local);
entries.put(local.getName(), local);
busy = true;
}
@Override
public boolean isOpen() {
return !closed;
}
@Override
@DischargesObligation
public void close() throws IOException {
if (closed) return;
closed = true;
busy = false;
closeArchiveEntry();
}
} // EntryOutputStream
/**
* This entry output stream writes the entry to a temporary file.
* When the stream is closed, the temporary file is then copied to this
* output stream and finally deleted.
*/
@CleanupObligation
private final class BufferedEntryOutputStream
extends DecoratingOutputStream {
final IOPool.Entry> buffer;
final TarDriverEntry local;
boolean closed;
@CreatesObligation
@edu.umd.cs.findbugs.annotations.SuppressWarnings("OBL_UNSATISFIED_OBLIGATION")
BufferedEntryOutputStream(final TarDriverEntry local)
throws IOException {
super(null);
this.local = local;
final IOPool.Entry> buffer = this.buffer = pool.allocate();
try {
this.delegate = buffer.getOutputSocket().newOutputStream();
} catch (final IOException ex) {
try {
buffer.release();
} catch (final IOException ex2) {
if (JSE7.AVAILABLE) ex.addSuppressed(ex2);
}
throw ex;
}
entries.put(local.getName(), local);
busy = true;
}
@Override
@DischargesObligation
public void close() throws IOException {
if (closed) return;
closed = true;
busy = false;
delegate.close();
updateProperties(local, buffer);
storeBuffer();
}
void storeBuffer() throws IOException {
final IOPool.Entry> buffer = this.buffer;
final SequentialIOExceptionBuilder builder
= SequentialIOExceptionBuilder.create(IOException.class, SequentialIOException.class);
try {
final InputStream in = buffer.getInputSocket().newInputStream();
try {
final TarArchiveOutputStream taos = TarOutputShop.this;
taos.putArchiveEntry(local);
try {
Streams.cat(in, taos);
} catch (final InputException ex) { // NOT IOException!
builder.warn(ex);
}
try {
taos.closeArchiveEntry();
} catch (final IOException ex) {
builder.warn(ex);
}
} catch (final IOException ex) {
builder.warn(ex);
} finally {
try {
in.close();
} catch (final IOException ex) {
builder.warn(ex);
}
}
} finally {
try {
buffer.release();
} catch (final IOException ex) {
builder.warn(ex);
}
}
builder.check();
}
} // BufferedEntryOutputStream
}