/* * Data Hub Service (DHuS) - For Space data distribution. * Copyright (C) 2013,2014,2015 GAEL Systems * * This file is part of DHuS software sources. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package fr.gael.dhus.datastore; import fr.gael.dhus.database.object.Product; import fr.gael.dhus.datastore.exception.DataStoreException; import fr.gael.dhus.system.config.ConfigurationManager; import fr.gael.dhus.util.UnZip; import org.apache.commons.io.FileUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; /** * Manages binaries of Product on the local file system. */ @Component public class FileSystemDataStore implements DataStore<Product> { private static final Logger LOGGER = LogManager.getLogger(FileSystemDataStore.class); private final int BUFFER_SIZE = 1024; @Autowired private ConfigurationManager cfgManager; @Autowired private IncomingManager incomingManager; // Getters & Setters public ConfigurationManager getCfgManager () { return cfgManager; } public void setCfgManager (ConfigurationManager cfgManager) { this.cfgManager = cfgManager; } public IncomingManager getIncomingManager () { return incomingManager; } public void setIncomingManager (IncomingManager incomingManager) { this.incomingManager = incomingManager; } @Override public void add (Product product) { throw new UnsupportedOperationException ("No supported yet !"); } @Override public void remove (Product product, Destination destination) { if (product == null) { throw new DataStoreException ("Cannot remove a null product"); } if (product.getLocked ()) { throw new DataStoreException ("Cannot remove product: " + product + ", it is locked by the system"); } if (!isRemovable (product)) { LOGGER.warn("Cannot delete product :" + product); } switch (destination) { case TRASH: moveProduct (product, cfgManager.getArchiveConfiguration () .getEvictionConfiguration ().getTrashPath ()); break; case ERROR: moveProduct (product, cfgManager.getArchiveConfiguration () .getIncomingConfiguration ().getErrorPath ()); break; case NONE: break; default: LOGGER.warn("Unknown destination the product will be deleted"); } removeFiles (product); } /** * Deletes all binaries of the given product. * * @param product product to delete */ private void removeFiles (Product product) { try { // Delete product file from path String prodPath = product.getPath ().toString (); if (!prodPath.equals (product.getOrigin ())) { prodPath = prodPath.replaceAll ("file://?", "/"); deleteIncomingFolder (prodPath); } // Delete product file from download path deleteIncomingFolder (product.getDownloadablePath ()); // Delete product thumbnail if (product.getThumbnailFlag ()) { deleteIncomingFolder (product.getThumbnailPath ()); } // Delete product quick-look if (product.getQuicklookFlag ()) { deleteIncomingFolder (product.getQuicklookPath ()); } } catch (Exception e) { LOGGER.error("There was an error while removing processed files for" + " product '" + product.getIdentifier () + "'", e); } } /** * Delete a file or a directory via it path. * * @param path path of file to delete. * @throws IOException in case deletion is unsuccessful. */ private void deleteIncomingFolder (String path) throws IOException { if (path == null) { return; } File container = new File (path); if (!container.exists () || !incomingManager.isInIncoming (container)) { return; } if (IncomingManager.INCOMING_PRODUCT_DIR.equals (container .getParentFile ().getName ())) { container = container.getParentFile (); } if (HierarchicalDirectoryBuilder.DHUS_ENTRY_NAME.equals (container .getParentFile ().getName ())) { container = container.getParentFile (); } if (container != null) { FileUtils.forceDelete (container); } } /** * Returns true if binaries of product are in the file system storage defined * in configuration. * * @param product product to check for deletion. * @return true if product is present in file system storage. */ private boolean isRemovable (Product product) { String a_path = cfgManager.getArchiveConfiguration () .getIncomingConfiguration ().getPath (); if (a_path.startsWith ("file:/")) { a_path = a_path.substring (6); } File a_file = new File (a_path); if (a_file.exists () && a_file.isDirectory ()) { a_path = a_file.getAbsolutePath (); String p_file = product.getPath ().getPath (); return p_file.startsWith (a_path) && !product.getLocked (); } return false; } /** * Moves product download zip into the given destination. * <p><b>Note:</b> generates the zip of product if necessary.</p> * * @param product product to move. * @param destination destination of product */ private void moveProduct (Product product, String destination) { if (destination == null || destination.trim ().isEmpty ()) { return; } Path zip_destination = Paths.get (destination); String download_path = product.getDownloadablePath (); try { if (download_path != null) { File product_zip_file = Paths.get (download_path).toFile (); FileUtils.moveFileToDirectory ( product_zip_file, zip_destination.toFile (), true); } else { Path product_path = Paths.get (product.getPath ().getPath ()); if (UnZip.supported (product_path.toAbsolutePath ().toString ())) { FileUtils.moveFileToDirectory ( product_path.toFile (), zip_destination.toFile (), true); } else { zip_destination.resolve (product_path.getFileName ()); generateZip (product_path.toFile (), zip_destination.toFile ()); } } } catch (IOException e) { LOGGER.error("Cannot move product: " + product.getPath () + " into " + destination, e); } } /** * Generates a zip file. * * @param source source file or directory to compress. * @param destination destination of zipped file. * @return the zipped file. */ private void generateZip (File source, File destination) throws IOException { if (source == null || !source.exists ()) { throw new IllegalArgumentException ("source file should exist"); } if (destination == null) { throw new IllegalArgumentException ( "destination file should be not null"); } FileOutputStream output = new FileOutputStream (destination); ZipOutputStream zip_out = new ZipOutputStream (output); zip_out.setLevel ( cfgManager.getDownloadConfiguration ().getCompressionLevel ()); List<QualifiedFile> file_list = getFileList (source); byte[] buffer = new byte[BUFFER_SIZE]; for (QualifiedFile qualified_file : file_list) { ZipEntry entry = new ZipEntry (qualified_file.getQualifier ()); InputStream input = new FileInputStream (qualified_file.getFile ()); int read; zip_out.putNextEntry (entry); while ((read = input.read (buffer)) != -1) { zip_out.write (buffer, 0, read); } input.close (); zip_out.closeEntry (); } zip_out.close (); output.close (); } /** * Computes all normal files present in a file. * @param src * @return a list of {@link QualifiedFile} */ private List<QualifiedFile> getFileList (File src) { ArrayList<QualifiedFile> result = new ArrayList<> (); getFileList (src, null, result); return result; } /** * Internal method of {@link FileSystemDataStore#getFileList(File)} */ private void getFileList (File src, String prefix_folder, ArrayList<QualifiedFile> files) { String qualifier; if (prefix_folder == null) { qualifier = src.getName (); } else { qualifier = prefix_folder + File.separator + src.getName (); } if (src.isFile ()) { files.add (new QualifiedFile (src, qualifier)); } else if (src.isDirectory ()) { for (File file : src.listFiles ()) { getFileList (file, qualifier, files); } } } /** * Represent a {@link File} with a qualifier name. */ private static class QualifiedFile { private final File file; private final String qualifier; public QualifiedFile (File file, String qualifier) { this.qualifier = qualifier; this.file = file; } public String getQualifier () { return qualifier; } public File getFile () { return file; } @Override public String toString () { return file.getAbsolutePath () + " --> " + qualifier; } } }