All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.schlichtherle.truezip.fs.archive.tar.TarOutputShop Maven / Gradle / Ivy

/*
 * 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 }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy