From 4a19490f017c4375933c0c8ce2967ffdfe391a3c Mon Sep 17 00:00:00 2001 From: Reinhard Pointner Date: Mon, 10 Jun 2019 22:18:59 +0700 Subject: [PATCH] Refactor AnimeList client --- .../net/filebot/format/MediaBindingBean.java | 15 +- .../web/{AnimeLists.java => AnimeList.java} | 249 +++++++++++------- source/net/filebot/web/EpisodeUtilities.java | 2 +- source/net/filebot/web/XEM.java | 1 - 4 files changed, 164 insertions(+), 103 deletions(-) rename source/net/filebot/web/{AnimeLists.java => AnimeList.java} (56%) diff --git a/source/net/filebot/format/MediaBindingBean.java b/source/net/filebot/format/MediaBindingBean.java index 5d490e54..9663a829 100644 --- a/source/net/filebot/format/MediaBindingBean.java +++ b/source/net/filebot/format/MediaBindingBean.java @@ -65,7 +65,7 @@ import net.filebot.mediainfo.MediaInfoException; import net.filebot.similarity.Normalization; import net.filebot.similarity.SimilarityComparator; import net.filebot.util.FileUtilities; -import net.filebot.web.AnimeLists; +import net.filebot.web.AnimeList; import net.filebot.web.AudioTrack; import net.filebot.web.Episode; import net.filebot.web.EpisodeFormat; @@ -797,7 +797,7 @@ public class MediaBindingBean { } if (AniDB.getIdentifier().equals(e.getSeriesInfo().getDatabase())) { - return AnimeLists.AniDB.map(e, AnimeLists.TheTVDB).map(this::createBindingObject).orElse(null); // map AniDB to TheTVDB bindings + return new AnimeList().map(e, AnimeList.getDB(e), AnimeList.DB.TheTVDB).map(this::createBindingObject).orElse(null); // map AniDB to TheTVDB bindings } return createBindingObject(fetchEpisode(e, SortOrder.Airdate, null)); @@ -1173,17 +1173,15 @@ public class MediaBindingBean { }); } - @Define("AnimeLists") + @Define("AnimeList") public DynamicBindings getAnimeLists() { - return new DynamicBindings(AnimeLists::names, k -> { + return new DynamicBindings(AnimeList.DB::names, k -> { if (infoObject instanceof Episode) { Episode e = getEpisode(); - AnimeLists origin = AnimeLists.forName(e.getSeriesInfo().getDatabase()); - AnimeLists destination = AnimeLists.forName(k); - if (origin == destination) { + if (AnimeList.getDB(e) == AnimeList.getDB(k)) { return e; } - return origin.map(e, destination).orElse(e); + return new AnimeList().map(e, AnimeList.getDB(e), AnimeList.getDB(k)).orElse(e); } return undefined(k); }); @@ -1197,7 +1195,6 @@ public class MediaBindingBean { debug.warning("Failed to retrieve primary series info: " + e); // default to seriesInfo property } } - return getSeriesInfo(); } diff --git a/source/net/filebot/web/AnimeLists.java b/source/net/filebot/web/AnimeList.java similarity index 56% rename from source/net/filebot/web/AnimeLists.java rename to source/net/filebot/web/AnimeList.java index b02ca624..2292b641 100644 --- a/source/net/filebot/web/AnimeLists.java +++ b/source/net/filebot/web/AnimeList.java @@ -12,8 +12,9 @@ import java.io.StringWriter; import java.net.URL; import java.util.List; import java.util.Locale; -import java.util.Map; +import java.util.Objects; import java.util.Optional; +import java.util.stream.IntStream; import java.util.stream.Stream; import javax.xml.bind.JAXBContext; @@ -31,19 +32,27 @@ import net.filebot.CacheType; import net.filebot.Resource; import net.filebot.WebServices; -public enum AnimeLists { +public class AnimeList { - AniDB, TheTVDB; + private Model model; - public Optional map(Episode episode, AnimeLists destination) throws Exception { + public AnimeList() throws Exception { + this.model = MODEL.get(); + } + + public Model getModel() { + return model; + } + + public Optional map(Episode episode, DB source, DB destination) throws Exception { int id = episode.getSeriesInfo().getId(); - int series = getSeasonNumber(episode); + int series = getSeasonNumber(source, episode); - return find(id, series).map(a -> { + return find(source, id, series).map(a -> { // auto-align mode if (a.defaulttvdbseason == null) { try { - return destination.mapAutoAligned(a, episode); + return mapAutoAligned(destination, a, episode); } catch (Exception e) { debug.warning(e::toString); } @@ -51,76 +60,94 @@ public enum AnimeLists { } // offset mode - int s = getSeasonNumber(episode); - int e = getEpisodeNumber(episode); + int s = getSeasonNumber(source, episode); + int e = episode.isSpecial() ? episode.getSpecial() : episode.getEpisode(); // check explicit episode mapping if (a.mapping != null) { for (Mapping m : a.mapping) { - if (s == getSeason(m)) { - Optional episodeMapping = destination.getEpisodeNumber(m, e); + if (s == getSeason(source, m)) { + Optional episodeMapping = getEpisodeNumber(source, m, e); if (episodeMapping.isPresent()) { - return destination.derive(a, episode, destination.getSeason(m), episodeMapping.get()); + return derive(destination, a, episode, getSeason(destination, m), episodeMapping.get()); } } } } // apply default season - s = destination.getSeason(a, episode); + s = getSeason(destination, a, episode); // apply episode offset - e += destination.getEpisodeNumberOffset(a); + e += getEpisodeNumberOffset(destination, a); - return destination.derive(a, episode, s, e); + return derive(destination, a, episode, s, e); }).findFirst(); } - private Episode derive(Entry a, Episode episode, int s, int e) { + private Episode derive(DB db, Entry a, Episode episode, int s, int e) { if (s == 0) { // special - return this == AniDB ? episode.derive(a.name, null, null, null, e) : episode.deriveSpecial(e); + switch (db) { + case AniDB: + return episode.derive(a.name, null, null, null, e); + default: + return episode.deriveSpecial(e); + } } else { // regular - return this == AniDB ? episode.derive(a.name, null, e, null, null) : episode.derive(s, e); + switch (db) { + case AniDB: + return episode.derive(a.name, null, e, null, null); + default: + return episode.derive(s, e); + } } } - public Optional map(int id, int s, AnimeLists destination) throws Exception { - return find(id, s).map(destination::getId).findFirst(); + public Optional map(int id, int s, DB source, DB destination) throws Exception { + return find(source, id, s).map(a -> getId(destination, a)).findFirst(); } - protected Episode mapAutoAligned(Entry a, Episode episode) throws Exception { - switch (this) { - case TheTVDB: - return WebServices.TheTVDB.getEpisodeList(a.tvdbid, SortOrder.Airdate, Locale.ENGLISH).stream().filter(e -> { - return episode.getEpisode() != null && episode.getEpisode().equals(e.getAbsolute()); - }).findFirst().orElse(null); + protected Episode mapAutoAligned(DB db, Entry a, Episode episode) throws Exception { + switch (db) { case AniDB: return WebServices.AniDB.getEpisodeList(a.anidbid, SortOrder.Absolute, Locale.ENGLISH).stream().filter(e -> { return episode.getAbsolute() != null && episode.getAbsolute().equals(e.getEpisode()); }).findFirst().orElse(null); + default: + return WebServices.TheTVDB.getEpisodeList(a.tvdbid, SortOrder.Airdate, Locale.ENGLISH).stream().filter(e -> { + return episode.getEpisode() != null && episode.getEpisode().equals(e.getAbsolute()); + }).findFirst().orElse(null); } - return null; } - protected Optional getEpisodeNumber(Mapping m, Integer e) { + protected Optional getEpisodeNumber(DB db, Mapping m, Integer e) { if (m.numbers != null) { - switch (this) { - case TheTVDB: - return Optional.ofNullable(m.numbers.get(e)); + switch (db) { case AniDB: - return m.numbers.entrySet().stream().filter(it -> e.equals(it.getValue())).map(it -> it.getKey()).findFirst(); + return stream(m.numbers).filter(i -> e == i[0]).map(i -> i[1]).findFirst(); + default: + return stream(m.numbers).filter(i -> e == i[1]).map(i -> i[0]).findFirst(); } } return Optional.empty(); } - protected int getEpisodeNumberOffset(Entry a) { - return a.episodeoffset == null ? 0 : this == TheTVDB ? a.episodeoffset : -a.episodeoffset; + protected int getEpisodeNumberOffset(DB db, Entry a) { + if (a.episodeoffset == null) { + return 0; + } + + switch (db) { + case AniDB: + return -a.episodeoffset; + default: + return a.episodeoffset; + } } - protected int getSeasonNumber(Episode e) { + protected int getSeasonNumber(DB db, Episode e) { // special episode if (e.isSpecial()) { return 0; @@ -128,39 +155,64 @@ public enum AnimeLists { // regular absolute episode if (e.getSeason() == null) { - return this == AniDB ? 1 : -1; + switch (db) { + case AniDB: + return 1; + default: + return -1; + } } // regular SxE episode return e.getSeason(); } - protected int getEpisodeNumber(Episode e) { - return e.isSpecial() ? e.getSpecial() : e.getEpisode(); + protected int getSeason(DB db, Mapping m) { + switch (db) { + case AniDB: + return m.anidbseason; + default: + return m.tvdbseason; + } } - protected int getSeason(Mapping m) { - return this == AniDB ? m.anidbseason : m.tvdbseason; + protected int getSeason(DB db, Entry a, Episode e) { + if (e.isSpecial()) { + return 0; + } + + switch (db) { + case AniDB: + return 1; + default: + return a.defaulttvdbseason; + } } - protected int getSeason(Entry a, Episode e) { - return e.isSpecial() ? 0 : this == AniDB ? 1 : a.defaulttvdbseason; - } - - protected int getId(Entry a) { - return this == AniDB ? a.anidbid : a.tvdbid; + protected int getId(DB db, Entry a) { + switch (db) { + case AniDB: + return a.anidbid; + default: + return a.tvdbid; + } } protected boolean isValid(Entry a) { return a.anidbid != null && a.tvdbid != null; } - public Stream find(int id) throws Exception { - return stream(MODEL.get().anime).filter(this::isValid).filter(a -> id == getId(a)); + public Stream find(DB db, int id) throws Exception { + return stream(model.anime).filter(this::isValid).filter(a -> id == getId(db, a)); } - public Stream find(int id, int s) throws Exception { - return this == AniDB ? find(id) : find(id).filter(a -> a.defaulttvdbseason == null || s == a.defaulttvdbseason); + public Stream find(DB db, int id, int s) throws Exception { + switch (db) { + case AniDB: + return find(db, id); + default: + return find(db, id).filter(a -> a.defaulttvdbseason == null || s == a.defaulttvdbseason); + } } protected static Cache getCache() { @@ -173,13 +225,39 @@ public enum AnimeLists { // NOTE: GitHub only supports If-None-Match (If-Modified-Since is ignored) Cache cache = getCache(); - return cache.bytes(file, AnimeLists::getResource).fetch(fetchIfNoneMatch(url -> file, cache)).expire(Cache.ONE_MONTH).get(); + return cache.bytes(file, AnimeList::getResource).fetch(fetchIfNoneMatch(url -> file, cache)).expire(Cache.ONE_MONTH).get(); } protected static URL getResource(String file) throws Exception { return new URL("https://raw.githubusercontent.com/ScudLee/anime-lists/master/" + file); } + public static DB getDB(Episode e) { + return DB.forName(e.getSeriesInfo().getDatabase()); + } + + public static DB getDB(String s) { + return DB.forName(s); + } + + public enum DB { + + AniDB, TheTVDB; + + public static List names() { + return stream(values()).map(Enum::name).collect(toList()); + } + + public static DB forName(String name) { + for (DB db : values()) { + if (db.name().equalsIgnoreCase(name)) { + return db; + } + } + throw new IllegalArgumentException(String.format("%s not in %s", name, asList(values()))); + } + } + @XmlRootElement(name = "anime-list") public static class Model { @@ -242,12 +320,39 @@ public enum AnimeLists { @XmlJavaTypeAdapter(NumberMapAdapter.class) @XmlValue - public Map numbers; + public int[][] numbers; @Override public String toString() { return marshal(this, Mapping.class); } + + } + + private static class NumberAdapter extends XmlAdapter { + + @Override + public Integer unmarshal(String s) throws Exception { + return matchInteger(s); + } + + @Override + public String marshal(Integer i) throws Exception { + return Objects.toString(i, ""); + } + } + + private static class NumberMapAdapter extends XmlAdapter { + + @Override + public int[][] unmarshal(String s) throws Exception { + return tokenize(s, SEMICOLON).map(m -> matchIntegers(m)).filter(m -> m.size() == 2).map(m -> m.stream().mapToInt(i -> i).toArray()).toArray(int[][]::new); + } + + @Override + public String marshal(int[][] m) throws Exception { + return stream(m).map(e -> join(IntStream.of(e).boxed(), "-")).collect(joining(";")); + } } private static T unmarshal(byte[] bytes, Class type) throws Exception { @@ -267,44 +372,4 @@ public enum AnimeLists { } } - private static class NumberAdapter extends XmlAdapter { - - @Override - public Integer unmarshal(String s) throws Exception { - return matchInteger(s); - } - - @Override - public String marshal(Integer i) throws Exception { - return String.valueOf(i); - } - } - - private static class NumberMapAdapter extends XmlAdapter> { - - @Override - public Map unmarshal(String s) throws Exception { - return tokenize(s, SEMICOLON).map(m -> matchIntegers(m)).filter(m -> m.size() >= 2).collect(toMap(m -> m.get(0), m -> m.get(1))); - } - - @Override - public String marshal(Map m) throws Exception { - return m.entrySet().stream().map(e -> join(Stream.of(e.getKey(), e.getValue()), "-")).collect(joining(";")); - } - } - - public static List names() { - return stream(values()).map(Enum::name).collect(toList()); - } - - public static AnimeLists forName(String name) { - for (AnimeLists db : values()) { - if (db.name().equalsIgnoreCase(name)) { - return db; - } - } - - throw new IllegalArgumentException(String.format("%s not in %s", name, asList(values()))); - } - } diff --git a/source/net/filebot/web/EpisodeUtilities.java b/source/net/filebot/web/EpisodeUtilities.java index 664cf228..db2a37f5 100644 --- a/source/net/filebot/web/EpisodeUtilities.java +++ b/source/net/filebot/web/EpisodeUtilities.java @@ -55,7 +55,7 @@ public final class EpisodeUtilities { if (episode.isAnime() && episode.isRegular()) { return mapEpisode(episode, e -> { try { - return AnimeLists.forName(e.getSeriesInfo().getDatabase()).map(e, AnimeLists.TheTVDB).orElse(e); + return new AnimeList().map(e, AnimeList.getDB(e), AnimeList.DB.TheTVDB).orElse(e); } catch (Exception ioe) { debug.warning(ioe::toString); return e; diff --git a/source/net/filebot/web/XEM.java b/source/net/filebot/web/XEM.java index 36c12e28..c27aaab8 100644 --- a/source/net/filebot/web/XEM.java +++ b/source/net/filebot/web/XEM.java @@ -194,7 +194,6 @@ public enum XEM { return db; } } - throw new IllegalArgumentException(String.format("%s not in %s", name, asList(values()))); }