Merge branch 'add-mobile-art-download' into 'master'

Add option to download missing card art on the fly

See merge request core-developers/forge!1049
This commit is contained in:
Michael Kamensky
2018-11-03 16:23:35 +00:00
15 changed files with 277 additions and 75 deletions

View File

@@ -1,5 +1,9 @@
package forge.interfaces;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public interface IDeviceAdapter {
boolean isConnectedToInternet();
boolean isConnectedToWifi();
@@ -10,4 +14,5 @@ public interface IDeviceAdapter {
void preventSystemSleep(boolean preventSleep);
void restart();
void exit();
void convertToJPEG(InputStream input, OutputStream output) throws IOException;
}

View File

@@ -16,12 +16,14 @@ import forge.match.HostedMatch;
import forge.sound.IAudioClip;
import forge.sound.IAudioMusic;
import forge.util.Callback;
import forge.util.ImageFetcher;
public interface IGuiBase {
boolean isRunningOnDesktop();
boolean isLibgdxPort();
String getCurrentVersion();
String getAssetsDir();
ImageFetcher getImageFetcher();
void invokeInEdtNow(Runnable runnable);
void invokeInEdtLater(Runnable runnable);
void invokeInEdtAndWait(Runnable proc);

View File

@@ -0,0 +1,124 @@
package forge.util;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import forge.FThreads;
import forge.ImageKeys;
import forge.StaticData;
import org.apache.commons.lang3.tuple.Pair;
import forge.item.PaperCard;
import forge.model.FModel;
import forge.properties.ForgeConstants;
import forge.properties.ForgePreferences;
public abstract class ImageFetcher {
private static final ExecutorService threadPool = Executors.newCachedThreadPool();
private HashMap<String, HashSet<Callback>> currentFetches = new HashMap<>();
private HashMap<String, String> tokenImages;
public void fetchImage(final String imageKey, final Callback callback) {
FThreads.assertExecutedByEdt(true);
if (!FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.UI_ENABLE_ONLINE_IMAGE_FETCHER))
return;
// Fake card (like the ante prompt) trying to be "fetched"
if (imageKey.length() < 2)
return;
final String prefix = imageKey.substring(0, 2);
final ArrayList<String> downloadUrls = new ArrayList<>();
File destFile = null;
if (prefix.equals(ImageKeys.CARD_PREFIX)) {
PaperCard paperCard = ImageUtil.getPaperCardFromImageKey(imageKey);
if (paperCard == null) {
System.err.println("Paper card not found for: " + imageKey);
return;
}
final boolean backFace = imageKey.endsWith(ImageKeys.BACKFACE_POSTFIX);
final String filename = ImageUtil.getImageKey(paperCard, backFace, true);
destFile = new File(ForgeConstants.CACHE_CARD_PICS_DIR + "/" + filename + ".jpg");
// First try to download the LQ Set URL, then fetch from scryfall/magiccards.info
StringBuilder setDownload = new StringBuilder(ForgeConstants.URL_PIC_DOWNLOAD);
setDownload.append(ImageUtil.getDownloadUrl(paperCard, backFace));
downloadUrls.add(setDownload.toString());
final StaticData data = StaticData.instance();
final int cardNum = data.getCommonCards().getCardCollectorNumber(paperCard.getName(), paperCard.getEdition());
if (cardNum != -1) {
String suffix = "";
if (paperCard.getRules().getOtherPart() != null) {
suffix = (backFace ? "b" : "a");
}
final String editionMciCode = data.getEditions().getMciCodeByCode(paperCard.getEdition());
downloadUrls.add(String.format("https://img.scryfall.com/cards/normal/en/%s/%d%s.jpg", editionMciCode, cardNum, suffix));
downloadUrls.add(String.format("https://magiccards.info/scans/en/%s/%d%s.jpg", editionMciCode, cardNum, suffix));
}
} else if (prefix.equals(ImageKeys.TOKEN_PREFIX)) {
if (tokenImages == null) {
tokenImages = new HashMap<>();
for (Pair<String, String> nameUrlPair : FileUtil.readNameUrlFile(ForgeConstants.IMAGE_LIST_TOKENS_FILE)) {
tokenImages.put(nameUrlPair.getLeft(), nameUrlPair.getRight());
}
}
final String filename = imageKey.substring(2) + ".jpg";
String tokenUrl = tokenImages.get(filename);
if (tokenUrl == null) {
System.err.println("No specified file.. Attempting to download from default Url");
tokenUrl = String.format("%s%s", ForgeConstants.URL_TOKEN_DOWNLOAD, filename);
}
destFile = new File(ForgeConstants.CACHE_TOKEN_PICS_DIR, filename);
downloadUrls.add(tokenUrl);
}
if (downloadUrls.isEmpty()) {
System.err.println("No download URLs for: " + imageKey);
return;
}
if (destFile.exists()) {
// 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
// EDT thread (see assert on top) and so is the notification of observers.
HashSet<Callback> observers = currentFetches.get(destPath);
if (observers != null) {
// Already in the queue, simply add the new observer.
observers.add(callback);
return;
}
observers = new HashSet<>();
observers.add(callback);
currentFetches.put(destPath, observers);
final Runnable notifyObservers = new Runnable() {
public void run() {
FThreads.assertExecutedByEdt(true);
for (Callback o : currentFetches.get(destPath)) {
o.onImageFetched();
}
currentFetches.remove(destPath);
}
};
threadPool.submit(getDownloadTask(downloadUrls.toArray(new String[0]), destPath, notifyObservers));
}
protected abstract Runnable getDownloadTask(String[] toArray, String destPath, Runnable notifyObservers);
public static interface Callback {
public void onImageFetched();
}
}