/* * Eoulsan development code * * This code may be freely distributed and modified under the * terms of the GNU Lesser General Public License version 2.1 or * later and CeCILL-C. This should be distributed with the code. * If you do not have a copy, see: * * http://www.gnu.org/licenses/lgpl-2.1.txt * http://www.cecill.info/licences/Licence_CeCILL-C_V1-en.txt * * Copyright for this code is held jointly by the Genomic platform * of the Institut de Biologie de l'École normale supérieure and * the individual authors. These should be listed in @author doc * comments. * * For more information on the Eoulsan project and its aims, * or to join the Eoulsan Google group, visit the home page * at: * * http://outils.genomique.biologie.ens.fr/eoulsan * */ package fr.ens.biologie.genomique.eoulsan.data.protocols; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.Collections; import java.util.List; import fr.ens.biologie.genomique.eoulsan.annotations.HadoopCompatible; import fr.ens.biologie.genomique.eoulsan.data.DataFile; import fr.ens.biologie.genomique.eoulsan.data.DataFileMetadata; import fr.ens.biologie.genomique.eoulsan.data.DataFormat; import fr.ens.biologie.genomique.eoulsan.data.DataFormatRegistry; import fr.ens.biologie.genomique.eoulsan.io.CompressionType; import fr.ens.biologie.genomique.eoulsan.util.FileUtils; import fr.ens.biologie.genomique.eoulsan.util.StringUtils; /** * This class implements a File Protocol. * @since 1.0 * @author Laurent Jourdren */ @HadoopCompatible public class FileDataProtocol extends AbstractDataProtocol { /** Protocol name. */ public static final String PROTOCOL_NAME = "file"; @Override public String getName() { return PROTOCOL_NAME; } @Override public File getSourceAsFile(final DataFile dataFile) { if (dataFile == null || dataFile.getSource() == null) { throw new NullPointerException("The source is null."); } final String protocolName = dataFile.getProtocolPrefixInSource(); if (protocolName == null) { return new File(dataFile.getSource()); } return new File(dataFile.getSource().substring(protocolName.length() + 1)); } @Override public InputStream getData(final DataFile src) throws IOException { return FileUtils.createInputStream(getSourceAsFile(src)); } @Override public OutputStream putData(final DataFile src) throws IOException { return FileUtils.createOutputStream(getSourceAsFile(src)); } @Override public DataFileMetadata getMetadata(final DataFile src) throws IOException { if (!exists(src, true)) { throw new FileNotFoundException("File not found: " + src); } final File f = getSourceAsFile(src); final SimpleDataFileMetadata result = new SimpleDataFileMetadata(); result.setContentLength(f.length()); result.setLastModified(f.lastModified()); final DataFormat format = DataFormatRegistry.getInstance() .getDataFormatFromFilename(src.getName()); result.setDataFormat(format); if (format != null) { result.setContentType(format.getContentType()); } else { result.setContentType(StringUtils.getCommonContentTypeFromExtension( StringUtils.extensionWithoutCompressionExtension(src.getName()))); } final CompressionType ct = CompressionType.getCompressionTypeByFilename(src.getSource()); if (ct != null) { result.setContentEncoding(ct.getContentEncoding()); } if (f.isDirectory()) { result.setDirectory(true); } if (Files.isSymbolicLink(f.toPath())) { result.setSymbolicLink(new DataFile(Files.readSymbolicLink(f.toPath()))); } return result; } @Override public boolean exists(final DataFile src, final boolean followLink) { final Path path = getSourceAsFile(src).toPath(); if (followLink) { Files.exists(path); } return Files.exists(path, LinkOption.NOFOLLOW_LINKS); } @Override public boolean canRead() { return true; } @Override public boolean canWrite() { return true; } @Override public void mkdir(final DataFile dir) throws IOException { final File file = getSourceAsFile(dir); if (!file.mkdir()) { throw new IOException("Unable to create the directory: " + dir); } } @Override public void mkdirs(final DataFile dir) throws IOException { final File file = getSourceAsFile(dir); if (!file.mkdirs()) { throw new IOException("Unable to create the directory: " + dir); } } @Override public boolean canMkdir() { return true; } @Override public void symlink(final DataFile target, final DataFile link) throws IOException { if (target == null) { throw new NullPointerException("target argument is null"); } if (link == null) { throw new NullPointerException("link argument is null"); } if (link.exists()) { throw new IOException("the symlink already exists"); } if (target.getProtocol() != this) { throw new IOException("the protocol of the target is not " + getName() + " protocol: " + target); } if (link.getProtocol() != this) { throw new IOException("the protocol of the link is not " + getName() + " protocol: " + link); } final Path targetPath = target.toFile().toPath(); final Path linkPath = link.toFile().toPath(); Files.createSymbolicLink(linkPath, targetPath); } @Override public boolean canSymlink() { return true; } @Override public void delete(final DataFile file, final boolean recursive) throws IOException { final Path path = getSourceAsFile(file).toPath(); // Check if use wants to remove / if (new File("/").toPath().equals(path.normalize().toAbsolutePath())) { throw new IOException("Cannot remove /: " + file); } // Non recursive deletion if (!(recursive && Files.isDirectory(path, LinkOption.NOFOLLOW_LINKS))) { Files.delete(path); return; } // Remove recursively a directory Files.walkFileTree(path, new SimpleFileVisitor<Path>() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { Files.delete(file); return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException e) throws IOException { Files.delete(dir); return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFileFailed(Path file, IOException e) throws IOException { throw new IOException("Cannot remove file: " + file); } }); } @Override public boolean canDelete() { return true; } @Override public List<DataFile> list(final DataFile file) throws IOException { final File directoryFile = getSourceAsFile(file); if (!directoryFile.exists()) { throw new FileNotFoundException("File not found: " + file); } if (!directoryFile.isDirectory()) { throw new IOException("The file is not a directory: " + file); } // List directory final File[] files = directoryFile.listFiles(); if (files == null) { return Collections.emptyList(); } // Convert the File array to a list of DataFile final List<DataFile> result = new ArrayList<>(files.length); for (File f : files) { result.add(new DataFile(f)); } // Return an unmodifiable list return Collections.unmodifiableList(result); } @Override public boolean canList() { return true; } @Override public void rename(final DataFile src, final DataFile dest) throws IOException { if (dest == null) { throw new NullPointerException("dest argument is null"); } if (dest.getProtocol() != this) { throw new IOException("the protocol of the dest is not " + getName() + " protocol: " + dest); } final File file = getSourceAsFile(src); final File destFile = getSourceAsFile(dest); if (!file.renameTo(destFile)) { throw new IOException("Cannot rename " + src + " to " + dest); } } @Override public boolean canRename() { return true; } }