View Javadoc
1   /*
2    * Copyright (C) 2005-2015 Schlichtherle IT Services.
3    * All rights reserved. Use is subject to license terms.
4    */
5   package net.java.truevfs.driver.tar.gzip;
6   
7   import java.io.IOException;
8   import java.io.InputStream;
9   import java.io.OutputStream;
10  import java.util.zip.Deflater;
11  import java.util.zip.GZIPInputStream;
12  import javax.annotation.concurrent.Immutable;
13  import net.java.truecommons.io.AbstractSink;
14  import net.java.truecommons.io.AbstractSource;
15  import net.java.truecommons.io.Streams;
16  import net.java.truecommons.shed.BitField;
17  import net.java.truevfs.comp.tardriver.TarDriver;
18  import net.java.truevfs.comp.tardriver.TarDriverEntry;
19  import net.java.truevfs.comp.tardriver.TarInputService;
20  import net.java.truevfs.comp.tardriver.TarOutputService;
21  import net.java.truevfs.kernel.spec.FsAccessOption;
22  import static net.java.truevfs.kernel.spec.FsAccessOption.STORE;
23  import net.java.truevfs.kernel.spec.FsController;
24  import net.java.truevfs.kernel.spec.FsInputSocketSource;
25  import net.java.truevfs.kernel.spec.FsModel;
26  import net.java.truevfs.kernel.spec.FsNodeName;
27  import net.java.truevfs.kernel.spec.FsOutputSocketSink;
28  import net.java.truecommons.cio.InputService;
29  import net.java.truevfs.kernel.spec.cio.MultiplexingOutputService;
30  import net.java.truecommons.cio.OutputService;
31  
32  /**
33   * An archive driver for GZIP compressed TAR files (TAR.GZIP).
34   * <p>
35   * Subclasses must be thread-safe and should be immutable!
36   *
37   * @author Christian Schlichtherle
38   */
39  @Immutable
40  public class TarGZipDriver extends TarDriver {
41  
42      /**
43       * Returns the size of the I/O buffer.
44       * <p>
45       * The implementation in the class {@link TarGZipDriver} returns
46       * {@link Streams#BUFFER_SIZE}.
47       *
48       * @return The size of the I/O buffer.
49       */
50      public int getBufferSize() {
51          return Streams.BUFFER_SIZE;
52      }
53  
54      /**
55       * Returns the compression level to use when writing a GZIP sink stream.
56       * <p>
57       * The implementation in the class {@link TarGZipDriver} returns
58       * {@link Deflater#BEST_COMPRESSION}.
59       *
60       * @return The compression level to use when writing a GZIP sink stream.
61       */
62      public int getLevel() {
63          return Deflater.BEST_COMPRESSION;
64      }
65  
66      @Override
67      protected InputService<TarDriverEntry> newInput(
68              final FsModel model,
69              final FsInputSocketSource source)
70      throws IOException {
71          final class Source extends AbstractSource {
72              @Override
73              public InputStream stream() throws IOException {
74                  final InputStream in = source.stream();
75                  try {
76                      return new GZIPInputStream(in, getBufferSize());
77                  } catch(final Throwable ex) {
78                      try {
79                          in.close();
80                      } catch (final Throwable ex2) {
81                          ex.addSuppressed(ex2);
82                      }
83                      throw ex;
84                  }
85              }
86          } // Source
87  
88          return new TarInputService(model, new Source(), this);
89      }
90  
91      @Override
92      protected OutputService<TarDriverEntry> newOutput(
93              final FsModel model,
94              final FsOutputSocketSink sink,
95              final InputService<TarDriverEntry> input)
96      throws IOException {
97          final class Sink extends AbstractSink {
98              @Override
99              public OutputStream stream() throws IOException {
100                 final OutputStream out = sink.stream();
101                 try {
102                     return new GZIPOutputStream(out, getBufferSize(), getLevel());
103                 } catch(final Throwable ex) {
104                     try {
105                         out.close();
106                     } catch (final Throwable ex2) {
107                         ex.addSuppressed(ex2);
108                     }
109                     throw ex;
110                 }
111             }
112         } // Sink
113 
114         return new MultiplexingOutputService<>(getPool(),
115                 new TarOutputService(model, new Sink(), this));
116     }
117 
118     /**
119      * Sets {@link FsAccessOption#STORE} in {@code options} before
120      * forwarding the call to {@code controller}.
121      */
122     @Override
123     protected FsOutputSocketSink sink(
124             BitField<FsAccessOption> options,
125             final FsController controller,
126             final FsNodeName name) {
127         // Leave FsAccessOption.COMPRESS untouched - the driver shall be given
128         // opportunity to apply its own preferences to sort out such a conflict.
129         options = options.set(STORE);
130         return new FsOutputSocketSink(options,
131                 controller.output(options, name, null));
132     }
133 
134     /** Extends its super class to set the deflater level. */
135     private static final class GZIPOutputStream
136     extends java.util.zip.GZIPOutputStream {
137         GZIPOutputStream(OutputStream out, int size, int level)
138         throws IOException {
139             super(out, size);
140             def.setLevel(level);
141         }
142     } // GZIPOutputStream
143 }