* automatic episode list download and matching in RenamePanel

* added SeriesNameMatcher
* added SeasonEpisodeMatcher
* access Preferences via new Settings class
* adapt TVDotComClient to site changes (episodes  no longer ordered in reverse)
* added ActionPopup (inspired by the eclipse quickfix popup)

refactoring:
* renamed *Util classes to *Utilities
* renamed HyperlinkLabel to LinkButton as it extends JButton now
* refactored FileBotUtilities and FileUtilities
This commit is contained in:
Reinhard Pointner 2009-01-25 00:08:57 +00:00
parent c5c12513fa
commit ac9473ff07
87 changed files with 1961 additions and 960 deletions

BIN
lib/commons-logging.jar Normal file

Binary file not shown.

BIN
lib/junrar-custom.jar Normal file

Binary file not shown.

View File

@ -125,7 +125,7 @@
Simple memory cache named web. Time to live is 5 min. This cache is used by TheTVDBClient and TVRageClient.
-->
<cache name="web"
maxElementsInMemory="40"
maxElementsInMemory="120"
eternal="false"
timeToIdleSeconds="300"
timeToLiveSeconds="300"

View File

@ -2,26 +2,16 @@
package net.sourceforge.filebot;
import java.io.File;
import java.util.Arrays;
import java.util.Collections;
import java.io.FileFilter;
import java.util.AbstractList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.sourceforge.tuned.FileUtil;
import net.sourceforge.tuned.FileUtilities.ExtensionFileFilter;
public final class FileBotUtil {
public static String getApplicationName() {
return "FileBot";
};
public static String getApplicationVersion() {
return "1.9";
};
public final class FileBotUtilities {
/**
* Invalid characters in filenames: \, /, :, *, ?, ", <, >, |, \r and \n
@ -36,13 +26,13 @@ public final class FileBotUtil {
* @param filename original filename
* @return valid filename stripped of invalid characters
*/
public static String validateFileName(String filename) {
public static String validateFileName(CharSequence filename) {
// strip invalid characters from filename
return INVALID_CHARACTERS_PATTERN.matcher(filename).replaceAll("");
}
public static boolean isInvalidFileName(String filename) {
public static boolean isInvalidFileName(CharSequence filename) {
return INVALID_CHARACTERS_PATTERN.matcher(filename).find();
}
@ -54,13 +44,13 @@ public final class FileBotUtil {
public static final Pattern EMBEDDED_CHECKSUM_PATTERN = Pattern.compile("(?<=\\[|\\()(\\p{XDigit}{8,})(?=\\]|\\))");
public static String getEmbeddedChecksum(String string) {
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(0);
embeddedChecksum = matcher.group();
}
return embeddedChecksum;
@ -71,41 +61,52 @@ public final class FileBotUtil {
return string.replaceAll("[\\(\\[]\\p{XDigit}{8}[\\]\\)]", "");
}
public static final List<String> TORRENT_FILE_EXTENSIONS = unmodifiableList("torrent");
public static final List<String> SFV_FILE_EXTENSIONS = unmodifiableList("sfv");
public static final List<String> LIST_FILE_EXTENSIONS = unmodifiableList("txt", "list", "");
public static final List<String> SUBTITLE_FILE_EXTENSIONS = unmodifiableList("srt", "sub", "ssa", "smi");
public static String join(Object[] values, String separator) {
if (values == null) {
return null;
}
private static List<String> unmodifiableList(String... elements) {
return Collections.unmodifiableList(Arrays.asList(elements));
StringBuilder sb = new StringBuilder();
for (int i = 0; i < values.length; i++) {
sb.append(values[i]);
if (i < values.length - 1) {
sb.append(separator);
}
}
return sb.toString();
}
public static boolean containsOnlyFolders(Iterable<File> files) {
for (File file : files) {
if (!file.isDirectory())
return false;
}
public static List<String> asStringList(final List<?> list) {
return new AbstractList<String>() {
return true;
@Override
public String get(int index) {
return list.get(index).toString();
}
public static boolean containsOnly(Iterable<File> files, Iterable<String> extensions) {
for (File file : files) {
if (!FileUtil.hasExtension(file, extensions))
return false;
@Override
public int size() {
return list.size();
}
};
}
return true;
}
public static final FileFilter TORRENT_FILES = new ExtensionFileFilter("torrent");
public static final FileFilter SFV_FILES = new ExtensionFileFilter("sfv");
public static final FileFilter LIST_FILES = new ExtensionFileFilter("txt", "list", "");
public static final FileFilter SUBTITLE_FILES = new ExtensionFileFilter("srt", "sub", "ssa", "ass", "smi");
/**
* Dummy constructor to prevent instantiation.
*/
private FileBotUtil() {
private FileBotUtilities() {
throw new UnsupportedOperationException();
}

View File

@ -5,8 +5,6 @@ package net.sourceforge.filebot;
import java.util.logging.ConsoleHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
@ -23,11 +21,24 @@ public class Main {
/**
* @param args
*/
public static void main(String... args) {
public static void main(String... args) throws Exception {
final ArgumentBean argumentBean = handleArguments(args);
final ArgumentBean argumentBean = initializeArgumentBean(args);
setupLogging();
if (argumentBean.isHelp()) {
printUsage(argumentBean);
// just print help message and exit afterwards
System.exit(0);
}
if (argumentBean.isClear()) {
// clear preferences
Settings.userRoot().clear();
}
initializeLogging();
initializeSettings();
try {
// UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
@ -57,7 +68,7 @@ public class Main {
}
private static void setupLogging() {
private static void initializeLogging() {
Logger uiLogger = Logger.getLogger("ui");
// don't use parent handlers
@ -74,35 +85,24 @@ public class Main {
}
private static ArgumentBean handleArguments(String... args) {
private static void initializeSettings() {
Settings.userRoot().putDefault("thetvdb.apikey", "58B4AA94C59AD656");
}
private static ArgumentBean initializeArgumentBean(String... args) throws CmdLineException {
ArgumentBean argumentBean = new ArgumentBean();
CmdLineParser argumentParser = new CmdLineParser(argumentBean);
try {
argumentParser.parseArgument(args);
} catch (CmdLineException e) {
Logger.getLogger("global").log(Level.WARNING, e.getMessage());
}
if (argumentBean.isHelp()) {
System.out.println("Options:");
argumentParser.printUsage(System.out);
// just print help message and exit afterwards
System.exit(0);
}
if (argumentBean.isClear()) {
// clear preferences
try {
Preferences.userNodeForPackage(Main.class).removeNode();
} catch (BackingStoreException e) {
Logger.getLogger("global").log(Level.SEVERE, e.toString(), e);
}
}
new CmdLineParser(argumentBean).parseArgument(args);
return argumentBean;
}
private static void printUsage(ArgumentBean argumentBean) {
System.out.println("Options:");
new CmdLineParser(argumentBean).printUsage(System.out);
}
}

View File

@ -0,0 +1,102 @@
package net.sourceforge.filebot;
import java.util.List;
import java.util.Map;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
import net.sourceforge.tuned.ExceptionUtil;
import net.sourceforge.tuned.PreferencesList;
import net.sourceforge.tuned.PreferencesMap;
import net.sourceforge.tuned.PreferencesMap.Adapter;
public final class Settings {
public static String getApplicationName() {
return "FileBot";
};
public static String getApplicationVersion() {
return "1.9";
};
private static final Settings userRoot = new Settings(Preferences.userRoot(), getApplicationName());
public static Settings userRoot() {
return userRoot;
}
private final Preferences prefs;
private Settings(Preferences parentNode, String name) {
this.prefs = parentNode.node(name.toLowerCase());
}
public Settings node(String nodeName) {
return new Settings(prefs, nodeName);
}
public void put(String key, String value) {
prefs.put(key, value);
}
public void putDefault(String key, String value) {
if (get(key) == null) {
put(key, value);
}
}
public String get(String key) {
return get(key, null);
}
public String get(String key, String def) {
return prefs.get(key, def);
}
public <T> Map<String, T> asMap(Class<T> type) {
return PreferencesMap.map(prefs, type);
}
public <T> Map<String, T> asMap(Adapter<T> adapter) {
return PreferencesMap.map(prefs, adapter);
}
public <T> List<T> asList(Class<T> type) {
return PreferencesList.map(prefs, type);
}
public <T> List<T> asList(Adapter<T> adapter) {
return PreferencesList.map(prefs, adapter);
}
public void clear() {
try {
// remove child nodes
for (String nodeName : prefs.childrenNames()) {
prefs.node(nodeName).removeNode();
}
// remove entries
prefs.clear();
} catch (BackingStoreException e) {
throw ExceptionUtil.asRuntimeException(e);
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 706 B

View File

@ -48,7 +48,7 @@ public class Matcher<V, C> {
}
// match recursively
match(possibleMatches, 0);
deepMatch(possibleMatches, 0);
// restore order according to the given values
List<Match<V, C>> result = new ArrayList<Match<V, C>>();
@ -74,17 +74,17 @@ public class Matcher<V, C> {
}
public List<V> remainingValues() {
public synchronized List<V> remainingValues() {
return Collections.unmodifiableList(values);
}
public List<C> remainingCandidates() {
public synchronized List<C> remainingCandidates() {
return Collections.unmodifiableList(candidates);
}
protected void match(Collection<Match<V, C>> possibleMatches, int level) throws InterruptedException {
protected void deepMatch(Collection<Match<V, C>> possibleMatches, int level) throws InterruptedException {
if (level >= metrics.size() || possibleMatches.isEmpty()) {
// no further refinement possible
disjointMatchCollection.addAll(possibleMatches);
@ -106,8 +106,8 @@ public class Matcher<V, C> {
// remove invalid matches
removeCollected(matchesWithEqualSimilarity);
// matches are ambiguous, more refined matching required
match(matchesWithEqualSimilarity, level + 1);
// matches may be ambiguous, more refined matching required
deepMatch(matchesWithEqualSimilarity, level + 1);
}
}

View File

@ -2,7 +2,7 @@
package net.sourceforge.filebot.similarity;
import static net.sourceforge.filebot.FileBotUtil.removeEmbeddedChecksum;
import static net.sourceforge.filebot.FileBotUtilities.removeEmbeddedChecksum;
import uk.ac.shef.wit.simmetrics.similaritymetrics.AbstractStringMetric;
import uk.ac.shef.wit.simmetrics.similaritymetrics.MongeElkan;
import uk.ac.shef.wit.simmetrics.tokenisers.TokeniserQGram3Extended;
@ -30,7 +30,7 @@ public class NameSimilarityMetric implements SimilarityMetric {
String name = removeEmbeddedChecksum(object.toString());
// normalize separators
name = name.replaceAll("[^\\p{Alnum}]+", " ");
name = name.replaceAll("[\\p{Punct}\\p{Space}]+", " ");
// normalize case and trim
return name.trim().toLowerCase();

View File

@ -2,7 +2,7 @@
package net.sourceforge.filebot.similarity;
import static net.sourceforge.filebot.FileBotUtil.removeEmbeddedChecksum;
import static net.sourceforge.filebot.FileBotUtilities.removeEmbeddedChecksum;
import java.util.ArrayList;
import java.util.HashSet;

View File

@ -0,0 +1,150 @@
package net.sourceforge.filebot.similarity;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class SeasonEpisodeMatcher {
private final SeasonEpisodePattern[] patterns;
public SeasonEpisodeMatcher() {
patterns = new SeasonEpisodePattern[3];
// match patterns like S01E01, s01e02, ... [s01]_[e02], s01.e02, ...
patterns[0] = new SeasonEpisodePattern("(?<!\\p{Alnum})[Ss](\\d{1,2})[^\\p{Alnum}]{0,3}[Ee](\\d{1,3})(?!\\p{Digit})");
// match patterns like 1x01, 1x02, ... 10x01, 10x02, ...
patterns[1] = new SeasonEpisodePattern("(?<!\\p{Alnum})(\\d{1,2})x(\\d{1,3})(?!\\p{Digit})");
// match patterns like 01, 102, 1003 (enclosed in separators)
patterns[2] = new SeasonEpisodePattern("(?<=^|[\\._ ])([0-2]?\\d?)(\\d{2})(?=[\\._ ]|$)");
}
/**
* Try to get season and episode numbers for the given string.
*
* @param name match this string against the a set of know patterns
* @return the matches returned by the first pattern that returns any matches for this
* string, or null if no pattern returned any matches
*/
public List<SxE> match(CharSequence name) {
for (SeasonEpisodePattern pattern : patterns) {
List<SxE> match = pattern.match(name);
if (!match.isEmpty()) {
// current pattern did match
return match;
}
}
return null;
}
public int find(CharSequence name) {
for (SeasonEpisodePattern pattern : patterns) {
int index = pattern.find(name);
if (index >= 0) {
// current pattern did match
return index;
}
}
return -1;
}
public static class SxE {
public final int season;
public final int episode;
public SxE(int season, int episode) {
this.season = season;
this.episode = episode;
}
public SxE(String season, String episode) {
this.season = parse(season);
this.episode = parse(episode);
}
protected int parse(String number) {
return number == null || number.isEmpty() ? 0 : Integer.parseInt(number);
}
@Override
public boolean equals(Object object) {
if (object instanceof SxE) {
SxE other = (SxE) object;
return this.season == other.season && this.episode == other.episode;
}
return false;
}
@Override
public String toString() {
return String.format("%dx%02d", season, episode);
}
}
protected static class SeasonEpisodePattern {
protected final Pattern pattern;
protected final int seasonGroup;
protected final int episodeGroup;
public SeasonEpisodePattern(String pattern) {
this(Pattern.compile(pattern), 1, 2);
}
public SeasonEpisodePattern(Pattern pattern, int seasonGroup, int episodeGroup) {
this.pattern = pattern;
this.seasonGroup = seasonGroup;
this.episodeGroup = episodeGroup;
}
public List<SxE> match(CharSequence name) {
// name will probably contain no more than one match, but may contain more
List<SxE> matches = new ArrayList<SxE>(1);
Matcher matcher = pattern.matcher(name);
while (matcher.find()) {
matches.add(new SxE(matcher.group(seasonGroup), matcher.group(episodeGroup)));
}
return matches;
}
public int find(CharSequence name) {
Matcher matcher = pattern.matcher(name);
if (matcher.find())
return matcher.start();
return -1;
}
}
}

View File

@ -2,38 +2,23 @@
package net.sourceforge.filebot.similarity;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.sourceforge.filebot.similarity.SeasonEpisodeMatcher.SxE;
public class SeasonEpisodeSimilarityMetric implements SimilarityMetric {
private final NumericSimilarityMetric fallbackMetric = new NumericSimilarityMetric();
private final SeasonEpisodePattern[] patterns;
public SeasonEpisodeSimilarityMetric() {
patterns = new SeasonEpisodePattern[3];
// match patterns like S01E01, s01e02, ... [s01]_[e02], s01.e02, ...
patterns[0] = new SeasonEpisodePattern("(?<!\\p{Alnum})[Ss](\\d{1,2})[^\\p{Alnum}]{0,3}[Ee](\\d{1,3})(?!\\p{Digit})");
// match patterns like 1x01, 1x02, ... 10x01, 10x02, ...
patterns[1] = new SeasonEpisodePattern("(?<!\\p{Alnum})(\\d{1,2})x(\\d{1,3})(?!\\p{Digit})");
// match patterns like 01, 102, 1003 (enclosed in separators)
patterns[2] = new SeasonEpisodePattern("(?<=^|[\\._ ])([0-2]?\\d?)(\\d{2})(?=[\\._ ]|$)");
}
private final SeasonEpisodeMatcher seasonEpisodeMatcher = new SeasonEpisodeMatcher();
@Override
public float getSimilarity(Object o1, Object o2) {
List<SxE> sxeVector1 = match(normalize(o1));
List<SxE> sxeVector2 = match(normalize(o2));
Collection<SxE> sxeVector1 = parse(o1);
Collection<SxE> sxeVector2 = parse(o2);
if (sxeVector1 == null || sxeVector2 == null) {
// name does not match any known pattern, return numeric similarity
@ -50,29 +35,8 @@ public class SeasonEpisodeSimilarityMetric implements SimilarityMetric {
}
/**
* Try to get season and episode numbers for the given string.
*
* @param name match this string against the a set of know patterns
* @return the matches returned by the first pattern that returns any matches for this
* string, or null if no pattern returned any matches
*/
protected List<SxE> match(String name) {
for (SeasonEpisodePattern pattern : patterns) {
List<SxE> match = pattern.match(name);
if (!match.isEmpty()) {
// current pattern did match
return match;
}
}
return null;
}
protected String normalize(Object object) {
return object.toString();
protected Collection<SxE> parse(Object o) {
return seasonEpisodeMatcher.match(o.toString());
}
@ -93,79 +57,4 @@ public class SeasonEpisodeSimilarityMetric implements SimilarityMetric {
return getClass().getName();
}
protected static class SxE {
public final int season;
public final int episode;
public SxE(int season, int episode) {
this.season = season;
this.episode = episode;
}
public SxE(String season, String episode) {
this(parseNumber(season), parseNumber(episode));
}
private static int parseNumber(String number) {
return number == null || number.isEmpty() ? 0 : Integer.parseInt(number);
}
@Override
public boolean equals(Object object) {
if (object instanceof SxE) {
SxE other = (SxE) object;
return this.season == other.season && this.episode == other.episode;
}
return false;
}
@Override
public String toString() {
return String.format("%dx%02d", season, episode);
}
}
protected static class SeasonEpisodePattern {
protected final Pattern pattern;
protected final int seasonGroup;
protected final int episodeGroup;
public SeasonEpisodePattern(String pattern) {
this(Pattern.compile(pattern), 1, 2);
}
public SeasonEpisodePattern(Pattern pattern, int seasonGroup, int episodeGroup) {
this.pattern = pattern;
this.seasonGroup = seasonGroup;
this.episodeGroup = episodeGroup;
}
public List<SxE> match(String name) {
// name will probably contain no more than one match, but may contain more
List<SxE> matches = new ArrayList<SxE>(1);
Matcher matcher = pattern.matcher(name);
while (matcher.find()) {
matches.add(new SxE(matcher.group(seasonGroup), matcher.group(episodeGroup)));
}
return matches;
}
}
}

View File

@ -0,0 +1,319 @@
package net.sourceforge.filebot.similarity;
import static net.sourceforge.filebot.FileBotUtilities.join;
import java.util.AbstractCollection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.TreeMap;
public class SeriesNameMatcher {
protected final SeasonEpisodeMatcher seasonEpisodeMatcher = new SeasonEpisodeMatcher();
protected final int threshold;
public SeriesNameMatcher(int threshold) {
if (threshold <= 0)
throw new IllegalArgumentException("threshold must be greater than 0");
this.threshold = threshold;
}
public Collection<String> matchAll(List<String> names) {
SeriesNameCollection seriesNames = new SeriesNameCollection();
// use pattern matching with frequency threshold
seriesNames.addAll(flatMatchAll(names));
// deep match common word sequences
seriesNames.addAll(deepMatchAll(names));
return seriesNames;
}
/**
* Try to match and verify all series names using known season episode patterns.
*
* @param names list of episode names
* @return series names that have been matched one or multiple times depending on the size
* of the given list
*/
protected Collection<String> flatMatchAll(Iterable<String> names) {
ThresholdCollection<String> seriesNames = new ThresholdCollection<String>(threshold, String.CASE_INSENSITIVE_ORDER);
for (String name : names) {
String match = matchBySeasonEpisodePattern(name);
if (match != null) {
seriesNames.add(match);
}
}
return seriesNames;
}
/**
* Try to match all common word sequences in the given list.
*
* @param names list of episode names
* @return all common word sequences that have been found
*/
protected Collection<String> deepMatchAll(List<String> names) {
// don't use common word sequence matching for less than 5 names
if (names.size() < threshold) {
return Collections.emptySet();
}
String common = matchByFirstCommonWordSequence(names);
if (common != null) {
// common word sequence found
return Collections.singleton(common);
}
// recursive divide and conquer
List<String> results = new ArrayList<String>();
if (names.size() >= 2) {
// split list in two and try to match common word sequence on those
results.addAll(deepMatchAll(names.subList(0, names.size() / 2)));
results.addAll(deepMatchAll(names.subList(names.size() / 2, names.size())));
}
return results;
}
/**
* Try to match a series name from the given episode name using known season episode
* patterns.
*
* @param name episode name
* @return a substring of the given name that ends before the first occurrence of a season
* episode pattern, or null
*/
public String matchBySeasonEpisodePattern(String name) {
int seasonEpisodePosition = seasonEpisodeMatcher.find(name);
if (seasonEpisodePosition > 0) {
// series name ends at the first season episode pattern
return normalize(name.substring(0, seasonEpisodePosition));
}
return null;
}
/**
* Try to match a series name from the first common word sequence.
*
* @param names various episode names (5 or more for accurate results)
* @return a word sequence all episode names have in common, or null
*/
public String matchByFirstCommonWordSequence(Collection<String> names) {
if (names.size() <= 1) {
// can't match common sequence from less than two names
return null;
}
String[] common = null;
for (String name : names) {
String[] words = normalize(name).split("\\s+");
if (common == null) {
// initialize common with current word array
common = words;
} else {
// find common sequence
common = firstCommonSequence(common, words, String.CASE_INSENSITIVE_ORDER);
if (common == null) {
// no common sequence
return null;
}
}
}
// join will return null, if common is null
return join(common, " ");
}
protected String normalize(String name) {
// remove group names (remove any [...])
name = name.replaceAll("\\[[^\\]]+\\]", "");
// remove special characters
name = name.replaceAll("[\\p{Punct}\\p{Space}]+", " ");
return name.trim();
}
protected <T> T[] firstCommonSequence(T[] seq1, T[] seq2, Comparator<T> equalsComparator) {
for (int i = 0; i < seq1.length; i++) {
for (int j = 0; j < seq2.length; j++) {
// common sequence length
int len = 0;
// iterate over common sequence
while ((i + len < seq1.length) && (j + len < seq2.length) && (equalsComparator.compare(seq1[i + len], seq2[j + len]) == 0)) {
len++;
}
// check if a common sequence was found
if (len > 0) {
if (i == 0 && len == seq1.length)
return seq1;
if (j == 0 && len == seq2.length)
return seq2;
return Arrays.copyOfRange(seq1, i, i + len);
}
}
}
// no intersection at all
return null;
}
protected static class SeriesNameCollection extends AbstractCollection<String> {
private final Map<String, String> data = new LinkedHashMap<String, String>();
@Override
public boolean add(String value) {
String key = value.toLowerCase();
String current = data.get(key);
// prefer strings with similar upper/lower case ration (e.g. prefer Roswell over roswell)
if (current == null || firstCharacterCaseBalance(current) < firstCharacterCaseBalance(value)) {
data.put(key, value);
return true;
}
return false;
}
protected float firstCharacterCaseBalance(String s) {
int upper = 0;
int lower = 0;
Scanner scanner = new Scanner(s); // Scanner has white space delimiter by default
while (scanner.hasNext()) {
char c = scanner.next().charAt(0);
if (Character.isLowerCase(c))
lower++;
else if (Character.isUpperCase(c))
upper++;
}
// give upper case characters a slight boost
return (lower + (upper * 1.01f)) / Math.abs(lower - upper);
}
@Override
public boolean contains(Object o) {
return data.containsKey(o.toString().toLowerCase());
}
@Override
public Iterator<String> iterator() {
return data.values().iterator();
}
@Override
public int size() {
return data.size();
}
}
protected static class ThresholdCollection<E> extends AbstractCollection<E> {
private final Collection<E> heaven;
private final Map<E, Collection<E>> limbo;
private final int threshold;
public ThresholdCollection(int threshold, Comparator<E> equalityComparator) {
this.heaven = new ArrayList<E>();
this.limbo = new TreeMap<E, Collection<E>>(equalityComparator);
this.threshold = threshold;
}
@Override
public boolean add(E e) {
Collection<E> buffer = limbo.get(e);
if (buffer == null) {
// initialize buffer
buffer = new ArrayList<E>(threshold);
limbo.put(e, buffer);
}
if (buffer == heaven) {
// threshold reached
heaven.add(e);
return true;
}
// add element to buffer
buffer.add(e);
// check if threshold has been reached
if (buffer.size() >= threshold) {
heaven.addAll(buffer);
// replace buffer with heaven
limbo.put(e, heaven);
return true;
}
return false;
};
@Override
public Iterator<E> iterator() {
return heaven.iterator();
}
@Override
public int size() {
return heaven.size();
}
}
}

View File

@ -33,7 +33,7 @@ import net.sourceforge.filebot.web.SearchResult;
import net.sourceforge.tuned.ExceptionUtil;
import net.sourceforge.tuned.ui.LabelProvider;
import net.sourceforge.tuned.ui.SelectButtonTextField;
import net.sourceforge.tuned.ui.TunedUtil;
import net.sourceforge.tuned.ui.TunedUtilities;
import ca.odell.glazedlists.BasicEventList;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.swing.AutoCompleteSupport;
@ -86,7 +86,7 @@ public abstract class AbstractSearchPanel<S, E> extends FileBotPanel {
AutoCompleteSupport.install(searchTextField.getEditor(), searchHistory);
TunedUtil.putActionForKeystroke(this, KeyStroke.getKeyStroke("ENTER"), searchAction);
TunedUtilities.putActionForKeystroke(this, KeyStroke.getKeyStroke("ENTER"), searchAction);
}
@ -356,7 +356,7 @@ public abstract class AbstractSearchPanel<S, E> extends FileBotPanel {
protected void configureSelectDialog(SelectDialog<SearchResult> selectDialog) {
selectDialog.setIconImage(TunedUtil.getImage(getIcon()));
selectDialog.setIconImage(TunedUtilities.getImage(getIcon()));
}

View File

@ -18,7 +18,7 @@ import net.sourceforge.filebot.ui.transfer.DefaultTransferHandler;
import net.sourceforge.filebot.ui.transfer.TextFileExportHandler;
import net.sourceforge.filebot.ui.transfer.TransferablePolicy;
import net.sourceforge.tuned.ui.DefaultFancyListCellRenderer;
import net.sourceforge.tuned.ui.TunedUtil;
import net.sourceforge.tuned.ui.TunedUtilities;
import ca.odell.glazedlists.BasicEventList;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.swing.EventListModel;
@ -50,7 +50,7 @@ public class FileBotList<E> extends JComponent {
// Shortcut DELETE, disabled by default
removeAction.setEnabled(false);
TunedUtil.putActionForKeystroke(this, KeyStroke.getKeyStroke("pressed DELETE"), removeAction);
TunedUtilities.putActionForKeystroke(this, KeyStroke.getKeyStroke("pressed DELETE"), removeAction);
}

View File

@ -34,4 +34,10 @@ public class FileBotPanel extends JComponent {
return null;
}
@Override
public String toString() {
return getPanelName();
}
}

View File

@ -18,7 +18,7 @@ import javax.swing.Timer;
import javax.swing.border.EmptyBorder;
import net.sourceforge.tuned.ui.DefaultFancyListCellRenderer;
import net.sourceforge.tuned.ui.TunedUtil;
import net.sourceforge.tuned.ui.TunedUtilities;
import ca.odell.glazedlists.BasicEventList;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.swing.EventListModel;
@ -95,7 +95,7 @@ class FileBotPanelSelectionList extends JList {
@Override
public void dragEnter(DropTargetDragEvent dtde) {
dragEnterTimer = TunedUtil.invokeLater(SELECTDELAY_ON_DRAG_OVER, new Runnable() {
dragEnterTimer = TunedUtilities.invokeLater(SELECTDELAY_ON_DRAG_OVER, new Runnable() {
@Override
public void run() {

View File

@ -14,7 +14,7 @@ import javax.swing.SwingConstants;
import net.miginfocom.swing.MigLayout;
import net.sourceforge.filebot.ResourceManager;
import net.sourceforge.tuned.ui.ProgressIndicator;
import net.sourceforge.tuned.ui.TunedUtil;
import net.sourceforge.tuned.ui.TunedUtilities;
public class FileBotTabComponent extends JComponent {
@ -57,7 +57,7 @@ public class FileBotTabComponent extends JComponent {
public void setIcon(Icon icon) {
iconLabel.setIcon(icon);
progressIndicator.setPreferredSize(icon != null ? TunedUtil.getDimension(icon) : progressIndicator.getMinimumSize());
progressIndicator.setPreferredSize(icon != null ? TunedUtilities.getDimension(icon) : progressIndicator.getMinimumSize());
}
@ -88,7 +88,7 @@ public class FileBotTabComponent extends JComponent {
JButton button = new JButton(icon);
button.setRolloverIcon(rolloverIcon);
button.setPreferredSize(TunedUtil.getDimension(rolloverIcon));
button.setPreferredSize(TunedUtilities.getDimension(rolloverIcon));
button.setMaximumSize(button.getPreferredSize());
button.setContentAreaFilled(false);

View File

@ -2,14 +2,14 @@
package net.sourceforge.filebot.ui;
import static net.sourceforge.filebot.FileBotUtil.getApplicationName;
import static net.sourceforge.filebot.FileBotUtilities.asStringList;
import static net.sourceforge.filebot.Settings.getApplicationName;
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Image;
import java.util.ArrayList;
import java.util.List;
import java.util.prefs.Preferences;
import javax.swing.JComponent;
import javax.swing.JFrame;
@ -22,6 +22,7 @@ import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import net.sourceforge.filebot.ResourceManager;
import net.sourceforge.filebot.Settings;
import net.sourceforge.filebot.ui.panel.analyze.AnalyzePanel;
import net.sourceforge.filebot.ui.panel.episodelist.EpisodeListPanel;
import net.sourceforge.filebot.ui.panel.list.ListPanel;
@ -65,7 +66,7 @@ public class FileBotWindow extends JFrame implements ListSelectionListener {
// restore the panel selection from last time,
// switch to EpisodeListPanel by default (e.g. first start)
int selectedPanel = Preferences.userNodeForPackage(getClass()).getInt("selectedPanel", 3);
int selectedPanel = asStringList(panelSelectionList.getPanelModel()).indexOf(Settings.userRoot().get("selectedPanel"));
panelSelectionList.setSelectedIndex(selectedPanel);
// connect message handlers to message bus
@ -103,7 +104,7 @@ public class FileBotWindow extends JFrame implements ListSelectionListener {
c.revalidate();
c.repaint();
Preferences.userNodeForPackage(getClass()).putInt("selectedPanel", panelSelectionList.getSelectedIndex());
Settings.userRoot().put("selectedPanel", panelSelectionList.getSelectedValue().toString());
}

View File

@ -9,12 +9,13 @@ import java.util.ArrayList;
import java.util.List;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
import net.miginfocom.swing.MigLayout;
import net.sourceforge.tuned.ui.HyperlinkLabel;
import net.sourceforge.tuned.ui.LinkButton;
public class HistoryPanel extends JPanel {
@ -63,19 +64,16 @@ public class HistoryPanel extends JPanel {
public void add(String column1, URI link, Icon icon, String column2, String column3) {
JLabel label1 = (link != null) ? new HyperlinkLabel(column1, link) : new JLabel(column1);
JLabel label2 = new JLabel(column2, SwingConstants.RIGHT);
JLabel label3 = new JLabel(column3, SwingConstants.RIGHT);
JComponent c1 = link != null ? new LinkButton(column1, icon, link) : new JLabel(column1, icon, SwingConstants.LEFT);
JComponent c2 = new JLabel(column2, SwingConstants.RIGHT);
JComponent c3 = new JLabel(column3, SwingConstants.RIGHT);
label1.setIcon(icon);
label1.setIconTextGap(7);
add(label1, "align left");
add(c1, "align left");
// set minimum with to 100px so the text is aligned to the right,
// even though the whole label is centered
add(label2, "align center, wmin 100");
add(c2, "align center, wmin 100");
add(label3, "align right");
add(c3, "align right");
}
}

View File

@ -2,7 +2,7 @@
package net.sourceforge.filebot.ui;
import static net.sourceforge.filebot.FileBotUtil.getApplicationName;
import static net.sourceforge.filebot.Settings.getApplicationName;
import java.util.logging.Handler;
import java.util.logging.Level;
@ -35,26 +35,39 @@ public class NotificationLoggingHandler extends Handler {
@Override
public void publish(final LogRecord record) {
public void publish(LogRecord record) {
final Level level = record.getLevel();
final String message = getMessage(record);
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
Level level = record.getLevel();
if (level == Level.INFO) {
show(record.getMessage(), ResourceManager.getIcon("message.info"), timeout * 1);
show(message, ResourceManager.getIcon("message.info"), timeout * 1);
} else if (level == Level.WARNING) {
show(record.getMessage(), ResourceManager.getIcon("message.warning"), timeout * 2);
show(message, ResourceManager.getIcon("message.warning"), timeout * 2);
} else if (level == Level.SEVERE) {
show(record.getMessage(), ResourceManager.getIcon("message.error"), timeout * 3);
show(message, ResourceManager.getIcon("message.error"), timeout * 3);
}
}
});
}
private void show(String message, Icon icon, int timeout) {
protected String getMessage(LogRecord record) {
String message = record.getMessage();
if (message == null || message.isEmpty()) {
// if message is empty, display exception string
message = record.getThrown().toString();
}
return message;
}
protected void show(String message, Icon icon, int timeout) {
notificationManager.show(new MessageNotification(getApplicationName(), message, icon, timeout));
}

View File

@ -23,7 +23,7 @@ import net.miginfocom.swing.MigLayout;
import net.sourceforge.filebot.ResourceManager;
import net.sourceforge.tuned.ui.ArrayListModel;
import net.sourceforge.tuned.ui.DefaultFancyListCellRenderer;
import net.sourceforge.tuned.ui.TunedUtil;
import net.sourceforge.tuned.ui.TunedUtilities;
public class SelectDialog<T> extends JDialog {
@ -62,13 +62,13 @@ public class SelectDialog<T> extends JDialog {
// set default size and location
setSize(new Dimension(210, 210));
setLocation(TunedUtil.getPreferredLocation(this));
setLocation(TunedUtilities.getPreferredLocation(this));
// Shortcut Enter
TunedUtil.putActionForKeystroke(list, KeyStroke.getKeyStroke("released ENTER"), selectAction);
TunedUtilities.putActionForKeystroke(list, KeyStroke.getKeyStroke("released ENTER"), selectAction);
// Shortcut Escape
TunedUtil.putActionForKeystroke(list, KeyStroke.getKeyStroke("released ESCAPE"), cancelAction);
TunedUtilities.putActionForKeystroke(list, KeyStroke.getKeyStroke("released ESCAPE"), cancelAction);
}

View File

@ -2,6 +2,8 @@
package net.sourceforge.filebot.ui.panel.analyze;
import static net.sourceforge.filebot.ui.transfer.BackgroundFileTransferablePolicy.LOADING_PROPERTY;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
@ -18,7 +20,7 @@ import net.sourceforge.filebot.ResourceManager;
import net.sourceforge.filebot.ui.transfer.DefaultTransferHandler;
import net.sourceforge.filebot.ui.transfer.LoadAction;
import net.sourceforge.tuned.ui.LoadingOverlayPane;
import net.sourceforge.tuned.ui.TunedUtil;
import net.sourceforge.tuned.ui.TunedUtilities;
class FileTreePanel extends JComponent {
@ -38,10 +40,20 @@ class FileTreePanel extends JComponent {
add(new JButton(loadAction));
add(new JButton(clearAction), "gap 1.2mm, wrap 1.2mm");
transferablePolicy.addPropertyChangeListener("loading", loadingListener);
TunedUtilities.syncPropertyChangeEvents(boolean.class, LOADING_PROPERTY, transferablePolicy, this);
// update tree when loading is finished
transferablePolicy.addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
if (LOADING_PROPERTY.equals(evt.getPropertyName()) && !(Boolean) evt.getNewValue()) {
fireFileTreeChange();
}
}
});
// Shortcut DELETE
TunedUtil.putActionForKeystroke(fileTree, KeyStroke.getKeyStroke("pressed DELETE"), removeAction);
TunedUtilities.putActionForKeystroke(fileTree, KeyStroke.getKeyStroke("pressed DELETE"), removeAction);
}
@ -91,18 +103,4 @@ class FileTreePanel extends JComponent {
firePropertyChange("filetree", null, fileTree);
}
private final PropertyChangeListener loadingListener = new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
boolean loading = (Boolean) evt.getNewValue();
// relay loading property changes for loading overlay
firePropertyChange(evt.getPropertyName(), evt.getOldValue(), evt.getNewValue());
if (!loading) {
fireFileTreeChange();
}
}
};
}

View File

@ -9,7 +9,7 @@ import net.sourceforge.filebot.ui.panel.analyze.FileTree.AbstractTreeNode;
import net.sourceforge.filebot.ui.panel.analyze.FileTree.FileNode;
import net.sourceforge.filebot.ui.panel.analyze.FileTree.FolderNode;
import net.sourceforge.filebot.ui.transfer.BackgroundFileTransferablePolicy;
import net.sourceforge.tuned.FileUtil;
import net.sourceforge.tuned.FileUtilities;
class FileTreeTransferablePolicy extends BackgroundFileTransferablePolicy<AbstractTreeNode> {
@ -68,7 +68,7 @@ class FileTreeTransferablePolicy extends BackgroundFileTransferablePolicy<Abstra
if (file.isDirectory()) {
File[] files = file.listFiles();
FolderNode node = new FolderNode(FileUtil.getFolderName(file), files.length);
FolderNode node = new FolderNode(FileUtilities.getFolderName(file), files.length);
// add folders first
for (File f : files) {

View File

@ -21,7 +21,7 @@ import javax.swing.tree.TreeModel;
import net.miginfocom.swing.MigLayout;
import net.sourceforge.filebot.ui.panel.analyze.FileTree.FolderNode;
import net.sourceforge.filebot.ui.transfer.DefaultTransferHandler;
import net.sourceforge.tuned.FileUtil;
import net.sourceforge.tuned.FileUtilities;
import net.sourceforge.tuned.ui.GradientStyle;
import net.sourceforge.tuned.ui.LoadingOverlayPane;
import net.sourceforge.tuned.ui.notification.SeparatorBorder;
@ -62,7 +62,7 @@ public class SplitTool extends Tool<TreeModel> implements ChangeListener {
private long getSplitSize() {
return spinnerModel.getNumber().intValue() * FileUtil.MEGA;
return spinnerModel.getNumber().intValue() * FileUtilities.MEGA;
}
private FolderNode sourceModel = null;

View File

@ -2,9 +2,10 @@
package net.sourceforge.filebot.ui.panel.analyze;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import static net.sourceforge.tuned.ui.LoadingOverlayPane.LOADING_PROPERTY;
import java.io.File;
import java.util.ConcurrentModificationException;
import java.util.List;
import java.util.concurrent.Semaphore;
import java.util.logging.Level;
@ -15,8 +16,9 @@ import javax.swing.SwingWorker;
import net.sourceforge.filebot.ui.panel.analyze.FileTree.FileNode;
import net.sourceforge.filebot.ui.panel.analyze.FileTree.FolderNode;
import net.sourceforge.tuned.FileUtil;
import net.sourceforge.tuned.ui.LoadingOverlayPane;
import net.sourceforge.tuned.ExceptionUtil;
import net.sourceforge.tuned.FileUtilities;
import net.sourceforge.tuned.ui.TunedUtilities;
abstract class Tool<M> extends JComponent {
@ -36,7 +38,10 @@ abstract class Tool<M> extends JComponent {
}
updateTask = new UpdateModelTask(sourceModel);
updateTask.addPropertyChangeListener(loadingListener);
// sync events for loading overlay
TunedUtilities.syncPropertyChangeEvents(boolean.class, LOADING_PROPERTY, updateTask, this);
updateTask.execute();
}
@ -66,9 +71,9 @@ abstract class Tool<M> extends JComponent {
M model = null;
if (!isCancelled()) {
firePropertyChange(LoadingOverlayPane.LOADING_PROPERTY, false, true);
firePropertyChange(LOADING_PROPERTY, false, true);
model = createModelInBackground(sourceModel);
firePropertyChange(LoadingOverlayPane.LOADING_PROPERTY, true, false);
firePropertyChange(LOADING_PROPERTY, true, false);
}
return model;
@ -85,8 +90,12 @@ abstract class Tool<M> extends JComponent {
try {
setModel(get());
} catch (Exception e) {
if (ExceptionUtil.getRootCause(e) instanceof ConcurrentModificationException) {
// if it happens, it is supposed to
} else {
// should not happen
Logger.getLogger("global").log(Level.WARNING, e.toString());
Logger.getLogger("global").log(Level.WARNING, e.toString(), e);
}
}
}
}
@ -107,21 +116,9 @@ abstract class Tool<M> extends JComponent {
String numberOfFiles = String.format("%,d %s", files.size(), files.size() == 1 ? "file" : "files");
// set node text (e.g. txt (1 file, 42 Byte))
folder.setTitle(String.format("%s (%s, %s)", name, numberOfFiles, FileUtil.formatSize(totalSize)));
folder.setTitle(String.format("%s (%s, %s)", name, numberOfFiles, FileUtilities.formatSize(totalSize)));
return folder;
}
private final PropertyChangeListener loadingListener = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
// propagate loading events
if (evt.getPropertyName().equals(LoadingOverlayPane.LOADING_PROPERTY)) {
firePropertyChange(evt.getPropertyName(), evt.getOldValue(), evt.getNewValue());
}
}
};
}

View File

@ -4,10 +4,12 @@ package net.sourceforge.filebot.ui.panel.analyze;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.TreeMap;
import java.util.Map.Entry;
import java.util.Map;
import javax.swing.BorderFactory;
import javax.swing.JScrollPane;
@ -17,7 +19,7 @@ import javax.swing.tree.TreeModel;
import net.miginfocom.swing.MigLayout;
import net.sourceforge.filebot.ui.panel.analyze.FileTree.FolderNode;
import net.sourceforge.filebot.ui.transfer.DefaultTransferHandler;
import net.sourceforge.tuned.FileUtil;
import net.sourceforge.tuned.FileUtilities;
import net.sourceforge.tuned.ui.LoadingOverlayPane;
@ -42,11 +44,11 @@ public class TypeTool extends Tool<TreeModel> {
@Override
protected TreeModel createModelInBackground(FolderNode sourceModel) throws InterruptedException {
TreeMap<String, List<File>> map = new TreeMap<String, List<File>>();
Map<String, List<File>> map = new HashMap<String, List<File>>();
for (Iterator<File> iterator = sourceModel.fileIterator(); iterator.hasNext();) {
File file = iterator.next();
String extension = FileUtil.getExtension(file);
String extension = FileUtilities.getExtension(file);
List<File> files = map.get(extension);
@ -58,10 +60,22 @@ public class TypeTool extends Tool<TreeModel> {
files.add(file);
}
List<String> keys = new ArrayList<String>(map.keySet());
// sort strings like always, handle null as empty string
Collections.sort(keys, new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return ((s1 != null) ? s1 : "").compareTo((s2 != null) ? s2 : "");
}
});
// create tree model
FolderNode root = new FolderNode();
for (Entry<String, List<File>> entry : map.entrySet()) {
root.add(createStatisticsNode(entry.getKey(), entry.getValue()));
for (String key : keys) {
root.add(createStatisticsNode(key, map.get(key)));
// unwind thread, if we have been cancelled
if (Thread.interrupted()) {

View File

@ -3,6 +3,7 @@ package net.sourceforge.filebot.ui.panel.episodelist;
import static net.sourceforge.filebot.ui.panel.episodelist.SeasonSpinnerModel.ALL_SEASONS;
import static net.sourceforge.filebot.web.Episode.formatEpisodeNumbers;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
@ -21,6 +22,7 @@ import javax.swing.JSpinner;
import javax.swing.KeyStroke;
import net.sourceforge.filebot.ResourceManager;
import net.sourceforge.filebot.Settings;
import net.sourceforge.filebot.ui.AbstractSearchPanel;
import net.sourceforge.filebot.ui.FileBotList;
import net.sourceforge.filebot.ui.FileBotListExportHandler;
@ -38,7 +40,7 @@ import net.sourceforge.filebot.web.TheTVDBClient;
import net.sourceforge.tuned.ui.LabelProvider;
import net.sourceforge.tuned.ui.SelectButton;
import net.sourceforge.tuned.ui.SimpleLabelProvider;
import net.sourceforge.tuned.ui.TunedUtil;
import net.sourceforge.tuned.ui.TunedUtilities;
public class EpisodeListPanel extends AbstractSearchPanel<EpisodeListClient, Episode> {
@ -65,8 +67,8 @@ public class EpisodeListPanel extends AbstractSearchPanel<EpisodeListClient, Epi
searchTextField.getSelectButton().addPropertyChangeListener(SelectButton.SELECTED_VALUE, selectButtonListener);
TunedUtil.putActionForKeystroke(this, KeyStroke.getKeyStroke("shift UP"), new SpinSeasonAction(1));
TunedUtil.putActionForKeystroke(this, KeyStroke.getKeyStroke("shift DOWN"), new SpinSeasonAction(-1));
TunedUtilities.putActionForKeystroke(this, KeyStroke.getKeyStroke("shift UP"), new SpinSeasonAction(1));
TunedUtilities.putActionForKeystroke(this, KeyStroke.getKeyStroke("shift DOWN"), new SpinSeasonAction(-1));
}
@ -76,8 +78,8 @@ public class EpisodeListPanel extends AbstractSearchPanel<EpisodeListClient, Epi
engines.add(new TVRageClient());
engines.add(new AnidbClient());
engines.add(new TheTVDBClient("58B4AA94C59AD656"));
engines.add(new TVDotComClient());
engines.add(new TheTVDBClient(Settings.userRoot().get("thetvdb.apikey")));
return engines;
}
@ -216,29 +218,7 @@ public class EpisodeListPanel extends AbstractSearchPanel<EpisodeListClient, Epi
episodes = request.getClient().getEpisodeList(getSearchResult());
}
// find max. episode number string length
int maxLength = 1;
for (Episode episode : episodes) {
String num = episode.getEpisodeNumber();
if (num.matches("\\d+") && num.length() > maxLength) {
maxLength = num.length();
}
}
// pad episode numbers with zeros (e.g. %02d) so all episode numbers have the same number of digits
String format = "%0" + maxLength + "d";
for (Episode episode : episodes) {
try {
episode.setEpisodeNumber(String.format(format, Integer.parseInt(episode.getEpisodeNumber())));
} catch (NumberFormatException e) {
// ignore
}
}
return episodes;
return formatEpisodeNumbers(episodes, 2);
}

View File

@ -2,9 +2,9 @@
package net.sourceforge.filebot.ui.panel.list;
import static net.sourceforge.filebot.FileBotUtil.TORRENT_FILE_EXTENSIONS;
import static net.sourceforge.filebot.FileBotUtil.containsOnly;
import static net.sourceforge.filebot.FileBotUtil.containsOnlyFolders;
import static net.sourceforge.filebot.FileBotUtilities.TORRENT_FILES;
import static net.sourceforge.tuned.FileUtilities.FOLDERS;
import static net.sourceforge.tuned.FileUtilities.containsOnly;
import java.io.File;
import java.io.IOException;
@ -16,7 +16,7 @@ import java.util.logging.Logger;
import net.sourceforge.filebot.torrent.Torrent;
import net.sourceforge.filebot.ui.FileBotList;
import net.sourceforge.filebot.ui.transfer.FileTransferablePolicy;
import net.sourceforge.tuned.FileUtil;
import net.sourceforge.tuned.FileUtilities;
class FileListTransferablePolicy extends FileTransferablePolicy {
@ -44,15 +44,15 @@ class FileListTransferablePolicy extends FileTransferablePolicy {
@Override
protected void load(List<File> files) {
// set title based on parent folder of first file
list.setTitle(FileUtil.getFolderName(files.get(0).getParentFile()));
list.setTitle(FileUtilities.getFolderName(files.get(0).getParentFile()));
if (containsOnlyFolders(files)) {
if (containsOnly(files, FOLDERS)) {
loadFolders(files);
} else if (containsOnly(files, TORRENT_FILE_EXTENSIONS)) {
} else if (containsOnly(files, TORRENT_FILES)) {
loadTorrents(files);
} else {
for (File file : files) {
list.getModel().add(FileUtil.getFileName(file));
list.getModel().add(FileUtilities.getName(file));
}
}
}
@ -61,12 +61,12 @@ class FileListTransferablePolicy extends FileTransferablePolicy {
private void loadFolders(List<File> folders) {
if (folders.size() == 1) {
// if only one folder was dropped, use its name as title
list.setTitle(FileUtil.getFolderName(folders.get(0)));
list.setTitle(FileUtilities.getFolderName(folders.get(0)));
}
for (File folder : folders) {
for (File file : folder.listFiles()) {
list.getModel().add(FileUtil.getFileName(file));
list.getModel().add(FileUtilities.getName(file));
}
}
}
@ -81,12 +81,12 @@ class FileListTransferablePolicy extends FileTransferablePolicy {
}
if (torrentFiles.size() == 1) {
list.setTitle(FileUtil.getNameWithoutExtension(torrents.get(0).getName()));
list.setTitle(FileUtilities.getNameWithoutExtension(torrents.get(0).getName()));
}
for (Torrent torrent : torrents) {
for (Torrent.Entry entry : torrent.getFiles()) {
list.getModel().add(FileUtil.getNameWithoutExtension(entry.getName()));
list.getModel().add(FileUtilities.getNameWithoutExtension(entry.getName()));
}
}
} catch (IOException e) {

View File

@ -29,7 +29,7 @@ import net.sourceforge.filebot.ui.FileTransferableMessageHandler;
import net.sourceforge.filebot.ui.transfer.LoadAction;
import net.sourceforge.filebot.ui.transfer.SaveAction;
import net.sourceforge.tuned.MessageHandler;
import net.sourceforge.tuned.ui.TunedUtil;
import net.sourceforge.tuned.ui.TunedUtilities;
public class ListPanel extends FileBotPanel {
@ -80,7 +80,7 @@ public class ListPanel extends FileBotPanel {
list.add(buttonPanel, BorderLayout.SOUTH);
TunedUtil.putActionForKeystroke(this, KeyStroke.getKeyStroke("ENTER"), createAction);
TunedUtilities.putActionForKeystroke(this, KeyStroke.getKeyStroke("ENTER"), createAction);
}

View File

@ -0,0 +1,124 @@
package net.sourceforge.filebot.ui.panel.rename;
import static net.sourceforge.filebot.FileBotUtilities.SUBTITLE_FILES;
import static net.sourceforge.filebot.FileBotUtilities.asStringList;
import static net.sourceforge.filebot.web.Episode.formatEpisodeNumbers;
import static net.sourceforge.tuned.FileUtilities.FILES;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import javax.swing.SwingWorker;
import net.sourceforge.filebot.similarity.Match;
import net.sourceforge.filebot.similarity.Matcher;
import net.sourceforge.filebot.similarity.SeriesNameMatcher;
import net.sourceforge.filebot.similarity.SimilarityMetric;
import net.sourceforge.filebot.web.Episode;
import net.sourceforge.filebot.web.EpisodeListClient;
import net.sourceforge.filebot.web.SearchResult;
class AutoEpisodeListMatcher extends SwingWorker<List<Match<FileEntry, Episode>>, Void> {
private final List<FileEntry> remainingFiles = new ArrayList<FileEntry>();
private final List<FileEntry> files;
private final EpisodeListClient client;
private final Collection<SimilarityMetric> metrics;
public AutoEpisodeListMatcher(EpisodeListClient client, List<FileEntry> files, Collection<SimilarityMetric> metrics) {
this.client = client;
this.files = files;
this.metrics = metrics;
}
public Collection<FileEntry> remainingFiles() {
return Collections.unmodifiableCollection(remainingFiles);
}
protected Collection<String> matchSeriesNames(List<FileEntry> episodes) {
int threshold = Math.min(episodes.size(), 5);
return new SeriesNameMatcher(threshold).matchAll(asStringList(episodes));
}
@Override
protected List<Match<FileEntry, Episode>> doInBackground() throws Exception {
List<Callable<Collection<Episode>>> fetchTasks = new ArrayList<Callable<Collection<Episode>>>();
// match series names and create episode list fetch tasks
for (final String seriesName : matchSeriesNames(files)) {
fetchTasks.add(new Callable<Collection<Episode>>() {
@Override
public Collection<Episode> call() throws Exception {
Collection<SearchResult> searchResults = client.search(seriesName);
if (searchResults.isEmpty())
return Collections.emptyList();
return formatEpisodeNumbers(client.getEpisodeList(searchResults.iterator().next()), 2);
}
});
}
if (fetchTasks.isEmpty()) {
throw new IllegalArgumentException("Failed to auto-detect series name.");
}
// fetch episode lists concurrently
List<Episode> episodeList = new ArrayList<Episode>();
ExecutorService executor = Executors.newFixedThreadPool(fetchTasks.size());
for (Future<Collection<Episode>> future : executor.invokeAll(fetchTasks)) {
episodeList.addAll(future.get());
}
executor.shutdown();
List<Match<FileEntry, Episode>> matches = new ArrayList<Match<FileEntry, Episode>>();
for (List<FileEntry> entryList : splitByFileType(files)) {
Matcher<FileEntry, Episode> matcher = new Matcher<FileEntry, Episode>(entryList, episodeList, metrics);
matches.addAll(matcher.match());
remainingFiles.addAll(matcher.remainingValues());
}
return matches;
}
@SuppressWarnings("unchecked")
protected Collection<List<FileEntry>> splitByFileType(Collection<FileEntry> files) {
List<FileEntry> subtitles = new ArrayList<FileEntry>();
List<FileEntry> other = new ArrayList<FileEntry>();
for (FileEntry file : files) {
// check for for subtitles first, then files in general
if (SUBTITLE_FILES.accept(file.getFile())) {
subtitles.add(file);
} else if (FILES.accept(file.getFile())) {
other.add(file);
}
}
return Arrays.asList(other, subtitles);
}
}

View File

@ -4,7 +4,7 @@ package net.sourceforge.filebot.ui.panel.rename;
import java.io.File;
import net.sourceforge.tuned.FileUtil;
import net.sourceforge.tuned.FileUtilities;
class FileEntry extends AbstractFileEntry {
@ -14,10 +14,10 @@ class FileEntry extends AbstractFileEntry {
public FileEntry(File file) {
super(FileUtil.getFileName(file), file.length());
super(FileUtilities.getName(file), file.length());
this.file = file;
this.type = FileUtil.getFileType(file);
this.type = FileUtilities.getType(file);
}

View File

@ -2,7 +2,8 @@
package net.sourceforge.filebot.ui.panel.rename;
import static net.sourceforge.filebot.FileBotUtil.containsOnlyFolders;
import static net.sourceforge.tuned.FileUtilities.FOLDERS;
import static net.sourceforge.tuned.FileUtilities.containsOnly;
import java.io.File;
import java.util.Arrays;
@ -35,7 +36,7 @@ class FilesListTransferablePolicy extends FileTransferablePolicy {
@Override
protected void load(List<File> files) {
if (containsOnlyFolders(files)) {
if (containsOnly(files, FOLDERS)) {
for (File folder : files) {
loadFiles(Arrays.asList(folder.listFiles()));
}

View File

@ -20,7 +20,7 @@ import javax.swing.text.Highlighter;
import javax.swing.text.JTextComponent;
import net.sourceforge.tuned.ui.AbstractFancyListCellRenderer;
import net.sourceforge.tuned.ui.TunedUtil;
import net.sourceforge.tuned.ui.TunedUtilities;
class HighlightListCellRenderer extends AbstractFancyListCellRenderer {
@ -42,7 +42,7 @@ class HighlightListCellRenderer extends AbstractFancyListCellRenderer {
textComponent.setBorder(new EmptyBorder(padding, padding, padding, padding));
// make text component transparent, should work for all LAFs (setOpaque(false) may not, e.g. Nimbus)
textComponent.setBackground(TunedUtil.TRANSLUCENT);
textComponent.setBackground(TunedUtilities.TRANSLUCENT);
this.add(textComponent, BorderLayout.WEST);

View File

@ -7,6 +7,8 @@ import java.awt.Window;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@ -26,6 +28,8 @@ import net.sourceforge.filebot.similarity.Matcher;
import net.sourceforge.filebot.similarity.NameSimilarityMetric;
import net.sourceforge.filebot.similarity.SeasonEpisodeSimilarityMetric;
import net.sourceforge.filebot.similarity.SimilarityMetric;
import net.sourceforge.filebot.similarity.SeasonEpisodeMatcher.SxE;
import net.sourceforge.filebot.web.Episode;
import net.sourceforge.tuned.ui.ProgressDialog;
import net.sourceforge.tuned.ui.SwingWorkerPropertyChangeAdapter;
import net.sourceforge.tuned.ui.ProgressDialog.Cancellable;
@ -35,17 +39,21 @@ class MatchAction extends AbstractAction {
private final RenameModel model;
private final SimilarityMetric[] metrics;
private final Collection<SimilarityMetric> metrics;
public MatchAction(RenameModel model) {
super("Match", ResourceManager.getIcon("action.match"));
putValue(SHORT_DESCRIPTION, "Match names to files");
this.model = model;
this.metrics = createMetrics();
metrics = new SimilarityMetric[3];
putValue(SHORT_DESCRIPTION, "Match names to files");
}
protected Collection<SimilarityMetric> createMetrics() {
SimilarityMetric[] metrics = new SimilarityMetric[3];
// 1. pass: match by file length (fast, but only works when matching torrents or files)
metrics[0] = new LengthEqualsMetric() {
@ -61,19 +69,48 @@ class MatchAction extends AbstractAction {
};
// 2. pass: match by season / episode numbers, or generic numeric similarity
metrics[1] = new SeasonEpisodeSimilarityMetric();
metrics[1] = new SeasonEpisodeSimilarityMetric() {
@Override
protected Collection<SxE> parse(Object o) {
if (o instanceof Episode) {
Episode episode = (Episode) o;
try {
// create SxE from episode
return Collections.singleton(new SxE(episode.getSeasonNumber(), episode.getEpisodeNumber()));
} catch (NumberFormatException e) {
// some kind of special episode, no SxE
return null;
}
}
return super.parse(o);
}
};
// 3. pass: match by generic name similarity (slow, but most matches will have been determined in second pass)
metrics[2] = new NameSimilarityMetric();
return Arrays.asList(metrics);
}
public Collection<SimilarityMetric> getMetrics() {
return Collections.unmodifiableCollection(metrics);
}
public void actionPerformed(ActionEvent evt) {
if (model.names().isEmpty() || model.files().isEmpty()) {
return;
}
JComponent eventSource = (JComponent) evt.getSource();
SwingUtilities.getRoot(eventSource).setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
BackgroundMatcher backgroundMatcher = new BackgroundMatcher(model, Arrays.asList(metrics));
BackgroundMatcher backgroundMatcher = new BackgroundMatcher(model, metrics);
backgroundMatcher.execute();
try {
@ -114,16 +151,12 @@ class MatchAction extends AbstractAction {
}
protected static class BackgroundMatcher extends SwingWorker<List<Match<Object, FileEntry>>, Void> implements Cancellable {
private final RenameModel model;
protected class BackgroundMatcher extends SwingWorker<List<Match<Object, FileEntry>>, Void> implements Cancellable {
private final Matcher<Object, FileEntry> matcher;
public BackgroundMatcher(RenameModel model, List<SimilarityMetric> metrics) {
this.model = model;
public BackgroundMatcher(RenameModel model, Collection<SimilarityMetric> metrics) {
// match names against files
this.matcher = new Matcher<Object, FileEntry>(model.names(), model.files(), metrics);
}
@ -141,8 +174,15 @@ class MatchAction extends AbstractAction {
return;
try {
List<Match<Object, FileEntry>> matches = get();
model.clear();
// put new data into model
model.setData(get());
for (Match<Object, FileEntry> match : matches) {
model.names().add(match.getValue());
model.files().add(match.getCandidate());
}
// insert objects that could not be matched at the end
model.names().addAll(matcher.remainingValues());

View File

@ -3,12 +3,12 @@ package net.sourceforge.filebot.ui.panel.rename;
import static java.awt.datatransfer.DataFlavor.stringFlavor;
import static net.sourceforge.filebot.FileBotUtil.LIST_FILE_EXTENSIONS;
import static net.sourceforge.filebot.FileBotUtil.TORRENT_FILE_EXTENSIONS;
import static net.sourceforge.filebot.FileBotUtil.containsOnly;
import static net.sourceforge.filebot.FileBotUtil.containsOnlyFolders;
import static net.sourceforge.filebot.FileBotUtil.isInvalidFileName;
import static net.sourceforge.tuned.FileUtil.getNameWithoutExtension;
import static net.sourceforge.filebot.FileBotUtilities.LIST_FILES;
import static net.sourceforge.filebot.FileBotUtilities.TORRENT_FILES;
import static net.sourceforge.filebot.FileBotUtilities.isInvalidFileName;
import static net.sourceforge.tuned.FileUtilities.FOLDERS;
import static net.sourceforge.tuned.FileUtilities.containsOnly;
import static net.sourceforge.tuned.FileUtilities.getNameWithoutExtension;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
@ -25,7 +25,7 @@ import javax.swing.SwingUtilities;
import net.sourceforge.filebot.torrent.Torrent;
import net.sourceforge.filebot.ui.transfer.FileTransferablePolicy;
import net.sourceforge.tuned.FileUtil;
import net.sourceforge.tuned.FileUtilities;
class NamesListTransferablePolicy extends FileTransferablePolicy {
@ -121,11 +121,11 @@ class NamesListTransferablePolicy extends FileTransferablePolicy {
@Override
protected void load(List<File> files) {
if (containsOnly(files, LIST_FILE_EXTENSIONS)) {
if (containsOnly(files, LIST_FILES)) {
loadListFiles(files);
} else if (containsOnly(files, TORRENT_FILE_EXTENSIONS)) {
} else if (containsOnly(files, TORRENT_FILES)) {
loadTorrentFiles(files);
} else if (containsOnlyFolders(files)) {
} else if (containsOnly(files, FOLDERS)) {
// load files from each folder
for (File folder : files) {
loadFiles(Arrays.asList(folder.listFiles()));
@ -138,7 +138,7 @@ class NamesListTransferablePolicy extends FileTransferablePolicy {
protected void loadFiles(List<File> files) {
for (File file : files) {
list.getModel().add(new AbstractFileEntry(FileUtil.getFileName(file), file.length()));
list.getModel().add(new AbstractFileEntry(FileUtilities.getName(file), file.length()));
}
}

View File

@ -13,7 +13,7 @@ import javax.swing.AbstractAction;
import net.sourceforge.filebot.ResourceManager;
import net.sourceforge.filebot.similarity.Match;
import net.sourceforge.tuned.FileUtil;
import net.sourceforge.tuned.FileUtilities;
class RenameAction extends AbstractAction {
@ -38,8 +38,16 @@ class RenameAction extends AbstractAction {
for (Match<Object, FileEntry> match : model.matches()) {
File source = match.getCandidate().getFile();
String newName = match.getValue().toString() + FileUtil.getExtension(source, true);
File target = new File(source.getParentFile(), newName);
String extension = FileUtilities.getExtension(source);
StringBuilder nameBuilder = new StringBuilder();
nameBuilder.append(match.getValue());
if (extension != null) {
nameBuilder.append(".").append(extension);
}
File target = new File(source.getParentFile(), nameBuilder.toString());
todoQueue.addLast(new Match<File, File>(source, target));
}

View File

@ -28,6 +28,7 @@ class RenameList<E> extends FileBotList<E> {
setModel(model);
list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
list.setFixedCellHeight(28);
list.addMouseListener(dndReorderMouseAdapter);
list.addMouseMotionListener(dndReorderMouseAdapter);

View File

@ -8,12 +8,13 @@ import java.util.Collection;
import net.sourceforge.filebot.similarity.Match;
import ca.odell.glazedlists.BasicEventList;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.GlazedLists;
class RenameModel {
private final EventList<Object> names = new BasicEventList<Object>();
private final EventList<FileEntry> files = new BasicEventList<FileEntry>();
private final EventList<Object> names = GlazedLists.threadSafeList(new BasicEventList<Object>());
private final EventList<FileEntry> files = GlazedLists.threadSafeList(new BasicEventList<FileEntry>());
public EventList<Object> names() {
@ -32,18 +33,6 @@ class RenameModel {
}
public void setData(Collection<Match<Object, FileEntry>> matches) {
// clear names and files
clear();
// add all matches
for (Match<Object, FileEntry> match : matches) {
names.add(match.getValue());
files.add(match.getCandidate());
}
}
public int matchCount() {
return Math.min(names.size(), files.size());
}

View File

@ -2,16 +2,37 @@
package net.sourceforge.filebot.ui.panel.rename;
import static javax.swing.SwingUtilities.getWindowAncestor;
import static net.sourceforge.tuned.ui.LoadingOverlayPane.LOADING_PROPERTY;
import static net.sourceforge.filebot.FileBotUtilities.*;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.DefaultListSelectionModel;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.ListSelectionModel;
import javax.swing.SwingConstants;
import net.miginfocom.swing.MigLayout;
import net.sourceforge.filebot.ResourceManager;
import net.sourceforge.filebot.Settings;
import net.sourceforge.filebot.similarity.Match;
import net.sourceforge.filebot.ui.FileBotPanel;
import net.sourceforge.filebot.web.AnidbClient;
import net.sourceforge.filebot.web.Episode;
import net.sourceforge.filebot.web.EpisodeListClient;
import net.sourceforge.filebot.web.TVRageClient;
import net.sourceforge.filebot.web.TheTVDBClient;
import net.sourceforge.tuned.ExceptionUtil;
import net.sourceforge.tuned.ui.ActionPopup;
import net.sourceforge.tuned.ui.LoadingOverlayPane;
import ca.odell.glazedlists.event.ListEvent;
import ca.odell.glazedlists.event.ListEventListener;
@ -28,6 +49,8 @@ public class RenamePanel extends FileBotPanel {
private RenameAction renameAction = new RenameAction(model);
private ActionPopup matchActionPopup = new ActionPopup("Fetch Episode List", ResourceManager.getIcon("action.match.small"));
public RenamePanel() {
super("Rename", ResourceManager.getIcon("panel.rename"));
@ -63,9 +86,18 @@ public class RenamePanel extends FileBotPanel {
renameButton.setVerticalTextPosition(SwingConstants.BOTTOM);
renameButton.setHorizontalTextPosition(SwingConstants.CENTER);
// create actions for match popup
matchActionPopup.add(new AutoFetchEpisodeListAction(new TVRageClient()));
matchActionPopup.add(new AutoFetchEpisodeListAction(new AnidbClient()));
matchActionPopup.add(new AutoFetchEpisodeListAction(new TheTVDBClient(Settings.userRoot().get("thetvdb.apikey"))));
// set match action popup
matchButton.setComponentPopupMenu(matchActionPopup);
matchButton.addActionListener(showPopupAction);
setLayout(new MigLayout("fill, insets dialog, gapx 10px", null, "align 33%"));
add(namesList, "grow");
add(new LoadingOverlayPane(namesList, this, "28px", "30px"), "grow, sizegroupx list");
// make buttons larger
matchButton.setMargin(new Insets(3, 14, 2, 14));
@ -74,15 +106,115 @@ public class RenamePanel extends FileBotPanel {
add(matchButton, "split 2, flowy, sizegroupx button");
add(renameButton, "gapy 30px, sizegroupx button");
add(filesList, "grow");
add(filesList, "grow, sizegroupx list");
// repaint on change
model.names().addListEventListener(new RepaintHandler<Object>());
model.files().addListEventListener(new RepaintHandler<FileEntry>());
}
protected final Action showPopupAction = new AbstractAction("Show Popup") {
private class RepaintHandler<E> implements ListEventListener<E> {
@Override
public void actionPerformed(ActionEvent e) {
// show popup on actionPerformed only when names list is empty
if (model.names().isEmpty()) {
JComponent source = (JComponent) e.getSource();
// display popup below component
source.getComponentPopupMenu().show(source, -3, source.getHeight() + 4);
}
}
};
private boolean autoMatchInProgress = false;
protected void setAutoMatchInProgress(boolean flag) {
this.autoMatchInProgress = flag;
firePropertyChange(LOADING_PROPERTY, !flag, flag);
matchActionPopup.setStatus(flag ? "in progress" : null);
}
protected boolean isAutoMatchInProgress() {
return autoMatchInProgress;
}
protected class AutoFetchEpisodeListAction extends AbstractAction {
private final EpisodeListClient client;
public AutoFetchEpisodeListAction(EpisodeListClient client) {
super(client.getName(), client.getIcon());
this.client = client;
}
@Override
public void actionPerformed(ActionEvent evt) {
if (model.files().isEmpty() || isAutoMatchInProgress())
return;
AutoEpisodeListMatcher worker = new AutoEpisodeListMatcher(client, new ArrayList<FileEntry>(model.files()), matchAction.getMetrics()) {
@Override
protected void done() {
// background worker is finished
setAutoMatchInProgress(false);
try {
List<StringEntry> names = new ArrayList<StringEntry>();
List<FileEntry> files = new ArrayList<FileEntry>();
List<StringEntry> invalidNames = new ArrayList<StringEntry>();
for (Match<FileEntry, Episode> match : get()) {
StringEntry name = new StringEntry(match.getCandidate());
if (isInvalidFileName(name.toString())) {
invalidNames.add(name);
}
names.add(new StringEntry(name));
files.add(match.getValue());
}
if (!invalidNames.isEmpty()) {
ValidateNamesDialog dialog = new ValidateNamesDialog(getWindowAncestor(RenamePanel.this), invalidNames);
dialog.setVisible(true);
if (dialog.isCancelled()) {
// don't touch model
return;
}
}
model.clear();
model.names().addAll(names);
model.files().addAll(files);
// add remaining file entries again
model.files().addAll(remainingFiles());
} catch (Exception e) {
Logger.getLogger("ui").log(Level.WARNING, ExceptionUtil.getRootCause(e).getMessage(), e);
}
}
};
worker.execute();
// background worker started
setAutoMatchInProgress(true);
}
}
protected class RepaintHandler<E> implements ListEventListener<E> {
@Override
public void listChanged(ListEvent<E> listChanges) {

View File

@ -7,8 +7,8 @@ class StringEntry {
private String value;
public StringEntry(String value) {
this.value = value;
public StringEntry(Object value) {
setValue(value);
}
@ -17,8 +17,8 @@ class StringEntry {
}
public void setValue(String value) {
this.value = value;
public void setValue(Object value) {
this.value = String.valueOf(value);
}

View File

@ -2,8 +2,8 @@
package net.sourceforge.filebot.ui.panel.rename;
import static net.sourceforge.filebot.FileBotUtil.INVALID_CHARACTERS_PATTERN;
import static net.sourceforge.filebot.FileBotUtil.validateFileName;
import static net.sourceforge.filebot.FileBotUtilities.INVALID_CHARACTERS_PATTERN;
import static net.sourceforge.filebot.FileBotUtilities.validateFileName;
import java.awt.AlphaComposite;
import java.awt.Color;
@ -26,7 +26,7 @@ import javax.swing.KeyStroke;
import net.miginfocom.swing.MigLayout;
import net.sourceforge.filebot.ResourceManager;
import net.sourceforge.tuned.ui.ArrayListModel;
import net.sourceforge.tuned.ui.TunedUtil;
import net.sourceforge.tuned.ui.TunedUtilities;
class ValidateNamesDialog extends JDialog {
@ -67,7 +67,7 @@ class ValidateNamesDialog extends JDialog {
setSize(365, 280);
TunedUtil.putActionForKeystroke(c, KeyStroke.getKeyStroke("released ESCAPE"), cancelAction);
TunedUtilities.putActionForKeystroke(c, KeyStroke.getKeyStroke("released ESCAPE"), cancelAction);
}

View File

@ -7,7 +7,7 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import net.sourceforge.filebot.FileBotUtil;
import net.sourceforge.filebot.FileBotUtilities;
public class ChecksumRow {
@ -50,7 +50,7 @@ public class ChecksumRow {
*/
private static Long getEmbeddedChecksum(String name) {
// look for a checksum pattern like [49A93C5F]
String match = FileBotUtil.getEmbeddedChecksum(name);
String match = FileBotUtilities.getEmbeddedChecksum(name);
if (match != null)
return Long.parseLong(match, 16);

View File

@ -11,7 +11,7 @@ import java.util.Map;
import java.util.Map.Entry;
import net.sourceforge.filebot.ui.transfer.TextFileExportHandler;
import net.sourceforge.tuned.FileUtil;
import net.sourceforge.tuned.FileUtilities;
public class ChecksumTableExportHandler extends TextFileExportHandler {
@ -75,7 +75,7 @@ public class ChecksumTableExportHandler extends TextFileExportHandler {
String name = "";
if (column != null)
name = FileUtil.getFileName(column);
name = FileUtilities.getName(column);
if (name.isEmpty())
name = "name";

View File

@ -16,7 +16,7 @@ import javax.swing.event.TableModelEvent;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableModel;
import net.sourceforge.tuned.FileUtil;
import net.sourceforge.tuned.FileUtilities;
class ChecksumTableModel extends AbstractTableModel {
@ -48,7 +48,7 @@ class ChecksumTableModel extends AbstractTableModel {
File column = columns.get(columnIndex - checksumColumnOffset);
// works for files too and simply returns the name unchanged
return FileUtil.getFolderName(column);
return FileUtilities.getFolderName(column);
}
return null;

View File

@ -22,9 +22,9 @@ import net.sourceforge.filebot.ui.FileTransferableMessageHandler;
import net.sourceforge.filebot.ui.SelectDialog;
import net.sourceforge.filebot.ui.transfer.LoadAction;
import net.sourceforge.filebot.ui.transfer.SaveAction;
import net.sourceforge.tuned.FileUtil;
import net.sourceforge.tuned.FileUtilities;
import net.sourceforge.tuned.MessageHandler;
import net.sourceforge.tuned.ui.TunedUtil;
import net.sourceforge.tuned.ui.TunedUtilities;
public class SfvPanel extends FileBotPanel {
@ -54,7 +54,7 @@ public class SfvPanel extends FileBotPanel {
contentPane.add(totalProgressPanel, "gap left indent:push, gap bottom 2px, gap right 7px, hidemode 3");
// Shortcut DELETE
TunedUtil.putActionForKeystroke(this, KeyStroke.getKeyStroke("pressed DELETE"), removeAction);
TunedUtilities.putActionForKeystroke(this, KeyStroke.getKeyStroke("pressed DELETE"), removeAction);
}
@ -150,7 +150,7 @@ public class SfvPanel extends FileBotPanel {
@Override
protected String convertValueToString(Object value) {
return FileUtil.getFolderName((File) value);
return FileUtilities.getFolderName((File) value);
}
};

View File

@ -13,7 +13,7 @@ import javax.swing.plaf.basic.BasicTableUI;
import javax.swing.table.TableColumn;
import javax.swing.table.TableModel;
import net.sourceforge.filebot.FileBotUtil;
import net.sourceforge.filebot.FileBotUtilities;
import net.sourceforge.filebot.ui.panel.sfv.ChecksumTableModel.ChecksumTableModelEvent;
import net.sourceforge.filebot.ui.transfer.DefaultTransferHandler;
@ -46,7 +46,7 @@ class SfvTable extends JTable {
setUI(new DragDropRowTableUI());
// highlight CRC32 patterns in filenames in green and with smaller font-size
setDefaultRenderer(String.class, new HighlightPatternCellRenderer(FileBotUtil.EMBEDDED_CHECKSUM_PATTERN, "#009900", "smaller"));
setDefaultRenderer(String.class, new HighlightPatternCellRenderer(FileBotUtilities.EMBEDDED_CHECKSUM_PATTERN, "#009900", "smaller"));
setDefaultRenderer(ChecksumRow.State.class, new StateIconTableCellRenderer());
setDefaultRenderer(Checksum.class, new ChecksumTableCellRenderer());
}

View File

@ -2,8 +2,8 @@
package net.sourceforge.filebot.ui.panel.sfv;
import static net.sourceforge.filebot.FileBotUtil.SFV_FILE_EXTENSIONS;
import static net.sourceforge.filebot.FileBotUtil.containsOnly;
import static net.sourceforge.filebot.FileBotUtilities.SFV_FILES;
import static net.sourceforge.tuned.FileUtilities.containsOnly;
import java.io.BufferedReader;
import java.io.File;
@ -96,7 +96,7 @@ class SfvTransferablePolicy extends BackgroundFileTransferablePolicy<ChecksumTab
@Override
protected void load(List<File> files) {
try {
if (containsOnly(files, SFV_FILE_EXTENSIONS)) {
if (containsOnly(files, SFV_FILES)) {
// one or more sfv files
for (File file : files) {
loadSfvFile(file);

View File

@ -10,7 +10,7 @@ import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JProgressBar;
import net.sourceforge.tuned.ui.TunedUtil;
import net.sourceforge.tuned.ui.TunedUtilities;
class TotalProgressPanel extends Box {
@ -51,7 +51,7 @@ class TotalProgressPanel extends Box {
Boolean active = (Boolean) evt.getNewValue();
if (active) {
TunedUtil.invokeLater(millisToSetVisible, new Runnable() {
TunedUtilities.invokeLater(millisToSetVisible, new Runnable() {
@Override
public void run() {

View File

@ -1,180 +0,0 @@
package net.sourceforge.filebot.ui.panel.subtitle;
import java.awt.AlphaComposite;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.Map;
import java.util.TreeMap;
import javax.swing.JPanel;
import javax.swing.JToggleButton;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.FunctionList;
import ca.odell.glazedlists.ListSelection;
import ca.odell.glazedlists.UniqueList;
import ca.odell.glazedlists.FunctionList.Function;
import ca.odell.glazedlists.event.ListEvent;
import ca.odell.glazedlists.event.ListEventListener;
public class LanguageSelectionPanel extends JPanel {
private final ListSelection<Language> selectionModel;
private final Map<String, Boolean> defaultSelection = new TreeMap<String, Boolean>(String.CASE_INSENSITIVE_ORDER);
// private final Map<String, Boolean> globalSelection = Settings.getSettings().asBooleanMap(Settings.SUBTITLE_LANGUAGE);
public LanguageSelectionPanel(EventList<SubtitlePackage> source) {
super(new FlowLayout(FlowLayout.RIGHT, 5, 1));
// defaultSelection.putAll(globalSelection);
EventList<Language> languageList = new FunctionList<SubtitlePackage, Language>(source, new LanguageFunction());
EventList<Language> languageSet = new UniqueList<Language>(languageList);
selectionModel = new ListSelection<Language>(languageSet);
selectionModel.getSource().addListEventListener(new SourceChangeHandler());
}
public EventList<Language> getSelected() {
return selectionModel.getSelected();
}
private boolean isSelectedByDefault(Language language) {
Boolean selected = defaultSelection.get(language.getName());
if (selected != null)
return selected;
// deselected by default
return false;
}
private void setSelected(Language language, boolean selected) {
String key = language.getName();
defaultSelection.put(key, selected);
// globalSelection.put(key, selected);
if (selected)
selectionModel.select(language);
else
selectionModel.deselect(selectionModel.getSource().indexOf(language));
}
/**
* Provide the binding between this panel and the source {@link EventList}.
*/
private class SourceChangeHandler implements ListEventListener<Language> {
/**
* Handle an inserted element.
*/
private void insert(int index) {
Language language = selectionModel.getSource().get(index);
LanguageToggleButton button = new LanguageToggleButton(language);
button.setSelected(isSelectedByDefault(language));
add(button, index);
}
/**
* Handle a deleted element.
*/
private void delete(int index) {
remove(index);
}
/**
* When the components list changes, this updates the panel.
*/
public void listChanged(ListEvent<Language> listChanges) {
while (listChanges.next()) {
int type = listChanges.getType();
int index = listChanges.getIndex();
if (type == ListEvent.INSERT) {
insert(index);
} else if (type == ListEvent.DELETE) {
delete(index);
}
}
// repaint the panel
revalidate();
repaint();
}
}
private class LanguageToggleButton extends JToggleButton implements ItemListener {
private final Language language;
public LanguageToggleButton(Language language) {
super(language.getIcon());
this.language = language;
setToolTipText(language.getName());
setContentAreaFilled(false);
setFocusPainted(false);
setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
setPreferredSize(new Dimension(getIcon().getIconWidth(), getIcon().getIconHeight()));
addItemListener(this);
}
@Override
protected void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
// make transparent if not selected
if (!isSelected()) {
AlphaComposite composite = AlphaComposite.SrcOver.derive(0.2f);
g2d.setComposite(composite);
}
super.paintComponent(g2d);
}
@Override
public void itemStateChanged(ItemEvent e) {
LanguageSelectionPanel.this.setSelected(language, isSelected());
}
}
private class LanguageFunction implements Function<SubtitlePackage, Language> {
@Override
public Language evaluate(SubtitlePackage sourceValue) {
return sourceValue.getLanguage();
}
}
}

View File

@ -14,7 +14,7 @@ import javax.swing.SwingConstants;
import net.sourceforge.tuned.ui.ColorTintImageFilter;
import net.sourceforge.tuned.ui.IconViewCellRenderer;
import net.sourceforge.tuned.ui.TunedUtil;
import net.sourceforge.tuned.ui.TunedUtilities;
public class SubtitleCellRenderer extends IconViewCellRenderer {
@ -56,7 +56,7 @@ public class SubtitleCellRenderer extends IconViewCellRenderer {
Icon icon = subtitle.getArchiveIcon();
if (isSelected) {
setIcon(new ImageIcon(createImage(new FilteredImageSource(TunedUtil.getImage(icon).getSource(), new ColorTintImageFilter(list.getSelectionBackground(), 0.5f)))));
setIcon(new ImageIcon(createImage(new FilteredImageSource(TunedUtilities.getImage(icon).getSource(), new ColorTintImageFilter(list.getSelectionBackground(), 0.5f)))));
info1.setForeground(list.getSelectionForeground());
info2.setForeground(list.getSelectionForeground());

View File

@ -4,10 +4,10 @@ package net.sourceforge.filebot.ui.panel.subtitle;
import java.awt.BorderLayout;
import javax.swing.JPanel;
import javax.swing.JComponent;
public class SubtitleDownloadPanel extends JPanel {
public class SubtitleDownloadPanel extends JComponent {
private final SubtitlePackagePanel packagePanel = new SubtitlePackagePanel();

View File

@ -3,20 +3,22 @@ package net.sourceforge.filebot.ui.panel.subtitle;
import java.awt.BorderLayout;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.EventListener;
import javax.swing.JComponent;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import ca.odell.glazedlists.BasicEventList;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.FilterList;
import ca.odell.glazedlists.GlazedLists;
import ca.odell.glazedlists.ObservableElementList;
import ca.odell.glazedlists.swing.EventListModel;
public class SubtitlePackagePanel extends JPanel {
public class SubtitlePackagePanel extends JComponent {
private final EventList<SubtitlePackage> model = new BasicEventList<SubtitlePackage>();
@ -24,7 +26,7 @@ public class SubtitlePackagePanel extends JPanel {
public SubtitlePackagePanel() {
super(new BorderLayout());
setLayout(new BorderLayout());
add(languageSelection, BorderLayout.NORTH);
add(new JScrollPane(createList()), BorderLayout.CENTER);
}
@ -37,13 +39,58 @@ public class SubtitlePackagePanel extends JPanel {
protected JList createList() {
FilterList<SubtitlePackage> filterList = new FilterList<SubtitlePackage>(model, new LanguageMatcherEditor(languageSelection));
ObservableElementList<SubtitlePackage> observableList = new ObservableElementList<SubtitlePackage>(filterList, GlazedLists.beanConnector(SubtitlePackage.class));
ObservableElementList<SubtitlePackage> observableList = new ObservableElementList<SubtitlePackage>(filterList, new SubtitlePackageConnector());
JList list = new JList(new EventListModel<SubtitlePackage>(observableList));
return list;
}
private static class SubtitlePackageConnector implements ObservableElementList.Connector<SubtitlePackage> {
/**
* The list which contains the elements being observed via this
* {@link ObservableElementList.Connector}.
*/
private ObservableElementList<SubtitlePackage> list = null;
public EventListener installListener(SubtitlePackage element) {
PropertyChangeListener listener = new SubtitlePackageListener(element);
element.getDownloadTask().addPropertyChangeListener(listener);
return listener;
}
public void uninstallListener(SubtitlePackage element, EventListener listener) {
element.getDownloadTask().removePropertyChangeListener((PropertyChangeListener) listener);
}
public void setObservableElementList(ObservableElementList<SubtitlePackage> list) {
this.list = list;
}
private class SubtitlePackageListener implements PropertyChangeListener {
private final SubtitlePackage subtitlePackage;
public SubtitlePackageListener(SubtitlePackage subtitlePackage) {
this.subtitlePackage = subtitlePackage;
}
public void propertyChange(PropertyChangeEvent evt) {
list.elementChanged(subtitlePackage);
}
}
}
/*
private void updateLanguageFilterButtonPanel() {

View File

@ -2,17 +2,17 @@
package net.sourceforge.filebot.ui.panel.subtitle;
import static net.sourceforge.filebot.FileBotUtil.getApplicationName;
import static net.sourceforge.filebot.FileBotUtil.getApplicationVersion;
import static net.sourceforge.filebot.Settings.getApplicationName;
import static net.sourceforge.filebot.Settings.getApplicationVersion;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.prefs.Preferences;
import net.sourceforge.filebot.ResourceManager;
import net.sourceforge.filebot.Settings;
import net.sourceforge.filebot.ui.AbstractSearchPanel;
import net.sourceforge.filebot.ui.SelectDialog;
import net.sourceforge.filebot.web.OpenSubtitlesSubtitleClient;
@ -21,7 +21,6 @@ import net.sourceforge.filebot.web.SubsceneSubtitleClient;
import net.sourceforge.filebot.web.SubtitleClient;
import net.sourceforge.filebot.web.SubtitleDescriptor;
import net.sourceforge.tuned.ListChangeSynchronizer;
import net.sourceforge.tuned.PreferencesList;
import net.sourceforge.tuned.ui.LabelProvider;
import net.sourceforge.tuned.ui.SimpleLabelProvider;
@ -35,10 +34,8 @@ public class SubtitlePanel extends AbstractSearchPanel<SubtitleClient, SubtitleP
historyPanel.setColumnHeader(1, "Number of Subtitles");
// get preferences node that contains the history entries
Preferences historyNode = Preferences.systemNodeForPackage(getClass()).node("history");
// get a StringList that read and writes directly from and to the preferences
List<String> persistentSearchHistory = PreferencesList.map(historyNode, String.class);
// and get a StringList that read and writes directly from and to the preferences
List<String> persistentSearchHistory = Settings.userRoot().node("subtitles/history").asList(String.class);
// add history from the preferences to the current in-memory history (for completion)
getSearchHistory().addAll(persistentSearchHistory);

View File

@ -125,12 +125,12 @@ public abstract class BackgroundFileTransferablePolicy<V> extends FileTransferab
private final PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
propertyChangeSupport.addPropertyChangeListener(propertyName, listener);
public void addPropertyChangeListener(PropertyChangeListener listener) {
propertyChangeSupport.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
propertyChangeSupport.removePropertyChangeListener(propertyName, listener);
public void removePropertyChangeListener(PropertyChangeListener listener) {
propertyChangeSupport.removePropertyChangeListener(listener);
}
}

View File

@ -10,6 +10,7 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Scanner;
import java.util.logging.Level;
@ -29,7 +30,7 @@ public abstract class FileTransferablePolicy extends TransferablePolicy {
public boolean accept(Transferable tr) {
List<File> files = getFilesFromTransferable(tr);
if (files == null || files.isEmpty())
if (files.isEmpty())
return false;
return accept(files);
@ -81,7 +82,7 @@ public abstract class FileTransferablePolicy extends TransferablePolicy {
throw new RuntimeException(e);
}
return null;
return Collections.emptyList();
}

View File

@ -2,8 +2,8 @@
package net.sourceforge.filebot.ui.transfer;
import static net.sourceforge.filebot.FileBotUtil.getApplicationName;
import static net.sourceforge.filebot.FileBotUtil.validateFileName;
import static net.sourceforge.filebot.FileBotUtilities.validateFileName;
import static net.sourceforge.filebot.Settings.getApplicationName;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;

View File

@ -12,7 +12,7 @@ import javax.swing.AbstractAction;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import net.sourceforge.filebot.FileBotUtil;
import net.sourceforge.filebot.FileBotUtilities;
import net.sourceforge.filebot.ResourceManager;
@ -60,7 +60,7 @@ public class SaveAction extends AbstractAction {
chooser.setMultiSelectionEnabled(false);
chooser.setSelectedFile(new File(getDefaultFolder(), FileBotUtil.validateFileName(getDefaultFileName())));
chooser.setSelectedFile(new File(getDefaultFolder(), FileBotUtilities.validateFileName(getDefaultFileName())));
if (chooser.showSaveDialog((JComponent) evt.getSource()) != JFileChooser.APPROVE_OPTION)
return;

View File

@ -7,22 +7,22 @@ import java.io.Serializable;
public class Episode implements Serializable {
private String showName;
private String seriesName;
private String seasonNumber;
private String episodeNumber;
private String title;
public Episode(String showName, String seasonNumber, String episodeNumber, String title) {
this.showName = showName;
public Episode(String seriesName, String seasonNumber, String episodeNumber, String title) {
this.seriesName = seriesName;
this.seasonNumber = seasonNumber;
this.episodeNumber = episodeNumber;
this.title = title;
}
public Episode(String showName, String episodeNumber, String title) {
this(showName, null, episodeNumber, title);
public Episode(String seriesName, String episodeNumber, String title) {
this(seriesName, null, episodeNumber, title);
}
@ -36,8 +36,8 @@ public class Episode implements Serializable {
}
public String getShowName() {
return showName;
public String getSeriesName() {
return seriesName;
}
@ -46,8 +46,8 @@ public class Episode implements Serializable {
}
public void setShowName(String seriesName) {
this.showName = seriesName;
public void setSeriesName(String seriesName) {
this.seriesName = seriesName;
}
@ -70,17 +70,43 @@ public class Episode implements Serializable {
public String toString() {
StringBuilder sb = new StringBuilder(40);
sb.append(showName);
sb.append(" - ");
sb.append(seriesName).append(" - ");
if (seasonNumber != null)
sb.append(seasonNumber + "x");
if (seasonNumber != null) {
sb.append(seasonNumber).append("x");
}
sb.append(episodeNumber);
sb.append(" - ");
sb.append(title);
sb.append(episodeNumber).append(" - ").append(title);
return sb.toString();
}
public static <T extends Iterable<Episode>> T formatEpisodeNumbers(T episodes, int minDigits) {
// find max. episode number length
for (Episode episode : episodes) {
try {
int n = Integer.parseInt(episode.getEpisodeNumber());
if (n > 0) {
minDigits = Math.max(minDigits, (int) (Math.log(n) / Math.log(10)));
}
} catch (NumberFormatException e) {
// ignore
}
}
// pad episode numbers with zeros (e.g. %02d) so all episode numbers have the same number of digits
String numberFormat = "%0" + minDigits + "d";
for (Episode episode : episodes) {
try {
episode.setEpisodeNumber(String.format(numberFormat, Integer.parseInt(episode.getEpisodeNumber())));
} catch (NumberFormatException e) {
// ignore
}
}
return episodes;
}
}

View File

@ -12,6 +12,7 @@ import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.sourceforge.filebot.web.OpenSubtitlesSubtitleDescriptor.Property;
import redstone.xmlrpc.XmlRpcClient;
import redstone.xmlrpc.XmlRpcException;
import redstone.xmlrpc.XmlRpcFault;
@ -156,8 +157,8 @@ public class OpenSubtitlesClient {
List<OpenSubtitlesSubtitleDescriptor> subs = new ArrayList<OpenSubtitlesSubtitleDescriptor>();
try {
for (Map<String, String> subtitle : response.get("data")) {
subs.add(new OpenSubtitlesSubtitleDescriptor(subtitle));
for (Map<String, String> subtitleData : response.get("data")) {
subs.add(new OpenSubtitlesSubtitleDescriptor(Property.asEnumMap(subtitleData)));
}
} catch (ClassCastException e) {
// if the response is an error message, generic types won't match

View File

@ -4,8 +4,10 @@ package net.sourceforge.filebot.web;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Collections;
import java.util.EnumMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
@ -19,10 +21,10 @@ import net.sourceforge.tuned.DownloadTask;
*/
public class OpenSubtitlesSubtitleDescriptor implements SubtitleDescriptor {
private final Map<String, String> properties;
private final Map<Property, String> properties;
public static enum Properties {
public static enum Property {
IDSubMovieFile,
MovieHash,
MovieByteSize,
@ -54,45 +56,59 @@ public class OpenSubtitlesSubtitleDescriptor implements SubtitleDescriptor {
ISO639,
LanguageName,
SubDownloadLink,
ZipDownloadLink,
ZipDownloadLink;
public static <V> EnumMap<Property, V> asEnumMap(Map<String, V> stringMap) {
EnumMap<Property, V> enumMap = new EnumMap<Property, V>(Property.class);
for (Entry<String, V> entry : stringMap.entrySet()) {
try {
enumMap.put(Property.valueOf(entry.getKey()), entry.getValue());
} catch (IllegalArgumentException e) {
// illegal enum constant, just ignore
}
}
return enumMap;
}
}
public OpenSubtitlesSubtitleDescriptor(Map<String, String> properties) {
this.properties = new HashMap<String, String>(properties);
public OpenSubtitlesSubtitleDescriptor(Map<Property, String> properties) {
this.properties = new EnumMap<Property, String>(properties);
}
public String getProperty(Properties property) {
return properties.get(property.name());
public Map<Property, String> getProperties() {
return Collections.unmodifiableMap(properties);
}
@Override
public String getName() {
return getProperty(Properties.SubFileName);
return properties.get(Property.SubFileName);
}
@Override
public String getLanguageName() {
return getProperty(Properties.LanguageName);
return properties.get(Property.LanguageName);
}
@Override
public String getAuthor() {
return getProperty(Properties.UserNickName);
return properties.get(Property.UserNickName);
}
public long getSize() {
return Long.parseLong(getProperty(Properties.SubSize));
return Long.parseLong(properties.get(Property.SubSize));
}
public URL getDownloadLink() {
String link = getProperty(Properties.ZipDownloadLink);
String link = properties.get(Property.ZipDownloadLink);
try {
return new URL(link);

View File

@ -4,13 +4,13 @@ package net.sourceforge.filebot.web;
public class SeasonOutOfBoundsException extends IndexOutOfBoundsException {
private final String showName;
private final String seriesName;
private final int season;
private final int maxSeason;
public SeasonOutOfBoundsException(String showName, int season, int maxSeason) {
this.showName = showName;
public SeasonOutOfBoundsException(String seriesName, int season, int maxSeason) {
this.seriesName = seriesName;
this.season = season;
this.maxSeason = maxSeason;
}
@ -18,12 +18,12 @@ public class SeasonOutOfBoundsException extends IndexOutOfBoundsException {
@Override
public String getMessage() {
return String.format("%s has only %d season%s.", showName, maxSeason, maxSeason != 1 ? "s" : "");
return String.format("%s has only %d season%s.", seriesName, maxSeason, maxSeason != 1 ? "s" : "");
}
public String getShowName() {
return showName;
public String getSeriesName() {
return seriesName;
}

View File

@ -27,7 +27,7 @@ import java.util.regex.Pattern;
import javax.swing.Icon;
import net.sourceforge.filebot.ResourceManager;
import net.sourceforge.tuned.FileUtil;
import net.sourceforge.tuned.FileUtilities;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
@ -239,7 +239,7 @@ public class SubsceneSubtitleClient implements SubtitleClient {
private URL getDownloadUrl(URL referer, String subtitleId, String typeId) throws MalformedURLException {
String basePath = FileUtil.getNameWithoutExtension(referer.getFile());
String basePath = FileUtilities.getNameWithoutExtension(referer.getFile());
String path = String.format("%s-dlpath-%s/%s.zipx", basePath, subtitleId, typeId);
return new URL(referer.getProtocol(), referer.getHost(), path);

View File

@ -11,7 +11,6 @@ import java.net.URI;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
@ -141,12 +140,6 @@ public class TVDotComClient implements EpisodeListClient {
List<Node> nodes = selectNodes("id('eps_table')//TD[@class='ep_title']/parent::TR", dom);
// create mutable list from nodes so we can reverse the list
nodes = new ArrayList<Node>(nodes);
// episodes are ordered in reverse ... we definitely don't want that!
Collections.reverse(nodes);
Integer episodeOffset = null;
ArrayList<Episode> episodes = new ArrayList<Episode>(nodes.size());

View File

@ -94,7 +94,7 @@ public class TVRageClient implements EpisodeListClient {
Document dom = getDocument(episodeListUrl);
String showName = selectString("Show/name", dom);
String seriesName = selectString("Show/name", dom);
List<Node> nodes = selectNodes("Show/Episodelist/Season/episode", dom);
List<Episode> episodes = new ArrayList<Episode>(nodes.size());
@ -104,7 +104,7 @@ public class TVRageClient implements EpisodeListClient {
String episodeNumber = getTextContent("seasonnum", node);
String seasonNumber = node.getParentNode().getAttributes().getNamedItem("no").getTextContent();
episodes.add(new Episode(showName, seasonNumber, episodeNumber, title));
episodes.add(new Episode(seriesName, seasonNumber, episodeNumber, title));
}
// populate cache

View File

@ -51,6 +51,9 @@ public class TheTVDBClient implements EpisodeListClient {
public TheTVDBClient(String apikey) {
if (apikey == null)
throw new NullPointerException("apikey must not be null");
this.apikey = apikey;
}
@ -131,8 +134,9 @@ public class TheTVDBClient implements EpisodeListClient {
}
}
if (episodes.isEmpty())
if (episodes.isEmpty()) {
throw new SeasonOutOfBoundsException(searchResult.getName(), season, maxSeason);
}
return episodes;
}

View File

@ -1,126 +0,0 @@
package net.sourceforge.tuned;
import java.io.File;
public final class FileUtil {
public final static long KILO = 1024;
public final static long MEGA = KILO * 1024;
public final static 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 boolean hasExtension(File file, Iterable<String> extensions) {
if (file.isDirectory())
return false;
return hasExtension(file.getName(), extensions);
}
public static boolean hasExtension(String filename, Iterable<String> extensions) {
String extension = getExtension(filename, false);
for (String ext : extensions) {
if (ext.equalsIgnoreCase(extension))
return true;
}
return false;
}
public static String getExtension(File file) {
return getExtension(file, false);
}
public static String getExtension(File file, boolean includeDot) {
return getExtension(file.getName(), includeDot);
}
public static String getExtension(String name, boolean includeDot) {
int dotIndex = name.lastIndexOf(".");
// .config -> no extension, just hidden
if (dotIndex >= 1) {
int startIndex = dotIndex;
if (!includeDot)
startIndex += 1;
if (startIndex <= name.length()) {
return name.substring(startIndex, name.length());
}
}
return "";
}
public static String getNameWithoutExtension(String name) {
int dotIndex = name.lastIndexOf(".");
if (dotIndex < 1)
return name;
return name.substring(0, dotIndex);
}
public static String getFileName(File file) {
if (file.isDirectory())
return getFolderName(file);
return getNameWithoutExtension(file.getName());
}
public static String getFolderName(File file) {
String name = file.getName();
if (!name.isEmpty())
return name;
// file might be a drive (only has a path, but no name)
return file.toString();
}
public static String getFileType(File file) {
if (file.isDirectory())
return "Folder";
String extension = getExtension(file.getName(), false);
if (!extension.isEmpty())
return extension;
// some file with no suffix
return "File";
}
/**
* Dummy constructor to prevent instantiation.
*/
private FileUtil() {
throw new UnsupportedOperationException();
}
}

View File

@ -0,0 +1,164 @@
package net.sourceforge.tuned;
import java.io.File;
import java.io.FileFilter;
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);
}
public static String getExtension(File file) {
return getExtension(file.getName());
}
public static String getExtension(String name) {
int dotIndex = name.lastIndexOf(".");
// .hidden -> no extension, just hidden
if (dotIndex > 0 && dotIndex < name.length() - 1) {
return name.substring(dotIndex + 1);
}
return null;
}
public static boolean hasExtension(File file, String... extensions) {
if (file.isDirectory())
return false;
return hasExtension(file.getName(), extensions);
}
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;
}
}
return false;
}
public static String getNameWithoutExtension(String name) {
int dotIndex = name.lastIndexOf(".");
if (dotIndex > 0)
return name.substring(0, dotIndex);
// no extension, return given name
return name;
}
public static String getName(File file) {
if (file.isDirectory())
return getFolderName(file);
return getNameWithoutExtension(file.getName());
}
public static String getFolderName(File file) {
String name = file.getName();
if (!name.isEmpty())
return name;
// file might be a drive (only has a path, but no name)
return file.toString();
}
public static String getType(File file) {
if (file.isDirectory())
return "Folder";
String extension = getExtension(file.getName());
if (!extension.isEmpty())
return extension;
// some file with no extension
return "File";
}
public static boolean containsOnly(Iterable<File> files, FileFilter filter) {
for (File file : files) {
if (!filter.accept(file))
return false;
}
return true;
}
public static final FileFilter FOLDERS = new FileFilter() {
@Override
public boolean accept(File file) {
return file.isDirectory();
}
};
public static final FileFilter FILES = new FileFilter() {
@Override
public boolean accept(File file) {
return file.isFile();
}
};
public static class ExtensionFileFilter implements FileFilter {
private final String[] extensions;
public ExtensionFileFilter(String... extensions) {
this.extensions = extensions;
}
@Override
public boolean accept(File file) {
return hasExtension(file, extensions);
}
public String[] getExtensions() {
return extensions.clone();
}
}
/**
* Dummy constructor to prevent instantiation.
*/
private FileUtilities() {
throw new UnsupportedOperationException();
}
}

View File

@ -0,0 +1,109 @@
package net.sourceforge.tuned.ui;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.Action;
import javax.swing.Icon;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JSeparator;
import net.miginfocom.swing.MigLayout;
public class ActionPopup extends JPopupMenu {
protected JLabel headerLabel = new JLabel();
protected JLabel descriptionLabel = new JLabel();
protected JLabel statusLabel = new JLabel();
protected JPanel actionPanel = new JPanel(new MigLayout("insets 0, wrap 1"));
public ActionPopup(String label, Icon icon) {
headerLabel.setText(label);
headerLabel.setIcon(icon);
headerLabel.setIconTextGap(5);
statusLabel.setFont(statusLabel.getFont().deriveFont(10f));
statusLabel.setForeground(Color.GRAY);
actionPanel.setOpaque(false);
setLayout(new MigLayout("nogrid, fill, insets 0"));
add(headerLabel, "gapx 4px 4px, gapy 1px, wrap 3px");
add(new JSeparator(), "growx, wrap 1px");
add(descriptionLabel, "gapx 4px, wrap 3px");
add(actionPanel, "gapx 12px 12px, wrap");
add(new JSeparator(), "growx, wrap 0px");
add(statusLabel, "growx, h 11px!, gapx 3px, wrap 1px");
}
@Override
public JMenuItem add(Action a) {
LinkButton link = new LinkButton(a);
// close popup when action is triggered
link.addActionListener(closeListener);
// underline text
link.setText(String.format("<html><u>%s</u></html>", link.getText()));
// use rollover color
link.setRolloverEnabled(false);
link.setColor(link.getRolloverColor());
actionPanel.add(link);
return null;
}
@Override
public void setLabel(String label) {
headerLabel.setText(label);
}
@Override
public String getLabel() {
return headerLabel.getText();
}
public void setDescription(String string) {
descriptionLabel.setText(string);
}
public String getDescription() {
return descriptionLabel.getText();
}
public void setStatus(String string) {
statusLabel.setText(string);
}
public String getStatus() {
return statusLabel.getText();
}
private final ActionListener closeListener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
setVisible(false);
}
};
}

View File

@ -1,58 +0,0 @@
package net.sourceforge.tuned.ui;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Desktop;
import java.awt.SystemColor;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.net.URI;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JLabel;
public class HyperlinkLabel extends JLabel {
private final URI link;
private final Color defaultColor;
public HyperlinkLabel(String label, URI link) {
super(label);
this.link = link;
defaultColor = getForeground();
addMouseListener(linker);
setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
}
private MouseListener linker = new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent event) {
try {
Desktop.getDesktop().browse(link);
} catch (Exception e) {
// should not happen
Logger.getLogger("global").log(Level.SEVERE, e.toString(), e);
}
}
@Override
public void mouseEntered(MouseEvent e) {
setForeground(SystemColor.textHighlight);
}
@Override
public void mouseExited(MouseEvent e) {
setForeground(defaultColor);
}
};
}

View File

@ -0,0 +1,124 @@
package net.sourceforge.tuned.ui;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Desktop;
import java.awt.SystemColor;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.net.URI;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.Icon;
import javax.swing.JButton;
public class LinkButton extends JButton {
private Color color = getForeground();
private Color rolloverColor = SystemColor.textHighlight;
public LinkButton(String text, Icon icon, URI uri) {
this(new OpenUriAction(text, icon, uri));
}
public LinkButton(Action action) {
setAction(action);
setFocusPainted(false);
setOpaque(false);
setContentAreaFilled(false);
setBorder(null);
setIconTextGap(6);
setRolloverEnabled(true);
setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
}
@Override
public void setRolloverEnabled(boolean enabled) {
super.setRolloverEnabled(enabled);
// always remove listener if there is one
removeMouseListener(rolloverListener);
if (enabled) {
// add listener again, if enabled
addMouseListener(rolloverListener);
}
}
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
this.setForeground(color);
}
public Color getRolloverColor() {
return rolloverColor;
}
public void setRolloverColor(Color rolloverColor) {
this.rolloverColor = rolloverColor;
}
protected final MouseListener rolloverListener = new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent e) {
setForeground(rolloverColor);
}
@Override
public void mouseExited(MouseEvent e) {
setForeground(color);
}
};
protected static class OpenUriAction extends AbstractAction {
public static final String URI = "uri";
public OpenUriAction(String text, Icon icon, URI uri) {
super(text, icon);
putValue(URI, uri);
}
@Override
public void actionPerformed(ActionEvent evt) {
try {
URI uri = (URI) getValue(URI);
if (uri != null) {
Desktop.getDesktop().browse(uri);
}
} catch (Exception e) {
// should not happen
Logger.getLogger("global").log(Level.SEVERE, e.toString(), e);
}
}
}
}

View File

@ -22,21 +22,27 @@ public class LoadingOverlayPane extends JComponent {
public LoadingOverlayPane(JComponent component, JComponent propertyChangeSource) {
this(component, new ProgressIndicator(), propertyChangeSource);
this(component, propertyChangeSource, null, null);
}
public LoadingOverlayPane(JComponent component, JComponent animationComponent, JComponent propertyChangeSource) {
public LoadingOverlayPane(JComponent component, JComponent propertyChangeSource, String offsetX, String offsetY) {
setLayout(new MigLayout("insets 0, fill"));
this.animationComponent = animationComponent;
add(animationComponent, "pos n 8px 100%-18px n");
add(component, "grow");
animationComponent = new ProgressIndicator();
animationComponent.setVisible(false);
add(animationComponent, String.format("pos n %s 100%%-%s n", offsetY != null ? offsetY : "8px", offsetX != null ? offsetX : "18px"));
add(component, "grow");
if (propertyChangeSource != null) {
propertyChangeSource.addPropertyChangeListener(LOADING_PROPERTY, loadingListener);
propertyChangeSource.addPropertyChangeListener(LOADING_PROPERTY, new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
setOverlayVisible((Boolean) evt.getNewValue());
}
});
}
}
@ -51,7 +57,7 @@ public class LoadingOverlayPane extends JComponent {
overlayEnabled = b;
if (overlayEnabled) {
TunedUtil.invokeLater(millisToOverlay, new Runnable() {
TunedUtilities.invokeLater(millisToOverlay, new Runnable() {
@Override
public void run() {
@ -66,15 +72,4 @@ public class LoadingOverlayPane extends JComponent {
}
}
private final PropertyChangeListener loadingListener = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
Boolean loading = (Boolean) evt.getNewValue();
setOverlayVisible(loading);
}
};
}

View File

@ -50,7 +50,7 @@ public class ProgressDialog extends JDialog {
setSize(240, 155);
setLocation(TunedUtil.getPreferredLocation(this));
setLocation(TunedUtilities.getPreferredLocation(this));
}

View File

@ -43,8 +43,8 @@ public class SelectButtonTextField<T> extends JComponent {
editor.setUI(new TextFieldComboBoxUI());
TunedUtil.putActionForKeystroke(this, KeyStroke.getKeyStroke("ctrl UP"), new SpinClientAction(-1));
TunedUtil.putActionForKeystroke(this, KeyStroke.getKeyStroke("ctrl DOWN"), new SpinClientAction(1));
TunedUtilities.putActionForKeystroke(this, KeyStroke.getKeyStroke("ctrl UP"), new SpinClientAction(-1));
TunedUtilities.putActionForKeystroke(this, KeyStroke.getKeyStroke("ctrl DOWN"), new SpinClientAction(1));
}

View File

@ -11,6 +11,9 @@ import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.reflect.Method;
import javax.swing.Action;
import javax.swing.Icon;
@ -21,8 +24,10 @@ import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import net.sourceforge.tuned.ExceptionUtil;
public final class TunedUtil {
public final class TunedUtilities {
public static final Color TRANSLUCENT = new Color(255, 255, 255, 0);
@ -55,10 +60,13 @@ public final class TunedUtil {
public static Image getImage(Icon icon) {
if (icon instanceof ImageIcon) {
return ((ImageIcon) icon).getImage();
}
if (icon == null)
return null;
if (icon instanceof ImageIcon)
return ((ImageIcon) icon).getImage();
// draw icon into a new image
BufferedImage image = new BufferedImage(icon.getIconWidth(), icon.getIconHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = image.createGraphics();
@ -91,10 +99,58 @@ public final class TunedUtil {
}
public static void syncPropertyChangeEvents(Class<?> propertyType, String property, Object from, Object to) {
PropertyChangeDelegate.create(propertyType, property, from, to);
}
private static class PropertyChangeDelegate implements PropertyChangeListener {
private final String property;
private final Object target;
private final Method firePropertyChange;
public static PropertyChangeDelegate create(Class<?> propertyType, String property, Object source, Object target) {
try {
PropertyChangeDelegate listener = new PropertyChangeDelegate(propertyType, property, target);
source.getClass().getMethod("addPropertyChangeListener", PropertyChangeListener.class).invoke(source, listener);
return listener;
} catch (Exception e) {
throw ExceptionUtil.asRuntimeException(e);
}
}
protected PropertyChangeDelegate(Class<?> propertyType, String property, Object target) throws SecurityException, NoSuchMethodException {
this.property = property;
this.target = target;
this.firePropertyChange = target.getClass().getMethod("firePropertyChange", String.class, propertyType, propertyType);
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (property.equals(evt.getPropertyName())) {
try {
firePropertyChange.invoke(target, evt.getPropertyName(), evt.getOldValue(), evt.getNewValue());
} catch (Exception e) {
throw ExceptionUtil.asRuntimeException(e);
}
}
}
}
/**
* Dummy constructor to prevent instantiation.
*/
private TunedUtil() {
private TunedUtilities() {
throw new UnsupportedOperationException();
}

View File

@ -9,7 +9,7 @@ package net.sourceforge.tuned.ui.notification;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import net.sourceforge.tuned.ui.TunedUtil;
import net.sourceforge.tuned.ui.TunedUtilities;
public class NotificationManager {
@ -28,7 +28,7 @@ public class NotificationManager {
public void show(NotificationWindow notification) {
TunedUtil.checkEventDispatchThread();
TunedUtilities.checkEventDispatchThread();
notification.addWindowListener(new RemoveListener());
layout.add(notification);

View File

@ -17,7 +17,7 @@ import java.awt.event.WindowEvent;
import javax.swing.JWindow;
import javax.swing.Timer;
import net.sourceforge.tuned.ui.TunedUtil;
import net.sourceforge.tuned.ui.TunedUtilities;
public class NotificationWindow extends JWindow {
@ -51,7 +51,7 @@ public class NotificationWindow extends JWindow {
public final void close() {
TunedUtil.checkEventDispatchThread();
TunedUtilities.checkEventDispatchThread();
// window events are not fired automatically, required for layout updates
processWindowEvent(new WindowEvent(this, WindowEvent.WINDOW_CLOSING));
@ -72,7 +72,7 @@ public class NotificationWindow extends JWindow {
@Override
public void componentShown(ComponentEvent e) {
if (timeout >= 0) {
timer = TunedUtil.invokeLater(timeout, new Runnable() {
timer = TunedUtilities.invokeLater(timeout, new Runnable() {
@Override
public void run() {

View File

@ -15,13 +15,13 @@ public class NameSimilarityMetricTest {
@Test
public void getSimilarity() {
// normalize separators, lower-case
assertEquals(1, metric.getSimilarity("test s01e01 first", "test.S01E01.First"));
assertEquals(1, metric.getSimilarity("test s01e02 second", "test_[S01E02]_Second"));
assertEquals(1, metric.getSimilarity("test s01e03 third", "__test__S01E03__Third__"));
assertEquals(1, metric.getSimilarity("test s01e04 four", "test s01e04 four"));
assertEquals(1, metric.getSimilarity("test s01e01 first", "test.S01E01.First"), 0);
assertEquals(1, metric.getSimilarity("test s01e02 second", "test_[S01E02]_Second"), 0);
assertEquals(1, metric.getSimilarity("test s01e03 third", "__test__S01E03__Third__"), 0);
assertEquals(1, metric.getSimilarity("test s01e04 four", "test s01e04 four"), 0);
// remove checksum
assertEquals(1, metric.getSimilarity("test", "test [EF62DF13]"));
assertEquals(1, metric.getSimilarity("test", "test [EF62DF13]"), 0);
}
}

View File

@ -89,8 +89,6 @@ public class NumericSimilarityMetricTest {
}
}
// System.out.println(String.format("[%f, %s, %s]", maxSimilarity, value, mostSimilar));
return mostSimilar;
}
}

View File

@ -0,0 +1,72 @@
package net.sourceforge.filebot.similarity;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
public class SeasonEpisodeMatcherTest {
private static SeasonEpisodeMatcher matcher = new SeasonEpisodeMatcher();
@Test
public void patternPrecedence() {
// S01E01 pattern has highest precedence
assertEquals("1x03", matcher.match("Test.101.1x02.S01E03").get(0).toString());
// multiple values
assertEquals("1x02", matcher.match("Test.42.s01e01.s01e02.300").get(1).toString());
}
@Test
public void pattern_1x01() {
assertEquals("1x01", matcher.match("1x01").get(0).toString());
// test multiple matches
assertEquals("1x02", matcher.match("Test - 1x01 and 1x02 - Multiple MatchCollection").get(1).toString());
// test high values
assertEquals("12x345", matcher.match("Test - 12x345 - High Values").get(0).toString());
// test lookahead and lookbehind
assertEquals("1x03", matcher.match("Test_-_103_[1280x720]").get(0).toString());
}
@Test
public void pattern_S01E01() {
assertEquals("1x01", matcher.match("S01E01").get(0).toString());
// test multiple matches
assertEquals("1x02", matcher.match("S01E01 and S01E02 - Multiple MatchCollection").get(1).toString());
// test separated values
assertEquals("1x03", matcher.match("[s01]_[e03]").get(0).toString());
// test high values
assertEquals("12x345", matcher.match("Test - S12E345 - High Values").get(0).toString());
}
@Test
public void pattern_101() {
assertEquals("1x01", matcher.match("Test.101").get(0).toString());
// test 2-digit number
assertEquals("0x02", matcher.match("02").get(0).toString());
// test high values
assertEquals("10x01", matcher.match("[Test]_1001_High_Values").get(0).toString());
// first two digits <= 29
assertEquals(null, matcher.match("The 4400"));
// test lookbehind
assertEquals(null, matcher.match("720p"));
}
}

View File

@ -15,82 +15,24 @@ public class SeasonEpisodeSimilarityMetricTest {
@Test
public void getSimilarity() {
// single pattern match, single episode match
assertEquals(1.0, metric.getSimilarity("1x01", "s01e01"));
assertEquals(1.0, metric.getSimilarity("1x01", "s01e01"), 0);
// multiple pattern matches, single episode match
assertEquals(1.0, metric.getSimilarity("1x02a", "101 102 103"));
assertEquals(1.0, metric.getSimilarity("1x02a", "101 102 103"), 0);
// multiple pattern matches, no episode match
assertEquals(0.0, metric.getSimilarity("1x03b", "104 105 106"));
assertEquals(0.0, metric.getSimilarity("1x03b", "104 105 106"), 0);
// no pattern match, no episode match
assertEquals(0.0, metric.getSimilarity("abc", "xyz"));
assertEquals(0.0, metric.getSimilarity("abc", "xyz"), 0);
}
@Test
public void fallbackMetric() {
assertEquals(1.0, metric.getSimilarity("1x01", "sno=1, eno=1"));
assertEquals(1.0, metric.getSimilarity("1x01", "sno=1, eno=1"), 0);
assertEquals(1.0, metric.getSimilarity("1x02", "Dexter - Staffel 1 Episode 2"));
}
@Test
public void patternPrecedence() {
// S01E01 pattern has highest precedence
assertEquals("1x03", metric.match("Test.101.1x02.S01E03").get(0).toString());
// multiple values
assertEquals("1x02", metric.match("Test.42.s01e01.s01e02.300").get(1).toString());
}
@Test
public void pattern_1x01() {
assertEquals("1x01", metric.match("1x01").get(0).toString());
// test multiple matches
assertEquals("1x02", metric.match("Test - 1x01 and 1x02 - Multiple MatchCollection").get(1).toString());
// test high values
assertEquals("12x345", metric.match("Test - 12x345 - High Values").get(0).toString());
// test lookahead and lookbehind
assertEquals("1x03", metric.match("Test_-_103_[1280x720]").get(0).toString());
}
@Test
public void pattern_S01E01() {
assertEquals("1x01", metric.match("S01E01").get(0).toString());
// test multiple matches
assertEquals("1x02", metric.match("S01E01 and S01E02 - Multiple MatchCollection").get(1).toString());
// test separated values
assertEquals("1x03", metric.match("[s01]_[e03]").get(0).toString());
// test high values
assertEquals("12x345", metric.match("Test - S12E345 - High Values").get(0).toString());
}
@Test
public void pattern_101() {
assertEquals("1x01", metric.match("Test.101").get(0).toString());
// test 2-digit number
assertEquals("0x02", metric.match("02").get(0).toString());
// test high values
assertEquals("10x01", metric.match("[Test]_1001_High_Values").get(0).toString());
// first two digits <= 29
assertEquals(null, metric.match("The 4400"));
// test lookbehind
assertEquals(null, metric.match("720p"));
assertEquals(1.0, metric.getSimilarity("1x02", "Dexter - Staffel 1 Episode 2"), 0);
}
}

View File

@ -0,0 +1,57 @@
package net.sourceforge.filebot.similarity;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import net.sourceforge.filebot.similarity.SeriesNameMatcher.SeriesNameCollection;
import org.junit.Test;
public class SeriesNameMatcherTest {
private static SeriesNameMatcher matcher = new SeriesNameMatcher(5);
@Test
public void matchBeforeSeasonEpisodePattern() {
assertEquals("The Test", matcher.matchBySeasonEpisodePattern("The Test - 1x01"));
// real world test
assertEquals("Mushishi", matcher.matchBySeasonEpisodePattern("[niizk]_Mushishi_-_01_-_The_Green_Gathering"));
}
@Test
public void normalize() {
// non-letter and non-digit characters
assertEquals("The Test", matcher.normalize("_The_Test_-_ ..."));
// brackets
assertEquals("Luffy", matcher.normalize("[strawhat] Luffy [D.] [@Monkey]"));
}
@Test
public void firstCommonSequence() {
String[] seq1 = "[abc] Common Name 1".split("\\s");
String[] seq2 = "[xyz] Common Name 2".split("\\s");
assertArrayEquals(new String[] { "Common", "Name" }, matcher.firstCommonSequence(seq1, seq2, String.CASE_INSENSITIVE_ORDER));
}
@Test
public void firstCharacterCaseBalance() {
SeriesNameCollection n = new SeriesNameCollection();
assertTrue(n.firstCharacterCaseBalance("My Name is Earl") > n.firstCharacterCaseBalance("My Name Is Earl"));
assertTrue(n.firstCharacterCaseBalance("My Name is Earl") > n.firstCharacterCaseBalance("my name is earl"));
// boost upper case ration
assertTrue(n.firstCharacterCaseBalance("Roswell") > n.firstCharacterCaseBalance("roswell"));
}
}

View File

@ -8,7 +8,7 @@ import org.junit.runners.Suite.SuiteClasses;
@RunWith(Suite.class)
@SuiteClasses( { NameSimilarityMetricTest.class, NumericSimilarityMetricTest.class, SeasonEpisodeSimilarityMetricTest.class })
@SuiteClasses( { SeriesNameMatcherTest.class, SeasonEpisodeMatcherTest.class, NameSimilarityMetricTest.class, NumericSimilarityMetricTest.class, SeasonEpisodeSimilarityMetricTest.class })
public class SimilarityTestSuite {
}

View File

@ -65,7 +65,7 @@ public class AnidbClientTest {
Episode first = list.get(0);
assertEquals("Monster", first.getShowName());
assertEquals("Monster", first.getSeriesName());
assertEquals("Herr Dr. Tenma", first.getTitle());
assertEquals("1", first.getEpisodeNumber());
assertEquals(null, first.getSeasonNumber());
@ -80,7 +80,7 @@ public class AnidbClientTest {
Episode first = list.get(0);
assertEquals("Juuni Kokuki", first.getShowName());
assertEquals("Juuni Kokuki", first.getSeriesName());
assertEquals("Shadow of the Moon, The Sea of Shadow - Chapter 1", first.getTitle());
assertEquals("1", first.getEpisodeNumber());
assertEquals(null, first.getSeasonNumber());

View File

@ -62,7 +62,7 @@ public class TVDotComClientTest {
Episode chosen = list.get(21);
assertEquals("Buffy the Vampire Slayer", chosen.getShowName());
assertEquals("Buffy the Vampire Slayer", chosen.getSeriesName());
assertEquals("Chosen", chosen.getTitle());
assertEquals("22", chosen.getEpisodeNumber());
assertEquals("7", chosen.getSeasonNumber());
@ -77,7 +77,7 @@ public class TVDotComClientTest {
Episode first = list.get(0);
assertEquals("Buffy the Vampire Slayer", first.getShowName());
assertEquals("Buffy the Vampire Slayer", first.getSeriesName());
assertEquals("Unaired Pilot", first.getTitle());
assertEquals("Pilot", first.getEpisodeNumber());
assertEquals(null, first.getSeasonNumber());
@ -92,7 +92,7 @@ public class TVDotComClientTest {
Episode fourth = list.get(3);
assertEquals("Firefly", fourth.getShowName());
assertEquals("Firefly", fourth.getSeriesName());
assertEquals("Jaynestown", fourth.getTitle());
assertEquals("4", fourth.getEpisodeNumber());
assertEquals("1", fourth.getSeasonNumber());
@ -116,7 +116,7 @@ public class TVDotComClientTest {
Episode episode = list.get(13);
assertEquals("Lost", episode.getShowName());
assertEquals("Lost", episode.getSeriesName());
assertEquals("Exposé", episode.getTitle());
assertEquals("14", episode.getEpisodeNumber());
assertEquals("3", episode.getSeasonNumber());

View File

@ -41,7 +41,7 @@ public class TVRageClientTest {
Episode chosen = list.get(21);
assertEquals("Buffy the Vampire Slayer", chosen.getShowName());
assertEquals("Buffy the Vampire Slayer", chosen.getSeriesName());
assertEquals("Chosen", chosen.getTitle());
assertEquals("22", chosen.getEpisodeNumber());
assertEquals("7", chosen.getSeasonNumber());
@ -56,7 +56,7 @@ public class TVRageClientTest {
Episode first = list.get(0);
assertEquals("Buffy the Vampire Slayer", first.getShowName());
assertEquals("Buffy the Vampire Slayer", first.getSeriesName());
assertEquals("Unaired Pilot", first.getTitle());
assertEquals("00", first.getEpisodeNumber());
assertEquals("0", first.getSeasonNumber());

View File

@ -69,7 +69,7 @@ public class TheTVDBClientTest {
Episode first = list.get(0);
assertEquals("Buffy the Vampire Slayer", first.getShowName());
assertEquals("Buffy the Vampire Slayer", first.getSeriesName());
assertEquals("Unaired Pilot", first.getTitle());
assertEquals("1", first.getEpisodeNumber());
assertEquals("0", first.getSeasonNumber());
@ -84,7 +84,7 @@ public class TheTVDBClientTest {
Episode chosen = list.get(21);
assertEquals("Buffy the Vampire Slayer", chosen.getShowName());
assertEquals("Buffy the Vampire Slayer", chosen.getSeriesName());
assertEquals("Chosen", chosen.getTitle());
assertEquals("22", chosen.getEpisodeNumber());
assertEquals("7", chosen.getSeasonNumber());