Java tutorial
/* * Copyright (c) 2004-2015 YAMJ Members * https://github.com/organizations/YAMJ/teams * * This file is part of the Yet Another Media Jukebox (YAMJ). * * YAMJ is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * any later version. * * YAMJ 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with YAMJ. If not, see <http://www.gnu.org/licenses/>. * * Web: https://github.com/YAMJ/yamj-v3 * */ package org.yamj.core.service.file; import java.io.*; import java.nio.channels.FileChannel; import java.util.ArrayList; import java.util.Collection; import java.util.StringTokenizer; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.yamj.common.tools.PropertyTools; import org.yamj.core.database.model.StageDirectory; import org.yamj.core.database.model.StageFile; public class FileTools { public static final String DEFAULT_CHARSET = "UTF-8"; private static final Logger LOG = LoggerFactory.getLogger(FileTools.class); private static final int BUFF_SIZE = 16 * 1024; private static final Collection<ReplaceEntry> UNSAFE_CHARS = new ArrayList<>(); private static Lock mkdirsLock = new ReentrantLock(); static { // What to do if the user specifies a blank encodeEscapeChar? Disable encoding! String encodeEscapeCharString = PropertyTools.getProperty("yamj3.file.filename.encodingEscapeChar", "$"); if (encodeEscapeCharString.length() > 0) { // What to do if the user specifies a >1 character long string? I guess just use the first char. final Character ENCODE_ESCAPE_CHAR = encodeEscapeCharString.charAt(0); String repChars = PropertyTools.getProperty("yamj3.file.filename.unsafeChars", "<>:\"/\\|?*"); for (String repChar : repChars.split("")) { if (repChar.length() > 0) { char ch = repChar.charAt(0); // Don't encode characters that are hex digits // Also, don't encode the escape char -- it is safe by definition! if (!Character.isDigit(ch) && -1 == "AaBbCcDdEeFf".indexOf(ch) && !ENCODE_ESCAPE_CHAR.equals(ch)) { String hex = Integer.toHexString(ch).toUpperCase(); UNSAFE_CHARS.add(new ReplaceEntry(repChar, ENCODE_ESCAPE_CHAR + hex)); } } } } // parse transliteration map: (source_character [-] transliteration_sequence [,])+ StringTokenizer st = new StringTokenizer( PropertyTools.getProperty("yamj3.file.filename.translateChars", ""), ","); while (st.hasMoreElements()) { final String token = st.nextToken(); String beforeStr = StringUtils.substringBefore(token, "-"); final String character = beforeStr.length() == 1 && (beforeStr.equals("\t") || beforeStr.equals(" ")) ? beforeStr : StringUtils.trimToNull(beforeStr); if (character == null) { // TODO Error message? continue; } String afterStr = StringUtils.substringAfter(token, "-"); final String translation = afterStr.length() == 1 && (afterStr.equals("\t") || afterStr.equals(" ")) ? afterStr : StringUtils.trimToNull(afterStr); if (translation == null) { // TODO Error message? // TODO Allow empty transliteration? continue; } UNSAFE_CHARS.add(new ReplaceEntry(character.toUpperCase(), translation.toUpperCase())); UNSAFE_CHARS.add(new ReplaceEntry(character.toLowerCase(), translation.toLowerCase())); } } private FileTools() { throw new UnsupportedOperationException("Utility class cannot be instantiated"); } private static class ReplaceEntry { private final String oldText; private final String newText; private final int oldLength; public ReplaceEntry(String oldtext, String newtext) { this.oldText = oldtext; this.newText = newtext; oldLength = oldtext.length(); } public String check(String filename) { String newFilename = filename; int pos = newFilename.indexOf(oldText, 0); while (pos >= 0) { newFilename = newFilename.substring(0, pos) + newText + newFilename.substring(pos + oldLength); pos = newFilename.indexOf(oldText, pos + oldLength); } return newFilename; } } /** * One buffer for each thread to allow threaded copies */ private static final ThreadLocal<byte[]> THREAD_BUFFER = new ThreadLocal<byte[]>() { @Override protected byte[] initialValue() { return new byte[BUFF_SIZE]; } }; public static int copy(InputStream is, OutputStream os) throws IOException { int bytesCopied = 0; byte[] buffer = THREAD_BUFFER.get(); try { while (Boolean.TRUE) { int amountRead = is.read(buffer); if (amountRead == -1) { break; } bytesCopied += amountRead; os.write(buffer, 0, amountRead); } } finally { try { if (is != null) { is.close(); } } catch (IOException error) { // ignore } try { if (os != null) { os.close(); } } catch (IOException error) { // ignore } } return bytesCopied; } /** * Copy the source file to the destination * * @param src * @param dst * @return */ public static boolean copyFile(String src, String dst) { File srcFile = new File(src); File dstFile = new File(dst); return copyFile(srcFile, dstFile); } /** * Copy the source file to the destination * * @param src * @param dst * @return */ public static boolean copyFile(File src, File dst) { boolean returnValue = Boolean.FALSE; if (!src.exists()) { LOG.error("The file '{}' does not exist", src); return returnValue; } if (dst.isDirectory()) { makeDirectories(dst); returnValue = copyFile(src, new File(dst + File.separator + src.getName())); } else { try (FileInputStream inSource = new FileInputStream(src); FileOutputStream outSource = new FileOutputStream(dst); FileChannel inChannel = inSource.getChannel(); FileChannel outChannel = outSource.getChannel()) { long p = 0, s = inChannel.size(); while (p < s) { p += inChannel.transferTo(p, 1024 * 1024, outChannel); } return Boolean.TRUE; } catch (IOException error) { LOG.error("Failed copying file '{}' to '{}'", src, dst); LOG.error("File copying error", error); returnValue = Boolean.FALSE; } } return returnValue; } /** * Create all directories up to the level of the file passed * * @param filename Source directory or file to create the directories * directories * @return */ public static boolean makeDirectories(String filename) { return makeDirectories(new File(filename)); } /** * Create all directories up to the level of the file passed * * @param filename Source directory or file to create the directories * @param numOfTries Number of attempts that will be made to create the * directories * @return */ public static boolean makeDirectories(String filename, int numOfTries) { return makeDirectories(new File(filename), numOfTries); } /** * Create all directories up to the level of the file passed * * @param file Source directory or file to create the directories * directories * @return */ public static boolean makeDirectories(File file) { return makeDirectories(file, 10); } /** * Create all directories up to the level of the file passed * * @param sourceDirectory Source directory or file to create the directories * @param numOfTries Number of attempts that will be made to create the * directories * @return */ public static boolean makeDirectories(final File sourceDirectory, int numOfTries) { File targetDirectory; if (sourceDirectory.isDirectory()) { targetDirectory = sourceDirectory; } else { targetDirectory = sourceDirectory.getParentFile(); } if (targetDirectory.exists()) { return Boolean.TRUE; } LOG.debug("Creating directories for {} ", targetDirectory.getAbsolutePath()); mkdirsLock.lock(); try { boolean status = targetDirectory.mkdirs(); int looper = 1; while (!status && looper++ <= numOfTries) { status = targetDirectory.mkdirs(); } if (status && looper > 10) { LOG.error("Failed creating the directory '{}'. Ensure this directory is read/write!", targetDirectory.getAbsolutePath()); return Boolean.FALSE; } return Boolean.TRUE; } finally { mkdirsLock.unlock(); } } /** * Create a directory hash from the filename * * @param filename * @return */ public static String createDirHash(final String filename) { // Skip if the filename is invalid OR has already been hashed if (StringUtils.isBlank(filename) || filename.contains(File.separator)) { return filename; } // Remove all the non-word characters from the filename, replacing with an underscore String cleanFilename = filename.replaceAll("[^\\p{L}\\p{N}]", "_").toLowerCase().trim(); StringBuilder dirHash = new StringBuilder(); dirHash.append(cleanFilename.substring(0, 1)).append(File.separator); dirHash.append(cleanFilename.substring(0, cleanFilename.length() > 1 ? 2 : 1)).append(File.separator); dirHash.append(filename); return dirHash.toString(); } /** * Create a directory has from the filename of a file * * @param file * @return */ public static String createDirHash(final File file) { return createDirHash(file.getName()); } /** * Read a file and return it as a string using default encoding * * @param file * @return the file content */ public static String readFileToString(File file) { return readFileToString(file, DEFAULT_CHARSET); } /** * Read a file and return it as a string * * @param file * @param encoding * @return the file content */ public static String readFileToString(File file, String encoding) { String data = ""; if (file == null) { LOG.error("Failed reading file, file is null"); } else { try { data = FileUtils.readFileToString(file, encoding); } catch (Exception ex) { LOG.error("Failed reading file {}", file.getName()); LOG.error("Error", ex); } } return data; } public static boolean isFileScannable(StageFile stageFile) { boolean scannable; if (StringUtils.isBlank(stageFile.getContent())) { scannable = isFileReadable(stageFile); } else { scannable = true; } return scannable; } public static boolean isFileReadable(StageFile stageFile) { boolean readable = false; try { File file = new File(stageFile.getFullPath()); final boolean exists = file.exists(); final boolean canRead = file.canRead(); readable = (exists && canRead); LOG.trace("File '{}' exists: {}", stageFile.getFullPath(), exists); LOG.trace("File '{}' readable: {}", stageFile.getFullPath(), canRead); } catch (Exception e) { LOG.trace("Could not determine if file '" + stageFile.getFullPath() + "' is readable", e); } return readable; } public static boolean isFileReadable(StageDirectory stageDirectory) { boolean readable = false; try { File file = new File(stageDirectory.getDirectoryPath()); final boolean exists = file.exists(); final boolean canRead = file.canRead(); readable = (exists && canRead); LOG.trace("Directory '{}' exists: {}", stageDirectory.getDirectoryPath(), exists); LOG.trace("Directory '{}' readable: {}", stageDirectory.getDirectoryPath(), canRead); } catch (Exception e) { LOG.trace("Could not determine if directory '" + stageDirectory.getDirectoryPath() + "' is readable", e); } return readable; } public static String makeSafeFilename(String filename) { String newFilename = filename; for (ReplaceEntry rep : UNSAFE_CHARS) { newFilename = rep.check(newFilename); } if (!newFilename.equals(filename)) { LOG.debug("Encoded filename string '{}' to '{}'", filename, newFilename); } return newFilename; } public static boolean isWithinSpecialFolder(StageFile stageFile, String folderName) { if (StringUtils.isBlank(folderName) || stageFile == null) { return false; } StageDirectory directory = stageFile.getStageDirectory(); if (directory == null) { return false; } if (directory.getDirectoryName().equalsIgnoreCase(folderName)) { return true; } return StringUtils.containsIgnoreCase(directory.getDirectoryPath(), getPathFragment(folderName)); } public static String getPathFragment(String folderName) { return FilenameUtils.separatorsToUnix("/" + folderName + "/"); } }