* 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:
parent
c5c12513fa
commit
ac9473ff07
Binary file not shown.
Binary file not shown.
|
@ -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"
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
||||
private static List<String> unmodifiableList(String... elements) {
|
||||
return Collections.unmodifiableList(Arrays.asList(elements));
|
||||
}
|
||||
|
||||
|
||||
public static boolean containsOnlyFolders(Iterable<File> files) {
|
||||
for (File file : files) {
|
||||
if (!file.isDirectory())
|
||||
return false;
|
||||
public static String join(Object[] values, String separator) {
|
||||
if (values == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public static boolean containsOnly(Iterable<File> files, Iterable<String> extensions) {
|
||||
for (File file : files) {
|
||||
if (!FileUtil.hasExtension(file, extensions))
|
||||
return false;
|
||||
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 true;
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
public static List<String> asStringList(final List<?> list) {
|
||||
return new AbstractList<String>() {
|
||||
|
||||
@Override
|
||||
public String get(int index) {
|
||||
return list.get(index).toString();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return list.size();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 |
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
private final SeasonEpisodeMatcher seasonEpisodeMatcher = new SeasonEpisodeMatcher();
|
||||
|
||||
|
||||
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})(?=[\\._ ]|$)");
|
||||
}
|
||||
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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()));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -34,4 +34,10 @@ public class FileBotPanel extends JComponent {
|
|||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getPanelName();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
// should not happen
|
||||
Logger.getLogger("global").log(Level.WARNING, e.toString());
|
||||
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(), 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());
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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") {
|
||||
|
||||
@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 class RepaintHandler<E> implements ListEventListener<E> {
|
||||
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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ public class ProgressDialog extends JDialog {
|
|||
|
||||
setSize(240, 155);
|
||||
|
||||
setLocation(TunedUtil.getPreferredLocation(this));
|
||||
setLocation(TunedUtilities.getPreferredLocation(this));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -89,8 +89,6 @@ public class NumericSimilarityMetricTest {
|
|||
}
|
||||
}
|
||||
|
||||
// System.out.println(String.format("[%f, %s, %s]", maxSimilarity, value, mostSimilar));
|
||||
|
||||
return mostSimilar;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"));
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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"));
|
||||
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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());
|
||||
|
|
Loading…
Reference in New Issue