diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/CardImageUrls.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/CardImageUrls.java index 7a68b2724af..3add12dcb5f 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/CardImageUrls.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/CardImageUrls.java @@ -1,80 +1,45 @@ package org.mage.plugins.card.dl.sources; import java.util.ArrayList; +import java.util.Collection; import java.util.List; -import java.util.Objects; /** * @author JayDi85 */ public class CardImageUrls { - public String baseUrl; - public List alternativeUrls; - - public CardImageUrls() { - this.baseUrl = null; - this.alternativeUrls = new ArrayList<>(); - } + private final List urls = new ArrayList<>(); public CardImageUrls(String baseUrl) { - this(baseUrl, null); + addUrl(baseUrl); } - public CardImageUrls(String baseUrl, String alternativeUrl) { - this(); - - this.baseUrl = baseUrl; - - if (alternativeUrl != null - && !alternativeUrl.isEmpty() - && !Objects.equals(baseUrl, alternativeUrl)) { - this.alternativeUrls.add(alternativeUrl); + public CardImageUrls(String... urls) { + for (String url : urls) { + addUrl(url); } } - public CardImageUrls(String baseUrl, String alternativeUrl , String nextaltUrl) { - this(); - - this.baseUrl = baseUrl; - - if (alternativeUrl != null - && !alternativeUrl.isEmpty() - && !Objects.equals(baseUrl, alternativeUrl)) { - this.alternativeUrls.add(alternativeUrl); - } - - if (nextaltUrl != null - && !nextaltUrl.isEmpty() - && !Objects.equals(baseUrl, nextaltUrl)) { - this.alternativeUrls.add(nextaltUrl); + public CardImageUrls(Collection urls) { + for (String url : urls) { + addUrl(url); } } public List getDownloadList() { - List downloadUrls = new ArrayList<>(); - - if (this.baseUrl != null && !this.baseUrl.isEmpty()) { - downloadUrls.add(this.baseUrl); - } - - // no needs in base url duplicate - if (this.alternativeUrls != null) { - for (String url : this.alternativeUrls) { - if (!url.equals(this.baseUrl)) { - downloadUrls.add(url); - } - } - } - - return downloadUrls; + return urls; } - public void addAlternativeUrl(String url) { - if (url != null && !url.isEmpty()) { - this.alternativeUrls.add(url); - } else { - throw new IllegalArgumentException(); + // for tests + public String getBaseUrl() { + return urls.stream().findFirst().orElse(null); + } + + public void addUrl(String url) { + // ignore nulls and duplicates + if (url != null && !url.isEmpty() && !urls.contains(url)) { + this.urls.add(url); } } } diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSource.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSource.java index 2402301b0ff..e1e5ced5b91 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSource.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSource.java @@ -21,16 +21,20 @@ import java.util.*; /** * @author JayDi85 */ -public enum ScryfallImageSource implements CardImageSource { +public class ScryfallImageSource implements CardImageSource { - instance; + private static final ScryfallImageSource instance = new ScryfallImageSource(); private static final Logger logger = Logger.getLogger(ScryfallImageSource.class); private final Map languageAliases; private CardLanguage currentLanguage = CardLanguage.ENGLISH; // working language private final Map preparedUrls = new HashMap<>(); - private final int DOWNLOAD_TIMEOUT_MS = 100; + private static final int DOWNLOAD_TIMEOUT_MS = 100; + + public static ScryfallImageSource getInstance() { + return instance; + } ScryfallImageSource() { // LANGUAGES @@ -52,7 +56,7 @@ public enum ScryfallImageSource implements CardImageSource { private CardImageUrls innerGenerateURL(CardDownloadData card, boolean isToken) { String prepared = preparedUrls.getOrDefault(card, null); if (prepared != null) { - return new CardImageUrls(prepared, null); + return new CardImageUrls(prepared); } String defaultCode = CardLanguage.ENGLISH.getCode(); @@ -144,11 +148,11 @@ public enum ScryfallImageSource implements CardImageSource { String apiUrl = ScryfallImageSupportCards.findDirectDownloadLink(card.getSet(), card.getName(), card.getCollectorId()); if (apiUrl != null) { if (apiUrl.endsWith("*/")) { - apiUrl = apiUrl.substring(0 , apiUrl.length() -2) + "★/" ; + apiUrl = apiUrl.substring(0 , apiUrl.length() - 2) + "★/" ; } else if (apiUrl.endsWith("+/")) { - apiUrl = apiUrl.substring(0 , apiUrl.length() -2) + "†/" ; + apiUrl = apiUrl.substring(0 , apiUrl.length() - 2) + "†/" ; } else if (apiUrl.endsWith("Ph/")) { - apiUrl = apiUrl.substring(0 , apiUrl.length() -3) + "Φ/" ; + apiUrl = apiUrl.substring(0 , apiUrl.length() - 3) + "Φ/" ; } // BY DIRECT URL // direct links via hardcoded API path. Used for cards with non-ASCII collector numbers diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSourceSmall.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSourceSmall.java new file mode 100644 index 00000000000..7e6b21b2e88 --- /dev/null +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSourceSmall.java @@ -0,0 +1,45 @@ +package org.mage.plugins.card.dl.sources; + +import org.mage.plugins.card.images.CardDownloadData; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * @author tiera3 + */ +public class ScryfallImageSourceSmall extends ScryfallImageSource { + + private static final ScryfallImageSourceSmall instanceSmall = new ScryfallImageSourceSmall(); + + public static ScryfallImageSource getInstance() { + return instanceSmall; + } + + private static String innerModifyUrlString(String oneUrl) { + return oneUrl.replaceFirst("/large/","/small/").replaceFirst("format=image","format=image&version=small"); + } + + private static CardImageUrls innerModifyUrl(CardImageUrls cardUrls) { + List downloadUrls = cardUrls.getDownloadList().stream() + .map(ScryfallImageSourceSmall::innerModifyUrlString) + .collect(Collectors.toList()); + return new CardImageUrls(downloadUrls); + } + + @Override + public CardImageUrls generateCardUrl(CardDownloadData card) throws Exception { + return innerModifyUrl(super.generateCardUrl(card)); + } + + @Override + public CardImageUrls generateTokenUrl(CardDownloadData card) throws Exception { + return innerModifyUrl(super.generateTokenUrl(card)); + } + + @Override + public float getAverageSize() { + return 13; // initial estimate - TODO calculate a more accurate number + } + +} diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPicturesService.java b/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPicturesService.java index 0e747148eb4..089e7dda22f 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPicturesService.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPicturesService.java @@ -76,7 +76,8 @@ public class DownloadPicturesService extends DefaultBoundedRangeModel implements enum DownloadSources { WIZARDS("1. wizards.com - low quality CARDS, multi-language, slow download", WizardCardsImageSource.instance), TOKENS("2. tokens.mtg.onl - high quality TOKENS", TokensMtgImageSource.instance), - SCRYFALL("3. scryfall.com - high quality CARDS and TOKENS, multi-language", ScryfallImageSource.instance), + SCRYFALL("3. scryfall.com - high quality CARDS and TOKENS, multi-language", ScryfallImageSource.getInstance()), + SCRYFALL_SMALL("3a. scryfall.com small images - low quality CARDS and TOKENS, multi-language", ScryfallImageSourceSmall.getInstance()), MAGIDEX("4. magidex.com - high quality CARDS", MagidexImageSource.instance), GRAB_BAG("5. GrabBag - STAR WARS cards and tokens", GrabbagImageSource.instance), MYTHICSPOILER("6. mythicspoiler.com", MythicspoilerComSource.instance), @@ -164,7 +165,7 @@ public class DownloadPicturesService extends DefaultBoundedRangeModel implements // SOURCES - scryfall is default source uiDialog.getSourcesCombo().setModel(new DefaultComboBoxModel(DownloadSources.values())); uiDialog.getSourcesCombo().setSelectedItem(DownloadSources.SCRYFALL); - selectedSource = ScryfallImageSource.instance; + selectedSource = ScryfallImageSource.getInstance(); uiDialog.getSourcesCombo().addItemListener((ItemEvent event) -> { if (event.getStateChange() == ItemEvent.SELECTED) { comboboxSourceSelected(event); @@ -729,7 +730,7 @@ public class DownloadPicturesService extends DefaultBoundedRangeModel implements DownloadTask(CardDownloadData card, String baseUrl, String actualFilename, int count) { this.card = card; - this.urls = new CardImageUrls(baseUrl, null); + this.urls = new CardImageUrls(baseUrl); this.count = count; this.actualFilename = actualFilename; this.useSpecifiedPaths = true; diff --git a/Mage.Client/src/test/java/mage/client/game/ScryfallImagesDownloadTest.java b/Mage.Client/src/test/java/mage/client/game/ScryfallImagesDownloadTest.java index bb2fa692172..003db308954 100644 --- a/Mage.Client/src/test/java/mage/client/game/ScryfallImagesDownloadTest.java +++ b/Mage.Client/src/test/java/mage/client/game/ScryfallImagesDownloadTest.java @@ -6,6 +6,7 @@ import org.junit.Test; import org.mage.plugins.card.dl.sources.CardImageSource; import org.mage.plugins.card.dl.sources.CardImageUrls; import org.mage.plugins.card.dl.sources.ScryfallImageSource; +import org.mage.plugins.card.dl.sources.ScryfallImageSourceSmall; import org.mage.plugins.card.images.CardDownloadData; /** @@ -15,25 +16,25 @@ public class ScryfallImagesDownloadTest { @Test public void test_Cards_DownloadLinks() throws Exception { - CardImageSource imageSource = ScryfallImageSource.instance; + CardImageSource imageSource = ScryfallImageSource.getInstance(); // normal card CardImageUrls urls = imageSource.generateCardUrl(new CardDownloadData("Grizzly Bears", "10E", "268", false, 0)); - Assert.assertEquals("https://api.scryfall.com/cards/10e/268/en?format=image", urls.baseUrl); + Assert.assertEquals("https://api.scryfall.com/cards/10e/268/en?format=image", urls.getBaseUrl()); // various card urls = imageSource.generateCardUrl(new CardDownloadData("Grizzly Bears", "30A", "195", true, 1)); - Assert.assertEquals("https://api.scryfall.com/cards/30a/195/en?format=image", urls.baseUrl); + Assert.assertEquals("https://api.scryfall.com/cards/30a/195/en?format=image", urls.getBaseUrl()); urls = imageSource.generateCardUrl(new CardDownloadData("Grizzly Bears", "30A", "492", true, 2)); - Assert.assertEquals("https://api.scryfall.com/cards/30a/492/en?format=image", urls.baseUrl); + Assert.assertEquals("https://api.scryfall.com/cards/30a/492/en?format=image", urls.getBaseUrl()); // api link urls = imageSource.generateCardUrl(new CardDownloadData("Ajani, the Greathearted", "WAR", "184*", false, 0)); - Assert.assertEquals("https://api.scryfall.com/cards/war/184★/en?format=image", urls.baseUrl); + Assert.assertEquals("https://api.scryfall.com/cards/war/184★/en?format=image", urls.getBaseUrl()); // direct api link urls = imageSource.generateCardUrl(new CardDownloadData("Command Tower", "REX", "26b", false, 0)); - Assert.assertEquals("https://api.scryfall.com/cards/rex/26/en?format=image&face=back", urls.baseUrl); + Assert.assertEquals("https://api.scryfall.com/cards/rex/26/en?format=image&face=back", urls.getBaseUrl()); // the one ring Assert.assertTrue("LTR must use The One Ring with 001 number, not 0", TheLordOfTheRingsTalesOfMiddleEarth.getInstance().getSetCardInfo() @@ -42,6 +43,37 @@ public class ScryfallImagesDownloadTest { .anyMatch(c -> c.getCardNumber().equals("001")) ); urls = imageSource.generateCardUrl(new CardDownloadData("The One Ring", "LTR", "001", false, 0)); - Assert.assertEquals("https://api.scryfall.com/cards/ltr/0/en?format=image", urls.baseUrl); + Assert.assertEquals("https://api.scryfall.com/cards/ltr/0/en?format=image", urls.getBaseUrl()); + + + // added same tests for small images + CardImageSource imageSourceSmall = ScryfallImageSourceSmall.getInstance(); + + // normal card + urls = imageSourceSmall.generateCardUrl(new CardDownloadData("Grizzly Bears", "10E", "268", false, 0)); + Assert.assertEquals("https://api.scryfall.com/cards/10e/268/en?format=image&version=small", urls.getBaseUrl()); + + // various card + urls = imageSourceSmall.generateCardUrl(new CardDownloadData("Grizzly Bears", "30A", "195", true, 1)); + Assert.assertEquals("https://api.scryfall.com/cards/30a/195/en?format=image&version=small", urls.getBaseUrl()); + urls = imageSourceSmall.generateCardUrl(new CardDownloadData("Grizzly Bears", "30A", "492", true, 2)); + Assert.assertEquals("https://api.scryfall.com/cards/30a/492/en?format=image&version=small", urls.getBaseUrl()); + + // api link + urls = imageSourceSmall.generateCardUrl(new CardDownloadData("Ajani, the Greathearted", "WAR", "184*", false, 0)); + Assert.assertEquals("https://api.scryfall.com/cards/war/184★/en?format=image&version=small", urls.getBaseUrl()); + + // direct api link + urls = imageSourceSmall.generateCardUrl(new CardDownloadData("Command Tower", "REX", "26b", false, 0)); + Assert.assertEquals("https://api.scryfall.com/cards/rex/26/en?format=image&version=small&face=back", urls.getBaseUrl()); + + // the one ring + Assert.assertTrue("LTR must use The One Ring with 001 number, not 0", TheLordOfTheRingsTalesOfMiddleEarth.getInstance().getSetCardInfo() + .stream() + .filter(c -> c.getName().equals("The One Ring")) + .anyMatch(c -> c.getCardNumber().equals("001")) + ); + urls = imageSourceSmall.generateCardUrl(new CardDownloadData("The One Ring", "LTR", "001", false, 0)); + Assert.assertEquals("https://api.scryfall.com/cards/ltr/0/en?format=image&version=small", urls.getBaseUrl()); } } diff --git a/Mage.Client/src/test/java/mage/client/game/TokensMtgImageSourceTest.java b/Mage.Client/src/test/java/mage/client/game/TokensMtgImageSourceTest.java index 4b6100a76bd..e1e6c209a2b 100644 --- a/Mage.Client/src/test/java/mage/client/game/TokensMtgImageSourceTest.java +++ b/Mage.Client/src/test/java/mage/client/game/TokensMtgImageSourceTest.java @@ -19,15 +19,15 @@ public class TokensMtgImageSourceTest { CardImageSource imageSource = TokensMtgImageSource.instance; CardImageUrls url = imageSource.generateTokenUrl(new CardDownloadData("Thopter", "ORI", "0", false, 1)); - Assert.assertEquals("https://tokens.mtg.onl/tokens/ORI_010-Thopter.jpg", url.baseUrl); + Assert.assertEquals("https://tokens.mtg.onl/tokens/ORI_010-Thopter.jpg", url.getBaseUrl()); url = imageSource.generateTokenUrl(new CardDownloadData("Thopter", "ORI", "0", false, 2)); - Assert.assertEquals("https://tokens.mtg.onl/tokens/ORI_011-Thopter.jpg", url.baseUrl); + Assert.assertEquals("https://tokens.mtg.onl/tokens/ORI_011-Thopter.jpg", url.getBaseUrl()); url = imageSource.generateTokenUrl(new CardDownloadData("Ashaya, the Awoken World", "ORI", "0", false, 0)); - Assert.assertEquals("https://tokens.mtg.onl/tokens/ORI_007-Ashaya,-the-Awoken-World.jpg", url.baseUrl); + Assert.assertEquals("https://tokens.mtg.onl/tokens/ORI_007-Ashaya,-the-Awoken-World.jpg", url.getBaseUrl()); url = imageSource.generateTokenUrl(new CardDownloadData("Emblem Gideon, Ally of Zendikar", "BFZ", "0", false, 0)); - Assert.assertEquals("https://tokens.mtg.onl/tokens/BFZ_012-Gideon-Emblem.jpg", url.baseUrl); + Assert.assertEquals("https://tokens.mtg.onl/tokens/BFZ_012-Gideon-Emblem.jpg", url.getBaseUrl()); } }