From 3ff3a85289f8cf192d92f25b9d1058c64b4ca362 Mon Sep 17 00:00:00 2001 From: Reinhard Pointner Date: Mon, 10 Aug 2009 11:46:24 +0000 Subject: [PATCH] + auto-lookup verification file + allow absolute paths in verification files * lots of refactoring --- .../net/sourceforge/filebot/ArgumentBean.java | 2 +- .../sourceforge/filebot/FileBotUtilities.java | 74 ------ .../net/sourceforge/filebot/MediaTypes.java | 33 ++- .../filebot/format/EpisodeBindingBean.java | 50 +--- .../sourceforge/filebot/hash/HashType.java | 37 +-- .../sourceforge/filebot/hash/SfvFormat.java | 5 +- ...anner.java => VerificationFileReader.java} | 15 +- .../filebot/hash/VerificationFormat.java | 7 +- .../filebot/hash/VerificationUtilities.java | 92 +++++++ source/net/sourceforge/filebot/media.types | 7 +- .../filebot/mediainfo/MediaInfo.java | 2 +- .../filebot/subtitle/MicroDVDReader.java | 5 +- .../filebot/subtitle/SubRipReader.java | 5 +- .../subtitle/SubStationAlphaReader.java | 5 +- .../filebot/subtitle/SubViewerReader.java | 5 +- .../filebot/subtitle/SubtitleFormat.java | 12 +- .../filebot/subtitle/SubtitleReader.java | 13 +- .../list/FileListTransferablePolicy.java | 2 +- .../rename/AutoFetchEpisodeListMatcher.java | 2 +- .../ui/panel/rename/EpisodeBindingDialog.java | 9 +- .../panel/rename/MatchSimilarityMetric.java | 5 +- .../rename/NamesListTransferablePolicy.java | 19 +- .../ui/panel/rename/ValidateDialog.java | 4 +- .../ui/panel/sfv/ChecksumCellRenderer.java | 5 +- .../ui/panel/sfv/ChecksumComputationTask.java | 6 +- .../filebot/ui/panel/sfv/ChecksumRow.java | 12 +- .../filebot/ui/panel/sfv/ChecksumTable.java | 4 +- .../panel/sfv/ChecksumTableExportHandler.java | 13 +- .../sfv/ChecksumTableTransferablePolicy.java | 234 ++++++++++++------ .../ui/panel/sfv/SwingWorkerCellRenderer.java | 6 +- .../subtitle/SubtitleDownloadComponent.java | 3 +- .../ui/panel/subtitle/SubtitlePackage.java | 2 +- .../subtitle/SubtitlePackageCellRenderer.java | 4 +- .../ui/transfer/ByteBufferTransferable.java | 6 +- .../filebot/ui/transfer/SaveAction.java | 7 +- .../net/sourceforge/tuned/FileUtilities.java | 78 +++--- .../filebot/subtitle/MicroDVDReaderTest.java | 10 +- .../filebot/subtitle/SubRipReaderTest.java | 6 +- .../sourceforge/tuned/FileUtilitiesTest.java | 8 + 39 files changed, 465 insertions(+), 349 deletions(-) delete mode 100644 source/net/sourceforge/filebot/FileBotUtilities.java rename source/net/sourceforge/filebot/hash/{VerificationFileScanner.java => VerificationFileReader.java} (78%) create mode 100644 source/net/sourceforge/filebot/hash/VerificationUtilities.java diff --git a/source/net/sourceforge/filebot/ArgumentBean.java b/source/net/sourceforge/filebot/ArgumentBean.java index 7e4707a8..9537ccf0 100644 --- a/source/net/sourceforge/filebot/ArgumentBean.java +++ b/source/net/sourceforge/filebot/ArgumentBean.java @@ -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"))); } diff --git a/source/net/sourceforge/filebot/FileBotUtilities.java b/source/net/sourceforge/filebot/FileBotUtilities.java deleted file mode 100644 index 80340609..00000000 --- a/source/net/sourceforge/filebot/FileBotUtilities.java +++ /dev/null @@ -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(); - } - -} diff --git a/source/net/sourceforge/filebot/MediaTypes.java b/source/net/sourceforge/filebot/MediaTypes.java index ea5360cf..0c55b6a0 100644 --- a/source/net/sourceforge/filebot/MediaTypes.java +++ b/source/net/sourceforge/filebot/MediaTypes.java @@ -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 getExtensionList(String name) { + public List getExtensionList(String name) { List list = new ArrayList(); - 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"); } diff --git a/source/net/sourceforge/filebot/format/EpisodeBindingBean.java b/source/net/sourceforge/filebot/format/EpisodeBindingBean.java index fb5b1f93..d6fc81fe 100644 --- a/source/net/sourceforge/filebot/format/EpisodeBindingBean.java +++ b/source/net/sourceforge/filebot/format/EpisodeBindingBean.java @@ -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 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 diff --git a/source/net/sourceforge/filebot/hash/HashType.java b/source/net/sourceforge/filebot/hash/HashType.java index 58379556..acf1083d 100644 --- a/source/net/sourceforge/filebot/hash/HashType.java +++ b/source/net/sourceforge/filebot/hash/HashType.java @@ -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(); } diff --git a/source/net/sourceforge/filebot/hash/SfvFormat.java b/source/net/sourceforge/filebot/hash/SfvFormat.java index 9aa27560..da5390ca 100644 --- a/source/net/sourceforge/filebot/hash/SfvFormat.java +++ b/source/net/sourceforge/filebot/hash/SfvFormat.java @@ -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 | * */ - private final Pattern pattern = Pattern.compile("(.+)\\s+(\\p{XDigit}{8})"); - + private final Pattern pattern = Pattern.compile("^(.+)\\s+(\\p{XDigit}{8})$"); + @Override public Entry parseObject(String line) throws ParseException { Matcher matcher = pattern.matcher(line); diff --git a/source/net/sourceforge/filebot/hash/VerificationFileScanner.java b/source/net/sourceforge/filebot/hash/VerificationFileReader.java similarity index 78% rename from source/net/sourceforge/filebot/hash/VerificationFileScanner.java rename to source/net/sourceforge/filebot/hash/VerificationFileReader.java index aaa67c56..284e3bca 100644 --- a/source/net/sourceforge/filebot/hash/VerificationFileScanner.java +++ b/source/net/sourceforge/filebot/hash/VerificationFileReader.java @@ -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>, Closeable { +public class VerificationFileReader implements Iterator>, Closeable { private final Scanner scanner; @@ -26,15 +26,14 @@ public class VerificationFileScanner implements Iterator>, 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; } diff --git a/source/net/sourceforge/filebot/hash/VerificationFormat.java b/source/net/sourceforge/filebot/hash/VerificationFormat.java index 9ac1ab28..45d561bf 100644 --- a/source/net/sourceforge/filebot/hash/VerificationFormat.java +++ b/source/net/sourceforge/filebot/hash/VerificationFormat.java @@ -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 | * */ - private final Pattern pattern = Pattern.compile("(\\p{XDigit}+)\\s+(?:\\?\\w+)?\\*?(.+)"); - + private final Pattern pattern = Pattern.compile("^(\\p{XDigit}+)\\s+(?:\\?\\w+)?\\*?(.+)$"); + @Override public Entry parseObject(String line) throws ParseException { Matcher matcher = pattern.matcher(line); diff --git a/source/net/sourceforge/filebot/hash/VerificationUtilities.java b/source/net/sourceforge/filebot/hash/VerificationUtilities.java new file mode 100644 index 00000000..92042fe0 --- /dev/null +++ b/source/net/sourceforge/filebot/hash/VerificationUtilities.java @@ -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 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(); + } + +} diff --git a/source/net/sourceforge/filebot/media.types b/source/net/sourceforge/filebot/media.types index 210f38df..5a777818 100644 --- a/source/net/sourceforge/filebot/media.types +++ b/source/net/sourceforge/filebot/media.types @@ -21,12 +21,13 @@ sfv - + md5 - - + + sha1 + sha diff --git a/source/net/sourceforge/filebot/mediainfo/MediaInfo.java b/source/net/sourceforge/filebot/mediainfo/MediaInfo.java index f86639e2..45707839 100644 --- a/source/net/sourceforge/filebot/mediainfo/MediaInfo.java +++ b/source/net/sourceforge/filebot/mediainfo/MediaInfo.java @@ -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"); } } diff --git a/source/net/sourceforge/filebot/subtitle/MicroDVDReader.java b/source/net/sourceforge/filebot/subtitle/MicroDVDReader.java index 561f256d..573b9870 100644 --- a/source/net/sourceforge/filebot/subtitle/MicroDVDReader.java +++ b/source/net/sourceforge/filebot/subtitle/MicroDVDReader.java @@ -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); } diff --git a/source/net/sourceforge/filebot/subtitle/SubRipReader.java b/source/net/sourceforge/filebot/subtitle/SubRipReader.java index eb11fc27..7ff5521e 100644 --- a/source/net/sourceforge/filebot/subtitle/SubRipReader.java +++ b/source/net/sourceforge/filebot/subtitle/SubRipReader.java @@ -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); diff --git a/source/net/sourceforge/filebot/subtitle/SubStationAlphaReader.java b/source/net/sourceforge/filebot/subtitle/SubStationAlphaReader.java index 48082451..37d2c4f9 100644 --- a/source/net/sourceforge/filebot/subtitle/SubStationAlphaReader.java +++ b/source/net/sourceforge/filebot/subtitle/SubStationAlphaReader.java @@ -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 format; - public SubStationAlphaReader(Scanner scanner) { - super(scanner); + public SubStationAlphaReader(Readable source) { + super(source); } diff --git a/source/net/sourceforge/filebot/subtitle/SubViewerReader.java b/source/net/sourceforge/filebot/subtitle/SubViewerReader.java index 2ed4939a..37cdb06b 100644 --- a/source/net/sourceforge/filebot/subtitle/SubViewerReader.java +++ b/source/net/sourceforge/filebot/subtitle/SubViewerReader.java @@ -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); } diff --git a/source/net/sourceforge/filebot/subtitle/SubtitleFormat.java b/source/net/sourceforge/filebot/subtitle/SubtitleFormat.java index 89ade471..398101d6 100644 --- a/source/net/sourceforge/filebot/subtitle/SubtitleFormat.java +++ b/source/net/sourceforge/filebot/subtitle/SubtitleFormat.java @@ -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); } } diff --git a/source/net/sourceforge/filebot/subtitle/SubtitleReader.java b/source/net/sourceforge/filebot/subtitle/SubtitleReader.java index 29bdeda6..23c7b3c6 100644 --- a/source/net/sourceforge/filebot/subtitle/SubtitleReader.java +++ b/source/net/sourceforge/filebot/subtitle/SubtitleReader.java @@ -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, 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); } diff --git a/source/net/sourceforge/filebot/ui/panel/list/FileListTransferablePolicy.java b/source/net/sourceforge/filebot/ui/panel/list/FileListTransferablePolicy.java index 9d29af08..fe2277ec 100644 --- a/source/net/sourceforge/filebot/ui/panel/list/FileListTransferablePolicy.java +++ b/source/net/sourceforge/filebot/ui/panel/list/FileListTransferablePolicy.java @@ -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); diff --git a/source/net/sourceforge/filebot/ui/panel/rename/AutoFetchEpisodeListMatcher.java b/source/net/sourceforge/filebot/ui/panel/rename/AutoFetchEpisodeListMatcher.java index 5a052055..3c560239 100644 --- a/source/net/sourceforge/filebot/ui/panel/rename/AutoFetchEpisodeListMatcher.java +++ b/source/net/sourceforge/filebot/ui/panel/rename/AutoFetchEpisodeListMatcher.java @@ -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; diff --git a/source/net/sourceforge/filebot/ui/panel/rename/EpisodeBindingDialog.java b/source/net/sourceforge/filebot/ui/panel/rename/EpisodeBindingDialog.java index 884bcba7..cd517b86 100644 --- a/source/net/sourceforge/filebot/ui/panel/rename/EpisodeBindingDialog.java +++ b/source/net/sourceforge/filebot/ui/panel/rename/EpisodeBindingDialog.java @@ -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 extensions = new ArrayList(); - 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); diff --git a/source/net/sourceforge/filebot/ui/panel/rename/MatchSimilarityMetric.java b/source/net/sourceforge/filebot/ui/panel/rename/MatchSimilarityMetric.java index ff28e09b..a955f9a2 100644 --- a/source/net/sourceforge/filebot/ui/panel/rename/MatchSimilarityMetric.java +++ b/source/net/sourceforge/filebot/ui/panel/rename/MatchSimilarityMetric.java @@ -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); } diff --git a/source/net/sourceforge/filebot/ui/panel/rename/NamesListTransferablePolicy.java b/source/net/sourceforge/filebot/ui/panel/rename/NamesListTransferablePolicy.java index 1617ef49..db9fcec2 100644 --- a/source/net/sourceforge/filebot/ui/panel/rename/NamesListTransferablePolicy.java +++ b/source/net/sourceforge/filebot/ui/panel/rename/NamesListTransferablePolicy.java @@ -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 files) throws IOException { List values = new ArrayList(); - 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 files, List 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()) { diff --git a/source/net/sourceforge/filebot/ui/panel/rename/ValidateDialog.java b/source/net/sourceforge/filebot/ui/panel/rename/ValidateDialog.java index ae7580cd..37dc17dd 100644 --- a/source/net/sourceforge/filebot/ui/panel/rename/ValidateDialog.java +++ b/source/net/sourceforge/filebot/ui/panel/rename/ValidateDialog.java @@ -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:"); diff --git a/source/net/sourceforge/filebot/ui/panel/sfv/ChecksumCellRenderer.java b/source/net/sourceforge/filebot/ui/panel/sfv/ChecksumCellRenderer.java index c962f00e..1e948aa3 100644 --- a/source/net/sourceforge/filebot/ui/panel/sfv/ChecksumCellRenderer.java +++ b/source/net/sourceforge/filebot/ui/panel/sfv/ChecksumCellRenderer.java @@ -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)); } diff --git a/source/net/sourceforge/filebot/ui/panel/sfv/ChecksumComputationTask.java b/source/net/sourceforge/filebot/ui/panel/sfv/ChecksumComputationTask.java index c4650a03..2d934520 100644 --- a/source/net/sourceforge/filebot/ui/panel/sfv/ChecksumComputationTask.java +++ b/source/net/sourceforge/filebot/ui/panel/sfv/ChecksumComputationTask.java @@ -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, 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, Void> { @Override protected Map doInBackground() throws Exception { - if (!file.exists()) - throw new FileNotFoundException("File not found"); - // create hash instance Hash hash = hashType.newHash(); diff --git a/source/net/sourceforge/filebot/ui/panel/sfv/ChecksumRow.java b/source/net/sourceforge/filebot/ui/panel/sfv/ChecksumRow.java index d70865e6..8c55e342 100644 --- a/source/net/sourceforge/filebot/ui/panel/sfv/ChecksumRow.java +++ b/source/net/sourceforge/filebot/ui/panel/sfv/ChecksumRow.java @@ -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); } diff --git a/source/net/sourceforge/filebot/ui/panel/sfv/ChecksumTable.java b/source/net/sourceforge/filebot/ui/panel/sfv/ChecksumTable.java index 987adc0f..1da25461 100644 --- a/source/net/sourceforge/filebot/ui/panel/sfv/ChecksumTable.java +++ b/source/net/sourceforge/filebot/ui/panel/sfv/ChecksumTable.java @@ -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()); } diff --git a/source/net/sourceforge/filebot/ui/panel/sfv/ChecksumTableExportHandler.java b/source/net/sourceforge/filebot/ui/panel/sfv/ChecksumTableExportHandler.java index 072cdea5..60a920ad 100644 --- a/source/net/sourceforge/filebot/ui/panel/sfv/ChecksumTableExportHandler.java +++ b/source/net/sourceforge/filebot/ui/panel/sfv/ChecksumTableExportHandler.java @@ -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(); } } diff --git a/source/net/sourceforge/filebot/ui/panel/sfv/ChecksumTableTransferablePolicy.java b/source/net/sourceforge/filebot/ui/panel/sfv/ChecksumTableTransferablePolicy.java index 4c4067d6..bebc232a 100644 --- a/source/net/sourceforge/filebot/ui/panel/sfv/ChecksumTableTransferablePolicy.java +++ b/source/net/sourceforge/filebot/ui/panel/sfv/ChecksumTableTransferablePolicy.java @@ -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 { @@ -25,7 +27,7 @@ class ChecksumTableTransferablePolicy extends BackgroundFileTransferablePolicy 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 files) { - for (HashType hash : HashType.values()) { - if (containsOnly(files, new ExtensionFileFilter(hash.getExtension()))) { - return hash; - } - } - - return null; - } + private final ThreadLocal executor = new ThreadLocal(); + private final ThreadLocal verificationTracker = new ThreadLocal(); - 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 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 executor = new ThreadLocal(); - - @Override protected void load(List 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 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 hashByVerificationFile = verificationTracker.get().getHashByVerificationFile(file); + + for (Entry 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 seen = new HashMap(); + private final Map> cache = new HashMap>(); + private final Map types = new HashMap(); + + private final int maxDepth; + + + public VerificationTracker(int maxDepth) { + this.maxDepth = maxDepth; + } + + + public Map 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 result = new HashMap(2); + + for (Entry> 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 importVerificationFile(File verificationFile, HashType hashType, File baseFolder) throws IOException { + VerificationFileReader reader = new VerificationFileReader(verificationFile, hashType.getFormat()); + Map content = new HashMap(); + + try { + while (reader.hasNext()) { + Entry 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; + } + + } + } diff --git a/source/net/sourceforge/filebot/ui/panel/sfv/SwingWorkerCellRenderer.java b/source/net/sourceforge/filebot/ui/panel/sfv/SwingWorkerCellRenderer.java index f05fa2e3..fd873c8b 100644 --- a/source/net/sourceforge/filebot/ui/panel/sfv/SwingWorkerCellRenderer.java +++ b/source/net/sourceforge/filebot/ui/panel/sfv/SwingWorkerCellRenderer.java @@ -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); diff --git a/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitleDownloadComponent.java b/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitleDownloadComponent.java index 5e1bca5f..bb5107d6 100644 --- a/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitleDownloadComponent.java +++ b/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitleDownloadComponent.java @@ -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; diff --git a/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitlePackage.java b/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitlePackage.java index c8c2a5a5..c26fe8c4 100644 --- a/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitlePackage.java +++ b/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitlePackage.java @@ -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; diff --git a/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitlePackageCellRenderer.java b/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitlePackageCellRenderer.java index 954e6567..92526908 100644 --- a/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitlePackageCellRenderer.java +++ b/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitlePackageCellRenderer.java @@ -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"); } diff --git a/source/net/sourceforge/filebot/ui/transfer/ByteBufferTransferable.java b/source/net/sourceforge/filebot/ui/transfer/ByteBufferTransferable.java index b8ce1d63..e4a1b40d 100644 --- a/source/net/sourceforge/filebot/ui/transfer/ByteBufferTransferable.java +++ b/source/net/sourceforge/filebot/ui/transfer/ByteBufferTransferable.java @@ -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 }; } diff --git a/source/net/sourceforge/filebot/ui/transfer/SaveAction.java b/source/net/sourceforge/filebot/ui/transfer/SaveAction.java index 6c710fa9..ee508662 100644 --- a/source/net/sourceforge/filebot/ui/transfer/SaveAction.java +++ b/source/net/sourceforge/filebot/ui/transfer/SaveAction.java @@ -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; diff --git a/source/net/sourceforge/tuned/FileUtilities.java b/source/net/sourceforge/tuned/FileUtilities.java index cd646d98..8ed95ad2 100644 --- a/source/net/sourceforge/tuned/FileUtilities.java +++ b/source/net/sourceforge/tuned/FileUtilities.java @@ -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 extensions() { - return Arrays.asList(extensions); + public String[] extensions() { + return extensions.clone(); } } diff --git a/test/net/sourceforge/filebot/subtitle/MicroDVDReaderTest.java b/test/net/sourceforge/filebot/subtitle/MicroDVDReaderTest.java index 53fc9838..b40f1336 100644 --- a/test/net/sourceforge/filebot/subtitle/MicroDVDReaderTest.java +++ b/test/net/sourceforge/filebot/subtitle/MicroDVDReaderTest.java @@ -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"); diff --git a/test/net/sourceforge/filebot/subtitle/SubRipReaderTest.java b/test/net/sourceforge/filebot/subtitle/SubRipReaderTest.java index 4952766e..ef58eeeb 100644 --- a/test/net/sourceforge/filebot/subtitle/SubRipReaderTest.java +++ b/test/net/sourceforge/filebot/subtitle/SubRipReaderTest.java @@ -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 list = new LinkedList(); 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()) { diff --git a/test/net/sourceforge/tuned/FileUtilitiesTest.java b/test/net/sourceforge/tuned/FileUtilitiesTest.java index 627e599c..98aa7852 100644 --- a/test/net/sourceforge/tuned/FileUtilitiesTest.java +++ b/test/net/sourceforge/tuned/FileUtilitiesTest.java @@ -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"));