/* * Copyright [2014] [Christian Loehnert, krampenschiesser@gmail.com] * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package de.ks.file; import com.google.common.net.MediaType; import de.ks.activity.executor.ActivityExecutor; import de.ks.idnadrev.entity.FileReference; import de.ks.option.Options; import de.ks.persistence.PersistentWork; import de.ks.persistence.transaction.TransactionProvider; import de.ks.reflection.PropertyPath; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; import javax.persistence.criteria.Predicate; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.util.List; import java.util.concurrent.CompletableFuture; public class FileStore { private static final Logger log = LoggerFactory.getLogger(FileStore.class); private static final String KEY_MIMETYPE = PropertyPath.property(FileReference.class, r -> r.getMimeType()); private final FileOptions options; @Inject ActivityExecutor executorService; public FileStore() { options = Options.get(FileOptions.class); } public CompletableFuture<FileReference> getReference(File file) { if (!file.exists()) { throw new IllegalArgumentException("File " + file + " does not exist"); } CompletableFuture<String> md5Sum = CompletableFuture.supplyAsync(() -> getMd5(file), executorService); return md5Sum.thenApply(md5 -> resolveReference(md5, file)); } protected FileReference resolveReference(String md5, File file) { String mimeType = getMimeType(file); long size = getFileSize(file); FileReference fileReference = PersistentWork.forName(FileReference.class, file.getName()); if (fileReference != null) { String originalMd5 = fileReference.getMd5Sum(); if (!originalMd5.equals(md5)) { log.info("MD5Sum of file {} has changed from {} to {}", file.getName(), originalMd5, md5); } fileReference.setMd5Sum(md5); fileReference.setMimeType(mimeType); fileReference.setSizeInBytes(size); return fileReference; } else { FileReference reference = new FileReference(file.getName(), md5); reference.setSizeInBytes(size); reference.setMimeType(mimeType); return reference; } } private long getFileSize(File file) { try { return Files.size(file.toPath()); } catch (IOException e) { log.error("Could not get filesize from {}", file, e); return -1; } } private String getMimeType(File file) { Path path = file.toPath(); try { return Files.probeContentType(path); } catch (IOException e) { log.error("Could not get mime type from ", file, e); return null; } } protected String getMd5(File file) { try { return DigestUtils.md5Hex(new FileInputStream(file)); } catch (IOException e) { log.error("Could not read md5 from file {}", file, e); throw new RuntimeException(e); } } public File getFile(FileReference fileReference) { Path path = Paths.get(getFileStoreDir(), fileReference.getMd5Sum(), fileReference.getName()); return path.toFile(); } public void scheduleCopy(FileReference reference, File file) { CopyFileAfterCommit synchronization = new CopyFileAfterCommit(() -> { saveInFileStore(reference, file); }); TransactionProvider.instance.getCurrentTransaction().ifPresent(tx -> { tx.registerSynchronization(synchronization); }); } public void saveInFileStore(FileReference ref, File file) { if (!file.exists()) { throw new IllegalArgumentException("File " + file + " has to exist"); } if (ref.getMd5Sum() == null) { throw new IllegalArgumentException("MD5 sum has to be calculated"); } Path dir = Paths.get(getFileStoreDir(), ref.getMd5Sum()); try { Files.createDirectories(dir); } catch (IOException e) { log.error("Could not store create parent directory {}", dir, e); return; } Path targetPath = Paths.get(getFileStoreDir(), ref.getMd5Sum(), ref.getName()); if (options.shouldCopy()) { try { Files.copy(file.toPath(), targetPath, StandardCopyOption.REPLACE_EXISTING); } catch (IOException e) { log.error("could not copy {} to {}", file.toPath(), targetPath); throw new RuntimeException(e); } } else { try { Files.move(file.toPath(), targetPath, StandardCopyOption.REPLACE_EXISTING); } catch (IOException e) { log.error("could not move {} to {}", file.toPath(), targetPath); throw new RuntimeException(e); } } } public String getFileStoreDir() { return Options.get(FileOptions.class).getFileStoreDir(); } public String replaceFileStoreDir(String description) { String replacement = "file://" + getFileStoreDir(); if (!replacement.endsWith(File.separator)) { replacement = replacement + File.separator; } String newDescription = StringUtils.replace(description, FileReference.FILESTORE_VAR, replacement); return newDescription; } public List<FileReference> getFilesByMimeType(MediaType mediaType) { assert mediaType != null; List<FileReference> references = PersistentWork.from(FileReference.class, (root, query, builder) -> { javax.persistence.criteria.Path<String> mimeType = root.get(KEY_MIMETYPE); if (!mediaType.type().equals("*")) { String pattern = mediaType.type() + "%"; Predicate like = builder.like(mimeType, pattern); query.where(like); } }, null); log.info("Found {} references for mimeType {}", references.size(), mediaType.type()); return references; } }