com.moviejukebox.tools.FileTools Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of yamj Show documentation
Show all versions of yamj Show documentation
Static analysis of MovieJukebox project
The newest version!
/*
* Copyright (c) 2004-2013 YAMJ Members
* http://code.google.com/p/moviejukebox/people/list
*
* This file is part of the Yet Another Movie Jukebox (YAMJ).
*
* The 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 the YAMJ. If not, see .
*
* Web: http://code.google.com/p/moviejukebox/
*
*/
package com.moviejukebox.tools;
import com.moviejukebox.model.Jukebox;
import com.moviejukebox.model.Movie;
import com.moviejukebox.model.MovieFile;
import com.moviejukebox.scanner.IArchiveScanner;
import static com.moviejukebox.tools.PropertiesUtil.getProperty;
import java.io.*;
import java.nio.channels.FileChannel;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.commons.io.FilenameUtils;
import static org.apache.commons.lang3.StringUtils.*;
import org.apache.log4j.Logger;
public class FileTools {
private static final Logger logger = Logger.getLogger(FileTools.class);
private static final String LOG_MESSAGE = "FileTools: ";
static final int BUFF_SIZE = 16 * 1024;
private static final Collection SUBTITLE_EXTENSIONS = new ArrayList();
private static final Collection UNSAFE_CHARS = new ArrayList();
private static final Collection GENERATED_FILENAMES = Collections.synchronizedCollection(new ArrayList());
private static boolean videoimageDownload = PropertiesUtil.getBooleanProperty("mjb.includeVideoImages", Boolean.FALSE);
private static int footerImageEnabled = PropertiesUtil.getIntProperty("mjb.footer.count", 0);
private static String indexFilesPrefix = getProperty("mjb.indexFilesPrefix", "");
// Literals
private static final String TO = "' to '";
private static final String BDMV_STREAM = File.separator + "BDMV" + File.separator + "STREAM";
// File Cache
public static ScannedFilesCache fileCache = new ScannedFilesCache();
// Lock for mkdirs
private static Lock fsLock = new ReentrantLock();
public static void initSubtitleExtensions() {
if (SUBTITLE_EXTENSIONS.isEmpty()) {
SUBTITLE_EXTENSIONS.addAll(Arrays.asList(PropertiesUtil.getProperty("filename.scanner.subtitle", "SRT,SUB,SSA,SMI,PGS").split(",")));
}
}
/**
* Gabriel Corneanu: One buffer for each thread to allow threaded copies
*/
private static final ThreadLocal THREAD_BUFFER = new ThreadLocal() {
@Override
protected byte[] initialValue() {
return new byte[BUFF_SIZE];
}
;
};
private static class ReplaceEntry {
private String oldText, newText;
private 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;
}
};
public static void initUnsafeChars() {
if (!UNSAFE_CHARS.isEmpty()) {
return;
}
// What to do if the user specifies a blank encodeEscapeChar? I guess disable encoding.
String encodeEscapeCharString = PropertiesUtil.getProperty("mjb.charset.filenameEncodingEscapeChar", "$");
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 = PropertiesUtil.getProperty("mjb.charset.unsafeFilenameChars", "<>:\"/\\|?*");
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(PropertiesUtil.getProperty("mjb.charset.filename.translate", ""), ",");
while (st.hasMoreElements()) {
final String token = st.nextToken();
String beforeStr = substringBefore(token, "-");
final String character = beforeStr.length() == 1 && (beforeStr.equals("\t") || beforeStr.equals(" ")) ? beforeStr : trimToNull(beforeStr);
if (character == null) {
// TODO Error message?
continue;
}
String afterStr = substringAfter(token, "-");
final String translation = afterStr.length() == 1 && (afterStr.equals("\t") || afterStr.equals(" ")) ? afterStr : 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("Class cannot be instantiated");
}
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;
} else {
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()) {
logger.error(LOG_MESSAGE + "The file '" + src + "' does not exist!");
return returnValue;
}
if (dst.isDirectory()) {
makeDirectories(dst);
returnValue = copyFile(src, new File(dst + File.separator + src.getName()));
} else {
FileInputStream inSource = null;
FileOutputStream outSource = null;
FileChannel inChannel = null;
FileChannel outChannel = null;
try {
// gc: copy using file channels, potentially much faster
inSource = new FileInputStream(src);
outSource = new FileOutputStream(dst);
inChannel = inSource.getChannel();
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) {
logger.error(LOG_MESSAGE + "Failed copying file '" + src + TO + dst + "'");
logger.error(SystemTools.getStackTrace(error));
returnValue = Boolean.FALSE;
} finally {
if (inChannel != null) {
try {
inChannel.close();
} catch (IOException ex) {
// Ignore
}
}
if (inSource != null) {
try {
inSource.close();
} catch (IOException ex) {
// Ignore
}
}
if (outChannel != null) {
try {
outChannel.close();
} catch (IOException ex) {
// Ignore
}
}
if (outSource != null) {
try {
outSource.close();
} catch (IOException ex) {
// Ignore
}
}
}
}
return returnValue;
}
/**
* Copy files from one directory to another
*
* @param srcPathName The source directory to copy from
* @param dstPathName The target directory to copy to
* @param updateDisplay Display an update to the console
*/
public static void copyDir(String srcPathName, String dstPathName, boolean updateDisplay) {
copyDir(srcPathName, dstPathName, updateDisplay, null);
}
/**
* Copy files from one directory to another
*
* @param srcPathName The source directory to copy from
* @param dstPathName The target directory to copy to
* @param updateDisplay Display an update to the console
* @param pathRoot The root of the srcPath for display purposes
*/
public static void copyDir(String srcPathName, String dstPathName, boolean updateDisplay, String pathRoot) {
String displayRoot;
if (StringTools.isNotValidString(pathRoot)) {
int pos = srcPathName.lastIndexOf(File.separator);
if (pos > 0) {
try {
displayRoot = srcPathName.substring(pos + 1);
} catch (Exception error) {
displayRoot = srcPathName;
}
} else {
displayRoot = srcPathName;
}
} else {
displayRoot = pathRoot;
}
try {
File srcDir = new File(srcPathName);
if (!srcDir.exists()) {
logger.error(LOG_MESSAGE + "Source directory " + srcPathName + " does not exist!");
return;
}
File dstDir = new File(dstPathName);
dstDir.mkdirs();
if (!dstDir.exists()) {
logger.error(LOG_MESSAGE + "Target directory " + dstPathName + " does not exist!");
return;
}
if (srcDir.isFile()) {
copyFile(srcDir, dstDir);
} else {
File[] contentList = srcDir.listFiles();
if (contentList != null) {
List files = Arrays.asList(contentList);
Collections.sort(files);
int totalSize = files.size();
int currentFile = 0;
String displayPath = srcDir.getCanonicalPath();
int pos = displayPath.lastIndexOf(displayRoot);
if (pos > 0) {
displayPath = displayPath.substring(pos);
}
for (File file : files) {
currentFile++;
if (!file.getName().equals(".svn")) {
if (file.isDirectory()) {
copyDir(file.getAbsolutePath(), dstPathName + File.separator + file.getName(), updateDisplay, pathRoot);
} else {
if (updateDisplay) {
System.out.print("\r Copying directory " + displayPath + " (" + currentFile + "/" + totalSize + ")");
if (logger.isTraceEnabled()) {
logger.trace(LOG_MESSAGE + "Copying: " + file.getName());
}
}
copyFile(file, dstDir);
}
}
}
if (updateDisplay && files.size() > 0) {
System.out.print("\n");
}
logger.debug(LOG_MESSAGE + "Copied " + totalSize + " files from " + srcDir.getCanonicalPath());
}
}
} catch (IOException error) {
logger.error(LOG_MESSAGE + "Failed to copy '" + srcPathName + TO + dstPathName + "'");
logger.error(SystemTools.getStackTrace(error));
}
}
/**
* Read a file and return it as a string
*
* @param file
* @return
*/
public static String readFileToString(File file) {
StringBuilder out = new StringBuilder();
if (file != null) {
BufferedReader in = null;
FileReader fr = null;
try {
fr = new FileReader(file);
in = new BufferedReader(fr);
String line = in.readLine();
while (line != null) {
out.append(line).append(" "); // Add a space to avoid unwanted concatenation
line = in.readLine();
}
} catch (IOException ex) {
logger.error(LOG_MESSAGE + "Failed reading file " + file.getName() + " - Error: " + ex.getMessage());
} finally {
if (fr != null) {
try {
fr.close();
} catch (IOException ex) {
logger.trace(LOG_MESSAGE + "Failed to close reader file for " + file.getAbsolutePath());
}
}
if (in != null) {
try {
in.close();
} catch (IOException ex) {
logger.trace(LOG_MESSAGE + "Failed to close buffered reader file for " + file.getAbsolutePath());
}
}
}
}
return out.toString();
}
/**
* Write string to a file (Used for debugging)
*
* @param filename
* @param outputString
*/
public static void writeStringToFile(String filename, String outputString) {
FileWriter out = null;
try {
File outFile = new File(filename);
out = new FileWriter(outFile);
out.write(outputString);
} catch (Exception ignore) {
logger.debug(LOG_MESSAGE + "Error writing string to " + filename);
} finally {
try {
out.close();
} catch (IOException e) {
// Failed to close out file.
}
}
}
/**
* *
* @author Stuart Boston
* @param file1 - first file to compare
* @param file2 - second file to compare
* @return true if the files exist and file2 is older, false otherwise.
*
* Note that file1 will be checked to see if it's newer than file2
*/
public static boolean isNewer(File file1, File file2) {
// TODO: Update this routine to use fileCache
// If file1 exists and file2 doesn't then return true
if (file1.exists()) {
// If file2 doesn't exist then file1 is newer
if (!file2.exists()) {
return Boolean.TRUE;
}
} else {
// File1 doesn't exist so return false
return Boolean.FALSE;
}
// Compare the file dates. This is only true if the first file is newer than the second, as the second file is the file2 file
if (file1.lastModified() <= file2.lastModified()) {
// file1 is older than the file2.
return Boolean.FALSE;
} else {
// file1 is newer than file2
return Boolean.TRUE;
}
}
public static String createCategoryKey(String key2) {
return key2;
}
public static String createPrefix(String category, String key) {
StringBuilder prefix = new StringBuilder(indexFilesPrefix);
prefix.append(category).append('_').append(key).append('_');
return prefix.toString();
}
public static OutputStream createFileOutputStream(File f, int size) throws FileNotFoundException {
// return new FileOutputStream(f);
return new BufferedOutputStream(new FileOutputStream(f), size);
}
public static OutputStream createFileOutputStream(File f) throws FileNotFoundException {
return createFileOutputStream(f, 10 * 1024);
}
public static OutputStream createFileOutputStream(String f) throws FileNotFoundException {
return createFileOutputStream(new File(f));
}
public static OutputStream createFileOutputStream(String f, int size) throws FileNotFoundException {
return createFileOutputStream(new File(f), size);
}
public static InputStream createFileInputStream(File f) throws FileNotFoundException {
// return new FileInputStream(f);
return new BufferedInputStream(new FileInputStream(f), 10 * 1024);
}
public static InputStream createFileInputStream(String f) throws FileNotFoundException {
return createFileInputStream(new File(f));
}
public static String makeSafeFilename(String filename) {
String newFilename = filename;
for (ReplaceEntry rep : UNSAFE_CHARS) {
newFilename = rep.check(newFilename);
}
if (!newFilename.equals(filename)) {
logger.debug(LOG_MESSAGE + "Encoded filename string '" + filename + TO + newFilename + "'");
}
return newFilename;
}
/**
* Returns the given path in canonical form
*
* i.e. no duplicated separators, no ".", ".."..., and ending without trailing separator the only exception is a root! the
* canonical form for a root INCLUDES the separator
*/
public static String getCanonicalPath(String path) {
try {
return new File(path).getCanonicalPath();
} catch (IOException e) {
return path;
}
}
/**
* when concatenating paths and the source MIGHT be a root, use this function to safely add the separator
*/
public static String getDirPathWithSeparator(String path) {
return path.endsWith(File.separator) ? path : path + File.separator;
}
/**
* Get the extension of a file.
*
* @param filename
* @return
*/
public static String getFileExtension(String filename) {
return FilenameUtils.getExtension(filename);
}
/**
* Returns the parent folder name only; used when searching for artwork...
*/
public static String getParentFolderName(File file) {
if (file == null) {
return "";
}
String path = file.getParent();
return path.substring(path.lastIndexOf(File.separator) + 1);
}
/**
*
* Pass in the filename and a list of extensions.
*
* This function will scan for the filename plus extensions and return the File
*
* @param filename
* @param fileExtensions
* @return always a File, to be tested with exists() for valid file
*/
public static File findFileFromExtensions(String fullBaseFilename, Collection fileExtensions) {
File localFile = null;
for (String extension : fileExtensions) {
localFile = fileCache.getFile(fullBaseFilename + "." + extension);
if (localFile.exists()) {
if (logger.isTraceEnabled()) {
logger.trace(LOG_MESSAGE + "Found " + localFile + " in the file cache");
}
return localFile;
}
}
return (localFile == null ? new File(Movie.UNKNOWN) : localFile); //just in case
}
/**
* Search for the filename in the cache and look for each with the extensions
*
* @param searchFilename
* @param fileExtensions
* @param jukebox
* @param logPrefix
* @return
*/
public static File findFilenameInCache(String searchFilename, Collection fileExtensions, Jukebox jukebox, String logPrefix) {
return findFilenameInCache(searchFilename, fileExtensions, jukebox, logPrefix, Boolean.FALSE, Movie.UNKNOWN);
}
/**
* Search for the filename in the cache and look for each with the extensions
*
* @param searchFilename
* @param fileExtensions
* @param jukebox
* @param logPrefix
* @param includeJukebox
* @return
*/
public static File findFilenameInCache(String searchFilename, Collection fileExtensions, Jukebox jukebox, String logPrefix, boolean includeJukebox) {
return findFilenameInCache(searchFilename, fileExtensions, jukebox, logPrefix, includeJukebox, Movie.UNKNOWN);
}
/**
* Search for the filename in the cache and look for each with the extensions
*
* @param searchFilename
* @param fileExtensions
* @param jukebox
* @param logPrefix
* @param includeJukebox
* @param subFolder
* @return
*/
public static File findFilenameInCache(String searchFilename, Collection fileExtensions, Jukebox jukebox, String logPrefix, boolean includeJukebox, String subFolder) {
File searchFile = null;
String safeFilename = makeSafeFilename(searchFilename);
safeFilename = File.separator + safeFilename;
StringBuilder jukeboxSubFolder = new StringBuilder(jukebox.getJukeboxRootLocationDetails().toLowerCase());
if (StringTools.isValidString(subFolder)) {
jukeboxSubFolder.append(File.separator).append((subFolder.toLowerCase()));
}
Collection files = FileTools.fileCache.searchFilename(safeFilename, Boolean.TRUE);
if (files.size() > 0) {
// Copy the synchronized list to avoid ConcurrentModificationException
Iterator iter = new ArrayList(FileTools.fileCache.searchFilename(safeFilename, Boolean.TRUE)).iterator();
while (iter.hasNext() && (searchFile == null)) {
File file = iter.next();
String abPath = file.getAbsolutePath().toLowerCase();
if (!includeJukebox && abPath.startsWith(jukeboxSubFolder.toString())) {
// Skip any files found in the jukebox
continue;
}
// Loop round the filename+extension to see if any exist and add them to the array
String checkFilename;
for (String extension : fileExtensions) {
checkFilename = safeFilename + "." + extension;
if (abPath.endsWith((checkFilename).toLowerCase())) {
searchFile = file;
// We've found the file, so we should quit the loop
break;
}
}
}
if (searchFile != null) {
logger.debug(logPrefix + "Using first one found: " + searchFile.getAbsolutePath());
} else {
logger.debug(logPrefix + "No matching files found for " + safeFilename);
}
} else {
logger.debug(logPrefix + "No scanned files found for " + searchFilename);
}
return searchFile;
}
/**
* Download the image for the specified URL into the specified file. Utilises the WebBrowser downloadImage function to allow for
* proxy connections.
*
* @param imageFile
* @param imageURL
* @throws IOException
*/
public static boolean downloadImage(File imageFile, String imageURL) throws IOException {
WebBrowser webBrowser = new WebBrowser();
return webBrowser.downloadImage(imageFile, imageURL);
}
/**
* Find the parent directory of the movie file.
*
* @param file (movieFile)
* @return Parent folder
* @author Stuart Boston
*/
public static String getParentFolder(File movieFile) {
String parentFolder;
if (movieFile.isDirectory()) { // for VIDEO_TS
parentFolder = movieFile.getPath();
} else {
parentFolder = movieFile.getParent();
}
// Issue 1070, /BDMV/STREAM is being appended to the parent path
if (parentFolder.toUpperCase().endsWith(BDMV_STREAM)) {
parentFolder = parentFolder.substring(0, parentFolder.length() - 12);
}
return parentFolder;
}
/**
* Recursively delete a directory
*
* @param dir
* @return
*/
public static boolean deleteDir(String dir) {
return deleteDir(new File(dir));
}
/**
* Recursively delete a directory
*
* @param dir
* @return
*/
public static boolean deleteDir(File dir) {
if (dir.isDirectory()) {
String[] children = dir.list();
for (int i = 0; i < children.length; i++) {
boolean success = deleteDir(new File(dir, children[i]));
if (!success) {
// System.out.println("Failed");
return Boolean.FALSE;
}
}
}
// The directory is now empty so delete it
// System.out.println("Deleting: " + dir.getAbsolutePath());
return dir.delete();
}
/**
* Add a list of files to the jukebox filenames
*
* @param filenames
*/
public static void addJukeboxFiles(Collection filenames) {
GENERATED_FILENAMES.addAll(filenames);
}
/**
* Add an individual filename to the jukebox cleaning exclusion list
*
* @param filename
*/
public static void addJukeboxFile(String filename) {
if (StringTools.isValidString(filename)) {
GENERATED_FILENAMES.add(filename);
if (logger.isTraceEnabled()) {
logger.trace(LOG_MESSAGE + "Adding " + filename + " to safe jukebox files");
}
}
}
/**
* Process the movie and add all the files to the jukebox cleaning exclusion list
*
* @param movie
*/
public static void addMovieToJukeboxFilenames(Movie movie) {
addJukeboxFile(movie.getPosterFilename());
addJukeboxFile(movie.getDetailPosterFilename());
addJukeboxFile(movie.getThumbnailFilename());
addJukeboxFile(movie.getBannerFilename());
addJukeboxFile(movie.getFanartFilename());
if (videoimageDownload) {
for (MovieFile mf : movie.getFiles()) {
for (int part = mf.getFirstPart(); part <= mf.getLastPart(); part++) {
addJukeboxFile(mf.getVideoImageFilename(part));
}
}
}
addJukeboxFile(movie.getClearArtFilename());
addJukeboxFile(movie.getClearLogoFilename());
addJukeboxFile(movie.getSeasonThumbFilename());
addJukeboxFile(movie.getTvThumbFilename());
addJukeboxFile(movie.getMovieDiscFilename());
// Are footer images enabled?
if (footerImageEnabled > 0) {
addJukeboxFiles(movie.getFooterFilename());
}
}
public static Collection getJukeboxFiles() {
return GENERATED_FILENAMES;
}
/**
* Special File with "cached" attributes used to minimize file system access which slows down everything
*
* @author Gabriel Corneanu
*/
public static class FileEx extends File {
private static final long serialVersionUID = 1L;
private volatile Boolean isDir = null;
private volatile Boolean fileExists = null;
private volatile Boolean isfile = null;
private volatile Long fileLen = null;
private volatile Long fileLastModified = null;
private IArchiveScanner[] archiveScanners;
private volatile File[] listFiles;
//Standard constructors
public FileEx(String parent, String child) {
super(parent, child);
}
public FileEx(String pathname) {
super(pathname);
}
public FileEx(File parent, String child) {
super(parent, child);
}
private FileEx(String pathname, boolean exists) {
this(pathname);
fileExists = exists;
}
// archive scanner supporting constructors
public FileEx(String pathname, IArchiveScanner[] archiveScanners) {
super(pathname);
if (archiveScanners == null) {
this.archiveScanners = null;
} else {
this.archiveScanners = (IArchiveScanner[]) archiveScanners.clone();
}
}
public FileEx(File parent, String child, IArchiveScanner[] archiveScanners) {
this(parent, child);
if (archiveScanners == null) {
this.archiveScanners = null;
} else {
this.archiveScanners = (IArchiveScanner[]) archiveScanners.clone();
}
}
@Override
public boolean isDirectory() {
if (isDir == null) {
synchronized (this) {
if (isDir == null) {
isDir = super.isDirectory();
}
}
}
return isDir;
}
@Override
public boolean exists() {
if (fileExists == null) {
synchronized (this) {
if (fileExists == null) {
fileExists = super.exists();
}
}
}
return fileExists;
}
@Override
public boolean isFile() {
if (isfile == null) {
synchronized (this) {
if (isfile == null) {
isfile = super.isFile();
}
}
}
return isfile;
}
@Override
public long length() {
if (fileLen == null) {
synchronized (this) {
if (fileLen == null) {
fileLen = super.length();
}
}
}
return fileLen;
}
@Override
public long lastModified() {
if (fileLastModified == null) {
synchronized (this) {
if (fileLastModified == null) {
fileLastModified = super.lastModified();
}
}
}
return fileLastModified;
}
@Override
public File getParentFile() {
String p = this.getParent();
if (p == null) {
return null;
}
return new FileEx(p, archiveScanners);
}
@Override
public File[] listFiles() {
synchronized (this) {
if (listFiles != null) {
return listFiles;
}
String[] nameStrings = list();
if (nameStrings == null) {
return null;
}
List mutableNames = new ArrayList(Arrays.asList(nameStrings));
List files = new ArrayList();
if (archiveScanners != null) {
for (IArchiveScanner as : archiveScanners) {
files.addAll(as.getArchiveFiles(this, mutableNames));
}
}
for (String name : mutableNames) {
FileEx fe = new FileEx(this, name, archiveScanners);
fe.fileExists = Boolean.TRUE;
files.add(fe);
}
listFiles = (File[]) files.toArray(new File[files.size()]);
}
return listFiles;
}
@Override
public File[] listFiles(FilenameFilter filter) {
File[] src = listFiles();
if (src == null) {
return null;
}
if (filter == null) {
return Arrays.copyOf(src, src.length);
}
List l = new ArrayList();
for (File f : src) {
if (filter.accept(this, f.getName())) {
l.add(f);
}
}
return (File[]) l.toArray(new File[l.size()]);
}
}
/**
* cached File instances the key is always absolute path in upper-case, so it will NOT work for case only differences
*
* @author Gabriel Corneanu
*/
public static class ScannedFilesCache {
//cache for ALL files found during initial scan
private Map cachedFiles = new ConcurrentHashMap(1000);
/**
* Check whether the file exists
*
* @param absPath
* @return
*/
public boolean fileExists(String absPath) {
return cachedFiles.containsKey(absPath.toUpperCase());
}
/**
* Check whether the file exists
*
* @param file
* @return
*/
public boolean fileExists(File file) {
return cachedFiles.containsKey(file.getAbsolutePath().toUpperCase());
}
/**
* Add a file instance to cache
*
* @param file
*/
public void fileAdd(File file) {
cachedFiles.put(file.getAbsolutePath().toUpperCase(), file);
}
/**
* Retrieve a file from cache
*
* If it is NOT found, construct one instance and mark it as non-existing.
*
* The exist() test is used very often throughout the library to search for specific files.
*
* The path MUST be canonical (i.e. carefully constructed)
*
* We do NOT want here to make it canonical because it goes to the file system and it's slow.
*
* @param path
* @return
*/
public File getFile(String path) {
File f = cachedFiles.get(path.toUpperCase());
return (f == null ? new FileEx(path, Boolean.FALSE) : f);
}
/**
* Add a full directory listing; used for existing jukebox
*
* @param dir
* @param depth
*/
public void addDir(File dir, int depth) {
File[] files = dir.listFiles();
if (files == null) {
return;
}
if (files.length == 0) {
return;
}
addFiles(files);
if (depth <= 0) {
return;
}
for (File f : files) {
if (f.isDirectory()) {
addDir(f, depth - 1);
}
}
}
public void addFiles(File[] files) {
if (files.length == 0) {
return;
}
Map map = new HashMap(files.length);
for (File f : files) {
map.put(f.getAbsolutePath().toUpperCase(), f);
}
cachedFiles.putAll(map);
}
public long size() {
return cachedFiles.size();
}
public Collection searchFilename(String searchName, boolean findAll) {
ArrayList files = new ArrayList();
String upperName = searchName.toUpperCase();
for (String listName : cachedFiles.keySet()) {
if (listName.contains(upperName)) {
files.add(cachedFiles.get(listName));
if (!findAll) {
// We only look for the first
break;
}
}
}
return files;
}
public void saveFileList(String filename) throws FileNotFoundException, UnsupportedEncodingException {
PrintWriter p = new PrintWriter(new OutputStreamWriter(new FileOutputStream(filename, Boolean.TRUE), "UTF-8"));
Set names = cachedFiles.keySet();
String[] sortednames = names.toArray(new String[names.size()]);
Arrays.sort(sortednames);
for (String f : sortednames) {
p.println(f);
}
p.close();
}
}
/**
* Look for any subtitle files for a file
*
* @param fileToScan
* @return
*/
public static File findSubtitles(File fileToScan) {
String basename = FilenameUtils.removeExtension(fileToScan.getAbsolutePath().toUpperCase());
return findFileFromExtensions(basename, SUBTITLE_EXTENSIONS);
}
/**
* 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 (StringTools.isNotValidString(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());
}
/**
* Create all directories up to the level of the file passed
*
* @param sourceDirectory 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;
}
logger.debug(LOG_MESSAGE + "Creating directories for " + targetDirectory.getAbsolutePath());
fsLock.lock();
try {
boolean status = targetDirectory.mkdirs();
int looper = 1;
while (!status && looper++ <= numOfTries) {
status = targetDirectory.mkdirs();
}
if (status && looper > 10) {
logger.error("Failed creating the directory (" + targetDirectory.getAbsolutePath() + "). Ensure this directory is read/write!");
return Boolean.FALSE;
}
return Boolean.TRUE;
} finally {
fsLock.unlock();
}
}
}