org.tinymediamanager.core.Utils Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2012 - 2019 Manuel Laggner
*
* 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 org.tinymediamanager.core;
import org.apache.commons.io.FileExistsException;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.LocaleUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.apache.commons.text.StringEscapeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tinymediamanager.Globals;
import org.tinymediamanager.LaunchUtil;
import org.tinymediamanager.core.Message.MessageLevel;
import org.tinymediamanager.scraper.util.StrgUtils;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.URI;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.AtomicMoveNotSupportedException;
import java.nio.file.DirectoryStream;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitOption;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.security.CodeSource;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import static java.nio.file.FileVisitResult.CONTINUE;
/**
* The Class Utils.
*
* @author Manuel Laggner / Myron Boyle
*/
public class Utils {
private static final Logger LOGGER = LoggerFactory.getLogger(Utils.class);
private static final Pattern localePattern = Pattern.compile("messages_(.{2})_?(.{2}){0,1}\\.properties", Pattern.CASE_INSENSITIVE);
// <0-N>
private static final Pattern stackingPattern1 = Pattern.compile("(.*?)[ _.-]+((?:cd|dvd|p(?:ar)?t|dis[ck])[ _.-]*[1-9]{1})(\\.[^.]+)$",
Pattern.CASE_INSENSITIVE);
//
private static final Pattern stackingPattern2 = Pattern.compile("(.*?)[ _.-]+((?:cd|dvd|p(?:ar)?t|dis[ck])[ _.-]*[a-d])(\\.[^.]+)$",
Pattern.CASE_INSENSITIVE);
// moviename-a.avi // modified mandatory delimiter (but no space), and A-D must be at end!
private static final Pattern stackingPattern3 = Pattern.compile("(.*?)[_.-]+([a-d])(\\.[^.]+)$", Pattern.CASE_INSENSITIVE);
// moviename-1of2.avi, moviename-1 of 2.avi
private static final Pattern stackingPattern4 = Pattern.compile("(.*?)[ \\(_.-]+([1-9][ .]?of[ .]?[1-9])[ \\)_-]?(\\.[^.]+)$",
Pattern.CASE_INSENSITIVE);
// folder stacking marker <0-N> - must be last part
private static final Pattern folderStackingPattern = Pattern.compile("(.*?)[ _.-]*((?:cd|dvd|p(?:ar)?t|dis[ck])[ _.-]*[1-9]{1})$",
Pattern.CASE_INSENSITIVE);
private static List availableLocales = new ArrayList<>();
private static String tempFolder;
static {
// get the systems default temp folder
try {
String temp = System.getProperty("java.io.tmpdir");
Path tempFolder = Paths.get(temp);
if (Files.exists(tempFolder) && Files.isWritable(tempFolder)) {
Utils.tempFolder = temp;
}
else {
Utils.tempFolder = "tmp";
}
}
catch (Exception | Error ignored) {
if (tempFolder != null) {
tempFolder = "tmp";
}
}
}
private Utils() {
// hide public constructor for utility classes
}
/**
* gets the filename part, and returns last extension
*
* @param path
* the path to get the last extension for
* @return the last extension found
*/
public static String getExtension(Path path) {
String ext = "";
String fn = path.getFileName().toString();
int i = fn.lastIndexOf('.');
if (i > 0) {
ext = fn.substring(i + 1);
}
return ext;
}
/**
* this is the TMM variant of isRegularFiles()
* because deduplication creates windows junction points, we check here if it is
* not a directory, and either a regular file or "other" one.
* see http://serverfault.com/a/667220
*
* @param file
* @return
*/
public static boolean isRegularFile(Path file) {
// see windows impl http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/7u40-b43/sun/nio/fs/WindowsFileAttributes.java#451
try {
BasicFileAttributes attr = Files.readAttributes(file, BasicFileAttributes.class);
return (attr.isRegularFile() || attr.isOther()) && !attr.isDirectory();
}
catch (IOException e) {
return false;
}
}
/**
* this is the TMM variant of isRegularFiles()
* because deduplication creates windows junction points, we check here if it is
* not a directory, and either a regular file or "other" one.
* see http://serverfault.com/a/667220
*
* @param attr
* @return
*/
public static boolean isRegularFile(BasicFileAttributes attr) {
// see windows impl http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/7u40-b43/sun/nio/fs/WindowsFileAttributes.java#451
return (attr.isRegularFile() || attr.isOther()) && !attr.isDirectory();
}
/**
* dumps a complete Object (incl sub-classes 5 levels deep) to System.out
*
* @param o
* the object to dump
*/
public static void dumpObject(Object o) {
System.out.println(ReflectionToStringBuilder.toString(o, new RecursiveToStringStyle(5))); // NOSONAR
}
/**
* returns the relative path of 2 absolute file paths
* "/a/b & /a/b/c/d -> c/d
*
* @param parent
* the directory
* @param child
* the subdirectory
* @return relative path
*/
public static String relPath(String parent, String child) {
return relPath(Paths.get(parent), Paths.get(child));
}
/**
* returns the relative path of 2 absolute file paths
* "/a/b & /a/b/c/d -> c/d
*
* @param parent
* the directory
* @param child
* the subdirectory
* @return relative path
*/
public static String relPath(Path parent, Path child) {
return parent.relativize(child).toString();
}
/**
* Returns the sortable variant of title/originaltitle
* eg "The Bourne Legacy" -> "Bourne Legacy, The".
*
* @param title
* the title
* @return the title/originaltitle in its sortable format
*/
public static String getSortableName(String title) {
if (title == null || title.isEmpty()) {
return "";
}
if (title.toLowerCase(Locale.ROOT).matches("^die hard$") || title.toLowerCase(Locale.ROOT).matches("^die hard[:\\s].*")) {
return title;
}
if (title.toLowerCase(Locale.ROOT).matches("^die another day$") || title.toLowerCase(Locale.ROOT).matches("^die another day[:\\s].*")) {
return title;
}
for (String prfx : Settings.getInstance().getTitlePrefix()) {
String delim = "\\s+"; // one or more spaces needed
if (prfx.matches(".*['`´]$")) { // ends with hand-picked delim, so no space might be possible
delim = "";
}
// only move the first found prefix
if (title.matches("(?i)^" + Pattern.quote(prfx) + delim + "(.*)")) {
title = title.replaceAll("(?i)^" + Pattern.quote(prfx) + delim + "(.*)", "$1, " + prfx);
break;
}
}
return title.trim();
}
/**
* Returns the common name of title/originaltitle when it is named sortable
* eg "Bourne Legacy, The" -> "The Bourne Legacy".
*
* @param title
* the title
* @return the original title
*/
public static String removeSortableName(String title) {
if (title == null || title.isEmpty()) {
return "";
}
for (String prfx : Settings.getInstance().getTitlePrefix()) {
String delim = " "; // one spaces as delim
if (prfx.matches(".*['`´]$")) { // ends with hand-picked delim, so no space between prefix and title
delim = "";
}
title = title.replaceAll("(?i)(.*), " + prfx + "$", prfx + delim + "$1");
}
return title.trim();
}
/**
* Clean stacking markers.
* Same logic as detection, but just returning string w/o
*
* @param filename
* the filename WITH extension
* @return the string
*/
public static String cleanStackingMarkers(String filename) {
if (!StringUtils.isEmpty(filename)) {
// see http://kodi.wiki/view/Advancedsettings.xml#moviestacking
// basically returning (Title)(Stacking)(Ignore)(Extension)
// <0-N>
Matcher m = stackingPattern1.matcher(filename);
if (m.matches()) {
return m.group(1) + m.group(3); // just return String w/o stacking
}
//
m = stackingPattern2.matcher(filename);
if (m.matches()) {
return m.group(1) + m.group(3); // just return String w/o stacking
}
// moviename-2.avi // modified mandatory delimiter, and AD must be at end!
m = stackingPattern3.matcher(filename);
if (m.matches()) {
return m.group(1) + m.group(3); // just return String w/o stacking
}
// moviename-1of2.avi, moviename-1 of 2.avi
m = stackingPattern4.matcher(filename);
if (m.matches()) {
return m.group(1) + m.group(3); // just return String w/o stacking
}
}
return filename; // no cleanup, return 1:1
}
/**
* Clean stacking markers.
* Same logic as detection, but just returning string w/o
*
* @param filename
* the filename WITH extension
* @return the string
*/
public static String cleanFolderStackingMarkers(String filename) {
if (!StringUtils.isEmpty(filename)) {
Matcher m = folderStackingPattern.matcher(filename);
if (m.matches()) {
return m.group(1); // just return String w/o stacking
}
}
return filename;
}
/**
* Returns the stacking information from FOLDER name
*
* @param filename
* the filename
* @return the stacking information
*/
public static String getFolderStackingMarker(String filename) {
if (!StringUtils.isEmpty(filename)) {
// see http://kodi.wiki/view/Advancedsettings.xml#moviestacking
// basically returning (Title)(Volume)(Ignore)(Extension)
// <0-N> // FIXME: check for first delimiter (optional/mandatory)!
Matcher m = folderStackingPattern.matcher(filename);
if (m.matches()) {
return m.group(2);
}
}
return "";
}
/**
* Returns the stacking information from filename
*
* @param filename
* the filename
* @return the stacking information
*/
public static String getStackingMarker(String filename) {
if (!StringUtils.isEmpty(filename)) {
// see http://kodi.wiki/view/Advancedsettings.xml#moviestacking
// basically returning (Title)(Stacking)(Ignore)(Extension)
// <0-N>
Matcher m = stackingPattern1.matcher(filename);
if (m.matches()) {
return m.group(2);
}
//
m = stackingPattern2.matcher(filename);
if (m.matches()) {
return m.group(2);
}
// moviename-a.avi // modified mandatory delimiter, and AD must be at end!
m = stackingPattern3.matcher(filename);
if (m.matches()) {
return m.group(2);
}
// moviename-1of2.avi, moviename-1 of 2.avi
m = stackingPattern4.matcher(filename);
if (m.matches()) {
return m.group(2);
}
}
return "";
}
public static String substr(String str, String pattern) {
Pattern regex = Pattern.compile(pattern);
Matcher m = regex.matcher(str);
if (m.find()) {
return m.group(1);
}
else {
return "";
}
}
/**
* Returns the stacking prefix
*
* @param filename
* the filename
* @return the stacking prefix - might be empty
*/
public static String getStackingPrefix(String filename) {
String stack = getStackingMarker(filename).replaceAll("[0-9]", "");
if (stack.length() == 1 || stack.contains("of")) {
// A-D and (X of Y) - no prefix here
stack = "";
}
return stack;
}
/**
* Returns the stacking information from filename
*
* @param filename
* the filename
* @return the stacking information
*/
public static int getStackingNumber(String filename) {
if (!StringUtils.isEmpty(filename)) {
String stack = getStackingMarker(filename);
if (!stack.isEmpty()) {
if (stack.equalsIgnoreCase("a")) {
return 1;
}
else if (stack.equalsIgnoreCase("b")) {
return 2;
}
else if (stack.equalsIgnoreCase("c")) {
return 3;
}
else if (stack.equalsIgnoreCase("d")) {
return 4;
}
if (stack.contains("of")) {
stack = stack.replaceAll("of.*", ""); // strip all after "of", so we have the first number
}
try {
return Integer.parseInt(stack.replaceAll("[^0-9]", ""));
}
catch (Exception e) {
return 0;
}
}
}
return 0;
}
/**
* Checks if is valid imdb id.
*
* @param imdbId
* the imdb id
* @return true, if is valid imdb id
*/
public static boolean isValidImdbId(String imdbId) {
if (StringUtils.isEmpty(imdbId)) {
return false;
}
return imdbId.matches("tt\\d{6,8}");
}
/**
* Unquote.
*
* @param str
* the str
* @return the string
*/
public static String unquote(String str) {
if (str == null)
return null;
return str.replaceFirst("^\\\"(.*)\\\"$", "$1");
}
/**
* gets the UTF-8 encoded System property.
*
* @param prop
* the property to fetch
* @return the enc prop
*/
@SuppressWarnings("deprecation")
private static String getEncProp(String prop) {
String property = System.getProperty(prop);
if (StringUtils.isBlank(property)) {
return "";
}
try {
return URLEncoder.encode(property, "UTF-8");
}
catch (UnsupportedEncodingException e) {
return URLEncoder.encode(property);
}
}
public static void removeEmptyStringsFromList(List list) {
list.removeAll(Collections.singleton(null));
list.removeAll(Collections.singleton(""));
}
/**
* replaces a string with placeholder ({}) with the string from the replacement array the strings in the replacement array have to be in the same
* order as the placeholder in the source string
*
* @param source
* string
* @param replacements
* array
* @return replaced string
*/
public static String replacePlaceholders(String source, String[] replacements) {
String result = source;
int index = 0;
Pattern pattern = Pattern.compile("\\{\\}");
while (true) {
Matcher matcher = pattern.matcher(result);
if (matcher.find()) {
try {
if (replacements.length > index) {
result = result.replaceFirst(pattern.pattern(), StringEscapeUtils.escapeJava(replacements[index]));
}
else {
result = result.replaceFirst(pattern.pattern(), "");
}
}
catch (Exception e) {
result = result.replaceFirst(pattern.pattern(), "");
}
index++;
}
else {
break;
}
}
return StrgUtils.removeDuplicateWhitespace(result);
}
/**
* modified version of commons-io FileUtils.moveDirectory(); adapted to Java 7 NIO
* since renameTo() might not work in first place, retry it up to 5 times.
* (better wait 5 sec for success, than always copying a 50gig directory ;)
* And NO, we're NOT doing a copy+delete as fallback!
*
* @param srcDir
* the directory to be moved
* @param destDir
* the destination directory
* @return true, if successful
* @throws IOException
* if an IO error occurs moving the file
*/
public static boolean moveDirectorySafe(Path srcDir, Path destDir) throws IOException {
// rip-off from
// http://svn.apache.org/repos/asf/commons/proper/io/trunk/src/main/java/org/apache/commons/io/FileUtils.java
if (srcDir == null) {
throw new NullPointerException("Source must not be null"); // NOSONAR
}
if (destDir == null) {
throw new NullPointerException("Destination must not be null"); // NOSONAR
}
if (!srcDir.toAbsolutePath().toString().equals(destDir.toAbsolutePath().toString())) {
LOGGER.debug("try to move folder {} to {}", srcDir, destDir);
if (!Files.isDirectory(srcDir)) {
throw new FileNotFoundException("Source '{}" + srcDir + "' does not exist, or is not a directory"); // NOSONAR
}
if (Files.exists(destDir) && !Files.isSameFile(destDir, srcDir)) {
// extra check for Windows/OSX, where the File.equals is case insensitive
// so we know now, that the Dir is the same, but the absolute name does not match
throw new FileExistsException("Destination '" + destDir + "' already exists"); // NOSONAR
}
if (destDir.getParent() != null && !Files.exists(destDir.getParent())) {
// create parent folder structure, else renameTo does not work
// NULL parent means, that we just have one relative folder like Paths.get("bla") - no need to create anything
try {
Files.createDirectories(destDir.getParent());
}
catch (Exception e) {
LOGGER.error("could not create directory structure {}", destDir.getParent());
// but we try a move anyway...
}
}
// rename folder; try 5 times and wait a sec
boolean rename = false;
for (int i = 0; i < 5; i++) {
try {
// need atomic fs move for changing cASE
Files.move(srcDir, destDir, StandardCopyOption.ATOMIC_MOVE);
rename = true;// no exception
}
catch (AtomicMoveNotSupportedException a) {
// if it fails (b/c not on same file system) use that; original documentation
/*
* When moving a directory requires that its entries be moved then this method fails (by throwing an {@code IOException}). To move a file
* tree may involve copying rather than moving directories and this can be done using the {@link #copy copy} method in conjunction with
* the {@link #walkFileTree Files.walkFileTree} utility method.
*/
// in this case we do a recursive copy & delete
// copy all files (with re-creating symbolic links if there are some)
try (Stream stream = Files.walk(srcDir)) {
Iterator srcFiles = stream.iterator();
while (srcFiles.hasNext()) {
Path source = srcFiles.next();
Path destination = destDir.resolve(srcDir.relativize(source));
if (Files.isSymbolicLink(source)) {
Files.createSymbolicLink(destination, source.toRealPath());
continue;
}
if (Files.isDirectory(source)) {
if (!Files.exists(destination)) {
Files.createDirectory(destination);
}
continue;
}
Files.copy(source, destination, StandardCopyOption.REPLACE_EXISTING);
}
// delete source files
Utils.deleteDirectoryRecursive(srcDir);
rename = true;
}
catch (IOException e) {
LOGGER.warn("rename problem (fallbacl): {}", e.getMessage()); // NOSONAR
}
}
catch (IOException e) {
LOGGER.warn("rename problem: {}", e.getMessage()); // NOSONAR
}
if (rename) {
break; // ok it worked, step out
}
try {
LOGGER.debug("rename did not work - sleep a while and try again..."); // NOSONAR
Thread.sleep(1000);
}
catch (InterruptedException e) { // NOSONAR
// we will not let the JVM abort the thread here -> just finish the logic without waiting any longer
LOGGER.warn("I'm so excited - could not sleep"); // NOSONAR
break;
}
}
// ok, we tried it 5 times - it still seems to be locked somehow. Continue
// with copying as fallback
// NOOO - we don't like to have some files copied and some not.
if (!rename) {
LOGGER.error("Failed to rename directory {} to {}", srcDir, destDir);
LOGGER.error("Movie renaming aborted.");
MessageManager.instance.pushMessage(new Message(MessageLevel.ERROR, srcDir, "message.renamer.failedrename")); // NOSONAR
return false;
}
else {
LOGGER.info("Successfully moved folder {} to {}", srcDir, destDir);
return true;
}
}
return true; // dir are equal
}
/**
* modified version of commons-io FileUtils.moveFile(); adapted to Java 7 NIO
* since renameTo() might not work in first place, retry it up to 5 times.
* (better wait 5 sec for success, than always copying a 50gig directory ;)
* And NO, we're NOT doing a copy+delete as fallback!
*
* @param srcFile
* the file to be moved
* @param destFile
* the destination file
* @throws NullPointerException
* if source or destination is {@code null}
* @throws FileExistsException
* if the destination file exists
* @throws IOException
* if source or destination is invalid
* @throws IOException
* if an IO error occurs moving the file
*/
public static boolean moveFileSafe(final Path srcFile, final Path destFile) throws IOException {
if (srcFile == null) {
throw new NullPointerException("Source must not be null");
}
if (destFile == null) {
throw new NullPointerException("Destination must not be null");
}
if (!srcFile.toAbsolutePath().toString().equals(destFile.toAbsolutePath().toString())) {
LOGGER.debug("try to move file {} to {}", srcFile, destFile);
if (!Files.exists(srcFile)) {
// allow moving of symlinks
// https://github.com/tinyMediaManager/tinyMediaManager/issues/410
if (!Files.isSymbolicLink(srcFile)) {
throw new FileNotFoundException("Source '" + srcFile + "' does not exist"); // NOSONAR
}
}
if (Files.isDirectory(srcFile)) {
throw new IOException("Source '" + srcFile + "' is a directory"); // NOSONAR
}
if (Files.exists(destFile) && !Files.isSameFile(destFile, srcFile)) {
// extra check for windows, where the File.equals is case insensitive
// so we know now, that the File is the same, but the absolute name does not match
throw new FileExistsException("Destination '" + destFile + "' already exists");
}
if (Files.isDirectory(destFile)) {
throw new IOException("Destination '" + destFile + "' is a directory");
}
// rename folder; try 5 times and wait a sec
boolean rename = false;
for (int i = 0; i < 5; i++) {
try {
// need atomic fs move for changing cASE
Files.move(srcFile, destFile, StandardCopyOption.ATOMIC_MOVE);
rename = true;// no exception
}
catch (AtomicMoveNotSupportedException a) {
// if it fails (b/c not on same file system) use that
try {
Files.copy(srcFile, destFile, StandardCopyOption.REPLACE_EXISTING);
Files.delete(srcFile);
rename = true; // no exception
}
catch (IOException e) {
LOGGER.warn("rename problem (fallbacl): {}", e.getMessage()); // NOSONAR
}
}
catch (IOException e) {
LOGGER.warn("rename problem: {}", e.getMessage()); // NOSONAR
}
if (rename) {
break; // ok it worked, step out
}
try {
LOGGER.debug("rename did not work - sleep a while and try again...");
Thread.sleep(1000);
}
catch (InterruptedException e) { // NOSONAR
// we will not let the JVM abort the thread here -> just finish the logic without waiting any longer
LOGGER.warn("I'm so excited - could not sleep");
break;
}
}
if (!rename) {
LOGGER.error("Failed to rename file {} to {}", srcFile, destFile);
MessageManager.instance.pushMessage(new Message(MessageLevel.ERROR, srcFile, "message.renamer.failedrename")); // NOSONAR
return false;
}
else {
LOGGER.info("Successfully moved file from {} to {}", srcFile, destFile);
return true;
}
}
return true; // files are equal
}
/**
* copy a file, preserving the attributes, but NOT overwrite it
*
* @param srcFile
* the file to be copied
* @param destFile
* the target
* @return true/false
* @throws NullPointerException
* if source or destination is {@code null}
* @throws FileExistsException
* if the destination file exists
* @throws IOException
* if source or destination is invalid
* @throws IOException
* if an IO error occurs moving the file
*/
public static boolean copyFileSafe(final Path srcFile, final Path destFile) throws IOException {
return copyFileSafe(srcFile, destFile, false);
}
/**
* copy a file, preserving the attributes
*
* @param srcFile
* the file to be copied
* @param destFile
* the target
* @param overwrite
* overwrite the target?
* @return true/false
* @throws NullPointerException
* if source or destination is {@code null}
* @throws FileExistsException
* if the destination file exists
* @throws IOException
* if source or destination is invalid
* @throws IOException
* if an IO error occurs moving the file
*/
public static boolean copyFileSafe(final Path srcFile, final Path destFile, boolean overwrite) throws IOException {
if (srcFile == null) {
throw new NullPointerException("Source must not be null");
}
if (destFile == null) {
throw new NullPointerException("Destination must not be null");
}
if (!srcFile.toAbsolutePath().toString().equals(destFile.toAbsolutePath().toString())) {
LOGGER.debug("try to copy file {} to {}", srcFile, destFile);
if (!Files.exists(srcFile)) {
throw new FileNotFoundException("Source '" + srcFile + "' does not exist");
}
if (Files.isDirectory(srcFile)) {
throw new IOException("Source '" + srcFile + "' is a directory");
}
if (!overwrite) {
if (Files.exists(destFile) && !Files.isSameFile(destFile, srcFile)) {
// extra check for windows, where the File.equals is case insensitive
// so we know now, that the File is the same, but the absolute name does not match
throw new FileExistsException("Destination '" + destFile + "' already exists");
}
}
if (Files.isDirectory(destFile)) {
throw new IOException("Destination '" + destFile + "' is a directory");
}
// rename folder; try 5 times and wait a sec
boolean rename = false;
for (int i = 0; i < 5; i++) {
try {
// replace existing for changing cASE
Files.copy(srcFile, destFile, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES);
rename = true;// no exception
}
catch (UnsupportedOperationException u) {
// maybe copy with attributes does not work here (across file systems), just try without file attributes
try {
// replace existing for changing cASE
Files.copy(srcFile, destFile, StandardCopyOption.REPLACE_EXISTING);
rename = true;// no exception
}
catch (IOException e) {
LOGGER.warn("copy did not work (fallback): {}", e.getMessage());
}
}
catch (IOException e) {
LOGGER.warn("copy did not work: {}", e.getMessage());
}
if (rename) {
break; // ok it worked, step out
}
try {
LOGGER.debug("rename did not work - sleep a while and try again..."); // NOSONAR
Thread.sleep(1000);
}
catch (InterruptedException e) { // NOSONAR
// we will not let the JVM abort the thread here -> just finish the logic without waiting any longer
LOGGER.warn("I'm so excited - could not sleep"); // NOSONAR
break;
}
}
if (!rename) {
LOGGER.error("Failed to rename file {} to {}", srcFile, destFile);
MessageManager.instance.pushMessage(new Message(MessageLevel.ERROR, srcFile, "message.renamer.failedrename")); // NOSONAR
return false;
}
else {
LOGGER.info("Successfully moved file from {} to {}", srcFile, destFile);
return true;
}
}
return true; // files are equal
}
/**
* PHYSICALLY deletes a file by moving it to datasource backup folder
* DS\.backup\<filename>
* maintaining its originating directory
*
* @param file
* the file to be deleted
* @param datasource
* the data source (for the location of the backup folder)
* @return true/false if successful
*/
public static boolean deleteFileWithBackup(Path file, String datasource) {
Path ds = Paths.get(datasource);
if (!file.startsWith(ds)) { // safety
LOGGER.warn("could not delete file '{}': datasource '{}' does not match", file, datasource);
return false;
}
if (Files.isDirectory(file)) {
LOGGER.warn("could not delete file '{}': file is a directory!", file);
return false;
}
// backup
try {
// create path
Path backup = Paths.get(ds.toAbsolutePath().toString(), Constants.BACKUP_FOLDER, ds.relativize(file).toString());
if (!Files.exists(backup.getParent())) {
Files.createDirectories(backup.getParent());
}
// overwrite backup file by deletion prior
Files.deleteIfExists(backup);
return moveFileSafe(file, backup);
}
catch (IOException e) {
LOGGER.warn("Could not delete file: {}", e.getMessage());
return false;
}
}
/**
* PHYSICALLY deletes a file (w/o backup)
* only doing a check if it is not a directory
*
* @param file
* the file to be deleted
* @return true/false if successful
*/
public static boolean deleteFileSafely(Path file) {
file = file.toAbsolutePath();
if (Files.isDirectory(file)) {
LOGGER.warn("Will not delete file '{}': file is a directory!", file);
return false;
}
try {
Files.deleteIfExists(file);
}
catch (Exception e) {
LOGGER.warn("Could not delete file: {}", e.getMessage());
return false;
}
return true;
}
/**
* PHYSICALLY deletes a complete directory by moving it to datasource backup folder
* DS\.backup\<foldername>
* maintaining its originating directory
*
* @param folder
* the folder to be deleted
* @param datasource
* the datasource of this folder
* @return true/false if successful
*/
public static boolean deleteDirectorySafely(Path folder, String datasource) {
folder = folder.toAbsolutePath();
Path ds = Paths.get(datasource);
if (!Files.isDirectory(folder)) {
LOGGER.warn("Will not delete folder '{}': folder is a file, NOT a directory!", folder);
return false;
}
if (!folder.startsWith(ds)) { // safety
LOGGER.warn("Will not delete folder '{}': datasource '{}' does not match", folder, datasource);
return false;
}
// backup
try {
Instant instant = Instant.now();
long timeStampSeconds = instant.getEpochSecond();
// create path
Path backup = Paths.get(ds.toAbsolutePath().toString(), Constants.BACKUP_FOLDER, ds.relativize(folder).toString() + timeStampSeconds);
if (!Files.exists(backup.getParent())) {
Files.createDirectories(backup.getParent());
}
// overwrite backup file by deletion prior
// deleteDirectoryRecursive(backup); // we timestamped our folder - no need for it
return moveDirectorySafe(folder, backup);
}
catch (IOException e) {
LOGGER.warn("could not delete directory: {}", e.getMessage());
return false;
}
}
/**
* returns a list of all available GUI languages
*
* @return List of Locales
*/
public static List getLanguages() {
if (!availableLocales.isEmpty()) {
// do not return the original list to avoid external manipulation
return new ArrayList<>(availableLocales);
}
availableLocales.add(getLocaleFromLanguage(Locale.ENGLISH.getLanguage()));
try {
// list all properties files from the classpath
InputStream is = Utils.class.getResourceAsStream("/");
if (is != null) {
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String resource;
while ((resource = br.readLine()) != null) {
parseLocaleFromFilename(resource);
}
}
else {
// we may be in a .jar file
CodeSource src = Utils.class.getProtectionDomain().getCodeSource();
if (src != null) {
URL jar = src.getLocation();
try (InputStream jarInputStream = jar.openStream(); ZipInputStream zip = new ZipInputStream(jarInputStream)) {
while (true) {
ZipEntry e = zip.getNextEntry();
if (e == null) {
break;
}
parseLocaleFromFilename(e.getName());
}
}
}
}
}
catch (Exception e) {
LOGGER.warn("could not read locales: " + e.getMessage(), e);
}
// do not return the original list to avoid external manipulation
return new ArrayList<>(availableLocales);
}
private static void parseLocaleFromFilename(String filename) {
Matcher matcher = localePattern.matcher(filename);
if (matcher.matches()) {
Locale myloc;
String language = matcher.group(1);
String country = matcher.group(2);
if (country != null) {
// found language & country
myloc = new Locale(language, country);
}
else {
// found only language
myloc = getLocaleFromLanguage(language);
}
if (myloc != null && !availableLocales.contains(myloc)) {
availableLocales.add(myloc);
}
}
}
/**
* Gets a correct Locale (language + country) from given language.
*
* @param language
* as 2char
* @return Locale
*/
public static Locale getLocaleFromLanguage(String language) {
if (StringUtils.isBlank(language)) {
return Locale.getDefault();
}
// do we have a newer locale settings style?
if (language.length() > 2) {
try {
return LocaleUtils.toLocale(language);
}
catch (Exception e) {
// Whoopsie. try to fix string....
if (language.matches("^\\w\\w_\\w\\w.*")) {
return LocaleUtils.toLocale(language.substring(0, 5));
}
}
}
if (language.equalsIgnoreCase("en")) {
return Locale.US; // don't mess around; at least fixate this
}
// try to find country based locale
Locale l = null;
List countries = LocaleUtils.countriesByLanguage(language.toLowerCase(Locale.ROOT));
for (Locale locale : countries) {
if (locale.getCountry().equalsIgnoreCase(language)) {
// map to main countries; de->de_DE (and not de_CH)
l = locale;
}
}
if (l == null && !countries.isEmpty()) {
// well, take the first one
l = countries.get(0);
}
if (l == null) {
l = new Locale(language); // let java decide..?
}
return l;
}
/**
* creates a zipped backup of file in backup folder with yyyy-MM-dd timestamp
* does overwrite already existing file from today!
*
* @param file
* the file to backup
*/
public static void createBackupFile(Path file) {
createBackupFile(file, true);
}
/**
* creates a zipped backup of file in backup folder with yyyy-MM-dd timestamp
*
* @param file
* the file to backup
* @param overwrite
* if file is already there, ignore that and overwrite with new copy
*/
public static void createBackupFile(Path file, boolean overwrite) {
Path backup = Paths.get(Globals.BACKUP_FOLDER);
try {
if (!Files.exists(backup)) {
Files.createDirectory(backup);
}
if (!Files.exists(file)) {
return;
}
DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
String date = formatter.format(new Date());
backup = backup.resolve(file.getFileName() + "." + date + ".zip");
if (!Files.exists(backup) || overwrite) {
createZip(backup, file, "/" + file.getFileName().toString()); // just put in main dir
}
}
catch (IOException e) {
LOGGER.error("Could not backup file {}: {}", file, e.getMessage());
}
}
/**
* Deletes old backup files in backup folder; keep only last X files
*
* @param file
* the file of backup to be deleted
* @param keep
* keep last X versions
*/
public static void deleteOldBackupFile(Path file, int keep) {
ArrayList al = new ArrayList<>();
String fname = file.getFileName().toString();
try (DirectoryStream directoryStream = Files.newDirectoryStream(Paths.get(Globals.BACKUP_FOLDER))) {
for (Path path : directoryStream) {
if (path.getFileName().toString().matches(fname + "\\.\\d{4}\\-\\d{2}\\-\\d{2}\\.zip") || // name.ext.yyyy-mm-dd.zip
path.getFileName().toString().matches(fname + "\\.\\d{4}\\-\\d{2}\\-\\d{2}")) { // old name.ext.yyyy-mm-dd
al.add(path);
}
}
}
catch (IOException e) {
LOGGER.error("could not list files from the backup folder: {}", e.getMessage());
return;
}
// sort files by creation date
al.sort((o1, o2) -> (int) (o1.toFile().lastModified() - o2.toFile().lastModified()));
for (int i = 0; i < al.size() - keep; i++) {
Path backupFile = al.get(i);
LOGGER.debug("deleting old backup file {}", backupFile.getFileName());
deleteFileSafely(backupFile);
}
}
/**
* Sends a wake-on-lan packet for specified MAC address across subnet
*
* @param macAddr
* the mac address to 'wake up'
*/
public static void sendWakeOnLanPacket(String macAddr) {
// Broadcast IP address
final String IP = "255.255.255.255";
final int port = 7;
try {
final byte[] MACBYTE = new byte[6];
final String[] hex = macAddr.split("(\\:|\\-)");
for (int i = 0; i < 6; i++) {
MACBYTE[i] = (byte) Integer.parseInt(hex[i], 16);
}
final byte[] bytes = new byte[6 + 16 * MACBYTE.length];
for (int i = 0; i < 6; i++) {
bytes[i] = (byte) 0xff;
}
for (int i = 6; i < bytes.length; i += MACBYTE.length) {
System.arraycopy(MACBYTE, 0, bytes, i, MACBYTE.length);
}
// Send UDP packet here
final InetAddress address = InetAddress.getByName(IP);
final DatagramPacket packet = new DatagramPacket(bytes, bytes.length, address, port);
try (DatagramSocket socket = new DatagramSocket()) {
socket.send(packet);
}
LOGGER.info("Sent WOL packet to {}", macAddr);
}
catch (final Exception e) {
LOGGER.error("Error sending WOL packet to {} - {}", macAddr, e.getMessage());
}
}
/**
* create a ProcessBuilder for restarting TMM
*
* @return the process builder
*/
public static ProcessBuilder getPBforTMMrestart() {
Path f = Paths.get("tmm.jar");
if (!Files.exists(f)) {
LOGGER.error("cannot restart TMM - tmm.jar not found.");
return null; // when we are in GIT, return null = normal close
}
List arguments = getJVMArguments();
arguments.add(0, LaunchUtil.getJVMPath()); // java exe before JVM args
arguments.add("-Dsilent=noupdate"); // start GD.jar instead of TMM.jar, since we don't have the libs in manifest
arguments.add("-jar");
arguments.add("getdown.jar"); // NOSONAR
arguments.add(".");
ProcessBuilder pb = new ProcessBuilder(arguments);
pb.directory(Paths.get("").toAbsolutePath().toFile()); // set working directory (current TMM dir)
return pb;
}
/**
* create a ProcessBuilder for restarting TMM to the updater
*
* @return the process builder
*/
public static ProcessBuilder getPBforTMMupdate() {
Path f = Paths.get("getdown.jar");
if (!Files.exists(f)) {
LOGGER.error("cannot start updater - getdown.jar not found.");
return null; // when we are in GIT, return null = normal close
}
List arguments = getJVMArguments();
arguments.add(0, LaunchUtil.getJVMPath()); // java exe before JVM args
arguments.add("-jar");
arguments.add("getdown.jar"); // NOSONAR
arguments.add(".");
ProcessBuilder pb = new ProcessBuilder(arguments);
pb.directory(Paths.get("").toAbsolutePath().toFile()); // set working directory (current TMM dir)
return pb;
}
/**
* gets all the JVM parameters used for starting TMM
* like -Dfile.encoding=UTF8 or others
* needed for restarting tmm :)
*
* @return list of jvm parameters
*/
private static List getJVMArguments() {
RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
List arguments = new ArrayList<>(runtimeMxBean.getInputArguments());
// fixtate some
if (!arguments.contains("-Djava.net.preferIPv4Stack=true")) {
arguments.add("-Djava.net.preferIPv4Stack=true");
}
if (!arguments.contains("-Dfile.encoding=UTF-8")) {
arguments.add("-Dfile.encoding=UTF-8");
}
return arguments;
}
/**
* Deletes a complete directory recursively, using Java NIO
*
* @param dir
* directory to delete
* @throws IOException
*/
public static void deleteDirectoryRecursive(Path dir) throws IOException {
if (!Files.exists(dir)) {
return;
}
LOGGER.info("Deleting complete directory: {}", dir);
Files.walkFileTree(dir, new FileVisitor() {
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) {
LOGGER.warn("Could not delete {} - {}", file, exc.getMessage());
return FileVisitResult.CONTINUE;
}
});
}
/**
* check whether a folder is empty or not
*
* @param folder
* the folder to be checked
* @return true/false
* @throws IOException
*/
public static boolean isFolderEmpty(final Path folder) throws IOException {
try (DirectoryStream dirStream = Files.newDirectoryStream(folder)) {
return !dirStream.iterator().hasNext();
}
}
/**
* Creates (or adds) a file to a ZIP
*
* @param zipFile
* Path of zip file
* @param toBeAdded
* Path to be added
* @param internalPath
* the location inside the ZIP like /aa/a.txt
*/
public static void createZip(Path zipFile, Path toBeAdded, String internalPath) {
Map env = new HashMap<>();
try {
// check if file exists
env.put("create", String.valueOf(!Files.exists(zipFile)));
// use a Zip filesystem URI
URI fileUri = zipFile.toUri(); // here
URI zipUri = new URI("jar:" + fileUri.getScheme(), fileUri.getPath(), null);
// zip
// try with resource
try (FileSystem zipfs = FileSystems.newFileSystem(zipUri, env)) {
// Create internal path in the zipfs
Path internalTargetPath = zipfs.getPath(internalPath);
if (!Files.exists(internalTargetPath)) {
// Create directory
Files.createDirectory(internalTargetPath);
}
// copy a file into the zip file
if (Files.isDirectory(toBeAdded)) {
try (Stream files = Files.walk(toBeAdded)) {
files.forEach(source -> {
try {
if (Files.isSameFile(source, toBeAdded)) {
return;
}
if (Files.isDirectory(source)) {
// Create directory
Files.createDirectory(internalTargetPath.resolve(toBeAdded.relativize(source)));
}
else {
Files.copy(source, internalTargetPath.resolve(toBeAdded.relativize(source).toString()), StandardCopyOption.REPLACE_EXISTING);
}
}
catch (Exception e) {
LOGGER.error("Failed to create zip file: {}", e.getMessage()); // NOSONAR
}
});
}
}
else {
Files.copy(toBeAdded, internalTargetPath, StandardCopyOption.REPLACE_EXISTING);
}
}
}
catch (Exception e) {
LOGGER.error("Failed to create zip file: {}", e.getMessage()); // NOSONAR
}
}
/**
* Unzips the specified zip file to the specified destination directory. Replaces any files in the destination, if they already exist.
*
* @param zipFile
* the name of the zip file to extract
* @param destDir
* the directory to unzip to
* @throws IOException
*/
public static void unzip(Path zipFile, final Path destDir) {
Map env = new HashMap<>();
try {
// if the destination doesn't exist, create it
if (!Files.exists(destDir)) {
Files.createDirectories(destDir);
}
// check if file exists
env.put("create", String.valueOf(!Files.exists(zipFile)));
// use a Zip filesystem URI
URI fileUri = zipFile.toUri(); // here
URI zipUri = new URI("jar:" + fileUri.getScheme(), fileUri.getPath(), null);
try (FileSystem zipfs = FileSystems.newFileSystem(zipUri, env)) {
final Path root = zipfs.getPath("/");
// walk the zip file tree and copy files to the destination
Files.walkFileTree(root, new SimpleFileVisitor() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
final Path destFile = Paths.get(destDir.toString(), file.toString());
LOGGER.debug("Extracting file {} to {}", file, destFile);
Files.copy(file, destFile, StandardCopyOption.REPLACE_EXISTING);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
final Path dirToCreate = Paths.get(destDir.toString(), dir.toString());
if (!Files.exists(dirToCreate)) {
LOGGER.debug("Creating directory {}", dirToCreate);
Files.createDirectory(dirToCreate);
}
return FileVisitResult.CONTINUE;
}
});
}
}
catch (Exception e) {
LOGGER.error("Failed to create zip file: {}", e.getMessage()); // NOSONAR
}
}
/**
* extract our templates (only if non existing)
*/
public static void extractTemplates() {
extractTemplates(false);
}
/**
* extract our templates (use force to overwrite)
*/
public static void extractTemplates(boolean force) {
Path dest = Paths.get("templates");
try (DirectoryStream directoryStream = Files.newDirectoryStream(dest)) {
// count the directory amount if not forced
int dirCount = 0;
if (!force) {
for (Path path : directoryStream) {
if (Files.isDirectory(path)) {
dirCount++;
}
}
}
// if forced or the directory count is zero, we will extract the templates.zip
if (dirCount == 0 || force) {
Utils.unzip(dest.resolve("templates.jar"), dest);
}
}
catch (IOException e) {
LOGGER.warn("failed to extract templates: {}", e.getMessage());
}
}
/**
* Java NIO replacement of commons-io
*
* @param file
* the file to write the string to
* @param text
* the text to be written into the file
* @throws IOException
* any {@link IOException} thrown
*/
public static void writeStringToFile(Path file, String text) throws IOException {
byte[] buf = text.getBytes(StandardCharsets.UTF_8);
Files.write(file, buf);
}
/**
* Java NIO replacement of commons-io
*
* @param file
* the file to read the string from
* @return the read string
* @throws IOException
* any {@link IOException} thrown
*/
public static String readFileToString(Path file) throws IOException {
byte[] fileArray = Files.readAllBytes(file);
return new String(fileArray, StandardCharsets.UTF_8);
}
/**
* Copies a complete directory recursively, using Java NIO
*
* @param from
* source
* @param to
* destination
* @throws IOException
* any {@link IOException} thrown
*/
public static void copyDirectoryRecursive(Path from, Path to) throws IOException {
LOGGER.info("Copyin complete directory from {} to {}", from, to);
Files.walkFileTree(from, new CopyFileVisitor(to));
}
/**
* logback does not clean older log files than 32 days in the past. We have to clean the log files too
*/
public static void cleanOldLogs() {
Pattern pattern = Pattern.compile("\\d{4}-\\d{2}-\\d{2}");
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DATE, -30);
Date dateBefore30Days = cal.getTime();
// the log file pattern is logs/tmm.%d.%i.log.gz
try (DirectoryStream directoryStream = Files.newDirectoryStream(Paths.get("logs"))) {
for (Path path : directoryStream) {
Matcher matcher = pattern.matcher(path.getFileName().toString());
if (matcher.find()) {
try {
Date date = StrgUtils.parseDate(matcher.group());
if (dateBefore30Days.after(date)) {
Utils.deleteFileSafely(path);
}
}
catch (Exception e) {
LOGGER.debug("could not clean old logs: {}", e.getMessage());
}
}
}
}
catch (IOException e) {
LOGGER.debug("could not clean old logs: {}", e.getMessage());
}
}
public static String getArtworkExtension(String url) {
String ext = FilenameUtils.getExtension(url);
if (StringUtils.isBlank(ext) || "tbn".equals(ext)) {
// no extension or tbn? fall back to jpg
ext = "jpg";
}
return ext.toLowerCase(Locale.ROOT);
}
/**
* get all files from the given path
*
* @param root
* the root folder to search files for
* @return a list of all found files
*/
public static List listFiles(Path root) {
final List filesFound = new ArrayList<>();
if (!Files.isDirectory(root)) {
return filesFound;
}
try (DirectoryStream directoryStream = Files.newDirectoryStream(root)) {
for (Path path : directoryStream) {
if (Files.isRegularFile(path)) {
filesFound.add(path);
}
}
}
catch (IOException e) {
LOGGER.warn("could not get a file listing: {}", e.getMessage());
}
return filesFound;
}
/**
* get all files from the given path recursive
*
* @param root
* the root folder to search files for
* @return a list of all found files
*/
public static List listFilesRecursive(Path root) {
final List filesFound = new ArrayList<>();
if (!Files.isDirectory(root)) {
return filesFound;
}
try {
Files.walkFileTree(root, EnumSet.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE, new AbstractFileVisitor() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
if (Utils.isRegularFile(file)) {
filesFound.add(file);
}
return FileVisitResult.CONTINUE;
}
});
}
catch (Exception e) {
LOGGER.warn("could not get a file listing: {}", e.getMessage());
}
return filesFound;
}
/**
* flush the {@link FileOutputStream} to the disk
*/
public static void flushFileOutputStreamToDisk(FileOutputStream fileOutputStream) {
if (fileOutputStream == null) {
return;
}
try {
fileOutputStream.flush();
fileOutputStream.getFD().sync(); // wait until file has been completely written
// give it a few milliseconds
Thread.sleep(150);
}
catch (Exception e) {
LOGGER.error("could not flush to disk: {}", e.getMessage());
}
}
/**
* Visitor for copying a directory recursively
* Usage: Files.walkFileTree(sourcePath, new CopyFileVisitor(targetPath));
*/
public static class CopyFileVisitor extends SimpleFileVisitor {
private final Path targetPath;
private Path sourcePath = null;
public CopyFileVisitor(Path targetPath) {
this.targetPath = targetPath;
}
@Override
public FileVisitResult preVisitDirectory(final Path dir, final BasicFileAttributes attrs) {
if (sourcePath == null) {
sourcePath = dir;
}
Path target = targetPath.resolve(sourcePath.relativize(dir));
if (!Files.exists(target)) {
try {
Files.createDirectories(target);
}
catch (FileAlreadyExistsException e) {
// ignore
}
catch (IOException x) {
return FileVisitResult.SKIP_SUBTREE;
}
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
Files.copy(file, targetPath.resolve(sourcePath.relativize(file)), StandardCopyOption.REPLACE_EXISTING);
return FileVisitResult.CONTINUE;
}
}
/**
* get the temporary folder for this tmm instance
*
* @return a string to the temporary folder
*/
public static String getTempFolder() {
return tempFolder;
}
/**
* Method to get a list of files with the given regular expression
*
* @param regexList list of regular expression
* @return a list of files
*/
public static HashSet getUnknownFilesByRegex(Path folder, List regexList) {
GetUnknownFilesVisitor visitor = new GetUnknownFilesVisitor(regexList);
try {
Files.walkFileTree(folder, EnumSet.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE, visitor);
} catch (IOException e) {
LOGGER.error("could not get unknown files: {}", e.getMessage());
}
return visitor.fileList;
}
private static class GetUnknownFilesVisitor extends AbstractFileVisitor {
private HashSet fileList = new HashSet<>();
private List regexList;
GetUnknownFilesVisitor(List regexList) {
this.regexList = regexList;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attr) {
for (String regex : regexList) {
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(file.getFileName().toString());
if (m.find()) {
fileList.add(file);
}
}
return CONTINUE;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy