From 30fbe55ac3ef34d3d8051c422908d26b24778e44 Mon Sep 17 00:00:00 2001 From: drdev Date: Sun, 25 Jan 2015 05:10:32 +0000 Subject: [PATCH] Add Net Decks support --- .gitattributes | 4 + .../src/main/java/forge/GuiDesktop.java | 8 + .../java/forge/deckchooser/DecksComboBox.java | 21 +- .../java/forge/deckchooser/FDeckChooser.java | 95 ++++++-- .../java/forge/download/GuiDownloader.java | 10 + .../main/java/forge/toolbox/FComboBox.java | 16 +- .../java/forge/toolbox/FComboBoxWrapper.java | 69 +++--- .../main/java/forge/toolbox/FProgressBar.java | 19 +- forge-gui-mobile/src/forge/GuiMobile.java | 8 + .../src/forge/assets/AssetsDownloader.java | 122 +---------- .../src/forge/deck/FDeckChooser.java | 101 +++++++-- .../forge/screens/settings/GuiDownloader.java | 15 +- .../src/forge/toolbox/FComboBox.java | 9 +- .../src/forge/toolbox/FEvent.java | 3 +- forge-gui/CHANGES.txt | 6 + forge-gui/res/lists/net-decks-commander.txt | 10 + forge-gui/res/lists/net-decks.txt | 28 +++ .../src/main/java/forge/deck/DeckProxy.java | 8 + .../src/main/java/forge/deck/DeckType.java | 3 +- .../main/java/forge/deck/NetDeckCategory.java | 127 +++++++++++ .../forge/download/GuiDownloadService.java | 112 ++++++---- .../forge/download/GuiDownloadZipService.java | 203 ++++++++++++++++++ .../main/java/forge/interfaces/IGuiBase.java | 3 + .../forge/itemmanager/ItemManagerConfig.java | 2 + .../java/forge/properties/ForgeConstants.java | 5 + 25 files changed, 756 insertions(+), 251 deletions(-) create mode 100644 forge-gui/res/lists/net-decks-commander.txt create mode 100644 forge-gui/res/lists/net-decks.txt create mode 100644 forge-gui/src/main/java/forge/deck/NetDeckCategory.java create mode 100644 forge-gui/src/main/java/forge/download/GuiDownloadZipService.java diff --git a/.gitattributes b/.gitattributes index 7362b551887..10da3c5bdf7 100644 --- a/.gitattributes +++ b/.gitattributes @@ -16050,6 +16050,8 @@ forge-gui/res/lists/TypeLists.txt svneol=native#text/plain forge-gui/res/lists/booster-images.txt svneol=native#text/plain forge-gui/res/lists/boosterbox-images.txt -text forge-gui/res/lists/fatpack-images.txt svneol=native#text/plain +forge-gui/res/lists/net-decks-commander.txt -text +forge-gui/res/lists/net-decks.txt -text forge-gui/res/lists/precon-images.txt svneol=native#text/plain forge-gui/res/lists/quest-opponent-icons.txt svneol=native#text/plain forge-gui/res/lists/quest-pet-shop-icons.txt svneol=native#text/plain @@ -17331,6 +17333,7 @@ forge-gui/src/main/java/forge/deck/DeckGeneratorTheme.java -text forge-gui/src/main/java/forge/deck/DeckProxy.java -text forge-gui/src/main/java/forge/deck/DeckType.java -text forge-gui/src/main/java/forge/deck/DeckgenUtil.java -text +forge-gui/src/main/java/forge/deck/NetDeckCategory.java -text forge-gui/src/main/java/forge/deck/RandomDeckGenerator.java -text forge-gui/src/main/java/forge/deck/io/DeckPreferences.java -text forge-gui/src/main/java/forge/deck/io/OldDeckParser.java -text @@ -17339,6 +17342,7 @@ forge-gui/src/main/java/forge/download/GuiDownloadPrices.java -text forge-gui/src/main/java/forge/download/GuiDownloadQuestImages.java -text forge-gui/src/main/java/forge/download/GuiDownloadService.java -text forge-gui/src/main/java/forge/download/GuiDownloadSetPicturesLQ.java -text +forge-gui/src/main/java/forge/download/GuiDownloadZipService.java -text forge-gui/src/main/java/forge/error/BugReporter.java -text forge-gui/src/main/java/forge/error/ExceptionHandler.java svneol=native#text/plain forge-gui/src/main/java/forge/error/package-info.java svneol=native#text/plain diff --git a/forge-gui-desktop/src/main/java/forge/GuiDesktop.java b/forge-gui-desktop/src/main/java/forge/GuiDesktop.java index 242c6f8975d..defbf11900d 100644 --- a/forge-gui-desktop/src/main/java/forge/GuiDesktop.java +++ b/forge-gui-desktop/src/main/java/forge/GuiDesktop.java @@ -23,6 +23,8 @@ import forge.assets.FSkinProp; import forge.assets.ISkinImage; import forge.control.GuiTimer; import forge.deck.CardPool; +import forge.download.GuiDownloadService; +import forge.download.GuiDownloader; import forge.error.BugReportDialog; import forge.game.GameEntity; import forge.game.GameEntityView; @@ -50,6 +52,7 @@ import forge.toolbox.FOptionPane; import forge.toolbox.FSkin; import forge.toolbox.FSkin.SkinImage; import forge.util.BuildInfo; +import forge.util.Callback; import forge.util.FCollectionView; import forge.util.FileUtil; import forge.util.gui.SGuiChoose; @@ -258,6 +261,11 @@ public class GuiDesktop implements IGuiBase { return fc.getSelectedFile(); } + @Override + public void download(GuiDownloadService service, Callback callback) { + new GuiDownloader(service, callback); + } + @Override public void copyToClipboard(String text) { StringSelection ss = new StringSelection(text); diff --git a/forge-gui-desktop/src/main/java/forge/deckchooser/DecksComboBox.java b/forge-gui-desktop/src/main/java/forge/deckchooser/DecksComboBox.java index b339b22debb..816051fc4e2 100644 --- a/forge-gui-desktop/src/main/java/forge/deckchooser/DecksComboBox.java +++ b/forge-gui-desktop/src/main/java/forge/deckchooser/DecksComboBox.java @@ -33,13 +33,16 @@ public class DecksComboBox extends FComboBoxWrapper { return new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - MouseUtil.setCursor(Cursor.WAIT_CURSOR); - DeckType newDeckType = (DeckType)getSelectedItem(); - if (newDeckType != selectedDeckType) { - notifyDeckTypeSelected(newDeckType); - selectedDeckType = newDeckType; + Object selectedItem = getSelectedItem(); + if (selectedItem instanceof DeckType) { + MouseUtil.setCursor(Cursor.WAIT_CURSOR); + DeckType newDeckType = (DeckType)selectedItem; + if (newDeckType != selectedDeckType) { + selectedDeckType = newDeckType; + notifyDeckTypeSelected(newDeckType); + } + MouseUtil.resetCursor(); } - MouseUtil.resetCursor(); } }; } @@ -68,4 +71,10 @@ public class DecksComboBox extends FComboBoxWrapper { selectedDeckType = valueOf; setSelectedItem(selectedDeckType); } + + @Override + public void setText(String text0) { + selectedDeckType = null; //ensure selecting current deck type again raises event + super.setText(text0); + } } diff --git a/forge-gui-desktop/src/main/java/forge/deckchooser/FDeckChooser.java b/forge-gui-desktop/src/main/java/forge/deckchooser/FDeckChooser.java index 5a4d22e8304..79248046292 100644 --- a/forge-gui-desktop/src/main/java/forge/deckchooser/FDeckChooser.java +++ b/forge-gui-desktop/src/main/java/forge/deckchooser/FDeckChooser.java @@ -7,6 +7,7 @@ import forge.deck.Deck; import forge.deck.DeckProxy; import forge.deck.DeckType; import forge.deck.DeckgenUtil; +import forge.deck.NetDeckCategory; import forge.deck.RandomDeckGenerator; import forge.game.GameType; import forge.game.player.RegisteredPlayer; @@ -38,6 +39,8 @@ public class FDeckChooser extends JPanel implements IDecksComboBoxListener { private DecksComboBox decksComboBox; private DeckType selectedDeckType; private ItemManagerContainer lstDecksContainer; + private NetDeckCategory netDeckCategory; + private boolean refreshingDeckType; private final DeckManager lstDecks = new DeckManager(GameType.Constructed); private final FLabel btnViewDeck = new FLabel.ButtonBuilder().text("View Deck").fontSize(14).build(); @@ -219,6 +222,26 @@ public class FDeckChooser extends JPanel implements IDecksComboBoxListener { }); } + private void updateNetDecks() { + if (netDeckCategory != null) { + decksComboBox.setText(netDeckCategory.toString()); + } + lstDecks.setAllowMultipleSelections(false); + + lstDecks.setPool(DeckProxy.getNetDecks(netDeckCategory)); + lstDecks.setup(ItemManagerConfig.NET_DECKS); + + btnRandom.setText("Random Deck"); + btnRandom.setCommand(new UiCommand() { + @Override + public void run() { + DeckgenUtil.randomSelect(lstDecks); + } + }); + + lstDecks.setSelectedIndex(0); + } + public Deck getDeck() { DeckProxy proxy = lstDecks.getSelectedItem(); return proxy.getDeck(); @@ -269,14 +292,36 @@ public class FDeckChooser extends JPanel implements IDecksComboBoxListener { } public void setIsAi(boolean isAiDeck) { - this.isAi = isAiDeck; + isAi = isAiDeck; } - /* (non-Javadoc) - * @see forge.gui.deckchooser.IDecksComboBoxListener#deckTypeSelected(forge.gui.deckchooser.DecksComboBoxEvent) - */ @Override - public void deckTypeSelected(DecksComboBoxEvent ev) { + public void deckTypeSelected(final DecksComboBoxEvent ev) { + if (ev.getDeckType() == DeckType.NET_DECK && !refreshingDeckType) { + FThreads.invokeInBackgroundThread(new Runnable() { //needed for loading net decks + @Override + public void run() { + final NetDeckCategory category = NetDeckCategory.selectAndLoad(lstDecks.getGameType()); + + FThreads.invokeInEdtLater(new Runnable() { + @Override + public void run() { + if (category == null) { + decksComboBox.setDeckType(selectedDeckType); //restore old selection if user cancels + if (selectedDeckType == DeckType.NET_DECK && netDeckCategory != null) { + decksComboBox.setText(netDeckCategory.toString()); + } + return; + } + + netDeckCategory = category; + refreshDecksList(DeckType.NET_DECK, true, ev); + } + }); + } + }); + return; + } refreshDecksList(ev.getDeckType(), false, ev); } @@ -285,7 +330,9 @@ public class FDeckChooser extends JPanel implements IDecksComboBoxListener { selectedDeckType = deckType; if (ev == null) { + refreshingDeckType = true; decksComboBox.refresh(deckType); + refreshingDeckType = false; } lstDecks.setCaption(deckType.toString()); @@ -308,6 +355,9 @@ public class FDeckChooser extends JPanel implements IDecksComboBoxListener { case RANDOM_DECK: updateRandom(); break; + case NET_DECK: + updateNetDecks(); + break; } } @@ -322,8 +372,15 @@ public class FDeckChooser extends JPanel implements IDecksComboBoxListener { } private String getState() { - String deckType = decksComboBox.getDeckType().name(); - StringBuilder state = new StringBuilder(deckType); + StringBuilder state = new StringBuilder(); + if (decksComboBox.getDeckType() == null || decksComboBox.getDeckType() == DeckType.NET_DECK) { + //handle special case of net decks + if (netDeckCategory == null) { return ""; } + state.append(NetDeckCategory.PREFIX + netDeckCategory.getName()); + } + else { + state.append(decksComboBox.getDeckType().name()); + } state.append(";"); joinSelectedDecks(state, SELECTED_DECK_DELIMITER); return state.toString(); @@ -345,25 +402,20 @@ public class FDeckChooser extends JPanel implements IDecksComboBoxListener { } } - /** Returns a clean name from the state that can be used for labels. */ - public final String getStateForLabel() { - String deckType = decksComboBox.getDeckType().toString(); - StringBuilder state = new StringBuilder(deckType); - state.append(": "); - joinSelectedDecks(state, ", "); - return state.toString(); - } - private void restoreSavedState() { + DeckType oldDeckType = selectedDeckType; if (stateSetting == null) { //if can't restore saved state, just refresh deck list - refreshDecksList(selectedDeckType, true, null); + refreshDecksList(oldDeckType, true, null); return; } String savedState = prefs.getPref(stateSetting); refreshDecksList(getDeckTypeFromSavedState(savedState), true, null); - lstDecks.setSelectedStrings(getSelectedDecksFromSavedState(savedState)); + if (!lstDecks.setSelectedStrings(getSelectedDecksFromSavedState(savedState))) { + //if can't select old decks, just refresh deck list + refreshDecksList(oldDeckType, true, null); + } } private DeckType getDeckTypeFromSavedState(String savedState) { @@ -372,7 +424,12 @@ public class FDeckChooser extends JPanel implements IDecksComboBoxListener { return selectedDeckType; } else { - return DeckType.valueOf(savedState.split(";")[0]); + String deckType = savedState.split(";")[0]; + if (deckType.startsWith(NetDeckCategory.PREFIX)) { + netDeckCategory = NetDeckCategory.selectAndLoad(lstDecks.getGameType(), deckType.substring(NetDeckCategory.PREFIX.length())); + return DeckType.NET_DECK; + } + return DeckType.valueOf(deckType); } } catch (IllegalArgumentException ex) { diff --git a/forge-gui-desktop/src/main/java/forge/download/GuiDownloader.java b/forge-gui-desktop/src/main/java/forge/download/GuiDownloader.java index 7b5559e9dd0..5632e956420 100644 --- a/forge-gui-desktop/src/main/java/forge/download/GuiDownloader.java +++ b/forge-gui-desktop/src/main/java/forge/download/GuiDownloader.java @@ -23,6 +23,7 @@ import forge.UiCommand; import forge.assets.FSkinProp; import forge.gui.SOverlayUtils; import forge.toolbox.*; +import forge.util.Callback; import net.miginfocom.swing.MigLayout; import javax.swing.*; @@ -47,6 +48,10 @@ public class GuiDownloader extends DefaultBoundedRangeModel { // Kill overlay SOverlayUtils.hideOverlay(); + + if (callback != null) { + callback.run(btnStart.getText() == "OK"); //determine result based on whether download finished + } } }; @@ -58,9 +63,14 @@ public class GuiDownloader extends DefaultBoundedRangeModel { private final FRadioButton radProxyHTTP = new FRadioButton("HTTP Proxy"); private final GuiDownloadService service; + private final Callback callback; public GuiDownloader(GuiDownloadService service0) { + this(service0, null); + } + public GuiDownloader(GuiDownloadService service0, Callback callback0) { service = service0; + callback = callback0; String radConstraints = "w 100%!, h 30px!, gap 2% 0 0 10px"; JXButtonPanel grpPanel = new JXButtonPanel(); diff --git a/forge-gui-desktop/src/main/java/forge/toolbox/FComboBox.java b/forge-gui-desktop/src/main/java/forge/toolbox/FComboBox.java index 24522ccbdab..9fb8fecb55b 100644 --- a/forge-gui-desktop/src/main/java/forge/toolbox/FComboBox.java +++ b/forge-gui-desktop/src/main/java/forge/toolbox/FComboBox.java @@ -52,7 +52,19 @@ public class FComboBox extends SkinnedComboBox implements IComboBox { private Border getDefaultBorder() { return UIManager.getBorder("ComboBox.border"); } - + + public String getText() { + Object selectedItem = getSelectedItem(); + if (selectedItem == null) { + return ""; + } + return selectedItem.toString(); + } + public void setText(String text0) { + setSelectedItem(null); + dataModel.setSelectedItem(text0); //use this to get around inability to set selected item that's not in items + } + public TextAlignment getTextAlignment() { return textAlignment; } @@ -60,7 +72,7 @@ public class FComboBox extends SkinnedComboBox implements IComboBox { public void setTextAlignment(TextAlignment align) { textAlignment = align; } - + public SkinFont getSkinFont() { return this.skinFont; } diff --git a/forge-gui-desktop/src/main/java/forge/toolbox/FComboBoxWrapper.java b/forge-gui-desktop/src/main/java/forge/toolbox/FComboBoxWrapper.java index 4d6bb332579..1db54d85517 100644 --- a/forge-gui-desktop/src/main/java/forge/toolbox/FComboBoxWrapper.java +++ b/forge-gui-desktop/src/main/java/forge/toolbox/FComboBoxWrapper.java @@ -32,138 +32,145 @@ public class FComboBoxWrapper implements IComboBox { public FComboBoxWrapper() { super(); - this.comboBox = new FComboBox(); + comboBox = new FComboBox(); allWrappers.add(this); } public FComboBoxWrapper(E[] items) { super(); - this.comboBox = new FComboBox(items); + comboBox = new FComboBox(items); allWrappers.add(this); } public FComboBoxWrapper(Vector items) { super(); - this.comboBox = new FComboBox(items); + comboBox = new FComboBox(items); allWrappers.add(this); } public FComboBoxWrapper(ComboBoxModel aModel) { super(); - this.comboBox = new FComboBox(aModel); + comboBox = new FComboBox(aModel); allWrappers.add(this); } public void addItem(E item) { - this.comboBox.addItem(item); + comboBox.addItem(item); } public void removeItem(E item) { - this.comboBox.removeItem(item); + comboBox.removeItem(item); } public void removeAllItems() { - this.comboBox.removeAllItems(); + comboBox.removeAllItems(); } @SuppressWarnings("unchecked") public E getSelectedItem() { - Object res = this.comboBox.getSelectedItem(); + Object res = comboBox.getSelectedItem(); return res == null ? null : (E) res; } public void setSelectedItem(Object item) { - this.comboBox.setSelectedItem(item); + comboBox.setSelectedItem(item); } public int getSelectedIndex() { - return this.comboBox.getSelectedIndex(); + return comboBox.getSelectedIndex(); } public void setSelectedIndex(int index) { - this.comboBox.setSelectedIndex(index); + comboBox.setSelectedIndex(index); + } + + public String getText() { + return comboBox.getText(); + } + public void setText(String text0) { + comboBox.setText(text0); } public void setMaximumRowCount(int count) { - this.comboBox.setMaximumRowCount(count); + comboBox.setMaximumRowCount(count); } public int getItemCount() { - return this.comboBox.getItemCount(); + return comboBox.getItemCount(); } public E getItemAt(int index) { - return this.comboBox.getItemAt(index); + return comboBox.getItemAt(index); } public void addActionListener(ActionListener l) { - this.comboBox.addActionListener(l); + comboBox.addActionListener(l); } public void addItemListener(ItemListener l) { - this.comboBox.addItemListener(l); + comboBox.addItemListener(l); } public void addKeyListener(KeyListener l) { - this.comboBox.addKeyListener(l); + comboBox.addKeyListener(l); } public void setRenderer(ListCellRenderer aRenderer) { - this.comboBox.setRenderer(aRenderer); + comboBox.setRenderer(aRenderer); } public void setModel(ComboBoxModel aModel) { - this.comboBox.setModel(aModel); + comboBox.setModel(aModel); } public void setTextAlignment(TextAlignment align) { - this.comboBox.setTextAlignment(align); + comboBox.setTextAlignment(align); } public void setSkinFont(SkinFont skinFont) { - this.comboBox.setSkinFont(skinFont); + comboBox.setSkinFont(skinFont); } @Override public boolean isVisible() { - return this.comboBox.isVisible(); + return comboBox.isVisible(); } @Override public void setVisible(boolean aFlag) { - this.comboBox.setVisible(aFlag); + comboBox.setVisible(aFlag); } @Override public boolean isEnabled() { - return this.comboBox.isEnabled(); + return comboBox.isEnabled(); } @Override public void setEnabled(boolean aFlag) { - this.comboBox.setEnabled(aFlag); + comboBox.setEnabled(aFlag); } public int getAutoSizeWidth() { - return this.comboBox.getAutoSizeWidth(); + return comboBox.getAutoSizeWidth(); } public void addTo(Container container) { - this.addTo(container, null); + addTo(container, null); } public void addTo(Container container, Object constraints0) { - container.add(this.comboBox, constraints0); - this.constraints = constraints0; + container.add(comboBox, constraints0); + constraints = constraints0; } //disguise as component for sake of rare places that need to access component in wrapper //use addTo instead if you want constraints remembered after refreshing skin public JComponent getComponent() { - return this.comboBox; + return comboBox; } private void refreshSkin() { - this.comboBox = refreshComboBoxSkin(this.comboBox, this.constraints); + comboBox = refreshComboBoxSkin(comboBox, constraints); } //refresh combo box skin by replacing it with a copy of itself diff --git a/forge-gui-desktop/src/main/java/forge/toolbox/FProgressBar.java b/forge-gui-desktop/src/main/java/forge/toolbox/FProgressBar.java index 0a4931b886c..6abbdbf545f 100644 --- a/forge-gui-desktop/src/main/java/forge/toolbox/FProgressBar.java +++ b/forge-gui-desktop/src/main/java/forge/toolbox/FProgressBar.java @@ -1,6 +1,5 @@ package forge.toolbox; -import forge.FThreads; import forge.interfaces.IProgressBar; import javax.swing.*; @@ -9,9 +8,6 @@ import java.util.Date; /** * A simple progress bar component using the Forge skin. - * - * Can show - * */ @SuppressWarnings("serial") public class FProgressBar extends JProgressBar implements IProgressBar { @@ -23,20 +19,13 @@ public class FProgressBar extends JProgressBar implements IProgressBar { private boolean percentMode = false; - /** */ public FProgressBar() { super(); reset(); setStringPainted(true); } - /** - * Sets description on bar. Must be called from EDT. - * - * @param s0   A description to prepend before statistics. - */ public void setDescription(final String s0) { - FThreads.assertExecutedByEdt(true); desc = s0; setString(s0); } @@ -79,9 +68,8 @@ public class FProgressBar extends JProgressBar implements IProgressBar { setString(sb.toString()); } - /** Resets the various values required for this class. Must be called from EDT. */ + /** Resets the various values required for this class. */ public void reset() { - FThreads.assertExecutedByEdt(true); setIndeterminate(true); setValue(0); tempVal = 0; @@ -91,17 +79,14 @@ public class FProgressBar extends JProgressBar implements IProgressBar { setShowCount(true); } - /** @param b0   Boolean, show the ETA statistic or not */ public void setShowETA(boolean b0) { showETA = b0; } - /** @param b0   Boolean, show the ETA statistic or not */ public void setShowCount(boolean b0) { showCount = b0; } - /** */ private void calculateETA(int v0) { float tempMillis = new Date().getTime(); float timePerUnit = (tempMillis - startMillis) / v0; @@ -111,9 +96,7 @@ public class FProgressBar extends JProgressBar implements IProgressBar { public boolean isPercentMode() { return percentMode; } - public void setPercentMode(boolean value) { percentMode = value; } - } diff --git a/forge-gui-mobile/src/forge/GuiMobile.java b/forge-gui-mobile/src/forge/GuiMobile.java index 243b91b2d72..a1e2a43dae6 100644 --- a/forge-gui-mobile/src/forge/GuiMobile.java +++ b/forge-gui-mobile/src/forge/GuiMobile.java @@ -27,6 +27,7 @@ import forge.deck.CardPool; import forge.deck.Deck; import forge.deck.FDeckViewer; import forge.deck.FSideboardDialog; +import forge.download.GuiDownloadService; import forge.error.BugReportDialog; import forge.game.GameEntity; import forge.game.GameEntityView; @@ -40,12 +41,14 @@ import forge.player.PlayerControllerHuman; import forge.properties.ForgeConstants; import forge.screens.match.MatchController; import forge.screens.quest.QuestMenu; +import forge.screens.settings.GuiDownloader; import forge.sound.AudioClip; import forge.sound.AudioMusic; import forge.sound.IAudioClip; import forge.sound.IAudioMusic; import forge.toolbox.FOptionPane; import forge.toolbox.GuiChoose; +import forge.util.Callback; import forge.util.FCollectionView; import forge.util.FileUtil; import forge.util.MessageUtil; @@ -291,6 +294,11 @@ public class GuiMobile implements IGuiBase { return defaultFile; //TODO: Show dialog } + @Override + public void download(GuiDownloadService service, Callback callback) { + new GuiDownloader(service, callback); + } + @Override public void copyToClipboard(String text) { Forge.getClipboard().setContents(text); diff --git a/forge-gui-mobile/src/forge/assets/AssetsDownloader.java b/forge-gui-mobile/src/forge/assets/AssetsDownloader.java index fd75b1584f3..f3ec71c9719 100644 --- a/forge-gui-mobile/src/forge/assets/AssetsDownloader.java +++ b/forge-gui-mobile/src/forge/assets/AssetsDownloader.java @@ -1,29 +1,19 @@ package forge.assets; -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; import java.net.URL; -import java.net.URLConnection; -import java.util.Enumeration; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; import org.apache.commons.lang3.StringUtils; import com.badlogic.gdx.Application.ApplicationType; import com.badlogic.gdx.Gdx; -import com.esotericsoftware.minlog.Log; import forge.FThreads; import forge.Forge; +import forge.download.GuiDownloadZipService; import forge.properties.ForgeConstants; import forge.screens.SplashScreen; -import forge.toolbox.FProgressBar; import forge.util.FileUtil; import forge.util.gui.SOptionPane; @@ -52,9 +42,10 @@ public class AssetsDownloader { message += " If so, you may want to connect to wifi first. The download is around 6.5MB."; } if (SOptionPane.showConfirmDialog(message, "New Version Available", "Update Now", "Update Later")) { - String apkFile = downloadFile("update", "forge-android-" + version + "-signed-aligned.apk", - "http://cardforge.org/android/releases/forge/forge-gui-android/" + version + "/", - Forge.getDeviceAdapter().getDownloadsDir(), splashScreen.getProgressBar()); + String filename = "forge-android-" + version + "-signed-aligned.apk"; + String apkFile = new GuiDownloadZipService("", "update", + "http://cardforge.org/android/releases/forge/forge-gui-android/" + version + "/" + filename, + Forge.getDeviceAdapter().getDownloadsDir(), null, splashScreen.getProgressBar()).download(filename); if (apkFile != null) { Forge.getDeviceAdapter().openFile(apkFile); Forge.exit(true); @@ -136,7 +127,9 @@ public class AssetsDownloader { return; } - downloadAssets(splashScreen.getProgressBar()); + new GuiDownloadZipService("", "resource files", + "http://cardforge.org/android/releases/forge/forge-gui-android/" + Forge.CURRENT_VERSION + "/" + "assets.zip", + ForgeConstants.ASSETS_DIR, ForgeConstants.RES_DIR, splashScreen.getProgressBar()).downloadAndUnzip(); FSkinFont.deleteCachedFiles(); //delete cached font files in case any skin's .ttf file changed @@ -153,103 +146,4 @@ public class AssetsDownloader { //so they don't need to be re-downloaded until you upgrade again FileUtil.writeFile(versionFile, Forge.CURRENT_VERSION); } - - private static String downloadFile(final String desc, final String filename, final String sourceFolder, final String destFolder, final FProgressBar progressBar) { - progressBar.reset(); - progressBar.setPercentMode(true); - progressBar.setDescription("Downloading " + desc); - - try { - URL url = new URL(sourceFolder + filename); - URLConnection conn = url.openConnection(); - conn.connect(); - - long contentLength = conn.getContentLength(); - progressBar.setMaximum(100); - - // input stream to read file - with 8k buffer - InputStream input = new BufferedInputStream(url.openStream(), 8192); - - // output stream to write file - String destFile = destFolder + filename; - OutputStream output = new FileOutputStream(destFile); - - int count; - long total = 0; - byte data[] = new byte[1024]; - - while ((count = input.read(data)) != -1) { - total += count; - progressBar.setValue((int)(100 * total / contentLength)); - output.write(data, 0, count); - } - - output.flush(); - output.close(); - input.close(); - return destFile; - } - catch (final Exception ex) { - Log.error("Downloading " + desc, "Error downloading " + desc, ex); - } - return null; - } - - private static void downloadAssets(final FProgressBar progressBar) { - String assetsFile = downloadFile("resource files", "assets.zip", - "http://cardforge.org/android/releases/forge/forge-gui-android/" + Forge.CURRENT_VERSION + "/", - ForgeConstants.ASSETS_DIR, progressBar); - if (assetsFile == null) { return; } - - //if assets.zip downloaded successfully, unzip into destination folder - try { - File resDir = new File(ForgeConstants.RES_DIR); - if (resDir.exists()) { - //attempt to delete previous res directory if to be rebuilt - progressBar.reset(); - progressBar.setDescription("Deleting old resource files..."); - FileUtil.deleteDirectory(resDir); - } - - ZipFile zipFile = new ZipFile(assetsFile); - Enumeration entries = zipFile.entries(); - - progressBar.reset(); - progressBar.setPercentMode(true); - progressBar.setDescription("Unzipping resource files"); - progressBar.setMaximum(zipFile.size()); - - int count = 0; - while (entries.hasMoreElements()) { - ZipEntry entry = (ZipEntry)entries.nextElement(); - - String path = ForgeConstants.ASSETS_DIR + entry.getName(); - if (entry.isDirectory()) { - new File(path).mkdir(); - progressBar.setValue(++count); - continue; - } - copyInputStream(zipFile.getInputStream(entry), new BufferedOutputStream(new FileOutputStream(path))); - progressBar.setValue(++count); - } - - zipFile.close(); - new File(assetsFile).delete(); - } - catch (Exception e) { - e.printStackTrace(); - } - } - - public static final void copyInputStream(InputStream in, OutputStream out) throws IOException{ - byte[] buffer = new byte[1024]; - int len; - - while((len = in.read(buffer)) >= 0) { - out.write(buffer, 0, len); - } - - in.close(); - out.close(); - } } diff --git a/forge-gui-mobile/src/forge/deck/FDeckChooser.java b/forge-gui-mobile/src/forge/deck/FDeckChooser.java index 86500eef639..d4cbe39c672 100644 --- a/forge-gui-mobile/src/forge/deck/FDeckChooser.java +++ b/forge-gui-mobile/src/forge/deck/FDeckChooser.java @@ -41,6 +41,8 @@ public class FDeckChooser extends FScreen { private DeckType selectedDeckType; private boolean needRefreshOnActivate; private Callback callback; + private NetDeckCategory netDeckCategory; + private boolean refreshingDeckType; private final DeckManager lstDecks; private final FButton btnNewDeck = new FButton("New Deck"); @@ -261,6 +263,12 @@ public class FDeckChooser extends FScreen { cmbDeckTypes.addItem(DeckType.COLOR_DECK); cmbDeckTypes.addItem(DeckType.THEME_DECK); cmbDeckTypes.addItem(DeckType.RANDOM_DECK); + cmbDeckTypes.addItem(DeckType.NET_DECK); + break; + case Commander: + cmbDeckTypes.addItem(DeckType.CUSTOM_DECK); + cmbDeckTypes.addItem(DeckType.RANDOM_DECK); + cmbDeckTypes.addItem(DeckType.NET_DECK); break; default: cmbDeckTypes.addItem(DeckType.CUSTOM_DECK); @@ -271,7 +279,32 @@ public class FDeckChooser extends FScreen { restoreSavedState(); cmbDeckTypes.setChangedHandler(new FEventHandler() { @Override - public void handleEvent(FEvent e) { + public void handleEvent(final FEvent e) { + if (cmbDeckTypes.getSelectedItem() == DeckType.NET_DECK && !refreshingDeckType) { + FThreads.invokeInBackgroundThread(new Runnable() { //needed for loading net decks + @Override + public void run() { + final NetDeckCategory category = NetDeckCategory.selectAndLoad(lstDecks.getGameType()); + + FThreads.invokeInEdtLater(new Runnable() { + @Override + public void run() { + if (category == null) { + cmbDeckTypes.setSelectedItem(selectedDeckType); //restore old selection if user cancels + if (selectedDeckType == DeckType.NET_DECK && netDeckCategory != null) { + cmbDeckTypes.setText(netDeckCategory.toString()); + } + return; + } + + netDeckCategory = category; + refreshDecksList(DeckType.NET_DECK, true, e); + } + }); + } + }); + return; + } refreshDecksList(cmbDeckTypes.getSelectedItem(), false, e); } }); @@ -493,6 +526,32 @@ public class FDeckChooser extends FScreen { }); } + private void updateNetDecks() { + if (netDeckCategory != null) { + cmbDeckTypes.setText(netDeckCategory.toString()); + } + lstDecks.setSelectionSupport(1, 1); + + lstDecks.setPool(DeckProxy.getNetDecks(netDeckCategory)); + lstDecks.setup(ItemManagerConfig.NET_DECKS); + + btnNewDeck.setText("New Deck"); + btnNewDeck.setWidth(btnEditDeck.getWidth()); + btnEditDeck.setVisible(true); + + btnViewDeck.setVisible(true); + btnRandom.setText("Random Deck"); + btnRandom.setWidth(btnNewDeck.getWidth()); + btnRandom.setLeft(getWidth() - PADDING - btnRandom.getWidth()); + btnRandom.setCommand(new FEventHandler() { + @Override + public void handleEvent(FEvent e) { + DeckgenUtil.randomSelect(lstDecks); + accept(); + } + }); + } + public Deck getDeck() { DeckProxy proxy = lstDecks.getSelectedItem(); if (proxy == null) { return null; } @@ -530,7 +589,9 @@ public class FDeckChooser extends FScreen { selectedDeckType = deckType; if (e == null) { + refreshingDeckType = true; cmbDeckTypes.setSelectedItem(deckType); + refreshingDeckType = false; } if (deckType == null) { return; } @@ -553,6 +614,9 @@ public class FDeckChooser extends FScreen { case RANDOM_DECK: updateRandom(); break; + case NET_DECK: + updateNetDecks(); + break; } if (e != null) { //set default list selection if from combo box change event @@ -577,8 +641,15 @@ public class FDeckChooser extends FScreen { } private String getState() { - String deckType = cmbDeckTypes.getSelectedItem().name(); - StringBuilder state = new StringBuilder(deckType); + StringBuilder state = new StringBuilder(); + if (cmbDeckTypes.getSelectedItem() == null || cmbDeckTypes.getSelectedItem() == DeckType.NET_DECK) { + //handle special case of net decks + if (netDeckCategory == null) { return ""; } + state.append(NetDeckCategory.PREFIX + netDeckCategory.getName()); + } + else { + state.append(cmbDeckTypes.getSelectedItem().name()); + } state.append(";"); joinSelectedDecks(state, SELECTED_DECK_DELIMITER); return state.toString(); @@ -600,25 +671,20 @@ public class FDeckChooser extends FScreen { } } - /** Returns a clean name from the state that can be used for labels. */ - public final String getStateForLabel() { - String deckType = cmbDeckTypes.getSelectedItem().toString(); - StringBuilder state = new StringBuilder(deckType); - state.append(": "); - joinSelectedDecks(state, ", "); - return state.toString(); - } - private void restoreSavedState() { + DeckType oldDeckType = selectedDeckType; if (stateSetting == null) { //if can't restore saved state, just refresh deck list - refreshDecksList(selectedDeckType, true, null); + refreshDecksList(oldDeckType, true, null); return; } String savedState = prefs.getPref(stateSetting); refreshDecksList(getDeckTypeFromSavedState(savedState), true, null); - lstDecks.setSelectedStrings(getSelectedDecksFromSavedState(savedState)); + if (!lstDecks.setSelectedStrings(getSelectedDecksFromSavedState(savedState))) { + //if can't select old decks, just refresh deck list + refreshDecksList(oldDeckType, true, null); + } } private DeckType getDeckTypeFromSavedState(String savedState) { @@ -627,7 +693,12 @@ public class FDeckChooser extends FScreen { return selectedDeckType; } else { - return DeckType.valueOf(savedState.split(";")[0]); + String deckType = savedState.split(";")[0]; + if (deckType.startsWith(NetDeckCategory.PREFIX)) { + netDeckCategory = NetDeckCategory.selectAndLoad(lstDecks.getGameType(), deckType.substring(NetDeckCategory.PREFIX.length())); + return DeckType.NET_DECK; + } + return DeckType.valueOf(deckType); } } catch (IllegalArgumentException ex) { diff --git a/forge-gui-mobile/src/forge/screens/settings/GuiDownloader.java b/forge-gui-mobile/src/forge/screens/settings/GuiDownloader.java index 71264fe66c4..890a073f0dc 100644 --- a/forge-gui-mobile/src/forge/screens/settings/GuiDownloader.java +++ b/forge-gui-mobile/src/forge/screens/settings/GuiDownloader.java @@ -24,9 +24,11 @@ import com.badlogic.gdx.Gdx; import forge.UiCommand; import forge.assets.FSkinFont; import forge.download.GuiDownloadService; +import forge.download.GuiDownloadZipService; import forge.toolbox.*; import forge.toolbox.FEvent.FEventHandler; import forge.toolbox.FRadioButton.RadioButtonGroup; +import forge.util.Callback; public class GuiDownloader extends FDialog { public static final Proxy.Type[] TYPES = Proxy.Type.values(); @@ -44,16 +46,25 @@ public class GuiDownloader extends FDialog { private final UiCommand cmdClose = new UiCommand() { @Override public void run() { + Gdx.graphics.setContinuousRendering(false); service.setCancel(true); hide(); + if (callback != null) { + callback.run(btnStart.getText() == "OK"); //determine result based on whether download finished + } } }; private final GuiDownloadService service; + private final Callback callback; public GuiDownloader(GuiDownloadService service0) { + this(service0, null); + } + public GuiDownloader(GuiDownloadService service0, Callback callback0) { super(service0.getTitle()); service = service0; + callback = callback0; txtAddress.setGhostText("Proxy Address"); txtPort.setGhostText("Proxy Port"); @@ -85,7 +96,9 @@ public class GuiDownloader extends FDialog { service.initialize(txtAddress, txtPort, progressBar, btnStart, cmdClose, new Runnable() { @Override public void run() { - Gdx.graphics.setContinuousRendering(false); + if (!(service instanceof GuiDownloadZipService)) { //retain continuous rendering for zip service + Gdx.graphics.setContinuousRendering(false); + } progressBar.setShowProgressTrail(false); } }, null); diff --git a/forge-gui-mobile/src/forge/toolbox/FComboBox.java b/forge-gui-mobile/src/forge/toolbox/FComboBox.java index d0c9ffcefc8..f582701b1aa 100644 --- a/forge-gui-mobile/src/forge/toolbox/FComboBox.java +++ b/forge-gui-mobile/src/forge/toolbox/FComboBox.java @@ -174,10 +174,15 @@ public class FComboBox extends FTextField implements IComboBox { } @Override - protected float getRightPadding() { + protected float getLeftPadding() { if (getAlignment() == HAlignment.CENTER) { - return super.getRightPadding(); + return getRightPadding(); //match right padding if center aligned } + return super.getLeftPadding(); + } + + @Override + protected float getRightPadding() { return getDivotWidth() + 2 * PADDING; } diff --git a/forge-gui-mobile/src/forge/toolbox/FEvent.java b/forge-gui-mobile/src/forge/toolbox/FEvent.java index 9ca32890fe4..6cbcc069274 100644 --- a/forge-gui-mobile/src/forge/toolbox/FEvent.java +++ b/forge-gui-mobile/src/forge/toolbox/FEvent.java @@ -7,7 +7,8 @@ public class FEvent { CHANGE, ACTIVATE, SAVE, - DELETE + DELETE, + CLOSE } private FDisplayObject source; diff --git a/forge-gui/CHANGES.txt b/forge-gui/CHANGES.txt index 4bb1eb90f15..adba3f6e3c3 100644 --- a/forge-gui/CHANGES.txt +++ b/forge-gui/CHANGES.txt @@ -8,6 +8,12 @@ Forge Beta: 1-#-2015 ver 1.5.33 Release Notes ------------- +- Net Decks support - +On the Constructed screen, you can now select a new Net Decks category from the drop down +Contains all decks from gos's Forge Decks subforums, broken up by the column/event they were taken from +Won't cause startup to be slower since they are loaded on the fly + + - Zoom to a XLHQ card picture - Forge now supports showing XLHQ (extra large high quality) card pictures when zooming in on a card if these pictures are available. Forge will look for XLHQ card art in the "XLHQ" subfolder of the "pics/cards" folder in Forge cache. XLHQ pictures should have the ".xlhq.jpg" extension instead of the ".full.jpg" one (CCGHQ XLHQ releases comply with this naming scheme). For example, while the regular 10th edition Blaze will be looked up as "pics/cards/10E/Blaze.full.jpg", the XLHQ version will be looked up as "pics/cards/XLHQ/10E/Blaze.xlhq.jpg". If the XLHQ picture is not available Forge will show you the regular card picture in the zoomed view, as usual. Please note that XLHQ versions of cards are *only* showed in the zoom view, regular card pictures are still used (LQ/HQ, depending on what you're using) on the battlefield and elsewhere in the game because XLHQ art is significantly more taxing in memory consumption even when compared to standard high quality (HQ) releases (and in addition to that, XLHQ card borders are not cropped the way Forge expects them in order to show them properly on the battlefield anyway). XLHQ tokens are also supported, but the naming scheme for them is a little different - they are looked up in "pics/tokens/XLHQ" and have their ordinary names. For example, the 1/1 red Goblin token from the Magic 2015 set is normally looked up at "pics/tokens/r_1_1_goblin_m15.jpg", while its XLHQ version is looked up at "pics/tokens/XLHQ/r_1_1_goblin_m15.jpg". diff --git a/forge-gui/res/lists/net-decks-commander.txt b/forge-gui/res/lists/net-decks-commander.txt new file mode 100644 index 00000000000..311df2bf0c5 --- /dev/null +++ b/forge-gui/res/lists/net-decks-commander.txt @@ -0,0 +1,10 @@ +The Week That Was | http://www.slightlymagic.net/forum/download/file.php?id=19986 +WotC Preconstructed | http://www.slightlymagic.net/forum/download/file.php?id=21293 +Serious Fun | http://www.slightlymagic.net/forum/download/file.php?id=21279 +ReConstructed | http://www.slightlymagic.net/forum/download/file.php?id=21275 +Latest Developments | http://www.slightlymagic.net/forum/download/file.php?id=13259 +From the Lab | http://www.slightlymagic.net/forum/download/file.php?id=21263 +Feature Article | http://www.slightlymagic.net/forum/download/file.php?id=21259 +Daily Deck List | http://www.slightlymagic.net/forum/download/file.php?id=18670 +Command Tower | http://www.slightlymagic.net/forum/download/file.php?id=21255 +Going Rogue | http://www.slightlymagic.net/forum/download/file.php?id=14064 \ No newline at end of file diff --git a/forge-gui/res/lists/net-decks.txt b/forge-gui/res/lists/net-decks.txt new file mode 100644 index 00000000000..f6062249046 --- /dev/null +++ b/forge-gui/res/lists/net-decks.txt @@ -0,0 +1,28 @@ +Top Decks | http://www.slightlymagic.net/forum/download/file.php?id=21296 +World Championship | http://www.slightlymagic.net/forum/download/file.php?id=21289 +The Week That Was | http://www.slightlymagic.net/forum/download/file.php?id=21285 +Single Card Strategy | http://www.slightlymagic.net/forum/download/file.php?id=21282 +WotC Preconstructed | http://www.slightlymagic.net/forum/download/file.php?id=21292 +Serious Fun | http://www.slightlymagic.net/forum/download/file.php?id=21278 +ReConstructed | http://www.slightlymagic.net/forum/download/file.php?id=21274 +Pro Tour | http://www.slightlymagic.net/forum/download/file.php?id=21272 +Perilous Research | http://www.slightlymagic.net/forum/download/file.php?id=21270 +Into the AEther | http://www.slightlymagic.net/forum/download/file.php?id=18683 +Level One | http://www.slightlymagic.net/forum/download/file.php?id=21268 +Latest Developments | http://www.slightlymagic.net/forum/download/file.php?id=21266 +From the Lab | http://www.slightlymagic.net/forum/download/file.php?id=21262 +Feature Article | http://www.slightlymagic.net/forum/download/file.php?id=21260 +Daily Deck List | http://www.slightlymagic.net/forum/download/file.php?id=21257 +Building on a Budget | http://www.slightlymagic.net/forum/download/file.php?id=21253 +Breaking Through | http://www.slightlymagic.net/forum/download/file.php?id=21251 +Woo Brews | http://www.slightlymagic.net/forum/download/file.php?id=19091 +Swimming with Sharks | http://www.slightlymagic.net/forum/download/file.php?id=17441 +Learning Curve | http://www.slightlymagic.net/forum/download/file.php?id=16447 +Squandered Resources | http://www.slightlymagic.net/forum/download/file.php?id=16074 +Deconstructing Famous | http://www.slightlymagic.net/forum/download/file.php?id=14190 +Going Rogue | http://www.slightlymagic.net/forum/download/file.php?id=13498 +Uncommon Knowledge | http://www.slightlymagic.net/forum/download/file.php?id=13877 +MagicTheGathering.Combos | http://www.slightlymagic.net/forum/download/file.php?id=9316 +Players Championship 2012 | http://www.slightlymagic.net/forum/download/file.php?id=7324 +Brewing on a Budget | http://www.slightlymagic.net/forum/download/file.php?id=6584 +Grand Prix Lincoln 2012 | http://www.slightlymagic.net/forum/download/file.php?id=5318 \ No newline at end of file diff --git a/forge-gui/src/main/java/forge/deck/DeckProxy.java b/forge-gui/src/main/java/forge/deck/DeckProxy.java index bacd0e94f53..d3ae4290021 100644 --- a/forge-gui/src/main/java/forge/deck/DeckProxy.java +++ b/forge-gui/src/main/java/forge/deck/DeckProxy.java @@ -445,6 +445,14 @@ public class DeckProxy implements InventoryItem { return decks; } + public static List getNetDecks(NetDeckCategory category) { + ArrayList decks = new ArrayList(); + if (category != null) { + addDecksRecursivelly("Constructed", GameType.Constructed, decks, "", category); + } + return decks; + } + public static final Predicate IS_WHITE = new Predicate() { @Override public boolean apply(final DeckProxy deck) { diff --git a/forge-gui/src/main/java/forge/deck/DeckType.java b/forge-gui/src/main/java/forge/deck/DeckType.java index a1c9d8e7853..ca3b0e66de1 100644 --- a/forge-gui/src/main/java/forge/deck/DeckType.java +++ b/forge-gui/src/main/java/forge/deck/DeckType.java @@ -6,7 +6,8 @@ public enum DeckType { QUEST_OPPONENT_DECK ("Quest Opponent Decks"), COLOR_DECK ("Random Color Decks"), THEME_DECK ("Random Theme Decks"), - RANDOM_DECK ("Random Decks"); + RANDOM_DECK ("Random Decks"), + NET_DECK ("Net Decks"); private String value; private DeckType(String value) { diff --git a/forge-gui/src/main/java/forge/deck/NetDeckCategory.java b/forge-gui/src/main/java/forge/deck/NetDeckCategory.java new file mode 100644 index 00000000000..21ce0773a33 --- /dev/null +++ b/forge-gui/src/main/java/forge/deck/NetDeckCategory.java @@ -0,0 +1,127 @@ +package forge.deck; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import forge.GuiBase; +import forge.deck.io.DeckSerializer; +import forge.deck.io.DeckStorage; +import forge.download.GuiDownloadZipService; +import forge.game.GameType; +import forge.properties.ForgeConstants; +import forge.util.FileUtil; +import forge.util.WaitCallback; +import forge.util.gui.SGuiChoose; +import forge.util.storage.StorageBase; + +public class NetDeckCategory extends StorageBase { + public static final String PREFIX = "NET_DECK_"; + private static Map constructed, commander; + + private static Map loadCategories(String filename) { + Map categories = new TreeMap(); + if (FileUtil.doesFileExist(filename)) { + List lines = FileUtil.readFile(filename); + for (String line : lines) { + int idx = line.indexOf('|'); + if (idx != -1) { + String name = line.substring(0, idx).trim(); + String url = line.substring(idx + 1).trim(); + categories.put(name, new NetDeckCategory(name, url)); + } + } + } + return categories; + } + + public static NetDeckCategory selectAndLoad(GameType gameType) { + return selectAndLoad(gameType, null); + } + public static NetDeckCategory selectAndLoad(GameType gameType, String name) { + Map categories; + switch (gameType) { + case Constructed: + case Gauntlet: + if (constructed == null) { + constructed = loadCategories(ForgeConstants.NET_DECKS_LIST_FILE); + } + categories = constructed; + break; + case Commander: + if (commander == null) { + commander = loadCategories(ForgeConstants.NET_DECKS_COMMANDER_LIST_FILE); + } + categories = commander; + break; + default: + return null; + } + + if (name != null) { + NetDeckCategory category = categories.get(name); + if (category != null && category.map.isEmpty()) { + //if name passed in, try to load decks from current cached files + File downloadDir = new File(category.getDownloadLocation()); + if (downloadDir.exists()) { + for (File file : downloadDir.listFiles(DeckStorage.DCK_FILE_FILTER)) { + Deck deck = DeckSerializer.fromFile(file); + if (deck != null) { + category.map.put(deck.getName(), deck); + } + } + } + } + return category; + } + + final NetDeckCategory c= SGuiChoose.oneOrNone("Select a Net Deck category", categories.values()); + if (c == null) { return null; } + + if (c.map.isEmpty()) { //only download decks once per session + WaitCallback callback = new WaitCallback() { + @Override + public void run() { + String downloadLoc = c.getDownloadLocation(); + GuiBase.getInterface().download(new GuiDownloadZipService(c.getName(), "decks", c.getUrl(), downloadLoc, downloadLoc, null) { + @Override + protected void copyInputStream(InputStream in, String outPath) throws IOException { + super.copyInputStream(in, outPath); + + Deck deck = DeckSerializer.fromFile(new File(outPath)); + if (deck != null) { + c.map.put(deck.getName(), deck); + } + } + }, this); + } + }; + if (!callback.invokeAndWait()) { return null; } //wait for download to finish + } + return c; + } + + private final String url; + + private NetDeckCategory(String name0, String downloadLocation0) { + super(name0, new HashMap()); + url = downloadLocation0; + } + + public String getDownloadLocation() { + return ForgeConstants.DECK_NET_DIR + name + "/"; + } + + public String getUrl() { + return url; + } + + @Override + public String toString() { + return "Net Decks - " + name; + } +} diff --git a/forge-gui/src/main/java/forge/download/GuiDownloadService.java b/forge-gui/src/main/java/forge/download/GuiDownloadService.java index 4aa254c188a..7cc5e8f73dd 100644 --- a/forge-gui/src/main/java/forge/download/GuiDownloadService.java +++ b/forge-gui/src/main/java/forge/download/GuiDownloadService.java @@ -53,7 +53,7 @@ public abstract class GuiDownloadService implements Runnable { //Components passed from GUI component displaying download private ITextField txtAddress; private ITextField txtPort; - private IProgressBar progressBar; + protected IProgressBar progressBar; private IButton btnStart; private UiCommand cmdClose; private Runnable onUpdate; @@ -73,7 +73,7 @@ public abstract class GuiDownloadService implements Runnable { // Progress variables private Map files; // local path -> url - private boolean cancel; + protected boolean cancel; private final long[] times = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; private int tptr = 0; private int skipped = 0; @@ -90,27 +90,50 @@ public abstract class GuiDownloadService implements Runnable { cmdClose = cmdClose0; onUpdate = onUpdate0; - // Free up the EDT by assembling card list on a background thread - FThreads.invokeInBackgroundThread(new Runnable() { - @Override - public void run() { - try { - files = getNeededFiles(); - } - catch (Exception e) { - e.printStackTrace(); - } - FThreads.invokeInEdtLater(new Runnable() { - @Override - public void run() { - if (onReadyToStart != null) { - onReadyToStart.run(); - } - readyToStart(); + String startOverrideDesc = getStartOverrideDesc(); + if (startOverrideDesc == null) { + // Free up the EDT by assembling card list on a background thread + FThreads.invokeInBackgroundThread(new Runnable() { + @Override + public void run() { + try { + files = getNeededFiles(); } - }); + catch (Exception e) { + e.printStackTrace(); + } + FThreads.invokeInEdtLater(new Runnable() { + @Override + public void run() { + if (onReadyToStart != null) { + onReadyToStart.run(); + } + readyToStart(); + } + }); + } + }); + } + else { + //handle special case of zip service + if (onReadyToStart != null) { + onReadyToStart.run(); } - }); + progressBar.setDescription("Click \"Start\" to download and extract " + startOverrideDesc); + btnStart.setCommand(cmdStartDownload); + btnStart.setEnabled(true); + + FThreads.invokeInEdtLater(new Runnable() { + @Override + public void run() { + btnStart.requestFocusInWindow(); + } + }); + } + } + + protected String getStartOverrideDesc() { + return null; } private void readyToStart() { @@ -198,10 +221,7 @@ public abstract class GuiDownloadService implements Runnable { else { sb.append(String.format("%d of %d items finished! Skipped " + skipped + " items. Please close!", count, files.size())); - btnStart.setText("OK"); - btnStart.setCommand(cmdClose); - btnStart.setEnabled(true); - btnStart.requestFocusInWindow(); + finish(); } progressBar.setValue(count); @@ -211,25 +231,18 @@ public abstract class GuiDownloadService implements Runnable { }); } + protected void finish() { + btnStart.setText("OK"); + btnStart.setCommand(cmdClose); + btnStart.setEnabled(true); + btnStart.requestFocusInWindow(); + } + @Override - public final void run() { + public void run() { final Random r = MyRandom.getRandom(); - Proxy p = null; - if (type == 0) { - p = Proxy.NO_PROXY; - } - else { - try { - p = new Proxy(TYPES[type], new InetSocketAddress(txtAddress.getText(), Integer.parseInt(txtPort.getText()))); - } - catch (final Exception ex) { - BugReporter.reportException(ex, - "Proxy connection could not be established!\nProxy address: %s\nProxy port: %s", - txtAddress.getText(), txtPort.getText()); - return; - } - } + Proxy p = getProxy(); int bufferLength; int iCard = 0; @@ -304,6 +317,23 @@ public abstract class GuiDownloadService implements Runnable { } } + protected Proxy getProxy() { + if (type == 0) { + return Proxy.NO_PROXY; + } + else { + try { + return new Proxy(TYPES[type], new InetSocketAddress(txtAddress.getText(), Integer.parseInt(txtPort.getText()))); + } + catch (final Exception ex) { + BugReporter.reportException(ex, + "Proxy connection could not be established!\nProxy address: %s\nProxy port: %s", + txtAddress.getText(), txtPort.getText()); + } + } + return null; + } + public abstract String getTitle(); protected abstract Map getNeededFiles(); diff --git a/forge-gui/src/main/java/forge/download/GuiDownloadZipService.java b/forge-gui/src/main/java/forge/download/GuiDownloadZipService.java new file mode 100644 index 00000000000..d71041f4ff2 --- /dev/null +++ b/forge-gui/src/main/java/forge/download/GuiDownloadZipService.java @@ -0,0 +1,203 @@ +package forge.download; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.Charset; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import com.esotericsoftware.minlog.Log; +import com.google.common.io.Files; + +import forge.FThreads; +import forge.interfaces.IProgressBar; +import forge.util.FileUtil; + +public class GuiDownloadZipService extends GuiDownloadService { + private final String name, desc, sourceUrl, destFolder, deleteFolder; + private int filesDownloaded; + + public GuiDownloadZipService(String name0, String desc0, String sourceUrl0, String destFolder0, String deleteFolder0, IProgressBar progressBar0) { + name = name0; + desc = desc0; + sourceUrl = sourceUrl0; + destFolder = destFolder0; + deleteFolder = deleteFolder0; + progressBar = progressBar0; + } + + @Override + public String getTitle() { + return "Download " + name; + } + + @Override + protected String getStartOverrideDesc() { + return desc; + } + + @Override + protected final Map getNeededFiles() { + HashMap files = new HashMap(); + files.put("_", "_"); + return files; //not needed by zip service, so just return map of size 1 + } + + @Override + public final void run() { + downloadAndUnzip(); + if (!cancel) { + FThreads.invokeInEdtNowOrLater(new Runnable() { + @Override + public void run() { + progressBar.setDescription(filesDownloaded + " " + desc + " downloaded"); + finish(); + } + }); + } + } + + public void downloadAndUnzip() { + filesDownloaded = 0; + String zipFilename = download("temp.zip"); + if (zipFilename == null) { return; } + + //if assets.zip downloaded successfully, unzip into destination folder + try { + if (deleteFolder != null) { + File deleteDir = new File(deleteFolder); + if (deleteDir.exists()) { + //attempt to delete previous res directory if to be rebuilt + progressBar.reset(); + progressBar.setDescription("Deleting old " + desc + "..."); + if (deleteFolder.equals(destFolder)) { //move zip file to prevent deleting it + String oldZipFilename = zipFilename; + zipFilename = deleteDir.getParentFile().getAbsolutePath() + File.separator + "temp.zip"; + Files.move(new File(oldZipFilename), new File(zipFilename)); + } + FileUtil.deleteDirectory(deleteDir); + } + } + + ZipFile zipFile = new ZipFile(zipFilename, Charset.forName("CP866")); //ensure unzip doesn't fail due to non UTF-8 chars + Enumeration entries = zipFile.entries(); + + progressBar.reset(); + progressBar.setPercentMode(true); + progressBar.setDescription("Extracting " + desc); + progressBar.setMaximum(zipFile.size()); + + FileUtil.ensureDirectoryExists(destFolder); + + int count = 0; + while (entries.hasMoreElements()) { + if (cancel) { break; } + + ZipEntry entry = (ZipEntry)entries.nextElement(); + + String path = destFolder + entry.getName(); + if (entry.isDirectory()) { + new File(path).mkdir(); + progressBar.setValue(++count); + continue; + } + copyInputStream(zipFile.getInputStream(entry), path); + progressBar.setValue(++count); + filesDownloaded++; + } + + zipFile.close(); + new File(zipFilename).delete(); + } + catch (Exception e) { + e.printStackTrace(); + } + } + + public String download(String filename) { + progressBar.reset(); + progressBar.setPercentMode(true); + progressBar.setDescription("Downloading " + desc); + + try { + URL url = new URL(sourceUrl); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(getProxy()); + + if (url.getPath().endsWith(".php")) { + //ensure file can be downloaded if returned from PHP script + conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 4.01; Windows NT)"); + } + + conn.connect(); + + if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) { + return null; + } + + long contentLength = conn.getContentLengthLong(); + if (contentLength == 0) { + return null; + } + + progressBar.setMaximum(100); + + // input stream to read file - with 8k buffer + InputStream input = new BufferedInputStream(conn.getInputStream(), 8192); + + FileUtil.ensureDirectoryExists(destFolder); + + // output stream to write file + String destFile = destFolder + filename; + OutputStream output = new FileOutputStream(destFile); + + int count; + long total = 0; + byte data[] = new byte[1024]; + + while ((count = input.read(data)) != -1) { + if (cancel) { break; } + + total += count; + progressBar.setValue((int)(100 * total / contentLength)); + output.write(data, 0, count); + } + + output.flush(); + output.close(); + input.close(); + + if (cancel) { + new File(destFile).delete(); + return null; + } + return destFile; + } + catch (final Exception ex) { + Log.error("Downloading " + desc, "Error downloading " + desc, ex); + } + return null; + } + + protected void copyInputStream(InputStream in, String outPath) throws IOException{ + byte[] buffer = new byte[1024]; + int len; + BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(outPath)); + + while((len = in.read(buffer)) >= 0) { + out.write(buffer, 0, len); + } + + in.close(); + out.close(); + } +} diff --git a/forge-gui/src/main/java/forge/interfaces/IGuiBase.java b/forge-gui/src/main/java/forge/interfaces/IGuiBase.java index 9abe6506e29..a37ec212f7c 100644 --- a/forge-gui/src/main/java/forge/interfaces/IGuiBase.java +++ b/forge-gui/src/main/java/forge/interfaces/IGuiBase.java @@ -10,6 +10,7 @@ import forge.LobbyPlayer; import forge.assets.FSkinProp; import forge.assets.ISkinImage; import forge.deck.CardPool; +import forge.download.GuiDownloadService; import forge.game.GameEntity; import forge.game.GameEntityView; import forge.game.card.CardView; @@ -19,6 +20,7 @@ import forge.item.PaperCard; import forge.player.PlayerControllerHuman; import forge.sound.IAudioClip; import forge.sound.IAudioMusic; +import forge.util.Callback; import forge.util.FCollectionView; public interface IGuiBase { @@ -45,6 +47,7 @@ public interface IGuiBase { GameEntityView chooseSingleEntityForEffect(String title, FCollectionView optionList, DelayedReveal delayedReveal, boolean isOptional, PlayerControllerHuman controller); String showFileDialog(String title, String defaultDir); File getSaveFile(File defaultFile); + void download(GuiDownloadService service, Callback callback); void showCardList(final String title, final String message, final List list); boolean showBoxedProduct(final String title, final String message, final List list); void setCard(CardView card); diff --git a/forge-gui/src/main/java/forge/itemmanager/ItemManagerConfig.java b/forge-gui/src/main/java/forge/itemmanager/ItemManagerConfig.java index 1aa69fbf377..f6bdbdd9528 100644 --- a/forge-gui/src/main/java/forge/itemmanager/ItemManagerConfig.java +++ b/forge-gui/src/main/java/forge/itemmanager/ItemManagerConfig.java @@ -88,6 +88,8 @@ public enum ItemManagerConfig { null, null, 3, 0), QUEST_EVENT_DECKS(SColumnUtil.getDecksDefaultColumns(false, false), false, false, false, null, null, 3, 0), + NET_DECKS(SColumnUtil.getDecksDefaultColumns(false, false), false, false, false, + null, null, 3, 0), SIDEBOARD(SColumnUtil.getDeckEditorDefaultColumns(), false, false, true, GroupDef.DEFAULT, ColumnDef.CMC, 3, 0); diff --git a/forge-gui/src/main/java/forge/properties/ForgeConstants.java b/forge-gui/src/main/java/forge/properties/ForgeConstants.java index 7a0d2a8ded4..8795c036aeb 100644 --- a/forge-gui/src/main/java/forge/properties/ForgeConstants.java +++ b/forge-gui/src/main/java/forge/properties/ForgeConstants.java @@ -40,6 +40,8 @@ public final class ForgeConstants { public static final String IMAGE_LIST_QUEST_BOOSTERBOXES_FILE = LISTS_DIR + "boosterbox-images.txt"; public static final String IMAGE_LIST_QUEST_PRECONS_FILE = LISTS_DIR + "precon-images.txt"; public static final String IMAGE_LIST_QUEST_TOURNAMENTPACKS_FILE = LISTS_DIR + "tournamentpack-images.txt"; + public static final String NET_DECKS_LIST_FILE = LISTS_DIR + "net-decks.txt"; + public static final String NET_DECKS_COMMANDER_LIST_FILE = LISTS_DIR + "net-decks-commander.txt"; public static final String CHANGES_FILE = ASSETS_DIR + "CHANGES.txt"; public static final String LICENSE_FILE = ASSETS_DIR + "LICENSE.txt"; @@ -114,6 +116,7 @@ public final class ForgeConstants { public static final String DECK_SCHEME_DIR = DECK_BASE_DIR + "scheme/"; public static final String DECK_PLANE_DIR = DECK_BASE_DIR + "planar/"; public static final String DECK_COMMANDER_DIR = DECK_BASE_DIR + "commander/"; + public static final String DECK_NET_DIR = DECK_BASE_DIR + "net/"; public static final String QUEST_SAVE_DIR = USER_QUEST_DIR + "saves/"; public static final String CONQUEST_SAVE_DIR = USER_CONQUEST_DIR + "saves/"; public static final String MAIN_PREFS_FILE = USER_PREFS_DIR + "forge.preferences"; @@ -159,6 +162,8 @@ public final class ForgeConstants { DECK_SEALED_DIR, DECK_SCHEME_DIR, DECK_PLANE_DIR, + DECK_COMMANDER_DIR, + DECK_NET_DIR, QUEST_SAVE_DIR, CACHE_TOKEN_PICS_DIR, CACHE_ICON_PICS_DIR,