package org.mage.plugins.card.images; import java.awt.*; import java.awt.image.BufferedImage; import java.io.*; import java.net.*; import java.nio.file.AccessDeniedException; import java.util.*; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import javax.imageio.IIOImage; import javax.imageio.ImageIO; import javax.imageio.ImageWriteParam; import javax.imageio.ImageWriter; import javax.imageio.stream.FileImageOutputStream; import javax.swing.*; import mage.cards.repository.CardInfo; import mage.client.constants.Constants; import mage.client.dialog.PreferencesDialog; import mage.client.util.sets.ConstructedFormats; import mage.remote.Connection; import mage.util.RandomUtil; import net.java.truevfs.access.TFile; import net.java.truevfs.access.TFileOutputStream; import net.java.truevfs.access.TVFS; import net.java.truevfs.kernel.spec.FsSyncException; import org.apache.log4j.Logger; import org.mage.plugins.card.dl.sources.*; import org.mage.plugins.card.properties.SettingsManager; import org.mage.plugins.card.utils.CardImageUtils; public class DownloadPictures extends DefaultBoundedRangeModel implements Runnable { private static final Logger logger = Logger.getLogger(DownloadPictures.class); private final JProgressBar bar; private final JOptionPane dlg; private boolean cancel; private final JButton closeButton; private final JButton startDownloadButton; private int cardIndex; private List<CardDownloadData> cards; private List<CardDownloadData> type2cards; private final JComboBox jComboBox1; private final JLabel jLabel1; private static boolean offlineMode = false; private JCheckBox checkBox; private final Object sync = new Object(); private static CardImageSource cardImageSource; private Proxy p = Proxy.NO_PROXY; // private ExecutorService executor = Executors.newFixedThreadPool(10); public static void main(String[] args) { startDownload(null, null); } public static void startDownload(JFrame frame, List<CardInfo> allCards) { List<CardDownloadData> cards = getNeededCards(allCards); /* * if (cards == null || cards.isEmpty()) { * JOptionPane.showMessageDialog(null, * "All card pictures have been downloaded."); return; } */ DownloadPictures download = new DownloadPictures(cards); JDialog dlg = download.getDlg(frame); dlg.setVisible(true); dlg.dispose(); download.cancel = true; } public JDialog getDlg(JFrame frame) { String title = "Downloading"; final JDialog dialog = this.dlg.createDialog(frame, title); closeButton.addActionListener(e -> dialog.setVisible(false)); return dialog; } public void setCancel(boolean cancel) { this.cancel = cancel; } public DownloadPictures(List<CardDownloadData> cards) { this.cards = cards; bar = new JProgressBar(this); JPanel p0 = new JPanel(); p0.setLayout(new BoxLayout(p0, BoxLayout.Y_AXIS)); p0.add(Box.createVerticalStrut(5)); jLabel1 = new JLabel(); jLabel1.setText("Please select server:"); jLabel1.setAlignmentX(Component.LEFT_ALIGNMENT); p0.add(jLabel1); p0.add(Box.createVerticalStrut(5)); ComboBoxModel jComboBox1Model = new DefaultComboBoxModel(new String[]{ "magiccards.info", "wizards.com", "mythicspoiler.com", "tokens.mtg.onl", //"mtgimage.com (HQ)", "mtg.onl", "alternative.mtg.onl", "GrabBag", "magidex.com" //"mtgathering.ru HQ", //"mtgathering.ru MQ", //"mtgathering.ru LQ", }); jComboBox1 = new JComboBox(); cardImageSource = MagicCardsImageSource.instance; jComboBox1.setModel(jComboBox1Model); jComboBox1.setAlignmentX(Component.LEFT_ALIGNMENT); jComboBox1.addActionListener(e -> { JComboBox cb = (JComboBox) e.getSource(); switch (cb.getSelectedIndex()) { case 0: cardImageSource = MagicCardsImageSource.instance; break; case 1: cardImageSource = WizardCardsImageSource.instance; break; case 2: cardImageSource = MythicspoilerComSource.instance; break; case 3: cardImageSource = TokensMtgImageSource.instance; break; case 4: cardImageSource = MtgOnlTokensImageSource.instance; break; case 5: cardImageSource = AltMtgOnlTokensImageSource.instance; break; case 6: cardImageSource = GrabbagImageSource.instance; break; case 7: cardImageSource = MagidexImageSource.instance; break; } updateCardsToDownload(); }); p0.add(jComboBox1); p0.add(Box.createVerticalStrut(5)); // Start startDownloadButton = new JButton("Start download"); startDownloadButton.addActionListener(e -> { new Thread(DownloadPictures.this).start(); startDownloadButton.setEnabled(false); checkBox.setEnabled(false); }); p0.add(Box.createVerticalStrut(5)); // Progress p0.add(bar); bar.setStringPainted(true); int count = cards.size(); float mb = (count * cardImageSource.getAverageSize()) / 1024; bar.setString(String.format(cardIndex == cards.size() ? "%d of %d cards finished! Please close!" : "%d of %d cards finished! Please wait! [%.1f Mb]", 0, cards.size(), mb)); Dimension d = bar.getPreferredSize(); d.width = 300; bar.setPreferredSize(d); p0.add(Box.createVerticalStrut(5)); checkBox = new JCheckBox("Download images for Standard (Type2) only"); p0.add(checkBox); p0.add(Box.createVerticalStrut(5)); checkBox.addActionListener(e -> updateCardsToDownload()); // JOptionPane Object[] options = {startDownloadButton, closeButton = new JButton("Cancel")}; dlg = new JOptionPane(p0, JOptionPane.PLAIN_MESSAGE, JOptionPane.DEFAULT_OPTION, null, options, options[1]); } public static boolean checkForMissingCardImages(List<CardInfo> allCards) { AtomicBoolean missedCardTFiles = new AtomicBoolean(); allCards.parallelStream().forEach(card -> { if (!missedCardTFiles.get()) { if (!card.getCardNumber().isEmpty() && !"0".equals(card.getCardNumber()) && !card.getSetCode().isEmpty()) { CardDownloadData url = new CardDownloadData(card.getName(), card.getSetCode(), card.getCardNumber(), card.usesVariousArt(), 0, "", "", false, card.isDoubleFaced(), card.isNightCard()); TFile file = new TFile(CardImageUtils.generateImagePath(url)); if (!file.exists()) { missedCardTFiles.set(true); } } } }); return missedCardTFiles.get(); } private void updateCardsToDownload() { List<CardDownloadData> cardsToDownload = cards; if (type2cardsOnly()) { selectType2andTokenCardsIfNotYetDone(); cardsToDownload = type2cards; } updateProgressText(cardsToDownload.size()); } private boolean type2cardsOnly() { return checkBox.isSelected(); } private void selectType2andTokenCardsIfNotYetDone() { if (type2cards == null) { type2cards = new ArrayList<>(); for (CardDownloadData data : cards) { if (data.isType2() || data.isToken()) { type2cards.add(data); } } } } private void updateProgressText(int cardCount) { float mb = (cardCount * cardImageSource.getAverageSize()) / 1024; bar.setString(String.format(cardIndex == cardCount ? "%d of %d cards finished! Please close!" : "%d of %d cards finished! Please wait! [%.1f Mb]", 0, cardCount, mb)); } private static String createDownloadName(CardInfo card) { String className = card.getClassName(); return className.substring(className.lastIndexOf('.') + 1); } private static List<CardDownloadData> getNeededCards(List<CardInfo> allCards) { /** * read all card names and urls */ HashSet<String> ignoreUrls = SettingsManager.getIntance().getIgnoreUrls(); /** * get filter for Standard Type 2 cards */ Set<String> type2SetsFilter = new HashSet<>(); List<String> constructedFormats = ConstructedFormats.getSetsByFormat(ConstructedFormats.STANDARD); if (constructedFormats != null && !constructedFormats.isEmpty()) { type2SetsFilter.addAll(constructedFormats); } else { logger.warn("No formats defined. Try connecting to a server first!"); } int numberCardImages = allCards.size(); int numberWithoutTokens = 0; List<CardDownloadData> allCardsUrls = Collections.synchronizedList(new ArrayList<>()); try { offlineMode = true; allCards.parallelStream().forEach(card -> { if (!card.getCardNumber().isEmpty() && !"0".equals(card.getCardNumber()) && !card.getSetCode().isEmpty() && !ignoreUrls.contains(card.getSetCode())) { String cardName = card.getName(); boolean isType2 = type2SetsFilter.contains(card.getSetCode()); CardDownloadData url = new CardDownloadData(cardName, card.getSetCode(), card.getCardNumber(), card.usesVariousArt(), 0, "", "", false, card.isDoubleFaced(), card.isNightCard()); if (url.getUsesVariousArt()) { url.setDownloadName(createDownloadName(card)); } url.setFlipCard(card.isFlipCard()); url.setSplitCard(card.isSplitCard()); url.setType2(isType2); allCardsUrls.add(url); if (card.isDoubleFaced()) { if (card.getSecondSideName() == null || card.getSecondSideName().trim().isEmpty()) { throw new IllegalStateException("Second side card can't have empty name."); } url = new CardDownloadData(card.getSecondSideName(), card.getSetCode(), card.getCardNumber(), card.usesVariousArt(), 0, "", "", false, card.isDoubleFaced(), true); url.setType2(isType2); allCardsUrls.add(url); } if (card.isFlipCard()) { if (card.getFlipCardName() == null || card.getFlipCardName().trim().isEmpty()) { throw new IllegalStateException("Flipped card can't have empty name."); } CardDownloadData cardDownloadData = new CardDownloadData(card.getFlipCardName(), card.getSetCode(), card.getCardNumber(), card.usesVariousArt(), 0, "", "", false, card.isDoubleFaced(), card.isNightCard()); cardDownloadData.setFlipCard(true); cardDownloadData.setFlippedSide(true); cardDownloadData.setType2(isType2); allCardsUrls.add(cardDownloadData); } } else if (card.getCardNumber().isEmpty() || "0".equals(card.getCardNumber())) { System.err.println("There was a critical error!"); logger.error("Card has no collector ID and won't be sent to client: " + card.getName()); } else if (card.getSetCode().isEmpty()) { System.err.println("There was a critical error!"); logger.error("Card has no set name and won't be sent to client:" + card.getName()); } }); numberWithoutTokens = allCards.size(); allCardsUrls.addAll(getTokenCardUrls()); } catch (Exception e) { logger.error(e); } int numberAllTokenImages = allCardsUrls.size() - numberWithoutTokens; /** * check to see which cards we already have */ List<CardDownloadData> cardsToDownload = Collections.synchronizedList(new ArrayList<>()); allCardsUrls.parallelStream().forEach(card -> { TFile file = new TFile(CardImageUtils.generateImagePath(card)); logger.debug(card.getName() + " (is_token=" + card.isToken() + "). Image is here:" + file.getAbsolutePath() + " (exists=" + file.exists() + ')'); if (!file.exists()) { logger.debug("Missing: " + file.getAbsolutePath()); cardsToDownload.add(card); } }); int tokenImages = 0; for (CardDownloadData card : cardsToDownload) { logger.debug((card.isToken() ? "Token" : "Card") + " image to download: " + card.getName() + " (" + card.getSet() + ')'); if (card.isToken()) { tokenImages++; } } logger.info("Check download images (total card images: " + numberCardImages + ", total token images: " + numberAllTokenImages + ')'); logger.info(" => Missing card images: " + (cardsToDownload.size() - tokenImages)); logger.info(" => Missing token images: " + tokenImages); return new ArrayList<>(cardsToDownload); } public static ArrayList<CardDownloadData> getTokenCardUrls() throws RuntimeException { ArrayList<CardDownloadData> list = new ArrayList<>(); InputStream in = DownloadPictures.class.getClassLoader().getResourceAsStream("card-pictures-tok.txt"); if (in == null) { logger.error("resources input stream is null"); return list; } try (InputStreamReader input = new InputStreamReader(in); BufferedReader reader = new BufferedReader(input)) { String line = reader.readLine(); while (line != null) { line = line.trim(); if (line.startsWith("|")) { // new format String[] params = line.split("\\|", -1); if (params.length >= 5) { int type = 0; if (params[4] != null && !params[4].isEmpty()) { type = Integer.parseInt(params[4].trim()); } String fileName = ""; if (params.length > 5 && params[5] != null && !params[5].isEmpty()) { fileName = params[5].trim(); } String tokenClassName = ""; if (params.length > 7 && params[6] != null && !params[6].isEmpty()) { tokenClassName = params[6].trim(); } if (params[1].toLowerCase().equals("generate") && params[2].startsWith("TOK:")) { String set = params[2].substring(4); CardDownloadData card = new CardDownloadData(params[3], set, "0", false, type, "", "", true); card.setTokenClassName(tokenClassName); list.add(card); } else if (params[1].toLowerCase().equals("generate") && params[2].startsWith("EMBLEM:")) { String set = params[2].substring(7); CardDownloadData card = new CardDownloadData("Emblem " + params[3], set, "0", false, type, "", "", true, fileName); card.setTokenClassName(tokenClassName); list.add(card); } else if (params[1].toLowerCase().equals("generate") && params[2].startsWith("EMBLEM-:")) { String set = params[2].substring(8); CardDownloadData card = new CardDownloadData(params[3] + " Emblem", set, "0", false, type, "", "", true, fileName); card.setTokenClassName(tokenClassName); list.add(card); } else if (params[1].toLowerCase().equals("generate") && params[2].startsWith("EMBLEM!:")) { String set = params[2].substring(8); CardDownloadData card = new CardDownloadData(params[3], set, "0", false, type, "", "", true, fileName); card.setTokenClassName(tokenClassName); list.add(card); } } else { logger.error("wrong format for image urls: " + line); } } line = reader.readLine(); } } catch (Exception ex) { logger.error(ex); throw new RuntimeException("DownloadPictures : readFile() error"); } return list; } @Override public void run() { this.cardIndex = 0; File base = new File(Constants.IO.imageBaseDir); if (!base.exists()) { base.mkdir(); } Connection.ProxyType configProxyType = Connection.ProxyType.valueByText(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_PROXY_TYPE, "None")); Proxy.Type type = Proxy.Type.DIRECT; switch (configProxyType) { case HTTP: type = Proxy.Type.HTTP; break; case SOCKS: type = Proxy.Type.SOCKS; break; case NONE: default: p = Proxy.NO_PROXY; break; } if (type != Proxy.Type.DIRECT) { try { String address = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_PROXY_ADDRESS, ""); Integer port = Integer.parseInt(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_PROXY_PORT, "80")); p = new Proxy(type, new InetSocketAddress(address, port)); } catch (Exception ex) { throw new RuntimeException("Gui_DownloadPictures : error 1 - " + ex); } } if (p != null) { HashSet<String> ignoreUrls = SettingsManager.getIntance().getIgnoreUrls(); List<CardDownloadData> cardsToDownload = this.checkBox.isSelected() ? type2cards : cards; update(0, cardsToDownload.size()); int numberOfThreads = Integer.parseInt(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_CARD_IMAGES_THREADS, "10")); ExecutorService executor = Executors.newFixedThreadPool(numberOfThreads); for (int i = 0; i < cardsToDownload.size() && !cancel; i++) { try { CardDownloadData card = cardsToDownload.get(i); logger.debug("Downloading card: " + card.getName() + " (" + card.getSet() + ')'); String url; if (ignoreUrls.contains(card.getSet()) || card.isToken()) { if (!"0".equals(card.getCollectorId())) { continue; } url = cardImageSource.generateTokenUrl(card); } else { url = cardImageSource.generateURL(card); } if (url == null) { String imageRef = cardImageSource.getNextHttpImageUrl(); String fileName = cardImageSource.getFileForHttpImage(imageRef); if (imageRef != null && fileName != null) { imageRef = cardImageSource.getSourceName() + imageRef; try { URL imageUrl = new URL(imageRef); card.setToken(cardImageSource.isTokenSource()); Runnable task = new DownloadTask(card, imageUrl, fileName, cardImageSource.getTotalImages()); executor.execute(task); } catch (Exception ex) { } } else if (cardImageSource.getTotalImages() == -1) { logger.info("Card not available on " + cardImageSource.getSourceName() + ": " + card.getName() + " (" + card.getSet() + ')'); synchronized (sync) { update(cardIndex + 1, cardsToDownload.size()); } } } else { Runnable task = new DownloadTask(card, new URL(url), cardsToDownload.size()); executor.execute(task); } } catch (Exception ex) { logger.error(ex, ex); } } executor.shutdown(); while (!executor.isTerminated()) { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException ie) { } } } try { TVFS.umount(); } catch (FsSyncException e) { logger.fatal("Couldn't unmount zip files", e); JOptionPane.showMessageDialog(null, "Couldn't unmount zip files", "Error", JOptionPane.ERROR_MESSAGE); } finally { System.gc(); } closeButton.setText("Close"); } static String convertStreamToString(java.io.InputStream is) { java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A"); return s.hasNext() ? s.next() : ""; } private final class DownloadTask implements Runnable { private final CardDownloadData card; private final URL url; private final int count; private final String actualFilename; private final boolean useSpecifiedPaths; DownloadTask(CardDownloadData card, URL url, int count) { this.card = card; this.url = url; this.count = count; this.actualFilename = ""; useSpecifiedPaths = false; } DownloadTask(CardDownloadData card, URL url, String actualFilename, int count) { this.card = card; this.url = url; this.count = count; this.actualFilename = actualFilename; useSpecifiedPaths = true; } @Override public void run() { StringBuilder filePath = new StringBuilder(); File temporaryFile = null; TFile outputFile = null; try { filePath.append(Constants.IO.imageBaseDir); if (!useSpecifiedPaths && card != null) { filePath.append(card.hashCode()).append('.').append(card.getName().replace(":", "").replace("//", "-")).append(".jpg"); temporaryFile = new File(filePath.toString()); } String imagePath; if (useSpecifiedPaths) { if (card != null && card.isToken()) { imagePath = CardImageUtils.getTokenBasePath() + actualFilename; } else if (card != null) { imagePath = CardImageUtils.getImageBasePath() + actualFilename; } else { imagePath = Constants.IO.imageBaseDir; } String tmpFile = filePath + "temporary" + actualFilename; temporaryFile = new File(tmpFile); if (!temporaryFile.exists()) { temporaryFile.getParentFile().mkdirs(); } } else { imagePath = CardImageUtils.generateImagePath(card); } outputFile = new TFile(imagePath); if (!outputFile.exists()) { outputFile.getParentFile().mkdirs(); } File existingFile = new File(imagePath.replaceFirst("\\w{3}.zip", "")); if (existingFile.exists()) { try { new TFile(existingFile).cp_rp(outputFile); } catch (IOException e) { logger.error("Error while copying file " + card.getName(), e); } synchronized (sync) { update(cardIndex + 1, count); } existingFile.delete(); File parent = existingFile.getParentFile(); if (parent != null && parent.isDirectory() && parent.list().length == 0) { parent.delete(); } return; } // Logger.getLogger(this.getClass()).info(url.toString()); boolean useTempFile = false; int responseCode = 0; URLConnection httpConn = null; if (temporaryFile != null && temporaryFile.length() > 100) { useTempFile = true; } else { cardImageSource.doPause(url.getPath()); httpConn = url.openConnection(p); setUpConnection(httpConn); httpConn.connect(); responseCode = ((HttpURLConnection) httpConn).getResponseCode(); } if (responseCode == 200 || useTempFile) { if (!useTempFile) { BufferedOutputStream out; try (BufferedInputStream in = new BufferedInputStream(httpConn.getInputStream())) { //try (BufferedInputStream in = new BufferedInputStream(url.openConnection(p).getInputStream())) { out = new BufferedOutputStream(new TFileOutputStream(temporaryFile)); byte[] buf = new byte[1024]; int len; while ((len = in.read(buf)) != -1) { // user cancelled if (cancel) { in.close(); out.flush(); out.close(); temporaryFile.delete(); return; } out.write(buf, 0, len); } } out.flush(); out.close(); } if (card != null && card.isTwoFacedCard()) { BufferedImage image = ImageIO.read(temporaryFile); if (image.getHeight() == 470) { BufferedImage renderedImage = new BufferedImage(265, 370, BufferedImage.TYPE_INT_RGB); renderedImage.getGraphics(); Graphics2D graphics2D = renderedImage.createGraphics(); if (card.isTwoFacedCard() && card.isSecondSide()) { graphics2D.drawImage(image, 0, 0, 265, 370, 313, 62, 578, 432, null); } else { graphics2D.drawImage(image, 0, 0, 265, 370, 41, 62, 306, 432, null); } graphics2D.dispose(); writeImageToFile(renderedImage, outputFile); } else { outputFile.getParentFile().mkdirs(); new TFile(temporaryFile).cp_rp(outputFile); } //temporaryFile.delete(); } else { outputFile.getParentFile().mkdirs(); new TFile(temporaryFile).cp_rp(outputFile); } } else { if (card != null && !useSpecifiedPaths) { logger.warn("Image download for " + card.getName() + (!card.getDownloadName().equals(card.getName()) ? " downloadname: " + card.getDownloadName() : "") + '(' + card.getSet() + ") failed - responseCode: " + responseCode + " url: " + url.toString()); } if (logger.isDebugEnabled()) { // Shows the returned html from the request to the web server logger.debug("Returned HTML ERROR:\n" + convertStreamToString(((HttpURLConnection) httpConn).getErrorStream())); } } } catch (AccessDeniedException e) { logger.error("The file " + (outputFile != null ? outputFile.toString() : "to add the image of " + card.getName() + '(' + card.getSet() + ')') + " can't be accessed. Try rebooting your system to remove the file lock."); } catch (Exception e) { logger.error(e, e); } finally { if (temporaryFile != null) { temporaryFile.delete(); } } synchronized (sync) { update(cardIndex + 1, count); } } private void setUpConnection(URLConnection httpConn) { // images download from magiccards.info may not work with default 'User-Agent: Java/1.x.x' request header switch (RandomUtil.nextInt(3)) { // chrome case 0: httpConn.setRequestProperty("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"); httpConn.setRequestProperty("Accept-Encoding", "gzip, deflate, sdch"); httpConn.setRequestProperty("Accept-Language", "en-US,en;q=0.8"); httpConn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36"); break; // ff case 1: httpConn.setRequestProperty("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); httpConn.setRequestProperty("Accept-Encoding", "gzip, deflate"); httpConn.setRequestProperty("Accept-Language", "en-US;q=0.5,en;q=0.3"); httpConn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 6.3; Win64; x64; rv:50.0) Gecko/20100101 Firefox/50.0"); break; // ie case 2: httpConn.setRequestProperty("Accept", "text/html, application/xhtml+xml, */*"); httpConn.setRequestProperty("Accept-Encoding", "gzip, deflate"); httpConn.setRequestProperty("Accept-Language", "en-US"); httpConn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko"); break; } } private void writeImageToFile(BufferedImage image, TFile file) throws IOException { Iterator iter = ImageIO.getImageWritersByFormatName("jpg"); ImageWriter writer = (ImageWriter) iter.next(); ImageWriteParam iwp = writer.getDefaultWriteParam(); iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); iwp.setCompressionQuality(0.96f); File tempFile = new File(Constants.IO.imageBaseDir + File.separator + image.hashCode() + file.getName()); FileImageOutputStream output = new FileImageOutputStream(tempFile); writer.setOutput(output); IIOImage image2 = new IIOImage(image, null, null); writer.write(null, image2, iwp); writer.dispose(); output.close(); new TFile(tempFile).cp_rp(file); tempFile.delete(); } } private void update(int card, int count) { this.cardIndex = card; if (cardIndex < count) { float mb = ((count - card) * cardImageSource.getAverageSize()) / 1024; bar.setString(String.format("%d of %d cards finished! Please wait! [%.1f Mb]", card, count, mb)); } else { List<CardDownloadData> remainingCards = Collections.synchronizedList(new ArrayList<>()); DownloadPictures.this.cards.parallelStream().forEach(cardDownloadData -> { TFile file = new TFile(CardImageUtils.generateImagePath(cardDownloadData)); if (!file.exists()) { remainingCards.add(cardDownloadData); } }); DownloadPictures.this.cards = new ArrayList<>(remainingCards); count = DownloadPictures.this.cards.size(); if (count == 0) { bar.setString("0 cards remaining! Please close!"); } else { bar.setString(String.format("%d cards remaining! Please choose another source!", count)); startDownloadButton.setEnabled(true); } } } private static final long serialVersionUID = 1L; }