/* * This file is part of vospace-file-service * Copyright (C) 2021 Istituto Nazionale di Astrofisica * SPDX-License-Identifier: GPL-3.0-or-later */ package it.inaf.ia2.transfer.service; import it.inaf.ia2.transfer.persistence.FileDAO; import it.inaf.ia2.transfer.persistence.model.FileInfo; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.security.Principal; import java.util.List; import org.kamranzafar.jtar.TarEntry; import org.kamranzafar.jtar.TarOutputStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.util.FileSystemUtils; @Service public class ArchiveService { private static final Logger LOG = LoggerFactory.getLogger(ArchiveService.class); private final static int BUFFER_SIZE = 100 * 1024; @Autowired private FileDAO fileDAO; @Autowired private AuthorizationService authorizationService; private final File generatedDir; public ArchiveService(@Value("${generated.dir}") String generatedDir) { this.generatedDir = new File(generatedDir); if (!this.generatedDir.exists()) { if (!this.generatedDir.mkdirs()) { throw new IllegalStateException("Unable to create directory " + this.generatedDir.getAbsolutePath()); } } } public void createArchive(ArchiveJob job) { LOG.trace("Started archive job " + job.getJobId()); try { // TODO: check total size limit // TODO: switch on archive type File parentDir = getArchiveParentDir(job.getPrincipal()); if (!parentDir.exists()) { if (!parentDir.mkdirs()) { throw new IllegalStateException("Unable to create directory " + parentDir.getAbsolutePath()); } } File archiveFile = parentDir.toPath().resolve(job.getJobId() + "." + job.getType().getExtension()).toFile(); if (!archiveFile.createNewFile()) { throw new IllegalStateException("Unable to create file " + archiveFile.getAbsolutePath()); } String commonParent = getCommonParent(job.getVosPaths()); // support directory used to generate folder inside tar files (path is redefined each time by TarEntry class) File supportDir = Files.createTempDirectory("dir").toFile(); try ( TarOutputStream tos = new TarOutputStream( new BufferedOutputStream(new FileOutputStream(archiveFile)))) { for (FileInfo fileInfo : fileDAO.getArchiveFileInfos(job.getVosPaths())) { String relPath = fileInfo.getVirtualPath().substring(commonParent.length()); if (fileInfo.isDirectory()) { tos.putNextEntry(new TarEntry(supportDir, relPath)); continue; } // TODO: handle different locations if (!authorizationService.isDownloadable(fileInfo, job.getPrincipal())) { // TODO: proper exception type throw new RuntimeException("Unauthorized"); } File file = new File(fileInfo.getOsPath()); LOG.trace("Adding file " + file.getAbsolutePath() + " to tar archive"); writeFileIntoTarArchive(file, relPath, tos); } } finally { FileSystemUtils.deleteRecursively(supportDir); } // TODO: update job status } catch (Throwable t) { LOG.error("Error happened creating archive", t); } } public File getArchiveParentDir(Principal principal) { return generatedDir.toPath().resolve(principal.getName()).toFile(); } private String getCommonParent(List vosPaths) { String commonParent = null; for (String vosPath : vosPaths) { if (commonParent == null) { commonParent = vosPath; } else { StringBuilder newCommonParent = new StringBuilder(); boolean same = true; for (int i = 0; same && i < Math.min(commonParent.length(), vosPath.length()); i++) { if (commonParent.charAt(i) == vosPath.charAt(i)) { newCommonParent.append(commonParent.charAt(i)); } else { same = false; } } commonParent = newCommonParent.toString(); } } return commonParent; } private void writeFileIntoTarArchive(File file, String path, TarOutputStream tos) throws IOException { TarEntry tarEntry = new TarEntry(file, path); try ( InputStream is = new FileInputStream(file)) { tos.putNextEntry(tarEntry); try ( BufferedInputStream origin = new BufferedInputStream(is)) { int count; byte data[] = new byte[BUFFER_SIZE]; while ((count = origin.read(data)) != -1) { tos.write(data, 0, count); } tos.flush(); } } } }