diff --git a/fw/action.match.file2name.png b/fw/action.match.file2name.png deleted file mode 100644 index f5726518..00000000 Binary files a/fw/action.match.file2name.png and /dev/null differ diff --git a/fw/action.match.name2file.png b/fw/action.match.name2file.png deleted file mode 100644 index 9297b1b9..00000000 Binary files a/fw/action.match.name2file.png and /dev/null differ diff --git a/fw/action.match.png b/fw/action.match.png new file mode 100644 index 00000000..faafd8a5 Binary files /dev/null and b/fw/action.match.png differ diff --git a/source/net/sourceforge/filebot/FileBotUtil.java b/source/net/sourceforge/filebot/FileBotUtil.java index f78cd352..94957192 100644 --- a/source/net/sourceforge/filebot/FileBotUtil.java +++ b/source/net/sourceforge/filebot/FileBotUtil.java @@ -66,6 +66,11 @@ public final class FileBotUtil { return embeddedChecksum; } + + public static String removeEmbeddedChecksum(String string) { + return string.replaceAll("[\\(\\[]\\p{XDigit}{8}[\\]\\)]", ""); + } + public static final List TORRENT_FILE_EXTENSIONS = unmodifiableList("torrent"); public static final List SFV_FILE_EXTENSIONS = unmodifiableList("sfv"); public static final List LIST_FILE_EXTENSIONS = unmodifiableList("txt", "list", ""); diff --git a/source/net/sourceforge/filebot/Main.java b/source/net/sourceforge/filebot/Main.java index d14fd32c..2e6c365e 100644 --- a/source/net/sourceforge/filebot/Main.java +++ b/source/net/sourceforge/filebot/Main.java @@ -2,6 +2,7 @@ package net.sourceforge.filebot; +import java.util.logging.ConsoleHandler; import java.util.logging.Level; import java.util.logging.Logger; import java.util.prefs.BackingStoreException; @@ -58,8 +59,18 @@ public class Main { private static void setupLogging() { Logger uiLogger = Logger.getLogger("ui"); - uiLogger.addHandler(new NotificationLoggingHandler()); + + // don't use parent handlers uiLogger.setUseParentHandlers(false); + + // ui handler + uiLogger.addHandler(new NotificationLoggingHandler()); + + // console handler (for warnings and errors only) + ConsoleHandler consoleHandler = new ConsoleHandler(); + consoleHandler.setLevel(Level.WARNING); + + uiLogger.addHandler(consoleHandler); } diff --git a/source/net/sourceforge/filebot/resources/action.match.file2name.png b/source/net/sourceforge/filebot/resources/action.match.file2name.png deleted file mode 100644 index a49c912b..00000000 Binary files a/source/net/sourceforge/filebot/resources/action.match.file2name.png and /dev/null differ diff --git a/source/net/sourceforge/filebot/resources/action.match.name2file.png b/source/net/sourceforge/filebot/resources/action.match.name2file.png deleted file mode 100644 index de0c1fa2..00000000 Binary files a/source/net/sourceforge/filebot/resources/action.match.name2file.png and /dev/null differ diff --git a/source/net/sourceforge/filebot/resources/action.match.png b/source/net/sourceforge/filebot/resources/action.match.png new file mode 100644 index 00000000..f9189064 Binary files /dev/null and b/source/net/sourceforge/filebot/resources/action.match.png differ diff --git a/source/net/sourceforge/filebot/similarity/LengthEqualsMetric.java b/source/net/sourceforge/filebot/similarity/LengthEqualsMetric.java new file mode 100644 index 00000000..4474ebec --- /dev/null +++ b/source/net/sourceforge/filebot/similarity/LengthEqualsMetric.java @@ -0,0 +1,49 @@ + +package net.sourceforge.filebot.similarity; + + +import java.io.File; + + +public class LengthEqualsMetric implements SimilarityMetric { + + @Override + public float getSimilarity(Object o1, Object o2) { + long l1 = getLength(o1); + + if (l1 >= 0 && l1 == getLength(o2)) { + // objects have the same non-negative length + return 1; + } + + return 0; + } + + + protected long getLength(Object o) { + if (o instanceof File) { + return ((File) o).length(); + } + + return -1; + } + + + @Override + public String getDescription() { + return "Check whether file size is equal or not"; + } + + + @Override + public String getName() { + return "Length"; + } + + + @Override + public String toString() { + return getClass().getName(); + } + +} diff --git a/source/net/sourceforge/filebot/similarity/Match.java b/source/net/sourceforge/filebot/similarity/Match.java new file mode 100644 index 00000000..25f4ed37 --- /dev/null +++ b/source/net/sourceforge/filebot/similarity/Match.java @@ -0,0 +1,44 @@ + +package net.sourceforge.filebot.similarity; + + +public class Match { + + private final V value; + private final C candidate; + + + public Match(V value, C candidate) { + this.value = value; + this.candidate = candidate; + } + + + public V getValue() { + return value; + } + + + public C getCandidate() { + return candidate; + } + + + /** + * Check if the given match has the same value or the same candidate. This method uses an + * identity equality test. + * + * @param match a match + * @return Returns true if the specified match has no value common. + */ + public boolean disjoint(Match match) { + return (value != match.value && candidate != match.candidate); + } + + + @Override + public String toString() { + return String.format("[%s, %s]", value, candidate); + } + +} diff --git a/source/net/sourceforge/filebot/similarity/Matcher.java b/source/net/sourceforge/filebot/similarity/Matcher.java new file mode 100644 index 00000000..e0a7df64 --- /dev/null +++ b/source/net/sourceforge/filebot/similarity/Matcher.java @@ -0,0 +1,237 @@ + +package net.sourceforge.filebot.similarity; + + +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + + +public class Matcher { + + private final List values; + private final List candidates; + + private final List metrics; + + private final DisjointMatchCollection disjointMatchCollection; + + + public Matcher(Collection values, Collection candidates, Collection metrics) { + this.values = new LinkedList(values); + this.candidates = new LinkedList(candidates); + + this.metrics = new ArrayList(metrics); + + this.disjointMatchCollection = new DisjointMatchCollection(); + } + + + public synchronized List> match() throws InterruptedException { + + // list of all combinations of values and candidates + List> possibleMatches = new ArrayList>(values.size() * candidates.size()); + + // populate with all possible matches + for (V value : values) { + for (C candidate : candidates) { + possibleMatches.add(new Match(value, candidate)); + } + } + + // match recursively + match(possibleMatches, 0); + + // restore order according to the given values + List> result = new ArrayList>(); + + for (V value : values) { + Match match = disjointMatchCollection.getByValue(value); + + if (match != null) { + result.add(match); + } + } + + // remove matched objects + for (Match match : result) { + values.remove(match.getValue()); + candidates.remove(match.getCandidate()); + } + + // clear collected matches + disjointMatchCollection.clear(); + + return result; + } + + + public List remainingValues() { + return Collections.unmodifiableList(values); + } + + + public List remainingCandidates() { + return Collections.unmodifiableList(candidates); + } + + + protected void match(Collection> possibleMatches, int level) throws InterruptedException { + if (level >= metrics.size() || possibleMatches.isEmpty()) { + // no further refinement possible + disjointMatchCollection.addAll(possibleMatches); + return; + } + + for (List> matchesWithEqualSimilarity : mapBySimilarity(possibleMatches, metrics.get(level)).values()) { + // some matches may already be unique + List> disjointMatches = disjointMatches(matchesWithEqualSimilarity); + + if (!disjointMatches.isEmpty()) { + // collect disjoint matches + disjointMatchCollection.addAll(disjointMatches); + + // no need for further matching + matchesWithEqualSimilarity.removeAll(disjointMatches); + } + + // remove invalid matches + removeCollected(matchesWithEqualSimilarity); + + // matches are ambiguous, more refined matching required + match(matchesWithEqualSimilarity, level + 1); + } + } + + + protected void removeCollected(Collection> matches) { + for (Iterator> iterator = matches.iterator(); iterator.hasNext();) { + if (!disjointMatchCollection.disjoint(iterator.next())) + iterator.remove(); + } + } + + + protected SortedMap>> mapBySimilarity(Collection> possibleMatches, SimilarityMetric metric) throws InterruptedException { + // map sorted by similarity descending + SortedMap>> similarityMap = new TreeMap>>(Collections.reverseOrder()); + + // use metric on all matches + for (Match possibleMatch : possibleMatches) { + float similarity = metric.getSimilarity(possibleMatch.getValue(), possibleMatch.getCandidate()); + + List> list = similarityMap.get(similarity); + + if (list == null) { + list = new ArrayList>(); + similarityMap.put(similarity, list); + } + + list.add(possibleMatch); + + // unwind this thread if we have been interrupted + if (Thread.interrupted()) { + throw new InterruptedException(); + } + } + + return similarityMap; + } + + + protected List> disjointMatches(Collection> collection) { + List> disjointMatches = new ArrayList>(); + + for (Match m1 : collection) { + boolean disjoint = true; + + for (Match m2 : collection) { + // ignore same element + if (m1 != m2 && !m1.disjoint(m2)) { + disjoint = false; + break; + } + } + + if (disjoint) { + disjointMatches.add(m1); + } + } + + return disjointMatches; + } + + + protected static class DisjointMatchCollection extends AbstractList> { + + private final List> matches; + + private final Map> values; + private final Map> candidates; + + + public DisjointMatchCollection() { + matches = new ArrayList>(); + values = new IdentityHashMap>(); + candidates = new IdentityHashMap>(); + } + + + @Override + public boolean add(Match match) { + if (disjoint(match)) { + values.put(match.getValue(), match); + candidates.put(match.getCandidate(), match); + + return matches.add(match); + } + + return false; + } + + + public boolean disjoint(Match match) { + return !values.containsKey(match.getValue()) && !candidates.containsKey(match.getCandidate()); + } + + + public Match getByValue(V value) { + return values.get(value); + } + + + public Match getByCandidate(C candidate) { + return candidates.get(candidate); + } + + + @Override + public Match get(int index) { + return matches.get(index); + } + + + @Override + public int size() { + return matches.size(); + } + + + @Override + public void clear() { + matches.clear(); + values.clear(); + candidates.clear(); + } + + } + +} diff --git a/source/net/sourceforge/filebot/similarity/NameSimilarityMetric.java b/source/net/sourceforge/filebot/similarity/NameSimilarityMetric.java new file mode 100644 index 00000000..093692f8 --- /dev/null +++ b/source/net/sourceforge/filebot/similarity/NameSimilarityMetric.java @@ -0,0 +1,56 @@ + +package net.sourceforge.filebot.similarity; + + +import static net.sourceforge.filebot.FileBotUtil.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; + + +public class NameSimilarityMetric implements SimilarityMetric { + + private final AbstractStringMetric metric; + + + public NameSimilarityMetric() { + // MongeElkan metric with a QGram3Extended tokenizer seems to work best for similarity of names + metric = new MongeElkan(new TokeniserQGram3Extended()); + } + + + @Override + public float getSimilarity(Object o1, Object o2) { + return metric.getSimilarity(normalize(o1), normalize(o2)); + } + + + protected String normalize(Object object) { + // remove embedded checksum from name, if any + String name = removeEmbeddedChecksum(object.toString()); + + // normalize separators + name = name.replaceAll("[\\._ ]+", " "); + + // normalize case and trim + return name.trim().toLowerCase(); + } + + + @Override + public String getDescription() { + return "Similarity of names"; + } + + + @Override + public String getName() { + return metric.getShortDescriptionString(); + } + + + @Override + public String toString() { + return getClass().getName(); + } +} diff --git a/source/net/sourceforge/filebot/ui/panel/rename/metric/NumericSimilarityMetric.java b/source/net/sourceforge/filebot/similarity/NumericSimilarityMetric.java similarity index 58% rename from source/net/sourceforge/filebot/ui/panel/rename/metric/NumericSimilarityMetric.java rename to source/net/sourceforge/filebot/similarity/NumericSimilarityMetric.java index ebb4e90b..93b92379 100644 --- a/source/net/sourceforge/filebot/ui/panel/rename/metric/NumericSimilarityMetric.java +++ b/source/net/sourceforge/filebot/similarity/NumericSimilarityMetric.java @@ -1,34 +1,42 @@ -package net.sourceforge.filebot.ui.panel.rename.metric; +package net.sourceforge.filebot.similarity; +import static net.sourceforge.filebot.FileBotUtil.removeEmbeddedChecksum; + import java.util.ArrayList; import java.util.HashSet; import java.util.Scanner; import java.util.Set; import uk.ac.shef.wit.simmetrics.similaritymetrics.AbstractStringMetric; -import uk.ac.shef.wit.simmetrics.similaritymetrics.EuclideanDistance; +import uk.ac.shef.wit.simmetrics.similaritymetrics.QGramsDistance; import uk.ac.shef.wit.simmetrics.tokenisers.InterfaceTokeniser; import uk.ac.shef.wit.simmetrics.wordhandlers.DummyStopTermHandler; import uk.ac.shef.wit.simmetrics.wordhandlers.InterfaceTermHandler; -public class NumericSimilarityMetric extends AbstractNameSimilarityMetric { +public class NumericSimilarityMetric implements SimilarityMetric { private final AbstractStringMetric metric; public NumericSimilarityMetric() { - // I have absolutely no clue as to why, but I get a good matching behavior - // when using a numeric tokensier with EuclideanDistance - metric = new EuclideanDistance(new NumberTokeniser()); + // I don't really know why, but I get a good matching behavior + // when using QGramsDistance or BlockDistance + metric = new QGramsDistance(new NumberTokeniser()); } @Override - public float getSimilarity(String a, String b) { - return metric.getSimilarity(a, b); + public float getSimilarity(Object o1, Object o2) { + return metric.getSimilarity(normalize(o1), normalize(o2)); + } + + + protected String normalize(Object object) { + // delete checksum pattern, because it will mess with the number tokens + return removeEmbeddedChecksum(object.toString()); } @@ -43,10 +51,16 @@ public class NumericSimilarityMetric extends AbstractNameSimilarityMetric { return "Numbers"; } + + @Override + public String toString() { + return getClass().getName(); + } - private static class NumberTokeniser implements InterfaceTokeniser { + + protected static class NumberTokeniser implements InterfaceTokeniser { - private static final String delimiter = "(\\D)+"; + private final String delimiter = "\\D+"; @Override @@ -54,10 +68,13 @@ public class NumericSimilarityMetric extends AbstractNameSimilarityMetric { ArrayList tokens = new ArrayList(); Scanner scanner = new Scanner(input); + + // scan for number patterns, use non-number pattern as delimiter scanner.useDelimiter(delimiter); while (scanner.hasNextInt()) { - tokens.add(Integer.toString(scanner.nextInt())); + // remove leading zeros from number tokens by scanning for Integers + tokens.add(String.valueOf(scanner.nextInt())); } return tokens; diff --git a/source/net/sourceforge/filebot/similarity/SeasonEpisodeSimilarityMetric.java b/source/net/sourceforge/filebot/similarity/SeasonEpisodeSimilarityMetric.java new file mode 100644 index 00000000..b217da0e --- /dev/null +++ b/source/net/sourceforge/filebot/similarity/SeasonEpisodeSimilarityMetric.java @@ -0,0 +1,171 @@ + +package net.sourceforge.filebot.similarity; + + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + + +public class SeasonEpisodeSimilarityMetric implements SimilarityMetric { + + private final NumericSimilarityMetric fallbackMetric = new NumericSimilarityMetric(); + + private final SeasonEpisodePattern[] patterns; + + + public SeasonEpisodeSimilarityMetric() { + patterns = new SeasonEpisodePattern[3]; + + // match patterns like S01E01, s01e02, ... [s01]_[e02], s01.e02, ... + patterns[0] = new SeasonEpisodePattern("(? sxeVector1 = match(normalize(o1)); + List sxeVector2 = match(normalize(o2)); + + if (sxeVector1 == null || sxeVector2 == null) { + // name does not match any known pattern, return numeric similarity + return fallbackMetric.getSimilarity(o1, o2); + } + + if (Collections.disjoint(sxeVector1, sxeVector2)) { + // vectors have no episode matches in common + return 0; + } + + // vectors have at least one episode match in common + return 1; + } + + + /** + * 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 match(String name) { + for (SeasonEpisodePattern pattern : patterns) { + List match = pattern.match(name); + + if (!match.isEmpty()) { + // current pattern did match + return match; + } + } + + return null; + } + + + protected String normalize(Object object) { + return object.toString(); + } + + + @Override + public String getDescription() { + return "Similarity of season and episode numbers"; + } + + + @Override + public String getName() { + return "Season and Episode"; + } + + + @Override + public String toString() { + 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 match(String name) { + // name will probably contain no more than one match, but may contain more + List matches = new ArrayList(1); + + Matcher matcher = pattern.matcher(name); + + while (matcher.find()) { + matches.add(new SxE(matcher.group(seasonGroup), matcher.group(episodeGroup))); + } + + return matches; + } + } + +} diff --git a/source/net/sourceforge/filebot/similarity/SimilarityMetric.java b/source/net/sourceforge/filebot/similarity/SimilarityMetric.java new file mode 100644 index 00000000..b95f3c5a --- /dev/null +++ b/source/net/sourceforge/filebot/similarity/SimilarityMetric.java @@ -0,0 +1,15 @@ + +package net.sourceforge.filebot.similarity; + + +public interface SimilarityMetric { + + public float getSimilarity(Object o1, Object o2); + + + public String getDescription(); + + + public String getName(); + +} diff --git a/source/net/sourceforge/filebot/torrent/Torrent.java b/source/net/sourceforge/filebot/torrent/Torrent.java index 47f966be..5e5a597d 100644 --- a/source/net/sourceforge/filebot/torrent/Torrent.java +++ b/source/net/sourceforge/filebot/torrent/Torrent.java @@ -189,13 +189,13 @@ public class Torrent { } - public Long getLength() { - return length; + public String getName() { + return name; } - public String getName() { - return name; + public Long getLength() { + return length; } diff --git a/source/net/sourceforge/filebot/ui/AbstractSearchPanel.java b/source/net/sourceforge/filebot/ui/AbstractSearchPanel.java index 8a64f4bf..2d5470ce 100644 --- a/source/net/sourceforge/filebot/ui/AbstractSearchPanel.java +++ b/source/net/sourceforge/filebot/ui/AbstractSearchPanel.java @@ -184,8 +184,7 @@ public abstract class AbstractSearchPanel extends FileBotPanel { } catch (Exception e) { tab.close(); - Logger.getLogger("ui").warning(ExceptionUtil.getRootCause(e).getMessage()); - Logger.getLogger("global").log(Level.WARNING, "Search failed", e); + Logger.getLogger("ui").log(Level.WARNING, ExceptionUtil.getRootCause(e).getMessage(), e); } } @@ -241,8 +240,7 @@ public abstract class AbstractSearchPanel extends FileBotPanel { } catch (Exception e) { tab.close(); - Logger.getLogger("ui").warning(ExceptionUtil.getRootCause(e).getMessage()); - Logger.getLogger("global").log(Level.WARNING, "Fetch failed", e); + Logger.getLogger("ui").log(Level.WARNING, ExceptionUtil.getRootCause(e).getMessage(), e); } finally { tab.setLoading(false); } @@ -333,7 +331,7 @@ public abstract class AbstractSearchPanel extends FileBotPanel { switch (searchResults.size()) { case 0: - Logger.getLogger("ui").warning(String.format("\"%s\" has not been found.", request.getSearchText())); + Logger.getLogger("ui").warning(String.format("'%s' has not been found.", request.getSearchText())); return null; case 1: return searchResults.iterator().next(); diff --git a/source/net/sourceforge/filebot/ui/panel/analyze/FileTree.java b/source/net/sourceforge/filebot/ui/panel/analyze/FileTree.java index c6c5f36b..898abf7e 100644 --- a/source/net/sourceforge/filebot/ui/panel/analyze/FileTree.java +++ b/source/net/sourceforge/filebot/ui/panel/analyze/FileTree.java @@ -18,7 +18,6 @@ import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; -import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.AbstractAction; @@ -160,7 +159,6 @@ public class FileTree extends JTree { } } catch (Exception e) { Logger.getLogger("ui").warning(e.getMessage()); - Logger.getLogger("global").log(Level.SEVERE, "Failed to open file", e); } } } diff --git a/source/net/sourceforge/filebot/ui/panel/analyze/SplitTool.java b/source/net/sourceforge/filebot/ui/panel/analyze/SplitTool.java index ca114353..6f1241ca 100644 --- a/source/net/sourceforge/filebot/ui/panel/analyze/SplitTool.java +++ b/source/net/sourceforge/filebot/ui/panel/analyze/SplitTool.java @@ -75,7 +75,7 @@ public class SplitTool extends Tool implements ChangeListener { @Override - protected TreeModel createModelInBackground(FolderNode sourceModel, Cancellable cancellable) { + protected TreeModel createModelInBackground(FolderNode sourceModel) throws InterruptedException { this.sourceModel = sourceModel; FolderNode root = new FolderNode(); @@ -87,7 +87,7 @@ public class SplitTool extends Tool implements ChangeListener { List remainder = new ArrayList(50); long totalSize = 0; - for (Iterator iterator = sourceModel.fileIterator(); iterator.hasNext() && !cancellable.isCancelled();) { + for (Iterator iterator = sourceModel.fileIterator(); iterator.hasNext();) { File file = iterator.next(); long fileSize = file.length(); @@ -108,6 +108,11 @@ public class SplitTool extends Tool implements ChangeListener { totalSize += fileSize; currentPart.add(file); + + // unwind thread, if we have been cancelled + if (Thread.interrupted()) { + throw new InterruptedException(); + } } if (!currentPart.isEmpty()) { diff --git a/source/net/sourceforge/filebot/ui/panel/analyze/Tool.java b/source/net/sourceforge/filebot/ui/panel/analyze/Tool.java index 434cd20c..4b2861c7 100644 --- a/source/net/sourceforge/filebot/ui/panel/analyze/Tool.java +++ b/source/net/sourceforge/filebot/ui/panel/analyze/Tool.java @@ -32,7 +32,7 @@ abstract class Tool extends JComponent { public synchronized void setSourceModel(FolderNode sourceModel) { if (updateTask != null) { - updateTask.cancel(false); + updateTask.cancel(true); } updateTask = new UpdateModelTask(sourceModel); @@ -41,13 +41,13 @@ abstract class Tool extends JComponent { } - protected abstract M createModelInBackground(FolderNode sourceModel, Cancellable cancellable); + protected abstract M createModelInBackground(FolderNode sourceModel) throws InterruptedException; protected abstract void setModel(M model); - private class UpdateModelTask extends SwingWorker implements Cancellable { + private class UpdateModelTask extends SwingWorker { private final FolderNode sourceModel; @@ -67,7 +67,7 @@ abstract class Tool extends JComponent { if (!isCancelled()) { firePropertyChange(LoadingOverlayPane.LOADING_PROPERTY, false, true); - model = createModelInBackground(sourceModel, this); + model = createModelInBackground(sourceModel); firePropertyChange(LoadingOverlayPane.LOADING_PROPERTY, true, false); } @@ -92,12 +92,6 @@ abstract class Tool extends JComponent { } } - - protected static interface Cancellable { - - boolean isCancelled(); - } - protected FolderNode createStatisticsNode(String name, List files) { FolderNode folder = new FolderNode(null, files.size()); diff --git a/source/net/sourceforge/filebot/ui/panel/analyze/TypeTool.java b/source/net/sourceforge/filebot/ui/panel/analyze/TypeTool.java index f4b38133..bcbf6eb1 100644 --- a/source/net/sourceforge/filebot/ui/panel/analyze/TypeTool.java +++ b/source/net/sourceforge/filebot/ui/panel/analyze/TypeTool.java @@ -41,10 +41,10 @@ public class TypeTool extends Tool { @Override - protected TreeModel createModelInBackground(FolderNode sourceModel, Cancellable cancellable) { + protected TreeModel createModelInBackground(FolderNode sourceModel) throws InterruptedException { TreeMap> map = new TreeMap>(); - for (Iterator iterator = sourceModel.fileIterator(); iterator.hasNext() && !cancellable.isCancelled();) { + for (Iterator iterator = sourceModel.fileIterator(); iterator.hasNext();) { File file = iterator.next(); String extension = FileUtil.getExtension(file); @@ -62,6 +62,11 @@ public class TypeTool extends Tool { for (Entry> entry : map.entrySet()) { root.add(createStatisticsNode(entry.getKey(), entry.getValue())); + + // unwind thread, if we have been cancelled + if (Thread.interrupted()) { + throw new InterruptedException(); + } } return new DefaultTreeModel(root); diff --git a/source/net/sourceforge/filebot/ui/panel/list/ListPanel.java b/source/net/sourceforge/filebot/ui/panel/list/ListPanel.java index fc9e9656..2ee7b3e3 100644 --- a/source/net/sourceforge/filebot/ui/panel/list/ListPanel.java +++ b/source/net/sourceforge/filebot/ui/panel/list/ListPanel.java @@ -99,7 +99,7 @@ public class ListPanel extends FileBotPanel { String pattern = textField.getText(); if (!pattern.contains(INDEX_VARIABLE)) { - Logger.getLogger("ui").warning(String.format("Pattern does not contain index variable %s.", INDEX_VARIABLE)); + Logger.getLogger("ui").warning(String.format("Pattern must contain index variable %s.", INDEX_VARIABLE)); return; } diff --git a/source/net/sourceforge/filebot/ui/panel/rename/AbstractFileEntry.java b/source/net/sourceforge/filebot/ui/panel/rename/AbstractFileEntry.java new file mode 100644 index 00000000..80668aa0 --- /dev/null +++ b/source/net/sourceforge/filebot/ui/panel/rename/AbstractFileEntry.java @@ -0,0 +1,32 @@ + +package net.sourceforge.filebot.ui.panel.rename; + + +public class AbstractFileEntry { + + private final String name; + private final long length; + + + public AbstractFileEntry(String name, long length) { + this.name = name; + this.length = length; + } + + + public String getName() { + return name; + } + + + public long getLength() { + return length; + } + + + @Override + public String toString() { + return getName(); + } + +} diff --git a/source/net/sourceforge/filebot/ui/panel/rename/entry/FileEntry.java b/source/net/sourceforge/filebot/ui/panel/rename/FileEntry.java similarity index 63% rename from source/net/sourceforge/filebot/ui/panel/rename/entry/FileEntry.java rename to source/net/sourceforge/filebot/ui/panel/rename/FileEntry.java index f4d8c9ef..89c5c74e 100644 --- a/source/net/sourceforge/filebot/ui/panel/rename/entry/FileEntry.java +++ b/source/net/sourceforge/filebot/ui/panel/rename/FileEntry.java @@ -1,5 +1,5 @@ -package net.sourceforge.filebot.ui.panel.rename.entry; +package net.sourceforge.filebot.ui.panel.rename; import java.io.File; @@ -10,33 +10,24 @@ import net.sourceforge.tuned.FileUtil; public class FileEntry extends AbstractFileEntry { private final File file; - - private final long length; private final String type; public FileEntry(File file) { - super(FileUtil.getFileName(file)); + super(FileUtil.getFileName(file), file.length()); this.file = file; - this.length = file.length(); this.type = FileUtil.getFileType(file); } - @Override - public long getLength() { - return length; - } - - - public String getType() { - return type; - } - - public File getFile() { return file; } + + public String getType() { + return type; + } + } diff --git a/source/net/sourceforge/filebot/ui/panel/rename/FilesListTransferablePolicy.java b/source/net/sourceforge/filebot/ui/panel/rename/FilesListTransferablePolicy.java index caf1d594..389e2101 100644 --- a/source/net/sourceforge/filebot/ui/panel/rename/FilesListTransferablePolicy.java +++ b/source/net/sourceforge/filebot/ui/panel/rename/FilesListTransferablePolicy.java @@ -8,17 +8,15 @@ import java.io.File; import java.util.Arrays; import java.util.List; -import net.sourceforge.filebot.ui.panel.rename.entry.FileEntry; import net.sourceforge.filebot.ui.transfer.FileTransferablePolicy; -import ca.odell.glazedlists.EventList; class FilesListTransferablePolicy extends FileTransferablePolicy { - private final EventList model; + private final List model; - public FilesListTransferablePolicy(EventList model) { + public FilesListTransferablePolicy(List model) { this.model = model; } diff --git a/source/net/sourceforge/filebot/ui/panel/rename/MatchAction.java b/source/net/sourceforge/filebot/ui/panel/rename/MatchAction.java index c6399d78..692fedcb 100644 --- a/source/net/sourceforge/filebot/ui/panel/rename/MatchAction.java +++ b/source/net/sourceforge/filebot/ui/panel/rename/MatchAction.java @@ -3,8 +3,10 @@ package net.sourceforge.filebot.ui.panel.rename; import java.awt.Cursor; +import java.awt.Window; import java.awt.event.ActionEvent; -import java.util.ArrayList; +import java.beans.PropertyChangeEvent; +import java.util.Arrays; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -18,141 +20,121 @@ import javax.swing.SwingUtilities; import javax.swing.SwingWorker; import net.sourceforge.filebot.ResourceManager; -import net.sourceforge.filebot.ui.panel.rename.entry.FileEntry; -import net.sourceforge.filebot.ui.panel.rename.entry.ListEntry; -import net.sourceforge.filebot.ui.panel.rename.matcher.Match; -import net.sourceforge.filebot.ui.panel.rename.matcher.Matcher; -import net.sourceforge.filebot.ui.panel.rename.metric.CompositeSimilarityMetric; -import net.sourceforge.filebot.ui.panel.rename.metric.NumericSimilarityMetric; -import net.sourceforge.filebot.ui.panel.rename.metric.SimilarityMetric; -import net.sourceforge.tuned.ui.SwingWorkerProgressMonitor; +import net.sourceforge.filebot.similarity.LengthEqualsMetric; +import net.sourceforge.filebot.similarity.Match; +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.tuned.ui.ProgressDialog; +import net.sourceforge.tuned.ui.SwingWorkerPropertyChangeAdapter; +import net.sourceforge.tuned.ui.ProgressDialog.Cancellable; class MatchAction extends AbstractAction { - private CompositeSimilarityMetric metrics; + private final List namesModel; + private final List filesModel; - private final RenameList namesList; - private final RenameList filesList; - - private boolean matchName2File; - - public static final String MATCH_NAMES_2_FILES_DESCRIPTION = "Match names to files"; - public static final String MATCH_FILES_2_NAMES_DESCRIPTION = "Match files to names"; + private final SimilarityMetric[] metrics; - public MatchAction(RenameList namesList, RenameList filesList) { - super("Match"); + public MatchAction(List namesModel, List filesModel) { + super("Match", ResourceManager.getIcon("action.match")); - this.namesList = namesList; - this.filesList = filesList; + putValue(SHORT_DESCRIPTION, "Match names to files"); - // length similarity will only effect torrent <-> file matches - metrics = new CompositeSimilarityMetric(new NumericSimilarityMetric()); + this.namesModel = namesModel; + this.filesModel = filesModel; - setMatchName2File(true); + metrics = new SimilarityMetric[3]; + + // 1. pass: match by file length (fast, but only works when matching torrents or files) + metrics[0] = new LengthEqualsMetric() { + + @Override + protected long getLength(Object o) { + if (o instanceof AbstractFileEntry) { + return ((AbstractFileEntry) o).getLength(); + } + + return super.getLength(o); + } + }; + + // 2. pass: match by season / episode numbers, or generic numeric similarity + metrics[1] = new SeasonEpisodeSimilarityMetric(); + + // 3. pass: match by generic name similarity (slow, but most matches will have been determined in second pass) + metrics[2] = new NameSimilarityMetric(); } - public void setMatchName2File(boolean matchName2File) { - this.matchName2File = matchName2File; - - if (matchName2File) { - putValue(SMALL_ICON, ResourceManager.getIcon("action.match.name2file")); - putValue(SHORT_DESCRIPTION, MATCH_NAMES_2_FILES_DESCRIPTION); - } else { - putValue(SMALL_ICON, ResourceManager.getIcon("action.match.file2name")); - putValue(SHORT_DESCRIPTION, MATCH_FILES_2_NAMES_DESCRIPTION); - } - } - - - public CompositeSimilarityMetric getMetrics() { - return metrics; - } - - - public boolean isMatchName2File() { - return matchName2File; - } - - - @SuppressWarnings("unchecked") public void actionPerformed(ActionEvent evt) { - JComponent source = (JComponent) evt.getSource(); + JComponent eventSource = (JComponent) evt.getSource(); - SwingUtilities.getRoot(source).setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - - RenameList primaryList = (RenameList) (matchName2File ? namesList : filesList); - RenameList secondaryList = (RenameList) (matchName2File ? filesList : namesList); - - BackgroundMatcher backgroundMatcher = new BackgroundMatcher(primaryList, secondaryList, metrics); - SwingWorkerProgressMonitor monitor = new SwingWorkerProgressMonitor(SwingUtilities.getWindowAncestor(source), backgroundMatcher, (Icon) getValue(SMALL_ICON)); + SwingUtilities.getRoot(eventSource).setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + BackgroundMatcher backgroundMatcher = new BackgroundMatcher(namesModel, filesModel, Arrays.asList(metrics)); backgroundMatcher.execute(); try { - // wait a for little while (matcher might finish within a few seconds) - backgroundMatcher.get(monitor.getMillisToPopup(), TimeUnit.MILLISECONDS); + // wait a for little while (matcher might finish in less than a second) + backgroundMatcher.get(2, TimeUnit.SECONDS); } catch (TimeoutException ex) { - // matcher will take longer, stop blocking EDT - monitor.getProgressDialog().setVisible(true); + // matcher will probably take a while + ProgressDialog progressDialog = createProgressDialog(SwingUtilities.getWindowAncestor(eventSource), backgroundMatcher); + + // display progress dialog and stop blocking EDT + progressDialog.setVisible(true); } catch (Exception e) { Logger.getLogger("global").log(Level.SEVERE, e.toString(), e); } - SwingUtilities.getRoot(source).setCursor(Cursor.getDefaultCursor()); + SwingUtilities.getRoot(eventSource).setCursor(Cursor.getDefaultCursor()); + } + + + protected ProgressDialog createProgressDialog(Window parent, final BackgroundMatcher worker) { + final ProgressDialog progressDialog = new ProgressDialog(parent, worker); + + // configure dialog + progressDialog.setTitle("Matching..."); + progressDialog.setNote("Processing..."); + progressDialog.setIcon((Icon) getValue(SMALL_ICON)); + + // close progress dialog when worker is finished + worker.addPropertyChangeListener(new SwingWorkerPropertyChangeAdapter() { + + @Override + protected void done(PropertyChangeEvent evt) { + progressDialog.close(); + } + }); + + return progressDialog; } - private static class BackgroundMatcher extends SwingWorker, Void> { + protected static class BackgroundMatcher extends SwingWorker>, Void> implements Cancellable { - private final RenameList primaryList; - private final RenameList secondaryList; + private final List namesModel; + private final List filesModel; - private final Matcher matcher; + private final Matcher matcher; - public BackgroundMatcher(RenameList primaryList, RenameList secondaryList, SimilarityMetric similarityMetric) { - this.primaryList = primaryList; - this.secondaryList = secondaryList; + public BackgroundMatcher(List namesModel, List filesModel, List metrics) { + this.namesModel = namesModel; + this.filesModel = filesModel; - matcher = new Matcher(primaryList.getEntries(), secondaryList.getEntries(), similarityMetric); + this.matcher = new Matcher(namesModel, filesModel, metrics); } @Override - protected List doInBackground() throws Exception { - - firePropertyChange(SwingWorkerProgressMonitor.PROPERTY_TITLE, null, "Matching..."); - - int total = matcher.remainingMatches(); - - List matches = new ArrayList(total); - - while (matcher.hasNext() && !isCancelled()) { - firePropertyChange(SwingWorkerProgressMonitor.PROPERTY_NOTE, null, getNote()); - - matches.add(matcher.next()); - - setProgress((matches.size() * 100) / total); - firePropertyChange(SwingWorkerProgressMonitor.PROPERTY_PROGRESS_STRING, null, String.format("%d / %d", matches.size(), total)); - } - - return matches; - } - - - private String getNote() { - ListEntry current = matcher.getFirstPrimaryEntry(); - - if (current == null) - current = matcher.getFirstSecondaryEntry(); - - if (current == null) - return ""; - - return current.getName(); + protected List> doInBackground() throws Exception { + return matcher.match(); } @@ -162,23 +144,29 @@ class MatchAction extends AbstractAction { return; try { - List matches = get(); + List> matches = get(); - primaryList.getModel().clear(); - secondaryList.getModel().clear(); - for (Match match : matches) { - primaryList.getModel().add(match.getA()); - secondaryList.getModel().add(match.getB()); + namesModel.clear(); + filesModel.clear(); + + for (Match match : matches) { + namesModel.add(match.getValue()); + filesModel.add(match.getCandidate()); } - primaryList.getModel().addAll(matcher.getPrimaryList()); - secondaryList.getModel().addAll(matcher.getSecondaryList()); - + namesModel.addAll(matcher.remainingValues()); + namesModel.addAll(matcher.remainingCandidates()); } catch (Exception e) { Logger.getLogger("global").log(Level.SEVERE, e.toString(), e); } } + + @Override + public boolean cancel() { + return cancel(true); + } + } } diff --git a/source/net/sourceforge/filebot/ui/panel/rename/NamesListTransferablePolicy.java b/source/net/sourceforge/filebot/ui/panel/rename/NamesListTransferablePolicy.java index 7eb7da14..84b3bed3 100644 --- a/source/net/sourceforge/filebot/ui/panel/rename/NamesListTransferablePolicy.java +++ b/source/net/sourceforge/filebot/ui/panel/rename/NamesListTransferablePolicy.java @@ -2,39 +2,34 @@ 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.isInvalidFileName; +import static net.sourceforge.tuned.FileUtil.getNameWithoutExtension; import java.awt.datatransfer.Transferable; -import java.io.BufferedReader; +import java.awt.datatransfer.UnsupportedFlavorException; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; -import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; +import java.util.Scanner; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.SwingUtilities; import net.sourceforge.filebot.torrent.Torrent; -import net.sourceforge.filebot.ui.panel.rename.entry.ListEntry; -import net.sourceforge.filebot.ui.panel.rename.entry.StringEntry; -import net.sourceforge.filebot.ui.panel.rename.entry.TorrentEntry; -import net.sourceforge.filebot.ui.transfer.StringTransferablePolicy; class NamesListTransferablePolicy extends FilesListTransferablePolicy { - private final RenameList list; - - private final TextPolicy textPolicy = new TextPolicy(); + private final RenameList list; - public NamesListTransferablePolicy(RenameList list) { + public NamesListTransferablePolicy(RenameList list) { super(list.getModel()); this.list = list; @@ -43,24 +38,39 @@ class NamesListTransferablePolicy extends FilesListTransferablePolicy { @Override public boolean accept(Transferable tr) { - return textPolicy.accept(tr) || super.accept(tr); + return tr.isDataFlavorSupported(stringFlavor) || super.accept(tr); } @Override public void handleTransferable(Transferable tr, TransferAction action) { - if (super.accept(tr)) - super.handleTransferable(tr, action); - else if (textPolicy.accept(tr)) - textPolicy.handleTransferable(tr, action); + if (action == TransferAction.PUT) { + clear(); + } + + if (tr.isDataFlavorSupported(stringFlavor)) { + // string transferable + try { + load((String) tr.getTransferData(stringFlavor)); + } catch (UnsupportedFlavorException e) { + // should not happen + throw new RuntimeException(e); + } catch (IOException e) { + // should not happen + throw new RuntimeException(e); + } + } else if (super.accept(tr)) { + // file transferable + load(getFilesFromTransferable(tr)); + } } - private void submit(List entries) { - List invalidEntries = new ArrayList(); + protected void submit(List entries) { + List invalidEntries = new ArrayList(); - for (ListEntry entry : entries) { - if (isInvalidFileName(entry.getName())) + for (StringEntry entry : entries) { + if (isInvalidFileName(entry.getValue())) invalidEntries.add(entry); } @@ -68,17 +78,35 @@ class NamesListTransferablePolicy extends FilesListTransferablePolicy { ValidateNamesDialog dialog = new ValidateNamesDialog(SwingUtilities.getWindowAncestor(list), invalidEntries); dialog.setVisible(true); - if (dialog.isCancelled()) + if (dialog.isCancelled()) { + // return immediately, don't add items to list return; + } } list.getModel().addAll(entries); } + protected void load(String string) { + List entries = new ArrayList(); + + Scanner scanner = new Scanner(string).useDelimiter(LINE_SEPARATOR); + + while (scanner.hasNext()) { + String line = scanner.next(); + + if (line.trim().length() > 0) { + entries.add(new StringEntry(line)); + } + } + + submit(entries); + } + + @Override protected void load(List files) { - if (containsOnly(files, LIST_FILE_EXTENSIONS)) { loadListFiles(files); } else if (containsOnly(files, TORRENT_FILE_EXTENSIONS)) { @@ -91,20 +119,20 @@ class NamesListTransferablePolicy extends FilesListTransferablePolicy { private void loadListFiles(List files) { try { - List entries = new ArrayList(); + List entries = new ArrayList(); for (File file : files) { - BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8")); + Scanner scanner = new Scanner(file, "UTF-8").useDelimiter(LINE_SEPARATOR); - String line = null; - - while ((line = in.readLine()) != null) { + while (scanner.hasNext()) { + String line = scanner.next(); + if (line.trim().length() > 0) { entries.add(new StringEntry(line)); } } - in.close(); + scanner.close(); } submit(entries); @@ -116,17 +144,18 @@ class NamesListTransferablePolicy extends FilesListTransferablePolicy { private void loadTorrentFiles(List files) { try { - List entries = new ArrayList(); + List entries = new ArrayList(); for (File file : files) { Torrent torrent = new Torrent(file); for (Torrent.Entry entry : torrent.getFiles()) { - entries.add(new TorrentEntry(entry)); + entries.add(new AbstractFileEntry(getNameWithoutExtension(entry.getName()), entry.getLength())); } } - submit(entries); + // add torrent entries directly without checking file names for invalid characters + list.getModel().addAll(entries); } catch (IOException e) { Logger.getLogger("global").log(Level.SEVERE, e.toString(), e); } @@ -138,32 +167,4 @@ class NamesListTransferablePolicy extends FilesListTransferablePolicy { return "text files and torrent files"; } - - private class TextPolicy extends StringTransferablePolicy { - - @Override - protected void clear() { - NamesListTransferablePolicy.this.clear(); - } - - - @Override - protected void load(String string) { - List entries = new ArrayList(); - - String[] lines = string.split("\r?\n"); - - for (String line : lines) { - - if (!line.isEmpty()) - entries.add(new StringEntry(line)); - } - - if (!entries.isEmpty()) { - submit(entries); - } - } - - } - } diff --git a/source/net/sourceforge/filebot/ui/panel/rename/RenameAction.java b/source/net/sourceforge/filebot/ui/panel/rename/RenameAction.java index 4fa85a7e..5570480b 100644 --- a/source/net/sourceforge/filebot/ui/panel/rename/RenameAction.java +++ b/source/net/sourceforge/filebot/ui/panel/rename/RenameAction.java @@ -4,63 +4,84 @@ package net.sourceforge.filebot.ui.panel.rename; import java.awt.event.ActionEvent; import java.io.File; +import java.io.IOException; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.Iterator; import java.util.List; import java.util.logging.Logger; import javax.swing.AbstractAction; -import javax.swing.Action; import net.sourceforge.filebot.ResourceManager; -import net.sourceforge.filebot.ui.panel.rename.entry.FileEntry; -import net.sourceforge.filebot.ui.panel.rename.entry.ListEntry; +import net.sourceforge.filebot.similarity.Match; import net.sourceforge.tuned.FileUtil; public class RenameAction extends AbstractAction { - private final RenameList namesList; - private final RenameList filesList; + private final List namesModel; + private final List filesModel; - public RenameAction(RenameList namesList, RenameList filesList) { + public RenameAction(List namesModel, List filesModel) { super("Rename", ResourceManager.getIcon("action.rename")); - this.namesList = namesList; - this.filesList = filesList; - putValue(Action.SHORT_DESCRIPTION, "Rename files"); + putValue(SHORT_DESCRIPTION, "Rename files"); + + this.namesModel = namesModel; + this.filesModel = filesModel; } - public void actionPerformed(ActionEvent e) { - List nameEntries = namesList.getEntries(); - List fileEntries = filesList.getEntries(); + public void actionPerformed(ActionEvent evt) { - int minLength = Math.min(nameEntries.size(), fileEntries.size()); + Deque> renameMatches = new ArrayDeque>(); + Deque> revertMatches = new ArrayDeque>(); - int i = 0; - int errors = 0; + Iterator names = namesModel.iterator(); + Iterator files = filesModel.iterator(); - for (i = 0; i < minLength; i++) { - FileEntry fileEntry = fileEntries.get(i); - File f = fileEntry.getFile(); + while (names.hasNext() && files.hasNext()) { + File source = files.next().getFile(); - String newName = nameEntries.get(i).toString() + FileUtil.getExtension(f, true); + String targetName = names.next().toString() + FileUtil.getExtension(source, true); + File target = new File(source.getParentFile(), targetName); - File newFile = new File(f.getParentFile(), newName); + renameMatches.addLast(new Match(source, target)); + } + + try { + int renameCount = renameMatches.size(); - if (f.renameTo(newFile)) { - filesList.getModel().remove(fileEntry); - } else { - errors++; + for (Match match : renameMatches) { + // rename file + if (!match.getValue().renameTo(match.getCandidate())) + throw new IOException(String.format("Failed to rename file: %s.", match.getValue().getName())); + + // revert in reverse order if renaming of all matches fails + revertMatches.addFirst(match); + } + + // renamed all matches successfully + Logger.getLogger("ui").info(String.format("%d files renamed.", renameCount)); + } catch (IOException e) { + // rename failed + Logger.getLogger("ui").warning(e.getMessage()); + + boolean revertFailed = false; + + // revert rename operations + for (Match match : revertMatches) { + if (!match.getCandidate().renameTo(match.getValue())) { + revertFailed = true; + } + } + + if (revertFailed) { + Logger.getLogger("ui").severe("Failed to revert all rename operations."); } } - if (errors > 0) - Logger.getLogger("ui").info(String.format("%d of %d files renamed.", i - errors, i)); - else - Logger.getLogger("ui").info(String.format("%d files renamed.", i)); - - namesList.repaint(); - filesList.repaint(); } } diff --git a/source/net/sourceforge/filebot/ui/panel/rename/RenameList.java b/source/net/sourceforge/filebot/ui/panel/rename/RenameList.java index fe46bf1f..3204c571 100644 --- a/source/net/sourceforge/filebot/ui/panel/rename/RenameList.java +++ b/source/net/sourceforge/filebot/ui/panel/rename/RenameList.java @@ -18,12 +18,11 @@ import javax.swing.ListSelectionModel; import net.miginfocom.swing.MigLayout; import net.sourceforge.filebot.ResourceManager; import net.sourceforge.filebot.ui.FileBotList; -import net.sourceforge.filebot.ui.panel.rename.entry.ListEntry; import net.sourceforge.filebot.ui.transfer.LoadAction; import net.sourceforge.filebot.ui.transfer.TransferablePolicy; -class RenameList extends FileBotList { +class RenameList extends FileBotList { private JButton loadButton = new JButton(); diff --git a/source/net/sourceforge/filebot/ui/panel/rename/RenameListCellRenderer.java b/source/net/sourceforge/filebot/ui/panel/rename/RenameListCellRenderer.java index 53209348..692ecbaa 100644 --- a/source/net/sourceforge/filebot/ui/panel/rename/RenameListCellRenderer.java +++ b/source/net/sourceforge/filebot/ui/panel/rename/RenameListCellRenderer.java @@ -19,7 +19,7 @@ import javax.swing.ListModel; import javax.swing.border.CompoundBorder; import javax.swing.border.EmptyBorder; -import net.sourceforge.filebot.ui.panel.rename.entry.FileEntry; + import net.sourceforge.tuned.ui.DefaultFancyListCellRenderer; diff --git a/source/net/sourceforge/filebot/ui/panel/rename/RenamePanel.java b/source/net/sourceforge/filebot/ui/panel/rename/RenamePanel.java index 410a7c0d..53379d2c 100644 --- a/source/net/sourceforge/filebot/ui/panel/rename/RenamePanel.java +++ b/source/net/sourceforge/filebot/ui/panel/rename/RenamePanel.java @@ -2,44 +2,30 @@ package net.sourceforge.filebot.ui.panel.rename; -import java.awt.Font; import java.awt.Insets; -import java.awt.event.ActionEvent; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import javax.swing.AbstractAction; import javax.swing.DefaultListSelectionModel; import javax.swing.JButton; import javax.swing.JList; -import javax.swing.JMenuItem; -import javax.swing.JPopupMenu; import javax.swing.JViewport; import javax.swing.ListSelectionModel; import javax.swing.SwingConstants; -import javax.swing.SwingUtilities; -import javax.swing.event.ListDataEvent; -import javax.swing.event.ListDataListener; import net.miginfocom.swing.MigLayout; import net.sourceforge.filebot.ResourceManager; import net.sourceforge.filebot.ui.FileBotPanel; -import net.sourceforge.filebot.ui.panel.rename.entry.FileEntry; -import net.sourceforge.filebot.ui.panel.rename.entry.ListEntry; +import ca.odell.glazedlists.event.ListEvent; +import ca.odell.glazedlists.event.ListEventListener; public class RenamePanel extends FileBotPanel { - private RenameList namesList = new RenameList(); + private RenameList namesList = new RenameList(); private RenameList filesList = new RenameList(); - private MatchAction matchAction = new MatchAction(namesList, filesList); + private MatchAction matchAction = new MatchAction(namesList.getModel(), filesList.getModel()); - private RenameAction renameAction = new RenameAction(namesList, filesList); - - private SimilarityPanel similarityPanel; - - private ViewPortSynchronizer viewPortSynchroniser; + private RenameAction renameAction = new RenameAction(namesList.getModel(), filesList.getModel()); public RenamePanel() { @@ -65,16 +51,11 @@ public class RenamePanel extends FileBotPanel { namesListComponent.setSelectionModel(selectionModel); filesListComponent.setSelectionModel(selectionModel); - viewPortSynchroniser = new ViewPortSynchronizer((JViewport) namesListComponent.getParent(), (JViewport) filesListComponent.getParent()); - - similarityPanel = new SimilarityPanel(namesListComponent, filesListComponent); - - similarityPanel.setVisible(false); - similarityPanel.setMetrics(matchAction.getMetrics()); + // synchronize viewports + new ViewPortSynchronizer((JViewport) namesListComponent.getParent(), (JViewport) filesListComponent.getParent()); // create Match button JButton matchButton = new JButton(matchAction); - matchButton.addMouseListener(new MatchPopupListener()); matchButton.setVerticalTextPosition(SwingConstants.BOTTOM); matchButton.setHorizontalTextPosition(SwingConstants.CENTER); @@ -96,123 +77,19 @@ public class RenamePanel extends FileBotPanel { add(filesList, "grow"); - namesListComponent.getModel().addListDataListener(repaintOnDataChange); - filesListComponent.getModel().addListDataListener(repaintOnDataChange); + namesList.getModel().addListEventListener(new RepaintHandler()); + filesList.getModel().addListEventListener(new RepaintHandler()); } - private final ListDataListener repaintOnDataChange = new ListDataListener() { + + private class RepaintHandler implements ListEventListener { - public void contentsChanged(ListDataEvent e) { - repaintBoth(); - } - - - public void intervalAdded(ListDataEvent e) { - repaintBoth(); - } - - - public void intervalRemoved(ListDataEvent e) { - repaintBoth(); - } - - - private void repaintBoth() { + @Override + public void listChanged(ListEvent listChanges) { namesList.repaint(); filesList.repaint(); } }; - - private class MatcherSelectPopup extends JPopupMenu { - - public MatcherSelectPopup() { - JMenuItem names2files = new JMenuItem(new SetNames2FilesAction(true)); - JMenuItem files2names = new JMenuItem(new SetNames2FilesAction(false)); - - if (matchAction.isMatchName2File()) - highlight(names2files); - else - highlight(files2names); - - add(names2files); - add(files2names); - - addSeparator(); - add(new ToggleSimilarityAction(!similarityPanel.isVisible())); - } - - - public void highlight(JMenuItem item) { - item.setFont(item.getFont().deriveFont(Font.BOLD)); - } - - - private class SetNames2FilesAction extends AbstractAction { - - private boolean names2files; - - - public SetNames2FilesAction(boolean names2files) { - this.names2files = names2files; - - if (names2files) { - putValue(SMALL_ICON, ResourceManager.getIcon("action.match.name2file")); - putValue(NAME, MatchAction.MATCH_NAMES_2_FILES_DESCRIPTION); - } else { - putValue(SMALL_ICON, ResourceManager.getIcon("action.match.file2name")); - putValue(NAME, MatchAction.MATCH_FILES_2_NAMES_DESCRIPTION); - } - } - - - public void actionPerformed(ActionEvent e) { - matchAction.setMatchName2File(names2files); - } - } - - - private class ToggleSimilarityAction extends AbstractAction { - - private boolean showSimilarityPanel; - - - public ToggleSimilarityAction(boolean showSimilarityPanel) { - this.showSimilarityPanel = showSimilarityPanel; - - if (showSimilarityPanel) { - putValue(NAME, "Show Similarity"); - } else { - putValue(NAME, "Hide Similarity"); - } - } - - - public void actionPerformed(ActionEvent e) { - if (showSimilarityPanel) { - viewPortSynchroniser.setEnabled(false); - similarityPanel.hook(); - similarityPanel.setVisible(true); - } else { - similarityPanel.setVisible(false); - similarityPanel.unhook(); - viewPortSynchroniser.setEnabled(true); - } - } - } - } - - - private class MatchPopupListener extends MouseAdapter { - - @Override - public void mouseReleased(MouseEvent e) { - if (SwingUtilities.isRightMouseButton(e)) { - MatcherSelectPopup popup = new MatcherSelectPopup(); - popup.show(e.getComponent(), e.getX(), e.getY()); - } - } - } - } diff --git a/source/net/sourceforge/filebot/ui/panel/rename/SimilarityPanel.java b/source/net/sourceforge/filebot/ui/panel/rename/SimilarityPanel.java deleted file mode 100644 index ebcd5b55..00000000 --- a/source/net/sourceforge/filebot/ui/panel/rename/SimilarityPanel.java +++ /dev/null @@ -1,183 +0,0 @@ - -package net.sourceforge.filebot.ui.panel.rename; - - -import java.awt.Color; -import java.awt.GridLayout; -import java.text.NumberFormat; -import java.util.ArrayList; -import java.util.List; - -import javax.swing.BorderFactory; -import javax.swing.Box; -import javax.swing.BoxLayout; -import javax.swing.JLabel; -import javax.swing.JList; -import javax.swing.JPanel; -import javax.swing.border.Border; -import javax.swing.event.ListSelectionEvent; -import javax.swing.event.ListSelectionListener; - -import net.sourceforge.filebot.ui.panel.rename.entry.ListEntry; -import net.sourceforge.filebot.ui.panel.rename.metric.CompositeSimilarityMetric; -import net.sourceforge.filebot.ui.panel.rename.metric.SimilarityMetric; -import net.sourceforge.tuned.ui.notification.SeparatorBorder; - - -class SimilarityPanel extends Box { - - private JPanel grid = new JPanel(new GridLayout(0, 2, 25, 1)); - - private JList nameList; - - private JList fileList; - - private UpdateMetricsListener updateMetricsListener = new UpdateMetricsListener(); - - private NumberFormat numberFormat = NumberFormat.getNumberInstance(); - - private List updaterList = new ArrayList(); - - private Border labelMarginBorder = BorderFactory.createEmptyBorder(0, 3, 0, 0); - - private Border separatorBorder = new SeparatorBorder(1, new Color(0xACA899), SeparatorBorder.Position.TOP); - - - public SimilarityPanel(JList nameList, JList fileList) { - super(BoxLayout.PAGE_AXIS); - - this.nameList = nameList; - this.fileList = fileList; - - numberFormat.setMinimumFractionDigits(2); - numberFormat.setMaximumFractionDigits(2); - - Box subBox = Box.createVerticalBox(); - - add(subBox); - add(Box.createVerticalStrut(15)); - - subBox.add(grid); - - subBox.setBorder(BorderFactory.createTitledBorder("Similarity")); - - Border pane = BorderFactory.createLineBorder(Color.LIGHT_GRAY); - Border margin = BorderFactory.createEmptyBorder(5, 5, 5, 5); - - grid.setBorder(BorderFactory.createCompoundBorder(pane, margin)); - grid.setBackground(Color.WHITE); - grid.setOpaque(true); - } - - - public void setMetrics(CompositeSimilarityMetric metrics) { - grid.removeAll(); - updaterList.clear(); - - for (SimilarityMetric metric : metrics) { - JLabel name = new JLabel(metric.getName()); - name.setToolTipText(metric.getDescription()); - - JLabel value = new JLabel(); - - name.setBorder(labelMarginBorder); - value.setBorder(labelMarginBorder); - - MetricUpdater updater = new MetricUpdater(value, metric); - updaterList.add(updater); - - grid.add(name); - grid.add(value); - } - - JLabel name = new JLabel(metrics.getName()); - - JLabel value = new JLabel(); - - MetricUpdater updater = new MetricUpdater(value, metrics); - updaterList.add(updater); - - Border border = BorderFactory.createCompoundBorder(separatorBorder, labelMarginBorder); - name.setBorder(border); - value.setBorder(border); - - grid.add(name); - grid.add(value); - } - - - public void hook() { - updateMetrics(); - nameList.addListSelectionListener(updateMetricsListener); - fileList.addListSelectionListener(updateMetricsListener); - } - - - public void unhook() { - nameList.removeListSelectionListener(updateMetricsListener); - fileList.removeListSelectionListener(updateMetricsListener); - } - - private ListEntry lastListEntryA = null; - - private ListEntry lastListEntryB = null; - - - public void updateMetrics() { - ListEntry a = (ListEntry) nameList.getSelectedValue(); - ListEntry b = (ListEntry) fileList.getSelectedValue(); - - if ((a == lastListEntryA) && (b == lastListEntryB)) - return; - - lastListEntryA = a; - lastListEntryB = b; - - boolean reset = ((a == null) || (b == null)); - - for (MetricUpdater updater : updaterList) { - if (!reset) - updater.update(a, b); - else - updater.reset(); - } - } - - - private class UpdateMetricsListener implements ListSelectionListener { - - public void valueChanged(ListSelectionEvent e) { - if (e.getValueIsAdjusting()) - return; - - updateMetrics(); - } - } - - - private class MetricUpdater { - - private JLabel value; - - private SimilarityMetric metric; - - - public MetricUpdater(JLabel value, SimilarityMetric metric) { - this.value = value; - this.metric = metric; - - reset(); - } - - - public void update(ListEntry a, ListEntry b) { - value.setText(numberFormat.format(metric.getSimilarity(a, b))); - } - - - public void reset() { - value.setText(numberFormat.format(0)); - } - } - -} diff --git a/source/net/sourceforge/filebot/ui/panel/rename/StringEntry.java b/source/net/sourceforge/filebot/ui/panel/rename/StringEntry.java new file mode 100644 index 00000000..2160e434 --- /dev/null +++ b/source/net/sourceforge/filebot/ui/panel/rename/StringEntry.java @@ -0,0 +1,30 @@ + +package net.sourceforge.filebot.ui.panel.rename; + + +public class StringEntry { + + private String value; + + + public StringEntry(String value) { + this.value = value; + } + + + public String getValue() { + return value; + } + + + public void setValue(String value) { + this.value = value; + } + + + @Override + public String toString() { + return getValue(); + } + +} diff --git a/source/net/sourceforge/filebot/ui/panel/rename/ValidateNamesDialog.java b/source/net/sourceforge/filebot/ui/panel/rename/ValidateNamesDialog.java index df8d4e41..4eaf30ce 100644 --- a/source/net/sourceforge/filebot/ui/panel/rename/ValidateNamesDialog.java +++ b/source/net/sourceforge/filebot/ui/panel/rename/ValidateNamesDialog.java @@ -25,23 +25,22 @@ import javax.swing.KeyStroke; import net.miginfocom.swing.MigLayout; import net.sourceforge.filebot.ResourceManager; -import net.sourceforge.filebot.ui.panel.rename.entry.ListEntry; import net.sourceforge.tuned.ui.ArrayListModel; import net.sourceforge.tuned.ui.TunedUtil; public class ValidateNamesDialog extends JDialog { - private final Collection entries; + private final Collection entries; private boolean cancelled = true; - private final ValidateAction validateAction = new ValidateAction(); - private final ContinueAction continueAction = new ContinueAction(); - private final CancelAction cancelAction = new CancelAction(); + protected final Action validateAction = new ValidateAction(); + protected final Action continueAction = new ContinueAction(); + protected final Action cancelAction = new CancelAction(); - public ValidateNamesDialog(Window owner, Collection entries) { + public ValidateNamesDialog(Window owner, Collection entries) { super(owner, "Invalid Names", ModalityType.DOCUMENT_MODAL); this.entries = entries; @@ -95,8 +94,8 @@ public class ValidateNamesDialog extends JDialog { @Override public void actionPerformed(ActionEvent e) { - for (ListEntry entry : entries) { - entry.setName(validateFileName(entry.getName())); + for (StringEntry entry : entries) { + entry.setValue(validateFileName(entry.getValue())); } setEnabled(false); @@ -127,7 +126,7 @@ public class ValidateNamesDialog extends JDialog { }; - private class CancelAction extends AbstractAction { + protected class CancelAction extends AbstractAction { public CancelAction() { super("Cancel", ResourceManager.getIcon("dialog.cancel")); @@ -140,7 +139,7 @@ public class ValidateNamesDialog extends JDialog { }; - private static class AlphaButton extends JButton { + protected static class AlphaButton extends JButton { private float alpha; diff --git a/source/net/sourceforge/filebot/ui/panel/rename/entry/AbstractFileEntry.java b/source/net/sourceforge/filebot/ui/panel/rename/entry/AbstractFileEntry.java deleted file mode 100644 index 0ae8bb5c..00000000 --- a/source/net/sourceforge/filebot/ui/panel/rename/entry/AbstractFileEntry.java +++ /dev/null @@ -1,14 +0,0 @@ - -package net.sourceforge.filebot.ui.panel.rename.entry; - - -public abstract class AbstractFileEntry extends ListEntry { - - public AbstractFileEntry(String name) { - super(name); - } - - - public abstract long getLength(); - -} diff --git a/source/net/sourceforge/filebot/ui/panel/rename/entry/ListEntry.java b/source/net/sourceforge/filebot/ui/panel/rename/entry/ListEntry.java deleted file mode 100644 index 532c86a5..00000000 --- a/source/net/sourceforge/filebot/ui/panel/rename/entry/ListEntry.java +++ /dev/null @@ -1,29 +0,0 @@ - -package net.sourceforge.filebot.ui.panel.rename.entry; - - -public class ListEntry { - - private String name; - - - public ListEntry(String name) { - this.name = name; - } - - - public String getName() { - return name; - } - - - public void setName(String name) { - this.name = name; - } - - - @Override - public String toString() { - return getName(); - } -} diff --git a/source/net/sourceforge/filebot/ui/panel/rename/entry/StringEntry.java b/source/net/sourceforge/filebot/ui/panel/rename/entry/StringEntry.java deleted file mode 100644 index 5ed4418d..00000000 --- a/source/net/sourceforge/filebot/ui/panel/rename/entry/StringEntry.java +++ /dev/null @@ -1,11 +0,0 @@ - -package net.sourceforge.filebot.ui.panel.rename.entry; - - -public class StringEntry extends ListEntry { - - public StringEntry(String string) { - super(string); - } - -} diff --git a/source/net/sourceforge/filebot/ui/panel/rename/entry/TorrentEntry.java b/source/net/sourceforge/filebot/ui/panel/rename/entry/TorrentEntry.java deleted file mode 100644 index 494a3d9c..00000000 --- a/source/net/sourceforge/filebot/ui/panel/rename/entry/TorrentEntry.java +++ /dev/null @@ -1,26 +0,0 @@ - -package net.sourceforge.filebot.ui.panel.rename.entry; - - -import net.sourceforge.filebot.torrent.Torrent.Entry; -import net.sourceforge.tuned.FileUtil; - - -public class TorrentEntry extends AbstractFileEntry { - - private final Entry entry; - - - public TorrentEntry(Entry entry) { - super(FileUtil.getNameWithoutExtension(entry.getName())); - - this.entry = entry; - } - - - @Override - public long getLength() { - return entry.getLength(); - } - -} diff --git a/source/net/sourceforge/filebot/ui/panel/rename/matcher/Match.java b/source/net/sourceforge/filebot/ui/panel/rename/matcher/Match.java deleted file mode 100644 index 0e4bbe44..00000000 --- a/source/net/sourceforge/filebot/ui/panel/rename/matcher/Match.java +++ /dev/null @@ -1,29 +0,0 @@ - -package net.sourceforge.filebot.ui.panel.rename.matcher; - - -import net.sourceforge.filebot.ui.panel.rename.entry.ListEntry; - - -public class Match { - - private final ListEntry a; - private final ListEntry b; - - - public Match(ListEntry a, ListEntry b) { - this.a = a; - this.b = b; - } - - - public ListEntry getA() { - return a; - } - - - public ListEntry getB() { - return b; - } - -} diff --git a/source/net/sourceforge/filebot/ui/panel/rename/matcher/Matcher.java b/source/net/sourceforge/filebot/ui/panel/rename/matcher/Matcher.java deleted file mode 100644 index 8e3be765..00000000 --- a/source/net/sourceforge/filebot/ui/panel/rename/matcher/Matcher.java +++ /dev/null @@ -1,99 +0,0 @@ - -package net.sourceforge.filebot.ui.panel.rename.matcher; - - -import java.util.Collections; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; - -import net.sourceforge.filebot.ui.panel.rename.entry.ListEntry; -import net.sourceforge.filebot.ui.panel.rename.metric.SimilarityMetric; - - -public class Matcher implements Iterator { - - private final LinkedList primaryList; - private final LinkedList secondaryList; - private final SimilarityMetric similarityMetric; - - - public Matcher(List primaryList, List secondaryList, SimilarityMetric similarityMetric) { - this.primaryList = new LinkedList(primaryList); - this.secondaryList = new LinkedList(secondaryList); - this.similarityMetric = similarityMetric; - } - - - @Override - public boolean hasNext() { - return remainingMatches() > 0; - } - - - @Override - public Match next() { - ListEntry primaryEntry = primaryList.removeFirst(); - - float maxSimilarity = -1; - ListEntry mostSimilarSecondaryEntry = null; - - for (ListEntry secondaryEntry : secondaryList) { - float similarity = similarityMetric.getSimilarity(primaryEntry, secondaryEntry); - - if (similarity > maxSimilarity) { - maxSimilarity = similarity; - mostSimilarSecondaryEntry = secondaryEntry; - } - } - - if (mostSimilarSecondaryEntry != null) { - secondaryList.remove(mostSimilarSecondaryEntry); - } - - return new Match(primaryEntry, mostSimilarSecondaryEntry); - } - - - public ListEntry getFirstPrimaryEntry() { - if (primaryList.isEmpty()) - return null; - - return primaryList.getFirst(); - } - - - public ListEntry getFirstSecondaryEntry() { - if (secondaryList.isEmpty()) - return null; - - return secondaryList.getFirst(); - } - - - public int remainingMatches() { - return Math.min(primaryList.size(), secondaryList.size()); - } - - - public List getPrimaryList() { - return Collections.unmodifiableList(primaryList); - } - - - public List getSecondaryList() { - return Collections.unmodifiableList(secondaryList); - } - - - /** - * The remove operation is not supported by this implementation of Iterator. - * - * @throws UnsupportedOperationException if this method is invoked. - * @see java.util.Iterator - */ - @Override - public void remove() { - throw new UnsupportedOperationException(); - } -} diff --git a/source/net/sourceforge/filebot/ui/panel/rename/metric/AbstractNameSimilarityMetric.java b/source/net/sourceforge/filebot/ui/panel/rename/metric/AbstractNameSimilarityMetric.java deleted file mode 100644 index 6c565171..00000000 --- a/source/net/sourceforge/filebot/ui/panel/rename/metric/AbstractNameSimilarityMetric.java +++ /dev/null @@ -1,36 +0,0 @@ - -package net.sourceforge.filebot.ui.panel.rename.metric; - - -import net.sourceforge.filebot.ui.panel.rename.entry.ListEntry; - - -public abstract class AbstractNameSimilarityMetric implements SimilarityMetric { - - @Override - public float getSimilarity(ListEntry a, ListEntry b) { - return getSimilarity(normalize(a.getName()), normalize(b.getName())); - } - - - protected String normalize(String name) { - name = stripChecksum(name); - name = normalizeSeparators(name); - - return name.trim().toLowerCase(); - } - - - protected String normalizeSeparators(String name) { - return name.replaceAll("[\\._ ]+", " "); - } - - - protected String stripChecksum(String name) { - return name.replaceAll("\\[\\p{XDigit}{8}\\]", ""); - } - - - public abstract float getSimilarity(String a, String b); - -} diff --git a/source/net/sourceforge/filebot/ui/panel/rename/metric/CompositeSimilarityMetric.java b/source/net/sourceforge/filebot/ui/panel/rename/metric/CompositeSimilarityMetric.java deleted file mode 100644 index fe5a61a9..00000000 --- a/source/net/sourceforge/filebot/ui/panel/rename/metric/CompositeSimilarityMetric.java +++ /dev/null @@ -1,51 +0,0 @@ - -package net.sourceforge.filebot.ui.panel.rename.metric; - - -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; - -import net.sourceforge.filebot.ui.panel.rename.entry.ListEntry; - - -public class CompositeSimilarityMetric implements SimilarityMetric, Iterable { - - private List similarityMetrics; - - - public CompositeSimilarityMetric(SimilarityMetric... metrics) { - similarityMetrics = Arrays.asList(metrics); - } - - - @Override - public float getSimilarity(ListEntry a, ListEntry b) { - float similarity = 0; - - for (SimilarityMetric metric : similarityMetrics) { - similarity += metric.getSimilarity(a, b) / similarityMetrics.size(); - } - - return similarity; - } - - - @Override - public String getDescription() { - return null; - } - - - @Override - public String getName() { - return "Average"; - } - - - @Override - public Iterator iterator() { - return similarityMetrics.iterator(); - } - -} diff --git a/source/net/sourceforge/filebot/ui/panel/rename/metric/LengthEqualsMetric.java b/source/net/sourceforge/filebot/ui/panel/rename/metric/LengthEqualsMetric.java deleted file mode 100644 index a3f24a6c..00000000 --- a/source/net/sourceforge/filebot/ui/panel/rename/metric/LengthEqualsMetric.java +++ /dev/null @@ -1,36 +0,0 @@ - -package net.sourceforge.filebot.ui.panel.rename.metric; - - -import net.sourceforge.filebot.ui.panel.rename.entry.AbstractFileEntry; -import net.sourceforge.filebot.ui.panel.rename.entry.ListEntry; - - -public class LengthEqualsMetric implements SimilarityMetric { - - @Override - public float getSimilarity(ListEntry a, ListEntry b) { - if ((a instanceof AbstractFileEntry) && (b instanceof AbstractFileEntry)) { - long lengthA = ((AbstractFileEntry) a).getLength(); - long lengthB = ((AbstractFileEntry) b).getLength(); - - if (lengthA == lengthB) - return 1; - } - - return 0; - } - - - @Override - public String getDescription() { - return "Check whether file size is equal or not"; - } - - - @Override - public String getName() { - return "Length"; - } - -} diff --git a/source/net/sourceforge/filebot/ui/panel/rename/metric/SimilarityMetric.java b/source/net/sourceforge/filebot/ui/panel/rename/metric/SimilarityMetric.java deleted file mode 100644 index 45b07bf8..00000000 --- a/source/net/sourceforge/filebot/ui/panel/rename/metric/SimilarityMetric.java +++ /dev/null @@ -1,17 +0,0 @@ - -package net.sourceforge.filebot.ui.panel.rename.metric; - - -import net.sourceforge.filebot.ui.panel.rename.entry.ListEntry; - - -public interface SimilarityMetric { - - public float getSimilarity(ListEntry a, ListEntry b); - - - public String getDescription(); - - - public String getName(); -} diff --git a/source/net/sourceforge/filebot/ui/panel/rename/metric/StringSimilarityMetric.java b/source/net/sourceforge/filebot/ui/panel/rename/metric/StringSimilarityMetric.java deleted file mode 100644 index 896022d4..00000000 --- a/source/net/sourceforge/filebot/ui/panel/rename/metric/StringSimilarityMetric.java +++ /dev/null @@ -1,41 +0,0 @@ - -package net.sourceforge.filebot.ui.panel.rename.metric; - - -import uk.ac.shef.wit.simmetrics.similaritymetrics.AbstractStringMetric; -import uk.ac.shef.wit.simmetrics.similaritymetrics.MongeElkan; -import uk.ac.shef.wit.simmetrics.tokenisers.TokeniserQGram3Extended; - - -public class StringSimilarityMetric extends AbstractNameSimilarityMetric { - - private final AbstractStringMetric metric; - - - public StringSimilarityMetric() { - // I have absolutely no clue as to why, but I get a good matching behavior - // when using MongeElkan with a QGram3Extended (far from perfect though) - metric = new MongeElkan(new TokeniserQGram3Extended()); - - //TODO QGram3Extended VS Whitespace (-> normalized values) - } - - - @Override - public float getSimilarity(String a, String b) { - return metric.getSimilarity(a, b); - } - - - @Override - public String getDescription() { - return "Similarity of names"; - } - - - @Override - public String getName() { - return metric.getShortDescriptionString(); - } - -} diff --git a/source/net/sourceforge/filebot/ui/transfer/FileTransferablePolicy.java b/source/net/sourceforge/filebot/ui/transfer/FileTransferablePolicy.java index cc617a7a..79a77cbb 100644 --- a/source/net/sourceforge/filebot/ui/transfer/FileTransferablePolicy.java +++ b/source/net/sourceforge/filebot/ui/transfer/FileTransferablePolicy.java @@ -12,12 +12,20 @@ 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; import java.util.logging.Logger; +import java.util.regex.Pattern; public abstract class FileTransferablePolicy extends TransferablePolicy { + /** + * Pattern that will match Windows (\r\n), Unix (\n) and Mac (\r) line separators. + */ + public static final Pattern LINE_SEPARATOR = Pattern.compile("\r?\n|\r\n?"); + + @Override public boolean accept(Transferable tr) { List files = getFilesFromTransferable(tr); @@ -37,19 +45,22 @@ public abstract class FileTransferablePolicy extends TransferablePolicy { return (List) tr.getTransferData(DataFlavor.javaFileListFlavor); } else if (tr.isDataFlavorSupported(FileTransferable.uriListFlavor)) { // file URI list flavor - String transferString = (String) tr.getTransferData(FileTransferable.uriListFlavor); + String transferData = (String) tr.getTransferData(FileTransferable.uriListFlavor); - String lines[] = transferString.split("\r?\n"); - ArrayList files = new ArrayList(lines.length); + Scanner scanner = new Scanner(transferData).useDelimiter(LINE_SEPARATOR); - for (String line : lines) { - if (line.startsWith("#")) { - // the line is a comment (as per the RFC 2483) + ArrayList files = new ArrayList(); + + while (scanner.hasNext()) { + String uri = scanner.next(); + + if (uri.startsWith("#")) { + // the line is a comment (as per RFC 2483) continue; } try { - File file = new File(new URI(line)); + File file = new File(new URI(uri)); if (!file.exists()) throw new FileNotFoundException(file.toString()); @@ -57,7 +68,7 @@ public abstract class FileTransferablePolicy extends TransferablePolicy { files.add(file); } catch (Exception e) { // URISyntaxException, IllegalArgumentException, FileNotFoundException - Logger.getLogger("global").log(Level.WARNING, "Invalid file url: " + line); + Logger.getLogger("global").log(Level.WARNING, "Invalid file uri: " + uri); } } @@ -79,7 +90,7 @@ public abstract class FileTransferablePolicy extends TransferablePolicy { public void handleTransferable(Transferable tr, TransferAction action) { List files = getFilesFromTransferable(tr); - if (action != TransferAction.ADD) { + if (action == TransferAction.PUT) { clear(); } diff --git a/source/net/sourceforge/filebot/ui/transfer/StringTransferablePolicy.java b/source/net/sourceforge/filebot/ui/transfer/StringTransferablePolicy.java deleted file mode 100644 index 5b600b3e..00000000 --- a/source/net/sourceforge/filebot/ui/transfer/StringTransferablePolicy.java +++ /dev/null @@ -1,47 +0,0 @@ - -package net.sourceforge.filebot.ui.transfer; - - -import java.awt.datatransfer.DataFlavor; -import java.awt.datatransfer.Transferable; -import java.awt.datatransfer.UnsupportedFlavorException; -import java.io.IOException; - - -public abstract class StringTransferablePolicy extends TransferablePolicy { - - @Override - public boolean accept(Transferable tr) { - return tr.isDataFlavorSupported(DataFlavor.stringFlavor); - } - - - @Override - public void handleTransferable(Transferable tr, TransferAction action) { - String string; - - try { - string = (String) tr.getTransferData(DataFlavor.stringFlavor); - } catch (UnsupportedFlavorException e) { - // should no happen - throw new RuntimeException(e); - } catch (IOException e) { - // should no happen - throw new RuntimeException(e); - } - - if (action != TransferAction.ADD) - clear(); - - load(string); - } - - - protected void clear() { - - } - - - protected abstract void load(String string); - -} diff --git a/source/net/sourceforge/filebot/web/Episode.java b/source/net/sourceforge/filebot/web/Episode.java index 9da5bc52..edb0679a 100644 --- a/source/net/sourceforge/filebot/web/Episode.java +++ b/source/net/sourceforge/filebot/web/Episode.java @@ -70,16 +70,17 @@ public class Episode implements Serializable { public String toString() { StringBuilder sb = new StringBuilder(40); - sb.append(showName + " - "); + sb.append(showName); + sb.append(" - "); if (seasonNumber != null) sb.append(seasonNumber + "x"); sb.append(episodeNumber); - sb.append(" - " + title); + sb.append(" - "); + sb.append(title); return sb.toString(); } - } diff --git a/source/net/sourceforge/tuned/ui/ProgressDialog.java b/source/net/sourceforge/tuned/ui/ProgressDialog.java index b78b6b72..afa584ed 100644 --- a/source/net/sourceforge/tuned/ui/ProgressDialog.java +++ b/source/net/sourceforge/tuned/ui/ProgressDialog.java @@ -2,12 +2,8 @@ package net.sourceforge.tuned.ui; -import java.awt.Font; import java.awt.Window; import java.awt.event.ActionEvent; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; -import java.awt.event.WindowListener; import javax.swing.AbstractAction; import javax.swing.Action; @@ -26,33 +22,31 @@ public class ProgressDialog extends JDialog { private final JProgressBar progressBar = new JProgressBar(0, 100); private final JLabel iconLabel = new JLabel(); private final JLabel headerLabel = new JLabel(); - private final JLabel noteLabel = new JLabel(); - private final JButton cancelButton; - - private boolean cancelled = false; + private final Cancellable cancellable; - public ProgressDialog(Window owner) { + public ProgressDialog(Window owner, Cancellable cancellable) { super(owner, ModalityType.DOCUMENT_MODAL); - cancelButton = new JButton(cancelAction); + this.cancellable = cancellable; - addWindowListener(closeListener); + // disable window close button + setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); - headerLabel.setFont(headerLabel.getFont().deriveFont(Font.BOLD)); + headerLabel.setFont(headerLabel.getFont().deriveFont(18f)); + progressBar.setIndeterminate(true); progressBar.setStringPainted(true); JPanel c = (JPanel) getContentPane(); - c.setLayout(new MigLayout("insets panel, fill")); + c.setLayout(new MigLayout("insets dialog, nogrid, fill")); - c.add(iconLabel, "spany 2, grow 0 0, gap right 1mm"); - c.add(headerLabel, "align left, wmax 70%, grow 100 0, wrap"); - c.add(noteLabel, "align left, wmax 70%, grow 100 0, wrap"); - c.add(progressBar, "spanx 2, gap top unrel, gap bottom unrel, grow, wrap"); + c.add(iconLabel, "h pref!, w pref!"); + c.add(headerLabel, "gap 3mm, wrap paragraph"); + c.add(progressBar, "grow, wrap paragraph"); - c.add(cancelButton, "spanx 2, align center"); + c.add(new JButton(cancelAction), "align center"); setSize(240, 155); @@ -60,22 +54,19 @@ public class ProgressDialog extends JDialog { } - public boolean isCancelled() { - return cancelled; - } - - public void setIcon(Icon icon) { iconLabel.setIcon(icon); } public void setNote(String text) { - noteLabel.setText(text); + progressBar.setString(text); } - public void setHeader(String text) { + @Override + public void setTitle(String text) { + super.setTitle(text); headerLabel.setText(text); } @@ -85,32 +76,26 @@ public class ProgressDialog extends JDialog { } - public JButton getCancelButton() { - return cancelButton; - } - - public void close() { setVisible(false); dispose(); } - private final Action cancelAction = new AbstractAction("Cancel") { + protected final Action cancelAction = new AbstractAction("Cancel") { @Override public void actionPerformed(ActionEvent e) { - cancelled = true; - close(); + cancellable.cancel(); } - }; - private final WindowListener closeListener = new WindowAdapter() { + + public static interface Cancellable { - @Override - public void windowClosing(WindowEvent e) { - cancelAction.actionPerformed(null); - } - }; + boolean isCancelled(); + + + boolean cancel(); + } } diff --git a/source/net/sourceforge/tuned/ui/SwingWorkerProgressMonitor.java b/source/net/sourceforge/tuned/ui/SwingWorkerProgressMonitor.java deleted file mode 100644 index ab0c4e68..00000000 --- a/source/net/sourceforge/tuned/ui/SwingWorkerProgressMonitor.java +++ /dev/null @@ -1,129 +0,0 @@ - -package net.sourceforge.tuned.ui; - - -import java.awt.Window; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.beans.PropertyChangeEvent; - -import javax.swing.Icon; -import javax.swing.SwingWorker; -import javax.swing.Timer; - - -public class SwingWorkerProgressMonitor { - - public static final String PROPERTY_TITLE = "title"; - public static final String PROPERTY_NOTE = "note"; - public static final String PROPERTY_PROGRESS_STRING = "progress string"; - - private final SwingWorker worker; - private final ProgressDialog progressDialog; - - private int millisToPopup = 2000; - - - public SwingWorkerProgressMonitor(Window owner, SwingWorker worker, Icon progressDialogIcon) { - this.worker = worker; - - progressDialog = new ProgressDialog(owner); - progressDialog.setIcon(progressDialogIcon); - - worker.addPropertyChangeListener(listener); - - progressDialog.getCancelButton().addActionListener(cancelListener); - } - - - public ProgressDialog getProgressDialog() { - return progressDialog; - } - - - public void setMillisToPopup(int millisToPopup) { - this.millisToPopup = millisToPopup; - } - - - public int getMillisToPopup() { - return millisToPopup; - } - - private final SwingWorkerPropertyChangeAdapter listener = new SwingWorkerPropertyChangeAdapter() { - - private Timer popupTimer = null; - - - @Override - public void propertyChange(PropertyChangeEvent evt) { - if (evt.getPropertyName().equals(PROPERTY_PROGRESS_STRING)) - progressString(evt); - else if (evt.getPropertyName().equals(PROPERTY_NOTE)) - note(evt); - else if (evt.getPropertyName().equals(PROPERTY_TITLE)) - title(evt); - else - super.propertyChange(evt); - } - - - @Override - protected void started(PropertyChangeEvent evt) { - popupTimer = TunedUtil.invokeLater(millisToPopup, new Runnable() { - - @Override - public void run() { - if (!worker.isDone() && !progressDialog.isVisible()) { - progressDialog.setVisible(true); - } - } - }); - } - - - @Override - protected void done(PropertyChangeEvent evt) { - if (popupTimer != null) { - popupTimer.stop(); - } - - progressDialog.close(); - } - - - @Override - protected void progress(PropertyChangeEvent evt) { - progressDialog.getProgressBar().setValue((Integer) evt.getNewValue()); - } - - - protected void progressString(PropertyChangeEvent evt) { - progressDialog.getProgressBar().setString(evt.getNewValue().toString()); - } - - - protected void note(PropertyChangeEvent evt) { - progressDialog.setNote(evt.getNewValue().toString()); - } - - - protected void title(PropertyChangeEvent evt) { - String title = evt.getNewValue().toString(); - - progressDialog.setHeader(title); - progressDialog.setTitle(title); - } - - }; - - private final ActionListener cancelListener = new ActionListener() { - - @Override - public void actionPerformed(ActionEvent e) { - worker.cancel(false); - } - - }; - -} diff --git a/test/net/sourceforge/filebot/FileBotTestSuite.java b/test/net/sourceforge/filebot/FileBotTestSuite.java index ec025e18..a1f612ed 100644 --- a/test/net/sourceforge/filebot/FileBotTestSuite.java +++ b/test/net/sourceforge/filebot/FileBotTestSuite.java @@ -2,7 +2,7 @@ package net.sourceforge.filebot; -import net.sourceforge.filebot.ui.panel.rename.MatcherTestSuite; +import net.sourceforge.filebot.similarity.SimilarityTestSuite; import net.sourceforge.filebot.web.WebTestSuite; import org.junit.runner.RunWith; @@ -11,7 +11,7 @@ import org.junit.runners.Suite.SuiteClasses; @RunWith(Suite.class) -@SuiteClasses( { MatcherTestSuite.class, WebTestSuite.class, ArgumentBeanTest.class }) +@SuiteClasses( { SimilarityTestSuite.class, WebTestSuite.class, ArgumentBeanTest.class }) public class FileBotTestSuite { } diff --git a/test/net/sourceforge/filebot/similarity/NameSimilarityMetricTest.java b/test/net/sourceforge/filebot/similarity/NameSimilarityMetricTest.java new file mode 100644 index 00000000..70beb974 --- /dev/null +++ b/test/net/sourceforge/filebot/similarity/NameSimilarityMetricTest.java @@ -0,0 +1,27 @@ + +package net.sourceforge.filebot.similarity; + + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + + +public class NameSimilarityMetricTest { + + private static NameSimilarityMetric metric = new NameSimilarityMetric(); + + + @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")); + + // remove checksum + assertEquals(1, metric.getSimilarity("test", "test [EF62DF13]")); + } + +} diff --git a/test/net/sourceforge/filebot/ui/panel/rename/metric/NumericSimilarityMetricTest.java b/test/net/sourceforge/filebot/similarity/NumericSimilarityMetricTest.java similarity index 88% rename from test/net/sourceforge/filebot/ui/panel/rename/metric/NumericSimilarityMetricTest.java rename to test/net/sourceforge/filebot/similarity/NumericSimilarityMetricTest.java index c9251421..38824e66 100644 --- a/test/net/sourceforge/filebot/ui/panel/rename/metric/NumericSimilarityMetricTest.java +++ b/test/net/sourceforge/filebot/similarity/NumericSimilarityMetricTest.java @@ -1,5 +1,5 @@ -package net.sourceforge.filebot.ui.panel.rename.metric; +package net.sourceforge.filebot.similarity; import static org.junit.Assert.assertEquals; @@ -60,7 +60,7 @@ public class NumericSimilarityMetricTest { return TestUtil.asParameters(matches.keySet()); } - private String normalizedName; + private final String normalizedName; public NumericSimilarityMetricTest(String normalizedName) { @@ -77,18 +77,20 @@ public class NumericSimilarityMetricTest { public String getBestMatch(String value, Collection testdata) { - float maxSimilarity = -1; + double maxSimilarity = -1; String mostSimilar = null; - for (String comparisonValue : testdata) { - float similarity = metric.getSimilarity(value, comparisonValue); + for (String current : testdata) { + double similarity = metric.getSimilarity(value, current); if (similarity > maxSimilarity) { maxSimilarity = similarity; - mostSimilar = comparisonValue; + mostSimilar = current; } } + // System.out.println(String.format("[%f, %s, %s]", maxSimilarity, value, mostSimilar)); + return mostSimilar; } } diff --git a/test/net/sourceforge/filebot/similarity/SeasonEpisodeSimilarityMetricTest.java b/test/net/sourceforge/filebot/similarity/SeasonEpisodeSimilarityMetricTest.java new file mode 100644 index 00000000..e1fa5f98 --- /dev/null +++ b/test/net/sourceforge/filebot/similarity/SeasonEpisodeSimilarityMetricTest.java @@ -0,0 +1,93 @@ + +package net.sourceforge.filebot.similarity; + + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + + +public class SeasonEpisodeSimilarityMetricTest { + + private static SeasonEpisodeSimilarityMetric metric = new SeasonEpisodeSimilarityMetric(); + + + @Test + public void getSimilarity() { + // single pattern match, single episode match + assertEquals(1.0, metric.getSimilarity("1x01", "s01e01")); + + // multiple pattern matches, single episode match + assertEquals(1.0, metric.getSimilarity("1x02a", "101 102 103")); + + // multiple pattern matches, no episode match + assertEquals(0.0, metric.getSimilarity("1x03b", "104 105 106")); + + // no pattern match, no episode match + assertEquals(0.0, metric.getSimilarity("abc", "xyz")); + } + + + @Test + public void fallbackMetric() { + assertEquals(1.0, metric.getSimilarity("1x01", "sno=1, eno=1")); + + 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")); + } + +} diff --git a/test/net/sourceforge/filebot/similarity/SimilarityTestSuite.java b/test/net/sourceforge/filebot/similarity/SimilarityTestSuite.java new file mode 100644 index 00000000..ac7975ef --- /dev/null +++ b/test/net/sourceforge/filebot/similarity/SimilarityTestSuite.java @@ -0,0 +1,14 @@ + +package net.sourceforge.filebot.similarity; + + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; +import org.junit.runners.Suite.SuiteClasses; + + +@RunWith(Suite.class) +@SuiteClasses( { NameSimilarityMetricTest.class, NumericSimilarityMetricTest.class, SeasonEpisodeSimilarityMetricTest.class }) +public class SimilarityTestSuite { + +} diff --git a/test/net/sourceforge/filebot/ui/panel/rename/MatcherTestSuite.java b/test/net/sourceforge/filebot/ui/panel/rename/MatcherTestSuite.java deleted file mode 100644 index 2a8b3a7f..00000000 --- a/test/net/sourceforge/filebot/ui/panel/rename/MatcherTestSuite.java +++ /dev/null @@ -1,17 +0,0 @@ - -package net.sourceforge.filebot.ui.panel.rename; - - -import net.sourceforge.filebot.ui.panel.rename.metric.AbstractNameSimilarityMetricTest; -import net.sourceforge.filebot.ui.panel.rename.metric.NumericSimilarityMetricTest; - -import org.junit.runner.RunWith; -import org.junit.runners.Suite; -import org.junit.runners.Suite.SuiteClasses; - - -@RunWith(Suite.class) -@SuiteClasses( { AbstractNameSimilarityMetricTest.class, NumericSimilarityMetricTest.class }) -public class MatcherTestSuite { - -} diff --git a/test/net/sourceforge/filebot/ui/panel/rename/metric/AbstractNameSimilarityMetricTest.java b/test/net/sourceforge/filebot/ui/panel/rename/metric/AbstractNameSimilarityMetricTest.java deleted file mode 100644 index 33670f8c..00000000 --- a/test/net/sourceforge/filebot/ui/panel/rename/metric/AbstractNameSimilarityMetricTest.java +++ /dev/null @@ -1,83 +0,0 @@ - -package net.sourceforge.filebot.ui.panel.rename.metric; - - -import static org.junit.Assert.assertEquals; - -import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Map.Entry; - -import net.sourceforge.tuned.TestUtil; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; - - -@RunWith(Parameterized.class) -public class AbstractNameSimilarityMetricTest { - - private static final BasicNameSimilarityMetric metric = new BasicNameSimilarityMetric(); - - - @Parameters - public static Collection createParameters() { - Map matches = new LinkedHashMap(); - - // normalize separators - matches.put("test s01e01 first", "test.S01E01.First"); - matches.put("test s01e02 second", "test_S01E02_Second"); - matches.put("test s01e03 third", "__test__S01E03__Third__"); - matches.put("test s01e04 four", "test s01e04 four"); - - // strip checksum - matches.put("test", "test [EF62DF13]"); - - // lower-case - matches.put("the a-team", "The A-Team"); - - return TestUtil.asParameters(matches.entrySet()); - } - - private Entry entry; - - - public AbstractNameSimilarityMetricTest(Entry entry) { - this.entry = entry; - } - - - @Test - public void normalize() { - String normalizedName = entry.getKey(); - String unnormalizedName = entry.getValue(); - - assertEquals(normalizedName, metric.normalize(unnormalizedName)); - } - - - private static class BasicNameSimilarityMetric extends AbstractNameSimilarityMetric { - - @Override - public float getSimilarity(String a, String b) { - return a.equals(b) ? 1 : 0; - } - - - @Override - public String getDescription() { - return "Equals"; - } - - - @Override - public String getName() { - return "Equals"; - } - - } - -}