package net.sourceforge.filebot.similarity; import static java.util.Collections.*; import java.io.File; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import net.sourceforge.filebot.similarity.SeasonEpisodeMatcher.SxE; import net.sourceforge.filebot.web.Episode; import net.sourceforge.filebot.web.MultiEpisode; public class EpisodeMatcher extends Matcher { public EpisodeMatcher(Collection values, Collection candidates, boolean strict) { // use strict matcher as to force a result from the final top similarity set super(values, candidates, strict, strict ? StrictEpisodeMetrics.defaultSequence(false) : EpisodeMetrics.defaultSequence(false)); } @Override protected void deepMatch(Collection> possibleMatches, int level) throws InterruptedException { Map> episodeSets = new IdentityHashMap>(); for (Match it : possibleMatches) { List episodes = episodeSets.get(it.getValue()); if (episodes == null) { episodes = new ArrayList(); episodeSets.put(it.getValue(), episodes); } episodes.add((Episode) it.getCandidate()); } Map> episodeIdentifierSets = new IdentityHashMap>(); for (Entry> it : episodeSets.entrySet()) { Set sxe = new HashSet(it.getValue().size()); for (Episode ep : it.getValue()) { sxe.add(new SxE(ep.getSeason(), ep.getEpisode())); } episodeIdentifierSets.put(it.getKey(), sxe); } boolean modified = false; for (Match it : possibleMatches) { File file = it.getValue(); Set uniqueFiles = parseEpisodeIdentifer(file); Set uniqueEpisodes = episodeIdentifierSets.get(file); if (uniqueFiles.equals(uniqueEpisodes)) { Episode[] episodes = episodeSets.get(file).toArray(new Episode[0]); if (isMultiEpisode(episodes)) { MultiEpisode episode = new MultiEpisode(episodes); disjointMatchCollection.add(new Match(file, episode)); modified = true; } } } if (modified) { removeCollected(possibleMatches); } super.deepMatch(possibleMatches, level); } private final SeasonEpisodeMatcher seasonEpisodeMatcher = new SeasonEpisodeMatcher(SeasonEpisodeMatcher.DEFAULT_SANITY, false); private final Map> transformCache = synchronizedMap(new HashMap>(64, 4)); private Set parseEpisodeIdentifer(File file) { Set result = transformCache.get(file); if (result != null) { return result; } List sxe = seasonEpisodeMatcher.match(file.getName()); if (sxe != null) { result = new HashSet(sxe); } else { result = emptySet(); } transformCache.put(file, result); return result; } private boolean isMultiEpisode(Episode[] episodes) { // check episode sequence integrity Integer seqIndex = null; for (Episode ep : episodes) { if (seqIndex != null && !ep.getEpisode().equals(seqIndex + 1)) return false; seqIndex = ep.getEpisode(); } // check drill-down integrity String seriesName = null; for (Episode ep : episodes) { if (seriesName != null && !seriesName.equals(ep.getSeriesName())) return false; seriesName = ep.getSeriesName(); } return true; } }