From b076a578114c6e10c4b5ffd4d7968d875a5ebea3 Mon Sep 17 00:00:00 2001 From: leriomaggio Date: Fri, 28 May 2021 19:19:08 +0100 Subject: [PATCH 1/2] New utility method to compose Scryfall Download url that is compliant with new API specs --- .../src/main/java/forge/util/ImageUtil.java | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/forge-core/src/main/java/forge/util/ImageUtil.java b/forge-core/src/main/java/forge/util/ImageUtil.java index 3ff22655c5e..63dae0114e3 100644 --- a/forge-core/src/main/java/forge/util/ImageUtil.java +++ b/forge-core/src/main/java/forge/util/ImageUtil.java @@ -99,7 +99,7 @@ public class ImageUtil { final CardDb db = StaticData.instance().getCommonCards(); return db.getRules(card.getMeldWith()).getOtherPart().getName(); } else { - return null; + return null; } else return null; @@ -118,6 +118,27 @@ public class ImageUtil { return getImageRelativePath(cp, backFace, true, true); } + public static String getScryfallDownloadUrl(PaperCard cp, boolean backFace, String setCode){ + return getScryfallDownloadUrl(cp, backFace, setCode, "en"); + } + + public static String getScryfallDownloadUrl(PaperCard cp, boolean backFace, String setCode, String langCode){ + String editionCode; + if ((setCode != null) && (setCode.length() > 0)) + editionCode = setCode; + else + editionCode = cp.getEdition().toLowerCase(); + String cardCollectorNumber = cp.getCollectorNumber(); + // Hack to account for variations in Arabian Nights + cardCollectorNumber = cardCollectorNumber.replace("+", "†"); + String faceParam = ""; + if (cp.getRules().getOtherPart() != null) { + faceParam = (backFace ? "&face=back" : "&face=front"); + } + return String.format("%s/%s/%s?format=image&version=normal%s", editionCode, cardCollectorNumber, + langCode, faceParam); + } + public static String toMWSFilename(String in) { final StringBuilder out = new StringBuilder(); char c; @@ -130,4 +151,4 @@ public class ImageUtil { } return out.toString(); } -} +} \ No newline at end of file From 5c2f62defa29fe2ebe2d1e2a030ec7ff19f1a492 Mon Sep 17 00:00:00 2001 From: leriomaggio Date: Fri, 28 May 2021 23:29:29 +0100 Subject: [PATCH 2/2] FIX Scryfall Download retrieval, that is robust with different set-codes. Imagefetcher now tries MCI code first and if that's not working, tries other set codes until one matching scryfall will be found. If none, the not-working set will be recorded to avoid future computation, and returns. --- .../main/java/forge/util/ImageFetcher.java | 105 +++++++++++++----- 1 file changed, 77 insertions(+), 28 deletions(-) diff --git a/forge-gui/src/main/java/forge/util/ImageFetcher.java b/forge-gui/src/main/java/forge/util/ImageFetcher.java index 1fea628cdd9..750c37b2ff3 100644 --- a/forge-gui/src/main/java/forge/util/ImageFetcher.java +++ b/forge-gui/src/main/java/forge/util/ImageFetcher.java @@ -1,14 +1,14 @@ package forge.util; import java.io.File; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.*; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.regex.Matcher; -import java.util.regex.Pattern; +import forge.card.CardEdition; import forge.item.IPaperCard; import org.apache.commons.lang3.tuple.Pair; @@ -25,6 +25,9 @@ public abstract class ImageFetcher { // see https://scryfall.com/docs/api/languages and // https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes private static final HashMap langCodeMap = new HashMap<>(); + private static final Map scryfallSetCodes = new HashMap<>(); + private static final String INVALID_SCRYFALL_SET_CODE = "NotFound"; + static { langCodeMap.put("en-US", "en"); langCodeMap.put("es-ES", "es"); @@ -41,6 +44,65 @@ public abstract class ImageFetcher { private HashMap> currentFetches = new HashMap<>(); private HashMap tokenImages; + private static boolean isValidScryfallURL(final String urlString){ + try { + URL u = new URL(urlString); + HttpURLConnection huc = (HttpURLConnection) u.openConnection(); + huc.setInstanceFollowRedirects(true); + huc.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.1.2) " + + "Gecko/20090729 Firefox/3.5.2 (.NET CLR 3.5.30729)"); + huc.setRequestMethod("HEAD"); + return (huc.getResponseCode() == HttpURLConnection.HTTP_OK); + } catch (IOException e) { + return false; + } + + } + + private String getScryfallDownloadURL(PaperCard c, boolean backFace, String langCode){ + String setCode = scryfallSetCodes.getOrDefault(c.getEdition(), null); + if ((setCode != null) && (!setCode.equals(INVALID_SCRYFALL_SET_CODE))){ + return ForgeConstants.URL_PIC_SCRYFALL_DOWNLOAD + + ImageUtil.getScryfallDownloadUrl(c, backFace, setCode, langCode); + } + + // No entry matched yet for edition + StaticData data = StaticData.instance(); + CardEdition edition = data.getEditions().get(c.getEdition()); + if (edition == null) // edition does not exist - some error occurred with card data + return null; + // 1. Try MCI code first, as it original. + String mciCode = edition.getMciCode().toLowerCase(); + String url = ForgeConstants.URL_PIC_SCRYFALL_DOWNLOAD + + ImageUtil.getScryfallDownloadUrl(c, backFace, mciCode, langCode); + if (isValidScryfallURL(url)) { + scryfallSetCodes.put(c.getEdition(), setCode); + return url; + } + // 2. MCI didn't work, so now try all other codes available in edition, alias included. + // skipping dups with set, and returning as soon as one will work. + Set cardSetCodes = new HashSet<>(); + // all set-codes should be lower case + cardSetCodes.add(mciCode); // add MCI + cardSetCodes.add(edition.getCode().toLowerCase()); + cardSetCodes.add(edition.getCode2().toLowerCase()); + if (edition.getAlias() != null) + cardSetCodes.add(edition.getAlias().toLowerCase()); + for (String code : cardSetCodes) { + if (code.equals(mciCode)) + continue; // Already checked, SKIP + url = ForgeConstants.URL_PIC_SCRYFALL_DOWNLOAD + + ImageUtil.getScryfallDownloadUrl(c, backFace, code, langCode); + if (isValidScryfallURL(url)) { + scryfallSetCodes.put(c.getEdition(), setCode); + return url; + } + } + // If we're here, no valid URL has been found. Record this for the future + scryfallSetCodes.put(c.getEdition(), INVALID_SCRYFALL_SET_CODE); + return null; + } + public void fetchImage(final String imageKey, final Callback callback) { FThreads.assertExecutedByEdt(true); @@ -63,38 +125,24 @@ public abstract class ImageFetcher { System.err.println("Paper card not found for: " + imageKey); return; } - final boolean backFace = imageKey.endsWith(ImageKeys.BACKFACE_POSTFIX); + final boolean backFace = ImageUtil.hasBackFacePicture(paperCard); final String filename = ImageUtil.getImageKey(paperCard, backFace, true); - destFile = new File(ForgeConstants.CACHE_CARD_PICS_DIR + "/" + filename + ".jpg"); + destFile = new File(ForgeConstants.CACHE_CARD_PICS_DIR, filename + ".jpg"); //move priority of ftp image here StringBuilder setDownload = new StringBuilder(ForgeConstants.URL_PIC_DOWNLOAD); setDownload.append(ImageUtil.getDownloadUrl(paperCard, backFace)); downloadUrls.add(setDownload.toString()); - - int artIndex = 1; - final Pattern pattern = Pattern.compile("^.:([^|]*\\|){2}(\\d+).*$"); - Matcher matcher = pattern.matcher(imageKey); - if (matcher.matches()) { - artIndex = Integer.parseInt(matcher.group(2)); - } - final StaticData data = StaticData.instance(); final String cardCollectorNumber = paperCard.getCollectorNumber(); if (!cardCollectorNumber.equals(IPaperCard.NO_COLLECTOR_NUMBER)){ - String faceParam = ""; - if (paperCard.getRules().getOtherPart() != null) { - faceParam = (backFace ? "&face=back" : "&face=front"); - } - final String editionMciCode = data.getEditions().getMciCodeByCode(paperCard.getEdition()); String langCode = "en"; String UILang = FModel.getPreferences().getPref(ForgePreferences.FPref.UI_LANGUAGE); - if (langCodeMap.containsKey(UILang)) { + if (langCodeMap.containsKey(UILang)) langCode = langCodeMap.get(UILang); - } - // see https://scryfall.com/blog 2020/8/6, and - // https://scryfall.com/docs/api/cards/collector - downloadUrls.add(String.format("https://api.scryfall.com/cards/%s/%s/%s?format=image&version=normal%s", - editionMciCode, cardCollectorNumber, langCode, faceParam)); + final String scryfallURL = this.getScryfallDownloadURL(paperCard, backFace, langCode); + if (scryfallURL == null) + return; // Non existing card, or Card's set not found in Scryfall + downloadUrls.add(scryfallURL); } } else if (prefix.equals(ImageKeys.TOKEN_PREFIX)) { @@ -122,10 +170,11 @@ public abstract class ImageFetcher { } if (destFile.exists()) { - // TODO: Figure out why this codepath gets reached. Ideally, fetchImage() wouldn't - // be called if we already have the image. + // TODO: Figure out why this codepath gets reached. + // Ideally, fetchImage() wouldn't be called if we already have the image. return; } + final String destPath = destFile.getAbsolutePath(); // Note: No synchronization is needed here because this is executed on