+ auto-lookup verification file

+ allow absolute paths in verification files
* lots of refactoring
This commit is contained in:
Reinhard Pointner 2009-08-10 11:46:24 +00:00
parent 07ff02c0a5
commit 3ff3a85289
39 changed files with 465 additions and 349 deletions

View File

@ -51,7 +51,7 @@ public class ArgumentBean {
public boolean sfv() {
return sfv || (open && FileUtilities.containsOnly(arguments, MediaTypes.getFilter("verification")));
return sfv || (open && FileUtilities.containsOnly(arguments, MediaTypes.getDefaultFilter("verification")));
}

View File

@ -1,74 +0,0 @@
package net.sourceforge.filebot;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.sourceforge.tuned.FileUtilities.ExtensionFileFilter;
public final class FileBotUtilities {
/**
* Invalid filename characters: \, /, :, *, ?, ", <, >, |, \r and \n
*/
public static final Pattern INVALID_CHARACTERS = Pattern.compile("[\\\\/:*?\"<>|\\r\\n]");
/**
* Strip filename of invalid characters
*
* @param filename original filename
* @return valid filename stripped of invalid characters
*/
public static String validateFileName(CharSequence filename) {
// strip invalid characters from filename
return INVALID_CHARACTERS.matcher(filename).replaceAll("");
}
public static boolean isInvalidFileName(CharSequence filename) {
return INVALID_CHARACTERS.matcher(filename).find();
}
/**
* A {@link Pattern} that will match checksums enclosed in brackets ("[]" or "()"). A
* checksum string is a hex number with at least 8 digits. Capturing group 0 will contain
* the matched checksum string.
*/
public static final Pattern EMBEDDED_CHECKSUM_PATTERN = Pattern.compile("(?<=\\[|\\()(\\p{XDigit}{8})(?=\\]|\\))");
public static String getEmbeddedChecksum(CharSequence string) {
Matcher matcher = EMBEDDED_CHECKSUM_PATTERN.matcher(string);
String embeddedChecksum = null;
// get last match
while (matcher.find()) {
embeddedChecksum = matcher.group();
}
return embeddedChecksum;
}
public static String removeEmbeddedChecksum(String string) {
// match embedded checksum and surrounding brackets
return string.replaceAll("[\\(\\[]\\p{XDigit}{8}[\\]\\)]", "");
}
public static final ExtensionFileFilter VIDEO_FILES = MediaTypes.getFilter("video");
public static final ExtensionFileFilter SUBTITLE_FILES = MediaTypes.getFilter("subtitle");
/**
* Dummy constructor to prevent instantiation.
*/
private FileBotUtilities() {
throw new UnsupportedOperationException();
}
}

View File

@ -4,6 +4,7 @@ package net.sourceforge.filebot;
import static java.util.Collections.*;
import java.io.FileFilter;
import java.util.ArrayList;
import java.util.List;
@ -34,13 +35,12 @@ public class MediaTypes {
}
private static final MediaTypes data = unmarshal();
private static final MediaTypes defaultInstance = unmarshal();
private static MediaTypes unmarshal() {
try {
Unmarshaller unmarshaller = JAXBContext.newInstance(MediaTypes.class).createUnmarshaller();
return (MediaTypes) unmarshaller.unmarshal(MediaTypes.class.getResource("media.types"));
} catch (JAXBException e) {
throw new RuntimeException(e);
@ -48,15 +48,10 @@ public class MediaTypes {
}
public static ExtensionFileFilter getFilter(String name) {
return new ExtensionFileFilter(getExtensionList(name));
}
public static List<String> getExtensionList(String name) {
public List<String> getExtensionList(String name) {
List<String> list = new ArrayList<String>();
for (Type type : data.types) {
for (Type type : defaultInstance.types) {
if (type.name.startsWith(name)) {
addAll(list, type.extensions);
}
@ -65,4 +60,24 @@ public class MediaTypes {
return list;
}
public FileFilter getFilter(String name) {
return new ExtensionFileFilter(getExtensionList(name));
}
public static MediaTypes getDefault() {
return defaultInstance;
}
public static ExtensionFileFilter getDefaultFilter(String name) {
return new ExtensionFileFilter(getDefault().getExtensionList(name));
}
// some convenience filters
public static final ExtensionFileFilter AUDIO_FILES = MediaTypes.getDefaultFilter("audio");
public static final ExtensionFileFilter VIDEO_FILES = MediaTypes.getDefaultFilter("video");
public static final ExtensionFileFilter SUBTITLE_FILES = MediaTypes.getDefaultFilter("subtitle");
}

View File

@ -2,24 +2,21 @@
package net.sourceforge.filebot.format;
import static net.sourceforge.filebot.FileBotUtilities.*;
import static net.sourceforge.filebot.MediaTypes.*;
import static net.sourceforge.filebot.format.Define.*;
import static net.sourceforge.filebot.hash.VerificationUtilities.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;
import java.util.Map.Entry;
import java.util.zip.CRC32;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
import net.sourceforge.filebot.FileBotUtilities;
import net.sourceforge.filebot.MediaTypes;
import net.sourceforge.filebot.hash.SfvFormat;
import net.sourceforge.filebot.hash.VerificationFileScanner;
import net.sourceforge.filebot.hash.HashType;
import net.sourceforge.filebot.mediainfo.MediaInfo;
import net.sourceforge.filebot.mediainfo.MediaInfo.StreamKind;
import net.sourceforge.filebot.web.Episode;
@ -126,13 +123,13 @@ public class EpisodeBindingBean {
File inferredMediaFile = getInferredMediaFile();
// try to get checksum from file name
String checksum = FileBotUtilities.getEmbeddedChecksum(inferredMediaFile.getName());
String checksum = getEmbeddedChecksum(inferredMediaFile.getName());
if (checksum != null)
return checksum;
// try to get checksum from sfv file
checksum = getChecksumFromSfvFile(inferredMediaFile.getParentFile(), inferredMediaFile, 0, 3);
checksum = getHashFromVerificationFile(inferredMediaFile, HashType.SFV, 3);
if (checksum != null)
return checksum;
@ -247,43 +244,14 @@ public class EpisodeBindingBean {
}
private String getChecksumFromSfvFile(File folder, File target, int depth, int maxDepth) throws IOException {
// stop if we reached max depth or the file system root
if (folder == null || depth > maxDepth)
return null;
// scan all sfv files in this folder
for (File sfvFile : folder.listFiles(MediaTypes.getFilter("verification/sfv"))) {
VerificationFileScanner scanner = new VerificationFileScanner(sfvFile, new SfvFormat());
try {
while (scanner.hasNext()) {
Entry<File, String> entry = scanner.next();
// resolve relative file path
File file = new File(folder, entry.getKey().getPath());
if (target.equals(file)) {
return entry.getValue();
}
}
} finally {
scanner.close();
}
}
return getChecksumFromSfvFile(folder.getParentFile(), target, depth + 1, maxDepth);
}
private String crc32(File file) throws IOException, InterruptedException {
// try to get checksum from cache
Cache cache = CacheManager.getInstance().getCache("checksum");
Element cacheEntry = cache.get(file);
if (cacheEntry != null) {
return String.format("%08X", cacheEntry.getValue());
try {
return String.format("%08X", cache.get(file).getValue());
} catch (Exception e) {
// checksum is not cached
}
// calculate checksum

View File

@ -4,6 +4,9 @@ package net.sourceforge.filebot.hash;
import java.util.zip.CRC32;
import net.sourceforge.filebot.MediaTypes;
import net.sourceforge.tuned.FileUtilities.ExtensionFileFilter;
public enum HashType {
@ -21,6 +24,12 @@ public enum HashType {
return new SfvFormat();
}
@Override
public ExtensionFileFilter getFilter() {
return MediaTypes.getDefaultFilter("verification/sfv");
}
},
MD5 {
@ -37,6 +46,12 @@ public enum HashType {
return new VerificationFormat();
}
@Override
public ExtensionFileFilter getFilter() {
return MediaTypes.getDefaultFilter("verification/md5sum");
}
},
SHA1 {
@ -54,6 +69,12 @@ public enum HashType {
}
@Override
public ExtensionFileFilter getFilter() {
return MediaTypes.getDefaultFilter("verification/sha1sum");
}
@Override
public String toString() {
return "SHA-1";
@ -67,20 +88,6 @@ public enum HashType {
public abstract VerificationFormat getFormat();
public String getExtension() {
return name().toLowerCase();
}
public static HashType forName(String name) {
for (HashType value : HashType.values()) {
if (value.name().equalsIgnoreCase(name)) {
return value;
}
}
// value not found
return null;
}
public abstract ExtensionFileFilter getFilter();
}

View File

@ -17,6 +17,7 @@ public class SfvFormat extends VerificationFormat {
return String.format("%s %s", path, hash);
}
/**
* Pattern used to parse the lines of a sfv file.
*
@ -26,9 +27,9 @@ public class SfvFormat extends VerificationFormat {
* | Group 1 | | Gr.2 |
* </pre>
*/
private final Pattern pattern = Pattern.compile("(.+)\\s+(\\p{XDigit}{8})");
private final Pattern pattern = Pattern.compile("^(.+)\\s+(\\p{XDigit}{8})$");
@Override
public Entry<File, String> parseObject(String line) throws ParseException {
Matcher matcher = pattern.matcher(line);

View File

@ -5,8 +5,8 @@ package net.sourceforge.filebot.hash;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.text.ParseException;
import java.util.Iterator;
import java.util.NoSuchElementException;
@ -16,7 +16,7 @@ import java.util.logging.Level;
import java.util.logging.Logger;
public class VerificationFileScanner implements Iterator<Entry<File, String>>, Closeable {
public class VerificationFileReader implements Iterator<Entry<File, String>>, Closeable {
private final Scanner scanner;
@ -26,15 +26,14 @@ public class VerificationFileScanner implements Iterator<Entry<File, String>>, C
private int lineNumber = 0;
public VerificationFileScanner(File file, VerificationFormat format) throws FileNotFoundException {
// don't use new Scanner(File) because of BUG 6368019 (http://bugs.sun.com/view_bug.do?bug_id=6368019)
this(new Scanner(new FileInputStream(file), "UTF-8"), format);
public VerificationFileReader(File file, VerificationFormat format) throws IOException {
this(new InputStreamReader(new FileInputStream(file), "UTF-8"), format);
}
public VerificationFileScanner(Scanner scanner, VerificationFormat format) {
this.scanner = scanner;
public VerificationFileReader(Readable source, VerificationFormat format) {
this.scanner = new Scanner(source);
this.format = format;
}

View File

@ -17,7 +17,7 @@ public class VerificationFormat extends Format {
private final String hashTypeHint;
public VerificationFormat() {
this.hashTypeHint = "";
}
@ -45,6 +45,7 @@ public class VerificationFormat extends Format {
return String.format("%s %s*%s", hash, hashTypeHint, path);
}
/**
* Pattern used to parse the lines of a md5 or sha1 file.
*
@ -58,9 +59,9 @@ public class VerificationFormat extends Format {
* | Group 1 | | Group 2 |
* </pre>
*/
private final Pattern pattern = Pattern.compile("(\\p{XDigit}+)\\s+(?:\\?\\w+)?\\*?(.+)");
private final Pattern pattern = Pattern.compile("^(\\p{XDigit}+)\\s+(?:\\?\\w+)?\\*?(.+)$");
@Override
public Entry<File, String> parseObject(String line) throws ParseException {
Matcher matcher = pattern.matcher(line);

View File

@ -0,0 +1,92 @@
package net.sourceforge.filebot.hash;
import java.io.File;
import java.io.IOException;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public final class VerificationUtilities {
/**
* A {@link Pattern} that will match checksums enclosed in brackets ("[]" or "()"). A
* checksum string is a hex number with at least 8 digits. Capturing group 0 will contain
* the matched checksum string.
*/
public static final Pattern EMBEDDED_CHECKSUM = Pattern.compile("(?<=\\[|\\()(\\p{XDigit}{8})(?=\\]|\\))");
public static String getEmbeddedChecksum(CharSequence string) {
Matcher matcher = EMBEDDED_CHECKSUM.matcher(string);
String embeddedChecksum = null;
// get last match
while (matcher.find()) {
embeddedChecksum = matcher.group();
}
return embeddedChecksum;
}
public static String removeEmbeddedChecksum(String string) {
// match embedded checksum and surrounding brackets
return string.replaceAll("[\\(\\[]\\p{XDigit}{8}[\\]\\)]", "");
}
public static String getHashFromVerificationFile(File file, HashType type, int maxDepth) throws IOException {
return getHashFromVerificationFile(file.getParentFile(), file, type, 0, maxDepth);
}
private static String getHashFromVerificationFile(File folder, File target, HashType type, int depth, int maxDepth) throws IOException {
// stop if we reached max depth or the file system root
if (folder == null || depth > maxDepth)
return null;
// scan all sfv files in this folder
for (File verificationFile : folder.listFiles(type.getFilter())) {
VerificationFileReader scanner = new VerificationFileReader(verificationFile, type.getFormat());
try {
while (scanner.hasNext()) {
Entry<File, String> entry = scanner.next();
// resolve relative file path
File file = new File(folder, entry.getKey().getPath());
if (target.equals(file)) {
return entry.getValue();
}
}
} finally {
scanner.close();
}
}
return getHashFromVerificationFile(folder.getParentFile(), target, type, depth + 1, maxDepth);
}
public static HashType getHashType(File verificationFile) {
for (HashType hashType : HashType.values()) {
if (hashType.getFilter().accept(verificationFile))
return hashType;
}
return null;
}
/**
* Dummy constructor to prevent instantiation.
*/
private VerificationUtilities() {
throw new UnsupportedOperationException();
}
}

View File

@ -21,12 +21,13 @@
<extension>sfv</extension>
</type>
<type name="verification/md5">
<type name="verification/md5sum">
<extension>md5</extension>
</type>
<type name="verification/sha-1">
<type name="verification/sha1sum">
<extension>sha1</extension>
<extension>sha</extension>
</type>

View File

@ -26,7 +26,7 @@ public class MediaInfo implements Closeable {
// We need to load dependencies first, because we know where our native libs are (e.g. Java Web Start Cache).
// If we do not, the system will look for dependencies, but only in the library path.
NativeLibrary.getInstance("zen");
} catch (Exception e) {
} catch (LinkageError e) {
Logger.getLogger(MediaInfo.class.getName()).warning("Failed to preload libzen");
}
}

View File

@ -6,7 +6,6 @@ import static net.sourceforge.tuned.StringUtilities.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class MicroDVDReader extends SubtitleReader {
@ -14,8 +13,8 @@ public class MicroDVDReader extends SubtitleReader {
private double fps = 23.976;
public MicroDVDReader(Scanner scanner) {
super(scanner);
public MicroDVDReader(Readable source) {
super(source);
}

View File

@ -9,7 +9,6 @@ import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Scanner;
import java.util.TimeZone;
@ -18,8 +17,8 @@ public class SubRipReader extends SubtitleReader {
private final DateFormat timeFormat;
public SubRipReader(Scanner scanner) {
super(scanner);
public SubRipReader(Readable source) {
super(source);
// format used to parse time stamps (e.g. 00:02:26,407 --> 00:02:31,356)
timeFormat = new SimpleDateFormat("HH:mm:ss,SSS", Locale.ROOT);

View File

@ -9,7 +9,6 @@ import java.util.Arrays;
import java.util.EnumMap;
import java.util.InputMismatchException;
import java.util.Map;
import java.util.Scanner;
import java.util.regex.Pattern;
@ -21,8 +20,8 @@ public class SubStationAlphaReader extends SubtitleReader {
private Map<EventProperty, Integer> format;
public SubStationAlphaReader(Scanner scanner) {
super(scanner);
public SubStationAlphaReader(Readable source) {
super(source);
}

View File

@ -6,7 +6,6 @@ import static net.sourceforge.tuned.StringUtilities.*;
import java.text.DateFormat;
import java.util.InputMismatchException;
import java.util.Scanner;
import java.util.regex.Pattern;
@ -16,8 +15,8 @@ public class SubViewerReader extends SubtitleReader {
private final Pattern newline = Pattern.compile(Pattern.quote("[br]"), Pattern.CASE_INSENSITIVE);
public SubViewerReader(Scanner scanner) {
super(scanner);
public SubViewerReader(Readable source) {
super(source);
}

View File

@ -2,8 +2,6 @@
package net.sourceforge.filebot.subtitle;
import java.util.Scanner;
import net.sourceforge.filebot.MediaTypes;
import net.sourceforge.tuned.FileUtilities.ExtensionFileFilter;
@ -14,7 +12,7 @@ public enum SubtitleFormat {
@Override
public SubtitleReader newReader(Readable readable) {
return new SubRipReader(new Scanner(readable));
return new SubRipReader(readable);
}
},
@ -22,7 +20,7 @@ public enum SubtitleFormat {
@Override
public SubtitleReader newReader(Readable readable) {
return new MicroDVDReader(new Scanner(readable));
return new MicroDVDReader(readable);
}
},
@ -30,7 +28,7 @@ public enum SubtitleFormat {
@Override
public SubtitleReader newReader(Readable readable) {
return new SubViewerReader(new Scanner(readable));
return new SubViewerReader(readable);
}
},
@ -38,7 +36,7 @@ public enum SubtitleFormat {
@Override
public SubtitleReader newReader(Readable readable) {
return new SubStationAlphaReader(new Scanner(readable));
return new SubStationAlphaReader(readable);
}
};
@ -46,7 +44,7 @@ public enum SubtitleFormat {
public ExtensionFileFilter getFilter() {
return MediaTypes.getFilter("subtitle/" + this);
return MediaTypes.getDefaultFilter("subtitle/" + this);
}
}

View File

@ -3,9 +3,6 @@ package net.sourceforge.filebot.subtitle;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Iterator;
import java.util.NoSuchElementException;
@ -21,14 +18,8 @@ public abstract class SubtitleReader implements Iterator<SubtitleElement>, Close
protected SubtitleElement current;
public SubtitleReader(File file) throws FileNotFoundException {
// don't use new Scanner(File) because of BUG 6368019 (http://bugs.sun.com/view_bug.do?bug_id=6368019)
this(new Scanner(new FileInputStream(file), "UTF-8"));
}
public SubtitleReader(Scanner scanner) {
this.scanner = scanner;
public SubtitleReader(Readable source) {
this.scanner = new Scanner(source);
}

View File

@ -49,7 +49,7 @@ class FileListTransferablePolicy extends FileTransferablePolicy {
if (containsOnly(files, FOLDERS)) {
loadFolders(files);
} else if (containsOnly(files, MediaTypes.getFilter("application/torrent"))) {
} else if (containsOnly(files, MediaTypes.getDefaultFilter("application/torrent"))) {
loadTorrents(files);
} else {
loadFiles(files);

View File

@ -2,7 +2,7 @@
package net.sourceforge.filebot.ui.panel.rename;
import static net.sourceforge.filebot.FileBotUtilities.*;
import static net.sourceforge.filebot.MediaTypes.*;
import static net.sourceforge.tuned.ui.TunedUtilities.*;
import java.io.File;

View File

@ -2,6 +2,7 @@
package net.sourceforge.filebot.ui.panel.rename;
import static net.sourceforge.filebot.MediaTypes.*;
import static net.sourceforge.tuned.ui.TunedUtilities.*;
import java.awt.Color;
@ -13,6 +14,7 @@ import java.awt.event.WindowEvent;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
@ -51,7 +53,6 @@ import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;
import net.miginfocom.swing.MigLayout;
import net.sourceforge.filebot.MediaTypes;
import net.sourceforge.filebot.ResourceManager;
import net.sourceforge.filebot.format.EpisodeBindingBean;
import net.sourceforge.filebot.format.ExpressionFormat;
@ -385,9 +386,9 @@ class EpisodeBindingDialog extends JDialog {
// collect media file extensions (video, audio and subtitle files)
List<String> extensions = new ArrayList<String>();
extensions.addAll(MediaTypes.getExtensionList("video"));
extensions.addAll(MediaTypes.getExtensionList("audio"));
extensions.addAll(MediaTypes.getExtensionList("subtitle"));
Collections.addAll(extensions, VIDEO_FILES.extensions());
Collections.addAll(extensions, AUDIO_FILES.extensions());
Collections.addAll(extensions, SUBTITLE_FILES.extensions());
chooser.setFileFilter(new FileNameExtensionFilter("Media files", extensions.toArray(new String[0])));
chooser.setMultiSelectionEnabled(false);

View File

@ -2,11 +2,12 @@
package net.sourceforge.filebot.ui.panel.rename;
import static net.sourceforge.filebot.hash.VerificationUtilities.*;
import java.io.File;
import java.util.Collection;
import java.util.Collections;
import net.sourceforge.filebot.FileBotUtilities;
import net.sourceforge.filebot.similarity.LengthEqualsMetric;
import net.sourceforge.filebot.similarity.NameSimilarityMetric;
import net.sourceforge.filebot.similarity.NumericSimilarityMetric;
@ -109,7 +110,7 @@ enum MatchSimilarityMetric implements SimilarityMetric {
}
// remove embedded checksum from name, if any
return FileBotUtilities.removeEmbeddedChecksum(name);
return removeEmbeddedChecksum(name);
}

View File

@ -3,6 +3,7 @@ package net.sourceforge.filebot.ui.panel.rename;
import static java.awt.datatransfer.DataFlavor.*;
import static net.sourceforge.filebot.hash.VerificationUtilities.*;
import static net.sourceforge.filebot.ui.transfer.FileTransferable.*;
import static net.sourceforge.tuned.FileUtilities.*;
@ -19,7 +20,7 @@ import java.util.Scanner;
import net.sourceforge.filebot.MediaTypes;
import net.sourceforge.filebot.hash.HashType;
import net.sourceforge.filebot.hash.VerificationFileScanner;
import net.sourceforge.filebot.hash.VerificationFileReader;
import net.sourceforge.filebot.torrent.Torrent;
import net.sourceforge.filebot.ui.transfer.ArrayTransferable;
import net.sourceforge.filebot.ui.transfer.FileTransferablePolicy;
@ -97,13 +98,13 @@ class NamesListTransferablePolicy extends FileTransferablePolicy {
protected void load(List<File> files) throws IOException {
List<Object> values = new ArrayList<Object>();
if (containsOnly(files, MediaTypes.getFilter("application/list"))) {
if (containsOnly(files, MediaTypes.getDefaultFilter("application/list"))) {
// list files
loadListFiles(files, values);
} else if (containsOnly(files, MediaTypes.getFilter("verification"))) {
} else if (containsOnly(files, MediaTypes.getDefaultFilter("verification"))) {
// verification files
loadVerificationFiles(files, values);
} else if (containsOnly(files, MediaTypes.getFilter("application/torrent"))) {
} else if (containsOnly(files, MediaTypes.getDefaultFilter("application/torrent"))) {
// torrent files
loadTorrentFiles(files, values);
} else if (containsOnly(files, FOLDERS)) {
@ -139,15 +140,15 @@ class NamesListTransferablePolicy extends FileTransferablePolicy {
protected void loadVerificationFiles(List<File> files, List<Object> values) throws IOException {
for (File file : files) {
HashType format = HashType.forName(getExtension(file));
for (File verificationFile : files) {
HashType type = getHashType(verificationFile);
// check if format is valid
if (format == null)
// check if type is supported
if (type == null)
continue;
// add all file names from verification file
VerificationFileScanner scanner = new VerificationFileScanner(file, format.getFormat());
VerificationFileReader scanner = new VerificationFileReader(verificationFile, type.getFormat());
try {
while (scanner.hasNext()) {

View File

@ -3,7 +3,7 @@ package net.sourceforge.filebot.ui.panel.rename;
import static java.util.Collections.*;
import static net.sourceforge.filebot.FileBotUtilities.*;
import static net.sourceforge.tuned.FileUtilities.*;
import static net.sourceforge.tuned.ui.TunedUtilities.*;
import java.awt.Color;
@ -47,7 +47,7 @@ class ValidateDialog extends JDialog {
list = new JList(model);
list.setEnabled(false);
list.setCellRenderer(new HighlightListCellRenderer(INVALID_CHARACTERS, new CharacterHighlightPainter(new Color(0xFF4200), new Color(0xFF1200)), 4));
list.setCellRenderer(new HighlightListCellRenderer(ILLEGAL_CHARACTERS, new CharacterHighlightPainter(new Color(0xFF4200), new Color(0xFF1200)), 4));
JLabel label = new JLabel("Some names contain invalid characters:");

View File

@ -7,6 +7,7 @@ import static net.sourceforge.tuned.ui.TunedUtilities.*;
import java.awt.Color;
import java.awt.Component;
import java.io.FileNotFoundException;
import javax.swing.JTable;
import javax.swing.SwingWorker;
@ -20,7 +21,7 @@ public class ChecksumCellRenderer extends DefaultTableCellRenderer {
private final SwingWorkerCellRenderer progressRenderer = new SwingWorkerCellRenderer();
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
boolean pendingWorker = false;
@ -50,6 +51,8 @@ public class ChecksumCellRenderer extends DefaultTableCellRenderer {
setText("Pending...");
} else if (value == null && !isSelected) {
setBackground(derive(table.getGridColor(), 0.1f));
} else if (value instanceof FileNotFoundException) {
setText("File not found");
} else if (value instanceof Throwable) {
setText(ExceptionUtilities.getRootCauseMessage((Throwable) value));
}

View File

@ -4,7 +4,6 @@ package net.sourceforge.filebot.ui.panel.sfv;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.Collections;
import java.util.Map;
@ -23,7 +22,7 @@ class ChecksumComputationTask extends SwingWorker<Map<HashType, String>, Void> {
private final File file;
private final HashType hashType;
public ChecksumComputationTask(File file, HashType hashType) {
this.file = file;
this.hashType = hashType;
@ -32,9 +31,6 @@ class ChecksumComputationTask extends SwingWorker<Map<HashType, String>, Void> {
@Override
protected Map<HashType, String> doInBackground() throws Exception {
if (!file.exists())
throw new FileNotFoundException("File not found");
// create hash instance
Hash hash = hashType.newHash();

View File

@ -2,6 +2,8 @@
package net.sourceforge.filebot.ui.panel.sfv;
import static net.sourceforge.filebot.hash.VerificationUtilities.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
@ -15,7 +17,6 @@ import java.util.Set;
import javax.swing.event.SwingPropertyChangeSupport;
import net.sourceforge.filebot.FileBotUtilities;
import net.sourceforge.filebot.hash.HashType;
@ -31,7 +32,7 @@ class ChecksumRow {
*/
private String embeddedChecksum;
public static enum State {
UNKNOWN,
OK,
@ -39,10 +40,10 @@ class ChecksumRow {
ERROR
}
public ChecksumRow(String name) {
this.name = name;
this.embeddedChecksum = FileBotUtilities.getEmbeddedChecksum(name);
this.embeddedChecksum = getEmbeddedChecksum(name);
}
@ -167,6 +168,7 @@ class ChecksumRow {
return String.format("%s %s %s", state, name, hashes);
}
private final PropertyChangeListener updateStateListener = new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
@ -178,7 +180,7 @@ class ChecksumRow {
private SwingPropertyChangeSupport pcs = new SwingPropertyChangeSupport(this, true);
public void addPropertyChangeListener(PropertyChangeListener listener) {
pcs.addPropertyChangeListener(listener);
}

View File

@ -2,7 +2,7 @@
package net.sourceforge.filebot.ui.panel.sfv;
import static net.sourceforge.filebot.FileBotUtilities.*;
import static net.sourceforge.filebot.hash.VerificationUtilities.*;
import java.awt.Color;
@ -33,7 +33,7 @@ class ChecksumTable extends JTable {
setBackground(Color.WHITE);
// highlight CRC32 patterns in filenames in green and with smaller font-size
setDefaultRenderer(String.class, new HighlightPatternCellRenderer(EMBEDDED_CHECKSUM_PATTERN));
setDefaultRenderer(String.class, new HighlightPatternCellRenderer(EMBEDDED_CHECKSUM));
setDefaultRenderer(ChecksumRow.State.class, new StateIconCellRenderer());
setDefaultRenderer(ChecksumCell.class, new ChecksumCellRenderer());
}

View File

@ -18,7 +18,7 @@ class ChecksumTableExportHandler extends TextFileExportHandler {
private final ChecksumTableModel model;
public ChecksumTableExportHandler(ChecksumTableModel model) {
this.model = model;
}
@ -92,11 +92,12 @@ class ChecksumTableExportHandler extends TextFileExportHandler {
public String getDefaultFileName(File column) {
StringBuilder sb = new StringBuilder();
if (column != null)
sb.append(FileUtilities.getName(column));
else
sb.append("name");
// append file name
sb.append(column != null ? FileUtilities.getName(column) : "name");
return sb.append(".").append(model.getHashType().getExtension()).toString();
// append file extension
sb.append('.').append(model.getHashType().name().toLowerCase());
return sb.toString();
}
}

View File

@ -2,22 +2,24 @@
package net.sourceforge.filebot.ui.panel.sfv;
import static net.sourceforge.tuned.FileUtilities.*;
import static java.util.Collections.*;
import static net.sourceforge.filebot.hash.VerificationUtilities.*;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ExecutorService;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.sourceforge.filebot.MediaTypes;
import net.sourceforge.filebot.hash.HashType;
import net.sourceforge.filebot.hash.VerificationFileScanner;
import net.sourceforge.filebot.hash.VerificationFileReader;
import net.sourceforge.filebot.ui.transfer.BackgroundFileTransferablePolicy;
import net.sourceforge.tuned.ExceptionUtilities;
import net.sourceforge.tuned.FileUtilities.ExtensionFileFilter;
class ChecksumTableTransferablePolicy extends BackgroundFileTransferablePolicy<ChecksumCell> {
@ -25,7 +27,7 @@ class ChecksumTableTransferablePolicy extends BackgroundFileTransferablePolicy<C
private final ChecksumTableModel model;
private final ChecksumComputationService computationService;
public ChecksumTableTransferablePolicy(ChecksumTableModel model, ChecksumComputationService checksumComputationService) {
this.model = model;
this.computationService = checksumComputationService;
@ -49,10 +51,8 @@ class ChecksumTableTransferablePolicy extends BackgroundFileTransferablePolicy<C
@Override
protected void prepare(List<File> files) {
HashType type = getVerificationType(files);
if (type != null) {
model.setHashType(type);
if (files.size() == 1 && getHashType(files.get(0)) != null) {
model.setHashType(getHashType(files.get(0)));
}
}
@ -69,86 +69,75 @@ class ChecksumTableTransferablePolicy extends BackgroundFileTransferablePolicy<C
}
protected HashType getVerificationType(List<File> files) {
for (HashType hash : HashType.values()) {
if (containsOnly(files, new ExtensionFileFilter(hash.getExtension()))) {
return hash;
}
}
return null;
}
private final ThreadLocal<ExecutorService> executor = new ThreadLocal<ExecutorService>();
private final ThreadLocal<VerificationTracker> verificationTracker = new ThreadLocal<VerificationTracker>();
protected void loadVerificationFile(File file, HashType type) throws IOException {
// don't use new Scanner(File) because of BUG 6368019 (http://bugs.sun.com/view_bug.do?bug_id=6368019)
VerificationFileScanner scanner = new VerificationFileScanner(file, type.getFormat());
try {
// root for relative file paths in verification file
File root = file.getParentFile();
while (scanner.hasNext()) {
Entry<File, String> entry = scanner.next();
String name = normalizeRelativePath(entry.getKey());
String hash = entry.getValue();
ChecksumCell correct = new ChecksumCell(name, file, Collections.singletonMap(type, hash));
ChecksumCell current = createComputationCell(name, root, type);
publish(correct, current);
if (Thread.interrupted()) {
break;
}
}
} finally {
scanner.close();
}
}
private final ThreadLocal<ExecutorService> executor = new ThreadLocal<ExecutorService>();
@Override
protected void load(List<File> files) throws IOException {
// initialize drop parameters
executor.set(computationService.newExecutor());
verificationTracker.set(new VerificationTracker(3));
try {
HashType verificationType = getVerificationType(files);
if (verificationType != null) {
// handle single verification file drop
if (files.size() == 1 && getHashType(files.get(0)) != null) {
loadVerificationFile(files.get(0), getHashType(files.get(0)));
}
// handle single folder drop
else if (files.size() == 1 && files.get(0).isDirectory()) {
for (File file : files.get(0).listFiles()) {
load(file, null, files.get(0));
}
}
// handle all other drops
else {
for (File file : files) {
loadVerificationFile(file, verificationType);
}
} else if ((files.size() == 1) && files.get(0).isDirectory()) {
// one single folder
File file = files.get(0);
for (File f : file.listFiles()) {
load(f, null, file);
}
} else {
// bunch of files
for (File f : files) {
load(f, null, f.getParentFile());
load(file, null, file.getParentFile());
}
}
} catch (InterruptedException e) {
// supposed to happen if background execution was aborted
// supposed to happen if background execution is aborted
} finally {
// shutdown executor after all tasks have been completed
executor.get().shutdown();
// remove drop parameters
executor.remove();
verificationTracker.remove();
}
}
protected void load(File file, File relativeFile, File root) throws InterruptedException {
protected void loadVerificationFile(File file, HashType type) throws IOException, InterruptedException {
VerificationFileReader scanner = new VerificationFileReader(file, type.getFormat());
try {
// root for relative file paths in verification file
File baseFolder = file.getParentFile();
while (scanner.hasNext()) {
Entry<File, String> entry = scanner.next();
String name = normalizePath(entry.getKey());
String hash = new String(entry.getValue());
ChecksumCell correct = new ChecksumCell(name, file, singletonMap(type, hash));
ChecksumCell current = createComputationCell(name, baseFolder, type);
publish(correct, current);
// make this long-running operation interruptible
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
scanner.close();
}
}
protected void load(File file, File relativeFile, File root) throws IOException, InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
@ -161,7 +150,18 @@ class ChecksumTableTransferablePolicy extends BackgroundFileTransferablePolicy<C
load(child, relativeFile, root);
}
} else {
publish(createComputationCell(normalizeRelativePath(relativeFile), root, model.getHashType()));
String name = normalizePath(relativeFile);
// publish computation cell first
publish(createComputationCell(name, root, model.getHashType()));
// publish verification cell, if we can
Map<File, String> hashByVerificationFile = verificationTracker.get().getHashByVerificationFile(file);
for (Entry<File, String> entry : hashByVerificationFile.entrySet()) {
HashType hashType = verificationTracker.get().getVerificationFileType(entry.getKey());
publish(new ChecksumCell(name, entry.getKey(), singletonMap(hashType, entry.getValue())));
}
}
}
@ -176,10 +176,7 @@ class ChecksumTableTransferablePolicy extends BackgroundFileTransferablePolicy<C
}
protected String normalizeRelativePath(File file) {
if (file.isAbsolute())
throw new IllegalArgumentException("Path must be relative");
protected String normalizePath(File file) {
return file.getPath().replace('\\', '/');
}
@ -189,4 +186,97 @@ class ChecksumTableTransferablePolicy extends BackgroundFileTransferablePolicy<C
return "files, folders and sfv files";
}
private static class VerificationTracker {
private final Map<File, Integer> seen = new HashMap<File, Integer>();
private final Map<File, Map<File, String>> cache = new HashMap<File, Map<File, String>>();
private final Map<File, HashType> types = new HashMap<File, HashType>();
private final int maxDepth;
public VerificationTracker(int maxDepth) {
this.maxDepth = maxDepth;
}
public Map<File, String> getHashByVerificationFile(File file) throws IOException {
// cache all verification files
File folder = file.getParentFile();
int depth = 0;
while (folder != null && depth <= maxDepth) {
Integer seenLevel = seen.get(folder);
if (seenLevel != null && seenLevel <= depth) {
// we have completely seen this parent tree before
break;
}
if (seenLevel == null) {
// folder we have never encountered before
for (File verificationFile : folder.listFiles(MediaTypes.getDefaultFilter("verification"))) {
HashType hashType = getHashType(verificationFile);
cache.put(verificationFile, importVerificationFile(verificationFile, hashType, verificationFile.getParentFile()));
types.put(verificationFile, hashType);
}
}
// update
seen.put(folder, depth);
// step down
folder = folder.getParentFile();
depth++;
}
// just return if we know we won't find anything
if (cache.isEmpty()) {
return emptyMap();
}
// search all cached verification files
Map<File, String> result = new HashMap<File, String>(2);
for (Entry<File, Map<File, String>> entry : cache.entrySet()) {
String hash = entry.getValue().get(file);
if (hash != null) {
result.put(entry.getKey(), hash);
}
}
return result;
}
public HashType getVerificationFileType(File verificationFile) {
return types.get(verificationFile);
}
/**
* Completely read a verification file and resolve all relative file paths against a given base folder
*/
private Map<File, String> importVerificationFile(File verificationFile, HashType hashType, File baseFolder) throws IOException {
VerificationFileReader reader = new VerificationFileReader(verificationFile, hashType.getFormat());
Map<File, String> content = new HashMap<File, String>();
try {
while (reader.hasNext()) {
Entry<File, String> entry = reader.next();
// resolve relative path, the hash is probably a substring, so we compact it, for memory reasons
content.put(new File(baseFolder, entry.getKey().getPath()), new String(entry.getValue()));
}
} finally {
reader.close();
}
return content;
}
}
}

View File

@ -18,12 +18,12 @@ class SwingWorkerCellRenderer extends JPanel implements TableCellRenderer {
private final JProgressBar progressBar = new JProgressBar(0, 100);
public SwingWorkerCellRenderer() {
super(new BorderLayout());
// create margin for progress bar,
// because setting margin for progress bar directly does not work (border size is not respected in the paint method)
// set margin for progress bar on parent component,
// because setting it on the progress bar itself does not work (border size is not respected in the paint method)
setBorder(new EmptyBorder(2, 2, 2, 2));
progressBar.setStringPainted(true);

View File

@ -2,8 +2,9 @@
package net.sourceforge.filebot.ui.panel.subtitle;
import static net.sourceforge.filebot.FileBotUtilities.*;
import static net.sourceforge.filebot.MediaTypes.*;
import static net.sourceforge.filebot.ui.panel.subtitle.SubtitleUtilities.*;
import static net.sourceforge.tuned.FileUtilities.*;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;

View File

@ -3,7 +3,7 @@ package net.sourceforge.filebot.ui.panel.subtitle;
import static java.util.Collections.*;
import static net.sourceforge.filebot.FileBotUtilities.*;
import static net.sourceforge.filebot.MediaTypes.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

View File

@ -2,7 +2,7 @@
package net.sourceforge.filebot.ui.panel.subtitle;
import static net.sourceforge.filebot.FileBotUtilities.*;
import static net.sourceforge.filebot.MediaTypes.*;
import java.awt.Color;
import java.awt.Insets;
@ -61,7 +61,7 @@ class SubtitlePackageCellRenderer extends AbstractFancyListCellRenderer {
private Icon getIcon(SubtitlePackage subtitle) {
switch (subtitle.getDownload().getPhase()) {
case PENDING:
if (ArchiveType.forName(subtitle.getType()) != ArchiveType.UNDEFINED || SUBTITLE_FILES.extensions().contains(subtitle.getType())) {
if (ArchiveType.forName(subtitle.getType()) != ArchiveType.UNDEFINED || SUBTITLE_FILES.acceptExtension(subtitle.getType())) {
return ResourceManager.getIcon("bullet.green");
}

View File

@ -2,8 +2,8 @@
package net.sourceforge.filebot.ui.transfer;
import static net.sourceforge.filebot.FileBotUtilities.*;
import static net.sourceforge.filebot.Settings.*;
import static net.sourceforge.tuned.FileUtilities.*;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
@ -90,9 +90,7 @@ public class ByteBufferTransferable implements Transferable {
@Override
public DataFlavor[] getTransferDataFlavors() {
return new DataFlavor[] {
DataFlavor.javaFileListFlavor, FileTransferable.uriListFlavor
};
return new DataFlavor[] { DataFlavor.javaFileListFlavor, FileTransferable.uriListFlavor };
}

View File

@ -2,6 +2,8 @@
package net.sourceforge.filebot.ui.transfer;
import static net.sourceforge.tuned.FileUtilities.*;
import java.awt.event.ActionEvent;
import java.io.File;
import java.io.IOException;
@ -13,7 +15,6 @@ import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import net.sourceforge.filebot.FileBotUtilities;
import net.sourceforge.filebot.ResourceManager;
@ -21,7 +22,7 @@ public class SaveAction extends AbstractAction {
public static final String EXPORT_HANDLER = "exportHandler";
public SaveAction(FileExportHandler exportHandler) {
this("Save as ...", ResourceManager.getIcon("action.save"), exportHandler);
}
@ -67,7 +68,7 @@ public class SaveAction extends AbstractAction {
chooser.setMultiSelectionEnabled(false);
chooser.setSelectedFile(new File(getDefaultFolder(), FileBotUtilities.validateFileName(getDefaultFileName())));
chooser.setSelectedFile(new File(getDefaultFolder(), validateFileName(getDefaultFileName())));
if (chooser.showSaveDialog((JComponent) evt.getSource()) != JFileChooser.APPROVE_OPTION)
return;

View File

@ -5,7 +5,6 @@ package net.sourceforge.tuned;
import java.io.File;
import java.io.FileFilter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.regex.Matcher;
@ -14,27 +13,12 @@ import java.util.regex.Pattern;
public final class FileUtilities {
public static final long KILO = 1024;
public static final long MEGA = KILO * 1024;
public static final long GIGA = MEGA * 1024;
public static String formatSize(long size) {
if (size >= MEGA)
return String.format("%,d MB", size / MEGA);
else if (size >= KILO)
return String.format("%,d KB", size / KILO);
else
return String.format("%,d Byte", size);
}
/**
* Pattern used for matching file extensions.
*
* e.g. "file.txt" -> match "txt", ".hidden" -> no match
*/
private static final Pattern extension = Pattern.compile("(?<=.[.])\\p{Alnum}+$");
public static final Pattern EXTENSION = Pattern.compile("(?<=.[.])\\p{Alnum}+$");
public static String getExtension(File file) {
@ -46,7 +30,7 @@ public final class FileUtilities {
public static String getExtension(String name) {
Matcher matcher = extension.matcher(name);
Matcher matcher = EXTENSION.matcher(name);
if (matcher.find()) {
// extension without leading '.'
@ -59,21 +43,17 @@ public final class FileUtilities {
public static boolean hasExtension(File file, String... extensions) {
if (file.isDirectory())
return false;
return hasExtension(file.getName(), extensions);
// avoid native call for speed, if possible
return hasExtension(file.getName(), extensions) && !file.isDirectory();
}
public static boolean hasExtension(String filename, String... extensions) {
String extension = getExtension(filename);
if (extension != null) {
for (String entry : extensions) {
if (extension.equalsIgnoreCase(entry))
return true;
}
for (String value : extensions) {
if ((extension == null && value == null) || (extension != null && extension.equalsIgnoreCase(value)))
return true;
}
return false;
@ -81,7 +61,7 @@ public final class FileUtilities {
public static String getNameWithoutExtension(String name) {
Matcher matcher = extension.matcher(name);
Matcher matcher = EXTENSION.matcher(name);
if (matcher.find()) {
return name.substring(0, matcher.start() - 1);
@ -137,6 +117,44 @@ public final class FileUtilities {
}
/**
* Invalid filename characters: \, /, :, *, ?, ", <, >, |, \r and \n
*/
public static final Pattern ILLEGAL_CHARACTERS = Pattern.compile("[\\\\/:*?\"<>|\\r\\n]");
/**
* Strip filename of invalid characters
*
* @param filename original filename
* @return valid filename stripped of invalid characters
*/
public static String validateFileName(CharSequence filename) {
// strip invalid characters from filename
return ILLEGAL_CHARACTERS.matcher(filename).replaceAll("");
}
public static boolean isInvalidFileName(CharSequence filename) {
return ILLEGAL_CHARACTERS.matcher(filename).find();
}
public static final long KILO = 1024;
public static final long MEGA = KILO * 1024;
public static final long GIGA = MEGA * 1024;
public static String formatSize(long size) {
if (size >= MEGA)
return String.format("%,d MB", size / MEGA);
else if (size >= KILO)
return String.format("%,d KB", size / KILO);
else
return String.format("%,d Byte", size);
}
public static final FileFilter FOLDERS = new FileFilter() {
@Override
@ -190,8 +208,8 @@ public final class FileUtilities {
}
public List<String> extensions() {
return Arrays.asList(extensions);
public String[] extensions() {
return extensions.clone();
}
}

View File

@ -4,16 +4,16 @@ package net.sourceforge.filebot.subtitle;
import static org.junit.Assert.*;
import java.util.*;
import java.io.StringReader;
import org.junit.*;
import org.junit.Test;
public class MicroDVDReaderTest {
@Test
public void parse() throws Exception {
MicroDVDReader reader = new MicroDVDReader(new Scanner("{856}{900}what's the plan?"));
MicroDVDReader reader = new MicroDVDReader(new StringReader("{856}{900}what's the plan?"));
SubtitleElement element = reader.next();
@ -25,7 +25,7 @@ public class MicroDVDReaderTest {
@Test
public void fps() throws Exception {
MicroDVDReader reader = new MicroDVDReader(new Scanner("{1}{1}100\n{300}{400} trim me "));
MicroDVDReader reader = new MicroDVDReader(new StringReader("{1}{1}100\n{300}{400} trim me "));
SubtitleElement element = reader.next();
@ -37,7 +37,7 @@ public class MicroDVDReaderTest {
@Test
public void newline() throws Exception {
MicroDVDReader reader = new MicroDVDReader(new Scanner("\n\n{300}{400} l1|l2|l3| \n\n"));
MicroDVDReader reader = new MicroDVDReader(new StringReader("\n\n{300}{400} l1|l2|l3| \n\n"));
String[] lines = reader.next().getText().split("\\n");

View File

@ -5,9 +5,9 @@ package net.sourceforge.filebot.subtitle;
import static org.junit.Assert.*;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.LinkedList;
import java.util.Scanner;
import java.util.zip.GZIPInputStream;
import org.junit.Test;
@ -20,9 +20,9 @@ public class SubRipReaderTest {
LinkedList<SubtitleElement> list = new LinkedList<SubtitleElement>();
URL resource = new URL("http://www.opensubtitles.org/en/download/file/1951733951.gz");
InputStream stream = new GZIPInputStream(resource.openStream());
InputStream source = new GZIPInputStream(resource.openStream());
SubRipReader reader = new SubRipReader(new Scanner(stream, "UTF-8"));
SubRipReader reader = new SubRipReader(new InputStreamReader(source, "UTF-8"));
try {
while (reader.hasNext()) {

View File

@ -9,6 +9,14 @@ import org.junit.Test;
public class FileUtilitiesTest {
@Test
public void hasExtension() {
assertTrue(FileUtilities.hasExtension("abc.txt", null, "txt"));
assertTrue(FileUtilities.hasExtension(".hidden", null, "txt"));
assertFalse(FileUtilities.hasExtension(".hidden", "txt"));
}
@Test
public void getExtension() {
assertEquals("txt", FileUtilities.getExtension("abc.txt"));