001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.vfs2.provider.tar;
018
019import java.io.File;
020import java.io.FileInputStream;
021import java.io.IOException;
022import java.io.InputStream;
023import java.util.ArrayList;
024import java.util.Collection;
025import java.util.List;
026import java.util.zip.GZIPInputStream;
027
028import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
029import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
030import org.apache.commons.logging.Log;
031import org.apache.commons.logging.LogFactory;
032import org.apache.commons.vfs2.Capability;
033import org.apache.commons.vfs2.FileObject;
034import org.apache.commons.vfs2.FileSystemException;
035import org.apache.commons.vfs2.FileSystemOptions;
036import org.apache.commons.vfs2.Selectors;
037import org.apache.commons.vfs2.VfsLog;
038import org.apache.commons.vfs2.provider.AbstractFileName;
039import org.apache.commons.vfs2.provider.AbstractFileSystem;
040import org.apache.commons.vfs2.provider.UriParser;
041import org.apache.commons.vfs2.provider.bzip2.Bzip2FileObject;
042
043/**
044 * A read-only file system for Tar files.
045 */
046public class TarFileSystem extends AbstractFileSystem {
047    private static final int DEFAULT_INDEX_SIZE = 100;
048
049    private static final Log LOG = LogFactory.getLog(TarFileSystem.class);
050
051    private final File file;
052    private TarArchiveInputStream tarFile;
053
054    protected TarFileSystem(final AbstractFileName rootName, final FileObject parentLayer,
055            final FileSystemOptions fileSystemOptions) throws FileSystemException {
056        super(rootName, parentLayer, fileSystemOptions);
057
058        // Make a local copy of the file
059        file = parentLayer.getFileSystem().replicateFile(parentLayer, Selectors.SELECT_SELF);
060
061        // Open the Tar file
062        if (!file.exists()) {
063            // Don't need to do anything
064            tarFile = null;
065            return;
066        }
067
068        // tarFile = createTarFile(this.file);
069    }
070
071    @Override
072    public void init() throws FileSystemException {
073        super.init();
074
075        // Build the index
076        try {
077            final List<TarFileObject> strongRef = new ArrayList<>(DEFAULT_INDEX_SIZE);
078            TarArchiveEntry entry;
079            while ((entry = getTarFile().getNextTarEntry()) != null) {
080                final AbstractFileName name = (AbstractFileName) getFileSystemManager().resolveName(getRootName(),
081                        UriParser.encode(entry.getName()));
082
083                // Create the file
084                TarFileObject fileObj;
085                if (entry.isDirectory() && getFileFromCache(name) != null) {
086                    fileObj = (TarFileObject) getFileFromCache(name);
087                    fileObj.setTarEntry(entry);
088                    continue;
089                }
090
091                fileObj = createTarFileObject(name, entry);
092                putFileToCache(fileObj);
093                strongRef.add(fileObj);
094                fileObj.holdObject(strongRef);
095
096                // Make sure all ancestors exist
097                // TODO - create these on demand
098                TarFileObject parent = null;
099                for (AbstractFileName parentName = (AbstractFileName) name
100                        .getParent(); parentName != null; fileObj = parent, parentName = (AbstractFileName) parentName
101                                .getParent()) {
102                    // Locate the parent
103                    parent = (TarFileObject) getFileFromCache(parentName);
104                    if (parent == null) {
105                        parent = createTarFileObject(parentName, null);
106                        putFileToCache(parent);
107                        strongRef.add(parent);
108                        parent.holdObject(strongRef);
109                    }
110
111                    // Attach child to parent
112                    parent.attachChild(fileObj.getName());
113                }
114            }
115        } catch (final IOException e) {
116            throw new FileSystemException(e);
117        } finally {
118            closeCommunicationLink();
119        }
120    }
121
122    public InputStream getInputStream(final TarArchiveEntry entry) throws FileSystemException {
123        resetTarFile();
124        try {
125            while (!tarFile.getNextEntry().equals(entry)) {
126            }
127            return tarFile;
128        } catch (final IOException e) {
129            throw new FileSystemException(e);
130        }
131    }
132
133    protected void resetTarFile() throws FileSystemException {
134        // Reading specific entries requires skipping through the tar file from the beginning
135        // Not especially elegant, but we don't have the ability to seek to specific positions
136        // with an input stream.
137        if (this.file.exists()) {
138            recreateTarFile();
139        }
140    }
141
142    private void recreateTarFile() throws FileSystemException {
143        if (this.tarFile != null) {
144            try {
145                this.tarFile.close();
146            } catch (final IOException e) {
147                throw new FileSystemException("vfs.provider.tar/close-tar-file.error", file, e);
148            }
149            tarFile = null;
150        }
151        final TarArchiveInputStream tarFile = createTarFile(this.file);
152        this.tarFile = tarFile;
153    }
154
155    protected TarArchiveInputStream getTarFile() throws FileSystemException {
156        if (tarFile == null && this.file.exists()) {
157            recreateTarFile();
158        }
159
160        return tarFile;
161    }
162
163    protected TarFileObject createTarFileObject(final AbstractFileName name, final TarArchiveEntry entry)
164            throws FileSystemException {
165        return new TarFileObject(name, entry, this, true);
166    }
167
168    protected TarArchiveInputStream createTarFile(final File file) throws FileSystemException {
169        try {
170            if ("tgz".equalsIgnoreCase(getRootName().getScheme())) {
171                return new TarArchiveInputStream(new GZIPInputStream(new FileInputStream(file)));
172            } else if ("tbz2".equalsIgnoreCase(getRootName().getScheme())) {
173                return new TarArchiveInputStream(
174                        Bzip2FileObject.wrapInputStream(file.getAbsolutePath(), new FileInputStream(file)));
175            }
176            return new TarArchiveInputStream(new FileInputStream(file));
177        } catch (final IOException ioe) {
178            throw new FileSystemException("vfs.provider.tar/open-tar-file.error", file, ioe);
179        }
180    }
181
182    @Override
183    protected void doCloseCommunicationLink() {
184        // Release the tar file
185        try {
186            if (tarFile != null) {
187                tarFile.close();
188                tarFile = null;
189            }
190        } catch (final IOException e) {
191            // getLogger().warn("vfs.provider.tar/close-tar-file.error :" + file, e);
192            VfsLog.warn(getLogger(), LOG, "vfs.provider.tar/close-tar-file.error :" + file, e);
193        }
194    }
195
196    /**
197     * Returns the capabilities of this file system.
198     */
199    @Override
200    protected void addCapabilities(final Collection<Capability> caps) {
201        caps.addAll(TarFileProvider.capabilities);
202    }
203
204    /**
205     * Creates a file object.
206     */
207    @Override
208    protected FileObject createFile(final AbstractFileName name) throws FileSystemException {
209        // This is only called for files which do not exist in the Tar file
210        return new TarFileObject(name, null, this, false);
211    }
212
213    /**
214     * will be called after all file-objects closed their streams. protected void notifyAllStreamsClosed() {
215     * closeCommunicationLink(); }
216     */
217}