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}